/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2016 OpenFOAM Foundation
\\/ M anipulation | Copyright (C) 2017 OpenCFD Ltd.
-------------------------------------------------------------------------------
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 .
\*---------------------------------------------------------------------------*/
#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()
{
if (dict_.readIfPresent("timeStart", timeStart_))
{
timeStart_ = time_.userTimeToTime(timeStart_);
}
if (dict_.readIfPresent("timeEnd", timeEnd_))
{
timeEnd_ = time_.userTimeToTime(timeEnd_);
}
deltaTCoeff_ = GREAT;
if (dict_.readIfPresent("deltaTCoeff", deltaTCoeff_))
{
nStepsToStartTimeChange_ = labelMax;
}
else
{
nStepsToStartTimeChange_ = 3;
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());
}
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;
ratioMax = 1/ratioMax;
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, mag(requiredSteps)-1);
scalar firstDeltaRatio = deltaTLoop/deltaT0_;
// Avoid division by zero for ratio = 1.0
scalar Sn =
deltaTLoop
*(pow(newRatio, mag(requiredSteps))-1)
/(newRatio-1+SMALL);
if (debug)
{
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 = mag(nSteps);
if (debug)
{
Info<< "firstDeltaRatio " << firstDeltaRatio << " rampDir"
<< rampDirectionUp << " newRatio " << newRatio
<< " y " << y << " steps " << requiredSteps < ratioMax && rampDirectionUp)
{
requiredSteps++;
if (debug)
{
Info<< "First ratio " << firstDeltaRatio
<< " exceeds threshold " << ratioMax << nl
<< "Increasing required steps " << requiredSteps
<< endl;
}
}
else if (firstDeltaRatio < ratioMax && !rampDirectionUp)
{
requiredSteps--;
if (debug)
{
Info<< "First ratio " << firstDeltaRatio
<< " exceeds threshold " << ratioMax << nl
<< "Decreasing required steps " << requiredSteps
<< endl;
}
}
else if
(
(newRatio > ratioMax && rampDirectionUp)
|| (newRatio < ratioMax && !rampDirectionUp)
)
{
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)
{
Sn = deltaT0_*firstDeltaRatio;
seriesDTCoeff_ = GREAT;
}
// Situations where we achieve wantedDT but fail to achieve
// multiple of writeInterval
scalar jumpError =
remainder(Sn + presentTime, wantedDT) / wantedDT;
if (mag(jumpError) > ROOTSMALL)
{
requiredSteps = label(timeToNextWrite/wantedDT);
firstDeltaRatio = timeToNextWrite/requiredSteps/deltaT0_;
}
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;
}
}
}
// * * * * * * * * * * * * * * * * 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_(labelMax),
executeControl_(t, dict, "execute"),
writeControl_(t, dict, "write"),
foPtr_(functionObject::New(name, t, dict_)),
executeTimeIndex_(-1),
deltaT0_(0),
seriesDTCoeff_(GREAT)
{
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()
{
// Store old deltaT for reference
deltaT0_ = time_.deltaTValue();
if (active() && (postProcess || executeControl_.execute()))
{
executeTimeIndex_ = time_.timeIndex();
foPtr_->execute();
}
return true;
}
bool Foam::functionObjects::timeControl::execute(const label subIndex)
{
if (active())
{
// Call underlying function object directly
foPtr_->execute(subIndex);
}
return true;
}
bool Foam::functionObjects::timeControl::write()
{
if (active() && (postProcess || writeControl_.execute()))
{
// Ensure written results reflect the current state
if (executeTimeIndex_ != time_.timeIndex())
{
executeTimeIndex_ = time_.timeIndex();
foPtr_->execute();
}
foPtr_->write();
}
return true;
}
bool Foam::functionObjects::timeControl::end()
{
if (active() && (executeControl_.execute() || writeControl_.execute()))
{
foPtr_->end();
}
return true;
}
bool Foam::functionObjects::timeControl::adjustTimeStep()
{
if (active())
{
if
(
writeControl_.control()
== Foam::timeControl::ocAdjustableRunTime
)
{
const scalar presentTime = time_.value();
//const scalar oldDeltaT = time_.deltaTValue();
// Call underlying fo to do time step adjusting
foPtr_->adjustTimeStep();
const scalar wantedDT = time_.deltaTValue();
//Pout<< "adjustTimeStep by " << foPtr_->name()
// << " from " << oldDeltaT << " to " << wantedDT << endl;
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
&& (
deltaTCoeff_ != GREAT
|| nSteps < nStepsToStartTimeChange_
)
)
{
// 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