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-2018 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 "LUscalarMatrix.H"
28 
29 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
30 
31 template<class CompType, class ThermoType>
33 (
34  const dictionary& chemistryProperties,
36 )
37 :
39  (
40  chemistryProperties,
41  chemistry
42  ),
43  chemisTree_(chemistry, this->coeffsDict_),
44  scaleFactor_(chemistry.nEqns() + ((this->variableTimeStep()) ? 1 : 0), 1),
45  runTime_(chemistry.time()),
46  chPMaxLifeTime_
47  (
48  this->coeffsDict_.lookupOrDefault("chPMaxLifeTime", INT_MAX)
49  ),
50  maxGrowth_(this->coeffsDict_.lookupOrDefault("maxGrowth", INT_MAX)),
51  checkEntireTreeInterval_
52  (
53  this->coeffsDict_.lookupOrDefault("checkEntireTreeInterval", INT_MAX)
54  ),
55  maxDepthFactor_
56  (
57  this->coeffsDict_.lookupOrDefault
58  (
59  "maxDepthFactor",
60  (chemisTree_.maxNLeafs() - 1)
61  /(log(scalar(chemisTree_.maxNLeafs()))/log(2.0))
62  )
63  ),
64  minBalanceThreshold_
65  (
66  this->coeffsDict_.lookupOrDefault
67  (
68  "minBalanceThreshold", 0.1*chemisTree_.maxNLeafs()
69  )
70  ),
71  MRURetrieve_(this->coeffsDict_.lookupOrDefault("MRURetrieve", false)),
72  maxMRUSize_(this->coeffsDict_.lookupOrDefault("maxMRUSize", 0)),
73  lastSearch_(nullptr),
74  growPoints_(this->coeffsDict_.lookupOrDefault("growPoints", true)),
75  nRetrieved_(0),
76  nGrowth_(0),
77  nAdd_(0),
78  cleaningRequired_(false)
79 {
80  if (this->active_)
81  {
82  dictionary scaleDict(this->coeffsDict_.subDict("scaleFactor"));
83  label Ysize = this->chemistry_.Y().size();
84  scalar otherScaleFactor = readScalar(scaleDict.lookup("otherSpecies"));
85  for (label i=0; i<Ysize; i++)
86  {
87  if (!scaleDict.found(this->chemistry_.Y()[i].member()))
88  {
89  scaleFactor_[i] = otherScaleFactor;
90  }
91  else
92  {
93  scaleFactor_[i] =
95  (
96  scaleDict.lookup(this->chemistry_.Y()[i].member())
97  );
98  }
99  }
100  scaleFactor_[Ysize] = readScalar(scaleDict.lookup("Temperature"));
101  scaleFactor_[Ysize + 1] = readScalar(scaleDict.lookup("Pressure"));
102  if (this->variableTimeStep())
103  {
104  scaleFactor_[Ysize + 2] = readScalar(scaleDict.lookup("deltaT"));
105  }
106  }
107 
108  if (this->variableTimeStep())
109  {
110  nAdditionalEqns_ = 3;
111  }
112  else
113  {
114  nAdditionalEqns_ = 2;
115  }
116 
117  if (this->log())
118  {
119  nRetrievedFile_ = chemistry.logFile("found_isat.out");
120  nGrowthFile_ = chemistry.logFile("growth_isat.out");
121  nAddFile_ = chemistry.logFile("add_isat.out");
122  sizeFile_ = chemistry.logFile("size_isat.out");
123  }
124 }
125 
126 
127 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
128 
129 template<class CompType, class ThermoType>
131 {}
132 
133 
134 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
135 
136 template<class CompType, class ThermoType>
138 (
140 )
141 {
142  if (maxMRUSize_ > 0 && MRURetrieve_)
143  {
144  // First search if the chemPoint is already in the list
145  bool isInList = false;
146  typename SLList <chemPointISAT<CompType, ThermoType>*>::iterator iter =
147  MRUList_.begin();
148  for ( ; iter != MRUList_.end(); ++iter)
149  {
150  if (iter() == phi0)
151  {
152  isInList = true;
153  break;
154  }
155  }
156  // If it is in the list, then move it to front
157  if (isInList)
158  {
159  if (iter() != MRUList_.first())
160  {
161  // iter hold the position of the element to move
162  MRUList_.remove(iter);
163 
164  // Insert the element in front of the list
165  MRUList_.insert(phi0);
166  }
167  }
168  else // chemPoint not yet in the list, iter is last
169  {
170  if (MRUList_.size() == maxMRUSize_)
171  {
172  MRUList_.remove(iter);
173  MRUList_.insert(phi0);
174  }
175  else
176  {
177  MRUList_.insert(phi0);
178  }
179  }
180  }
181 }
182 
183 
184 template<class CompType, class ThermoType>
186 (
188  const scalarField& phiq,
189  scalarField& Rphiq
190 )
191 {
192  label nEqns = this->chemistry_.nEqns(); // Species, T, p
193  bool mechRedActive = this->chemistry_.mechRed()->active();
194  Rphiq = phi0->Rphi();
195  scalarField dphi(phiq-phi0->phi());
196  const scalarSquareMatrix& gradientsMatrix = phi0->A();
197  List<label>& completeToSimplified(phi0->completeToSimplifiedIndex());
198 
199  // Rphiq[i]=Rphi0[i]+A(i, j)dphi[j]
200  // where Aij is dRi/dphi_j
201  for (label i=0; i<nEqns-nAdditionalEqns_; i++)
202  {
203  if (mechRedActive)
204  {
205  label si = completeToSimplified[i];
206  // The species is active
207  if (si != -1)
208  {
209  for (label j=0; j<nEqns-2; j++)
210  {
211  label sj = completeToSimplified[j];
212  if (sj != -1)
213  {
214  Rphiq[i] += gradientsMatrix(si, sj)*dphi[j];
215  }
216  }
217  Rphiq[i] +=
218  gradientsMatrix(si, phi0->nActiveSpecies())*dphi[nEqns - 2];
219  Rphiq[i] +=
220  gradientsMatrix(si, phi0->nActiveSpecies() + 1)
221  *dphi[nEqns - 1];
222 
223  if (this->variableTimeStep())
224  {
225  Rphiq[i] +=
226  gradientsMatrix(si, phi0->nActiveSpecies() + 2)
227  *dphi[nEqns];
228  }
229 
230  // As we use an approximation of A, Rphiq should be checked for
231  // negative values
232  Rphiq[i] = max(0, Rphiq[i]);
233  }
234  // The species is not active A(i, j) = I(i, j)
235  else
236  {
237  Rphiq[i] += dphi[i];
238  Rphiq[i] = max(0, Rphiq[i]);
239  }
240  }
241  else // Mechanism reduction is not active
242  {
243  for (label j=0; j<nEqns; j++)
244  {
245  Rphiq[i] += gradientsMatrix(i, j)*dphi[j];
246  }
247  // As we use a first order gradient matrix, Rphiq should be checked
248  // for negative values
249  Rphiq[i] = max(0, Rphiq[i]);
250  }
251  }
252 }
253 
254 
255 template<class CompType, class ThermoType>
257 (
259  const scalarField& phiq,
260  const scalarField& Rphiq
261 )
262 {
263  // If the pointer to the chemPoint is nullptr, the function stops
264  if (!phi0)
265  {
266  return false;
267  }
268 
269  // Raise a flag when the chemPoint used has been grown more than the
270  // allowed number of time
271  if (phi0->nGrowth() > maxGrowth_)
272  {
273  cleaningRequired_ = true;
274  phi0->toRemove() = true;
275  return false;
276  }
277 
278  // If the solution RphiQ is still within the tolerance we try to grow it
279  // in some cases this might result in a failure and the grow function of
280  // the chemPoint returns false
281  if (phi0->checkSolution(phiq, Rphiq))
282  {
283  return phi0->grow(phiq);
284  }
285  // The actual solution and the approximation given by ISAT are too different
286  else
287  {
288  return false;
289  }
290 }
291 
292 
293 template<class CompType, class ThermoType>
294 bool
296 {
297  bool treeModified(false);
298 
299  // Check all chemPoints to see if we need to delete some of the chemPoints
300  // according to the elapsed time and number of growths
301  chemPointISAT<CompType, ThermoType>* x = chemisTree_.treeMin();
302  while(x != nullptr)
303  {
305  chemisTree_.treeSuccessor(x);
306 
307  scalar elapsedTimeSteps = this->chemistry_.timeSteps() - x->timeTag();
308 
309  if ((elapsedTimeSteps > chPMaxLifeTime_) || (x->nGrowth() > maxGrowth_))
310  {
311  chemisTree_.deleteLeaf(x);
312  treeModified = true;
313  }
314  x = xtmp;
315  }
316 
317  MRUList_.clear();
318 
319  // Check if the tree should be balanced according to criterion:
320  // -the depth of the tree bigger than a*log2(size), log2(size) being the
321  // ideal depth (e.g. 4 leafs can be stored in a tree of depth 2)
322  if
323  (
324  chemisTree_.size() > minBalanceThreshold_
325  && chemisTree_.depth() >
326  maxDepthFactor_*log(scalar(chemisTree_.size()))/log(2.0)
327  )
328  {
329  chemisTree_.balance();
330  treeModified = true;
331  }
332 
333  // Return a bool to specify if the tree structure has been modified and is
334  // now below the user specified limit (true if not full)
335  return (treeModified && !chemisTree_.isFull());
336 }
337 
338 
339 template<class CompType, class ThermoType>
341 (
343  const scalarField& Rphiq,
344  const scalar rhoi,
345  const scalar dt
346 )
347 {
348  bool mechRedActive = this->chemistry_.mechRed()->active();
349  label speciesNumber = this->chemistry_.nSpecie();
350  scalarField Rcq(this->chemistry_.nEqns() + nAdditionalEqns_ - 2);
351  for (label i=0; i<speciesNumber; i++)
352  {
353  label s2c = i;
354  if (mechRedActive)
355  {
356  s2c = this->chemistry_.simplifiedToCompleteIndex()[i];
357  }
358  Rcq[i] = rhoi*Rphiq[s2c]/this->chemistry_.specieThermo()[s2c].W();
359  }
360  Rcq[speciesNumber] = Rphiq[Rphiq.size() - nAdditionalEqns_];
361  Rcq[speciesNumber + 1] = Rphiq[Rphiq.size() - nAdditionalEqns_ + 1];
362  if (this->variableTimeStep())
363  {
364  Rcq[speciesNumber + 2] = Rphiq[Rphiq.size() - nAdditionalEqns_ + 2];
365  }
366 
367  // Aaa is computed implicitly,
368  // A is given by A = C(psi0, t0+dt), where C is obtained through solving
369  // d/dt C(psi0, t) = J(psi(t))C(psi0, t)
370  // If we solve it implicitly:
371  // (C(psi0, t0+dt) - C(psi0, t0))/dt = J(psi(t0+dt))C(psi0, t0+dt)
372  // The Jacobian is thus computed according to the mapping
373  // C(psi0,t0+dt)*(I-dt*J(psi(t0+dt))) = C(psi0, t0)
374  // A = C(psi0,t0)/(I-dt*J(psi(t0+dt)))
375  // where C(psi0,t0) = I
376  scalarField dcdt(speciesNumber + 2, Zero);
377  this->chemistry_.jacobian(runTime_.value(), Rcq, dcdt, A);
378 
379  // The jacobian is computed according to the molar concentration
380  // the following conversion allows the code to use A with mass fraction
381  for (label i=0; i<speciesNumber; i++)
382  {
383  label si = i;
384 
385  if (mechRedActive)
386  {
387  si = this->chemistry_.simplifiedToCompleteIndex()[i];
388  }
389 
390  for (label j=0; j<speciesNumber; j++)
391  {
392  label sj = j;
393  if (mechRedActive)
394  {
395  sj = this->chemistry_.simplifiedToCompleteIndex()[j];
396  }
397  A(i, j) *=
398  -dt*this->chemistry_.specieThermo()[si].W()
399  /this->chemistry_.specieThermo()[sj].W();
400  }
401 
402  A(i, i) += 1;
403  // Columns for pressure and temperature
404  A(i, speciesNumber) *=
405  -dt*this->chemistry_.specieThermo()[si].W()/rhoi;
406  A(i, speciesNumber + 1) *=
407  -dt*this->chemistry_.specieThermo()[si].W()/rhoi;
408  }
409 
410  // For the temperature and pressure lines, ddc(dTdt)
411  // should be converted in ddY(dTdt)
412  for (label i=0; i<speciesNumber; i++)
413  {
414  label si = i;
415  if (mechRedActive)
416  {
417  si = this->chemistry_.simplifiedToCompleteIndex()[i];
418  }
419 
420  A(speciesNumber, i) *=
421  -dt*rhoi/this->chemistry_.specieThermo()[si].W();
422  A(speciesNumber + 1, i) *=
423  -dt*rhoi/this->chemistry_.specieThermo()[si].W();
424  }
425 
426  A(speciesNumber, speciesNumber) = -dt*A(speciesNumber, speciesNumber) + 1;
427 
428  A(speciesNumber + 1, speciesNumber + 1) =
429  -dt*A(speciesNumber + 1, speciesNumber + 1) + 1;
430 
431  if (this->variableTimeStep())
432  {
433  A(speciesNumber + 2, speciesNumber + 2) = 1;
434  }
435 
436  // Inverse of (I-dt*J(psi(t0+dt)))
437  LUscalarMatrix LUA(A);
438  LUA.inv(A);
439 
440  // After inversion, lines of p and T are set to 0 except diagonal. This
441  // avoid skewness of the ellipsoid of accuracy and potential issues in the
442  // binary tree.
443  for (label i=0; i<speciesNumber; i++)
444  {
445  A(speciesNumber, i) = 0;
446  A(speciesNumber + 1, i) = 0;
447  }
448 }
449 
450 
451 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
452 
453 template<class CompType, class ThermoType>
455 (
456  const Foam::scalarField& phiq,
457  scalarField& Rphiq
458 )
459 {
460  bool retrieved(false);
462 
463  // If the tree is not empty
464  if (chemisTree_.size())
465  {
466  chemisTree_.binaryTreeSearch(phiq, chemisTree_.root(), phi0);
467 
468  // lastSearch keeps track of the chemPoint we obtain by the regular
469  // binary tree search
470  lastSearch_ = phi0;
471  if (phi0->inEOA(phiq))
472  {
473  retrieved = true;
474  }
475  // After a successful secondarySearch, phi0 store a pointer to the
476  // found chemPoint
477  else if (chemisTree_.secondaryBTSearch(phiq, phi0))
478  {
479  retrieved = true;
480  }
481  else if (MRURetrieve_)
482  {
483  typename SLList
484  <
486  >::iterator iter = MRUList_.begin();
487 
488  for ( ; iter != MRUList_.end(); ++iter)
489  {
490  phi0 = iter();
491  if (phi0->inEOA(phiq))
492  {
493  retrieved = true;
494  break;
495  }
496  }
497  }
498  }
499  // The tree is empty, retrieved is still false
500  else
501  {
502  // There is no chempoints that we can try to grow
503  lastSearch_ = nullptr;
504  }
505 
506  if (retrieved)
507  {
508  phi0->increaseNumRetrieve();
509  scalar elapsedTimeSteps =
510  this->chemistry_.timeSteps() - phi0->timeTag();
511 
512  // Raise a flag when the chemPoint has been used more than the allowed
513  // number of time steps
514  if (elapsedTimeSteps > chPMaxLifeTime_ && !phi0->toRemove())
515  {
516  cleaningRequired_ = true;
517  phi0->toRemove() = true;
518  }
519  lastSearch_->lastTimeUsed() = this->chemistry_.timeSteps();
520  addToMRU(phi0);
521  calcNewC(phi0, phiq, Rphiq);
522  nRetrieved_++;
523  return true;
524  }
525  else
526  {
527  // This point is reached when every retrieve trials have failed
528  // or if the tree is empty
529  return false;
530  }
531 }
532 
533 
534 template<class CompType, class ThermoType>
536 (
537  const scalarField& phiq,
538  const scalarField& Rphiq,
539  const scalar rho,
540  const scalar deltaT
541 )
542 {
543  label growthOrAddFlag = 1;
544  // If lastSearch_ holds a valid pointer to a chemPoint AND the growPoints_
545  // option is on, the code first tries to grow the point hold by lastSearch_
546  if (lastSearch_ && growPoints_)
547  {
548  if (grow(lastSearch_, phiq, Rphiq))
549  {
550  nGrowth_++;
551  growthOrAddFlag = 0;
552  addToMRU(lastSearch_);
553  //the structure of the tree is not modified, return false
554  return growthOrAddFlag;
555  }
556  }
557 
558  // If the code reach this point, it is either because lastSearch_ is not
559  // valid, OR because growPoints_ is not on, OR because the grow operation
560  // has failed. In the three cases, a new point is added to the tree.
561  if (chemisTree().isFull())
562  {
563  // If cleanAndBalance operation do not result in a reduction of the tree
564  // size, the last possibility is to delete completely the tree.
565  // It can be partially rebuild with the MRU list if this is used.
566  if (!cleanAndBalance())
567  {
569  if (maxMRUSize_>0)
570  {
571  // Create a copy of each chemPointISAT of the MRUList_ before
572  // they are deleted
573  typename SLList
574  <
576  >::iterator iter = MRUList_.begin();
577  for ( ; iter != MRUList_.end(); ++iter)
578  {
579  tempList.append
580  (
582  );
583  }
584  }
585  chemisTree().clear();
586 
587  // Pointers to chemPoint are not valid anymore, clear the list
588  MRUList_.clear();
589 
590  // Construct the tree without giving a reference to attach to it
591  // since the structure has been completely discarded
593  forAll(tempList, i)
594  {
595  chemisTree().insertNewLeaf
596  (
597  tempList[i]->phi(),
598  tempList[i]->Rphi(),
599  tempList[i]->A(),
600  scaleFactor(),
601  this->tolerance(),
602  scaleFactor_.size(),
603  nulPhi
604  );
605  deleteDemandDrivenData(tempList[i]);
606  }
607  }
608 
609  // The structure has been changed, it will force the binary tree to
610  // perform a new search and find the most appropriate point still stored
611  lastSearch_ = nullptr;
612  }
613 
614  // Compute the A matrix needed to store the chemPoint.
615  label ASize = this->chemistry_.nEqns() + nAdditionalEqns_ - 2;
616  scalarSquareMatrix A(ASize, Zero);
617  computeA(A, Rphiq, rho, deltaT);
618 
619  chemisTree().insertNewLeaf
620  (
621  phiq,
622  Rphiq,
623  A,
624  scaleFactor(),
625  this->tolerance(),
626  scaleFactor_.size(),
627  lastSearch_ // lastSearch_ may be nullptr (handled by binaryTree)
628  );
629  if (lastSearch_ != nullptr)
630  {
631  addToMRU(lastSearch_);
632  }
633  nAdd_++;
634 
635  return growthOrAddFlag;
636 }
637 
638 
639 template<class CompType, class ThermoType>
640 void
642 {
643  if (this->log())
644  {
645  nRetrievedFile_()
646  << runTime_.timeOutputValue() << " " << nRetrieved_ << endl;
647  nRetrieved_ = 0;
648 
649  nGrowthFile_()
650  << runTime_.timeOutputValue() << " " << nGrowth_ << endl;
651  nGrowth_ = 0;
652 
653  nAddFile_()
654  << runTime_.timeOutputValue() << " " << nAdd_ << endl;
655  nAdd_ = 0;
656 
657  sizeFile_()
658  << runTime_.timeOutputValue() << " " << this->size() << endl;
659  }
660 }
661 
662 
663 // ************************************************************************* //
#define readScalar
Definition: doubleScalar.C:38
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
const scalarSquareMatrix & A() const
surfaceScalarField & phi
dimensionedScalar log(const dimensionedScalar &ds)
A list of keyword definitions, which are a keyword followed by any number of values (e...
Definition: dictionary.H:158
dimensioned< Type > max(const dimensioned< Type > &, const dimensioned< Type > &)
Class to perform the LU decomposition on a symmetric matrix.
const iterator & end()
Definition: LList.H:286
Template class for non-intrusive linked lists.
Definition: LList.H:51
T & first()
Return the first entry added.
Definition: LList.H:139
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:163
bool checkSolution(const scalarField &phiq, const scalarField &Rphiq)
If phiq is not in the EOA, then the mapping is computed.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:256
Leaf of the binary tree. The chemPoint stores the composition &#39;phi&#39;, the mapping of this composition ...
List< label > & completeToSimplifiedIndex()
ISAT(const dictionary &chemistryProperties, TDACChemistryModel< CompType, ThermoType > &chemistry)
Construct from dictionary.
Definition: ISAT.C:33
const dimensionedScalar phi0
Magnetic flux quantum: default SI units: [Wb].
virtual label add(const scalarField &phiq, const scalarField &Rphiq, const scalar rho, const scalar deltaT)
Add information to the tabulation.
Definition: ISAT.C:536
BasicChemistryModel< rhoReactionThermo > & chemistry
const label & timeTag()
virtual bool retrieve(const Foam::scalarField &phiq, scalarField &Rphiq)
Find the closest stored leaf of phiQ and store the result in.
Definition: ISAT.C:455
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
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.
static const zero Zero
Definition: zero.H:97
const scalarField & phi() const
iterator begin()
Definition: LList.H:281
const scalarField & Rphi() const
An abstract class for chemistry tabulation.
virtual label nEqns() const
Number of ODE&#39;s to solve.
const Time & time() const
Return time.
Definition: IOobject.C:360
void increaseNumRetrieve()
Increases the number of retrieves the chempoint has generated.
bool inEOA(const scalarField &phiq)
To RETRIEVE the mapping from the stored chemPoint phi, the query.
autoPtr< OFstream > logFile(const word &name) const
Create and return a TDAC log file of the given name.
Implementation of the ISAT (In-situ adaptive tabulation), for chemistry calculation.
Definition: ISAT.H:57
void deleteDemandDrivenData(DataPtr &dataPtr)
void inv(scalarSquareMatrix &M) const
Set M to the inverse of this square matrix.