DASolver.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2 
3  DAFoam : Discrete Adjoint with OpenFOAM
4  Version : v3
5 
6 \*---------------------------------------------------------------------------*/
7 
8 #include "DASolver.H"
9 
10 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
11 // initialize the static variable, which will be used in forward mode AD
12 // computation for AOA and BC derivatives
14 
15 // initialize the python call back function static pointers
16 void* Foam::DAUtility::pyCalcBeta = NULL;
18 
21 
22 namespace Foam
23 {
24 
25 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
26 
27 defineTypeNameAndDebug(DASolver, 0);
28 defineRunTimeSelectionTable(DASolver, dictionary);
29 
30 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
31 
32 DASolver::DASolver(
33  char* argsAll,
34  PyObject* pyOptions)
35  : argsAll_(argsAll),
36  pyOptions_(pyOptions),
37  argsPtr_(nullptr),
38  runTimePtr_(nullptr),
39  meshPtr_(nullptr),
40  daOptionPtr_(nullptr),
41  daModelPtr_(nullptr),
42  daIndexPtr_(nullptr),
43  daFieldPtr_(nullptr),
44  daCheckMeshPtr_(nullptr),
45  daLinearEqnPtr_(nullptr),
46  daResidualPtr_(nullptr)
47 #ifdef CODI_AD_REVERSE
48  ,
49  globalADTape_(codi::RealReverse::getTape())
50 #endif
51 {
52  // initialize fvMesh and Time object pointer
53 #include "setArgs.H"
54 #include "setRootCasePython.H"
55 #include "createTimePython.H"
56 #include "createMeshPython.H"
57  Info << "Initializing mesh and runtime for DASolver" << endl;
58 
60 
61  primalMinResTol_ = daOptionPtr_->getOption<scalar>("primalMinResTol");
62  primalMinIters_ = daOptionPtr_->getOption<label>("primalMinIters");
63 
64  Info << "DAOpton initialized " << endl;
65 }
66 
67 // * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * d* * * * //
68 
69 autoPtr<DASolver> DASolver::New(
70  char* argsAll,
71  PyObject* pyOptions)
72 {
73  // standard setup for runtime selectable classes
74 
75  // look up the solver name defined in pyOptions
76  dictionary allOptions;
78  word modelType;
79  allOptions.readEntry<word>("solverName", modelType);
80 
81  if (allOptions.lookupOrDefault<label>("debug", 0))
82  {
83  Info << "Selecting " << modelType << " for DASolver" << endl;
84  }
85 
86  dictionaryConstructorTable::iterator cstrIter =
87  dictionaryConstructorTablePtr_->find(modelType);
88 
89  // if the solver name is not found in any child class, print an error
90  if (cstrIter == dictionaryConstructorTablePtr_->end())
91  {
92  FatalErrorIn(
93  "DASolver::New"
94  "("
95  " char*,"
96  " PyObject*"
97  ")")
98  << "Unknown DASolver type "
99  << modelType << nl << nl
100  << "Valid DASolver types:" << endl
101  << dictionaryConstructorTablePtr_->sortedToc()
102  << exit(FatalError);
103  }
104 
105  // child class found
106  return autoPtr<DASolver>(
107  cstrIter()(argsAll, pyOptions));
108 }
109 
110 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
111 
113 {
114  /*
115  Description:
116  The loop method to increment the runtime. The reason we implent this is
117  because the runTime.loop() and simple.loop() give us seg fault...
118  */
119 
120  scalar endTime = runTime.endTime().value();
121  scalar deltaT = runTime.deltaT().value();
122  scalar t = runTime.timeOutputValue();
123 
124  // execute functionObjectList, e.g., field averaging, sampling
125  functionObjectList& funcObj = const_cast<functionObjectList&>(runTime.functionObjects());
126  if (runTime.timeIndex() == runTime.startTimeIndex())
127  {
128  funcObj.start();
129  }
130  else
131  {
132  funcObj.execute();
133  }
134 
135  // check exit condition
136  if (primalMinRes_ < primalMinResTol_ && runTime.timeIndex() > primalMinIters_)
137  {
138  Info << "Time = " << t << endl;
139  Info << "Minimal residual " << primalMinRes_ << " satisfied the prescribed tolerance " << primalMinResTol_ << endl
140  << endl;
141  this->printAllObjFuncs();
142  runTime.writeNow();
143  prevPrimalSolTime_ = t;
144  funcObj.end();
145  return 0;
146  }
147  else if (t > endTime - 0.5 * deltaT)
148  {
149  prevPrimalSolTime_ = t;
150  funcObj.end();
151  return 0;
152  }
153  else
154  {
155  ++runTime;
156  return 1;
157  }
158 }
159 
161 {
162  /*
163  Description:
164  Calculate the values of all objective functions and print them to screen
165  NOTE: we need to call DASolver::setDAObjFuncList before calling this function!
166  */
167 
168  if (daObjFuncPtrList_.size() == 0)
169  {
170  FatalErrorIn("printAllObjFuncs") << "daObjFuncPtrList_.size() ==0... "
171  << "Forgot to call setDAObjFuncList?"
172  << abort(FatalError);
173  }
174 
176  {
177  DAObjFunc& daObjFunc = daObjFuncPtrList_[idxI];
178  word objFuncName = daObjFunc.getObjFuncName();
179  scalar objFuncVal = daObjFunc.getObjFuncValue();
180  Info << objFuncName
181  << "-" << daObjFunc.getObjFuncPart()
182  << "-" << daObjFunc.getObjFuncType()
183  << ": " << objFuncVal;
184 #ifdef CODI_AD_FORWARD
185 
186  // if the forwardModeAD is active,, we need to get the total derivatives here
187  if (daOptionPtr_->getAllOptions().subDict("useAD").getWord("mode") == "forward")
188  {
189  Info << " ForwardAD Deriv: " << objFuncVal.getGradient();
190 
191  // assign the forward mode AD derivative to forwardADDerivVal_
192  // such that we can get this value later
193  forwardADDerivVal_.set(objFuncName, objFuncVal.getGradient());
194  }
195 #endif
196  Info << endl;
197  }
198 }
199 
200 scalar DASolver::getObjFuncValue(const word objFuncName)
201 {
202  /*
203  Description:
204  Return the value of the objective function.
205  NOTE: we will sum up all the parts in objFuncName
206 
207  Input:
208  objFuncName: the name of the objective function
209 
210  Output:
211  objFuncValue: the value of the objective
212  */
213 
214  if (daObjFuncPtrList_.size() == 0)
215  {
216  FatalErrorIn("printAllObjFuncs") << "daObjFuncPtrList_.size() ==0... "
217  << "Forgot to call setDAObjFuncList?"
218  << abort(FatalError);
219  }
220 
221  scalar objFuncValue = 0.0;
222 
224  {
225  DAObjFunc& daObjFunc = daObjFuncPtrList_[idxI];
226  if (daObjFunc.getObjFuncName() == objFuncName)
227  {
228  objFuncValue += daObjFunc.getObjFuncValue();
229  }
230  }
231 
232  return objFuncValue;
233 }
234 
236 {
237  /*
238  Description:
239  Set up the objective function list such that we can call printAllObjFuncs and getObjFuncValue
240  NOTE: this function needs to be called before calculating any objective functions
241 
242  Example:
243  A typical objFunc dictionary looks like this:
244 
245  "objFunc":
246  {
247  "func1":
248  {
249  "part1":
250  {
251  "objFuncName": "force",
252  "source": "patchToFace",
253  "patches": ["walls", "wallsbump"],
254  "scale": 0.5,
255  "addToAdjoint": True,
256  },
257  "part2":
258  {
259  "objFuncName": "force",
260  "source": "patchToFace",
261  "patches": ["wallsbump", "frontandback"],
262  "scale": 0.5,
263  "addToAdjoint": True,
264  },
265  },
266  "func2":
267  {
268  "part1":
269  {
270  "objFuncName": "force",
271  "source": "patchToFace",
272  "patches": ["walls", "wallsbump", "frontandback"],
273  "scale": 1.0,
274  "addToAdjoint": False,
275  }
276  },
277  }
278  */
279 
280  const dictionary& allOptions = daOptionPtr_->getAllOptions();
281 
282  const dictionary& objFuncDict = allOptions.subDict("objFunc");
283 
284  // loop over all objFuncs and parts and calc the number of
285  // DAObjFunc instances we need
286  label nObjFuncInstances = 0;
287  forAll(objFuncDict.toc(), idxI)
288  {
289  word objFunI = objFuncDict.toc()[idxI];
290  const dictionary& objFuncSubDict = objFuncDict.subDict(objFunI);
291  forAll(objFuncSubDict.toc(), idxJ)
292  {
293  nObjFuncInstances++;
294  }
295  }
296 
297  daObjFuncPtrList_.setSize(nObjFuncInstances);
298 
299  // we need to repeat the loop to initialize the
300  // DAObjFunc instances
301  label objFuncInstanceI = 0;
302  forAll(objFuncDict.toc(), idxI)
303  {
304  word objFunI = objFuncDict.toc()[idxI];
305  const dictionary& objFuncSubDict = objFuncDict.subDict(objFunI);
306  forAll(objFuncSubDict.toc(), idxJ)
307  {
308 
309  word objPart = objFuncSubDict.toc()[idxJ];
310  const dictionary& objFuncSubDictPart = objFuncSubDict.subDict(objPart);
311 
312  fvMesh& mesh = meshPtr_();
313 
314  daObjFuncPtrList_.set(
315  objFuncInstanceI,
317  mesh,
318  daOptionPtr_(),
319  daModelPtr_(),
320  daIndexPtr_(),
321  daResidualPtr_(),
322  objFunI,
323  objPart,
324  objFuncSubDictPart)
325  .ptr());
326 
327  objFuncInstanceI++;
328  }
329  }
330 }
331 
333 {
334  /*
335  Description:
336  Get the number of faces for the MDO coupling patches
337  */
338  wordList patchList;
339  this->getCouplingPatchList(patchList);
340  // get the total number of points and faces for the patchList
341  label nPoints, nFaces;
342  this->getPatchInfo(nPoints, nFaces, patchList);
343 
344  return nFaces;
345 }
346 
348 {
349  /*
350  Description:
351  Get the number of points for the MDO coupling patches
352  */
353  wordList patchList;
354  this->getCouplingPatchList(patchList);
355  // get the total number of points and faces for the patchList
356  label nPoints, nFaces;
357  this->getPatchInfo(nPoints, nFaces, patchList);
358 
359  return nPoints;
360 }
361 
363  const scalar* volCoords,
364  scalar* surfCoords)
365 {
366  /*
367  Description:
368  Calculate a list of face center coordinates for the MDO coupling patches, given
369  the volume mesh point coordinates
370 
371  Input:
372  volCoords: volume mesh point coordinates
373 
374  Output:
375  surfCoords: face center coordinates for coupling patches
376  */
377  this->updateOFMesh(volCoords);
378 
379  wordList patchList;
380  this->getCouplingPatchList(patchList);
381  // get the total number of points and faces for the patchList
382  label nPoints, nFaces;
383  this->getPatchInfo(nPoints, nFaces, patchList);
384 
385  // ******** first loop
386  label counterFaceI = 0;
387  forAll(patchList, cI)
388  {
389  // get the patch id label
390  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
391  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
392  {
393  for (label i = 0; i < 3; i++)
394  {
395  surfCoords[counterFaceI] = meshPtr_->Cf().boundaryField()[patchI][faceI][i];
396  counterFaceI++;
397  }
398  }
399  }
400 
401  // ******** second loop
402  // NOTE. Since we create two duplicated surface point coordinates for transferring two variables
403  // we need to translate the 2nd one by 1000, so the meld component will find the correct
404  // coordinates for interpolation. If these two sets of coords are overlapped, we will have
405  // wrong interpolations from meld.
406  forAll(patchList, cI)
407  {
408  // get the patch id label
409  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
410  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
411  {
412  for (label i = 0; i < 3; i++)
413  {
414  surfCoords[counterFaceI] = meshPtr_->Cf().boundaryField()[patchI][faceI][i] + 1000.0;
415  counterFaceI++;
416  }
417  }
418  }
419 }
420 
422  const double* volCoords,
423  const double* seeds,
424  double* product)
425 {
426 #ifdef CODI_AD_REVERSE
427 
428  label nCouplingFaces = this->getNCouplingFaces();
429 
430  scalar* volCoordsArray = new scalar[daIndexPtr_->nLocalXv];
431  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
432  {
433  volCoordsArray[i] = volCoords[i];
434  }
435 
436  scalar* surfCoordsArray = new scalar[nCouplingFaces * 6];
437 
438  // update the OpenFOAM variables and reset their seeds (gradient part) to zeros
439  this->resetOFSeeds();
440  // reset the AD tape
441  this->globalADTape_.reset();
442  // start recording
443  this->globalADTape_.setActive();
444 
445  // register inputs
446  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
447  {
448  this->globalADTape_.registerInput(volCoordsArray[i]);
449  }
450 
451  // calculate outputs
452  this->calcCouplingFaceCoords(volCoordsArray, surfCoordsArray);
453 
454  // register outputs
455  for (label i = 0; i < nCouplingFaces * 6; i++)
456  {
457  this->globalADTape_.registerOutput(surfCoordsArray[i]);
458  }
459 
460  // stop recording
461  this->globalADTape_.setPassive();
462 
463  // set seeds to the outputs
464  for (label i = 0; i < nCouplingFaces * 6; i++)
465  {
466  surfCoordsArray[i].setGradient(seeds[i]);
467  }
468 
469  // now calculate the reverse matrix-vector product
470  this->globalADTape_.evaluate();
471 
472  // get the matrix-vector product from the inputs
473  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
474  {
475  product[i] = volCoordsArray[i].getGradient();
476  }
477 
478  delete[] volCoordsArray;
479  delete[] surfCoordsArray;
480 
481  // clean up AD
482  this->globalADTape_.clearAdjoints();
483  this->globalADTape_.reset();
484 
485 #endif
486 }
487 
489  wordList& patchList,
490  word groupName)
491 {
492  /*
493  Description:
494  Return the couplingPatchList for MDO
495  We loop over all the scenario in couplingInfo and find which scenario is active.
496  If yes, we return the patchList defined in that scenario.
497  If none of the scenario is active, we return the designSurface as the patchList
498  NOTE: we always sort the returned patchList
499 
500  Input:
501  groupName (optional): a specific group name one wants to extract the patchList from.
502  The default is NONE and we will extract the first group.
503 
504  Output:
505  patchList: a word list of the coupling Patch for MDO or just the design surface patches
506  */
507 
508  // first, we read couplingInfo
509  dictionary couplingInfo = daOptionPtr_->getAllOptions().subDict("couplingInfo");
510 
511  // loop over all the keys to check active one
512  forAll(couplingInfo.toc(), idxI)
513  {
514  word scenario = couplingInfo.toc()[idxI];
515  label active = couplingInfo.subDict(scenario).getLabel("active");
516  if (active)
517  {
518  dictionary couplingGroups = couplingInfo.subDict(scenario).subDict("couplingSurfaceGroups");
519  // we support only one couplingSurfaceGroups
520  if (groupName == "NONE")
521  {
522  groupName = couplingGroups.toc()[0];
523  couplingGroups.readEntry<wordList>(groupName, patchList);
524  sort(patchList);
525  return;
526  }
527  else
528  {
529  couplingGroups.readEntry<wordList>(groupName, patchList);
530  sort(patchList);
531  return;
532  }
533  }
534  }
535 
536  // if none of the scenarios are active, we return the design surface patches and print a warning
537  Info << "************************ WARNING ************************" << endl;
538  Info << "getCouplingPatchList is called but none of " << endl;
539  Info << "scenario is active in couplingInfo dict! " << endl;
540  Info << "return designSurfaces as patchList.." << endl;
541  Info << "************************ WARNING ************************" << endl;
542 
543  daOptionPtr_->getAllOptions().readEntry<wordList>("designSurfaces", patchList);
544 
545  sort(patchList);
546 
547  return;
548 }
549 
550 void DASolver::setThermal(scalar* thermal)
551 {
552  /*
553  Description:
554  Assign the thermal BC values to all of the faces on the conjugate heat
555  transfer patches.
556 
557  We assume the conjugate coupling patches are of mixed type, so we need to assign
558  refValue = neighbour near wall temperature
559  refGrad = 0
560  valueFraction = neighKDeltaCoeffs / ( neighKDeltaCoeffs + myKDeltaCoeffs)
561 
562  NOTE: we have two separate variables saved in the thermal array.
563  One is the neighbour near wall temperature and the other is the neighbour kappa/d.
564  So the size of thermal array is 2 * nCouplingFaces
565  NOTE: this function can be called by either fluid or solid domain!
566 
567  This conjugate heat transfer coupling uses the OpenFOAM's implementation in
568  turbulentTemperatureCoupledBaffleMixed.C
569 
570  Inputs:
571  thermal: the thermal BC values on the conjugate heat transfer patch
572 
573  Outputs:
574  The T field in OpenFOAM
575  */
576 
577  List<word> patchList;
578  this->getCouplingPatchList(patchList);
579 
580  volScalarField& T =
581  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>("T"));
582 
583  // ********* first loop, set the refValue
584  label localFaceI = 0;
585  forAll(patchList, cI)
586  {
587  // get the patch id label
588  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
589 
590  mixedFvPatchField<scalar>& mixedPatch =
591  refCast<mixedFvPatchField<scalar>>(T.boundaryFieldRef()[patchI]);
592 
593  forAll(mixedPatch.refValue(), faceI)
594  {
595  mixedPatch.refValue()[faceI] = thermal[localFaceI];
596  mixedPatch.refGrad()[faceI] = 0;
597  localFaceI++;
598  }
599  }
600 
601  // ********* second loop, set the valueFraction:
602  // neighKDeltaCoeffs / ( neighKDeltaCoeffs + myKDeltaCoeffs)
603 
604 #ifdef IncompressibleFlow
605  // for incompressible flow Q = Cp * alphaEff * dT/dz, so kappa = Cp * alphaEff
606  DATurbulenceModel& daTurb = const_cast<DATurbulenceModel&>(daModelPtr_->getDATurbulenceModel());
607  volScalarField alphaEff = daTurb.alphaEff();
608 
609  IOdictionary transportProperties(
610  IOobject(
611  "transportProperties",
612  meshPtr_->time().constant(),
613  meshPtr_(),
614  IOobject::MUST_READ,
615  IOobject::NO_WRITE,
616  false));
617  scalar Cp = readScalar(transportProperties.lookup("Cp"));
618 
619  forAll(patchList, cI)
620  {
621  // get the patch id label
622  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
623 
624  mixedFvPatchField<scalar>& mixedPatch =
625  refCast<mixedFvPatchField<scalar>>(T.boundaryFieldRef()[patchI]);
626 
627  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
628  {
629  // deltaCoeffs = 1 / d
630  scalar deltaCoeffs = T.boundaryField()[patchI].patch().deltaCoeffs()[faceI];
631  scalar alphaEffBf = alphaEff.boundaryField()[patchI][faceI];
632  scalar myKDeltaCoeffs = Cp * alphaEffBf * deltaCoeffs;
633  scalar neighKDeltaCoeffs = thermal[localFaceI];
634  mixedPatch.valueFraction()[faceI] = neighKDeltaCoeffs / (myKDeltaCoeffs + neighKDeltaCoeffs);
635  localFaceI++;
636  }
637  }
638 #endif
639 
640 #ifdef CompressibleFlow
641  // for compressible flow Q = alphaEff * dHE/dz, so if enthalpy is used, kappa = Cp * alphaEff
642  // if the internalEnergy is used, kappa = (Cp - R) * alphaEff
643 
644  DATurbulenceModel& daTurb = const_cast<DATurbulenceModel&>(daModelPtr_->getDATurbulenceModel());
645  volScalarField alphaEff = daTurb.alphaEff();
646  // compressible flow, H = alphaEff * dHE/dz
647  fluidThermo& thermo = const_cast<fluidThermo&>(daModelPtr_->getThermo());
648  volScalarField& he = thermo.he();
649 
650  const IOdictionary& thermoDict = meshPtr_->thisDb().lookupObject<IOdictionary>("thermophysicalProperties");
651  dictionary mixSubDict = thermoDict.subDict("mixture");
652  dictionary specieSubDict = mixSubDict.subDict("specie");
653  scalar molWeight = specieSubDict.getScalar("molWeight");
654  dictionary thermodynamicsSubDict = mixSubDict.subDict("thermodynamics");
655  scalar Cp = thermodynamicsSubDict.getScalar("Cp");
656 
657  // 8314.4700665 gas constant in OpenFOAM
658  // src/OpenFOAM/global/constants/thermodynamic/thermodynamicConstants.H
659  scalar RR = Foam::constant::thermodynamic::RR;
660 
661  // R = RR/molWeight
662  // Foam::specie::R() function in src/thermophysicalModels/specie/specie/specieI.H
663  scalar R = RR / molWeight;
664 
665  scalar tmpVal = 0;
666  // e = (Cp - R) * T, so Q = alphaEff * (Cp-R) * dT/dz
667  if (he.name() == "e")
668  {
669  tmpVal = Cp - R;
670  }
671  // h = Cp * T, so Q = alphaEff * Cp * dT/dz
672  else
673  {
674  tmpVal = Cp;
675  }
676 
677  forAll(patchList, cI)
678  {
679  // get the patch id label
680  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
681 
682  mixedFvPatchField<scalar>& mixedPatch =
683  refCast<mixedFvPatchField<scalar>>(T.boundaryFieldRef()[patchI]);
684 
685  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
686  {
687  // deltaCoeffs = 1 / d
688  scalar deltaCoeffs = T.boundaryField()[patchI].patch().deltaCoeffs()[faceI];
689  scalar alphaEffBf = alphaEff.boundaryField()[patchI][faceI];
690  scalar myKDeltaCoeffs = tmpVal * alphaEffBf * deltaCoeffs;
691  scalar neighKDeltaCoeffs = thermal[localFaceI];
692  mixedPatch.valueFraction()[faceI] = neighKDeltaCoeffs / (myKDeltaCoeffs + neighKDeltaCoeffs);
693  localFaceI++;
694  }
695  }
696 #endif
697 
698 #ifdef SolidDASolver
699  // for solid solvers Q = k * dT/dz, so kappa = k
700  IOdictionary transportProperties(
701  IOobject(
702  "transportProperties",
703  meshPtr_->time().constant(),
704  meshPtr_(),
705  IOobject::MUST_READ,
706  IOobject::NO_WRITE,
707  false));
708  scalar k = readScalar(transportProperties.lookup("k"));
709 
710  forAll(patchList, cI)
711  {
712  // get the patch id label
713  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
714 
715  mixedFvPatchField<scalar>& mixedPatch =
716  refCast<mixedFvPatchField<scalar>>(T.boundaryFieldRef()[patchI]);
717 
718  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
719  {
720  // deltaCoeffs = 1 / d
721  scalar deltaCoeffs = T.boundaryField()[patchI].patch().deltaCoeffs()[faceI];
722  scalar myKDeltaCoeffs = k * deltaCoeffs;
723  scalar neighKDeltaCoeffs = thermal[localFaceI];
724  mixedPatch.valueFraction()[faceI] = neighKDeltaCoeffs / (myKDeltaCoeffs + neighKDeltaCoeffs);
725  localFaceI++;
726  }
727  }
728 #endif
729 }
730 
732  const scalar* volCoords,
733  const scalar* states,
734  scalar* thermal)
735 {
736  /*
737  Description:
738  Compute the thermal variables for all of the faces on the conjugate heat
739  transfer patches.
740 
741  NOTE: we have two separate variables to assign to the thermal array.
742  One is the near wall temperature and the other is kappa/d.
743  So the size of thermal array is 2 * nCouplingFaces
744 
745  NOTE: this function can be called by either fluid or solid domain!
746 
747  This conjugate heat transfer coupling uses the OpenFOAM's implementation in
748  turbulentTemperatureCoupledBaffleMixed.C
749 
750  Inputs:
751 
752  volCoords: volume coordinates
753 
754  states: state variables
755 
756  Output:
757  thermal: the thermal variables on the conjugate heat transfer patch
758  */
759 
760  this->updateOFMesh(volCoords);
761  this->updateOFField(states);
762 
763  List<word> patchList;
764  this->getCouplingPatchList(patchList);
765 
766  const objectRegistry& db = meshPtr_->thisDb();
767  const volScalarField& T = db.lookupObject<volScalarField>("T");
768 
769  // ************ first loop, get the near wall cell temperature
770  label localFaceI = 0;
771  forAll(patchList, cI)
772  {
773  // get the patch id label
774  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
775  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
776  {
777  label faceCellI = meshPtr_->boundaryMesh()[patchI].faceCells()[faceI];
778  thermal[localFaceI] = T[faceCellI];
779  localFaceI++;
780  }
781  }
782 
783  // ********* second loop, get the (kappa / d) coefficient
784 
785 #ifdef IncompressibleFlow
786 
787  // for incompressible flow Q = Cp * alphaEff * dT/dz, so kappa = Cp * alphaEff
788 
789  DATurbulenceModel& daTurb = const_cast<DATurbulenceModel&>(daModelPtr_->getDATurbulenceModel());
790  volScalarField alphaEff = daTurb.alphaEff();
791 
792  IOdictionary transportProperties(
793  IOobject(
794  "transportProperties",
795  meshPtr_->time().constant(),
796  meshPtr_(),
797  IOobject::MUST_READ,
798  IOobject::NO_WRITE,
799  false));
800  scalar Cp = readScalar(transportProperties.lookup("Cp"));
801 
802  forAll(patchList, cI)
803  {
804  // get the patch id label
805  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
806  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
807  {
808  // deltaCoeffs = 1 / d
809  scalar deltaCoeffs = T.boundaryField()[patchI].patch().deltaCoeffs()[faceI];
810  scalar alphaEffBf = alphaEff.boundaryField()[patchI][faceI];
811  thermal[localFaceI] = Cp * alphaEffBf * deltaCoeffs;
812  localFaceI++;
813  }
814  }
815 #endif
816 
817 #ifdef CompressibleFlow
818  // for compressible flow Q = alphaEff * dHE/dz, so if enthalpy is used, kappa = Cp * alphaEff
819  // if the internalEnergy is used, kappa = (Cp - R) * alphaEff
820 
821  DATurbulenceModel& daTurb = const_cast<DATurbulenceModel&>(daModelPtr_->getDATurbulenceModel());
822  volScalarField alphaEff = daTurb.alphaEff();
823  // compressible flow, H = alphaEff * dHE/dz
824  fluidThermo& thermo = const_cast<fluidThermo&>(daModelPtr_->getThermo());
825  volScalarField& he = thermo.he();
826 
827  const IOdictionary& thermoDict = meshPtr_->thisDb().lookupObject<IOdictionary>("thermophysicalProperties");
828  dictionary mixSubDict = thermoDict.subDict("mixture");
829  dictionary specieSubDict = mixSubDict.subDict("specie");
830  scalar molWeight = specieSubDict.getScalar("molWeight");
831  dictionary thermodynamicsSubDict = mixSubDict.subDict("thermodynamics");
832  scalar Cp = thermodynamicsSubDict.getScalar("Cp");
833 
834  // 8314.4700665 gas constant in OpenFOAM
835  // src/OpenFOAM/global/constants/thermodynamic/thermodynamicConstants.H
836  scalar RR = Foam::constant::thermodynamic::RR;
837 
838  // R = RR/molWeight
839  // Foam::specie::R() function in src/thermophysicalModels/specie/specie/specieI.H
840  scalar R = RR / molWeight;
841 
842  scalar tmpVal = 0;
843  // e = (Cp - R) * T, so Q = alphaEff * (Cp-R) * dT/dz
844  if (he.name() == "e")
845  {
846  tmpVal = Cp - R;
847  }
848  // h = Cp * T, so Q = alphaEff * Cp * dT/dz
849  else
850  {
851  tmpVal = Cp;
852  }
853 
854  forAll(patchList, cI)
855  {
856  // get the patch id label
857  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
858  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
859  {
860  // deltaCoeffs = 1 / d
861  scalar deltaCoeffs = T.boundaryField()[patchI].patch().deltaCoeffs()[faceI];
862  scalar alphaEffBf = alphaEff.boundaryField()[patchI][faceI];
863  thermal[localFaceI] = tmpVal * alphaEffBf * deltaCoeffs;
864  localFaceI++;
865  }
866  }
867 #endif
868 
869 #ifdef SolidDASolver
870  // for solid solvers Q = k * dT/dz, so kappa = k
871  IOdictionary transportProperties(
872  IOobject(
873  "transportProperties",
874  meshPtr_->time().constant(),
875  meshPtr_(),
876  IOobject::MUST_READ,
877  IOobject::NO_WRITE,
878  false));
879  scalar k = readScalar(transportProperties.lookup("k"));
880 
881  forAll(patchList, cI)
882  {
883  // get the patch id label
884  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
885  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
886  {
887  // deltaCoeffs = 1 / d
888  scalar deltaCoeffs = T.boundaryField()[patchI].patch().deltaCoeffs()[faceI];
889  thermal[localFaceI] = k * deltaCoeffs;
890  localFaceI++;
891  }
892  }
893 #endif
894 }
895 
897  const word inputName,
898  const double* volCoords,
899  const double* states,
900  const double* seeds,
901  double* product)
902 {
903  /*
904  Description:
905  Calculate dThermaldStates or dThermaldXv using reverse-mode AD. Mode can be either temperature or heatFlux
906 
907  Input:
908  inputName: either volCoords or states
909 
910  volCoords: the volume mesh coordinate
911 
912  states: the state variable
913 
914  seeds: the derivative seed
915 
916  Output:
917  product: [dTemperature/dW]^T * psi, [dTemperature/dXv]^T * psi, [dHeatFlux/dW]^T * psi, or [dHeatFlux/dXv]^T * psi
918  */
919 
920 #ifdef CODI_AD_REVERSE
921 
922  label nCouplingFaces = this->getNCouplingFaces();
923 
924  scalar* volCoordsArray = new scalar[daIndexPtr_->nLocalXv];
925  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
926  {
927  volCoordsArray[i] = volCoords[i];
928  }
929 
930  scalar* statesArray = new scalar[daIndexPtr_->nLocalAdjointStates];
931  for (label i = 0; i < daIndexPtr_->nLocalAdjointStates; i++)
932  {
933  statesArray[i] = states[i];
934  }
935 
936  scalar* thermalArray = new scalar[nCouplingFaces * 2];
937 
938  // update the OpenFOAM variables and reset their seeds (gradient part) to zeros
939  this->resetOFSeeds();
940  // reset the AD tape
941  this->globalADTape_.reset();
942  // start recording
943  this->globalADTape_.setActive();
944 
945  // register inputs
946  if (inputName == "volCoords")
947  {
948  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
949  {
950  this->globalADTape_.registerInput(volCoordsArray[i]);
951  }
952  }
953  else if (inputName == "states")
954  {
955  for (label i = 0; i < daIndexPtr_->nLocalAdjointStates; i++)
956  {
957  this->globalADTape_.registerInput(statesArray[i]);
958  }
959  }
960  else
961  {
962  FatalErrorIn("getThermalAD") << " inputName not valid. "
963  << abort(FatalError);
964  }
965 
966  // calculate outputs
967  this->getThermal(volCoordsArray, statesArray, thermalArray);
968 
969  // register outputs
970  for (label i = 0; i < nCouplingFaces * 2; i++)
971  {
972  this->globalADTape_.registerOutput(thermalArray[i]);
973  }
974 
975  // stop recording
976  this->globalADTape_.setPassive();
977 
978  // set seeds to the outputs
979  for (label i = 0; i < nCouplingFaces * 2; i++)
980  {
981  thermalArray[i].setGradient(seeds[i]);
982  }
983 
984  // now calculate the reverse matrix-vector product
985  this->globalADTape_.evaluate();
986 
987  // get the matrix-vector product from the inputs
988  if (inputName == "volCoords")
989  {
990  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
991  {
992  product[i] = volCoordsArray[i].getGradient();
993  }
994  }
995  else if (inputName == "states")
996  {
997  for (label i = 0; i < daIndexPtr_->nLocalAdjointStates; i++)
998  {
999  product[i] = statesArray[i].getGradient();
1000  }
1001  }
1002  else
1003  {
1004  FatalErrorIn("getThermalAD") << " inputName not valid. "
1005  << abort(FatalError);
1006  }
1007 
1008  // ******************************* NOTE! ******************************
1009  // This step is tricky. We need to clean up the AD seeds for all variables before the next AD
1010  // This is usually done by theses two calls: updateOFField(wVec) and updateOFMesh(xvVec)
1011  // The above two calls will assign zeros to the gradient() part of all scalar variables because
1012  // the wVec and xvVec's gradient() part is zero (they are double instead of scalar).
1013  // HOWEVER, the above two calls will NOT clean the seeds for field variables's boundary values
1014  // This will not cause problems for most of the functions because their boundary values always
1015  // have zero seeds. However, the getThermal function actually propagates non-zeros seeds for boundary
1016  // values, so here we need to manually clean up the BC's gradient part. NOT doing this will
1017  // mess up the AD computation for the following function.
1018  // The way we clean it is that we set a zero-seed thermal var and call getThermal to propagate
1019  // the zero seeds to all intermediate vars.
1020  // ******************************* NOTE! ******************************
1021  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
1022  {
1023  volCoordsArray[i].setGradient(0.0);
1024  }
1025  for (label i = 0; i < daIndexPtr_->nLocalAdjointStates; i++)
1026  {
1027  statesArray[i].setGradient(0.0);
1028  }
1029  this->getThermal(volCoordsArray, statesArray, thermalArray);
1030 
1031  delete[] volCoordsArray;
1032  delete[] statesArray;
1033  delete[] thermalArray;
1034 
1035  // clean up AD
1036  this->globalADTape_.clearAdjoints();
1037  this->globalADTape_.reset();
1038 
1039 #endif
1040 }
1041 
1042 void DASolver::getForces(Vec fX, Vec fY, Vec fZ)
1043 {
1044  /*
1045  Description:
1046  Compute the nodal forces for all of the nodes on the fluid-structure-interaction
1047  patches. This routine is a wrapper that exposes the actual force computation
1048  routine to the Python layer using PETSc vectors. For the actual force computation
1049  routine view the getForcesInternal() function.
1050 
1051  Inputs:
1052  fX: Vector of X-component of forces
1053 
1054  fY: Vector of Y-component of forces
1055 
1056  fZ: Vector of Z-component of forces
1057 
1058  Output:
1059  fX, fY, fZ, and pointList are modified / set in place.
1060  */
1061 #ifndef SolidDASolver
1062  // Get Data
1063  label nPoints, nFaces;
1064  List<word> patchList;
1065  this->getCouplingPatchList(patchList);
1066  this->getPatchInfo(nPoints, nFaces, patchList);
1067 
1068  // Allocate arrays
1069  List<scalar> fXTemp(nPoints);
1070  List<scalar> fYTemp(nPoints);
1071  List<scalar> fZTemp(nPoints);
1072 
1073  // Compute forces
1074  this->getForcesInternal(fXTemp, fYTemp, fZTemp, patchList);
1075 
1076  // Zero PETSc Arrays
1077  VecZeroEntries(fX);
1078  VecZeroEntries(fY);
1079  VecZeroEntries(fZ);
1080 
1081  // Get PETSc arrays
1082  PetscScalar* vecArrayFX;
1083  VecGetArray(fX, &vecArrayFX);
1084  PetscScalar* vecArrayFY;
1085  VecGetArray(fY, &vecArrayFY);
1086  PetscScalar* vecArrayFZ;
1087  VecGetArray(fZ, &vecArrayFZ);
1088 
1089  // Transfer to PETSc Array
1090  label pointCounter = 0;
1091  forAll(fXTemp, cI)
1092  {
1093  // Get Values
1094  PetscScalar val1, val2, val3;
1095  assignValueCheckAD(val1, fXTemp[pointCounter]);
1096  assignValueCheckAD(val2, fYTemp[pointCounter]);
1097  assignValueCheckAD(val3, fZTemp[pointCounter]);
1098 
1099  // Set Values
1100  vecArrayFX[pointCounter] = val1;
1101  vecArrayFY[pointCounter] = val2;
1102  vecArrayFZ[pointCounter] = val3;
1103 
1104  // Increment counter
1105  pointCounter += 1;
1106  }
1107  VecRestoreArray(fX, &vecArrayFX);
1108  VecRestoreArray(fY, &vecArrayFY);
1109  VecRestoreArray(fZ, &vecArrayFZ);
1110 #endif
1111  return;
1112 }
1113 
1114 void DASolver::getAcousticData(Vec x, Vec y, Vec z, Vec nX, Vec nY, Vec nZ, Vec a, Vec fX, Vec fY, Vec fZ, word groupName)
1115 {
1116  /*
1117  Description:
1118  Compute the nodal forces for all of the nodes on the fluid-structure-interaction
1119  patches. This routine is a wrapper that exposes the actual force computation
1120  routine to the Python layer using PETSc vectors. For the actual force computation
1121  routine view the getForcesInternal() function.
1122 
1123  Inputs:
1124  x: Vector of X-component coordinates
1125 
1126  y: Vector of Y-component coordinates
1127 
1128  z: Vector of Z-component coordinates
1129 
1130  nX: Vector of X-component of normal vectors
1131 
1132  nY: Vector of Y-component of normal vectors
1133 
1134  nZ: Vector of Z-component of normal vectors
1135 
1136  a: Vector of areas
1137 
1138  fX: Vector of X-component of forces
1139 
1140  fY: Vector of Y-component of forces
1141 
1142  fZ: Vector of Z-component of forces
1143 
1144  groupName: Name of acoustic group
1145 
1146  Output:
1147  x, y, z, nX, nY, nZ, a, fX, fY, fZ, and patchList are modified / set in place.
1148  */
1149 #ifndef SolidDASolver
1150  // Get Data
1151  label nPoints, nFaces;
1152  List<word> patchList;
1153  this->getCouplingPatchList(patchList, groupName);
1154  this->getPatchInfo(nPoints, nFaces, patchList);
1155 
1156  // Allocate arrays
1157  List<scalar> xTemp(nFaces);
1158  List<scalar> yTemp(nFaces);
1159  List<scalar> zTemp(nFaces);
1160  List<scalar> nXTemp(nFaces);
1161  List<scalar> nYTemp(nFaces);
1162  List<scalar> nZTemp(nFaces);
1163  List<scalar> aTemp(nFaces);
1164  List<scalar> fXTemp(nFaces);
1165  List<scalar> fYTemp(nFaces);
1166  List<scalar> fZTemp(nFaces);
1167 
1168  // Compute forces
1169  this->getAcousticDataInternal(xTemp, yTemp, zTemp, nXTemp, nYTemp, nZTemp, aTemp, fXTemp, fYTemp, fZTemp, patchList);
1170 
1171  // Zero PETSc Arrays
1172  VecZeroEntries(x);
1173  VecZeroEntries(y);
1174  VecZeroEntries(z);
1175  VecZeroEntries(nX);
1176  VecZeroEntries(nY);
1177  VecZeroEntries(nZ);
1178  VecZeroEntries(a);
1179  VecZeroEntries(fX);
1180  VecZeroEntries(fY);
1181  VecZeroEntries(fZ);
1182 
1183  // Get PETSc arrays
1184  PetscScalar* vecArrayX;
1185  VecGetArray(x, &vecArrayX);
1186  PetscScalar* vecArrayY;
1187  VecGetArray(y, &vecArrayY);
1188  PetscScalar* vecArrayZ;
1189  VecGetArray(z, &vecArrayZ);
1190  PetscScalar* vecArrayNX;
1191  VecGetArray(nX, &vecArrayNX);
1192  PetscScalar* vecArrayNY;
1193  VecGetArray(nY, &vecArrayNY);
1194  PetscScalar* vecArrayNZ;
1195  VecGetArray(nZ, &vecArrayNZ);
1196  PetscScalar* vecArrayA;
1197  VecGetArray(a, &vecArrayA);
1198  PetscScalar* vecArrayFX;
1199  VecGetArray(fX, &vecArrayFX);
1200  PetscScalar* vecArrayFY;
1201  VecGetArray(fY, &vecArrayFY);
1202  PetscScalar* vecArrayFZ;
1203  VecGetArray(fZ, &vecArrayFZ);
1204 
1205  // Transfer to PETSc Array
1206  label pointCounter = 0;
1207  forAll(xTemp, cI)
1208  {
1209  // Get Values
1210  PetscScalar val1, val2, val3, val4, val5, val6, val7, val8, val9, val10;
1211  assignValueCheckAD(val1, xTemp[pointCounter]);
1212  assignValueCheckAD(val2, yTemp[pointCounter]);
1213  assignValueCheckAD(val3, zTemp[pointCounter]);
1214  assignValueCheckAD(val4, nXTemp[pointCounter]);
1215  assignValueCheckAD(val5, nYTemp[pointCounter]);
1216  assignValueCheckAD(val6, nZTemp[pointCounter]);
1217  assignValueCheckAD(val7, aTemp[pointCounter]);
1218  assignValueCheckAD(val8, fXTemp[pointCounter]);
1219  assignValueCheckAD(val9, fYTemp[pointCounter]);
1220  assignValueCheckAD(val10, fZTemp[pointCounter]);
1221 
1222  // Set Values
1223  vecArrayX[pointCounter] = val1;
1224  vecArrayY[pointCounter] = val2;
1225  vecArrayZ[pointCounter] = val3;
1226  vecArrayNX[pointCounter] = val4;
1227  vecArrayNY[pointCounter] = val5;
1228  vecArrayNZ[pointCounter] = val6;
1229  vecArrayA[pointCounter] = val7;
1230  vecArrayFX[pointCounter] = val8;
1231  vecArrayFY[pointCounter] = val9;
1232  vecArrayFZ[pointCounter] = val10;
1233 
1234  // Increment counter
1235  pointCounter += 1;
1236  }
1237  VecRestoreArray(x, &vecArrayX);
1238  VecRestoreArray(y, &vecArrayY);
1239  VecRestoreArray(z, &vecArrayZ);
1240  VecRestoreArray(nX, &vecArrayNX);
1241  VecRestoreArray(nY, &vecArrayNY);
1242  VecRestoreArray(nZ, &vecArrayNZ);
1243  VecRestoreArray(a, &vecArrayA);
1244  VecRestoreArray(fX, &vecArrayFX);
1245  VecRestoreArray(fY, &vecArrayFY);
1246  VecRestoreArray(fZ, &vecArrayFZ);
1247 #endif
1248  return;
1249 }
1250 
1252  label& nPoints,
1253  label& nFaces,
1254  List<word>& patchList)
1255 {
1256  /*
1257  Description:
1258  Compute information needed to compute surface forces and other vars on walls.
1259  This includes total number of nodes and faces and a list of patches to
1260  include in the computation.
1261 
1262  Inputs:
1263  nPoints: Number of nodes included in the force computation
1264 
1265  nFaces: number of faces
1266 
1267  patchList: Patches included in the force computation
1268 
1269  Outputs:
1270  nPoints and patchList are modified / set in-place
1271  */
1272  // Generate patches, point mesh, and point boundary mesh
1273  const pointMesh& pMesh = pointMesh::New(meshPtr_());
1274  const pointBoundaryMesh& boundaryMesh = pMesh.boundary();
1275 
1276  // compute size of point and connectivity arrays
1277  nPoints = 0;
1278  nFaces = 0;
1279  forAll(patchList, cI)
1280  {
1281  // Get number of points in patch
1282  label patchIPoints = boundaryMesh.findPatchID(patchList[cI]);
1283  nPoints += boundaryMesh[patchIPoints].size();
1284 
1285  // get number of faces in patch
1286  label patchI = meshPtr_->boundaryMesh().findPatchID(patchList[cI]);
1287  nFaces += meshPtr_->boundaryMesh()[patchI].size();
1288  }
1289  return;
1290 }
1291 
1293  List<scalar>& fX,
1294  List<scalar>& fY,
1295  List<scalar>& fZ,
1296  List<word>& patchList)
1297 {
1298  /*
1299  Description:
1300  Wrapped version force computation routine to perform match to compute forces specified patches.
1301 
1302  Inputs:
1303  fX: Vector of X-component of forces
1304 
1305  fY: Vector of Y-component of forces
1306 
1307  fZ: Vector of Z-component of forces
1308 
1309  Output:
1310  fX, fY, fZ, and patchList are modified / set in place.
1311  */
1312 #ifndef SolidDASolver
1313  // Get reference pressure
1314  dictionary couplingInfo = daOptionPtr_->getAllOptions().subDict("couplingInfo");
1315  scalar pRef = couplingInfo.subDict("aerostructural").getScalar("pRef");
1316 
1317  SortableList<word> patchListSort(patchList);
1318 
1319  // Initialize surface field for face-centered forces
1320  volVectorField volumeForceField(
1321  IOobject(
1322  "volumeForceField",
1323  meshPtr_->time().timeName(),
1324  meshPtr_(),
1325  IOobject::NO_READ,
1326  IOobject::NO_WRITE),
1327  meshPtr_(),
1328  dimensionedVector("surfaceForce", dimensionSet(1, 1, -2, 0, 0, 0, 0), vector::zero),
1329  fixedValueFvPatchScalarField::typeName);
1330 
1331  // this code is pulled from:
1332  // src/functionObjects/forces/forces.C
1333  // modified slightly
1334  vector force(vector::zero);
1335 
1336  const objectRegistry& db = meshPtr_->thisDb();
1337  const volScalarField& p = db.lookupObject<volScalarField>("p");
1338 
1339  const surfaceVectorField::Boundary& Sfb = meshPtr_->Sf().boundaryField();
1340 
1341  const DATurbulenceModel& daTurb = daModelPtr_->getDATurbulenceModel();
1342  tmp<volSymmTensorField> tdevRhoReff = daTurb.devRhoReff();
1343  const volSymmTensorField::Boundary& devRhoReffb = tdevRhoReff().boundaryField();
1344 
1345  const pointMesh& pMesh = pointMesh::New(meshPtr_());
1346  const pointBoundaryMesh& boundaryMesh = pMesh.boundary();
1347 
1348  // iterate over patches and extract boundary surface forces
1349  forAll(patchListSort, cI)
1350  {
1351  // get the patch id label
1352  label patchI = meshPtr_->boundaryMesh().findPatchID(patchListSort[cI]);
1353  // create a shorter handle for the boundary patch
1354  const fvPatch& patch = meshPtr_->boundary()[patchI];
1355  // normal force
1356  vectorField fN(Sfb[patchI] * (p.boundaryField()[patchI] - pRef));
1357  // tangential force
1358  vectorField fT(Sfb[patchI] & devRhoReffb[patchI]);
1359  // sum them up
1360  forAll(patch, faceI)
1361  {
1362  force.x() = fN[faceI].x() + fT[faceI].x();
1363  force.y() = fN[faceI].y() + fT[faceI].y();
1364  force.z() = fN[faceI].z() + fT[faceI].z();
1365  volumeForceField.boundaryFieldRef()[patchI][faceI] = force;
1366  }
1367  }
1368  volumeForceField.write();
1369 
1370  // The above volumeForceField is face-centered, we need to interpolate it to point-centered
1371  pointField meshPoints = meshPtr_->points();
1372 
1373  vector nodeForce(vector::zero);
1374 
1375  label patchStart = 0;
1376  forAll(patchListSort, cI)
1377  {
1378  // get the patch id label
1379  label patchI = meshPtr_->boundaryMesh().findPatchID(patchListSort[cI]);
1380  label patchIPoints = boundaryMesh.findPatchID(patchListSort[cI]);
1381 
1382  label nPointsPatch = boundaryMesh[patchIPoints].size();
1383  List<scalar> fXTemp(nPointsPatch);
1384  List<scalar> fYTemp(nPointsPatch);
1385  List<scalar> fZTemp(nPointsPatch);
1386  List<label> pointListTemp(nPointsPatch);
1387  pointListTemp = -1;
1388 
1389  label pointCounter = 0;
1390  // Loop over Faces
1391  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
1392  {
1393  // Get number of points
1394  const label nPoints = meshPtr_->boundaryMesh()[patchI][faceI].size();
1395 
1396  // Divide force to nodes
1397  nodeForce = volumeForceField.boundaryFieldRef()[patchI][faceI] / double(nPoints);
1398 
1399  forAll(meshPtr_->boundaryMesh()[patchI][faceI], pointI)
1400  {
1401  // this is the index that corresponds to meshPoints, which contains both volume and surface points
1402  // so we can't directly reuse this index because we want to have only surface points
1403  label faceIPointIndexI = meshPtr_->boundaryMesh()[patchI][faceI][pointI];
1404 
1405  // Loop over pointListTemp array to check if this node is already included in this patch
1406  bool found = false;
1407  label iPoint = -1;
1408  for (label i = 0; i < pointCounter; i++)
1409  {
1410  if (faceIPointIndexI == pointListTemp[i])
1411  {
1412  found = true;
1413  iPoint = i;
1414  break;
1415  }
1416  }
1417 
1418  // If node is already included, add value to its entry
1419  if (found)
1420  {
1421  // Add Force
1422  fXTemp[iPoint] += nodeForce[0];
1423  fYTemp[iPoint] += nodeForce[1];
1424  fZTemp[iPoint] += nodeForce[2];
1425  }
1426  // If node is not already included, add it as the newest point and add global index mapping
1427  else
1428  {
1429  // Add Force
1430  fXTemp[pointCounter] = nodeForce[0];
1431  fYTemp[pointCounter] = nodeForce[1];
1432  fZTemp[pointCounter] = nodeForce[2];
1433 
1434  // Add to Node Order Array
1435  pointListTemp[pointCounter] = faceIPointIndexI;
1436 
1437  // Increment counter
1438  pointCounter += 1;
1439  }
1440  }
1441  }
1442 
1443  // Sort Patch Indices and Insert into Global Arrays
1444  SortableList<label> pointListSort(pointListTemp);
1445  forAll(pointListSort.indices(), indexI)
1446  {
1447  fX[patchStart + indexI] = fXTemp[pointListSort.indices()[indexI]];
1448  fY[patchStart + indexI] = fYTemp[pointListSort.indices()[indexI]];
1449  fZ[patchStart + indexI] = fZTemp[pointListSort.indices()[indexI]];
1450  }
1451 
1452  // Increment Patch Start Index
1453  patchStart += nPointsPatch;
1454  }
1455 #endif
1456  return;
1457 }
1458 
1460  List<scalar>& x,
1461  List<scalar>& y,
1462  List<scalar>& z,
1463  List<scalar>& nX,
1464  List<scalar>& nY,
1465  List<scalar>& nZ,
1466  List<scalar>& a,
1467  List<scalar>& fX,
1468  List<scalar>& fY,
1469  List<scalar>& fZ,
1470  List<word>& patchList)
1471 {
1472  /*
1473  Description:
1474  Wrapped version force computation routine to perform match to compute forces specified patches.
1475 
1476  Inputs:
1477  x: Vector of X-component coordinates
1478 
1479  y: Vector of Y-component coordinates
1480 
1481  z: Vector of Z-component coordinates
1482 
1483  nX: Vector of X-component of normal vectors
1484 
1485  nY: Vector of Y-component of normal vectors
1486 
1487  nZ: Vector of Z-component of normal vectors
1488 
1489  a: Vector of areas
1490 
1491  fX: Vector of X-component of forces
1492 
1493  fY: Vector of Y-component of forces
1494 
1495  fZ: Vector of Z-component of forces
1496 
1497  patchList: Lift of patches to use for computation
1498 
1499  Output:
1500  x, y, z, nX, nY, nZ, a, fX, fY, fZ, and patchList are modified / set in place.
1501  */
1502 #ifndef SolidDASolver
1503  // Get reference pressure
1504  scalar pRef;
1505  daOptionPtr_->getAllOptions().subDict("couplingInfo").subDict("aeroacoustic").readEntry<scalar>("pRef", pRef);
1506 
1507  SortableList<word> patchListSort(patchList);
1508 
1509  // ========================================================================
1510  // Compute Values
1511  // ========================================================================
1512  // this code is pulled from:
1513  // src/functionObjects/forcces/forces.C
1514  // modified slightly
1515  const objectRegistry& db = meshPtr_->thisDb();
1516  const volScalarField& p = db.lookupObject<volScalarField>("p");
1517 
1518  const surfaceVectorField::Boundary& Sfb = meshPtr_->Sf().boundaryField();
1519 
1520  const DATurbulenceModel& daTurb = daModelPtr_->getDATurbulenceModel();
1521  tmp<volSymmTensorField> tdevRhoReff = daTurb.devRhoReff();
1522  const volSymmTensorField::Boundary& devRhoReffb = tdevRhoReff().boundaryField();
1523 
1524  // iterate over patches and extract boundary surface forces
1525  label iFace = 0;
1526  forAll(patchListSort, cI)
1527  {
1528  // get the patch id label
1529  label patchI = meshPtr_->boundaryMesh().findPatchID(patchListSort[cI]);
1530  // create a shorter handle for the boundary patch
1531  const fvPatch& patch = meshPtr_->boundary()[patchI];
1532 
1533  // normal force
1534  vectorField fN(Sfb[patchI] * (p.boundaryField()[patchI] - pRef));
1535  // tangential force
1536  vectorField fT(Sfb[patchI] & devRhoReffb[patchI]);
1537 
1538  // Store Values of Faces
1539  forAll(patch, faceI)
1540  {
1541  // Position
1542  x[iFace] = patch.Cf()[faceI].x();
1543  y[iFace] = patch.Cf()[faceI].y();
1544  z[iFace] = patch.Cf()[faceI].z();
1545 
1546  // Normal
1547  nX[iFace] = -patch.Sf()[faceI].x() / patch.magSf()[faceI];
1548  nY[iFace] = -patch.Sf()[faceI].y() / patch.magSf()[faceI];
1549  nZ[iFace] = -patch.Sf()[faceI].z() / patch.magSf()[faceI];
1550 
1551  // Area
1552  a[iFace] = patch.magSf()[faceI];
1553 
1554  // Force
1555  fX[iFace] = -(fN[faceI].x() + fT[faceI].x());
1556  fY[iFace] = -(fN[faceI].y() + fT[faceI].y());
1557  fZ[iFace] = -(fN[faceI].z() + fT[faceI].z());
1558 
1559  iFace++;
1560  }
1561  }
1562 #endif
1563  return;
1564 }
1565 
1567  Vec center,
1568  Vec aForceL,
1569  Vec tForceL,
1570  Vec rDistL)
1571 {
1572  /*
1573  Description:
1574  Calculate the radial profile of forces on the propeller surface
1575  We need to call this function from the propeller component
1576 
1577  Input:
1578  State variables
1579 
1580  Output:
1581  xForce, the radial profile of force in the x direction
1582  */
1583 
1584  /*
1585 
1586  // Get Data
1587  label nPoints = daOptionPtr_->getSubDictOption<scalar>("wingProp", "nForceSections");
1588  fvMesh& mesh = meshPtr_();
1589 
1590  // Allocate Arrays
1591  Vector<scalar> centerTemp;
1592  Field<scalar> aForceTemp(nPoints);
1593  Field<scalar> tForceTemp(nPoints);
1594  List<scalar> rDistLTemp(nPoints+2);
1595 
1596  // Get PETSc Arrays
1597  PetscScalar* vecArrayCenter;
1598  VecGetArray(center, &vecArrayCenter);
1599 
1600  // Set Values
1601  centerTemp[0] = vecArrayCenter[0];
1602  centerTemp[1] = vecArrayCenter[1];
1603  centerTemp[2] = vecArrayCenter[2];
1604 
1605  // Compute force profiles
1606  this->calcForceProfileInternal(mesh, centerTemp, aForceTemp, tForceTemp, rDistLTemp);
1607 
1608  VecZeroEntries(aForceL);
1609  VecZeroEntries(tForceL);
1610  VecZeroEntries(rDistL);
1611  PetscScalar* vecArrayAForceL;
1612  VecGetArray(aForceL, &vecArrayAForceL);
1613  PetscScalar* vecArrayTForceL;
1614  VecGetArray(tForceL, &vecArrayTForceL);
1615  PetscScalar* vecArrayRDistL;
1616  VecGetArray(rDistL, &vecArrayRDistL);
1617 
1618  // Tranfer to PETSc Array for force profiles and radius
1619  forAll(aForceTemp, cI)
1620  {
1621  // Get Values
1622  PetscScalar val1, val2;
1623  assignValueCheckAD(val1, aForceTemp[cI]);
1624  assignValueCheckAD(val2, tForceTemp[cI]);
1625 
1626  // Set Values
1627  vecArrayAForceL[cI] = val1;
1628  vecArrayTForceL[cI] = val2;
1629  }
1630  forAll(aForceTemp, cI)
1631  {
1632  // Get Values
1633  PetscScalar val1;
1634  assignValueCheckAD(val1, rDistLTemp[cI]);
1635 
1636  // Set Values
1637  vecArrayRDistL[cI] = val1;
1638  }
1639 
1640  VecRestoreArray(center, &vecArrayCenter);
1641  VecRestoreArray(aForceL, &vecArrayAForceL);
1642  VecRestoreArray(tForceL, &vecArrayTForceL);
1643  VecRestoreArray(rDistL, &vecArrayRDistL);
1644 
1645  return;
1646  */
1647 }
1648 
1650  fvMesh& mesh,
1651  const vector& center,
1652  scalarList& aForceL,
1653  scalarList& tForceL,
1654  scalarList& rDistL)
1655 {
1656  /*
1657  Description:
1658  Same as calcForceProfile but for internal AD
1659  */
1660 
1661  /*
1662 
1663  // fvMesh& mesh = meshPtr_();
1664  scalar sections = daOptionPtr_->getSubDictOption<scalar>("wingProp", "nForceSections");
1665  scalarList axisDummy = daOptionPtr_->getSubDictOption<scalarList>("wingProp", "axis");
1666  vector axis;
1667  axis[0] = axisDummy[0];
1668  axis[1] = axisDummy[1];
1669  axis[2] = axisDummy[2];
1670 
1671  int quot;
1672  vector cellDir, projP, cellCen;
1673  scalar length, axialDist;
1674 
1675  // get the pressure in the memory
1676  const volScalarField& p = mesh.thisDb().lookupObject<volScalarField>("p");
1677 
1678  // name of the blade
1679  word bladePatchName = "propeller";
1680 
1681  // find the patch ID of the blade surface
1682  label bladePatchI = mesh.boundaryMesh().findPatchID(bladePatchName);
1683 
1684  // radiiCell initialization, cell radii will be stored in it
1685  scalarList radiiCell(p.boundaryField()[bladePatchI].size());
1686 
1687  // meshTanDir initialization, mesh tangential direction will be stored in it
1688  vectorField meshTanDir = mesh.Cf().boundaryField()[bladePatchI] * 0;
1689 
1690  // radius limits initialization
1691  scalar minRadius = 1000000;
1692  scalar minRadiIndx = -1;
1693  scalar maxRadius = -1000000;
1694  scalar maxRadiIndx = -1;
1695 
1696  forAll(p.boundaryField()[bladePatchI], faceI)
1697  {
1698  // directional vector from the propeller center to the cell center & dictance between them
1699  cellCen = mesh.Cf().boundaryField()[bladePatchI][faceI];
1700  cellDir = cellCen - center;
1701  length = Foam:: sqrt(sqr(cellDir[0])+sqr(cellDir[1])+sqr(cellDir[2]));
1702 
1703  // unit vector conversion
1704  cellDir = cellDir / length;
1705 
1706  // axial distance between the propeller center and the cell center
1707  axialDist = (cellDir & axis) * length;
1708 
1709  // projected point of the cell center on the axis
1710  projP = {center[0] + axis[0] * axialDist, center[1] + axis[1] * axialDist, center[2] + axis[2] * axialDist};
1711 
1712  // radius of the cell center
1713  radiiCell[faceI] = Foam::sqrt(sqr(cellCen[0] - projP[0]) + sqr(cellCen[1] - projP[1]) + sqr(cellCen[2] - projP[2]));
1714 
1715  if(radiiCell[faceI] < minRadius)
1716  {
1717  minRadius = radiiCell[faceI];
1718  minRadiIndx = faceI;
1719  }
1720  if(radiiCell[faceI] > maxRadius)
1721  {
1722  maxRadius = radiiCell[faceI];
1723  maxRadiIndx = faceI;
1724  }
1725 
1726  // storing tangential vector as a unit vector
1727  meshTanDir[faceI] = axis ^ cellDir;
1728  length = Foam:: sqrt(sqr(meshTanDir[faceI][0])+sqr(meshTanDir[faceI][1])+sqr(meshTanDir[faceI][2]));
1729  meshTanDir[faceI] = meshTanDir[faceI] / length;
1730  }
1731 
1732  // generating empty lists
1733  scalarList axialForce(sections);
1734  forAll(axialForce, Index){axialForce[Index] = 0;}
1735  scalarList tangtForce = axialForce;
1736  scalarList radialDist(sections + 2);
1737 
1738  // sectional radius computation
1739  scalar sectRad = (maxRadius - minRadius) / sections;
1740  radialDist[0] = minRadius;
1741  radialDist[sections + 1] = maxRadius;
1742  for(int Index = 1; Index < sections + 1; Index++){radialDist[Index] = minRadius + sectRad * (Index - 0.5);}
1743  scalarList counter = axialForce;
1744 
1745  // computation of forces
1746  forAll(p.boundaryField()[bladePatchI], faceI)
1747  {
1748  // finding the section of the cell
1749  quot = (radiiCell[faceI] - minRadius) / sectRad;
1750  if(quot == sections){quot = quot - 1;}
1751 
1752  // pressure direction is opposite of the surface normal
1753  axialForce[quot] = axialForce[quot] - (mesh.Sf().boundaryField()[bladePatchI][faceI] & axis) * p.boundaryField()[bladePatchI][faceI] / sectRad;
1754  tangtForce[quot] = tangtForce[quot] - (mesh.Sf().boundaryField()[bladePatchI][faceI] & meshTanDir[faceI]) * p.boundaryField()[bladePatchI][faceI] / sectRad;
1755  counter[quot] = counter[quot] + 1;
1756  }
1757  */
1758 }
1759 
1761  const word mode,
1762  Vec xvVec,
1763  Vec stateVec,
1764  Vec psiVec,
1765  Vec prodVec)
1766 {
1767 }
1768 
1770  const word propName,
1771  const scalarField& aForce,
1772  const scalarField& tForce,
1773  const scalarField& rDist,
1774  const scalarList& targetForce,
1775  const vector& center,
1776  volVectorField& fvSource)
1777 {
1778  /*
1779  Description:
1780  Smoothing the force distribution on propeller blade on the entire mesh to ensure that it will not diverge during optimization.
1781  Forces are smoothed using 4th degree polynomial distribution for inner radius, normal distribution for outer radius, and Gaussiam distribution for axial direction.
1782  Inputs:
1783  aForce: Axis force didtribution on propeller blade
1784  tForce: Tangential force distribution on propeller blade
1785  rDist: Force distribution locations and radii of propeller (first element is inner radius, last element is outer radius)
1786  Output:
1787  fvSource: Smoothed forces in each mesh cell
1788  */
1789 
1790  vector axis;
1791  const dictionary& propSubDict = daOptionPtr_->getAllOptions().subDict("wingProp").subDict(propName);
1792  scalar actEps = propSubDict.getScalar("actEps");
1793  word rotDir = propSubDict.getWord("rotDir");
1794  word interpScheme = propSubDict.getWord("interpScheme");
1795  scalarList axisDummy;
1796  propSubDict.readEntry<scalarList>("axis", axisDummy);
1797  axis[0] = axisDummy[0];
1798  axis[1] = axisDummy[1];
1799  axis[2] = axisDummy[2];
1800 
1801  if (interpScheme == "poly4Gauss")
1802  {
1803  // Fit a 4th order polynomial for the inner and Gaussian function for the outer
1804 
1805  scalar rotDirCon = 0.0;
1806  if (rotDir == "right")
1807  {
1808  rotDirCon = -1.0;
1809  }
1810  else if (rotDir == "left")
1811  {
1812  rotDirCon = 1.0;
1813  }
1814  else
1815  {
1816  FatalErrorIn("calcFvSourceInternal") << "Rotation direction must be either right of left"
1817  << abort(FatalError);
1818  }
1819 
1820  // meshC is the cell center coordinates & meshV is the cell volume
1821  const volVectorField& meshC = fvSource.mesh().C();
1822  const scalarField& meshV = fvSource.mesh().V();
1823 
1824  // dummy vector field for storing the tangential vector of each cell
1825  volVectorField meshTanDir = meshC * 0;
1826 
1827  // Normalization of the blade radius distribution.
1828  scalar rOuter = (3 * rDist[rDist.size() - 1] - rDist[rDist.size() - 2]) / 2;
1829  scalarField rNorm = rDist; // normalized blade radius distribution
1830  forAll(rDist, index)
1831  {
1832  rNorm[index] = rDist[index] / rOuter;
1833  }
1834 
1835  // Inner and outer radius distribution limits
1836  scalar rStarMin = rNorm[0];
1837  scalar rStarMax = rNorm[rNorm.size() - 1];
1838 
1839  // Polynomial (inner) and Normal (outer) distribution parameters' initialization
1840  scalar f1 = aForce[aForce.size() - 2];
1841  scalar f2 = aForce[aForce.size() - 1];
1842  scalar f3 = aForce[0];
1843  scalar f4 = aForce[1];
1844  scalar g1 = tForce[tForce.size() - 2];
1845  scalar g2 = tForce[tForce.size() - 1];
1846  scalar g3 = tForce[0];
1847  scalar g4 = tForce[1];
1848  scalar r1 = rNorm[rNorm.size() - 2];
1849  scalar r2 = rNorm[rNorm.size() - 1];
1850  scalar r3 = rNorm[0];
1851  scalar r4 = rNorm[1];
1852  scalar df3 = (f4 - f3) / (r4 - r3);
1853  scalar dg3 = (g4 - g3) / (r4 - r3);
1854 
1855  // Polynomial (inner) and Normal (outer) distribution parameters' computation
1856  // Axial Outer
1857  scalar mu = 2 * r1 - r2;
1858  scalar maxI = 100;
1859  scalar sigmaS = 0;
1860  scalar i = 0;
1861  for (i = 0; i < maxI; i++)
1862  {
1863  sigmaS = ((r2 - mu) * (r2 - mu) - (r1 - mu) * (r1 - mu)) / (2 * log(f1 / f2));
1864  mu = r1 - sqrt(-2 * sigmaS * log(f1 * sqrt(2 * degToRad(180) * sigmaS)));
1865  if (mu > r1)
1866  {
1867  mu = 2 * r1 - mu;
1868  }
1869  }
1870  scalar sigmaAxialOut = sqrt(sigmaS);
1871  scalar muAxialOut = mu;
1872 
1873  // Tangential Outer
1874  mu = 2 * r1 - r2;
1875  for (i = 0; i < maxI; i++)
1876  {
1877  sigmaS = ((r2 - mu) * (r2 - mu) - (r1 - mu) * (r1 - mu)) / (2 * log(g1 / g2));
1878  mu = r1 - sqrt(-2 * sigmaS * log(g1 * sqrt(2 * degToRad(180) * sigmaS)));
1879  if (mu > r1)
1880  {
1881  mu = 2 * r1 - mu;
1882  }
1883  }
1884  scalar sigmaTangentialOut = sqrt(sigmaS);
1885  scalar muTangentialOut = mu;
1886 
1887  // Axial Inner
1888  scalar coefAAxialIn = (df3 * r3 - 2 * f3) / (2 * pow(r3, 4));
1889  scalar coefBAxialIn = (f3 - coefAAxialIn * pow(r3, 4)) / (r3 * r3);
1890 
1891  // Tangential Inner
1892  scalar coefATangentialIn = (dg3 * r3 - 2 * g3) / (2 * pow(r3, 4));
1893  scalar coefBTangentialIn = (g3 - coefATangentialIn * pow(r3, 4)) / (r3 * r3);
1894 
1895  // Cell 3D force computation loop
1896  forAll(meshC, cellI)
1897  {
1898  // Finding directional vector from mesh cell to the actuator center
1899  vector cellDir = meshC[cellI] - center;
1900  scalar length = sqrt(sqr(cellDir[0]) + sqr(cellDir[1]) + sqr(cellDir[2]));
1901  cellDir = cellDir / length;
1902 
1903  // Finding axial distance from mesh cell to the actuator center & projected point of mesh cell on the axis
1904  scalar meshDist = (axis & cellDir) * length;
1905  vector projP = {center[0] - axis[0] * meshDist, center[1] - axis[1] * meshDist, center[2] - axis[2] * meshDist};
1906  meshDist = mag(meshDist);
1907 
1908  // Finding the radius of the point
1909  scalar meshR = sqrt(sqr(meshC[cellI][0] - projP[0]) + sqr(meshC[cellI][1] - projP[1]) + sqr(meshC[cellI][2] - projP[2]));
1910 
1911  // Tangential component of the radius vector of the cell center
1912  vector cellAxDir = cellDir ^ axis;
1913 
1914  // Storing the tangential component
1915  meshTanDir[cellI] = cellAxDir;
1916 
1917  scalar rStar = meshR / rOuter;
1918 
1919  if (rStar < rStarMin)
1920  {
1921  fvSource[cellI] = ((coefAAxialIn * pow(rStar, 4) + coefBAxialIn * pow(rStar, 2)) * axis + (coefATangentialIn * pow(rStar, 4) + coefBTangentialIn * pow(rStar, 2)) * cellAxDir * rotDirCon) * exp(-sqr(meshDist / actEps));
1922  }
1923  else if (rStar > rStarMax)
1924  {
1925  fvSource[cellI] = (1 / (sigmaAxialOut * sqrt(2 * degToRad(180)))) * exp(-0.5 * sqr((rStar - muAxialOut) / sigmaAxialOut)) * axis;
1926  fvSource[cellI] = fvSource[cellI] + (1 / (sigmaTangentialOut * sqrt(2 * degToRad(180)))) * exp(-0.5 * sqr((rStar - muTangentialOut) / sigmaTangentialOut)) * cellAxDir * rotDirCon;
1927  fvSource[cellI] = fvSource[cellI] * exp(-sqr(meshDist / actEps));
1928  }
1929  else
1930  {
1931  fvSource[cellI] = (interpolateSplineXY(rStar, rNorm, aForce) * axis + interpolateSplineXY(rStar, rNorm, tForce) * cellAxDir * rotDirCon) * exp(-sqr(meshDist / actEps));
1932  }
1933  }
1934 
1935  // Scale factor computation loop
1936  scalar scaleAxial = 0;
1937  scalar scaleTangential = 0;
1938  forAll(meshV, cellI)
1939  {
1940  scaleAxial = scaleAxial + (fvSource[cellI] & axis) * meshV[cellI];
1941  scaleTangential = scaleTangential + (fvSource[cellI] & meshTanDir[cellI]) * meshV[cellI];
1942  }
1943  reduce(scaleAxial, sumOp<scalar>());
1944  reduce(scaleTangential, sumOp<scalar>());
1945  scaleAxial = targetForce[0] / scaleAxial;
1946  scaleTangential = targetForce[1] / scaleTangential * rotDirCon;
1947 
1948  // Cell 3D force scaling loop
1949  forAll(meshV, cellI)
1950  {
1951  fvSource[cellI][0] = fvSource[cellI][0] * mag(axis[0]) * scaleAxial + fvSource[cellI][0] * mag(meshTanDir[cellI][0]) * scaleTangential;
1952  fvSource[cellI][1] = fvSource[cellI][1] * mag(axis[1]) * scaleAxial + fvSource[cellI][1] * mag(meshTanDir[cellI][1]) * scaleTangential;
1953  fvSource[cellI][2] = fvSource[cellI][2] * mag(axis[2]) * scaleAxial + fvSource[cellI][2] * mag(meshTanDir[cellI][2]) * scaleTangential;
1954  }
1955  }
1956  else if (interpScheme == "gauss")
1957  {
1958  fvMesh& mesh = meshPtr_();
1959 
1960  scalar rInner = rDist[0];
1961  scalar rOuter = rDist[rDist.size() - 1];
1962  scalar fAxialInner = aForce[0];
1963  scalar fAxialOuter = aForce[aForce.size() - 1];
1964  scalar fTanInner = tForce[0];
1965  scalar fTanOuter = tForce[tForce.size() - 1];
1966 
1967  vector dirNorm = {axis[0], axis[1], axis[2]};
1968  dirNorm /= mag(axis);
1969 
1970  // first loop, we calculate the integral force and then compute the scaling factor
1971  scalar axialForceSum = 0.0;
1972  scalar tangentialForceSum = 0.0;
1973  forAll(mesh.cells(), cellI)
1974  {
1975  // the cell center coordinates of this cellI
1976  vector cellC = mesh.C()[cellI];
1977  // cell center to disk center vector
1978  vector cellC2AVec = cellC - center;
1979  // tmp tensor for calculating the axial/radial components of cellC2AVec
1980  tensor cellC2AVecE(tensor::zero);
1981  cellC2AVecE.xx() = cellC2AVec.x();
1982  cellC2AVecE.yy() = cellC2AVec.y();
1983  cellC2AVecE.zz() = cellC2AVec.z();
1984 
1985  // now we need to decompose cellC2AVec into axial and radial components
1986  // the axial component of cellC2AVec vector
1987  vector cellC2AVecA = cellC2AVecE & dirNorm;
1988  // the radial component of cellC2AVec vector
1989  vector cellC2AVecR = cellC2AVec - cellC2AVecA;
1990 
1991  // the magnitude of radial component of cellC2AVecR
1992  scalar cellC2AVecRLen = mag(cellC2AVecR);
1993  // the magnitude of axial component of cellC2AVecR
1994  scalar cellC2AVecALen = mag(cellC2AVecA);
1995 
1996  scalar fAxial = 0.0;
1997  scalar fTan = 0.0;
1998 
1999  scalar dA2_Eps2 = (cellC2AVecALen * cellC2AVecALen) / actEps / actEps;
2000 
2001  // we need to smooth the force in the radial and axial directions if r is outside of [rInner:rOuter]
2002  // if r is inside, we just interpolate the prescribed aForce and tForce and smooth the
2003  // force in the axial direction only
2004  if (cellC2AVecRLen < rInner)
2005  {
2006  scalar dR2_Eps2 = (cellC2AVecRLen - rInner) * (cellC2AVecRLen - rInner) / actEps / actEps;
2007  fAxial = fAxialInner * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2008  fTan = fTanInner * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2009  }
2010  else if (cellC2AVecRLen >= rInner && cellC2AVecRLen <= rOuter)
2011  {
2012  fAxial = interpolateSplineXY(cellC2AVecRLen, rDist, aForce) * exp(-dA2_Eps2);
2013  fTan = interpolateSplineXY(cellC2AVecRLen, rDist, tForce) * exp(-dA2_Eps2);
2014  }
2015  else
2016  {
2017  scalar dR2_Eps2 = (cellC2AVecRLen - rOuter) * (cellC2AVecRLen - rOuter) / actEps / actEps;
2018  fAxial = fAxialOuter * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2019  fTan = fTanOuter * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2020  }
2021 
2022  axialForceSum += fAxial * mesh.V()[cellI];
2023  tangentialForceSum += fTan * mesh.V()[cellI];
2024  }
2025  reduce(axialForceSum, sumOp<scalar>());
2026  reduce(tangentialForceSum, sumOp<scalar>());
2027 
2028  scalar aForceScale = targetForce[0] / axialForceSum;
2029  scalar tForceScale = targetForce[1] / tangentialForceSum;
2030 
2031  // loop again with the correct scale so that the integral forces matches the prescribed ones
2032  axialForceSum = 0;
2033  tangentialForceSum = 0;
2034  forAll(mesh.cells(), cellI)
2035  {
2036  // the cell center coordinates of this cellI
2037  vector cellC = mesh.C()[cellI];
2038  // cell center to disk center vector
2039  vector cellC2AVec = cellC - center;
2040  // tmp tensor for calculating the axial/radial components of cellC2AVec
2041  tensor cellC2AVecE(tensor::zero);
2042  cellC2AVecE.xx() = cellC2AVec.x();
2043  cellC2AVecE.yy() = cellC2AVec.y();
2044  cellC2AVecE.zz() = cellC2AVec.z();
2045 
2046  // now we need to decompose cellC2AVec into axial and radial components
2047  // the axial component of cellC2AVec vector
2048  vector cellC2AVecA = cellC2AVecE & dirNorm;
2049  // the radial component of cellC2AVec vector
2050  vector cellC2AVecR = cellC2AVec - cellC2AVecA;
2051 
2052  // now we can use the cross product to compute the tangential
2053  // (circ) direction of cellI
2054  vector cellC2AVecC(vector::zero);
2055  if (rotDir == "left")
2056  {
2057  // propeller rotates counter-clockwise viewed from the tail of the aircraft looking forward
2058  cellC2AVecC = cellC2AVecR ^ dirNorm; // circ
2059  }
2060  else if (rotDir == "right")
2061  {
2062  // propeller rotates clockwise viewed from the tail of the aircraft looking forward
2063  cellC2AVecC = dirNorm ^ cellC2AVecR; // circ
2064  }
2065  else
2066  {
2067  FatalErrorIn(" ") << "rotDir not valid" << abort(FatalError);
2068  }
2069 
2070  // the magnitude of radial component of cellC2AVecR
2071  scalar cellC2AVecRLen = mag(cellC2AVecR);
2072  // the magnitude of tangential component of cellC2AVecR
2073  scalar cellC2AVecCLen = mag(cellC2AVecC);
2074  // the magnitude of axial component of cellC2AVecR
2075  scalar cellC2AVecALen = mag(cellC2AVecA);
2076  // the normalized cellC2AVecC (tangential) vector
2077  vector cellC2AVecCNorm = cellC2AVecC / cellC2AVecCLen;
2078 
2079  scalar fAxial = 0.0;
2080  scalar fTan = 0.0;
2081 
2082  scalar dA2_Eps2 = (cellC2AVecALen * cellC2AVecALen) / actEps / actEps;
2083 
2084  // we need to smooth the force in the radial and axial directions if r is outside of [rInner:rOuter]
2085  // if r is inside, we just interpolate the prescribed aForce and tForce and smooth the
2086  // force in the axial direction only
2087  if (cellC2AVecRLen < rInner)
2088  {
2089  scalar dR2_Eps2 = (cellC2AVecRLen - rInner) * (cellC2AVecRLen - rInner) / actEps / actEps;
2090  fAxial = aForceScale * fAxialInner * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2091  fTan = tForceScale * fTanInner * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2092  }
2093  else if (cellC2AVecRLen >= rInner && cellC2AVecRLen <= rOuter)
2094  {
2095  fAxial = aForceScale * interpolateSplineXY(cellC2AVecRLen, rDist, aForce) * exp(-dA2_Eps2);
2096  fTan = tForceScale * interpolateSplineXY(cellC2AVecRLen, rDist, tForce) * exp(-dA2_Eps2);
2097  }
2098  else
2099  {
2100  scalar dR2_Eps2 = (cellC2AVecRLen - rOuter) * (cellC2AVecRLen - rOuter) / actEps / actEps;
2101  fAxial = aForceScale * fAxialOuter * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2102  fTan = tForceScale * fTanOuter * exp(-dR2_Eps2) * exp(-dA2_Eps2);
2103  }
2104 
2105  vector sourceVec = (fAxial * dirNorm + fTan * cellC2AVecCNorm);
2106 
2107  axialForceSum += fAxial * mesh.V()[cellI];
2108  tangentialForceSum += fTan * mesh.V()[cellI];
2109 
2110  fvSource[cellI] += sourceVec;
2111  }
2112 
2113  reduce(axialForceSum, sumOp<scalar>());
2114  reduce(tangentialForceSum, sumOp<scalar>());
2115 
2116  if (daOptionPtr_->getOption<word>("runStatus") == "solvePrimal")
2117  {
2118  Info << "Integrated Axial Force for " << propName << ": " << axialForceSum << endl;
2119  Info << "Integrated Tangential Force for " << propName << ": " << tangentialForceSum << endl;
2120  }
2121  }
2122  else
2123  {
2124  FatalErrorIn("") << "interpScheme not valid! Options: poly4Gauss or gauss"
2125  << abort(FatalError);
2126  }
2127 }
2128 
2130  const word propName,
2131  Vec aForce,
2132  Vec tForce,
2133  Vec rDist,
2134  Vec targetForce,
2135  Vec center,
2136  Vec fvSource)
2137 {
2138  /*
2139  Description:
2140  Calculate the fvSource based on the radial force profile and the propeller parameters
2141  We need to call this function from the wing component
2142 
2143  Input:
2144  parameters: propeller parameters, i.e., center_x, center_y, center_z, r_inner, r_outer
2145 
2146  force: the radial force profiles (fx1, fy1, fz1, fx2, fy2, fz2, ... )
2147 
2148  Output:
2149  fvSource: a volVectorField variable that will be added to the momentum eqn
2150  */
2151 
2152  // Get Data
2153  const dictionary& propSubDict = daOptionPtr_->getAllOptions().subDict("wingProp").subDict(propName);
2154  label nPoints = propSubDict.getLabel("nForceSections");
2155  // label meshSize = meshPtr_->nCells();
2156 
2157  // Allocate Arrays
2158  Field<scalar> aForceTemp(nPoints);
2159  Field<scalar> tForceTemp(nPoints);
2160  Field<scalar> rDistTemp(nPoints);
2161  List<scalar> targetForceTemp(2);
2162  Vector<scalar> centerTemp;
2163  volVectorField fvSourceTemp(
2164  IOobject(
2165  "fvSourceTemp",
2166  meshPtr_->time().timeName(),
2167  meshPtr_(),
2168  IOobject::NO_READ,
2169  IOobject::NO_WRITE),
2170  meshPtr_(),
2171  dimensionedVector("surfaceForce", dimensionSet(0, 0, 0, 0, 0, 0, 0), vector::zero),
2172  fixedValueFvPatchScalarField::typeName);
2173 
2174  // Get PETSc Arrays
2175  PetscScalar* vecArrayAForce;
2176  VecGetArray(aForce, &vecArrayAForce);
2177  PetscScalar* vecArrayTForce;
2178  VecGetArray(tForce, &vecArrayTForce);
2179  PetscScalar* vecArrayRDist;
2180  VecGetArray(rDist, &vecArrayRDist);
2181  PetscScalar* vecArrayTargetForce;
2182  VecGetArray(targetForce, &vecArrayTargetForce);
2183  PetscScalar* vecArrayCenter;
2184  VecGetArray(center, &vecArrayCenter);
2185 
2186  // Set Values
2187  forAll(aForceTemp, cI)
2188  {
2189  aForceTemp[cI] = vecArrayAForce[cI];
2190  tForceTemp[cI] = vecArrayTForce[cI];
2191  rDistTemp[cI] = vecArrayRDist[cI];
2192  }
2193  targetForceTemp[0] = vecArrayTargetForce[0];
2194  targetForceTemp[1] = vecArrayTargetForce[1];
2195  centerTemp[0] = vecArrayCenter[0];
2196  centerTemp[1] = vecArrayCenter[1];
2197  centerTemp[2] = vecArrayCenter[2];
2198 
2199  // Compute fvSource
2200  this->calcFvSourceInternal(propName, aForceTemp, tForceTemp, rDistTemp, targetForceTemp, centerTemp, fvSourceTemp);
2201 
2202  VecZeroEntries(fvSource);
2203  PetscScalar* vecArrayFvSource;
2204  VecGetArray(fvSource, &vecArrayFvSource);
2205 
2206  // Tranfer to PETSc Array for fvSource
2207  forAll(fvSourceTemp, cI)
2208  {
2209  // Get Values
2210  PetscScalar val1, val2, val3;
2211  assignValueCheckAD(val1, fvSourceTemp[cI][0]);
2212  assignValueCheckAD(val2, fvSourceTemp[cI][1]);
2213  assignValueCheckAD(val3, fvSourceTemp[cI][2]);
2214 
2215  // Set Values
2216  vecArrayFvSource[3 * cI] = val1;
2217  vecArrayFvSource[3 * cI + 1] = val2;
2218  vecArrayFvSource[3 * cI + 2] = val3;
2219  }
2220 
2221  VecRestoreArray(aForce, &vecArrayAForce);
2222  VecRestoreArray(tForce, &vecArrayTForce);
2223  VecRestoreArray(rDist, &vecArrayRDist);
2224  VecRestoreArray(targetForce, &vecArrayTargetForce);
2225  VecRestoreArray(center, &vecArrayCenter);
2226  VecRestoreArray(fvSource, &vecArrayFvSource);
2227 
2228  return;
2229 }
2230 
2232  const word propName,
2233  const word mode,
2234  Vec aForce,
2235  Vec tForce,
2236  Vec rDist,
2237  Vec targetForce,
2238  Vec center,
2239  Vec psi,
2240  Vec dFvSource)
2241 {
2242  /*
2243  Description:
2244  Calculate the matrix-vector product for either [dFvSource/dParameters]^T * psi, or [dFvSource/dForce]^T * psi
2245  */
2246 
2247 #ifdef CODI_AD_REVERSE
2248 
2249  Info << "Calculating [dFvSource/dInputs]^T*Psi using reverse-mode AD. PropName: "
2250  << propName << " mode: " << mode << endl;
2251 
2252  VecZeroEntries(dFvSource);
2253 
2254  const dictionary& propSubDict = daOptionPtr_->getAllOptions().subDict("wingProp").subDict(propName);
2255  label nPoints = propSubDict.getLabel("nForceSections");
2256 
2257  Field<scalar> aForceField(nPoints);
2258  Field<scalar> tForceField(nPoints);
2259  Field<scalar> rDistField(nPoints);
2260  List<scalar> targetForceList(2);
2261  vector centerVector = vector::zero;
2262 
2263  volVectorField fvSourceVField(
2264  IOobject(
2265  "fvSourceVField",
2266  meshPtr_->time().timeName(),
2267  meshPtr_(),
2268  IOobject::NO_READ,
2269  IOobject::NO_WRITE),
2270  meshPtr_(),
2271  dimensionedVector("surfaceForce", dimensionSet(0, 0, 0, 0, 0, 0, 0), vector::zero),
2272  fixedValueFvPatchScalarField::typeName);
2273 
2274  PetscScalar* vecArrayAForce;
2275  PetscScalar* vecArrayTForce;
2276  PetscScalar* vecArrayRDist;
2277  PetscScalar* vecArrayTargetForce;
2278  PetscScalar* vecArrayCenter;
2279  const PetscScalar* vecArrayPsi;
2280 
2281  VecGetArray(aForce, &vecArrayAForce);
2282  for (label i = 0; i < nPoints; i++)
2283  {
2284  aForceField[i] = vecArrayAForce[i];
2285  }
2286  VecRestoreArray(aForce, &vecArrayAForce);
2287 
2288  VecGetArray(tForce, &vecArrayTForce);
2289  for (label i = 0; i < nPoints; i++)
2290  {
2291  tForceField[i] = vecArrayTForce[i];
2292  }
2293  VecRestoreArray(tForce, &vecArrayTForce);
2294 
2295  VecGetArray(rDist, &vecArrayRDist);
2296  for (label i = 0; i < nPoints; i++)
2297  {
2298  rDistField[i] = vecArrayRDist[i];
2299  }
2300  VecRestoreArray(rDist, &vecArrayRDist);
2301 
2302  VecGetArray(targetForce, &vecArrayTargetForce);
2303  targetForceList[0] = vecArrayTargetForce[0];
2304  targetForceList[1] = vecArrayTargetForce[1];
2305  VecRestoreArray(targetForce, &vecArrayTargetForce);
2306 
2307  VecGetArray(center, &vecArrayCenter);
2308  centerVector[0] = vecArrayCenter[0];
2309  centerVector[1] = vecArrayCenter[1];
2310  centerVector[2] = vecArrayCenter[2];
2311  VecRestoreArray(center, &vecArrayCenter);
2312 
2313  this->globalADTape_.reset();
2314  this->globalADTape_.setActive();
2315 
2316  if (mode == "aForce")
2317  {
2318  for (label i = 0; i < nPoints; i++)
2319  {
2320  this->globalADTape_.registerInput(aForceField[i]);
2321  }
2322  }
2323  else if (mode == "tForce")
2324  {
2325  for (label i = 0; i < nPoints; i++)
2326  {
2327  this->globalADTape_.registerInput(tForceField[i]);
2328  }
2329  }
2330  else if (mode == "rDist")
2331  {
2332  for (label i = 0; i < nPoints; i++)
2333  {
2334  this->globalADTape_.registerInput(rDistField[i]);
2335  }
2336  }
2337  else if (mode == "targetForce")
2338  {
2339  for (label i = 0; i < 2; i++)
2340  {
2341  this->globalADTape_.registerInput(targetForceList[i]);
2342  }
2343  }
2344  else if (mode == "center")
2345  {
2346  for (label i = 0; i < 3; i++)
2347  {
2348  this->globalADTape_.registerInput(centerVector[i]);
2349  }
2350  }
2351  else
2352  {
2353  FatalErrorIn("calcdFvSourcedInputsTPsiAD") << "mode not valid"
2354  << abort(FatalError);
2355  }
2356 
2357  // Step 3
2358  this->calcFvSourceInternal(propName, aForceField, tForceField, rDistField, targetForceList, centerVector, fvSourceVField);
2359 
2360  // Step 4
2361  forAll(fvSourceVField, i)
2362  {
2363  this->globalADTape_.registerOutput(fvSourceVField[i][0]);
2364  this->globalADTape_.registerOutput(fvSourceVField[i][1]);
2365  this->globalADTape_.registerOutput(fvSourceVField[i][2]);
2366  }
2367 
2368  this->globalADTape_.setPassive();
2369 
2370  // Step 6
2371  VecGetArrayRead(psi, &vecArrayPsi);
2372  forAll(fvSourceVField, i)
2373  {
2374  // Set seeds
2375  fvSourceVField[i][0].setGradient(vecArrayPsi[i * 3]);
2376  fvSourceVField[i][1].setGradient(vecArrayPsi[i * 3 + 1]);
2377  fvSourceVField[i][2].setGradient(vecArrayPsi[i * 3 + 2]);
2378  }
2379  VecRestoreArrayRead(psi, &vecArrayPsi);
2380 
2381  this->globalADTape_.evaluate();
2382 
2383  PetscScalar* vecArrayProd;
2384  VecGetArray(dFvSource, &vecArrayProd);
2385  if (mode == "aForce")
2386  {
2387  forAll(aForceField, i)
2388  {
2389  vecArrayProd[i] = aForceField[i].getGradient();
2390  }
2391  }
2392  else if (mode == "tForce")
2393  {
2394  forAll(tForceField, i)
2395  {
2396  vecArrayProd[i] = tForceField[i].getGradient();
2397  }
2398  }
2399  else if (mode == "rDist")
2400  {
2401  forAll(rDistField, i)
2402  {
2403  vecArrayProd[i] = rDistField[i].getGradient();
2404  }
2405  }
2406  else if (mode == "targetForce")
2407  {
2408  forAll(targetForceList, i)
2409  {
2410  vecArrayProd[i] = targetForceList[i].getGradient();
2411  }
2412  }
2413  else if (mode == "center")
2414  {
2415  forAll(centerVector, i)
2416  {
2417  vecArrayProd[i] = centerVector[i].getGradient();
2418  }
2419  }
2420 
2421  VecRestoreArray(dFvSource, &vecArrayProd);
2422 
2423  this->globalADTape_.clearAdjoints();
2424  this->globalADTape_.reset();
2425 #endif
2426 }
2427 
2429  const dictionary& maxResConLv4JacPCMat,
2430  HashTable<List<List<word>>>& stateResConInfo) const
2431 {
2432  /*
2433  Description:
2434  Reduce the connectivity levels for stateResConInfo
2435  based on maxResConLv4JacPCMat specified in DAOption
2436 
2437  Input:
2438  maxResConLv4JacPCMat: the maximal levels of connectivity for each
2439  state variable residual
2440 
2441  Output:
2442  stateResConInfo: reduced connectivity level.
2443 
2444  Example:
2445 
2446  If the original stateResConInfo reads:
2447 
2448  stateResConInfo
2449  {
2450  "URes":
2451  {
2452  {"U", "p", "phi"}, // level 0
2453  {"U", "p"}, // level 1
2454  {"U"} // level 2
2455  }
2456  }
2457  And maxResConLv4JacPCMat in DAOption reads:
2458 
2459  maxResConLv4JacPCMat
2460  {
2461  "URes": 1
2462  }
2463 
2464  Then, calling reduceStateResConLevel will give:
2465 
2466  stateResConInfo
2467  {
2468  "URes":
2469  {
2470  {"U", "p", "phi"}, // level 0
2471  {"U", "p"}, // level 1
2472  }
2473  }
2474 
2475  Note that the level 2 of the connectivity in URes is removed becasue
2476  "URes"=1 in maxResConLv4JacPCMat
2477 
2478  */
2479 
2480  // if no maxResConLv4JacPCMat is specified, just return;
2481  if (maxResConLv4JacPCMat.toc().size() == 0)
2482  {
2483  Info << "maxResConLv4JacPCMat is empty, just return" << endl;
2484  return;
2485  }
2486 
2487  // now check if maxResConLv4JacPCMat has all the maxRes level defined
2488  // and these max levels are <= stateResConInfo.size()
2489  forAll(stateResConInfo.toc(), idxJ)
2490  {
2491  word key1 = stateResConInfo.toc()[idxJ];
2492  bool keyFound = false;
2493  forAll(maxResConLv4JacPCMat.toc(), idxI)
2494  {
2495  word key = maxResConLv4JacPCMat.toc()[idxI];
2496  if (key == key1)
2497  {
2498  keyFound = true;
2499  label maxLv = maxResConLv4JacPCMat.getLabel(key);
2500  label maxLv1 = stateResConInfo[key1].size() - 1;
2501  if (maxLv > maxLv1)
2502  {
2503  FatalErrorIn("") << "maxResConLv4JacPCMat maxLevel"
2504  << maxLv << " for " << key
2505  << " larger than stateResConInfo maxLevel "
2506  << maxLv1 << " for " << key1
2507  << abort(FatalError);
2508  }
2509  }
2510  }
2511  if (!keyFound)
2512  {
2513  FatalErrorIn("") << key1 << " not found in maxResConLv4JacPCMat"
2514  << abort(FatalError);
2515  }
2516  }
2517 
2518  if (daOptionPtr_->getOption<label>("debug"))
2519  {
2520  Info << "Reducing max connectivity level of Jacobian PC Mat to : ";
2521  Info << maxResConLv4JacPCMat << endl;
2522  }
2523 
2524  // assign stateResConInfo to stateResConInfoBK
2525  HashTable<List<List<word>>> stateResConInfoBK;
2526  forAll(stateResConInfo.toc(), idxI)
2527  {
2528  word key = stateResConInfo.toc()[idxI];
2529  stateResConInfoBK.set(key, stateResConInfo[key]);
2530  }
2531 
2532  // now we can erase stateResConInfo
2533  stateResConInfo.clearStorage();
2534 
2535  // get the reduced stateResConInfo
2536  forAll(stateResConInfoBK.toc(), idxI)
2537  {
2538  word key = stateResConInfoBK.toc()[idxI];
2539  label maxConLevel = maxResConLv4JacPCMat.getLabel(key);
2540  label conSize = stateResConInfoBK[key].size();
2541  if (conSize > maxConLevel + 1)
2542  {
2543  List<List<word>> conList;
2544  conList.setSize(maxConLevel + 1);
2545  for (label i = 0; i <= maxConLevel; i++) // NOTE: it is <=
2546  {
2547  conList[i] = stateResConInfoBK[key][i];
2548  }
2549  stateResConInfo.set(key, conList);
2550  }
2551  else
2552  {
2553  stateResConInfo.set(key, stateResConInfoBK[key]);
2554  }
2555  }
2556  //Info<<stateResConInfo<<endl;
2557 }
2558 
2560  const word mode,
2561  const label writeRes)
2562 {
2563  /*
2564  Description:
2565  Calculate the mean, max, and norm2 for all residuals and print it to screen
2566  */
2567 
2568  if (mode == "print")
2569  {
2570  // print the primal residuals to screen
2571  Info << "Printing Primal Residual Statistics." << endl;
2572  }
2573  else if (mode == "calc")
2574  {
2575  // we will just calculate but not printting anything
2576  }
2577  else
2578  {
2579  FatalErrorIn("") << "mode not valid" << abort(FatalError);
2580  }
2581 
2582  label isPC = 0;
2583  dictionary options;
2584  options.set("isPC", isPC);
2585  daResidualPtr_->calcResiduals(options);
2586  daModelPtr_->calcResiduals(options);
2587 
2588  forAll(stateInfo_["volVectorStates"], idxI)
2589  {
2590  const word stateName = stateInfo_["volVectorStates"][idxI];
2591  const word resName = stateName + "Res";
2592  const volVectorField& stateRes = meshPtr_->thisDb().lookupObject<volVectorField>(resName);
2593 
2594  vector vecResMax(0, 0, 0);
2595  vector vecResNorm2(0, 0, 0);
2596  vector vecResMean(0, 0, 0);
2597  forAll(stateRes, cellI)
2598  {
2599  vecResNorm2.x() += pow(stateRes[cellI].x(), 2.0);
2600  vecResNorm2.y() += pow(stateRes[cellI].y(), 2.0);
2601  vecResNorm2.z() += pow(stateRes[cellI].z(), 2.0);
2602  vecResMean.x() += fabs(stateRes[cellI].x());
2603  vecResMean.y() += fabs(stateRes[cellI].y());
2604  vecResMean.z() += fabs(stateRes[cellI].z());
2605  if (fabs(stateRes[cellI].x()) > vecResMax.x())
2606  {
2607  vecResMax.x() = fabs(stateRes[cellI].x());
2608  }
2609  if (fabs(stateRes[cellI].y()) > vecResMax.y())
2610  {
2611  vecResMax.y() = fabs(stateRes[cellI].y());
2612  }
2613  if (fabs(stateRes[cellI].z()) > vecResMax.z())
2614  {
2615  vecResMax.z() = fabs(stateRes[cellI].z());
2616  }
2617  }
2618  vecResMean = vecResMean / stateRes.size();
2619  reduce(vecResMean, sumOp<vector>());
2620  vecResMean = vecResMean / Pstream::nProcs();
2621  reduce(vecResNorm2, sumOp<vector>());
2622  reduce(vecResMax, maxOp<vector>());
2623  vecResNorm2.x() = pow(vecResNorm2.x(), 0.5);
2624  vecResNorm2.y() = pow(vecResNorm2.y(), 0.5);
2625  vecResNorm2.z() = pow(vecResNorm2.z(), 0.5);
2626  if (mode == "print")
2627  {
2628  Info << stateName << " Residual Norm2: " << vecResNorm2 << endl;
2629  Info << stateName << " Residual Mean: " << vecResMean << endl;
2630  Info << stateName << " Residual Max: " << vecResMax << endl;
2631  }
2632 
2633  if (writeRes)
2634  {
2635  stateRes.write();
2636  }
2637  }
2638 
2639  forAll(stateInfo_["volScalarStates"], idxI)
2640  {
2641  const word stateName = stateInfo_["volScalarStates"][idxI];
2642  const word resName = stateName + "Res";
2643  const volScalarField& stateRes = meshPtr_->thisDb().lookupObject<volScalarField>(resName);
2644 
2645  scalar scalarResMax = 0, scalarResNorm2 = 0, scalarResMean = 0;
2646  forAll(stateRes, cellI)
2647  {
2648  scalarResNorm2 += pow(stateRes[cellI], 2.0);
2649  scalarResMean += fabs(stateRes[cellI]);
2650  if (fabs(stateRes[cellI]) > scalarResMax)
2651  scalarResMax = fabs(stateRes[cellI]);
2652  }
2653  scalarResMean = scalarResMean / stateRes.size();
2654  reduce(scalarResMean, sumOp<scalar>());
2655  scalarResMean = scalarResMean / Pstream::nProcs();
2656  reduce(scalarResNorm2, sumOp<scalar>());
2657  reduce(scalarResMax, maxOp<scalar>());
2658  scalarResNorm2 = pow(scalarResNorm2, 0.5);
2659  if (mode == "print")
2660  {
2661  Info << stateName << " Residual Norm2: " << scalarResNorm2 << endl;
2662  Info << stateName << " Residual Mean: " << scalarResMean << endl;
2663  Info << stateName << " Residual Max: " << scalarResMax << endl;
2664  }
2665 
2666  if (writeRes)
2667  {
2668  stateRes.write();
2669  }
2670  }
2671 
2672  forAll(stateInfo_["modelStates"], idxI)
2673  {
2674  const word stateName = stateInfo_["modelStates"][idxI];
2675  const word resName = stateName + "Res";
2676  const volScalarField& stateRes = meshPtr_->thisDb().lookupObject<volScalarField>(resName);
2677 
2678  scalar scalarResMax = 0, scalarResNorm2 = 0, scalarResMean = 0;
2679  forAll(stateRes, cellI)
2680  {
2681  scalarResNorm2 += pow(stateRes[cellI], 2.0);
2682  scalarResMean += fabs(stateRes[cellI]);
2683  if (fabs(stateRes[cellI]) > scalarResMax)
2684  scalarResMax = fabs(stateRes[cellI]);
2685  }
2686  scalarResMean = scalarResMean / stateRes.size();
2687  reduce(scalarResMean, sumOp<scalar>());
2688  scalarResMean = scalarResMean / Pstream::nProcs();
2689  reduce(scalarResNorm2, sumOp<scalar>());
2690  reduce(scalarResMax, maxOp<scalar>());
2691  scalarResNorm2 = pow(scalarResNorm2, 0.5);
2692  if (mode == "print")
2693  {
2694  Info << stateName << " Residual Norm2: " << scalarResNorm2 << endl;
2695  Info << stateName << " Residual Mean: " << scalarResMean << endl;
2696  Info << stateName << " Residual Max: " << scalarResMax << endl;
2697  }
2698 
2699  if (writeRes)
2700  {
2701  stateRes.write();
2702  }
2703  }
2704 
2705  forAll(stateInfo_["surfaceScalarStates"], idxI)
2706  {
2707  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
2708  const word resName = stateName + "Res";
2709  const surfaceScalarField& stateRes = meshPtr_->thisDb().lookupObject<surfaceScalarField>(resName);
2710 
2711  scalar phiResMax = 0, phiResNorm2 = 0, phiResMean = 0;
2712  forAll(stateRes, faceI)
2713  {
2714  phiResNorm2 += pow(stateRes[faceI], 2.0);
2715  phiResMean += fabs(stateRes[faceI]);
2716  if (fabs(stateRes[faceI]) > phiResMax)
2717  phiResMax = fabs(stateRes[faceI]);
2718  }
2719  forAll(stateRes.boundaryField(), patchI)
2720  {
2721  forAll(stateRes.boundaryField()[patchI], faceI)
2722  {
2723  scalar bPhiRes = stateRes.boundaryField()[patchI][faceI];
2724  phiResNorm2 += pow(bPhiRes, 2.0);
2725  phiResMean += fabs(bPhiRes);
2726  if (fabs(bPhiRes) > phiResMax)
2727  phiResMax = fabs(bPhiRes);
2728  }
2729  }
2730  phiResMean = phiResMean / meshPtr_->nFaces();
2731  reduce(phiResMean, sumOp<scalar>());
2732  phiResMean = phiResMean / Pstream::nProcs();
2733  reduce(phiResNorm2, sumOp<scalar>());
2734  reduce(phiResMax, maxOp<scalar>());
2735  phiResNorm2 = pow(phiResNorm2, 0.5);
2736  if (mode == "print")
2737  {
2738  Info << stateName << " Residual Norm2: " << phiResNorm2 << endl;
2739  Info << stateName << " Residual Mean: " << phiResMean << endl;
2740  Info << stateName << " Residual Max: " << phiResMax << endl;
2741  }
2742 
2743  if (writeRes)
2744  {
2745  stateRes.write();
2746  }
2747  }
2748 
2749  return;
2750 }
2751 
2753  const Vec xvVec,
2754  const Vec wVec,
2755  const label isPC,
2756  Mat dRdWT)
2757 {
2758  /*
2759  Description:
2760  This function computes partials derivatives dRdWT or dRdWTPC.
2761  PC means preconditioner matrix
2762 
2763  Input:
2764  xvVec: the volume mesh coordinate vector
2765 
2766  wVec: the state variable vector
2767 
2768  isPC: isPC=1 computes dRdWTPC, isPC=0 computes dRdWT
2769 
2770  Output:
2771  dRdWT: the partial derivative matrix [dR/dW]^T
2772  NOTE: You need to call MatCreate for the dRdWT matrix before calling this function.
2773  No need to call MatSetSize etc because they will be done in this function
2774  */
2775 
2776  word matName;
2777  if (isPC == 0)
2778  {
2779  matName = "dRdWT";
2780  }
2781  else if (isPC == 1)
2782  {
2783  matName = "dRdWTPC";
2784  }
2785  else
2786  {
2787  FatalErrorIn("") << "isPC " << isPC << " not supported! "
2788  << "Options are: 0 (for dRdWT) and 1 (for dRdWTPC)." << abort(FatalError);
2789  }
2790 
2791  if (daOptionPtr_->getOption<label>("debug"))
2792  {
2793  this->calcPrimalResidualStatistics("print");
2794  }
2795 
2796  Info << "Computing " << matName << " " << runTimePtr_->elapsedClockTime() << " s" << endl;
2797  Info << "Initializing dRdWCon. " << runTimePtr_->elapsedClockTime() << " s" << endl;
2798 
2799  // initialize DAJacCon object
2800  word modelType = "dRdW";
2801  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
2802  modelType,
2803  meshPtr_(),
2804  daOptionPtr_(),
2805  daModelPtr_(),
2806  daIndexPtr_()));
2807 
2808  dictionary options;
2809  const HashTable<List<List<word>>>& stateResConInfo = daStateInfoPtr_->getStateResConInfo();
2810 
2811  if (isPC == 1)
2812  {
2813  // need to reduce the JacCon for PC to reduce memory usage
2814  HashTable<List<List<word>>> stateResConInfoReduced = stateResConInfo;
2815 
2816  dictionary maxResConLv4JacPCMat = daOptionPtr_->getAllOptions().subDict("maxResConLv4JacPCMat");
2817 
2818  this->reduceStateResConLevel(maxResConLv4JacPCMat, stateResConInfoReduced);
2819  options.set("stateResConInfo", stateResConInfoReduced);
2820  }
2821  else
2822  {
2823  options.set("stateResConInfo", stateResConInfo);
2824  }
2825 
2826  // need to first setup preallocation vectors for the dRdWCon matrix
2827  // because directly initializing the dRdWCon matrix will use too much memory
2828  daJacCon->setupJacConPreallocation(options);
2829 
2830  // now we can initialize dRdWCon
2831  daJacCon->initializeJacCon(options);
2832 
2833  // setup dRdWCon
2834  daJacCon->setupJacCon(options);
2835  Info << "dRdWCon Created. " << runTimePtr_->elapsedClockTime() << " s" << endl;
2836 
2837  // read the coloring
2838  daJacCon->readJacConColoring();
2839 
2840  // initialize partDeriv object
2841  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
2842  modelType,
2843  meshPtr_(),
2844  daOptionPtr_(),
2845  daModelPtr_(),
2846  daIndexPtr_(),
2847  daJacCon(),
2848  daResidualPtr_()));
2849 
2850  // we want transposed dRdW
2851  dictionary options1;
2852  options1.set("transposed", 1);
2853  options1.set("isPC", isPC);
2854  // we can set lower bounds for the Jacobians to save memory
2855  if (isPC == 1)
2856  {
2857  options1.set("lowerBound", daOptionPtr_->getSubDictOption<scalar>("jacLowerBounds", "dRdWPC"));
2858  }
2859  else
2860  {
2861  options1.set("lowerBound", daOptionPtr_->getSubDictOption<scalar>("jacLowerBounds", "dRdW"));
2862  }
2863 
2864  // initialize dRdWT matrix
2865  daPartDeriv->initializePartDerivMat(options1, dRdWT);
2866 
2867  // calculate dRdWT
2868  daPartDeriv->calcPartDerivMat(options1, xvVec, wVec, dRdWT);
2869 
2870  if (daOptionPtr_->getOption<label>("debug"))
2871  {
2872  this->calcPrimalResidualStatistics("print");
2873  }
2874 
2875  wordList writeJacobians;
2876  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
2877  if (writeJacobians.found("dRdWT") || writeJacobians.found("all"))
2878  {
2879  DAUtility::writeMatrixBinary(dRdWT, matName);
2880  }
2881 
2882  // clear up
2883  daJacCon->clear();
2884 }
2885 
2887  const Vec xvVec,
2888  const Vec wVec,
2889  const word objFuncName,
2890  Vec dFdW)
2891 {
2892  /*
2893  Description:
2894  This function computes partials derivatives dFdW
2895 
2896  Input:
2897  xvVec: the volume mesh coordinate vector
2898 
2899  wVec: the state variable vector
2900 
2901  objFuncName: name of the objective function F
2902 
2903  Output:
2904  dFdW: the partial derivative vector dF/dW
2905  NOTE: You need to fully initialize the dFdW vec before calliing this function,
2906  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
2907  */
2908 
2909  VecZeroEntries(dFdW);
2910 
2911  // get the subDict for this objective function
2912  dictionary objFuncSubDict =
2913  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
2914 
2915  // loop over all parts for this objFuncName
2916  forAll(objFuncSubDict.toc(), idxJ)
2917  {
2918  // get the subDict for this part
2919  word objFuncPart = objFuncSubDict.toc()[idxJ];
2920  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
2921 
2922  // NOTE: dFdW is a matrix here and it has nObjFuncCellSources+nObjFuncFaceSources rows
2923  Mat dFdWMat;
2924  MatCreate(PETSC_COMM_WORLD, &dFdWMat);
2925 
2926  // initialize DAJacCon object
2927  word modelType = "dFdW";
2928  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
2929  modelType,
2930  meshPtr_(),
2931  daOptionPtr_(),
2932  daModelPtr_(),
2933  daIndexPtr_()));
2934 
2935  // initialize objFunc to get objFuncCellSources and objFuncFaceSources
2936  autoPtr<DAObjFunc> daObjFunc(DAObjFunc::New(
2937  meshPtr_(),
2938  daOptionPtr_(),
2939  daModelPtr_(),
2940  daIndexPtr_(),
2941  daResidualPtr_(),
2942  objFuncName,
2943  objFuncPart,
2944  objFuncSubDictPart));
2945 
2946  // setup options for daJacCondFdW computation
2947  dictionary options;
2948  const List<List<word>>& objFuncConInfo = daObjFunc->getObjFuncConInfo();
2949  const labelList& objFuncFaceSources = daObjFunc->getObjFuncFaceSources();
2950  const labelList& objFuncCellSources = daObjFunc->getObjFuncCellSources();
2951  options.set("objFuncConInfo", objFuncConInfo);
2952  options.set("objFuncFaceSources", objFuncFaceSources);
2953  options.set("objFuncCellSources", objFuncCellSources);
2954  options.set("objFuncName", objFuncName);
2955  options.set("objFuncPart", objFuncPart);
2956  options.set("objFuncSubDictPart", objFuncSubDictPart);
2957 
2958  // now we can initilaize dFdWCon
2959  daJacCon->initializeJacCon(options);
2960 
2961  // setup dFdWCon
2962  daJacCon->setupJacCon(options);
2963  Info << "dFdWCon Created. " << meshPtr_->time().elapsedClockTime() << " s" << endl;
2964 
2965  // read the coloring
2966  word postFix = "_" + objFuncName + "_" + objFuncPart;
2967  daJacCon->readJacConColoring(postFix);
2968 
2969  // initialize DAPartDeriv to computing dFdW
2970  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
2971  modelType,
2972  meshPtr_(),
2973  daOptionPtr_(),
2974  daModelPtr_(),
2975  daIndexPtr_(),
2976  daJacCon(),
2977  daResidualPtr_()));
2978 
2979  // initialize dFdWMat
2980  daPartDeriv->initializePartDerivMat(options, dFdWMat);
2981 
2982  // compute it
2983  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dFdWMat);
2984 
2985  // now we need to add all the rows of dFdW together to get dFdWPart
2986  // NOTE: dFdW is a matrix with nObjFuncCellSources+nObjFuncFaceSources rows
2987  // and nLocalAdjStates columns. So we can do dFdWPart = oneVec*dFdW
2988  Vec dFdWPart, oneVec;
2989  label objGeoSize = objFuncFaceSources.size() + objFuncCellSources.size();
2990  VecCreate(PETSC_COMM_WORLD, &oneVec);
2991  VecSetSizes(oneVec, objGeoSize, PETSC_DETERMINE);
2992  VecSetFromOptions(oneVec);
2993  // assign one to all elements
2994  VecSet(oneVec, 1.0);
2995  VecDuplicate(wVec, &dFdWPart);
2996  VecZeroEntries(dFdWPart);
2997  // dFdWPart = oneVec*dFdW
2998  MatMultTranspose(dFdWMat, oneVec, dFdWPart);
2999 
3000  // we need to add dFdWPart to dFdW because we want to sum all dFdWPart
3001  // for all parts of this objFuncName. When solving the adjoint equation, we use
3002  // dFdW
3003  VecAXPY(dFdW, 1.0, dFdWPart);
3004 
3005  if (daOptionPtr_->getOption<label>("debug"))
3006  {
3007  this->calcPrimalResidualStatistics("print");
3008  }
3009 
3010  MatDestroy(&dFdWMat);
3011  VecDestroy(&dFdWPart);
3012  VecDestroy(&oneVec);
3013 
3014  // clear up
3015  daJacCon->clear();
3016  daObjFunc->clear();
3017  }
3018 
3019  wordList writeJacobians;
3020  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3021  if (writeJacobians.found("dFdW") || writeJacobians.found("all"))
3022  {
3023  word outputName = "dFdW_" + objFuncName;
3024  DAUtility::writeVectorBinary(dFdW, outputName);
3025  DAUtility::writeVectorASCII(dFdW, outputName);
3026  }
3027 }
3028 
3030  const Vec xvVec,
3031  const Vec wVec,
3032  const word designVarName,
3033  Mat dRdBC)
3034 {
3035  /*
3036  Description:
3037  This function computes partials derivatives dRdBC
3038 
3039  Input:
3040  xvVec: the volume mesh coordinate vector
3041 
3042  wVec: the state variable vector
3043 
3044  designVarName: the name of the design variable
3045 
3046  Output:
3047  dRdBC: the partial derivative matrix dR/dBC
3048  NOTE: You need to call MatCreate for the dRdBC matrix before calling this function.
3049  No need to call MatSetSize etc because they will be done in this function
3050  */
3051 
3052  dictionary designVarDict = daOptionPtr_->getAllOptions().subDict("designVar");
3053 
3054  // get the subDict for this dvName
3055  dictionary dvSubDict = designVarDict.subDict(designVarName);
3056 
3057  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3058  // name of the variable for changing the boundary condition
3059  word varName = dvSubDict.getWord("variable");
3060  // name of the boundary patch
3061  wordList patches;
3062  dvSubDict.readEntry<wordList>("patches", patches);
3063  // the compoent of a vector variable, ignore when it is a scalar
3064  label comp = dvSubDict.getLabel("comp");
3065 
3066  // no coloring is need for BC, so we create a dummy DAJacCon
3067  word dummyType = "dummy";
3068  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
3069  dummyType,
3070  meshPtr_(),
3071  daOptionPtr_(),
3072  daModelPtr_(),
3073  daIndexPtr_()));
3074 
3075  // ********************** compute dRdBC **********************
3076 
3077  // create DAPartDeriv object
3078  word modelType = "dRdBC";
3079  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
3080  modelType,
3081  meshPtr_(),
3082  daOptionPtr_(),
3083  daModelPtr_(),
3084  daIndexPtr_(),
3085  daJacCon(),
3086  daResidualPtr_()));
3087 
3088  // setup options to compute dRdBC
3089  dictionary options;
3090  options.set("variable", varName);
3091  options.set("patches", patches);
3092  options.set("comp", comp);
3093  options.set("isPC", 0);
3094 
3095  // initialize the dRdBC matrix
3096  daPartDeriv->initializePartDerivMat(options, dRdBC);
3097 
3098  // compute it using brute force finite-difference
3099  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dRdBC);
3100 
3101  if (daOptionPtr_->getOption<label>("debug"))
3102  {
3103  this->calcPrimalResidualStatistics("print");
3104  }
3105 
3106  wordList writeJacobians;
3107  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3108  if (writeJacobians.found("dRdBC") || writeJacobians.found("all"))
3109  {
3110  word outputName = "dRdBC_" + designVarName;
3111  DAUtility::writeMatrixBinary(dRdBC, outputName);
3112  DAUtility::writeMatrixASCII(dRdBC, outputName);
3113  }
3114 }
3115 
3117  const Vec xvVec,
3118  const Vec wVec,
3119  const word objFuncName,
3120  const word designVarName,
3121  Vec dFdBC)
3122 {
3123  /*
3124  Description:
3125  This function computes partials derivatives dFdW
3126 
3127  Input:
3128  xvVec: the volume mesh coordinate vector
3129 
3130  wVec: the state variable vector
3131 
3132  objFuncName: name of the objective function F
3133 
3134  designVarName: the name of the design variable
3135 
3136  Output:
3137  dFdBC: the partial derivative vector dF/dBC
3138  NOTE: You need to fully initialize the dFdBC vec before calliing this function,
3139  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
3140  */
3141 
3142  VecZeroEntries(dFdBC);
3143 
3144  // no coloring is need for BC, so we create a dummy DAJacCon
3145  word dummyType = "dummy";
3146  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
3147  dummyType,
3148  meshPtr_(),
3149  daOptionPtr_(),
3150  daModelPtr_(),
3151  daIndexPtr_()));
3152 
3153  // get the subDict for this dvName
3154  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
3155 
3156  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3157  // name of the variable for changing the boundary condition
3158  word varName = dvSubDict.getWord("variable");
3159  // name of the boundary patch
3160  wordList patches;
3161  dvSubDict.readEntry<wordList>("patches", patches);
3162  // the compoent of a vector variable, ignore when it is a scalar
3163  label comp = dvSubDict.getLabel("comp");
3164 
3165  // get the subDict for this objective function
3166  dictionary objFuncSubDict =
3167  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
3168 
3169  // loop over all parts of this objFuncName
3170  forAll(objFuncSubDict.toc(), idxK)
3171  {
3172  word objFuncPart = objFuncSubDict.toc()[idxK];
3173  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
3174 
3175  Mat dFdBCMat;
3176  MatCreate(PETSC_COMM_WORLD, &dFdBCMat);
3177 
3178  // initialize DAPartDeriv for dFdBC
3179  word modelType = "dFdBC";
3180  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
3181  modelType,
3182  meshPtr_(),
3183  daOptionPtr_(),
3184  daModelPtr_(),
3185  daIndexPtr_(),
3186  daJacCon(),
3187  daResidualPtr_()));
3188 
3189  // initialize options
3190  dictionary options;
3191  options.set("objFuncName", objFuncName);
3192  options.set("objFuncPart", objFuncPart);
3193  options.set("objFuncSubDictPart", objFuncSubDictPart);
3194  options.set("variable", varName);
3195  options.set("patches", patches);
3196  options.set("comp", comp);
3197 
3198  // initialize dFdBC
3199  daPartDeriv->initializePartDerivMat(options, dFdBCMat);
3200 
3201  // calculate it
3202  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dFdBCMat);
3203 
3204  // now we need to add all the rows of dFdBCMat together to get dFdBC
3205  // NOTE: dFdBCMat is a 1 by 1 matrix but we just do a matrix-vector product
3206  // to convert dFdBCMat from a matrix to a vector
3207  Vec dFdBCPart, oneVec;
3208  VecDuplicate(dFdBC, &oneVec);
3209  VecSet(oneVec, 1.0);
3210  VecDuplicate(dFdBC, &dFdBCPart);
3211  VecZeroEntries(dFdBCPart);
3212  // dFdBCPart = dFdBCMat * oneVec
3213  MatMult(dFdBCMat, oneVec, dFdBCPart);
3214 
3215  // we need to add dFdBCPart to dFdBC because we want to sum
3216  // all parts of this objFuncName.
3217  VecAXPY(dFdBC, 1.0, dFdBCPart);
3218 
3219  if (daOptionPtr_->getOption<label>("debug"))
3220  {
3221  this->calcPrimalResidualStatistics("print");
3222  }
3223 
3224  // clear up
3225  MatDestroy(&dFdBCMat);
3226  VecDestroy(&dFdBCPart);
3227  VecDestroy(&oneVec);
3228  }
3229 
3230  wordList writeJacobians;
3231  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3232  if (writeJacobians.found("dFdBC") || writeJacobians.found("all"))
3233  {
3234  word outputName = "dFdBC_" + designVarName;
3235  DAUtility::writeVectorBinary(dFdBC, outputName);
3236  DAUtility::writeVectorASCII(dFdBC, outputName);
3237  }
3238 }
3239 
3241  const Vec xvVec,
3242  const Vec wVec,
3243  const word designVarName,
3244  Mat dRdAOA)
3245 {
3246  /*
3247  Description:
3248  This function computes partials derivatives dRdAOA
3249 
3250  Input:
3251  xvVec: the volume mesh coordinate vector
3252 
3253  wVec: the state variable vector
3254 
3255  designVarName: the name of the design variable
3256 
3257  Output:
3258  dRdAOA: the partial derivative matrix dR/dAOA
3259  NOTE: You need to call MatCreate for the dRdAOA matrix before calling this function.
3260  No need to call MatSetSize etc because they will be done in this function
3261  */
3262 
3263  dictionary designVarDict = daOptionPtr_->getAllOptions().subDict("designVar");
3264 
3265  // get the subDict for this dvName
3266  dictionary dvSubDict = designVarDict.subDict(designVarName);
3267 
3268  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3269  // name of the boundary patch
3270  wordList patches;
3271  dvSubDict.readEntry<wordList>("patches", patches);
3272  // the streamwise axis of aoa, aoa = tan( U_normal/U_flow )
3273  word flowAxis = dvSubDict.getWord("flowAxis");
3274  word normalAxis = dvSubDict.getWord("normalAxis");
3275 
3276  // no coloring is need for BC, so we create a dummy DAJacCon
3277  word dummyType = "dummy";
3278  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
3279  dummyType,
3280  meshPtr_(),
3281  daOptionPtr_(),
3282  daModelPtr_(),
3283  daIndexPtr_()));
3284 
3285  // ********************** compute dRdAOA **********************
3286 
3287  // create DAPartDeriv object
3288  word modelType = "dRdAOA";
3289  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
3290  modelType,
3291  meshPtr_(),
3292  daOptionPtr_(),
3293  daModelPtr_(),
3294  daIndexPtr_(),
3295  daJacCon(),
3296  daResidualPtr_()));
3297 
3298  // setup options to compute dRdAOA
3299  dictionary options;
3300  options.set("patches", patches);
3301  options.set("flowAxis", flowAxis);
3302  options.set("normalAxis", normalAxis);
3303  options.set("isPC", 0);
3304 
3305  // initialize the dRdAOA matrix
3306  daPartDeriv->initializePartDerivMat(options, dRdAOA);
3307 
3308  // compute it using brute force finite-difference
3309  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dRdAOA);
3310 
3311  if (daOptionPtr_->getOption<label>("debug"))
3312  {
3313  this->calcPrimalResidualStatistics("print");
3314  }
3315 
3316  wordList writeJacobians;
3317  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3318  if (writeJacobians.found("dRdAOA") || writeJacobians.found("all"))
3319  {
3320  word outputName = "dRdAOA_" + designVarName;
3321  DAUtility::writeMatrixBinary(dRdAOA, outputName);
3322  DAUtility::writeMatrixASCII(dRdAOA, outputName);
3323  }
3324 }
3325 
3327  const Vec xvVec,
3328  const Vec wVec,
3329  const Vec psi,
3330  const word designVarName,
3331  Vec dRdBCTPsi)
3332 {
3333 #ifdef CODI_AD_REVERSE
3334  /*
3335  Description:
3336  Compute the matrix-vector products dRdBC^T*Psi using reverse-mode AD
3337 
3338  Input:
3339 
3340  xvVec: the volume mesh coordinate vector
3341 
3342  wVec: the state variable vector
3343 
3344  psi: the vector to multiply dRdBC
3345 
3346  designVarName: name of the design variable
3347 
3348  Output:
3349  dRdBCTPsi: the matrix-vector products dRdBC^T * Psi
3350  */
3351 
3352  Info << "Calculating [dRdBC]^T * Psi using reverse-mode AD" << endl;
3353 
3354  VecZeroEntries(dRdBCTPsi);
3355 
3356  this->updateOFField(wVec);
3357  this->updateOFMesh(xvVec);
3358 
3359  dictionary designVarDict = daOptionPtr_->getAllOptions().subDict("designVar");
3360 
3361  // get the subDict for this dvName
3362  dictionary dvSubDict = designVarDict.subDict(designVarName);
3363 
3364  // create a common variable BC, it will be the input of the AD
3365  scalar BC = -1e16;
3366 
3367  if (designVarName == "MRF")
3368  {
3369  const IOMRFZoneListDF& MRF = meshPtr_->thisDb().lookupObject<IOMRFZoneListDF>("MRFProperties");
3370 
3371  // first, we get the current value of omega and assign it to BC
3372  scalar& omega = const_cast<scalar&>(MRF.getOmegaRef());
3373  BC = omega;
3374 
3375  this->globalADTape_.reset();
3376  this->globalADTape_.setActive();
3377  // register BC as the input
3378  this->globalADTape_.registerInput(BC);
3379  // ******* now set BC ******
3380  omega = BC;
3381  }
3382  else if (designVarName == "fvSource")
3383  {
3384  volVectorField& fvSource = const_cast<volVectorField&>(
3385  meshPtr_->thisDb().lookupObject<volVectorField>("fvSource"));
3386 
3387  label comp = dvSubDict.getLabel("comp");
3388 
3389  BC = fvSource[0][comp];
3390 
3391  this->globalADTape_.reset();
3392  this->globalADTape_.setActive();
3393  // register BC as the input
3394  this->globalADTape_.registerInput(BC);
3395  // ******* now set BC ******
3396  forAll(fvSource, cellI)
3397  {
3398  fvSource[cellI][comp] = BC;
3399  }
3400  }
3401  else
3402  {
3403 
3404  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3405  // name of the variable for changing the boundary condition
3406  word varName = dvSubDict.getWord("variable");
3407  // name of the boundary patch
3408  wordList patches;
3409  dvSubDict.readEntry<wordList>("patches", patches);
3410  // the component of a vector variable, ignore when it is a scalar
3411  label comp = dvSubDict.getLabel("comp");
3412 
3413  // Now get the BC value
3414  forAll(patches, idxI)
3415  {
3416  word patchName = patches[idxI];
3417  label patchI = meshPtr_->boundaryMesh().findPatchID(patchName);
3418  if (meshPtr_->thisDb().foundObject<volVectorField>(varName))
3419  {
3420  volVectorField& state(const_cast<volVectorField&>(
3421  meshPtr_->thisDb().lookupObject<volVectorField>(varName)));
3422  // for decomposed domain, don't set BC if the patch is empty
3423  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3424  {
3425  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3426  {
3427  BC = state.boundaryFieldRef()[patchI][0][comp];
3428  }
3429  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3430  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3431  {
3432  mixedFvPatchField<vector>& inletOutletPatch =
3433  refCast<mixedFvPatchField<vector>>(state.boundaryFieldRef()[patchI]);
3434  BC = inletOutletPatch.refValue()[0][comp];
3435  }
3436  }
3437  }
3438  else if (meshPtr_->thisDb().foundObject<volScalarField>(varName))
3439  {
3440  volScalarField& state(const_cast<volScalarField&>(
3441  meshPtr_->thisDb().lookupObject<volScalarField>(varName)));
3442  // for decomposed domain, don't set BC if the patch is empty
3443  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3444  {
3445  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3446  {
3447  BC = state.boundaryFieldRef()[patchI][0];
3448  }
3449  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3450  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3451  {
3452  mixedFvPatchField<scalar>& inletOutletPatch =
3453  refCast<mixedFvPatchField<scalar>>(state.boundaryFieldRef()[patchI]);
3454  BC = inletOutletPatch.refValue()[0];
3455  }
3456  }
3457  }
3458  }
3459  // need to reduce the BC value across all processors, this is because some of
3460  // the processors might not own the prescribed patches so their BC value will be still -1e16, but
3461  // when calling the following reduce function, they will get the correct BC from other processors
3462  reduce(BC, maxOp<scalar>());
3463 
3464  this->globalADTape_.reset();
3465  this->globalADTape_.setActive();
3466  // register BC as the input
3467  this->globalADTape_.registerInput(BC);
3468  // ******* now set BC ******
3469  forAll(patches, idxI)
3470  {
3471  word patchName = patches[idxI];
3472  label patchI = meshPtr_->boundaryMesh().findPatchID(patchName);
3473  if (meshPtr_->thisDb().foundObject<volVectorField>(varName))
3474  {
3475  volVectorField& state(const_cast<volVectorField&>(
3476  meshPtr_->thisDb().lookupObject<volVectorField>(varName)));
3477  // for decomposed domain, don't set BC if the patch is empty
3478  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3479  {
3480  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3481  {
3482  forAll(state.boundaryFieldRef()[patchI], faceI)
3483  {
3484  state.boundaryFieldRef()[patchI][faceI][comp] = BC;
3485  }
3486  }
3487  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3488  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3489  {
3490  mixedFvPatchField<vector>& inletOutletPatch =
3491  refCast<mixedFvPatchField<vector>>(state.boundaryFieldRef()[patchI]);
3492  vector val = inletOutletPatch.refValue()[0];
3493  val[comp] = BC;
3494  inletOutletPatch.refValue() = val;
3495  }
3496  }
3497  }
3498  else if (meshPtr_->thisDb().foundObject<volScalarField>(varName))
3499  {
3500  volScalarField& state(const_cast<volScalarField&>(
3501  meshPtr_->thisDb().lookupObject<volScalarField>(varName)));
3502  // for decomposed domain, don't set BC if the patch is empty
3503  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3504  {
3505  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3506  {
3507  forAll(state.boundaryFieldRef()[patchI], faceI)
3508  {
3509  state.boundaryFieldRef()[patchI][faceI] = BC;
3510  }
3511  }
3512  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3513  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3514  {
3515  mixedFvPatchField<scalar>& inletOutletPatch =
3516  refCast<mixedFvPatchField<scalar>>(state.boundaryFieldRef()[patchI]);
3517  inletOutletPatch.refValue() = BC;
3518  }
3519  }
3520  }
3521  }
3522  }
3523  // ******* now set BC done******
3524  // compute residuals
3525  daResidualPtr_->correctBoundaryConditions();
3526  daResidualPtr_->updateIntermediateVariables();
3527  daModelPtr_->correctBoundaryConditions();
3528  daModelPtr_->updateIntermediateVariables();
3529  label isPC = 0;
3530  dictionary options;
3531  options.set("isPC", isPC);
3532  daResidualPtr_->calcResiduals(options);
3533  daModelPtr_->calcResiduals(options);
3534 
3535  this->registerResidualOutput4AD();
3536  this->globalADTape_.setPassive();
3537 
3538  this->assignVec2ResidualGradient(psi);
3539  this->globalADTape_.evaluate();
3540 
3541  PetscScalar derivVal = BC.getGradient();
3542  // we need to do ADD_VALUES to get contribution from all procs
3543  VecSetValue(dRdBCTPsi, 0, derivVal, ADD_VALUES);
3544 
3545  VecAssemblyBegin(dRdBCTPsi);
3546  VecAssemblyEnd(dRdBCTPsi);
3547 
3548  this->globalADTape_.clearAdjoints();
3549  this->globalADTape_.reset();
3550 
3551  wordList writeJacobians;
3552  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3553  if (writeJacobians.found("dRdBCTPsi") || writeJacobians.found("all"))
3554  {
3555  word outputName = "dRdBCTPsi_" + designVarName;
3556  DAUtility::writeVectorBinary(dRdBCTPsi, outputName);
3557  DAUtility::writeVectorASCII(dRdBCTPsi, outputName);
3558  }
3559 #endif
3560 }
3561 
3563  const Vec xvVec,
3564  const Vec wVec,
3565  const word objFuncName,
3566  const word designVarName,
3567  Vec dFdBC)
3568 {
3569 #ifdef CODI_AD_REVERSE
3570  /*
3571  Description:
3572  This function computes partials derivatives dFdBC
3573 
3574  Input:
3575  xvVec: the volume mesh coordinate vector
3576  wVec: the state variable vector
3577  objFuncName: name of the objective function F
3578  designVarName: the name of the design variable
3579 
3580  Output:
3581  dFdBC: the partial derivative vector dF/dBC
3582  NOTE: You need to fully initialize the dF vec before calliing this function,
3583  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
3584  */
3585 
3586  Info << "Calculating dFdBC using reverse-mode AD for " << designVarName << endl;
3587 
3588  VecZeroEntries(dFdBC);
3589 
3590  // this is needed because the self.solverAD object in the Python layer
3591  // never run the primal solution, so the wVec and xvVec is not always
3592  // update to date
3593  this->updateOFField(wVec);
3594  this->updateOFMesh(xvVec);
3595 
3596  dictionary designVarDict = daOptionPtr_->getAllOptions().subDict("designVar");
3597 
3598  // get the subDict for this dvName
3599  dictionary dvSubDict = designVarDict.subDict(designVarName);
3600 
3601  scalar BC = -1e16;
3602 
3603  // now we need to get the BC value
3604  if (designVarName == "MRF")
3605  {
3606  const IOMRFZoneListDF& MRF = meshPtr_->thisDb().lookupObject<IOMRFZoneListDF>("MRFProperties");
3607  // first, we get the current value of omega and assign it to BC
3608  scalar& omega = const_cast<scalar&>(MRF.getOmegaRef());
3609  BC = omega;
3610  }
3611  else if (designVarName == "fvSource")
3612  {
3613  volVectorField& fvSource = const_cast<volVectorField&>(
3614  meshPtr_->thisDb().lookupObject<volVectorField>("fvSource"));
3615 
3616  label comp = dvSubDict.getLabel("comp");
3617 
3618  BC = fvSource[0][comp];
3619  }
3620  else
3621  {
3622  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3623  // name of the variable for changing the boundary condition
3624  word varName = dvSubDict.getWord("variable");
3625  // name of the boundary patch
3626  wordList patches;
3627  dvSubDict.readEntry<wordList>("patches", patches);
3628  // the compoent of a vector variable, ignore when it is a scalar
3629  label comp = dvSubDict.getLabel("comp");
3630 
3631  // Now get the BC value
3632  forAll(patches, idxI)
3633  {
3634  word patchName = patches[idxI];
3635  label patchI = meshPtr_->boundaryMesh().findPatchID(patchName);
3636  if (meshPtr_->thisDb().foundObject<volVectorField>(varName))
3637  {
3638  volVectorField& state(const_cast<volVectorField&>(
3639  meshPtr_->thisDb().lookupObject<volVectorField>(varName)));
3640  // for decomposed domain, don't set BC if the patch is empty
3641  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3642  {
3643  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3644  {
3645  BC = state.boundaryFieldRef()[patchI][0][comp];
3646  }
3647  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3648  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3649  {
3650  mixedFvPatchField<vector>& inletOutletPatch =
3651  refCast<mixedFvPatchField<vector>>(state.boundaryFieldRef()[patchI]);
3652  BC = inletOutletPatch.refValue()[0][comp];
3653  }
3654  }
3655  }
3656  else if (meshPtr_->thisDb().foundObject<volScalarField>(varName))
3657  {
3658  volScalarField& state(const_cast<volScalarField&>(
3659  meshPtr_->thisDb().lookupObject<volScalarField>(varName)));
3660  // for decomposed domain, don't set BC if the patch is empty
3661  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3662  {
3663  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3664  {
3665  BC = state.boundaryFieldRef()[patchI][0];
3666  }
3667  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3668  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3669  {
3670  mixedFvPatchField<scalar>& inletOutletPatch =
3671  refCast<mixedFvPatchField<scalar>>(state.boundaryFieldRef()[patchI]);
3672  BC = inletOutletPatch.refValue()[0];
3673  }
3674  }
3675  }
3676  }
3677  // need to reduce the BC value across all processors, this is because some of
3678  // the processors might not own the prescribed patches so their BC value will be still -1e16, but
3679  // when calling the following reduce function, they will get the correct BC from other processors
3680  reduce(BC, maxOp<scalar>());
3681  }
3682 
3683  // get the subDict for this objective function
3684  dictionary objFuncSubDict =
3685  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
3686  // loop over all parts of this objFuncName
3687  forAll(objFuncSubDict.toc(), idxK)
3688  {
3689  word objFuncPart = objFuncSubDict.toc()[idxK];
3690  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
3691 
3692  // initialize objFunc to get objFuncCellSources and objFuncFaceSources
3693  autoPtr<DAObjFunc> daObjFunc(DAObjFunc::New(
3694  meshPtr_(),
3695  daOptionPtr_(),
3696  daModelPtr_(),
3697  daIndexPtr_(),
3698  daResidualPtr_(),
3699  objFuncName,
3700  objFuncPart,
3701  objFuncSubDictPart));
3702 
3703  // reset tape
3704  this->globalADTape_.reset();
3705  // activate tape, start recording
3706  this->globalADTape_.setActive();
3707  // register BC as the input
3708  this->globalADTape_.registerInput(BC);
3709  // now set BC
3710  if (designVarName == "MRF")
3711  {
3712  const IOMRFZoneListDF& MRF = meshPtr_->thisDb().lookupObject<IOMRFZoneListDF>("MRFProperties");
3713  // first, we get the current value of omega and assign it to BC
3714  scalar& omega = const_cast<scalar&>(MRF.getOmegaRef());
3715  omega = BC;
3716  }
3717  else if (designVarName == "fvSource")
3718  {
3719  volVectorField& fvSource = const_cast<volVectorField&>(
3720  meshPtr_->thisDb().lookupObject<volVectorField>("fvSource"));
3721 
3722  label comp = dvSubDict.getLabel("comp");
3723 
3724  // ******* now set BC ******
3725  forAll(fvSource, cellI)
3726  {
3727  fvSource[cellI][comp] = BC;
3728  }
3729  }
3730  else
3731  {
3732  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3733  // name of the variable for changing the boundary condition
3734  word varName = dvSubDict.getWord("variable");
3735  // name of the boundary patch
3736  wordList patches;
3737  dvSubDict.readEntry<wordList>("patches", patches);
3738  // the compoent of a vector variable, ignore when it is a scalar
3739  label comp = dvSubDict.getLabel("comp");
3740  forAll(patches, idxI)
3741  {
3742  word patchName = patches[idxI];
3743  label patchI = meshPtr_->boundaryMesh().findPatchID(patchName);
3744  if (meshPtr_->thisDb().foundObject<volVectorField>(varName))
3745  {
3746  volVectorField& state(const_cast<volVectorField&>(
3747  meshPtr_->thisDb().lookupObject<volVectorField>(varName)));
3748  // for decomposed domain, don't set BC if the patch is empty
3749  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3750  {
3751  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3752  {
3753  forAll(state.boundaryFieldRef()[patchI], faceI)
3754  {
3755  state.boundaryFieldRef()[patchI][faceI][comp] = BC;
3756  }
3757  }
3758  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3759  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3760  {
3761  mixedFvPatchField<vector>& inletOutletPatch =
3762  refCast<mixedFvPatchField<vector>>(state.boundaryFieldRef()[patchI]);
3763  vector val = inletOutletPatch.refValue()[0];
3764  val[comp] = BC;
3765  inletOutletPatch.refValue() = val;
3766  }
3767  }
3768  }
3769  else if (meshPtr_->thisDb().foundObject<volScalarField>(varName))
3770  {
3771  volScalarField& state(const_cast<volScalarField&>(
3772  meshPtr_->thisDb().lookupObject<volScalarField>(varName)));
3773  // for decomposed domain, don't set BC if the patch is empty
3774  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
3775  {
3776  if (state.boundaryFieldRef()[patchI].type() == "fixedValue")
3777  {
3778  forAll(state.boundaryFieldRef()[patchI], faceI)
3779  {
3780  state.boundaryFieldRef()[patchI][faceI] = BC;
3781  }
3782  }
3783  else if (state.boundaryFieldRef()[patchI].type() == "inletOutlet"
3784  || state.boundaryFieldRef()[patchI].type() == "outletInlet")
3785  {
3786  mixedFvPatchField<scalar>& inletOutletPatch =
3787  refCast<mixedFvPatchField<scalar>>(state.boundaryFieldRef()[patchI]);
3788  inletOutletPatch.refValue() = BC;
3789  }
3790  }
3791  }
3792  }
3793  }
3794  // update all intermediate variables and boundary conditions
3795  daResidualPtr_->correctBoundaryConditions();
3796  daResidualPtr_->updateIntermediateVariables();
3797  daModelPtr_->correctBoundaryConditions();
3798  daModelPtr_->updateIntermediateVariables();
3799  // compute the objective function
3800  scalar fRef = daObjFunc->getObjFuncValue();
3801  // register f as the output
3802  this->globalADTape_.registerOutput(fRef);
3803  // stop recording
3804  this->globalADTape_.setPassive();
3805  // Note: since we used reduced objFunc, we only need to
3806  // assign the seed for master proc
3807  if (Pstream::master())
3808  {
3809  fRef.setGradient(1.0);
3810  }
3811  // evaluate tape to compute derivative
3812  this->globalADTape_.evaluate();
3813 
3814  // assign the computed derivatives from the OpenFOAM variable to dFdFieldPart
3815  Vec dFdBCPart;
3816  VecDuplicate(dFdBC, &dFdBCPart);
3817  VecZeroEntries(dFdBCPart);
3818  PetscScalar derivVal = BC.getGradient();
3819  // we need to do ADD_VALUES to get contribution from all procs
3820  VecSetValue(dFdBCPart, 0, derivVal, ADD_VALUES);
3821  VecAssemblyBegin(dFdBCPart);
3822  VecAssemblyEnd(dFdBCPart);
3823 
3824  // need to clear adjoint and tape after the computation is done!
3825  this->globalADTape_.clearAdjoints();
3826  this->globalADTape_.reset();
3827 
3828  // we need to add dFdBCPart to dFdBC because we want to sum
3829  // all dFdBCPart for all parts of this objFuncName.
3830  VecAXPY(dFdBC, 1.0, dFdBCPart);
3831 
3832  if (daOptionPtr_->getOption<label>("debug"))
3833  {
3834  Info << "In calcdFdBCAD" << endl;
3835  this->calcPrimalResidualStatistics("print");
3836  Info << objFuncName << ": " << fRef << endl;
3837  }
3838 
3839  VecDestroy(&dFdBCPart);
3840  }
3841 
3842  wordList writeJacobians;
3843  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3844  if (writeJacobians.found("dFdBC") || writeJacobians.found("all"))
3845  {
3846  word outputName = "dFdBC_" + designVarName + "_" + objFuncName;
3847  DAUtility::writeVectorBinary(dFdBC, outputName);
3848  DAUtility::writeVectorASCII(dFdBC, outputName);
3849  }
3850 #endif
3851 }
3852 
3854  const Vec xvVec,
3855  const Vec wVec,
3856  const word objFuncName,
3857  const word designVarName,
3858  Vec dFdAOA)
3859 {
3860  /*
3861  Description:
3862  This function computes partials derivatives dFdAOA
3863 
3864  Input:
3865  xvVec: the volume mesh coordinate vector
3866 
3867  wVec: the state variable vector
3868 
3869  objFuncName: name of the objective function F
3870 
3871  designVarName: the name of the design variable
3872 
3873  Output:
3874  dFdAOA: the partial derivative vector dF/dAOA
3875  NOTE: You need to fully initialize the dFdAOA vec before calliing this function,
3876  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
3877  */
3878 
3879  VecZeroEntries(dFdAOA);
3880 
3881  dictionary designVarDict = daOptionPtr_->getAllOptions().subDict("designVar");
3882 
3883  // get the subDict for this dvName
3884  dictionary dvSubDict = designVarDict.subDict(designVarName);
3885 
3886  // get info from dvSubDict. This needs to be defined in the pyDAFoam
3887  // name of the boundary patch
3888  wordList patches;
3889  dvSubDict.readEntry<wordList>("patches", patches);
3890  // the streamwise axis of aoa, aoa = tan( U_normal/U_flow )
3891  word flowAxis = dvSubDict.getWord("flowAxis");
3892  word normalAxis = dvSubDict.getWord("normalAxis");
3893 
3894  // no coloring is need for BC, so we create a dummy DAJacCon
3895  word dummyType = "dummy";
3896  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
3897  dummyType,
3898  meshPtr_(),
3899  daOptionPtr_(),
3900  daModelPtr_(),
3901  daIndexPtr_()));
3902 
3903  // get the subDict for this objective function
3904  dictionary objFuncSubDict =
3905  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
3906 
3907  // loop over all parts of this objFuncName
3908  forAll(objFuncSubDict.toc(), idxK)
3909  {
3910  word objFuncPart = objFuncSubDict.toc()[idxK];
3911  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
3912 
3913  Mat dFdAOAMat;
3914  MatCreate(PETSC_COMM_WORLD, &dFdAOAMat);
3915 
3916  // initialize DAPartDeriv for dFdAOAMat
3917  word modelType = "dFdAOA";
3918  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
3919  modelType,
3920  meshPtr_(),
3921  daOptionPtr_(),
3922  daModelPtr_(),
3923  daIndexPtr_(),
3924  daJacCon(),
3925  daResidualPtr_()));
3926 
3927  // initialize options
3928  dictionary options;
3929  options.set("objFuncName", objFuncName);
3930  options.set("objFuncPart", objFuncPart);
3931  options.set("objFuncSubDictPart", objFuncSubDictPart);
3932  options.set("patches", patches);
3933  options.set("flowAxis", flowAxis);
3934  options.set("normalAxis", normalAxis);
3935 
3936  // initialize dFdAOA
3937  daPartDeriv->initializePartDerivMat(options, dFdAOAMat);
3938 
3939  // calculate it
3940  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dFdAOAMat);
3941 
3942  // NOTE: dFdAOAMat is a 1 by 1 matrix but we just do a matrix-vector product
3943  // to convert dFdAOAMat from a matrix to a vector
3944  Vec dFdAOAPart, oneVec;
3945  VecDuplicate(dFdAOA, &oneVec);
3946  VecSet(oneVec, 1.0);
3947  VecDuplicate(dFdAOA, &dFdAOAPart);
3948  VecZeroEntries(dFdAOAPart);
3949  // dFdAOAPart = dFdAOAMat * oneVec
3950  MatMult(dFdAOAMat, oneVec, dFdAOAPart);
3951 
3952  // we need to add dFdAOAVec to dFdAOAVecAllParts because we want to sum
3953  // all dFdAOAVec for all parts of this objFuncName.
3954  VecAXPY(dFdAOA, 1.0, dFdAOAPart);
3955 
3956  if (daOptionPtr_->getOption<label>("debug"))
3957  {
3958  this->calcPrimalResidualStatistics("print");
3959  }
3960 
3961  // clear up
3962  MatDestroy(&dFdAOAMat);
3963  VecDestroy(&dFdAOAPart);
3964  VecDestroy(&oneVec);
3965  }
3966 
3967  wordList writeJacobians;
3968  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
3969  if (writeJacobians.found("dFdAOA") || writeJacobians.found("all"))
3970  {
3971  word outputName = "dFdAOA_" + designVarName;
3972  DAUtility::writeVectorBinary(dFdAOA, outputName);
3973  DAUtility::writeVectorASCII(dFdAOA, outputName);
3974  }
3975 }
3976 
3978  const Vec xvVec,
3979  const Vec wVec,
3980  const Vec psi,
3981  const word designVarName,
3982  Vec dRdAOATPsi)
3983 {
3984 #ifdef CODI_AD_REVERSE
3985  /*
3986  Description:
3987  Compute the matrix-vector products dRdAOA^T*Psi using reverse-mode AD
3988  Similar to dF/dAlpha, here
3989  dR/dAlpha = dR/dTan(Alpha) * dTan(Alpha)/dAlpha
3990  = dR/d(Uy/Ux) / Cos(Alpha)^2
3991  = dR/dUy * Ux / Cos(Alpha)^2
3992 
3993  Input:
3994 
3995  xvVec: the volume mesh coordinate vector
3996 
3997  wVec: the state variable vector
3998 
3999  psi: the vector to multiply dRdAOA
4000 
4001  designVarName: name of the design variable
4002 
4003  Output:
4004  dRdAOATPsi: the matrix-vector products dRdAOA^T * Psi
4005  */
4006 
4007  Info << "Calculating [dRdAOA]^T * Psi using reverse-mode AD" << endl;
4008 
4009  VecZeroEntries(dRdAOATPsi);
4010 
4011  this->updateOFField(wVec);
4012  this->updateOFMesh(xvVec);
4013 
4014  dictionary designVarDict = daOptionPtr_->getAllOptions().subDict("designVar");
4015 
4016  // get the subDict for this dvName
4017  dictionary dvSubDict = designVarDict.subDict(designVarName);
4018 
4019  // get info from dvSubDict. This needs to be defined in the pyDAFoam
4020  // name of the boundary patch
4021  wordList patches;
4022  dvSubDict.readEntry<wordList>("patches", patches);
4023  // the streamwise axis of aoa, aoa = tan( U_normal/U_flow )
4024  word flowAxis = dvSubDict.getWord("flowAxis");
4025  word normalAxis = dvSubDict.getWord("normalAxis");
4026 
4027  HashTable<label> axisIndices;
4028  axisIndices.set("x", 0);
4029  axisIndices.set("y", 1);
4030  axisIndices.set("z", 2);
4031  label flowAxisIndex = axisIndices[flowAxis];
4032  label normalAxisIndex = axisIndices[normalAxis];
4033 
4034  volVectorField& U = const_cast<volVectorField&>(
4035  meshPtr_->thisDb().lookupObject<volVectorField>("U"));
4036 
4037  // now we need to get the Ux, Uy values from the inout patches
4038  scalar Ux0 = -1e16, Uy0 = -1e16;
4039  forAll(patches, idxI)
4040  {
4041  word patchName = patches[idxI];
4042  label patchI = meshPtr_->boundaryMesh().findPatchID(patchName);
4043  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
4044  {
4045  if (U.boundaryField()[patchI].type() == "fixedValue")
4046  {
4047  Uy0 = U.boundaryField()[patchI][0][normalAxisIndex];
4048  Ux0 = U.boundaryField()[patchI][0][flowAxisIndex];
4049  break;
4050  }
4051  else if (U.boundaryField()[patchI].type() == "inletOutlet")
4052  {
4053  mixedFvPatchField<vector>& inletOutletPatch =
4054  refCast<mixedFvPatchField<vector>>(U.boundaryFieldRef()[patchI]);
4055  Uy0 = inletOutletPatch.refValue()[0][normalAxisIndex];
4056  Ux0 = inletOutletPatch.refValue()[0][flowAxisIndex];
4057  break;
4058  }
4059  else
4060  {
4061  FatalErrorIn("") << "boundaryType: " << U.boundaryFieldRef()[patchI].type()
4062  << " not supported!"
4063  << "Avaiable options are: fixedValue, inletOutlet"
4064  << abort(FatalError);
4065  }
4066  }
4067  }
4068  // need to reduce the U value across all processors, this is because some of
4069  // the processors might not own the prescribed patches so their U value will be still -1e16, but
4070  // when calling the following reduce function, they will get the correct U from other processors
4071  reduce(Ux0, maxOp<scalar>());
4072  reduce(Uy0, maxOp<scalar>());
4073  scalar aoa = atan(Uy0 / Ux0);
4074 
4075  this->globalADTape_.reset();
4076  this->globalADTape_.setActive();
4077  // register aoa as the input
4078  this->globalADTape_.registerInput(aoa);
4079  // set far field U
4080  forAll(patches, idxI)
4081  {
4082  word patchName = patches[idxI];
4083  label patchI = meshPtr_->boundaryMesh().findPatchID(patchName);
4084 
4085  if (meshPtr_->boundaryMesh()[patchI].size() > 0)
4086  {
4087  scalar UMag = sqrt(Ux0 * Ux0 + Uy0 * Uy0);
4088  scalar UxNew = UMag * cos(aoa);
4089  scalar UyNew = UMag * sin(aoa);
4090 
4091  if (U.boundaryField()[patchI].type() == "fixedValue")
4092  {
4093  forAll(U.boundaryField()[patchI], faceI)
4094  {
4095  U.boundaryFieldRef()[patchI][faceI][flowAxisIndex] = UxNew;
4096  U.boundaryFieldRef()[patchI][faceI][normalAxisIndex] = UyNew;
4097  }
4098  }
4099  else if (U.boundaryField()[patchI].type() == "inletOutlet")
4100  {
4101  mixedFvPatchField<vector>& inletOutletPatch =
4102  refCast<mixedFvPatchField<vector>>(U.boundaryFieldRef()[patchI]);
4103 
4104  forAll(U.boundaryField()[patchI], faceI)
4105  {
4106  inletOutletPatch.refValue()[faceI][flowAxisIndex] = UxNew;
4107  inletOutletPatch.refValue()[faceI][normalAxisIndex] = UyNew;
4108  }
4109  }
4110  }
4111  }
4112  // compute residuals
4113  daResidualPtr_->correctBoundaryConditions();
4114  daResidualPtr_->updateIntermediateVariables();
4115  daModelPtr_->correctBoundaryConditions();
4116  daModelPtr_->updateIntermediateVariables();
4117  label isPC = 0;
4118  dictionary options;
4119  options.set("isPC", isPC);
4120  daResidualPtr_->calcResiduals(options);
4121  daModelPtr_->calcResiduals(options);
4122 
4123  this->registerResidualOutput4AD();
4124  this->globalADTape_.setPassive();
4125 
4126  this->assignVec2ResidualGradient(psi);
4127  this->globalADTape_.evaluate();
4128 
4129  // need to convert dFdAOA from radian to degree
4130  PetscScalar derivVal = aoa.getGradient() * constant::mathematical::pi.getValue() / 180.0;
4131  // we need to do ADD_VALUES to get contribution from all procs
4132  VecSetValue(dRdAOATPsi, 0, derivVal, ADD_VALUES);
4133 
4134  VecAssemblyBegin(dRdAOATPsi);
4135  VecAssemblyEnd(dRdAOATPsi);
4136 
4137  this->globalADTape_.clearAdjoints();
4138  this->globalADTape_.reset();
4139 
4140  wordList writeJacobians;
4141  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
4142  if (writeJacobians.found("dRdAOATPsi") || writeJacobians.found("all"))
4143  {
4144  word outputName = "dRdAOATPsi_" + designVarName;
4145  DAUtility::writeVectorBinary(dRdAOATPsi, outputName);
4146  DAUtility::writeVectorASCII(dRdAOATPsi, outputName);
4147  }
4148 #endif
4149 }
4150 
4152  const Vec xvVec,
4153  const Vec wVec,
4154  const word designVarName,
4155  Mat dRdFFD)
4156 {
4157  /*
4158  Description:
4159  This function computes partials derivatives dRdFFD
4160 
4161  Input:
4162  xvVec: the volume mesh coordinate vector
4163 
4164  wVec: the state variable vector
4165 
4166  designVarName: the name of the design variable
4167 
4168  Output:
4169  dRdFFD: the partial derivative matrix dR/dFFD
4170  NOTE: You need to call MatCreate for the dRdFFD matrix before calling this function.
4171  No need to call MatSetSize etc because they will be done in this function
4172  */
4173 
4174  // get the size of dXvdFFDMat_, nCols will be the number of FFD points
4175  // for this design variable
4176  // NOTE: dXvdFFDMat_ needs to be assigned by calling DASolver::setdXvdFFDMat in
4177  // the python layer
4178  label nDesignVars = -9999;
4179  MatGetSize(dXvdFFDMat_, NULL, &nDesignVars);
4180 
4181  // no coloring is need for FFD, so we create a dummy DAJacCon
4182  word dummyType = "dummy";
4183  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
4184  dummyType,
4185  meshPtr_(),
4186  daOptionPtr_(),
4187  daModelPtr_(),
4188  daIndexPtr_()));
4189 
4190  // create DAPartDeriv object
4191  word modelType = "dRdFFD";
4192  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
4193  modelType,
4194  meshPtr_(),
4195  daOptionPtr_(),
4196  daModelPtr_(),
4197  daIndexPtr_(),
4198  daJacCon(),
4199  daResidualPtr_()));
4200 
4201  // setup options
4202  dictionary options;
4203  options.set("nDesignVars", nDesignVars);
4204  options.set("isPC", 0);
4205 
4206  // for FFD, we need to first assign dXvdFFDMat to daPartDeriv
4207  daPartDeriv->setdXvdFFDMat(dXvdFFDMat_);
4208 
4209  // initialize the dRdFFD matrix
4210  daPartDeriv->initializePartDerivMat(options, dRdFFD);
4211 
4212  // compute it using brute force finite-difference
4213  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dRdFFD);
4214 
4215  if (daOptionPtr_->getOption<label>("debug"))
4216  {
4217  this->calcPrimalResidualStatistics("print");
4218  }
4219 
4220  wordList writeJacobians;
4221  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
4222  if (writeJacobians.found("dRdFFD") || writeJacobians.found("all"))
4223  {
4224  word outputName = "dRdFFD_" + designVarName;
4225  DAUtility::writeMatrixBinary(dRdFFD, outputName);
4226  DAUtility::writeMatrixASCII(dRdFFD, outputName);
4227  }
4228 
4229  // clear up dXvdFFD Mat in daPartDeriv
4230  daPartDeriv->clear();
4231 }
4232 
4234  const Vec xvVec,
4235  const Vec wVec,
4236  const word objFuncName,
4237  const word designVarName,
4238  Vec dFdFFD)
4239 {
4240  /*
4241  Description:
4242  This function computes partials derivatives dFdFFD
4243 
4244  Input:
4245  xvVec: the volume mesh coordinate vector
4246 
4247  wVec: the state variable vector
4248 
4249  objFuncName: name of the objective function F
4250 
4251  designVarName: the name of the design variable
4252 
4253  Output:
4254  dFdFFD: the partial derivative vector dF/dFFD
4255  NOTE: You need to fully initialize the dFdFFD vec before calliing this function,
4256  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
4257  */
4258 
4259  VecZeroEntries(dFdFFD);
4260 
4261  // get the size of dXvdFFDMat_, nCols will be the number of FFD points
4262  // for this design variable
4263  // NOTE: dXvdFFDMat_ needs to be assigned by calling DASolver::setdXvdFFDMat in
4264  // the python layer
4265  label nDesignVars = -9999;
4266  MatGetSize(dXvdFFDMat_, NULL, &nDesignVars);
4267 
4268  // no coloring is need for FFD, so we create a dummy DAJacCon
4269  word dummyType = "dummy";
4270  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
4271  dummyType,
4272  meshPtr_(),
4273  daOptionPtr_(),
4274  daModelPtr_(),
4275  daIndexPtr_()));
4276 
4277  // get the subDict for this objective function
4278  dictionary objFuncSubDict =
4279  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
4280 
4281  // loop over all parts of this objFuncName
4282  forAll(objFuncSubDict.toc(), idxK)
4283  {
4284  word objFuncPart = objFuncSubDict.toc()[idxK];
4285  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
4286 
4287  Mat dFdFFDMat;
4288  MatCreate(PETSC_COMM_WORLD, &dFdFFDMat);
4289 
4290  // initialize DAPartDeriv for dFdFFD
4291  word modelType = "dFdFFD";
4292  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
4293  modelType,
4294  meshPtr_(),
4295  daOptionPtr_(),
4296  daModelPtr_(),
4297  daIndexPtr_(),
4298  daJacCon(),
4299  daResidualPtr_()));
4300 
4301  // initialize options
4302  dictionary options;
4303  options.set("objFuncName", objFuncName);
4304  options.set("objFuncPart", objFuncPart);
4305  options.set("objFuncSubDictPart", objFuncSubDictPart);
4306  options.set("nDesignVars", nDesignVars);
4307 
4308  // for FFD, we need to first assign dXvdFFDMat to daPartDeriv
4309  daPartDeriv->setdXvdFFDMat(dXvdFFDMat_);
4310 
4311  // initialize dFdFFD
4312  daPartDeriv->initializePartDerivMat(options, dFdFFDMat);
4313 
4314  // calculate it
4315  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dFdFFDMat);
4316 
4317  // now we need to convert the dFdFFD mat to dFdFFDPart
4318  // NOTE: dFdFFDMat is a 1 by nDesignVars matrix but dFdFFDPart is
4319  // a nDesignVars by 1 vector, we need to do
4320  // dFdFFDPart = (dFdFFDMat)^T * oneVec
4321  Vec dFdFFDPart, oneVec;
4322  VecCreate(PETSC_COMM_WORLD, &oneVec);
4323  VecSetSizes(oneVec, PETSC_DETERMINE, 1);
4324  VecSetFromOptions(oneVec);
4325  VecSet(oneVec, 1.0);
4326  VecDuplicate(dFdFFD, &dFdFFDPart);
4327  VecZeroEntries(dFdFFDPart);
4328  // dFdFFDVec = oneVec*dFdFFD
4329  MatMultTranspose(dFdFFDMat, oneVec, dFdFFDPart);
4330 
4331  // we need to add dFdFFDPart to dFdFFD because we want to sum
4332  // all dFdFFDPart for all parts of this objFuncName.
4333  VecAXPY(dFdFFD, 1.0, dFdFFDPart);
4334 
4335  if (daOptionPtr_->getOption<label>("debug"))
4336  {
4337  this->calcPrimalResidualStatistics("print");
4338  }
4339 
4340  MatDestroy(&dFdFFDMat);
4341  VecDestroy(&dFdFFDPart);
4342  VecDestroy(&oneVec);
4343 
4344  // clear up dXvdFFD Mat in daPartDeriv
4345  daPartDeriv->clear();
4346  }
4347 
4348  wordList writeJacobians;
4349  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
4350  if (writeJacobians.found("dFdFFD") || writeJacobians.found("all"))
4351  {
4352  word outputName = "dFdFFD_" + designVarName;
4353  DAUtility::writeVectorBinary(dFdFFD, outputName);
4354  DAUtility::writeVectorASCII(dFdFFD, outputName);
4355  }
4356 }
4357 
4359  const Vec xvVec,
4360  const Vec wVec,
4361  const word designVarName,
4362  const word designVarType,
4363  Mat dRdACT)
4364 {
4365  /*
4366  Description:
4367  This function computes partials derivatives dRdACT
4368 
4369  Input:
4370  xvVec: the volume mesh coordinate vector
4371 
4372  wVec: the state variable vector
4373 
4374  designVarName: the name of the design variable
4375 
4376  designVarType: the type of the design variable: ACTP, ACTL, ACTD
4377 
4378  Output:
4379  dRdACT: the partial derivative matrix dR/dACT
4380  NOTE: You need to call MatCreate for the dRdACT matrix before calling this function.
4381  No need to call MatSetSize etc because they will be done in this function
4382  */
4383 
4384  // get the subDict for this dvName
4385  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
4386  word actuatorName = dvSubDict.getWord("actuatorName");
4387 
4388  // no coloring is need for actuator, so we create a dummy DAJacCon
4389  word dummyType = "dummy";
4390  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
4391  dummyType,
4392  meshPtr_(),
4393  daOptionPtr_(),
4394  daModelPtr_(),
4395  daIndexPtr_()));
4396 
4397  // create DAPartDeriv object
4398  word modelType = "dRd" + designVarType;
4399  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
4400  modelType,
4401  meshPtr_(),
4402  daOptionPtr_(),
4403  daModelPtr_(),
4404  daIndexPtr_(),
4405  daJacCon(),
4406  daResidualPtr_()));
4407 
4408  // setup options to compute dRdACT*
4409  dictionary options;
4410  options.set("actuatorName", actuatorName);
4411  options.set("isPC", 0);
4412 
4413  // initialize the dRdACT* matrix
4414  daPartDeriv->initializePartDerivMat(options, dRdACT);
4415 
4416  // compute it using brute force finite-difference
4417  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dRdACT);
4418 
4419  if (daOptionPtr_->getOption<label>("debug"))
4420  {
4421  this->calcPrimalResidualStatistics("print");
4422  }
4423 
4424  wordList writeJacobians;
4425  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
4426  if (writeJacobians.found("dRdACT") || writeJacobians.found("all"))
4427  {
4428  word outputName = "dRd" + designVarType + "_" + designVarName;
4429  DAUtility::writeMatrixBinary(dRdACT, outputName);
4430  DAUtility::writeMatrixASCII(dRdACT, outputName);
4431  }
4432 }
4433 
4435  const Vec xvVec,
4436  const Vec wVec,
4437  const word objFuncName,
4438  const word designVarName,
4439  const word designVarType,
4440  Vec dFdACT)
4441 {
4442  /*
4443  Description:
4444  This function computes partials derivatives dFdW
4445 
4446  Input:
4447  xvVec: the volume mesh coordinate vector
4448 
4449  wVec: the state variable vector
4450 
4451  objFuncName: name of the objective function F
4452 
4453  designVarName: the name of the design variable
4454 
4455  Output:
4456  dFdACT: the partial derivative vector dF/dACT
4457  NOTE: You need to fully initialize the dFdACT vec before calliing this function,
4458  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
4459  */
4460 
4461  VecZeroEntries(dFdACT);
4462 
4463  if (designVarType != "ACTD")
4464  {
4465  return;
4466  }
4467 
4468  // no coloring is need for ACT, so we create a dummy DAJacCon
4469  word dummyType = "dummy";
4470  autoPtr<DAJacCon> daJacCon(DAJacCon::New(
4471  dummyType,
4472  meshPtr_(),
4473  daOptionPtr_(),
4474  daModelPtr_(),
4475  daIndexPtr_()));
4476 
4477  // get the subDict for this dvName
4478  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
4479  word actuatorName = dvSubDict.getWord("actuatorName");
4480 
4481  // get the subDict for this objective function
4482  dictionary objFuncSubDict =
4483  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
4484 
4485  // loop over all parts of this objFuncName
4486  forAll(objFuncSubDict.toc(), idxK)
4487  {
4488  word objFuncPart = objFuncSubDict.toc()[idxK];
4489  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
4490 
4491  Mat dFdACTMat;
4492  MatCreate(PETSC_COMM_WORLD, &dFdACTMat);
4493 
4494  // initialize DAPartDeriv for dFdACT
4495  word modelType = "dFd" + designVarType;
4496  autoPtr<DAPartDeriv> daPartDeriv(DAPartDeriv::New(
4497  modelType,
4498  meshPtr_(),
4499  daOptionPtr_(),
4500  daModelPtr_(),
4501  daIndexPtr_(),
4502  daJacCon(),
4503  daResidualPtr_()));
4504 
4505  // initialize options
4506  dictionary options;
4507  options.set("objFuncName", objFuncName);
4508  options.set("objFuncPart", objFuncPart);
4509  options.set("objFuncSubDictPart", objFuncSubDictPart);
4510  options.set("actuatorName", actuatorName);
4511 
4512  // initialize dFdACT
4513  daPartDeriv->initializePartDerivMat(options, dFdACTMat);
4514 
4515  // calculate it
4516  daPartDeriv->calcPartDerivMat(options, xvVec, wVec, dFdACTMat);
4517 
4518  // now we need to extract the dFdACT from dFdACTMatrix
4519  // NOTE: dFdACTMat is a nACTDVs by 1 matrix
4520  Vec dFdACTPart;
4521  VecDuplicate(dFdACT, &dFdACTPart);
4522  VecZeroEntries(dFdACTPart);
4523  MatGetColumnVector(dFdACTMat, dFdACTPart, 0);
4524 
4525  // we need to add dFdACTPart to dFdACT because we want to sum
4526  // all parts of this objFuncName.
4527  VecAXPY(dFdACT, 1.0, dFdACTPart);
4528 
4529  if (daOptionPtr_->getOption<label>("debug"))
4530  {
4531  this->calcPrimalResidualStatistics("print");
4532  }
4533 
4534  // clear up
4535  MatDestroy(&dFdACTMat);
4536  VecDestroy(&dFdACTPart);
4537  }
4538 
4539  wordList writeJacobians;
4540  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
4541  if (writeJacobians.found("dFdACT") || writeJacobians.found("all"))
4542  {
4543  word outputName = "dFdACT_" + designVarName;
4544  DAUtility::writeVectorBinary(dFdACT, outputName);
4545  DAUtility::writeVectorASCII(dFdACT, outputName);
4546  }
4547 }
4548 
4550  const Vec xvVec,
4551  const Vec wVec,
4552  const word objFuncName,
4553  const word designVarName,
4554  Vec dFdField)
4555 {
4556 #ifdef CODI_AD_REVERSE
4557  /*
4558  Description:
4559  This function computes partials derivatives dFdField
4560 
4561  Input:
4562  xvVec: the volume mesh coordinate vector
4563 
4564  wVec: the state variable vector
4565 
4566  objFuncName: name of the objective function F
4567 
4568  designVarName: the name of the design variable
4569 
4570  Output:
4571  dFdField: the partial derivative vector dF/dField
4572  NOTE: You need to fully initialize the dF vec before calliing this function,
4573  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
4574  */
4575 
4576  Info << "Calculating dFdField using reverse-mode AD for " << designVarName << endl;
4577 
4578  VecZeroEntries(dFdField);
4579 
4580  // this is needed because the self.solverAD object in the Python layer
4581  // never run the primal solution, so the wVec and xvVec is not always
4582  // update to date
4583  this->updateOFField(wVec);
4584  this->updateOFMesh(xvVec);
4585 
4586  // get the subDict for this objective function
4587  dictionary objFuncSubDict =
4588  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
4589 
4590  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
4591 
4592  word fieldName = dvSubDict.getWord("fieldName");
4593  word fieldType = dvSubDict.getWord("fieldType");
4594 
4595  // loop over all parts of this objFuncName
4596  forAll(objFuncSubDict.toc(), idxK)
4597  {
4598  word objFuncPart = objFuncSubDict.toc()[idxK];
4599  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
4600 
4601  // initialize objFunc to get objFuncCellSources and objFuncFaceSources
4602  autoPtr<DAObjFunc> daObjFunc(DAObjFunc::New(
4603  meshPtr_(),
4604  daOptionPtr_(),
4605  daModelPtr_(),
4606  daIndexPtr_(),
4607  daResidualPtr_(),
4608  objFuncName,
4609  objFuncPart,
4610  objFuncSubDictPart));
4611 
4612  // reset tape
4613  this->globalADTape_.reset();
4614  // activate tape, start recording
4615  this->globalADTape_.setActive();
4616  // register states as the input
4617  this->registerFieldVariableInput4AD(fieldName, fieldType);
4618  this->updateBoundaryConditions(fieldName, fieldType);
4619  // update all intermediate variables and boundary conditions
4620  daResidualPtr_->correctBoundaryConditions();
4621  daResidualPtr_->updateIntermediateVariables();
4622  daModelPtr_->correctBoundaryConditions();
4623  daModelPtr_->updateIntermediateVariables();
4624  // compute the objective function
4625  scalar fRef = daObjFunc->getObjFuncValue();
4626  // register f as the output
4627  this->globalADTape_.registerOutput(fRef);
4628  // stop recording
4629  this->globalADTape_.setPassive();
4630 
4631  // Note: since we used reduced objFunc, we only need to
4632  // assign the seed for master proc
4633  if (Pstream::master())
4634  {
4635  fRef.setGradient(1.0);
4636  }
4637  // evaluate tape to compute derivative
4638  this->globalADTape_.evaluate();
4639 
4640  // assign the computed derivatives from the OpenFOAM variable to dFdFieldPart
4641  Vec dFdFieldPart;
4642  VecDuplicate(dFdField, &dFdFieldPart);
4643  VecZeroEntries(dFdFieldPart);
4644  this->assignFieldGradient2Vec(fieldName, fieldType, dFdFieldPart);
4645 
4646  // need to clear adjoint and tape after the computation is done!
4647  this->globalADTape_.clearAdjoints();
4648  this->globalADTape_.reset();
4649 
4650  // we need to add dFdFieldPart to dFdField because we want to sum
4651  // all dFdFieldPart for all parts of this objFuncName.
4652  VecAXPY(dFdField, 1.0, dFdFieldPart);
4653 
4654  if (daOptionPtr_->getOption<label>("debug"))
4655  {
4656  Info << "In calcdFdFieldAD" << endl;
4657  this->calcPrimalResidualStatistics("print");
4658  Info << objFuncName << ": " << fRef << endl;
4659  }
4660 
4661  VecDestroy(&dFdFieldPart);
4662  }
4663 
4664  wordList writeJacobians;
4665  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
4666  if (writeJacobians.found("dFdField") || writeJacobians.found("all"))
4667  {
4668  word outputName = "dFdField_" + designVarName;
4669  DAUtility::writeVectorBinary(dFdField, outputName);
4670  DAUtility::writeVectorASCII(dFdField, outputName);
4671  }
4672 #endif
4673 }
4674 
4676  const Mat jacMat,
4677  const Mat jacPCMat,
4678  KSP ksp)
4679 {
4680  /*
4681  Description:
4682  Call createMLRKSP from DALinearEqn
4683  This is the main function we need to call to initialize the KSP and set
4684  up parameters for solving the linear equations
4685  */
4686 
4687  daLinearEqnPtr_->createMLRKSP(jacMat, jacPCMat, ksp);
4688 }
4689 
4691  const Mat jacPCMat,
4692  KSP ksp)
4693 {
4694 #ifdef CODI_AD_REVERSE
4695  /*
4696  Description:
4697  Call createMLRKSP from DALinearEqn
4698  This is the main function we need to call to initialize the KSP and set
4699  up parameters for solving the linear equations
4700  NOTE: this is the matrix-free version of the createMLRKSP function.
4701  We dont need to input the jacMat because we will use dRdWTMF_: the
4702  matrix-free state Jacobian matrix
4703  */
4704 
4705  daLinearEqnPtr_->createMLRKSP(dRdWTMF_, jacPCMat, ksp);
4706 #endif
4707 }
4708 
4710  const KSP ksp,
4711  const Vec rhsVec,
4712  Vec solVec)
4713 {
4714  /*
4715  Description:
4716  Call solveLinearEqn from DALinearEqn to solve a linear equation.
4717 
4718  Input:
4719  ksp: the KSP object, obtained from calling Foam::createMLRKSP
4720 
4721  rhsVec: the right-hand-side petsc vector
4722 
4723  Output:
4724  solVec: the solution vector
4725 
4726  Return 0 if the linear equation solution finished successfully otherwise return 1
4727  */
4728 
4729  label error = daLinearEqnPtr_->solveLinearEqn(ksp, rhsVec, solVec);
4730 
4731  // need to reset globalADTapeInitialized to 0 because every matrix-free
4732  // adjoint solution need to re-initialize the AD tape
4734 
4735  return error;
4736 }
4737 
4739 {
4740  /*
4741  Description:
4742  RESET the seeds to all state variables and vol coordinates to zeros
4743  This is done by passing a double array to the OpenFOAM's scalar field
4744  and setting all the gradient part to zero.
4745  In CODIPack, if we pass a double value to a scalar value, it will assign
4746  an zero to the scalar variable's gradient part (seeds).
4747  NOTE. this is important because CODIPack's tape.reset does not clean
4748  the seeds in the OpenFOAM's variables. So we need to manually reset them
4749  Not doing this will cause inaccurate AD values.
4750 
4751  Outputs:
4752 
4753  The OpenFOAM variables's seed values
4754  */
4755 
4756  this->setPrimalBoundaryConditions(0);
4757  daFieldPtr_->resetOFSeeds();
4758 
4759  daResidualPtr_->correctBoundaryConditions();
4760  daResidualPtr_->updateIntermediateVariables();
4761  daModelPtr_->correctBoundaryConditions();
4762  daModelPtr_->updateIntermediateVariables();
4763 }
4764 
4765 void DASolver::updateOFField(const Vec wVec)
4766 {
4767  /*
4768  Description:
4769  Update the OpenFOAM field values (including both internal
4770  and boundary fields) based on the state vector wVec
4771 
4772  Input:
4773  wVec: state variable vector
4774 
4775  Output:
4776  OpenFoam flow fields (internal and boundary)
4777  */
4778 
4779  label printInfo = 0;
4780  if (daOptionPtr_->getOption<label>("debug"))
4781  {
4782  Info << "Updating the OpenFOAM field..." << endl;
4783  printInfo = 1;
4784  }
4785  this->setPrimalBoundaryConditions(printInfo);
4786  daFieldPtr_->stateVec2OFField(wVec);
4787  // We need to call correctBC multiple times to reproduce
4788  // the exact residual, this is needed for some boundary conditions
4789  // and intermediate variables (e.g., U for inletOutlet, nut with wall functions)
4790  label maxCorrectBCCalls = daOptionPtr_->getOption<label>("maxCorrectBCCalls");
4791  for (label i = 0; i < maxCorrectBCCalls; i++)
4792  {
4793  daResidualPtr_->correctBoundaryConditions();
4794  daResidualPtr_->updateIntermediateVariables();
4795  daModelPtr_->correctBoundaryConditions();
4796  daModelPtr_->updateIntermediateVariables();
4797  }
4798 }
4799 
4800 void DASolver::updateOFField(const scalar* states)
4801 {
4802  label printInfo = 0;
4803  if (daOptionPtr_->getOption<label>("debug"))
4804  {
4805  Info << "Updating the OpenFOAM field..." << endl;
4806  printInfo = 1;
4807  }
4808  this->setPrimalBoundaryConditions(printInfo);
4809  daFieldPtr_->state2OFField(states);
4810  // We need to call correctBC multiple times to reproduce
4811  // the exact residual, this is needed for some boundary conditions
4812  // and intermediate variables (e.g., U for inletOutlet, nut with wall functions)
4813  label maxCorrectBCCalls = daOptionPtr_->getOption<label>("maxCorrectBCCalls");
4814  for (label i = 0; i < maxCorrectBCCalls; i++)
4815  {
4816  daResidualPtr_->correctBoundaryConditions();
4817  daResidualPtr_->updateIntermediateVariables();
4818  daModelPtr_->correctBoundaryConditions();
4819  daModelPtr_->updateIntermediateVariables();
4820  }
4821 }
4822 
4823 void DASolver::updateOFMesh(const Vec xvVec)
4824 {
4825  /*
4826  Description:
4827  Update the OpenFOAM mesh based on the point vector xvVec
4828 
4829  Input:
4830  xvVec: point coordinate vector
4831 
4832  Output:
4833  OpenFoam flow fields (internal and boundary)
4834  */
4835  if (daOptionPtr_->getOption<label>("debug"))
4836  {
4837  Info << "Updating the OpenFOAM mesh..." << endl;
4838  }
4839  daFieldPtr_->pointVec2OFMesh(xvVec);
4840 }
4841 
4842 void DASolver::updateOFMesh(const scalar* volCoords)
4843 {
4844  /*
4845  Description:
4846  Update the OpenFOAM mesh based on the volume coordinates point volCoords
4847 
4848  Input:
4849  volCoords: point coordinate array
4850 
4851  Output:
4852  OpenFoam flow fields (internal and boundary)
4853  */
4854  if (daOptionPtr_->getOption<label>("debug"))
4855  {
4856  Info << "Updating the OpenFOAM mesh..." << endl;
4857  }
4858  daFieldPtr_->point2OFMesh(volCoords);
4859 }
4860 
4862  const Vec xvVec,
4863  const Vec wVec)
4864 {
4865 #ifdef CODI_AD_REVERSE
4866  /*
4867  Description:
4868  This function initialize the matrix-free dRdWT, which will be
4869  used later in the adjoint solution
4870  */
4871 
4872  // this is needed because the self.solverAD object in the Python layer
4873  // never run the primal solution, so the wVec and xvVec is not always
4874  // update to date
4875  this->updateOFField(wVec);
4876  this->updateOFMesh(xvVec);
4877 
4878  if (daOptionPtr_->getOption<label>("debug"))
4879  {
4880  Info << "In initializedRdWTMatrixFree" << endl;
4881  this->calcPrimalResidualStatistics("print");
4882  }
4883 
4884  // No need to set the size, instead, we need to provide a function to compute
4885  // matrix-vector product, i.e., the dRdWTMatVecMultFunction function
4886  label localSize = daIndexPtr_->nLocalAdjointStates;
4887  MatCreateShell(PETSC_COMM_WORLD, localSize, localSize, PETSC_DETERMINE, PETSC_DETERMINE, this, &dRdWTMF_);
4888  MatShellSetOperation(dRdWTMF_, MATOP_MULT, (void (*)(void))dRdWTMatVecMultFunction);
4889  MatSetUp(dRdWTMF_);
4890  Info << "dRdWT Jacobian Free created!" << endl;
4891 
4892 #endif
4893 }
4894 
4896 {
4897 #ifdef CODI_AD_REVERSE
4898  /*
4899  Description:
4900  Destroy dRdWTMF_
4901  */
4902  MatDestroy(&dRdWTMF_);
4903 #endif
4904 }
4905 
4906 PetscErrorCode DASolver::dRdWTMatVecMultFunction(Mat dRdWTMF, Vec vecX, Vec vecY)
4907 {
4908 #ifdef CODI_AD_REVERSE
4909  /*
4910  Description:
4911  This function implements a way to compute matrix-vector products
4912  associated with dRdWTMF matrix.
4913  Here we need to return vecY = dRdWTMF * vecX.
4914  We use the reverse-mode AD to compute vecY in a matrix-free manner
4915  */
4916  DASolver* ctx;
4917  MatShellGetContext(dRdWTMF, (void**)&ctx);
4918 
4919  // Need to re-initialize the tape, setup inputs and outputs,
4920  // and run the forward computation and save the intermediate
4921  // variables in the tape, such that we don't re-compute them
4922  // for each GMRES iteration. This initialization needs to
4923  // happen for each adjoint solution. We will reset
4924  // globalADTape4dRdWTInitialized = 0 in DASolver::solveLinearEqn function
4925  if (!ctx->globalADTape4dRdWTInitialized)
4926  {
4927  ctx->initializeGlobalADTape4dRdWT();
4928  ctx->globalADTape4dRdWTInitialized = 1;
4929  }
4930 
4931  // assign the variable in vecX as the residual gradient for reverse AD
4932  ctx->assignVec2ResidualGradient(vecX);
4933  // do the backward computation to propagate the derivatives to the states
4934  ctx->globalADTape_.evaluate();
4935  // assign the derivatives stored in the states to the vecY vector
4936  ctx->assignStateGradient2Vec(vecY);
4937  // NOTE: we need to normalize the vecY vector.
4938  ctx->normalizeGradientVec(vecY);
4939  // clear the adjoint to prepare the next matrix-free GMRES iteration
4940  ctx->globalADTape_.clearAdjoints();
4941 
4942 #endif
4943 
4944  return 0;
4945 }
4946 
4948 {
4949 #ifdef CODI_AD_REVERSE
4950  /*
4951  Description:
4952  Initialize the global tape for computing dRdWT*psi
4953  using revere-mode AD. Here we need to register inputs
4954  and outputs, compute the residuals, and record all the
4955  intermediate variables in the tape. Then in the
4956  dRdWTMatVecMultFunction function, we can assign gradients
4957  and call tape.evaluate multiple times
4958  */
4959 
4960  // always reset the tape before recording
4961  this->globalADTape_.reset();
4962  // set the tape to active and start recording intermediate variables
4963  this->globalADTape_.setActive();
4964  // register state variables as the inputs
4966  // need to correct BC and update all intermediate variables
4967  daResidualPtr_->correctBoundaryConditions();
4968  daResidualPtr_->updateIntermediateVariables();
4969  daModelPtr_->correctBoundaryConditions();
4970  daModelPtr_->updateIntermediateVariables();
4971  // Now we can compute the residuals
4972  label isPC = 0;
4973  dictionary options;
4974  options.set("isPC", isPC);
4975  daResidualPtr_->calcResiduals(options);
4976  daModelPtr_->calcResiduals(options);
4977  // Set the residual as the output
4978  this->registerResidualOutput4AD();
4979  // All done, set the tape to passive
4980  this->globalADTape_.setPassive();
4981 
4982  // Now the tape is ready to use in the matrix-free GMRES solution
4983 #endif
4984 }
4985 
4987  const Vec xvVec,
4988  const Vec wVec,
4989  const word objFuncName,
4990  Vec dFdW)
4991 {
4992 #ifdef CODI_AD_REVERSE
4993  /*
4994  Description:
4995  This function computes partials derivatives dFdW using AD
4996 
4997  Input:
4998  xvVec: the volume mesh coordinate vector
4999 
5000  wVec: the state variable vector
5001 
5002  objFuncName: name of the objective function F
5003 
5004  Output:
5005  dFdW: the partial derivative vector dF/dW
5006  NOTE: You need to fully initialize the dFdW vec before calliing this function,
5007  i.e., VecCreate, VecSetSize, VecSetFromOptions etc. Or call VeDuplicate
5008  */
5009 
5010  Info << "Calculating dFdW using reverse-mode AD" << endl;
5011 
5012  VecZeroEntries(dFdW);
5013 
5014  // this is needed because the self.solverAD object in the Python layer
5015  // never run the primal solution, so the wVec and xvVec is not always
5016  // update to date
5017  this->updateOFField(wVec);
5018  this->updateOFMesh(xvVec);
5019 
5020  // get the subDict for this objective function
5021  dictionary objFuncSubDict =
5022  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
5023 
5024  // loop over all parts for this objFuncName
5025  forAll(objFuncSubDict.toc(), idxJ)
5026  {
5027  // get the subDict for this part
5028  word objFuncPart = objFuncSubDict.toc()[idxJ];
5029  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
5030 
5031  // initialize objFunc to get objFuncCellSources and objFuncFaceSources
5032  autoPtr<DAObjFunc> daObjFunc(DAObjFunc::New(
5033  meshPtr_(),
5034  daOptionPtr_(),
5035  daModelPtr_(),
5036  daIndexPtr_(),
5037  daResidualPtr_(),
5038  objFuncName,
5039  objFuncPart,
5040  objFuncSubDictPart));
5041 
5042  // reset tape
5043  this->globalADTape_.reset();
5044  // activate tape, start recording
5045  this->globalADTape_.setActive();
5046  // register states as the input
5048  // update all intermediate variables and boundary conditions
5049  daResidualPtr_->correctBoundaryConditions();
5050  daResidualPtr_->updateIntermediateVariables();
5051  daModelPtr_->correctBoundaryConditions();
5052  daModelPtr_->updateIntermediateVariables();
5053  // compute the objective function
5054  scalar fRef = daObjFunc->getObjFuncValue();
5055  // register f as the output
5056  this->globalADTape_.registerOutput(fRef);
5057  // stop recording
5058  this->globalADTape_.setPassive();
5059 
5060  // Note: since we used reduced objFunc, we only need to
5061  // assign the seed for master proc
5062  if (Pstream::master())
5063  {
5064  fRef.setGradient(1.0);
5065  }
5066  // evaluate tape to compute derivative
5067  this->globalADTape_.evaluate();
5068 
5069  // assign the computed derivatives from the OpenFOAM variable to dFdWPart
5070  Vec dFdWPart;
5071  VecDuplicate(dFdW, &dFdWPart);
5072  VecZeroEntries(dFdWPart);
5073  this->assignStateGradient2Vec(dFdWPart);
5074 
5075  // need to clear adjoint and tape after the computation is done!
5076  this->globalADTape_.clearAdjoints();
5077  this->globalADTape_.reset();
5078 
5079  // we need to add dFdWPart to dFdW because we want to sum
5080  // all dFdWPart for all parts of this objFuncName.
5081  VecAXPY(dFdW, 1.0, dFdWPart);
5082 
5083  if (daOptionPtr_->getOption<label>("debug"))
5084  {
5085  Info << "In calcdFdWAD" << endl;
5086  this->calcPrimalResidualStatistics("print");
5087  Info << objFuncName << ": " << fRef << endl;
5088  }
5089 
5090  VecDestroy(&dFdWPart);
5091  }
5092 
5093  // NOTE: we need to normalize dFdW!
5094  this->normalizeGradientVec(dFdW);
5095 
5096  wordList writeJacobians;
5097  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
5098  if (writeJacobians.found("dFdW") || writeJacobians.found("all"))
5099  {
5100  word outputName = "dFdW_" + objFuncName;
5101  DAUtility::writeVectorBinary(dFdW, outputName);
5102  DAUtility::writeVectorASCII(dFdW, outputName);
5103  }
5104 
5105 #endif
5106 }
5107 
5109  const Vec xvVec,
5110  const Vec wVec,
5111  const word objFuncName,
5112  const word designVarName,
5113  Vec dFdXv)
5114 {
5115 #ifdef CODI_AD_REVERSE
5116  /*
5117  Description:
5118  Compute dFdXv using reverse-mode AD
5119 
5120  Input:
5121 
5122  xvVec: the volume mesh coordinate vector
5123 
5124  wVec: the state variable vector
5125 
5126  objFuncName: the name of the objective function
5127 
5128  designVarName: name of the design variable
5129 
5130  Output:
5131  dFdXv: dF/dXv
5132  */
5133 
5134  Info << "Calculating dFdXv using reverse-mode AD" << endl;
5135 
5136  VecZeroEntries(dFdXv);
5137 
5138  this->updateOFField(wVec);
5139  this->updateOFMesh(xvVec);
5140 
5141  // get the subDict for this objective function
5142  dictionary objFuncSubDict =
5143  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
5144 
5145  // loop over all parts for this objFuncName
5146  forAll(objFuncSubDict.toc(), idxJ)
5147  {
5148  // get the subDict for this part
5149  word objFuncPart = objFuncSubDict.toc()[idxJ];
5150  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
5151 
5152  // initialize objFunc to get objFuncCellSources and objFuncFaceSources
5153  autoPtr<DAObjFunc> daObjFunc(DAObjFunc::New(
5154  meshPtr_(),
5155  daOptionPtr_(),
5156  daModelPtr_(),
5157  daIndexPtr_(),
5158  daResidualPtr_(),
5159  objFuncName,
5160  objFuncPart,
5161  objFuncSubDictPart));
5162 
5163  pointField meshPoints = meshPtr_->points();
5164 
5165  // reset tape
5166  this->globalADTape_.reset();
5167  // activate tape, start recording
5168  this->globalADTape_.setActive();
5169  // register points as the input
5170  forAll(meshPoints, i)
5171  {
5172  for (label j = 0; j < 3; j++)
5173  {
5174  this->globalADTape_.registerInput(meshPoints[i][j]);
5175  }
5176  }
5177  meshPtr_->movePoints(meshPoints);
5178  meshPtr_->moving(false);
5179  // update all intermediate variables and boundary conditions
5180  daResidualPtr_->correctBoundaryConditions();
5181  daResidualPtr_->updateIntermediateVariables();
5182  daModelPtr_->correctBoundaryConditions();
5183  daModelPtr_->updateIntermediateVariables();
5184  // compute the objective function
5185  scalar fRef = daObjFunc->getObjFuncValue();
5186  // register f as the output
5187  this->globalADTape_.registerOutput(fRef);
5188  // stop recording
5189  this->globalADTape_.setPassive();
5190 
5191  // Note: since we used reduced objFunc, we only need to
5192  // assign the seed for master proc
5193  if (Pstream::master())
5194  {
5195  fRef.setGradient(1.0);
5196  }
5197  // evaluate tape to compute derivative
5198  this->globalADTape_.evaluate();
5199 
5200  // assign the computed derivatives from the OpenFOAM variable to dFd*Part
5201  Vec dFdXvPart;
5202  VecDuplicate(dFdXv, &dFdXvPart);
5203  VecZeroEntries(dFdXvPart);
5204 
5205  forAll(meshPoints, i)
5206  {
5207  for (label j = 0; j < 3; j++)
5208  {
5209  label rowI = daIndexPtr_->getGlobalXvIndex(i, j);
5210  PetscScalar val = meshPoints[i][j].getGradient();
5211  VecSetValue(dFdXvPart, rowI, val, INSERT_VALUES);
5212  }
5213  }
5214  VecAssemblyBegin(dFdXvPart);
5215  VecAssemblyEnd(dFdXvPart);
5216 
5217  // need to clear adjoint and tape after the computation is done!
5218  this->globalADTape_.clearAdjoints();
5219  this->globalADTape_.reset();
5220 
5221  // we need to add dFd*Part to dFd* because we want to sum
5222  // all dFd*Part for all parts of this objFuncName.
5223  VecAXPY(dFdXv, 1.0, dFdXvPart);
5224 
5225  if (daOptionPtr_->getOption<label>("debug"))
5226  {
5227  this->calcPrimalResidualStatistics("print");
5228  Info << objFuncName << ": " << fRef << endl;
5229  }
5230 
5231  VecDestroy(&dFdXvPart);
5232  }
5233 
5234  wordList writeJacobians;
5235  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
5236  if (writeJacobians.found("dFdXv") || writeJacobians.found("all"))
5237  {
5238  word outputName = "dFdXv_" + objFuncName + "_" + designVarName;
5239  DAUtility::writeVectorBinary(dFdXv, outputName);
5240  DAUtility::writeVectorASCII(dFdXv, outputName);
5241  }
5242 #endif
5243 }
5244 
5246  const double* volCoords,
5247  const double* states,
5248  const double* thermal,
5249  const double* seeds,
5250  double* product)
5251 {
5252 #ifdef CODI_AD_REVERSE
5253  /*
5254  Description:
5255  Compute the matrix-vector products dRdThermal^T*Psi using reverse-mode AD
5256 
5257  Input:
5258 
5259  xvVec: the volume mesh coordinate vector
5260 
5261  wVec: the state variable vector
5262 
5263  psi: the vector to multiply dRdXv
5264 
5265  Output:
5266  prodVec: the matrix-vector products dRdThermal^T * Psi
5267  */
5268 
5269  Info << "Calculating [dRdThermal]^T * Psi using reverse-mode AD" << endl;
5270 
5271  scalar* volCoordsArray = new scalar[daIndexPtr_->nLocalXv];
5272  for (label i = 0; i < daIndexPtr_->nLocalXv; i++)
5273  {
5274  volCoordsArray[i] = volCoords[i];
5275  }
5276 
5277  scalar* statesArray = new scalar[daIndexPtr_->nLocalAdjointStates];
5278  for (label i = 0; i < daIndexPtr_->nLocalAdjointStates; i++)
5279  {
5280  statesArray[i] = states[i];
5281  }
5282 
5283  label nCouplingFaces = this->getNCouplingFaces();
5284  scalar* thermalArray = new scalar[nCouplingFaces * 2];
5285  for (label i = 0; i < nCouplingFaces * 2; i++)
5286  {
5287  thermalArray[i] = thermal[i];
5288  }
5289 
5290  this->updateOFMesh(volCoordsArray);
5291  this->updateOFField(statesArray);
5292 
5293  // update the OpenFOAM variables and reset their seeds (gradient part) to zeros
5294  this->resetOFSeeds();
5295  // reset the AD tape
5296  this->globalADTape_.reset();
5297  // start recording
5298  this->globalADTape_.setActive();
5299 
5300  // register inputs
5301  for (label i = 0; i < nCouplingFaces * 2; i++)
5302  {
5303  this->globalADTape_.registerInput(thermalArray[i]);
5304  }
5305 
5306  // calculate outputs
5307  this->setThermal(thermalArray);
5308 
5309  // compute residuals
5310  daResidualPtr_->correctBoundaryConditions();
5311  daResidualPtr_->updateIntermediateVariables();
5312  daModelPtr_->correctBoundaryConditions();
5313  daModelPtr_->updateIntermediateVariables();
5314  label isPC = 0;
5315  dictionary options;
5316  options.set("isPC", isPC);
5317  daResidualPtr_->calcResiduals(options);
5318  daModelPtr_->calcResiduals(options);
5319 
5320  // register outputs
5321  this->registerResidualOutput4AD();
5322 
5323  // stop recording
5324  this->globalADTape_.setPassive();
5325 
5326  // set seeds to the outputs
5327  this->assignSeeds2ResidualGradient(seeds);
5328 
5329  // now calculate the reverse matrix-vector product
5330  this->globalADTape_.evaluate();
5331 
5332  // get the matrix-vector product from the inputs
5333  for (label i = 0; i < nCouplingFaces * 2; i++)
5334  {
5335  product[i] = thermalArray[i].getGradient();
5336  }
5337 
5338  // ******************************* NOTE! ******************************
5339  // This step is tricky. We need to clean up the AD seeds for all variables before the next AD
5340  // This is usually done by theses two calls: updateOFField(wVec) and updateOFMesh(xvVec)
5341  // The above two calls will assign zeros to the gradient() part of all scalar variables because
5342  // the wVec and xvVec's gradient() part is zero (they are double instead of scalar).
5343  // HOWEVER, the above two calls will NOT clean the seeds for field variables's boundary values
5344  // This will not cause problems for most of the functions because their boundary values always
5345  // have zero seeds. However, the setThermal function actually sets non-zeros seeds for boundary
5346  // values, so here we need to manually clean up the BC's gradient part. NOT doing this will
5347  // mess up the AD computation for the following function.
5348  // The way we clean it is that we set a zero-seed thermal var and call setThermal to propagate
5349  // the zero seeds to all intermediate vars.
5350  // ******************************* NOTE! ******************************
5351  for (label i = 0; i < nCouplingFaces * 2; i++)
5352  {
5353  thermalArray[i].setGradient(0.0);
5354  }
5355  this->setThermal(thermalArray);
5356 
5357  delete[] thermalArray;
5358  delete[] volCoordsArray;
5359  delete[] statesArray;
5360 
5361  // clean up AD
5362  this->globalADTape_.clearAdjoints();
5363  this->globalADTape_.reset();
5364 
5365 #endif
5366 }
5367 
5369  const Vec xvVec,
5370  const Vec wVec,
5371  const Vec psi,
5372  Vec dRdXvTPsi)
5373 {
5374 #ifdef CODI_AD_REVERSE
5375  /*
5376  Description:
5377  Compute the matrix-vector products dRdXv^T*Psi using reverse-mode AD
5378 
5379  Input:
5380 
5381  xvVec: the volume mesh coordinate vector
5382 
5383  wVec: the state variable vector
5384 
5385  psi: the vector to multiply dRdXv
5386 
5387  Output:
5388  dRdXvTPsi: the matrix-vector products dRdXv^T * Psi
5389  */
5390 
5391  Info << "Calculating [dRdXv]^T * Psi using reverse-mode AD" << endl;
5392 
5393  VecZeroEntries(dRdXvTPsi);
5394 
5395  this->updateOFField(wVec);
5396  this->updateOFMesh(xvVec);
5397 
5398  pointField meshPoints = meshPtr_->points();
5399  this->globalADTape_.reset();
5400  this->globalADTape_.setActive();
5401  forAll(meshPoints, i)
5402  {
5403  for (label j = 0; j < 3; j++)
5404  {
5405  this->globalADTape_.registerInput(meshPoints[i][j]);
5406  }
5407  }
5408  meshPtr_->movePoints(meshPoints);
5409  meshPtr_->moving(false);
5410  // compute residuals
5411  daResidualPtr_->correctBoundaryConditions();
5412  daResidualPtr_->updateIntermediateVariables();
5413  daModelPtr_->correctBoundaryConditions();
5414  daModelPtr_->updateIntermediateVariables();
5415  label isPC = 0;
5416  dictionary options;
5417  options.set("isPC", isPC);
5418  daResidualPtr_->calcResiduals(options);
5419  daModelPtr_->calcResiduals(options);
5420 
5421  this->registerResidualOutput4AD();
5422  this->globalADTape_.setPassive();
5423 
5424  this->assignVec2ResidualGradient(psi);
5425  this->globalADTape_.evaluate();
5426 
5427  forAll(meshPoints, i)
5428  {
5429  for (label j = 0; j < 3; j++)
5430  {
5431  label rowI = daIndexPtr_->getGlobalXvIndex(i, j);
5432  PetscScalar val = meshPoints[i][j].getGradient();
5433  VecSetValue(dRdXvTPsi, rowI, val, INSERT_VALUES);
5434  }
5435  }
5436 
5437  VecAssemblyBegin(dRdXvTPsi);
5438  VecAssemblyEnd(dRdXvTPsi);
5439 
5440  this->globalADTape_.clearAdjoints();
5441  this->globalADTape_.reset();
5442 #endif
5443 }
5444 
5446  const Vec xvVec,
5447  const Vec wVec,
5448  const Vec fBarVec,
5449  Vec dForcedXv)
5450 {
5451 #ifdef CODI_AD_REVERSE
5452  /*
5453  Description:
5454  Calculate dForcedXv using reverse-mode AD
5455 
5456  Input:
5457 
5458  xvVec: the volume mesh coordinate vector
5459 
5460  wVec: the state variable vector
5461 
5462  fBarVec: the derivative seed vector
5463 
5464  Output:
5465  dForcedXv: dForce/dXv
5466  */
5467 
5468  Info << "Calculating dForcedXvAD using reverse-mode AD" << endl;
5469 
5470  VecZeroEntries(dForcedXv);
5471 
5472  this->updateOFField(wVec);
5473  this->updateOFMesh(xvVec);
5474 
5475  pointField meshPoints = meshPtr_->points();
5476  this->globalADTape_.reset();
5477  this->globalADTape_.setActive();
5478  forAll(meshPoints, i)
5479  {
5480  for (label j = 0; j < 3; j++)
5481  {
5482  this->globalADTape_.registerInput(meshPoints[i][j]);
5483  }
5484  }
5485  meshPtr_->movePoints(meshPoints);
5486  meshPtr_->moving(false);
5487  // compute residuals
5488  daResidualPtr_->correctBoundaryConditions();
5489  daResidualPtr_->updateIntermediateVariables();
5490  daModelPtr_->correctBoundaryConditions();
5491  daModelPtr_->updateIntermediateVariables();
5492 
5493  // Allocate arrays
5494  label nPoints, nFaces;
5495  List<word> patchList;
5496  this->getCouplingPatchList(patchList);
5497  this->getPatchInfo(nPoints, nFaces, patchList);
5498  List<scalar> fX(nPoints);
5499  List<scalar> fY(nPoints);
5500  List<scalar> fZ(nPoints);
5501 
5502  this->getForcesInternal(fX, fY, fZ, patchList);
5503  this->registerForceOutput4AD(fX, fY, fZ);
5504  this->globalADTape_.setPassive();
5505 
5506  this->assignVec2ForceGradient(fBarVec, fX, fY, fZ);
5507  this->globalADTape_.evaluate();
5508 
5509  forAll(meshPoints, i)
5510  {
5511  for (label j = 0; j < 3; j++)
5512  {
5513  label rowI = daIndexPtr_->getGlobalXvIndex(i, j);
5514  PetscScalar val = meshPoints[i][j].getGradient();
5515  VecSetValue(dForcedXv, rowI, val, INSERT_VALUES);
5516  }
5517  }
5518 
5519  VecAssemblyBegin(dForcedXv);
5520  VecAssemblyEnd(dForcedXv);
5521 
5522  this->globalADTape_.clearAdjoints();
5523  this->globalADTape_.reset();
5524 #endif
5525 }
5526 
5528  const Vec xvVec,
5529  const Vec wVec,
5530  const Vec fBarVec,
5531  Vec dAcoudXv,
5532  const word varName,
5533  const word groupName)
5534 {
5535 #ifdef CODI_AD_REVERSE
5536  /*
5537  Description:
5538  Calculate dAcoudXv using reverse-mode AD
5539 
5540  Input:
5541 
5542  xvVec: the volume mesh coordinate vector
5543 
5544  wVec: the state variable vector
5545 
5546  fBarVec: the derivative seed vector
5547 
5548  Output:
5549  dAcouXv: dAcou/dXv
5550  */
5551 
5552  Info << "Calculating dAcoudXvAD using reverse-mode AD" << endl;
5553 
5554  VecZeroEntries(dAcoudXv);
5555 
5556  this->updateOFField(wVec);
5557  this->updateOFMesh(xvVec);
5558 
5559  pointField meshPoints = meshPtr_->points();
5560  this->globalADTape_.reset();
5561  this->globalADTape_.setActive();
5562  forAll(meshPoints, i)
5563  {
5564  for (label j = 0; j < 3; j++)
5565  {
5566  this->globalADTape_.registerInput(meshPoints[i][j]);
5567  }
5568  }
5569  meshPtr_->movePoints(meshPoints);
5570  meshPtr_->moving(false);
5571  // compute residuals
5572  daResidualPtr_->correctBoundaryConditions();
5573  daResidualPtr_->updateIntermediateVariables();
5574  daModelPtr_->correctBoundaryConditions();
5575  daModelPtr_->updateIntermediateVariables();
5576 
5577  // Allocate arrays
5578  label nPoints, nFaces;
5579  List<word> patchList;
5580  this->getCouplingPatchList(patchList, groupName);
5581  this->getPatchInfo(nPoints, nFaces, patchList);
5582  List<scalar> x(nFaces);
5583  List<scalar> y(nFaces);
5584  List<scalar> z(nFaces);
5585  List<scalar> nX(nFaces);
5586  List<scalar> nY(nFaces);
5587  List<scalar> nZ(nFaces);
5588  List<scalar> a(nFaces);
5589  List<scalar> fX(nFaces);
5590  List<scalar> fY(nFaces);
5591  List<scalar> fZ(nFaces);
5592 
5593  this->getAcousticDataInternal(x, y, z, nX, nY, nZ, a, fX, fY, fZ, patchList);
5594 
5595  if (varName == "xAcou")
5596  {
5597  this->registerAcousticOutput4AD(x);
5598  this->registerAcousticOutput4AD(y);
5599  this->registerAcousticOutput4AD(z);
5600  }
5601  else if (varName == "nAcou")
5602  {
5603  this->registerAcousticOutput4AD(nX);
5604  this->registerAcousticOutput4AD(nY);
5605  this->registerAcousticOutput4AD(nZ);
5606  }
5607  else if (varName == "aAcou")
5608  {
5609  this->registerAcousticOutput4AD(a);
5610  }
5611  else if (varName == "fAcou")
5612  {
5613  this->registerAcousticOutput4AD(fX);
5614  this->registerAcousticOutput4AD(fY);
5615  this->registerAcousticOutput4AD(fZ);
5616  }
5617  this->globalADTape_.setPassive();
5618 
5619  if (varName == "xAcou")
5620  {
5621  this->assignVec2AcousticGradient(fBarVec, x, 0, 3);
5622  this->assignVec2AcousticGradient(fBarVec, y, 1, 3);
5623  this->assignVec2AcousticGradient(fBarVec, z, 2, 3);
5624  }
5625  else if (varName == "nAcou")
5626  {
5627  this->assignVec2AcousticGradient(fBarVec, nX, 0, 3);
5628  this->assignVec2AcousticGradient(fBarVec, nY, 1, 3);
5629  this->assignVec2AcousticGradient(fBarVec, nZ, 2, 3);
5630  }
5631  else if (varName == "aAcou")
5632  {
5633  this->assignVec2AcousticGradient(fBarVec, a, 0, 1);
5634  }
5635  else if (varName == "fAcou")
5636  {
5637  this->assignVec2AcousticGradient(fBarVec, fX, 0, 3);
5638  this->assignVec2AcousticGradient(fBarVec, fY, 1, 3);
5639  this->assignVec2AcousticGradient(fBarVec, fZ, 2, 3);
5640  }
5641  this->globalADTape_.evaluate();
5642 
5643  forAll(meshPoints, i)
5644  {
5645  for (label j = 0; j < 3; j++)
5646  {
5647  label rowI = daIndexPtr_->getGlobalXvIndex(i, j);
5648  PetscScalar val = meshPoints[i][j].getGradient();
5649  VecSetValue(dAcoudXv, rowI, val, INSERT_VALUES);
5650  }
5651  }
5652 
5653  VecAssemblyBegin(dAcoudXv);
5654  VecAssemblyEnd(dAcoudXv);
5655 
5656  this->globalADTape_.clearAdjoints();
5657  this->globalADTape_.reset();
5658 #endif
5659 }
5660 
5662  const Vec xvVec,
5663  const Vec wVec,
5664  const Vec psi,
5665  const word designVarName,
5666  Vec dRdFieldTPsi)
5667 {
5668 #ifdef CODI_AD_REVERSE
5669  /*
5670  Description:
5671  Compute the matrix-vector products dRdField^T*Psi using reverse-mode AD
5672 
5673  Input:
5674 
5675  xvVec: the volume mesh coordinate vector
5676 
5677  wVec: the state variable vector
5678 
5679  psi: the vector to multiply dRdField
5680 
5681  designVarName: name of the design variable
5682 
5683  Output:
5684  dRdFieldTPsi: the matrix-vector products dRdField^T * Psi
5685  */
5686 
5687  Info << "Calculating [dRdField]^T * Psi using reverse-mode AD" << endl;
5688 
5689  VecZeroEntries(dRdFieldTPsi);
5690 
5691  this->updateOFField(wVec);
5692  this->updateOFMesh(xvVec);
5693 
5694  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
5695 
5696  word fieldName = dvSubDict.getWord("fieldName");
5697  word fieldType = dvSubDict.getWord("fieldType");
5698 
5699  this->globalADTape_.reset();
5700  this->globalADTape_.setActive();
5701  this->registerFieldVariableInput4AD(fieldName, fieldType);
5702  this->updateBoundaryConditions(fieldName, fieldType);
5703  // compute residuals
5704  daResidualPtr_->correctBoundaryConditions();
5705  daResidualPtr_->updateIntermediateVariables();
5706  daModelPtr_->correctBoundaryConditions();
5707  daModelPtr_->updateIntermediateVariables();
5708  label isPC = 0;
5709  dictionary options;
5710  options.set("isPC", isPC);
5711  daResidualPtr_->calcResiduals(options);
5712  daModelPtr_->calcResiduals(options);
5713 
5714  this->registerResidualOutput4AD();
5715  this->globalADTape_.setPassive();
5716 
5717  this->assignVec2ResidualGradient(psi);
5718  this->globalADTape_.evaluate();
5719 
5720  this->assignFieldGradient2Vec(fieldName, fieldType, dRdFieldTPsi);
5721 
5722  VecAssemblyBegin(dRdFieldTPsi);
5723  VecAssemblyEnd(dRdFieldTPsi);
5724 
5725  this->globalADTape_.clearAdjoints();
5726  this->globalADTape_.reset();
5727 
5728  wordList writeJacobians;
5729  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
5730  if (writeJacobians.found("dRdFieldTPsi") || writeJacobians.found("all"))
5731  {
5732  word outputName = "dRdFieldTPsi_" + designVarName;
5733  DAUtility::writeVectorBinary(dRdFieldTPsi, outputName);
5734  DAUtility::writeVectorASCII(dRdFieldTPsi, outputName);
5735  }
5736 #endif
5737 }
5738 
5740  const Vec xvVec,
5741  const Vec wVec,
5742  const word objFuncName,
5743  const word designVarName,
5744  Vec dFdACT)
5745 {
5746 #ifdef CODI_AD_REVERSE
5747  /*
5748  Description:
5749  Compute dFdACT using reverse-mode AD
5750 
5751  Input:
5752 
5753  xvVec: the volume mesh coordinate vector
5754 
5755  wVec: the state variable vector
5756 
5757  objFuncName: the name of the objective function
5758 
5759  designVarName: name of the design variable
5760 
5761  Output:
5762  dFdACT: dF/dACT
5763  */
5764 
5765  Info << "Calculating dFdACT using reverse-mode AD" << endl;
5766 
5767  VecZeroEntries(dFdACT);
5768 
5769  // first check if the input is valid
5770  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
5771  word designVarType = dvSubDict.getWord("designVarType");
5772  if (designVarType == "ACTD")
5773  {
5774  DAFvSource& fvSource = const_cast<DAFvSource&>(
5775  meshPtr_->thisDb().lookupObject<DAFvSource>("DAFvSource"));
5776 
5777  word diskName = dvSubDict.getWord("actuatorName");
5778  dictionary fvSourceSubDict = daOptionPtr_->getAllOptions().subDict("fvSource");
5779  word source = fvSourceSubDict.subDict(diskName).getWord("source");
5780  if (source == "cylinderAnnulusSmooth")
5781  {
5782  this->updateOFField(wVec);
5783  this->updateOFMesh(xvVec);
5784 
5785  // get the subDict for this objective function
5786  dictionary objFuncSubDict =
5787  daOptionPtr_->getAllOptions().subDict("objFunc").subDict(objFuncName);
5788 
5789  // loop over all parts for this objFuncName
5790  forAll(objFuncSubDict.toc(), idxJ)
5791  {
5792  // get the subDict for this part
5793  word objFuncPart = objFuncSubDict.toc()[idxJ];
5794  dictionary objFuncSubDictPart = objFuncSubDict.subDict(objFuncPart);
5795 
5796  // initialize objFunc to get objFuncCellSources and objFuncFaceSources
5797  autoPtr<DAObjFunc> daObjFunc(DAObjFunc::New(
5798  meshPtr_(),
5799  daOptionPtr_(),
5800  daModelPtr_(),
5801  daIndexPtr_(),
5802  daResidualPtr_(),
5803  objFuncName,
5804  objFuncPart,
5805  objFuncSubDictPart));
5806 
5807  // get the design variable vals
5808  scalarList actDVList(10);
5809  for (label i = 0; i < 10; i++)
5810  {
5811  actDVList[i] = fvSource.getActuatorDVs(diskName, i);
5812  }
5813 
5814  // reset tape
5815  this->globalADTape_.reset();
5816  // activate tape, start recording
5817  this->globalADTape_.setActive();
5818  // register the input
5819  for (label i = 0; i < 10; i++)
5820  {
5821  this->globalADTape_.registerInput(actDVList[i]);
5822  }
5823  // set dv values to fvSource obj for all procs
5824  for (label i = 0; i < 10; i++)
5825  {
5826  fvSource.setActuatorDVs(diskName, i, actDVList[i]);
5827  }
5828  // the actuatorDVs are updated, now we need to recompute fvSource
5829  // this is not needed for the residual partials because fvSource
5830  // will be automatically calculated in the UEqn, but for the
5831  // obj partials, we need to manually recompute fvSource
5832  fvSource.updateFvSource();
5833 
5834  // update all intermediate variables and boundary conditions
5835  daResidualPtr_->correctBoundaryConditions();
5836  daResidualPtr_->updateIntermediateVariables();
5837  daModelPtr_->correctBoundaryConditions();
5838  daModelPtr_->updateIntermediateVariables();
5839  // compute the objective function
5840  scalar fRef = daObjFunc->getObjFuncValue();
5841  // register f as the output
5842  this->globalADTape_.registerOutput(fRef);
5843  // stop recording
5844  this->globalADTape_.setPassive();
5845 
5846  // Note: since we used reduced objFunc, we only need to
5847  // assign the seed for master proc
5848  if (Pstream::master())
5849  {
5850  fRef.setGradient(1.0);
5851  }
5852  // evaluate tape to compute derivative
5853  this->globalADTape_.evaluate();
5854 
5855  // assign the computed derivatives from the OpenFOAM variable to dFd*Part
5856  Vec dFdACTPart;
5857  VecDuplicate(dFdACT, &dFdACTPart);
5858  VecZeroEntries(dFdACTPart);
5859 
5860  for (label i = 0; i < 10; i++)
5861  {
5862  PetscScalar valIn = actDVList[i].getGradient();
5863  // we need to do ADD_VALUES to get contribution from all procs
5864  VecSetValue(dFdACTPart, i, valIn, ADD_VALUES);
5865  }
5866 
5867  VecAssemblyBegin(dFdACTPart);
5868  VecAssemblyEnd(dFdACTPart);
5869 
5870  // need to clear adjoint and tape after the computation is done!
5871  this->globalADTape_.clearAdjoints();
5872  this->globalADTape_.reset();
5873 
5874  // we need to add dFd*Part to dFd* because we want to sum
5875  // all dFd*Part for all parts of this objFuncName.
5876  VecAXPY(dFdACT, 1.0, dFdACTPart);
5877 
5878  if (daOptionPtr_->getOption<label>("debug"))
5879  {
5880  this->calcPrimalResidualStatistics("print");
5881  Info << objFuncName << ": " << fRef << endl;
5882  }
5883 
5884  VecDestroy(&dFdACTPart);
5885  }
5886  }
5887  else
5888  {
5889  FatalErrorIn("") << "source not supported. Options: cylinderAnnulusSmooth"
5890  << abort(FatalError);
5891  }
5892  }
5893  else
5894  {
5895  FatalErrorIn("") << "designVarType not supported. Options: ACTD"
5896  << abort(FatalError);
5897  }
5898 
5899  wordList writeJacobians;
5900  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
5901  if (writeJacobians.found("dFdACT") || writeJacobians.found("all"))
5902  {
5903  word outputName = "dFdACT_" + objFuncName + "_" + designVarName;
5904  DAUtility::writeVectorBinary(dFdACT, outputName);
5905  DAUtility::writeVectorASCII(dFdACT, outputName);
5906  }
5907 #endif
5908 }
5909 
5911  const Vec xvVec,
5912  const Vec wVec,
5913  const Vec psi,
5914  const word designVarName,
5915  Vec dRdActTPsi)
5916 {
5917 #ifdef CODI_AD_REVERSE
5918  /*
5919  Description:
5920  Compute the matrix-vector products dRdAct^T*Psi using reverse-mode AD
5921 
5922  Input:
5923 
5924  xvVec: the volume mesh coordinate vector
5925 
5926  wVec: the state variable vector
5927 
5928  psi: the vector to multiply dRdAct
5929 
5930  designVarName: name of the design variable
5931 
5932  Output:
5933  dRdActTPsi: the matrix-vector products dRdAct^T * Psi
5934  */
5935 
5936  Info << "Calculating [dRdAct]^T * Psi using reverse-mode AD" << endl;
5937 
5938  VecZeroEntries(dRdActTPsi);
5939 
5940  dictionary dvSubDict = daOptionPtr_->getAllOptions().subDict("designVar").subDict(designVarName);
5941  word designVarType = dvSubDict.getWord("designVarType");
5942  if (designVarType == "ACTD")
5943  {
5944 
5945  DAFvSource& fvSource = const_cast<DAFvSource&>(
5946  meshPtr_->thisDb().lookupObject<DAFvSource>("DAFvSource"));
5947 
5948  word diskName = dvSubDict.getWord("actuatorName");
5949 
5950  dictionary fvSourceSubDict = daOptionPtr_->getAllOptions().subDict("fvSource");
5951  word source = fvSourceSubDict.subDict(diskName).getWord("source");
5952  if (source == "cylinderAnnulusSmooth")
5953  {
5954 
5955  this->updateOFField(wVec);
5956  this->updateOFMesh(xvVec);
5957 
5958  scalarList actDVList(10);
5959  for (label i = 0; i < 10; i++)
5960  {
5961  actDVList[i] = fvSource.getActuatorDVs(diskName, i);
5962  }
5963 
5964  this->globalADTape_.reset();
5965  this->globalADTape_.setActive();
5966 
5967  for (label i = 0; i < 10; i++)
5968  {
5969  this->globalADTape_.registerInput(actDVList[i]);
5970  }
5971 
5972  // set dv values to fvSource obj for all procs
5973  for (label i = 0; i < 10; i++)
5974  {
5975  fvSource.setActuatorDVs(diskName, i, actDVList[i]);
5976  }
5977 
5978  // compute residuals
5979  daResidualPtr_->correctBoundaryConditions();
5980  daResidualPtr_->updateIntermediateVariables();
5981  daModelPtr_->correctBoundaryConditions();
5982  daModelPtr_->updateIntermediateVariables();
5983  label isPC = 0;
5984  dictionary options;
5985  options.set("isPC", isPC);
5986  daResidualPtr_->calcResiduals(options);
5987  daModelPtr_->calcResiduals(options);
5988 
5989  this->registerResidualOutput4AD();
5990  this->globalADTape_.setPassive();
5991 
5992  this->assignVec2ResidualGradient(psi);
5993  this->globalADTape_.evaluate();
5994 
5995  for (label i = 0; i < 10; i++)
5996  {
5997  PetscScalar valIn = actDVList[i].getGradient();
5998  // we need to do ADD_VALUES to get contribution from all procs
5999  VecSetValue(dRdActTPsi, i, valIn, ADD_VALUES);
6000  }
6001 
6002  VecAssemblyBegin(dRdActTPsi);
6003  VecAssemblyEnd(dRdActTPsi);
6004 
6005  this->globalADTape_.clearAdjoints();
6006  this->globalADTape_.reset();
6007  }
6008  else
6009  {
6010  FatalErrorIn("") << "source not supported. Options: cylinderAnnulusSmooth"
6011  << abort(FatalError);
6012  }
6013  }
6014  else
6015  {
6016  FatalErrorIn("") << "designVarType not supported. Options: ACTD"
6017  << abort(FatalError);
6018  }
6019  wordList writeJacobians;
6020  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
6021  if (writeJacobians.found("dRdActTPsi") || writeJacobians.found("all"))
6022  {
6023  word outputName = "dRdActTPsi_" + designVarName;
6024  DAUtility::writeVectorBinary(dRdActTPsi, outputName);
6025  DAUtility::writeVectorASCII(dRdActTPsi, outputName);
6026  }
6027 #endif
6028 }
6029 
6031  const Vec xvVec,
6032  const Vec wVec,
6033  const Vec fBarVec,
6034  Vec dForcedW)
6035 {
6036 #ifdef CODI_AD_REVERSE
6037  /*
6038  Description:
6039  Calculate dForcedW using reverse-mode AD
6040 
6041  Input:
6042  xvVec: the volume mesh coordinate vector
6043 
6044  wVec: the state variable vector
6045 
6046  fBarVec: the derivative seed vector
6047 
6048  Output:
6049  dForcedW: dForce/dW
6050  */
6051 
6052  Info << "Calculating dForcesdW using reverse-mode AD" << endl;
6053 
6054  VecZeroEntries(dForcedW);
6055 
6056  // this is needed because the self.solverAD object in the Python layer
6057  // never run the primal solution, so the wVec and xvVec is not always
6058  // update to date
6059  this->updateOFField(wVec);
6060  this->updateOFMesh(xvVec);
6061 
6062  this->globalADTape_.reset();
6063  this->globalADTape_.setActive();
6064 
6066 
6067  // compute residuals
6068  daResidualPtr_->correctBoundaryConditions();
6069  daResidualPtr_->updateIntermediateVariables();
6070  daModelPtr_->correctBoundaryConditions();
6071  daModelPtr_->updateIntermediateVariables();
6072 
6073  // Allocate arrays
6074  label nPoints, nFaces;
6075  List<word> patchList;
6076  this->getCouplingPatchList(patchList);
6077  this->getPatchInfo(nPoints, nFaces, patchList);
6078  List<scalar> fX(nPoints);
6079  List<scalar> fY(nPoints);
6080  List<scalar> fZ(nPoints);
6081 
6082  this->getForcesInternal(fX, fY, fZ, patchList);
6083  this->registerForceOutput4AD(fX, fY, fZ);
6084  this->globalADTape_.setPassive();
6085 
6086  this->assignVec2ForceGradient(fBarVec, fX, fY, fZ);
6087  this->globalADTape_.evaluate();
6088 
6089  // get the deriv values
6090  this->assignStateGradient2Vec(dForcedW);
6091 
6092  // NOTE: we need to normalize dForcedW!
6093  this->normalizeGradientVec(dForcedW);
6094 
6095  VecAssemblyBegin(dForcedW);
6096  VecAssemblyEnd(dForcedW);
6097 
6098  this->globalADTape_.clearAdjoints();
6099  this->globalADTape_.reset();
6100 #endif
6101 }
6102 
6104  const Vec xvVec,
6105  const Vec wVec,
6106  const Vec fBarVec,
6107  Vec dAcoudW,
6108  word varName,
6109  word groupName)
6110 {
6111 #ifdef CODI_AD_REVERSE
6112  /*
6113  Description:
6114  Calculate dForcedW using reverse-mode AD
6115 
6116  Input:
6117  xvVec: the volume mesh coordinate vector
6118 
6119  wVec: the state variable vector
6120 
6121  fBarVec: the derivative seed vector
6122 
6123  Output:
6124  dAcoudW: dAcou/dW
6125  */
6126 
6127  Info << "Calculating dAcoudW using reverse-mode AD" << endl;
6128 
6129  VecZeroEntries(dAcoudW);
6130 
6131  // this is needed because the self.solverAD object in the Python layer
6132  // never run the primal solution, so the wVec and xvVec is not always
6133  // update to date
6134  this->updateOFField(wVec);
6135  this->updateOFMesh(xvVec);
6136 
6137  this->globalADTape_.reset();
6138  this->globalADTape_.setActive();
6139 
6141 
6142  // compute residuals
6143  daResidualPtr_->correctBoundaryConditions();
6144  daResidualPtr_->updateIntermediateVariables();
6145  daModelPtr_->correctBoundaryConditions();
6146  daModelPtr_->updateIntermediateVariables();
6147 
6148  // Allocate arrays
6149  label nPoints, nFaces;
6150  List<word> patchList;
6151  this->getCouplingPatchList(patchList, groupName);
6152  this->getPatchInfo(nPoints, nFaces, patchList);
6153  List<scalar> x(nFaces);
6154  List<scalar> y(nFaces);
6155  List<scalar> z(nFaces);
6156  List<scalar> nX(nFaces);
6157  List<scalar> nY(nFaces);
6158  List<scalar> nZ(nFaces);
6159  List<scalar> a(nFaces);
6160  List<scalar> fX(nFaces);
6161  List<scalar> fY(nFaces);
6162  List<scalar> fZ(nFaces);
6163 
6164  this->getAcousticDataInternal(x, y, z, nX, nY, nZ, a, fX, fY, fZ, patchList);
6165 
6166  if (varName == "xAcou")
6167  {
6168  this->registerAcousticOutput4AD(x);
6169  this->registerAcousticOutput4AD(y);
6170  this->registerAcousticOutput4AD(z);
6171  }
6172  else if (varName == "nAcou")
6173  {
6174  this->registerAcousticOutput4AD(nX);
6175  this->registerAcousticOutput4AD(nY);
6176  this->registerAcousticOutput4AD(nZ);
6177  }
6178  else if (varName == "aAcou")
6179  {
6180  this->registerAcousticOutput4AD(a);
6181  }
6182  else if (varName == "fAcou")
6183  {
6184  this->registerAcousticOutput4AD(fX);
6185  this->registerAcousticOutput4AD(fY);
6186  this->registerAcousticOutput4AD(fZ);
6187  }
6188  this->globalADTape_.setPassive();
6189 
6190  if (varName == "xAcou")
6191  {
6192  this->assignVec2AcousticGradient(fBarVec, x, 0, 3);
6193  this->assignVec2AcousticGradient(fBarVec, y, 1, 3);
6194  this->assignVec2AcousticGradient(fBarVec, z, 2, 3);
6195  }
6196  else if (varName == "nAcou")
6197  {
6198  this->assignVec2AcousticGradient(fBarVec, nX, 0, 3);
6199  this->assignVec2AcousticGradient(fBarVec, nY, 1, 3);
6200  this->assignVec2AcousticGradient(fBarVec, nZ, 2, 3);
6201  }
6202  else if (varName == "aAcou")
6203  {
6204  this->assignVec2AcousticGradient(fBarVec, a, 0, 1);
6205  }
6206  else if (varName == "fAcou")
6207  {
6208  this->assignVec2AcousticGradient(fBarVec, fX, 0, 3);
6209  this->assignVec2AcousticGradient(fBarVec, fY, 1, 3);
6210  this->assignVec2AcousticGradient(fBarVec, fZ, 2, 3);
6211  }
6212  this->globalADTape_.evaluate();
6213 
6214  // get the deriv values
6215  this->assignStateGradient2Vec(dAcoudW);
6216 
6217  // NOTE: we need to normalize dAcoudW!
6218  this->normalizeGradientVec(dAcoudW);
6219 
6220  VecAssemblyBegin(dAcoudW);
6221  VecAssemblyEnd(dAcoudW);
6222 
6223  this->globalADTape_.clearAdjoints();
6224  this->globalADTape_.reset();
6225 #endif
6226 }
6227 
6229  const label isInit,
6230  const Vec psi,
6231  Vec dRdWTPsi)
6232 {
6233 #ifdef CODI_AD_REVERSE
6234  /*
6235  Description:
6236  Compute the matrix-vector products dRdW^T*Psi using reverse-mode AD
6237  Note that this function does not assign wVec and xVec to OF fields
6238 
6239  Input:
6240 
6241  mode: either "init" or "run"
6242 
6243  psi: the vector to multiply dRdW0^T
6244 
6245  Output:
6246  dRdWTPsi: the matrix-vector products dRdW^T * Psi
6247  */
6248 
6249  // this function is not used and commented out for now
6250 
6251  /*
6252  Info << "Calculating [dRdW]^T * Psi using reverse-mode AD" << endl;
6253 
6254  VecZeroEntries(dRdWTPsi);
6255 
6256  if (isInit)
6257  {
6258  this->globalADTape_.reset();
6259  this->globalADTape_.setActive();
6260 
6261  this->registerStateVariableInput4AD();
6262 
6263  // compute residuals
6264  daResidualPtr_->correctBoundaryConditions();
6265  daResidualPtr_->updateIntermediateVariables();
6266  daModelPtr_->correctBoundaryConditions();
6267  daModelPtr_->updateIntermediateVariables();
6268  label isPC = 0;
6269  dictionary options;
6270  options.set("isPC", isPC);
6271  daResidualPtr_->calcResiduals(options);
6272  daModelPtr_->calcResiduals(options);
6273 
6274  this->registerResidualOutput4AD();
6275  this->globalADTape_.setPassive();
6276  }
6277 
6278  this->assignVec2ResidualGradient(psi);
6279  this->globalADTape_.evaluate();
6280 
6281  // get the deriv values
6282  this->assignStateGradient2Vec(dRdWTPsi);
6283 
6284  VecAssemblyBegin(dRdWTPsi);
6285  VecAssemblyEnd(dRdWTPsi);
6286 
6287  this->globalADTape_.clearAdjoints();
6288  */
6289 
6290 #endif
6291 }
6292 
6294  const Vec xvVec,
6295  const Vec wVec,
6296  const Vec psi,
6297  Vec dRdWTPsi)
6298 {
6299 #ifdef CODI_AD_REVERSE
6300  /*
6301  Description:
6302  Compute the matrix-vector products dRdW^T*Psi using reverse-mode AD
6303 
6304  Input:
6305  xvVec: the volume mesh coordinate vector
6306 
6307  wVec: the state variable vector
6308 
6309  psi: the vector to multiply dRdW0^T
6310 
6311  Output:
6312  dRdWTPsi: the matrix-vector products dRdW^T * Psi
6313  */
6314 
6315  Info << "Calculating [dRdW]^T * Psi using reverse-mode AD" << endl;
6316 
6317  VecZeroEntries(dRdWTPsi);
6318 
6319  // this is needed because the self.solverAD object in the Python layer
6320  // never run the primal solution, so the wVec and xvVec is not always
6321  // update to date
6322  this->updateOFField(wVec);
6323  this->updateOFMesh(xvVec);
6324 
6325  this->globalADTape_.reset();
6326  this->globalADTape_.setActive();
6327 
6329 
6330  // compute residuals
6331  daResidualPtr_->correctBoundaryConditions();
6332  daResidualPtr_->updateIntermediateVariables();
6333  daModelPtr_->correctBoundaryConditions();
6334  daModelPtr_->updateIntermediateVariables();
6335  label isPC = 0;
6336  dictionary options;
6337  options.set("isPC", isPC);
6338  daResidualPtr_->calcResiduals(options);
6339  daModelPtr_->calcResiduals(options);
6340 
6341  this->registerResidualOutput4AD();
6342  this->globalADTape_.setPassive();
6343 
6344  this->assignVec2ResidualGradient(psi);
6345  this->globalADTape_.evaluate();
6346 
6347  // get the deriv values
6348  this->assignStateGradient2Vec(dRdWTPsi);
6349 
6350  // NOTE: we need to normalize dRdWTPsi!
6351  this->normalizeGradientVec(dRdWTPsi);
6352 
6353  VecAssemblyBegin(dRdWTPsi);
6354  VecAssemblyEnd(dRdWTPsi);
6355 
6356  this->globalADTape_.clearAdjoints();
6357  this->globalADTape_.reset();
6358 
6359  wordList writeJacobians;
6360  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
6361  if (writeJacobians.found("dRdWTPsi") || writeJacobians.found("all"))
6362  {
6363  word outputName = "dRdWTPsi";
6364  DAUtility::writeVectorBinary(dRdWTPsi, outputName);
6365  DAUtility::writeVectorASCII(dRdWTPsi, outputName);
6366  }
6367 #endif
6368 }
6369 
6371  const label oldTimeLevel,
6372  const Vec psi,
6373  Vec dRdWOldTPsi)
6374 {
6375 #ifdef CODI_AD_REVERSE
6376  /*
6377  Description:
6378  Compute the matrix-vector products dRdWOld^T*Psi using reverse-mode AD
6379  Here WOld means the state variable from previous time step,
6380  if oldTimeLevel = 1, WOld means W0, if oldTimeLevel=2, WOld means W00
6381  R is always the residuals for the current time step
6382  NOTE: if the oldTimeLevel is greater than the max nOldTimes a variable
6383  has, the derivative will be zero. This is done by registering input
6384  only for variables that have enough oldTimes in registerStateVariableInput4AD
6385 
6386  Input:
6387 
6388  oldTimeLevel: 1-dRdW0^T 2-dRdW00^T
6389 
6390  psi: the vector to multiply dRdW0^T
6391 
6392  Output:
6393  dRdWOldTPsi: the matrix-vector products dRdWOld^T * Psi
6394  */
6395 
6396  Info << "Calculating [dRdWOld]^T * Psi using reverse-mode AD with level " << oldTimeLevel << endl;
6397 
6398  VecZeroEntries(dRdWOldTPsi);
6399 
6400  this->globalADTape_.reset();
6401  this->globalADTape_.setActive();
6402 
6403  this->registerStateVariableInput4AD(oldTimeLevel);
6404 
6405  // compute residuals
6406  daResidualPtr_->correctBoundaryConditions();
6407  daResidualPtr_->updateIntermediateVariables();
6408  daModelPtr_->correctBoundaryConditions();
6409  daModelPtr_->updateIntermediateVariables();
6410  label isPC = 0;
6411  dictionary options;
6412  options.set("isPC", isPC);
6413  daResidualPtr_->calcResiduals(options);
6414  daModelPtr_->calcResiduals(options);
6415 
6416  this->registerResidualOutput4AD();
6417  this->globalADTape_.setPassive();
6418 
6419  this->assignVec2ResidualGradient(psi);
6420  this->globalADTape_.evaluate();
6421 
6422  // get the deriv values
6423  this->assignStateGradient2Vec(dRdWOldTPsi, oldTimeLevel);
6424 
6425  // NOTE: we need to normalize dRdWOldTPsi!
6426  this->normalizeGradientVec(dRdWOldTPsi);
6427 
6428  VecAssemblyBegin(dRdWOldTPsi);
6429  VecAssemblyEnd(dRdWOldTPsi);
6430 
6431  this->globalADTape_.clearAdjoints();
6432  this->globalADTape_.reset();
6433 
6434  wordList writeJacobians;
6435  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
6436  if (writeJacobians.found("dRdWOldTPsi") || writeJacobians.found("all"))
6437  {
6438  word outputName = "dRdWOldTPsi";
6439  DAUtility::writeVectorBinary(dRdWOldTPsi, outputName);
6440  DAUtility::writeVectorASCII(dRdWOldTPsi, outputName);
6441  }
6442 #endif
6443 }
6444 
6445 void DASolver::registerStateVariableInput4AD(const label oldTimeLevel)
6446 {
6447 #ifdef CODI_AD_REVERSE
6448  /*
6449  Description:
6450  Register all state variables as the input for reverse-mode AD
6451 
6452  Input:
6453  oldTimeLevel: which time level to register, the default value
6454  is 0, meaning it will register the state itself. If its
6455  value is 1, it will register state.oldTime(), if its value
6456  is 2, it will register state.oldTime().oldTime(). For
6457  steady-state adjoint oldTimeLevel = 0
6458  */
6459 
6460  if (oldTimeLevel < 0 || oldTimeLevel > 2)
6461  {
6462  FatalErrorIn("") << "oldTimeLevel not valid. Options: 0, 1, or 2"
6463  << abort(FatalError);
6464  }
6465 
6466  forAll(stateInfo_["volVectorStates"], idxI)
6467  {
6468  const word stateName = stateInfo_["volVectorStates"][idxI];
6469  volVectorField& state = const_cast<volVectorField&>(
6470  meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
6471 
6472  label maxOldTimes = state.nOldTimes();
6473 
6474  if (maxOldTimes >= oldTimeLevel)
6475  {
6476  forAll(state, cellI)
6477  {
6478  for (label i = 0; i < 3; i++)
6479  {
6480  if (oldTimeLevel == 0)
6481  {
6482  this->globalADTape_.registerInput(state[cellI][i]);
6483  }
6484  else if (oldTimeLevel == 1)
6485  {
6486  this->globalADTape_.registerInput(state.oldTime()[cellI][i]);
6487  }
6488  else if (oldTimeLevel == 2)
6489  {
6490  this->globalADTape_.registerInput(state.oldTime().oldTime()[cellI][i]);
6491  }
6492  }
6493  }
6494  }
6495  }
6496 
6497  forAll(stateInfo_["volScalarStates"], idxI)
6498  {
6499  const word stateName = stateInfo_["volScalarStates"][idxI];
6500  volScalarField& state = const_cast<volScalarField&>(
6501  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
6502 
6503  label maxOldTimes = state.nOldTimes();
6504 
6505  if (maxOldTimes >= oldTimeLevel)
6506  {
6507  forAll(state, cellI)
6508  {
6509  if (oldTimeLevel == 0)
6510  {
6511  this->globalADTape_.registerInput(state[cellI]);
6512  }
6513  else if (oldTimeLevel == 1)
6514  {
6515  this->globalADTape_.registerInput(state.oldTime()[cellI]);
6516  }
6517  else if (oldTimeLevel == 2)
6518  {
6519  this->globalADTape_.registerInput(state.oldTime().oldTime()[cellI]);
6520  }
6521  }
6522  }
6523  }
6524 
6525  forAll(stateInfo_["modelStates"], idxI)
6526  {
6527  const word stateName = stateInfo_["modelStates"][idxI];
6528  volScalarField& state = const_cast<volScalarField&>(
6529  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
6530 
6531  label maxOldTimes = state.nOldTimes();
6532 
6533  if (maxOldTimes >= oldTimeLevel)
6534  {
6535  forAll(state, cellI)
6536  {
6537  if (oldTimeLevel == 0)
6538  {
6539  this->globalADTape_.registerInput(state[cellI]);
6540  }
6541  else if (oldTimeLevel == 1)
6542  {
6543  this->globalADTape_.registerInput(state.oldTime()[cellI]);
6544  }
6545  else if (oldTimeLevel == 2)
6546  {
6547  this->globalADTape_.registerInput(state.oldTime().oldTime()[cellI]);
6548  }
6549  }
6550  }
6551  }
6552 
6553  forAll(stateInfo_["surfaceScalarStates"], idxI)
6554  {
6555  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
6556  surfaceScalarField& state = const_cast<surfaceScalarField&>(
6557  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
6558 
6559  label maxOldTimes = state.nOldTimes();
6560 
6561  if (maxOldTimes >= oldTimeLevel)
6562  {
6563  forAll(state, faceI)
6564  {
6565  if (oldTimeLevel == 0)
6566  {
6567  this->globalADTape_.registerInput(state[faceI]);
6568  }
6569  else if (oldTimeLevel == 1)
6570  {
6571  this->globalADTape_.registerInput(state.oldTime()[faceI]);
6572  }
6573  else if (oldTimeLevel == 2)
6574  {
6575  this->globalADTape_.registerInput(state.oldTime().oldTime()[faceI]);
6576  }
6577  }
6578  forAll(state.boundaryField(), patchI)
6579  {
6580  forAll(state.boundaryField()[patchI], faceI)
6581  {
6582  if (oldTimeLevel == 0)
6583  {
6584  this->globalADTape_.registerInput(state.boundaryFieldRef()[patchI][faceI]);
6585  }
6586  else if (oldTimeLevel == 1)
6587  {
6588  this->globalADTape_.registerInput(state.oldTime().boundaryFieldRef()[patchI][faceI]);
6589  }
6590  else if (oldTimeLevel == 2)
6591  {
6592  this->globalADTape_.registerInput(state.oldTime().oldTime().boundaryFieldRef()[patchI][faceI]);
6593  }
6594  }
6595  }
6596  }
6597  }
6598 
6599 #endif
6600 }
6601 
6603  const word fieldName,
6604  const word fieldType)
6605 {
6606 #ifdef CODI_AD_REVERSE
6607  /*
6608  Description:
6609  Register field variables as the input for reverse-mode AD
6610 
6611  Input:
6612  fieldName: the name of the flow field to register
6613 
6614  fieldType: can be either scalar or vector
6615  */
6616 
6617  if (fieldType == "scalar")
6618  {
6619  volScalarField& state = const_cast<volScalarField&>(
6620  meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
6621 
6622  forAll(state, cellI)
6623  {
6624  this->globalADTape_.registerInput(state[cellI]);
6625  }
6626  }
6627  else if (fieldType == "vector")
6628  {
6629  volVectorField& state = const_cast<volVectorField&>(
6630  meshPtr_->thisDb().lookupObject<volVectorField>(fieldName));
6631 
6632  forAll(state, cellI)
6633  {
6634  for (label i = 0; i < 3; i++)
6635  {
6636  this->globalADTape_.registerInput(state[cellI][i]);
6637  }
6638  }
6639  }
6640  else
6641  {
6642  FatalErrorIn("") << "fieldType not valid. Options: scalar or vector"
6643  << abort(FatalError);
6644  }
6645 
6646 #endif
6647 }
6648 
6650 {
6651 #ifdef CODI_AD_REVERSE
6652  /*
6653  Description:
6654  Register all residuals as the output for reverse-mode AD
6655  */
6656 
6657  forAll(stateInfo_["volVectorStates"], idxI)
6658  {
6659  const word stateName = stateInfo_["volVectorStates"][idxI];
6660  const word stateResName = stateName + "Res";
6661  volVectorField& stateRes = const_cast<volVectorField&>(
6662  meshPtr_->thisDb().lookupObject<volVectorField>(stateResName));
6663 
6664  forAll(stateRes, cellI)
6665  {
6666  for (label i = 0; i < 3; i++)
6667  {
6668  this->globalADTape_.registerOutput(stateRes[cellI][i]);
6669  }
6670  }
6671  }
6672 
6673  forAll(stateInfo_["volScalarStates"], idxI)
6674  {
6675  const word stateName = stateInfo_["volScalarStates"][idxI];
6676  const word stateResName = stateName + "Res";
6677  volScalarField& stateRes = const_cast<volScalarField&>(
6678  meshPtr_->thisDb().lookupObject<volScalarField>(stateResName));
6679 
6680  forAll(stateRes, cellI)
6681  {
6682  this->globalADTape_.registerOutput(stateRes[cellI]);
6683  }
6684  }
6685 
6686  forAll(stateInfo_["modelStates"], idxI)
6687  {
6688  const word stateName = stateInfo_["modelStates"][idxI];
6689  const word stateResName = stateName + "Res";
6690  volScalarField& stateRes = const_cast<volScalarField&>(
6691  meshPtr_->thisDb().lookupObject<volScalarField>(stateResName));
6692 
6693  forAll(stateRes, cellI)
6694  {
6695  this->globalADTape_.registerOutput(stateRes[cellI]);
6696  }
6697  }
6698 
6699  forAll(stateInfo_["surfaceScalarStates"], idxI)
6700  {
6701  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
6702  const word stateResName = stateName + "Res";
6703  surfaceScalarField& stateRes = const_cast<surfaceScalarField&>(
6704  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateResName));
6705 
6706  forAll(stateRes, faceI)
6707  {
6708  this->globalADTape_.registerOutput(stateRes[faceI]);
6709  }
6710  forAll(stateRes.boundaryField(), patchI)
6711  {
6712  forAll(stateRes.boundaryField()[patchI], faceI)
6713  {
6714  this->globalADTape_.registerOutput(stateRes.boundaryFieldRef()[patchI][faceI]);
6715  }
6716  }
6717  }
6718 #endif
6719 }
6720 
6722  List<scalar>& fX,
6723  List<scalar>& fY,
6724  List<scalar>& fZ)
6725 {
6726 #if defined(CODI_AD_REVERSE)
6727  /*
6728  Description:
6729  Register all force components as the output for reverse-mode AD
6730 
6731  Inputs:
6732  fX: Vector of X-component of forces
6733 
6734  fY: Vector of Y-component of forces
6735 
6736  fZ: Vector of Z-component of forces
6737  */
6738  forAll(fX, cI)
6739  {
6740  // Set seeds
6741  this->globalADTape_.registerOutput(fX[cI]);
6742  this->globalADTape_.registerOutput(fY[cI]);
6743  this->globalADTape_.registerOutput(fZ[cI]);
6744  }
6745 #endif
6746 }
6747 
6749  List<scalar>& a)
6750 {
6751 #if defined(CODI_AD_REVERSE)
6752  /*
6753  Description:
6754  Register all acoustic components as the output for reverse-mode AD
6755 
6756  Inputs:
6757  a: Vector of scalar entries
6758  */
6759  forAll(a, cI)
6760  {
6761  // Set seeds
6762  this->globalADTape_.registerOutput(a[cI]);
6763  }
6764 #endif
6765 }
6766 
6768 {
6769 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
6770  /*
6771  Description:
6772  Normalize the reverse-mode AD derivatives stored in vecY
6773 
6774  Input/Output:
6775  vecY: vector to be normalized. vecY = vecY * scalingFactor
6776  the scalingFactor depends on states.
6777  This is needed for the matrix-vector products in matrix-free adjoint
6778 
6779  */
6780 
6781  dictionary normStateDict = daOptionPtr_->getAllOptions().subDict("normalizeStates");
6782 
6783  PetscScalar* vecArray;
6784  VecGetArray(vecY, &vecArray);
6785 
6786  forAll(stateInfo_["volVectorStates"], idxI)
6787  {
6788  const word stateName = stateInfo_["volVectorStates"][idxI];
6789  // if normalized state not defined, skip
6790  if (normStateDict.found(stateName))
6791  {
6792 
6793  scalar scalingFactor = normStateDict.getScalar(stateName);
6794 
6795  forAll(meshPtr_->cells(), cellI)
6796  {
6797  for (label i = 0; i < 3; i++)
6798  {
6799  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
6800  vecArray[localIdx] *= scalingFactor.getValue();
6801  }
6802  }
6803  }
6804  }
6805 
6806  forAll(stateInfo_["volScalarStates"], idxI)
6807  {
6808  const word stateName = stateInfo_["volScalarStates"][idxI];
6809  // if normalized state not defined, skip
6810  if (normStateDict.found(stateName))
6811  {
6812  scalar scalingFactor = normStateDict.getScalar(stateName);
6813 
6814  forAll(meshPtr_->cells(), cellI)
6815  {
6816  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
6817  vecArray[localIdx] *= scalingFactor.getValue();
6818  }
6819  }
6820  }
6821 
6822  forAll(stateInfo_["modelStates"], idxI)
6823  {
6824  const word stateName = stateInfo_["modelStates"][idxI];
6825  // if normalized state not defined, skip
6826  if (normStateDict.found(stateName))
6827  {
6828 
6829  scalar scalingFactor = normStateDict.getScalar(stateName);
6830 
6831  forAll(meshPtr_->cells(), cellI)
6832  {
6833  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
6834  vecArray[localIdx] *= scalingFactor.getValue();
6835  }
6836  }
6837  }
6838 
6839  forAll(stateInfo_["surfaceScalarStates"], idxI)
6840  {
6841  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
6842  // if normalized state not defined, skip
6843  if (normStateDict.found(stateName))
6844  {
6845  scalar scalingFactor = normStateDict.getScalar(stateName);
6846 
6847  forAll(meshPtr_->faces(), faceI)
6848  {
6849  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
6850 
6851  if (faceI < daIndexPtr_->nLocalInternalFaces)
6852  {
6853  scalar meshSf = meshPtr_->magSf()[faceI];
6854  vecArray[localIdx] *= scalingFactor.getValue() * meshSf.getValue();
6855  }
6856  else
6857  {
6858  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
6859  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
6860  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
6861  scalar meshSf = meshPtr_->magSf().boundaryField()[patchIdx][faceIdx];
6862  vecArray[localIdx] *= scalingFactor.getValue() * meshSf.getValue();
6863  }
6864  }
6865  }
6866  }
6867 
6868  VecRestoreArray(vecY, &vecArray);
6869 
6870 #endif
6871 }
6872 
6874 {
6875 
6876 /*
6877  Description:
6878  Assign the reverse-mode AD input seeds from vecX to the residuals in OpenFOAM
6879 
6880  Input:
6881  vecX: vector storing the input seeds
6882 
6883  Output:
6884  All residual variables in OpenFOAM will be set: stateRes[cellI].setGradient(vecX[localIdx])
6885  */
6886 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
6887 
6888  forAll(stateInfo_["volVectorStates"], idxI)
6889  {
6890  const word stateName = stateInfo_["volVectorStates"][idxI];
6891  const word resName = stateName + "Res";
6892  volVectorField& stateRes = const_cast<volVectorField&>(
6893  meshPtr_->thisDb().lookupObject<volVectorField>(resName));
6894 
6895  forAll(meshPtr_->cells(), cellI)
6896  {
6897  for (label i = 0; i < 3; i++)
6898  {
6899  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
6900  stateRes[cellI][i].setGradient(seeds[localIdx]);
6901  }
6902  }
6903  }
6904 
6905  forAll(stateInfo_["volScalarStates"], idxI)
6906  {
6907  const word stateName = stateInfo_["volScalarStates"][idxI];
6908  const word resName = stateName + "Res";
6909  volScalarField& stateRes = const_cast<volScalarField&>(
6910  meshPtr_->thisDb().lookupObject<volScalarField>(resName));
6911 
6912  forAll(meshPtr_->cells(), cellI)
6913  {
6914  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
6915  stateRes[cellI].setGradient(seeds[localIdx]);
6916  }
6917  }
6918 
6919  forAll(stateInfo_["modelStates"], idxI)
6920  {
6921  const word stateName = stateInfo_["modelStates"][idxI];
6922  const word resName = stateName + "Res";
6923  volScalarField& stateRes = const_cast<volScalarField&>(
6924  meshPtr_->thisDb().lookupObject<volScalarField>(resName));
6925 
6926  forAll(meshPtr_->cells(), cellI)
6927  {
6928  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
6929  stateRes[cellI].setGradient(seeds[localIdx]);
6930  }
6931  }
6932 
6933  forAll(stateInfo_["surfaceScalarStates"], idxI)
6934  {
6935  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
6936  const word resName = stateName + "Res";
6937  surfaceScalarField& stateRes = const_cast<surfaceScalarField&>(
6938  meshPtr_->thisDb().lookupObject<surfaceScalarField>(resName));
6939 
6940  forAll(meshPtr_->faces(), faceI)
6941  {
6942  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
6943 
6944  if (faceI < daIndexPtr_->nLocalInternalFaces)
6945  {
6946  stateRes[faceI].setGradient(seeds[localIdx]);
6947  }
6948  else
6949  {
6950  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
6951  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
6952  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
6953  stateRes.boundaryFieldRef()[patchIdx][faceIdx].setGradient(seeds[localIdx]);
6954  }
6955  }
6956  }
6957 #endif
6958 }
6959 
6961 {
6962 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
6963  /*
6964  Description:
6965  Assign the reverse-mode AD input seeds from vecX to the residuals in OpenFOAM
6966 
6967  Input:
6968  vecX: vector storing the input seeds
6969 
6970  Output:
6971  All residual variables in OpenFOAM will be set: stateRes[cellI].setGradient(vecX[localIdx])
6972  */
6973 
6974  const PetscScalar* vecArray;
6975  VecGetArrayRead(vecX, &vecArray);
6976 
6977  forAll(stateInfo_["volVectorStates"], idxI)
6978  {
6979  const word stateName = stateInfo_["volVectorStates"][idxI];
6980  const word resName = stateName + "Res";
6981  volVectorField& stateRes = const_cast<volVectorField&>(
6982  meshPtr_->thisDb().lookupObject<volVectorField>(resName));
6983 
6984  forAll(meshPtr_->cells(), cellI)
6985  {
6986  for (label i = 0; i < 3; i++)
6987  {
6988  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
6989  stateRes[cellI][i].setGradient(vecArray[localIdx]);
6990  }
6991  }
6992  }
6993 
6994  forAll(stateInfo_["volScalarStates"], idxI)
6995  {
6996  const word stateName = stateInfo_["volScalarStates"][idxI];
6997  const word resName = stateName + "Res";
6998  volScalarField& stateRes = const_cast<volScalarField&>(
6999  meshPtr_->thisDb().lookupObject<volScalarField>(resName));
7000 
7001  forAll(meshPtr_->cells(), cellI)
7002  {
7003  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
7004  stateRes[cellI].setGradient(vecArray[localIdx]);
7005  }
7006  }
7007 
7008  forAll(stateInfo_["modelStates"], idxI)
7009  {
7010  const word stateName = stateInfo_["modelStates"][idxI];
7011  const word resName = stateName + "Res";
7012  volScalarField& stateRes = const_cast<volScalarField&>(
7013  meshPtr_->thisDb().lookupObject<volScalarField>(resName));
7014 
7015  forAll(meshPtr_->cells(), cellI)
7016  {
7017  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
7018  stateRes[cellI].setGradient(vecArray[localIdx]);
7019  }
7020  }
7021 
7022  forAll(stateInfo_["surfaceScalarStates"], idxI)
7023  {
7024  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
7025  const word resName = stateName + "Res";
7026  surfaceScalarField& stateRes = const_cast<surfaceScalarField&>(
7027  meshPtr_->thisDb().lookupObject<surfaceScalarField>(resName));
7028 
7029  forAll(meshPtr_->faces(), faceI)
7030  {
7031  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
7032 
7033  if (faceI < daIndexPtr_->nLocalInternalFaces)
7034  {
7035  stateRes[faceI].setGradient(vecArray[localIdx]);
7036  }
7037  else
7038  {
7039  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
7040  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
7041  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
7042  stateRes.boundaryFieldRef()[patchIdx][faceIdx].setGradient(vecArray[localIdx]);
7043  }
7044  }
7045  }
7046 
7047  VecRestoreArrayRead(vecX, &vecArray);
7048 #endif
7049 }
7050 
7052  Vec fBarVec,
7053  List<scalar>& fX,
7054  List<scalar>& fY,
7055  List<scalar>& fZ)
7056 {
7057 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
7058  /*
7059  Description:
7060  Assign the reverse-mode AD input seeds from fBarVec to the force vectors
7061 
7062  Inputs:
7063  fBarVec: vector storing the input seeds
7064 
7065  fX: Vector of X-component of forces
7066 
7067  fY: Vector of Y-component of forces
7068 
7069  fZ: Vector of Z-component of forces
7070 
7071  Outputs:
7072  All force variables (for computing surface forces) will be set
7073  */
7074  PetscScalar* vecArrayFBarVec;
7075  VecGetArray(fBarVec, &vecArrayFBarVec);
7076 
7077  label i = 0;
7078  forAll(fX, cI)
7079  {
7080  // Set seeds
7081  fX[cI].setGradient(vecArrayFBarVec[i]);
7082  fY[cI].setGradient(vecArrayFBarVec[i + 1]);
7083  fZ[cI].setGradient(vecArrayFBarVec[i + 2]);
7084 
7085  // Increment counter
7086  i += 3;
7087  }
7088 
7089  VecRestoreArray(fBarVec, &vecArrayFBarVec);
7090 #endif
7091 }
7092 
7094  Vec fBarVec,
7095  List<scalar>& a,
7096  label offset,
7097  label step)
7098 {
7099 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
7100  /*
7101  Description:
7102  Assign the reverse-mode AD input seeds from fBarVec to the force vectors
7103 
7104  Inputs:
7105  fBarVec: vector storing the input seeds
7106 
7107  a: Vector of entries 1
7108 
7109  Outputs:
7110  All acoustic variables will be set
7111  */
7112  PetscScalar* vecArrayFBarVec;
7113  VecGetArray(fBarVec, &vecArrayFBarVec);
7114 
7115  label i = 0;
7116  forAll(a, cI)
7117  {
7118  // Set seeds
7119  a[cI].setGradient(vecArrayFBarVec[i + offset]);
7120 
7121  // Increment counter
7122  i += step;
7123  }
7124 
7125  VecRestoreArray(fBarVec, &vecArrayFBarVec);
7126 #endif
7127 }
7128 
7130  Vec vecY,
7131  const label oldTimeLevel)
7132 {
7133 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
7134  /*
7135  Description:
7136  Set the reverse-mode AD derivatives from the state variables in OpenFOAM to vecY
7137 
7138  Input:
7139  OpenFOAM state variables that contain the reverse-mode derivative
7140 
7141  oldTimeLevel: which time level to register, the default value
7142  is 0, meaning it will register the state itself. If its
7143  value is 1, it will register state.oldTime(), if its value
7144  is 2, it will register state.oldTime().oldTime(). For
7145  steady-state adjoint oldTimeLevel = 0
7146 
7147  Output:
7148  vecY: a vector to store the derivatives. The order of this vector is
7149  the same as the state variable vector
7150  */
7151 
7152  if (oldTimeLevel < 0 || oldTimeLevel > 2)
7153  {
7154  FatalErrorIn("") << "oldTimeLevel not valid. Options: 0, 1, or 2"
7155  << abort(FatalError);
7156  }
7157 
7158  PetscScalar* vecArray;
7159  VecGetArray(vecY, &vecArray);
7160 
7161  forAll(stateInfo_["volVectorStates"], idxI)
7162  {
7163  const word stateName = stateInfo_["volVectorStates"][idxI];
7164  volVectorField& state = const_cast<volVectorField&>(
7165  meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
7166 
7167  label maxOldTimes = state.nOldTimes();
7168 
7169  if (maxOldTimes >= oldTimeLevel)
7170  {
7171  forAll(meshPtr_->cells(), cellI)
7172  {
7173  for (label i = 0; i < 3; i++)
7174  {
7175  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
7176  if (oldTimeLevel == 0)
7177  {
7178  vecArray[localIdx] = state[cellI][i].getGradient();
7179  }
7180  else if (oldTimeLevel == 1)
7181  {
7182  vecArray[localIdx] = state.oldTime()[cellI][i].getGradient();
7183  }
7184  else if (oldTimeLevel == 2)
7185  {
7186  vecArray[localIdx] = state.oldTime().oldTime()[cellI][i].getGradient();
7187  }
7188  }
7189  }
7190  }
7191  }
7192 
7193  forAll(stateInfo_["volScalarStates"], idxI)
7194  {
7195  const word stateName = stateInfo_["volScalarStates"][idxI];
7196  volScalarField& state = const_cast<volScalarField&>(
7197  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
7198 
7199  label maxOldTimes = state.nOldTimes();
7200 
7201  if (maxOldTimes >= oldTimeLevel)
7202  {
7203  forAll(meshPtr_->cells(), cellI)
7204  {
7205  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
7206  if (oldTimeLevel == 0)
7207  {
7208  vecArray[localIdx] = state[cellI].getGradient();
7209  }
7210  else if (oldTimeLevel == 1)
7211  {
7212  vecArray[localIdx] = state.oldTime()[cellI].getGradient();
7213  }
7214  else if (oldTimeLevel == 2)
7215  {
7216  vecArray[localIdx] = state.oldTime().oldTime()[cellI].getGradient();
7217  }
7218  }
7219  }
7220  }
7221 
7222  forAll(stateInfo_["modelStates"], idxI)
7223  {
7224  const word stateName = stateInfo_["modelStates"][idxI];
7225  volScalarField& state = const_cast<volScalarField&>(
7226  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
7227 
7228  label maxOldTimes = state.nOldTimes();
7229 
7230  if (maxOldTimes >= oldTimeLevel)
7231  {
7232  forAll(meshPtr_->cells(), cellI)
7233  {
7234  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
7235  if (oldTimeLevel == 0)
7236  {
7237  vecArray[localIdx] = state[cellI].getGradient();
7238  }
7239  else if (oldTimeLevel == 1)
7240  {
7241  vecArray[localIdx] = state.oldTime()[cellI].getGradient();
7242  }
7243  else if (oldTimeLevel == 2)
7244  {
7245  vecArray[localIdx] = state.oldTime().oldTime()[cellI].getGradient();
7246  }
7247  }
7248  }
7249  }
7250 
7251  forAll(stateInfo_["surfaceScalarStates"], idxI)
7252  {
7253  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
7254  surfaceScalarField& state = const_cast<surfaceScalarField&>(
7255  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
7256 
7257  label maxOldTimes = state.nOldTimes();
7258 
7259  if (maxOldTimes >= oldTimeLevel)
7260  {
7261  forAll(meshPtr_->faces(), faceI)
7262  {
7263  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
7264 
7265  if (faceI < daIndexPtr_->nLocalInternalFaces)
7266  {
7267  if (oldTimeLevel == 0)
7268  {
7269  vecArray[localIdx] = state[faceI].getGradient();
7270  }
7271  else if (oldTimeLevel == 1)
7272  {
7273  vecArray[localIdx] = state.oldTime()[faceI].getGradient();
7274  }
7275  else if (oldTimeLevel == 2)
7276  {
7277  vecArray[localIdx] = state.oldTime().oldTime()[faceI].getGradient();
7278  }
7279  }
7280  else
7281  {
7282  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
7283  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
7284  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
7285 
7286  if (oldTimeLevel == 0)
7287  {
7288  vecArray[localIdx] =
7289  state.boundaryField()[patchIdx][faceIdx].getGradient();
7290  }
7291  else if (oldTimeLevel == 1)
7292  {
7293  vecArray[localIdx] =
7294  state.oldTime().boundaryField()[patchIdx][faceIdx].getGradient();
7295  }
7296  else if (oldTimeLevel == 2)
7297  {
7298  vecArray[localIdx] =
7299  state.oldTime().oldTime().boundaryField()[patchIdx][faceIdx].getGradient();
7300  }
7301  }
7302  }
7303  }
7304  }
7305 
7306  VecRestoreArray(vecY, &vecArray);
7307 
7308 #endif
7309 }
7310 
7312  const word fieldName,
7313  const word fieldType,
7314  Vec vecY)
7315 {
7316 #if defined(CODI_AD_FORWARD) || defined(CODI_AD_REVERSE)
7317  /*
7318  Description:
7319  Set the reverse-mode AD derivatives from the field variables in OpenFOAM to vecY
7320 
7321  Input:
7322  OpenFOAM field variables that contain the reverse-mode derivative
7323 
7324  Output:
7325  vecY: a vector to store the derivatives. The order of this vector is
7326  the same as the field variable vector
7327  */
7328 
7329  PetscScalar* vecArray;
7330  VecGetArray(vecY, &vecArray);
7331 
7332  if (fieldType == "scalar")
7333  {
7334  volScalarField& state = const_cast<volScalarField&>(
7335  meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
7336 
7337  forAll(state, cellI)
7338  {
7339  vecArray[cellI] = state[cellI].getGradient();
7340  }
7341  }
7342  else if (fieldType == "vector")
7343  {
7344  volVectorField& state = const_cast<volVectorField&>(
7345  meshPtr_->thisDb().lookupObject<volVectorField>(fieldName));
7346 
7347  forAll(state, cellI)
7348  {
7349  for (label i = 0; i < 3; i++)
7350  {
7351  label localIdx = cellI * 3 + i;
7352  vecArray[localIdx] = state[cellI][i].getGradient();
7353  }
7354  }
7355  }
7356  else
7357  {
7358  FatalErrorIn("") << "fieldType not valid. Options: scalar or vector"
7359  << abort(FatalError);
7360  }
7361 
7362  VecRestoreArray(vecY, &vecArray);
7363 
7364 #endif
7365 }
7366 
7368  const Vec mpiVec,
7369  Vec seqVec)
7370 {
7371  /*
7372  Description:
7373  Convert a MPI vec to a seq vec by using VecScatter
7374 
7375  Input:
7376  mpiVec: the MPI vector in parallel
7377 
7378  Output:
7379  seqVec: the seq vector in serial
7380  */
7381  label vecSize;
7382  VecGetSize(mpiVec, &vecSize);
7383 
7384  // scatter colors to local array for all procs
7385  Vec vout;
7386  VecScatter ctx;
7387  VecScatterCreateToAll(mpiVec, &ctx, &vout);
7388  VecScatterBegin(ctx, mpiVec, vout, INSERT_VALUES, SCATTER_FORWARD);
7389  VecScatterEnd(ctx, mpiVec, vout, INSERT_VALUES, SCATTER_FORWARD);
7390 
7391  PetscScalar* voutArray;
7392  VecGetArray(vout, &voutArray);
7393 
7394  PetscScalar* seqVecArray;
7395  VecGetArray(seqVec, &seqVecArray);
7396 
7397  for (label i = 0; i < vecSize; i++)
7398  {
7399  seqVecArray[i] = voutArray[i];
7400  }
7401  VecRestoreArray(vout, &voutArray);
7402  VecRestoreArray(seqVec, &seqVecArray);
7403  VecScatterDestroy(&ctx);
7404  VecDestroy(&vout);
7405 }
7406 
7407 void DASolver::setdXvdFFDMat(const Mat dXvdFFDMat)
7408 {
7409  /*
7410  Description:
7411  Set the value for dXvdFFDMat_. Basically we use MatConvert
7412  */
7413  MatConvert(dXvdFFDMat, MATSAME, MAT_INITIAL_MATRIX, &dXvdFFDMat_);
7414  //MatDuplicate(dXvdFFDMat, MAT_COPY_VALUES, &dXvdFFDMat_);
7415  MatAssemblyBegin(dXvdFFDMat_, MAT_FINAL_ASSEMBLY);
7416  MatAssemblyEnd(dXvdFFDMat_, MAT_FINAL_ASSEMBLY);
7417 }
7418 
7420 {
7421  /*
7422  Description:
7423  Set the value for FFD2XvSeedVec_
7424 
7425  Input:
7426  vecIn: this vector will be copied to FFD2XvSeedVec_
7427  */
7428  VecDuplicate(vecIn, &FFD2XvSeedVec_);
7429  VecCopy(vecIn, FFD2XvSeedVec_);
7430 }
7431 
7433 {
7434  /*
7435  Description:
7436  Check whether the min residual in primal satisfy the prescribed tolerance
7437  If yes, return 0 else return 1
7438  */
7439 
7440  scalar tol = daOptionPtr_->getOption<scalar>("primalMinResTol");
7441  scalar tolMax = daOptionPtr_->getOption<scalar>("primalMinResTolDiff");
7442  if (primalMinRes_ / tol > tolMax)
7443  {
7444  Info << "********************************************" << endl;
7445  Info << "Primal min residual " << primalMinRes_ << endl
7446  << "did not satisfy the prescribed tolerance "
7447  << tol << endl;
7448  Info << "Primal solution failed!" << endl;
7449  Info << "********************************************" << endl;
7450  return 1;
7451  }
7452  else
7453  {
7454  return 0;
7455  }
7456 
7457  return 1;
7458 }
7459 
7461  const Time& runTime,
7462  const label printInterval) const
7463 {
7464  /*
7465  Description:
7466  Check if it is print time
7467  */
7468  if (runTime.timeIndex() % printInterval == 0 || runTime.timeIndex() == 1)
7469  {
7470  return 1;
7471  }
7472  else
7473  {
7474  return 0;
7475  }
7476 }
7477 
7479 {
7480  /*
7481  Description:
7482  Write associated fields such as relative velocity
7483  */
7484 
7485  IOobject MRFIO(
7486  "MRFProperties",
7487  runTimePtr_->constant(),
7488  meshPtr_(),
7489  IOobject::MUST_READ,
7490  IOobject::NO_WRITE,
7491  false); // do not register
7492 
7493  if (MRFIO.typeHeaderOk<IOdictionary>(true))
7494  {
7495  IOdictionary MRFProperties(MRFIO);
7496 
7497  bool activeMRF(MRFProperties.subDict("MRF").lookupOrDefault("active", true));
7498 
7499  if (activeMRF)
7500  {
7501  const volVectorField& U = meshPtr_->thisDb().lookupObject<volVectorField>("U");
7502 
7503  volVectorField URel("URel", U);
7504  IOMRFZoneList MRF(meshPtr_());
7505  MRF.makeRelative(URel);
7506  URel.write();
7507  }
7508  }
7509 }
7510 
7512  const word fieldName,
7513  const scalar val,
7514  const label localCellI,
7515  const label compI)
7516 {
7517  /*
7518  Description:
7519  Set the field value based on the local cellI.
7520 
7521  Input:
7522  fieldName: the name of the field to set
7523 
7524  val: the value to set
7525 
7526  localCellI: the local cell index
7527 
7528  compI: which component to set (only for vectors such as U)
7529  */
7530 
7531  if (meshPtr_->thisDb().foundObject<volVectorField>(fieldName))
7532  {
7533  volVectorField& field =
7534  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(fieldName));
7535  field[localCellI][compI] = val;
7536  }
7537  else if (meshPtr_->thisDb().foundObject<volScalarField>(fieldName))
7538  {
7539  volScalarField& field =
7540  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
7541  field[localCellI] = val;
7542  }
7543  else
7544  {
7545  FatalErrorIn("") << fieldName << " not found in volScalar and volVector Fields "
7546  << abort(FatalError);
7547  }
7548 }
7549 
7551  const word fieldName,
7552  const scalar val,
7553  const label globalCellI,
7554  const label compI)
7555 {
7556  /*
7557  Description:
7558  Set the field value based on the global cellI. This is usually
7559  used if the state variables are design variables, e.g., betaSA
7560  The reason to use global cell index, instead of local one, is
7561  because this index is usually provided by the optimizer. Optimizer
7562  uses global cell index as the design variable
7563 
7564  Input:
7565  fieldName: the name of the field to set
7566 
7567  val: the value to set
7568 
7569  globalCellI: the global cell index
7570 
7571  compI: which component to set (only for vectors such as U)
7572  */
7573 
7574  if (meshPtr_->thisDb().foundObject<volVectorField>(fieldName))
7575  {
7576  if (daIndexPtr_->globalCellVectorNumbering.isLocal(globalCellI))
7577  {
7578  volVectorField& field =
7579  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(fieldName));
7580  label localCellI = daIndexPtr_->globalCellVectorNumbering.toLocal(globalCellI);
7581  field[localCellI][compI] = val;
7582  }
7583  }
7584  else if (meshPtr_->thisDb().foundObject<volScalarField>(fieldName))
7585  {
7586  if (daIndexPtr_->globalCellNumbering.isLocal(globalCellI))
7587  {
7588  volScalarField& field =
7589  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
7590  label localCellI = daIndexPtr_->globalCellNumbering.toLocal(globalCellI);
7591  field[localCellI] = val;
7592  }
7593  }
7594  else
7595  {
7596  FatalErrorIn("") << fieldName << " not found in volScalar and volVector Fields "
7597  << abort(FatalError);
7598  }
7599 }
7600 
7602 {
7603  /*
7604  Description:
7605  Calculate the residual and assign it to resVec
7606 
7607  Input/Output:
7608  resVec: residual vector
7609  */
7610 
7611  // compute residuals
7612  daResidualPtr_->correctBoundaryConditions();
7613  daResidualPtr_->updateIntermediateVariables();
7614  daModelPtr_->correctBoundaryConditions();
7615  daModelPtr_->updateIntermediateVariables();
7616  label isPC = 0;
7617  dictionary options;
7618  options.set("isPC", isPC);
7619  daResidualPtr_->calcResiduals(options);
7620  daModelPtr_->calcResiduals(options);
7621 
7622  PetscScalar* vecArray;
7623  VecGetArray(resVec, &vecArray);
7624 
7625  forAll(stateInfo_["volVectorStates"], idxI)
7626  {
7627  const word stateName = stateInfo_["volVectorStates"][idxI];
7628  const word resName = stateName + "Res";
7629  const volVectorField& stateRes = meshPtr_->thisDb().lookupObject<volVectorField>(resName);
7630 
7631  forAll(meshPtr_->cells(), cellI)
7632  {
7633  for (label i = 0; i < 3; i++)
7634  {
7635  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
7636  assignValueCheckAD(vecArray[localIdx], stateRes[cellI][i]);
7637  }
7638  }
7639  }
7640 
7641  forAll(stateInfo_["volScalarStates"], idxI)
7642  {
7643  const word stateName = stateInfo_["volScalarStates"][idxI];
7644  const word resName = stateName + "Res";
7645  const volScalarField& stateRes = meshPtr_->thisDb().lookupObject<volScalarField>(resName);
7646 
7647  forAll(meshPtr_->cells(), cellI)
7648  {
7649  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
7650  assignValueCheckAD(vecArray[localIdx], stateRes[cellI]);
7651  }
7652  }
7653 
7654  forAll(stateInfo_["modelStates"], idxI)
7655  {
7656  const word stateName = stateInfo_["modelStates"][idxI];
7657  const word resName = stateName + "Res";
7658  const volScalarField& stateRes = meshPtr_->thisDb().lookupObject<volScalarField>(resName);
7659 
7660  forAll(meshPtr_->cells(), cellI)
7661  {
7662  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
7663  assignValueCheckAD(vecArray[localIdx], stateRes[cellI]);
7664  }
7665  }
7666 
7667  forAll(stateInfo_["surfaceScalarStates"], idxI)
7668  {
7669  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
7670  const word resName = stateName + "Res";
7671  const surfaceScalarField& stateRes = meshPtr_->thisDb().lookupObject<surfaceScalarField>(resName);
7672 
7673  forAll(meshPtr_->faces(), faceI)
7674  {
7675  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
7676 
7677  if (faceI < daIndexPtr_->nLocalInternalFaces)
7678  {
7679  assignValueCheckAD(vecArray[localIdx], stateRes[faceI]);
7680  }
7681  else
7682  {
7683  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
7684  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
7685  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
7686  assignValueCheckAD(vecArray[localIdx], stateRes.boundaryField()[patchIdx][faceIdx]);
7687  }
7688  }
7689  }
7690 
7691  VecRestoreArray(resVec, &vecArray);
7692 }
7693 
7695  const word fieldName,
7696  const word fieldType)
7697 {
7698  /*
7699  Description:
7700  Update the boundary condition for a field
7701 
7702  Input:
7703  fieldName: the name of the field to update
7704 
7705  fieldType: either scalar or vector
7706  */
7707 
7708  if (fieldType == "scalar")
7709  {
7710  volScalarField& field =
7711  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
7712  field.correctBoundaryConditions();
7713  }
7714  else if (fieldType == "vector")
7715  {
7716  volVectorField& field =
7717  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(fieldName));
7718  field.correctBoundaryConditions();
7719  }
7720  else
7721  {
7722  FatalErrorIn("") << fieldType << " not support. Options are: vector or scalar "
7723  << abort(FatalError);
7724  }
7725 }
7726 
7727 void DASolver::saveTimeInstanceFieldHybrid(label& timeInstanceI)
7728 {
7729  /*
7730  Description:
7731  Save primal variable to time instance list for unsteady adjoint
7732  Here we save the last nTimeInstances snapshots
7733  */
7734 
7735  scalar endTime = runTimePtr_->endTime().value();
7736  scalar t = runTimePtr_->timeOutputValue();
7737  scalar instanceStart =
7738  endTime - periodicity_ / nTimeInstances_ * (nTimeInstances_ - 1 - timeInstanceI);
7739 
7740  // the 2nd condition is for t=9.999999999999 scenario)
7741  if (t > instanceStart || fabs(t - endTime) < 1e-8)
7742  {
7743  Info << "Saving time instance " << timeInstanceI << " at Time = " << t << endl;
7744 
7745  // save fields
7746  daFieldPtr_->ofField2List(
7747  stateAllInstances_[timeInstanceI],
7748  stateBoundaryAllInstances_[timeInstanceI]);
7749 
7750  // save objective functions
7751  forAll(daOptionPtr_->getAllOptions().subDict("objFunc").toc(), idxI)
7752  {
7753  word objFuncName = daOptionPtr_->getAllOptions().subDict("objFunc").toc()[idxI];
7754  scalar objFuncVal = this->getObjFuncValue(objFuncName);
7755  objFuncsAllInstances_[timeInstanceI].set(objFuncName, objFuncVal);
7756  }
7757 
7758  // save runTime
7759  runTimeAllInstances_[timeInstanceI] = t;
7760  runTimeIndexAllInstances_[timeInstanceI] = runTimePtr_->timeIndex();
7761 
7762  if (daOptionPtr_->getOption<label>("debug"))
7763  {
7764  this->calcPrimalResidualStatistics("print");
7765  }
7766 
7767  timeInstanceI++;
7768  }
7769  return;
7770 }
7771 
7773 {
7774  /*
7775  Description:
7776  Save primal variable to time instance list for unsteady adjoint
7777  Here we save every time step
7778  */
7779  // save fields
7780  daFieldPtr_->ofField2List(
7781  stateAllInstances_[timeInstanceI],
7782  stateBoundaryAllInstances_[timeInstanceI]);
7783 
7784  // save objective functions
7785  forAll(daOptionPtr_->getAllOptions().subDict("objFunc").toc(), idxI)
7786  {
7787  word objFuncName = daOptionPtr_->getAllOptions().subDict("objFunc").toc()[idxI];
7788  scalar objFuncVal = this->getObjFuncValue(objFuncName);
7789  objFuncsAllInstances_[timeInstanceI].set(objFuncName, objFuncVal);
7790  }
7791 
7792  // save runTime
7793  scalar t = runTimePtr_->timeOutputValue();
7794  runTimeAllInstances_[timeInstanceI] = t;
7795  runTimeIndexAllInstances_[timeInstanceI] = runTimePtr_->timeIndex();
7796 
7797  timeInstanceI++;
7798 }
7799 
7800 void DASolver::setTimeInstanceField(const label instanceI)
7801 {
7802  /*
7803  Description:
7804  Assign primal variables based on the current time instance
7805  If unsteady adjoint solvers are used, this virtual function should be
7806  implemented in a child class, otherwise, return error if called
7807  */
7808 
7809  Info << "Setting fields for time instance " << instanceI << endl;
7810 
7811  label idxI = -9999;
7812 
7813  // set run time
7814  // NOTE: we need to call setTime before updating the oldTime fields, this is because
7815  // the setTime call will assign field to field.oldTime()
7816  runTimePtr_->setTime(runTimeAllInstances_[instanceI], runTimeIndexAllInstances_[instanceI]);
7817 
7818  word mode = daOptionPtr_->getSubDictOption<word>("unsteadyAdjoint", "mode");
7819 
7820  // set fields
7821  label oldTimeLevel = 0;
7822  daFieldPtr_->list2OFField(
7823  stateAllInstances_[instanceI],
7824  stateBoundaryAllInstances_[instanceI],
7825  oldTimeLevel);
7826 
7827  // for time accurate adjoint, in addition to assign current fields,
7828  // we need to assign oldTime fields.
7829  if (mode == "timeAccurateAdjoint")
7830  {
7831  // assign U.oldTime()
7832  oldTimeLevel = 1;
7833  // if instanceI - 1 < 0, we just assign idxI = 0. This is essentially
7834  // assigning U.oldTime() = U0
7835  idxI = max(instanceI - 1, 0);
7836  daFieldPtr_->list2OFField(
7837  stateAllInstances_[idxI],
7839  oldTimeLevel);
7840 
7841  // assign U.oldTime().oldTime()
7842  oldTimeLevel = 2;
7843  // if instanceI - 2 < 0, we just assign idxI = 0, This is essentially
7844  // assigning U.oldTime().oldTime() = U0
7845  idxI = max(instanceI - 2, 0);
7846  daFieldPtr_->list2OFField(
7847  stateAllInstances_[idxI],
7849  oldTimeLevel);
7850  }
7851 
7852  // We need to call correctBC multiple times to reproduce
7853  // the exact residual for mulitpoint, this is needed for some boundary conditions
7854  // and intermediate variables (e.g., U for inletOutlet, nut with wall functions)
7855  for (label i = 0; i < 10; i++)
7856  {
7857  daResidualPtr_->correctBoundaryConditions();
7858  daResidualPtr_->updateIntermediateVariables();
7859  daModelPtr_->correctBoundaryConditions();
7860  daModelPtr_->updateIntermediateVariables();
7861  }
7862 }
7863 
7865  const word mode,
7866  Mat stateMat,
7867  Mat stateBCMat,
7868  Vec timeVec,
7869  Vec timeIdxVec)
7870 {
7871  PetscInt Istart, Iend;
7872  MatGetOwnershipRange(stateMat, &Istart, &Iend);
7873 
7874  PetscInt IstartBC, IendBC;
7875  MatGetOwnershipRange(stateBCMat, &IstartBC, &IendBC);
7876 
7877  for (label n = 0; n < nTimeInstances_; n++)
7878  {
7879  for (label i = Istart; i < Iend; i++)
7880  {
7881  label relIdx = i - Istart;
7882  PetscScalar val;
7883  if (mode == "mat2List")
7884  {
7885  MatGetValues(stateMat, 1, &i, 1, &n, &val);
7886  stateAllInstances_[n][relIdx] = val;
7887  }
7888  else if (mode == "list2Mat")
7889  {
7890  assignValueCheckAD(val, stateAllInstances_[n][relIdx]);
7891  MatSetValue(stateMat, i, n, val, INSERT_VALUES);
7892  }
7893  else
7894  {
7895  FatalErrorIn("") << "mode not valid!" << abort(FatalError);
7896  }
7897  }
7898 
7899  for (label i = IstartBC; i < IendBC; i++)
7900  {
7901  label relIdx = i - IstartBC;
7902  PetscScalar val;
7903  if (mode == "mat2List")
7904  {
7905  MatGetValues(stateBCMat, 1, &i, 1, &n, &val);
7906  stateBoundaryAllInstances_[n][relIdx] = val;
7907  }
7908  else if (mode == "list2Mat")
7909  {
7911  MatSetValue(stateBCMat, i, n, val, INSERT_VALUES);
7912  }
7913  else
7914  {
7915  FatalErrorIn("") << "mode not valid!" << abort(FatalError);
7916  }
7917  }
7918  }
7919 
7920  if (mode == "list2Mat")
7921  {
7922  MatAssemblyBegin(stateMat, MAT_FINAL_ASSEMBLY);
7923  MatAssemblyEnd(stateMat, MAT_FINAL_ASSEMBLY);
7924  MatAssemblyBegin(stateBCMat, MAT_FINAL_ASSEMBLY);
7925  MatAssemblyEnd(stateBCMat, MAT_FINAL_ASSEMBLY);
7926  }
7927 
7928  PetscScalar* timeVecArray;
7929  PetscScalar* timeIdxVecArray;
7930  VecGetArray(timeVec, &timeVecArray);
7931  VecGetArray(timeIdxVec, &timeIdxVecArray);
7932 
7933  for (label n = 0; n < nTimeInstances_; n++)
7934  {
7935  if (mode == "mat2List")
7936  {
7937  runTimeAllInstances_[n] = timeVecArray[n];
7938  runTimeIndexAllInstances_[n] = round(timeIdxVecArray[n]);
7939  }
7940  else if (mode == "list2Mat")
7941  {
7942  assignValueCheckAD(timeVecArray[n], runTimeAllInstances_[n]);
7943  timeIdxVecArray[n] = runTimeIndexAllInstances_[n];
7944  }
7945  else
7946  {
7947  FatalErrorIn("") << "mode not valid!" << abort(FatalError);
7948  }
7949  }
7950 
7951  VecRestoreArray(timeVec, &timeVecArray);
7952  VecRestoreArray(timeIdxVec, &timeIdxVecArray);
7953 }
7954 
7956 {
7957  /*
7958  Description:
7959  If the mesh fails, we set the time to 10000 and write the results to the disk.
7960  This way, the results will be renamed to 0.00000x during optimization, so that we
7961  can visualize them in Paraview to debug which part of the mesh is failing.
7962  */
7963  if (daOptionPtr_->getOption<label>("writeMinorIterations"))
7964  {
7965  runTimePtr_->setTime(10000.0, 10000);
7966  runTimePtr_->writeNow();
7967  }
7968 }
7969 
7971  const label instanceI,
7972  const word objFuncName)
7973 {
7974  /*
7975  Description:
7976  Return the value of objective function at the given time instance and name
7977  */
7978 
7979  return objFuncsAllInstances_[instanceI].getScalar(objFuncName);
7980 }
7981 
7982 void DASolver::setPrimalBoundaryConditions(const label printInfo)
7983 {
7984  /*
7985  Description:
7986  Update the state boundary conditions based on the ones defined in primalBC
7987  */
7988 
7989  // first check if we need to change the boundary conditions based on
7990  // the primalBC dict in DAOption. NOTE: this will overwrite whatever
7991  // boundary conditions defined in the "0" folder
7992  dictionary bcDict = daOptionPtr_->getAllOptions().subDict("primalBC");
7993  if (bcDict.toc().size() != 0)
7994  {
7995  if (printInfo)
7996  {
7997  Info << "Setting up primal boundary conditions based on pyOptions: " << endl;
7998  }
7999  daFieldPtr_->setPrimalBoundaryConditions(printInfo);
8000  }
8001 }
8002 
8004  const Vec xvVec,
8005  const Vec wVec,
8006  Vec dFdW,
8007  Vec psi)
8008 {
8009  /*
8010  Description:
8011  Solve the adjoint using the fixed-point iteration approach
8012  */
8013 
8014  FatalErrorIn("DASolver::runFPAdj")
8015  << "Child class not implemented!"
8016  << abort(FatalError);
8017 
8018  return 1;
8019 }
8020 
8021 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
8022 
8023 } // End namespace Foam
8024 
8025 // ************************************************************************* //
Foam::DASolver::calcFvSourceInternal
void calcFvSourceInternal(const word propName, const scalarField &aForce, const scalarField &tForce, const scalarField &rDistList, const scalarList &targetForce, const vector &center, volVectorField &fvSource)
calculate the fvSource based on the radial force profile
Definition: DASolver.C:1769
Foam::DASolver::calcdFdFFD
void calcdFdFFD(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdFFD)
compute dFdFFD
Definition: DASolver.C:4233
Foam::DASolver::getForcesInternal
void getForcesInternal(List< scalar > &fX, List< scalar > &fY, List< scalar > &fZ, List< word > &patchList)
compute the forces of the desired fluid-structure-interation patches
Definition: DASolver.C:1292
Foam::DASolver::calcdFdFieldAD
void calcdFdFieldAD(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdField)
compute dFdField
Definition: DASolver.C:4549
mu
volScalarField & mu
Definition: createRefsSolidDisplacement.H:6
Foam::DASolver::writeFailedMesh
void writeFailedMesh()
write the failed mesh to disk
Definition: DASolver.C:7955
Foam::DASolver::assignStateGradient2Vec
void assignStateGradient2Vec(Vec vecY, const label oldTimeLevel=0)
set the reverse-mode AD derivatives from the state variables in OpenFOAM to vecY
Definition: DASolver.C:7129
Foam::DAFvSource
Definition: DAFvSource.H:34
Foam::DASolver::updateOFField
void updateOFField(const Vec wVec)
Update the OpenFOAM field values (including both internal and boundary fields) based on the state vec...
Definition: DASolver.C:4765
Foam::DASolver::calcCouplingFaceCoords
void calcCouplingFaceCoords(const scalar *volCoords, scalar *surfCoords)
return the face coordinates based on vol coords
Definition: DASolver.C:362
Foam::DASolver::initializedRdWTMatrixFree
void initializedRdWTMatrixFree(const Vec xvVec, const Vec wVec)
initialize matrix free dRdWT
Definition: DASolver.C:4861
Foam::DASolver::calcdRdAOA
void calcdRdAOA(const Vec xvVec, const Vec wVec, const word designVarName, Mat dRdAOA)
compute dRdAOA
Definition: DASolver.C:3240
allOptions
const dictionary & allOptions
Definition: createRefsRhoSimpleC.H:15
Foam::DAUtility::pyCalcBetaJacVecProdInterface
static pyJacVecProdInterface pyCalcBetaJacVecProdInterface
Definition: DAUtility.H:133
Foam::DASolver::calcdAcousticsdWAD
void calcdAcousticsdWAD(const Vec xvVec, const Vec wVec, const Vec fBarVec, Vec dForcedW, word varName, word groupName)
Definition: DASolver.C:6103
Foam::DASolver::loop
label loop(Time &runTime)
return whether to loop the primal solution, similar to runTime::loop() except we don't do file IO
Definition: DASolver.C:112
U
U
Definition: pEqnPimpleDyM.H:60
Foam::DASolver::calcForceProfileInternal
void calcForceProfileInternal(fvMesh &mesh, const vector &center, scalarList &aForceL, scalarList &tForceL, scalarList &rDistL)
Definition: DASolver.C:1649
Foam::DASolver::calcdFdXvAD
void calcdFdXvAD(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdXv)
compute dFdXv AD
Definition: DASolver.C:5108
Foam::DASolver::calcdRdBCTPsiAD
void calcdRdBCTPsiAD(const Vec xvVec, const Vec wVec, const Vec psi, const word designVarName, Vec dRdBCTPsi)
compute dRdBCAD
Definition: DASolver.C:3326
Foam::DASolver::setFieldValue4GlobalCellI
void setFieldValue4GlobalCellI(const word fieldName, const scalar val, const label globalCellI, const label compI=0)
set the field value
Definition: DASolver.C:7550
Foam::DASolver::stateInfo_
HashTable< wordList > stateInfo_
the stateInfo_ list from DAStateInfo object
Definition: DASolver.H:103
Foam::DASolver::setTimeInstanceField
void setTimeInstanceField(const label instanceI)
assign primal variables based on the current time instance
Definition: DASolver.C:7800
Foam::DAUtility::pyCalcBeta
static void * pyCalcBeta
define a function pointer template for Python call back
Definition: DAUtility.H:129
Foam::DASolver::calcdFdACT
void calcdFdACT(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, const word designVarType, Vec dFdACT)
compute dFdACT
Definition: DASolver.C:4434
forAll
forAll(pseudoP.boundaryField(), patchI)
Definition: solvePseudoPEqn.H:10
Foam::DASolver::daModelPtr_
autoPtr< DAModel > daModelPtr_
DAModel pointer.
Definition: DASolver.H:79
Foam::DAUtility::pyCalcBetaInterface
static pyComputeInterface pyCalcBetaInterface
Definition: DAUtility.H:130
Foam::DASolver::assignFieldGradient2Vec
void assignFieldGradient2Vec(const word fieldName, const word fieldType, Vec vecY)
set the reverse-mode AD derivatives from the field variables in OpenFOAM to vecY
Definition: DASolver.C:7311
Foam::DASolver::getPatchInfo
void getPatchInfo(label &nPoints, label &nFaces, List< word > &patchList)
return the number of points and faces for MDO patches
Definition: DASolver.C:1251
Foam::DASolver::registerStateVariableInput4AD
void registerStateVariableInput4AD(const label oldTimeLevel=0)
register all state variables as the input for reverse-mode AD
Definition: DASolver.C:6445
Foam::DASolver::daIndexPtr_
autoPtr< DAIndex > daIndexPtr_
DAIndex pointer.
Definition: DASolver.H:82
Foam::DASolver::setFieldValue4LocalCellI
void setFieldValue4LocalCellI(const word fieldName, const scalar val, const label localCellI, const label compI=0)
Definition: DASolver.C:7511
Foam::DASolver::reduceStateResConLevel
void reduceStateResConLevel(const dictionary &maxResConLv4JacPCMat, HashTable< List< List< word >>> &stateResConInfo) const
reduce the connectivity level for Jacobian connectivity mat
Definition: DASolver.C:2428
URel
volVectorField & URel
Definition: createRefsTurbo.H:10
Foam::DASolver::setTimeInstanceVar
void setTimeInstanceVar(const word mode, Mat stateMat, Mat stateBCMat, Vec timeVec, Vec timeIdxVec)
assign the time instance mats to/from the lists in the OpenFOAM layer depending on the mode
Definition: DASolver.C:7864
Foam::DASolver::daResidualPtr_
autoPtr< DAResidual > daResidualPtr_
DAResidual pointer.
Definition: DASolver.H:97
Foam::DASolver::dXvdFFDMat_
Mat dXvdFFDMat_
the matrix that stores the partials dXv/dFFD computed from the idwarp and pygeo in the pyDAFoam....
Definition: DASolver.H:130
Foam::DASolver::calcdFdW
void calcdFdW(const Vec xvVec, const Vec wVec, const word objFuncName, Vec dFdW)
compute dFdW
Definition: DASolver.C:2886
Foam::DASolver::destroydRdWTMatrixFree
void destroydRdWTMatrixFree()
destroy the matrix free dRdWT
Definition: DASolver.C:4895
Foam::DASolver::registerAcousticOutput4AD
void registerAcousticOutput4AD(List< scalar > &a)
register acoustic as the ouptut for referse-mod AD
Definition: DASolver.C:6748
Foam::DASolver::primalMinResTol_
scalar primalMinResTol_
primal residual tolerance
Definition: DASolver.H:195
Foam::DASolver
Definition: DASolver.H:49
Foam::DASolver::resetOFSeeds
void resetOFSeeds()
reset the seeds (gradient to zeros) for all OpenFOAM variables
Definition: DASolver.C:4738
Foam::DAOption
Definition: DAOption.H:29
Foam::DASolver::normalizeGradientVec
void normalizeGradientVec(Vec vecY)
normalize the reverse-mode AD derivatives stored in vecY
Definition: DASolver.C:6767
Foam::DASolver::registerForceOutput4AD
void registerForceOutput4AD(List< scalar > &fX, List< scalar > &fY, List< scalar > &fZ)
register all force as the ouptut for referse-mod AD
Definition: DASolver.C:6721
setRootCasePython.H
Foam::DASolver::daFieldPtr_
autoPtr< DAField > daFieldPtr_
DAField pointer.
Definition: DASolver.H:85
Foam::DASolver::runTimeIndexAllInstances_
List< label > runTimeIndexAllInstances_
the runtime index for all instances (unsteady)
Definition: DASolver.H:186
Foam::DASolver::initializeGlobalADTape4dRdWT
void initializeGlobalADTape4dRdWT()
initialize the CoDiPack reverse-mode AD global tape for computing dRdWT*psi
Definition: DASolver.C:4947
Foam::DASolver::calcdFdACTAD
void calcdFdACTAD(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdACT)
compute dFdACT AD
Definition: DASolver.C:5739
Foam::DASolver::getNCouplingFaces
label getNCouplingFaces()
Get the number of faces for the MDO coupling patches.
Definition: DASolver.C:332
Foam::DAUtility::pyCalcBetaJacVecProd
static void * pyCalcBetaJacVecProd
Definition: DAUtility.H:132
Foam::DASolver::daOptionPtr_
autoPtr< DAOption > daOptionPtr_
DAOption pointer.
Definition: DASolver.H:76
Foam::DASolver::saveTimeInstanceFieldHybrid
void saveTimeInstanceFieldHybrid(label &timeInstanceI)
save primal variable to time instance list for hybrid adjoint (unsteady)
Definition: DASolver.C:7727
MRF
IOMRFZoneListDF & MRF
Definition: createRefsRhoSimple.H:18
Foam::DASolver::calcdRdActTPsiAD
void calcdRdActTPsiAD(const Vec xvVec, const Vec wVec, const Vec psi, const word designVarName, Vec dRdActTPsi)
Definition: DASolver.C:5910
Foam::DAUtility::angleOfAttackRadForwardAD
static scalar angleOfAttackRadForwardAD
angle of attack in radian used in forward mode AD
Definition: DAUtility.H:126
Foam::DASolver::calcdFvSourcedInputsTPsiAD
void calcdFvSourcedInputsTPsiAD(const word propName, const word mode, Vec aForce, Vec tForce, Vec rDist, Vec targetForce, Vec center, Vec psi, Vec dFvSource)
Definition: DASolver.C:2231
Foam::DASolver::calcdRdXvTPsiAD
void calcdRdXvTPsiAD(const Vec xvVec, const Vec wVec, const Vec psi, Vec dRdXvTPsi)
compute dRdXv^T*Psi
Definition: DASolver.C:5368
Foam::DASolver::pyOptions_
PyObject * pyOptions_
all options in DAFoam
Definition: DASolver.H:64
Foam::DASolver::registerResidualOutput4AD
void registerResidualOutput4AD()
register all residuals as the output for reverse-mode AD
Definition: DASolver.C:6649
Foam::DASolver::setdXvdFFDMat
void setdXvdFFDMat(const Mat dXvdFFDMat)
set the value for DASolver::dXvdFFDMat_
Definition: DASolver.C:7407
createMeshPython.H
fvSource
volScalarField & fvSource
Definition: createRefsHeatTransfer.H:7
p
volScalarField & p
Definition: createRefsPimple.H:6
Foam::DASolver::calcPrimalResidualStatistics
void calcPrimalResidualStatistics(const word mode, const label writeRes=0)
calculate the norms of all residuals and print to screen
Definition: DASolver.C:2559
Foam::DAObjFunc::getObjFuncPart
word getObjFuncPart()
return the part of objective function
Definition: DAObjFunc.H:193
Foam::DASolver::solveLinearEqn
label solveLinearEqn(const KSP ksp, const Vec rhsVec, Vec solVec)
solve the linear equation given a ksp and right-hand-side vector
Definition: DASolver.C:4709
Foam::DASolver::assignVec2ForceGradient
void assignVec2ForceGradient(Vec fBarVec, List< scalar > &fX, List< scalar > &fY, List< scalar > &fZ)
assign the reverse-mode AD input seeds from fBarVec to the force vectors in OpenFOAM
Definition: DASolver.C:7051
Foam::DASolver::calcdRdFFD
void calcdRdFFD(const Vec xvVec, const Vec wVec, const word designVarName, Mat dRdFFD)
compute dRdFFD
Definition: DASolver.C:4151
Foam::DAObjFunc::getObjFuncValue
scalar getObjFuncValue()
calculate the value of objective function
Definition: DAObjFunc.C:290
Foam::DASolver::setFFD2XvSeedVec
void setFFD2XvSeedVec(Vec vecIn)
set the value for DASolver::FFD2XvSeedVec_
Definition: DASolver.C:7419
Foam::DASolver::calcdRdACT
void calcdRdACT(const Vec xvVec, const Vec wVec, const word designVarName, const word designVarType, Mat dRdACT)
compute dRdACT
Definition: DASolver.C:4358
Foam::DASolver::globalADTape4dRdWTInitialized
label globalADTape4dRdWTInitialized
a flag in dRdWTMatVecMultFunction to determine if the global tap is initialized
Definition: DASolver.H:171
Foam::DASolver::getAcousticDataInternal
void getAcousticDataInternal(List< scalar > &x, List< scalar > &y, List< scalar > &z, List< scalar > &nX, List< scalar > &nY, List< scalar > &nZ, List< scalar > &a, List< scalar > &fX, List< scalar > &fY, List< scalar > &fZ, List< word > &patchList)
compute the positions, normals, areas, and forces of the desired acoustic patches
Definition: DASolver.C:1459
Foam::DAObjFunc::getObjFuncType
word getObjFuncType()
return the part of objective function
Definition: DAObjFunc.H:199
mesh
fvMesh & mesh
Definition: createRefsHeatTransfer.H:4
Foam::DASolver::isPrintTime
label isPrintTime(const Time &runTime, const label printInterval) const
Definition: DASolver.C:7460
Foam::DAUtility::pyDict2OFDict
static void pyDict2OFDict(PyObject *pyDict, dictionary &ofDict)
convert a python dictionary object to OpenFoam dictionary
Definition: DAUtility.C:24
Foam::defineRunTimeSelectionTable
defineRunTimeSelectionTable(DAFvSource, dictionary)
Foam::DASolver::getAcousticData
void getAcousticData(Vec x, Vec y, Vec z, Vec nX, Vec nY, Vec nZ, Vec a, Vec fX, Vec fY, Vec fZ, word groupName)
return the positions, normals, areas, and forces of the desired acoustic patches
Definition: DASolver.C:1114
Foam::DASolver::assignSeeds2ResidualGradient
void assignSeeds2ResidualGradient(const double *seeds)
assign the reverse-mode AD input seeds from vecX to the residuals in OpenFOAM
Definition: DASolver.C:6873
Foam::DASolver::daLinearEqnPtr_
autoPtr< DALinearEqn > daLinearEqnPtr_
DALinearEqn pointer.
Definition: DASolver.H:94
dafoam_plot3dtransform.axis
axis
Definition: dafoam_plot3dtransform.py:73
Foam::DAUtility::writeVectorBinary
static void writeVectorBinary(const Vec vecIn, const word prefix)
write petsc vector in binary format
Definition: DAUtility.C:259
Foam::DASolver::updateBoundaryConditions
void updateBoundaryConditions(const word fieldName, const word fieldType)
update the boundary condition for a field
Definition: DASolver.C:7694
Foam::DASolver::saveTimeInstanceFieldTimeAccurate
void saveTimeInstanceFieldTimeAccurate(label &timeInstanceI)
save primal variable to time instance list for time accurate adjoint (unsteady)
Definition: DASolver.C:7772
Foam::DASolver::calcFvSource
void calcFvSource(const word propName, Vec aForce, Vec tForce, Vec rDist, Vec targetForce, Vec center, Vec fvSource)
Definition: DASolver.C:2129
Foam::DASolver::calcdRdWTPsiAD
void calcdRdWTPsiAD(const Vec xvVec, const Vec wVec, const Vec psi, Vec dRdWTPsi)
compute [dRdW]^T*Psi
Definition: DASolver.C:6293
assignValueCheckAD
#define assignValueCheckAD(valA, valB)
Definition: DAMacroFunctions.H:103
Foam::DAPartDeriv::New
static autoPtr< DAPartDeriv > New(const word modelType, const fvMesh &mesh, const DAOption &daOption, const DAModel &daModel, const DAIndex &daIndex, const DAJacCon &daJacCon, const DAResidual &daResidual)
Definition: DAPartDeriv.C:47
Foam::DASolver::calcdRdBC
void calcdRdBC(const Vec xvVec, const Vec wVec, const word designVarName, Mat dRdBC)
compute dRdBC
Definition: DASolver.C:3029
Foam::DASolver::calcdFdBCAD
void calcdFdBCAD(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdBC)
compute dFdBCAD
Definition: DASolver.C:3562
Foam::DASolver::forwardADDerivVal_
HashTable< PetscScalar > forwardADDerivVal_
the derivative value computed by the forward mode primal solution.
Definition: DASolver.H:136
Foam::DASolver::updateOFMesh
void updateOFMesh(const Vec xvVec)
Update the OpenFoam mesh point coordinates based on the point vector xvVec.
Definition: DASolver.C:4823
Foam::DASolver::primalMinIters_
label primalMinIters_
primal min number of iterations
Definition: DASolver.H:198
Foam::MRFZoneListDF::reset
void reset(const dictionary &dict)
Definition: MRFZoneListDF.C:75
Foam::DASolver::registerFieldVariableInput4AD
void registerFieldVariableInput4AD(const word fieldName, const word fieldType)
register field variables as the input for reverse-mode AD
Definition: DASolver.C:6602
createTimePython.H
Foam::DASolver::getThermalAD
void getThermalAD(const word inputName, const double *volCoords, const double *states, const double *seeds, double *product)
compute the temperature on the conjugate heat transfer patches AD
Definition: DASolver.C:896
Foam::DASolver::prevPrimalSolTime_
scalar prevPrimalSolTime_
the solution time for the previous primal solution
Definition: DASolver.H:142
k
dimensionedScalar & k
Definition: createRefsHeatTransfer.H:6
Foam
Definition: multiFreqScalarFvPatchField.C:144
Foam::DASolver::runTimeAllInstances_
List< scalar > runTimeAllInstances_
the runtime value for all instances (unsteady)
Definition: DASolver.H:183
Foam::DASolver::FFD2XvSeedVec_
Vec FFD2XvSeedVec_
the vector that stores the AD seeds that propagate from FFD to Xv and will be used in forward mode AD
Definition: DASolver.H:133
Foam::DASolver::setDAObjFuncList
void setDAObjFuncList()
initialize DASolver::daObjFuncPtrList_ one needs to call this before calling printAllObjFuncs
Definition: DASolver.C:235
T
volScalarField & T
Definition: createRefsHeatTransfer.H:5
Foam::DASolver::writeAssociatedFields
void writeAssociatedFields()
write associated fields such as relative velocity
Definition: DASolver.C:7478
Foam::DASolver::assignVec2AcousticGradient
void assignVec2AcousticGradient(Vec fBarVec, List< scalar > &a, label offset, label step)
assign the reverse-mode AD input seeds from fBarVec to the acoustic vectors in OpenFOAM
Definition: DASolver.C:7093
Foam::DASolver::convertMPIVec2SeqVec
void convertMPIVec2SeqVec(const Vec mpiVec, Vec seqVec)
convert the mpi vec to a seq vec
Definition: DASolver.C:7367
Foam::DATurbulenceModel::devRhoReff
tmp< volSymmTensorField > devRhoReff() const
dev terms
Definition: DATurbulenceModel.C:306
Foam::DASolver::dRdWTMatVecMultFunction
static PetscErrorCode dRdWTMatVecMultFunction(Mat dRdWT, Vec vecX, Vec vecY)
matrix free matrix-vector product function to compute vecY=dRdWT*vecX
Definition: DASolver.C:4906
alphaEff
volScalarField alphaEff("alphaEff", turbulencePtr_->nu()/Pr+alphat)
Foam::DAObjFunc::New
static autoPtr< DAObjFunc > New(const fvMesh &mesh, const DAOption &daOption, const DAModel &daModel, const DAIndex &daIndex, const DAResidual &daResidual, const word objFuncName, const word objFuncPart, const dictionary &objFuncDict)
Definition: DAObjFunc.C:62
Foam::pyJacVecProdInterface
void(* pyJacVecProdInterface)(const double *, double *, int, const double *, const double *, int, void *)
Definition: DAUtility.H:28
Foam::DASolver::printAllObjFuncs
void printAllObjFuncs()
calculate the values of all objective functions and print them to screen
Definition: DASolver.C:160
Foam::IOMRFZoneListDF
Definition: IOMRFZoneListDF.H:49
Foam::DASolver::objFuncsAllInstances_
List< dictionary > objFuncsAllInstances_
objective function for all instances (unsteady)
Definition: DASolver.H:180
Foam::DASolver::calcdFdBC
void calcdFdBC(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdBC)
compute dFdBC
Definition: DASolver.C:3116
source
pseudoPEqn source()
Foam::DASolver::getObjFuncValue
scalar getObjFuncValue(const word objFuncName)
return the value of the objective function
Definition: DASolver.C:200
Foam::DASolver::primalMinRes_
scalar primalMinRes_
the maximal residual for primal solution
Definition: DASolver.H:139
Foam::DASolver::calcdRdAOATPsiAD
void calcdRdAOATPsiAD(const Vec xvVec, const Vec wVec, const Vec psi, const word designVarName, Vec dRdAOATPsi)
compute dRdAOAAD
Definition: DASolver.C:3977
Foam::DASolver::calcdFdWAD
void calcdFdWAD(const Vec xvVec, const Vec wVec, const word objFuncName, Vec dFdW)
compute dFdW using AD
Definition: DASolver.C:4986
Foam::DATurbulenceModel
Definition: DATurbulenceModel.H:61
Foam::DASolver::runTimePtr_
autoPtr< Time > runTimePtr_
runTime pointer
Definition: DASolver.H:70
Foam::DAUtility::writeMatrixBinary
static void writeMatrixBinary(const Mat matIn, const word prefix)
write petsc matrix in binary format
Definition: DAUtility.C:355
Foam::DASolver::getCouplingPatchList
void getCouplingPatchList(wordList &patchList, word groupName="NONE")
return the coupling patch list if any scenario is active on couplingInfo dict otherwise return design...
Definition: DASolver.C:488
Foam::DASolver::setThermal
void setThermal(scalar *thermal)
assign temperature values to the conjugate heat transfer patches
Definition: DASolver.C:550
thermo
fluidThermo & thermo
Definition: createRefsRhoSimpleC.H:6
Foam::DASolver::meshPtr_
autoPtr< fvMesh > meshPtr_
fvMesh pointer
Definition: DASolver.H:73
Foam::DASolver::calcCouplingFaceCoordsAD
void calcCouplingFaceCoordsAD(const double *volCoords, const double *seeds, double *product)
calc matrix-vector products for calcCouplingFaceCoords
Definition: DASolver.C:421
Foam::defineTypeNameAndDebug
defineTypeNameAndDebug(DAFvSource, 0)
Foam::DASolver::calcForceProfile
void calcForceProfile(Vec center, Vec aForceL, Vec tForceL, Vec rDistL)
calculate the radial profile of the force on the propeller surface
Definition: DASolver.C:1566
Foam::DAUtility::writeVectorASCII
static void writeVectorASCII(const Vec vecIn, const word prefix)
write petsc vector in ascii format
Definition: DAUtility.C:291
he
volScalarField & he
Definition: EEqnRhoSimple.H:5
Foam::DAUtility::writeMatrixASCII
static void writeMatrixASCII(const Mat matIn, const word prefix)
write petsc matrix in ascii format
Definition: DAUtility.C:387
Foam::DASolver::createMLRKSP
void createMLRKSP(const Mat jacMat, const Mat jacPCMat, KSP ksp)
create a multi-level, Richardson KSP object
Definition: DASolver.C:4675
Foam::DATurbulenceModel::alphaEff
tmp< volScalarField > alphaEff()
return effective thermal diffusivity
Definition: DATurbulenceModel.C:236
Foam::DASolver::calcdRdWT
void calcdRdWT(const Vec xvVec, const Vec wVec, const label isPC, Mat dRdWT)
compute dRdWT
Definition: DASolver.C:2752
Foam::DASolver::checkResidualTol
label checkResidualTol()
check whether the min residual in primal satisfy the prescribed tolerance
Definition: DASolver.C:7432
Foam::pyComputeInterface
void(* pyComputeInterface)(const double *, int, double *, int, void *)
Definition: DAUtility.H:27
Foam::DASolver::calcdFdAOA
void calcdFdAOA(const Vec xvVec, const Vec wVec, const word objFuncName, const word designVarName, Vec dFdAOA)
compute dFdAOA
Definition: DASolver.C:3853
Foam::DASolver::getForces
void getForces(Vec fX, Vec fY, Vec fZ)
return the forces of the desired fluid-structure-interaction patches
Definition: DASolver.C:1042
Foam::DASolver::nTimeInstances_
label nTimeInstances_
number of time instances for hybird adjoint (unsteady)
Definition: DASolver.H:189
Foam::DASolver::calcdForcedWAD
void calcdForcedWAD(const Vec xvVec, const Vec wVec, const Vec fBarVec, Vec dForcedW)
Definition: DASolver.C:6030
Foam::DASolver::calcResidualVec
void calcResidualVec(Vec resVec)
calculate the residual and assign it to the resVec vector
Definition: DASolver.C:7601
Foam::DASolver::runFPAdj
virtual label runFPAdj(const Vec xvVec, const Vec wVec, Vec dFdW, Vec psi)
solve the adjoint equation using the fixed-point iteration method
Definition: DASolver.C:8003
Foam::DASolver::calcdRdFieldTPsiAD
void calcdRdFieldTPsiAD(const Vec xvVec, const Vec wVec, const Vec psi, const word designVarName, Vec dRdFieldTPsi)
compute dRdField^T*Psi
Definition: DASolver.C:5661
Foam::DASolver::calcdRdThermalTPsiAD
void calcdRdThermalTPsiAD(const double *volCoords, const double *states, const double *thermal, const double *seeds, double *product)
Definition: DASolver.C:5245
Foam::DASolver::createMLRKSPMatrixFree
void createMLRKSPMatrixFree(const Mat jacPCMat, KSP ksp)
create a multi-level, Richardson KSP object with matrix-free Jacobians
Definition: DASolver.C:4690
Foam::DASolver::setPrimalBoundaryConditions
void setPrimalBoundaryConditions(const label printInfo=1)
update the primal state boundary condition based on the primalBC dict
Definition: DASolver.C:7982
setArgs.H
Foam::DASolver::daStateInfoPtr_
autoPtr< DAStateInfo > daStateInfoPtr_
DAStateInfo pointer.
Definition: DASolver.H:100
runTime
Time & runTime
Definition: createRefsHeatTransfer.H:1
DASolver.H
Foam::DASolver::stateAllInstances_
List< List< scalar > > stateAllInstances_
state variable list for all instances (unsteady)
Definition: DASolver.H:174
Foam::DASolver::getTimeInstanceObjFunc
scalar getTimeInstanceObjFunc(const label instanceI, const word objFuncName)
return the value of objective function at the given time instance and name
Definition: DASolver.C:7970
Foam::DASolver::calcdForcedXvAD
void calcdForcedXvAD(const Vec xvVec, const Vec wVec, const Vec fBarVec, Vec dForcedXv)
compute dForcedXv
Definition: DASolver.C:5445
Foam::DASolver::periodicity_
scalar periodicity_
periodicity of oscillating flow variables (unsteady)
Definition: DASolver.H:192
Foam::DASolver::calcdForcedStateTPsiAD
void calcdForcedStateTPsiAD(const word mode, Vec xvVec, Vec stateVec, Vec psiVec, Vec prodVec)
Definition: DASolver.C:1760
Foam::DASolver::calcdRdWOldTPsiAD
void calcdRdWOldTPsiAD(const label oldTimeLevel, const Vec psi, Vec dRdWOldTPsi)
compute dRdWOld^T*Psi
Definition: DASolver.C:6370
Foam::DAObjFunc::getObjFuncName
word getObjFuncName()
return the name of objective function
Definition: DAObjFunc.H:187
Foam::DASolver::getNCouplingPoints
label getNCouplingPoints()
Get the number of points for the MDO coupling patches.
Definition: DASolver.C:347
Foam::DASolver::getThermal
void getThermal(const scalar *volCoords, const scalar *states, scalar *thermal)
compute the temperature on the conjugate heat transfer patches
Definition: DASolver.C:731
Foam::DASolver::assignVec2ResidualGradient
void assignVec2ResidualGradient(Vec vecX)
assign the reverse-mode AD input seeds from vecX to the residuals in OpenFOAM
Definition: DASolver.C:6960
Foam::DASolver::daObjFuncPtrList_
UPtrList< DAObjFunc > daObjFuncPtrList_
a list of DAObjFunc pointers
Definition: DASolver.H:88
Foam::DASolver::stateBoundaryAllInstances_
List< List< scalar > > stateBoundaryAllInstances_
state boundary variable list for all instances (unsteady)
Definition: DASolver.H:177
psi
const volScalarField & psi
Definition: createRefsRhoSimpleC.H:14
Foam::DASolver::dRdWTMF_
Mat dRdWTMF_
matrix-free dRdWT matrix used in GMRES solution
Definition: DASolver.H:168
dafoam_plot3dtransform.mode
mode
Definition: dafoam_plot3dtransform.py:21
Foam::DAJacCon::New
static autoPtr< DAJacCon > New(const word modelType, const fvMesh &mesh, const DAOption &daOption, const DAModel &daModel, const DAIndex &daIndex)
Definition: DAJacCon.C:47
Foam::DAObjFunc
Definition: DAObjFunc.H:33
Foam::DASolver::calcdAcousticsdXvAD
void calcdAcousticsdXvAD(const Vec xvVec, const Vec wVec, const Vec fBarVec, Vec dForcedXv, word varName, word groupName)
compute dAcoudXv
Definition: DASolver.C:5527
Foam::DASolver::New
static autoPtr< DASolver > New(char *argsAll, PyObject *pyOptions)
Definition: DASolver.C:69