Skip to content

Commit 1da9f10

Browse files
rs-bh-npre-commit-ci[bot]Revathyvenugopal162pyansys-ci-botkoubaa
authored
feat: Plate thickness optimization example (#868)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Revathy Venugopal <[email protected]> Co-authored-by: pyansys-ci-bot <[email protected]> Co-authored-by: Mohamed Koubaa <[email protected]>
1 parent f5278f3 commit 1da9f10

File tree

4 files changed

+383
-0
lines changed

4 files changed

+383
-0
lines changed

doc/changelog/868.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Plate thickness optimization example
89.1 KB
Loading
Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
"""
24+
Plate Thickness Optimization
25+
------------------
26+
This example shows how to use PyDyna to import a mesh, set up a plate thickness
27+
optimization analysis, run the analysis iteratively until a target displacement
28+
is reached,and then display the results of that optimization.
29+
30+
.. LINKS AND REFERENCES
31+
.. _ls_dyna_knowledge_base: https://lsdyna.ansys.com/knowledge-base/
32+
"""
33+
34+
###############################################################################
35+
# Perform required imports
36+
# ~~~~~~~~~~~~~~~~~~~~~~~~
37+
# Import required packages, including those for the keywords, deck, and solver.
38+
39+
import os
40+
import pathlib
41+
import shutil
42+
import tempfile
43+
44+
import ansys.dpf.core as dpf
45+
from ansys.dpf.core import operators as ops
46+
import matplotlib.pyplot as plt
47+
import pandas as pd
48+
49+
from ansys.dyna.core import Deck
50+
from ansys.dyna.core import keywords as kwd
51+
from ansys.dyna.core.pre.examples.download_utilities import EXAMPLES_PATH, DownloadManager
52+
from ansys.dyna.core.run.linux_runner import LinuxRunner
53+
from ansys.dyna.core.run.options import MemoryUnit, MpiOption, Precision
54+
from ansys.dyna.core.run.windows_runner import WindowsRunner
55+
56+
# sphinx_gallery_thumbnail_path = '_static/pre/opt/plate_thickness.png'
57+
58+
59+
workdir = tempfile.TemporaryDirectory()
60+
61+
mesh_file_name = "bar_impact_mesh.k"
62+
63+
mesh_file = DownloadManager().download_file(
64+
mesh_file_name, "ls-dyna", "Bar_Impact", destination=os.path.join(EXAMPLES_PATH, "Bar_Impact")
65+
)
66+
67+
# If you'd like to insert your own path to a local mesh file you can do so by replacing the line
68+
# above with:
69+
# mesh_file = "C:\Path\to\file\\bar_impact_mesh.k"
70+
71+
###############################################################################
72+
# Set analysis parameters
73+
# ~~~~~~~~~~~~~~~~~~~~~~~~
74+
# Define the number of iterations, thickness increment, target displacement,
75+
# and initial velocity for the analysis
76+
77+
max_iterations = 20
78+
initial_thickness = 0.1
79+
thickness_increment = 0.05
80+
target_displacement = 1.0
81+
initial_velocity = 275.0e2
82+
83+
###############################################################################
84+
# Create a deck and keywords
85+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
86+
# Create a deck, which is the container for all the keywords.
87+
# Then, create and append individual keywords to the deck.
88+
89+
90+
def create_input_deck(thickness):
91+
deck = Deck()
92+
deck.title = "Bar Thickness - %.4s" % thickness
93+
94+
# Define bar material
95+
mat_1 = kwd.Mat003(mid=1)
96+
mat_1.ro = 7.85000e-9
97+
mat_1.e = 150000.0
98+
mat_1.pr = 0.34
99+
mat_1.sigy = 390.0
100+
mat_1.etan = 90.0
101+
102+
# Define bar section
103+
sec_1 = kwd.SectionSolid(secid=1)
104+
sec_1.elform = 1
105+
106+
# Define plate material
107+
mat_2 = kwd.Mat003(mid=2)
108+
mat_2.ro = 7.85000e-9
109+
mat_2.e = 1500000.0
110+
mat_2.pr = 0.34
111+
mat_2.sigy = 3900.0
112+
mat_2.etan = 900.0
113+
114+
# Define plate section
115+
sec_2 = kwd.SectionShell(secid=2)
116+
sec_2.elform = 2
117+
sec_2.t1 = thickness
118+
sec_2.t2 = thickness
119+
sec_2.t3 = thickness
120+
sec_2.t4 = thickness
121+
122+
# Define bar part
123+
part_1 = kwd.Part()
124+
part_1.parts = pd.DataFrame({"pid": [1], "mid": [mat_1.mid], "secid": [sec_1.secid]})
125+
126+
# Define plate part
127+
part_2 = kwd.Part()
128+
part_2.parts = pd.DataFrame({"pid": [2], "mid": [mat_2.mid], "secid": [sec_2.secid]})
129+
130+
# Define coordinate system
131+
cs_1 = kwd.DefineCoordinateSystem(cid=1)
132+
cs_1.xl = 1.0
133+
cs_1.yp = 1.0
134+
135+
# Define initial velocity
136+
init_vel = kwd.InitialVelocityGeneration()
137+
init_vel.id = part_1.parts["pid"][0]
138+
init_vel.styp = 2
139+
init_vel.vy = initial_velocity
140+
init_vel.icid = cs_1.cid
141+
142+
# Define box for node set
143+
box_1 = kwd.DefineBox(boxid=1, xmn=-500, xmx=500, ymn=39.0, ymx=40.1, zmn=-500, zmx=500)
144+
145+
# Create node set
146+
set_node_1 = kwd.SetNodeGeneral()
147+
set_node_1.sid = 1
148+
set_node_1.option = "BOX"
149+
set_node_1.e1 = box_1.boxid
150+
151+
# Define boxes for boundary conditions
152+
box_plate_zN = kwd.DefineBox(boxid=2, xmn=-0.1, xmx=10.1, ymn=41.0, ymx=43.0, zmn=-10.1, zmx=-9.9)
153+
box_plate_zP = kwd.DefineBox(boxid=3, xmn=0.1, xmx=9.9, ymn=41.0, ymx=43.0, zmn=-0.1, zmx=0.1)
154+
box_plate_xP = kwd.DefineBox(boxid=4, xmn=9.9, xmx=10.1, ymn=41.0, ymx=43.0, zmn=-10.1, zmx=0.1)
155+
box_plate_xN = kwd.DefineBox(boxid=5, xmn=-0.1, xmx=0.1, ymn=41.0, ymx=43.0, zmn=-9.9, zmx=-0.1)
156+
157+
# Create node set for fixed BC
158+
set_node_Fixed = kwd.SetNodeGeneral()
159+
set_node_Fixed.sid = 2
160+
set_node_Fixed.option = "BOX"
161+
set_node_Fixed.e1 = box_plate_zN.boxid
162+
set_node_Fixed.e2 = box_plate_xP.boxid
163+
164+
# Define fixed Boundary Conditions
165+
fixed_bc = kwd.BoundarySpcSet(dofx=1, dofy=1, dofz=1, dofrx=1, dofry=1, dofrz=1)
166+
fixed_bc.nsid = set_node_Fixed.sid
167+
168+
# Create node set for symmetric BC normal to Z Axis
169+
set_node_zNormal = kwd.SetNodeGeneral()
170+
set_node_zNormal.sid = 3
171+
set_node_zNormal.option = "BOX"
172+
set_node_zNormal.e1 = box_plate_zP.boxid
173+
174+
# Define zNormal Boundary Conditions
175+
zNormal_bc = kwd.BoundarySpcSet(dofx=0, dofy=0, dofz=1, dofrx=1, dofry=1, dofrz=0)
176+
zNormal_bc.nsid = set_node_zNormal.sid
177+
178+
# Create node set for symmetric BC normal to X Axis
179+
set_node_xNormal = kwd.SetNodeGeneral()
180+
set_node_xNormal.sid = 4
181+
set_node_xNormal.option = "BOX"
182+
set_node_xNormal.e1 = box_plate_xN.boxid
183+
184+
# Define xNormal Boundary Conditions
185+
xNormal_bc = kwd.BoundarySpcSet(dofx=1, dofy=0, dofz=0, dofrx=0, dofry=1, dofrz=1)
186+
xNormal_bc.nsid = set_node_xNormal.sid
187+
188+
# Define box for node set of plate
189+
box_plate = kwd.DefineBox(boxid=6, xmn=-1, xmx=11, ymn=39.0, ymx=40.1, zmn=-11, zmx=1)
190+
191+
# Create node set for plate
192+
set_node_plate = kwd.SetNodeGeneral()
193+
set_node_plate.sid = 5
194+
set_node_plate.option = "BOX"
195+
set_node_plate.e1 = box_plate.boxid
196+
197+
# Define contact
198+
contact = kwd.ContactAutomaticSingleSurface(surfa=0)
199+
contact.fs = 0.3
200+
201+
# Define control termination
202+
control_term = kwd.ControlTermination(endtim=2.00000e-4, dtmin=0.001)
203+
204+
# Define database cards
205+
deck_dt_out = 8.00000e-8
206+
deck_glstat = kwd.DatabaseGlstat(dt=deck_dt_out, binary=3)
207+
deck_matsum = kwd.DatabaseMatsum(dt=deck_dt_out, binary=3)
208+
deck_nodout = kwd.DatabaseNodout(dt=deck_dt_out, binary=3)
209+
deck_elout = kwd.DatabaseElout(dt=deck_dt_out, binary=3)
210+
deck_rwforc = kwd.DatabaseRwforc(dt=deck_dt_out, binary=3)
211+
deck_d3plot = kwd.DatabaseBinaryD3Plot(dt=4.00000e-6)
212+
213+
# Define deck history node
214+
deck_hist_node_1 = kwd.DatabaseHistoryNodeSet()
215+
deck_hist_node_1.id1 = set_node_1.sid
216+
217+
# Append all cards to input deck
218+
deck.extend(
219+
[
220+
deck_glstat,
221+
deck_matsum,
222+
deck_nodout,
223+
deck_elout,
224+
deck_rwforc,
225+
deck_d3plot,
226+
set_node_1,
227+
control_term,
228+
contact,
229+
box_1,
230+
box_plate_zN,
231+
box_plate_zP,
232+
box_plate_xP,
233+
box_plate_xN,
234+
box_plate,
235+
set_node_Fixed,
236+
set_node_zNormal,
237+
set_node_xNormal,
238+
set_node_plate,
239+
fixed_bc,
240+
zNormal_bc,
241+
xNormal_bc,
242+
init_vel,
243+
cs_1,
244+
part_1,
245+
mat_1,
246+
sec_1,
247+
part_2,
248+
mat_2,
249+
sec_2,
250+
deck_hist_node_1,
251+
]
252+
)
253+
254+
return deck
255+
256+
257+
def write_input_deck(**kwargs):
258+
thickness = kwargs.get("thickness")
259+
wd = kwargs.get("wd")
260+
if not all((thickness, wd)):
261+
raise Exception("Missing input!")
262+
deck = create_input_deck(thickness)
263+
# Import mesh
264+
deck.append(kwd.Include(filename=mesh_file_name))
265+
266+
# Write LS-DYNA input deck
267+
os.makedirs(wd, exist_ok=True)
268+
deck.export_file(os.path.join(wd, "input.k"))
269+
shutil.copyfile(mesh_file, os.path.join(wd, mesh_file_name))
270+
271+
272+
###############################################################################
273+
# Define the Dyna solver function
274+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
275+
#
276+
277+
278+
def run_job(directory):
279+
if os.name == "nt":
280+
runner = WindowsRunner(
281+
ncpu=2, memory=2, precision=Precision.SINGLE, mpi_option=MpiOption.MPP_INTEL_MPI, memory_unit=MemoryUnit.MB
282+
)
283+
elif os.name == "posix":
284+
runner = LinuxRunner(
285+
ncpu=2, memory=2, precision=Precision.DOUBLE, mpi_option=MpiOption.MPP_INTEL_MPI, memory_unit=MemoryUnit.MB
286+
)
287+
runner.set_input("input.k", directory)
288+
runner.run() # Run LS-DYNA simulation
289+
assert os.path.isfile(os.path.join(directory, "d3plot")), "No result file found"
290+
291+
292+
###############################################################################
293+
# Define the DPF output function
294+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
295+
#
296+
297+
298+
def get_plate_displacement(directory):
299+
ds = dpf.DataSources()
300+
result_file = os.path.join(directory, "d3plot")
301+
assert os.path.isfile(result_file)
302+
ds.set_result_file_path(result_file, "d3plot")
303+
model = dpf.Model(ds)
304+
305+
# Create mesh operator
306+
mes_op = dpf.operators.mesh.mesh_provider()
307+
mes_op.inputs.data_sources.connect(ds)
308+
# Isolate Part 2
309+
mes_op.connect(25, [2])
310+
# Extract mesh
311+
part_mesh = mes_op.outputs.mesh()
312+
313+
# Create scoping operator from mesh using from_mesh scoping operator
314+
part_mesh_op = dpf.operators.scoping.from_mesh()
315+
part_mesh_op.inputs.mesh.connect(part_mesh)
316+
part_scoping = part_mesh_op.outputs.scoping()
317+
318+
# create displacement entity, apply part scoping
319+
disp = model.results.displacement
320+
disp.on_mesh_scoping(part_scoping)
321+
322+
disp_op = disp.on_all_time_freqs()
323+
324+
# Find min and max displacement
325+
min_max_op = ops.min_max.min_max_fc(ops.math.norm_fc(disp_op))
326+
327+
min_displ = min_max_op.outputs.field_min()
328+
max_displ = min_max_op.outputs.field_max()
329+
330+
max_disp_data = max_displ.data
331+
min_disp_data = min_displ.data
332+
333+
tdata = model.metadata.time_freq_support.time_frequencies.data
334+
335+
return tdata, max_disp_data, min_disp_data
336+
337+
338+
###############################################################################
339+
# Run solver iteratively until target displacement is reached
340+
# ~~~~~~~~~~~~~~~~~~~~~~
341+
#
342+
343+
for iteration in range(0, max_iterations):
344+
# Define thickness based on iteration
345+
thickness = initial_thickness + thickness_increment * iteration
346+
wd = os.path.join(workdir.name, "thickness_%.4s" % thickness)
347+
print(wd)
348+
pathlib.Path(wd).mkdir(exist_ok=True)
349+
# Create LS-Dyna input deck with new thickness
350+
write_input_deck(thickness=thickness, wd=wd)
351+
# Run Solver
352+
try:
353+
run_job(wd)
354+
# Run PyDPF Post
355+
time_data, max_disp_data, min_disp_data = get_plate_displacement(wd)
356+
reduced_time_data = [t * 1000 for t in time_data]
357+
# Determine if target displacement is reached
358+
if max(max_disp_data) <= target_displacement:
359+
print("Final Thickness: %.4s, Max Plate Displacement: %.5s" % (thickness, max(max_disp_data)))
360+
plt.plot(
361+
reduced_time_data, max_disp_data, "r", label="Max Plate Displacement with %.4s thickness" % thickness
362+
)
363+
break
364+
# Add series to the plot
365+
plt.plot(reduced_time_data, max_disp_data, "b")
366+
367+
except Exception as e:
368+
print(e)
369+
###############################################################################
370+
# Generate graphical output
371+
# ~~~~~~~~~~~~~~~~~~~~~~~~~
372+
#
373+
374+
plt.xlabel("Time (e^-3 s)")
375+
plt.ylabel("Displacement (mm)")
376+
plt.legend()
377+
plt.show()

examples/Optimization/README.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Optimization
2+
=================
3+
4+
This example demonstrates how to optimize the thickness of a plate using PyDYNA.
5+

0 commit comments

Comments
 (0)