Newer
Older
Henry Weller
committed
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2016 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2017 OpenCFD Ltd.
Henry Weller
committed
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "timeControlFunctionObject.H"
#include "polyMesh.H"
#include "mapPolyMesh.H"
#include "addToRunTimeSelectionTable.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
namespace functionObjects
{
defineTypeNameAndDebug(timeControl, 0);
}
}
// * * * * * * * * * * * * * * * Private Members * * * * * * * * * * * * * * //
void Foam::functionObjects::timeControl::readControls()
{
Andrew Heather
committed
if (dict_.readIfPresent("timeStart", timeStart_))
{
timeStart_ = time_.userTimeToTime(timeStart_);
}
if (dict_.readIfPresent("timeEnd", timeEnd_))
{
timeEnd_ = time_.userTimeToTime(timeEnd_);
}
deltaTCoeff_ = dict_.lookupOrDefault("deltaTCoeff", GREAT);
Andrew Heather
committed
Henry Weller
committed
dict_.readIfPresent("nStepsToStartTimeChange", nStepsToStartTimeChange_);
}
bool Foam::functionObjects::timeControl::active() const
{
return
time_.value() >= (timeStart_ - 0.5*time_.deltaTValue())
&& time_.value() <= (timeEnd_ + 0.5*time_.deltaTValue());
Henry Weller
committed
}
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
Foam::scalar Foam::functionObjects::timeControl::calcExpansion
(
const scalar startRatio,
const scalar y,
const label n
)
{
scalar ratio = startRatio;
// This function is used to calculate the 'expansion ratio' or
// 'time step growth factor'. Inputs:
// - ratio: wanted ratio
// - y: overall time required (divided by deltaT) to get from 1 to ratio
// - n: number of steps in which to get to wanted ratio
// Let a0 = first term in geometric progression (GP), an = last term,
// n = number of terms, ratio = ratio of GP, Sn = sum of series for
// first n terms.
// We have an=a0*ratio^(n-1) Sn = a0(ratio^n -1)/(ratio -1)
// -> Sn=an/ratio^(n-1)*(ratio^n -1)/(ratio -1), assume y=Sn/an
// -> y*ratio^(n-1)=(ratio^n -1)/(ratio -1)
// -> y*ratio^n - y*ratio^(n-1) -ratio^n +1 = 0
// Solve using Newton-Raphson iteration to determine the expansion
// ratio giving the desired overall timestep change.
// Note max iteration count to avoid hanging; function generally
// converges in 5 iterations or so.
for (label iter = 0; iter < 100; iter++)
{
// Dimensionless equation
scalar f = (y-1)*pow(ratio, n)+1-y*pow(ratio, n-1);
scalar dfdratio = (y-1)*n*pow(ratio, n-1)-y*(n-1)*pow(ratio, n-2);
scalar dratio = f/(dfdratio + SMALL);
ratio -= dratio;
// Exit if function is satisfied
if (mag(f) < 1e-6)
{
return ratio;
}
}
if (debug)
{
WarningInFunction
<< "Did not converge to find new timestep growth factor given "
<< "overall factor " << y << " and " << n << " steps to do it in."
<< endl << " Returning current best guess " << ratio << endl;
}
return ratio;
}
void Foam::functionObjects::timeControl::calcDeltaTCoeff
(
scalar& requiredDeltaTCoeff,
const scalar wantedDT,
const label nSteps,
const scalar presentTime,
const scalar timeToNextWrite,
const bool rampDirectionUp
)
{
const scalar writeInterval = writeControl_.interval();
// Set new deltaT approximated to last step value
// adjust to include geometric series sum of nSteps terms
scalar newDeltaT = deltaT0_;
if (seriesDTCoeff_ != GREAT)
{
newDeltaT *= seriesDTCoeff_;
}
// Recalculate new deltaTCoeff_ based on rounded steps
requiredDeltaTCoeff = Foam::exp(Foam::log(wantedDT/newDeltaT)/nSteps);
// Calculate total time required with given dT increment
// to reach specified dT value
scalar requiredTimeInterval = newDeltaT;
// For nSteps = 1 during we get coeff = 1.0. Adding SMALL in denominator
// might lead to SMALL requiredTimeInterval, and further unnecessary
// calculations
if (requiredDeltaTCoeff != 1.0)
{
requiredTimeInterval *=
(pow(requiredDeltaTCoeff, nSteps) - 1)
/(requiredDeltaTCoeff - 1);
}
// Nearest time which is multiple of wantedDT, after
// requiredTimeInterval
scalar timeToNextMultiple = -presentTime;
if (rampDirectionUp)
{
timeToNextMultiple +=
ceil
(
(presentTime + requiredTimeInterval)
/wantedDT
)
*wantedDT;
}
else
{
timeToNextMultiple +=
floor
(
(presentTime - requiredTimeInterval)
/wantedDT
)
*wantedDT;
}
// Check nearest time and decide parameters
if (timeToNextWrite <= timeToNextMultiple)
{
// As the ramping can't be achieved, use current, unadapted deltaT
// (from e.g. maxCo calculation in solver) and adjust when
// writeInterval is closer than this deltaT
newDeltaT = deltaT0_;
if (timeToNextWrite < newDeltaT)
{
newDeltaT = timeToNextWrite;
}
// Corner case when required time span is less than writeInterval
if (requiredTimeInterval > writeInterval)
{
WarningInFunction
<< "With given ratio needed time span "
<< requiredTimeInterval
<< " exceeds available writeInterval "
<< writeInterval << nl
<< "Disabling all future time step ramping"
<< endl;
deltaTCoeff_ = GREAT;
newDeltaT = wantedDT;
}
else
{
// Reset the series ratio, as this could change later
seriesDTCoeff_ = GREAT;
if (debug)
{
Info<< "Disabling ramping until time "
<< presentTime+timeToNextWrite << endl;
}
}
requiredDeltaTCoeff = newDeltaT/deltaT0_;
}
else
{
// Find the minimum number of time steps (with constant deltaT
// variation) to get us to the writeInterval and multiples of wantedDT.
// Increases the number of nSteps to do the adjustment in until it
// has reached one that is possible. Returns the new expansionratio.
scalar y = timeToNextMultiple/wantedDT;
label requiredSteps = nSteps;
scalar ratioEstimate = deltaTCoeff_;
scalar ratioMax = deltaTCoeff_;
if (seriesDTCoeff_ != GREAT)
{
ratioEstimate = seriesDTCoeff_;
}
if (!rampDirectionUp)
{
ratioEstimate = 1/ratioEstimate;
requiredSteps *= -1;
}
// Provision for fall-back value if we can't satisfy requirements
bool searchConverged = false;
for (label iter = 0; iter < 100; iter++)
{
// Calculate the new expansion and the overall time step changes
const scalar newRatio = calcExpansion
(
ratioEstimate,
y,
requiredSteps
);
const scalar deltaTLoop = wantedDT/pow(newRatio, requiredSteps-1);
scalar firstDeltaRatio = deltaTLoop/deltaT0_;
if (debug)
{
// Avoid division by zero for ratio = 1.0
scalar Sn =
deltaTLoop
*(pow(newRatio, requiredSteps)-1)
/(newRatio-1+SMALL);
Info<< " nSteps " << requiredSteps
<< " ratioEstimate " << ratioEstimate
<< " a0 " << deltaTLoop
<< " firstDeltaRatio " << firstDeltaRatio
<< " Sn " << Sn << " Sn+Time " << Sn+presentTime
<< " seriesRatio "<< newRatio << endl;
}
// If series ratio becomes unity check next deltaT multiple
// Do the same if first ratio is decreasing when ramping up!
if
(
(firstDeltaRatio < 1.0 && rampDirectionUp)
|| (firstDeltaRatio > 1.0 && !rampDirectionUp)
|| newRatio == 1.0
)
{
y += 1;
requiredSteps = nSteps;
if (debug)
{
Info<< "firstDeltaRatio " << firstDeltaRatio << " rampDir"
<< rampDirectionUp << " newRatio " << newRatio
<< " y " << y << " steps " << requiredSteps <<endl;
}
continue;
}
if (firstDeltaRatio > ratioMax)
{
requiredSteps++;
if (debug)
{
Info<< "First ratio " << firstDeltaRatio
<< " exceeds threshold " << ratioMax << nl
<< "Increasing required steps " << requiredSteps
<< endl;
}
}
else if (newRatio > ratioMax)
{
y += 1;
requiredSteps = nSteps;
if (debug)
{
Info<< "Series ratio " << newRatio << "exceeds threshold "
<< ratioMax << nl << "Consider next deltaT multiple "
<< y*wantedDT + presentTime << endl;
}
}
else
{
seriesDTCoeff_ = newRatio;
// Reset future series expansion at last step
if (requiredSteps == 1)
{
seriesDTCoeff_ = GREAT;
}
if (debug)
{
Info<< "All conditions satisfied deltaT0_:" << deltaT0_
<< " calculated deltaTCoeff:" << firstDeltaRatio
<< " series ratio set to:" << seriesDTCoeff_ << endl;
}
searchConverged = true;
requiredDeltaTCoeff = firstDeltaRatio;
break;
}
}
if (!searchConverged)
{
if (debug)
{
// Not converged. Return fall-back value
WarningInFunction
<< "Did not converge to find new timestep growth factor"
<< " given overall factor " << y
<< " and " << requiredSteps << " steps to do it in."
<< nl << " Falling back to non-adjusted deltatT "
<< deltaT0_ << endl;
}
requiredDeltaTCoeff = 1.0;
}
}
}
Henry Weller
committed
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::functionObjects::timeControl::timeControl
(
const word& name,
const Time& t,
const dictionary& dict
)
:
functionObject(name),
time_(t),
dict_(dict),
timeStart_(-VGREAT),
timeEnd_(VGREAT),
nStepsToStartTimeChange_
(
dict.lookupOrDefault("nStepsToStartTimeChange", 3)
),
executeControl_(t, dict, "execute"),
writeControl_(t, dict, "write"),
foPtr_(functionObject::New(name, t, dict_)),
executeTimeIndex_(-1),
deltaT0_(0),
seriesDTCoeff_(GREAT)
Henry Weller
committed
{
readControls();
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
bool Foam::functionObjects::timeControl::entriesPresent(const dictionary& dict)
{
if
(
Foam::timeControl::entriesPresent(dict, "write")
|| Foam::timeControl::entriesPresent(dict, "output") // backwards compat
|| Foam::timeControl::entriesPresent(dict, "execute")
|| dict.found("timeStart")
|| dict.found("timeEnd")
)
{
return true;
}
return false;
}
bool Foam::functionObjects::timeControl::execute()
Henry Weller
committed
{
// Store old deltaT for reference
deltaT0_ = time_.deltaTValue();
if (active() && (postProcess || executeControl_.execute()))
Henry Weller
committed
{
executeTimeIndex_ = time_.timeIndex();
foPtr_->execute();
Henry Weller
committed
}
return true;
}
bool Foam::functionObjects::timeControl::write()
Henry Weller
committed
{
if (active() && (postProcess || writeControl_.execute()))
Henry Weller
committed
{
// Ensure written results reflect the current state
if (executeTimeIndex_ != time_.timeIndex())
{
executeTimeIndex_ = time_.timeIndex();
foPtr_->execute();
}
foPtr_->write();
Henry Weller
committed
}
return true;
}
bool Foam::functionObjects::timeControl::end()
{
if (active() && (executeControl_.execute() || writeControl_.execute()))
Henry Weller
committed
{
foPtr_->end();
Henry Weller
committed
}
return true;
}
bool Foam::functionObjects::timeControl::adjustTimeStep()
{
Henry Weller
committed
{
Henry Weller
committed
(
writeControl_.control()
== Foam::timeControl::ocAdjustableRunTime
)
{
const scalar presentTime = time_.value();
Henry Weller
committed
//const scalar oldDeltaT = time_.deltaTValue();
Henry Weller
committed
// Call underlying fo to do time step adjusting
foPtr_->adjustTimeStep();
const scalar wantedDT = time_.deltaTValue();
Henry Weller
committed
//Pout<< "adjustTimeStep by " << foPtr_->name()
// << " from " << oldDeltaT << " to " << wantedDT << endl;
Henry Weller
committed
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
const label writeTimeIndex = writeControl_.executionIndex();
const scalar writeInterval = writeControl_.interval();
// Get time to next writeInterval
scalar timeToNextWrite =
(writeTimeIndex+1)*writeInterval
- (presentTime - time_.startTime().value());
if (timeToNextWrite <= 0.0)
{
timeToNextWrite = writeInterval;
}
// The target DT (coming from fixed dT or maxCo etc) may not be
// an integral multiple of writeInterval. If so ignore any step
// calculation and give priority to writeInterval.
// Note looser tolerance on the relative intervalError - SMALL is
// too strict.
scalar intervalError =
remainder(writeInterval, wantedDT)/writeInterval;
if
(
mag(intervalError) > ROOTSMALL
|| deltaTCoeff_ == GREAT
)
{
scalar deltaT = time_.deltaTValue();
scalar nSteps = timeToNextWrite/deltaT;
// For tiny deltaT the label can overflow!
if (nSteps < labelMax)
{
// nSteps can be < 1 so make sure at least 1
label nStepsToNextWrite = max(1, round(nSteps));
scalar newDeltaT = timeToNextWrite/nStepsToNextWrite;
// Backwards compatibility: clip deltaT to 0.2 .. 2
scalar clipThreshold = 2;
if (deltaTCoeff_ != GREAT)
{
clipThreshold = deltaTCoeff_;
}
// Adjust time step
if (newDeltaT >= deltaT)
{
deltaT = min(newDeltaT, clipThreshold*deltaT);
}
else
{
clipThreshold = 1/clipThreshold;
deltaT = max(newDeltaT, clipThreshold*deltaT);
}
const_cast<Time&>(time_).setDeltaT(deltaT, false);
}
}
else
{
// Set initial approximation to user defined ratio.
scalar requiredDeltaTCoeff = deltaTCoeff_;
// Use if we have series expansion ratio from last attempt.
// Starting from user defined coefficient, might get different
// steps and convergence time (could be recursive - worst case)
// This is more likely to happen when coefficient limit is high
if (seriesDTCoeff_ != GREAT)
{
requiredDeltaTCoeff = seriesDTCoeff_;
}
// Avoid divide by zero if we need ratio = 1
if (1/Foam::log(requiredDeltaTCoeff)> labelMax)
{
requiredDeltaTCoeff = deltaTCoeff_;
}
// Try and observe the deltaTCoeff and the writeInterval
// and the wanted deltaT. Below code calculates the
// minimum number of time steps in which to end up on
// writeInterval+n*deltaT with the minimum possible deltaTCoeff.
// This is non-trivial!
// Approx steps required from present dT to required dT
label nSteps = 0;
if (wantedDT < deltaT0_)
{
nSteps = label
(
floor
(
Foam::log(wantedDT/deltaT0_)
/Foam::log(requiredDeltaTCoeff + SMALL)
)
);
}
else
{
nSteps = label
(
ceil
(
Foam::log(wantedDT/deltaT0_)
/Foam::log(requiredDeltaTCoeff + SMALL)
)
);
}
// Negative steps -> ramp down needed,
// zero steps -> we are at writeInterval-multiple time!
if (nSteps < 1)
{
// Not enough steps to do gradual time step adjustment
// if earlier deltaT larger than wantedDT
if (wantedDT < deltaT0_)
{
requiredDeltaTCoeff = 1/requiredDeltaTCoeff;
calcDeltaTCoeff
(
requiredDeltaTCoeff,
wantedDT,
nSteps,
presentTime,
timeToNextWrite,
false
);
}
else
{
if (timeToNextWrite > wantedDT)
{
requiredDeltaTCoeff = wantedDT/deltaT0_;
}
else
{
requiredDeltaTCoeff = timeToNextWrite/deltaT0_;
}
}
// When allowed deltaTCoeff can't give required ramp
if (requiredDeltaTCoeff > deltaTCoeff_ && debug)
{
WarningInFunction
<< "Required deltaTCoeff "<< requiredDeltaTCoeff
<< " is larger than allowed value "<< deltaTCoeff_
<< endl;
}
}
else
{
calcDeltaTCoeff
(
requiredDeltaTCoeff,
wantedDT,
nSteps,
presentTime,
timeToNextWrite,
true
);
}
// Calculate deltaT to be used based on ramp
scalar newDeltaT = deltaT0_*requiredDeltaTCoeff;
// Set time, deltaT adjustments for writeInterval purposes
// are already taken care. Hence disable auto-update
const_cast<Time&>( time_).setDeltaT(newDeltaT, false);
if (seriesDTCoeff_ < 1.0)
{
requiredDeltaTCoeff = 1/requiredDeltaTCoeff;
seriesDTCoeff_ = 1/seriesDTCoeff_;
}
}
}
else
{
foPtr_->adjustTimeStep();
const scalar wantedDT = time_.deltaTValue();
Henry Weller
committed
Henry Weller
committed
{
// Clip time step change to deltaTCoeff
scalar requiredDeltaTCoeff =
(
max
(
1.0/deltaTCoeff_,
min(deltaTCoeff_, wantedDT/deltaT0_)
)
);
scalar newDeltaT = deltaT0_*requiredDeltaTCoeff;
// Set time, deltaT adjustments for writeInterval purposes
// are already taken care. Hence disable auto-update
const_cast<Time&>( time_).setDeltaT(newDeltaT, false);
}
else
{
// Set the DT without any ramping
const_cast<Time&>( time_).setDeltaT(wantedDT, false);
Henry Weller
committed
}
}
}
return true;
}
bool Foam::functionObjects::timeControl::read(const dictionary& dict)
Henry Weller
committed
{
if (dict != dict_)
{
dict_ = dict;
writeControl_.read(dict);
executeControl_.read(dict);
readControls();
return true;
}
else
{
return false;
}
}
bool Foam::functionObjects::timeControl::filesModified() const
{
bool mod = false;
if (active())
{
mod = foPtr_->filesModified();
}
return mod;
}
void Foam::functionObjects::timeControl::updateMesh(const mapPolyMesh& mpm)
Henry Weller
committed
{
if (active())
{
foPtr_->updateMesh(mpm);
}
}
void Foam::functionObjects::timeControl::movePoints(const polyMesh& mesh)