Skip to content

Commit 0fc0d07

Browse files
authored
Merge pull request #2059 from su2code/py_wrapper_example
Add new example for adjoint fluid flow with custom mesh deformation, fix CGNS issue with compiler flags
2 parents e12db69 + 0911ef2 commit 0fc0d07

4 files changed

Lines changed: 392 additions & 63 deletions

File tree

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/wavy_wall"
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: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
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+
147+
with open('config.cfg', 'w') as f:
148+
f.write(common_settings.replace('__WIDTH__', str(channel_width)) + primal_settings)
149+
150+
# Initialize the corresponding driver of SU2, this includes solver preprocessing.
151+
try:
152+
driver = pysu2.CSinglezoneDriver('config.cfg', 1, comm)
153+
except TypeError as exception:
154+
print('A TypeError occured in pysu2.CSinglezoneDriver : ', exception)
155+
raise
156+
157+
marker_ids = GetMarkerIds(driver)
158+
159+
for marker_id in marker_ids:
160+
Deformation(0, deform_amplitude, marker_id, driver)
161+
162+
driver.StartSolver()
163+
avg_p_drop = driver.GetOutputValue('p_drop')
164+
165+
# Finalize the solver and exit cleanly
166+
driver.Finalize()
167+
168+
return avg_p_drop
169+
170+
171+
def RunAdjoint(channel_width, deform_amplitude):
172+
"""
173+
Runs the adjoint solver for a given amplitude of the deformation, and channel width.
174+
Returns the sensitivity of the objective function to the amplitude and width.
175+
"""
176+
comm = MPI.COMM_WORLD
177+
178+
with open('config_ad.cfg', 'w') as f:
179+
f.write(common_settings.replace('__WIDTH__', str(channel_width)) + adjoint_settings)
180+
181+
# Initialize the corresponding driver of SU2, this includes solver preprocessing.
182+
try:
183+
driver = pysu2.CDiscAdjSinglezoneDriver('config_ad.cfg', 1, comm)
184+
except TypeError as exception:
185+
print('A TypeError occured in pysu2.CDiscAdjSinglezoneDriver : ', exception)
186+
raise
187+
188+
marker_ids = GetMarkerIds(driver)
189+
190+
# Preprocess adjoint to load the primal solution.
191+
driver.Preprocess(0)
192+
193+
# Check the surface deformation is the expected from loading the primal results.
194+
for marker_id in marker_ids:
195+
Deformation(0, deform_amplitude, marker_id, driver, True)
196+
197+
driver.Run()
198+
driver.Postprocess()
199+
driver.Update()
200+
201+
# Accumulate the sensitivity to the deformation amplitude.
202+
sens = 0
203+
for marker_id in marker_ids:
204+
sens += SumSensitivity(0, marker_id, driver)
205+
206+
driver.Monitor(0)
207+
driver.Output(0)
208+
209+
size_sens = 0.0
210+
sensitivity = driver.Sensitivity(driver.GetSolverIndices()['ADJ.FLOW'])
211+
coords = driver.InitialCoordinates()
212+
213+
for i_node in range(driver.GetNumberNodes() - driver.GetNumberHaloNodes()):
214+
y = coords.Get(i_node, 1)
215+
dy_dw = y / channel_width
216+
size_sens += dy_dw * sensitivity(i_node, 1)
217+
218+
# Finalize the solver and exit cleanly
219+
driver.Finalize()
220+
221+
return comm.allreduce(size_sens), comm.allreduce(sens)
222+
223+
224+
def Phase(t):
225+
# 10 cycles per second
226+
return 10 * 2 * np.pi * t
227+
228+
229+
def DeformationBottom(x, phase):
230+
# Deformation between 0.1 and 0.4.
231+
if x < 0.1 or x > 0.4:
232+
return 0
233+
t = (x - 0.1) / 0.3 * 2 * np.pi
234+
ampl = (1 - np.cos(t)) / 2
235+
236+
# Shape parameter, makes the peaks narrower or wider.
237+
N = 1
238+
239+
p = N * (t - phase)
240+
denom = N * 2 * np.pi
241+
n = round(p / denom)
242+
p -= n * denom
243+
wave = (1 - np.cos(min(max(0, p + np.pi), 2 * np.pi))) / 2
244+
245+
return ampl * wave
246+
247+
248+
def DeformationTop(x, phase):
249+
# Top deforms downward with a phase shift.
250+
return -DeformationBottom(x, phase + np.pi)
251+
252+
253+
def Deformation(t, amplitude, marker_id, driver, check = False):
254+
"""
255+
Applies the deformation to the marker or checks that the current mesh
256+
coordinates (loaded by the adjoint solver) match the expected for the time.
257+
"""
258+
if marker_id < 0:
259+
return
260+
ini_coords = driver.MarkerInitialCoordinates(marker_id)
261+
coords = driver.MarkerCoordinates(marker_id)
262+
phase = Phase(t)
263+
264+
for i_vertex in range(ini_coords.Shape()[0]):
265+
x, y = ini_coords.Get(i_vertex)
266+
if (y > 1e-3):
267+
dy = DeformationTop(x, phase)
268+
else:
269+
dy = DeformationBottom(x, phase)
270+
if not check:
271+
driver.SetMarkerCustomDisplacement(marker_id, i_vertex, (0.0, amplitude * dy))
272+
else:
273+
assert abs((coords(i_vertex, 1) - y) / amplitude - dy) < 1e-9
274+
275+
276+
def SumSensitivity(t, marker_id, driver):
277+
"""Integrates the sensitivity with respect to the amplitude of the deformation."""
278+
if marker_id < 0:
279+
return 0.0
280+
ini_coords = driver.MarkerInitialCoordinates(marker_id)
281+
phase = Phase(t)
282+
sens = 0
283+
284+
for i_vertex in range(ini_coords.Shape()[0]):
285+
i_point = driver.GetMarkerNode(marker_id, i_vertex)
286+
if not driver.GetNodeDomain(i_point):
287+
continue
288+
289+
x, y = ini_coords.Get(i_vertex)
290+
if (y > 1e-3):
291+
dy = DeformationTop(x, phase)
292+
else:
293+
dy = DeformationBottom(x, phase)
294+
sens_x, sens_y = driver.GetMarkerDisplacementSensitivity(marker_id, i_vertex)
295+
sens += sens_y * dy
296+
297+
return sens
298+
299+
300+
def main():
301+
comm = MPI.COMM_WORLD
302+
rank = comm.Get_rank()
303+
304+
p_drop = RunPrimal(0.055, 0.045)
305+
w_sens, a_sens = RunAdjoint(0.055, 0.045)
306+
307+
# Print results for the regression script to check.
308+
# The derivates were verified with finite difference steps of 5e-5.
309+
if rank == 0:
310+
print("\n------------------------------ Begin Solver -----------------------------\n")
311+
print(100, 100, p_drop / 1000, w_sens / 100000, a_sens / 100000, '\n')
312+
313+
314+
if __name__ == '__main__':
315+
main()

0 commit comments

Comments
 (0)