Transient Analysis Result Example

This example shows how to post-process a transient result and visualize the outputs.

import numpy as np
import matplotlib.pyplot as plt

from ansys.dpf import core as dpf
from ansys.dpf.core import examples
from ansys.dpf.core import operators as ops

Begin by downloading the example transient result. This result is not included in the core module by default to speed up the install. Download should only take a few seconds.

Next, create the model and display the state of the result. Note that this transient result file contains several individual results, each at a different timestamp.

transient = examples.download_transient_result()
model = dpf.Model(transient)
print(model)

Out:

DPF Model
------------------------------
DPF Result Info
  Analysis: static
  Physics Type: mecanic
  Unit system: MKS: m, kg, N, s, V, A, degC
  Available results:
    U Displacement :nodal displacements
    RF Force :nodal reaction forces
    ENF Element nodal Forces :element nodal forces
    S Stress :element nodal component stresses
    ENG_VOL Volume :element volume
    ENG_SE Energy-stiffness matrix :element energy associated with the stiffness matrix
    ENG_AHO Hourglass Energy :artificial hourglass energy
    ENG_TH thermal dissipation energy :thermal dissipation energy
    ENG_KE Kinetic Energy :kinetic energy
    ENG_CO co-energy :co-energy (magnetics)
    ENG_INC incremental energy :incremental energy (magnetics)
    EPEL Strain :element nodal component elastic strains
    ETH Thermal Strains :element nodal component thermal strains
    ETH_EQV Thermal Strains eqv :element nodal equivalent component thermal strains
    ETH_SWL Swelling Strains :element nodal swelling strains
    BFE Temperature :element structural nodal temperatures
------------------------------
DPF  Meshed Region:
  3820 nodes
  789 elements
  Unit: m
  With solid (3D) elements, shell (2D) elements, shell (3D) elements
------------------------------
DPF  Time/Freq Support:
  Number of sets: 35
Cumulative     Time (s)       LoadStep       Substep
1              0.000000       1              1
2              0.019975       1              2
3              0.039975       1              3
4              0.059975       1              4
5              0.079975       1              5
6              0.099975       1              6
7              0.119975       1              7
8              0.139975       1              8
9              0.159975       1              9
10             0.179975       1              10
11             0.199975       1              11
12             0.218975       1              12
13             0.238975       1              13
14             0.258975       1              14
15             0.278975       1              15
16             0.298975       1              16
17             0.318975       1              17
18             0.338975       1              18
19             0.358975       1              19
20             0.378975       1              20
21             0.398975       1              21
22             0.417975       1              22
23             0.437975       1              23
24             0.457975       1              24
25             0.477975       1              25
26             0.497975       1              26
27             0.517975       1              27
28             0.537550       1              28
29             0.557253       1              29
30             0.577118       1              30
31             0.597021       1              31
32             0.616946       1              32
33             0.636833       1              33
34             0.656735       1              34
35             0.676628       1              35

Get the timestamps for each substep as a numpy array

tf = model.metadata.time_freq_support
print(tf.time_frequencies.data)

Out:

[0.         0.019975   0.039975   0.059975   0.079975   0.099975
 0.119975   0.139975   0.159975   0.179975   0.199975   0.218975
 0.238975   0.258975   0.278975   0.298975   0.318975   0.338975
 0.358975   0.378975   0.398975   0.417975   0.437975   0.457975
 0.477975   0.497975   0.517975   0.53754972 0.55725277 0.57711786
 0.59702054 0.61694639 0.63683347 0.65673452 0.67662783]

Obtain Minimum and Maximum Displacement for all Results

Create a displacement operator and set its time scoping request to the entire time freq support.

disp = model.results.displacement()
timeids = range(1, tf.n_sets + 1)  # must use 1-based indexing
disp.inputs.time_scoping(timeids)

# Then chain the displacement operator with norm and min_max operators
min_max_op =ops.min_max.min_max_fc(ops.math.norm_fc(disp))

min_disp = min_max_op.outputs.field_min()
max_disp = min_max_op.outputs.field_max()
print(max_disp.data)

Out:

[0.         0.00062674 0.0025094  0.00564185 0.00999992 0.01552154
 0.02207871 0.02944459 0.03725894 0.04499722 0.05195353 0.05703912
 0.05982844 0.05897617 0.05358419 0.04310436 0.02759782 0.00798431
 0.0137951  0.03478255 0.05130461 0.05942392 0.05715204 0.04272116
 0.01787116 0.01244994 0.04062977 0.05913066 0.06042056 0.0418829
 0.01201879 0.03526532 0.05950852 0.06077103 0.03733769]

Plot the minimum and maximum displacements over time

tdata = tf.time_frequencies.data
plt.plot(tdata, max_disp.data, 'r', label='Max')
plt.plot(tdata, min_disp.data, 'b', label="Min")
plt.xlabel("Time (s)")
plt.ylabel("Displacement (m)")
plt.legend()
plt.show()
00 basic transient

Plot the minimum and maximum displacements over time for the X component.

disp_z = disp.Z()
min_max_op = ops.min_max.min_max_fc(ops.math.norm_fc(disp_z))

min_disp_z = min_max_op.outputs.field_min()
max_disp_z = min_max_op.outputs.field_max()

tdata = tf.time_frequencies.data
plt.plot(tdata, max_disp_z.data, 'r', label='Max')
plt.plot(tdata, min_disp_z.data, 'b', label="Min")
plt.xlabel("Time (s)")
plt.ylabel("X Displacement (m)")
plt.legend()
plt.show()
00 basic transient

Post-Processing Stress

Create a equivalent (von mises) stress operator and set its time scoping to the entire time freq support.

# Component stress operator (stress)
stress = model.results.stress()

# Equivalent stress operator
eqv = stress.eqv()
eqv.inputs.time_scoping(timeids)

# connect to the min_max operator and return the minimum and maximum
# fields
min_max_eqv = ops.min_max.min_max_fc(eqv)
eqv_min = min_max_eqv.outputs.field_min()
eqv_max = min_max_eqv.outputs.field_max()

print(eqv_min)

Out:

DPF stress_0.s_eqv Field
  Location: Nodal
  Unit: Pa
  35 entities
  Data:1 components and 35 elementary data

Plot the maximum stress over time

plt.plot(tdata, eqv_min.data, 'b', label="Minimum")
plt.plot(tdata, eqv_max.data, 'r', label='Maximum')
plt.xlabel("Time (s)")
plt.ylabel("Equivalent Stress (Pa)")
plt.legend()
plt.show()
00 basic transient

Scoping and Stress Field Coordinates

The scoping of the stress field can be used to extract the coordinates used for each result.

# extract a single field from the equivalent stress operator
field = eqv.outputs.fields_container()[28]

# print the first node IDs from the field
print(field.scoping.ids[:10])

Out:

[508, 509, 909, 910, 524, 525, 534, 533, 513, 908]

As you can see, these node numbers are not in order. Additionally, there may be less entries in the field than nodes in the model. For example, stresses are not computed at mid-side nodes.

To extract the coordinates for these node ids, load the mesh from the model and then extract a coordinate for each node index.

This is an inefficient way of getting the coordinates as each individual request must be sent to the DPF service.

# load the mesh from the model
meshed_region = model.metadata.meshed_region

# print the first 10 coordinates for the field
node_ids = field.scoping.ids
for node_id in node_ids[:10]:
    # fetch each individual node by node ID
    node_coord = meshed_region.nodes.node_by_id(node_id).coordinates
    print(f'Node ID {node_id} : %8.5f, %8.5f, %8.5f' % tuple(node_coord))

Out:

Node ID 508 : -0.01251,  0.01403,  0.02310
Node ID 509 : -0.01378,  0.00218,  0.02310
Node ID 909 : -0.03000,  0.00000,  0.02310
Node ID 910 : -0.02121,  0.02121,  0.02310
Node ID 524 : -0.01251,  0.01403,  0.00000
Node ID 525 : -0.01378,  0.00218,  0.00000
Node ID 534 : -0.03000,  0.00000,  0.00000
Node ID 533 : -0.02121,  0.02121,  0.00000
Node ID 513 : -0.00891, -0.00952,  0.02310
Node ID 908 : -0.02121, -0.02121,  0.02310

Rather than individually querying for each node coordinate of the field, you can instead remap the field data to match the order of the nodes in the meshed region by using the map_scoping method.

This provides the indices needed to get the data from field.data to match the order of nodes in the mesh.

nodes = meshed_region.nodes
ind, mask = nodes.map_scoping(field.scoping)

# show that the order of the remapped node scoping matches the field scoping
print('Scoping matches:', np.allclose(np.array(nodes.scoping.ids)[ind],
                                      field.scoping.ids))

# We can now plot the von mises stress relative to the Z coordinates
z_coord = nodes.coordinates_field.data[ind, 2]

plt.plot(z_coord, field.data, '.')
plt.xlabel('Z Coordinate (m)')
plt.ylabel('Equivalent Stress (Pa)')
plt.show()
00 basic transient

Out:

Scoping matches: True

Total running time of the script: ( 0 minutes 2.209 seconds)

Gallery generated by Sphinx-Gallery