20
20
limitations under the License.
21
21
"""
22
22
23
- from __future__ import print_function
23
+ from __future__ import print_function , annotations
24
24
25
25
__all__ = ["RadiationInterface" ]
26
26
27
27
import numpy as np
28
28
import os
29
+ from typing import TYPE_CHECKING
29
30
from funtofem import TransferScheme
30
31
from ._solver_interface import SolverInterface
31
32
33
+ if TYPE_CHECKING :
34
+ from ..model .body import Body
35
+ from ..model .scenario import Scenario
36
+
32
37
33
38
class RadiationInterface (SolverInterface ):
34
39
"""
@@ -38,7 +43,7 @@ class RadiationInterface(SolverInterface):
38
43
39
44
"""
40
45
41
- def __init__ (self , comm , model , conv_hist = False ):
46
+ def __init__ (self , comm , model , conv_hist = False , complex_mode = False ):
42
47
"""
43
48
The instantiation of the thermal radiation interface class will populate the model
44
49
with the aerodynamic surface mesh, body.aero_X and body.aero_nnodes.
@@ -54,6 +59,7 @@ def __init__(self, comm, model, conv_hist=False):
54
59
"""
55
60
self .comm = comm
56
61
self .conv_hist = conv_hist
62
+ self .complex_mode = complex_mode
57
63
58
64
# setup forward and adjoint tolerances
59
65
super ().__init__ ()
@@ -68,6 +74,74 @@ def __init__(self, comm, model, conv_hist=False):
68
74
# Get the initial aerodynamic surface meshes
69
75
self .initialize (model .scenarios [0 ], model .bodies )
70
76
77
+ def set_variables (self , scenario , bodies ):
78
+ """
79
+ Set the design variables into the solver.
80
+ The scenario and bodies objects have dictionaries of :class:`~variable.Variable` objects.
81
+ The interface class should pick out which type of variables it needs and pass them into the solver
82
+
83
+ Parameters
84
+ ----------
85
+ scenario: :class:`~scenario.Scenario`
86
+ The current scenario
87
+ bodies: list of :class:`~body.Body` objects
88
+ The bodies in the model
89
+ """
90
+ pass
91
+
92
+ def set_functions (self , scenario , bodies ):
93
+ """
94
+ Set the function definitions into the solver.
95
+ The scenario has a list of function objects.
96
+
97
+ Parameters
98
+ ----------
99
+ scenario: :class:`~scenario.Scenario`
100
+ The current scenario
101
+ bodies: list of :class:`~body.Body` objects
102
+ The bodies in the model
103
+ """
104
+ pass
105
+
106
+ def get_functions (self , scenario , bodies ):
107
+ """
108
+ Put the function values from the solver in the value attribute of the scneario's functions.
109
+ The scenario has the list of function objects where the functions owned by this solver will be set.
110
+ You can evaluate the functions based on the name or based on the functions set during
111
+ :func:`~solver_interface.SolverInterface.set_functions`.
112
+ The solver is only responsible for returning the values of functions it owns.
113
+
114
+ Parameters
115
+ ----------
116
+ scenario: :class:`~scenario.Scenario`
117
+ The current scenario
118
+ bodies: list of :class:`~body.Body` objects
119
+ The bodies in the model
120
+ """
121
+ pass
122
+
123
+ def get_function_gradients (self , scenario , bodies ):
124
+ """
125
+ Get the derivatives of all the functions with respect to design variables associated with this solver.
126
+
127
+ Each solver sets the function gradients for its own variables into the function objects using either
128
+ ``function.set_gradient(var, value)`` or ``function.add_gradient(var, vaule)``. Note that before
129
+ this function is called, all gradient components are zeroed.
130
+
131
+ The derivatives are stored in a dictionary in each function class. As a result, the gradients are
132
+ stored in an unordered format. The gradients returned by ``model.get_function_gradients()`` are
133
+ flattened into a list of lists whose order is determined by the variable list stored in the model
134
+ class.
135
+
136
+ Parameters
137
+ ----------
138
+ scenario: :class:`~scenario.Scenario`
139
+ The current scenario
140
+ bodies: list of :class:`~body.Body` objects
141
+ The bodies in the model
142
+ """
143
+ pass
144
+
71
145
def initialize (self , scenario , bodies ):
72
146
"""
73
147
Initialize the thermal radiation interface and solver.
@@ -109,26 +183,7 @@ def initialize(self, scenario, bodies):
109
183
110
184
return 0
111
185
112
- def get_functions (self , scenario , bodies ):
113
- """
114
- Populate the scenario with the aerodynamic function values.
115
-
116
- Parameters
117
- ----------
118
- scenario: :class:`~scenario.Scenario`
119
- The scenario
120
- bodies: :class:`~body.Body`
121
- list of FUNtoFEM bodies
122
- """
123
- pass
124
- # for function in scenario.functions:
125
- # if function.analysis_type=='aerodynamic':
126
- # # the [6] index returns the value
127
- # if self.comm.Get_rank() == 0:
128
- # function.value = interface.design_pull_composite_func(function.id)[6]
129
- # function.value = self.comm.bcast(function.value,root=0)
130
-
131
- def iterate (self , scenario , bodies , step ):
186
+ def iterate (self , scenario : Scenario , bodies : list [Body ], step ):
132
187
"""
133
188
Forward iteration of thermal radiation.
134
189
@@ -156,19 +211,93 @@ def iterate(self, scenario, bodies, step):
156
211
aero_nnodes = body .get_num_aero_nodes ()
157
212
158
213
aero_temps = body .get_aero_temps (scenario , time_index = step )
214
+ print (f"aero_temps: { aero_temps } " )
159
215
heat_rad = self .calc_heat_flux (aero_temps , scenario )
160
216
161
217
heat_flux = body .get_aero_heat_flux (scenario , time_index = step )
162
218
heat_flux += heat_rad
163
219
164
220
return 0
165
221
222
+ def post (self , scenario , bodies ):
223
+ """
224
+ Perform any tasks the solver needs to do after the forward steps are complete, e.g., evaluate functions,
225
+ post-process, deallocate unneeded memory.
226
+
227
+ Parameters
228
+ ----------
229
+ scenario: :class:`~scenario.Scenario`
230
+ The current scenario
231
+ bodies: list of :class:`~body.Body` objects
232
+ The bodies in the model
233
+ """
234
+ pass
235
+
236
+ def initialize_adjoint (self , scenario , bodies ):
237
+ """
238
+ Perform any tasks the solver needs to do before taking adjoint steps
239
+
240
+ Parameters
241
+ ----------
242
+ scenario: :class:`~scenario.Scenario`
243
+ The current scenario
244
+ bodies: list of :class:`~body.Body` objects
245
+ The bodies in the model
246
+ """
247
+ return 0
248
+
249
+ def iterate_adjoint (self , scenario : Scenario , bodies : list [Body ], step ):
250
+ """
251
+ Adjoint iteration of thermal radiation.
252
+
253
+ Add contribution to aerodynamic temperature adjoint.
254
+ """
255
+
256
+ nfuncs = scenario .count_adjoint_functions ()
257
+ for ibody , body in enumerate (bodies , 1 ):
258
+ # Get the adjoint-Jacobian product for the heat flux
259
+ aero_flux_ajp = body .get_aero_heat_flux_ajp (scenario )
260
+ aero_nnodes = body .get_num_aero_nodes ()
261
+ aero_flux = body .get_aero_heat_flux (scenario , time_index = step )
262
+ aero_temps = body .get_aero_temps (scenario , time_index = step )
263
+
264
+ aero_temps_ajp = body .get_aero_temps_ajp (scenario )
265
+
266
+ if aero_flux_ajp is not None and aero_nnodes > 0 :
267
+ # Solve the aero heat flux computation adjoint
268
+ # dR/dhA^{T} * psi_R = - dQ/dhA^{T} * psi_Q = - aero_flux_ajp
269
+ psi_R = aero_flux_ajp
270
+
271
+ rad_heat_deriv = self .calc_heat_flux_deriv (aero_temps , scenario )
272
+
273
+ dtype = TransferScheme .dtype
274
+ lam = np .zeros ((aero_nnodes , nfuncs ), dtype = dtype )
275
+
276
+ for func in range (nfuncs ):
277
+ lam [:, func ] = psi_R [:, func ] * rad_heat_deriv [:]
278
+
279
+ if not self .complex_mode :
280
+ lam = lam .astype (np .double )
281
+
282
+ for func in range (nfuncs ):
283
+ print (f"aero_flux_ajp: { aero_flux_ajp [:, func ]} " )
284
+ print (f"lam: { lam [:, func ]} " )
285
+ aero_flux_ajp [:, func ] += lam [:, func ]
286
+
287
+ return
288
+
289
+ def post_adjoint (self , scenario , bodies ):
290
+ """
291
+ Any actions that need to be performed after completing the adjoint solve, e.g., evaluating gradients, deallocating memory, etc.
292
+ """
293
+ pass
294
+
166
295
def calc_heat_flux (self , temps , scenario = None ):
167
296
"""
168
297
Implementation of thermal radiation from a surface to a low-temperature environment.
169
298
Calculate the heat flux per area as a function of temperature.
170
299
171
- Paramters
300
+ Parameters
172
301
---------
173
302
temps:
174
303
Array of temperatures.
@@ -191,3 +320,27 @@ def calc_heat_flux(self, temps, scenario=None):
191
320
rad_heat [indx ] = - sigma_sb * emis * F_v * (temp_i ** 4 - T_v ** 4 )
192
321
193
322
return rad_heat
323
+
324
+ def calc_heat_flux_deriv (self , temps , scenario = None ):
325
+ """
326
+ Calculate the derivative of radiative heat flux residual dR/dtA
327
+
328
+ This forms a diagonal matrix, since the heat flux depends only on the temperature
329
+ at its corresponding node.
330
+ """
331
+ if scenario is None :
332
+ emis = 0.8
333
+ F_v = 1.0
334
+ T_v = 0.0
335
+ else :
336
+ emis = scenario .emis
337
+ F_v = scenario .F_v
338
+ T_v = scenario .T_v
339
+
340
+ sigma_sb = 5.670374419e-8
341
+ rad_heat_deriv = np .zeros_like (temps , dtype = TransferScheme .dtype )
342
+
343
+ for indx , temp_i in enumerate (temps ):
344
+ rad_heat_deriv [indx ] = - 4 * sigma_sb * emis * F_v * temp_i ** 3
345
+
346
+ return rad_heat_deriv
0 commit comments