Note
Go to the end to download the full example code.
Transient analysis result example#
This example shows how to postprocess a transient result and visualize the outputs.
# Import the necessary modules
import matplotlib.pyplot as plt
import numpy as np
from ansys.dpf import core as dpf
from ansys.dpf.core import examples
from ansys.dpf.core import operators as ops
Download the transient result example. This example is not included in DPF-Core by default to speed up the installation. Downloading this example should take only a few seconds.
Next, create the model and display the state of the result. This transient result file contains several individual results, each at a different timestamp.
transient = examples.download_transient_result()
model = dpf.Model(transient)
print(model)
DPF Model
------------------------------
Static analysis
Unit system: MKS: m, kg, N, s, V, A, degC
Physics Type: Mechanical
Available results:
- displacement: Nodal Displacement
- reaction_force: Nodal Force
- elemental_summable_miscellaneous_data: Elemental Elemental Summable Miscellaneous Data
- element_nodal_forces: ElementalNodal Element nodal Forces
- stress: ElementalNodal Stress
- elemental_volume: Elemental Volume
- stiffness_matrix_energy: Elemental Energy-stiffness matrix
- artificial_hourglass_energy: Elemental Hourglass Energy
- thermal_dissipation_energy: Elemental thermal dissipation energy
- kinetic_energy: Elemental Kinetic Energy
- co_energy: Elemental co-energy
- incremental_energy: Elemental incremental energy
- elastic_strain: ElementalNodal Strain
- thermal_strain: ElementalNodal Thermal Strains
- thermal_strains_eqv: ElementalNodal Thermal Strains eqv
- swelling_strains: ElementalNodal Swelling Strains
- element_euler_angles: ElementalNodal Element Euler Angles
- structural_temperature: ElementalNodal Structural temperature
------------------------------
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)
[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 displacements for all results#
Create a displacement operator and set its time scoping request to the entire time frequency support:
disp = model.results.displacement()
timeids = range(1, tf.n_sets + 1) # Must use 1-based indexing.
disp.inputs.time_scoping(timeids)
# 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)
[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()
Plot the minimum and maximum displacements over time for the X component.
disp_z = disp.Z()
disp_z.inputs.time_scoping(timeids)
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()
Postprocessing stress#
Create an equivalent (von Mises) stress operator and set its time scoping to the entire time frequency 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)
DPF stress_0.s_eqv Field
Location: Nodal
Unit: Pa
35 entities
Data: 1 components and 35 elementary data
IDs data(Pa)
------------ ----------
0 0.000000e+00
1 6.215362e+02
2 1.017913e+03
...
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()
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])
[508 509 909 910 524 525 534 533 513 908]
As you can see, these node IDs are not in order. Additionally, there may be fewer 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.
Here 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))
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 use the map_scoping
to remap the field data to match the order of the nodes in the meshed region.
Obtain 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))
# 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()
Scoping matches: True
Total running time of the script: (0 minutes 0.531 seconds)