diff --git a/src/finiteVolume/Make/files b/src/finiteVolume/Make/files
index db88df52ae..e7023dea21 100644
--- a/src/finiteVolume/Make/files
+++ b/src/finiteVolume/Make/files
@@ -425,6 +425,7 @@ $(coupling)/externalFileCoupler.C
solutionControl = $(general)/solutionControl
$(solutionControl)/solutionControl/solutionControl.C
+$(solutionControl)/loopControl/loopControl.C
$(solutionControl)/simpleControl/simpleControl.C
$(solutionControl)/pimpleControl/pimpleControl.C
$(solutionControl)/pisoControl/pisoControl.C
diff --git a/src/finiteVolume/cfdTools/general/solutionControl/loopControl/fvSolution b/src/finiteVolume/cfdTools/general/solutionControl/loopControl/fvSolution
new file mode 100644
index 0000000000..19d8b0fe0b
--- /dev/null
+++ b/src/finiteVolume/cfdTools/general/solutionControl/loopControl/fvSolution
@@ -0,0 +1,47 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| ========= | |
+| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
+| \\ / O peration | Version: plus |
+| \\ / A nd | Web: www.OpenFOAM.com |
+| \\/ M anipulation | |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+ version 2.0;
+ format ascii;
+ class dictionary;
+ object fvSolution;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+
+SIMPLE
+{
+ energyCoupling
+ {
+ // (Max) number of loops
+ iterations 200;
+
+ // The interval to execute onLoop function-objects
+ interval 0;
+
+ // Convergence criteria to terminate loop
+ convergence
+ {
+ "h" 1e-3;
+ }
+
+ // Names of function objects to fire with execute(int) when looping
+ onLoop ( );
+
+ // Names of function objects to fire with execute(int) when converged
+ onConverged ( );
+
+ // Names of function objects to fire with execute(int) when loop ends
+ // without convergence
+ onEnd ( );
+ }
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteVolume/cfdTools/general/solutionControl/loopControl/loopControl.C b/src/finiteVolume/cfdTools/general/solutionControl/loopControl/loopControl.C
new file mode 100644
index 0000000000..f453124368
--- /dev/null
+++ b/src/finiteVolume/cfdTools/general/solutionControl/loopControl/loopControl.C
@@ -0,0 +1,280 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | Copyright (C) 2017 OpenCFD Ltd.
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+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 "loopControl.H"
+#include "fvSolution.H"
+#include "wordRes.H"
+#include "solutionControl.H"
+
+// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
+
+void Foam::loopControl::clear()
+{
+ total_ = 0;
+ interval_ = 0;
+
+ convergenceDict_.clear();
+ onLoop_.clear();
+ onConverged_.clear();
+ onEnd_.clear();
+
+ converged_ = false;
+}
+
+
+void Foam::loopControl::read(const dictionary& dict)
+{
+ clear();
+
+ bool enabled = dict.lookupOrDefault("enabled", true);
+
+ if (enabled)
+ {
+ scalar timeStart;
+ if (dict.readIfPresent("timeStart", timeStart))
+ {
+ timeStart = time_.userTimeToTime(timeStart);
+
+ enabled =
+ (
+ enabled
+ && time_.value() >= (timeStart - 0.5*time_.deltaTValue())
+ );
+ }
+
+ scalar timeEnd;
+ if (dict.readIfPresent("timeEnd", timeEnd))
+ {
+ timeEnd = time_.userTimeToTime(timeEnd);
+
+ enabled =
+ (
+ enabled
+ && time_.value() <= (timeEnd + 0.5*time_.deltaTValue())
+ );
+ }
+ }
+
+ if (!enabled)
+ {
+ return;
+ }
+
+ dict.readIfPresent("iterations", total_);
+ dict.readIfPresent("interval", interval_);
+
+ convergenceDict_ = dict.subOrEmptyDict("convergence");
+
+ dict.readIfPresent("onLoop", onLoop_);
+ dict.readIfPresent("onConverged", onConverged_);
+ dict.readIfPresent("onEnd", onEnd_);
+}
+
+
+bool Foam::loopControl::checkConverged() const
+{
+ if (convergenceDict_.empty())
+ {
+ return false;
+ }
+
+ HashTable meshes = time_.lookupClass();
+
+ bool achieved = true;
+ bool checked = false; // safety that some checks were indeed performed
+
+ forAllConstIters(meshes, meshIter)
+ {
+ const fvMesh& regionMesh = *(meshIter.object());
+
+ const dictionary& solverDict = regionMesh.solverPerformanceDict();
+
+ forAllConstIters(solverDict, iter)
+ {
+ const entry& dataDictEntry = *iter;
+
+ const word& variableName = dataDictEntry.keyword();
+
+ const scalar absTol =
+ convergenceDict_.lookupOrDefault(variableName, -1);
+
+ if (absTol > 0)
+ {
+ // Treat like a SIMPLE control
+
+ Pair residuals =
+ solutionControl::maxResidual
+ (
+ regionMesh,
+ dataDictEntry
+ );
+
+ checked = true;
+ achieved = achieved && (residuals.first() < absTol);
+ }
+ }
+ }
+
+ return checked && achieved;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
+
+Foam::loopControl::loopControl
+(
+ Time& runTime,
+ const label nCycles,
+ const word& loopName
+)
+:
+ subLoopTime(runTime, nCycles),
+ name_(loopName),
+ interval_(0),
+ convergenceDict_(),
+ onLoop_(),
+ onConverged_(),
+ onEnd_(),
+ converged_(false)
+{}
+
+
+Foam::loopControl::loopControl
+(
+ Time& runTime,
+ const dictionary& algorithmDict,
+ const word& dictName
+)
+:
+ loopControl(runTime, 0, dictName)
+{
+ // The loop sub-dictionary
+ const dictionary* dictptr = algorithmDict.subDictPtr(dictName);
+
+ if (dictptr)
+ {
+ // Info<< dictName << *dictptr << endl;
+ read(*dictptr);
+ }
+}
+
+
+Foam::loopControl::loopControl
+(
+ Time& runTime,
+ const word& algorithmName,
+ const word& dictName
+)
+:
+ loopControl(runTime, 0, dictName)
+{
+ fvSolution fvsol(time_);
+
+ // Eg, PIMPLE or SIMPLE from
+ const dictionary* dictptr =
+ fvsol.solutionDict().subDictPtr(algorithmName);
+
+ if (dictptr)
+ {
+ // The loop sub-dictionary
+ dictptr = dictptr->subDictPtr(dictName);
+
+ if (dictptr)
+ {
+ // Info<< dictName << *dictptr << endl;
+ read(*dictptr);
+ }
+ }
+}
+
+
+// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
+
+Foam::loopControl::~loopControl()
+{
+ stop();
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
+
+bool Foam::loopControl::loop()
+{
+ bool active = (index_ < total_); // as per status()
+
+ if (active)
+ {
+ operator++();
+
+ converged_ = checkConverged();
+
+ if (converged_)
+ {
+ time_.functionObjects().execute(onConverged_, index_);
+ stop();
+ return false;
+ }
+ else if
+ (
+ interval_ && !(index_ % interval_)
+ && !onLoop_.empty()
+ )
+ {
+ time_.functionObjects().execute(onLoop_, index_);
+ }
+ }
+ else if (index_)
+ {
+ // Not active, the loop condition has now exiting on the last subloop
+
+ if (!converged_ && !onEnd_.empty())
+ {
+ time_.functionObjects().execute(onEnd_, index_);
+ }
+ }
+
+ return active;
+}
+
+
+// * * * * * * * * * * * * * * Ostream Operator * * * * * * * * * * * * * * //
+
+Foam::Ostream& Foam::operator<<(Ostream& os, const loopControl& ctrl)
+{
+ os << ctrl.name() << ": ";
+ if (ctrl.nCycles() && ctrl.index() <= ctrl.nCycles())
+ {
+ os << ctrl.index() << '/' << ctrl.nCycles();
+ }
+ else
+ {
+ os << "off";
+ }
+
+ return os;
+}
+
+
+// ************************************************************************* //
diff --git a/src/finiteVolume/cfdTools/general/solutionControl/loopControl/loopControl.H b/src/finiteVolume/cfdTools/general/solutionControl/loopControl/loopControl.H
new file mode 100644
index 0000000000..ced72d57e3
--- /dev/null
+++ b/src/finiteVolume/cfdTools/general/solutionControl/loopControl/loopControl.H
@@ -0,0 +1,227 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | Copyright (C) 2017 OpenCFD Ltd.
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+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 .
+
+Class
+ Foam::loopControl
+
+Description
+ A class for managing arbitrary loops with the ability to invoke
+ function object execution.
+
+Usage
+ Examples of function object specification:
+ \verbatim
+ SIMPLE
+ {
+ energyCoupling
+ {
+ iterations 100;
+ onLoop ();
+ onConverged ( externalCoupled "loopThings.*" );
+
+ convergence
+ {
+ "h" 1e-3;
+ }
+ }
+ }
+
+ Where the loop entries comprise:
+ \table
+ Property | Description | Required | Default
+ enabled | active/deactive loop | no | true
+ iteration | times to loop | no | 0
+ timeStart | begin time for loop activation | no | -VGREAT
+ timeEnd | end time of loop activation | no | VGREAT
+ interval | sub-interval to execute onLoop | no | 0
+ onLoop | function object names to call at executeInterval | no
+ onConverged | function object names to call when converged | no
+ onEnd | function object names to call when loop ends | no
+ convergence | dictionary of convergence values to check | no
+ \endtable
+
+ The function object names listed by \c onLoop, \c onConverged, \c onEnd
+ must implement an \c execute(int) method.
+ If the time controls \c timeStart or \c timeEnd are used for the loop,
+ these values are only inspected upon creation, not during execution.
+
+SeeAlso
+ fvSolution
+
+SourceFiles
+ loopControl.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef loopControl_H
+#define loopControl_H
+
+#include "subLoopTime.H"
+#include "dictionary.H"
+#include "wordReList.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+ Class loopControl Declaration
+\*---------------------------------------------------------------------------*/
+
+class loopControl
+:
+ public subLoopTime
+{
+ // Private Member Functions
+
+ //- Reset
+ void clear();
+
+ //- Read settings from dictionary
+ void read(const dictionary& dict);
+
+ //- Execute specified function names
+ bool checkConverged() const;
+
+ //- Disallow default bitwise copy construct
+ loopControl(const loopControl&) = delete;
+
+ //- Disallow default bitwise assignment
+ void operator=(const loopControl&) = delete;
+
+protected:
+
+ // Protected data
+
+ //- Name of the loop control (the lookup dictionary name).
+ word name_;
+
+ //- The interval to execute onLoop function-objects
+ label interval_;
+
+ //- Dictionary for checking convergence (all regions)
+ dictionary convergenceDict_;
+
+ //- Function object names to fire during the loop (at executeInterval)
+ List onLoop_;
+
+ //- Function object names to fire on convergence
+ List onConverged_;
+
+ //- Function object names to fire when the loop exits without
+ //- convergence
+ List onEnd_;
+
+ //- Convergence tests passed
+ bool converged_;
+
+public:
+
+ // Constructors
+
+ //- Construct from time with fixed number of cycles
+ // \param runTime the top-level time
+ // \param nCycles the number of times to loop
+ // \param loopName the name of the loop
+ loopControl
+ (
+ Time& runTime,
+ const label nCycles,
+ const word& dictName = "loop"
+ );
+
+ //- Construct from fvSolution dictionary based on time and the name
+ //- of the controlling algorithm
+ // \param runTime the top-level time
+ // \param algorithmName the name of the fvSolution dictionary,
+ // typically PIMPLE or SIMPLE
+ // \param dictName the name of the control dictionary
+ loopControl
+ (
+ Time& runTime,
+ const word& algorithmName,
+ const word& dictName = "loop"
+ );
+
+ //- Construct from fvSolution dictionary based on time and the name
+ //- of the controlling algorithm
+ // \param runTime the top-level time
+ // \param algorithmDict the fvSolution algorithm dictionary,
+ // typically PIMPLE or SIMPLE
+ // \param dictName the name of the control dictionary
+ loopControl
+ (
+ Time& runTime,
+ const dictionary& algorithmDict,
+ const word& dictName = "loop"
+ );
+
+
+ //- Destructor
+ ~loopControl();
+
+
+ // Member Functions
+
+ //- Name of the loop control
+ inline const word& name() const
+ {
+ return name_;
+ }
+
+ //- The interval to execute onLoop function-objects
+ inline label interval() const
+ {
+ return interval_;
+ }
+
+ //- True if looping is active, increments the index and executes
+ //- the onLoop and onConverged functions.
+ // Example usage,
+ // \code
+ // while (control.loop())
+ // {
+ // solve;
+ // }
+ // \endcode
+ bool loop();
+
+
+ // IOstream operators
+
+ //- Write name and state (on/off, index/total) to Ostream
+ friend Ostream& operator<<(Ostream& os, const loopControl& ctrl);
+
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //