mphys_dafoam.py
Go to the documentation of this file.
1 import sys
2 from openmdao.api import Group, ImplicitComponent, ExplicitComponent, AnalysisError
3 import openmdao.api as om
4 from dafoam import PYDAFOAM
5 from idwarp import USMesh
6 from mphys.builder import Builder
7 import petsc4py
8 from petsc4py import PETSc
9 import numpy as np
10 from mpi4py import MPI
11 from mphys.utils.directory_utils import cd
12 
13 petsc4py.init(sys.argv)
14 
15 
16 class DAFoamBuilder(Builder):
17  """
18  DAFoam builder called from runScript.py
19  """
20 
21  def __init__(
22  self,
23  options, # DAFoam options
24  mesh_options=None, # IDWarp options
25  scenario="aerodynamic", # scenario type to configure the groups
26  run_directory="", # the directory to run this case in, default is the current directory
27  ):
28 
29  # options dictionary for DAFoam
30  self.options = options
31 
32  # mesh warping option. If no design variables are mesh related,
33  # e.g., topology optimization, set it to None.
34  self.mesh_options = mesh_options
35  # flag to determine if the mesh warping component is added
36  # in the nonlinear solver loop (e.g. for aerostructural)
37  # or as a preprocessing step like the surface mesh coordinates
38  # (e.g. for aeropropulsive). This will avoid doing extra work
39  # for mesh deformation when the volume mesh does not change
40  # during nonlinear iterations
41  self.warp_in_solver = False
42  # flag for aerostructural coupling variables
43  self.struct_coupling = False
44  # thermal coupling defaults to false
45  self.thermal_coupling = False
46 
47  # the directory to run this case in, default is the current directory
48  self.run_directory = run_directory
49 
50  # depending on the scenario we are building for, we adjust a few internal parameters:
51  if scenario.lower() == "aerodynamic":
52  # default
53  pass
54  elif scenario.lower() == "aerostructural":
55  # volume mesh warping needs to be inside the coupling loop for aerostructural
56  self.warp_in_solver = True
57  self.struct_coupling = True
58  elif scenario.lower() == "aerothermal":
59  # volume mesh warping needs to be inside the coupling loop for aerothermal
60  self.thermal_coupling = True
61  else:
62  raise AnalysisError(
63  "scenario %s not valid! Options: aerodynamic, aerostructural, and aerothermal" % scenario
64  )
65 
66  # api level method for all builders
67  def initialize(self, comm):
68 
69  self.comm = comm
70 
71  with cd(self.run_directory):
72  # initialize the PYDAFOAM class, defined in pyDAFoam.py
73  self.DASolver = PYDAFOAM(options=self.options, comm=comm)
74  if self.mesh_options is not None:
75  # always set the mesh
76  mesh = USMesh(options=self.mesh_options, comm=comm)
77  self.DASolver.setMesh(mesh) # add the design surface family group
78  self.DASolver.printFamilyList()
79 
80  def get_solver(self):
81  # this method is only used by the RLT transfer scheme
82  return self.DASolver
83 
84  # api level method for all builders
85  def get_coupling_group_subsystem(self, scenario_name=None):
86  dafoam_group = DAFoamGroup(
87  solver=self.DASolver,
88  use_warper=self.warp_in_solver,
89  struct_coupling=self.struct_coupling,
90  thermal_coupling=self.thermal_coupling,
91  run_directory=self.run_directory,
92  )
93  return dafoam_group
94 
95  def get_mesh_coordinate_subsystem(self, scenario_name=None):
96 
97  # just return the component that outputs the surface mesh.
98  return DAFoamMesh(solver=self.DASolver)
99 
100  def get_pre_coupling_subsystem(self, scenario_name=None):
101 
102  if self.mesh_options is None:
103  return None
104  else:
105  return DAFoamPrecouplingGroup(
106  solver=self.DASolver, warp_in_solver=self.warp_in_solver, thermal_coupling=self.thermal_coupling
107  )
108 
109  def get_post_coupling_subsystem(self, scenario_name=None):
110  return DAFoamPostcouplingGroup(solver=self.DASolver)
111 
112  def get_number_of_nodes(self, groupName=None):
113  # Get number of aerodynamic nodes
114  if groupName is None:
115  groupName = self.DASolver.designSurfacesGroup
116  nodes = int(self.DASolver.getSurfaceCoordinates(groupName=groupName).size / 3)
117 
118  return nodes
119 
120  def get_ndof(self):
121  # The number of degrees of freedom used at each output location.
122  return -1
123 
124 
125 class DAFoamGroup(Group):
126  """
127  DAFoam solver group
128  """
129 
130  def initialize(self):
131  self.options.declare("solver", recordable=False)
132  self.options.declare("struct_coupling", default=False)
133  self.options.declare("use_warper", default=True)
134  self.options.declare("thermal_coupling", default=False)
135  self.options.declare("run_directory", default="")
136 
137  def setup(self):
138 
139  self.DASolver = self.options["solver"]
140  self.struct_coupling = self.options["struct_coupling"]
141  self.use_warper = self.options["use_warper"]
142  self.thermal_coupling = self.options["thermal_coupling"]
143  self.run_directory = self.options["run_directory"]
144  self.discipline = self.DASolver.getOption("discipline")
145 
146  if self.use_warper:
147 
148  # if we dont have geo_disp, we also need to promote the x_a as x_a0 from the deformer component
149  self.add_subsystem(
150  "deformer",
151  DAFoamWarper(
152  solver=self.DASolver,
153  ),
154  promotes_inputs=["x_%s" % self.discipline],
155  promotes_outputs=["%s_vol_coords" % self.discipline],
156  )
157 
158  # add the solver implicit component
159  self.add_subsystem(
160  "solver",
161  DAFoamSolver(solver=self.DASolver, run_directory=self.run_directory),
162  promotes_inputs=["*"],
163  promotes_outputs=["%s_states" % self.discipline],
164  )
165 
166  if self.struct_coupling:
167  self.add_subsystem(
168  "force",
169  DAFoamForces(solver=self.DASolver),
170  promotes_inputs=["%s_vol_coords" % self.discipline, "%s_states" % self.discipline],
171  promotes_outputs=["f_aero"],
172  )
173 
174  if self.thermal_coupling:
175  self.add_subsystem(
176  "get_%s" % self.discipline,
177  DAFoamThermal(solver=self.DASolver),
178  promotes_inputs=["*"],
179  promotes_outputs=["*"],
180  )
181 
182 
184  """
185  Pre-coupling group that configures any components that happen before the solver and post-processor.
186  """
187 
188  def initialize(self):
189  self.options.declare("solver", default=None, recordable=False)
190  self.options.declare("warp_in_solver", default=None, recordable=False)
191  self.options.declare("thermal_coupling", default=None, recordable=False)
192 
193  def setup(self):
194  self.DASolver = self.options["solver"]
195  self.warp_in_solver = self.options["warp_in_solver"]
196  self.thermal_coupling = self.options["thermal_coupling"]
197  self.discipline = self.DASolver.getOption("discipline")
198 
199  # Return the warper only if it is not in the solver
200  if not self.warp_in_solver:
201  self.add_subsystem(
202  "warper",
203  DAFoamWarper(solver=self.DASolver),
204  promotes_inputs=["x_%s" % self.discipline],
205  promotes_outputs=["%s_vol_coords" % self.discipline],
206  )
207 
208  if self.thermal_coupling:
209  self.add_subsystem(
210  "%s_xs" % self.discipline,
211  DAFoamFaceCoords(solver=self.DASolver),
212  promotes_inputs=["*"],
213  promotes_outputs=["*"],
214  )
215 
216 
218  """
219  Post-coupling group that configures any components that happen in the post-processor.
220  """
221 
222  def initialize(self):
223  self.options.declare("solver", default=None, recordable=False)
224 
225  def setup(self):
226  self.DASolver = self.options["solver"]
227 
228  # Add Functionals
229  self.add_subsystem("functionals", DAFoamFunctions(solver=self.DASolver), promotes=["*"])
230 
231 
232 class DAFoamSolver(ImplicitComponent):
233  """
234  OpenMDAO component that wraps the DAFoam flow and adjoint solvers
235  """
236 
237  def initialize(self):
238  self.options.declare("solver", recordable=False)
239  self.options.declare("run_directory", default="")
240 
241  def setup(self):
242  # NOTE: the setup function will be called everytime a new scenario is created.
243 
244  self.DASolver = self.options["solver"]
245  DASolver = self.DASolver
246 
247  self.run_directory = self.options["run_directory"]
248 
249  self.discipline = self.DASolver.getOption("discipline")
250 
252 
253  # pointer to the DVGeo object
254  self.DVGeo = None
255 
256  # pointer to the DVCon object
257  self.DVCon = None
258 
259  # setup some names
260  self.stateName = "%s_states" % self.discipline
261  self.residualName = "%s_residuals" % self.discipline
262  self.volCoordName = "%s_vol_coords" % self.discipline
263 
264  # initialize the dRdWT matrix-free matrix in DASolver
265  DASolver.solverAD.initializedRdWTMatrixFree()
266 
267  # create the adjoint vector
268  self.localAdjSize = DASolver.getNLocalAdjointStates()
269  self.psi = PETSc.Vec().create(comm=PETSc.COMM_WORLD)
270  self.psi.setSizes((self.localAdjSize, PETSc.DECIDE), bsize=1)
271  self.psi.setFromOptions()
272  self.psi.zeroEntries()
273 
274  # if true, we need to compute the coloring
275  if DASolver.getOption("adjEqnSolMethod") == "fixedPoint":
276  self.runColoring = False
277  else:
278  self.runColoring = True
279 
280  # setup input and output for the solver
281  # we need to add states as outputs for all cases
282  local_state_size = DASolver.getNLocalAdjointStates()
283  self.add_output(self.stateName, distributed=True, shape=local_state_size, tags=["mphys_coupling"])
284 
285  # now loop over the solver input keys to determine which other variables we need to add as inputs
286  inputDict = DASolver.getOption("inputInfo")
287  for inputName in list(inputDict.keys()):
288  # this input is attached to solver comp
289  if "solver" in inputDict[inputName]["components"]:
290  inputType = inputDict[inputName]["type"]
291  inputSize = DASolver.solver.getInputSize(inputName, inputType)
292  inputDistributed = DASolver.solver.getInputDistributed(inputName, inputType)
293  self.add_input(inputName, distributed=inputDistributed, shape=inputSize, tags=["mphys_coupling"])
294 
295  def add_dvgeo(self, DVGeo):
296  self.DVGeo = DVGeo
297 
298  def add_dvcon(self, DVCon):
299  self.DVCon = DVCon
300 
301  # calculate the residual
302  # def apply_nonlinear(self, inputs, outputs, residuals):
303  # DASolver = self.DASolver
304  # # NOTE: we do not pass the states from inputs to the OF layer.
305  # # this can cause potential convergence issue because the initial states
306  # # in the inputs are set to all ones. So passing this all-ones states
307  # # into the OF layer may diverge the primal solver. Here we can always
308  # # use the states from the OF layer to compute the residuals.
309  # # DASolver.setStates(outputs["%s_states" % self.discipline])
310  # # get flow residuals from DASolver
311  # residuals[self.stateName] = DASolver.getResiduals()
312 
313  # solve the flow
314  def solve_nonlinear(self, inputs, outputs):
315 
316  with cd(self.run_directory):
317 
318  DASolver = self.DASolver
319 
320  # set the solver input, including mesh, boundary etc.
321  DASolver.set_solver_input(inputs, self.DVGeo)
322 
323  # before running the primal, we need to check if the mesh
324  # quality is good
325  meshOK = DASolver.solver.checkMesh()
326 
327  # if the mesh is not OK, do not run the primal
328  if meshOK != 1:
329  DASolver.solver.writeFailedMesh()
330  raise AnalysisError("Mesh quality error!")
331  return
332 
333  # call the primal
334  DASolver()
335 
336  # if the primal fails, do not set states and return
337  if DASolver.primalFail != 0:
338  raise AnalysisError("Primal solution failed!")
339  return
340 
341  # we can use step-average state variables, this can be useful when the
342  # primal has LCO
343  if DASolver.getOption("useMeanStates"):
344  DASolver.solver.meanStatesToStates()
345 
346  # after solving the primal, we need to print its residual info
347  if DASolver.getOption("useAD")["mode"] == "forward":
348  DASolver.solverAD.calcPrimalResidualStatistics("print")
349  else:
350  DASolver.solver.calcPrimalResidualStatistics("print")
351 
352  # assign the computed flow states to outputs
353  states = DASolver.getStates()
354  outputs[self.stateName] = states
355 
356  # set states
357  DASolver.setStates(states)
358 
359  # We also need to just calculate the residual for the AD mode to initialize vars like URes
360  # We do not print the residual for AD, though
361  DASolver.solverAD.calcPrimalResidualStatistics("calc")
362 
363  def linearize(self, inputs, outputs, residuals):
364  # NOTE: we do not do any computation in this function, just print some information
365 
366  self.DASolver.setStates(outputs[self.stateName])
367 
368  def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode):
369  # compute the matrix vector products for states and volume mesh coordinates
370  # i.e., dRdWT*psi, dRdXvT*psi
371 
372  # we do not support forward mode
373  if mode == "fwd":
374  om.issue_warning(
375  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
376  prefix="",
377  stacklevel=2,
378  category=om.OpenMDAOWarning,
379  )
380  return
381 
382  DASolver = self.DASolver
383 
384  # assign the states in outputs to the OpenFOAM flow fields
385  # NOTE: this is not quite necessary because setStates have been called before in the solve_nonlinear
386  # here we call it just be on the safe side
387  DASolver.setStates(outputs[self.stateName])
388 
389  if self.stateName in d_residuals:
390 
391  # get the reverse mode AD seed from d_residuals
392  seed = d_residuals[self.stateName]
393 
394  # this computes [dRdW]^T*Psi using reverse mode AD
395  if self.stateName in d_outputs:
396  product = np.zeros(self.localAdjSize)
397  jacInput = outputs[self.stateName]
398  DASolver.solverAD.calcJacTVecProduct(
399  self.stateName,
400  "stateVar",
401  jacInput,
402  self.residualName,
403  "residual",
404  seed,
405  product,
406  )
407  d_outputs[self.stateName] += product
408 
409  # loop over all inputs keys and compute the matrix-vector products accordingly
410  inputDict = DASolver.getOption("inputInfo")
411  for inputName in list(inputs.keys()):
412  inputType = inputDict[inputName]["type"]
413  jacInput = inputs[inputName]
414  product = np.zeros_like(jacInput)
415  DASolver.solverAD.calcJacTVecProduct(
416  inputName,
417  inputType,
418  jacInput,
419  self.residualName,
420  "residual",
421  seed,
422  product,
423  )
424  d_inputs[inputName] += product
425 
426  def solve_linear(self, d_outputs, d_residuals, mode):
427  # solve the adjoint equation [dRdW]^T * Psi = dFdW
428 
429  # we do not support forward mode
430  if mode == "fwd":
431  om.issue_warning(
432  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
433  prefix="",
434  stacklevel=2,
435  category=om.OpenMDAOWarning,
436  )
437  return
438 
439  with cd(self.run_directory):
440 
441  DASolver = self.DASolver
442 
443  adjEqnSolMethod = DASolver.getOption("adjEqnSolMethod")
444 
445  # right hand side array from d_outputs
446  dFdWArray = d_outputs[self.stateName]
447  # convert the array to vector
448  dFdW = DASolver.array2Vec(dFdWArray)
449 
450  # run coloring
451  if self.DASolver.getOption("adjUseColoring") and self.runColoring:
452  self.DASolver.solver.runColoring()
453  self.runColoring = False
454 
455  if adjEqnSolMethod == "Krylov":
456  # solve the adjoint equation using the Krylov method
457 
458  # if writeMinorIterations=True, we rename the solution in pyDAFoam.py. So we don't recompute the PC
459  if DASolver.getOption("writeMinorIterations"):
460  if DASolver.dRdWTPC is None or DASolver.ksp is None:
461  DASolver.dRdWTPC = PETSc.Mat().create(self.comm)
462  DASolver.solver.calcdRdWT(1, DASolver.dRdWTPC)
463  DASolver.ksp = PETSc.KSP().create(self.comm)
464  DASolver.solverAD.createMLRKSPMatrixFree(DASolver.dRdWTPC, DASolver.ksp)
465  # otherwise, we need to recompute the PC mat based on adjPCLag
466  else:
467  # NOTE: this function will be called multiple times (one time for one obj func) in each opt iteration
468  # so we don't want to print the total info and recompute PC for each obj, we need to use renamed
469  # to check if a recompute is needed. In other words, we only recompute the PC for the first obj func
470  # adjoint solution
471 
472  solutionTime, renamed = DASolver.renameSolution(self.solution_counter)
473 
474  if renamed:
475  # write the deformed FFD for post-processing
476  if DASolver.getOption("writeDeformedFFDs"):
477  if self.DVGeo is None:
478  raise RuntimeError(
479  "writeDeformedFFDs is set but no DVGeo object found! Please call add_dvgeo in the run script!"
480  )
481  else:
482  self.DVGeo.writeTecplot("deformedFFDs_%d.dat" % self.solution_counter)
483  self.DVGeo.writePlot3d("deformedFFDs_%d.xyz" % self.solution_counter)
484 
485  # write the deformed constraints for post-processing
486  if DASolver.getOption("writeDeformedConstraints"):
487  if self.DVCon is None:
488  raise RuntimeError(
489  "writeDeformedConstraints is set but no DVCon object found! Please call add_dvcon in the run script!"
490  )
491  else:
492  self.DVCon.writeTecplot("deformedConstraints_%d.dat" % self.solution_counter)
493 
494  # print the solution counter
495  if self.comm.rank == 0:
496  print("Driver total derivatives for iteration: %d" % self.solution_counter, flush=True)
497  print("---------------------------------------------", flush=True)
498  self.solution_counter += 1
499 
500  # compute the preconditioner matrix for the adjoint linear equation solution
501  # and initialize the ksp object. We reinitialize them every adjPCLag
502  adjPCLag = DASolver.getOption("adjPCLag")
503  if DASolver.dRdWTPC is None or DASolver.ksp is None or (self.solution_counter - 1) % adjPCLag == 0:
504  if renamed:
505  # calculate the PC mat
506  if DASolver.dRdWTPC is not None:
507  DASolver.dRdWTPC.destroy()
508  DASolver.dRdWTPC = PETSc.Mat().create(self.comm)
509  DASolver.solver.calcdRdWT(1, DASolver.dRdWTPC)
510  # reset the KSP
511  if DASolver.ksp is not None:
512  DASolver.ksp.destroy()
513  DASolver.ksp = PETSc.KSP().create(self.comm)
514  DASolver.solverAD.createMLRKSPMatrixFree(DASolver.dRdWTPC, DASolver.ksp)
515 
516  # if useNonZeroInitGuess is False, we will manually reset self.psi to zero
517  # this is important because we need the correct psi to update the KSP tolerance
518  # in the next line
519  if not self.DASolver.getOption("adjEqnOption")["useNonZeroInitGuess"]:
520  self.psi.set(0)
521  else:
522  # if useNonZeroInitGuess is True, we will assign the OM's psi to self.psi
523  self.psi = DASolver.array2Vec(d_residuals[self.stateName].copy())
524 
525  if self.DASolver.getOption("adjEqnOption")["dynAdjustTol"]:
526  # if we want to dynamically adjust the tolerance, call this function. This is mostly used
527  # in the block Gauss-Seidel method in two discipline coupling
528  # update the KSP tolerances the coupled adjoint before solving
529  self._updateKSPTolerances(self.psi, dFdW, DASolver.ksp)
530 
531  # actually solving the adjoint linear equation using Petsc
532  fail = DASolver.solverAD.solveLinearEqn(DASolver.ksp, dFdW, self.psi)
533 
534  elif adjEqnSolMethod == "fixedPoint":
535  solutionTime, renamed = DASolver.renameSolution(self.solution_counter)
536  if renamed:
537  # write the deformed FFD for post-processing
538  # DASolver.writeDeformedFFDs(self.solution_counter)
539  # print the solution counter
540  if self.comm.rank == 0:
541  print("Driver total derivatives for iteration: %d" % self.solution_counter, flush=True)
542  print("---------------------------------------------", flush=True)
543  self.solution_counter += 1
544  # solve the adjoint equation using the fixed-point adjoint approach
545  fail = DASolver.solverAD.runFPAdj(dFdW, self.psi)
546  else:
547  raise RuntimeError("adjEqnSolMethod=%s not valid! Options are: Krylov or fixedPoint" % adjEqnSolMethod)
548 
549  # optionally write the adjoint vector as OpenFOAM field format for post-processing
550  psi_array = DASolver.vec2Array(self.psi)
551  solTimeFloat = (self.solution_counter - 1) / 1e4
552  DASolver.writeAdjointFields("function", solTimeFloat, psi_array)
553 
554  # convert the solution vector to array and assign it to d_residuals
555  d_residuals[self.stateName] = DASolver.vec2Array(self.psi)
556 
557  # if the adjoint solution fail, we return analysisError and let the optimizer handle it
558  if fail:
559  raise AnalysisError("Adjoint solution failed!")
560 
561  def _updateKSPTolerances(self, psi, dFdW, ksp):
562  # Here we need to manually update the KSP tolerances because the default
563  # relative tolerance will always want to converge the adjoint to a fixed
564  # tolerance during the LINGS adjoint solution. However, what we want is
565  # to converge just a few orders of magnitude. Here we need to bypass the
566  # rTol in Petsc and manually calculate the aTol.
567 
568  DASolver = self.DASolver
569  # calculate the initial residual for the adjoint before solving
570  rArray = np.zeros(self.localAdjSize)
571  jacInput = DASolver.getStates()
572  seed = DASolver.vec2Array(psi)
573  DASolver.solverAD.calcJacTVecProduct(
574  self.stateName,
575  "stateVar",
576  jacInput,
577  self.residualName,
578  "residual",
579  seed,
580  rArray,
581  )
582  rVec = DASolver.array2Vec(rArray)
583  rVec.axpy(-1.0, dFdW)
584  # NOTE, this is the norm for the global vec
585  rNorm = rVec.norm()
586 
587  # read the rTol and aTol from DAOption
588  rTol0 = self.DASolver.getOption("adjEqnOption")["gmresRelTol"]
589  aTol0 = self.DASolver.getOption("adjEqnOption")["gmresAbsTol"]
590  # calculate the new absolute tolerance that gives you rTol residual drop
591  aTolNew = rNorm * rTol0
592  # if aTolNew is smaller than aTol0, assign aTol0 to aTolNew
593  if aTolNew < aTol0:
594  aTolNew = aTol0
595  # assign the atolNew and distable rTol
596  ksp.setTolerances(rtol=0.0, atol=aTolNew, divtol=None, max_it=None)
597 
598 
599 class DAFoamMesh(ExplicitComponent):
600  """
601  Component to get the partitioned initial surface mesh coordinates
602  """
603 
604  def initialize(self):
605  self.options.declare("solver", recordable=False)
606 
607  def setup(self):
608 
609  self.DASolver = self.options["solver"]
610 
611  self.discipline = self.DASolver.getOption("discipline")
612 
613  # design surface coordinates
614  self.x_a0 = self.DASolver.getSurfaceCoordinates(self.DASolver.designSurfacesGroup).flatten(order="C")
615 
616  # add output
617  coord_size = self.x_a0.size
618  self.add_output(
619  "x_%s0" % self.discipline,
620  distributed=True,
621  shape=coord_size,
622  desc="initial aerodynamic surface node coordinates",
623  tags=["mphys_coordinates"],
624  )
625 
627  self.add_input(
628  "x_%s0_points" % self.discipline,
629  distributed=True,
630  shape_by_conn=True,
631  desc="aerodynamic surface with geom changes",
632  )
633 
634  # return the promoted name and coordinates
635  return "x_%s0_points" % self.discipline, self.x_a0
636 
638  return self.x_a0
639 
640  def mphys_get_triangulated_surface(self, groupName=None):
641  # this is a list of lists of 3 points
642  # p0, v1, v2
643 
644  return self.DASolver.getTriangulatedMeshSurface()
645 
646  def mphys_get_surface_size(self, groupName):
647  return self.DASolver._getSurfaceSize(groupName)
648 
649  def compute(self, inputs, outputs):
650  # just assign the surface mesh coordinates
651  if "x_%s0_points" % self.discipline in inputs:
652  outputs["x_%s0" % self.discipline] = inputs["x_%s0_points" % self.discipline]
653  else:
654  outputs["x_%s0" % self.discipline] = self.x_a0
655 
656  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
657  # we do not support forward mode AD
658  if mode == "fwd":
659  om.issue_warning(
660  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
661  prefix="",
662  stacklevel=2,
663  category=om.OpenMDAOWarning,
664  )
665  return
666 
667  # just assign the matrix-vector product
668  if "x_%s0_points" % self.discipline in d_inputs:
669  d_inputs["x_%s0_points" % self.discipline] += d_outputs["x_%s0" % self.discipline]
670 
671 
672 class DAFoamFunctions(ExplicitComponent):
673  """
674  DAFoam objective and constraint functions component
675  """
676 
677  def initialize(self):
678  self.options.declare("solver", recordable=False)
679 
680  # a list that contains all function names, e.g., CD, CL
681  self.funcs = None
682 
683  def setup(self):
684 
685  self.DASolver = self.options["solver"]
686  DASolver = self.DASolver
687 
688  self.discipline = self.DASolver.getOption("discipline")
689 
690  # setup some names
691  self.stateName = "%s_states" % self.discipline
692  self.volCoordName = "%s_vol_coords" % self.discipline
693 
694  # setup input and output for the function
695  # we need to add states for all cases
696  self.add_input(self.stateName, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
697 
698  # now loop over the solver input keys to determine which other variables we need to add as inputs
699  inputDict = DASolver.getOption("inputInfo")
700  for inputName in list(inputDict.keys()):
701  # this input is attached to function comp
702  if "function" in inputDict[inputName]["components"]:
703  inputType = inputDict[inputName]["type"]
704  inputSize = DASolver.solver.getInputSize(inputName, inputType)
705  inputDistributed = DASolver.solver.getInputDistributed(inputName, inputType)
706  self.add_input(inputName, distributed=inputDistributed, shape=inputSize, tags=["mphys_coupling"])
707 
708  # add outputs
709  functions = self.DASolver.getOption("function")
710  # loop over the functions here and create the output
711  for f_name in list(functions.keys()):
712  self.add_output(f_name, distributed=False, shape=1, units=None)
713 
714  # get the objective function from DASolver
715  def compute(self, inputs, outputs):
716 
717  # TODO. We should have added a call to assign inputs to the OF layer.
718  # This will not cause a problem for now because DAFoamFunctions is usually
719  # called right after the primal run.
720 
721  DASolver = self.DASolver
722 
723  DASolver.setStates(inputs[self.stateName])
724 
725  funcs = {}
726  DASolver.evalFunctions(funcs)
727  for f_name in list(outputs.keys()):
728  outputs[f_name] = funcs[f_name]
729 
730  # compute the partial derivatives of functions
731  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
732 
733  DASolver = self.DASolver
734 
735  # TODO. this may not be needed
736  DASolver.setStates(inputs[self.stateName])
737 
738  # we do not support forward mode AD
739  if mode == "fwd":
740  om.issue_warning(
741  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
742  prefix="",
743  stacklevel=2,
744  category=om.OpenMDAOWarning,
745  )
746  return
747 
748  # loop over all d_inputs keys and compute the partials accordingly
749  inputDict = DASolver.getOption("inputInfo")
750  for functionName in list(d_outputs.keys()):
751 
752  seed = d_outputs[functionName]
753 
754  # if the seed is zero, do not compute
755  if abs(seed) < 1e-12:
756  continue
757 
758  for inputName in list(d_inputs.keys()):
759  # compute dFdW * seed
760  if inputName == self.stateName:
761  jacInput = inputs[self.stateName]
762  product = np.zeros_like(jacInput)
763  DASolver.solverAD.calcJacTVecProduct(
764  self.stateName,
765  "stateVar",
766  jacInput,
767  functionName,
768  "function",
769  seed,
770  product,
771  )
772  d_inputs[self.stateName] += product
773  else:
774  inputType = inputDict[inputName]["type"]
775  jacInput = inputs[inputName]
776  product = np.zeros_like(jacInput)
777  DASolver.solverAD.calcJacTVecProduct(
778  inputName,
779  inputType,
780  jacInput,
781  functionName,
782  "function",
783  seed,
784  product,
785  )
786  d_inputs[inputName] += product
787 
788 
789 class DAFoamWarper(ExplicitComponent):
790  """
791  OpenMDAO component that wraps the warping.
792  """
793 
794  def initialize(self):
795  self.options.declare("solver", recordable=False)
796 
797  def setup(self):
798 
799  self.DASolver = self.options["solver"]
800  DASolver = self.DASolver
801 
802  self.discipline = self.DASolver.getOption("discipline")
803 
804  # state inputs and outputs
805  local_volume_coord_size = DASolver.mesh.getSolverGrid().size
806 
807  self.add_input("x_%s" % self.discipline, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
808  self.add_output(
809  "%s_vol_coords" % self.discipline, distributed=True, shape=local_volume_coord_size, tags=["mphys_coupling"]
810  )
811 
812  def compute(self, inputs, outputs):
813  # given the new surface mesh coordinates, compute the new volume mesh coordinates
814  # the mesh warping will be called in getSolverGrid()
815  DASolver = self.DASolver
816 
817  x_a = inputs["x_%s" % self.discipline].reshape((-1, 3))
818  DASolver.setSurfaceCoordinates(x_a, DASolver.designSurfacesGroup)
819  DASolver.mesh.warpMesh()
820  solverGrid = DASolver.mesh.getSolverGrid()
821  outputs["%s_vol_coords" % self.discipline] = solverGrid
822 
823  # compute the mesh warping products in IDWarp
824  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
825 
826  # we do not support forward mode AD
827  if mode == "fwd":
828  om.issue_warning(
829  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
830  prefix="",
831  stacklevel=2,
832  category=om.OpenMDAOWarning,
833  )
834  return
835 
836  # compute dXv/dXs such that we can propagate the partials (e.g., dF/dXv) to Xs
837  # then the partial will be further propagated to XFFD in pyGeo
838  if "%s_vol_coords" % self.discipline in d_outputs:
839  if "x_%s" % self.discipline in d_inputs:
840  dxV = d_outputs["%s_vol_coords" % self.discipline]
841  self.DASolver.mesh.warpDeriv(dxV)
842  dxS = self.DASolver.mesh.getdXs()
843  dxS = self.DASolver.mapVector(dxS, self.DASolver.allWallsGroup, self.DASolver.designSurfacesGroup)
844  d_inputs["x_%s" % self.discipline] += dxS.flatten()
845 
846 
847 class DAFoamThermal(ExplicitComponent):
848  """
849  OpenMDAO component that wraps conjugate heat transfer integration
850 
851  """
852 
853  def initialize(self):
854  self.options.declare("solver", recordable=False)
855 
856  def setup(self):
857 
858  self.DASolver = self.options["solver"]
859  DASolver = self.DASolver
860 
861  self.discipline = self.DASolver.getOption("discipline")
862 
863  self.stateName = "%s_states" % self.discipline
864  self.volCoordName = "%s_vol_coords" % self.discipline
865 
866  self.add_input(self.volCoordName, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
867  self.add_input(self.stateName, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
868 
869  # now loop over the solver input keys to determine which other variables we need to add as inputs
870  outputDict = DASolver.getOption("outputInfo")
871  for outputName in list(outputDict.keys()):
872  # this input is attached to the DAFoamThermal comp
873  if "thermalCoupling" in outputDict[outputName]["components"]:
874  self.outputName = outputName
875  self.outputType = outputDict[outputName]["type"]
876  self.outputSize = DASolver.solver.getOutputSize(outputName, self.outputType)
877  outputDistributed = DASolver.solver.getOutputDistributed(outputName, self.outputType)
878  self.add_output(
879  outputName, distributed=outputDistributed, shape=self.outputSize, tags=["mphys_coupling"]
880  )
881  break
882 
883  def compute(self, inputs, outputs):
884 
885  self.DASolver.setStates(inputs[self.stateName])
886  self.DASolver.setVolCoords(inputs[self.volCoordName])
887 
888  thermal = np.zeros(self.outputSize)
889 
890  self.DASolver.solver.calcOutput(self.outputName, self.outputType, thermal)
891 
892  outputs[self.outputName] = thermal
893 
894  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
895 
896  if mode == "fwd":
897  om.issue_warning(
898  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
899  prefix="",
900  stacklevel=2,
901  category=om.OpenMDAOWarning,
902  )
903  return
904 
905  DASolver = self.DASolver
906 
907  for outputName in list(d_outputs.keys()):
908  seeds = d_outputs[outputName]
909 
910  if self.stateName in d_inputs:
911  jacInput = inputs[self.stateName]
912  product = np.zeros_like(jacInput)
913  DASolver.solverAD.calcJacTVecProduct(
914  self.stateName,
915  "stateVar",
916  jacInput,
917  outputName,
918  "thermalCouplingOutput",
919  seeds,
920  product,
921  )
922  d_inputs[self.stateName] += product
923 
924  if self.volCoordName in d_inputs:
925  jacInput = inputs[self.volCoordName]
926  product = np.zeros_like(jacInput)
927  DASolver.solverAD.calcJacTVecProduct(
928  self.volCoordName,
929  "volCoord",
930  jacInput,
931  outputName,
932  "thermalCouplingOutput",
933  seeds,
934  product,
935  )
936  d_inputs[self.volCoordName] += product
937 
938 
939 class DAFoamFaceCoords(ExplicitComponent):
940  """
941  Calculate coupling surface coordinates based on volume coordinates
942 
943  """
944 
945  def initialize(self):
946  self.options.declare("solver", recordable=False)
947 
948  def setup(self):
949 
950  self.DASolver = self.options["solver"]
951  self.discipline = self.DASolver.getOption("discipline")
952  self.volCoordName = "%s_vol_coords" % self.discipline
953  self.surfCoordName = "x_%s_surface0" % self.discipline
954 
955  DASolver = self.DASolver
956 
957  self.add_input(self.volCoordName, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
958 
959  # now loop over the solver input keys to determine which other variables we need to add as inputs
960  self.nSurfCoords = None
961  outputDict = DASolver.getOption("outputInfo")
962  for outputName in list(outputDict.keys()):
963  # this input is attached to the DAFoamThermal comp
964  if "thermalCoupling" in outputDict[outputName]["components"]:
965  outputType = outputDict[outputName]["type"]
966  outputSize = DASolver.solver.getOutputSize(outputName, outputType)
967  # NOTE: here x_surface0 is the surface coordinate, which is 3 times the number of faces
968  self.nSurfCoords = outputSize * 3
969  self.add_output(self.surfCoordName, distributed=True, shape=self.nSurfCoords, tags=["mphys_coupling"])
970  break
971 
972  if self.nSurfCoords is None:
973  raise AnalysisError("no thermalCoupling output found!")
974 
975  def compute(self, inputs, outputs):
976 
977  volCoords = inputs[self.volCoordName]
978  surfCoords = np.zeros(self.nSurfCoords)
979  self.DASolver.solver.calcCouplingFaceCoords(volCoords, surfCoords)
980 
981  outputs[self.surfCoordName] = surfCoords
982 
983  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
984  # there is no need to compute the jacvec_product because FUN2FEM assumes the surfCoords will not updated during optimization
985  # so it will set a zero seed for this anyway
986  pass
987 
988 
989 class DAFoamForces(ExplicitComponent):
990  """
991  OpenMDAO component that wraps force integration
992 
993  """
994 
995  def initialize(self):
996  self.options.declare("solver", recordable=False)
997 
998  def setup(self):
999 
1000  self.DASolver = self.options["solver"]
1001 
1002  self.discipline = self.DASolver.getOption("discipline")
1003 
1004  self.stateName = "%s_states" % self.discipline
1005  self.volCoordName = "%s_vol_coords" % self.discipline
1006 
1007  self.add_input(self.volCoordName, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
1008  self.add_input(self.stateName, distributed=True, shape_by_conn=True, tags=["mphys_coupling"])
1009 
1010  # now loop over the solver input keys to determine which other variables we need to add as inputs
1011  outputDict = self.DASolver.getOption("outputInfo")
1012  for outputName in list(outputDict.keys()):
1013  # this input is attached to the DAFoamThermal comp
1014  if "forceCoupling" in outputDict[outputName]["components"]:
1015  self.outputName = outputName
1016  self.outputType = outputDict[outputName]["type"]
1017  outputSize = self.DASolver.solver.getOutputSize(self.outputName, self.outputType)
1018  self.add_output("f_aero", distributed=True, shape=outputSize, tags=["mphys_coupling"])
1019  break
1020 
1021  def compute(self, inputs, outputs):
1022 
1023  self.DASolver.setStates(inputs[self.stateName])
1024  self.DASolver.setVolCoords(inputs[self.volCoordName])
1025 
1026  forces = np.zeros_like(outputs["f_aero"])
1027 
1028  self.DASolver.solver.calcOutput(self.outputName, self.outputType, forces)
1029 
1030  outputs["f_aero"] = forces
1031 
1032  # print out the total forces. They shoud be consistent with the primal's print out
1033  forcesV = forces.reshape((-1, 3))
1034  fXSum = np.sum(forcesV[:, 0])
1035  fYSum = np.sum(forcesV[:, 1])
1036  fZSum = np.sum(forcesV[:, 2])
1037  fXTot = self.comm.allreduce(fXSum, op=MPI.SUM)
1038  fYTot = self.comm.allreduce(fYSum, op=MPI.SUM)
1039  fZTot = self.comm.allreduce(fZSum, op=MPI.SUM)
1040 
1041  if self.comm.rank == 0:
1042  print("Total force:", flush=True)
1043  print("Fx = %f" % fXTot, flush=True)
1044  print("Fy = %f" % fYTot, flush=True)
1045  print("Fz = %f" % fZTot, flush=True)
1046 
1047  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
1048 
1049  DASolver = self.DASolver
1050 
1051  if mode == "fwd":
1052  om.issue_warning(
1053  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
1054  prefix="",
1055  stacklevel=2,
1056  category=om.OpenMDAOWarning,
1057  )
1058  return
1059 
1060  if "f_aero" in d_outputs:
1061  seeds = d_outputs["f_aero"]
1062 
1063  if self.stateName in d_inputs:
1064  jacInput = inputs[self.stateName]
1065  product = np.zeros_like(jacInput)
1066  DASolver.solverAD.calcJacTVecProduct(
1067  self.stateName,
1068  "stateVar",
1069  jacInput,
1070  self.outputName,
1071  "forceCouplingOutput",
1072  seeds,
1073  product,
1074  )
1075  d_inputs[self.stateName] += product
1076 
1077  if self.volCoordName in d_inputs:
1078  jacInput = inputs[self.volCoordName]
1079  product = np.zeros_like(jacInput)
1080  DASolver.solverAD.calcJacTVecProduct(
1081  self.volCoordName,
1082  "volCoord",
1083  jacInput,
1084  self.outputName,
1085  "forceCouplingOutput",
1086  seeds,
1087  product,
1088  )
1089  d_inputs[self.volCoordName] += product
1090 
1091 
1092 class OptFuncs(object):
1093  """
1094  Some utility functions
1095  """
1096 
1097  def __init__(self, daOptions, om_prob):
1098  """
1099  daOptions: dict or list
1100  The daOptions dict from runScript.py. Support more than two dicts
1101 
1102  om_prob:
1103  The om.Problem() object
1104  """
1105 
1106  self.daOptions = daOptions
1107  self.om_prob = om_prob
1108  self.comm = MPI.COMM_WORLD
1109 
1111  self,
1112  constraints,
1113  designVars,
1114  targets,
1115  constraintsComp=None,
1116  designVarsComp=None,
1117  epsFD=None,
1118  maxIter=10,
1119  tol=1e-4,
1120  maxNewtonStep=None,
1121  ):
1122  """
1123  Find the design variables that meet the prescribed constraints. This can be used to get a
1124  feasible design to start the optimization. For example, finding the angle of attack and
1125  tail rotation angle that give the target lift and pitching moment. The sizes of cons and
1126  designvars have to be the same.
1127  NOTE: we use the Newton method to find the feasible design.
1128  """
1129 
1130  if self.comm.rank == 0:
1131  print("Finding a feasible design using the Newton method. ")
1132  print("Constraints: ", constraints)
1133  print("Design Vars: ", designVars)
1134  print("Target: ", targets)
1135 
1136  if len(constraints) != len(designVars):
1137  raise RuntimeError("Sizes of the constraints and designVars lists need to be the same! ")
1138 
1139  size = len(constraints)
1140 
1141  # if the component is empty, set it to 0
1142  if constraintsComp is None:
1143  constraintsComp = size * [0]
1144  if designVarsComp is None:
1145  designVarsComp = size * [0]
1146  # if the FD step size is None, set it to 1e-3
1147  if epsFD is None:
1148  epsFD = size * [1e-3]
1149  # if the max Newton step is None, set it to a very large value
1150  if maxNewtonStep is None:
1151  maxNewtonStep = size * [1e16]
1152 
1153  # main Newton loop
1154  for n in range(maxIter):
1155 
1156  # Newton Jacobian
1157  jacMat = np.zeros((size, size))
1158 
1159  # run the primal for the reference dvs
1160  self.om_prob.run_model()
1161 
1162  # get the reference design vars and constraints values
1163  dv0 = np.zeros(size)
1164  for i in range(size):
1165  dvName = designVars[i]
1166  comp = designVarsComp[i]
1167  val = self.om_prob.get_val(dvName)
1168  dv0[i] = val[comp]
1169  con0 = np.zeros(size)
1170  for i in range(size):
1171  conName = constraints[i]
1172  comp = constraintsComp[i]
1173  val = self.om_prob.get_val(conName)
1174  con0[i] = val[comp]
1175 
1176  # calculate the residual. Constraints - Targets
1177  res = con0 - targets
1178 
1179  # compute the residual norm
1180  norm = np.linalg.norm(res / targets)
1181 
1182  if self.comm.rank == 0:
1183  print("FindFeasibleDesign Iter: ", n, flush=True)
1184  print("DesignVars: ", dv0, flush=True)
1185  print("Constraints: ", con0, flush=True)
1186  print("Residual Norm: ", norm, flush=True)
1187 
1188  # break the loop if residual is already smaller than the tolerance
1189  if norm < tol:
1190  if self.comm.rank == 0:
1191  print("FindFeasibleDesign Converged! ", flush=True)
1192  break
1193 
1194  # perturb design variables and compute the Jacobian matrix
1195  for i in range(size):
1196  dvName = designVars[i]
1197  comp = designVarsComp[i]
1198  # perturb +step
1199  dvP = dv0[i] + epsFD[i]
1200  self.om_prob.set_val(dvName, dvP, indices=comp)
1201  # run the primal
1202  self.om_prob.run_model()
1203  # reset the perturbation
1204  self.om_prob.set_val(dvName, dv0[i], indices=comp)
1205 
1206  # get the perturb constraints and compute the Jacobian
1207  for j in range(size):
1208  conName = constraints[j]
1209  comp = constraintsComp[j]
1210  val = self.om_prob.get_val(conName)
1211  conP = val[comp]
1212 
1213  deriv = (conP - con0[j]) / epsFD[i]
1214  jacMat[j][i] = deriv
1215 
1216  # calculate the deltaDV using the Newton method
1217  deltaDV = -np.linalg.inv(jacMat).dot(res)
1218 
1219  # we can bound the delta change to ensure a more robust Newton solver.
1220  for i in range(size):
1221  if abs(deltaDV[i]) > abs(maxNewtonStep[i]):
1222  if deltaDV[i] > 0:
1223  deltaDV[i] = abs(maxNewtonStep[i])
1224  else:
1225  deltaDV[i] = -abs(maxNewtonStep[i])
1226 
1227  # update the dv
1228  dv1 = dv0 + deltaDV
1229  for i in range(size):
1230  dvName = designVars[i]
1231  comp = designVarsComp[i]
1232  self.om_prob.set_val(dvName, dv1[i], indices=comp)
1233 
1234 
1236 
1237  def initialize(self):
1238  self.options.declare("solver_options")
1239  self.options.declare("mesh_options", default=None)
1240  self.options.declare("run_directory", default="")
1241 
1242  def setup(self):
1243  self.run_directory = self.options["run_directory"]
1244  self.solver_options = self.options["solver_options"]
1245  self.mesh_options = self.options["mesh_options"]
1246 
1247  with cd(self.run_directory):
1248  # initialize the PYDAFOAM class, defined in pyDAFoam.py
1249  self.DASolver = PYDAFOAM(options=self.solver_options, comm=self.comm)
1250  if self.mesh_options is not None:
1251  # always set the mesh
1252  mesh = USMesh(options=self.mesh_options, comm=self.comm)
1253  self.DASolver.setMesh(mesh) # add the design surface family group
1254  self.DASolver.printFamilyList()
1255 
1256  self.x_a0 = self.DASolver.getSurfaceCoordinates(self.DASolver.designSurfacesGroup).flatten(order="C")
1257 
1258  # if we have volume coords as the input, add the warper comp here
1259  inputDict = self.DASolver.getOption("inputInfo")
1260  for inputName in list(inputDict.keys()):
1261  # this input is attached to solver comp
1262  if "solver" in inputDict[inputName]["components"]:
1263  inputType = inputDict[inputName]["type"]
1264  if inputType == "volCoord":
1265  self.add_subsystem("warper", DAFoamWarper(solver=self.DASolver), promotes=["*"])
1266  break
1267 
1268  # add the solver comp
1269  self.add_subsystem("solver", DAFoamSolverUnsteady(solver=self.DASolver), promotes=["*"])
1270 
1271  def get_surface_mesh(self):
1272  return self.x_a0
1273 
1274 
1275 class DAFoamSolverUnsteady(ExplicitComponent):
1276 
1277  def initialize(self):
1278  self.options.declare("solver")
1279  self.options.declare("run_directory", default="")
1280 
1281  def setup(self):
1282  self.DVGeo = None
1283  self.DASolver = self.options["solver"]
1284  self.run_directory = self.options["run_directory"]
1285 
1286  DASolver = self.DASolver
1287 
1288  self.dRdWTPC = None
1289 
1290  self.adjEqnSolMethod = DASolver.getOption("adjEqnSolMethod")
1291  if self.adjEqnSolMethod not in ["Krylov", "fixedPoint"]:
1292  raise AnalysisError("adjEqnSolMethod is not valid")
1293 
1294  inputDict = DASolver.getOption("inputInfo")
1295  for inputName in list(inputDict.keys()):
1296  # this input is attached to solver comp
1297  if "solver" in inputDict[inputName]["components"]:
1298  inputType = inputDict[inputName]["type"]
1299  inputSize = DASolver.solver.getInputSize(inputName, inputType)
1300  inputDistributed = DASolver.solver.getInputDistributed(inputName, inputType)
1301  self.add_input(inputName, distributed=inputDistributed, shape=inputSize)
1302 
1303  # here we define a set of unsteady component output, they can be a linear combination
1304  # of the functions defined in DAOption->function
1305  self.unsteadyCompOutput = DASolver.getOption("unsteadyCompOutput")
1306  if len(self.unsteadyCompOutput) == 0:
1307  raise AnalysisError("unsteadyCompOutput is not defined for unsteady cases")
1308 
1309  functions = DASolver.getOption("function")
1310  for outputName in list(self.unsteadyCompOutput.keys()):
1311  # add the output
1312  self.add_output(outputName, distributed=0, shape=1)
1313  # also verify if the function names defined in the unsteadyCompOutput subdict
1314  # is found in DAOption->function
1315  for funcName in self.unsteadyCompOutput[outputName]:
1316  if funcName not in functions:
1317  raise AnalysisError("%f is not found in DAOption-function" % funcName)
1318 
1319  def add_dvgeo(self, DVGeo):
1320  self.DVGeo = DVGeo
1321 
1322  def compute(self, inputs, outputs):
1323 
1324  with cd(self.run_directory):
1325 
1326  DASolver = self.DASolver
1327 
1328  # if readZeroFields, we need to read in the states from the 0 folder every time
1329  # we start the primal here we read in all time levels. If readZeroFields is not set,
1330  # we will use the latest flow fields (from a previous primal call) as the init conditions
1331  readZeroFields = DASolver.getOption("unsteadyAdjoint")["readZeroFields"]
1332  if readZeroFields:
1333  DASolver.solver.setTime(0.0, 0)
1334  deltaT = DASolver.solver.getDeltaT()
1335  DASolver.readStateVars(0.0, deltaT)
1336 
1337  # set the solver inputs.
1338  # NOTE: we need to set input after we read the zero fields for forward mode
1339  DASolver.set_solver_input(inputs, self.DVGeo)
1340  # if dyamic mesh is used, we need to deform the mesh points and save them to disk
1341  DASolver.deformDynamicMesh()
1342 
1343  # before running the primal, we need to check if the mesh
1344  # quality is good
1345  meshOK = DASolver.solver.checkMesh()
1346 
1347  # solve the flow with the current design variable
1348  # if the mesh is not OK, do not run the primal
1349  if meshOK:
1350  # solve the primal
1351  DASolver()
1352  else:
1353  # if the mesh fails, return
1354  raise AnalysisError("Primal mesh failed!")
1355  return
1356 
1357  # if the primal solution fails, return
1358  if DASolver.primalFail != 0:
1359  raise AnalysisError("Primal solution failed!")
1360  return
1361 
1362  # get the objective functions
1363  funcs = {}
1364  DASolver.evalFunctions(funcs)
1365 
1366  # now we can print the residual for the endTime state
1367  DASolver.solver.calcPrimalResidualStatistics("print")
1368 
1369  for outputName in list(self.unsteadyCompOutput.keys()):
1370  outputs[outputName] = 0.0
1371  # add all the function values for this output
1372  for funcName in self.unsteadyCompOutput[outputName]:
1373  outputs[outputName] += funcs[funcName]
1374 
1375  def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
1376  if mode == "fwd":
1377  om.issue_warning(
1378  " mode = %s, but the forward mode functions are not implemented for DAFoam!" % mode,
1379  prefix="",
1380  stacklevel=2,
1381  category=om.OpenMDAOWarning,
1382  )
1383  return
1384 
1385  DASolver = self.DASolver
1386 
1387  # run coloring
1388  if self.adjEqnSolMethod == "Krylov":
1389  DASolver.solver.runColoring()
1390 
1391  PCMatPrecomputeInterval = DASolver.getOption("unsteadyAdjoint")["PCMatPrecomputeInterval"]
1392  PCMatUpdateInterval = DASolver.getOption("unsteadyAdjoint")["PCMatUpdateInterval"]
1393 
1394  # NOTE: this step is critical because we need to compute the residual for
1395  # self.solverAD once to get the proper oldTime level for unsteady adjoint
1396  DASolver.solverAD.updateStateBoundaryConditions()
1397  DASolver.solverAD.calcPrimalResidualStatistics("calc")
1398 
1399  # calc the total number of time instances
1400  # we assume the adjoint is for deltaT to endTime
1401  # but users can also prescribed a custom time range
1402  deltaT = DASolver.solver.getDeltaT()
1403 
1404  endTime = DASolver.solver.getEndTime()
1405  endTimeIndex = round(endTime / deltaT)
1406 
1407  localAdjSize = DASolver.getNLocalAdjointStates()
1408 
1409  ddtSchemeOrder = DASolver.solver.getDdtSchemeOrder()
1410 
1411  # read the latest solution
1412  DASolver.solver.setTime(endTime, endTimeIndex)
1413  DASolver.solverAD.setTime(endTime, endTimeIndex)
1414  # now we can read the variables
1415  DASolver.readStateVars(endTime, deltaT)
1416  # if it is dynamic mesh, read the mesh points
1417  if DASolver.getOption("dynamicMesh")["active"]:
1418  DASolver.readDynamicMeshPoints(endTime, deltaT, endTimeIndex, ddtSchemeOrder)
1419 
1420  # now we can print the residual for the endTime state
1421  DASolver.solverAD.calcPrimalResidualStatistics("print")
1422 
1423  # init dRdWTMF
1424  if self.adjEqnSolMethod == "Krylov":
1425  DASolver.solverAD.initializedRdWTMatrixFree()
1426 
1427  # precompute the KSP preconditioner Mat and save them to the self.dRdWTPC dict
1428  if self.dRdWTPC is None and self.adjEqnSolMethod == "Krylov":
1429 
1430  self.dRdWTPC = {}
1431 
1432  # always calculate the PC mat for the endTime
1433  DASolver.solver.setTime(endTime, endTimeIndex)
1434  DASolver.solverAD.setTime(endTime, endTimeIndex)
1435  # now we can read the variables
1436  DASolver.readStateVars(endTime, deltaT)
1437  # if it is dynamic mesh, read the mesh points
1438  if DASolver.getOption("dynamicMesh")["active"]:
1439  DASolver.readDynamicMeshPoints(endTime, deltaT, endTimeIndex, ddtSchemeOrder)
1440  # calc the preconditioner mat for endTime
1441  if self.comm.rank == 0:
1442  print("Pre-Computing preconditiner mat for t = %f" % endTime, flush=True)
1443 
1444  dRdWTPC1 = PETSc.Mat().create(PETSc.COMM_WORLD)
1445  DASolver.solver.calcdRdWT(1, dRdWTPC1)
1446  # always update the PC mat values using OpenFOAM's fvMatrix
1447  # DASolver.solver.calcPCMatWithFvMatrix(dRdWTPC1)
1448  self.dRdWTPC[str(endTime)] = dRdWTPC1
1449 
1450  # if we define some extra PCMat in PCMatPrecomputeInterval, calculate them here
1451  # and set them to the self.dRdWTPC dict
1452  for timeIndex in range(endTimeIndex - 1, 0, -1):
1453  if timeIndex % PCMatPrecomputeInterval == 0:
1454  t = timeIndex * deltaT
1455  if self.comm.rank == 0:
1456  print("Pre-Computing preconditiner mat for t = %f" % t, flush=True)
1457  # read the latest solution
1458  DASolver.solver.setTime(t, timeIndex)
1459  DASolver.solverAD.setTime(t, timeIndex)
1460  # now we can read the variables
1461  DASolver.readStateVars(t, deltaT)
1462  # if it is dynamic mesh, read the mesh points
1463  if DASolver.getOption("dynamicMesh")["active"]:
1464  DASolver.readDynamicMeshPoints(t, deltaT, timeIndex, ddtSchemeOrder)
1465  # calc the preconditioner mat
1466  dRdWTPC1 = PETSc.Mat().create(PETSc.COMM_WORLD)
1467  DASolver.solver.calcdRdWT(1, dRdWTPC1)
1468  # always update the PC mat values using OpenFOAM's fvMatrix
1469  # DASolver.solver.calcPCMatWithFvMatrix(dRdWTPC1)
1470  self.dRdWTPC[str(t)] = dRdWTPC1
1471 
1472  if self.adjEqnSolMethod == "Krylov":
1473  # Initialize the KSP object using the PCMat from the endTime
1474  PCMat = self.dRdWTPC[str(endTime)]
1475  ksp = PETSc.KSP().create(PETSc.COMM_WORLD)
1476  DASolver.solverAD.createMLRKSPMatrixFree(PCMat, ksp)
1477 
1478  inputDict = DASolver.getOption("inputInfo")
1479 
1480  # init the dFdW vec
1481  dFdW = PETSc.Vec().create(PETSc.COMM_WORLD)
1482  dFdW.setSizes((localAdjSize, PETSc.DECIDE), bsize=1)
1483  dFdW.setFromOptions()
1484  dFdW.zeroEntries()
1485 
1486  # init the adjoint vector
1487  psi = dFdW.duplicate()
1488  psi.zeroEntries()
1489 
1490  # initialize the adjoint vecs
1491  dRdW0TPsi = np.zeros(localAdjSize)
1492  dRdW00TPsi = np.zeros(localAdjSize)
1493  dRdW00TPsiBuffer = np.zeros(localAdjSize)
1494  dFdWArray = np.zeros(localAdjSize)
1495  psiArray = np.zeros(localAdjSize)
1496  tempdFdWArray = np.zeros(localAdjSize)
1497 
1498  # loop over all function, calculate dFdW, and solve the adjoint
1499  # for functionName in list(d_outputs.keys()):
1500  for outputName in list(self.unsteadyCompOutput.keys()):
1501 
1502  # we need to zero the total derivative for each function
1503  totals = {}
1504  for inputName in list(inputs.keys()):
1505  totals[inputName] = np.zeros_like(inputs[inputName])
1506 
1507  seed = d_outputs[outputName]
1508 
1509  # if the seed is zero, do not compute the adjoint and pass to
1510  # the next funnction
1511  if np.linalg.norm(seed) < 1e-12:
1512  continue
1513 
1514  # zero the vecs
1515  dRdW0TPsi[:] = 0.0
1516  dRdW00TPsi[:] = 0.0
1517  dRdW00TPsiBuffer[:] = 0.0
1518  dFdW.zeroEntries()
1519 
1520  # loop over all time steps and solve the adjoint and accumulate the totals
1521  adjointFail = 0
1522  for n in range(endTimeIndex, 0, -1):
1523  timeVal = n * deltaT
1524 
1525  if self.comm.rank == 0:
1526  print("---- Solving unsteady adjoint for %s. t = %f ----" % (outputName, timeVal), flush=True)
1527 
1528  # set the time value and index in the OpenFOAM layer. Note: this is critical
1529  # because if timeIndex < 2, OpenFOAM will not use the oldTime.oldTime for 2nd
1530  # ddtScheme and mess up the totals. Check backwardDdtScheme.C
1531  DASolver.solver.setTime(timeVal, n)
1532  DASolver.solverAD.setTime(timeVal, n)
1533  # now we can read the variables
1534  # read the state, state.oldTime, etc and update self.wVec for this time instance
1535  DASolver.readStateVars(timeVal, deltaT)
1536  # if it is dynamic mesh, read the mesh points
1537  if DASolver.getOption("dynamicMesh")["active"]:
1538  DASolver.readDynamicMeshPoints(timeVal, deltaT, n, ddtSchemeOrder)
1539 
1540  # calculate dFd? scaling, if time index is within the unsteady objective function
1541  # index range, prescribed in unsteadyAdjointDict, we calculate dFdW
1542  # otherwise, we use dFdW=0 because the unsteady obj does not depend
1543  # on the state at this time index.
1544  # NOTE: we just use the first function in the output for dFScaling and
1545  # assume all the functions to have the same timeOp for this output
1546  firstFunctionName = self.unsteadyCompOutput[outputName][0]
1547  dFScaling = DASolver.solver.getdFScaling(firstFunctionName, n - 1)
1548 
1549  # loop over all function for this output, compute their dFdW, and add them up to
1550  # get the dFdW for this output
1551  dFdWArray[:] = 0.0
1552  for functionName in self.unsteadyCompOutput[outputName]:
1553 
1554  # calculate dFdW
1555  jacInput = DASolver.getStates()
1556  DASolver.solverAD.calcJacTVecProduct(
1557  "states",
1558  "stateVar",
1559  jacInput,
1560  functionName,
1561  "function",
1562  seed,
1563  tempdFdWArray, # NOTE: we just use tempdFdWArray to hold a temp dFdW for this function
1564  )
1565 
1566  dFdWArray += tempdFdWArray * dFScaling
1567 
1568  # do dFdW - dRdW0TPsi - dRdW00TPsi
1569  if ddtSchemeOrder == 1:
1570  dFdWArray = dFdWArray - dRdW0TPsi
1571  elif ddtSchemeOrder == 2:
1572  dFdWArray = dFdWArray - dRdW0TPsi - dRdW00TPsi
1573  # now copy the buffer vec dRdW00TPsiBuffer to dRdW00TPsi for the next time step
1574  dRdW00TPsi[:] = dRdW00TPsiBuffer
1575  else:
1576  print("ddtSchemeOrder not valid!" % ddtSchemeOrder)
1577 
1578  # check if we need to update the PC Mat vals or use the pre-computed PC matrix
1579  if self.adjEqnSolMethod == "Krylov":
1580  if str(timeVal) in list(self.dRdWTPC.keys()):
1581  if self.comm.rank == 0:
1582  print("Using pre-computed KSP PC mat for %f" % timeVal, flush=True)
1583  PCMat = self.dRdWTPC[str(timeVal)]
1584  DASolver.solverAD.updateKSPPCMat(PCMat, ksp)
1585  if n % PCMatUpdateInterval == 0 and n < endTimeIndex:
1586  # udpate part of the PC mat
1587  if self.comm.rank == 0:
1588  print("Updating dRdWTPC mat value using OF fvMatrix", flush=True)
1589  DASolver.solver.calcPCMatWithFvMatrix(PCMat)
1590 
1591  # now solve the adjoint eqn
1592  DASolver.arrayVal2Vec(dFdWArray, dFdW)
1593 
1594  if self.adjEqnSolMethod == "Krylov":
1595  adjointFail = DASolver.solverAD.solveLinearEqn(ksp, dFdW, psi)
1596  elif self.adjEqnSolMethod == "fixedPoint":
1597  adjointFail = DASolver.solverAD.solveAdjointFP(dFdW, psi)
1598 
1599  # if one adjoint solution fails, return immediate without solving for the rest of steps.
1600  if adjointFail > 0:
1601  break
1602 
1603  # loop over all inputs and compute total derivs
1604  for inputName in list(inputs.keys()):
1605 
1606  # calculate dFdX
1607  inputType = inputDict[inputName]["type"]
1608  jacInput = inputs[inputName]
1609  dFdX = np.zeros_like(jacInput)
1610  tempdFdX = np.zeros_like(jacInput)
1611 
1612  # loop over all function for this output, compute their dFdX, and add them up to
1613  # get the dFdX for this output
1614  for functionName in self.unsteadyCompOutput[outputName]:
1615  DASolver.solverAD.calcJacTVecProduct(
1616  inputName,
1617  inputType,
1618  jacInput,
1619  functionName,
1620  "function",
1621  seed,
1622  tempdFdX,
1623  )
1624  # we need to scale the dFdX for unsteady adjoint too
1625  dFdX += tempdFdX * dFScaling
1626 
1627  # calculate dRdX^T * psi
1628  dRdXTPsi = np.zeros_like(jacInput)
1629  DASolver.vecVal2Array(psi, psiArray)
1630  DASolver.solverAD.calcJacTVecProduct(
1631  inputName,
1632  inputType,
1633  jacInput,
1634  "residual",
1635  "residual",
1636  psiArray,
1637  dRdXTPsi,
1638  )
1639 
1640  # total derivative
1641  totals[inputName] += dFdX - dRdXTPsi
1642 
1643  # we need to calculate dRdW0TPsi for the previous time step
1644  if ddtSchemeOrder == 1:
1645  DASolver.solverAD.calcdRdWOldTPsiAD(1, psiArray, dRdW0TPsi)
1646  elif ddtSchemeOrder == 2:
1647  # do the same for the previous previous step, but we need to save it to a buffer vec
1648  # because dRdW00TPsi will be used 2 steps before
1649  DASolver.solverAD.calcdRdWOldTPsiAD(1, psiArray, dRdW0TPsi)
1650  DASolver.solverAD.calcdRdWOldTPsiAD(2, psiArray, dRdW00TPsiBuffer)
1651 
1652  for inputName in list(inputs.keys()):
1653  d_inputs[inputName] += totals[inputName]
1654 
1655  # once the adjoint is done, we will assign OF fields with the endTime solution
1656  # so, if the next primal does not read fields from the 0 time, we will continue
1657  # to use the latest solutions from the previous design as the initial field
1658  DASolver.solver.setTime(endTime, endTimeIndex)
1659  DASolver.solverAD.setTime(endTime, endTimeIndex)
1660  DASolver.readStateVars(endTime, deltaT)
dafoam.mphys.mphys_dafoam.DAFoamBuilder.options
options
Definition: mphys_dafoam.py:24
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.initialize
def initialize(self)
Definition: mphys_dafoam.py:945
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:975
dafoam.mphys.mphys_dafoam.DAFoamMesh.DASolver
DASolver
Definition: mphys_dafoam.py:609
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords
Definition: mphys_dafoam.py:939
dafoam.mphys.mphys_dafoam.DAFoamFunctions.DASolver
DASolver
Definition: mphys_dafoam.py:685
dafoam.mphys.mphys_dafoam.DAFoamSolver.run_directory
run_directory
Definition: mphys_dafoam.py:247
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup.discipline
discipline
Definition: mphys_dafoam.py:197
dafoam.mphys.mphys_dafoam.DAFoamThermal.outputSize
outputSize
Definition: mphys_dafoam.py:876
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.DASolver
DASolver
Definition: mphys_dafoam.py:1249
dafoam.mphys.mphys_dafoam.DAFoamBuilder.struct_coupling
struct_coupling
Definition: mphys_dafoam.py:37
dafoam.mphys.mphys_dafoam.DAFoamThermal.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:894
dafoam.mphys.mphys_dafoam.DAFoamThermal.outputType
outputType
Definition: mphys_dafoam.py:875
dafoam.mphys.mphys_dafoam.DAFoamSolver.stateName
stateName
Definition: mphys_dafoam.py:260
dafoam.mphys.mphys_dafoam.DAFoamSolver.localAdjSize
localAdjSize
Definition: mphys_dafoam.py:268
dafoam.mphys.mphys_dafoam.DAFoamPostcouplingGroup.initialize
def initialize(self)
Definition: mphys_dafoam.py:222
dafoam.mphys.mphys_dafoam.DAFoamMesh.discipline
discipline
Definition: mphys_dafoam.py:611
dafoam.mphys.mphys_dafoam.DAFoamMesh.initialize
def initialize(self)
Definition: mphys_dafoam.py:604
dafoam.mphys.mphys_dafoam.DAFoamForces.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:1021
dafoam.mphys.mphys_dafoam.DAFoamBuilder.initialize
def initialize(self, comm)
Definition: mphys_dafoam.py:67
dafoam.pyDAFoam.PYDAFOAM
Definition: pyDAFoam.py:620
dafoam.mphys.mphys_dafoam.OptFuncs.om_prob
om_prob
Definition: mphys_dafoam.py:1107
dafoam.mphys.mphys_dafoam.DAFoamSolver.solution_counter
solution_counter
Definition: mphys_dafoam.py:251
dafoam.mphys.mphys_dafoam.DAFoamGroup.thermal_coupling
thermal_coupling
Definition: mphys_dafoam.py:142
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.initialize
def initialize(self)
Definition: mphys_dafoam.py:1237
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.x_a0
x_a0
Definition: mphys_dafoam.py:1256
dafoam.mphys.mphys_dafoam.DAFoamWarper.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:812
dafoam.mphys.mphys_dafoam.DAFoamWarper.DASolver
DASolver
Definition: mphys_dafoam.py:799
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_coupling_group_subsystem
def get_coupling_group_subsystem(self, scenario_name=None)
Definition: mphys_dafoam.py:85
dafoam.mphys.mphys_dafoam.DAFoamFunctions.volCoordName
volCoordName
Definition: mphys_dafoam.py:692
dafoam.mphys.mphys_dafoam.DAFoamThermal.volCoordName
volCoordName
Definition: mphys_dafoam.py:864
dafoam.mphys.mphys_dafoam.DAFoamForces.stateName
stateName
Definition: mphys_dafoam.py:1004
dafoam.mphys.mphys_dafoam.DAFoamMesh.mphys_get_triangulated_surface
def mphys_get_triangulated_surface(self, groupName=None)
Definition: mphys_dafoam.py:640
dafoam.mphys.mphys_dafoam.DAFoamWarper.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:824
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:983
dafoam.mphys.mphys_dafoam.DAFoamBuilder.thermal_coupling
thermal_coupling
Definition: mphys_dafoam.py:39
dafoam.mphys.mphys_dafoam.DAFoamBuilder.comm
comm
Definition: mphys_dafoam.py:69
dafoam.mphys.mphys_dafoam.DAFoamBuilder.DASolver
DASolver
Definition: mphys_dafoam.py:73
dafoam.mphys.mphys_dafoam.DAFoamBuilder.__init__
def __init__(self, options, mesh_options=None, scenario="aerodynamic", run_directory="")
Definition: mphys_dafoam.py:21
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.discipline
discipline
Definition: mphys_dafoam.py:951
dafoam.mphys.mphys_dafoam.DAFoamGroup.initialize
def initialize(self)
Definition: mphys_dafoam.py:130
dafoam.mphys.mphys_dafoam.DAFoamMesh
Definition: mphys_dafoam.py:599
dafoam.mphys.mphys_dafoam.DAFoamForces.DASolver
DASolver
Definition: mphys_dafoam.py:1000
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.setup
def setup(self)
Definition: mphys_dafoam.py:1242
dafoam.mphys.mphys_dafoam.DAFoamMesh.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:649
dafoam.mphys.mphys_dafoam.OptFuncs.findFeasibleDesign
def findFeasibleDesign(self, constraints, designVars, targets, constraintsComp=None, designVarsComp=None, epsFD=None, maxIter=10, tol=1e-4, maxNewtonStep=None)
Definition: mphys_dafoam.py:1110
dafoam.mphys.mphys_dafoam.DAFoamSolver._updateKSPTolerances
def _updateKSPTolerances(self, psi, dFdW, ksp)
Definition: mphys_dafoam.py:561
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_number_of_nodes
def get_number_of_nodes(self, groupName=None)
Definition: mphys_dafoam.py:112
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.volCoordName
volCoordName
Definition: mphys_dafoam.py:952
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_pre_coupling_subsystem
def get_pre_coupling_subsystem(self, scenario_name=None)
Definition: mphys_dafoam.py:100
dafoam.mphys.mphys_dafoam.DAFoamFunctions.funcs
funcs
Definition: mphys_dafoam.py:681
dafoam.mphys.mphys_dafoam.DAFoamMesh.x_a0
x_a0
Definition: mphys_dafoam.py:614
dafoam.mphys.mphys_dafoam.DAFoamFunctions.stateName
stateName
Definition: mphys_dafoam.py:691
dafoam.mphys.mphys_dafoam.DAFoamMesh.mphys_add_coordinate_input
def mphys_add_coordinate_input(self)
Definition: mphys_dafoam.py:626
dafoam.mphys.mphys_dafoam.DAFoamGroup.setup
def setup(self)
Definition: mphys_dafoam.py:137
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady
Definition: mphys_dafoam.py:1275
dafoam.mphys.mphys_dafoam.DAFoamThermal.DASolver
DASolver
Definition: mphys_dafoam.py:858
dafoam.mphys.mphys_dafoam.DAFoamGroup.discipline
discipline
Definition: mphys_dafoam.py:144
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady
Definition: mphys_dafoam.py:1235
dafoam.mphys.mphys_dafoam.DAFoamWarper
Definition: mphys_dafoam.py:789
dafoam.mphys.mphys_dafoam.OptFuncs.daOptions
daOptions
Definition: mphys_dafoam.py:1106
dafoam.mphys.mphys_dafoam.DAFoamMesh.mphys_get_surface_mesh
def mphys_get_surface_mesh(self)
Definition: mphys_dafoam.py:637
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_solver
def get_solver(self)
Definition: mphys_dafoam.py:80
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.dRdWTPC
dRdWTPC
Definition: mphys_dafoam.py:1288
dafoam.mphys.mphys_dafoam.OptFuncs
Definition: mphys_dafoam.py:1092
dafoam.mphys.mphys_dafoam.DAFoamForces.outputType
outputType
Definition: mphys_dafoam.py:1016
dafoam.mphys.mphys_dafoam.DAFoamSolver.discipline
discipline
Definition: mphys_dafoam.py:249
dafoam.mphys.mphys_dafoam.DAFoamThermal.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:883
dafoam.mphys.mphys_dafoam.DAFoamSolver.initialize
def initialize(self)
Definition: mphys_dafoam.py:237
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.DVGeo
DVGeo
Definition: mphys_dafoam.py:1282
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_ndof
def get_ndof(self)
Definition: mphys_dafoam.py:120
dafoam.mphys.mphys_dafoam.DAFoamSolver.volCoordName
volCoordName
Definition: mphys_dafoam.py:262
dafoam.mphys.mphys_dafoam.DAFoamFunctions.setup
def setup(self)
Definition: mphys_dafoam.py:683
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:1322
dafoam.mphys.mphys_dafoam.DAFoamForces.volCoordName
volCoordName
Definition: mphys_dafoam.py:1005
dafoam.mphys.mphys_dafoam.DAFoamThermal
Definition: mphys_dafoam.py:847
dafoam.mphys.mphys_dafoam.DAFoamGroup
Definition: mphys_dafoam.py:125
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.nSurfCoords
nSurfCoords
Definition: mphys_dafoam.py:960
dafoam.mphys.mphys_dafoam.DAFoamMesh.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:656
dafoam.mphys.mphys_dafoam.DAFoamBuilder.mesh_options
mesh_options
Definition: mphys_dafoam.py:28
dafoam.mphys.mphys_dafoam.DAFoamForces.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:1047
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.add_dvgeo
def add_dvgeo(self, DVGeo)
Definition: mphys_dafoam.py:1319
dafoam.mphys.mphys_dafoam.DAFoamFunctions.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:731
dafoam.mphys.mphys_dafoam.DAFoamThermal.outputName
outputName
Definition: mphys_dafoam.py:874
dafoam.mphys.mphys_dafoam.DAFoamSolver.apply_linear
def apply_linear(self, inputs, outputs, d_inputs, d_outputs, d_residuals, mode)
Definition: mphys_dafoam.py:368
dafoam.mphys.mphys_dafoam.DAFoamSolver.DVCon
DVCon
Definition: mphys_dafoam.py:257
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.run_directory
run_directory
Definition: mphys_dafoam.py:1243
dafoam.mphys.mphys_dafoam.DAFoamSolver.solve_nonlinear
def solve_nonlinear(self, inputs, outputs)
Definition: mphys_dafoam.py:314
dafoam.mphys.mphys_dafoam.DAFoamThermal.initialize
def initialize(self)
Definition: mphys_dafoam.py:853
dafoam.mphys.mphys_dafoam.DAFoamForces.setup
def setup(self)
Definition: mphys_dafoam.py:998
dafoam.mphys.mphys_dafoam.DAFoamMesh.setup
def setup(self)
Definition: mphys_dafoam.py:607
dafoam.mphys.mphys_dafoam.DAFoamSolver.add_dvgeo
def add_dvgeo(self, DVGeo)
Definition: mphys_dafoam.py:295
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_post_coupling_subsystem
def get_post_coupling_subsystem(self, scenario_name=None)
Definition: mphys_dafoam.py:109
dafoam.mphys.mphys_dafoam.DAFoamSolver.solve_linear
def solve_linear(self, d_outputs, d_residuals, mode)
Definition: mphys_dafoam.py:426
dafoam.mphys.mphys_dafoam.DAFoamFunctions.discipline
discipline
Definition: mphys_dafoam.py:688
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup.thermal_coupling
thermal_coupling
Definition: mphys_dafoam.py:196
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.get_surface_mesh
def get_surface_mesh(self)
Definition: mphys_dafoam.py:1271
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup.warp_in_solver
warp_in_solver
Definition: mphys_dafoam.py:195
dafoam.mphys.mphys_dafoam.OptFuncs.__init__
def __init__(self, daOptions, om_prob)
Definition: mphys_dafoam.py:1097
dafoam.mphys.mphys_dafoam.DAFoamForces.initialize
def initialize(self)
Definition: mphys_dafoam.py:995
dafoam.mphys.mphys_dafoam.DAFoamBuilder.get_mesh_coordinate_subsystem
def get_mesh_coordinate_subsystem(self, scenario_name=None)
Definition: mphys_dafoam.py:95
dafoam.mphys.mphys_dafoam.DAFoamGroup.struct_coupling
struct_coupling
Definition: mphys_dafoam.py:140
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.run_directory
run_directory
Definition: mphys_dafoam.py:1284
dafoam.mphys.mphys_dafoam.DAFoamSolver.add_dvcon
def add_dvcon(self, DVCon)
Definition: mphys_dafoam.py:298
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.adjEqnSolMethod
adjEqnSolMethod
Definition: mphys_dafoam.py:1290
dafoam.mphys.mphys_dafoam.DAFoamWarper.initialize
def initialize(self)
Definition: mphys_dafoam.py:794
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.setup
def setup(self)
Definition: mphys_dafoam.py:1281
dafoam.mphys.mphys_dafoam.DAFoamSolver.psi
psi
Definition: mphys_dafoam.py:269
dafoam.mphys.mphys_dafoam.DAFoamSolver.linearize
def linearize(self, inputs, outputs, residuals)
Definition: mphys_dafoam.py:363
dafoam.mphys.mphys_dafoam.DAFoamWarper.discipline
discipline
Definition: mphys_dafoam.py:802
dafoam.mphys.mphys_dafoam.DAFoamBuilder.warp_in_solver
warp_in_solver
Definition: mphys_dafoam.py:35
dafoam.mphys.mphys_dafoam.OptFuncs.comm
comm
Definition: mphys_dafoam.py:1108
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup.DASolver
DASolver
Definition: mphys_dafoam.py:194
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.mesh_options
mesh_options
Definition: mphys_dafoam.py:1245
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup.initialize
def initialize(self)
Definition: mphys_dafoam.py:188
dafoam.mphys.mphys_dafoam.DAFoamSolver
Definition: mphys_dafoam.py:232
dafoam.mphys.mphys_dafoam.DAFoamBuilder
Definition: mphys_dafoam.py:16
dafoam.mphys.mphys_dafoam.DAFoamSolver.setup
def setup(self)
Definition: mphys_dafoam.py:241
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.setup
def setup(self)
Definition: mphys_dafoam.py:948
dafoam.mphys.mphys_dafoam.DAFoamWarper.setup
def setup(self)
Definition: mphys_dafoam.py:797
dafoam.mphys.mphys_dafoam.DAFoamForces
Definition: mphys_dafoam.py:989
dafoam.mphys.mphys_dafoam.DAFoamBuilderUnsteady.solver_options
solver_options
Definition: mphys_dafoam.py:1244
dafoam.mphys.mphys_dafoam.DAFoamForces.discipline
discipline
Definition: mphys_dafoam.py:1002
dafoam.mphys.mphys_dafoam.DAFoamForces.outputName
outputName
Definition: mphys_dafoam.py:1015
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.surfCoordName
surfCoordName
Definition: mphys_dafoam.py:953
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.initialize
def initialize(self)
Definition: mphys_dafoam.py:1277
dafoam.mphys.mphys_dafoam.DAFoamMesh.mphys_get_surface_size
def mphys_get_surface_size(self, groupName)
Definition: mphys_dafoam.py:646
dafoam.mphys.mphys_dafoam.DAFoamGroup.use_warper
use_warper
Definition: mphys_dafoam.py:141
dafoam.mphys.mphys_dafoam.DAFoamPostcouplingGroup
Definition: mphys_dafoam.py:217
dafoam.mphys.mphys_dafoam.DAFoamBuilder.run_directory
run_directory
Definition: mphys_dafoam.py:42
dafoam.mphys.mphys_dafoam.DAFoamPostcouplingGroup.DASolver
DASolver
Definition: mphys_dafoam.py:226
dafoam.mphys.mphys_dafoam.DAFoamSolver.runColoring
runColoring
Definition: mphys_dafoam.py:276
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.DASolver
DASolver
Definition: mphys_dafoam.py:1283
dafoam.mphys.mphys_dafoam.DAFoamThermal.stateName
stateName
Definition: mphys_dafoam.py:863
dafoam.mphys.mphys_dafoam.DAFoamThermal.discipline
discipline
Definition: mphys_dafoam.py:861
dafoam.mphys.mphys_dafoam.DAFoamFunctions.initialize
def initialize(self)
Definition: mphys_dafoam.py:677
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup
Definition: mphys_dafoam.py:183
dafoam.mphys.mphys_dafoam.DAFoamSolver.residualName
residualName
Definition: mphys_dafoam.py:261
dafoam.mphys.mphys_dafoam.DAFoamFunctions
Definition: mphys_dafoam.py:672
dafoam.mphys.mphys_dafoam.DAFoamSolver.DASolver
DASolver
Definition: mphys_dafoam.py:244
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.compute_jacvec_product
def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode)
Definition: mphys_dafoam.py:1375
dafoam.mphys.mphys_dafoam.DAFoamSolver.DVGeo
DVGeo
Definition: mphys_dafoam.py:254
dafoam.mphys.mphys_dafoam.DAFoamSolverUnsteady.unsteadyCompOutput
unsteadyCompOutput
Definition: mphys_dafoam.py:1305
dafoam.mphys.mphys_dafoam.DAFoamThermal.setup
def setup(self)
Definition: mphys_dafoam.py:856
dafoam.mphys.mphys_dafoam.DAFoamPrecouplingGroup.setup
def setup(self)
Definition: mphys_dafoam.py:193
dafoam.mphys.mphys_dafoam.DAFoamGroup.DASolver
DASolver
Definition: mphys_dafoam.py:139
dafoam.mphys.mphys_dafoam.DAFoamFaceCoords.DASolver
DASolver
Definition: mphys_dafoam.py:950
dafoam.mphys.mphys_dafoam.DAFoamFunctions.compute
def compute(self, inputs, outputs)
Definition: mphys_dafoam.py:715
dafoam.mphys.mphys_dafoam.DAFoamPostcouplingGroup.setup
def setup(self)
Definition: mphys_dafoam.py:225
dafoam.mphys.mphys_dafoam.DAFoamGroup.run_directory
run_directory
Definition: mphys_dafoam.py:143