seulex.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) 2013-2026 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 "seulex.H"
28 
29 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
30 
31 namespace Foam
32 {
34 
36 
37  const scalar
38  seulex::stepFactor1_ = 0.6,
39  seulex::stepFactor2_ = 0.93,
40  seulex::stepFactor3_ = 0.1,
41  seulex::stepFactor4_ = 4,
42  seulex::stepFactor5_ = 0.5,
43  seulex::kFactor1_ = 0.7,
44  seulex::kFactor2_ = 0.9;
45 }
46 
47 
48 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
49 
51 :
52  ODESolver(ode, dict),
53  jacRedo_(min(1e-4, min(relTol_))),
54  nSeq_(iMaxx_),
55  cpu_(iMaxx_),
56  coeff_(iMaxx_, iMaxx_),
57  theta_(2*jacRedo_),
58  table_(kMaxx_, n_),
59  dfdx_(n_),
60  dfdy_(n_),
61  a_(n_),
62  pivotIndices_(n_),
63  dxOpt_(iMaxx_),
64  temp_(iMaxx_),
65  y0_(n_),
66  ySequence_(n_),
67  scale_(n_),
68  dy_(n_),
69  yTemp_(n_),
70  dydx_(n_)
71 {
72  // The CPU time factors for the major parts of the algorithm
73  const scalar cpuFunc = 1, cpuJac = 5, cpuLU = 1, cpuSolve = 1;
74 
75  nSeq_[0] = 2;
76  nSeq_[1] = 3;
77 
78  for (int i=2; i<iMaxx_; i++)
79  {
80  nSeq_[i] = 2*nSeq_[i-2];
81  }
82  cpu_[0] = cpuJac + cpuLU + nSeq_[0]*(cpuFunc + cpuSolve);
83 
84  for (int k=0; k<kMaxx_; k++)
85  {
86  cpu_[k+1] = cpu_[k] + (nSeq_[k+1]-1)*(cpuFunc + cpuSolve) + cpuLU;
87  }
88 
89  // Set the extrapolation coefficients array
90  for (int k=0; k<iMaxx_; k++)
91  {
92  for (int l=0; l<k; l++)
93  {
94  scalar ratio = scalar(nSeq_[k])/nSeq_[l];
95  coeff_(k, l) = 1/(ratio - 1);
96  }
97  }
98 }
99 
100 
101 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
102 
103 bool Foam::seulex::seul
104 (
105  const scalar x0,
106  const scalarField& y0,
107  const label li,
108  const scalar dxTot,
109  const label k,
110  scalarField& y,
111  const scalarField& scale
112 ) const
113 {
114  label nSteps = nSeq_[k];
115  scalar dx = dxTot/nSteps;
116 
117  for (label i=0; i<n_; i++)
118  {
119  for (label j=0; j<n_; j++)
120  {
121  a_(i, j) = -dfdy_(i, j);
122  }
123 
124  a_(i, i) += 1/dx;
125  }
126 
127  LUDecompose(a_, pivotIndices_);
128 
129  scalar xnew = x0 + dx;
130  odes_.derivatives(xnew, y0, li, dy_);
131  LUBacksubstitute(a_, pivotIndices_, dy_);
132 
133  yTemp_ = y0;
134 
135  for (label nn=1; nn<nSteps; nn++)
136  {
137  yTemp_ += dy_;
138  xnew += dx;
139 
140  if (nn == 1 && k<=1)
141  {
142  scalar dy1 = 0;
143  for (label i=0; i<n_; i++)
144  {
145  dy1 += sqr(dy_[i]/scale[i]);
146  }
147  dy1 = sqrt(dy1);
148 
149  odes_.derivatives(x0 + dx, yTemp_, li, dydx_);
150  for (label i=0; i<n_; i++)
151  {
152  dy_[i] = dydx_[i] - dy_[i]/dx;
153  }
154 
155  LUBacksubstitute(a_, pivotIndices_, dy_);
156 
157  const scalar denom = max(1, dy1);
158 
159  scalar dy2 = 0;
160  for (label i=0; i<n_; i++)
161  {
162  // Test of dy_[i] to avoid overflow
163  if (mag(dy_[i]) > scale[i]*denom)
164  {
165  theta_ = 1;
166  return false;
167  }
168 
169  dy2 += sqr(dy_[i]/scale[i]);
170  }
171  dy2 = sqrt(dy2);
172  theta_ = dy2/denom;
173 
174  if (theta_ > 1)
175  {
176  return false;
177  }
178  }
179 
180  odes_.derivatives(xnew, yTemp_, li, dy_);
181  LUBacksubstitute(a_, pivotIndices_, dy_);
182  }
183 
184  for (label i=0; i<n_; i++)
185  {
186  y[i] = yTemp_[i] + dy_[i];
187  }
188 
189  return true;
190 }
191 
192 
193 void Foam::seulex::extrapolate
194 (
195  const label k,
197  scalarField& y
198 ) const
199 {
200  for (int j=k-1; j>0; j--)
201  {
202  for (label i=0; i<n_; i++)
203  {
204  table[j-1][i] =
205  table(j, i) + coeff_(k, j)*(table(j, i) - table[j-1][i]);
206  }
207  }
208 
209  for (int i=0; i<n_; i++)
210  {
211  y[i] = table(0, i) + coeff_(k, 0)*(table(0, i) - y[i]);
212  }
213 }
214 
215 
217 {
218  if (ODESolver::resize())
219  {
220  table_.shallowResize(kMaxx_, n_);
221  resizeField(dfdx_);
222  resizeMatrix(dfdy_);
223  resizeMatrix(a_);
224  resizeField(pivotIndices_);
225  resizeField(y0_);
226  resizeField(ySequence_);
227  resizeField(scale_);
228  resizeField(dy_);
229  resizeField(yTemp_);
230  resizeField(dydx_);
231 
232  return true;
233  }
234  else
235  {
236  return false;
237  }
238 }
239 
240 
242 (
243  scalar& x,
244  scalarField& y,
245  const label li,
246  stepState& step
247 ) const
248 {
249  temp_[0] = great;
250  scalar dx = step.dxTry;
251  y0_ = y;
252  dxOpt_[0] = mag(0.1*dx);
253 
254  if (step.first || step.prevReject)
255  {
256  theta_ = 2*jacRedo_;
257  }
258 
259  if (step.first)
260  {
261  // NOTE: the first element of relTol_ and absTol_ are used here.
262  scalar logTol = -log10(relTol_[0] + absTol_[0])*0.6 + 0.5;
263  kTarg_ = max(1, min(kMaxx_ - 1, int(logTol)));
264  }
265 
266  forAll(scale_, i)
267  {
268  scale_[i] = absTol_[i] + relTol_[i]*mag(y[i]);
269  }
270 
271  bool jacUpdated = false;
272 
273  if (theta_ > jacRedo_)
274  {
275  odes_.jacobian(x, y, li, dfdx_, dfdy_);
276  jacUpdated = true;
277  }
278 
279  int k;
280  scalar dxNew = mag(dx);
281  bool firstk = true;
282 
283  while (firstk || step.reject)
284  {
285  dx = step.forward ? dxNew : -dxNew;
286  firstk = false;
287  step.reject = false;
288 
289  if (mag(dx) <= mag(x)*sqr(small))
290  {
292  << "step size underflow :" << dx << endl;
293  }
294 
295  scalar errOld = 0;
296 
297  for (k=0; k<=kTarg_+1; k++)
298  {
299  bool success = seul(x, y0_, li, dx, k, ySequence_, scale_);
300 
301  if (!success)
302  {
303  step.reject = true;
304  dxNew = mag(dx)*stepFactor5_;
305  break;
306  }
307 
308  if (k == 0)
309  {
310  y = ySequence_;
311  }
312  else
313  {
314  forAll(ySequence_, i)
315  {
316  table_[k-1][i] = ySequence_[i];
317  }
318  }
319 
320  if (k != 0)
321  {
322  extrapolate(k, table_, y);
323  scalar err = 0;
324  forAll(scale_, i)
325  {
326  scale_[i] = absTol_[i] + relTol_[i]*mag(y0_[i]);
327  err += sqr((y[i] - table_(0, i))/scale_[i]);
328  }
329  err = sqrt(err/n_);
330  if (err > 1/small || (k > 1 && err >= errOld))
331  {
332  step.reject = true;
333  dxNew = mag(dx)*stepFactor5_;
334  break;
335  }
336  errOld = max(4*err, 1);
337  scalar expo = 1.0/(k + 1);
338  scalar facmin = pow(stepFactor3_, expo);
339  scalar fac;
340  if (err == 0)
341  {
342  fac = 1/facmin;
343  }
344  else
345  {
346  fac = stepFactor2_/pow(err/stepFactor1_, expo);
347  fac = max(facmin/stepFactor4_, min(1/facmin, fac));
348  }
349  dxOpt_[k] = mag(dx*fac);
350  temp_[k] = cpu_[k]/dxOpt_[k];
351 
352  if ((step.first || step.last) && err <= 1)
353  {
354  break;
355  }
356 
357  if
358  (
359  k == kTarg_ - 1
360  && !step.prevReject
361  && !step.first && !step.last
362  )
363  {
364  if (err <= 1)
365  {
366  break;
367  }
368  else if (err > nSeq_[kTarg_]*nSeq_[kTarg_ + 1]*4)
369  {
370  step.reject = true;
371  kTarg_ = k;
372  if (kTarg_>1 && temp_[k-1] < kFactor1_*temp_[k])
373  {
374  kTarg_--;
375  }
376  dxNew = dxOpt_[kTarg_];
377  break;
378  }
379  }
380 
381  if (k == kTarg_)
382  {
383  if (err <= 1)
384  {
385  break;
386  }
387  else if (err > nSeq_[k + 1]*2)
388  {
389  step.reject = true;
390  if (kTarg_>1 && temp_[k-1] < kFactor1_*temp_[k])
391  {
392  kTarg_--;
393  }
394  dxNew = dxOpt_[kTarg_];
395  break;
396  }
397  }
398 
399  if (k == kTarg_+1)
400  {
401  if (err > 1)
402  {
403  step.reject = true;
404  if
405  (
406  kTarg_ > 1
407  && temp_[kTarg_-1] < kFactor1_*temp_[kTarg_]
408  )
409  {
410  kTarg_--;
411  }
412  dxNew = dxOpt_[kTarg_];
413  }
414  break;
415  }
416  }
417  }
418  if (step.reject)
419  {
420  step.prevReject = true;
421  if (!jacUpdated)
422  {
423  theta_ = 2*jacRedo_;
424 
425  if (theta_ > jacRedo_ && !jacUpdated)
426  {
427  odes_.jacobian(x, y, li, dfdx_, dfdy_);
428  jacUpdated = true;
429  }
430  }
431  }
432  }
433 
434  jacUpdated = false;
435 
436  step.dxDid = dx;
437  x += dx;
438 
439  label kopt;
440  if (k == 1)
441  {
442  kopt = 2;
443  }
444  else if (k <= kTarg_)
445  {
446  kopt=k;
447  if (temp_[k-1] < kFactor1_*temp_[k])
448  {
449  kopt = k - 1;
450  }
451  else if (temp_[k] < kFactor2_*temp_[k - 1])
452  {
453  kopt = min(k + 1, kMaxx_ - 1);
454  }
455  }
456  else
457  {
458  kopt = k - 1;
459  if (k > 2 && temp_[k-2] < kFactor1_*temp_[k - 1])
460  {
461  kopt = k - 2;
462  }
463  if (temp_[k] < kFactor2_*temp_[kopt])
464  {
465  kopt = min(k, kMaxx_ - 1);
466  }
467  }
468 
469  if (step.prevReject)
470  {
471  kTarg_ = min(kopt, k);
472  dxNew = min(mag(dx), dxOpt_[kTarg_]);
473  step.prevReject = false;
474  }
475  else
476  {
477  if (kopt <= k)
478  {
479  dxNew = dxOpt_[kopt];
480  }
481  else
482  {
483  if (k < kTarg_ && temp_[k] < kFactor2_*temp_[k - 1])
484  {
485  dxNew = dxOpt_[k]*cpu_[kopt + 1]/cpu_[k];
486  }
487  else
488  {
489  dxNew = dxOpt_[k]*cpu_[kopt]/cpu_[k];
490  }
491  }
492  kTarg_ = kopt;
493  }
494 
495  step.dxTry = step.forward ? dxNew : -dxNew;
496 }
497 
498 
499 // ************************************************************************* //
bool success
scalar y
label k
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
Macros for easy insertion into run-time selection tables.
Abstract base-class for ODE system solvers.
Definition: ODESolver.H:51
virtual bool resize()=0
Resize the ODE solver.
Definition: ODESolver.C:89
Abstract base class for the systems of ordinary differential equations.
Definition: ODESystem.H:47
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
An extrapolation-algorithm, based on the linearly implicit Euler method with step size control and or...
Definition: seulex.H:62
seulex(const ODESystem &ode, const dictionary &dict)
Construct from ODESystem.
Definition: seulex.C:50
virtual bool resize()
Resize the ODE solver.
Definition: seulex.C:216
virtual void solve(scalar &x, scalarField &y, const label li, stepState &step) const
Solve the ODE system and the update the state.
Definition: seulex.C:242
#define WarningInFunction
Report a warning using Foam::Warning.
const dimensionedScalar e
Elementary charge.
const HashTable< dimensionSet > table
Table of dimensions.
Definition: dimensions.C:74
Namespace for OpenFOAM.
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
addToRunTimeSelectionTable(polyPatch, mergedCyclicPolyPatch, word)
RectangularMatrix< scalar > scalarRectangularMatrix
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
dimensionedScalar y0(const dimensionedScalar &ds)
void LUBacksubstitute(const scalarSquareMatrix &luMmatrix, const labelList &pivotIndices, List< Type > &source)
LU back-substitution with given source, returning the solution.
void LUDecompose(scalarSquareMatrix &matrix, labelList &pivotIndices)
LU decompose the matrix with pivoting.
dimensionedScalar log10(const dimensionedScalar &ds)
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< Type > min(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
tmp< DimensionedField< typename powProduct< Type, r >::type, GeoMesh, Field > > pow(const DimensionedField< Type, GeoMesh, PrimitiveField > &df, typename powProduct< Type, r >::type)
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
defineTypeNameAndDebug(atmosphericBoundaryLayer, 0)
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dictionary dict