DASolver.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2 
3  DAFoam : Discrete Adjoint with OpenFOAM
4  Version : v4
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 
24 
25 namespace Foam
26 {
27 
28 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
29 
30 defineTypeNameAndDebug(DASolver, 0);
31 defineRunTimeSelectionTable(DASolver, dictionary);
32 
33 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
34 
35 DASolver::DASolver(
36  char* argsAll,
37  PyObject* pyOptions)
38  : argsAll_(argsAll),
39  pyOptions_(pyOptions),
40  argsPtr_(nullptr),
41  runTimePtr_(nullptr),
42  meshPtr_(nullptr),
43  daOptionPtr_(nullptr),
44  daModelPtr_(nullptr),
45  daIndexPtr_(nullptr),
46  daFieldPtr_(nullptr),
47  daCheckMeshPtr_(nullptr),
48  daLinearEqnPtr_(nullptr),
49  daResidualPtr_(nullptr),
50  daRegressionPtr_(nullptr),
51  daGlobalVarPtr_(nullptr),
52  points0Ptr_(nullptr)
53 #ifdef CODI_ADR
54  ,
55  globalADTape_(codi::RealReverse::getTape())
56 #endif
57 {
58  // initialize fvMesh and Time object pointer
59 #include "setArgs.H"
60 #include "setRootCasePython.H"
61 #include "createTimePython.H"
62 #include "createMeshPython.H"
63  Info << "Initializing mesh and runtime for DASolver" << endl;
64 
66 
67  // if the dynamic mesh is used, set moving to true here
68  dictionary allOptions = daOptionPtr_->getAllOptions();
69  if (allOptions.subDict("dynamicMesh").getLabel("active"))
70  {
71  meshPtr_->moving(true);
72  // if we have volCoord as the input, there is no need to save the initial
73  // mesh because OpenMDAO will assign a new coordinates (set_solver_input) to OF
74  // before each primal run. however, if we do not have the volCoord as the input,
75  // we need to save the initial mesh, such that we can reset the OF's fvMesh
76  // info for each dynamicMesh primal run.
77  if (!this->hasVolCoordInput())
78  {
79  points0Ptr_.reset(new pointField(meshPtr_->points()));
80  }
81  // we need to initialize the dynamic mesh by calling move points to create V0, V00 etc
82  // this is usually done automatically in primal solution, but for the AD version,
83  // we never cal primal so we need to manually initialize V0 etc here
84  this->initDynamicMesh();
85  }
86  else
87  {
88  meshPtr_->moving(false);
89  }
90 
91  primalMinResTol_ = daOptionPtr_->getOption<scalar>("primalMinResTol");
92  primalMinIters_ = daOptionPtr_->getOption<label>("primalMinIters");
93  printInterval_ = daOptionPtr_->getOption<label>("printInterval");
94  printIntervalUnsteady_ = daOptionPtr_->getOption<label>("printIntervalUnsteady");
95 
96  // if inputInto has unsteadyField, we need to initial GlobalVar::inputFieldUnsteady here
97  this->initInputFieldUnsteady();
98 
99  Info << "DAOpton initialized " << endl;
100 }
101 
102 // * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * d* * * * //
103 
104 autoPtr<DASolver> DASolver::New(
105  char* argsAll,
106  PyObject* pyOptions)
107 {
108  // standard setup for runtime selectable classes
109 
110  // look up the solver name defined in pyOptions
111  dictionary allOptions;
113  word modelType;
114  allOptions.readEntry<word>("solverName", modelType);
115 
116  if (allOptions.lookupOrDefault<label>("debug", 0))
117  {
118  Info << "Selecting " << modelType << " for DASolver" << endl;
119  }
120 
121  dictionaryConstructorTable::iterator cstrIter =
122  dictionaryConstructorTablePtr_->find(modelType);
123 
124  // if the solver name is not found in any child class, print an error
125  if (cstrIter == dictionaryConstructorTablePtr_->end())
126  {
127  FatalErrorIn(
128  "DASolver::New"
129  "("
130  " char*,"
131  " PyObject*"
132  ")")
133  << "Unknown DASolver type "
134  << modelType << nl << nl
135  << "Valid DASolver types:" << endl
136  << dictionaryConstructorTablePtr_->sortedToc()
137  << exit(FatalError);
138  }
139 
140  // child class found
141  return autoPtr<DASolver>(
142  cstrIter()(argsAll, pyOptions));
143 }
144 
145 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
146 
148 {
149  /*
150  Description:
151  The loop method to increment the runtime. The reason we implement this is
152  because the runTime.loop() and simple.loop() give us seg fault...
153  */
154 
155  scalar endTime = runTime.endTime().value();
156  scalar deltaT = runTime.deltaT().value();
157  scalar t = runTime.timeOutputValue();
158 
159  // execute functionObjectList, e.g., field averaging, sampling
160  functionObjectList& funcObj = const_cast<functionObjectList&>(runTime.functionObjects());
161  if (runTime.timeIndex() == runTime.startTimeIndex())
162  {
163  funcObj.start();
164  }
165  else
166  {
167  funcObj.execute();
168  }
169 
170  // check exit condition, we need to satisfy both the residual and function std condition
171  if (daGlobalVarPtr_->primalMaxRes < primalMinResTol_ && runTime.timeIndex() > primalMinIters_)
172  {
173  Info << "Time = " << t << endl;
174 
175  Info << "Minimal residual " << daGlobalVarPtr_->primalMaxRes << " satisfied the prescribed tolerance " << primalMinResTol_ << endl
176  << endl;
177 
178  this->calcAllFunctions(1);
179  runTime.writeNow();
180  prevPrimalSolTime_ = t;
181  funcObj.end();
182  daRegressionPtr_->writeFeatures();
183  primalFinalTimeIndex_ = runTime.timeIndex();
184  return 0;
185  }
186  else if (t > endTime - 0.5 * deltaT)
187  {
188  prevPrimalSolTime_ = t;
189  funcObj.end();
190  daRegressionPtr_->writeFeatures();
191  primalFinalTimeIndex_ = runTime.timeIndex();
192  return 0;
193  }
194  else
195  {
196  ++runTime;
197  // initialize primalMaxRes with a small value for this iteration
198  daGlobalVarPtr_->primalMaxRes = -1e10;
199  printToScreen_ = this->isPrintTime(runTime, printInterval_);
200  return 1;
201  }
202 }
203 
204 void DASolver::calcAllFunctions(label print)
205 {
206  /*
207  Description:
208  Calculate the values of all objective functions and print them to screen
209  NOTE: we need to call DASolver::setDAFunctionList before calling this function!
210  */
211 
212  if (daFunctionPtrList_.size() == 0)
213  {
214  // if users do not set function, we can just skip this call
215  if (daOptionPtr_->getAllOptions().subDict("function").toc().size() != 0)
216  {
217  FatalErrorIn("printAllFunctions") << "daFunctionPtrList_.size() ==0... "
218  << "Forgot to call setDAFunctionList?"
219  << abort(FatalError);
220  }
221  else
222  {
223  return;
224  }
225  }
226 
227  label timeIndex = runTimePtr_->timeIndex();
228  label listIndex = timeIndex - 1;
229 
231  {
232  DAFunction& daFunction = daFunctionPtrList_[idxI];
233  word functionName = daFunction.getFunctionName();
234  word timeOpType = daFunction.getFunctionTimeOp();
235  scalar functionVal = daFunction.calcFunction();
236  functionTimeSteps_[idxI][listIndex] = functionVal;
237 
238  if (print)
239  {
240  const dictionary& functionDict = daOptionPtr_->getAllOptions().subDict("function").subDict(functionName);
241  label timeOpStartIndex = functionDict.lookupOrDefault<label>("timeOpStartIndex", 0);
242  scalar timeOpVal = 0.0;
243  // if timeOpStartIndex > listIndex, timeOpVal is zero
244  if (timeOpStartIndex <= listIndex)
245  {
246  timeOpVal = daTimeOpPtrList_[idxI].compute(
247  functionTimeSteps_[idxI], timeOpStartIndex, listIndex);
248  }
249 
250  Info << functionName
251  << ": " << functionVal
252  << " " << timeOpType << ": " << timeOpVal;
253 #ifdef CODI_ADF
254  Info << " ADF-Deriv: " << timeOpVal.getGradient();
255 #endif
256  Info << endl;
257  }
258  }
259 }
260 
261 double DASolver::getTimeOpFuncVal(const word functionName)
262 {
263  // return the function value based on timeOp
264  label listFinalIndex = primalFinalTimeIndex_ - 1;
265  scalar funcVal = 0.0;
267  {
268  DAFunction& daFunction = daFunctionPtrList_[idxI];
269  word functionName1 = daFunction.getFunctionName();
270 
271  if (functionName1 == functionName)
272  {
273  const dictionary& functionDict = daOptionPtr_->getAllOptions().subDict("function").subDict(functionName);
274  label timeOpStartIndex = functionDict.lookupOrDefault<label>("timeOpStartIndex", 0);
275  // if timeOpStartIndex should not be larger than listFinalIndex
276  if (timeOpStartIndex <= listFinalIndex)
277  {
278  funcVal = daTimeOpPtrList_[idxI].compute(
279  functionTimeSteps_[idxI], timeOpStartIndex, listFinalIndex);
280  }
281  else
282  {
283  FatalErrorIn("") << "timeOpStartIndex can not be larger than listFinalIndex!"
284  << abort(FatalError);
285  }
286  }
287  }
288 #ifdef CODI_ADF
289  return funcVal.getGradient();
290 #endif
291 
292 #ifdef CODI_ADR
293  return funcVal.getValue();
294 #endif
295 
296 #ifdef CODI_NO_AD
297  return funcVal;
298 #endif
299 }
300 
303  const word functionName,
304  const label timeIdx)
305 {
306  scalar scaling = 0.0;
307  label listFinalIndex = primalFinalTimeIndex_ - 1;
309  {
310  DAFunction& daFunction = daFunctionPtrList_[idxI];
311  word functionName1 = daFunction.getFunctionName();
312  if (functionName1 == functionName)
313  {
314  const dictionary& functionDict = daOptionPtr_->getAllOptions().subDict("function").subDict(functionName);
315  label timeOpStartIndex = functionDict.lookupOrDefault<label>("timeOpStartIndex", 0);
316  // if timeIdx is outside of [timeOpStartIndex, listFinalIndex], dFScaling is zero
317  if (timeIdx >= timeOpStartIndex && timeIdx <= listFinalIndex)
318  {
319  scaling = daTimeOpPtrList_[idxI].dFScaling(
320  functionTimeSteps_[idxI], timeOpStartIndex, listFinalIndex, timeIdx);
321  }
322  return scaling;
323  }
324  }
325  FatalErrorIn("getdFScaling") << "functionName not found! "
326  << abort(FatalError);
327  return scaling;
328 }
329 
331 {
332  /*
333  Description:
334  Set up the objective function list such that we can call calcAllFunctions and calcFunction
335  NOTE: this function needs to be called before calculating any objective functions
336 
337  Example:
338  A typical function dictionary looks like this:
339 
340  "function":
341  {
342  "func0":
343  {
344  "functionName": "force",
345  "source": "patchToFace",
346  "patches": ["walls", "wallsbump"],
347  "scale": 0.5,
348  "timeOp": "final"
349  },
350  "func1":
351  {
352  "functionName": "force",
353  "source": "patchToFace",
354  "patches": ["wallsbump", "frontandback"],
355  "scale": 0.5,
356  "timeOp": "average"
357  },
358  "func2":
359  {
360  "functionName": "force",
361  "source": "patchToFace",
362  "patches": ["walls", "wallsbump", "frontandback"],
363  "scale": 1.0,
364  "timeOp": "variance"
365  },
366  }
367  */
368 
369  const dictionary& allOptions = daOptionPtr_->getAllOptions();
370 
371  const dictionary& functionDict = allOptions.subDict("function");
372 
373  // loop over all functions and calc the number of
374  // DAFunction instances we need
375  label nFunctions = 0;
376  forAll(functionDict.toc(), idxI)
377  {
378  nFunctions++;
379  }
380 
381  daFunctionPtrList_.setSize(nFunctions);
382  daTimeOpPtrList_.setSize(nFunctions);
383 
384  // we need to repeat the loop to initialize the
385  // DAFunction instances
386  forAll(functionDict.toc(), idxI)
387  {
388  word functionName = functionDict.toc()[idxI];
389  dictionary funcSubDict = functionDict.subDict(functionName);
390  daFunctionPtrList_.set(
391  idxI,
393  meshPtr_(),
394  daOptionPtr_(),
395  daModelPtr_(),
396  daIndexPtr_(),
397  functionName)
398  .ptr());
399 
400  // initialize DATimeOp pointer list
401  word timeOp = daFunctionPtrList_[idxI].getFunctionTimeOp();
402  daTimeOpPtrList_.set(
403  idxI,
404  DATimeOp::New(timeOp, funcSubDict).ptr());
405  }
406 
407  // here we also initialize the functionTimeSteps lists
408  scalar endTime = runTimePtr_->endTime().value();
409  scalar deltaT = runTimePtr_->deltaT().value();
410  label nSteps = round(endTime / deltaT);
411  functionTimeSteps_.setSize(nFunctions);
413  {
414  functionTimeSteps_[idxI].setSize(nSteps);
415  forAll(functionTimeSteps_[idxI], idxJ)
416  {
417  functionTimeSteps_[idxI][idxJ] = 0.0;
418  }
419  }
420 }
421 
423  const dictionary& maxResConLv4JacPCMat,
424  HashTable<List<List<word>>>& stateResConInfo) const
425 {
426  /*
427  Description:
428  Reduce the connectivity levels for stateResConInfo
429  based on maxResConLv4JacPCMat specified in DAOption
430 
431  Input:
432  maxResConLv4JacPCMat: the maximal levels of connectivity for each
433  state variable residual
434 
435  Output:
436  stateResConInfo: reduced connectivity level.
437 
438  Example:
439 
440  If the original stateResConInfo reads:
441 
442  stateResConInfo
443  {
444  "URes":
445  {
446  {"U", "p", "phi"}, // level 0
447  {"U", "p"}, // level 1
448  {"U"} // level 2
449  }
450  }
451  And maxResConLv4JacPCMat in DAOption reads:
452 
453  maxResConLv4JacPCMat
454  {
455  "URes": 1
456  }
457 
458  Then, calling reduceStateResConLevel will give:
459 
460  stateResConInfo
461  {
462  "URes":
463  {
464  {"U", "p", "phi"}, // level 0
465  {"U", "p"}, // level 1
466  }
467  }
468 
469  Note that the level 2 of the connectivity in URes is removed becasue
470  "URes"=1 in maxResConLv4JacPCMat
471 
472  */
473 
474  // if no maxResConLv4JacPCMat is specified, just return;
475  if (maxResConLv4JacPCMat.toc().size() == 0)
476  {
477  Info << "maxResConLv4JacPCMat is empty, just return" << endl;
478  return;
479  }
480 
481  // now check if maxResConLv4JacPCMat has all the maxRes level defined
482  // and these max levels are <= stateResConInfo.size()
483  forAll(stateResConInfo.toc(), idxJ)
484  {
485  word key1 = stateResConInfo.toc()[idxJ];
486  bool keyFound = false;
487  forAll(maxResConLv4JacPCMat.toc(), idxI)
488  {
489  word key = maxResConLv4JacPCMat.toc()[idxI];
490  if (key == key1)
491  {
492  keyFound = true;
493  label maxLv = maxResConLv4JacPCMat.getLabel(key);
494  label maxLv1 = stateResConInfo[key1].size() - 1;
495  if (maxLv > maxLv1)
496  {
497  FatalErrorIn("") << "maxResConLv4JacPCMat maxLevel"
498  << maxLv << " for " << key
499  << " larger than stateResConInfo maxLevel "
500  << maxLv1 << " for " << key1
501  << abort(FatalError);
502  }
503  }
504  }
505  if (!keyFound)
506  {
507  FatalErrorIn("") << key1 << " not found in maxResConLv4JacPCMat"
508  << abort(FatalError);
509  }
510  }
511 
512  if (daOptionPtr_->getOption<label>("debug"))
513  {
514  Info << "Reducing max connectivity level of Jacobian PC Mat to : ";
515  Info << maxResConLv4JacPCMat << endl;
516  }
517 
518  // assign stateResConInfo to stateResConInfoBK
519  HashTable<List<List<word>>> stateResConInfoBK;
520  forAll(stateResConInfo.toc(), idxI)
521  {
522  word key = stateResConInfo.toc()[idxI];
523  stateResConInfoBK.set(key, stateResConInfo[key]);
524  }
525 
526  // now we can erase stateResConInfo
527  stateResConInfo.clearStorage();
528 
529  // get the reduced stateResConInfo
530  forAll(stateResConInfoBK.toc(), idxI)
531  {
532  word key = stateResConInfoBK.toc()[idxI];
533  label maxConLevel = maxResConLv4JacPCMat.getLabel(key);
534  label conSize = stateResConInfoBK[key].size();
535  if (conSize > maxConLevel + 1)
536  {
537  List<List<word>> conList;
538  conList.setSize(maxConLevel + 1);
539  for (label i = 0; i <= maxConLevel; i++) // NOTE: it is <=
540  {
541  conList[i] = stateResConInfoBK[key][i];
542  }
543  stateResConInfo.set(key, conList);
544  }
545  else
546  {
547  stateResConInfo.set(key, stateResConInfoBK[key]);
548  }
549  }
550  //Info<<stateResConInfo<<endl;
551 }
552 
555 {
556  /*
557  Description:
558  Run the coloring for dRdW and save them as dRdWColoring_n.bin where n is the number
559  of processors
560  */
561 
562  DAJacCon daJacCon("dRdW", meshPtr_(), daOptionPtr_(), daModelPtr_(), daIndexPtr_());
563 
564  if (!daJacCon.coloringExists())
565  {
566  dictionary options;
567  const HashTable<List<List<word>>>& stateResConInfo = daStateInfoPtr_->getStateResConInfo();
568  options.set("stateResConInfo", stateResConInfo);
569 
570  // need to first setup preallocation vectors for the dRdWCon matrix
571  // because directly initializing the dRdWCon matrix will use too much memory
572  daJacCon.setupJacConPreallocation(options);
573 
574  // now we can initilaize dRdWCon
575  daJacCon.initializeJacCon(options);
576 
577  // setup dRdWCon
578  daJacCon.setupJacCon(options);
579  Info << "dRdWCon Created. " << meshPtr_->time().elapsedCpuTime() << " s" << endl;
580 
581  // compute the coloring
582  Info << "Calculating dRdW Coloring... " << meshPtr_->time().elapsedCpuTime() << " s" << endl;
583  daJacCon.calcJacConColoring();
584  Info << "Calculating dRdW Coloring... Completed! " << meshPtr_->time().elapsedCpuTime() << " s" << endl;
585 
586  // clean up
587  daJacCon.clear();
588  }
589 }
590 
592  const word mode,
593  const label writeRes)
594 {
595  /*
596  Description:
597  Calculate the mean, max, and norm2 for all residuals and print it to screen
598  */
599 
600  if (mode == "print")
601  {
602  // print the primal residuals to screen
603  Info << "Printing Primal Residual Statistics." << endl;
604  }
605  else if (mode == "calc")
606  {
607  // we will just calculate but not printting anything
608  }
609  else
610  {
611  FatalErrorIn("") << "mode not valid" << abort(FatalError);
612  }
613 
614  this->calcResiduals();
615 
616  forAll(stateInfo_["volVectorStates"], idxI)
617  {
618  const word stateName = stateInfo_["volVectorStates"][idxI];
619  const word resName = stateName + "Res";
620  const volVectorField& stateRes = meshPtr_->thisDb().lookupObject<volVectorField>(resName);
621 
622  vector vecResMax(0, 0, 0);
623  vector vecResNorm2(0, 0, 0);
624  vector vecResMean(0, 0, 0);
625  forAll(stateRes, cellI)
626  {
627  vecResNorm2.x() += pow(stateRes[cellI].x(), 2.0);
628  vecResNorm2.y() += pow(stateRes[cellI].y(), 2.0);
629  vecResNorm2.z() += pow(stateRes[cellI].z(), 2.0);
630  vecResMean.x() += fabs(stateRes[cellI].x());
631  vecResMean.y() += fabs(stateRes[cellI].y());
632  vecResMean.z() += fabs(stateRes[cellI].z());
633  if (fabs(stateRes[cellI].x()) > vecResMax.x())
634  {
635  vecResMax.x() = fabs(stateRes[cellI].x());
636  }
637  if (fabs(stateRes[cellI].y()) > vecResMax.y())
638  {
639  vecResMax.y() = fabs(stateRes[cellI].y());
640  }
641  if (fabs(stateRes[cellI].z()) > vecResMax.z())
642  {
643  vecResMax.z() = fabs(stateRes[cellI].z());
644  }
645  }
646  vecResMean = vecResMean / stateRes.size();
647  reduce(vecResMean, sumOp<vector>());
648  vecResMean = vecResMean / Pstream::nProcs();
649  reduce(vecResNorm2, sumOp<vector>());
650  reduce(vecResMax, maxOp<vector>());
651  vecResNorm2.x() = pow(vecResNorm2.x(), 0.5);
652  vecResNorm2.y() = pow(vecResNorm2.y(), 0.5);
653  vecResNorm2.z() = pow(vecResNorm2.z(), 0.5);
654  if (mode == "print")
655  {
656  Info << stateName << " Residual Norm2: " << vecResNorm2 << endl;
657  Info << stateName << " Residual Mean: " << vecResMean << endl;
658  Info << stateName << " Residual Max: " << vecResMax << endl;
659  }
660 
661  if (writeRes)
662  {
663  stateRes.write();
664  }
665  }
666 
667  forAll(stateInfo_["volScalarStates"], idxI)
668  {
669  const word stateName = stateInfo_["volScalarStates"][idxI];
670  const word resName = stateName + "Res";
671  const volScalarField& stateRes = meshPtr_->thisDb().lookupObject<volScalarField>(resName);
672 
673  scalar scalarResMax = 0, scalarResNorm2 = 0, scalarResMean = 0;
674  forAll(stateRes, cellI)
675  {
676  scalarResNorm2 += pow(stateRes[cellI], 2.0);
677  scalarResMean += fabs(stateRes[cellI]);
678  if (fabs(stateRes[cellI]) > scalarResMax)
679  scalarResMax = fabs(stateRes[cellI]);
680  }
681  scalarResMean = scalarResMean / stateRes.size();
682  reduce(scalarResMean, sumOp<scalar>());
683  scalarResMean = scalarResMean / Pstream::nProcs();
684  reduce(scalarResNorm2, sumOp<scalar>());
685  reduce(scalarResMax, maxOp<scalar>());
686  scalarResNorm2 = pow(scalarResNorm2, 0.5);
687  if (mode == "print")
688  {
689  Info << stateName << " Residual Norm2: " << scalarResNorm2 << endl;
690  Info << stateName << " Residual Mean: " << scalarResMean << endl;
691  Info << stateName << " Residual Max: " << scalarResMax << endl;
692  }
693 
694  if (writeRes)
695  {
696  stateRes.write();
697  }
698  }
699 
700  forAll(stateInfo_["modelStates"], idxI)
701  {
702  const word stateName = stateInfo_["modelStates"][idxI];
703  const word resName = stateName + "Res";
704  const volScalarField& stateRes = meshPtr_->thisDb().lookupObject<volScalarField>(resName);
705 
706  scalar scalarResMax = 0, scalarResNorm2 = 0, scalarResMean = 0;
707  forAll(stateRes, cellI)
708  {
709  scalarResNorm2 += pow(stateRes[cellI], 2.0);
710  scalarResMean += fabs(stateRes[cellI]);
711  if (fabs(stateRes[cellI]) > scalarResMax)
712  scalarResMax = fabs(stateRes[cellI]);
713  }
714  scalarResMean = scalarResMean / stateRes.size();
715  reduce(scalarResMean, sumOp<scalar>());
716  scalarResMean = scalarResMean / Pstream::nProcs();
717  reduce(scalarResNorm2, sumOp<scalar>());
718  reduce(scalarResMax, maxOp<scalar>());
719  scalarResNorm2 = pow(scalarResNorm2, 0.5);
720  if (mode == "print")
721  {
722  Info << stateName << " Residual Norm2: " << scalarResNorm2 << endl;
723  Info << stateName << " Residual Mean: " << scalarResMean << endl;
724  Info << stateName << " Residual Max: " << scalarResMax << endl;
725  }
726 
727  if (writeRes)
728  {
729  stateRes.write();
730  }
731  }
732 
733  forAll(stateInfo_["surfaceScalarStates"], idxI)
734  {
735  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
736  const word resName = stateName + "Res";
737  const surfaceScalarField& stateRes = meshPtr_->thisDb().lookupObject<surfaceScalarField>(resName);
738 
739  scalar phiResMax = 0, phiResNorm2 = 0, phiResMean = 0;
740  forAll(stateRes, faceI)
741  {
742  phiResNorm2 += pow(stateRes[faceI], 2.0);
743  phiResMean += fabs(stateRes[faceI]);
744  if (fabs(stateRes[faceI]) > phiResMax)
745  phiResMax = fabs(stateRes[faceI]);
746  }
747  forAll(stateRes.boundaryField(), patchI)
748  {
749  forAll(stateRes.boundaryField()[patchI], faceI)
750  {
751  scalar bPhiRes = stateRes.boundaryField()[patchI][faceI];
752  phiResNorm2 += pow(bPhiRes, 2.0);
753  phiResMean += fabs(bPhiRes);
754  if (fabs(bPhiRes) > phiResMax)
755  phiResMax = fabs(bPhiRes);
756  }
757  }
758  phiResMean = phiResMean / meshPtr_->nFaces();
759  reduce(phiResMean, sumOp<scalar>());
760  phiResMean = phiResMean / Pstream::nProcs();
761  reduce(phiResNorm2, sumOp<scalar>());
762  reduce(phiResMax, maxOp<scalar>());
763  phiResNorm2 = pow(phiResNorm2, 0.5);
764  if (mode == "print")
765  {
766  Info << stateName << " Residual Norm2: " << phiResNorm2 << endl;
767  Info << stateName << " Residual Mean: " << phiResMean << endl;
768  Info << stateName << " Residual Max: " << phiResMax << endl;
769  }
770 
771  if (writeRes)
772  {
773  stateRes.write();
774  }
775  }
776 
777  Info << " " << endl;
778 
779  return;
780 }
781 
783  const label isPC,
784  Mat dRdWT)
785 {
786  /*
787  Description:
788  This function computes partials derivatives dRdWT or dRdWTPC.
789  PC means preconditioner matrix
790 
791  Input:
792 
793  isPC: isPC=1 computes dRdWTPC, isPC=0 computes dRdWT
794 
795  Output:
796  dRdWT: the partial derivative matrix [dR/dW]^T
797  NOTE: You need to call MatCreate for the dRdWT matrix before calling this function.
798  No need to call MatSetSize etc because they will be done in this function
799  */
800 
801  // create the state and volCoord vecs from the OF fields
802  Vec wVec, xvVec;
803  VecCreate(PETSC_COMM_WORLD, &wVec);
804  VecSetSizes(wVec, daIndexPtr_->nLocalAdjointStates, PETSC_DECIDE);
805  VecSetFromOptions(wVec);
806 
807  label nXvs = daIndexPtr_->nLocalPoints * 3;
808  VecCreate(PETSC_COMM_WORLD, &xvVec);
809  VecSetSizes(xvVec, nXvs, PETSC_DECIDE);
810  VecSetFromOptions(xvVec);
811 
812  daFieldPtr_->ofField2StateVec(wVec);
813  daFieldPtr_->ofMesh2PointVec(xvVec);
814 
815  word matName;
816  if (isPC == 0)
817  {
818  matName = "dRdWT";
819  }
820  else if (isPC == 1)
821  {
822  matName = "dRdWTPC";
823  }
824  else
825  {
826  FatalErrorIn("") << "isPC " << isPC << " not supported! "
827  << "Options are: 0 (for dRdWT) and 1 (for dRdWTPC)." << abort(FatalError);
828  }
829 
830  if (daOptionPtr_->getOption<label>("debug"))
831  {
832  this->calcPrimalResidualStatistics("print");
833  }
834 
835  Info << "Computing " << matName << " " << runTimePtr_->elapsedCpuTime() << " s" << endl;
836  Info << "Initializing dRdWCon. " << runTimePtr_->elapsedCpuTime() << " s" << endl;
837 
838  // initialize DAJacCon object
839  word modelType = "dRdW";
840  DAJacCon daJacCon(
841  modelType,
842  meshPtr_(),
843  daOptionPtr_(),
844  daModelPtr_(),
845  daIndexPtr_());
846 
847  dictionary options;
848  const HashTable<List<List<word>>>& stateResConInfo = daStateInfoPtr_->getStateResConInfo();
849 
850  if (isPC == 1)
851  {
852  // need to reduce the JacCon for PC to reduce memory usage
853  HashTable<List<List<word>>> stateResConInfoReduced = stateResConInfo;
854 
855  dictionary maxResConLv4JacPCMat = daOptionPtr_->getAllOptions().subDict("maxResConLv4JacPCMat");
856 
857  this->reduceStateResConLevel(maxResConLv4JacPCMat, stateResConInfoReduced);
858  options.set("stateResConInfo", stateResConInfoReduced);
859  }
860  else
861  {
862  options.set("stateResConInfo", stateResConInfo);
863  }
864 
865  // need to first setup preallocation vectors for the dRdWCon matrix
866  // because directly initializing the dRdWCon matrix will use too much memory
867  daJacCon.setupJacConPreallocation(options);
868 
869  // now we can initialize dRdWCon
870  daJacCon.initializeJacCon(options);
871 
872  // setup dRdWCon
873  daJacCon.setupJacCon(options);
874  Info << "dRdWCon Created. " << runTimePtr_->elapsedCpuTime() << " s" << endl;
875 
876  // read the coloring
877  daJacCon.readJacConColoring();
878 
879  // initialize partDeriv object
880  DAPartDeriv daPartDeriv(
881  modelType,
882  meshPtr_(),
883  daOptionPtr_(),
884  daModelPtr_(),
885  daIndexPtr_(),
886  daJacCon,
887  daResidualPtr_());
888 
889  // we want transposed dRdW
890  dictionary options1;
891  options1.set("transposed", 1);
892  options1.set("isPC", isPC);
893  // we can set lower bounds for the Jacobians to save memory
894  if (isPC == 1)
895  {
896  options1.set("lowerBound", daOptionPtr_->getSubDictOption<scalar>("jacLowerBounds", "dRdWPC"));
897  }
898  else
899  {
900  options1.set("lowerBound", daOptionPtr_->getSubDictOption<scalar>("jacLowerBounds", "dRdW"));
901  }
902 
903  // initialize dRdWT matrix
904  daPartDeriv.initializePartDerivMat(options1, dRdWT);
905 
906  // calculate dRdWT
907  daPartDeriv.calcPartDerivMat(options1, xvVec, wVec, dRdWT);
908 
909  if (daOptionPtr_->getOption<label>("debug"))
910  {
911  this->calcPrimalResidualStatistics("print");
912  }
913 
914  wordList writeJacobians;
915  daOptionPtr_->getAllOptions().readEntry<wordList>("writeJacobians", writeJacobians);
916  if (writeJacobians.found("dRdWT") || writeJacobians.found("all"))
917  {
918  DAUtility::writeMatrixBinary(dRdWT, matName);
919  }
920 
921  // clear up
922  daJacCon.clear();
923 }
924 
926  Mat PCMat,
927  KSP ksp)
928 {
929  /*
930  Description:
931  Update the preconditioner matrix for the ksp object
932  */
933  KSPSetOperators(ksp, dRdWTMF_, PCMat);
934 }
935 
937  const Mat jacPCMat,
938  KSP ksp)
939 {
940 #ifdef CODI_ADR
941  /*
942  Description:
943  Call createMLRKSP from DALinearEqn
944  This is the main function we need to call to initialize the KSP and set
945  up parameters for solving the linear equations
946  NOTE: this is the matrix-free version of the createMLRKSP function.
947  We dont need to input the jacMat because we will use dRdWTMF_: the
948  matrix-free state Jacobian matrix
949  */
950 
951  daLinearEqnPtr_->createMLRKSP(dRdWTMF_, jacPCMat, ksp);
952 #endif
953 }
954 
956  const KSP ksp,
957  const Vec rhsVec,
958  Vec solVec)
959 {
960  /*
961  Description:
962  Call solveLinearEqn from DALinearEqn to solve a linear equation.
963 
964  Input:
965  ksp: the KSP object, obtained from calling Foam::createMLRKSP
966 
967  rhsVec: the right-hand-side petsc vector
968 
969  Output:
970  solVec: the solution vector
971 
972  Return 0 if the linear equation solution finished successfully otherwise return 1
973  */
974 
975  label error = daLinearEqnPtr_->solveLinearEqn(ksp, rhsVec, solVec);
976 
977  // need to reset globalADTapeInitialized to 0 because every matrix-free
978  // adjoint solution need to re-initialize the AD tape
980 
981  // **********************************************************************************************
982  // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time
983  // **********************************************************************************************
986  this->calcResiduals();
987 
988  return error;
989 }
990 
991 void DASolver::getOFMeshPoints(double* points)
992 {
993  // get the flatten mesh points coordinates
994  pointField meshPoints(meshPtr_->points());
995  label counterI = 0;
996  forAll(meshPoints, pointI)
997  {
998  for (label i = 0; i < 3; i++)
999  {
1000  assignValueCheckAD(points[counterI], meshPoints[pointI][i]);
1001  counterI++;
1002  }
1003  }
1004 }
1005 
1007  const word fieldName,
1008  const word fieldType,
1009  double* fieldArray)
1010 {
1011  /*
1012  Description:
1013  assign a OpenFoam layer field variable in mesh.Db() to field
1014  */
1015 
1016  if (fieldType == "scalar")
1017  {
1018  const volScalarField& field = meshPtr_->thisDb().lookupObject<volScalarField>(fieldName);
1019  forAll(field, cellI)
1020  {
1021  assignValueCheckAD(fieldArray[cellI], field[cellI]);
1022  }
1023  }
1024  else if (fieldType == "vector")
1025  {
1026  const volVectorField& field = meshPtr_->thisDb().lookupObject<volVectorField>(fieldName);
1027  label localIdx = 0;
1028  forAll(field, cellI)
1029  {
1030  for (label comp = 0; comp < 3; comp++)
1031  {
1032  assignValueCheckAD(fieldArray[localIdx], field[cellI][comp]);
1033  localIdx++;
1034  }
1035  }
1036  }
1037  else
1038  {
1039  FatalErrorIn("getField") << " fieldType not valid. Options: scalar or vector"
1040  << abort(FatalError);
1041  }
1042 }
1043 
1044 void DASolver::updateOFFields(const scalar* states)
1045 {
1046  label printInfo = 0;
1047  if (daOptionPtr_->getOption<label>("debug"))
1048  {
1049  Info << "Updating the OpenFOAM field..." << endl;
1050  printInfo = 1;
1051  }
1052  daFieldPtr_->state2OFField(states);
1053 
1055 }
1056 
1057 void DASolver::updateOFMesh(const scalar* volCoords)
1058 {
1059  /*
1060  Description:
1061  Update the OpenFOAM mesh based on the volume coordinates point volCoords
1062 
1063  Input:
1064  volCoords: point coordinate array
1065 
1066  Output:
1067  OpenFoam flow fields (internal and boundary)
1068  */
1069  if (daOptionPtr_->getOption<label>("debug"))
1070  {
1071  Info << "Updating the OpenFOAM mesh..." << endl;
1072  }
1073  daFieldPtr_->point2OFMesh(volCoords);
1074 }
1075 
1077 {
1078 #ifdef CODI_ADR
1079  /*
1080  Description:
1081  This function initialize the matrix-free dRdWT, which will be
1082  used later in the adjoint solution
1083  */
1084 
1085  // this is needed because the self.solverAD object in the Python layer
1086  // never run the primal solution, so the wVec and xvVec is not always
1087  // update to date
1088  //this->updateOFField(wVec);
1089  //this->updateOFMesh(xvVec);
1090 
1091  if (daOptionPtr_->getOption<label>("debug"))
1092  {
1093  Info << "In initializedRdWTMatrixFree" << endl;
1094  this->calcPrimalResidualStatistics("print");
1095  }
1096 
1097  // No need to set the size, instead, we need to provide a function to compute
1098  // matrix-vector product, i.e., the dRdWTMatVecMultFunction function
1099  label localSize = daIndexPtr_->nLocalAdjointStates;
1100  MatCreateShell(PETSC_COMM_WORLD, localSize, localSize, PETSC_DETERMINE, PETSC_DETERMINE, this, &dRdWTMF_);
1101  MatShellSetOperation(dRdWTMF_, MATOP_MULT, (void (*)(void))dRdWTMatVecMultFunction);
1102  MatSetUp(dRdWTMF_);
1103  Info << "dRdWT Jacobian Free created!" << endl;
1104 
1105 #endif
1106 }
1107 
1109 {
1110 #ifdef CODI_ADR
1111  /*
1112  Description:
1113  Destroy dRdWTMF_
1114  */
1115  MatDestroy(&dRdWTMF_);
1116 #endif
1117 }
1118 
1119 PetscErrorCode DASolver::dRdWTMatVecMultFunction(Mat dRdWTMF, Vec vecX, Vec vecY)
1120 {
1121 #ifdef CODI_ADR
1122  /*
1123  Description:
1124  This function implements a way to compute matrix-vector products
1125  associated with dRdWTMF matrix.
1126  Here we need to return vecY = dRdWTMF * vecX.
1127  We use the reverse-mode AD to compute vecY in a matrix-free manner
1128  */
1129  DASolver* ctx;
1130  MatShellGetContext(dRdWTMF, (void**)&ctx);
1131 
1132  // Need to re-initialize the tape, setup inputs and outputs,
1133  // and run the forward computation and save the intermediate
1134  // variables in the tape, such that we don't re-compute them
1135  // for each GMRES iteration. This initialization needs to
1136  // happen for each adjoint solution. We will reset
1137  // globalADTape4dRdWTInitialized = 0 in DASolver::solveLinearEqn function
1138  if (!ctx->globalADTape4dRdWTInitialized)
1139  {
1140  ctx->initializeGlobalADTape4dRdWT();
1141  ctx->globalADTape4dRdWTInitialized = 1;
1142  }
1143 
1144  PetscScalar* vecArray;
1145  const PetscScalar* vecArrayRead;
1146  // assign the variable in vecX as the residual gradient for reverse AD
1147  VecGetArrayRead(vecX, &vecArrayRead);
1148  ctx->assignVec2ResidualGradient(vecArrayRead);
1149  VecRestoreArrayRead(vecX, &vecArrayRead);
1150  // do the backward computation to propagate the derivatives to the states
1151  ctx->globalADTape_.evaluate();
1152  // assign the derivatives stored in the states to the vecY vector
1153  VecGetArray(vecY, &vecArray);
1154  ctx->assignStateGradient2Vec(vecArray);
1155  // NOTE: we need to normalize the vecY vector.
1156  ctx->normalizeGradientVec(vecArray);
1157  VecRestoreArray(vecY, &vecArray);
1158  // clear the adjoint to prepare the next matrix-free GMRES iteration
1159  ctx->globalADTape_.clearAdjoints();
1160 
1161 #endif
1162 
1163  return 0;
1164 }
1165 
1167 {
1168 #ifdef CODI_ADR
1169  /*
1170  Description:
1171  Initialize the global tape for computing dRdWT*psi
1172  using revere-mode AD. Here we need to register inputs
1173  and outputs, compute the residuals, and record all the
1174  intermediate variables in the tape. Then in the
1175  dRdWTMatVecMultFunction function, we can assign gradients
1176  and call tape.evaluate multiple times
1177  */
1178 
1179  // always reset the tape before recording
1180  this->globalADTape_.reset();
1181  // set the tape to active and start recording intermediate variables
1182  this->globalADTape_.setActive();
1183  // register state variables as the inputs
1185  // need to correct BC and update all intermediate variables
1187  // Now we can compute the residuals
1188  this->calcResiduals();
1189  // Set the residual as the output
1190  this->registerResidualOutput4AD();
1191  // All done, set the tape to passive
1192  this->globalADTape_.setPassive();
1193 
1194  // Now the tape is ready to use in the matrix-free GMRES solution
1195 #endif
1196 }
1197 
1199  const word inputName,
1200  double* product)
1201 {
1202 
1203 #if defined(CODI_ADF) || defined(CODI_ADR)
1204  /*
1205  Description:
1206  Normalize the jacobian vector product that has states as the input such as dFdW and dRdW
1207 
1208  Input/Output:
1209 
1210  inputName:
1211  name of the input for the Jacobian, we normalize the product only if inputName=stateVar
1212 
1213  product:
1214  jacobian vector product to be normalized. vecY = vecY * scalingFactor
1215  the scalingFactor depends on states.
1216  This is needed for the matrix-vector products in matrix-free adjoint
1217 
1218  */
1219 
1220  if (inputName == "stateVar")
1221  {
1222 
1223  dictionary normStateDict = daOptionPtr_->getAllOptions().subDict("normalizeStates");
1224 
1225  forAll(stateInfo_["volVectorStates"], idxI)
1226  {
1227  const word stateName = stateInfo_["volVectorStates"][idxI];
1228  // if normalized state not defined, skip
1229  if (normStateDict.found(stateName))
1230  {
1231  scalar scalingFactor = normStateDict.getScalar(stateName);
1232 
1233  forAll(meshPtr_->cells(), cellI)
1234  {
1235  for (label i = 0; i < 3; i++)
1236  {
1237  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
1238  product[localIdx] *= scalingFactor.getValue();
1239  }
1240  }
1241  }
1242  }
1243 
1244  forAll(stateInfo_["volScalarStates"], idxI)
1245  {
1246  const word stateName = stateInfo_["volScalarStates"][idxI];
1247  // if normalized state not defined, skip
1248  if (normStateDict.found(stateName))
1249  {
1250  scalar scalingFactor = normStateDict.getScalar(stateName);
1251 
1252  forAll(meshPtr_->cells(), cellI)
1253  {
1254  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
1255  product[localIdx] *= scalingFactor.getValue();
1256  }
1257  }
1258  }
1259 
1260  forAll(stateInfo_["modelStates"], idxI)
1261  {
1262  const word stateName = stateInfo_["modelStates"][idxI];
1263  // if normalized state not defined, skip
1264  if (normStateDict.found(stateName))
1265  {
1266 
1267  scalar scalingFactor = normStateDict.getScalar(stateName);
1268 
1269  forAll(meshPtr_->cells(), cellI)
1270  {
1271  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
1272  product[localIdx] *= scalingFactor.getValue();
1273  }
1274  }
1275  }
1276 
1277  forAll(stateInfo_["surfaceScalarStates"], idxI)
1278  {
1279  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
1280  // if normalized state not defined, skip
1281  if (normStateDict.found(stateName))
1282  {
1283  scalar scalingFactor = normStateDict.getScalar(stateName);
1284 
1285  forAll(meshPtr_->faces(), faceI)
1286  {
1287  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
1288 
1289  if (faceI < daIndexPtr_->nLocalInternalFaces)
1290  {
1291  scalar meshSf = meshPtr_->magSf()[faceI];
1292  product[localIdx] *= scalingFactor.getValue() * meshSf.getValue();
1293  }
1294  else
1295  {
1296  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
1297  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
1298  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
1299  scalar meshSf = meshPtr_->magSf().boundaryField()[patchIdx][faceIdx];
1300  product[localIdx] *= scalingFactor.getValue() * meshSf.getValue();
1301  }
1302  }
1303  }
1304  }
1305  }
1306 
1307 #endif
1308 }
1309 
1311  const word inputName,
1312  const word inputType,
1313  const int inputSize,
1314  const double* input,
1315  const double* seed)
1316 {
1317  /*
1318  Description:
1319  Set seeds for forward mode AD using the DAInput class
1320  */
1321 
1322  // initialize the input and output objects
1323  autoPtr<DAInput> daInput(
1324  DAInput::New(
1325  inputName,
1326  inputType,
1327  meshPtr_(),
1328  daOptionPtr_(),
1329  daModelPtr_(),
1330  daIndexPtr_()));
1331 
1332  scalarList inputList(inputSize, 0.0);
1333 
1334  // assign the input array to the input list and the seed to its gradient()
1335  // Note: we need to use scalarList for AD
1336  forAll(inputList, idxI)
1337  {
1338  inputList[idxI] = input[idxI];
1339 #ifdef CODI_ADF
1340  inputList[idxI].gradient() = seed[idxI];
1341 #endif
1342  }
1343 
1344  // call daInput->run to assign inputList to OF variables
1345  daInput->run(inputList);
1346 }
1347 
1349  const word inputName,
1350  const word inputType)
1351 {
1352  autoPtr<DAInput> daInput(
1353  DAInput::New(
1354  inputName,
1355  inputType,
1356  meshPtr_(),
1357  daOptionPtr_(),
1358  daModelPtr_(),
1359  daIndexPtr_()));
1360 
1361  return daInput->size();
1362 }
1363 
1365  const word outputName,
1366  const word outputType)
1367 {
1368  autoPtr<DAOutput> daOutput(
1369  DAOutput::New(
1370  outputName,
1371  outputType,
1372  meshPtr_(),
1373  daOptionPtr_(),
1374  daModelPtr_(),
1375  daIndexPtr_(),
1376  daResidualPtr_(),
1378 
1379  return daOutput->size();
1380 }
1381 
1384  const word outputName,
1385  const word outputType,
1386  double* output)
1387 {
1388  autoPtr<DAOutput> daOutput(
1389  DAOutput::New(
1390  outputName,
1391  outputType,
1392  meshPtr_(),
1393  daOptionPtr_(),
1394  daModelPtr_(),
1395  daIndexPtr_(),
1396  daResidualPtr_(),
1398 
1399  label outputSize = daOutput->size();
1400 
1401  scalarList outputList(outputSize, 0.0);
1402 
1403  daOutput->run(outputList);
1404 
1405  forAll(outputList, idxI)
1406  {
1407  assignValueCheckAD(output[idxI], outputList[idxI]);
1408  }
1409 }
1410 
1412  const word inputName,
1413  const word inputType)
1414 {
1415  autoPtr<DAInput> daInput(
1416  DAInput::New(
1417  inputName,
1418  inputType,
1419  meshPtr_(),
1420  daOptionPtr_(),
1421  daModelPtr_(),
1422  daIndexPtr_()));
1423 
1424  return daInput->distributed();
1425 }
1426 
1428  const word outputName,
1429  const word outputType)
1430 {
1431  autoPtr<DAOutput> daOutput(
1432  DAOutput::New(
1433  outputName,
1434  outputType,
1435  meshPtr_(),
1436  daOptionPtr_(),
1437  daModelPtr_(),
1438  daIndexPtr_(),
1439  daResidualPtr_(),
1441 
1442  return daOutput->distributed();
1443 }
1444 
1446  const word inputName,
1447  const word inputType,
1448  const double* input,
1449  const word outputName,
1450  const word outputType,
1451  const double* seed,
1452  double* product)
1453 {
1454 #ifdef CODI_ADR
1455  /*
1456  Description:
1457  Calculate the Jacobian-matrix-transposed and vector product for [dOutput/dInput]^T * psi
1458 
1459  Input:
1460  inputName: name of the input. This is usually defined in inputInfo
1461 
1462  inputType: type of the input. This should be consistent with the child class type in DAInput
1463 
1464  input: the actual value of the input array
1465 
1466  outputName: name of the output.
1467 
1468  outputType: type of the output. This should be consistent with the child class type in DAOutput
1469 
1470  seed: the seed array
1471 
1472  Output:
1473  product: the mat-vec product array
1474  */
1475 
1476  Info << "Computing d[" << outputName << "]/d[" << inputName << "]^T * psi " << runTimePtr_->elapsedCpuTime() << " s" << endl;
1477 
1478  // initialize the input and output objects
1479  autoPtr<DAInput> daInput(
1480  DAInput::New(
1481  inputName,
1482  inputType,
1483  meshPtr_(),
1484  daOptionPtr_(),
1485  daModelPtr_(),
1486  daIndexPtr_()));
1487 
1488  autoPtr<DAOutput> daOutput(
1489  DAOutput::New(
1490  outputName,
1491  outputType,
1492  meshPtr_(),
1493  daOptionPtr_(),
1494  daModelPtr_(),
1495  daIndexPtr_(),
1496  daResidualPtr_(),
1498 
1499  label inputSize = daInput->size();
1500  label outputSize = daOutput->size();
1501 
1502  // create input and output lists
1503  scalarList inputList(inputSize, 0.0);
1504  scalarList outputList(outputSize, 0.0);
1505 
1506  // assign the input array to the input list.
1507  // Note: we need to use scalarList for AD
1508  forAll(inputList, idxI)
1509  {
1510  inputList[idxI] = input[idxI];
1511  }
1512 
1513  // reset tape
1514  this->globalADTape_.reset();
1515  // activate tape, start recording
1516  this->globalADTape_.setActive();
1517  // register input
1518  forAll(inputList, idxI)
1519  {
1520  this->globalADTape_.registerInput(inputList[idxI]);
1521  }
1522  // call daInput->run to assign inputList to OF variables
1523  daInput->run(inputList);
1524  // update all intermediate variables and boundary conditions
1526  // call daOutput->run to compute OF output variables and assign them to outputList
1527  daOutput->run(outputList);
1528  // register output
1529  forAll(outputList, idxI)
1530  {
1531  this->globalADTape_.registerOutput(outputList[idxI]);
1532  }
1533  // stop recording
1534  this->globalADTape_.setPassive();
1535  // assign the seed to the outputList's gradient
1536  forAll(outputList, idxI)
1537  {
1538  // if the output is in serial (e.g., function), we need to assign the seed to
1539  // only the master processor. This is because the serial output already called
1540  // a reduce in the daOutput->run function.
1541  if (daOutput().distributed())
1542  {
1543  // output is distributed, assign seed to all procs
1544  outputList[idxI].setGradient(seed[idxI]);
1545  }
1546  else
1547  {
1548  // output is in serial, assign seed to the master proc only
1549  if (Pstream::master())
1550  {
1551  outputList[idxI].setGradient(seed[idxI]);
1552  }
1553  }
1554  }
1555  // evaluate tape to compute derivative
1556  this->globalADTape_.evaluate();
1557  // get the matrix-vector product=[dOutput/dInput]^T*seed from the inputList
1558  // and assign it to the product array
1559  forAll(inputList, idxI)
1560  {
1561  product[idxI] = inputList[idxI].getGradient();
1562  // if the input is in serial (e.g., angle of attack), we need to reduce the product and
1563  // make sure the product is consistent among all processors
1564  if (!daInput().distributed())
1565  {
1566  reduce(product[idxI], sumOp<double>());
1567  }
1568  }
1569 
1570  // we need to normalize the jacobian vector product if inputType == stateVar
1571  this->normalizeJacTVecProduct(inputType, product);
1572 
1573  // need to clear adjoint and tape after the computation is done!
1574  this->globalADTape_.clearAdjoints();
1575  this->globalADTape_.reset();
1576 
1577  // clean up OF vars's AD seeds by deactivating the inputs (set its gradients to zeros)
1578  // and calculate the output one more time. This will propagate the zero seeds
1579  // to all the intermediate variables and reset their gradient to zeros
1580  // NOTE: cleaning up the seeds is critical; otherwise, it will create AD conflict
1581  forAll(inputList, idxI)
1582  {
1583  this->globalADTape_.deactivateValue(inputList[idxI]);
1584  }
1585  daInput->run(inputList);
1587  daOutput->run(outputList);
1588 
1589 #endif
1590 }
1591 
1593  const scalar* volCoords,
1594  scalar* surfCoords)
1595 {
1596  /*
1597  Description:
1598  Calculate a list of face center coordinates for the MDO coupling patches, given
1599  the volume mesh point coordinates
1600 
1601  Input:
1602  volCoords: volume mesh point coordinates
1603 
1604  Output:
1605  surfCoords: face center coordinates for coupling patches
1606  */
1607  this->updateOFMesh(volCoords);
1608 
1609  wordList patches;
1610  wordList components;
1611  dictionary outputInfo = daOptionPtr_->getAllOptions().subDict("outputInfo");
1612  forAll(outputInfo.toc(), idxI)
1613  {
1614  word outputName = outputInfo.toc()[idxI];
1615  outputInfo.subDict(outputName).readEntry("components", components);
1616  if (components.found("thermalCoupling"))
1617  {
1618  outputInfo.subDict(outputName).readEntry("patches", patches);
1619  break;
1620  }
1621  }
1622  // NOTE: always sort the patch because the order of the patch element matters in CHT coupling
1623  sort(patches);
1624 
1625  // ******** first loop
1626  label counterFaceI = 0;
1627  forAll(patches, cI)
1628  {
1629  // get the patch id label
1630  label patchI = meshPtr_->boundaryMesh().findPatchID(patches[cI]);
1631  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
1632  {
1633  for (label i = 0; i < 3; i++)
1634  {
1635  surfCoords[counterFaceI] = meshPtr_->Cf().boundaryField()[patchI][faceI][i];
1636  counterFaceI++;
1637  }
1638  }
1639  }
1640 
1641  // ******** second loop
1642  // NOTE. Since we create two duplicated surface point coordinates for transferring two variables
1643  // we need to translate the 2nd one by 1000, so the meld component will find the correct
1644  // coordinates for interpolation. If these two sets of coords are overlapped, we will have
1645  // wrong interpolations from meld.
1646  forAll(patches, cI)
1647  {
1648  // get the patch id label
1649  label patchI = meshPtr_->boundaryMesh().findPatchID(patches[cI]);
1650  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
1651  {
1652  for (label i = 0; i < 3; i++)
1653  {
1654  surfCoords[counterFaceI] = meshPtr_->Cf().boundaryField()[patchI][faceI][i] + 1000.0;
1655  counterFaceI++;
1656  }
1657  }
1658  }
1659 }
1660 
1662  const label oldTimeLevel,
1663  const double* psi,
1664  double* dRdWOldTPsi)
1665 {
1666 #ifdef CODI_ADR
1667  /*
1668  Description:
1669  Compute the matrix-vector products dRdWOld^T*Psi using reverse-mode AD
1670  Here WOld means the state variable from previous time step,
1671  if oldTimeLevel = 1, WOld means W0, if oldTimeLevel=2, WOld means W00
1672  R is always the residuals for the current time step
1673  NOTE: if the oldTimeLevel is greater than the max nOldTimes a variable
1674  has, the derivative will be zero. This is done by registering input
1675  only for variables that have enough oldTimes in registerStateVariableInput4AD
1676 
1677  Input:
1678 
1679  oldTimeLevel: 1-dRdW0^T 2-dRdW00^T
1680 
1681  psi: the array to multiply dRdW0^T
1682 
1683  Output:
1684  dRdWOldTPsi: the matrix-vector products dRdWOld^T * Psi
1685  */
1686 
1687  Info << "Computing [dRdWOld]^T * psi: level " << oldTimeLevel << ". " << runTimePtr_->elapsedCpuTime() << " s" << endl;
1688 
1689  this->globalADTape_.reset();
1690  this->globalADTape_.setActive();
1691 
1692  this->registerStateVariableInput4AD(oldTimeLevel);
1693 
1694  // compute residuals
1696  this->calcResiduals();
1697 
1698  this->registerResidualOutput4AD();
1699  this->globalADTape_.setPassive();
1700 
1701  this->assignVec2ResidualGradient(psi);
1702  this->globalADTape_.evaluate();
1703 
1704  // get the deriv values
1705  this->assignStateGradient2Vec(dRdWOldTPsi, oldTimeLevel);
1706 
1707  // NOTE: we need to normalize dRdWOldTPsi!
1708  this->normalizeGradientVec(dRdWOldTPsi);
1709 
1710  this->globalADTape_.clearAdjoints();
1711  this->globalADTape_.reset();
1712 
1713  // **********************************************************************************************
1714  // clean up OF vars's AD seeds by deactivating the inputs and call the forward func one more time
1715  // **********************************************************************************************
1716  this->deactivateStateVariableInput4AD(oldTimeLevel);
1718  this->calcResiduals();
1719 #endif
1720 }
1721 
1722 void DASolver::registerStateVariableInput4AD(const label oldTimeLevel)
1723 {
1724 #ifdef CODI_ADR
1725  /*
1726  Description:
1727  Register all state variables as the input for reverse-mode AD
1728 
1729  Input:
1730  oldTimeLevel: which time level to register, the default value
1731  is 0, meaning it will register the state itself. If its
1732  value is 1, it will register state.oldTime(), if its value
1733  is 2, it will register state.oldTime().oldTime(). For
1734  steady-state adjoint oldTimeLevel = 0
1735  */
1736 
1737  if (oldTimeLevel < 0 || oldTimeLevel > 2)
1738  {
1739  FatalErrorIn("") << "oldTimeLevel not valid. Options: 0, 1, or 2"
1740  << abort(FatalError);
1741  }
1742 
1743  forAll(stateInfo_["volVectorStates"], idxI)
1744  {
1745  const word stateName = stateInfo_["volVectorStates"][idxI];
1746  volVectorField& state = const_cast<volVectorField&>(
1747  meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
1748 
1749  label maxOldTimes = state.nOldTimes();
1750 
1751  if (maxOldTimes >= oldTimeLevel)
1752  {
1753  forAll(state, cellI)
1754  {
1755  for (label i = 0; i < 3; i++)
1756  {
1757  if (oldTimeLevel == 0)
1758  {
1759  this->globalADTape_.registerInput(state[cellI][i]);
1760  }
1761  else if (oldTimeLevel == 1)
1762  {
1763  this->globalADTape_.registerInput(state.oldTime()[cellI][i]);
1764  }
1765  else if (oldTimeLevel == 2)
1766  {
1767  this->globalADTape_.registerInput(state.oldTime().oldTime()[cellI][i]);
1768  }
1769  }
1770  }
1771  }
1772  }
1773 
1774  forAll(stateInfo_["volScalarStates"], idxI)
1775  {
1776  const word stateName = stateInfo_["volScalarStates"][idxI];
1777  volScalarField& state = const_cast<volScalarField&>(
1778  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
1779 
1780  label maxOldTimes = state.nOldTimes();
1781 
1782  if (maxOldTimes >= oldTimeLevel)
1783  {
1784  forAll(state, cellI)
1785  {
1786  if (oldTimeLevel == 0)
1787  {
1788  this->globalADTape_.registerInput(state[cellI]);
1789  }
1790  else if (oldTimeLevel == 1)
1791  {
1792  this->globalADTape_.registerInput(state.oldTime()[cellI]);
1793  }
1794  else if (oldTimeLevel == 2)
1795  {
1796  this->globalADTape_.registerInput(state.oldTime().oldTime()[cellI]);
1797  }
1798  }
1799  }
1800  }
1801 
1802  forAll(stateInfo_["modelStates"], idxI)
1803  {
1804  const word stateName = stateInfo_["modelStates"][idxI];
1805  volScalarField& state = const_cast<volScalarField&>(
1806  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
1807 
1808  label maxOldTimes = state.nOldTimes();
1809 
1810  if (maxOldTimes >= oldTimeLevel)
1811  {
1812  forAll(state, cellI)
1813  {
1814  if (oldTimeLevel == 0)
1815  {
1816  this->globalADTape_.registerInput(state[cellI]);
1817  }
1818  else if (oldTimeLevel == 1)
1819  {
1820  this->globalADTape_.registerInput(state.oldTime()[cellI]);
1821  }
1822  else if (oldTimeLevel == 2)
1823  {
1824  this->globalADTape_.registerInput(state.oldTime().oldTime()[cellI]);
1825  }
1826  }
1827  }
1828  }
1829 
1830  forAll(stateInfo_["surfaceScalarStates"], idxI)
1831  {
1832  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
1833  surfaceScalarField& state = const_cast<surfaceScalarField&>(
1834  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
1835 
1836  label maxOldTimes = state.nOldTimes();
1837 
1838  if (maxOldTimes >= oldTimeLevel)
1839  {
1840  forAll(state, faceI)
1841  {
1842  if (oldTimeLevel == 0)
1843  {
1844  this->globalADTape_.registerInput(state[faceI]);
1845  }
1846  else if (oldTimeLevel == 1)
1847  {
1848  this->globalADTape_.registerInput(state.oldTime()[faceI]);
1849  }
1850  else if (oldTimeLevel == 2)
1851  {
1852  this->globalADTape_.registerInput(state.oldTime().oldTime()[faceI]);
1853  }
1854  }
1855  forAll(state.boundaryField(), patchI)
1856  {
1857  forAll(state.boundaryField()[patchI], faceI)
1858  {
1859  if (oldTimeLevel == 0)
1860  {
1861  this->globalADTape_.registerInput(state.boundaryFieldRef()[patchI][faceI]);
1862  }
1863  else if (oldTimeLevel == 1)
1864  {
1865  this->globalADTape_.registerInput(state.oldTime().boundaryFieldRef()[patchI][faceI]);
1866  }
1867  else if (oldTimeLevel == 2)
1868  {
1869  this->globalADTape_.registerInput(state.oldTime().oldTime().boundaryFieldRef()[patchI][faceI]);
1870  }
1871  }
1872  }
1873  }
1874  }
1875 
1876 #endif
1877 }
1878 
1879 void DASolver::deactivateStateVariableInput4AD(const label oldTimeLevel)
1880 {
1881 #ifdef CODI_ADR
1882  /*
1883  Description:
1884  Deactivate all state variables as the input for reverse-mode AD
1885  Input:
1886  oldTimeLevel: which time level to register, the default value
1887  is 0, meaning it will register the state itself. If its
1888  value is 1, it will register state.oldTime(), if its value
1889  is 2, it will register state.oldTime().oldTime(). For
1890  steady-state adjoint oldTimeLevel = 0
1891  */
1892 
1893  if (oldTimeLevel < 0 || oldTimeLevel > 2)
1894  {
1895  FatalErrorIn("") << "oldTimeLevel not valid. Options: 0, 1, or 2"
1896  << abort(FatalError);
1897  }
1898 
1899  forAll(stateInfo_["volVectorStates"], idxI)
1900  {
1901  const word stateName = stateInfo_["volVectorStates"][idxI];
1902  volVectorField& state = const_cast<volVectorField&>(
1903  meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
1904 
1905  label maxOldTimes = state.nOldTimes();
1906 
1907  if (maxOldTimes >= oldTimeLevel)
1908  {
1909  forAll(state, cellI)
1910  {
1911  for (label i = 0; i < 3; i++)
1912  {
1913  if (oldTimeLevel == 0)
1914  {
1915  this->globalADTape_.deactivateValue(state[cellI][i]);
1916  }
1917  else if (oldTimeLevel == 1)
1918  {
1919  this->globalADTape_.deactivateValue(state.oldTime()[cellI][i]);
1920  }
1921  else if (oldTimeLevel == 2)
1922  {
1923  this->globalADTape_.deactivateValue(state.oldTime().oldTime()[cellI][i]);
1924  }
1925  }
1926  }
1927  }
1928  }
1929 
1930  forAll(stateInfo_["volScalarStates"], idxI)
1931  {
1932  const word stateName = stateInfo_["volScalarStates"][idxI];
1933  volScalarField& state = const_cast<volScalarField&>(
1934  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
1935 
1936  label maxOldTimes = state.nOldTimes();
1937 
1938  if (maxOldTimes >= oldTimeLevel)
1939  {
1940  forAll(state, cellI)
1941  {
1942  if (oldTimeLevel == 0)
1943  {
1944  this->globalADTape_.deactivateValue(state[cellI]);
1945  }
1946  else if (oldTimeLevel == 1)
1947  {
1948  this->globalADTape_.deactivateValue(state.oldTime()[cellI]);
1949  }
1950  else if (oldTimeLevel == 2)
1951  {
1952  this->globalADTape_.deactivateValue(state.oldTime().oldTime()[cellI]);
1953  }
1954  }
1955  }
1956  }
1957 
1958  forAll(stateInfo_["modelStates"], idxI)
1959  {
1960  const word stateName = stateInfo_["modelStates"][idxI];
1961  volScalarField& state = const_cast<volScalarField&>(
1962  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
1963 
1964  label maxOldTimes = state.nOldTimes();
1965 
1966  if (maxOldTimes >= oldTimeLevel)
1967  {
1968  forAll(state, cellI)
1969  {
1970  if (oldTimeLevel == 0)
1971  {
1972  this->globalADTape_.deactivateValue(state[cellI]);
1973  }
1974  else if (oldTimeLevel == 1)
1975  {
1976  this->globalADTape_.deactivateValue(state.oldTime()[cellI]);
1977  }
1978  else if (oldTimeLevel == 2)
1979  {
1980  this->globalADTape_.deactivateValue(state.oldTime().oldTime()[cellI]);
1981  }
1982  }
1983  }
1984  }
1985 
1986  forAll(stateInfo_["surfaceScalarStates"], idxI)
1987  {
1988  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
1989  surfaceScalarField& state = const_cast<surfaceScalarField&>(
1990  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
1991 
1992  label maxOldTimes = state.nOldTimes();
1993 
1994  if (maxOldTimes >= oldTimeLevel)
1995  {
1996  forAll(state, faceI)
1997  {
1998  if (oldTimeLevel == 0)
1999  {
2000  this->globalADTape_.deactivateValue(state[faceI]);
2001  }
2002  else if (oldTimeLevel == 1)
2003  {
2004  this->globalADTape_.deactivateValue(state.oldTime()[faceI]);
2005  }
2006  else if (oldTimeLevel == 2)
2007  {
2008  this->globalADTape_.deactivateValue(state.oldTime().oldTime()[faceI]);
2009  }
2010  }
2011  forAll(state.boundaryField(), patchI)
2012  {
2013  forAll(state.boundaryField()[patchI], faceI)
2014  {
2015  if (oldTimeLevel == 0)
2016  {
2017  this->globalADTape_.deactivateValue(state.boundaryFieldRef()[patchI][faceI]);
2018  }
2019  else if (oldTimeLevel == 1)
2020  {
2021  this->globalADTape_.deactivateValue(state.oldTime().boundaryFieldRef()[patchI][faceI]);
2022  }
2023  else if (oldTimeLevel == 2)
2024  {
2025  this->globalADTape_.deactivateValue(state.oldTime().oldTime().boundaryFieldRef()[patchI][faceI]);
2026  }
2027  }
2028  }
2029  }
2030  }
2031 
2032 #endif
2033 }
2034 
2036 {
2037 #ifdef CODI_ADR
2038  /*
2039  Description:
2040  Register all residuals as the output for reverse-mode AD
2041  */
2042 
2043  forAll(stateInfo_["volVectorStates"], idxI)
2044  {
2045  const word stateName = stateInfo_["volVectorStates"][idxI];
2046  const word stateResName = stateName + "Res";
2047  volVectorField& stateRes = const_cast<volVectorField&>(
2048  meshPtr_->thisDb().lookupObject<volVectorField>(stateResName));
2049 
2050  forAll(stateRes, cellI)
2051  {
2052  for (label i = 0; i < 3; i++)
2053  {
2054  this->globalADTape_.registerOutput(stateRes[cellI][i]);
2055  }
2056  }
2057  }
2058 
2059  forAll(stateInfo_["volScalarStates"], idxI)
2060  {
2061  const word stateName = stateInfo_["volScalarStates"][idxI];
2062  const word stateResName = stateName + "Res";
2063  volScalarField& stateRes = const_cast<volScalarField&>(
2064  meshPtr_->thisDb().lookupObject<volScalarField>(stateResName));
2065 
2066  forAll(stateRes, cellI)
2067  {
2068  this->globalADTape_.registerOutput(stateRes[cellI]);
2069  }
2070  }
2071 
2072  forAll(stateInfo_["modelStates"], idxI)
2073  {
2074  const word stateName = stateInfo_["modelStates"][idxI];
2075  const word stateResName = stateName + "Res";
2076  volScalarField& stateRes = const_cast<volScalarField&>(
2077  meshPtr_->thisDb().lookupObject<volScalarField>(stateResName));
2078 
2079  forAll(stateRes, cellI)
2080  {
2081  this->globalADTape_.registerOutput(stateRes[cellI]);
2082  }
2083  }
2084 
2085  forAll(stateInfo_["surfaceScalarStates"], idxI)
2086  {
2087  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
2088  const word stateResName = stateName + "Res";
2089  surfaceScalarField& stateRes = const_cast<surfaceScalarField&>(
2090  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateResName));
2091 
2092  forAll(stateRes, faceI)
2093  {
2094  this->globalADTape_.registerOutput(stateRes[faceI]);
2095  }
2096  forAll(stateRes.boundaryField(), patchI)
2097  {
2098  forAll(stateRes.boundaryField()[patchI], faceI)
2099  {
2100  this->globalADTape_.registerOutput(stateRes.boundaryFieldRef()[patchI][faceI]);
2101  }
2102  }
2103  }
2104 #endif
2105 }
2106 
2107 void DASolver::normalizeGradientVec(double* vecArray)
2108 {
2109 #if defined(CODI_ADF) || defined(CODI_ADR)
2110  /*
2111  Description:
2112  Normalize the reverse-mode AD derivatives stored in vecY
2113 
2114  Input/Output:
2115  vecY: vector to be normalized. vecY = vecY * scalingFactor
2116  the scalingFactor depends on states.
2117  This is needed for the matrix-vector products in matrix-free adjoint
2118 
2119  */
2120 
2121  dictionary normStateDict = daOptionPtr_->getAllOptions().subDict("normalizeStates");
2122 
2123  forAll(stateInfo_["volVectorStates"], idxI)
2124  {
2125  const word stateName = stateInfo_["volVectorStates"][idxI];
2126  // if normalized state not defined, skip
2127  if (normStateDict.found(stateName))
2128  {
2129 
2130  scalar scalingFactor = normStateDict.getScalar(stateName);
2131 
2132  forAll(meshPtr_->cells(), cellI)
2133  {
2134  for (label i = 0; i < 3; i++)
2135  {
2136  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
2137  vecArray[localIdx] *= scalingFactor.getValue();
2138  }
2139  }
2140  }
2141  }
2142 
2143  forAll(stateInfo_["volScalarStates"], idxI)
2144  {
2145  const word stateName = stateInfo_["volScalarStates"][idxI];
2146  // if normalized state not defined, skip
2147  if (normStateDict.found(stateName))
2148  {
2149  scalar scalingFactor = normStateDict.getScalar(stateName);
2150 
2151  forAll(meshPtr_->cells(), cellI)
2152  {
2153  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
2154  vecArray[localIdx] *= scalingFactor.getValue();
2155  }
2156  }
2157  }
2158 
2159  forAll(stateInfo_["modelStates"], idxI)
2160  {
2161  const word stateName = stateInfo_["modelStates"][idxI];
2162  // if normalized state not defined, skip
2163  if (normStateDict.found(stateName))
2164  {
2165 
2166  scalar scalingFactor = normStateDict.getScalar(stateName);
2167 
2168  forAll(meshPtr_->cells(), cellI)
2169  {
2170  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
2171  vecArray[localIdx] *= scalingFactor.getValue();
2172  }
2173  }
2174  }
2175 
2176  forAll(stateInfo_["surfaceScalarStates"], idxI)
2177  {
2178  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
2179  // if normalized state not defined, skip
2180  if (normStateDict.found(stateName))
2181  {
2182  scalar scalingFactor = normStateDict.getScalar(stateName);
2183 
2184  forAll(meshPtr_->faces(), faceI)
2185  {
2186  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
2187 
2188  if (faceI < daIndexPtr_->nLocalInternalFaces)
2189  {
2190  scalar meshSf = meshPtr_->magSf()[faceI];
2191  vecArray[localIdx] *= scalingFactor.getValue() * meshSf.getValue();
2192  }
2193  else
2194  {
2195  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
2196  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
2197  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
2198  scalar meshSf = meshPtr_->magSf().boundaryField()[patchIdx][faceIdx];
2199  vecArray[localIdx] *= scalingFactor.getValue() * meshSf.getValue();
2200  }
2201  }
2202  }
2203  }
2204 
2205 #endif
2206 }
2207 
2208 void DASolver::assignVec2ResidualGradient(const double* vecArray)
2209 {
2210 #if defined(CODI_ADF) || defined(CODI_ADR)
2211  /*
2212  Description:
2213  Assign the reverse-mode AD input seeds from vecX to the residuals in OpenFOAM
2214 
2215  Input:
2216  vecX: vector storing the input seeds
2217 
2218  Output:
2219  All residual variables in OpenFOAM will be set: stateRes[cellI].setGradient(vecX[localIdx])
2220  */
2221 
2222  forAll(stateInfo_["volVectorStates"], idxI)
2223  {
2224  const word stateName = stateInfo_["volVectorStates"][idxI];
2225  const word resName = stateName + "Res";
2226  volVectorField& stateRes = const_cast<volVectorField&>(
2227  meshPtr_->thisDb().lookupObject<volVectorField>(resName));
2228 
2229  forAll(meshPtr_->cells(), cellI)
2230  {
2231  for (label i = 0; i < 3; i++)
2232  {
2233  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
2234  stateRes[cellI][i].setGradient(vecArray[localIdx]);
2235  }
2236  }
2237  }
2238 
2239  forAll(stateInfo_["volScalarStates"], idxI)
2240  {
2241  const word stateName = stateInfo_["volScalarStates"][idxI];
2242  const word resName = stateName + "Res";
2243  volScalarField& stateRes = const_cast<volScalarField&>(
2244  meshPtr_->thisDb().lookupObject<volScalarField>(resName));
2245 
2246  forAll(meshPtr_->cells(), cellI)
2247  {
2248  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
2249  stateRes[cellI].setGradient(vecArray[localIdx]);
2250  }
2251  }
2252 
2253  forAll(stateInfo_["modelStates"], idxI)
2254  {
2255  const word stateName = stateInfo_["modelStates"][idxI];
2256  const word resName = stateName + "Res";
2257  volScalarField& stateRes = const_cast<volScalarField&>(
2258  meshPtr_->thisDb().lookupObject<volScalarField>(resName));
2259 
2260  forAll(meshPtr_->cells(), cellI)
2261  {
2262  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
2263  stateRes[cellI].setGradient(vecArray[localIdx]);
2264  }
2265  }
2266 
2267  forAll(stateInfo_["surfaceScalarStates"], idxI)
2268  {
2269  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
2270  const word resName = stateName + "Res";
2271  surfaceScalarField& stateRes = const_cast<surfaceScalarField&>(
2272  meshPtr_->thisDb().lookupObject<surfaceScalarField>(resName));
2273 
2274  forAll(meshPtr_->faces(), faceI)
2275  {
2276  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
2277 
2278  if (faceI < daIndexPtr_->nLocalInternalFaces)
2279  {
2280  stateRes[faceI].setGradient(vecArray[localIdx]);
2281  }
2282  else
2283  {
2284  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
2285  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
2286  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
2287  stateRes.boundaryFieldRef()[patchIdx][faceIdx].setGradient(vecArray[localIdx]);
2288  }
2289  }
2290  }
2291 
2292 #endif
2293 }
2294 
2296  double* vecArray,
2297  const label oldTimeLevel)
2298 {
2299 #if defined(CODI_ADF) || defined(CODI_ADR)
2300  /*
2301  Description:
2302  Set the reverse-mode AD derivatives from the state variables in OpenFOAM to vecY
2303 
2304  Input:
2305  OpenFOAM state variables that contain the reverse-mode derivative
2306 
2307  oldTimeLevel: which time level to register, the default value
2308  is 0, meaning it will register the state itself. If its
2309  value is 1, it will register state.oldTime(), if its value
2310  is 2, it will register state.oldTime().oldTime(). For
2311  steady-state adjoint oldTimeLevel = 0
2312 
2313  Output:
2314  vecY: a vector to store the derivatives. The order of this vector is
2315  the same as the state variable vector
2316  */
2317 
2318  if (oldTimeLevel < 0 || oldTimeLevel > 2)
2319  {
2320  FatalErrorIn("") << "oldTimeLevel not valid. Options: 0, 1, or 2"
2321  << abort(FatalError);
2322  }
2323 
2324  forAll(stateInfo_["volVectorStates"], idxI)
2325  {
2326  const word stateName = stateInfo_["volVectorStates"][idxI];
2327  volVectorField& state = const_cast<volVectorField&>(
2328  meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
2329 
2330  label maxOldTimes = state.nOldTimes();
2331 
2332  if (maxOldTimes >= oldTimeLevel)
2333  {
2334  forAll(meshPtr_->cells(), cellI)
2335  {
2336  for (label i = 0; i < 3; i++)
2337  {
2338  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
2339  if (oldTimeLevel == 0)
2340  {
2341  vecArray[localIdx] = state[cellI][i].getGradient();
2342  }
2343  else if (oldTimeLevel == 1)
2344  {
2345  vecArray[localIdx] = state.oldTime()[cellI][i].getGradient();
2346  }
2347  else if (oldTimeLevel == 2)
2348  {
2349  vecArray[localIdx] = state.oldTime().oldTime()[cellI][i].getGradient();
2350  }
2351  }
2352  }
2353  }
2354  }
2355 
2356  forAll(stateInfo_["volScalarStates"], idxI)
2357  {
2358  const word stateName = stateInfo_["volScalarStates"][idxI];
2359  volScalarField& state = const_cast<volScalarField&>(
2360  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
2361 
2362  label maxOldTimes = state.nOldTimes();
2363 
2364  if (maxOldTimes >= oldTimeLevel)
2365  {
2366  forAll(meshPtr_->cells(), cellI)
2367  {
2368  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
2369  if (oldTimeLevel == 0)
2370  {
2371  vecArray[localIdx] = state[cellI].getGradient();
2372  }
2373  else if (oldTimeLevel == 1)
2374  {
2375  vecArray[localIdx] = state.oldTime()[cellI].getGradient();
2376  }
2377  else if (oldTimeLevel == 2)
2378  {
2379  vecArray[localIdx] = state.oldTime().oldTime()[cellI].getGradient();
2380  }
2381  }
2382  }
2383  }
2384 
2385  forAll(stateInfo_["modelStates"], idxI)
2386  {
2387  const word stateName = stateInfo_["modelStates"][idxI];
2388  volScalarField& state = const_cast<volScalarField&>(
2389  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
2390 
2391  label maxOldTimes = state.nOldTimes();
2392 
2393  if (maxOldTimes >= oldTimeLevel)
2394  {
2395  forAll(meshPtr_->cells(), cellI)
2396  {
2397  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
2398  if (oldTimeLevel == 0)
2399  {
2400  vecArray[localIdx] = state[cellI].getGradient();
2401  }
2402  else if (oldTimeLevel == 1)
2403  {
2404  vecArray[localIdx] = state.oldTime()[cellI].getGradient();
2405  }
2406  else if (oldTimeLevel == 2)
2407  {
2408  vecArray[localIdx] = state.oldTime().oldTime()[cellI].getGradient();
2409  }
2410  }
2411  }
2412  }
2413 
2414  forAll(stateInfo_["surfaceScalarStates"], idxI)
2415  {
2416  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
2417  surfaceScalarField& state = const_cast<surfaceScalarField&>(
2418  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
2419 
2420  label maxOldTimes = state.nOldTimes();
2421 
2422  if (maxOldTimes >= oldTimeLevel)
2423  {
2424  forAll(meshPtr_->faces(), faceI)
2425  {
2426  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
2427 
2428  if (faceI < daIndexPtr_->nLocalInternalFaces)
2429  {
2430  if (oldTimeLevel == 0)
2431  {
2432  vecArray[localIdx] = state[faceI].getGradient();
2433  }
2434  else if (oldTimeLevel == 1)
2435  {
2436  vecArray[localIdx] = state.oldTime()[faceI].getGradient();
2437  }
2438  else if (oldTimeLevel == 2)
2439  {
2440  vecArray[localIdx] = state.oldTime().oldTime()[faceI].getGradient();
2441  }
2442  }
2443  else
2444  {
2445  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
2446  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
2447  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
2448 
2449  if (oldTimeLevel == 0)
2450  {
2451  vecArray[localIdx] =
2452  state.boundaryField()[patchIdx][faceIdx].getGradient();
2453  }
2454  else if (oldTimeLevel == 1)
2455  {
2456  vecArray[localIdx] =
2457  state.oldTime().boundaryField()[patchIdx][faceIdx].getGradient();
2458  }
2459  else if (oldTimeLevel == 2)
2460  {
2461  vecArray[localIdx] =
2462  state.oldTime().oldTime().boundaryField()[patchIdx][faceIdx].getGradient();
2463  }
2464  }
2465  }
2466  }
2467  }
2468 
2469 #endif
2470 }
2471 
2473 {
2474  /*
2475  Description:
2476  Check whether the primal solution fails. Yes: return 1. No: return 0
2477  - Check whether the min residual in primal satisfy the prescribed tolerance
2478  - Check whether the regression model computation fails
2479  */
2480 
2481  // when checking the tolerance, we relax the criteria by tolMax
2482 
2483  if (regModelFail_ != 0)
2484  {
2485  Info << "Regression model computation has invalid values. Primal solution failed!" << endl;
2486  return 1;
2487  }
2488 
2489  scalar tolMax = daOptionPtr_->getOption<scalar>("primalMinResTolDiff");
2490  if (daGlobalVarPtr_->primalMaxRes / primalMinResTol_ > tolMax)
2491  {
2492  Info << "********************************************" << endl;
2493  Info << "Primal min residual " << daGlobalVarPtr_->primalMaxRes << endl
2494  << "did not satisfy the prescribed tolerance "
2495  << primalMinResTol_ << endl;
2496  Info << "Primal solution failed!" << endl;
2497  Info << "********************************************" << endl;
2498  return 1;
2499  }
2500  else
2501  {
2502  return 0;
2503  }
2504 
2505  return 1;
2506 }
2507 
2509  const Time& runTime,
2510  const label printInterval) const
2511 {
2512  /*
2513  Description:
2514  Check if it is print time
2515  */
2516  if (runTime.timeIndex() % printInterval == 0 || runTime.timeIndex() == 1)
2517  {
2518  return 1;
2519  }
2520  else
2521  {
2522  return 0;
2523  }
2524 }
2525 
2527 {
2528  /*
2529  Description:
2530  Write associated fields such as relative velocity
2531  */
2532 
2533  IOobject MRFIO(
2534  "MRFProperties",
2535  runTimePtr_->constant(),
2536  meshPtr_(),
2537  IOobject::MUST_READ,
2538  IOobject::NO_WRITE,
2539  false); // do not register
2540 
2541  if (MRFIO.typeHeaderOk<IOdictionary>(true))
2542  {
2543  IOdictionary MRFProperties(MRFIO);
2544 
2545  bool activeMRF(MRFProperties.subDict("MRF").lookupOrDefault("active", true));
2546 
2547  if (activeMRF)
2548  {
2549  const volVectorField& U = meshPtr_->thisDb().lookupObject<volVectorField>("U");
2550 
2551  volVectorField URel("URel", U);
2552  IOMRFZoneList MRF(meshPtr_());
2553  MRF.makeRelative(URel);
2554  URel.write();
2555  }
2556  }
2557 }
2558 
2560  const word fieldName,
2561  const word fieldType)
2562 {
2563  /*
2564  Description:
2565  Update the boundary condition for a field
2566 
2567  Input:
2568  fieldName: the name of the field to update
2569 
2570  fieldType: either scalar or vector
2571  */
2572 
2573  if (fieldType == "scalar")
2574  {
2575  volScalarField& field =
2576  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
2577  field.correctBoundaryConditions();
2578  }
2579  else if (fieldType == "vector")
2580  {
2581  volVectorField& field =
2582  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(fieldName));
2583  field.correctBoundaryConditions();
2584  }
2585  else
2586  {
2587  FatalErrorIn("") << fieldType << " not support. Options are: vector or scalar "
2588  << abort(FatalError);
2589  }
2590 }
2591 
2592 void DASolver::calcResiduals(label isPC)
2593 {
2594  /*
2595  Description:
2596  Calculate the residuals and assign values to the residual OF variables in the DAResidual object, such as URes_, pRes_
2597 
2598  Inputs:
2599  isPC: whether the residual calculate is for preconditioner, default false
2600  */
2601 
2602  dictionary options;
2603  options.set("isPC", isPC);
2604  daResidualPtr_->calcResiduals(options);
2605  daModelPtr_->calcResiduals(options);
2606 }
2607 
2609 {
2610  /*
2611  Description:
2612  Update the boundary condition and intermediate variables for all state variables
2613  */
2614 
2615  label nBCCalls = daOptionPtr_->getOption<label>("maxCorrectBCCalls");
2616 
2617  for (label i = 0; i < nBCCalls; i++)
2618  {
2619  daResidualPtr_->correctBoundaryConditions();
2620  daResidualPtr_->updateIntermediateVariables();
2621  daModelPtr_->correctBoundaryConditions();
2622  daModelPtr_->updateIntermediateVariables();
2623  }
2624 
2625  // if we have regression models, we also need to update them because they will update the fields
2626  // NOTE we should have done it in DAInput, no need to call it again.
2627  this->regressionModelCompute();
2628 
2629  // we also need to update DAGlobaVar::inputUnsteadyField if unsteadyField is used in inputInfo
2630  this->updateInputFieldUnsteady();
2631 }
2632 
2633 void DASolver::calcPCMatWithFvMatrix(Mat PCMat, const label turbOnly)
2634 {
2635  /*
2636  Description:
2637  calculate the PC mat using fvMatrix. Here we only calculate the block diagonal components,
2638  e.g., dR_U/dU, dR_p/dp, etc.
2639  */
2640 
2641  //DAUtility::writeMatrixASCII(PCMat, "MatOrig");
2642 
2643  // MatZeroEntries(PCMat);
2644 
2645  // non turbulence variables
2646  if (!turbOnly)
2647  {
2648  daResidualPtr_->calcPCMatWithFvMatrix(PCMat);
2649  }
2650 
2651  // turbulence variables
2652  DATurbulenceModel& daTurb = const_cast<DATurbulenceModel&>(daModelPtr_->getDATurbulenceModel());
2653  const labelUList& owner = meshPtr_->owner();
2654  const labelUList& neighbour = meshPtr_->neighbour();
2655 
2656  PetscScalar val;
2657 
2658  dictionary normStateDict = daOptionPtr_->getAllOptions().subDict("normalizeStates");
2659  wordList normResDict = daOptionPtr_->getOption<wordList>("normalizeResiduals");
2660  forAll(stateInfo_["modelStates"], idxI)
2661  {
2662  const word stateName = stateInfo_["modelStates"][idxI];
2663  const word resName = stateName + "Res";
2664  label nCells = meshPtr_->nCells();
2665  label nInternalFaces = daIndexPtr_->nLocalInternalFaces;
2666  scalarField D(nCells, 0.0);
2667  scalarField upper(nInternalFaces, 0.0);
2668  scalarField lower(nInternalFaces, 0.0);
2669  daTurb.getFvMatrixFields(stateName, D, upper, lower);
2670 
2671  scalar stateScaling = 1.0;
2672  if (normStateDict.found(stateName))
2673  {
2674  stateScaling = normStateDict.getScalar(stateName);
2675  }
2676  scalar resScaling = 1.0;
2677  // set diag
2678  forAll(meshPtr_->cells(), cellI)
2679  {
2680  if (normResDict.found(resName))
2681  {
2682  resScaling = meshPtr_->V()[cellI];
2683  }
2684 
2685  PetscInt rowI = daIndexPtr_->getGlobalAdjointStateIndex(stateName, cellI);
2686  PetscInt colI = rowI;
2687  scalar val1 = D[cellI] * stateScaling / resScaling;
2688  assignValueCheckAD(val, val1);
2689  MatSetValues(PCMat, 1, &rowI, 1, &colI, &val, INSERT_VALUES);
2690  }
2691 
2692  // set lower/owner
2693  for (label faceI = 0; faceI < daIndexPtr_->nLocalInternalFaces; faceI++)
2694  {
2695  label ownerCellI = owner[faceI];
2696  label neighbourCellI = neighbour[faceI];
2697 
2698  if (normResDict.found(resName))
2699  {
2700  resScaling = meshPtr_->V()[neighbourCellI];
2701  }
2702 
2703  PetscInt rowI = daIndexPtr_->getGlobalAdjointStateIndex(stateName, neighbourCellI);
2704  PetscInt colI = daIndexPtr_->getGlobalAdjointStateIndex(stateName, ownerCellI);
2705  scalar val1 = lower[faceI] * stateScaling / resScaling;
2706  assignValueCheckAD(val, val1);
2707  MatSetValues(PCMat, 1, &colI, 1, &rowI, &val, INSERT_VALUES);
2708  }
2709 
2710  // set upper/neighbour
2711  for (label faceI = 0; faceI < daIndexPtr_->nLocalInternalFaces; faceI++)
2712  {
2713  label ownerCellI = owner[faceI];
2714  label neighbourCellI = neighbour[faceI];
2715 
2716  if (normResDict.found(resName))
2717  {
2718  resScaling = meshPtr_->V()[ownerCellI];
2719  }
2720 
2721  PetscInt rowI = daIndexPtr_->getGlobalAdjointStateIndex(stateName, ownerCellI);
2722  PetscInt colI = daIndexPtr_->getGlobalAdjointStateIndex(stateName, neighbourCellI);
2723  scalar val1 = upper[faceI] * stateScaling / resScaling;
2724  assignValueCheckAD(val, val1);
2725  MatSetValues(PCMat, 1, &colI, 1, &rowI, &val, INSERT_VALUES);
2726  }
2727  }
2728 
2729  MatAssemblyBegin(PCMat, MAT_FINAL_ASSEMBLY);
2730  MatAssemblyEnd(PCMat, MAT_FINAL_ASSEMBLY);
2731 
2732  //DAUtility::writeMatrixASCII(PCMat, "MatNew");
2733 }
2734 
2735 /*
2736 void DASolver::disableStateAutoWrite(const wordList& noWriteVars)
2737 {
2738 
2739 
2740  forAll(noWriteVars, idxI)
2741  {
2742  word varName = noWriteVars[idxI];
2743 
2744  if (varName == "None")
2745  {
2746  continue;
2747  }
2748 
2749  if (meshPtr_->thisDb().foundObject<volScalarField>(varName))
2750  {
2751  volScalarField& var =
2752  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(varName));
2753 
2754  var.writeOpt() = IOobject::NO_WRITE;
2755  }
2756  else if (meshPtr_->thisDb().foundObject<volVectorField>(varName))
2757  {
2758  volVectorField& var =
2759  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(varName));
2760 
2761  var.writeOpt() = IOobject::NO_WRITE;
2762  }
2763  else if (meshPtr_->thisDb().foundObject<surfaceScalarField>(varName))
2764  {
2765  surfaceScalarField& var =
2766  const_cast<surfaceScalarField&>(meshPtr_->thisDb().lookupObject<surfaceScalarField>(varName));
2767 
2768  var.writeOpt() = IOobject::NO_WRITE;
2769  }
2770  else
2771  {
2772  Info << "Warning! The prescribed reduceIOVars " << varName << " not found in the db!" << endl;
2773  }
2774  }
2775 }
2776 */
2777 
2779  const label writeMesh,
2780  const wordList& additionalOutput)
2781 {
2782  /*
2783  Description:
2784  Write only the adjoint states
2785  */
2786 
2787  if (runTimePtr_->writeTime())
2788  {
2789 
2790  // volVector states
2791  forAll(stateInfo_["volVectorStates"], idxI)
2792  {
2793  const word stateName = stateInfo_["volVectorStates"][idxI];
2794  volVectorField& state =
2795  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
2796 
2797  state.write();
2798  }
2799 
2800  // volScalar states
2801  forAll(stateInfo_["volScalarStates"], idxI)
2802  {
2803  const word stateName = stateInfo_["volScalarStates"][idxI];
2804  volScalarField& state =
2805  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
2806 
2807  state.write();
2808  }
2809 
2810  // model states
2811  forAll(stateInfo_["modelStates"], idxI)
2812  {
2813  const word stateName = stateInfo_["modelStates"][idxI];
2814  volScalarField& state =
2815  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
2816 
2817  state.write();
2818  }
2819 
2820  // surfaceScalar states
2821  forAll(stateInfo_["surfaceScalarStates"], idxI)
2822  {
2823  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
2824  surfaceScalarField& state =
2825  const_cast<surfaceScalarField&>(meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
2826 
2827  state.write();
2828  }
2829 
2830  // also write additional states
2831  forAll(additionalOutput, idxI)
2832  {
2833  word varName = additionalOutput[idxI];
2834 
2835  if (varName == "None")
2836  {
2837  continue;
2838  }
2839 
2840  if (meshPtr_->thisDb().foundObject<volScalarField>(varName))
2841  {
2842  volScalarField& var =
2843  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(varName));
2844 
2845  var.write();
2846  }
2847  else if (meshPtr_->thisDb().foundObject<volVectorField>(varName))
2848  {
2849  volVectorField& var =
2850  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(varName));
2851 
2852  var.write();
2853  }
2854  else if (meshPtr_->thisDb().foundObject<surfaceScalarField>(varName))
2855  {
2856  surfaceScalarField& var =
2857  const_cast<surfaceScalarField&>(meshPtr_->thisDb().lookupObject<surfaceScalarField>(varName));
2858 
2859  var.write();
2860  }
2861  else
2862  {
2863  Info << "Warning! The prescribed additionalOutput " << varName << " not found in the db! Ignoring it.." << endl;
2864  }
2865  }
2866 
2867  if (writeMesh)
2868  {
2869  pointIOField points = meshPtr_->thisDb().lookupObject<pointIOField>("points");
2870  points.write();
2871  }
2872  }
2873 }
2874 
2875 void DASolver::readMeshPoints(const scalar timeVal)
2876 {
2877  /*
2878  Description:
2879  read the mesh points from the disk and run movePoints to deform the mesh
2880 
2881  Inputs:
2882 
2883  timeVal: Which time to read, i.e., time.timeName()
2884  */
2885 
2886  pointIOField readPoints(
2887  IOobject(
2888  "points",
2889  Foam::name(timeVal),
2890  "polyMesh",
2891  meshPtr_(),
2892  IOobject::MUST_READ,
2893  IOobject::NO_WRITE));
2894 
2895  meshPtr_->movePoints(readPoints);
2896 }
2897 
2898 void DASolver::writeMeshPoints(const double* points, const scalar timeVal)
2899 {
2900  /*
2901  Description:
2902  write the mesh points to the disk for the given timeVal
2903 
2904  Inputs:
2905 
2906  timeVal: Which time to read, i.e., time.timeName()
2907  */
2908 
2909  pointIOField writePoints(
2910  IOobject(
2911  "points",
2912  Foam::name(timeVal),
2913  "polyMesh",
2914  runTimePtr_(),
2915  IOobject::NO_READ,
2916  IOobject::NO_WRITE,
2917  false),
2918  meshPtr_->points());
2919 
2920  //pointIOField writePoints = meshPtr_->points();
2921 
2922  label counterI = 0;
2923  forAll(writePoints, pointI)
2924  {
2925  for (label i = 0; i < 3; i++)
2926  {
2927  writePoints[pointI][i] = points[counterI];
2928  counterI++;
2929  }
2930  }
2931  // time index is not important here. Users need to reset the time after
2932  // calling this function
2933  runTimePtr_->setTime(timeVal, 0);
2934  //Info << "writing points to " << writePoints.path() << endl;
2935  writePoints.write();
2936 }
2937 
2939  scalar timeVal,
2940  label oldTimeLevel)
2941 {
2942  /*
2943  Description:
2944  Read the state variables from the disk and assign the value to the prescribe time level.
2945  NOTE: we use == to assign both internal and boundary fields!
2946  We always read oldTimes for volStates, no matter if the oldTimes are actually needed.
2947  This is not the case for phi. We only read phi oldTime if needed.
2948  This is to save memory because most of the time, we don't need phi.oldTime(); we do not
2949  include the ddtCorr term.
2950 
2951  Inputs:
2952 
2953  timeName: Which time to read, i.e., time.timeName()
2954 
2955  oldTimeLevel:
2956  0: read the states and assign to the current time level
2957  1: read the states and assign to the previous time level (oldTime())
2958  2: read the states and assign to the 2 previous time level (oldTime().oldTime())
2959 
2960  */
2961 
2962  // we can't read negatiev time, so if the timeName is negative, we just read the vars from the 0 folder
2963  word timeName = Foam::name(timeVal);
2964  if (timeVal < 0)
2965  {
2966  timeName = "0";
2967  }
2968 
2969  fvMesh& mesh = meshPtr_();
2970 
2971  forAll(stateInfo_["volVectorStates"], idxI)
2972  {
2973  const word stateName = stateInfo_["volVectorStates"][idxI];
2974  volVectorField& state =
2975  const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
2976 
2977  volVectorField stateRead(
2978  IOobject(
2979  stateName,
2980  timeName,
2981  mesh,
2982  IOobject::MUST_READ,
2983  IOobject::NO_WRITE),
2984  mesh);
2985 
2986  if (oldTimeLevel == 0)
2987  {
2988  state == stateRead;
2989  }
2990  else if (oldTimeLevel == 1)
2991  {
2992  state.oldTime() == stateRead;
2993  }
2994  else if (oldTimeLevel == 2)
2995  {
2996  if (timeVal < 0)
2997  {
2998  volVectorField state0Read(
2999  IOobject(
3000  stateName + "_0",
3001  timeName,
3002  mesh,
3003  IOobject::READ_IF_PRESENT,
3004  IOobject::NO_WRITE),
3005  stateRead);
3006  state.oldTime().oldTime() == state0Read;
3007  }
3008  else
3009  {
3010  state.oldTime().oldTime() == stateRead;
3011  }
3012  }
3013  else
3014  {
3015  FatalErrorIn("") << "oldTimeLevel can only be 0, 1, and 2!" << abort(FatalError);
3016  }
3017  }
3018 
3019  forAll(stateInfo_["volScalarStates"], idxI)
3020  {
3021  const word stateName = stateInfo_["volScalarStates"][idxI];
3022  volScalarField& state =
3023  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
3024 
3025  volScalarField stateRead(
3026  IOobject(
3027  stateName,
3028  timeName,
3029  mesh,
3030  IOobject::MUST_READ,
3031  IOobject::NO_WRITE),
3032  mesh);
3033 
3034  if (oldTimeLevel == 0)
3035  {
3036  state == stateRead;
3037  }
3038  else if (oldTimeLevel == 1)
3039  {
3040  state.oldTime() == stateRead;
3041  }
3042  else if (oldTimeLevel == 2)
3043  {
3044  if (timeVal < 0)
3045  {
3046  volScalarField state0Read(
3047  IOobject(
3048  stateName + "_0",
3049  timeName,
3050  mesh,
3051  IOobject::READ_IF_PRESENT,
3052  IOobject::NO_WRITE),
3053  stateRead);
3054  state.oldTime().oldTime() == state0Read;
3055  }
3056  else
3057  {
3058  state.oldTime().oldTime() == stateRead;
3059  }
3060  }
3061  else
3062  {
3063  FatalErrorIn("") << "oldTimeLevel can only be 0, 1, and 2!" << abort(FatalError);
3064  }
3065  }
3066 
3067  forAll(stateInfo_["modelStates"], idxI)
3068  {
3069  const word stateName = stateInfo_["modelStates"][idxI];
3070  volScalarField& state =
3071  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
3072 
3073  volScalarField stateRead(
3074  IOobject(
3075  stateName,
3076  timeName,
3077  mesh,
3078  IOobject::MUST_READ,
3079  IOobject::NO_WRITE),
3080  mesh);
3081 
3082  if (oldTimeLevel == 0)
3083  {
3084  state == stateRead;
3085  }
3086  else if (oldTimeLevel == 1)
3087  {
3088  state.oldTime() == stateRead;
3089  }
3090  else if (oldTimeLevel == 2)
3091  {
3092  if (timeVal < 0)
3093  {
3094  volScalarField state0Read(
3095  IOobject(
3096  stateName + "_0",
3097  timeName,
3098  mesh,
3099  IOobject::READ_IF_PRESENT,
3100  IOobject::NO_WRITE),
3101  stateRead);
3102  state.oldTime().oldTime() == state0Read;
3103  }
3104  else
3105  {
3106  state.oldTime().oldTime() == stateRead;
3107  }
3108  }
3109  else
3110  {
3111  FatalErrorIn("") << "oldTimeLevel can only be 0, 1, and 2!" << abort(FatalError);
3112  }
3113  }
3114 
3115  forAll(stateInfo_["surfaceScalarStates"], idxI)
3116  {
3117  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
3118  surfaceScalarField& state =
3119  const_cast<surfaceScalarField&>(meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
3120 
3121  label maxOldTimes = state.nOldTimes();
3122 
3123  if (maxOldTimes >= oldTimeLevel)
3124  {
3125  surfaceScalarField stateRead(
3126  IOobject(
3127  stateName,
3128  timeName,
3129  mesh,
3130  IOobject::MUST_READ,
3131  IOobject::NO_WRITE),
3132  mesh);
3133 
3134  if (oldTimeLevel == 0)
3135  {
3136  state == stateRead;
3137  }
3138  else if (oldTimeLevel == 1)
3139  {
3140  state.oldTime() == stateRead;
3141  }
3142  else if (oldTimeLevel == 2)
3143  {
3144  if (timeVal < 0)
3145  {
3146  surfaceScalarField state0Read(
3147  IOobject(
3148  stateName + "_0",
3149  timeName,
3150  mesh,
3151  IOobject::READ_IF_PRESENT,
3152  IOobject::NO_WRITE),
3153  stateRead);
3154  state.oldTime().oldTime() == state0Read;
3155  }
3156  else
3157  {
3158  state.oldTime().oldTime() == stateRead;
3159  }
3160  }
3161  else
3162  {
3163  FatalErrorIn("") << "oldTimeLevel can only be 0, 1, and 2!" << abort(FatalError);
3164  }
3165  }
3166  }
3167 
3168  // update the BC and intermediate variables. This is important, e.g., for turbulent cases
3170 }
3171 
3173 {
3174  /*
3175  Description:
3176  If the mesh fails, we set the time to 10000 and write the results to the disk.
3177  This way, the results will be renamed to 0.00000x during optimization, so that we
3178  can visualize them in Paraview to debug which part of the mesh is failing.
3179  */
3180  if (daOptionPtr_->getOption<label>("writeMinorIterations"))
3181  {
3182  runTimePtr_->setTime(10000.0, 10000);
3183  runTimePtr_->writeNow();
3184  }
3185 }
3186 
3187 void DASolver::setPrimalBoundaryConditions(const label printInfo)
3188 {
3189  /*
3190  Description:
3191  Update the state boundary conditions based on the ones defined in primalBC
3192  */
3193 
3194  // first check if we need to change the boundary conditions based on
3195  // the primalBC dict in DAOption. NOTE: this will overwrite whatever
3196  // boundary conditions defined in the "0" folder
3197  dictionary bcDict = daOptionPtr_->getAllOptions().subDict("primalBC");
3198  if (bcDict.toc().size() != 0)
3199  {
3200  if (printInfo)
3201  {
3202  Info << "Setting up primal boundary conditions based on pyOptions: " << endl;
3203  }
3204  daFieldPtr_->setPrimalBoundaryConditions(printInfo);
3205  }
3206 }
3207 
3209  Vec dFdW,
3210  Vec psi)
3211 {
3212  /*
3213  Description:
3214  Solve the adjoint using the fixed-point iteration approach
3215  */
3216 
3217  FatalErrorIn("DASolver::runFPAdj")
3218  << "Child class not implemented!"
3219  << abort(FatalError);
3220 
3221  return 1;
3222 }
3223 
3225  Vec dFdW,
3226  Vec psi)
3227 {
3228  /*
3229  Description:
3230  Solve the adjoint using the fixed-point iteration approach
3231  */
3232 
3233  FatalErrorIn("DASolver::solveAdjointFP")
3234  << "Child class not implemented!"
3235  << abort(FatalError);
3236 
3237  return 1;
3238 }
3239 
3240 void DASolver::getInitStateVals(HashTable<scalar>& initState)
3241 {
3242  /*
3243  Description:
3244  Get the initial state values from the field's average value
3245  */
3246 
3247  forAll(stateInfo_["volVectorStates"], idxI)
3248  {
3249  const word stateName = stateInfo_["volVectorStates"][idxI];
3250  const volVectorField& state = meshPtr_->thisDb().lookupObject<volVectorField>(stateName);
3251  scalarList avgState(3, 0.0);
3252  forAll(state, cellI)
3253  {
3254  for (label i = 0; i < 3; i++)
3255  {
3256  avgState[i] += state[cellI][i] / daIndexPtr_->nGlobalCells;
3257  }
3258  }
3259  reduce(avgState[0], sumOp<scalar>());
3260  reduce(avgState[1], sumOp<scalar>());
3261  reduce(avgState[2], sumOp<scalar>());
3262 
3263  for (label i = 0; i < 3; i++)
3264  {
3265  initState.set(stateName + Foam::name(i), avgState[i]);
3266  }
3267  }
3268 
3269  forAll(stateInfo_["volScalarStates"], idxI)
3270  {
3271  const word stateName = stateInfo_["volScalarStates"][idxI];
3272  const volScalarField& state = meshPtr_->thisDb().lookupObject<volScalarField>(stateName);
3273  scalar avgState = 0.0;
3274  forAll(state, cellI)
3275  {
3276  avgState += state[cellI];
3277  }
3278  avgState /= daIndexPtr_->nGlobalCells;
3279  reduce(avgState, sumOp<scalar>());
3280 
3281  initState.set(stateName, avgState);
3282  }
3283 
3284  forAll(stateInfo_["modelStates"], idxI)
3285  {
3286  const word stateName = stateInfo_["modelStates"][idxI];
3287  const volScalarField& state = meshPtr_->thisDb().lookupObject<volScalarField>(stateName);
3288  scalar avgState = 0.0;
3289  forAll(state, cellI)
3290  {
3291  avgState += state[cellI];
3292  }
3293  avgState /= daIndexPtr_->nGlobalCells;
3294  reduce(avgState, sumOp<scalar>());
3295 
3296  initState.set(stateName, avgState);
3297  }
3298 
3299  forAll(stateInfo_["surfaceScalarStates"], idxI)
3300  {
3301  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
3302  // const surfaceScalarField& state = meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName);
3303  // we can reset the flux to zeros
3304  initState.set(stateName, 0.0);
3305  }
3306 
3307  Info << "initState: " << initState << endl;
3308 }
3309 
3311 {
3312  /*
3313  Description:
3314  Reset the initial state values using DASolver::initStateVals_
3315  */
3316 
3317  Info << "Resetting state to its initial values" << endl;
3318 
3319  forAll(stateInfo_["volVectorStates"], idxI)
3320  {
3321  const word stateName = stateInfo_["volVectorStates"][idxI];
3322  volVectorField& state = const_cast<volVectorField&>(meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
3323  forAll(state, cellI)
3324  {
3325  for (label i = 0; i < 3; i++)
3326  {
3327  state[cellI][i] = initStateVals_[stateName + Foam::name(i)];
3328  }
3329  }
3330  state.correctBoundaryConditions();
3331  }
3332 
3333  forAll(stateInfo_["volScalarStates"], idxI)
3334  {
3335  const word stateName = stateInfo_["volScalarStates"][idxI];
3336  volScalarField& state = const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
3337  forAll(state, cellI)
3338  {
3339  state[cellI] = initStateVals_[stateName];
3340  }
3341  state.correctBoundaryConditions();
3342  }
3343 
3344  forAll(stateInfo_["modelStates"], idxI)
3345  {
3346  const word stateName = stateInfo_["modelStates"][idxI];
3347  volScalarField& state = const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
3348  forAll(state, cellI)
3349  {
3350  state[cellI] = initStateVals_[stateName];
3351  }
3352  state.correctBoundaryConditions();
3353  }
3354 
3355  forAll(stateInfo_["surfaceScalarStates"], idxI)
3356  {
3357  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
3358  surfaceScalarField& state = const_cast<surfaceScalarField&>(meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
3359  forAll(state, faceI)
3360  {
3361  state[faceI] = initStateVals_[stateName];
3362  }
3363  forAll(state.boundaryField(), patchI)
3364  {
3365  forAll(state.boundaryField()[patchI], faceI)
3366  {
3367  state.boundaryFieldRef()[patchI][faceI] = initStateVals_[stateName];
3368  }
3369  }
3370  // if this is a phi var, we inerpolate U to get phi
3371  if (stateName == "phi")
3372  {
3373  const volVectorField& U = meshPtr_->thisDb().lookupObject<volVectorField>("U");
3374  state = linearInterpolate(U) & meshPtr_->Sf();
3375  }
3376  }
3377 
3378  // we need to also update the BC and update all the intermediate variables
3380 }
3381 
3383 {
3384  /*
3385  Description:
3386  check if the state variables have valid values, if yes, return 1
3387  */
3388 
3389  label fail = 0;
3390 
3391  forAll(stateInfo_["volVectorStates"], idxI)
3392  {
3393  const word stateName = stateInfo_["volVectorStates"][idxI];
3394  const volVectorField& state = meshPtr_->thisDb().lookupObject<volVectorField>(stateName);
3395  fail += this->validateVectorField(state);
3396  }
3397 
3398  forAll(stateInfo_["volScalarStates"], idxI)
3399  {
3400  const word stateName = stateInfo_["volScalarStates"][idxI];
3401  const volScalarField& state = meshPtr_->thisDb().lookupObject<volScalarField>(stateName);
3402  fail += this->validateField(state);
3403  }
3404 
3405  forAll(stateInfo_["modelStates"], idxI)
3406  {
3407  const word stateName = stateInfo_["modelStates"][idxI];
3408  const volScalarField& state = meshPtr_->thisDb().lookupObject<volScalarField>(stateName);
3409  fail += this->validateField(state);
3410  }
3411 
3412  forAll(stateInfo_["surfaceScalarStates"], idxI)
3413  {
3414  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
3415  const surfaceScalarField& state = meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName);
3416  fail += this->validateField(state);
3417  }
3418 
3419  reduce(fail, sumOp<label>());
3420 
3421  if (fail > 0)
3422  {
3423  Info << "*************************************** Warning! ***************************************" << endl;
3424  Info << "Invalid values found. Return primal failure and reset the states to their initial values" << endl;
3425  Info << "*************************************** Warning! ***************************************" << endl;
3426  this->resetStateVals();
3427  return 1;
3428  }
3429  else
3430  {
3431  return 0;
3432  }
3433 }
3434 
3436  const word name,
3437  const double* dFdXs,
3438  const double* Xs,
3439  const label size,
3440  const double timeName)
3441 {
3442  /*
3443  Description:
3444  write the sensitivity map for all wall surfaces
3445 
3446  Inputs:
3447  name: the name of the sens map written tot the disk
3448 
3449  dFdXs: the derivative of an objective function wrt the surface point coordinates (flatten)
3450 
3451  Xs: flatten surface point coordinates
3452 
3453  size: the size of the dFdXs array
3454 
3455  timeName: the name of time folder to write sens
3456  */
3457 
3458  volVectorField sens(
3459  IOobject(
3460  name,
3461  meshPtr_->time().timeName(),
3462  meshPtr_(),
3463  IOobject::NO_READ,
3464  IOobject::NO_WRITE),
3465  meshPtr_(),
3466  dimensionedVector(name, dimensionSet(0, 0, 0, 0, 0, 0, 0), vector::zero),
3467  "fixedValue");
3468 
3469  forAll(sens.boundaryField(), patchI)
3470  {
3471  if (meshPtr_->boundaryMesh()[patchI].type() == "wall")
3472  {
3473  forAll(sens.boundaryField()[patchI], faceI)
3474  {
3475  sens.boundaryFieldRef()[patchI][faceI] = vector::zero;
3476  }
3477  }
3478  }
3479 
3480  label nSurfPoints = round(size / 3);
3481 
3482  List<point> surfCoords(nSurfPoints);
3483  List<vector> surfdFdXs(nSurfPoints);
3484  label counterI = 0;
3485  forAll(surfCoords, pointI)
3486  {
3487  for (label i = 0; i < 3; i++)
3488  {
3489  surfCoords[pointI][i] = Xs[counterI];
3490  surfdFdXs[pointI][i] = dFdXs[counterI];
3491  counterI++;
3492  }
3493  }
3494 
3495  pointField meshPoints = meshPtr_->points();
3496 
3497  scalar distance;
3498  scalar minDistanceNorm = 0.0;
3499  // loop over all boundary points
3500  forAll(meshPtr_->boundaryMesh(), patchI)
3501  {
3502  if (meshPtr_->boundaryMesh()[patchI].type() == "wall")
3503  {
3504  forAll(meshPtr_->boundaryMesh()[patchI], faceI)
3505  {
3506  forAll(meshPtr_->boundaryMesh()[patchI][faceI], pointI)
3507  {
3508  // for each point on the boundary, search for the closest point from surfCoords
3509  label faceIPointIndexI = meshPtr_->boundaryMesh()[patchI][faceI][pointI];
3510  scalar minDistance = 9999999;
3511  label minPointJ = -1;
3512  forAll(surfCoords, pointJ)
3513  {
3514  distance = mag(surfCoords[pointJ] - meshPoints[faceIPointIndexI]);
3515  if (distance < minDistance)
3516  {
3517  minDistance = distance;
3518  minPointJ = pointJ;
3519  }
3520  }
3521 
3522  minDistanceNorm += minDistance * minDistance;
3523 
3524  // add the sensitivty to the corresponding faces
3525  sens.boundaryFieldRef()[patchI][faceI] += surfdFdXs[minPointJ];
3526  }
3527  }
3528  }
3529  }
3530 
3531  minDistanceNorm = sqrt(minDistanceNorm);
3532 
3533  // finally, we need to divide the sens by the number of points for each face
3534  forAll(sens.boundaryField(), patchI)
3535  {
3536  if (meshPtr_->boundaryMesh()[patchI].type() == "wall")
3537  {
3538  forAll(sens.boundaryField()[patchI], faceI)
3539  {
3540  sens.boundaryFieldRef()[patchI][faceI] /= sens.boundaryFieldRef()[patchI][faceI].size();
3541  }
3542  }
3543  }
3544 
3545  Info << "Writing sensitivty map for " << name << endl;
3546  Info << "minDistance Norm: " << minDistanceNorm << endl;
3547 
3548  // switch to timeName and write sens, then reset the time
3549  scalar t = runTimePtr_->timeOutputValue();
3550  label timeIndex = runTimePtr_->timeIndex();
3551 
3552  runTimePtr_->setTime(timeName, timeIndex);
3553  sens.write();
3554  runTimePtr_->setTime(t, timeIndex);
3555 }
3556 
3558  const word name,
3559  const double* dFdField,
3560  const word fieldType,
3561  const double timeName)
3562 {
3563  /*
3564  Description:
3565  write the sensitivity map for the entire field
3566 
3567  Inputs:
3568  name: the name of the sens map written tot the disk
3569 
3570  dFdField: the derivative of an objective function wrt the field variable
3571 
3572  fieldType: scalar or vector for the field variable
3573 
3574  timeName: the name of time folder to write sens
3575  */
3576 
3577  if (fieldType == "scalar")
3578  {
3579  volScalarField sens(
3580  IOobject(
3581  name,
3582  meshPtr_->time().timeName(),
3583  meshPtr_(),
3584  IOobject::NO_READ,
3585  IOobject::NO_WRITE),
3586  meshPtr_(),
3587  dimensionedScalar(name, dimensionSet(0, 0, 0, 0, 0, 0, 0), 0.0),
3588  "fixedValue");
3589 
3590  forAll(sens, cellI)
3591  {
3592  sens[cellI] = dFdField[cellI];
3593  }
3594 
3595  Info << "Writing sensitivty map for " << name << endl;
3596 
3597  // switch to timeName and write sens, then reset the time
3598  scalar t = runTimePtr_->timeOutputValue();
3599  label timeIndex = runTimePtr_->timeIndex();
3600 
3601  runTimePtr_->setTime(timeName, timeIndex);
3602  sens.correctBoundaryConditions();
3603  sens.write();
3604 
3605  runTimePtr_->setTime(t, timeIndex);
3606  }
3607  else if (fieldType == "vector")
3608  {
3609  volVectorField sens(
3610  IOobject(
3611  name,
3612  meshPtr_->time().timeName(),
3613  meshPtr_(),
3614  IOobject::NO_READ,
3615  IOobject::NO_WRITE),
3616  meshPtr_(),
3617  dimensionedVector(name, dimensionSet(0, 0, 0, 0, 0, 0, 0), vector::zero),
3618  "fixedValue");
3619 
3620  label counterI = 0;
3621  forAll(sens, cellI)
3622  {
3623  for (label i = 0; i < 3; i++)
3624  {
3625  sens[cellI][i] = dFdField[counterI];
3626  counterI++;
3627  }
3628  }
3629 
3630  Info << "Writing sensitivty map for " << name << endl;
3631 
3632  // switch to timeName and write sens, then reset the time
3633  scalar t = runTimePtr_->timeOutputValue();
3634  label timeIndex = runTimePtr_->timeIndex();
3635 
3636  runTimePtr_->setTime(timeName, timeIndex);
3637  sens.correctBoundaryConditions();
3638  sens.write();
3639 
3640  runTimePtr_->setTime(t, timeIndex);
3641  }
3642  else
3643  {
3644  FatalErrorIn("DASolver::writeSensMapField")
3645  << "fieldType not supported!"
3646  << abort(FatalError);
3647  }
3648 }
3649 
3651  const word function,
3652  const double writeTime,
3653  const double* psi)
3654 {
3655  /*
3656  Description:
3657  write the adjoint variables to the disk as OpenFOAM variables so they can be viewed
3658  in ParaView
3659 
3660  Inputs:
3661  writeTime: solution time the fields will be saved to
3662  psi: the adjoint vector array, computed in the Python layer
3663  */
3664 
3665  Info << "Writting adjoint fields " << endl;
3666 
3667  runTimePtr_->setTime(writeTime, 0);
3668 
3669  forAll(stateInfo_["volVectorStates"], idxI)
3670  {
3671  const word stateName = stateInfo_["volVectorStates"][idxI];
3672  const volVectorField& state = meshPtr_->thisDb().lookupObject<volVectorField>(stateName);
3673  word varName = "adjoint_" + function + "_" + stateName;
3674  volVectorField adjointVar(varName, state);
3675  forAll(state, cellI)
3676  {
3677  for (label i = 0; i < 3; i++)
3678  {
3679  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI, i);
3680  adjointVar[cellI][i] = psi[localIdx];
3681  }
3682  }
3683  adjointVar.correctBoundaryConditions();
3684  adjointVar.write();
3685  }
3686 
3687  forAll(stateInfo_["volScalarStates"], idxI)
3688  {
3689  const word stateName = stateInfo_["volScalarStates"][idxI];
3690  const volScalarField& state = meshPtr_->thisDb().lookupObject<volScalarField>(stateName);
3691  word varName = "adjoint_" + function + "_" + stateName;
3692  volScalarField adjointVar(varName, state);
3693  forAll(state, cellI)
3694  {
3695  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
3696  adjointVar[cellI] = psi[localIdx];
3697  }
3698  adjointVar.correctBoundaryConditions();
3699  adjointVar.write();
3700  }
3701 
3702  forAll(stateInfo_["modelStates"], idxI)
3703  {
3704  const word stateName = stateInfo_["modelStates"][idxI];
3705  const volScalarField& state = meshPtr_->thisDb().lookupObject<volScalarField>(stateName);
3706  word varName = "adjoint_" + function + "_" + stateName;
3707  volScalarField adjointVar(varName, state);
3708  forAll(state, cellI)
3709  {
3710  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, cellI);
3711  adjointVar[cellI] = psi[localIdx];
3712  }
3713  adjointVar.correctBoundaryConditions();
3714  adjointVar.write();
3715  }
3716 
3717  forAll(stateInfo_["surfaceScalarStates"], idxI)
3718  {
3719  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
3720  const surfaceScalarField& state = meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName);
3721  word varName = "adjoint_" + function + "_" + stateName;
3722  surfaceScalarField adjointVar(varName, state);
3723 
3724  forAll(meshPtr_->faces(), faceI)
3725  {
3726  label localIdx = daIndexPtr_->getLocalAdjointStateIndex(stateName, faceI);
3727 
3728  if (faceI < daIndexPtr_->nLocalInternalFaces)
3729  {
3730  adjointVar[faceI] = psi[localIdx];
3731  }
3732  else
3733  {
3734  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
3735  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
3736  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
3737  adjointVar.boundaryFieldRef()[patchIdx][faceIdx] = psi[localIdx];
3738  }
3739  }
3740  adjointVar.write();
3741  }
3742 }
3743 
3745 {
3746  // whether the volCoord input is defined
3747  dictionary allOptions = daOptionPtr_->getAllOptions();
3748  label hasVolCoordInput = 0;
3749  forAll(allOptions.subDict("inputInfo").toc(), idxI)
3750  {
3751  word inputName = allOptions.subDict("inputInfo").toc()[idxI];
3752  word inputType = allOptions.subDict("inputInfo").subDict(inputName).getWord("type");
3753  if (inputType == "volCoord")
3754  {
3755  hasVolCoordInput = 1;
3756  }
3757  }
3758  return hasVolCoordInput;
3759 }
3760 
3762 {
3763  /*
3764  Description:
3765  Resetting internal info in fvMesh, which is needed for multiple primal runs
3766  For example, after one primal run, the mesh points is the final time t = N*dt
3767  If we directly start the primal again, the meshPhi from t=dt will use the mesh
3768  info from t=N*dt as the previous time step, this is wrong. Similary things happen
3769  for V0, V00 calculation. Therefore, to fix this problem, we need to call
3770  movePoints multiple times to rest all the internal info in fvMesh to t=0
3771  */
3772  label ddtSchemeOrder = this->getDdtSchemeOrder();
3773  label hasVolCoordInput = this->hasVolCoordInput();
3774 
3775  if (hasVolCoordInput)
3776  {
3777  // if we have volCoord as the input, the fvMesh is assigned by external components
3778  // such as IDWarap, and it is should be at t=0 already, so
3779  // we just call movePoints multiple times (depending on ddtSchemeOrder)
3780  // NOTE: we just use the i index starting from 100 to avoid conflict with
3781  // the starting timeIndex=1 because if the timeIndex equals the fvMesh's
3782  // internal timeIndex counter, it will not reset the internal mesh info
3783  pointField points0 = meshPtr_->points();
3784  for (label i = 100; i <= 100 + ddtSchemeOrder; i++)
3785  {
3786  runTimePtr_->setTime(0.0, i);
3787  meshPtr_->movePoints(points0);
3788  }
3789  }
3790  else
3791  {
3792  // if there is no volCoord as the input, the fvMesh is at t=N*dt
3793  // in this case, we need to use the initial mesh save in points0Ptr_
3794  for (label i = 100; i <= 100 + ddtSchemeOrder; i++)
3795  {
3796  runTimePtr_->setTime(0.0, i);
3797  meshPtr_->movePoints(points0Ptr_());
3798  }
3799  }
3800 
3801  // finally, reset the time to 0
3802  runTimePtr_->setTime(0.0, 0);
3803 }
3804 
3806 {
3807  // assign the mean states values to states
3808  forAll(stateInfo_["volVectorStates"], idxI)
3809  {
3810  const word stateName = stateInfo_["volVectorStates"][idxI];
3811  volVectorField& state = const_cast<volVectorField&>(
3812  meshPtr_->thisDb().lookupObject<volVectorField>(stateName));
3813  word meanStateName = stateName + "Mean";
3814  const volVectorField& stateMean =
3815  meshPtr_->thisDb().lookupObject<volVectorField>(meanStateName);
3816  forAll(state, cellI)
3817  {
3818  for (label i = 0; i < 3; i++)
3819  {
3820  state[cellI][i] = stateMean[cellI][i];
3821  }
3822  }
3823  state.correctBoundaryConditions();
3824  }
3825 
3826  forAll(stateInfo_["volScalarStates"], idxI)
3827  {
3828  const word stateName = stateInfo_["volScalarStates"][idxI];
3829  volScalarField& state = const_cast<volScalarField&>(
3830  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
3831  word meanStateName = stateName + "Mean";
3832  const volScalarField& stateMean =
3833  meshPtr_->thisDb().lookupObject<volScalarField>(meanStateName);
3834  forAll(state, cellI)
3835  {
3836  state[cellI] = stateMean[cellI];
3837  }
3838  state.correctBoundaryConditions();
3839  }
3840 
3841  forAll(stateInfo_["modelStates"], idxI)
3842  {
3843  const word stateName = stateInfo_["modelStates"][idxI];
3844  volScalarField& state = const_cast<volScalarField&>(
3845  meshPtr_->thisDb().lookupObject<volScalarField>(stateName));
3846  word meanStateName = stateName + "Mean";
3847  const volScalarField& stateMean =
3848  meshPtr_->thisDb().lookupObject<volScalarField>(meanStateName);
3849  forAll(state, cellI)
3850  {
3851  state[cellI] = stateMean[cellI];
3852  }
3853  state.correctBoundaryConditions();
3854  }
3855 
3856  forAll(stateInfo_["surfaceScalarStates"], idxI)
3857  {
3858  const word stateName = stateInfo_["surfaceScalarStates"][idxI];
3859  surfaceScalarField& state = const_cast<surfaceScalarField&>(
3860  meshPtr_->thisDb().lookupObject<surfaceScalarField>(stateName));
3861  word meanStateName = stateName + "Mean";
3862  const surfaceScalarField& stateMean =
3863  meshPtr_->thisDb().lookupObject<surfaceScalarField>(meanStateName);
3864 
3865  forAll(meshPtr_->faces(), faceI)
3866  {
3867  if (faceI < daIndexPtr_->nLocalInternalFaces)
3868  {
3869  state[faceI] = stateMean[faceI];
3870  }
3871  else
3872  {
3873  label relIdx = faceI - daIndexPtr_->nLocalInternalFaces;
3874  label patchIdx = daIndexPtr_->bFacePatchI[relIdx];
3875  label faceIdx = daIndexPtr_->bFaceFaceI[relIdx];
3876  state.boundaryFieldRef()[patchIdx][faceIdx] = stateMean.boundaryField()[patchIdx][faceIdx];
3877  }
3878  }
3879  }
3880 
3881  // we need to also update the BC for other variables
3883 }
3884 
3886 {
3887  /*
3888  Description:
3889  initialize inputFieldUnsteady from the GlobalVar class
3890  */
3891 
3892  DAGlobalVar& globalVar =
3893  const_cast<DAGlobalVar&>(meshPtr_->thisDb().lookupObject<DAGlobalVar>("DAGlobalVar"));
3894 
3895  dictionary inputInfoDict = daOptionPtr_->getAllOptions().subDict("inputInfo");
3896  forAll(inputInfoDict.toc(), idxI)
3897  {
3898  word inputName = inputInfoDict.toc()[idxI];
3899  word inputType = inputInfoDict.subDict(inputName).getWord("type");
3900  if (inputType == "fieldUnsteady")
3901  {
3902  label stepInterval = inputInfoDict.subDict(inputName).getLabel("stepInterval");
3903  scalar endTime = meshPtr_->time().endTime().value();
3904  scalar deltaT = meshPtr_->time().deltaT().value();
3905  label nSteps = round(endTime / deltaT);
3906  word interpMethod = inputInfoDict.subDict(inputName).getWord("interpolationMethod");
3907  label nParameters = -1;
3908  if (interpMethod == "linear")
3909  {
3910  nParameters = nSteps / stepInterval + 1;
3911  }
3912  else if (interpMethod == "rbf")
3913  {
3914  nParameters = 2 * (nSteps / stepInterval + 1);
3915  }
3916 
3917  // NOTE: inputFieldUnsteady is alway local!
3918  scalarList initVal(nParameters * meshPtr_->nCells(), 0.0);
3919  globalVar.inputFieldUnsteady.set(inputName, initVal);
3920  }
3921  }
3922 }
3923 
3925 {
3926  /*
3927  Description:
3928  Assign the inputFieldUnsteady values to the OF field vars
3929 
3930  For linear interpolation, the filed u are saved in this format
3931 
3932  ------ t = 0 ------|---- t=1interval ---|---- t=2interval ---
3933  u1, u2, u3, ... un | u1, u2, u3, ... un | u1, u2, u3, ... un
3934 
3935  For rbf interpolation, the data are saved in this format, here w and s are the parameters for rbf
3936 
3937  ------ t = 0 ------|---- t=1interval ---|---- t=2interval ---|------ t = 0 ------|---- t=1interval ---|---- t=2interval --
3938  w1, w2, w3, ... wn | w1, w2, w3, ... wn | w1, w2, w3, ... wn |s1, s2, s3, ... sn |s1, s2, s3, ... sn | s1, s2, s3, ... sn|
3939 
3940  */
3941 
3942  DAGlobalVar& globalVar =
3943  const_cast<DAGlobalVar&>(meshPtr_->thisDb().lookupObject<DAGlobalVar>("DAGlobalVar"));
3944 
3945  if (globalVar.inputFieldUnsteady.size() == 0)
3946  {
3947  return;
3948  }
3949 
3950  forAll(globalVar.inputFieldUnsteady.toc(), idxI)
3951  {
3952  word inputName = globalVar.inputFieldUnsteady.toc()[idxI];
3953  const dictionary& subDict = daOptionPtr_->getAllOptions().subDict("inputInfo").subDict(inputName);
3954  word fieldName = subDict.getWord("fieldName");
3955  word fieldType = subDict.getWord("fieldType");
3956  label stepInterval = subDict.getLabel("stepInterval");
3957  word interpMethod = subDict.getWord("interpolationMethod");
3958 
3959  label timeIndex = runTimePtr_->timeIndex();
3960 
3961  if (fieldType == "scalar")
3962  {
3963  volScalarField& field =
3964  const_cast<volScalarField&>(meshPtr_->thisDb().lookupObject<volScalarField>(fieldName));
3965 
3966  // linear interpolation
3967  if (interpMethod == "linear")
3968  {
3969  label timeR = timeIndex % stepInterval;
3970  label timeI = timeIndex / stepInterval;
3971  // set the initial index for the counter
3972  label counterI = timeI * meshPtr_->nCells();
3973  label deltaI = meshPtr_->nCells();
3974  forAll(field, cellI)
3975  {
3976  scalar val1 = globalVar.inputFieldUnsteady[inputName][counterI];
3977  if (timeR == 0)
3978  {
3979  // this should be the anchor field per stepInterval, no need to interpolate.
3980  field[cellI] = val1;
3981  }
3982  else
3983  {
3984  // we interpolate using counterI and counterI+deltaI
3985  label counterINextField = counterI + deltaI;
3986  scalar val2 = globalVar.inputFieldUnsteady[inputName][counterINextField];
3987  field[cellI] = val1 + (val2 - val1) * timeR / stepInterval;
3988  }
3989  counterI++;
3990  }
3991  }
3992  else if (interpMethod == "rbf")
3993  {
3994  scalar offset = subDict.getScalar("offset");
3995  scalar endTime = meshPtr_->time().endTime().value();
3996  scalar deltaT = meshPtr_->time().deltaT().value();
3997  label nSteps = round(endTime / deltaT);
3998  label nFields = nSteps / stepInterval + 1;
3999 
4000  forAll(field, cellI)
4001  {
4002  field[cellI] = offset;
4003  }
4004  // rbf interpolation y = f(t)
4005  // y = sum_i ( w_i * exp(-s_i^2 * (t-c)^2 ) )
4006  // here c is the interpolation point from 0 to T with an interval of stepInterval
4007  label halfSize = globalVar.inputFieldUnsteady[inputName].size() / 2;
4008  label deltaI = nFields * meshPtr_->nCells();
4009  for (label i = 0; i < halfSize; i++)
4010  {
4011  label cellI = i % meshPtr_->nCells();
4012  scalar w = globalVar.inputFieldUnsteady[inputName][i];
4013  scalar s = globalVar.inputFieldUnsteady[inputName][i + deltaI];
4014  label interpTimeIndex = i / meshPtr_->nCells() * stepInterval;
4015  scalar d = (timeIndex - interpTimeIndex);
4016  field[cellI] += w * exp(-s * s * d * d);
4017  }
4018  }
4019  field.correctBoundaryConditions();
4020  }
4021  else
4022  {
4023  FatalErrorIn("") << "fieldType not valid" << exit(FatalError);
4024  }
4025  }
4026 }
4027 
4028 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
4029 
4030 } // End namespace Foam
4031 
4032 // ************************************************************************* //
Foam::DASolver::writeSensMapField
void writeSensMapField(const word name, const double *dFdField, const word fieldType, const double timeName)
write the sensitivity map for the entire field
Definition: DASolver.C:3557
Foam::DASolver::writeFailedMesh
void writeFailedMesh()
write the failed mesh to disk
Definition: DASolver.C:3172
Foam::DAJacCon
Definition: DAJacCon.H:35
Foam::DAJacCon::initializeJacCon
void initializeJacCon(const dictionary &options)
initialize the state Jacobian connectivity matrix
Definition: DAJacCon.C:254
Foam::DASolver::calcCouplingFaceCoords
void calcCouplingFaceCoords(const scalar *volCoords, scalar *surfCoords)
return the face coordinates based on vol coords
Definition: DASolver.C:1592
U
volVectorField & U
Definition: createRefsPimpleDyM.H:7
Foam::DASolver::checkPrimalFailure
label checkPrimalFailure()
check whether the primal fails based on residual and regression fail flag
Definition: DASolver.C:2472
Foam::DASolver::getInputDistributed
label getInputDistributed(const word inputName, const word inputType)
get whether the input is distributed among processors
Definition: DASolver.C:1411
Foam::DASolver::updateStateBoundaryConditions
void updateStateBoundaryConditions()
update the boundary conditions for all states and intermediate variables
Definition: DASolver.C:2608
Foam::DAUtility::pyCalcBetaJacVecProdInterface
static pyJacVecProdInterface pyCalcBetaJacVecProdInterface
Definition: DAUtility.H:121
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:147
Foam::DASolver::printToScreen_
label printToScreen_
whether to print primal information to the screen
Definition: DASolver.H:150
Foam::DASolver::calcdRdWOldTPsiAD
void calcdRdWOldTPsiAD(const label oldTimeLevel, const double *psi, double *dRdWOldTPsi)
compute dRdWOld^T*Psi
Definition: DASolver.C:1661
Foam::DASolver::stateInfo_
HashTable< wordList > stateInfo_
the stateInfo_ list from DAStateInfo object
Definition: DASolver.H:120
Foam::DASolver::initDynamicMesh
void initDynamicMesh()
resetting internal info in fvMesh, which is needed for multiple primal runs
Definition: DASolver.C:3761
Foam::DAUtility::pyCalcBeta
static void * pyCalcBeta
define a function pointer template for Python call back
Definition: DAUtility.H:117
Foam::DASolver::daRegressionPtr_
autoPtr< DARegression > daRegressionPtr_
DARegression pointer.
Definition: DASolver.H:111
Foam::DASolver::daModelPtr_
autoPtr< DAModel > daModelPtr_
DAModel pointer.
Definition: DASolver.H:84
Foam::DASolver::runColoring
void runColoring()
run the coloring solver
Definition: DASolver.C:554
Foam::DAUtility::pyCalcBetaInterface
static pyComputeInterface pyCalcBetaInterface
Definition: DAUtility.H:118
Foam::DASolver::initInputFieldUnsteady
void initInputFieldUnsteady()
initialize inputFieldUnsteady from the GlobalVar class
Definition: DASolver.C:3885
Foam::DASolver::getInputSize
label getInputSize(const word inputName, const word inputType)
get the array size of an input type
Definition: DASolver.C:1348
Foam::DASolver::registerStateVariableInput4AD
void registerStateVariableInput4AD(const label oldTimeLevel=0)
register all state variables as the input for reverse-mode AD
Definition: DASolver.C:1722
Foam::DASolver::hasVolCoordInput
label hasVolCoordInput()
whether the volCoord input is defined
Definition: DASolver.C:3744
Foam::DASolver::daIndexPtr_
autoPtr< DAIndex > daIndexPtr_
DAIndex pointer.
Definition: DASolver.H:87
Foam::DASolver::primalFinalTimeIndex_
label primalFinalTimeIndex_
the final time index from the primal solve. for steady state cases it can converge before endTime
Definition: DASolver.H:168
Foam::DASolver::calcdRdWT
void calcdRdWT(const label isPC, Mat dRdWT)
compute dRdWT
Definition: DASolver.C:782
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:422
URel
volVectorField & URel
Definition: createRefsTurbo.H:10
Foam::DASolver::daResidualPtr_
autoPtr< DAResidual > daResidualPtr_
DAResidual pointer.
Definition: DASolver.H:105
D
volVectorField & D
Definition: createRefsSolidDisplacement.H:8
Foam::DASolver::normalizeGradientVec
void normalizeGradientVec(double *vecY)
normalize the reverse-mode AD derivatives stored in vecY
Definition: DASolver.C:2107
Foam::DASolver::initializedRdWTMatrixFree
void initializedRdWTMatrixFree()
initialize matrix free dRdWT
Definition: DASolver.C:1076
Foam::DASolver::destroydRdWTMatrixFree
void destroydRdWTMatrixFree()
destroy the matrix free dRdWT
Definition: DASolver.C:1108
Foam::DASolver::getTimeOpFuncVal
double getTimeOpFuncVal(const word functionName)
get the function value based on timeOp
Definition: DASolver.C:261
Foam::DASolver::primalMinResTol_
scalar primalMinResTol_
primal residual tolerance
Definition: DASolver.H:147
Foam::DASolver
Definition: DASolver.H:54
Foam::pySetCharInterface
void(* pySetCharInterface)(const char *, void *)
Definition: DAUtility.H:29
Foam::DASolver::updateOFMesh
void updateOFMesh(const scalar *volCoords)
Update the OpenFoam mesh point coordinates based on the volume point coords array.
Definition: DASolver.C:1057
Foam::DAOption
Definition: DAOption.H:29
setRootCasePython.H
Foam::DASolver::calcOutput
void calcOutput(const word outputName, const word outputType, double *output)
get whether the output is distributed among processors
Definition: DASolver.C:1383
Foam::DASolver::daFieldPtr_
autoPtr< DAField > daFieldPtr_
DAField pointer.
Definition: DASolver.H:90
Foam::DASolver::initializeGlobalADTape4dRdWT
void initializeGlobalADTape4dRdWT()
initialize the CoDiPack reverse-mode AD global tape for computing dRdWT*psi
Definition: DASolver.C:1166
Foam::DASolver::getDdtSchemeOrder
label getDdtSchemeOrder()
get the ddtScheme order
Definition: DASolver.H:230
Foam::DASolver::assignVec2ResidualGradient
void assignVec2ResidualGradient(const double *vecX)
assign the reverse-mode AD input seeds from vecX to the residuals in OpenFOAM
Definition: DASolver.C:2208
Foam::DAUtility::pyCalcBetaJacVecProd
static void * pyCalcBetaJacVecProd
Definition: DAUtility.H:120
Foam::DAUtility::pySetModelNameInterface
static pySetCharInterface pySetModelNameInterface
Definition: DAUtility.H:124
Foam::DAJacCon::setupJacCon
void setupJacCon(const dictionary &options)
assign 1 to all non-zero elements for the Jacobian connecitivyt matrix
Definition: DAJacCon.C:286
Foam::DASolver::daOptionPtr_
autoPtr< DAOption > daOptionPtr_
DAOption pointer.
Definition: DASolver.H:81
Foam::DAInput::New
static autoPtr< DAInput > New(const word inputName, const word inputType, fvMesh &mesh, const DAOption &daOption, const DAModel &daModel, const DAIndex &daIndex)
Definition: DAInput.C:44
MRF
IOMRFZoneListDF & MRF
Definition: createRefsRhoSimple.H:18
Foam::DAUtility::angleOfAttackRadForwardAD
static scalar angleOfAttackRadForwardAD
angle of attack in radian used in forward mode AD
Definition: DAUtility.H:114
Foam::DAPartDeriv::initializePartDerivMat
void initializePartDerivMat(const dictionary &options, Mat jacMat)
initialize partial derivative matrix
Definition: DAPartDeriv.C:317
Foam::DASolver::pyOptions_
PyObject * pyOptions_
all options in DAFoam
Definition: DASolver.H:69
Foam::DASolver::registerResidualOutput4AD
void registerResidualOutput4AD()
register all residuals as the output for reverse-mode AD
Definition: DASolver.C:2035
createMeshPython.H
Foam::DASolver::calcJacTVecProduct
void calcJacTVecProduct(const word inputName, const word inputType, const double *input, const word outputName, const word outputType, const double *seed, double *product)
calculate the Jacobian-matrix-transposed and vector product for product = [dOutput/dInput]^T * seed
Definition: DASolver.C:1445
Foam::DASolver::getOFMeshPoints
void getOFMeshPoints(double *points)
get the flatten mesh points coordinates
Definition: DASolver.C:991
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:591
Foam::DASolver::updateKSPPCMat
void updateKSPPCMat(Mat PCMat, KSP ksp)
Update the preconditioner matrix for the ksp object.
Definition: DASolver.C:925
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:955
Foam::DASolver::resetStateVals
void resetStateVals()
reset the states to its initial values this usually happens when we have nan in states
Definition: DASolver.C:3310
Foam::DASolver::readMeshPoints
void readMeshPoints(const scalar timeVal)
read the mesh points from the disk and run movePoints to deform the mesh
Definition: DASolver.C:2875
Foam::DAJacCon::clear
void clear()
clear members in parent and child objects
Definition: DAJacCon.C:2966
Foam::DASolver::setDAFunctionList
void setDAFunctionList()
initialize DASolver::daFunctionPtrList_ one needs to call this before calling printAllFunctions
Definition: DASolver.C:330
Foam::DASolver::globalADTape4dRdWTInitialized
label globalADTape4dRdWTInitialized
a flag in dRdWTMatVecMultFunction to determine if the global tap is initialized
Definition: DASolver.H:144
Foam::DASolver::getInitStateVals
void getInitStateVals(HashTable< scalar > &initState)
calculate the initial value for validate states
Definition: DASolver.C:3240
Foam::DASolver::daFunctionPtrList_
UPtrList< DAFunction > daFunctionPtrList_
a list of DAFunction pointers
Definition: DASolver.H:93
Foam::DASolver::printIntervalUnsteady_
label printIntervalUnsteady_
how frequent do you want to print the primal info default is every 100 steps
Definition: DASolver.H:162
Foam::DASolver::writeMeshPoints
void writeMeshPoints(const double *points, const scalar timeVal)
write the mesh points to the disk for the given timeVal
Definition: DASolver.C:2898
Foam::DAFunction::getFunctionTimeOp
word getFunctionTimeOp()
return the time operator such as final, sum, average, variance
Definition: DAFunction.H:154
Foam::DASolver::normalizeJacTVecProduct
void normalizeJacTVecProduct(const word inputName, double *product)
normalize the jacobian vector product that has states as the input such as dFdW and dRdW
Definition: DASolver.C:1198
mesh
fvMesh & mesh
Definition: createRefsHeatTransfer.H:4
Foam::DASolver::daTimeOpPtrList_
UPtrList< DATimeOp > daTimeOpPtrList_
a list of DATimeOp pointers
Definition: DASolver.H:96
Foam::DASolver::isPrintTime
label isPrintTime(const Time &runTime, const label printInterval) const
Definition: DASolver.C:2508
Foam::DAUtility::pyDict2OFDict
static void pyDict2OFDict(PyObject *pyDict, dictionary &ofDict)
convert a python dictionary object to OpenFoam dictionary
Definition: DAUtility.C:24
Foam::DAUtility::pySetModelName
static void * pySetModelName
Definition: DAUtility.H:123
Foam::DASolver::daLinearEqnPtr_
autoPtr< DALinearEqn > daLinearEqnPtr_
DALinearEqn pointer.
Definition: DASolver.H:102
Foam::DASolver::updateOFFields
void updateOFFields(const scalar *states)
Update the OpenFOAM field values (including both internal and boundary fields) based on the state arr...
Definition: DASolver.C:1044
Foam::DAFunction::getFunctionName
word getFunctionName()
return the name of objective function
Definition: DAFunction.H:142
Foam::DASolver::writeSensMapSurface
void writeSensMapSurface(const word name, const double *dFdXs, const double *Xs, const label size, const double timeName)
write the sensitivity map for all wall surfaces
Definition: DASolver.C:3435
Foam::DASolver::updateBoundaryConditions
void updateBoundaryConditions(const word fieldName, const word fieldType)
update the boundary condition for a field
Definition: DASolver.C:2559
Foam::DASolver::validateVectorField
label validateVectorField(const classType &field)
check if a field variable has nan
Definition: DASolver.H:802
Foam::DASolver::initStateVals_
HashTable< scalar > initStateVals_
initial values for validateStates
Definition: DASolver.H:171
Foam::DAOutput::New
static autoPtr< DAOutput > New(const word outputName, const word outputType, fvMesh &mesh, const DAOption &daOption, DAModel &daModel, const DAIndex &daIndex, DAResidual &daResidual, UPtrList< DAFunction > &daFunctionList)
Definition: DAOutput.C:48
Foam::defineRunTimeSelectionTable
defineRunTimeSelectionTable(DAFunction, dictionary)
Foam::DASolver::validateStates
label validateStates()
check if the state variables have valid values
Definition: DASolver.C:3382
assignValueCheckAD
#define assignValueCheckAD(valA, valB)
Definition: DAMacroFunctions.H:103
Foam::DASolver::deactivateStateVariableInput4AD
void deactivateStateVariableInput4AD(const label oldTimeLevel=0)
deactivate all state variables as the input for reverse-mode AD
Definition: DASolver.C:1879
Foam::DASolver::getOutputDistributed
label getOutputDistributed(const word outputName, const word outputType)
get whether the output is distributed among processors
Definition: DASolver.C:1427
Foam::DAGlobalVar::inputFieldUnsteady
HashTable< List< scalar > > inputFieldUnsteady
the unsteady field inputs with the key being the fieldName
Definition: DAGlobalVar.H:77
Foam::DASolver::primalMinIters_
label primalMinIters_
primal min number of iterations
Definition: DASolver.H:159
createTimePython.H
Foam::DAFunction
Definition: DAFunction.H:31
Foam::DASolver::regressionModelCompute
void regressionModelCompute()
call the compute method of the regression model
Definition: DASolver.H:653
Foam::DAFunction::calcFunction
virtual scalar calcFunction()=0
calculate the value of objective function
Foam::DASolver::calcResiduals
void calcResiduals(label isPC=0)
calculate the residuals
Definition: DASolver.C:2592
Foam::defineTypeNameAndDebug
defineTypeNameAndDebug(DAFunction, 0)
Foam::DASolver::readStateVars
void readStateVars(scalar timeVal, label oldTimeLevel=0)
read the state variables from the disk and assign the value to the prescribe time level
Definition: DASolver.C:2938
Foam::DASolver::prevPrimalSolTime_
scalar prevPrimalSolTime_
the solution time for the previous primal solution
Definition: DASolver.H:123
Foam::DASolver::validateField
label validateField(const classType &field)
check if a field variable has nan
Definition: DASolver.H:771
Foam
Definition: checkGeometry.C:32
Foam::DASolver::setSolverInput
void setSolverInput(const word inputName, const word inputType, const int inputSize, const double *input, const double *seed)
Definition: DASolver.C:1310
Foam::DASolver::writeAssociatedFields
void writeAssociatedFields()
write associated fields such as relative velocity
Definition: DASolver.C:2526
Foam::DASolver::getdFScaling
scalar getdFScaling(const word functionName, const label timeIdx)
get the scaling factor for dF/d? derivative computation
Definition: DASolver.C:302
Foam::DASolver::getOutputSize
label getOutputSize(const word outputName, const word outputType)
get the array size of an output type
Definition: DASolver.C:1364
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:1119
Foam::DATimeOp::New
static autoPtr< DATimeOp > New(const word timeOpType, const dictionary options)
Definition: DATimeOp.C:32
Foam::pyJacVecProdInterface
void(* pyJacVecProdInterface)(const double *, double *, int, const double *, const double *, int, void *)
Definition: DAUtility.H:28
Foam::DASolver::assignStateGradient2Vec
void assignStateGradient2Vec(double *vecY, const label oldTimeLevel=0)
set the reverse-mode AD derivatives from the state variables in OpenFOAM to vecY
Definition: DASolver.C:2295
allOptions
const dictionary & allOptions
Definition: createRefsPimpleDyM.H:14
Foam::DASolver::regModelFail_
label regModelFail_
whether the regModel compute fails
Definition: DASolver.H:153
Foam::DASolver::meanStatesToStates
void meanStatesToStates()
assign the mean states values to states
Definition: DASolver.C:3805
forAll
forAll(nuTilda1, cellI)
Definition: nuTilda1EqnIrkPimple.H:19
Foam::DAPartDeriv::calcPartDerivMat
void calcPartDerivMat(const dictionary &options, const Vec xvVec, const Vec wVec, Mat jacMat)
compute the partial derivative matrix
Definition: DAPartDeriv.C:350
Foam::DATurbulenceModel
Definition: DATurbulenceModel.H:52
Foam::DASolver::calcPCMatWithFvMatrix
void calcPCMatWithFvMatrix(Mat PCMat, const label turbOnly=0)
calculate the PC mat using fvMatrix
Definition: DASolver.C:2633
Foam::DATurbulenceModel::getFvMatrixFields
virtual void getFvMatrixFields(const word varName, scalarField &diag, scalarField &upper, scalarField &lower)
return the diag(), upper(), and lower() scalarFields from the turbulence model's fvMatrix
Definition: DATurbulenceModel.C:627
Foam::DAJacCon::coloringExists
label coloringExists(const word postFix="") const
whether the coloring file exists
Definition: DAJacCon.C:1886
Foam::DASolver::runTimePtr_
autoPtr< Time > runTimePtr_
runTime pointer
Definition: DASolver.H:75
Foam::DAUtility::writeMatrixBinary
static void writeMatrixBinary(const Mat matIn, const word prefix)
write petsc matrix in binary format
Definition: DAUtility.C:411
Foam::DASolver::printInterval_
label printInterval_
how frequent do you want to print the primal info default is every 100 steps
Definition: DASolver.H:156
Foam::DASolver::meshPtr_
autoPtr< fvMesh > meshPtr_
fvMesh pointer
Definition: DASolver.H:78
Foam::DASolver::solveAdjointFP
virtual label solveAdjointFP(Vec dFdW, Vec psi)
solve the adjoint equation using the fixed-point iteration method
Definition: DASolver.C:3224
Foam::DAFunction::New
static autoPtr< DAFunction > New(const fvMesh &mesh, const DAOption &daOption, const DAModel &daModel, const DAIndex &daIndex, const word functionName)
Definition: DAFunction.C:54
Foam::pyComputeInterface
void(* pyComputeInterface)(const double *, int, double *, int, void *)
Definition: DAUtility.H:27
Foam::DASolver::writeAdjointFields
void writeAdjointFields(const word function, const double writeTime, const double *psi)
write the adjoint variables for all states
Definition: DASolver.C:3650
Foam::DAJacCon::setupJacConPreallocation
void setupJacConPreallocation(const dictionary &options)
calculate the preallocation vector for initializing the JacCon mat, if necessary
Definition: DAJacCon.C:2016
Foam::DASolver::updateInputFieldUnsteady
void updateInputFieldUnsteady()
assign the inputFieldUnsteady values to the OF field vars
Definition: DASolver.C:3924
Foam::DAPartDeriv
Definition: DAPartDeriv.H:35
Foam::DAJacCon::calcJacConColoring
void calcJacConColoring(const word postFix="")
compute graph coloring for Jacobian connectivity matrix
Definition: DAJacCon.C:1917
Foam::DASolver::createMLRKSPMatrixFree
void createMLRKSPMatrixFree(const Mat jacPCMat, KSP ksp)
create a multi-level, Richardson KSP object with matrix-free Jacobians
Definition: DASolver.C:936
Foam::DASolver::setPrimalBoundaryConditions
void setPrimalBoundaryConditions(const label printInfo=1)
update the primal state boundary condition based on the primalBC dict
Definition: DASolver.C:3187
setArgs.H
Foam::DASolver::daStateInfoPtr_
autoPtr< DAStateInfo > daStateInfoPtr_
DAStateInfo pointer.
Definition: DASolver.H:108
runTime
Time & runTime
Definition: createRefsHeatTransfer.H:1
DASolver.H
Foam::DASolver::runFPAdj
virtual label runFPAdj(Vec dFdW, Vec psi)
solve the adjoint equation using the fixed-point iteration method
Definition: DASolver.C:3208
Foam::DASolver::getOFField
void getOFField(const word fieldName, const word fieldType, double *field)
get a field variable from OF layer
Definition: DASolver.C:1006
Foam::DAGlobalVar
Definition: DAGlobalVar.H:26
Foam::DASolver::points0Ptr_
autoPtr< pointField > points0Ptr_
the initial points for dynamicMesh without volCoord inputs
Definition: DASolver.H:117
Foam::DASolver::writeAdjStates
void writeAdjStates(const label writeMesh, const wordList &additionalOutput)
write state variables that are NO_WRITE to disk
Definition: DASolver.C:2778
Foam::DAJacCon::readJacConColoring
void readJacConColoring(const word postFix="")
read colors for JacCon
Definition: DAJacCon.C:1980
psi
const volScalarField & psi
Definition: createRefsRhoPimple.H:14
Foam::DASolver::dRdWTMF_
Mat dRdWTMF_
matrix-free dRdWT matrix used in GMRES solution
Definition: DASolver.H:141
Foam::DASolver::functionTimeSteps_
List< scalarList > functionTimeSteps_
a list list that saves the function value for all time steps
Definition: DASolver.H:165
dafoam_plot3dtransform.mode
mode
Definition: dafoam_plot3dtransform.py:21
Foam::DASolver::calcAllFunctions
void calcAllFunctions(label print=0)
calculate the values of all objective functions and print them to screen
Definition: DASolver.C:204
Foam::DASolver::New
static autoPtr< DASolver > New(char *argsAll, PyObject *pyOptions)
Definition: DASolver.C:104
Foam::DASolver::daGlobalVarPtr_
autoPtr< DAGlobalVar > daGlobalVarPtr_
DAGlobalVar pointer.
Definition: DASolver.H:114