Skip to content

Commit bba0c8a

Browse files
committed
add new example, fix #2058
1 parent 6891b44 commit bba0c8a

5 files changed

Lines changed: 395 additions & 64 deletions

File tree

.github/workflows/regression.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ jobs:
128128
uses: docker://ghcr.io/su2code/su2/test-su2:230225-2136
129129
with:
130130
# -t <Tutorials-branch> -c <Testcases-branch>
131-
args: -b ${{github.ref}} -t develop -c develop -s ${{matrix.testscript}}
131+
args: -b ${{github.ref}} -t develop -c py_wrapper_example -s ${{matrix.testscript}}
132132
- name: Cleanup
133133
uses: docker://ghcr.io/su2code/su2/test-su2:230225-2136
134134
with:

TestCases/parallel_regression_AD.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,9 @@ def main():
377377
################################################
378378
### Gradient check (dot) for flamelet solver ###
379379
################################################
380-
380+
381381
# 2D planar laminar premixed flame on isothermal burner (restart)
382-
# This test restarts on the output of test discadj_flamelet_ch4_hx and
382+
# This test restarts on the output of test discadj_flamelet_ch4_hx and
383383
# will only pass if test discadj_flamelet_ch4_hx passes.
384384
dot_flamelet_ch4_hx = TestCase('dot_flamelet_ch4_hx')
385385
dot_flamelet_ch4_hx.cfg_dir = "flamelet/02_laminar_premixed_ch4_flame_hx_ad"
@@ -395,7 +395,7 @@ def main():
395395
test_list.append(dot_flamelet_ch4_hx)
396396

397397
# 2D planar laminar premixed flame on isothermal burner with conjugate heat transfer (restart)
398-
# This test restarts on the output of test discadj_flamelet_ch4_cht and
398+
# This test restarts on the output of test discadj_flamelet_ch4_cht and
399399
# will only pass if test discadj_flamelet_ch4_cht passes.
400400
dot_flamelet_ch4_cht = TestCase('dot_flamelet_ch4_cht')
401401
dot_flamelet_ch4_cht.cfg_dir = "flamelet/04_laminar_premixed_ch4_flame_cht_ad"
@@ -518,6 +518,19 @@ def main():
518518
test_list.append(pywrapper_CFD_AD_MeshDisp)
519519
pass_list.append(pywrapper_CFD_AD_MeshDisp.run_test())
520520

521+
# Flow AD Mesh Displacement and Initial Coordinates Sensitivity
522+
pywrapper_wavy_wall_steady = TestCase('pywrapper_wavy_wall_steady')
523+
pywrapper_wavy_wall_steady.cfg_dir = "py_wrapper/disc_adj_flow/mesh_disp_sens"
524+
pywrapper_wavy_wall_steady.cfg_file = "run_steady.py"
525+
pywrapper_wavy_wall_steady.test_iter = 100
526+
pywrapper_wavy_wall_steady.test_vals = [-1.360044, 2.580709, -2.892473]
527+
pywrapper_wavy_wall_steady.command = TestCase.Command("mpirun -n 2", "python", "run_steady.py")
528+
pywrapper_wavy_wall_steady.timeout = 1600
529+
pywrapper_wavy_wall_steady.tol = 0.00001
530+
pywrapper_wavy_wall_steady.new_output = False
531+
test_list.append(pywrapper_wavy_wall_steady)
532+
pass_list.append(pywrapper_wavy_wall_steady.run_test())
533+
521534
####################################################################
522535
### Unsteady Disc. adj. compressible RANS restart optimization ###
523536
####################################################################
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
#!/usr/bin/env python
2+
3+
## \file run.py
4+
# \brief Channel with wave-like motion on walls (steady state version).
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 as pysu2
28+
from mpi4py import MPI
29+
import numpy as np
30+
31+
common_settings = """
32+
SOLVER= NAVIER_STOKES
33+
34+
FLUID_MODEL= IDEAL_GAS
35+
GAMMA_VALUE= 1.4
36+
GAS_CONSTANT= 287.87
37+
38+
MACH_NUMBER= 0.034
39+
INIT_OPTION= TD_CONDITIONS
40+
FREESTREAM_OPTION= TEMPERATURE_FS
41+
FREESTREAM_TEMPERATURE= 299.99
42+
FREESTREAM_PRESSURE= 98750
43+
REYNOLDS_NUMBER= 2000
44+
REYNOLDS_LENGTH= 0.055
45+
46+
VISCOSITY_MODEL= CONSTANT_VISCOSITY
47+
MU_CONSTANT= 0.0005
48+
49+
MARKER_HEATFLUX= ( y_minus, 0, y_plus, 0 )
50+
MARKER_INLET= ( x_minus, 300, 100000, 1, 0, 0 )
51+
MARKER_FAR= ( x_plus )
52+
53+
MARKER_MONITORING= ( y_minus, y_plus )
54+
MARKER_PLOTTING= ( x_minus, x_plus, y_minus, y_plus )
55+
56+
DEFORM_MESH= YES
57+
MARKER_DEFORM_MESH= ( y_minus, y_plus )
58+
DEFORM_CONSOLE_OUTPUT= YES
59+
DEFORM_LINEAR_SOLVER= CONJUGATE_GRADIENT
60+
DEFORM_LINEAR_SOLVER_PREC= ILU
61+
DEFORM_LINEAR_SOLVER_ERROR= 1e-12
62+
DEFORM_LINEAR_SOLVER_ITER= 1000
63+
DEFORM_STIFFNESS_TYPE= CONSTANT_STIFFNESS
64+
DEFORM_POISSONS_RATIO= 0.35
65+
66+
CONV_NUM_METHOD_FLOW= ROE
67+
ENTROPY_FIX_COEFF= 1e-5
68+
MUSCL_FLOW= YES
69+
SLOPE_LIMITER_FLOW= NONE
70+
NUM_METHOD_GRAD= GREEN_GAUSS
71+
72+
MESH_FORMAT= RECTANGLE
73+
MESH_BOX_SIZE= ( 193, 33, 0 )
74+
MESH_BOX_LENGTH= ( 0.75, __WIDTH__, 0 )
75+
76+
CUSTOM_OUTPUTS= 'p_tot : Macro{PRESSURE + 0.5 * DENSITY * (pow(VELOCITY_X, 2) + pow(VELOCITY_Y, 2))};\
77+
p_in : MassFlowAvg{$p_tot}[x_minus];\
78+
p_out : MassFlowAvg{$p_tot}[x_plus];\
79+
p_drop : Function{p_out - p_in}'
80+
81+
CUSTOM_OBJFUNC= 'p_drop'
82+
OBJECTIVE_FUNCTION= CUSTOM_OBJFUNC
83+
84+
SOLUTION_FILENAME= restart.dat
85+
OUTPUT_FILES= RESTART, PARAVIEW_MULTIBLOCK
86+
OUTPUT_WRT_FREQ= 9999
87+
SCREEN_WRT_FREQ_INNER= 10
88+
89+
INNER_ITER= 3000
90+
CONV_STARTITER= 10
91+
"""
92+
93+
primal_settings = """
94+
MATH_PROBLEM= DIRECT
95+
RESTART_SOL= YES
96+
97+
CFL_NUMBER= 1000
98+
LINEAR_SOLVER= FGMRES
99+
LINEAR_SOLVER_PREC= ILU
100+
LINEAR_SOLVER_ERROR= 0.1
101+
LINEAR_SOLVER_ITER= 20
102+
103+
SCREEN_OUTPUT= INNER_ITER, RMS_RES, LINSOL, p_drop
104+
HISTORY_OUTPUT= ITER, RMS_RES, CUSTOM
105+
VOLUME_OUTPUT= PRIMITIVE, VORTEX_IDENTIFICATION
106+
107+
CONV_RESIDUAL_MINVAL= -13
108+
CONV_FIELD= RMS_DENSITY
109+
"""
110+
111+
adjoint_settings = """
112+
MATH_PROBLEM= DISCRETE_ADJOINT
113+
114+
CFL_NUMBER= 1000
115+
DISCADJ_LIN_SOLVER= FGMRES
116+
DISCADJ_LIN_PREC= ILU
117+
LINEAR_SOLVER_ERROR= 0.001
118+
LINEAR_SOLVER_ITER= 20
119+
120+
SCREEN_OUTPUT= INNER_ITER, RMS_RES, LINSOL
121+
HISTORY_OUTPUT= ITER, RMS_RES, SENSITIVITY
122+
123+
QUASI_NEWTON_NUM_SAMPLES= 20
124+
125+
CONV_RESIDUAL_MINVAL= -8
126+
CONV_FIELD= REL_RMS_ADJ_DENSITY
127+
"""
128+
129+
130+
def GetMarkerIds(driver):
131+
"""Get the ID of the markers where the deformation is applied."""
132+
all_marker_ids = driver.GetMarkerIndices()
133+
marker_names = ['y_minus', 'y_plus']
134+
marker_ids = []
135+
for name in marker_names:
136+
marker_ids.append(all_marker_ids[name] if name in all_marker_ids else -1)
137+
return marker_ids
138+
139+
140+
def RunPrimal(channel_width, deform_amplitude):
141+
"""
142+
Runs the primal solver for a given amplitude of the deformation.
143+
Returns the objective function.
144+
"""
145+
comm = MPI.COMM_WORLD
146+
rank = comm.Get_rank()
147+
148+
with open('config.cfg', 'w') as f:
149+
f.write(common_settings.replace('__WIDTH__', str(channel_width)) + primal_settings)
150+
151+
# Initialize the corresponding driver of SU2, this includes solver preprocessing.
152+
try:
153+
driver = pysu2.CSinglezoneDriver('config.cfg', 1, comm)
154+
except TypeError as exception:
155+
print('A TypeError occured in pysu2.CSinglezoneDriver : ', exception)
156+
raise
157+
158+
marker_ids = GetMarkerIds(driver)
159+
160+
for marker_id in marker_ids:
161+
Deformation(0, deform_amplitude, marker_id, driver)
162+
163+
driver.StartSolver()
164+
avg_p_drop = driver.GetOutputValue('p_drop')
165+
166+
# Finalize the solver and exit cleanly
167+
driver.Finalize()
168+
169+
return avg_p_drop
170+
171+
172+
def RunAdjoint(channel_width, deform_amplitude):
173+
"""
174+
Runs the adjoint solver for a given amplitude of the deformation, and channel width.
175+
Returns the sensitivity of the objective function to the amplitude and width.
176+
"""
177+
comm = MPI.COMM_WORLD
178+
rank = comm.Get_rank()
179+
180+
with open('config_ad.cfg', 'w') as f:
181+
f.write(common_settings.replace('__WIDTH__', str(channel_width)) + adjoint_settings)
182+
183+
# Initialize the corresponding driver of SU2, this includes solver preprocessing.
184+
try:
185+
driver = pysu2.CDiscAdjSinglezoneDriver('config_ad.cfg', 1, comm)
186+
except TypeError as exception:
187+
print('A TypeError occured in pysu2.CDiscAdjSinglezoneDriver : ', exception)
188+
raise
189+
190+
marker_ids = GetMarkerIds(driver)
191+
192+
# Preprocess adjoint to load the primal solution.
193+
driver.Preprocess(0)
194+
195+
# Check the surface deformation is the expected from loading the primal results.
196+
for marker_id in marker_ids:
197+
Deformation(0, deform_amplitude, marker_id, driver, True)
198+
199+
driver.Run()
200+
driver.Postprocess()
201+
driver.Update()
202+
203+
# Accumulate the sensitivity to the deformation amplitude.
204+
sens = 0
205+
for marker_id in marker_ids:
206+
sens += SumSensitivity(0, marker_id, driver)
207+
208+
driver.Monitor(0)
209+
driver.Output(0)
210+
211+
size_sens = 0.0
212+
sensitivity = driver.Sensitivity(driver.GetSolverIndices()['ADJ.FLOW'])
213+
coords = driver.InitialCoordinates()
214+
215+
for i_node in range(driver.GetNumberNodes() - driver.GetNumberHaloNodes()):
216+
y = coords.Get(i_node, 1)
217+
dy_dw = y / channel_width
218+
size_sens += dy_dw * sensitivity(i_node, 1)
219+
220+
# Finalize the solver and exit cleanly
221+
driver.Finalize()
222+
223+
return comm.allreduce(size_sens), comm.allreduce(sens)
224+
225+
226+
def Phase(t):
227+
# 10 cycles per second
228+
return 10 * 2 * np.pi * t
229+
230+
231+
def DeformationBottom(x, phase):
232+
# Deformation between 0.1 and 0.4.
233+
if x < 0.1 or x > 0.4:
234+
return 0
235+
t = (x - 0.1) / 0.3 * 2 * np.pi
236+
ampl = (1 - np.cos(t)) / 2
237+
238+
# Shape parameter, makes the peaks narrower or wider.
239+
N = 1
240+
241+
p = N * (t - phase)
242+
denom = N * 2 * np.pi
243+
n = round(p / denom)
244+
p -= n * denom
245+
wave = (1 - np.cos(min(max(0, p + np.pi), 2 * np.pi))) / 2
246+
247+
return ampl * wave
248+
249+
250+
def DeformationTop(x, phase):
251+
# Top deforms downward with a phase shift.
252+
return -DeformationBottom(x, phase + np.pi)
253+
254+
255+
def Deformation(t, amplitude, marker_id, driver, check = False):
256+
"""
257+
Applies the deformation to the marker or checks that the current mesh
258+
coordinates (loaded by the adjoint solver) match the expected for the time.
259+
"""
260+
if marker_id < 0:
261+
return
262+
ini_coords = driver.MarkerInitialCoordinates(marker_id)
263+
coords = driver.MarkerCoordinates(marker_id)
264+
phase = Phase(t)
265+
266+
for i_vertex in range(ini_coords.Shape()[0]):
267+
x, y = ini_coords.Get(i_vertex)
268+
if (y > 1e-3):
269+
dy = DeformationTop(x, phase)
270+
else:
271+
dy = DeformationBottom(x, phase)
272+
if not check:
273+
driver.SetMarkerCustomDisplacement(marker_id, i_vertex, (0.0, amplitude * dy))
274+
else:
275+
assert abs((coords(i_vertex, 1) - y) / amplitude - dy) < 1e-9
276+
277+
278+
def SumSensitivity(t, marker_id, driver):
279+
"""Integrates the sensitivity with respect to the amplitude of the deformation."""
280+
if marker_id < 0:
281+
return
282+
ini_coords = driver.MarkerInitialCoordinates(marker_id)
283+
phase = Phase(t)
284+
sens = 0
285+
286+
for i_vertex in range(ini_coords.Shape()[0]):
287+
i_point = driver.GetMarkerNode(marker_id, i_vertex)
288+
if not driver.GetNodeDomain(i_point):
289+
continue
290+
291+
x, y = ini_coords.Get(i_vertex)
292+
if (y > 1e-3):
293+
dy = DeformationTop(x, phase)
294+
else:
295+
dy = DeformationBottom(x, phase)
296+
sens_x, sens_y = driver.GetMarkerDisplacementSensitivity(marker_id, i_vertex)
297+
sens += sens_y * dy
298+
299+
return sens
300+
301+
302+
def main():
303+
comm = MPI.COMM_WORLD
304+
rank = comm.Get_rank()
305+
306+
p_drop = RunPrimal(0.055, 0.045)
307+
w_sens, a_sens = RunAdjoint(0.055, 0.045)
308+
309+
# Print results for the regression script to check.
310+
# The derivates were verified with finite difference steps of 5e-5.
311+
if rank == 0:
312+
print("\n------------------------------ Begin Solver -----------------------------\n")
313+
print(100, 100, p_drop / 1000, w_sens / 100000, a_sens / 100000, '\n')
314+
315+
316+
if __name__ == '__main__':
317+
main()

0 commit comments

Comments
 (0)