|
| 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