ISAT.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2016-2022 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "ISAT.H"
27 #include "odeChemistryModel.H"
28 #include "LUscalarMatrix.H"
30 
31 
32 /* * * * * * * * * * * * * * * Static Member Data * * * * * * * * * * * * * */
33 
34 namespace Foam
35 {
36 namespace chemistryTabulationMethods
37 {
38  defineTypeNameAndDebug(ISAT, 0);
39  addToRunTimeSelectionTable(chemistryTabulationMethod, ISAT, dictionary);
40 }
41 }
42 
43 
44 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
45 
47 (
48  const dictionary& chemistryProperties,
49  const odeChemistryModel& chemistry
50 )
51 :
53  (
54  chemistryProperties,
55  chemistry
56  ),
57  coeffsDict_(chemistryProperties.subDict("tabulation")),
58  chemistry_(chemistry),
59  log_(coeffsDict_.lookupOrDefault<Switch>("log", false)),
60  reduction_(chemistry_.reduction()),
61  chemisTree_(*this, coeffsDict_),
62  scaleFactor_(chemistry.nEqns() + 1, 1),
63  runTime_(chemistry.time()),
64  timeSteps_(0),
65  chPMaxLifeTime_
66  (
67  coeffsDict_.lookupOrDefault("chPMaxLifeTime", INT_MAX)
68  ),
69  maxGrowth_(coeffsDict_.lookupOrDefault("maxGrowth", INT_MAX)),
70  checkEntireTreeInterval_
71  (
72  coeffsDict_.lookupOrDefault("checkEntireTreeInterval", INT_MAX)
73  ),
74  maxDepthFactor_
75  (
76  coeffsDict_.lookupOrDefault
77  (
78  "maxDepthFactor",
79  (chemisTree_.maxNLeafs() - 1)
80  /(Foam::log(scalar(chemisTree_.maxNLeafs()))/Foam::log(2.0))
81  )
82  ),
83  minBalanceThreshold_
84  (
85  coeffsDict_.lookupOrDefault
86  (
87  "minBalanceThreshold", 0.1*chemisTree_.maxNLeafs()
88  )
89  ),
90  MRURetrieve_(coeffsDict_.lookupOrDefault("MRURetrieve", false)),
91  maxMRUSize_(coeffsDict_.lookupOrDefault("maxMRUSize", 0)),
92  lastSearch_(nullptr),
93  growPoints_(coeffsDict_.lookupOrDefault("growPoints", true)),
94  tolerance_(coeffsDict_.lookupOrDefault("tolerance", 1e-4)),
95  nRetrieved_(0),
96  nGrowth_(0),
97  nAdd_(0),
98  addNewLeafCpuTime_(0),
99  growCpuTime_(0),
100  searchISATCpuTime_(0),
101  tabulationResults_
102  (
103  IOobject
104  (
105  chemistry.thermo().phasePropertyName("TabulationResults"),
106  chemistry.time().timeName(),
107  chemistry.mesh(),
110  ),
111  chemistry.mesh(),
112  scalar(0)
113  ),
114 
115  cleaningRequired_(false)
116 {
117  dictionary scaleDict(coeffsDict_.subDict("scaleFactor"));
118  label Ysize = chemistry_.Y().size();
119  scalar otherScaleFactor = scaleDict.lookup<scalar>("otherSpecies");
120  for (label i=0; i<Ysize; i++)
121  {
122  if (!scaleDict.found(chemistry_.Y()[i].member()))
123  {
124  scaleFactor_[i] = otherScaleFactor;
125  }
126  else
127  {
128  scaleFactor_[i] =
129  scaleDict.lookup<scalar>(chemistry_.Y()[i].member());
130  }
131  }
132  scaleFactor_[Ysize] = scaleDict.lookup<scalar>("Temperature");
133  scaleFactor_[Ysize + 1] = scaleDict.lookup<scalar>("Pressure");
134  scaleFactor_[Ysize + 2] = scaleDict.lookup<scalar>("deltaT");
135 
136  if (log_)
137  {
138  nRetrievedFile_ = chemistry.logFile("found_isat.out");
139  nGrowthFile_ = chemistry.logFile("growth_isat.out");
140  nAddFile_ = chemistry.logFile("add_isat.out");
141  sizeFile_ = chemistry.logFile("size_isat.out");
142 
143  cpuAddFile_ = chemistry.logFile("cpu_add.out");
144  cpuGrowFile_ = chemistry.logFile("cpu_grow.out");
145  cpuRetrieveFile_ = chemistry.logFile("cpu_retrieve.out");
146  }
147 }
148 
149 
150 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
151 
153 {}
154 
155 
156 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
157 
158 void Foam::chemistryTabulationMethods::ISAT::addToMRU
159 (
160  chemPointISAT* phi0
161 )
162 {
163  if (maxMRUSize_ > 0 && MRURetrieve_)
164  {
165  // First search if the chemPoint is already in the list
166  bool isInList = false;
167  typename SLList <chemPointISAT*>::iterator iter =
168  MRUList_.begin();
169  for ( ; iter != MRUList_.end(); ++iter)
170  {
171  if (iter() == phi0)
172  {
173  isInList = true;
174  break;
175  }
176  }
177  // If it is in the list, then move it to front
178  if (isInList)
179  {
180  if (iter() != MRUList_.first())
181  {
182  // iter hold the position of the element to move
183  MRUList_.remove(iter);
184 
185  // Insert the element in front of the list
186  MRUList_.insert(phi0);
187  }
188  }
189  else // chemPoint not yet in the list, iter is last
190  {
191  if (MRUList_.size() == maxMRUSize_)
192  {
193  MRUList_.remove(iter);
194  MRUList_.insert(phi0);
195  }
196  else
197  {
198  MRUList_.insert(phi0);
199  }
200  }
201  }
202 }
203 
204 
205 void Foam::chemistryTabulationMethods::ISAT::calcNewC
206 (
207  chemPointISAT* phi0,
208  const scalarField& phiq,
209  scalarField& Rphiq
210 )
211 {
212  const label nEqns = chemistry_.nEqns(); // Species, T, p
213  const List<label>& completeToSimplified = phi0->completeToSimplifiedIndex();
214 
215  const scalarField dphi(phiq - phi0->phi());
216  const scalarSquareMatrix& gradientsMatrix = phi0->A();
217 
218  // Linear extrapolation:
219  //
220  // Rphiq[i] = Rphi0[i] + (dR_i/dphi_j)*dphi[j]
221  // = Rphi0[i] + A(i, j)*dphi[j]
222  //
223 
224  Rphiq = phi0->Rphi();
225  for (label i=0; i<nEqns - 2; i++)
226  {
227  if (reduction_)
228  {
229  const label si = completeToSimplified[i];
230 
231  if (si != -1)
232  {
233  // If specie is active, or T or p, then extrapolate using the
234  // gradients matrix
235  for (label j=0; j<nEqns + 1; j++)
236  {
237  const label sj =
238  j < nEqns - 2
239  ? completeToSimplified[j]
240  : j - (nEqns - 2) + phi0->nActive();
241 
242  if (sj != -1)
243  {
244  Rphiq[i] += gradientsMatrix(si, sj)*dphi[j];
245  }
246  }
247  }
248  else
249  {
250  // If specie is inactive then use the tabulated value directly
251  Rphiq[i] += dphi[i];
252  }
253  }
254  else
255  {
256  // Extrapolate using the gradients matrix
257  for (label j=0; j<nEqns + 1; j++)
258  {
259  Rphiq[i] += gradientsMatrix(i, j)*dphi[j];
260  }
261  }
262 
263  // Clip
264  Rphiq[i] = max(0, Rphiq[i]);
265  }
266 }
267 
268 
269 bool Foam::chemistryTabulationMethods::ISAT::grow
270 (
271  chemPointISAT* phi0,
272  const scalarField& phiq,
273  const scalarField& Rphiq
274 )
275 {
276  // If the pointer to the chemPoint is nullptr, the function stops
277  if (!phi0)
278  {
279  return false;
280  }
281 
282  // Raise a flag when the chemPoint used has been grown more than the
283  // allowed number of time
284  if (phi0->nGrowth() > maxGrowth_)
285  {
286  cleaningRequired_ = true;
287  phi0->toRemove() = true;
288  return false;
289  }
290 
291  // If the solution RphiQ is still within the tolerance we try to grow it
292  // in some cases this might result in a failure and the grow function of
293  // the chemPoint returns false
294  if (phi0->checkSolution(phiq, Rphiq))
295  {
296  return phi0->grow(phiq);
297  }
298  // The actual solution and the approximation given by ISAT are too different
299  else
300  {
301  return false;
302  }
303 }
304 
305 
306 bool Foam::chemistryTabulationMethods::ISAT::cleanAndBalance()
307 {
308  bool treeModified(false);
309 
310  // Check all chemPoints to see if we need to delete some of the chemPoints
311  // according to the elapsed time and number of growths
312  chemPointISAT* x = chemisTree_.treeMin();
313  while(x != nullptr)
314  {
315  chemPointISAT* xtmp = chemisTree_.treeSuccessor(x);
316 
317  const scalar elapsedTimeSteps = timeSteps() - x->timeTag();
318 
319  if ((elapsedTimeSteps > chPMaxLifeTime_) || (x->nGrowth() > maxGrowth_))
320  {
321  chemisTree_.deleteLeaf(x);
322  treeModified = true;
323  }
324  x = xtmp;
325  }
326 
327  MRUList_.clear();
328 
329  // Check if the tree should be balanced according to criterion:
330  // -the depth of the tree bigger than a*log2(size), log2(size) being the
331  // ideal depth (e.g. 4 leafs can be stored in a tree of depth 2)
332  if
333  (
334  chemisTree_.size() > minBalanceThreshold_
335  && chemisTree_.depth() >
336  maxDepthFactor_*Foam::log(scalar(chemisTree_.size()))/Foam::log(2.0)
337  )
338  {
339  chemisTree_.balance();
340  treeModified = true;
341  }
342 
343  // Return a bool to specify if the tree structure has been modified and is
344  // now below the user specified limit (true if not full)
345  return (treeModified && !chemisTree_.isFull());
346 }
347 
348 
349 void Foam::chemistryTabulationMethods::ISAT::computeA
350 (
352  const scalarField& Rphiq,
353  const label li,
354  const scalar dt
355 )
356 {
357  const label nSpecie = chemistry_.nSpecie();
358 
359  scalarField Rphiqs(chemistry_.nEqns() + 1);
360  for (label i=0; i<nSpecie; i++)
361  {
362  const label si = chemistry_.sToc(i);
363  Rphiqs[i] = Rphiq[si];
364  }
365  Rphiqs[nSpecie] = Rphiq[Rphiq.size() - 3];
366  Rphiqs[nSpecie + 1] = Rphiq[Rphiq.size() - 2];
367  Rphiqs[nSpecie + 2] = Rphiq[Rphiq.size() - 1];
368 
369  // Aaa is computed implicitly,
370  // A is given by A = C(psi0, t0+dt), where C is obtained through solving
371  // d/dt C(psi0, t) = J(psi(t))C(psi0, t)
372  // If we solve it implicitly:
373  // (C(psi0, t0+dt) - C(psi0, t0))/dt = J(psi(t0+dt))C(psi0, t0+dt)
374  // The Jacobian is thus computed according to the mapping
375  // C(psi0,t0+dt)*(I-dt*J(psi(t0+dt))) = C(psi0, t0)
376  // A = C(psi0,t0)/(I-dt*J(psi(t0+dt)))
377  // where C(psi0,t0) = I
378  scalarField dYTpdt(nSpecie + 2, Zero);
379  chemistry_.jacobian(runTime_.value(), Rphiqs, li, dYTpdt, A);
380 
381  // Inverse of I - dt*J(psi(t0 + dt))
382  for (label i=0; i<nSpecie + 2; i++)
383  {
384  for (label j=0; j<nSpecie + 2; j++)
385  {
386  A(i, j) *= -dt;
387  }
388  A(i, i) += 1;
389  }
390  A(nSpecie + 2, nSpecie + 2) = 1;
391  LUscalarMatrix LUA(A);
392  LUA.inv(A);
393 
394  // After inversion, lines of p and T are set to 0 except diagonal. This
395  // avoid skewness of the ellipsoid of accuracy and potential issues in the
396  // binary tree.
397  for (label i=0; i<nSpecie; i++)
398  {
399  A(nSpecie, i) = 0;
400  A(nSpecie + 1, i) = 0;
401  }
402 }
403 
404 
405 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
406 
408 (
409  const Foam::scalarField& phiq,
410  scalarField& Rphiq
411 )
412 {
413  if (log_)
414  {
415  cpuTime_.cpuTimeIncrement();
416  }
417 
418  bool retrieved(false);
420 
421  // If the tree is not empty
422  if (chemisTree_.size())
423  {
424  chemisTree_.binaryTreeSearch(phiq, chemisTree_.root(), phi0);
425 
426  // lastSearch keeps track of the chemPoint we obtain by the regular
427  // binary tree search
428  lastSearch_ = phi0;
429  if (phi0->inEOA(phiq))
430  {
431  retrieved = true;
432  }
433  // After a successful secondarySearch, phi0 store a pointer to the
434  // found chemPoint
435  else if (chemisTree_.secondaryBTSearch(phiq, phi0))
436  {
437  retrieved = true;
438  }
439  else if (MRURetrieve_)
440  {
441  typename SLList
442  <
444  >::iterator iter = MRUList_.begin();
445 
446  for ( ; iter != MRUList_.end(); ++iter)
447  {
448  phi0 = iter();
449  if (phi0->inEOA(phiq))
450  {
451  retrieved = true;
452  break;
453  }
454  }
455  }
456  }
457  // The tree is empty, retrieved is still false
458  else
459  {
460  // There is no chempoints that we can try to grow
461  lastSearch_ = nullptr;
462  }
463 
464  if (retrieved)
465  {
466  phi0->increaseNumRetrieve();
467  const scalar elapsedTimeSteps = timeSteps() - phi0->timeTag();
468 
469  // Raise a flag when the chemPoint has been used more than the allowed
470  // number of time steps
471  if (elapsedTimeSteps > chPMaxLifeTime_ && !phi0->toRemove())
472  {
473  cleaningRequired_ = true;
474  phi0->toRemove() = true;
475  }
476  lastSearch_->lastTimeUsed() = timeSteps();
477  addToMRU(phi0);
478  calcNewC(phi0, phiq, Rphiq);
479  nRetrieved_++;
480  }
481 
482  if (log_)
483  {
484  searchISATCpuTime_ += cpuTime_.cpuTimeIncrement();
485  }
486 
487  return retrieved;
488 }
489 
490 
492 (
493  const scalarField& phiq,
494  const scalarField& Rphiq,
495  const label nActive,
496  const label li,
497  const scalar deltaT
498 )
499 {
500  if (log_)
501  {
502  cpuTime_.cpuTimeIncrement();
503  }
504 
505  label growthOrAddFlag = 1;
506 
507  // If lastSearch_ holds a valid pointer to a chemPoint AND the growPoints_
508  // option is on, the code first tries to grow the point hold by lastSearch_
509  if (lastSearch_ && growPoints_)
510  {
511  if (grow(lastSearch_, phiq, Rphiq))
512  {
513  nGrowth_++;
514  growthOrAddFlag = 0;
515  addToMRU(lastSearch_);
516 
517  tabulationResults_[li] = 1;
518 
519  if (log_)
520  {
521  growCpuTime_ += cpuTime_.cpuTimeIncrement();
522  }
523 
524  // the structure of the tree is not modified, return false
525  return growthOrAddFlag;
526  }
527  }
528 
529  // If the code reach this point, it is either because lastSearch_ is not
530  // valid, OR because growPoints_ is not on, OR because the grow operation
531  // has failed. In the three cases, a new point is added to the tree.
532  if (chemisTree().isFull())
533  {
534  // If cleanAndBalance operation do not result in a reduction of the tree
535  // size, the last possibility is to delete completely the tree.
536  // It can be partially rebuild with the MRU list if this is used.
537  if (!cleanAndBalance())
538  {
540  if (maxMRUSize_>0)
541  {
542  // Create a copy of each chemPointISAT of the MRUList_ before
543  // they are deleted
544  typename SLList
545  <
547  >::iterator iter = MRUList_.begin();
548  for ( ; iter != MRUList_.end(); ++iter)
549  {
550  tempList.append
551  (
552  new chemPointISAT(*iter())
553  );
554  }
555  }
556  chemisTree().clear();
557 
558  // Pointers to chemPoint are not valid anymore, clear the list
559  MRUList_.clear();
560 
561  // Construct the tree without giving a reference to attach to it
562  // since the structure has been completely discarded
563  chemPointISAT* nulPhi = 0;
564  forAll(tempList, i)
565  {
566  chemisTree().insertNewLeaf
567  (
568  tempList[i]->phi(),
569  tempList[i]->Rphi(),
570  tempList[i]->A(),
571  scaleFactor(),
572  tolerance_,
573  scaleFactor_.size(),
574  nActive,
575  nulPhi
576  );
577  deleteDemandDrivenData(tempList[i]);
578  }
579  }
580 
581  // The structure has been changed, it will force the binary tree to
582  // perform a new search and find the most appropriate point still stored
583  lastSearch_ = nullptr;
584  }
585 
586  // Compute the A matrix needed to store the chemPoint.
587  const label ASize = chemistry_.nEqns() + 1;
588  scalarSquareMatrix A(ASize, Zero);
589  computeA(A, Rphiq, li, deltaT);
590 
591  chemisTree().insertNewLeaf
592  (
593  phiq,
594  Rphiq,
595  A,
596  scaleFactor(),
597  tolerance_,
598  scaleFactor_.size(),
599  nActive,
600  lastSearch_ // lastSearch_ may be nullptr (handled by binaryTree)
601  );
602  if (lastSearch_ != nullptr)
603  {
604  addToMRU(lastSearch_);
605  }
606  nAdd_++;
607 
608  tabulationResults_[li] = 0;
609 
610  if (log_)
611  {
612  addNewLeafCpuTime_ += cpuTime_.cpuTimeIncrement();
613  }
614 
615  return growthOrAddFlag;
616 }
617 
618 
620 {
621  if (log_)
622  {
623  nRetrievedFile_()
624  << runTime_.userTimeValue() << " " << nRetrieved_ << endl;
625  nRetrieved_ = 0;
626 
627  nGrowthFile_()
628  << runTime_.userTimeValue() << " " << nGrowth_ << endl;
629  nGrowth_ = 0;
630 
631  nAddFile_()
632  << runTime_.userTimeValue() << " " << nAdd_ << endl;
633  nAdd_ = 0;
634 
635  sizeFile_()
636  << runTime_.userTimeValue() << " "
637  << chemisTree_.size() << endl;
638 
639  cpuRetrieveFile_()
640  << runTime_.userTimeValue()
641  << " " << searchISATCpuTime_ << endl;
642  searchISATCpuTime_ = 0;
643 
644  cpuGrowFile_()
645  << runTime_.userTimeValue()
646  << " " << growCpuTime_ << endl;
647  growCpuTime_ = 0;
648 
649  cpuAddFile_()
650  << runTime_.userTimeValue()
651  << " " << addNewLeafCpuTime_ << endl;
652  addNewLeafCpuTime_ = 0;
653  }
654 }
655 
656 
658 {
659  // Increment counter of time-step
660  timeSteps_++;
661 
662  forAll(tabulationResults_, i)
663  {
664  tabulationResults_[i] = 2;
665  }
666 }
667 
668 
670 {
671  bool updated = cleanAndBalance();
672  writePerformance();
673  return updated;
674 }
675 
676 
677 // ************************************************************************* //
const fvMesh & mesh() const
Return const access to the mesh database.
const label & timeTag()
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
layerAndWeight max(const layerAndWeight &a, const layerAndWeight &b)
static word phasePropertyName(const word &name, const word &phaseName)
Name of a property for a given phase.
Definition: basicThermo.H:150
dimensionedScalar log(const dimensionedScalar &ds)
A list of keyword definitions, which are a keyword followed by any number of values (e...
Definition: dictionary.H:156
Class to perform the LU decomposition on a symmetric matrix.
Template class for non-intrusive linked lists.
Definition: LList.H:51
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
bool checkSolution(const scalarField &phiq, const scalarField &Rphiq)
If phiq is not in the EOA, then the mapping is computed.
autoPtr< OFstream > logFile(const word &name) const
Create and return a TDAC log file of the given name.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:251
Leaf of the binary tree. The chemPoint stores the composition &#39;phi&#39;, the mapping of this composition ...
A simple wrapper around bool so that it can be read as a word: true/false, on/off, yes/no, y/n, t/f, or none/any.
Definition: Switch.H:60
An STL-conforming iterator.
Definition: LList.H:246
virtual label add(const scalarField &phiq, const scalarField &Rphiq, const label nActive, const label li, const scalar deltaT)
Add information to the tabulation.
Definition: ISAT.C:492
label nActive() const
addToRunTimeSelectionTable(chemistryTabulationMethod, ISAT, dictionary)
const label nSpecie
Macros for easy insertion into run-time selection tables.
const dictionary & subDict(const word &) const
Find and return a sub-dictionary.
Definition: dictionary.C:1002
const List< label > & completeToSimplifiedIndex() const
virtual bool retrieve(const Foam::scalarField &phiq, scalarField &Rphiq)
Find the closest stored leaf of phiQ and store the result in.
Definition: ISAT.C:408
label size() const
Return the number of elements in matrix (m*n)
Definition: MatrixI.H:71
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects...
Definition: DynamicList.H:56
static word timeName(const scalar, const int precision=curPrecision_)
Return time name of given scalar time.
Definition: Time.C:666
const scalarField & phi() const
label nGrowth() const
DynamicList< T, SizeInc, SizeMult, SizeDiv > & append(const T &)
Append an element at the end of the list.
Definition: DynamicListI.H:296
bool grow(const scalarField &phiq)
More details about the minimum-volume ellipsoid covering an.
phi
Definition: correctPhi.H:3
static const zero Zero
Definition: zero.H:97
const scalarField & Rphi() const
const fluidReactionThermo & thermo() const
Return const access to the thermo.
An abstract class for chemistry tabulation.
const Time & time() const
Return time.
Definition: IOobject.C:318
void increaseNumRetrieve()
Increases the number of retrieves the chempoint has generated.
ISAT(const dictionary &chemistryProperties, const odeChemistryModel &chemistry)
Construct from dictionary.
Definition: ISAT.C:47
bool inEOA(const scalarField &phiq)
To RETRIEVE the mapping from the stored chemPoint phi, the query.
const scalarSquareMatrix & A() const
const doubleScalar e
Elementary charge.
Definition: doubleScalar.H:105
const dimensionedScalar phi0
Magnetic flux quantum: default SI units: [Wb].
void deleteDemandDrivenData(DataPtr &dataPtr)
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:98
Extends base chemistry model adding an ODESystem and the reduction maps needed for tabulation...
virtual label nEqns() const
Number of ODE&#39;s to solve.
Namespace for OpenFOAM.
void inv(scalarSquareMatrix &M) const
Set M to the inverse of this square matrix.