Skip to content

Commit 355d5e0

Browse files
committed
add unsteady FEA ad testcase
1 parent 8d2b096 commit 355d5e0

2 files changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env python
2+
3+
## \file run.py
4+
# \brief Unsteady adjoint FEA case with custom load.
5+
# \version 7.5.1 "Blackbird"
6+
#
7+
# SU2 Project Website: https://su2code.github.io
8+
#
9+
# The SU2 Project is maintained by the SU2 Foundation
10+
# (http://su2foundation.org)
11+
#
12+
# Copyright 2012-2023, SU2 Contributors (cf. AUTHORS.md)
13+
#
14+
# SU2 is free software; you can redistribute it and/or
15+
# modify it under the terms of the GNU Lesser General Public
16+
# License as published by the Free Software Foundation; either
17+
# version 2.1 of the License, or (at your option) any later version.
18+
#
19+
# SU2 is distributed in the hope that it will be useful,
20+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22+
# Lesser General Public License for more details.
23+
#
24+
# You should have received a copy of the GNU Lesser General Public
25+
# License along with SU2. If not, see <http://www.gnu.org/licenses/>.
26+
27+
import pysu2ad
28+
from mpi4py import MPI
29+
30+
common_settings = """
31+
SOLVER= ELASTICITY
32+
GEOMETRIC_CONDITIONS= LARGE_DEFORMATIONS
33+
FORMULATION_ELASTICITY_2D= PLANE_STRESS
34+
35+
TIME_DOMAIN= YES
36+
TIME_STEP=0.01
37+
TIME_DISCRE_FEA= NEWMARK_IMPLICIT
38+
NEWMARK_GAMMA= 0.5
39+
NEWMARK_BETA= 0.25
40+
41+
MATERIAL_MODEL= NEO_HOOKEAN
42+
ELASTICITY_MODULUS= 10000
43+
POISSON_RATIO= 0.3
44+
MATERIAL_DENSITY= __DENSITY__
45+
46+
MARKER_CLAMPED= ( x_minus )
47+
MARKER_FLUID_LOAD= ( x_plus, y_minus, y_plus )
48+
49+
LINEAR_SOLVER= CONJUGATE_GRADIENT
50+
DISCADJ_LIN_SOLVER= CONJUGATE_GRADIENT
51+
LINEAR_SOLVER_PREC= ILU
52+
DISCADJ_LIN_PREC= ILU
53+
LINEAR_SOLVER_ERROR= 1E-5
54+
LINEAR_SOLVER_ITER= 100
55+
56+
MESH_FORMAT= RECTANGLE
57+
MESH_BOX_SIZE= ( 17, 5, 0 )
58+
MESH_BOX_LENGTH= ( 0.5, 0.05, 0 )
59+
60+
OUTPUT_FILES= RESTART, PARAVIEW
61+
OUTPUT_WRT_FREQ= 1
62+
OBJECTIVE_FUNCTION= STRESS_PENALTY
63+
STRESS_PENALTY_PARAM= ( 500, 20 )
64+
65+
INNER_ITER= 20
66+
CONV_RESIDUAL_MINVAL= -4
67+
CONV_STARTITER= 5
68+
MAX_TIME= 0.2
69+
TIME_ITER= 21
70+
"""
71+
72+
primal_settings = """
73+
MATH_PROBLEM= DIRECT
74+
SCREEN_OUTPUT= TIME_ITER, CUR_TIME, INNER_ITER, RMS_RES, LINSOL, VMS, STRESS_PENALTY, TAVG_STRESS_PENALTY
75+
HISTORY_OUTPUT= ITER, RMS_RES, STRUCT_COEFF, TAVG_STRUCT_COEFF
76+
CONV_FIELD= REL_RMS_RTOL
77+
"""
78+
79+
adjoint_settings = """
80+
MATH_PROBLEM= DISCRETE_ADJOINT
81+
SCREEN_OUTPUT= TIME_ITER, CUR_TIME, INNER_ITER, ADJOINT_DISP_X, ADJOINT_DISP_Y, LINSOL, SENSITIVITY
82+
CONV_FIELD= ADJOINT_DISP_X, ADJOINT_DISP_Y
83+
FEA_ADVANCED_MODE= YES
84+
UNST_ADJOINT_ITER= 21
85+
ITER_AVERAGE_OBJ= 0
86+
SOLUTION_FILENAME= restart.dat
87+
"""
88+
89+
90+
def ApplyLoad(driver, marker_id, peak_load):
91+
"""
92+
Apply a load based on the coordinates and return the derivatives
93+
of the nodal forces with respect to the peak load.
94+
"""
95+
derivatives = []
96+
if marker_id < 0: return
97+
98+
marker_coords = driver.MarkerCoordinates(marker_id)
99+
l = 0.5
100+
dx = l / 16 # known from mesh settings in this case.
101+
for i_vertex in range(driver.GetNumberMarkerNodes(marker_id)):
102+
x = marker_coords(i_vertex, 0)
103+
nodal_force = (peak_load * x / l) * dx
104+
# Half load due to half dx on first and last node.
105+
if abs(x) < 1e-6 or abs(x - l) < 1e-6:
106+
nodal_force = nodal_force / 2
107+
driver.SetMarkerCustomFEALoad(marker_id, i_vertex, (0, nodal_force))
108+
derivatives.append(nodal_force / peak_load)
109+
110+
return derivatives
111+
112+
113+
def RunPrimal(density, peak_load):
114+
"""Runs the primal solver for a given density and peak load and returns the time average objective function."""
115+
comm = MPI.COMM_WORLD
116+
117+
with open('config_unsteady.cfg', 'w') as f:
118+
f.write(common_settings.replace('__DENSITY__', str(density)) + primal_settings)
119+
120+
# Initialize the primal driver of SU2, this includes solver preprocessing.
121+
try:
122+
driver = pysu2ad.CSinglezoneDriver('config_unsteady.cfg', 1, comm)
123+
except TypeError as exception:
124+
print('A TypeError occured in pysu2ad.CSinglezoneDriver : ', exception)
125+
raise
126+
127+
# Get the ID of the marker where the load is applied.
128+
all_marker_ids = driver.GetMarkerIndices()
129+
marker_name = 'y_minus'
130+
marker_id = all_marker_ids[marker_name] if marker_name in all_marker_ids else -1
131+
132+
# Apply a load based on the coordinates.
133+
ApplyLoad(driver, marker_id, peak_load)
134+
135+
# Solve.
136+
driver.StartSolver()
137+
138+
# Get the time average
139+
tavg_stress_penalty = driver.GetOutputValue('TAVG_STRESS_PENALTY')
140+
141+
# Finalize the solver and exit cleanly.
142+
driver.Finalize()
143+
144+
return tavg_stress_penalty
145+
146+
147+
def RunAdjoint(density, peak_load):
148+
"""Runs the adjoint solver and returns the sensitivity of the objective function to the peak load."""
149+
comm = MPI.COMM_WORLD
150+
151+
with open('config_unsteady_ad.cfg', 'w') as f:
152+
f.write(common_settings.replace('__DENSITY__', str(density)) + adjoint_settings)
153+
154+
# Initialize the adjoint driver of SU2, this includes solver preprocessing.
155+
try:
156+
driver = pysu2ad.CDiscAdjSinglezoneDriver('config_unsteady_ad.cfg', 1, comm)
157+
except TypeError as exception:
158+
print('A TypeError occured in pysu2ad.CDiscAdjSinglezoneDriver : ', exception)
159+
raise
160+
161+
# Get the ID of the marker where the load is applied.
162+
all_marker_ids = driver.GetMarkerIndices()
163+
marker_name = 'y_minus'
164+
marker_id = all_marker_ids[marker_name] if marker_name in all_marker_ids else -1
165+
166+
# Apply the same load that was used in the primal problem.
167+
derivatives = ApplyLoad(driver, marker_id, peak_load)
168+
169+
n_vertex = driver.GetNumberMarkerNodes(marker_id) if marker_id >= 0 else 0
170+
load_sens = 0.0
171+
172+
# Run the time loop in python to extract sensitivities at each step.
173+
for time_iter in range(driver.GetNumberTimeIter()):
174+
# Preprocess adjoint iteration (AD recording).
175+
driver.Preprocess(time_iter)
176+
177+
# Run one time iteration.
178+
driver.Run()
179+
driver.Postprocess()
180+
driver.Update()
181+
182+
# Accumulate sensitivies.
183+
for i_vertex in range(n_vertex):
184+
load_sens += derivatives[i_vertex] * driver.GetMarkerFEALoadSensitivity(marker_id, i_vertex)[1]
185+
186+
# Monitor the solver and output solution to file if required.
187+
driver.Monitor(time_iter)
188+
driver.Output(time_iter)
189+
190+
# Finalize the solver and exit cleanly.
191+
driver.Finalize()
192+
193+
return load_sens
194+
195+
196+
def main():
197+
# Run the primal with 2 loads to compute the sensitivity via finite differences.
198+
obj_pert_load = RunPrimal(1, 2.002)
199+
obj_pert_rho = RunPrimal(1.0001, 2)
200+
obj = RunPrimal(1, 2)
201+
sens_load_fd = (obj_pert_load - obj) / 0.002
202+
sens_rho_fd = (obj_pert_rho - obj) / 0.0001
203+
204+
sens_load = RunAdjoint(1, 2)
205+
# Read the density sensitivity from the special output file.
206+
with open('Results_Reverse_Adjoint.txt') as f:
207+
sens_rho = float(f.readlines()[-1].strip().split('\t')[-1])
208+
209+
print(" Finite Differences\tDiscrete Adjoint")
210+
print(f"Load {sens_load_fd}\t{sens_load}")
211+
print(f"Rho {sens_rho_fd}\t{sens_rho}")
212+
213+
assert abs(sens_load / sens_load_fd - 1) < 1e-4, "Error in load derivative."
214+
assert abs(sens_rho / sens_rho_fd - 1) < 1e-3, "Error in density derivative."
215+
216+
# Print results for the regression script to check.
217+
print(100, 100, sens_load_fd, sens_load, sens_rho_fd, sens_rho)
218+
219+
220+
if __name__ == '__main__':
221+
main()

TestCases/serial_regression_AD.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,19 @@ def main():
343343
test_list.append(pywrapper_FEA_AD_FlowLoad)
344344
pass_list.append(pywrapper_FEA_AD_FlowLoad.run_test())
345345

346+
# FEA unsteady AD Load Sensitivity
347+
pywrapper_Unst_FEA_AD = TestCase('pywrapper_Unst_FEA_AD')
348+
pywrapper_Unst_FEA_AD.cfg_dir = "py_wrapper/custom_load_fea"
349+
pywrapper_Unst_FEA_AD.cfg_file = "config.cfg"
350+
pywrapper_Unst_FEA_AD.test_iter = 100
351+
pywrapper_Unst_FEA_AD.test_vals = [0.2569256889872473, 0.256926360722213, 0.03166391031539373, 0.0316910814265723]
352+
pywrapper_Unst_FEA_AD.command = TestCase.Command(exec = "python", param = "run_ad.py")
353+
pywrapper_Unst_FEA_AD.timeout = 1600
354+
pywrapper_Unst_FEA_AD.tol = 0.00001
355+
pywrapper_Unst_FEA_AD.new_output = False
356+
test_list.append(pywrapper_Unst_FEA_AD)
357+
pass_list.append(pywrapper_Unst_FEA_AD.run_test())
358+
346359
# Flow AD Mesh Displacement Sensitivity
347360
pywrapper_CFD_AD_MeshDisp = TestCase('pywrapper_CFD_AD_MeshDisp')
348361
pywrapper_CFD_AD_MeshDisp.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens"

0 commit comments

Comments
 (0)