Skip to content

Commit 8ea67e5

Browse files
complete beam example
1 parent d860a33 commit 8ea67e5

File tree

1 file changed

+226
-52
lines changed

1 file changed

+226
-52
lines changed

examples/14-lsdyna/01-lsdyna_beam.py

Lines changed: 226 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,18 @@
4444
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4545
# Create the model and print its contents. This LS-DYNA d3plot file contains
4646
# several individual results, each at different times. The d3plot file does not
47-
# contain information related to Units. In this case, as the simulation was run
48-
# through Mechanical, a file.actunits file is produced. If this file is
49-
# supplemented in the data_sources, the units will be correctly fetched for all
50-
# results in the file as well as for the mesh.
47+
# contain information related to Units.
48+
49+
# In this case, as the simulation was run through Mechanical, a ''file.actunits''
50+
# file is produced. If this file is supplemented in the data_sources, the units
51+
# will be correctly fetched for all results in the file as well as for the mesh.
5152

5253
d3plot = examples.download_d3plot_beam()
53-
ds = dpf.DataSources()
54-
ds.set_result_file_path(d3plot[0], "d3plot")
55-
ds.add_file_path(d3plot[3], "actunits")
56-
my_model = dpf.Model(ds)
57-
# print(my_model)
54+
my_data_sources = dpf.DataSources()
55+
my_data_sources.set_result_file_path(d3plot[0], key="d3plot")
56+
my_data_sources.add_file_path(d3plot[3], key="actunits")
57+
my_model = dpf.Model(my_data_sources)
58+
print(my_model)
5859

5960
###############################################################################
6061
# The model has solid (3D) elements and beam (1D) elements. Some of the results
@@ -72,89 +73,262 @@
7273
mesh=my_meshed_region, property=dpf.common.elemental_properties.element_shape
7374
).eval()
7475

75-
# Define the meshes in separate variables
76+
# Define the meshes for each body in separate variables
7677
ball_mesh = my_meshes.get_mesh(label_space_or_index={"body": 1, "elshape": 1})
7778
plate_mesh = my_meshes.get_mesh(label_space_or_index={"body": 2, "elshape": 2})
7879

7980
# print(my_meshes)
81+
8082
###############################################################################
8183
# Ball
8284

83-
# print(ball_mesh)
84-
# my_ball_mesh.plot()
85+
print("Ball mesh", "\n", ball_mesh, "\n")
86+
ball_mesh.plot(title="Ball mesh", text="Ball mesh")
8587

8688
###############################################################################
8789
# Plate
8890

89-
# print(my_plate_mesh)
90-
# my_plate_mesh.plot()
91+
print("Plate mesh", "\n", plate_mesh)
92+
plate_mesh.plot(title="Plate mesh", text="Plate mesh")
9193

9294
###############################################################################
95+
# Define the mesh scoping to use it with the operators
96+
my_meshes_scoping = ops.scoping.split_on_property_type(mesh=my_meshed_region).eval()
9397

94-
my_meshes_scopings = ops.scoping.split_on_property_type(mesh=my_meshed_region).eval()
98+
# Define the mesh scoping for each body/element shape in separate variables
99+
ball_scoping = my_meshes_scoping.get_scoping(label_space_or_index={"elshape": 1})
100+
plate_scoping = my_meshes_scoping.get_scoping(label_space_or_index={"elshape": 2})
95101

96-
# Define the mesh scoping in separate variables
97-
# Here we have a elemental location
98-
ball_scoping = my_meshes_scopings.get_scoping(label_space_or_index={"elshape": 1})
99-
plate_scoping = my_meshes_scopings.get_scoping(label_space_or_index={"elshape": 2})
100-
101-
# We will need a nodal location, so we have to transpose the mesh scoping from elemental to nodal
102-
ball_scoping_nodal = dpf.operators.scoping.transpose(
103-
mesh_scoping=ball_scoping, meshed_region=my_meshed_region
104-
).eval()
102+
# We will plot the results in a mesh deformed by the displacement. The displacement
103+
# is in a nodal location, so we need to define a nodal scoping for the palte
105104
plate_scoping_nodal = dpf.operators.scoping.transpose(
106105
mesh_scoping=plate_scoping, meshed_region=my_meshed_region
107106
).eval()
107+
108+
###############################################################################
109+
110+
# The next manipulations can be applied to the following beam operators
111+
# that handle the correspondent results :
112+
113+
# - beam_axial_force: Beam Axial Force
114+
# - beam_s_shear_force: Beam S Shear Force
115+
# - beam_t_shear_force: Beam T Shear Force
116+
# - beam_s_bending_moment: Beam S Bending Moment
117+
# - beam_t_bending_moment: Beam T Bending Moment
118+
# - beam_torsional_moment: Beam Torsional Moment
119+
# - beam_axial_stress: Beam Axial Stress
120+
# - beam_rs_shear_stress: Beam Rs Shear Stress
121+
# - beam_tr_shear_stress: Beam Tr Shear Stress
122+
# - beam_axial_plastic_strain: Beam Axial Plastic Strain
123+
# - beam_axial_total_strain: Beam Axial Total Strain
124+
125+
# We do not demonstrate separately how to use each of them in this example
126+
# once they have similar methods. We .... in the beam stress and forces results
127+
128+
# So, if you want to operate on other operator, uou just need to change their
129+
# scripting name in the code lines.
130+
108131
###############################################################################
109132
# Comparing results in different time steps
110133
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
111-
# 1) Define the time steps
134+
135+
# 1) Define the time steps set
112136
time_steps_set = [2, 6, 12]
137+
138+
# 2) Prepare the collections to store the results for each time step
139+
140+
# To compare the results in the same image you have to copy the mesh for each plot
141+
plate_meshes = dpf.MeshesContainer()
142+
plate_meshes.add_label("time")
143+
144+
# The displacements for each time steps to deform the mesh accordingly
145+
plate_displacements = dpf.FieldsContainer()
146+
plate_displacements.add_label(label="time")
147+
148+
# The axial force results for each time steps. Here
149+
plate_axial_force = dpf.FieldsContainer()
150+
plate_axial_force.add_label(label="time")
151+
152+
# 3) Use the :class: `Plotter <ansys.dpf.core.plotter.DpfPlotter>` class
153+
# to add the plots in the same image
154+
comparison_plot = dpf.plotter.DpfPlotter()
155+
156+
side_bar_args = dict(
157+
title="Beam axial force (N)", fmt="%.2e", title_font_size=15, label_font_size=15
158+
)
159+
160+
# 4) As we want to compare the results in the same plot we will need this variable.
161+
# It represents the distance between the meshes
113162
j = -400
114-
# 2) Copy the mesh of interest. Here it is the plate mesh that we copy along the X axis
115-
for i in time_steps_set:
163+
164+
# Here we use a loop where each iteration correspond to the manipulations for a given time step
165+
166+
# 5) Copy the mesh of interest. Here it is the plate mesh that we copy along the X axis
167+
for i in time_steps_set: # Loop through the time steps
116168
# Copy the mesh
117-
globals()[f"plate_mesh_{i}"] = plate_mesh.deep_copy()
169+
plate_meshes.add_mesh(label_space={"time": i}, mesh=plate_mesh.deep_copy())
118170

119-
# 3) Get the plot coordinates that will be changed
120-
coords_to_update = globals()[f"plate_mesh_{i}"].nodes.coordinates_field
121-
# 4) Define the coordinates where the new mesh will be placed
171+
# 6) Get the plot coordinates that will be changed (so we can compare the results side by side)
172+
coords_to_update = plate_meshes.get_mesh(
173+
label_space_or_index={"time": i}
174+
).nodes.coordinates_field
175+
176+
# 7) Define the coordinates where the new mesh will be placed
122177
overall_field = dpf.fields_factory.create_3d_vector_field(
123178
num_entities=1, location=dpf.locations.overall
124179
)
125180
overall_field.append(data=[j, 0.0, 0.0], scopingid=1)
126181

127-
# 5) Define the updated coordinates
182+
# 8) Define the updated coordinates
128183
new_coordinates = ops.math.add(fieldA=coords_to_update, fieldB=overall_field).eval()
129184
coords_to_update.data = new_coordinates.data
130185

131-
# 6) Extract the result, here we start by getting the displacement
132-
globals()[f"my_displacement_{i}"] = my_model.results.displacement(
133-
time_scoping=i, mesh_scoping=plate_scoping_nodal
134-
).eval()[0]
135-
136-
# Increment the coordinate value for the loop
186+
# 9) Extract the result, here we start by getting the beam_rs_shear_stress
187+
plate_axial_force.add_field(
188+
label_space={"time": i},
189+
field=my_model.results.beam_axial_force(
190+
time_scoping=i, mesh_scoping=plate_scoping_nodal
191+
).eval()[0],
192+
)
193+
# 10) We will also get the displacement to deform the mesh
194+
plate_displacements.add_field(
195+
label_space={"time": i},
196+
field=my_model.results.displacement(
197+
time_scoping=i, mesh_scoping=plate_scoping_nodal
198+
).eval()[0],
199+
)
200+
# 11) Add the result and the mesh to the plot
201+
comparison_plot.add_field(
202+
field=plate_axial_force.get_field(label_space_or_index={"time": i}),
203+
meshed_region=plate_meshes.get_mesh(label_space_or_index={"time": i}),
204+
deform_by=plate_displacements.get_field(label_space_or_index={"time": i}),
205+
scalar_bar_args=side_bar_args,
206+
)
207+
comparison_plot.add_node_labels(
208+
nodes=[289],
209+
labels=[f"Time step = {i}"],
210+
meshed_region=plate_meshes.get_mesh(label_space_or_index={"time": i}),
211+
font_size=10,
212+
)
213+
# 12) Increment the coordinate value for the loop
137214
j = j - 400
215+
216+
# Visualise the plot
217+
comparison_plot.show_figure()
218+
138219
###############################################################################
139-
# Use the :class: `Plotter <ansys.dpf.core.plotter.DpfPlotter>` class to add the plots
140-
# in the same image
220+
# Plot a graph over time for the elements with max and min results values
221+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
141222

142-
comparison_plot = dpf.plotter.DpfPlotter()
223+
# Here we make a workflow with a more verbose approach. This is useful because we use operators
224+
# having several matching inputs or outputs. So the connexions are more clear, and it is
225+
# easier to use and reuse the workflow.
143226

144-
comparison_plot.add_field(
145-
field=my_displacement_2, meshed_region=plate_mesh_2, deform_by=my_displacement_2
146-
)
147-
comparison_plot.add_field(
148-
field=my_displacement_6, meshed_region=plate_mesh_6, deform_by=my_displacement_6
149-
)
150-
comparison_plot.add_field(
151-
field=my_displacement_12, meshed_region=plate_mesh_12, deform_by=my_displacement_12
227+
# Find the element with the max values over all the time steps and return its ID
228+
229+
# Define the workflow object
230+
max_workflow = dpf.Workflow()
231+
max_workflow.progress_bar = False
232+
# Define the norm operator
233+
max_norm = ops.math.norm_fc()
234+
# Define the max of each entity with the evaluated norm as an input
235+
max_per_ent = ops.min_max.min_max_by_entity(fields_container=max_norm.outputs.fields_container)
236+
# Define the max over all entities
237+
global_max = ops.min_max.min_max(field=max_per_ent.outputs.field_max)
238+
# Get the scoping
239+
max_scop = ops.utility.extract_scoping(field_or_fields_container=global_max.outputs.field_max)
240+
# Get the id
241+
max_id = ops.scoping.scoping_get_attribute(
242+
scoping=max_scop.outputs.mesh_scoping_as_scoping, property_name="ids"
152243
)
153244

154-
comparison_plot.show_figure()
245+
# Add the operators to the workflow
246+
max_workflow.add_operators(operators=[max_norm, max_per_ent, global_max, max_scop, max_id])
247+
max_workflow.set_input_name("fields_container", max_norm.inputs.fields_container)
248+
max_workflow.set_output_name("max_id", max_id.outputs.property_as_vector_int32_)
249+
max_workflow.set_output_name("max_entity_scoping", max_scop.outputs.mesh_scoping_as_scoping)
155250

156251
###############################################################################
157-
# For example the ball velocity
158-
# v = my_model.results.velocity(time_scoping=my_time_scoping, mesh_scoping=ball_scoping).eval()
252+
# Get all the time steps
253+
time_all = my_model.metadata.time_freq_support.time_frequencies
254+
# Extract all the stresses results on the plate
255+
plate_beam_axial_stress = my_model.results.beam_axial_stress(
256+
time_scoping=time_all, mesh_scoping=plate_scoping
257+
).eval()
258+
plate_beam_rs_shear_stress = my_model.results.beam_rs_shear_stress(
259+
time_scoping=time_all, mesh_scoping=plate_scoping
260+
).eval()
261+
plate_beam_tr_shear_stress = my_model.results.beam_tr_shear_stress(
262+
time_scoping=time_all, mesh_scoping=plate_scoping
263+
).eval()
264+
265+
# List of operators to simplify the code
266+
beam_stresses = [plate_beam_axial_stress, plate_beam_rs_shear_stress, plate_beam_tr_shear_stress]
267+
graph_labels = [
268+
"Beam axial stress",
269+
"Beam rs shear stress",
270+
"Beam tr shear stress",
271+
]
272+
# List of elements ids
273+
max_stress_elements_ids = []
274+
# Scopings container
275+
max_stress_elements_scopings = dpf.ScopingsContainer()
276+
max_stress_elements_scopings.add_label("stress_result")
277+
278+
# Loop through each stress result that gets the elements with maximum solicitation id, re-escope the fields
279+
# container to keep only the data for this element, and finally plot a stress x time graph
280+
281+
for j in range(0, len(beam_stresses)): # Loop through each stress result
282+
# Use the pre-defined workflow to define the element with maximum solicitation
283+
max_workflow.connect(pin_name="fields_container", inpt=beam_stresses[j])
284+
max_stress_elements_ids.append(
285+
max_workflow.get_output(pin_name="max_id", output_type=dpf.types.vec_int)
286+
)
287+
max_stress_elements_scopings.add_scoping(
288+
label_space={"stress_result": j},
289+
scoping=max_workflow.get_output(
290+
pin_name="max_entity_scoping", output_type=dpf.types.scoping
291+
),
292+
)
293+
294+
# Re-scope the results to keep only the data for the identified element
295+
beam_stresses[j] = ops.scoping.rescope_fc(
296+
fields_container=beam_stresses[j],
297+
mesh_scoping=max_stress_elements_scopings.get_scoping(
298+
label_space_or_index={"stress_result": j}
299+
),
300+
).eval()
301+
302+
# The d3plot file gives us fields containers labeled by time. So in each field we have the stress value in a
303+
# given time for the chosen element. We need to rearrange the fields container into fields.
304+
305+
beam_stresses[j] = ops.utility.merge_to_field_matrix(fields1=beam_stresses[j]).eval()
306+
plt.plot(
307+
time_all.data,
308+
beam_stresses[j].data[0],
309+
label=f"{graph_labels[j]}, element id:{max_stress_elements_ids[j][0]}",
310+
)
311+
312+
plt.title("Beam stresses evolution")
313+
plt.xlabel("Time (s)")
314+
plt.ylabel("Beam stresses (MPa)")
315+
plt.legend()
316+
plt.show()
159317

160318
###############################################################################
319+
# Results coordinates system
320+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
321+
322+
# The results are given in the Cartesian coordinates system by default.
323+
324+
# The beam results are given directly in the local directions. For example the beam stresses:
325+
326+
# We have the axial stress, given in the beam axis, and the stresses defined in the
327+
# cross-section directions, tr stress in the transverse direction (t) and rs stress
328+
# perpendicular to the tr direction (s).
329+
330+
# Those results are given as scalars.
331+
332+
# Unfortunately there are no operators for LS-DYNA files that directly allows you to:
333+
# - Rotate results from local coordinate system to global coordinate system;
334+
# - Extract the rotation matrix between the local and global coordinate systems;

0 commit comments

Comments
 (0)