optimisationManager.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2007-2023 PCOpt/NTUA
9  Copyright (C) 2013-2023 FOSS GP
10  Copyright (C) 2019-2021 OpenCFD Ltd.
11 -------------------------------------------------------------------------------
12 License
13  This file is part of OpenFOAM.
14 
15  OpenFOAM is free software: you can redistribute it and/or modify it
16  under the terms of the GNU General Public License as published by
17  the Free Software Foundation, either version 3 of the License, or
18  (at your option) any later version.
19 
20  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
21  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
22  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23  for more details.
24 
25  You should have received a copy of the GNU General Public License
26  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
27 
28 \*---------------------------------------------------------------------------*/
29 
30 #include "adjointSolverManager.H"
31 #include "dictionary.H"
32 #include "optimisationManager.H"
33 
34 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
35 
36 namespace Foam
37 {
38  defineTypeNameAndDebug(optimisationManager, 0);
40 }
41 
42 
43 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
44 
46 (
48  const label startTimeIndex,
49  const scalar endTime
50 )
51 {
52  // Does nothing in base
53 }
54 
55 
57 {
58  // Compute direction of update
59  tmp<scalarField> tdirection = dvUpdate_->computeDirection();
60  const scalarField& direction = tdirection.ref();
61 
62  // Grab reference to line search
63  autoPtr<lineSearch>& lineSrch = dvUpdate_->getLineSearch();
64 
65  // Store starting point
66  dvUpdate_->getDesignVariables()->storeDesignVariables();
67 
68  // Compute merit function before update
69  scalar meritFunction = dvUpdate_->computeMeritFunction();
70  lineSrch->setOldMeritValue(meritFunction);
71  dvUpdate_->setOldObjectiveValue();
72 
73  // Get merit function derivative
74  scalar dirDerivative =
75  dvUpdate_->meritFunctionDirectionalDerivative();
76  lineSrch->setDeriv(dirDerivative);
77  lineSrch->setDirection(direction);
78 
79  // Reset initial step.
80  // Might be interpolated from previous optimisation cycles
81  lineSrch->reset();
82 
83  // Perform line search
84  while (lineSrch->loop())
85  {
86  Info<< "\n- - - - - - - - - - - - - - -" << endl;
87  Info<< "Line search iteration " << lineSrch->innerIter() << endl;
88  Info<< "- - - - - - - - - - - - - - -\n" << endl;
89 
90  // Update design variables. Multiplication with line search step
91  // happens inside the update(direction) function
92  moveDesignVariables(direction);
93 
94  const dimensionedScalar startTime = mesh_.time();
95  const label startTimeIndex = mesh_.time().timeIndex();
96  const scalar primalEndTime = mesh_.time().endTime().value();
97  // Solve all primal equations
98  solvePrimalEquations();
99 
100  // Compute and set new merit function
101  meritFunction = dvUpdate_->computeMeritFunction();
102  lineSrch->setNewMeritValue(meritFunction);
103 
104  if (lineSrch->computeGradient())
105  {
106  // Reset adjoint sensitivities in all adjoint solver managers
107  clearSensitivities();
108 
109  // Solve all adjoint equations
110  solveAdjointEquations();
111 
112  // Update objective and gradient information known by updateMethod
113  dvUpdate_->updateGradientsAndValues();
114 
115  // Update the directional derivative
116  dirDerivative =
117  dvUpdate_->meritFunctionDirectionalDerivative();
118  lineSrch->setNewDeriv(dirDerivative);
119  }
120 
121  if (lineSrch->converged())
122  {
123  // If line search criteria have been met, proceed
124  Info<< "Line search converged in " << lineSrch->innerIter()
125  << " iterations." << endl;
126  scalarField scaledCorrection(lineSrch->step()*direction);
127  dvUpdate_->postUpdate(scaledCorrection);
128  break;
129  }
130  else
131  {
132  // If maximum number of iteration has been reached, continue
133  if (lineSrch->innerIter() == lineSrch->maxIters())
134  {
135  Info<< "Line search reached max. number of iterations.\n"
136  << "Proceeding to the next optimisation cycle" << endl;
137  scalarField scaledCorrection(lineSrch->step()*direction);
138  dvUpdate_->postUpdate(scaledCorrection);
139  }
140  // Reset to initial design variables and update step
141  else
142  {
143  //- Reset time if necessary
144  this->resetTime(startTime, startTimeIndex, primalEndTime);
145  dvUpdate_->getDesignVariables()->resetDesignVariables();
146  lineSrch->updateStep();
147  }
148  }
149  }
150 
151  // If line search did not need to get the new gradient, do it now in order
152  // to have it for the next optimisation cycle
153  if (!lineSrch->computeGradient())
154  {
155  // Reset adjoint sensitivities in all adjoint solver managers
156  clearSensitivities();
157 
158  // Solve all adjoint equations
159  solveAdjointEquations();
160  }
161 }
162 
163 
165 {
166  // Update design variables
167  if (shouldUpdateDesignVariables_)
168  {
169  moveDesignVariables();
170  }
171 
172  // Solve primal equations
173  solvePrimalEquations();
174 
175  // Reset adjoint sensitivities in all adjoint solver managers
176  clearSensitivities();
177 
178  // Solve all adjoint equations
179  solveAdjointEquations();
180 }
181 
182 
184 {
185  // Update design variables
186  dvUpdate_->update();
187 }
188 
189 
191 (
192  const scalarField& direction
193 )
194 {
195  // Update design variables
196  dvUpdate_->update(direction);
197 }
198 
199 
201 {
202  dictionary& primalSolversDict = subDict("primalSolvers");
203  const wordList& primalSolverNames = primalSolversDict.toc();
204 
205  // Construct primal solvers
206  primalSolvers_.setSize(primalSolverNames.size());
207  forAll(primalSolvers_, solveri)
208  {
209  dictionary& solverDict =
210  primalSolversDict.subDict(primalSolverNames[solveri]);
211  if (primalSolvers_.size() > 1)
212  {
213  solverDict.add<bool>("useSolverNameForFields", true);
214  }
215  primalSolvers_.set
216  (
217  solveri,
219  (
220  mesh_,
221  managerType_,
222  solverDict,
223  primalSolverNames[solveri]
224  )
225  );
226  }
227 
228  // Construct adjointSolverManagers
229  const dictionary& adjointManagersDict = subDict("adjointManagers");
230  const wordList adjointManagerNames = adjointManagersDict.toc();
231  adjointSolverManagers_.setSize(adjointManagerNames.size());
232 
233  // Determine the number of adjoint solvers which are not null (i.e. need to
234  // allocate adjoint fields). Used to determine whether the adjoint field
235  // names should be appended by the solver name
236  label nNotNullAdjointSolvers(0);
237  for (const word& adjManager : adjointManagerNames)
238  {
239  const dictionary& adjSolversDict =
240  adjointManagersDict.subDict(adjManager).subDict("adjointSolvers");
241  const wordList adjointSolverNames = adjSolversDict.toc();
242  for (const word& adjSolver : adjointSolverNames)
243  {
244  if (adjSolversDict.subDict(adjSolver).get<word>("type") != "null")
245  {
246  ++nNotNullAdjointSolvers;
247  }
248  }
249  }
250  Info<< "Found "
251  << nNotNullAdjointSolvers
252  << " adjoint solvers that allocate fields"
253  << endl;
254  bool overrideUseSolverName(nNotNullAdjointSolvers > 1);
255 
256  forAll(adjointSolverManagers_, manageri)
257  {
258  adjointSolverManagers_.set
259  (
260  manageri,
261  new adjointSolverManager
262  (
263  mesh_,
264  designVars_,
265  managerType_,
266  adjointManagersDict.subDict(adjointManagerNames[manageri]),
267  overrideUseSolverName
268  )
269  );
270  }
271 
272  // Sanity checks on the naming convention
273  if (primalSolvers_.size() > 1)
274  {
275  for (const primalSolver& solveri : primalSolvers_)
276  {
277  if (!solveri.useSolverNameForFields())
278  {
280  << "Multiple primal solvers are present but "
281  << "useSolverNameForFields is set to false in "
282  << "primal solver " << solveri.solverName() << nl
283  << "This is considered fatal."
284  << exit(FatalError);
285  }
286  }
287  }
288 
289  if (nNotNullAdjointSolvers > 1)
290  {
291  for (const adjointSolverManager& amI : adjointSolverManagers_)
292  {
293  const PtrList<adjointSolver>& adjointSolvers = amI.adjointSolvers();
294  for (const adjointSolver& asI : adjointSolvers)
295  {
296  if (!asI.useSolverNameForFields())
297  {
299  << "Multiple adjoint solvers are present but "
300  << "useSolverNameForFields is set to false in "
301  << "adjoint solver " << asI.solverName() << nl
302  << "This is considered fatal."
303  << exit(FatalError);
304  }
305  }
306  }
307  }
308  if (designVars_)
309  {
310  designVars_().addFvOptions(primalSolvers_, adjointSolverManagers_);
311  }
312 }
313 
314 
315 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
316 
317 Foam::optimisationManager::optimisationManager(fvMesh& mesh)
318 :
320  (
321  IOobject
322  (
323  "optimisationDict",
324  mesh.time().system(),
325  mesh,
326  IOobject::MUST_READ_IF_MODIFIED,
327  IOobject::NO_WRITE,
328  IOobject::REGISTER
329  )
330  ),
331  mesh_(mesh),
332  time_(const_cast<Time&>(mesh.time())),
333  designVars_
334  (
335  this->subOrEmptyDict("optimisation").isDict("designVariables") ?
337  (
338  mesh_,
339  subDict("optimisation").subDict("designVariables")
340  ) :
341  nullptr
342  ),
343  primalSolvers_(),
344  adjointSolverManagers_(),
345  managerType_(get<word>("optimisationManager")),
346  dvUpdate_(nullptr),
347  shouldUpdateDesignVariables_(true)
348 {}
350 
351 // * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * //
352 
354 (
355  fvMesh& mesh
356 )
357 {
358  const IOdictionary dict
359  (
360  IOobject
361  (
362  "optimisationDict",
363  mesh.time().system(),
364  mesh,
368  )
369  );
370 
371  const word modelType(dict.get<word>("optimisationManager"));
372 
373  Info<< "optimisationManager type : " << modelType << endl;
374 
375  auto* ctorPtr = dictionaryConstructorTable(modelType);
376 
377  if (!ctorPtr)
378  {
380  (
381  dict,
382  "optimisationManager",
383  modelType,
384  *dictionaryConstructorTablePtr_
385  ) << exit(FatalIOError);
386  }
387 
388  return autoPtr<optimisationManager>(ctorPtr(mesh));
389 }
390 
391 
392 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * //
393 
395 {
396  if (regIOobject::read())
397  {
398  // Note: Only changing existing solvers - not adding any new
399  const dictionary& primalSolversDict = subDict("primalSolvers");
400  for (primalSolver& sol : primalSolvers_)
401  {
402  sol.readDict(primalSolversDict.subDict(sol.solverName()));
403  }
404 
405  const dictionary& adjointManagersDict = subDict("adjointManagers");
406  for (adjointSolverManager& man : adjointSolverManagers_)
407  {
408  man.readDict(adjointManagersDict.subDict(man.managerName()));
409  }
410 
411  if (designVars_)
412  {
413  designVars_->readDict
414  (subDict("optimisation").subDict("designVariables"));
415  }
416 
417  return true;
418  }
420  return false;
421 }
422 
423 
425 {
426  // Update design variables using either a line-search scheme or
427  // a fixed-step update
428  if (dvUpdate_->getLineSearch())
429  {
430  lineSearchUpdate();
431  }
432  else
433  {
434  fixedStepUpdate();
435  }
436 }
437 
438 
440 {
441  // Solve all primal equations
442  forAll(primalSolvers_, psI)
443  {
444  primalSolvers_[psI].solve();
445  }
446 }
447 
448 
450 {
451  // Solve all adjoint solver equations
452  forAll(adjointSolverManagers_, amI)
453  {
454  adjointSolverManagers_[amI].solveAdjointEquations();
455  }
456 }
457 
458 
460 {
461  // Compute senstivities from all adjoint solvers
462  forAll(adjointSolverManagers_, amI)
463  {
464  adjointSolverManagers_[amI].computeAllSensitivities();
465  }
466 }
467 
468 
470 {
471  for (adjointSolverManager& adjSolvManager : adjointSolverManagers_)
472  {
473  adjSolvManager.clearSensitivities();
474  }
475 }
476 
477 
479 {
480  forAll(adjointSolverManagers_, amI)
481  {
482  PtrList<adjointSolver>& adjointSolvers =
483  adjointSolverManagers_[amI].adjointSolvers();
484 
485  forAll(adjointSolvers, asI)
486  {
487  adjointSolvers[asI].updatePrimalBasedQuantities();
488  }
489  }
490 }
491 
492 
493 // ************************************************************************* //
List< ReturnType > get(const UPtrList< T > &list, const AccessOp &aop)
List of values generated by applying the access operation to each list item.
static autoPtr< optimisationManager > New(fvMesh &mesh)
Return a reference to the selected turbulence model.
dictionary dict
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
void setDirection(const scalarField &direction)
Set direction.
Definition: lineSearch.H:239
uint8_t direction
Definition: direction.H:46
void fixedStepUpdate()
Update design variables using a fixed step.
static autoPtr< primalSolver > New(fvMesh &mesh, const word &managerType, const dictionary &dict, const word &solverName)
Return a reference to the selected primal solver.
Definition: primalSolver.C:53
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
virtual void updatePrimalBasedQuantities()
Solve all primal equations.
virtual bool read()
Read object.
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:129
virtual void updateStep()=0
Update the line search step based on the specific line search strategy, e.g. bisection, quadratic fit, etc.
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:598
Base class for primal solvers.
Definition: primalSolver.H:46
T & ref() const
Return non-const reference to the contents of a non-null managed pointer.
Definition: tmpI.H:235
Abstract base class for optimisation methods.
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:531
tmp< DimensionedField< TypeR, GeoMesh > > New(const tmp< DimensionedField< TypeR, GeoMesh >> &tf1, const word &name, const dimensionSet &dimensions, const bool initCopy=false)
Global function forwards to reuseTmpDimensionedField::New.
entry * add(entry *entryPtr, bool mergeEntry=false)
Add a new entry.
Definition: dictionary.C:625
Ignore writing from objectRegistry::writeObject()
T get(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a T. FatalIOError if not found, or if the number of tokens is incorrect.
void setNewMeritValue(const scalar value)
Set new objective value.
Definition: lineSearch.C:150
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:360
const dictionary & subDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary.
Definition: dictionary.C:441
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:69
virtual void setNewDeriv(const scalar deriv)
Set new directional derivative.
Definition: lineSearch.C:144
virtual void initialize()
Initialization. Construct primal and adjoint solvers.
virtual void setDeriv(const scalar deriv)
Set directional derivative.
Definition: lineSearch.C:137
wordList toc() const
Return the table of contents.
Definition: dictionary.C:587
IOdictionary is derived from dictionary and IOobject to give the dictionary automatic IO functionalit...
Definition: IOdictionary.H:50
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:421
void setSize(const label n)
Alias for resize()
Definition: List.H:316
dynamicFvMesh & mesh
label innerIter() const
Get inner line search iteration.
Definition: lineSearch.H:262
A class for handling words, derived from Foam::string.
Definition: word.H:63
void setOldMeritValue(const scalar value)
Set old objective value.
Definition: lineSearch.C:157
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
const word & system() const noexcept
Return system name.
Definition: TimePathsI.H:118
void lineSearchUpdate()
Update design variables using a line-search.
virtual void reset()
Reset step to initial value.
Definition: lineSearch.C:164
virtual void resetTime(const dimensionedScalar startTime, const label startTimeIndex, const scalar endTime)
Reset time.
virtual void moveDesignVariables()
Update design variables.
virtual void computeSensitivities()
Compute all adjoint sensitivities.
scalar step() const
Get current step.
Definition: lineSearch.H:278
defineRunTimeSelectionTable(reactionRateFlameArea, dictionary)
defineTypeNameAndDebug(combustionModel, 0)
virtual void solveAdjointEquations()
Solve all adjoint equations.
List< word > wordList
List of word.
Definition: fileName.H:59
Class for managing adjoint solvers, which may be more than one per operating point.
Abstract base class for defining design variables.
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:78
messageStream Info
Information stream (stdout output on master, null elsewhere)
virtual void clearSensitivities()
Clear all adjoint sensitivities.
virtual bool loop()
Return true if lineSearch should continue and if so increment inner.
Definition: lineSearch.C:198
Pointer management similar to std::unique_ptr, with some additional methods and type checking...
Definition: HashPtrTable.H:48
int system(const std::string &command, const bool bg=false)
Execute the specified command via the shell.
Definition: POSIX.C:1702
A class for managing temporary objects.
Definition: HashPtrTable.H:50
Defines the attributes of an object for which implicit objectRegistry management is supported...
Definition: IOobject.H:172
Foam::label startTime
#define FatalIOErrorInLookup(ios, lookupTag, lookupName, lookupTable)
Report an error message using Foam::FatalIOError.
Definition: error.H:635
virtual bool read()
Changes in case of run-time update of optimisationDict.
label maxIters() const
Get max number of iterations.
Definition: lineSearch.H:270
virtual bool converged()=0
Return the correction of the design variables.
Do not request registration (bool: false)
virtual void solvePrimalEquations()
Solve all primal equations.
virtual void updateDesignVariables()
Update design variables.
Namespace for OpenFOAM.
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...
virtual bool computeGradient() const
Does line search need to update the gradient?
Definition: lineSearch.C:211