diff --git a/applications/solvers/incompressible/adjointOptimisationFoam/adjointOptimisationFoam.C b/applications/solvers/incompressible/adjointOptimisationFoam/adjointOptimisationFoam.C
index 7d29d4849783caabea3f670d343b0c722cd85d7d..ddf2c809abf5ac2cc9fddf04759989fb565727bf 100644
--- a/applications/solvers/incompressible/adjointOptimisationFoam/adjointOptimisationFoam.C
+++ b/applications/solvers/incompressible/adjointOptimisationFoam/adjointOptimisationFoam.C
@@ -64,13 +64,13 @@ int main(int argc, char *argv[])
         {
             // Solve all primal equations
             om.solvePrimalEquations();
-        }
 
-        // Update primal-based quantities of the adjoint solvers
-        om.updatePrimalBasedQuantities();
+            // Clear sensitivities
+            om.clearSensitivities();
 
-        // Solve all adjoint equations
-        om.solveAdjointEquations();
+            // Solve all adjoint equations
+            om.solveAdjointEquations();
+        }
     }
 
     Info<< "End\n" << endl;
diff --git a/applications/utilities/postProcessing/optimisation/computeSensitivities/computeSensitivities.C b/applications/utilities/postProcessing/optimisation/computeSensitivities/computeSensitivities.C
index b7ced6e309fc22fd18a6bbdc37a03ceb403e9979..b1ec73c7553b4ef6ca802299d224a7d8d205dc22 100644
--- a/applications/utilities/postProcessing/optimisation/computeSensitivities/computeSensitivities.C
+++ b/applications/utilities/postProcessing/optimisation/computeSensitivities/computeSensitivities.C
@@ -60,7 +60,8 @@ int main(int argc, char *argv[])
         forAll(adjointSolvers, asI)
         {
             adjointSolvers[asI].getObjectiveManager().updateAndWrite();
-            adjointSolvers[asI].computeObjectiveSensitivities();
+            adjointSolvers[asI].
+                computeObjectiveSensitivities(om.getDesignVariables());
         }
     }
 
diff --git a/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/files b/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/files
deleted file mode 100644
index c6e8c8fb9df222cf7edec704aa32792d9d3fd819..0000000000000000000000000000000000000000
--- a/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/files
+++ /dev/null
@@ -1,3 +0,0 @@
-writeActiveDesignVariables.C
-
-EXE = $(FOAM_APPBIN)/writeActiveDesignVariables
diff --git a/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/options b/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/options
deleted file mode 100644
index f5b51c5ca77603cf7808511472bb86c85434649f..0000000000000000000000000000000000000000
--- a/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/options
+++ /dev/null
@@ -1,14 +0,0 @@
-EXE_INC = \
-    -I$(LIB_SRC)/finiteVolume/lnInclude \
-    -I$(LIB_SRC)/meshTools/lnInclude \
-    -I$(LIB_SRC)/finiteVolume/cfdTools \
-    -I$(LIB_SRC)/dynamicMesh/lnInclude \
-    -I$(LIB_SRC)/optimisation/adjointOptimisation/adjoint/lnInclude
-
-EXE_LIBS = \
-    -lfiniteVolume \
-    -lmeshTools \
-    -lfvOptions \
-    -ldynamicMesh \
-    -lfvMotionSolvers \
-    -ladjointOptimisation
diff --git a/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/writeActiveDesignVariables.C b/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/writeActiveDesignVariables.C
deleted file mode 100644
index b87b0bb837a981aed18cc9cbab47ae873d0c59a0..0000000000000000000000000000000000000000
--- a/applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/writeActiveDesignVariables.C
+++ /dev/null
@@ -1,108 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019-2022 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 <http://www.gnu.org/licenses/>.
-
-Application
-    writeActiveDesignVariables
-
-Description
-    Writes the active design variables based on the selected parameterisation
-    scheme, as read from the meshMovement part of optimisationDict.
-    Keeps a back-up of the original optimisationDict in
-    system/optimisationDict.org, as comments in the dict will be lost.
-
-\*---------------------------------------------------------------------------*/
-
-#include "fvCFD.H"
-#include "optMeshMovement.H"
-#include "updateMethod.H"
-#include "OFstream.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-int main(int argc, char *argv[])
-{
-    #include "setRootCase.H"
-    #include "createTime.H"
-    #include "createMesh.H"
-
-    IOdictionary optDict
-    (
-        IOobject
-        (
-            "optimisationDict",
-            mesh.time().caseSystem(),
-            mesh,
-            IOobject::MUST_READ_IF_MODIFIED,
-            IOobject::NO_WRITE
-        )
-    );
-
-    // Back-up old optimisationDict, to maintain potential comments in it
-    if (Pstream::master())
-    {
-        Foam::cp(optDict.objectPath(), optDict.objectPath() + ".org");
-    }
-
-    // Construct mesh movement object and grab active design variables
-    // Will exit with error if not implemented for this type
-    const dictionary& movementDict =
-        optDict.subDict("optimisation").subDict("meshMovement");
-    // Empty patch list will do
-    labelList patchIDs(0);
-    autoPtr<optMeshMovement> movementPtr
-        (optMeshMovement::New(mesh, movementDict, patchIDs));
-    const labelList activeDesignVariables =
-        movementPtr().getActiveDesignVariables();
-
-    // Construct update method to grab the type
-    dictionary& updateMethodDict =
-        optDict.subDict("optimisation").subDict("updateMethod");
-    autoPtr<updateMethod> updMethod(updateMethod::New(mesh, updateMethodDict));
-
-    // Add to appropriate dictionary (creates it if it does not exist)
-    dictionary& coeffsDict = updateMethodDict.subDictOrAdd(updMethod().type());
-    coeffsDict.add<labelList>
-    (
-        "activeDesignVariables",
-        activeDesignVariables,
-        true
-    );
-
-    // Write modified dictionary
-    optDict.regIOobject::writeObject
-    (
-        IOstreamOption(IOstreamOption::ASCII),
-        true
-    );
-
-    Info<< "End\n" << endl;
-
-    return 0;
-}
-
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/ATCModel/ATCModel/ATCModel.C b/src/optimisation/adjointOptimisation/adjoint/ATCModel/ATCModel/ATCModel.C
index d6791326aabf9e58ebb0a331734147b705a48482..9a3bb69b9fe7c858ae734a2a68eb3c5e88ec2dae 100644
--- a/src/optimisation/adjointOptimisation/adjoint/ATCModel/ATCModel/ATCModel.C
+++ b/src/optimisation/adjointOptimisation/adjoint/ATCModel/ATCModel/ATCModel.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -214,6 +214,7 @@ void ATCModel::computeLimiter
             scheme.interpolate(limiter)
         );
         limiter = fvc::average(faceLimiter);
+        limiter.correctBoundaryConditions();
     }
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/ATCModel/zeroATCcells/faceCells/faceCells.C b/src/optimisation/adjointOptimisation/adjoint/ATCModel/zeroATCcells/faceCells/faceCells.C
index 9f1d346ed74e35c18af542ac91dd862eee08daee..714af8c9219209d9294bd5b493d9a5a53008e44b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/ATCModel/zeroATCcells/faceCells/faceCells.C
+++ b/src/optimisation/adjointOptimisation/adjoint/ATCModel/zeroATCcells/faceCells/faceCells.C
@@ -52,26 +52,26 @@ faceCells::faceCells
 :
     zeroATCcells(mesh, dict)
 {
+    DynamicList<label> cellIDs;
     for (const fvPatch& patch : mesh_.boundary())
     {
         for (const word& patchType : zeroATCPatches_)
         {
             if (patch.type() == patchType)
             {
-                const labelList& faceCells_ = patch.faceCells();
-                zeroATCcells_.append(faceCells_);
+                cellIDs.push_back(patch.faceCells());
             }
         }
     }
 
-    for (const label zoneID: zeroATCZones_)
+    for (const label zoneID : zeroATCZones_)
     {
-        if (zoneID !=-1)
+        if (zoneID != -1)
         {
-            const labelList& zoneCells = mesh_.cellZones()[zoneID];
-            zeroATCcells_.append(zoneCells);
+            cellIDs.push_back(mesh_.cellZones()[zoneID]);
         }
     }
+    zeroATCZones_.transfer(cellIDs);
 
     Info<< "Setting limiter on "
         << returnReduce(zeroATCcells_.size(), sumOp<label>())
diff --git a/src/optimisation/adjointOptimisation/adjoint/Make/files b/src/optimisation/adjointOptimisation/adjoint/Make/files
index c389a14403696b1e5a0aeee6670221c64043b159..8a1b1662f60c97c00dcc8afb75898825df3add20 100644
--- a/src/optimisation/adjointOptimisation/adjoint/Make/files
+++ b/src/optimisation/adjointOptimisation/adjoint/Make/files
@@ -27,6 +27,7 @@ solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.C
 solvers/adjointSolvers/adjointSolver/adjointSolver.C
 solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.C
 solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.C
+solvers/adjointSolvers/null/adjointNull.C
 
 /* ADJOINT SOLVER MANAGER */
 solvers/adjointSolverManager/adjointSolverManager.C
@@ -49,16 +50,16 @@ objectives/incompressible/objectiveForce/objectiveForce.C
 objectives/incompressible/objectiveMoment/objectiveMoment.C
 objectives/incompressible/objectivePtLosses/objectivePtLosses.C
 objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.C
-objectives/incompressible/objectivePartialVolume/objectivePartialVolume.C
 objectives/incompressible/objectiveNutSqr/objectiveNutSqr.C
 objectives/incompressible/objectiveFlowRate/objectiveFlowRate.C
 objectives/incompressible/objectiveFlowRatePartition/objectiveFlowRatePartition.C
 objectives/incompressible/objectiveUniformityPatch/objectiveUniformityPatch.C
 objectives/incompressible/objectiveUniformityCellZone/objectiveUniformityCellZone.C
+objectives/geometric/objectiveGeometric/objectiveGeometric.C
+objectives/geometric/objectivePartialVolume/objectivePartialVolume.C
 
 /* OBJECTIVE MANAGER*/
-objectiveManager/objectiveManager/objectiveManager.C
-objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.C
+objectiveManager/objectiveManager.C
 
 /* BOUNDARY ADJOINT CONTRIBUTIONS */
 boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.C
@@ -120,6 +121,7 @@ parameterization/Bezier/Bezier.C
 dynamicMesh/motionSolver/volumetricBSplinesMotionSolver/volumetricBSplinesMotionSolver.C
 dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.C
 dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.C
+dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.C
 
 /* DISPLACEMENT METHOD */
 displacementMethod/displacementMethod/displacementMethod.C
@@ -128,28 +130,24 @@ displacementMethod/displacementMethoddisplacementLaplacian/displacementMethoddis
 displacementMethod/displacementMethodvelocityLaplacian/displacementMethodvelocityLaplacian.C
 displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.C
 displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.C
+displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.C
 
 /* INTERPOLATION SCHEMES */
 interpolation/pointVolInterpolation/pointVolInterpolation.C
-
+interpolation/volPointInterpolation/volPointInterpolationAdjoint.C
 
 /* ADJOINT SENSITIVITY */
 optimisation/adjointSensitivity/sensitivity/sensitivity.C
-optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.C
-incoSens=optimisation/adjointSensitivity/incompressible
-$(incoSens)/adjointSensitivity/adjointSensitivityIncompressible.C
-$(incoSens)/shapeSensitivities/shapeSensitivitiesIncompressible.C
-$(incoSens)/adjointEikonalSolver/adjointEikonalSolverIncompressible.C
-$(incoSens)/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.C
-$(incoSens)/sensitivitySurface/sensitivitySurfaceIncompressible.C
-$(incoSens)/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.C
-$(incoSens)/sensitivityMultiple/sensitivityMultipleIncompressible.C
-$(incoSens)/SIBase/SIBaseIncompressible.C
-$(incoSens)/FIBase/FIBaseIncompressible.C
-$(incoSens)/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.C
-$(incoSens)/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.C
-$(incoSens)/sensitivityBezier/sensitivityBezierIncompressible.C
-$(incoSens)/sensitivityBezierFI/sensitivityBezierFIIncompressible.C
+optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.C
+optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.C
+optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.C
+optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.C
+optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.C
+optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.C
+optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.C
+optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.C
+optimisation/adjointSensitivity/adjointSensitivity/topO/sensitivityTopO.C
+optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.C
 
 /* LINE SEARCH */
 optimisation/lineSearch/lineSearch/lineSearch.C
@@ -159,32 +157,32 @@ optimisation/lineSearch/stepUpdate/bisection/bisection.C
 optimisation/lineSearch/stepUpdate/quadratic/quadratic.C
 
 /* UPDATE METHOD */
-updateMethod=optimisation/updateMethod/
+updateMethod=optimisation/updateMethod
 $(updateMethod)/updateMethod/updateMethod.C
 $(updateMethod)/constrainedOptimisationMethod/constrainedOptimisationMethod.C
 $(updateMethod)/steepestDescent/steepestDescent.C
+$(updateMethod)/quasiNewton/quasiNewton.C
 $(updateMethod)/BFGS/BFGS.C
 $(updateMethod)/DBFGS/DBFGS.C
 $(updateMethod)/LBFGS/LBFGS.C
 $(updateMethod)/SR1/SR1.C
 $(updateMethod)/conjugateGradient/conjugateGradient.C
 $(updateMethod)/constraintProjection/constraintProjection.C
+$(updateMethod)/SQPBase/SQPBase.C
 $(updateMethod)/SQP/SQP.C
+$(updateMethod)/ISQP/ISQP.C
 
-/* OPT MESH MOVEMENT */
-optMeshMovement=optimisation/optMeshMovement
-$(optMeshMovement)/optMeshMovement/optMeshMovement.C
-$(optMeshMovement)/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.C
-$(optMeshMovement)/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.C
-$(optMeshMovement)/optMeshMovementBezier/optMeshMovementBezier.C
-$(optMeshMovement)/optMeshMovementNULL/optMeshMovementNULL.C
-
-/* OPTIMIZATION TYPE */
-incoOptType=optimisation/optimisationType/incompressible
-$(incoOptType)/optimisationType/optimisationTypeIncompressible.C
-$(incoOptType)/shapeOptimisation/shapeOptimisationIncompressible.C
+/* DESIGN VARIABLES */
+optimisation/designVariables/designVariables/designVariables.C
+shapeVars=optimisation/designVariables/shape
+$(shapeVars)/shapeDesignVariables/shapeDesignVariables.C
+$(shapeVars)/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.C
+$(shapeVars)/volumetricBSplines/morphingBoxConstraints/none/noConstraint.C
+$(shapeVars)/volumetricBSplines/volumetricBSplinesDesignVariables.C
+$(shapeVars)/Bezier/BezierDesignVariables.C
 
 /* OPTIMIZATION MANAGER */
+optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.C
 optimisation/optimisationManager/optimisationManager/optimisationManager.C
 optimisation/optimisationManager/singleRun/singleRun.C
 optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.C
diff --git a/src/optimisation/adjointOptimisation/adjoint/Make/options b/src/optimisation/adjointOptimisation/adjoint/Make/options
index f3041856b6bfa06999842fbb96bea046f58490db..0853278b17d98c7b242ad34de6f9f0294530ffd1 100644
--- a/src/optimisation/adjointOptimisation/adjoint/Make/options
+++ b/src/optimisation/adjointOptimisation/adjoint/Make/options
@@ -9,8 +9,10 @@ EXE_INC = \
     -I$(LIB_SRC)/TurbulenceModels/turbulenceModels/lnInclude \
     -I$(LIB_SRC)/transportModels \
     -I$(LIB_SRC)/transportModels/incompressible/singlePhaseTransportModel \
+    -I$(LIB_SRC)/transportModels/geometricVoF/lnInclude \
     -I$(LIB_SRC)/fvMotionSolver/lnInclude \
     -I$(LIB_SRC)/dynamicMesh/lnInclude \
+    -I$(LIB_SRC)/fileFormats/lnInclude \
     -I$(LIB_SRC)/fvOptions/lnInclude
 
 LIB_LIBS = \
@@ -24,4 +26,5 @@ LIB_LIBS = \
     -lincompressibleTransportModels \
     -lfvMotionSolvers \
     -ldynamicMesh \
+    -lgeometricVoF \
     -lfvOptions
diff --git a/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointBoundaryCondition/adjointBoundaryCondition.C b/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointBoundaryCondition/adjointBoundaryCondition.C
index 9523408d2d2855d854e0efbc8ad660de76875787..d7f399de4fc397739b49851c9c9a2f6f6e6bb491 100644
--- a/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointBoundaryCondition/adjointBoundaryCondition.C
+++ b/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointBoundaryCondition/adjointBoundaryCondition.C
@@ -314,12 +314,7 @@ tmp
 >
 adjointBoundaryCondition<Type>::dxdbMult() const
 {
-    return
-        tmp<Field<typename Foam::outerProduct<Foam::vector, Type>::type>>::New
-        (
-            patch_.size(),
-            Zero
-        );
+    return nullptr;
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointWallVelocity/adjointWallVelocityFvPatchVectorField.C b/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointWallVelocity/adjointWallVelocityFvPatchVectorField.C
index 0172840bb03f01e65e680a2dda7bbfa24ee757a2..0b59ba177007f692142d09ad8d78b0a9183010fc 100644
--- a/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointWallVelocity/adjointWallVelocityFvPatchVectorField.C
+++ b/src/optimisation/adjointOptimisation/adjoint/adjointBoundaryConditions/adjointWallVelocity/adjointWallVelocityFvPatchVectorField.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -114,9 +114,10 @@ void Foam::adjointWallVelocityFvPatchVectorField::manipulateMatrix
     typedef Foam::nutUSpaldingWallFunctionFvPatchScalarField
         SAwallFunctionPatchField;
 
+    tmp<fvPatchScalarField> nutPatch(boundaryContrPtr_->turbulentDiffusivity());
     if
     (
-        isA<SAwallFunctionPatchField>(boundaryContrPtr_->turbulentDiffusivity())
+        isA<SAwallFunctionPatchField>(nutPatch())
      && patch().size() != 0
     )
     {
@@ -199,8 +200,8 @@ void Foam::adjointWallVelocityFvPatchVectorField::updateCoeffs()
     typedef Foam::nutUSpaldingWallFunctionFvPatchScalarField
         SAwallFunctionPatchField;
 
-    const fvPatchScalarField& nutb = boundaryContrPtr_->turbulentDiffusivity();
-    if (isA<SAwallFunctionPatchField>(nutb))
+    tmp<fvPatchScalarField> nutb(boundaryContrPtr_->turbulentDiffusivity());
+    if (isA<SAwallFunctionPatchField>(nutb()))
     {
         Uap_t1 = (Uac & tf1)*tf1;
         // leaving out second term for now
diff --git a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.C b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.C
index 004ea4b0ae786374c0502f46531d8e4219755ca5..a8220f2639d9af36ca102ef8e6e9a0ab17bbe3d8 100644
--- a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.C
+++ b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -28,6 +28,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "boundaryAdjointContribution.H"
+#include "fvPatchFieldsFwd.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -139,6 +140,13 @@ tmp<scalarField> boundaryAdjointContribution::TMVariable2()
 }
 
 
+tmp<fvPatchScalarField>
+boundaryAdjointContribution::turbulentDiffusivity() const
+{
+    NotImplemented;
+    return tmp<fvPatchScalarField>(nullptr);
+}
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 } // End namespace Foam
diff --git a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.H b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.H
index b9e52b829469056239160287ea1f0dcfa843f492..fc42c5fb81f7fc437e00b41680b2a7898712de0f 100644
--- a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.H
+++ b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContribution.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -79,6 +79,18 @@ protected:
         const fvPatch& patch_;
 
 
+    // Protected Member Functions
+
+        template<class returnType, class sourceType, class castType>
+        tmp<Field<returnType>> sumContributions
+        (
+            PtrList<sourceType>& sourceList,
+            const fvPatchField<returnType>&(castType::*boundaryFunction)
+                (const label),
+            bool (castType::*hasFunction)() const
+        );
+
+
 public:
 
     //- Runtime type information
@@ -156,7 +168,7 @@ public:
         virtual const fvPatchVectorField& Ub() const = 0;
         virtual const fvPatchScalarField& pb() const = 0;
         virtual const fvsPatchScalarField& phib() const = 0;
-        virtual const fvPatchScalarField& turbulentDiffusivity() const = 0;
+        virtual tmp<fvPatchScalarField> turbulentDiffusivity() const;
         virtual const fvPatchVectorField& Uab() const = 0;
         virtual const fvPatchScalarField& pab() const = 0;
         virtual const fvsPatchScalarField& phiab() const = 0;
@@ -173,6 +185,12 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+#ifdef NoRepository
+    #include "boundaryAdjointContributionTemplates.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 #endif
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressibleTemplates.C b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContributionTemplates.C
similarity index 79%
rename from src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressibleTemplates.C
rename to src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContributionTemplates.C
index 0fd968f7765a9cff9e32db14594514795c9a862c..dd6503e8a3230b1bea2bc6523f98cdbceaf70f37 100644
--- a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressibleTemplates.C
+++ b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContribution/boundaryAdjointContributionTemplates.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -31,10 +31,11 @@ License
 
 template<class returnType, class sourceType, class castType>
 Foam::tmp<Foam::Field<returnType>>
-Foam::boundaryAdjointContributionIncompressible::sumContributions
+Foam::boundaryAdjointContribution::sumContributions
 (
     PtrList<sourceType>& sourceList,
-    const fvPatchField<returnType>&(castType::*boundaryFunction)(const label)
+    const fvPatchField<returnType>& (castType::*boundaryFunction)(const label),
+    bool (castType::*hasFunction)() const
 )
 {
     // Objective function contribution
@@ -45,9 +46,12 @@ Foam::boundaryAdjointContributionIncompressible::sumContributions
     for (sourceType& funcI : sourceList)
     {
         castType& cfuncI = refCast<castType>(funcI);
-        const fvPatchField<returnType>& dJdvar =
-            (cfuncI.*boundaryFunction)(patch_.index());
-        dJtotdvar += cfuncI.weight()*dJdvar;
+        if ((cfuncI.*hasFunction)())
+        {
+            const fvPatchField<returnType>& dJdvar =
+                (cfuncI.*boundaryFunction)(patch_.index());
+            dJtotdvar += cfuncI.weight()*dJdvar;
+        }
     }
 
     return tdJtotdvar;
diff --git a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.C
index 3d1d19f474cdfd6359a5b0925652b86c5a7d4198..86f780e6b1cd46efdda2cde457fdd9b91d5b520e 100644
--- a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.C
+++ b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -93,7 +93,8 @@ tmp<vectorField> boundaryAdjointContributionIncompressible::velocitySource()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdv
+            &objectiveIncompressible::boundarydJdv,
+            &objectiveIncompressible::hasBoundarydJdv
         );
     vectorField& source = tsource.ref();
 
@@ -113,7 +114,8 @@ tmp<scalarField> boundaryAdjointContributionIncompressible::pressureSource()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdvn
+            &objectiveIncompressible::boundarydJdvn,
+            &objectiveIncompressible::hasBoundarydJdvn
         );
 
     scalarField& source = tsource.ref();
@@ -138,7 +140,8 @@ boundaryAdjointContributionIncompressible::tangentVelocitySource()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdvt
+            &objectiveIncompressible::boundarydJdvt,
+            &objectiveIncompressible::hasBoundarydJdvt
         );
 
     vectorField& source = tsource.ref();
@@ -165,7 +168,8 @@ boundaryAdjointContributionIncompressible::normalVelocitySource()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdp
+            &objectiveIncompressible::boundarydJdp,
+            &objectiveIncompressible::hasBoundarydJdp
         );
 
 }
@@ -177,7 +181,8 @@ tmp<scalarField> boundaryAdjointContributionIncompressible::energySource()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdT
+            &objectiveIncompressible::boundarydJdT,
+            &objectiveIncompressible::hasBoundarydJdT
         );
 
 }
@@ -190,7 +195,8 @@ boundaryAdjointContributionIncompressible::adjointTMVariable1Source()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdTMvar1
+            &objectiveIncompressible::boundarydJdTMvar1,
+            &objectiveIncompressible::hasBoundarydJdTMVar1
         );
 
 }
@@ -203,7 +209,8 @@ boundaryAdjointContributionIncompressible::adjointTMVariable2Source()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdTMvar2
+            &objectiveIncompressible::boundarydJdTMvar2,
+            &objectiveIncompressible::hasBoundarydJdTMVar2
         );
 
 }
@@ -216,7 +223,8 @@ boundaryAdjointContributionIncompressible::dJdnut()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdnut
+            &objectiveIncompressible::boundarydJdnut,
+            &objectiveIncompressible::hasBoundarydJdnut
         );
 }
 
@@ -228,7 +236,8 @@ boundaryAdjointContributionIncompressible::dJdGradU()
         sumContributions
         (
             objectiveManager_.getObjectiveFunctions(),
-            &objectiveIncompressible::boundarydJdGradU
+            &objectiveIncompressible::boundarydJdGradU,
+            &objectiveIncompressible::hasBoundarydJdGradU
         );
 }
 
@@ -331,14 +340,10 @@ boundaryAdjointContributionIncompressible::phib() const
 }
 
 
-const fvPatchScalarField&
+tmp<fvPatchScalarField>
 boundaryAdjointContributionIncompressible::turbulentDiffusivity() const
 {
-    return
-        primalVars_.RASModelVariables()().nutRef().boundaryField()
-        [
-            patch_.index()
-        ];
+    return primalVars_.RASModelVariables()().nutPatchField(patch_.index());
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.H
index d77cec9b254412f34ca8f18ffe1b414088adfa67..b4a4569bcc8935a71b6627020dd86134d0b2c6a3 100644
--- a/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressible.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -96,17 +96,6 @@ protected:
         const incompressibleAdjointSolver& adjointSolver_;
 
 
-    // Protected Member Functions
-
-        template<class returnType, class sourceType, class castType>
-        tmp<Field<returnType>> sumContributions
-        (
-            PtrList<sourceType>& sourceList,
-            const fvPatchField<returnType>&(castType::*boundaryFunction)
-                (const label)
-        );
-
-
 public:
 
     //- Runtime type information
@@ -155,7 +144,7 @@ public:
         const fvPatchVectorField& Ub() const;
         const fvPatchScalarField& pb() const;
         const fvsPatchScalarField& phib() const;
-        const fvPatchScalarField& turbulentDiffusivity() const;
+        tmp<fvPatchScalarField> turbulentDiffusivity() const;
         const fvPatchVectorField& Uab() const;
         const fvPatchScalarField& pab() const;
         const fvsPatchScalarField& phiab() const;
@@ -175,12 +164,6 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-#ifdef NoRepository
-#   include "boundaryAdjointContributionIncompressibleTemplates.C"
-#endif
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 #endif
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.C b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.C
index d0da1a72118b629d2fb6567d1d66e78bf8a5caaa..32d5a41fd4566598235aba7df811bb7774115d59 100644
--- a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.C
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.C
@@ -97,6 +97,12 @@ Foam::autoPtr<Foam::displacementMethod> Foam::displacementMethod::New
 
 // * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
 
+bool Foam::displacementMethod::preferPointField() const
+{
+    return true;
+}
+
+
 void Foam::displacementMethod::boundControlField(vectorField& controlField)
 {
     // Does nothing in base
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.H b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.H
index 0521fe4edcc39c2566ce12ce8d1ff9d686299dbb..84f30d1c5d279f7fe8391f1db3f6e175860bd20d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.H
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethod/displacementMethod.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -66,12 +66,15 @@ protected:
         fvMesh& mesh_;
 
         //- IDs of the patches to be moved
-        const labelList& patchIDs_;
+        labelList patchIDs_;
 
         autoPtr<motionSolver> motionPtr_;
 
         scalar maxDisplacement_;
 
+        //- Whether the motion solver prefers a point of a vol field as input
+        bool preferPointField_;
+
 
 private:
 
@@ -130,33 +133,42 @@ public:
 
     // Member Functions
 
-       //- Set motion filed related to model based on given motion
-       virtual void setMotionField(const pointVectorField& pointMovement) = 0;
+        //- Whether the motion solver prefers a point of a vol field as input
+        virtual bool preferPointField() const;
+
+        //- Set motion filed related to model based on given motion
+        virtual void setMotionField(const pointVectorField& pointMovement) = 0;
+
+        //- Set motion filed related to model based on given motion
+        virtual void setMotionField(const volVectorField& cellMovement) = 0;
 
-       //- Set motion filed related to model based on given motion
-       virtual void setMotionField(const volVectorField& cellMovement) = 0;
+        //- Set control field as a vectorField. For methods working with
+        //- parameters (RBF etc)
+        virtual void setControlField(const vectorField& controlField) = 0;
 
-       //- Set control field as a vectorField. For methods working with
-       //- parameters (RBF etc)
-       virtual void setControlField(const vectorField& controlField) = 0;
+        //- Set control field as a vectorField. For methods working with
+        //- parameters (RBF etc)
+        virtual void setControlField(const scalarField& controlField) = 0;
 
-       //- Set control field as a vectorField. For methods working with
-       //- parameters (RBF etc)
-       virtual void setControlField(const scalarField& controlField) = 0;
+        //- Bound control field in certain directions etc. For methods working
+        //- with parameters (RBF etc)
+        //- does nothing by default
+        virtual void boundControlField(vectorField& controlField);
 
-       //- Bound control field in certain directions etc. For methods working
-       //- with parameters (RBF etc)
-       //- does nothing by default
-       virtual void boundControlField(vectorField& controlField);
+        //- Get access to motionSolver
+        autoPtr<motionSolver>& getMotionSolver();
 
-       //- Get access to motionSolver
-       autoPtr<motionSolver>& getMotionSolver();
+        //- Get max displacement
+        scalar getMaxDisplacement() const;
 
-       //- Get max displacement
-       scalar getMaxDisplacement() const;
+        //- Set parametertised patch IDs
+        inline void setPatchIDs(const labelList& patchIDs)
+        {
+            patchIDs_ = patchIDs;
+        }
 
-       //- Update mesh
-       void update();
+        //- Update mesh
+        void update();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.C
index f26dc3b132bb9bcd1aa59ad442ea9d0c45d81264..8aa927342b69733473ee1cdba56db39de32448df 100644
--- a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -98,6 +98,12 @@ displacementMethodelasticityMotionSolver
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+bool displacementMethodelasticityMotionSolver::preferPointField() const
+{
+    return false;
+}
+
+
 void displacementMethodelasticityMotionSolver::setMotionField
 (
     const pointVectorField& pointMovement
@@ -105,17 +111,17 @@ void displacementMethodelasticityMotionSolver::setMotionField
 {
     if (resetFields_)
     {
-        pointMotionU_.primitiveFieldRef() = vector::zero;
-        cellMotionU_.primitiveFieldRef() = vector::zero;
+        pointMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.primitiveFieldRef() = Zero;
         cellMotionU_.correctBoundaryConditions();
     }
 
     maxDisplacement_ = SMALL;
 
-    // Update the boundary conditions of the pointField in order to make
-    // sure that the boundary will move according to the initial BCs
-    // without the interference of the volPointInterpolation in the elasticityMotionSolver
-    for (label patchI : patchIDs_)
+    // Update the boundary conditions of the pointField in order to make sure
+    // that the boundary will move according to the initial BCs without the
+    // interference of the volPointInterpolation in the elasticityMotionSolver
+    for (const label patchI : patchIDs_)
     {
         // Set boundary field. Needed for the motionSolver class
         pointMotionU_.boundaryFieldRef()[patchI] ==
@@ -145,7 +151,8 @@ void displacementMethodelasticityMotionSolver::setMotionField
             );
     }
 
-    // update the volField boundary conditions, used for the elasticity PDEs solution
+    // Update the volField boundary conditions,
+    // used for the elasticity PDEs solution
     const pointField& points = mesh_.points();
     for (label patchI : patchIDs_)
     {
@@ -164,15 +171,12 @@ void displacementMethodelasticityMotionSolver::setMotionField
     const volVectorField& cellMovement
 )
 {
-    auto cellMotionUbf = cellMotionU_.boundaryFieldRef();
+    auto& cellMotionUbf = cellMotionU_.boundaryFieldRef();
 
     // Set boundary mesh movement and calculate
     // max current boundary displacement
-    forAll(patchIDs_, pI)
+    for (const label patchI : patchIDs_)
     {
-        label patchI = patchIDs_[pI];
-
-        // Set boundary field. Needed for the motionSolver class
         cellMotionUbf[patchI] == cellMovement.boundaryField()[patchI];
 
         // Find max value
@@ -180,10 +184,7 @@ void displacementMethodelasticityMotionSolver::setMotionField
             max
             (
                 maxDisplacement_,
-                gMax
-                (
-                    mag(cellMotionUbf[patchI])
-                )
+                gMax(mag(cellMotionUbf[patchI]))
             );
     }
 }
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.H b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.H
index 50b8ec4df825bfab82c9f6b1a51075b49497b4e4..6077cbc4d85db7cbc61fa771354445e9b446077e 100644
--- a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodelasticityMotionSolver/displacementMethodelasticityMotionSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -105,6 +105,9 @@ public:
 
     // Member Functions
 
+       //- Whether the motion solver prefers a point of a vol field as input
+       virtual bool preferPointField() const;
+
        //- Set motion filed related to model based on given motion
        void setMotionField(const pointVectorField& pointMovement);
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.C
index 873aff3d7984bb481ea9a16506630a758a2f25aa..dfdcdb544479e277fd1cf5122773f6b9fd101301 100644
--- a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -57,24 +57,6 @@ displacementMethodlaplacianMotionSolver::displacementMethodlaplacianMotionSolver
     displacementMethod(mesh, patchIDs),
     pointMotionU_(refCast<laplacianMotionSolver>(motionPtr_()).pointMotionU()),
     cellMotionU_(refCast<laplacianMotionSolver>(motionPtr_()).cellMotionU()),
-    /*
-    // getting a reference from the database is dangerous since multiple
-    // fields with the same name might be registered
-    pointMotionU_
-    (
-        const_cast<pointVectorField&>
-        (
-            mesh_.lookupObject<pointVectorField>("pointMotionU")
-        )
-    ),
-    cellMotionU_
-    (
-        const_cast<volVectorField&>
-        (
-            mesh_.lookupObject<volVectorField>("cellMotionU")
-        )
-    ),
-    */
     resetFields_
     (
         IOdictionary::readContents
@@ -97,6 +79,12 @@ displacementMethodlaplacianMotionSolver::displacementMethodlaplacianMotionSolver
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+bool displacementMethodlaplacianMotionSolver::preferPointField() const
+{
+    return false;
+}
+
+
 void displacementMethodlaplacianMotionSolver::setMotionField
 (
     const pointVectorField& pointMovement
@@ -104,14 +92,16 @@ void displacementMethodlaplacianMotionSolver::setMotionField
 {
     if (resetFields_)
     {
-        pointMotionU_.primitiveFieldRef() = vector::zero;
-        cellMotionU_.primitiveFieldRef() = vector::zero;
+        pointMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.primitiveFieldRef() = Zero;
         cellMotionU_.correctBoundaryConditions();
     }
 
+    maxDisplacement_ = SMALL;
+
     // Set boundary mesh movement and calculate
     // max current boundary displacement
-    for (label patchI : patchIDs_)
+    for (const label patchI : patchIDs_)
     {
         // Set boundary field. Needed for the motionSolver class
         pointMotionU_.boundaryFieldRef()[patchI] ==
@@ -140,6 +130,8 @@ void displacementMethodlaplacianMotionSolver::setMotionField
                 )
             );
     }
+    // Transfer movement to cellMotionU
+    refCast<laplacianMotionSolver>(motionPtr_()).setBoundaryConditions();
 }
 
 
@@ -148,28 +140,28 @@ void displacementMethodlaplacianMotionSolver::setMotionField
     const volVectorField& cellMovement
 )
 {
-    NotImplemented;
-    /*
+    if (resetFields_)
+    {
+        pointMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.correctBoundaryConditions();
+    }
+
+    auto& cellMotionUbf = cellMotionU_.boundaryFieldRef();
     // Set boundary mesh movement and calculate max current boundary
     // displacement
-    forAll(patchIDs_, pI)
+    for (const label patchI : patchIDs_)
     {
-        label patchI = patchIDs_[pI];
-        cellMotionU_.boundaryField()[patchI] ==
-            cellMovement.boundaryField()[patchI];
+        cellMotionUbf[patchI] == cellMovement.boundaryField()[patchI];
 
         // Find max value
         maxDisplacement_ =
             max
             (
                 maxDisplacement_,
-                gMax
-                (
-                    mag(cellMovement.boundaryField()[patchI])
-                )
+                gMax(mag(cellMotionUbf[patchI]))
             );
     }
-    */
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.H b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.H
index 083ab095893b01209457aa1b5733ac12c6e13b07..e1d73c39c2397d4e3aba5af0f3cf383a5684556d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodlaplacianMotionSolver/displacementMethodlaplacianMotionSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -102,6 +102,9 @@ public:
 
     // Member Functions
 
+       //- Whether the motion solver prefers a point of a vol field as input
+       virtual bool preferPointField() const;
+
        //- Set motion filed related to model based on given motion
        void setMotionField(const pointVectorField& pointMovement);
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.C
new file mode 100644
index 0000000000000000000000000000000000000000..d73d0d270b327b9af3fab8296345e5978cdb5d90
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.C
@@ -0,0 +1,191 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 PCOpt/NTUA
+    Copyright (C) 2021 FOSS GP
+-------------------------------------------------------------------------------
+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 "displacementMethodpLaplacianMotionSolver.H"
+#include "pLaplacianMotionSolver.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+defineTypeNameAndDebug(displacementMethodpLaplacianMotionSolver, 1);
+addToRunTimeSelectionTable
+(
+    displacementMethod,
+    displacementMethodpLaplacianMotionSolver,
+    dictionary
+);
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+displacementMethodpLaplacianMotionSolver::
+displacementMethodpLaplacianMotionSolver
+(
+    fvMesh& mesh,
+    const labelList& patchIDs
+)
+:
+    displacementMethod(mesh, patchIDs),
+    pointMotionU_(refCast<pLaplacianMotionSolver>(motionPtr_()).pointMotionU()),
+    cellMotionU_(refCast<pLaplacianMotionSolver>(motionPtr_()).cellMotionU()),
+    resetFields_
+    (
+        IOdictionary
+        (
+            IOobject
+            (
+                "dynamicMeshDict",
+                mesh.time().constant(),
+                mesh,
+                IOobject::MUST_READ_IF_MODIFIED,
+                IOobject::AUTO_WRITE,
+                false
+            )
+        ).subDict("pLaplacianMotionSolverCoeffs").getOrDefault<bool>
+        (
+            "resetFields",
+            true
+        )
+    )
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool displacementMethodpLaplacianMotionSolver::preferPointField() const
+{
+    return false;
+}
+
+
+void displacementMethodpLaplacianMotionSolver::setMotionField
+(
+    const pointVectorField& pointMovement
+)
+{
+    if (resetFields_)
+    {
+        pointMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.correctBoundaryConditions();
+    }
+
+    maxDisplacement_ = SMALL;
+
+    // Set boundary mesh movement and calculate
+    // max current boundary displacement
+    for (label patchI : patchIDs_)
+    {
+        // Set boundary field. Needed for the motionSolver class
+        pointMotionU_.boundaryFieldRef()[patchI] ==
+            pointMovement.boundaryField()[patchI].patchInternalField()();
+
+        // Set boundary field values as seen from the internalField!
+        // Needed for determining the max displacement
+        pointMotionU_.boundaryFieldRef()[patchI].setInInternalField
+        (
+            pointMotionU_.primitiveFieldRef(),
+            pointMovement.boundaryField()[patchI].patchInternalField()()
+        );
+
+        // Find max value
+        maxDisplacement_ =
+            max
+            (
+                maxDisplacement_,
+                gMax
+                (
+                    mag
+                    (
+                        pointMotionU_.boundaryField()[patchI].
+                            patchInternalField()
+                    )
+                )
+            );
+    }
+    // Transfer movement to cellMotionU
+    refCast<pLaplacianMotionSolver>(motionPtr_()).setBoundaryConditions();
+}
+
+
+void displacementMethodpLaplacianMotionSolver::setMotionField
+(
+    const volVectorField& cellMovement
+)
+{
+    if (resetFields_)
+    {
+        pointMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.primitiveFieldRef() = Zero;
+        cellMotionU_.correctBoundaryConditions();
+    }
+
+    auto& cellMotionUbf = cellMotionU_.boundaryFieldRef();
+    // Set boundary mesh movement and calculate max current boundary
+    // displacement
+    for (const label patchI : patchIDs_)
+    {
+        cellMotionUbf[patchI] == cellMovement.boundaryField()[patchI];
+
+        // Find max value
+        maxDisplacement_ =
+            max
+            (
+                maxDisplacement_,
+                gMax(mag(cellMotionUbf[patchI]))
+            );
+    }
+}
+
+
+void displacementMethodpLaplacianMotionSolver::setControlField
+(
+    const vectorField& controlField
+)
+{
+    NotImplemented;
+}
+
+
+void displacementMethodpLaplacianMotionSolver::setControlField
+(
+    const scalarField& controlField
+)
+{
+    NotImplemented;
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.H b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.H
similarity index 54%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.H
rename to src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.H
index 5c39b97d7ffa03a6359f12edc1abdccc9c6107f3..dc599353593b2457ac2490aafbc9bfaa55cd62b1 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.H
+++ b/src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.H
@@ -5,9 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2021 PCOpt/NTUA
+    Copyright (C) 2021 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,23 +24,22 @@ License
     You should have received a copy of the GNU General Public License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
+
 Class
-    Foam::optMeshMovementVolumetricBSplines
+    Foam::displacementMethodpLaplacianMotionSolver
 
 Description
-    Converts NURBS volume control points update to actual mesh movement.
-    Internal points are also moved based on the movement of the control points
+    Wrapper class for the pLaplacian motion solver
 
 SourceFiles
-    optMeshMovementVolumetricBSplines.C
+    displacementMethodpLaplacianMotionSolver.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef optMeshMovementVolumetricBSplines_H
-#define optMeshMovementVolumetricBSplines_H
+#ifndef displacementMethodpLaplacianMotionSolver_H
+#define displacementMethodpLaplacianMotionSolver_H
 
-#include "optMeshMovement.H"
-#include "volBSplinesBase.H"
+#include "displacementMethod.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -49,27 +47,22 @@ namespace Foam
 {
 
 /*---------------------------------------------------------------------------*\
-              Class optMeshMovementVolumetricBSplines Declaration
+           Class displacementMethodpLaplacianMotionSolver Declaration
 \*---------------------------------------------------------------------------*/
 
-class optMeshMovementVolumetricBSplines
+class displacementMethodpLaplacianMotionSolver
 :
-    public optMeshMovement
+    public displacementMethod
 {
 protected:
 
     // Protected data
 
-        //- Reference to underlaying volumetric B-Splines morpher
-        volBSplinesBase& volBSplinesBase_;
-
-        //- Backup of initial control points. Useful for line-search
-        List<vectorField> cpsInit_;
-
+        pointVectorField& pointMotionU_;
 
-    // Protected Member Functions
+        volVectorField& cellMotionU_;
 
-        vectorField controlPointMovement(const scalarField& correction);
+        bool resetFields_;
 
 
 private:
@@ -77,53 +70,56 @@ private:
     // Private Member Functions
 
         //- No copy construct
-        optMeshMovementVolumetricBSplines
+        displacementMethodpLaplacianMotionSolver
         (
-            const optMeshMovementVolumetricBSplines&
+            const displacementMethodpLaplacianMotionSolver&
         ) = delete;
 
         //- No copy assignment
-        void operator=(const optMeshMovementVolumetricBSplines&) = delete;
+        void operator=
+        (
+            const displacementMethodpLaplacianMotionSolver&
+        ) = delete;
 
 
 public:
 
     //- Runtime type information
-    TypeName("volumetricBSplines");
+    TypeName("pLaplacianMotionSolver");
 
 
     // Constructors
 
         //- Construct from components
-        optMeshMovementVolumetricBSplines
+        displacementMethodpLaplacianMotionSolver
         (
             fvMesh& mesh,
-            const dictionary& dict,
             const labelList& patchIDs
         );
 
 
     //- Destructor
-    virtual ~optMeshMovementVolumetricBSplines() = default;
+    virtual ~displacementMethodpLaplacianMotionSolver() = default;
 
 
     // Member Functions
 
-       //- Calculates surface mesh movement
-       void moveMesh();
+       //- Whether the motion solver prefers a point of a vol field as input
+       virtual bool preferPointField() const;
 
-       //- Store design variables and mesh, to act as the starting point of
-       //- line search
-       virtual void storeDesignVariables();
+       //- Set motion filed related to model based on given motion
+       void setMotionField(const pointVectorField& pointMovement);
 
-       //- Reset to starting point of line search
-       virtual void resetDesignVariables();
+       //- Set motion filed related to model based on given motion
+       void setMotionField(const volVectorField& cellMovement);
 
-       //- Compute eta value based on max displacement
-       virtual scalar computeEta(const scalarField& correction);
+       //- Set control field as a vectorField. For methods working with
+       //- parameters (RBF etc)
+       void setControlField(const vectorField& controlField);
 
-       //- Return active design variables
-       virtual labelList getActiveDesignVariables() const;
+       //- Set control field as a vectorField. For methods working with
+       //- parameters (RBF etc)
+       void setControlField(const scalarField& controlField);
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.C
index 663f7e3ea9a510bd7823a48bca62ee5dcf5d812a..3778a5cce99a42d0236c23a170121958eb811e3c 100644
--- a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -29,6 +29,7 @@ License
 
 #include "elasticityMotionSolver.H"
 #include "motionInterpolation.H"
+#include "motionDiffusivity.H"
 #include "wallDist.H"
 #include "fixedValuePointPatchFields.H"
 #include "fvMatrices.H"
@@ -36,6 +37,7 @@ License
 #include "fvmDiv.H"
 #include "fvmDiv.H"
 #include "fvmLaplacian.H"
+#include "surfaceInterpolate.H"
 #include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -57,6 +59,19 @@ namespace Foam
 
 void Foam::elasticityMotionSolver::setBoundaryConditions()
 {
+    // Adjust boundary conditions based on the steps to be executed
+    forAll(cellMotionU_.boundaryField(), patchI)
+    {
+        fvPatchVectorField& bc =
+            cellMotionU_.boundaryFieldRef()[patchI];
+        if (isA<fixedValueFvPatchVectorField>(bc))
+        {
+            auto& fixedValueBCs =
+                refCast<fixedValueFvPatchVectorField>(bc);
+            fixedValueBCs == fixedValueBCs/scalar(nSteps_);
+        }
+    }
+    /*
     // Adjust boundary conditions based on the steps to be executed
     forAll(pointMotionU_.boundaryField(), patchI)
     {
@@ -88,6 +103,7 @@ void Foam::elasticityMotionSolver::setBoundaryConditions()
             }
         }
     }
+    */
 }
 
 
@@ -141,21 +157,10 @@ Foam::elasticityMotionSolver::elasticityMotionSolver
       ? motionInterpolation::New(fvMesh_, coeffDict().lookup("interpolation"))
       : motionInterpolation::New(fvMesh_)
     ),
-    E_
+    diffusivityPtr_
     (
-        IOobject
-        (
-            "mu",
-            mesh.time().timeName(),
-            mesh,
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        fvMesh_,
-        dimensionedScalar(dimless, Zero),
-        fvPatchFieldBase::zeroGradientType()
+        motionDiffusivity::New(fvMesh_, coeffDict().lookup("diffusivity"))
     ),
-    exponent_(this->coeffDict().get<scalar>("exponent")),
     nSteps_(this->coeffDict().get<label>("steps")),
     nIters_(this->coeffDict().get<label>("iters")),
     tolerance_(this->coeffDict().get<scalar>("tolerance"))
@@ -175,7 +180,7 @@ Foam::tmp<Foam::pointField> Foam::elasticityMotionSolver::curPoints() const
 void Foam::elasticityMotionSolver::solve()
 {
     // Re-init to zero
-    cellMotionU_.primitiveFieldRef() = vector::zero;
+    cellMotionU_.primitiveFieldRef() = Zero;
 
     // Adjust boundary conditions based on the number of steps to be executed
     // and interpolate to faces
@@ -187,19 +192,18 @@ void Foam::elasticityMotionSolver::solve()
         Info<< "Step " << istep << endl;
 
         // Update diffusivity
-        const scalarField& vols = mesh().cellVolumes();
-        E_.primitiveFieldRef() = 1./pow(vols, exponent_);
-        E_.correctBoundaryConditions();
-
+        diffusivityPtr_->correct();
+        const surfaceScalarField E(diffusivityPtr_->operator()());
+        const surfaceVectorField& Sf = fvMesh_.Sf();
         for (label iter = 0; iter < nIters_; ++iter)
         {
             Info<< "Iteration " << iter << endl;
             cellMotionU_.storePrevIter();
             fvVectorMatrix dEqn
             (
-                fvm::laplacian(2*E_, cellMotionU_)
-              + fvc::div(2*E_*T(fvc::grad(cellMotionU_)))
-              - fvc::div(E_*fvc::div(cellMotionU_)*tensor::I)
+                fvm::laplacian(2*E, cellMotionU_)
+              + fvc::div(2*E*(fvc::interpolate(fvc::grad(cellMotionU_)) & Sf))
+              - fvc::div(E*fvc::interpolate(fvc::div(cellMotionU_))*Sf)
             );
 
             scalar residual = mag(dEqn.solve().initialResidual());
@@ -218,12 +222,34 @@ void Foam::elasticityMotionSolver::solve()
             }
         }
 
+        interpolationPtr_->interpolate
+        (
+            cellMotionU_,
+            pointMotionU_
+        );
+
+        tmp<vectorField> newPoints
+        (
+            fvMesh_.points() + pointMotionU_.primitiveField()
+        );
+
+        /*
         // Interpolate from cells to points
         interpolationPtr_->interpolate(cellMotionU_, pointMotionU_);
+
+        syncTools::syncPointList
+        (
+            fvMesh_,
+            pointMotionU_.primitiveFieldRef(),
+            maxEqOp<vector>(),
+            vector::zero
+        );
+
         vectorField newPoints
         (
             mesh().points() + pointMotionU_.primitiveFieldRef()
         );
+        */
 
         // Move points and check mesh
         fvMesh_.movePoints(newPoints);
@@ -259,7 +285,14 @@ void Foam::elasticityMotionSolver::movePoints(const pointField&)
 
 void Foam::elasticityMotionSolver::updateMesh(const mapPolyMesh&)
 {
-    // Do nothing
+    // Update diffusivity. Note two stage to make sure old one is de-registered
+    // before creating/registering new one.
+    diffusivityPtr_.reset(nullptr);
+    diffusivityPtr_ = motionDiffusivity::New
+    (
+        fvMesh_,
+        coeffDict().lookup("diffusivity")
+    );
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.H b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.H
index f6c2f0dc058a5c3530de9800d2234c4acce0dc08..2fd11babf307685dea484a15ea7386a37c585e6b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/elasticityMotionSolver/elasticityMotionSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -60,6 +60,7 @@ namespace Foam
 
 // Forward class declarations
 class motionInterpolation;
+class motionDiffusivity;
 class mapPolyMesh;
 
 /*---------------------------------------------------------------------------*\
@@ -84,11 +85,8 @@ protected:
         //- Interpolation used to transfer cell displacement to the points
         autoPtr<motionInterpolation> interpolationPtr_;
 
-        //- Inverse cell volume diffusivity
-        volScalarField E_;
-
-        //- Exponent to stiffen highly morphed cells
-        scalar exponent_;
+        //- Diffusivity used to control the motion
+        autoPtr<motionDiffusivity> diffusivityPtr_;
 
         //- Intermediate steps to solve the PDEs
         label nSteps_;
diff --git a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.C
index fa7039ff5928754bddb53673a8892d9050c6f9f3..d53daf1d13e30128c3254d973c79a3238e00820e 100644
--- a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -29,8 +29,10 @@ License
 
 #include "laplacianMotionSolver.H"
 #include "motionInterpolation.H"
-#include "addToRunTimeSelectionTable.H"
+#include "motionDiffusivity.H"
 #include "fvmLaplacian.H"
+#include "syncTools.H"
+#include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -47,29 +49,6 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void Foam::laplacianMotionSolver::setBoundaryConditions()
-{
-    pointMotionU_.boundaryFieldRef().updateCoeffs();
-    auto& cellMotionUbf = cellMotionU_.boundaryFieldRef();
-
-    forAll(cellMotionU_.boundaryField(), pI)
-    {
-        fvPatchVectorField& bField = cellMotionUbf[pI];
-        if (isA<fixedValueFvPatchVectorField>(bField))
-        {
-            const pointField& points = fvMesh_.points();
-            const polyPatch& patch = fvMesh_.boundaryMesh()[pI];
-            forAll(bField, fI)
-            {
-                bField[fI] = patch[fI].average(points, pointMotionU_);
-            }
-        }
-    }
-}
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::laplacianMotionSolver::laplacianMotionSolver
@@ -114,6 +93,10 @@ Foam::laplacianMotionSolver::laplacianMotionSolver
       ? motionInterpolation::New(fvMesh_, coeffDict().lookup("interpolation"))
       : motionInterpolation::New(fvMesh_)
     ),
+    diffusivityPtr_
+    (
+        motionDiffusivity::New(fvMesh_, coeffDict().lookup("diffusivity"))
+    ),
     nIters_(this->coeffDict().get<label>("iters")),
     tolerance_(this->coeffDict().get<scalar>("tolerance"))
 {}
@@ -129,9 +112,17 @@ Foam::tmp<Foam::pointField> Foam::laplacianMotionSolver::curPoints() const
         pointMotionU_
     );
 
+    syncTools::syncPointList
+    (
+        fvMesh_,
+        pointMotionU_.primitiveFieldRef(),
+        maxEqOp<vector>(),
+        vector::zero
+    );
+
     tmp<vectorField> tcurPoints
     (
-        fvMesh_.points() + pointMotionU_.internalField()
+        fvMesh_.points() + pointMotionU_.primitiveField()
     );
 
     twoDCorrectPoints(tcurPoints.ref());
@@ -142,7 +133,7 @@ Foam::tmp<Foam::pointField> Foam::laplacianMotionSolver::curPoints() const
 
 void Foam::laplacianMotionSolver::solve()
 {
-    setBoundaryConditions();
+    diffusivityPtr_->correct();
 
     // Iteratively solve the Laplace equation, to account for non-orthogonality
     for (label iter = 0; iter < nIters_; ++iter)
@@ -150,7 +141,13 @@ void Foam::laplacianMotionSolver::solve()
         Info<< "Iteration " << iter << endl;
         fvVectorMatrix dEqn
         (
-            fvm::laplacian(cellMotionU_)
+            fvm::laplacian
+            (
+                dimensionedScalar("viscosity", dimViscosity, 1.0)
+              * diffusivityPtr_->operator()(),
+                cellMotionU_,
+                "laplacian(diffusivity,cellMotionU)"
+            )
         );
 
         scalar residual = mag(dEqn.solve().initialResidual());
@@ -169,6 +166,27 @@ void Foam::laplacianMotionSolver::solve()
 }
 
 
+void Foam::laplacianMotionSolver::setBoundaryConditions()
+{
+    pointMotionU_.boundaryFieldRef().updateCoeffs();
+    auto& cellMotionUbf = cellMotionU_.boundaryFieldRef();
+
+    forAll(cellMotionU_.boundaryField(), pI)
+    {
+        fvPatchVectorField& bField = cellMotionUbf[pI];
+        if (isA<fixedValueFvPatchVectorField>(bField))
+        {
+            const pointField& points = fvMesh_.points();
+            const polyPatch& patch = fvMesh_.boundaryMesh()[pI];
+            forAll(bField, fI)
+            {
+                bField[fI] = patch[fI].average(points, pointMotionU_);
+            }
+        }
+    }
+}
+
+
 void Foam::laplacianMotionSolver::movePoints(const pointField&)
 {
     // Do nothing
@@ -177,7 +195,14 @@ void Foam::laplacianMotionSolver::movePoints(const pointField&)
 
 void Foam::laplacianMotionSolver::updateMesh(const mapPolyMesh&)
 {
-    // Do nothing
+    // Update diffusivity. Note two stage to make sure old one is de-registered
+    // before creating/registering new one.
+    diffusivityPtr_.reset(nullptr);
+    diffusivityPtr_ = motionDiffusivity::New
+    (
+        fvMesh_,
+        coeffDict().lookup("diffusivity")
+    );
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.H b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.H
index f2cf0471d11c5ce044e4de4e573a90b5d22457c2..66f397c57e659d0324a528143df353eaa7042c73 100644
--- a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/laplacianMotionSolver/laplacianMotionSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -59,6 +59,7 @@ namespace Foam
 
 // Forward class declarations
 class motionInterpolation;
+class motionDiffusivity;
 class mapPolyMesh;
 
 /*---------------------------------------------------------------------------*\
@@ -80,6 +81,9 @@ protected:
         //- Interpolation used to transfer cell displacement to the points
         autoPtr<motionInterpolation> interpolationPtr_;
 
+        //- Diffusivity used to control the motion
+        autoPtr<motionDiffusivity> diffusivityPtr_;
+
         //- Number of laplacian iterations per solution step
         label nIters_;
 
@@ -87,16 +91,6 @@ protected:
         scalar tolerance_;
 
 
-    // Protected Member Functions
-
-        //- Set boundary conditions of cellMotionU based on pointMotionU.
-        //  Avoiding the use of the cellMotionFvPatchField bc
-        //  due to the use of the registry in order to grab pointMotionU, which
-        //  may give problems if multiple objects of this class are constructed
-        //  at the same time
-        void setBoundaryConditions();
-
-
 private:
 
 
@@ -144,6 +138,13 @@ public:
         //- Solve for motion
         virtual void solve();
 
+        //- Set boundary conditions of cellMotionU based on pointMotionU.
+        //  Avoiding the use of the cellMotionFvPatchField bc
+        //  due to the use of the registry in order to grab pointMotionU, which
+        //  may give problems if multiple objects of this class are constructed
+        //  at the same time
+        void setBoundaryConditions();
+
         //- Update local data for geometry changes
         virtual void movePoints(const pointField&);
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.C
new file mode 100644
index 0000000000000000000000000000000000000000..6b03ef4a5de3a9601c0e90cf3a94cf3a4890fbfb
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.C
@@ -0,0 +1,219 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "pLaplacianMotionSolver.H"
+#include "motionInterpolation.H"
+#include "addToRunTimeSelectionTable.H"
+#include "syncTools.H"
+#include "fvmLaplacian.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(pLaplacianMotionSolver, 1);
+
+    addToRunTimeSelectionTable
+    (
+        motionSolver,
+        pLaplacianMotionSolver,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::pLaplacianMotionSolver::pLaplacianMotionSolver
+(
+    const polyMesh& mesh,
+    const IOdictionary& dict
+)
+:
+    motionSolver(mesh, dict, typeName),
+    fvMotionSolver(mesh),
+    useFixedValuePointMotionUBCs_
+        (coeffDict().getOrDefault<bool>("useFixedValuePointMotionUBCs", false)),
+    pointMotionU_
+    (
+        IOobject
+        (
+            "pointMotionU",
+            mesh.time().timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        pointMesh::New(mesh),
+        dimensionedVector(dimless, Zero),
+        word
+        (
+            useFixedValuePointMotionUBCs_
+          ? fixedValuePointPatchVectorField::typeName
+          : calculatedPointPatchField<vector>::typeName
+        )
+    ),
+    cellMotionU_
+    (
+        IOobject
+        (
+            "cellMotionU",
+            mesh.time().timeName(),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        fvMesh_,
+        dimensionedVector(pointMotionU_.dimensions(), Zero),
+        pointMotionU_.boundaryField().types()
+    ),
+    interpolationPtr_
+    (
+        coeffDict().found("interpolation")
+      ? motionInterpolation::New(fvMesh_, coeffDict().lookup("interpolation"))
+      : motionInterpolation::New(fvMesh_)
+    ),
+    nIters_(this->coeffDict().get<label>("iters")),
+    tolerance_(this->coeffDict().get<scalar>("tolerance")),
+    toleranceIntermediate_
+    (
+        this->coeffDict().
+            getOrDefault<scalar>("toleranceIntermediate", 100*tolerance_)
+    ),
+    exponent_(this->coeffDict().get<label>("exponent"))
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::pLaplacianMotionSolver::setBoundaryConditions()
+{
+    pointMotionU_.boundaryFieldRef().updateCoeffs();
+    auto& cellMotionUbf = cellMotionU_.boundaryFieldRef();
+
+    forAll(cellMotionU_.boundaryField(), pI)
+    {
+        fvPatchVectorField& bField = cellMotionUbf[pI];
+        if (isA<fixedValueFvPatchVectorField>(bField))
+        {
+            const pointField& points = fvMesh_.points();
+            const polyPatch& patch = fvMesh_.boundaryMesh()[pI];
+            forAll(bField, fI)
+            {
+                bField[fI] = patch[fI].average(points, pointMotionU_);
+            }
+        }
+    }
+}
+
+
+
+Foam::tmp<Foam::pointField> Foam::pLaplacianMotionSolver::curPoints() const
+{
+    interpolationPtr_->interpolate
+    (
+        cellMotionU_,
+        pointMotionU_
+    );
+
+    syncTools::syncPointList
+    (
+        fvMesh_,
+        pointMotionU_.primitiveFieldRef(),
+        maxEqOp<vector>(),
+        vector::zero
+    );
+
+    tmp<vectorField> tcurPoints
+    (
+        fvMesh_.points() + pointMotionU_.internalField()
+    );
+
+    twoDCorrectPoints(tcurPoints.ref());
+
+    return tcurPoints;
+}
+
+
+void Foam::pLaplacianMotionSolver::solve()
+{
+//  setBoundaryConditions();
+
+    for (label exp = 2; exp < exponent_ + 1; ++exp)
+    {
+        scalar tolerance
+            (exp == exponent_ ? tolerance_ : toleranceIntermediate_);
+        Info<< "Solving for exponent " << exp << endl;
+
+        for (label iter = 0; iter < nIters_; ++iter)
+        {
+            Info<< "Iteration " << iter << endl;
+            cellMotionU_.storePrevIter();
+            volScalarField gamma(pow(mag(fvc::grad(cellMotionU_)), exp - 2));
+            gamma.correctBoundaryConditions();
+            fvVectorMatrix dEqn
+            (
+                fvm::laplacian(gamma, cellMotionU_)
+            );
+
+            scalar residual = mag(dEqn.solve().initialResidual());
+
+            cellMotionU_.relax();
+
+            // Print execution time
+            fvMesh_.time().printExecutionTime(Info);
+
+            // Check convergence
+            if (residual < tolerance)
+            {
+                Info<< "\n***Reached mesh movement convergence limit at"
+                    << " iteration " << iter << "***\n\n";
+                if (debug)
+                {
+                    gamma.write();
+                }
+                break;
+            }
+        }
+    }
+}
+
+
+void Foam::pLaplacianMotionSolver::movePoints(const pointField&)
+{
+    // Do nothing
+}
+
+
+void Foam::pLaplacianMotionSolver::updateMesh(const mapPolyMesh&)
+{
+    // Do nothing
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.H b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.H
new file mode 100644
index 0000000000000000000000000000000000000000..37baaa7041edb3d044a8de3ff4e4b3a885e6bbb2
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.H
@@ -0,0 +1,178 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+
+Class
+    Foam::pLaplacianMotionSolver
+
+Description
+    Similar to velocityLaplacian but with a variable diffusivity, based
+    on the gradient of the displacement.
+    The boundary displacement is set as a boundary condition
+    on pointMotionU; the latter is generated automatically if not found.
+
+SourceFiles
+    pLaplacianMotionSolver.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef pLaplacianMotionSolver_H
+#define pLaplacianMotionSolver_H
+
+#include "velocityMotionSolver.H"
+#include "fvMotionSolver.H"
+#include "volPointInterpolation.H"
+#include "polyMesh.H"
+#include "pointMesh.H"
+#include "pointPatchField.H"
+#include "pointPatchFieldsFwd.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward class declarations
+class motionInterpolation;
+class mapPolyMesh;
+
+/*---------------------------------------------------------------------------*\
+                    Class pLaplacianMotionSolver Declaration
+\*---------------------------------------------------------------------------*/
+
+class pLaplacianMotionSolver
+:
+    public motionSolver,
+    public fvMotionSolver
+{
+protected:
+
+    // Protected data
+
+        // Use a fixedValue boundary condition for the moving patches?  When
+        // using this motionSolver in an optimisation context, we usually go
+        // from point movement to face movement, solve the grid dispalcement
+        // PDE and then interpolate the movement back to the points. If the
+        // boundary conditions of pointMotionU are fixedValue, the initial
+        // values will be retained, otherwise they will be overwritten by the
+        // face-to-point interpolation. The latter is usually beneficial for
+        // the resulting mesh quality but does not give us the exact geometry.
+        bool useFixedValuePointMotionUBCs_;
+
+        mutable pointVectorField pointMotionU_;
+        volVectorField cellMotionU_;
+
+        //- Interpolation used to transfer cell displacement to the points
+        autoPtr<motionInterpolation> interpolationPtr_;
+
+        //- Number of pLaplacian iterations per solution step
+        label nIters_;
+
+        //- Residual threshold
+        scalar tolerance_;
+
+        //- Residual threshold for intermediate exponents
+        scalar toleranceIntermediate_;
+
+        //- Exponent defining the order or the p-Laplacian
+        label exponent_;
+
+
+private:
+
+
+    // Private Member Functions
+
+        //- No copy construct
+        pLaplacianMotionSolver(const pLaplacianMotionSolver&) = delete;
+
+        //- No copy assignment
+        void operator=(const pLaplacianMotionSolver&) = delete;
+
+public:
+
+    //- Runtime type information
+    TypeName("pLaplacianMotionSolver");
+
+
+    // Constructors
+
+        //- Construct from mesh and dictionary
+        pLaplacianMotionSolver
+        (
+            const polyMesh& mesh,
+            const IOdictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~pLaplacianMotionSolver() = default;
+
+
+    // Member Functions
+
+        //- Set boundary conditions of cellMotionU based on pointMotionU.
+        //  Avoiding the use of the cellMotionFvPatchField bc
+        //  due to the use of the registry in order to grab pointMotionU, which
+        //  may give problems if multiple objects of this class are constructed
+        //  at the same time
+        void setBoundaryConditions();
+
+        //- Get const and non-const references to pointMotionU
+        inline pointVectorField& pointMotionU();
+        inline const pointVectorField& pointMotionU() const;
+
+        //- Get const and non-const references to cellMotionU
+        inline volVectorField& cellMotionU();
+        inline const volVectorField& cellMotionU() const;
+
+        //- Return point location obtained from the current motion field
+        virtual tmp<pointField> curPoints() const;
+
+        //- Solve for motion
+        virtual void solve();
+
+        //- Update local data for geometry changes
+        virtual void movePoints(const pointField&);
+
+        //- Update the mesh corresponding to given map
+        virtual void updateMesh(const mapPolyMesh&);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "pLaplacianMotionSolverI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressibleI.H b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolverI.H
similarity index 65%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressibleI.H
rename to src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolverI.H
index c6274f3b4420b1cb540546b1ba1e411a388a3c81..3aba688f93022bcc105d76a4eff758fc7f7d1606 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressibleI.H
+++ b/src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolverI.H
@@ -5,9 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,47 +26,34 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-namespace Foam
-{
-
-namespace incompressible
-{
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline bool sensitivitySurface::getIncludeObjective() const
+inline Foam::pointVectorField& Foam::pLaplacianMotionSolver::pointMotionU()
 {
-    return includeObjective_;
+    return pointMotionU_;
 }
 
 
-inline bool sensitivitySurface::getIncludeSurfaceArea() const
+inline const Foam::pointVectorField&
+Foam::pLaplacianMotionSolver::pointMotionU() const
 {
-    return includeSurfaceArea_;
+    return pointMotionU_;
 }
 
 
-inline void sensitivitySurface::setIncludeObjective
-(
-    const bool includeObjective
-)
+inline Foam::volVectorField& Foam::pLaplacianMotionSolver::cellMotionU()
 {
-    includeObjective_ = includeObjective;
+    return cellMotionU_;
 }
 
 
-inline void sensitivitySurface::setIncludeSurfaceArea
-(
-    const bool includeSurfaceArea
-)
+inline const Foam::volVectorField&
+Foam::pLaplacianMotionSolver::cellMotionU() const
 {
-    includeSurfaceArea_ = includeSurfaceArea;
+    return cellMotionU_;
 }
 
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// ************************************************************************* //
 
-} // End namespace incompressible
-} // End namespace Foam
 
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolateAdjoint.C b/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolateAdjoint.C
new file mode 100644
index 0000000000000000000000000000000000000000..e174ef78889cb553931227efac3733400d566261
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolateAdjoint.C
@@ -0,0 +1,345 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2017 OpenFOAM Foundation
+    Copyright (C) 2016-2020 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 <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "volPointInterpolationAdjoint.H"
+#include "volFields.H"
+#include "pointFields.H"
+#include "emptyFvPatch.H"
+#include "coupledPointPatchField.H"
+#include "pointConstraints.H"
+#include "symmetryPolyPatch.H"
+#include "symmetryPlanePolyPatch.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+template<class Type>
+void Foam::volPointInterpolationAdjoint::pushUntransformedData
+(
+    List<Type>& pointData
+) const
+{
+    // Transfer onto coupled patch
+    const globalMeshData& gmd = mesh().globalData();
+    const indirectPrimitivePatch& cpp = gmd.coupledPatch();
+    const labelList& meshPoints = cpp.meshPoints();
+
+    const mapDistribute& slavesMap = gmd.globalCoPointSlavesMap();
+    const labelListList& slaves = gmd.globalCoPointSlaves();
+
+    List<Type> elems(slavesMap.constructSize());
+    forAll(meshPoints, i)
+    {
+        elems[i] = pointData[meshPoints[i]];
+    }
+
+    // Combine master data with slave data
+    forAll(slaves, i)
+    {
+        const labelList& slavePoints = slaves[i];
+
+        // Copy master data to slave slots
+        forAll(slavePoints, j)
+        {
+            elems[slavePoints[j]] = elems[i];
+        }
+    }
+
+    // Push slave-slot data back to slaves
+    slavesMap.reverseDistribute(elems.size(), elems, false);
+
+    // Extract back onto mesh
+    forAll(meshPoints, i)
+    {
+        pointData[meshPoints[i]] = elems[i];
+    }
+}
+
+
+template<class Type>
+Foam::tmp<Foam::Field<Type>> Foam::volPointInterpolationAdjoint::flatBoundaryField
+(
+    const GeometricField<Type, fvPatchField, volMesh>& vf
+) const
+{
+    const fvMesh& mesh = vf.mesh();
+    const fvBoundaryMesh& bm = mesh.boundary();
+
+    tmp<Field<Type>> tboundaryVals
+    (
+        new Field<Type>(mesh.nBoundaryFaces())
+    );
+    Field<Type>& boundaryVals = tboundaryVals.ref();
+
+    forAll(vf.boundaryField(), patchi)
+    {
+        label bFacei = bm[patchi].patch().start() - mesh.nInternalFaces();
+
+        if
+        (
+           !isA<emptyFvPatch>(bm[patchi])
+        && !vf.boundaryField()[patchi].coupled()
+        )
+        {
+            SubList<Type>
+            (
+                boundaryVals,
+                vf.boundaryField()[patchi].size(),
+                bFacei
+            ) = vf.boundaryField()[patchi];
+        }
+        else
+        {
+            const polyPatch& pp = bm[patchi].patch();
+
+            forAll(pp, i)
+            {
+                boundaryVals[bFacei++] = Zero;
+            }
+        }
+    }
+
+    return tboundaryVals;
+}
+
+
+template<class Type>
+void Foam::volPointInterpolationAdjoint::addSeparated
+(
+    GeometricField<Type, pointPatchField, pointMesh>& pf
+) const
+{
+    if (debug)
+    {
+        Pout<< "volPointInterpolation::addSeparated" << endl;
+    }
+
+    auto& pfi = pf.ref();
+    auto& pfbf = pf.boundaryFieldRef();
+
+    const label startOfRequests = UPstream::nRequests();
+
+    forAll(pfbf, patchi)
+    {
+        if (pfbf[patchi].coupled())
+        {
+            refCast<coupledPointPatchField<Type>>
+                (pfbf[patchi]).initSwapAddSeparated
+                (
+                    Pstream::commsTypes::nonBlocking,
+                    pfi
+                );
+        }
+    }
+
+    // Wait for outstanding requests
+    UPstream::waitRequests(startOfRequests);
+
+    forAll(pfbf, patchi)
+    {
+        if (pfbf[patchi].coupled())
+        {
+            refCast<coupledPointPatchField<Type>>
+                (pfbf[patchi]).swapAddSeparated
+                (
+                    Pstream::commsTypes::nonBlocking,
+                    pfi
+                );
+        }
+    }
+}
+
+
+template<class Type>
+void Foam::volPointInterpolationAdjoint::interpolateSensitivitiesField
+(
+    const GeometricField<Type, pointPatchField, pointMesh>& pf,
+    typename GeometricField<Type, fvPatchField, volMesh>::Boundary& vf,
+    const labelHashSet& patchIDs
+) const
+{
+    const Field<Type>& pfi = pf.primitiveField();
+
+    // Get face data in flat list
+    const fvMesh& Mesh = mesh();
+    const fvBoundaryMesh& bm = Mesh.boundary();
+
+    tmp<Field<Type>> tboundaryVals
+    (
+        new Field<Type>(Mesh.nBoundaryFaces(), Zero)
+    );
+    Field<Type>& boundaryVals = tboundaryVals.ref();
+
+    // Do points on 'normal' patches from the surrounding patch faces
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    const primitivePatch& boundary = boundaryPtr_();
+    const labelList& mp = boundary.meshPoints();
+
+    forAll(mp, i)
+    {
+        label pointi = mp[i];
+
+        if (isPatchPoint_[pointi])
+        {
+            const labelList& pFaces = boundary.pointFaces()[i];
+            const scalarList& pWeights = boundaryPointWeights_[i];
+            const Type& val = pfi[pointi];
+            // Face-to-point weights should, in general, have half the weight
+            // of what they actually do in volPointInterpolation since, in
+            // a complete case, a face laying on the opposite side of the
+            // symmetry plane would also contribute to a point laying on
+            // the symmetry plane.
+            // For face-to-point interpolation this is not a problem, but for
+            // the adjoint point-to-face interpolation, the correct value of
+            // the weight should be taken into consideration
+            scalar mod(isSymmetryPoint_[pointi] ? 0.5 : 1);
+
+            forAll(pFaces, j)
+            {
+                if (boundaryIsPatchFace_[pFaces[j]])
+                {
+                    boundaryVals[pFaces[j]] += mod*pWeights[j]*val;
+                }
+            }
+        }
+    }
+
+    // Transfer values to face-based sensitivity field
+    for (const label patchi : patchIDs)
+    {
+        label bFacei = bm[patchi].patch().start() - Mesh.nInternalFaces();
+        if (!isA<emptyFvPatch>(bm[patchi]) && !vf[patchi].coupled())
+        {
+            vf[patchi] =
+                SubList<Type>
+                (
+                    boundaryVals,
+                    vf[patchi].size(),
+                    bFacei
+                );
+        }
+    }
+}
+
+
+template<class Type>
+void Foam::volPointInterpolationAdjoint::interpolateBoundaryField
+(
+    const GeometricField<Type, fvPatchField, volMesh>& vf,
+    GeometricField<Type, pointPatchField, pointMesh>& pf
+) const
+{
+    const primitivePatch& boundary = boundaryPtr_();
+
+    Field<Type>& pfi = pf.primitiveFieldRef();
+
+    // Get face data in flat list
+    tmp<Field<Type>> tboundaryVals(flatBoundaryField(vf));
+    const Field<Type>& boundaryVals = tboundaryVals();
+
+
+    // Do points on 'normal' patches from the surrounding patch faces
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    const labelList& mp = boundary.meshPoints();
+
+    forAll(mp, i)
+    {
+        label pointi = mp[i];
+
+        if (isPatchPoint_[pointi])
+        {
+            const labelList& pFaces = boundary.pointFaces()[i];
+            const scalarList& pWeights = boundaryPointWeights_[i];
+
+            Type& val = pfi[pointi];
+
+            val = Zero;
+            forAll(pFaces, j)
+            {
+                if (boundaryIsPatchFace_[pFaces[j]])
+                {
+                    scalar mod(1);
+                    if (isSymmetryPoint_[pointi])
+                    {
+                        const label globalFaceI =
+                            mesh().nInternalFaces() + pFaces[j];
+                        const label facePatchID =
+                            mesh().boundaryMesh().whichPatch(globalFaceI);
+                        const polyPatch& pp = mesh().boundaryMesh()[facePatchID];
+                        if
+                        (
+                             isA<symmetryPolyPatch>(pp)
+                          || isA<symmetryPlanePolyPatch>(pp)
+                        )
+                        {
+                            mod = 0;
+                        }
+                      //else
+                      //{
+                      //    mod = 2;
+                      //}
+                    }
+                    val += mod*pWeights[j]*boundaryVals[pFaces[j]];
+                }
+            }
+        }
+    }
+
+    // Sum collocated contributions
+    pointConstraints::syncUntransformedData(mesh(), pfi, plusEqOp<Type>());
+
+    // And add separated contributions
+    addSeparated(pf);
+
+    // Optionally normalise
+    /*
+    if (normalisationPtr_)
+    {
+        const scalarField& normalisation = normalisationPtr_();
+        forAll(mp, i)
+        {
+            pfi[mp[i]] *= normalisation[i];
+        }
+    }
+    */
+
+
+    // Push master data to slaves. It is possible (not sure how often) for
+    // a coupled point to have its master on a different patch so
+    // to make sure just push master data to slaves.
+    pushUntransformedData(pfi);
+
+    // Apply displacement constraints
+    const pointConstraints& pcs = pointConstraints::New(pf.mesh());
+
+    pcs.constrain(pf, false);
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.C b/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.C
new file mode 100644
index 0000000000000000000000000000000000000000..63e07ae5266317080b92e899a5ffba5afe4507e0
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.C
@@ -0,0 +1,391 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "volPointInterpolationAdjoint.H"
+#include "fvMesh.H"
+#include "volFields.H"
+#include "pointFields.H"
+#include "pointConstraints.H"
+#include "surfaceFields.H"
+#include "processorPointPatch.H"
+#include "symmetryPolyPatch.H"
+#include "symmetryPlanePolyPatch.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(volPointInterpolationAdjoint, 0);
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::volPointInterpolationAdjoint::calcBoundaryAddressing()
+{
+    if (debug)
+    {
+        Pout<< "volPointInterpolationAdjoint::calcBoundaryAddressing() : "
+            << "constructing boundary addressing"
+            << endl;
+    }
+
+    boundaryPtr_.reset
+    (
+        new primitivePatch
+        (
+            SubList<face>
+            (
+                mesh().faces(),
+                mesh().nBoundaryFaces(),
+                mesh().nInternalFaces()
+            ),
+            mesh().points()
+        )
+    );
+    const primitivePatch& boundary = boundaryPtr_();
+
+    boundaryIsPatchFace_.setSize(boundary.size());
+    boundaryIsPatchFace_ = false;
+
+    // Store per mesh point whether it is on any 'real' patch. Currently
+    // boolList just so we can use syncUntransformedData (does not take
+    // bitSet. Tbd)
+    boolList isPatchPoint(mesh().nPoints(), false);
+    boolList isSymmetryPoint(mesh().nPoints(), false);
+
+    const polyBoundaryMesh& pbm = mesh().boundaryMesh();
+
+    // Get precalculated volField only so we can use coupled() tests for
+    // cyclicAMI
+    const surfaceScalarField& magSf = mesh().magSf();
+
+    forAll(pbm, patchi)
+    {
+        const polyPatch& pp = pbm[patchi];
+
+        if
+        (
+            !isA<emptyPolyPatch>(pp)
+         && !magSf.boundaryField()[patchi].coupled()
+         && !isA<symmetryPolyPatch>(pp)
+         && !isA<symmetryPlanePolyPatch>(pp)
+        )
+        {
+            label bFacei = pp.start()-mesh().nInternalFaces();
+
+            forAll(pp, i)
+            {
+                boundaryIsPatchFace_[bFacei] = true;
+
+                const face& f = boundary[bFacei++];
+
+                forAll(f, fp)
+                {
+                    isPatchPoint[f[fp]] = true;
+                }
+            }
+        }
+        else if (isA<symmetryPolyPatch>(pp) || isA<symmetryPlanePolyPatch>(pp))
+        {
+            const labelList& meshPoints = pp.meshPoints();
+            for (const label pointI : meshPoints)
+            {
+                isSymmetryPoint[pointI] = true;
+            }
+        }
+    }
+
+    // Make sure point status is synchronised so even processor that holds
+    // no face of a certain patch still can have boundary points marked.
+    pointConstraints::syncUntransformedData
+    (
+        mesh(),
+        isPatchPoint,
+        orEqOp<bool>()
+    );
+
+    // Convert to bitSet
+    isPatchPoint_.setSize(mesh().nPoints());
+    isPatchPoint_.assign(isPatchPoint);
+
+    isSymmetryPoint_.setSize(mesh().nPoints());
+    isSymmetryPoint_.assign(isSymmetryPoint);
+
+    if (debug)
+    {
+        label nPatchFace = 0;
+        forAll(boundaryIsPatchFace_, i)
+        {
+            if (boundaryIsPatchFace_[i])
+            {
+                nPatchFace++;
+            }
+        }
+        label nPatchPoint = 0;
+        forAll(isPatchPoint_, i)
+        {
+            if (isPatchPoint_[i])
+            {
+                nPatchPoint++;
+            }
+        }
+        Pout<< "boundary:" << nl
+            << "    faces :" << boundary.size() << nl
+            << "    of which on proper patch:" << nPatchFace << nl
+            << "    points:" << boundary.nPoints() << nl
+            << "    of which on proper patch:" << nPatchPoint << endl;
+    }
+}
+
+
+void Foam::volPointInterpolationAdjoint::makeBoundaryWeights
+(
+    scalarField& sumWeights
+)
+{
+    if (debug)
+    {
+        Pout<< "volPointInterpolationAdjoint::makeBoundaryWeights() : "
+            << "constructing weighting factors for boundary points." << endl;
+    }
+
+    const pointField& points = mesh().points();
+    const pointField& faceCentres = mesh().faceCentres();
+
+    const primitivePatch& boundary = boundaryPtr_();
+
+    boundaryPointWeights_.clear();
+    boundaryPointWeights_.setSize(boundary.meshPoints().size());
+
+    forAll(boundary.meshPoints(), i)
+    {
+        label pointi = boundary.meshPoints()[i];
+
+        if (isPatchPoint_[pointi])
+        {
+            const labelList& pFaces = boundary.pointFaces()[i];
+
+            scalarList& pw = boundaryPointWeights_[i];
+            pw.setSize(pFaces.size());
+
+            sumWeights[pointi] = 0.0;
+
+            forAll(pFaces, i)
+            {
+                if (boundaryIsPatchFace_[pFaces[i]])
+                {
+                    label facei = mesh().nInternalFaces() + pFaces[i];
+
+                    pw[i] = 1.0/mag(points[pointi] - faceCentres[facei]);
+                    sumWeights[pointi] += pw[i];
+                }
+                else
+                {
+                    pw[i] = 0.0;
+                }
+            }
+        }
+    }
+}
+
+
+void Foam::volPointInterpolationAdjoint::makeWeights()
+{
+    if (debug)
+    {
+        Pout<< "volPointInterpolationAdjoint::makeWeights() : "
+            << "constructing weighting factors"
+            << endl;
+    }
+
+    const pointMesh& pMesh = pointMesh::New(mesh());
+
+    // Update addressing over all boundary faces
+    calcBoundaryAddressing();
+
+
+    // Running sum of weights
+    tmp<pointScalarField> tsumWeights
+    (
+        new pointScalarField
+        (
+            IOobject
+            (
+                "volPointSumWeights",
+                mesh().polyMesh::instance(),
+                mesh()
+            ),
+            pMesh,
+            dimensionedScalar(dimless, Zero)
+        )
+    );
+    pointScalarField& sumWeights = tsumWeights.ref();
+
+
+    // Create boundary weights; sumWeights
+    makeBoundaryWeights(sumWeights);
+
+
+    const primitivePatch& boundary = boundaryPtr_();
+    const labelList& mp = boundary.meshPoints();
+
+
+    // Sum collocated contributions
+    pointConstraints::syncUntransformedData
+    (
+        mesh(),
+        sumWeights,
+        plusEqOp<scalar>()
+    );
+
+
+    // Push master data to slaves. It is possible (not sure how often) for
+    // a coupled point to have its master on a different patch so
+    // to make sure just push master data to slaves. Reuse the syncPointData
+    // structure.
+    pushUntransformedData(sumWeights);
+
+    // Normalise boundary weights
+    forAll(mp, i)
+    {
+        const label pointi = mp[i];
+
+        scalarList& pw = boundaryPointWeights_[i];
+        // Note:pw only sized for isPatchPoint
+        forAll(pw, i)
+        {
+            pw[i] /= sumWeights[pointi];
+        }
+    }
+
+    if (debug)
+    {
+        Pout<< "volPointInterpolationAdjoint::makeWeights() : "
+            << "finished constructing weighting factors"
+            << endl;
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * * //
+
+Foam::volPointInterpolationAdjoint::volPointInterpolationAdjoint(const fvMesh& vm)
+:
+    MeshObject<fvMesh, Foam::UpdateableMeshObject, volPointInterpolationAdjoint>
+    (
+        vm
+    )
+{
+    makeWeights();
+}
+
+
+// * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * * //
+
+Foam::volPointInterpolationAdjoint::~volPointInterpolationAdjoint()
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::volPointInterpolationAdjoint::updateMesh(const mapPolyMesh&)
+{
+    makeWeights();
+}
+
+
+bool Foam::volPointInterpolationAdjoint::movePoints()
+{
+    makeWeights();
+
+    return true;
+}
+
+
+void Foam::volPointInterpolationAdjoint::interpolateSensitivitiesField
+(
+    const vectorField& pf,
+    vectorField& vf,
+    const labelHashSet& patchIDs
+) const
+{
+    // Get face data in flat list
+    const fvMesh& Mesh = mesh();
+
+    vectorField boundaryVals(Mesh.nBoundaryFaces(), Zero);
+
+    // Do points on 'normal' patches from the surrounding patch faces
+    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    const primitivePatch& boundary = boundaryPtr_();
+    const labelList& mp = boundary.meshPoints();
+
+    forAll(mp, i)
+    {
+        label pointi = mp[i];
+
+        if (isPatchPoint_[pointi])
+        {
+            const labelList& pFaces = boundary.pointFaces()[i];
+            const scalarList& pWeights = boundaryPointWeights_[i];
+            const vector& val = pf[pointi];
+            // In symmetry planes, face-to-point weights should, in general,
+            // have half the weight of what they actually do in
+            // volPointInterpolation since, in a complete case, a face laying
+            // on the opposite side of the symmetry plane would also contribute
+            // to a point laying on the symmetry plane.
+            // For face-to-point interpolation this is not a problem, but for
+            // the adjoint point-to-face interpolation, the correct value of
+            // the weight should be taken into consideration
+            scalar mod(isSymmetryPoint_[pointi] ? 0.5 : 1);
+
+            forAll(pFaces, j)
+            {
+                if (boundaryIsPatchFace_[pFaces[j]])
+                {
+                    boundaryVals[pFaces[j]] += mod*pWeights[j]*val;
+                }
+            }
+        }
+    }
+
+    // Transfer values to face-based sensitivity field
+    label nPassedFaces(0);
+    for (const label patchi : patchIDs)
+    {
+        const fvPatch& patch = Mesh.boundary()[patchi];
+        label bFacei = patch.start() - Mesh.nInternalFaces();
+        SubList<vector> patchFaceSens(vf, patch.size(), nPassedFaces);
+        patchFaceSens = SubList<vector>(boundaryVals, patch.size(), bFacei);
+        nPassedFaces += patch.size();
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.H b/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.H
new file mode 100644
index 0000000000000000000000000000000000000000..78225457b8ba34b3a8a826df5bb05f2cc5954272
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.H
@@ -0,0 +1,193 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2011-2016 OpenFOAM Foundation
+    Copyright (C) 2016-2020 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 <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::volPointInterpolationAdjoint
+
+Description
+    Interpolate from cell centres to points (vertices) using inverse distance
+    weighting
+
+SourceFiles
+    volPointInterpolationAdjoint.C
+    volPointInterpolate.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef volPointInterpolationAdjoint_H
+#define volPointInterpolationAdjoint_H
+
+#include "MeshObject.H"
+#include "scalarList.H"
+#include "volFields.H"
+#include "pointFields.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+class fvMesh;
+class pointMesh;
+
+/*---------------------------------------------------------------------------*\
+                       Class volPointInterpolationAdjoint Declaration
+\*---------------------------------------------------------------------------*/
+
+class volPointInterpolationAdjoint
+:
+    public MeshObject<fvMesh, UpdateableMeshObject, volPointInterpolationAdjoint>
+{
+protected:
+
+    // Protected data
+
+        //- Boundary addressing
+        autoPtr<primitivePatch> boundaryPtr_;
+
+        //- Per boundary face whether is on non-coupled, non-empty patch
+        bitSet boundaryIsPatchFace_;
+
+        //- Per mesh(!) point whether is on non-coupled, non-empty patch (on
+        //  any processor)
+        bitSet isPatchPoint_;
+
+        //- Per mesh(!) point whether is on symmetry plane
+        //  any processor)
+        bitSet isSymmetryPoint_;
+
+        //- Per boundary point the weights per pointFaces.
+        scalarListList boundaryPointWeights_;
+
+
+    // Protected Member Functions
+
+        //- Construct addressing over all boundary faces
+        void calcBoundaryAddressing();
+
+        //- Make weights for points on uncoupled patches
+        void makeBoundaryWeights(scalarField& sumWeights);
+
+        //- Construct all point weighting factors
+        void makeWeights();
+
+        //- Helper: push master point data to collocated points
+        template<class Type>
+        void pushUntransformedData(List<Type>&) const;
+
+        //- Get boundary field in same order as boundary faces. Field is
+        //  zero on all coupled and empty patches
+        template<class Type>
+        tmp<Field<Type>> flatBoundaryField
+        (
+            const GeometricField<Type, fvPatchField, volMesh>& vf
+        ) const;
+
+        //- Add separated contributions
+        template<class Type>
+        void addSeparated
+        (
+            GeometricField<Type, pointPatchField, pointMesh>&
+        ) const;
+
+        //- No copy construct
+        volPointInterpolationAdjoint(const volPointInterpolationAdjoint&) = delete;
+
+        //- No copy assignment
+        void operator=(const volPointInterpolationAdjoint&) = delete;
+
+
+public:
+
+    // Declare name of the class and its debug switch
+    ClassName("volPointInterpolationAdjoint");
+
+
+    // Constructors
+
+        //- Constructor given fvMesh and pointMesh.
+        explicit volPointInterpolationAdjoint(const fvMesh&);
+
+
+    //- Destructor
+    ~volPointInterpolationAdjoint();
+
+
+    // Member functions
+
+    // Edit
+
+        //- Update mesh topology using the morph engine
+        void updateMesh(const mapPolyMesh&);
+
+        //- Correct weighting factors for moving mesh.
+        bool movePoints();
+
+
+    // Interpolation Functions
+
+        //- Interpolate sensitivties from points to faces
+        //  conditions
+        template<class Type>
+        void interpolateSensitivitiesField
+        (
+            const GeometricField<Type, pointPatchField, pointMesh>& pf,
+            typename GeometricField<Type, fvPatchField, volMesh>::Boundary& vf,
+            const labelHashSet& patchIDs
+        ) const;
+
+        template<class Type>
+        void interpolateBoundaryField
+        (
+            const GeometricField<Type, fvPatchField, volMesh>& vf,
+            GeometricField<Type, pointPatchField, pointMesh>& pf
+        ) const;
+
+        //- Interpolate sensitivties from points to faces
+        void interpolateSensitivitiesField
+        (
+            const vectorField& pf,
+            vectorField& vf,
+            const labelHashSet& patchIDs
+        ) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#ifdef NoRepository
+    #include "volPointInterpolateAdjoint.C"
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager/objectiveManager.C b/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager.C
similarity index 82%
rename from src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager/objectiveManager.C
rename to src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager.C
index f3d2819ba5a6e978e0fbad3352a13afce898b428..39b36884316b017a38265f9e9e2366b7bcfb4eec 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager/objectiveManager.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -38,7 +38,6 @@ namespace Foam
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 defineTypeNameAndDebug(objectiveManager, 0);
-defineRunTimeSelectionTable(objectiveManager, dictionary);
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
@@ -67,7 +66,7 @@ objectiveManager::objectiveManager
     adjointSolverName_(adjointSolverName),
     primalSolverName_(primalSolverName),
     objectives_(0),
-    weigthedObjectiveFile_(nullptr)
+    weightedObjectiveFile_(nullptr)
 {
     // Construct objectives
     //~~~~~~~~~~~~~~~~~~~~~
@@ -107,7 +106,7 @@ objectiveManager::objectiveManager
         if (objectives_.size() > 1)
         {
             const Time& time = mesh_.time();
-            weigthedObjectiveFile_.reset
+            weightedObjectiveFile_.reset
             (
                 new OFstream
                 (
@@ -117,59 +116,26 @@ objectiveManager::objectiveManager
             );
 
             unsigned int width = IOstream::defaultPrecision() + 5;
-            weigthedObjectiveFile_()
+            weightedObjectiveFile_()
                 << setw(4) << "#" << " "
                 << setw(width) << "weightedObjective" << " ";
             for (objective& objI : objectives_)
             {
-                weigthedObjectiveFile_()
+                weightedObjectiveFile_()
                     << setw(width) << objI.objectiveName() << " ";
             }
-            weigthedObjectiveFile_()
+            weightedObjectiveFile_()
                 << endl;
         }
     }
 }
 
 
-// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
-
-autoPtr<objectiveManager> objectiveManager::New
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    const word& adjointSolverName,
-    const word& primalSolverName
-)
-{
-    // Determine type of objectiveManager from objectiveType
-    const word objectiveType(dict.get<word>("type"));
-    const word managerType("objectiveManager" & objectiveType);
-
-    auto* ctorPtr = dictionaryConstructorTable(managerType);
-
-    if (!ctorPtr)
-    {
-        FatalIOErrorInLookup
-        (
-            dict,
-            "objectiveManagerType",
-            managerType,
-            *dictionaryConstructorTablePtr_
-        ) << exit(FatalIOError);
-    }
-
-    return autoPtr<objectiveManager>
-    (
-        ctorPtr(mesh, dict, adjointSolverName, primalSolverName)
-    );
-}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 bool objectiveManager::readDict(const dictionary& dict)
 {
+    dict_ = dict;
     for (objective& obj : objectives_)
     {
         obj.readDict
@@ -233,15 +199,20 @@ void objectiveManager::incrementIntegrationTimes(const scalar timeSpan)
 }
 
 
-scalar objectiveManager::print()
+scalar objectiveManager::print(bool negate)
 {
     scalar objValue(Zero);
     Info<< "Adjoint solver " << adjointSolverName_ << endl;
     for (objective& obj : objectives_)
     {
-        scalar cost = obj.JCycle();
-        scalar weight = obj.weight();
-        objValue += weight*cost;
+        // This function is used to obtain the value used to figure out if
+        // line search is converged or not. If the objective is not updated
+        // in each iteration of the primal solver, the old objective value
+        // might be returned. Force the update of the objective the next
+        // time objective::J() is called.
+        obj.setComputed(false);
+        const scalar cost = obj.JCycle(negate);
+        objValue += cost;
 
         Info<< obj.objectiveName() << " : " << cost << endl;
     }
@@ -252,35 +223,53 @@ scalar objectiveManager::print()
 }
 
 
-bool objectiveManager::writeObjectives
-(
-    const scalar weightedObjective,
-    const bool valid
-)
+void objectiveManager::setWrite(const bool shouldWrite)
+{
+    for (objective& obj : objectives_)
+    {
+        obj.setWrite(shouldWrite);
+    }
+}
+
+
+bool objectiveManager::writeObjectives()
 {
     for (const objective& obj : objectives_)
     {
         // Write objective function to file
-        obj.write();
-        obj.writeMeanValue();
+        if (obj.shouldWrite())
+        {
+            obj.write();
+            obj.writeMeanValue();
+        }
     }
 
-    if (weigthedObjectiveFile_)
+    return true;
+}
+
+
+bool objectiveManager::writeObjectives
+(
+    const scalar weightedObjective,
+    const bool valid
+)
+{
+    if (weightedObjectiveFile_.valid())
     {
         unsigned int width = IOstream::defaultPrecision() + 5;
-        weigthedObjectiveFile_()
+        weightedObjectiveFile_()
             << setw(4) << mesh_.time().timeName() << " "
             << setw(width) << weightedObjective << " ";
 
         for (objective& objI : objectives_)
         {
-            weigthedObjectiveFile_()
+            weightedObjectiveFile_()
                 << setw(width) << objI.JCycle() << " ";
         }
-        weigthedObjectiveFile_() << endl;
+        weightedObjectiveFile_() << endl;
     }
 
-    return true;
+    return writeObjectives();
 }
 
 
@@ -332,6 +321,24 @@ void objectiveManager::checkIntegrationTimes() const
 }
 
 
+void objectiveManager::addSource(fvVectorMatrix& matrix)
+{
+    for (objective& obj : objectives_)
+    {
+        obj.addSource(matrix);
+    }
+}
+
+
+void objectiveManager::addSource(fvScalarMatrix& matrix)
+{
+    for (objective& obj : objectives_)
+    {
+        obj.addSource(matrix);
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 } // End namespace Foam
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager/objectiveManager.H b/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager.H
similarity index 75%
rename from src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager/objectiveManager.H
rename to src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager.H
index 0aeacc6b484ba4a72b9e60bc82add78a68a85488..951e53c11633fbff494be6d21cb37d53bec876fe 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager/objectiveManager.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManager.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -29,7 +29,7 @@ Class
     Foam::objectiveManager
 
 Description
-    class for managing incompressible objective functions.
+    Class for managing objective functions.
 
 SourceFiles
     objectiveManager.C
@@ -61,11 +61,11 @@ protected:
     // Protected data
 
         const fvMesh& mesh_;
-        const dictionary& dict_;
+        dictionary dict_;
         const word adjointSolverName_;
         const word primalSolverName_;
         PtrList<objective> objectives_;
-        autoPtr<OFstream> weigthedObjectiveFile_;
+        autoPtr<OFstream> weightedObjectiveFile_;
 
 
 private:
@@ -83,22 +83,6 @@ public:
 
     TypeName("objectiveManager");
 
-    // Declare run-time constructor selection table
-
-        declareRunTimeSelectionTable
-        (
-            autoPtr,
-            objectiveManager,
-            dictionary,
-            (
-                const fvMesh& mesh,
-                const dictionary& dict,
-                const word& adjointSolverName,
-                const word& primalSolverName
-            ),
-            (mesh, dict, adjointSolverName, primalSolverName)
-        );
-
     // Constructors
 
         //- Construct from components
@@ -110,17 +94,6 @@ public:
             const word& primalSolverName
         );
 
-    // Selectors
-
-        //- Return a reference to the selected turbulence model
-        static autoPtr<objectiveManager> New
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            const word& adjointSolverName,
-            const word& primalSolverName
-        );
-
 
     //- Destructor
     virtual ~objectiveManager() = default;
@@ -143,7 +116,12 @@ public:
         void incrementIntegrationTimes(const scalar timeSpan);
 
         //- Print to screen
-        scalar print();
+        scalar print(bool negate = false);
+
+        //- Should the objectives be written to file upon calling write()?
+        void setWrite(const bool shouldWrite);
+        //- Write objective function history
+        virtual bool writeObjectives();
 
         //- Write objective function history
         virtual bool writeObjectives
@@ -175,16 +153,10 @@ public:
         void checkIntegrationTimes() const;
 
         //- Add contribution to adjoint momentum PDEs
-        virtual void addUaEqnSource(fvVectorMatrix& UaEqn) = 0;
-
-        //- Add contribution to adjoint momentum PDEs
-        virtual void addPaEqnSource(fvScalarMatrix& paEqn) = 0;
-
-        //- Add contribution to first adjoint turbulence model PDE
-        virtual void addTMEqn1Source(fvScalarMatrix& adjTMEqn1) = 0;
+        virtual void addSource(fvVectorMatrix& matrix);
 
-        //- Add contribution to second adjoint turbulence model PDE
-        virtual void addTMEqn2Source(fvScalarMatrix& adjTMEqn2) = 0;
+        //- Add contribution to a scalar adjoint PDEs
+        virtual void addSource(fvScalarMatrix& matrix);
 
 
     // IO
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.C
deleted file mode 100644
index 44aaa1a0a73ae7d38f2d104cb21330bb24163e16..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.C
+++ /dev/null
@@ -1,132 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "objectiveManagerIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(objectiveManagerIncompressible, 0);
-addToRunTimeSelectionTable
-(
-    objectiveManager,
-    objectiveManagerIncompressible,
-    dictionary
-);
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-objectiveManagerIncompressible::objectiveManagerIncompressible
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    const word& adjointSolverName,
-    const word& primalSolverName
-)
-:
-    objectiveManager(mesh, dict, adjointSolverName, primalSolverName)
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void objectiveManagerIncompressible::addUaEqnSource(fvVectorMatrix& UaEqn)
-{
-    // Add contributions from objective functions
-    for (objective& obj : objectives_)
-    {
-        auto& icoObj = refCast<objectiveIncompressible>(obj);
-
-        if (icoObj.hasdJdv())
-        {
-            scalar weight = icoObj.weight();
-            UaEqn += weight*icoObj.dJdv();
-        }
-    }
-}
-
-
-void objectiveManagerIncompressible::addPaEqnSource(fvScalarMatrix& paEqn)
-{
-    // Add contributions from objective functions
-    for (objective& obj : objectives_)
-    {
-        auto& icoObj = refCast<objectiveIncompressible>(obj);
-
-        if (icoObj.hasdJdp())
-        {
-            scalar weight = icoObj.weight();
-            paEqn += weight*icoObj.dJdp();
-        }
-    }
-}
-
-
-void objectiveManagerIncompressible::addTMEqn1Source(fvScalarMatrix& adjTMEqn1)
-{
-    // Add contributions from objective functions
-    for (objective& obj : objectives_)
-    {
-        auto& icoObj = refCast<objectiveIncompressible>(obj);
-
-        if (icoObj.hasdJdTMVar1())
-        {
-            scalar weight = icoObj.weight();
-            adjTMEqn1 += weight*icoObj.dJdTMvar1();
-        }
-    }
-}
-
-
-void objectiveManagerIncompressible::addTMEqn2Source(fvScalarMatrix& adjTMEqn2)
-{
-    // Add contributions from objective functions
-    for (objective& obj : objectives_)
-    {
-        auto& icoObj = refCast<objectiveIncompressible>(obj);
-
-        if (icoObj.hasdJdTMVar2())
-        {
-            scalar weight = icoObj.weight();
-            adjTMEqn2 += weight*icoObj.dJdTMvar2();
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.C b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.C
new file mode 100644
index 0000000000000000000000000000000000000000..548329f9a95788c69912d2cd9200ba6694e5d608
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.C
@@ -0,0 +1,119 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2023 PCOpt/NTUA
+    Copyright (C) 2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "objectiveGeometric.H"
+#include "createZeroField.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+defineTypeNameAndDebug(objectiveGeometric, 0);
+defineRunTimeSelectionTable(objectiveGeometric, dictionary);
+addToRunTimeSelectionTable
+(
+    objective,
+    objectiveGeometric,
+    objective
+);
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+objectiveGeometric::objectiveGeometric
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    const word& adjointSolverName,
+    const word& primalSolverName
+)
+:
+    objective(mesh, dict, adjointSolverName, primalSolverName)
+{
+    weight_ = dict.get<scalar>("weight");
+}
+
+
+// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
+
+autoPtr<objectiveGeometric> objectiveGeometric::New
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    const word& adjointSolverName,
+    const word& primalSolverName
+)
+{
+    const word modelType(dict.get<word>("type"));
+
+    Info<< "Creating objective function : " << dict.dictName()
+        << " of type " << modelType << endl;
+
+    auto* ctorPtr = dictionaryConstructorTable(modelType);
+
+    if (!ctorPtr)
+    {
+        FatalIOErrorInLookup
+        (
+            dict,
+            "objectiveGeometric",
+            modelType,
+            *dictionaryConstructorTablePtr_
+        ) << exit(FatalIOError);
+    }
+
+    return autoPtr<objectiveGeometric>
+    (
+        ctorPtr(mesh, dict, adjointSolverName, primalSolverName)
+    );
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void objectiveGeometric::update()
+{
+    // Update geometric fields
+    objective::update();
+
+    // Divide everything with normalization factor
+    doNormalization();
+
+    // Set objective as not computed, for the next optimisation cycle
+    computed_ = false;
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.H
similarity index 58%
rename from src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.H
index ff13355b09f3ff369302ddbde1e10a6df4df59cd..29e14cd11e1bf8ba484a30c703e938cbffe645e5 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.H
@@ -5,9 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2023 PCOpt/NTUA
+    Copyright (C) 2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,22 +26,21 @@ License
 
 
 Class
-    Foam::objectiveManagerIncompressible
+    Foam::objectiveGeometric
 
 Description
-    class for managing incompressible objective functions.
+    Abstract base class for objective functions that contain only geometric
+    quantities
 
 SourceFiles
-    objectiveManagerIncompressible.C
+    objectiveGeometric.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef objectiveManagerIncompressible_H
-#define objectiveManagerIncompressible_H
+#ifndef objectiveGeometric_H
+#define objectiveGeometric_H
 
-#include "objectiveManager.H"
-#include "objectiveIncompressible.H"
-#include "runTimeSelectionTables.H"
+#include "objective.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -50,35 +48,52 @@ namespace Foam
 {
 
 /*---------------------------------------------------------------------------*\
-               Class objectiveManagerIncompressible Declaration
+                      Class objectiveGeometric Declaration
 \*---------------------------------------------------------------------------*/
 
-class objectiveManagerIncompressible
+class objectiveGeometric
 :
-    public objectiveManager
+    public objective
 {
+
 private:
 
     // Private Member Functions
 
         //- No copy construct
-        objectiveManagerIncompressible
-        (
-            const objectiveManagerIncompressible&
-        ) = delete;
+        objectiveGeometric(const objectiveGeometric&) = delete;
 
         //- No copy assignment
-        void operator=(const objectiveManagerIncompressible&) = delete;
+        void operator=(const objectiveGeometric&) = delete;
 
 
 public:
 
-    TypeName("objectiveManagerIncompressible");
+    //- Runtime type information
+    TypeName("geometric");
+
+
+    // Declare run-time constructor selection table
+
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            objectiveGeometric,
+            dictionary,
+            (
+                const fvMesh& mesh,
+                const dictionary& dict,
+                const word& adjointSolverName,
+                const word& primalSolverName
+            ),
+            (mesh, dict, adjointSolverName, primalSolverName)
+        );
+
 
     // Constructors
 
         //- Construct from components
-        objectiveManagerIncompressible
+        objectiveGeometric
         (
             const fvMesh& mesh,
             const dictionary& dict,
@@ -87,23 +102,29 @@ public:
         );
 
 
-    //- Destructor
-    virtual ~objectiveManagerIncompressible() = default;
+    // Selectors
 
+        //- Return a reference to the selected turbulence model
+        static autoPtr<objectiveGeometric> New
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            const word& adjointSolverName,
+            const word& primalSolverName
+        );
 
-    // Member Functions
 
-        //- Add contribution to adjoint momentum PDEs
-        virtual void addUaEqnSource(fvVectorMatrix& UaEqn);
+    //- Destructor
+    virtual ~objectiveGeometric() = default;
+
 
-        //- Add contribution to adjoint momentum PDEs
-        virtual void addPaEqnSource(fvScalarMatrix& paEqn);
+    // Member Functions
 
-        //- Add contribution to adjoint turbulence model PDE
-        virtual void addTMEqn1Source(fvScalarMatrix& adjTMEqn1);
+        //- Return the objective function value
+        virtual scalar J() = 0;
 
-        //- Add contribution to adjoint turbulence model PDE
-        virtual void addTMEqn2Source(fvScalarMatrix& adjTMEqn2);
+        //- Update objective function derivatives
+        virtual void update();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePartialVolume/objectivePartialVolume.C b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectivePartialVolume/objectivePartialVolume.C
similarity index 88%
rename from src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePartialVolume/objectivePartialVolume.C
rename to src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectivePartialVolume/objectivePartialVolume.C
index 5df2db8b9de6cc5a353259eb40e1a771311d8936..f131efa41808030980d17e5a31937f4dc858663d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePartialVolume/objectivePartialVolume.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectivePartialVolume/objectivePartialVolume.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -45,7 +45,7 @@ namespace objectives
 defineTypeNameAndDebug(objectivePartialVolume, 1);
 addToRunTimeSelectionTable
 (
-    objectiveIncompressible,
+    objectiveGeometric,
     objectivePartialVolume,
     dictionary
 );
@@ -61,7 +61,7 @@ objectivePartialVolume::objectivePartialVolume
     const word& primalSolverName
 )
 :
-    objectiveIncompressible(mesh, dict, adjointSolverName, primalSolverName),
+    objectiveGeometric(mesh, dict, adjointSolverName, primalSolverName),
     initVol_(Zero),
     objectivePatches_
     (
@@ -72,7 +72,11 @@ objectivePartialVolume::objectivePartialVolume
     )
 {
     // Read target volume if present. Else use the current one as a target
-    if (!dict.readIfPresent("initialVolume", initVol_))
+    if
+    (
+        !objective::readIfPresent("initialVolume", initVol_)
+     && !dict.readIfPresent("initialVolume", initVol_)
+    )
     {
         const scalar oneThird(1.0/3.0);
         for (const label patchi : objectivePatches_)
@@ -129,10 +133,18 @@ void objectivePartialVolume::update_dSdbMultiplier()
 }
 
 
+bool objectivePartialVolume::writeData(Ostream& os) const
+{
+    os.writeEntry("initialVolume", initVol_);
+    return objective::writeData(os);
+}
+
+
 void objectivePartialVolume::addHeaderInfo() const
 {
     objFunctionFilePtr_()
-        << setw(width_) << "#VInit" << " "
+        << setw(4) << "#" << " "
+        << setw(width_) << "VInit" << " "
         << setw(width_) << initVol_ << endl;
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePartialVolume/objectivePartialVolume.H b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectivePartialVolume/objectivePartialVolume.H
similarity index 91%
rename from src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePartialVolume/objectivePartialVolume.H
rename to src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectivePartialVolume/objectivePartialVolume.H
index 4aef943fc8564bb560fed9112194039547da93cd..bc9527a5d7b9cd38427434b01e0b4e7aaf3b9bff 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePartialVolume/objectivePartialVolume.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectivePartialVolume/objectivePartialVolume.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -39,7 +39,7 @@ SourceFiles
 #ifndef objectivePartialVolume_H
 #define objectivePartialVolume_H
 
-#include "objectiveIncompressible.H"
+#include "objectiveGeometric.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -55,7 +55,7 @@ namespace objectives
 
 class objectivePartialVolume
 :
-    public objectiveIncompressible
+    public objectiveGeometric
 {
     // Private data
 
@@ -98,6 +98,9 @@ public:
         //- sensitivity term
         void update_dSdbMultiplier();
 
+        //- Write initial volume for continuation
+        virtual bool writeData(Ostream& os) const;
+
         // Helper write functions
 
             //- Write headers for additional columns
@@ -107,7 +110,7 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace objectivePartialVolume
+} // End namespace objectives
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveForce/objectiveForce.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveForce/objectiveForce.C
index 393f792ca111ff2bed482f1710a0887d588ed9a3..d201396112cdac90a802b579781af38e8f8a91a1 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveForce/objectiveForce.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveForce/objectiveForce.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2019, 2022 FOSS GP
+    Copyright (C) 2007-2023, 2022 PCOpt/NTUA
+    Copyright (C) 2013-2023, 2022 FOSS GP
     Copyright (C) 2019-2023 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -216,7 +216,7 @@ void objectiveForce::update_dxdbMultiplier()
     tgradp.clear();
 
     // Term coming from stresses
-    tmp<volScalarField> tnuEff = lamTransp.nu() + turbVars->nutRef();
+    tmp<volScalarField> tnuEff = lamTransp.nu() + turbVars->nut();
     tmp<volSymmTensorField> tstress = tnuEff*twoSymm(tgradU);
     const volSymmTensorField& stress = tstress.cref();
     autoPtr<volVectorField> ptemp
@@ -260,14 +260,14 @@ void objectiveForce::update_boundarydJdGradU()
     const autoPtr<incompressible::RASModelVariables>& turbVars =
         vars_.RASModelVariables();
     const singlePhaseTransportModel& lamTransp = vars_.laminarTransport();
-    volScalarField nuEff(lamTransp.nu() + turbVars->nutRef());
+    volScalarField nuEff(lamTransp.nu() + turbVars->nut());
     for (const label patchI : forcePatches_)
     {
         const fvPatch& patch = mesh_.boundary()[patchI];
         const vectorField& Sf = patch.Sf();
         bdJdGradUPtr_()[patchI] =
           - nuEff.boundaryField()[patchI]
-           *dev(forceDirection_*Sf + Sf*forceDirection_);
+           *dev(forceDirection_*Sf + Sf*forceDirection_)/denom();
     }
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.C
index 9c54784d847211ca8aad03392eaec6f673034be0..8fb87d88e64fd69f1d68b75bca6fe90450dfc8df 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -89,7 +89,6 @@ objectiveIncompressible::objectiveIncompressible
     bdJdnutPtr_(nullptr),
     bdJdGradUPtr_(nullptr)
 {
-    weight_ = dict.get<scalar>("weight");
     computeMeanFields_ = vars_.computeMeanFields();
 }
 
@@ -200,312 +199,10 @@ void objectiveIncompressible::doNormalization()
 }
 
 
-const volVectorField& objectiveIncompressible::dJdv()
-{
-    if (!dJdvPtr_)
-    {
-        // If pointer is not set, set it to a zero field
-        dJdvPtr_.reset
-        (
-            createZeroFieldPtr<vector>
-            (
-                mesh_,
-                ("dJdv_"+type()),
-                dimLength/sqr(dimTime)
-            )
-        );
-    }
-    return *dJdvPtr_;
-}
-
-
-const volScalarField& objectiveIncompressible::dJdp()
-{
-    if (!dJdpPtr_)
-    {
-        // If pointer is not set, set it to a zero field
-        dJdpPtr_.reset
-        (
-            createZeroFieldPtr<scalar>
-            (
-                mesh_,
-                ("dJdp_"+type()),
-                dimensionSet(0, 3, -2, 0, 0, 0, 0)
-            )
-        );
-    }
-    return *dJdpPtr_;
-}
-
-
-const volScalarField& objectiveIncompressible::dJdT()
-{
-    if (!dJdTPtr_)
-    {
-        // If pointer is not set, set it to a zero field
-        dJdTPtr_.reset
-        (
-            createZeroFieldPtr<scalar>
-            (
-                mesh_,
-                ("dJdT_"+type()),
-                dimensionSet(0, 3, -2, 0, 0, 0, 0)
-            )
-        );
-    }
-    return *dJdTPtr_;
-}
-
-
-const volScalarField& objectiveIncompressible::dJdTMvar1()
-{
-    if (!dJdTMvar1Ptr_)
-    {
-        // If pointer is not set, set it to a zero field
-        dJdTMvar1Ptr_.reset
-        (
-            createZeroFieldPtr<scalar>
-            (
-                mesh_,
-                ("dJdTMvar1_"+type()),
-                dimensionSet(0, 0, -2, 0, 0, 0, 0)
-            )
-        );
-    }
-    return *dJdTMvar1Ptr_;
-}
-
-
-const volScalarField& objectiveIncompressible::dJdTMvar2()
-{
-    if (!dJdTMvar2Ptr_)
-    {
-        // If pointer is not set, set it to a zero field
-        dJdTMvar2Ptr_.reset
-        (
-            createZeroFieldPtr<scalar>
-            (
-                mesh_,
-                ("dJdTMvar2_"+type()),
-                dimensionSet(0, 3, -2, 0, 0, 0, 0)
-            )
-        );
-    }
-    return *dJdTMvar2Ptr_;
-}
-
-
-const fvPatchVectorField& objectiveIncompressible::boundarydJdv
-(
-    const label patchI
-)
-{
-    if (!bdJdvPtr_)
-    {
-        bdJdvPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdvPtr_()[patchI];
-}
-
-
-const fvPatchScalarField& objectiveIncompressible::boundarydJdvn
-(
-    const label patchI
-)
-{
-    if (!bdJdvnPtr_)
-    {
-        bdJdvnPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdvnPtr_()[patchI];
-}
-
-
-const fvPatchVectorField& objectiveIncompressible::boundarydJdvt
-(
-    const label patchI
-)
-{
-    if (!bdJdvtPtr_)
-    {
-        bdJdvtPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdvtPtr_()[patchI];
-}
-
-
-const fvPatchVectorField& objectiveIncompressible::boundarydJdp
-(
-    const label patchI
-)
-{
-    if (!bdJdpPtr_)
-    {
-        bdJdpPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdpPtr_()[patchI];
-}
-
-
-const fvPatchScalarField& objectiveIncompressible::boundarydJdT
-(
-    const label patchI
-)
-{
-    if (!bdJdTPtr_)
-    {
-        bdJdTPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdTPtr_()[patchI];
-}
-
-
-const fvPatchScalarField& objectiveIncompressible::boundarydJdTMvar1
-(
-    const label patchI
-)
-{
-    if (!bdJdTMvar1Ptr_)
-    {
-        bdJdTMvar1Ptr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdTMvar1Ptr_()[patchI];
-}
-
-
-const fvPatchScalarField& objectiveIncompressible::boundarydJdTMvar2
-(
-    const label patchI
-)
-{
-    if (!bdJdTMvar2Ptr_)
-    {
-        bdJdTMvar2Ptr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdTMvar2Ptr_()[patchI];
-}
-
-
-const fvPatchScalarField& objectiveIncompressible::boundarydJdnut
-(
-    const label patchI
-)
-{
-    if (!bdJdnutPtr_)
-    {
-        bdJdnutPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdnutPtr_()[patchI];
-}
-
-
-const fvPatchTensorField& objectiveIncompressible::boundarydJdGradU
-(
-    const label patchI
-)
-{
-    if (!bdJdGradUPtr_)
-    {
-        bdJdGradUPtr_.reset(createZeroBoundaryPtr<tensor>(mesh_));
-    }
-    return bdJdGradUPtr_()[patchI];
-}
-
-
-const boundaryVectorField& objectiveIncompressible::boundarydJdv()
-{
-    if (!bdJdvPtr_)
-    {
-        bdJdvPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdvPtr_();
-}
-
-
-const boundaryScalarField& objectiveIncompressible::boundarydJdvn()
-{
-    if (!bdJdvnPtr_)
-    {
-        bdJdvnPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdvnPtr_();
-}
-
-
-const boundaryVectorField& objectiveIncompressible::boundarydJdvt()
-{
-    if (!bdJdvtPtr_)
-    {
-        bdJdvtPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdvtPtr_();
-}
-
-
-const boundaryVectorField& objectiveIncompressible::boundarydJdp()
-{
-    if (!bdJdpPtr_)
-    {
-        bdJdpPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdpPtr_();
-}
-
-
-const boundaryScalarField& objectiveIncompressible::boundarydJdT()
-{
-    if (!bdJdTPtr_)
-    {
-        bdJdTPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdTPtr_();
-}
-
-
-const boundaryScalarField& objectiveIncompressible::boundarydJdTMvar1()
-{
-    if (!bdJdTMvar1Ptr_)
-    {
-        bdJdTMvar1Ptr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdTMvar1Ptr_();
-}
-
-
-const boundaryScalarField& objectiveIncompressible::boundarydJdTMvar2()
-{
-    if (!bdJdTMvar2Ptr_)
-    {
-        bdJdTMvar2Ptr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdTMvar2Ptr_();
-}
-
-
-const boundaryScalarField& objectiveIncompressible::boundarydJdnut()
-{
-    if (!bdJdnutPtr_)
-    {
-        bdJdnutPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    }
-    return bdJdnutPtr_();
-}
-
-
-const boundaryTensorField& objectiveIncompressible::boundarydJdGradU()
-{
-    if (!bdJdGradUPtr_)
-    {
-        bdJdGradUPtr_.reset(createZeroBoundaryPtr<tensor>(mesh_));
-    }
-    return *bdJdGradUPtr_;
-}
-
-
 void objectiveIncompressible::update()
 {
-    // Objective function value
-    J();
+    // Update geometric fields
+    objective::update();
 
     // Update mean values here since they might be used in the
     // subsequent functions
@@ -517,9 +214,6 @@ void objectiveIncompressible::update()
     update_dJdT();
     update_dJdTMvar1();
     update_dJdTMvar2();
-    update_dJdb();
-    update_divDxDbMultiplier();
-    update_gradDxDbMultiplier();
 
     // boundaryFields
     update_boundarydJdv();
@@ -531,15 +225,12 @@ void objectiveIncompressible::update()
     update_boundarydJdTMvar2();
     update_boundarydJdnut();
     update_boundarydJdGradU();
-    update_boundarydJdb();
-    update_dSdbMultiplier();
-    update_dndbMultiplier();
-    update_dxdbMultiplier();
-    update_dxdbDirectMultiplier();
-    update_boundaryEdgeContribution();
 
     // Divide everything with normalization factor
     doNormalization();
+
+    // Set objective as not computed, for the next optimisation cycle
+    computed_ = false;
 }
 
 
@@ -722,6 +413,15 @@ void objectiveIncompressible::update_dJdTMvar
 }
 
 
+void objectiveIncompressible::addSource(fvVectorMatrix& matrix)
+{
+    if (fieldNames_.found(matrix.psi().name()) && hasdJdv())
+    {
+        matrix += weight()*dJdv();
+    }
+}
+
+
 bool objectiveIncompressible::write(const bool valid) const
 {
     return objective::write(valid);
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.H
index eb2b89fae94a58d4fc4ddf4098e5b28293e7b1d4..8af411bf18e49fff4a43043449d06ba8f55775fc 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressible.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -176,76 +176,76 @@ public:
         virtual void doNormalization();
 
         //- Contribution to field adjoint momentum eqs
-        const volVectorField& dJdv();
+        inline const volVectorField& dJdv();
 
         //- Contribution to field adjoint continuity eq
-        const volScalarField& dJdp();
+        inline const volScalarField& dJdp();
 
         //- Contribution to field adjoint energy eq
-        const volScalarField& dJdT();
+        inline const volScalarField& dJdT();
 
         //- Contribution to field adjoint turbulence model variable 1
-        const volScalarField& dJdTMvar1();
+        inline const volScalarField& dJdTMvar1();
 
         //- Contribution to field adjoint turbulence model variable 2
-        const volScalarField& dJdTMvar2();
+        inline const volScalarField& dJdTMvar2();
 
         //- Objective partial deriv wrt velocity for a specific patch
         const fvPatchVectorField& boundarydJdv(const label);
 
         //- Objective partial deriv wrt normal velocity for a specific patch
-        const fvPatchScalarField& boundarydJdvn(const label);
+        inline const fvPatchScalarField& boundarydJdvn(const label);
 
         //- Objective partial deriv wrt tangent velocity for a specific patch
-        const fvPatchVectorField& boundarydJdvt(const label);
+        inline const fvPatchVectorField& boundarydJdvt(const label);
 
         //- Objective partial deriv wrt pressure (times normal) for a specific
         //- patch
-        const fvPatchVectorField& boundarydJdp(const label);
+        inline const fvPatchVectorField& boundarydJdp(const label);
 
         //- Objective partial deriv wrt temperature for a specific patch
-        const fvPatchScalarField& boundarydJdT(const label);
+        inline const fvPatchScalarField& boundarydJdT(const label);
 
         //- Objective partial deriv wrt turbulence model var 1 for a specific
         //- patch
-        const fvPatchScalarField& boundarydJdTMvar1(const label);
+        inline const fvPatchScalarField& boundarydJdTMvar1(const label);
 
         //- Objective partial deriv wrt turbulence model var 2 for a specific
         //- patch
-        const fvPatchScalarField& boundarydJdTMvar2(const label);
+        inline const fvPatchScalarField& boundarydJdTMvar2(const label);
 
         //- Objective partial deriv wrt nut for a specific patch
-        const fvPatchScalarField& boundarydJdnut(const label);
+        inline const fvPatchScalarField& boundarydJdnut(const label);
 
         //- Objective partial deriv wrt stress tensor
-        const fvPatchTensorField& boundarydJdGradU(const label);
+        inline const fvPatchTensorField& boundarydJdGradU(const label);
 
         //- Objective partial deriv wrt velocity for all patches
-        const boundaryVectorField& boundarydJdv();
+        inline const boundaryVectorField& boundarydJdv();
 
         //- Objective partial deriv wrt normal velocity for all patches
-        const boundaryScalarField& boundarydJdvn();
+        inline const boundaryScalarField& boundarydJdvn();
 
         //- Objective partial deriv wrt tangent velocity for all patches
-        const boundaryVectorField& boundarydJdvt();
+        inline const boundaryVectorField& boundarydJdvt();
 
         //- Objective partial deriv wrt pressure (times normal) for all patches
-        const boundaryVectorField& boundarydJdp();
+        inline const boundaryVectorField& boundarydJdp();
 
         //- Objective partial deriv wrt temperature for all patches
-        const boundaryScalarField& boundarydJdT();
+        inline const boundaryScalarField& boundarydJdT();
 
         //- Objective partial deriv wrt turbulence model var 1 for all patches
-        const boundaryScalarField& boundarydJdTMvar1();
+        inline const boundaryScalarField& boundarydJdTMvar1();
 
         //- Objective partial deriv wrt turbulence model var 2 for all patches
-        const boundaryScalarField& boundarydJdTMvar2();
+        inline const boundaryScalarField& boundarydJdTMvar2();
 
         //- Objective partial deriv wrt nut for all patches
-        const boundaryScalarField& boundarydJdnut();
+        inline const boundaryScalarField& boundarydJdnut();
 
         //- Objective partial deriv wrt gradU
-        const boundaryTensorField& boundarydJdGradU();
+        inline const boundaryTensorField& boundarydJdGradU();
 
         //- Update objective function derivatives
         virtual void update();
@@ -295,6 +295,9 @@ public:
         virtual void update_dJdb()
         {}
 
+        virtual void update_dJdbField()
+        {}
+
         virtual void update_divDxDbMultiplier()
         {}
 
@@ -328,20 +331,16 @@ public:
         virtual void update_boundarydJdGradU()
         {}
 
-        virtual void update_boundarydJdb()
-        {}
-
-        virtual void update_dSdbMultiplier()
-        {}
-
-        virtual void update_dndbMultiplier()
-        {}
+        //- Vector sources can be given only to the adjoint momentum equations.
+        //- Implemented in base objectiveIncompressible
+        virtual void addSource(fvVectorMatrix& matrix);
 
-        virtual void update_dxdbMultiplier()
+        //- Scalar sources are more ambigious (adjoint pressure, turbulence
+        //- model, energy, etc equations), so the equivalent functions should
+        //- be overridden on an objective-basis
+        virtual void addSource(fvScalarMatrix& matrix)
         {}
 
-        virtual void update_dxdbDirectMultiplier()
-        {}
 
         //- Some objectives need to store some auxiliary values.
         //- If averaging is enabled, update these mean values here.
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressibleI.H b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressibleI.H
index a1f72dafb37ac335707815aeda7d5c37b2a06b6c..3e3b05324dc0acf44ad6bc4dfa9589fabe0c8c0b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressibleI.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveIncompressible/objectiveIncompressibleI.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -30,6 +30,189 @@ License
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+inline const Foam::volVectorField& Foam::objectiveIncompressible::dJdv()
+{
+    return *dJdvPtr_;
+}
+
+
+inline const Foam::volScalarField& Foam::objectiveIncompressible::dJdp()
+{
+    return *dJdpPtr_;
+}
+
+
+inline const Foam::volScalarField& Foam::objectiveIncompressible::dJdT()
+{
+    return *dJdTPtr_;
+}
+
+
+inline const Foam::volScalarField& Foam::objectiveIncompressible::dJdTMvar1()
+{
+    return *dJdTMvar1Ptr_;
+}
+
+
+inline const Foam::volScalarField& Foam::objectiveIncompressible::dJdTMvar2()
+{
+    return *dJdTMvar2Ptr_;
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objectiveIncompressible::boundarydJdv
+(
+    const label patchI
+)
+{
+    return bdJdvPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchScalarField&
+Foam::objectiveIncompressible::boundarydJdvn
+(
+    const label patchI
+)
+{
+    return bdJdvnPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objectiveIncompressible::boundarydJdvt
+(
+    const label patchI
+)
+{
+    return bdJdvtPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objectiveIncompressible::boundarydJdp
+(
+    const label patchI
+)
+{
+    return bdJdpPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchScalarField&
+Foam::objectiveIncompressible::boundarydJdT
+(
+    const label patchI
+)
+{
+    return bdJdTPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchScalarField&
+Foam::objectiveIncompressible::boundarydJdTMvar1
+(
+    const label patchI
+)
+{
+    return bdJdTMvar1Ptr_()[patchI];
+}
+
+
+inline const Foam::fvPatchScalarField&
+Foam::objectiveIncompressible::boundarydJdTMvar2
+(
+    const label patchI
+)
+{
+    return bdJdTMvar2Ptr_()[patchI];
+}
+
+
+inline const Foam::fvPatchScalarField&
+Foam::objectiveIncompressible::boundarydJdnut
+(
+    const label patchI
+)
+{
+    return bdJdnutPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchTensorField&
+Foam::objectiveIncompressible::boundarydJdGradU
+(
+    const label patchI
+)
+{
+    return bdJdGradUPtr_()[patchI];
+}
+
+
+inline const Foam::boundaryVectorField&
+Foam::objectiveIncompressible::boundarydJdv()
+{
+    return bdJdvPtr_();
+}
+
+
+inline const Foam::boundaryScalarField&
+Foam::objectiveIncompressible::boundarydJdvn()
+{
+    return bdJdvnPtr_();
+}
+
+
+inline const Foam::boundaryVectorField&
+Foam::objectiveIncompressible::boundarydJdvt()
+{
+    return bdJdvtPtr_();
+}
+
+
+inline const Foam::boundaryVectorField&
+Foam::objectiveIncompressible::boundarydJdp()
+{
+    return bdJdpPtr_();
+}
+
+
+inline const Foam::boundaryScalarField&
+Foam::objectiveIncompressible::boundarydJdT()
+{
+    return bdJdTPtr_();
+}
+
+
+inline const Foam::boundaryScalarField&
+Foam::objectiveIncompressible::boundarydJdTMvar1()
+{
+    return bdJdTMvar1Ptr_();
+}
+
+
+inline const Foam::boundaryScalarField&
+Foam::objectiveIncompressible::boundarydJdTMvar2()
+{
+    return bdJdTMvar2Ptr_();
+}
+
+
+inline const Foam::boundaryScalarField&
+Foam::objectiveIncompressible::boundarydJdnut()
+{
+    return bdJdnutPtr_();
+}
+
+
+inline const Foam::boundaryTensorField&
+Foam::objectiveIncompressible::boundarydJdGradU()
+{
+    return *bdJdGradUPtr_;
+}
+
+
 inline bool Foam::objectiveIncompressible::hasdJdv() const
 {
     return bool(dJdvPtr_);
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveMoment/objectiveMoment.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveMoment/objectiveMoment.C
index 52765b47e18909d14d9439442644b5a9b65d32a6..f1dd11d622aab086bd4e899930c87241067322ab 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveMoment/objectiveMoment.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveMoment/objectiveMoment.C
@@ -242,7 +242,7 @@ void objectiveMoment::update_dxdbMultiplier()
     tgradp.clear();
 
     // Term coming from stresses
-    tmp<volScalarField> tnuEff = lamTransp.nu() + turbVars->nutRef();
+    tmp<volScalarField> tnuEff = lamTransp.nu() + turbVars->nut();
     tmp<volSymmTensorField> tstress = tnuEff*twoSymm(tgradU);
     const volSymmTensorField& stress = tstress.cref();
     autoPtr<volVectorField> ptemp
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.C
index debd1ae1a026e700322ce24a356e6c0124149dcc..96e868c0861f01f9f2c67669aee772e9d06342db 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,6 +27,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "objectiveNutSqr.H"
+#include "incompressiblePrimalSolver.H"
 #include "incompressibleAdjointSolver.H"
 #include "createZeroField.H"
 #include "addToRunTimeSelectionTable.H"
@@ -50,6 +51,27 @@ addToRunTimeSelectionTable
 );
 
 
+void objectiveNutSqr::populateFieldNames()
+{
+    if (adjointTurbulenceNames_.empty())
+    {
+        const incompressibleAdjointSolver& adjSolver =
+            mesh_.lookupObject<incompressibleAdjointSolver>(adjointSolverName_);
+        const autoPtr<incompressibleAdjoint::adjointRASModel>& adjointRAS =
+            adjSolver.getAdjointVars().adjointTurbulence();
+        const wordList& baseNames =
+            adjointRAS().getAdjointTMVariablesBaseNames();
+        forAll(baseNames, nI)
+        {
+            fieldNames_.push_back
+                (adjSolver.extendedVariableName(baseNames[nI]));
+            adjointTurbulenceNames_.
+                push_back(adjSolver.extendedVariableName(baseNames[nI]));
+        }
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 objectiveNutSqr::objectiveNutSqr
@@ -61,7 +83,8 @@ objectiveNutSqr::objectiveNutSqr
 )
 :
     objectiveIncompressible(mesh, dict, adjointSolverName, primalSolverName),
-    zones_(mesh_.cellZones().indices(dict.get<wordRes>("zones")))
+    zones_(mesh_.cellZones().indices(dict.get<wordRes>("zones"))),
+    adjointTurbulenceNames_()
 {
     // Check if cellZones provided include at least one cell
     checkCellZonesSize(zones_);
@@ -70,13 +93,19 @@ objectiveNutSqr::objectiveNutSqr
     // Allocate term to be added to volume-based sensitivity derivatives
     divDxDbMultPtr_.reset
     (
-        createZeroFieldPtr<scalar>
+        new volScalarField
         (
+            IOobject
+            (
+                "divDxDbMult" + objectiveName_,
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
             mesh_,
-            ("divDxdbMult"+type()) ,
-            // Dimensions are set in a way that the gradient of this term
-            // matches the source of the adjoint grid displacement PDE
-            sqr(dimLength)/pow3(dimTime)
+            dimensionedScalar(sqr(dimLength)/pow3(dimTime), Zero),
+            fvPatchFieldBase::zeroGradientType()
         )
     );
 }
@@ -125,6 +154,9 @@ void objectiveNutSqr::update_dJdv()
         tmp<volVectorField> dnutdU = adjointRAS->nutJacobianU(dnutdUMult);
         if (dnutdU)
         {
+            // If nut depends on U, allocate dJdv and add Ua to the fieldNames.
+            // It should be safe to do this here since objectives are updated
+            // before the first adjoint solution
             if (!dJdvPtr_)
             {
                 dJdvPtr_.reset
@@ -137,6 +169,10 @@ void objectiveNutSqr::update_dJdv()
                     )
                 );
             }
+            if (!fieldNames_.size())
+            {
+                fieldNames_.push_back(adjSolver.extendedVariableName("Ua"));
+            }
             for (const label zI : zones_)
             {
                 const cellZone& zoneI = mesh_.cellZones()[zI];
@@ -204,6 +240,22 @@ void objectiveNutSqr::update_divDxDbMultiplier()
 }
 
 
+void objectiveNutSqr::addSource(fvScalarMatrix& matrix)
+{
+    populateFieldNames();
+    const label fieldI = fieldNames_.find(matrix.psi().name());
+
+    if (fieldI == 0)
+    {
+        matrix += weight()*dJdTMvar1();
+    }
+    if (fieldI == 1)
+    {
+        matrix += weight()*dJdTMvar2();
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 } // End namespace objectives
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.H b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.H
index a429c6d6a9ed68d3e029166facfb11ecd3dd5046..4b3b875258323f3a47e6c41579d45b52c6b339e8 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveNutSqr/objectiveNutSqr.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -76,6 +76,20 @@ class objectiveNutSqr
         //- Where to define the objective
         labelList zones_;
 
+        //- List with the names of the adjoint turbulence model fields
+        //  This is kept separately from fieldNames since the latter may
+        //  or may not include Ua, depending on whether nut is a function
+        //  of U.  This makes deciding on whether to add or not sources
+        //  to a given fvScalarMatrix tricky, hence the utilisation of
+        //  adjointTurbulenceNames_
+        wordList adjointTurbulenceNames_;
+
+
+    // Private Member Functions
+
+        //- Populate fieldNames
+        void populateFieldNames();
+
 
 public:
 
@@ -116,6 +130,9 @@ public:
         //- Update field to be added to be added to volume-based
         //- sensitivity derivatives, emerging from delta ( dV ) / delta b
         void update_divDxDbMultiplier();
+
+        //- Add source terms to the adjoint turbulence model equations
+        virtual void addSource(fvScalarMatrix& matrix);
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.C
index ae7f98112a96f4acd30f48519dce7fd1af6c5b0c..2a910db9a252a44611de3564d25b94a7946fb789 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -51,6 +51,25 @@ addToRunTimeSelectionTable
 );
 
 
+void objectivePowerDissipation::populateFieldNames()
+{
+    if (fieldNames_.size() == 1)
+    {
+        const incompressibleAdjointSolver& adjSolver =
+            mesh_.lookupObject<incompressibleAdjointSolver>(adjointSolverName_);
+        const autoPtr<incompressibleAdjoint::adjointRASModel>& adjointRAS =
+            adjSolver.getAdjointVars().adjointTurbulence();
+        const wordList& baseNames =
+            adjointRAS().getAdjointTMVariablesBaseNames();
+        forAll(baseNames, nI)
+        {
+            fieldNames_.push_back
+                (adjSolver.extendedVariableName(baseNames[nI]));
+        }
+    }
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 objectivePowerDissipation::objectivePowerDissipation
@@ -65,14 +84,12 @@ objectivePowerDissipation::objectivePowerDissipation
     zones_(mesh_.cellZones().indices(dict.get<wordRes>("zones")))
 {
     // Append Ua name to fieldNames
-    /*
     fieldNames_.setSize
     (
         1,
         mesh_.lookupObject<solver>(adjointSolverName_).
             extendedVariableName("Ua")
     );
-    */
 
     // Check if cellZones provided include at least one cell
     checkCellZonesSize(zones_);
@@ -93,13 +110,19 @@ objectivePowerDissipation::objectivePowerDissipation
     // Allocate terms to be added to volume-based sensitivity derivatives
     divDxDbMultPtr_.reset
     (
-        createZeroFieldPtr<scalar>
+        new volScalarField
         (
+            IOobject
+            (
+                "divDxDbMult" + objectiveName_,
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
             mesh_,
-            ("divDxdbMult" + type()),
-            // Dimensions are set in a way that the gradient of this term
-            // matches the source of the adjoint grid displacement PDE
-            sqr(dimLength)/pow3(dimTime)
+            dimensionedScalar(sqr(dimLength)/pow3(dimTime), Zero),
+            fvPatchFieldBase::zeroGradientType()
         )
     );
     gradDxDbMultPtr_.reset
@@ -250,7 +273,21 @@ void objectivePowerDissipation::update_gradDxDbMultiplier()
         }
     }
     gradDxDbMult.correctBoundaryConditions();
-    // Missing contribution from gradU in nut
+}
+
+
+void objectivePowerDissipation::addSource(fvScalarMatrix& matrix)
+{
+    populateFieldNames();
+    const label fieldI = fieldNames_.find(matrix.psi().name());
+    if (fieldI == 1)
+    {
+        matrix += weight()*dJdTMvar1Ptr_();
+    }
+    if (fieldI == 2)
+    {
+        matrix += weight()*dJdTMvar2Ptr_();
+    }
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.H b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.H
index 4f2379d113e98399f5448ab192e404b5b5a1c0ef..6f744de07b75e66cd95b9f4b9558bb716f6d0ac4 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePowerDissipation/objectivePowerDissipation.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -66,6 +66,12 @@ class objectivePowerDissipation
         labelList zones_;
 
 
+    // Private Member Functions
+
+        //- Populate fieldNames
+        void populateFieldNames();
+
+
 public:
 
     //- Runtime type information
@@ -107,6 +113,9 @@ public:
 
         //- Update grad(dx/db multiplier). Volume-based sensitivity term
         virtual void update_gradDxDbMultiplier();
+
+        //- Add source terms to the adjoint turbulence model equations
+        virtual void addSource(fvScalarMatrix& matrix);
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePtLosses/objectivePtLosses.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePtLosses/objectivePtLosses.C
index 20ee9e921f5c616b26c9bb0cd11cd1fc8652b9a0..fef3492b9579d4f1c2b18ae483a5f2dcd2d643cb 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePtLosses/objectivePtLosses.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectivePtLosses/objectivePtLosses.C
@@ -107,7 +107,7 @@ void objectivePtLosses::initialize()
                 const scalar mass = gSum(phiPatch);
                 if (mag(mass) > SMALL)
                 {
-                    objectiveReportPatches.append(patchI);
+                    objectiveReportPatches.push_back(patchI);
                 }
             }
         }
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityCellZone/objectiveUniformityCellZone.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityCellZone/objectiveUniformityCellZone.C
index 62fcd785042216b229f4123679e12de376f112da..7314896024b6cb98268bfab4486067c863f553d0 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityCellZone/objectiveUniformityCellZone.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityCellZone/objectiveUniformityCellZone.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -67,14 +67,12 @@ objectiveUniformityCellZone::objectiveUniformityCellZone
     volZone_(zones_.size(), Zero)
 {
     // Append Ua name to fieldNames
-    /*
     fieldNames_.setSize
     (
         1,
         mesh_.lookupObject<solver>(adjointSolverName_).
             extendedVariableName("Ua")
     );
-    */
 
     // Check if cellZones provided include at least one cell
     checkCellZonesSize(zones_);
@@ -92,13 +90,19 @@ objectiveUniformityCellZone::objectiveUniformityCellZone
     // Allocate term to be added to volume-based sensitivity derivatives
     divDxDbMultPtr_.reset
     (
-        createZeroFieldPtr<scalar>
+        new volScalarField
         (
+            IOobject
+            (
+                "divDxDbMult" + objectiveName_,
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
             mesh_,
-            ("divDxdbMult" + type()) ,
-            // Dimensions are set in a way that the gradient of this term
-            // matches the source of the adjoint grid displacement PDE
-            sqr(dimLength)/pow3(dimTime)
+            dimensionedScalar(sqr(dimLength)/pow3(dimTime), Zero),
+            fvPatchFieldBase::zeroGradientType()
         )
     );
 }
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityPatch/objectiveUniformityPatch.C b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityPatch/objectiveUniformityPatch.C
index f1a73419517bb2cef90608dd618da8bc8ee849a4..c6f5bd91debc0e802def6a369e02dacd04caa04d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityPatch/objectiveUniformityPatch.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/incompressible/objectiveUniformityPatch/objectiveUniformityPatch.C
@@ -106,7 +106,7 @@ void objectiveUniformityPatch::initialize()
                 const scalar mass = gSum(phiPatch);
                 if (mass > SMALL)
                 {
-                    objectiveReportPatches.append(patchI);
+                    objectiveReportPatches.push_back(patchI);
                 }
             }
         }
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.C b/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.C
index 76c4fb4e3a6bc1ae5b47bb64d2ef666c30bef71a..eca940fdf7af312e0ddb1ed7d3e00f75e76fe0ed 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.C
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -50,6 +50,10 @@ void objective::makeFolder()
         const Time& time = mesh_.time();
         objFunctionFolder_ =
             time.globalPath()/"optimisation"/type()/time.timeName();
+        if (mesh_.name() != polyMesh::defaultRegion)
+        {
+            objFunctionFolder_ /= mesh_.name();
+        }
 
         mkDir(objFunctionFolder_);
     }
@@ -131,10 +135,12 @@ objective::objective
     computeMeanFields_(false), // is reset in derived classes
     nullified_(false),
     normalize_(dict.getOrDefault<bool>("normalize", false)),
+    shouldWrite_(true),
 
     J_(Zero),
     JMean_(this->getOrDefault<scalar>("JMean", Zero)),
-    weight_(Zero),
+    weight_(dict.get<scalar>("weight")),
+    computed_(false),
     normFactor_(nullptr),
     target_
     (
@@ -142,14 +148,22 @@ objective::objective
         autoPtr<scalar>::New(dict.get<scalar>("target")) :
         nullptr
     ),
+    targetLeft_
+    (
+        dict.found("targetLeft") ?
+        autoPtr<scalar>::New(dict.get<scalar>("targetLeft")) :
+        nullptr
+    ),
     integrationStartTimePtr_(nullptr),
     integrationEndTimePtr_(nullptr),
+    fieldNames_(),
 
     // Initialize pointers to nullptr.
     // Not all of them are required for each objective function.
     // Each child should allocate whatever is needed.
 
     dJdbPtr_(nullptr),
+    dJdbFieldPtr_(nullptr),
     bdJdbPtr_(nullptr),
     bdSdbMultPtr_(nullptr),
     bdndbMultPtr_(nullptr),
@@ -196,6 +210,9 @@ objective::objective
             normFactor_.reset(new scalar(normFactor));
         }
     }
+
+    // Set the weight factor in case of continuation
+    this->readIfPresent("weight", weight_);
 }
 
 
@@ -245,7 +262,7 @@ bool objective::readDict(const dictionary& dict)
 }
 
 
-scalar objective::JCycle() const
+scalar objective::JCycle(bool negate) const
 {
     scalar J(J_);
     if
@@ -260,7 +277,14 @@ scalar objective::JCycle() const
     // Subtract target, in case the objective is used as a constraint
     if (target_.valid())
     {
-        J -= target_();
+        if (negate)
+        {
+            J = - J + targetLeft_();
+        }
+        else
+        {
+            J -= target_();
+        }
     }
 
     // Normalize here, in order to get the correct value for line search
@@ -268,6 +292,7 @@ scalar objective::JCycle() const
     {
         J /= normFactor_();
     }
+    J *= weight_;
 
     return J;
 }
@@ -277,7 +302,12 @@ void objective::updateNormalizationFactor()
 {
     if (normalize_ && !normFactor_)
     {
-        normFactor_.reset(new scalar(JCycle()));
+        scalar J(JCycle()/weight_);
+        normFactor_.reset(new scalar(J));
+        DebugInfo
+            << "objective " << name() << ":: updating norm factor "
+            << "to " << normFactor_()
+            << " for time = " << mesh_.time().timeName() << endl;
     }
 }
 
@@ -343,6 +373,10 @@ void objective::doNormalization()
         {
             dJdbPtr_().primitiveFieldRef() *= oneOverNorm;
         }
+        if (hasdJdbField())
+        {
+            dJdbFieldPtr_() *= oneOverNorm;
+        }
         if (hasBoundarydJdb())
         {
             bdJdbPtr_() *= oneOverNorm;
@@ -393,7 +427,8 @@ bool objective::isWithinIntegrationTime() const
     else
     {
         FatalErrorInFunction
-            << "Unallocated integration start or end time"
+            << "Unallocated integration start or end time for objective '"
+            << objectiveName_ << "'"
             << exit(FatalError);
     }
     return false;
@@ -416,193 +451,24 @@ void objective::incrementIntegrationTimes(const scalar timeSpan)
 }
 
 
-const volScalarField& objective::dJdb()
+void objective::update()
 {
-    if (!dJdbPtr_)
-    {
-        // If pointer is not set, set it to a zero field
-        dJdbPtr_.reset
-        (
-            createZeroFieldPtr<scalar>
-            (
-                mesh_,
-                ("dJdb_" + objectiveName_),
-                dimensionSet(0, 5, -2, 0, 0, 0, 0)
-            )
-        );
-    }
-
-    return *dJdbPtr_;
-}
+    // Objective function value
+    J();
 
+    // volFields
+    update_dJdb();
+    update_dJdbField();
+    update_divDxDbMultiplier();
+    update_gradDxDbMultiplier();
 
-const fvPatchVectorField& objective::boundarydJdb(const label patchI)
-{
-    if (!bdJdbPtr_)
-    {
-        bdJdbPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdJdbPtr_()[patchI];
-}
-
-
-const fvPatchVectorField& objective::dSdbMultiplier(const label patchI)
-{
-    if (!bdSdbMultPtr_)
-    {
-        bdSdbMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdSdbMultPtr_()[patchI];
-}
-
-
-const fvPatchVectorField& objective::dndbMultiplier(const label patchI)
-{
-    if (!bdndbMultPtr_)
-    {
-        bdndbMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdndbMultPtr_()[patchI];
-}
-
-
-const fvPatchVectorField& objective::dxdbMultiplier(const label patchI)
-{
-    if (!bdxdbMultPtr_)
-    {
-        bdxdbMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdxdbMultPtr_()[patchI];
-}
-
-
-const fvPatchVectorField& objective::dxdbDirectMultiplier(const label patchI)
-{
-    if (!bdxdbDirectMultPtr_)
-    {
-        bdxdbDirectMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return bdxdbDirectMultPtr_()[patchI];
-}
-
-
-const vectorField& objective::boundaryEdgeMultiplier
-(
-    const label patchI,
-    const label edgeI
-)
-{
-    if (!bdxdbDirectMultPtr_)
-    {
-        FatalErrorInFunction
-            << "Unallocated boundaryEdgeMultiplier field"
-            << exit(FatalError);
-    }
-    return bEdgeContribution_()[patchI][edgeI];
-}
-
-
-const boundaryVectorField& objective::boundarydJdb()
-{
-    if (!bdJdbPtr_)
-    {
-        bdJdbPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return *bdJdbPtr_;
-}
-
-
-const boundaryVectorField& objective::dSdbMultiplier()
-{
-    if (!bdSdbMultPtr_)
-    {
-        bdSdbMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return *bdSdbMultPtr_;
-}
-
-
-const boundaryVectorField& objective::dndbMultiplier()
-{
-    if (!bdndbMultPtr_)
-    {
-        bdndbMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return *bdndbMultPtr_;
-}
-
-
-const boundaryVectorField& objective::dxdbMultiplier()
-{
-    if (!bdxdbMultPtr_)
-    {
-        bdxdbMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return *bdxdbMultPtr_;
-}
-
-
-const boundaryVectorField& objective::dxdbDirectMultiplier()
-{
-    if (!bdxdbDirectMultPtr_)
-    {
-        bdxdbDirectMultPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    }
-    return *bdxdbDirectMultPtr_;
-}
-
-
-const vectorField3& objective::boundaryEdgeMultiplier()
-{
-    if (!bdxdbDirectMultPtr_)
-    {
-        FatalErrorInFunction
-            << "Unallocated boundaryEdgeMultiplier field"
-            << endl << endl
-            << exit(FatalError);
-    }
-    return *bEdgeContribution_;
-}
-
-
-const volScalarField& objective::divDxDbMultiplier()
-{
-    if (!divDxDbMultPtr_)
-    {
-        // If pointer is not set, set it to a zero field
-        divDxDbMultPtr_.reset
-        (
-            createZeroFieldPtr<scalar>
-            (
-                mesh_,
-                ("divDxDbMult"+objectiveName_),
-                // Variable dimensions!!
-                // Dummy dimensionless. Only the internalField will be used
-                dimless
-            )
-        );
-    }
-    return *divDxDbMultPtr_;
-}
-
-
-const volTensorField& objective::gradDxDbMultiplier()
-{
-    if (!gradDxDbMultPtr_)
-    {
-        // If pointer is not set, set it to a zero field
-        gradDxDbMultPtr_.reset
-        (
-            createZeroFieldPtr<tensor>
-            (
-                mesh_,
-                ("gradDxDbMult"+objectiveName_),
-                // Variable dimensions!!
-                dimensionSet(pow2(dimLength)/pow3(dimTime))
-            )
-        );
-    }
-    return *gradDxDbMultPtr_;
+    // boundaryFields
+    update_boundarydJdb();
+    update_dSdbMultiplier();
+    update_dndbMultiplier();
+    update_dxdbMultiplier();
+    update_dxdbDirectMultiplier();
+    update_boundaryEdgeContribution();
 }
 
 
@@ -614,6 +480,10 @@ void objective::nullify()
         {
             dJdbPtr_() == dimensionedScalar(dJdbPtr_().dimensions(), Zero);
         }
+        if (hasdJdbField())
+        {
+            dJdbFieldPtr_() = Zero;
+        }
         if (hasBoundarydJdb())
         {
             bdJdbPtr_() == vector::zero;
@@ -676,6 +546,11 @@ bool objective::write(const bool valid) const
                 file<< setw(width_) << "#target" << " "
                     << setw(width_) << target_() << endl;
             }
+            if (targetLeft_.valid())
+            {
+                file<< setw(width_) << "#targetLeft" << " "
+                    << setw(width_) << targetLeft_() << endl;
+            }
             if (normalize_)
             {
                 file<< setw(width_) << "#normFactor " << " "
@@ -685,6 +560,10 @@ bool objective::write(const bool valid) const
             file<< setw(4) << "#" << " ";
             file<< setw(width_) << "J" << " ";
             file<< setw(width_) << "JCycle" << " ";
+            if (targetLeft_)
+            {
+                file<< setw(width_) << "JCycleLeft" << " ";
+            }
             addHeaderColumns();
             file<< endl;
         }
@@ -693,6 +572,10 @@ bool objective::write(const bool valid) const
         file<< setw(4) << mesh_.time().value() << " ";
         file<< setw(width_) << J_ << " ";
         file<< setw(width_) << JCycle() << " ";
+        if (targetLeft_)
+        {
+            file<< setw(width_) << JCycle(true) << " ";
+        }
         addColumnValues();
         file<< endl;
     }
@@ -708,12 +591,14 @@ void objective::writeInstantaneousValue() const
         // File is opened only upon invocation of the write function
         // in order to avoid various instantiations of the same objective
         // opening the same file
+        unsigned int width = IOstream::defaultPrecision() + 6;
         if (!instantValueFilePtr_)
         {
             setInstantValueFilePtr();
         }
 
-        instantValueFilePtr_() << mesh_.time().value() << tab << J_ << endl;
+        instantValueFilePtr_()
+            << setw(width) << mesh_.time().value() << tab << J_ << endl;
     }
 }
 
@@ -764,6 +649,7 @@ bool objective::writeData(Ostream& os) const
     {
         os.writeEntry("normFactor", normFactor_());
     }
+    os.writeEntry("weight", weight_);
     return os.good();
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.H b/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.H
index 49f3d46343d43d507cc4eb9c7dfe6ba42df181d4..7d0270d8cc4a2c511a1e9b56823264108f91ec6f 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objective.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -73,6 +73,7 @@ protected:
         bool computeMeanFields_;
         bool nullified_;
         bool normalize_;
+        bool shouldWrite_;
 
         //- Objective function value and weight
         scalar J_;
@@ -83,23 +84,47 @@ protected:
         //- Objective weight
         scalar weight_;
 
+        //- Whether the objective is computed or not
+        //  Some objective (e.g. geometric ones) might not change from one
+        //  iteration of the primal solver to the next and might be expensive
+        //  to evaluate. This can be used to compute them only once per
+        //  optimisation cycle
+        bool computed_;
+
         //- Normalization factor
         autoPtr<scalar> normFactor_;
 
         //- Target value, in case the objective is used as a constraint
-        //  Should be used in caution and with updateMethods than get affected
-        //  by the target value, without requiring a sqr (e.g. SQP, MMA)
+        //  Should be used with caution and with updateMethods
+        //  than get affected by the target value, without
+        //  requiring a sqr (e.g. SQP, MMA)
         autoPtr<scalar> target_;
 
+        //- Target on the left hand-side of a double inequality,
+        //- for double sided constraints
+        autoPtr<scalar> targetLeft_;
+
         //- Objective integration start and end times (for unsteady flows)
         autoPtr<scalar> integrationStartTimePtr_;
         autoPtr<scalar> integrationEndTimePtr_;
 
+        //- List of adjoint fields for which this objective will contribute
+        //- sources to their equations.
+        //  Only for volume-based objectives for the moment
+        wordList fieldNames_;
+
         //- Contribution to field sensitivity derivatives
         //  Topology optimisation or other variants with
         //  as many design variables as the mesh cells
+        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         autoPtr<volScalarField> dJdbPtr_;
 
+        //- Contribution to sensitivity derivatives with a
+        //- random number of design variables
+        //- (neither surface, nor volume based)
+        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        autoPtr<scalarField> dJdbFieldPtr_;
+
         // Contribution to surface sensitivity derivatives
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -155,9 +180,6 @@ protected:
 
     // Protected Member Functions
 
-        //- Return objective dictionary
-        const dictionary& dict() const;
-
         //- Set the output file ptr
         void setObjectiveFilePtr() const;
 
@@ -241,9 +263,10 @@ public:
         //- Return the instantaneous objective function value
         virtual scalar J() = 0;
 
-        //- Return the mean objective function value, if it exists,
-        //- otherwise the mean one
-        scalar JCycle() const;
+        //- Return the objective function of the optimisation cycle.
+        //  This corresponds to the mean value, if it exists, or the
+        //  instantaneous value otherwise
+        scalar JCycle(bool negate = false) const;
 
         //- Accumulate contribution for the mean objective value
         //  For steady-state runs
@@ -256,6 +279,12 @@ public:
         //- Return the objective function weight
         scalar weight() const;
 
+        //- Return the normalization factor
+        const autoPtr<scalar>& normFactor() const;
+
+        //- Return the objective target value
+        const autoPtr<scalar>& target() const;
+
         //- Is the objective normalized
         bool normalize() const;
 
@@ -269,53 +298,56 @@ public:
         void incrementIntegrationTimes(const scalar timeSpan);
 
         //- Contribution to field sensitivities
-        const volScalarField& dJdb();
+        inline const volScalarField& dJdb();
+
+        //- Contribution to sensitivities with a random number of designVars
+        inline const scalarField& dJdbField();
 
         //- Contribution to surface sensitivities for a specific patch
-        const fvPatchVectorField& boundarydJdb(const label);
+        inline const fvPatchVectorField& boundarydJdb(const label);
 
         //- Multiplier of delta(n dS)/delta b
-        const fvPatchVectorField& dSdbMultiplier(const label);
+        inline const fvPatchVectorField& dSdbMultiplier(const label);
 
         //- Multiplier of delta(n dS)/delta b
-        const fvPatchVectorField& dndbMultiplier(const label);
+        inline const fvPatchVectorField& dndbMultiplier(const label);
 
         //- Multiplier of delta(x)/delta b
-        const fvPatchVectorField& dxdbMultiplier(const label);
+        inline const fvPatchVectorField& dxdbMultiplier(const label);
 
         //- Multiplier of delta(x)/delta b
-        const fvPatchVectorField& dxdbDirectMultiplier(const label);
+        inline const fvPatchVectorField& dxdbDirectMultiplier(const label);
 
         //- Multiplier located at patch boundary edges
-        const vectorField& boundaryEdgeMultiplier
+        inline const vectorField& boundaryEdgeMultiplier
         (
             const label patchI,
             const label edgeI
         );
 
         //- Contribution to surface sensitivities for all patches
-        const boundaryVectorField& boundarydJdb();
+        inline const boundaryVectorField& boundarydJdb();
 
         //- Multiplier of delta(n dS)/delta b for all patches
-        const boundaryVectorField& dSdbMultiplier();
+        inline const boundaryVectorField& dSdbMultiplier();
 
         //- Multiplier of delta(n dS)/delta b for all patches
-        const boundaryVectorField& dndbMultiplier();
+        inline const boundaryVectorField& dndbMultiplier();
 
         //- Multiplier of delta(x)/delta b for all patches
-        const boundaryVectorField& dxdbMultiplier();
+        inline const boundaryVectorField& dxdbMultiplier();
 
         //- Multiplier of delta(x)/delta b for all patches
-        const boundaryVectorField& dxdbDirectMultiplier();
+        inline const boundaryVectorField& dxdbDirectMultiplier();
 
         //- Multiplier located at patch boundary edges
-        const vectorField3& boundaryEdgeMultiplier();
+        inline const vectorField3& boundaryEdgeMultiplier();
 
         //- Multiplier of grad( delta(x)/delta b) for volume-based sensitivities
-        const volScalarField& divDxDbMultiplier();
+        inline const volScalarField& divDxDbMultiplier();
 
         //- Multiplier of grad( delta(x)/delta b) for volume-based sensitivities
-        const volTensorField& gradDxDbMultiplier();
+        inline const volTensorField& gradDxDbMultiplier();
 
         //- Update objective function derivatives
         virtual void update() = 0;
@@ -327,6 +359,13 @@ public:
         //- which the factor is not known a priori
         virtual void updateNormalizationFactor();
 
+
+        virtual void update_dJdb()
+        {}
+
+        virtual void update_dJdbField()
+        {}
+
         //- Update objective function derivative term
         virtual void update_boundarydJdb()
         {}
@@ -360,6 +399,15 @@ public:
         virtual void update_gradDxDbMultiplier()
         {}
 
+
+        //- Manipulate fvVectorMatrix through the objective
+        virtual void addSource(fvVectorMatrix& matrix)
+        {}
+
+        //- Manipulate fvVectorMatrix through the objective
+        virtual void addSource(fvScalarMatrix& matrix)
+        {}
+
         //- Write objective function history
         virtual bool write(const bool valid = true) const;
 
@@ -392,8 +440,15 @@ public:
         //- Return the objective name
         inline const word& objectiveName() const;
 
+        //- Should the objective be written to file upon calling write()?
+        inline bool shouldWrite() const;
+
+        //- Should the objective be written to file upon calling write()?
+        inline void setWrite(const bool shouldWrite);
+
         // Inline functions for checking whether pointers are set or not
         inline bool hasdJdb() const;
+        inline bool hasdJdbField() const;
         inline bool hasBoundarydJdb() const;
         inline bool hasdSdbMult() const;
         inline bool hasdndbMult() const;
@@ -406,6 +461,11 @@ public:
         // Inline functions for checking whether integration times are set
         inline bool hasIntegrationStartTime() const;
         inline bool hasIntegrationEndTime() const;
+        // Set the computed status of the objective
+        inline void setComputed(const bool isComputed);
+
+        //- Return objective dictionary
+        const dictionary& dict() const;
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objectiveI.H b/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objectiveI.H
index 4c11fa047d44b707b1b7fff8a3f8ae183f4d248d..5cb1f9d5e985cae08c236eb8fe2877e7ad2f08c5 100644
--- a/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objectiveI.H
+++ b/src/optimisation/adjointOptimisation/adjoint/objectives/objective/objectiveI.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -36,12 +36,148 @@ inline const Foam::word& Foam::objective::objectiveName() const
 }
 
 
+inline bool Foam::objective::shouldWrite() const
+{
+    return shouldWrite_;
+}
+
+
+inline void Foam::objective::setWrite(const bool shouldWrite)
+{
+    shouldWrite_ = shouldWrite;
+}
+
+
+inline const Foam::volScalarField& Foam::objective::dJdb()
+{
+    return *dJdbPtr_;
+}
+
+
+inline const Foam::scalarField& Foam::objective::dJdbField()
+{
+    return *dJdbFieldPtr_;
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objective::boundarydJdb(const label patchI)
+{
+    return bdJdbPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objective::dSdbMultiplier(const label patchI)
+{
+    return bdSdbMultPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objective::dndbMultiplier(const label patchI)
+{
+    return bdndbMultPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objective::dxdbMultiplier(const label patchI)
+{
+    return bdxdbMultPtr_()[patchI];
+}
+
+
+inline const Foam::fvPatchVectorField&
+Foam::objective::dxdbDirectMultiplier(const label patchI)
+{
+    return bdxdbDirectMultPtr_()[patchI];
+}
+
+
+inline const Foam::vectorField& Foam::objective::boundaryEdgeMultiplier
+(
+    const label patchI,
+    const label edgeI
+)
+{
+    if (!bdxdbDirectMultPtr_)
+    {
+        FatalErrorInFunction
+            << "Unallocated boundaryEdgeMultiplier field"
+            << exit(FatalError);
+    }
+    return bEdgeContribution_()[patchI][edgeI];
+}
+
+
+inline const Foam::boundaryVectorField& Foam::objective::boundarydJdb()
+{
+    return *bdJdbPtr_;
+}
+
+
+inline const Foam::boundaryVectorField& Foam::objective::dSdbMultiplier()
+{
+    return *bdSdbMultPtr_;
+}
+
+
+inline const Foam::boundaryVectorField& Foam::objective::dndbMultiplier()
+{
+    return *bdndbMultPtr_;
+}
+
+
+inline const Foam::boundaryVectorField& Foam::objective::dxdbMultiplier()
+{
+    return *bdxdbMultPtr_;
+}
+
+
+inline const Foam::boundaryVectorField& Foam::objective::dxdbDirectMultiplier()
+{
+    return *bdxdbDirectMultPtr_;
+}
+
+
+inline const Foam::vectorField3& Foam::objective::boundaryEdgeMultiplier()
+{
+    if (!bdxdbDirectMultPtr_)
+    {
+        FatalErrorInFunction
+            << "Unallocated boundaryEdgeMultiplier field"
+            << endl << endl
+            << exit(FatalError);
+    }
+    return *bEdgeContribution_;
+}
+
+
+inline const Foam::volScalarField& Foam::objective::divDxDbMultiplier()
+{
+    return *divDxDbMultPtr_;
+}
+
+
+inline const Foam::volTensorField& Foam::objective::gradDxDbMultiplier()
+{
+    return *gradDxDbMultPtr_;
+}
+
+
 inline bool Foam::objective::hasdJdb() const
 {
     return bool(dJdbPtr_);
 }
 
 
+inline bool Foam::objective::hasdJdbField() const
+{
+    return bool(dJdbFieldPtr_);
+}
+
+
 inline bool Foam::objective::hasBoundarydJdb() const
 {
     return bool(bdJdbPtr_);
@@ -101,4 +237,11 @@ inline bool Foam::objective::hasIntegrationEndTime() const
     return bool(integrationEndTimePtr_);
 }
 
+
+inline void Foam::objective::setComputed(const bool isComputed)
+{
+    computed_ = isComputed;
+}
+
+
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.C
similarity index 71%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.C
index bdffd2682eb2bacec758f5189c1e5a8d97b2b818..782f4c1e7834390fc7d73121e2f68bfc30a35c54 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.C
@@ -27,18 +27,23 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "adjointEikonalSolverIncompressible.H"
+#include "adjointEikonalSolver.H"
+#include "adjointSolver.H"
+#include "fvc.H"
+#include "fvm.H"
+#include "surfaceInterpolation.H"
+#include "volFieldsFwd.H"
 #include "wallFvPatch.H"
 #include "patchDistMethod.H"
+#include "fvOptions.H"
+#include "zeroGradientFvPatchField.H"
+#include "sensitivityTopO.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 defineTypeNameAndDebug(adjointEikonalSolver, 0);
@@ -55,7 +60,7 @@ wordList adjointEikonalSolver::patchTypes() const
 
     for (const label patchi : wallPatchIDs_)
     {
-        daTypes[patchi] = fvPatchFieldBase::zeroGradientType();
+        daTypes[patchi] = zeroGradientFvPatchScalarField::typeName;
     }
 
     return daTypes;
@@ -66,7 +71,6 @@ void adjointEikonalSolver::read()
 {
     nEikonalIters_ = dict_.getOrDefault<label>("iters", 1000);
     tolerance_ = dict_.getOrDefault<scalar>("tolerance", 1e-6);
-    epsilon_ = dict_.getOrDefault<scalar>("epsilon", 0.1);
     const scalar defaultEps =
         mesh_.schemesDict().subDict("wallDist").
             subOrEmptyDict("advectionDiffusionCoeffs").
@@ -78,7 +82,7 @@ void adjointEikonalSolver::read()
 tmp<surfaceScalarField> adjointEikonalSolver::computeYPhi()
 {
     // Primal distance field
-    const volScalarField& d = RASModelVars_().d();
+    const volScalarField& d = adjointSolver_.yWall();
 
     volVectorField ny
     (
@@ -118,15 +122,13 @@ adjointEikonalSolver::adjointEikonalSolver
 (
     const fvMesh& mesh,
     const dictionary& dict,
-    const autoPtr<incompressible::RASModelVariables>& RASModelVars,
-    incompressibleAdjointVars& adjointVars,
+    adjointSolver& adjointSolver,
     const labelHashSet& sensitivityPatchIDs
 )
 :
     mesh_(mesh),
     dict_(dict.subOrEmptyDict("adjointEikonalSolver")),
-    RASModelVars_(RASModelVars),
-    adjointTurbulence_(adjointVars.adjointTurbulence()),
+    adjointSolver_(adjointSolver),
     sensitivityPatchIDs_(sensitivityPatchIDs),
     nEikonalIters_(-1),
     tolerance_(-1),
@@ -138,17 +140,19 @@ adjointEikonalSolver::adjointEikonalSolver
         (
             word
             (
-                adjointVars.useSolverNameForFields() ?
-                "da" + adjointTurbulence_().adjointSolverName() :
+                adjointSolver.useSolverNameForFields() ?
+                "da" + adjointSolver.solverName() :
                 "da"
             ),
             mesh_.time().timeName(),
             mesh_,
             IOobject::READ_IF_PRESENT,
             IOobject::AUTO_WRITE
+//            adjointVars.writeFields() ?
+//              IOobject::AUTO_WRITE : IOobject::NO_WRITE
         ),
         mesh_,
-        dimensionedScalar(sqr(dimLength)/pow3(dimTime), Zero),
+        dimensionedScalar(adjointSolver.daDimensions() , Zero),
         patchTypes()
     ),
     source_
@@ -162,7 +166,7 @@ adjointEikonalSolver::adjointEikonalSolver
             IOobject::NO_WRITE
         ),
         mesh_,
-        dimensionedScalar(dimLength/pow3(dimTime), Zero)
+        dimensionedScalar(adjointSolver.daDimensions()/dimLength, Zero)
     ),
     distanceSensPtr_(createZeroBoundaryPtr<vector>(mesh_))
 {
@@ -175,6 +179,7 @@ adjointEikonalSolver::adjointEikonalSolver
 bool adjointEikonalSolver::readDict(const dictionary& dict)
 {
     dict_ = dict.subOrEmptyDict("adjointEikonalSolver");
+    read();
 
     return true;
 }
@@ -183,7 +188,7 @@ bool adjointEikonalSolver::readDict(const dictionary& dict)
 void adjointEikonalSolver::accumulateIntegrand(const scalar dt)
 {
     // Accumulate integrand from the current time step
-    source_ += adjointTurbulence_->distanceSensitivities()*dt;
+    source_ += adjointSolver_.adjointEikonalSource()*dt;
 }
 
 
@@ -192,12 +197,29 @@ void adjointEikonalSolver::solve()
     read();
 
     // Primal distance field
-    const volScalarField& d = RASModelVars_().d();
+    const volScalarField& d = adjointSolver_.yWall();
 
     // Convecting flux
     tmp<surfaceScalarField> tyPhi = computeYPhi();
     const surfaceScalarField& yPhi = tyPhi();
 
+    volScalarField scaleDims
+    (
+        IOobject
+        (
+            "scaleDims",
+            mesh_.time().timeName(),
+            mesh_,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE,
+            false
+        ),
+        mesh_,
+        dimensionedScalar("scaleDims", dimTime/dimLength, scalar(1))
+    );
+
+    fv::options& fvOptions(fv::options::New(this->mesh_));
+
     // Iterate the adjoint to the eikonal equation
     for (label iter = 0; iter < nEikonalIters_; ++iter)
     {
@@ -211,10 +233,15 @@ void adjointEikonalSolver::solve()
           + fvm::SuSp(-epsilon_*fvc::laplacian(d), da_)
           - epsilon_*fvm::laplacian(d, da_)
           + source_
+         ==
+            fvOptions(scaleDims, da_)
         );
 
         daEqn.relax();
+        fvOptions.constrain(daEqn);
         scalar residual = daEqn.solve().initialResidual();
+        fvOptions.correct(da_);
+
         Info<< "Max da " << gMax(mag(da_)()) << endl;
 
         mesh_.time().printExecutionTime(Info);
@@ -247,7 +274,7 @@ boundaryVectorField& adjointEikonalSolver::distanceSensitivities()
 
     boundaryVectorField& distanceSens = distanceSensPtr_();
 
-    const volScalarField& d = RASModelVars_().d();
+    const volScalarField& d = adjointSolver_.yWall();
     for (const label patchi : sensitivityPatchIDs_)
     {
         vectorField nf(mesh_.boundary()[patchi].nf());
@@ -262,32 +289,35 @@ boundaryVectorField& adjointEikonalSolver::distanceSensitivities()
 }
 
 
-tmp<volTensorField> adjointEikonalSolver::getFISensitivityTerm()  const
+tmp<volTensorField> adjointEikonalSolver::getFISensitivityTerm() const
 {
     Info<< "Calculating distance sensitivities " << endl;
 
-    const volScalarField& d = RASModelVars_().d();
+    const volScalarField& d = adjointSolver_.yWall();
     const volVectorField gradD(fvc::grad(d));
 
-    volVectorField gradDDa
+    auto gradDDa
     (
-        IOobject
+        tmp<volVectorField>::New
         (
-            "gradDDa",
-            mesh_.time().timeName(),
+            IOobject
+            (
+                "gradDDa",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::AUTO_WRITE
+            ),
             mesh_,
-            IOobject::NO_READ,
-            IOobject::AUTO_WRITE
-        ),
-        mesh_,
-        dimensionedVector(d.dimensions()*da_.dimensions()/dimLength, Zero),
-        patchDistMethod::patchTypes<vector>(mesh_, wallPatchIDs_)
+            dimensionedVector(d.dimensions()*da_.dimensions()/dimLength, Zero),
+            patchDistMethod::patchTypes<vector>(mesh_, wallPatchIDs_)
+        )
     );
-    gradDDa = fvc::grad(d*da_);
+    gradDDa.ref() = fvc::grad(d*da_);
 
-    tmp<volTensorField> tdistanceSens
+    auto tdistanceSens
     (
-        new volTensorField
+        tmp<volTensorField>::New
         (
             IOobject
             (
@@ -298,20 +328,46 @@ tmp<volTensorField> adjointEikonalSolver::getFISensitivityTerm()  const
                 IOobject::AUTO_WRITE
             ),
             mesh_,
-            dimensionedTensor(da_.dimensions(), Zero)
+            dimensionedTensor(da_.dimensions(), Zero),
+            zeroGradientFvPatchField<tensor>::typeName
         )
     );
     volTensorField& distanceSens = tdistanceSens.ref();
 
     distanceSens =
         - 2.*da_*gradD*gradD
-        - epsilon_*gradD*gradDDa
-        + epsilon_*da_*d*fvc::grad(gradD);
+        - epsilon_*gradDDa*gradD
+        // grad(gradD) is symmetric theoretically but not numerically when
+        // computed with the Gauss divergence theorem. The following maintains
+        // exactly the same behaviour as the one before the re-factoring of
+        // sensitivities but avoid calling the tranpose operator.
+        + epsilon_*da_*d*fvc::div(fvc::interpolate(gradD)*mesh_.Sf());
+    distanceSens.correctBoundaryConditions();
 
     return tdistanceSens;
 }
 
 
+tmp<scalarField> adjointEikonalSolver::topologySensitivities
+(
+    const word& designVarsName
+) const
+{
+    const volScalarField& d = adjointSolver_.yWall();
+
+    auto tres(tmp<scalarField>::New(d.primitiveField().size(), Zero));
+    scalarField dSens(d.primitiveField()*da_.primitiveField());
+
+    fv::options& fvOptions(fv::options::New(this->mesh_));
+    sensitivityTopO::postProcessSens
+    (
+        tres.ref(), dSens, fvOptions, d.name(), designVarsName
+    );
+
+    return tres;
+}
+
+
 const volScalarField& adjointEikonalSolver::da()
 {
     return da_;
@@ -320,7 +376,7 @@ const volScalarField& adjointEikonalSolver::da()
 
 tmp<volVectorField> adjointEikonalSolver::gradEikonal()
 {
-    const volScalarField& d = RASModelVars_().d();
+    const volScalarField& d = adjointSolver_.yWall();
     volVectorField gradD(fvc::grad(d));
     return tmp<volVectorField>::New("gradEikonal", 2*gradD & fvc::grad(gradD));
 }
@@ -328,7 +384,6 @@ tmp<volVectorField> adjointEikonalSolver::gradEikonal()
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.H
similarity index 91%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.H
index 7bfd9b5e29c3656f991096bc529ae177b4716b0a..3613265d4dcdacbda6ee61d4e46e658526285eed 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.H
@@ -27,7 +27,7 @@ License
 
 
 Class
-    Foam::incompressible::adjointEikonalSolver
+    Foam::adjointEikonalSolver
 
 Description
     Solver of the adjoint to the eikonal PDE
@@ -123,25 +123,26 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef adjointEikonalSolverIncompressible_H
-#define adjointEikonalSolverIncompressible_H
+#ifndef adjointEikonalSolver_H
+#define adjointEikonalSolver_H
 
 #include "IOdictionary.H"
-#include "incompressibleAdjointVars.H"
+#include "volFieldsFwd.H"
+#include "fvMesh.H"
+#include "calculatedFvPatchField.H"
 #include "createZeroField.H"
 #include "boundaryFieldsFwd.H"
-#include "RASModelVariables.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
+// Forward Declaration
+class adjointSolver;
 
 /*---------------------------------------------------------------------------*\
-                    Class adjointEikonalSolver Declaration
+                     Class adjointEikonalSolver Declaration
 \*---------------------------------------------------------------------------*/
 
 class adjointEikonalSolver
@@ -165,10 +166,7 @@ protected:
 
         dictionary dict_;
 
-        const autoPtr<incompressible::RASModelVariables>& RASModelVars_;
-
-        autoPtr<Foam::incompressibleAdjoint::adjointRASModel>&
-            adjointTurbulence_;
+        adjointSolver& adjointSolver_;
 
         const labelHashSet& sensitivityPatchIDs_;
 
@@ -214,8 +212,7 @@ public:
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            const autoPtr<incompressible::RASModelVariables>& RASModelVars,
-            incompressibleAdjointVars& adjointVars,
+            adjointSolver& adjointSolver,
             const labelHashSet& sensitivityPatchIDs
         );
 
@@ -244,6 +241,9 @@ public:
        //- Return the volume-based sensitivity term depending on da
        tmp<volTensorField> getFISensitivityTerm() const;
 
+       //- Return sensitivity contribution to topology optimisation
+       tmp<scalarField> topologySensitivities(const word& designVarsName) const;
+
        //-  Return the adjoint distance field
        const volScalarField& da();
 
@@ -257,7 +257,6 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.C
similarity index 58%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.C
index 0cd4e7565f097d8bd51d77f3c649aa6ea1b239f5..326c096f58bb328fa3b25ed05ff351f5b19e5a6c 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -27,41 +27,57 @@ License
 
 \*---------------------------------------------------------------------------*/
 
+#include "adjointEikonalSolver.H"
 #include "runTimeSelectionTables.H"
-#include "adjointSensitivityIncompressible.H"
-#include "boundaryAdjointContribution.H"
-#include "incompressibleAdjointSolver.H"
-#include "wallFvPatch.H"
+#include "adjointSensitivity.H"
+#include "adjointSolver.H"
+#include "designVariables.H"
 #include "fvOptions.H"
+#include "reverseLinear.H"
+#include "sensitivity.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 defineTypeNameAndDebug(adjointSensitivity, 0);
 defineRunTimeSelectionTable(adjointSensitivity, dictionary);
 
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 adjointSensitivity::adjointSensitivity
 (
     const fvMesh& mesh,
     const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
+    class adjointSolver& adjointSolver
 )
 :
     sensitivity(mesh, dict),
-    derivatives_(0),
     adjointSolver_(adjointSolver),
-    primalVars_(adjointSolver.getPrimalVars()),
-    adjointVars_(adjointSolver.getAdjointVars()),
-    objectiveManager_(adjointSolver.getObjectiveManager())
+    derivatives_(0),
+    suffix_(word::null),
+    includeDistance_
+    (
+        this->dict().getOrDefault<bool>
+        (
+            "includeDistance",
+            adjointSolver_.includeDistance()
+        )
+    ),
+    eikonalSolver_(nullptr),
+    gradDxDbMult_(nullptr),
+    divDxDbMult_(nullptr),
+    dxdbMult_(nullptr),
+    dSfdbMult_(nullptr),
+    dnfdbMult_(nullptr),
+    dxdbDirectMult_(nullptr),
+    pointDxDbDirectMult_(nullptr),
+    bcDxDbMult_(nullptr),
+    optionsDxDbMult_(nullptr)
 {}
 
 
@@ -71,14 +87,15 @@ autoPtr<adjointSensitivity> adjointSensitivity::New
 (
     const fvMesh& mesh,
     const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
+    class adjointSolver& adjointSolver
 )
 {
-    const word modelType(dict.get<word>("type"));
+    const word sensType =
+        dict.optionalSubDict(mesh.name()).get<word>("sensitivityType");
 
-    Info<< "adjointSensitivity type : " << modelType << endl;
+    Info<< "adjointSensitivity type : " << sensType << endl;
 
-    auto* ctorPtr = dictionaryConstructorTable(modelType);
+    auto* ctorPtr = dictionaryConstructorTable(sensType);
 
     if (!ctorPtr)
     {
@@ -86,7 +103,7 @@ autoPtr<adjointSensitivity> adjointSensitivity::New
         (
             dict,
             "adjointSensitivity",
-            modelType,
+            sensType,
             *dictionaryConstructorTablePtr_
         ) << exit(FatalIOError);
     }
@@ -100,81 +117,84 @@ autoPtr<adjointSensitivity> adjointSensitivity::New
 
 // * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
 
-const scalarField& adjointSensitivity::calculateSensitivities()
+bool adjointSensitivity::readDict(const dictionary& dict)
 {
-    assembleSensitivities();
-    write(type());
-    return derivatives_;
-}
+    if (sensitivity::readDict(dict))
+    {
+        // The adjoint eikonal solver requires the parameterized patches
+        // as an argument, if they exist. Allocation will be managed by
+        // derived classes that have access to them
+        includeDistance_ = this->dict().getOrDefault<bool>
+        (
+            "includeDistance",
+            adjointSolver_.includeDistance()
+        );
 
+        return true;
+    }
 
-const scalarField& adjointSensitivity::getSensitivities() const
-{
-    return derivatives_;
+    return false;
 }
 
 
-void adjointSensitivity::clearSensitivities()
+bool adjointSensitivity::computeDxDbInternalField() const
 {
-    derivatives_ = scalar(0);
-    if (fieldSensPtr_)
-    {
-        fieldSensPtr_().primitiveFieldRef() = scalar(0);
-    }
+    return false;
 }
 
 
-void adjointSensitivity::write(const word& baseName)
+void adjointSensitivity::assembleSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
 {
-    sensitivity::write(baseName);
+    derivatives_ = designVars->assembleSensitivities(*this);
 }
 
 
-tmp<volTensorField> adjointSensitivity::computeGradDxDbMultiplier()
+const scalarField& adjointSensitivity::calculateSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
 {
-    return adjointSolver_.computeGradDxDbMultiplier();
+    assembleSensitivities(designVars);
+    write(type());
+    return derivatives_;
 }
 
 
-tmp<volVectorField> adjointSensitivity::adjointMeshMovementSource()
+const scalarField& adjointSensitivity::getSensitivities() const
 {
-    tmp<volTensorField> tgradDxDbMult = computeGradDxDbMultiplier();
-    volTensorField& gradDxDbMult = tgradDxDbMult.ref();
-
-    tmp<volVectorField> tadjointMeshMovementSource
-    (
-        new volVectorField
-        (
-            IOobject
-            (
-               "adjointMeshMovementSource",
-               mesh_.time().timeName(),
-               mesh_,
-               IOobject::NO_READ,
-               IOobject::NO_WRITE
-            ),
-            mesh_,
-            dimensionedVector(gradDxDbMult.dimensions()/dimLength, Zero)
-        )
-    );
+    return derivatives_;
+}
 
-    volVectorField& source = tadjointMeshMovementSource.ref();
 
-    source -= fvc::div(gradDxDbMult.T());
+void adjointSensitivity::clearSensitivities()
+{
+    derivatives_ = Zero;
+    if (fieldSensPtr_)
+    {
+        fieldSensPtr_().primitiveFieldRef() = Zero;
+    }
+    if (eikonalSolver_)
+    {
+        eikonalSolver_->reset();
+    }
+    if (adjointMeshMovementSolver_)
+    {
+        adjointMeshMovementSolver_->reset();
+    }
+}
 
-    // Terms from fvOptions
-    fv::options::New(this->mesh_).postProcessSens
-    (
-        source.primitiveFieldRef(), adjointVars_.solverName()
-    );
 
-    return (tadjointMeshMovementSource);
+void adjointSensitivity::write(const word& baseName)
+{
+    sensitivity::write(baseName);
 }
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.H
new file mode 100644
index 0000000000000000000000000000000000000000..a476777a096cddfb99edd84ab43269a260f21c2f
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.H
@@ -0,0 +1,299 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::adjointSensitivity
+
+Description
+    Abstract base class for adjoint-based sensitivities
+
+SourceFiles
+    adjointSensitivity.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef adjointSensitivityIncompressible_H
+#define adjointSensitivityIncompressible_H
+
+#include "boundaryFieldsFwd.H"
+#include "adjointEikonalSolver.H"
+#include "adjointMeshMovementSolver.H"
+#include "sensitivity.H"
+#include "volFieldsFwd.H"
+#include "wallFvPatch.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward declaration
+class adjointSolver;
+
+/*---------------------------------------------------------------------------*\
+                     Class adjointSensitivity Declaration
+\*---------------------------------------------------------------------------*/
+
+class adjointSensitivity
+:
+    public sensitivity
+{
+protected:
+
+
+    // Protected data
+
+        //- Reference to the underlaying adjoint solver
+        adjointSolver& adjointSolver_;
+
+        //- The sensitivity derivative values
+        scalarField derivatives_;
+
+        //- Append this word to files related to the sensitivities
+        word suffix_;
+
+        //- Include distance variation in sensitivity computations
+        bool includeDistance_;
+
+        //- Adjoint eikonal equation solver
+        autoPtr<adjointEikonalSolver> eikonalSolver_;
+
+        //- Adjoint grid displacement solver
+        autoPtr<adjointMeshMovementSolver> adjointMeshMovementSolver_;
+
+        // Fields to accumulated through the adjoint solver
+
+            // Shape optimisation
+
+                //- Multiplier of grad(dx/b)
+                autoPtr<volTensorField> gradDxDbMult_;
+
+                //- Multiplier of div(dx/db)
+                autoPtr<scalarField> divDxDbMult_;
+
+                //- Multiplier of face dx/db
+                //  The term that multiplies the adjoint-related part of the
+                //  sensitivities in the (E)SI approach
+                autoPtr<boundaryVectorField> dxdbMult_;
+
+                //- Multiplier of dSf/db
+                autoPtr<boundaryVectorField> dSfdbMult_;
+
+                //- Multiplier of dnf/db
+                autoPtr<boundaryVectorField> dnfdbMult_;
+
+                //- Multiplier of dCf/db, found in the objective function
+                autoPtr<boundaryVectorField> dxdbDirectMult_;
+
+                //- Multiplier of dx/db computed at points,
+                //- found in the objective function
+                autoPtr<pointBoundaryVectorField> pointDxDbDirectMult_;
+
+                //- Multiplier of dx/db, coming from boundary conditions that
+                //- depend on the geometry, like rotatingWallVelocity
+                autoPtr<boundaryVectorField> bcDxDbMult_;
+
+                //- dx/db multiplier coming from fvOptions
+                autoPtr<vectorField> optionsDxDbMult_;
+
+
+private:
+
+    // Private Member Functions
+
+        //- No copy construct
+        adjointSensitivity(const adjointSensitivity&) = delete;
+
+        //- No copy assignment
+        void operator=(const adjointSensitivity&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("adjointSensitivity");
+
+
+    // Declare run-time constructor selection table
+
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            adjointSensitivity,
+            dictionary,
+            (
+                const fvMesh& mesh,
+                const dictionary& dict,
+                adjointSolver& adjointSolver
+            ),
+            (
+                mesh,
+                dict,
+                adjointSolver
+            )
+        );
+
+
+    // Constructors
+
+        //- Construct from components
+        adjointSensitivity
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            adjointSolver& adjointSolver
+        );
+
+    // Selectors
+
+        //- Return a reference to the selected turbulence model
+        static autoPtr<adjointSensitivity> New
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            adjointSolver& adjointSolver
+        );
+
+
+    //- Destructor
+    virtual ~adjointSensitivity() = default;
+
+
+    // Member Functions
+
+        //- Read dictionary if changed
+        virtual bool readDict(const dictionary& dict);
+
+        //- Const access to adjoint solver
+        inline const adjointSolver& getAdjointSolver() const
+        {
+            return adjointSolver_;
+        }
+
+        //- Non-const access to adjoint solver
+        inline adjointSolver& getAdjointSolver()
+        {
+            return adjointSolver_;
+        }
+
+        //- Should the adjoint eikonal PDE should be solved
+        inline bool includeDistance() const
+        {
+            return includeDistance_;
+        }
+
+        //- Return the adjoint eikonal solver
+        inline autoPtr<adjointEikonalSolver>& getAdjointEikonalSolver()
+        {
+            return eikonalSolver_;
+        }
+
+        //- Return the adjoint eikonal solver
+        inline autoPtr<adjointMeshMovementSolver>&
+        getAdjointMeshMovementSolver()
+        {
+            return adjointMeshMovementSolver_;
+        }
+
+        //- Set suffix
+        inline void setSuffix(const word& suffix)
+        {
+            suffix_ = suffix;
+        }
+
+        //- Get suffix
+        inline const word& getSuffix() const
+        {
+            return suffix_;
+        }
+
+        //- Should the parameterization compute the internalField of dxdb
+        virtual bool computeDxDbInternalField() const;
+
+        //- Accumulate sensitivity integrands
+        //  Corresponds to the flow and adjoint part of the sensitivities
+        virtual void accumulateIntegrand(const scalar dt) = 0;
+
+        //- Assemble sensitivities
+        //  Adds the geometric part of the sensitivities
+        virtual void assembleSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        );
+
+        //- Calculates and returns sensitivity fields.
+        //  Used with optimisation libraries
+        virtual const scalarField& calculateSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        );
+
+        //- Returns the sensitivity fields
+        //  Assumes it has already been updated/computed
+        const scalarField& getSensitivities() const;
+
+        //- Zero sensitivity fields and their constituents
+        virtual void clearSensitivities();
+
+        //- Write sensitivity fields.
+        //  If valid, copies boundaryFields to volFields and writes them.
+        //  Virtual to be reimplemented by control points-based methods
+        //  (Bezier, RBF) which do not need to write fields
+        virtual void write(const word& baseName = word::null);
+
+        // Access functions to multipliers
+
+            // Shape optimisation
+
+                inline const autoPtr<volTensorField>& gradDxDbMult() const;
+                inline autoPtr<volTensorField>& gradDxDbMult();
+                inline const autoPtr<scalarField>& divDxDbMult() const;
+                inline const autoPtr<boundaryVectorField>& dxdbMult() const;
+                inline const autoPtr<boundaryVectorField>& dSfdbMult() const;
+                inline const autoPtr<boundaryVectorField>& dnfdbMult() const;
+                inline const autoPtr<boundaryVectorField>&
+                    dxdbDirectMult() const;
+                inline const autoPtr<pointBoundaryVectorField>&
+                    pointDxDbDirectMult() const;
+                inline const autoPtr<boundaryVectorField>& bcDxDbMult() const;
+                inline const autoPtr<vectorField>& optionsDxDbMult() const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "adjointSensitivityI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivityI.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivityI.H
new file mode 100644
index 0000000000000000000000000000000000000000..a84fa65405ecb7c9b99bee91fa81e6694b4a2c3b
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivityI.H
@@ -0,0 +1,96 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2023 PCOpt/NTUA
+    Copyright (C) 2023 FOSS GP
+-------------------------------------------------------------------------------
+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 <Foam::http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+inline const Foam::autoPtr<Foam::volTensorField>&
+Foam::adjointSensitivity::gradDxDbMult() const
+{
+    return gradDxDbMult_;
+}
+
+
+inline Foam::autoPtr<Foam::volTensorField>&
+Foam::adjointSensitivity::gradDxDbMult()
+{
+    return gradDxDbMult_;
+}
+
+inline const Foam::autoPtr<Foam::scalarField>&
+Foam::adjointSensitivity::divDxDbMult() const
+{
+    return divDxDbMult_;
+}
+
+
+inline const Foam::autoPtr<Foam::boundaryVectorField>&
+Foam::adjointSensitivity::dxdbMult() const
+{
+    return dxdbMult_;
+}
+
+
+inline const Foam::autoPtr<Foam::boundaryVectorField>&
+Foam::adjointSensitivity::dSfdbMult() const
+{
+    return dSfdbMult_;
+}
+
+inline const Foam::autoPtr<Foam::boundaryVectorField>&
+Foam::adjointSensitivity::dnfdbMult() const
+{
+    return dnfdbMult_;
+}
+
+inline const Foam::autoPtr<Foam::boundaryVectorField>&
+Foam::adjointSensitivity::dxdbDirectMult() const
+{
+    return dxdbDirectMult_;
+}
+
+inline const Foam::autoPtr<Foam::pointBoundaryVectorField>&
+Foam::adjointSensitivity::pointDxDbDirectMult() const
+{
+    return pointDxDbDirectMult_;
+}
+
+inline const Foam::autoPtr<Foam::boundaryVectorField>&
+Foam::adjointSensitivity::bcDxDbMult() const
+{
+    return bcDxDbMult_;
+}
+
+inline const Foam::autoPtr<Foam::vectorField>&
+Foam::adjointSensitivity::optionsDxDbMult() const
+{
+    return optionsDxDbMult_;
+}
+
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.C
similarity index 82%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.C
index 2945d88272ba952a8ee51b488c9d615618fc81a4..f616b5aaeb1d27fb74ad7bd42315b4aa03b288bf 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.C
@@ -27,7 +27,8 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "sensitivityMultipleIncompressible.H"
+#include "adjointSensitivity.H"
+#include "sensitivityMultiple.H"
 #include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -35,9 +36,6 @@ License
 namespace Foam
 {
 
-namespace incompressible
-{
-
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 defineTypeNameAndDebug(sensitivityMultiple, 0);
@@ -54,11 +52,11 @@ sensitivityMultiple::sensitivityMultiple
 (
     const fvMesh& mesh,
     const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
+    adjointSolver& adjointSolver
 )
 :
     adjointSensitivity(mesh, dict, adjointSolver),
-    sensTypes_(dict.subDict("sensTypes").toc()),
+    sensTypes_(this->dict().get<wordList>("sensitivityTypes")),
     sens_(sensTypes_.size())
 {
     forAll(sensTypes_, sI)
@@ -69,10 +67,11 @@ sensitivityMultiple::sensitivityMultiple
             adjointSensitivity::New
             (
                 mesh,
-                dict.subDict("sensTypes").subDict(sensTypes_[sI]),
+                this->dict().subDict(sensTypes_[sI]),
                 adjointSolver
             )
         );
+        sens_[sI].setSuffix(sensTypes_[sI]);
     }
 }
 
@@ -81,14 +80,11 @@ sensitivityMultiple::sensitivityMultiple
 
 bool sensitivityMultiple::readDict(const dictionary& dict)
 {
-    if (sensitivity::readDict(dict))
+    if (adjointSensitivity::readDict(dict))
     {
         forAll(sens_, sI)
         {
-            sens_[sI].readDict
-            (
-                dict.subDict("sensTypes").subDict(sensTypes_[sI])
-            );
+            sens_[sI].readDict(dict.subDict(sensTypes_[sI]));
         }
 
         return true;
@@ -107,21 +103,27 @@ void sensitivityMultiple::accumulateIntegrand(const scalar dt)
 }
 
 
-void sensitivityMultiple::assembleSensitivities()
+void sensitivityMultiple::assembleSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
 {
     forAll(sens_, sI)
     {
-        sens_[sI].assembleSensitivities();
+        sens_[sI].assembleSensitivities(designVars);
     }
 }
 
 
-const scalarField& sensitivityMultiple::calculateSensitivities()
+const scalarField& sensitivityMultiple::calculateSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
 {
     forAll(sens_, sI)
     {
         Info<< "Computing sensitivities " << sensTypes_[sI] << endl;
-        derivatives_ = sens_[sI].calculateSensitivities();
+        sens_[sI].calculateSensitivities(designVars);
     }
     write(type());
 
@@ -149,7 +151,6 @@ void sensitivityMultiple::write(const word& baseName)
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.H
similarity index 89%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.H
index 09a14d66edcc5e8b1e1b3868221dc4c996edb83b..c882c7afe76d09840c66c0afdf19e6df3d5a15e8 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/multiple/sensitivityMultiple.H
@@ -26,7 +26,7 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::incompressible::sensitivityMultiple
+    Foam::sensitivityMultiple
 
 Description
     Calculation of adjoint based sensitivities of multiple types
@@ -36,19 +36,16 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef sensitivityMultipleIncompressible_H
-#define sensitivityMultipleIncompressible_H
+#ifndef sensitivityMultiple_H
+#define sensitivityMultiple_H
 
-#include "adjointSensitivityIncompressible.H"
+#include "adjointSensitivity.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
 /*---------------------------------------------------------------------------*\
                      Class sensitivityMultiple Declaration
 \*---------------------------------------------------------------------------*/
@@ -90,7 +87,7 @@ public:
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
+            adjointSolver& adjointSolver
         );
 
 
@@ -107,10 +104,13 @@ public:
        virtual void accumulateIntegrand(const scalar dt);
 
        //- Assemble sensitivities
-       virtual void assembleSensitivities();
+       virtual void assembleSensitivities(autoPtr<designVariables>& designVars);
 
        //- Calculates sensitivities at wall surface points
-       const scalarField& calculateSensitivities();
+       const scalarField& calculateSensitivities
+       (
+           autoPtr<designVariables>& designVars
+       );
 
        //- Zero sensitivity fields and their constituents
        virtual void clearSensitivities();
@@ -122,7 +122,6 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.C
new file mode 100644
index 0000000000000000000000000000000000000000..0e3c15ab24d5380e7554e2e89978ecfd34ab20c0
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.C
@@ -0,0 +1,168 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+    Copyright (C) 2019-2020 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
+    ESITNESS 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 "boundaryFieldsFwd.H"
+#include "sensitivityShapeESI.H"
+#include "adjointSolver.H"
+#include "ShapeSensitivitiesBase.H"
+#include "fvOptions.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+defineTypeNameAndDebug(sensitivityShapeESI, 0);
+addToRunTimeSelectionTable
+(
+    adjointSensitivity, sensitivityShapeESI, dictionary
+);
+
+
+void sensitivityShapeESI::computeDxDbMult()
+{
+    if (eikonalSolver_)
+    {
+        eikonalSolver_->solve();
+    }
+    if (adjointMeshMovementSolver_)
+    {
+        adjointMeshMovementSolver_->solve();
+        boundaryVectorField& meshMovementSens =
+            adjointMeshMovementSolver_->meshMovementSensitivities();
+        PtrList<objective>& functions =
+            adjointSolver_.getObjectiveManager().getObjectiveFunctions();
+        for (const label patchI : geometryVariationIntegrationPatches())
+        {
+            const fvPatch& patch = mesh_.boundary()[patchI];
+            const scalarField& magSf = patch.magSf();
+            const vectorField& Sf = patch.Sf();
+            dxdbMult_()[patchI] = meshMovementSens[patchI]*magSf;
+            for (objective& func : functions)
+            {
+                if (func.hasDivDxDbMult())
+                {
+                    Info<< func.objectiveName() << " " << patch.name() << endl;
+                    dxdbDirectMult_()[patchI] +=
+                        func.weight()
+                       *func.divDxDbMultiplier().boundaryField()[patchI]
+                       *Sf;
+                }
+            }
+        }
+    }
+    for (const label patchI : geometryVariationIntegrationPatches())
+    {
+        const vectorField& Sf = mesh_.boundary()[patchI].Sf();
+        dxdbMult_()[patchI] += Sf & gradDxDbMult_().boundaryField()[patchI];
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+sensitivityShapeESI::sensitivityShapeESI
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    adjointSolver& adjointSolver
+)
+:
+    ShapeSensitivitiesBase(mesh, dict, adjointSolver)
+{
+    dxdbMult_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    // The boundary values of divDxDbMultiplier are stored in dxdbDirectMult
+    // after applying the Gauss divergence theorem.
+    // Allocate dxdbDirectMult if necessary
+    if (hasMultiplier(&objective::hasDivDxDbMult))
+    {
+        dxdbDirectMult_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    }
+    if (dict.getOrDefault<bool>("includeMeshMovement", true))
+    {
+        adjointMeshMovementSolver_.reset
+        (
+            new adjointMeshMovementSolver(mesh, dict, *this)
+        );
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::sensitivityShapeESI::readDict(const dictionary& dict)
+{
+    if (ShapeSensitivitiesBase::readDict(dict))
+    {
+        bool includeMeshMovement =
+            dict.getOrDefault<bool>("includeMeshMovement", true);
+
+        if (includeMeshMovement)
+        {
+            if (adjointMeshMovementSolver_)
+            {
+                adjointMeshMovementSolver_->readDict(dict);
+            }
+            else
+            {
+                adjointMeshMovementSolver_.reset
+                (
+                    new adjointMeshMovementSolver(mesh_, dict, *this)
+                );
+            }
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+
+void sensitivityShapeESI::assembleSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
+{
+    computeDxDbMult();
+    if (designVars)
+    {
+        adjointSensitivity::assembleSensitivities(designVars);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.H
similarity index 59%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.H
index 94d19f2fa4aca4fe984e2b0d46d77b162a74e51c..d3f2ca8cc1714f096bf08bd804624138f3fde170 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -19,29 +19,30 @@ License
 
     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
+    ESITNESS 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/>.
 
-
 Class
-    Foam::optMeshMovementBezier
+    Foam::sensitivityShapeESI
 
 Description
-    Converts NURBS control points update to actual mesh movement
+    Class for computing sensitivity derivatives using the Enhanced Surface
+    Integral (E-SI) formulation, when a parameterization scheme is inluded
+    through the design variables. Sensitivity maps are implemented in class
+    sensitivitySurfacePoints
 
 SourceFiles
-    optMeshMovementBezier.C
+    sensitivityShapeESI.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef optMeshMovementBezier_H
-#define optMeshMovementBezier_H
+#ifndef sensitivityShapeESI_H
+#define sensitivityShapeESI_H
 
-#include "optMeshMovement.H"
-#include "Bezier.H"
+#include "ShapeSensitivitiesBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -49,30 +50,19 @@ namespace Foam
 {
 
 /*---------------------------------------------------------------------------*\
-                    Class optMeshMovementBezier Declaration
+                     Class sensitivityShapeESI Declaration
 \*---------------------------------------------------------------------------*/
 
-class optMeshMovementBezier
+class sensitivityShapeESI
 :
-    public optMeshMovement
+    public ShapeSensitivitiesBase
 {
 protected:
 
-    // Protected data
-
-        //- Parameterization based on NURBS curves
-        Bezier Bezier_;
-
-        //- Boundary movement due to change of NURBS control points
-        pointVectorField dx_;
-
-        //- Cumulative change of control points
-        vectorField cumulativeChange_;
-
-
     // Protected Member Functions
 
-        void computeBoundaryMovement(const scalarField& correction);
+        //- Compute dxdbMult from its various components
+        void computeDxDbMult();
 
 
 private:
@@ -80,43 +70,45 @@ private:
     // Private Member Functions
 
         //- No copy construct
-        optMeshMovementBezier(const optMeshMovementBezier&) = delete;
+        sensitivityShapeESI(const sensitivityShapeESI&) = delete;
 
         //- No copy assignment
-        void operator=(const optMeshMovementBezier&) = delete;
+        void operator=(const sensitivityShapeESI&) = delete;
 
 
 public:
 
     //- Runtime type information
-    TypeName("Bezier");
+    TypeName("shapeESI");
 
 
     // Constructors
 
         //- Construct from components
-        optMeshMovementBezier
+        sensitivityShapeESI
         (
-            fvMesh& mesh,
+            const fvMesh& mesh,
             const dictionary& dict,
-            const labelList& patchIDs
+            adjointSolver& adjointSolver
         );
 
 
     //- Destructor
-    virtual ~optMeshMovementBezier() = default;
+    virtual ~sensitivityShapeESI() = default;
 
 
     // Member Functions
 
-       //- Calculates surface mesh movement
-       void moveMesh();
-
-       //- Compute eta value based on max displacement
-       virtual scalar computeEta(const scalarField& correction);
+        //- Read dict if changed
+        virtual bool readDict(const dictionary& dict);
 
-       //- Return active design variables
-       virtual labelList getActiveDesignVariables() const;
+        //- Assemble sensitivities
+        //  Solve the adjoint eikonal PDE and the adjoint grid displacement PDE,
+        //  if needed, and assemble the sensitivities
+        virtual void assembleSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        );
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.C
new file mode 100644
index 0000000000000000000000000000000000000000..d68f332896c643bd592b7ed3ec2df4e7e236b083
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.C
@@ -0,0 +1,88 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+    Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "adjointSensitivity.H"
+#include "sensitivityShapeFI.H"
+#include "adjointSolver.H"
+#include "fvOptions.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+defineTypeNameAndDebug(sensitivityShapeFI, 0);
+addToRunTimeSelectionTable
+(
+    adjointSensitivity, sensitivityShapeFI, dictionary
+);
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+sensitivityShapeFI::sensitivityShapeFI
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    adjointSolver& adjointSolver
+)
+:
+    ShapeSensitivitiesBase(mesh, dict, adjointSolver)
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool sensitivityShapeFI::computeDxDbInternalField() const
+{
+    return true;
+}
+
+
+void sensitivityShapeFI::assembleSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
+{
+    if (eikonalSolver_)
+    {
+        eikonalSolver_->solve();
+    }
+    adjointSensitivity::assembleSensitivities(designVars);
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.H
similarity index 66%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.H
index eaccf4d71cdde16a11219b601ee9aa5bf0928e48..7edbc1a3b85fe2cbf2f6e506d4610dcda32d091b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -26,73 +26,79 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::optMeshMovementNULL
+    Foam::sensitivityShapeFI
 
 Description
-    A dummy optMeshMovement object
+    Class for computing Field Integral (FI)-based sensitivity derivatives
 
 SourceFiles
-    optMeshMovementNULL.C
+    sensitivityShapeFI.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef optMeshMovementNULL_H
-#define optMeshMovementNULL_H
+#ifndef sensitivityShapeFI_H
+#define sensitivityShapeFI_H
 
-#include "optMeshMovement.H"
+#include "ShapeSensitivitiesBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
+
 /*---------------------------------------------------------------------------*\
-                     Class optMeshMovementNULL Declaration
+                            Class sensitivityShapeFI Declaration
 \*---------------------------------------------------------------------------*/
 
-class optMeshMovementNULL
+class sensitivityShapeFI
 :
-    public optMeshMovement
+    public ShapeSensitivitiesBase
 {
 private:
 
     // Private Member Functions
 
         //- No copy construct
-        optMeshMovementNULL(const optMeshMovementNULL&) = delete;
+        sensitivityShapeFI(const sensitivityShapeFI&) = delete;
 
         //- No copy assignment
-        void operator=(const optMeshMovementNULL&) = delete;
+        void operator=(const sensitivityShapeFI&) = delete;
 
 
 public:
 
     //- Runtime type information
-    TypeName("none");
+    TypeName("shapeFI");
 
 
     // Constructors
 
         //- Construct from components
-        optMeshMovementNULL
+        sensitivityShapeFI
         (
-            fvMesh& mesh,
+            const fvMesh& mesh,
             const dictionary& dict,
-            const labelList& patchIDs
+            adjointSolver& adjointSolver
         );
 
 
     //- Destructor
-    virtual ~optMeshMovementNULL() = default;
+    virtual ~sensitivityShapeFI() = default;
 
 
     // Member Functions
 
-       //- Calculates surface mesh movement
-       void moveMesh();
+        //- Should the parameterization compute the internalField of dxdb
+        virtual bool computeDxDbInternalField() const;
 
-       //- Compute eta value based on max displacement
-       virtual scalar computeEta(const scalarField& correction);
+        //- Assemble sensitivities
+        //  Solve the adjoint eikonal PDE, if needed, and calls the assembles
+        //  the sensitivities
+        virtual void assembleSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        );
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.C
similarity index 53%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.C
index 663db50998daa3e5bbeac91d1872e577541a49a0..ea19da52b741373456d5a2bfe8a75292694f2a3b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.C
@@ -5,9 +5,9 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+    Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,133 +27,145 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "adjointMeshMovementSolverIncompressible.H"
-#include "incompressibleAdjointSolver.H"
-#include "fixedValueFvPatchFields.H"
-#include "subCycleTime.H"
+#include "adjointMeshMovementSolver.H"
+#include "adjointEikonalSolver.H"
+#include "adjointSolver.H"
+#include "fvc.H"
+#include "fvm.H"
+#include "ShapeSensitivitiesBase.H"
+#include "reverseLinear.H"
+#include "volFieldsFwd.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
 defineTypeNameAndDebug(adjointMeshMovementSolver, 0);
 
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+// * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * * //
 
 void adjointMeshMovementSolver::read()
 {
-    nLaplaceIters_ = dict_.getOrDefault<label>("iters", 1000);
-    tolerance_ = dict_.getOrDefault<scalar>("tolerance", 1e-6);
+    iters_ = dict_.getOrDefault<label>("iters", 1000);
+    tolerance_ = dict_.getOrDefault<scalar>("tolerance", 1.e-06);
 }
 
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+void adjointMeshMovementSolver::setSource()
+{
+    volTensorField& gradDxDbMult = adjointSensitivity_.gradDxDbMult()();
+
+    // Add part related to the adjoint eikaonal equation, if necessary
+    const autoPtr<adjointEikonalSolver>& eikonalSolver =
+        adjointSensitivity_.getAdjointEikonalSolver();
+    if (eikonalSolver)
+    {
+        gradDxDbMult += eikonalSolver->getFISensitivityTerm();
+    }
+
+    source_ -=
+        fvc::div
+        (
+            mesh_.Sf()
+          & reverseLinear<tensor>(mesh_).interpolate(gradDxDbMult)
+        );
+
+    // Terms from objectives defined in (part of the) internal field
+    PtrList<objective>& functions =
+        adjointSensitivity_.getAdjointSolver().getObjectiveManager().
+            getObjectiveFunctions();
+    for (objective& func : functions)
+    {
+        if (func.hasDivDxDbMult())
+        {
+            source_ -= func.weight()*fvc::grad(func.divDxDbMultiplier());
+        }
+    }
+
+    // Terms from fvOptions
+    source_.primitiveFieldRef() += adjointSensitivity_.optionsDxDbMult()();
+}
+
+
+// * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * * //
 
 adjointMeshMovementSolver::adjointMeshMovementSolver
 (
     const fvMesh& mesh,
     const dictionary& dict,
-    Foam::incompressible::adjointSensitivity& adjointSensitivity,
-    const labelHashSet& sensitivityPatchIDs,
-    const autoPtr<adjointEikonalSolver>& adjointEikonalSolverPtr
+    ShapeSensitivitiesBase& adjointSensitivity
 )
 :
     mesh_(mesh),
     dict_(dict.subOrEmptyDict("adjointMeshMovementSolver")),
+    meshMovementSensPtr_(createZeroBoundaryPtr<vector>(mesh)),
     adjointSensitivity_(adjointSensitivity),
-    sensitivityPatchIDs_(sensitivityPatchIDs),
-    nLaplaceIters_(-1),
-    tolerance_(-1),
     ma_
     (
-        IOobject
+        variablesSet::autoCreateMeshMovementField
         (
-            word
-            (
-                adjointSensitivity.adjointVars().useSolverNameForFields()
-              ? "ma" + adjointSensitivity.adjointSolver().solverName()
-              : "ma"
-            ),
-            mesh.time().timeName(),
-            mesh,
-            IOobject::READ_IF_PRESENT,
-            IOobject::AUTO_WRITE
-        ),
-        mesh,
-        dimensionedVector(pow3(dimLength/dimTime), Zero),
-        fixedValueFvPatchVectorField::typeName
+            mesh_,
+            adjointSensitivity.getAdjointSolver().useSolverNameForFields()
+          ? ("ma" + adjointSensitivity.getAdjointSolver().solverName())
+          : "ma",
+            adjointSensitivity.getAdjointSolver().maDimensions()
+        )
     ),
     source_
     (
         IOobject
         (
-            "sourceAdjointMeshMovement",
+            "sourceadjointMeshMovement",
             mesh_.time().timeName(),
             mesh_,
             IOobject::NO_READ,
             IOobject::NO_WRITE
         ),
         mesh_,
-        dimensionedVector(dimLength/pow3(dimTime), Zero)
+        dimensionedVector
+        (
+            adjointSensitivity.getAdjointSolver().maDimensions()/sqr(dimLength),
+            Zero
+        )
     ),
-    meshMovementSensPtr_(createZeroBoundaryPtr<vector>(mesh_)),
-    adjointEikonalSolverPtr_(adjointEikonalSolverPtr)
+    iters_(0),
+    tolerance_(Zero)
 {
     read();
 }
 
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
 
-bool adjointMeshMovementSolver::readDict(const dictionary& dict)
+bool adjointMeshMovementSolver::readDict
+(
+    const dictionary& dict
+)
 {
     dict_ = dict.subOrEmptyDict("adjointMeshMovementSolver");
+    read();
 
     return true;
 }
 
 
-void adjointMeshMovementSolver::accumulateIntegrand(const scalar dt)
-{
-    // Accumulate integrand from the current time step
-    source_ += adjointSensitivity_.adjointMeshMovementSource()*dt;
-
-    // Part of the source depending on the adjoint distance can be added only
-    // after solving the adjoint eikonal equation. Added in solve()
-}
-
-
 void adjointMeshMovementSolver::solve()
 {
-    read();
-
-    // Add source from the adjoint eikonal equation
-    if (adjointEikonalSolverPtr_)
-    {
-        source_ -=
-            fvc::div(adjointEikonalSolverPtr_().getFISensitivityTerm()().T());
-    }
+    setSource();
 
     // Iterate the adjoint to the mesh movement equation
-    for (label iter = 0; iter < nLaplaceIters_; iter++)
+    for (label iter = 0; iter < iters_; iter++)
     {
-        Info<< "Adjoint Mesh Movement Iteration: " << iter << endl;
+        Info<< "adjoint Mesh Movement Iteration: " << iter << endl;
 
         fvVectorMatrix maEqn
         (
-            fvm::laplacian(ma_)
-          + source_
+            fvm::laplacian(ma_) + source_
         );
 
         maEqn.boundaryManipulate(ma_.boundaryFieldRef());
 
-        //scalar residual = max(maEqn.solve().initialResidual());
         scalar residual =
             mag(Foam::solve(maEqn, mesh_.solverDict("ma")).initialResidual());
 
@@ -176,19 +188,22 @@ void adjointMeshMovementSolver::solve()
 void adjointMeshMovementSolver::reset()
 {
     source_ == dimensionedVector(source_.dimensions(), Zero);
-    meshMovementSensPtr_() = vector::zero;
+    meshMovementSensPtr_() = Zero;
 }
 
 
 boundaryVectorField& adjointMeshMovementSolver::meshMovementSensitivities()
 {
-    Info<< "Calculating mesh movement sensitivities " << endl;
-
     boundaryVectorField& meshMovementSens = meshMovementSensPtr_();
 
-    for (const label patchi : sensitivityPatchIDs_)
+    for
+    (
+        const label patchi
+      : adjointSensitivity_.geometryVariationIntegrationPatches()
+    )
     {
-        // No surface area included. Will be done by the actual sensitivity tool
+        // No surface area included.
+        // Will be added during the assembly of the sensitivities
         meshMovementSens[patchi] = -ma_.boundaryField()[patchi].snGrad();
     }
 
@@ -196,15 +211,8 @@ boundaryVectorField& adjointMeshMovementSolver::meshMovementSensitivities()
 }
 
 
-const volVectorField& adjointMeshMovementSolver::ma()
-{
-    return ma_;
-}
-
-
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.H
similarity index 58%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.H
index 814777256a6203c7d6b70fd441517536c1803386..1764cae2e1167ae90d8530f34a2450deb7893482 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.H
@@ -25,20 +25,23 @@ License
     You should have received a copy of the GNU General Public License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
-
 Class
-    Foam::incompressible::adjointMeshMovementSolver
+    Foam::adjointMeshMovementSolver
 
 Description
-    Solver of the adjoint to the Laplace grid displacement equation
+    Class solving the adjoint grid dispalcement PDEs.
+    Assumes the primal grid displacement PDE is a Laplace one with uniform
+    diffusivity.
 
     Reference:
     \verbatim
-        Kavvadias, I., Papoutsis-Kiachagias, E., & Giannakoglou, K. (2015).
-        On the proper treatment of grid sensitivities in continuous adjoint
-        methods for shape optimization.
-        Journal of Computational Physics, 301, 1–18.
-        http://doi.org/10.1016/j.jcp.2015.08.012
+        For the derivation of the adjoint grid displacement PDEs, see
+            Kavvadias, I., Papoutsis-Kiachagias, E., & Giannakoglou, K. (2015).
+            On the proper treatment of grid sensitivities in continuous adjoint
+            methods for shape optimization.
+            Journal of Computational Physics, 301, 1–18.
+            http://doi.org/10.1016/j.jcp.2015.08.012
+
     \endverbatim
 
 SourceFiles
@@ -46,48 +49,63 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef adjointMeshMovementSolverIncompressible_H
-#define adjointMeshMovementSolverIncompressible_H
+#ifndef adjointMeshMovementSolver_H
+#define adjointMeshMovementSolver_H
 
-#include "adjointSensitivityIncompressible.H"
-#include "adjointEikonalSolverIncompressible.H"
-#include "createZeroField.H"
 #include "boundaryFieldsFwd.H"
+#include "createZeroField.H"
+#include "variablesSet.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
+// Forward declarations
+class ShapeSensitivitiesBase;
 
 /*---------------------------------------------------------------------------*\
-                  Class adjointMeshMovementSolver Declaration
+                    Class adjointMeshMovementSolver Decleration
 \*---------------------------------------------------------------------------*/
 
 class adjointMeshMovementSolver
 {
 protected:
 
-    // Protected data
+    // Protected Data Members
 
+        //- Reference to mesh
         const fvMesh& mesh_;
+
+        //- Dictionary containing solution controls
         dictionary dict_;
-        Foam::incompressible::adjointSensitivity& adjointSensitivity_;
-        const labelHashSet& sensitivityPatchIDs_;
-        label nLaplaceIters_;
-        scalar tolerance_;
+
+        //- Part of sensitivity derivatives coming from the adjoint grid
+        //- displacement PDE
+        autoPtr<boundaryVectorField> meshMovementSensPtr_;
+
+        // Underlaying adjoint sensitivities
+        ShapeSensitivitiesBase& adjointSensitivity_;
+
+        //- Adjoint grid displacement field
         volVectorField ma_;
+
+        //- Source term of the adjoint grid displacement PDEs
         volVectorField source_;
 
-        //- Wall face sens w.r.t.(x, y.z) //wall face sens w.r.t. (x,y.z)
-        autoPtr<boundaryVectorField> meshMovementSensPtr_;
-        const autoPtr<adjointEikonalSolver>& adjointEikonalSolverPtr_;
+        //- Solution controls
+        label iters_;
+        scalar tolerance_;
+
+
+    // Protected Member Functions
 
         //- Read options each time a new solution is found
         void read();
 
+        //- Set the source term of the PDE
+        void setSource();
+
 
 private:
 
@@ -97,7 +115,7 @@ private:
         adjointMeshMovementSolver(const adjointMeshMovementSolver&) = delete;
 
         //- No copy assignment
-        void operator=(const adjointMeshMovementSolver&) = delete;
+        void operator=( const adjointMeshMovementSolver) = delete;
 
 
 public:
@@ -113,44 +131,44 @@ public:
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            Foam::incompressible::adjointSensitivity& adjointSensitivity,
-            const labelHashSet& sensitivityPatchIDs,
-            const autoPtr<adjointEikonalSolver>& adjointEikonalSolverPtr
+            ShapeSensitivitiesBase& adjointSensitivity
         );
 
-    //- Destructor
+
+    // Destructor
     virtual ~adjointMeshMovementSolver() = default;
 
 
     // Member Functions
 
-       //- Read dict if changed
-       virtual bool readDict(const dictionary& dict);
-
-       //- Accumulate source term
-       void accumulateIntegrand(const scalar dt);
+        //- Read dict if changed
+        virtual bool readDict(const dictionary& dict);
 
-       //- Calculate the adjoint distance field
-       void solve();
+        //- Calculate the adjoint distance field
+        virtual void solve();
 
-       //- Reset source term
-       void reset();
+        //- Reset the source term
+        void reset();
 
-       //- Return the sensitivity term depending on da
-       boundaryVectorField& meshMovementSensitivities();
+        //- Return the sensitivity term depending on ma
+        boundaryVectorField& meshMovementSensitivities();
 
-       //- Return the adjoint distance field
-       const volVectorField& ma();
+        //- Return the adjoint distance field
+        inline const volVectorField& ma() const
+        {
+            return ma_;
+        }
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #endif
 
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.C
similarity index 50%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.C
index 4206d7b787afeb8cf42e96c5310bde445bd27d06..b788697ca1ba3f88db1991c6b643128af2d11f65 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -27,20 +27,58 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "shapeSensitivitiesBase.H"
+#include "HashSet.H"
+#include "ShapeSensitivitiesBase.H"
+#include "adjointSensitivity.H"
+#include "adjointSolver.H"
 #include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
 namespace Foam
 {
-    defineTypeNameAndDebug(shapeSensitivitiesBase, 0);
+    defineTypeNameAndDebug(ShapeSensitivitiesBase, 0);
 }
 
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-void Foam::shapeSensitivitiesBase::writeFaceBasedSens() const
+void Foam::ShapeSensitivitiesBase::allocateEikonalSolver()
+{
+    // Allocate distance solver if needed
+    if (includeDistance_ && !eikonalSolver_)
+    {
+        eikonalSolver_.reset
+        (
+            new adjointEikonalSolver
+            (
+                mesh_,
+                this->dict(),
+                adjointSolver_,
+                geometryVariationIntegrationPatches()
+            )
+        );
+    }
+}
+
+
+bool Foam::ShapeSensitivitiesBase::hasMultiplier
+(
+    bool (objective::*hasFunction)() const
+)
+{
+    bool hasMult(false);
+    const PtrList<objective>& objectives =
+        adjointSolver_.getObjectiveManager().getObjectiveFunctions();
+    for (const objective& func : objectives)
+    {
+        hasMult = hasMult || (func.*hasFunction)();
+    }
+    return hasMult;
+}
+
+
+void Foam::ShapeSensitivitiesBase::writeFaceBasedSens() const
 {
     // Wall face sensitivity projected to normal
     if (wallFaceSensNormalPtr_)
@@ -48,7 +86,7 @@ void Foam::shapeSensitivitiesBase::writeFaceBasedSens() const
         constructAndWriteSensitivityField<scalar>
         (
             wallFaceSensNormalPtr_,
-            "faceSensNormal" + surfaceFieldSuffix_
+            "faceSensNormal" + suffix_
         );
     }
 
@@ -60,7 +98,7 @@ void Foam::shapeSensitivitiesBase::writeFaceBasedSens() const
             constructAndWriteSensitivityField<vector>
             (
                 wallFaceSensVecPtr_,
-                "faceSensVec" + surfaceFieldSuffix_
+                "faceSensVec" + suffix_
             );
         }
 
@@ -70,14 +108,14 @@ void Foam::shapeSensitivitiesBase::writeFaceBasedSens() const
             constructAndWriteSensitivityField<vector>
             (
                 wallFaceSensNormalVecPtr_,
-                "faceSensNormalVec" + surfaceFieldSuffix_
+                "faceSensNormalVec" + suffix_
             );
         }
     }
 }
 
 
-void Foam::shapeSensitivitiesBase::writePointBasedSens() const
+void Foam::ShapeSensitivitiesBase::writePointBasedSens() const
 {
     // Wall point sensitivity projected to normal
     if (wallPointSensNormalPtr_)
@@ -85,7 +123,7 @@ void Foam::shapeSensitivitiesBase::writePointBasedSens() const
         constructAndWriteSensitivtyPointField<scalar>
         (
             wallPointSensNormalPtr_,
-            "pointSensNormal" + surfaceFieldSuffix_
+            "pointSensNormal" + suffix_
         );
     }
 
@@ -100,7 +138,7 @@ void Foam::shapeSensitivitiesBase::writePointBasedSens() const
             constructAndWriteSensitivtyPointField<vector>
             (
                 wallPointSensVecPtr_,
-                "pointSensVec" + surfaceFieldSuffix_
+                "pointSensVec" + suffix_
             );
         }
 
@@ -110,69 +148,14 @@ void Foam::shapeSensitivitiesBase::writePointBasedSens() const
             constructAndWriteSensitivtyPointField<vector>
             (
                 wallPointSensNormalVecPtr_,
-                "pointSensNormalVec" + surfaceFieldSuffix_
+                "pointSensNormalVec" + suffix_
             );
         }
     }
 }
 
 
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::shapeSensitivitiesBase::shapeSensitivitiesBase
-(
-    const fvMesh& mesh,
-    const dictionary& dict
-)
-:
-    meshShape_(mesh),
-    surfaceFieldSuffix_(),
-    writeAllSurfaceFiles_
-    (
-        dict.getOrDefault<bool>
-        (
-            "writeAllSurfaceFiles",
-            false
-        )
-    ),
-    sensitivityPatchIDs_
-    (
-        mesh.boundaryMesh().patchSet
-        (
-            dict.get<wordRes>("patches", keyType::REGEX_RECURSIVE)
-        )
-    ),
-    wallFaceSensVecPtr_(nullptr),
-    wallFaceSensNormalPtr_(nullptr),
-    wallFaceSensNormalVecPtr_(nullptr),
-
-    wallPointSensVecPtr_(nullptr),
-    wallPointSensNormalPtr_(nullptr),
-    wallPointSensNormalVecPtr_(nullptr)
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-const Foam::labelHashSet&
-Foam::shapeSensitivitiesBase::sensitivityPatchIDs() const
-{
-    return sensitivityPatchIDs_;
-}
-
-
-void Foam::shapeSensitivitiesBase::setSensitivityPatchIDs
-(
-    const labelHashSet& sensPatchIDs
-)
-{
-    sensitivityPatchIDs_ = sensPatchIDs;
-}
-
-
-void Foam::shapeSensitivitiesBase::clearSensitivities()
+void Foam::ShapeSensitivitiesBase::clearSurfaceFields()
 {
     // Face-based boundary sens
     if (wallFaceSensVecPtr_)
@@ -213,21 +196,212 @@ void Foam::shapeSensitivitiesBase::clearSensitivities()
 }
 
 
-void Foam::shapeSensitivitiesBase::write()
+void Foam::ShapeSensitivitiesBase::allocateMultipliers()
 {
-    writeFaceBasedSens();
-    writePointBasedSens();
+    gradDxDbMult_.reset
+    (
+        new volTensorField
+        (
+            IOobject
+            (
+                "gradDxDbMult",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh_,
+            dimensionedTensor(sqr(dimLength)/pow3(dimTime), Zero)
+        )
+    );
+    if (hasMultiplier(&objective::hasDivDxDbMult))
+    {
+        divDxDbMult_.reset(new scalarField(mesh_.nCells(), Zero));
+    }
+    if (hasMultiplier(&objective::hasdSdbMult))
+    {
+        dSfdbMult_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    }
+    if (hasMultiplier(&objective::hasdndbMult))
+    {
+        dnfdbMult_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    }
+    if (hasMultiplier(&objective::hasdxdbDirectMult))
+    {
+        dxdbDirectMult_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    }
+    bcDxDbMult_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    optionsDxDbMult_.reset(new vectorField(mesh_.nCells(), Zero));
 }
 
 
-void Foam::shapeSensitivitiesBase::setSuffix(const word& suffix)
+void Foam::ShapeSensitivitiesBase::clearMultipliers()
 {
-    surfaceFieldSuffix_ = suffix;
+    gradDxDbMult_() = dimensionedTensor(gradDxDbMult_().dimensions(), Zero);
+    if (divDxDbMult_)
+    {
+        divDxDbMult_() = Zero;
+    }
+    if (eikonalSolver_)
+    {
+        eikonalSolver_->reset();
+    }
+    if (dxdbMult_)
+    {
+        dxdbMult_() = Zero;
+    }
+    if (dSfdbMult_)
+    {
+        dSfdbMult_() = Zero;
+    }
+    if (dnfdbMult_)
+    {
+        dnfdbMult_() = Zero;
+    }
+    if (dxdbDirectMult_)
+    {
+        dxdbDirectMult_() = Zero;
+    }
+    if (pointDxDbDirectMult_)
+    {
+        for (vectorField& field : pointDxDbDirectMult_())
+        {
+            field = Zero;
+        }
+    }
+    bcDxDbMult_() = Zero;
+    optionsDxDbMult_() = Zero;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ShapeSensitivitiesBase::ShapeSensitivitiesBase
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    class adjointSolver& adjointSolver
+)
+:
+    adjointSensitivity(mesh, dict, adjointSolver),
+    sensitivityPatchIDs_
+    (
+        mesh.boundaryMesh().patchSet
+        (
+            dict.optionalSubDict(mesh.name()).
+                get<wordRes>("patches", keyType::REGEX_RECURSIVE)
+        )
+    ),
+    writeAllSurfaceFiles_
+    (
+        dict.getOrDefault<bool>("writeAllSurfaceFiles", false)
+    ),
+    wallFaceSensVecPtr_(nullptr),
+    wallFaceSensNormalPtr_(nullptr),
+    wallFaceSensNormalVecPtr_(nullptr),
+
+    wallPointSensVecPtr_(nullptr),
+    wallPointSensNormalPtr_(nullptr),
+    wallPointSensNormalVecPtr_(nullptr)
+{
+    allocateEikonalSolver();
+    allocateMultipliers();
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::ShapeSensitivitiesBase::readDict(const dictionary& dict)
+{
+    if (adjointSensitivity::readDict(dict))
+    {
+        sensitivityPatchIDs_ =
+            mesh_.boundaryMesh().patchSet
+            (
+                dict_.optionalSubDict(mesh_.name()).
+                    get<wordRes>("patches", keyType::REGEX_RECURSIVE)
+            );
+        writeAllSurfaceFiles_ =
+            dict_.getOrDefault<bool>("writeAllSurfaceFiles", false);
+
+        if (includeDistance_)
+        {
+            if (eikonalSolver_)
+            {
+                eikonalSolver_().readDict(dict);
+            }
+            else
+            {
+                allocateEikonalSolver();
+            }
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+
+const Foam::labelHashSet&
+Foam::ShapeSensitivitiesBase::geometryVariationIntegrationPatches() const
+{
+    return sensitivityPatchIDs_;
+}
+
+
+void Foam::ShapeSensitivitiesBase::accumulateIntegrand(const scalar dt)
+{
+    // Accumulate multiplier of grad(dxdb)
+    adjointSolver_.accumulateGradDxDbMultiplier(gradDxDbMult_(), dt);
+
+    // Accumulate multiplier of div(dxdb)
+    adjointSolver_.accumulateDivDxDbMultiplier(divDxDbMult_, dt);
+
+    // Terms from fvOptions - missing contributions from turbulence models
+    adjointSolver_.accumulateOptionsDxDbMultiplier(optionsDxDbMult_(), dt);
+
+    // Accumulate source for the adjoint to the eikonal equation
+    if (eikonalSolver_)
+    {
+        eikonalSolver_->accumulateIntegrand(dt);
+    }
+
+    // Accumulate direct sensitivities
+    adjointSolver_.accumulateGeometryVariationsMultipliers
+    (
+        dSfdbMult_,
+        dnfdbMult_,
+        dxdbDirectMult_,
+        pointDxDbDirectMult_,
+        geometryVariationIntegrationPatches(),
+        dt
+    );
+
+    // Accumulate sensitivities due to boundary conditions
+    adjointSolver_.accumulateBCSensitivityIntegrand
+        (bcDxDbMult_, geometryVariationIntegrationPatches(), dt);
+}
+
+
+void Foam::ShapeSensitivitiesBase::clearSensitivities()
+{
+    adjointSensitivity::clearSensitivities();
+    clearSurfaceFields();
+    clearMultipliers();
+}
+
+
+void Foam::ShapeSensitivitiesBase::write(const word& baseName)
+{
+    adjointSensitivity::write(baseName);
+    writeFaceBasedSens();
+    writePointBasedSens();
 }
 
 
 Foam::tmp<Foam::volVectorField>
-Foam::shapeSensitivitiesBase::getWallFaceSensVec()
+Foam::ShapeSensitivitiesBase::getWallFaceSensVec()
 {
     if (wallFaceSensVecPtr_)
     {
@@ -235,7 +409,7 @@ Foam::shapeSensitivitiesBase::getWallFaceSensVec()
             constructVolSensitivtyField<vector>
             (
                 wallFaceSensVecPtr_,
-                "faceSensVec" + surfaceFieldSuffix_
+                "faceSensVec" + suffix_
             );
     }
     else
@@ -248,8 +422,8 @@ Foam::shapeSensitivitiesBase::getWallFaceSensVec()
             (
                 createZeroFieldPtr<vector>
                 (
-                    meshShape_,
-                    "faceSensVec" + surfaceFieldSuffix_,
+                    mesh_,
+                    "faceSensVec" + suffix_,
                     dimless
                 ).ptr()
             );
@@ -258,7 +432,7 @@ Foam::shapeSensitivitiesBase::getWallFaceSensVec()
 
 
 Foam::tmp<Foam::volScalarField>
-Foam::shapeSensitivitiesBase::getWallFaceSensNormal()
+Foam::ShapeSensitivitiesBase::getWallFaceSensNormal()
 {
     if (wallFaceSensNormalPtr_)
     {
@@ -266,7 +440,7 @@ Foam::shapeSensitivitiesBase::getWallFaceSensNormal()
             constructVolSensitivtyField<scalar>
             (
                 wallFaceSensNormalPtr_,
-                "faceSensNormal" + surfaceFieldSuffix_
+                "faceSensNormal" + suffix_
             );
     }
     else
@@ -279,8 +453,8 @@ Foam::shapeSensitivitiesBase::getWallFaceSensNormal()
             (
                 createZeroFieldPtr<scalar>
                 (
-                    meshShape_,
-                    "faceSensNormal" + surfaceFieldSuffix_, dimless
+                    mesh_,
+                    "faceSensNormal" + suffix_, dimless
                 ).ptr()
             );
     }
@@ -288,7 +462,7 @@ Foam::shapeSensitivitiesBase::getWallFaceSensNormal()
 
 
 Foam::tmp<Foam::volVectorField>
-Foam::shapeSensitivitiesBase::getWallFaceSensNormalVec()
+Foam::ShapeSensitivitiesBase::getWallFaceSensNormalVec()
 {
     if (wallFaceSensNormalVecPtr_)
     {
@@ -296,7 +470,7 @@ Foam::shapeSensitivitiesBase::getWallFaceSensNormalVec()
             constructVolSensitivtyField<vector>
             (
                 wallFaceSensNormalVecPtr_,
-                "faceSensNormalVec" + surfaceFieldSuffix_
+                "faceSensNormalVec" + suffix_
             );
     }
     else
@@ -310,8 +484,8 @@ Foam::shapeSensitivitiesBase::getWallFaceSensNormalVec()
             (
                 createZeroFieldPtr<vector>
                 (
-                    meshShape_,
-                    "faceSensNormalVec" + surfaceFieldSuffix_,
+                    mesh_,
+                    "faceSensNormalVec" + suffix_,
                     dimless
                 ).ptr()
             );
@@ -320,51 +494,51 @@ Foam::shapeSensitivitiesBase::getWallFaceSensNormalVec()
 
 
 Foam::tmp<Foam::pointVectorField>
-Foam::shapeSensitivitiesBase::getWallPointSensVec()
+Foam::ShapeSensitivitiesBase::getWallPointSensVec()
 {
     tmp<volVectorField> tWallFaceSensVec = getWallFaceSensVec();
-    volPointInterpolation volPointInter(meshShape_);
+    volPointInterpolation volPointInter(mesh_);
 
     return (volPointInter.interpolate(tWallFaceSensVec));
 }
 
 
 Foam::tmp<Foam::pointScalarField>
-Foam::shapeSensitivitiesBase::getWallPointSensNormal()
+Foam::ShapeSensitivitiesBase::getWallPointSensNormal()
 {
     tmp<volScalarField> tWallFaceSensNormal = getWallFaceSensNormal();
-    volPointInterpolation volPointInter(meshShape_);
+    volPointInterpolation volPointInter(mesh_);
 
     return (volPointInter.interpolate(tWallFaceSensNormal));
 }
 
 
 Foam::tmp<Foam::pointVectorField>
-Foam::shapeSensitivitiesBase::getWallPointSensNormalVec()
+Foam::ShapeSensitivitiesBase::getWallPointSensNormalVec()
 {
     tmp<volVectorField> tWallFaceSensNormalVec = getWallFaceSensNormalVec();
-    volPointInterpolation volPointInter(meshShape_);
+    volPointInterpolation volPointInter(mesh_);
 
     return (volPointInter.interpolate(tWallFaceSensNormalVec));
 }
 
 
 const Foam::boundaryVectorField&
-Foam::shapeSensitivitiesBase::getWallFaceSensVecBoundary() const
+Foam::ShapeSensitivitiesBase::getWallFaceSensVecBoundary() const
 {
     return wallFaceSensVecPtr_();
 }
 
 
 const Foam::boundaryScalarField&
-Foam::shapeSensitivitiesBase::getWallFaceSensNormalBoundary() const
+Foam::ShapeSensitivitiesBase::getWallFaceSensNormalBoundary() const
 {
     return wallFaceSensNormalPtr_();
 }
 
 
 const Foam::boundaryVectorField&
-Foam::shapeSensitivitiesBase::getWallFaceSensNormalVecBoundary() const
+Foam::ShapeSensitivitiesBase::getWallFaceSensNormalVecBoundary() const
 {
     return wallFaceSensNormalVecPtr_();
 }
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.H
similarity index 67%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.H
index 5389e6de82a74f9ac279dcd8d2cb5590400a1b60..e923ed5f4fcbc701551563088afd9ca245655121 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBase.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -26,24 +26,40 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::incompressible::shapeSensitivitiesBase
+    Foam::ShapeSensitivitiesBase
 
 Description
-    Base class supporting shape sensitivity derivatives
+    Base class supporting Shape sensitivity derivatives.
+
+    Reference:
+    \verbatim
+        For the FI formulation see
+            Kavvadias, I., Papoutsis-Kiachagias, E., & Giannakoglou, K. (2015).
+            On the proper treatment of grid sensitivities in continuous adjoint
+            methods for shape optimization.
+            Journal of Computational Physics, 301, 1–18.
+            http://doi.org/10.1016/j.jcp.2015.08.012
+
+        The ESI formulation is derived in a slightly different way than the
+        one described in this paper, to provide a common mathematical
+        formulation for both low- and high-Re meshes and to produce numerically
+        identical results as the FI formulation. In brief, the boundary-bound
+        part of the sensitivities is the patchInternalField of the tensor
+        multiplying grad(dxdb) in the FI formulation.
+
+    \endverbatim
 
 SourceFiles
-    shapeSensitivitiesBase.C
+    ShapeSensitivitiesBase.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef shapeSensitivitiesBase_H
-#define shapeSensitivitiesBase_H
+#ifndef ShapeSensitivitiesBase_H
+#define ShapeSensitivitiesBase_H
 
-#include "volFields.H"
-#include "surfaceFields.H"
-#include "dictionary.H"
+#include "adjointSensitivity.H"
+#include "objective.H"
 #include "volPointInterpolation.H"
-
 #include "pointMesh.H"
 #include "pointPatchField.H"
 #include "pointPatchFieldsFwd.H"
@@ -58,27 +74,29 @@ namespace Foam
 {
 
 /*---------------------------------------------------------------------------*\
-                    Class shapeSensitivitiesBase Declaration
+                    Class ShapeSensitivitiesBase Declaration
 \*---------------------------------------------------------------------------*/
 
-class shapeSensitivitiesBase
+class ShapeSensitivitiesBase
+    :
+    public adjointSensitivity
 {
+
 protected:
 
     // Protected data
 
-        const fvMesh& meshShape_;
-        word surfaceFieldSuffix_;
-        bool writeAllSurfaceFiles_;
-
-        // Patches on which to compute shape sensitivities
+        //- Patches on which to compute shape sensitivities
         labelHashSet sensitivityPatchIDs_;
 
+        //- Whether to write all surface sensitivity fields
+        bool writeAllSurfaceFiles_;
+
         // autoPtrs for fields holding sensitivities.
         // Not all of them are required for each case
 
         // Boundary sensitivities at faces. Shape opt & flow control
-        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
         //- Wall face sens w.r.t. (x,y.z)
         autoPtr<boundaryVectorField> wallFaceSensVecPtr_;
@@ -101,6 +119,16 @@ protected:
         //- Normal sens as vectors
         autoPtr<pointBoundaryVectorField> wallPointSensNormalVecPtr_;
 
+
+    // Protected Member Functions
+
+        //- Allocate the adjoint eikonal solver
+        void allocateEikonalSolver();
+
+        //- Check if any of the available objective has a certain multiplier,
+        //- provided through a function object
+        bool hasMultiplier(bool (objective::*hasFunction)() const);
+
         //- Constructs volField based on boundaryField and writes it
         template<class Type>
         void constructAndWriteSensitivityField
@@ -138,55 +166,78 @@ protected:
         //- Write point-based sensitivities, if present
         void writePointBasedSens() const;
 
+        //- Clear surface/point fields
+        void clearSurfaceFields();
+
+        //- Allocate multiplier fields
+        void allocateMultipliers();
+
+        //- Clear multipliers
+        void clearMultipliers();
+
 
 private:
 
     // Private Member Functions
 
         //- No copy construct
-        shapeSensitivitiesBase(const shapeSensitivitiesBase&) = delete;
+        ShapeSensitivitiesBase(const ShapeSensitivitiesBase&) = delete;
 
         //- No copy assignment
-        void operator=(const shapeSensitivitiesBase&) = delete;
+        void operator=(const ShapeSensitivitiesBase&) = delete;
 
 
 public:
 
     //- Runtime type information
-    TypeName("shapeSensitivitiesBase");
+    TypeName("ShapeSensitivitiesBase");
 
 
     // Constructors
 
         //- Construct from components
-        shapeSensitivitiesBase
+        ShapeSensitivitiesBase
         (
             const fvMesh& mesh,
-            const dictionary& dict
+            const dictionary& dict,
+            adjointSolver& adjointSolver
         );
 
 
     //- Destructor
-    virtual ~shapeSensitivitiesBase() = default;
+    virtual ~ShapeSensitivitiesBase() = default;
 
 
     // Member Functions
 
+        //- Read dict if changed
+        virtual bool readDict(const dictionary& dict);
+
         //- Get patch IDs on which sensitivities are computed
-        const labelHashSet& sensitivityPatchIDs() const;
+        inline const labelHashSet& sensitivityPatchIDs() const
+        {
+            return sensitivityPatchIDs_;
+        }
 
         //- Overwrite sensitivityPatchIDs
-        void setSensitivityPatchIDs(const labelHashSet& sensPatchIDs);
+        inline void setSensitivityPatchIDs(const labelHashSet& sensPatchIDs)
+        {
+            sensitivityPatchIDs_ = sensPatchIDs;
+        }
+
+        //- Return set of patches on which to compute direct sensitivities
+        virtual const labelHashSet& geometryVariationIntegrationPatches() const;
+
+        //- Accumulate sensitivity integrands
+        //  Common function for the FI and E-SI approaches
+        virtual void accumulateIntegrand(const scalar dt);
 
         //- Zero sensitivity fields and their constituents
         void clearSensitivities();
 
         //- Write sensitivity fields.
         //  If valid, copies boundaryFields to volFields and writes them.
-        void write();
-
-        //- Set suffix
-        void setSuffix(const word& suffix);
+        virtual void write(const word& baseName = word::null);
 
         //- Get wall face sensitivity vectors field
         tmp<volVectorField> getWallFaceSensVec();
@@ -229,7 +280,7 @@ public:
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #ifdef NoRepository
-    #include "shapeSensitivitiesBaseTemplates.C"
+    #include "ShapeSensitivitiesBaseTemplates.C"
 #endif
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBaseTemplates.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBaseTemplates.C
similarity index 89%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBaseTemplates.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBaseTemplates.C
index ef77ae31432822b9a00a53778d8d03fbdba7cfdd..fe12e31d730bad35b1f92d990ace39f7157baf6a 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/shapeSensitivitiesBase/shapeSensitivitiesBaseTemplates.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBaseTemplates.C
@@ -27,7 +27,7 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "shapeSensitivitiesBase.H"
+#include "ShapeSensitivitiesBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -37,7 +37,7 @@ namespace Foam
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
 template<class Type>
-void shapeSensitivitiesBase::constructAndWriteSensitivityField
+void ShapeSensitivitiesBase::constructAndWriteSensitivityField
 (
     const autoPtr
     <
@@ -51,12 +51,12 @@ void shapeSensitivitiesBase::constructAndWriteSensitivityField
         IOobject
         (
             name,
-            meshShape_.time().timeName(),
-            meshShape_,
+            mesh_.time().timeName(),
+            mesh_,
             IOobject::NO_READ,
             IOobject::NO_WRITE
         ),
-        meshShape_,
+        mesh_,
         dimensioned<Type>(dimless, Zero)
     );
 
@@ -70,7 +70,7 @@ void shapeSensitivitiesBase::constructAndWriteSensitivityField
 
 
 template<class Type>
-void shapeSensitivitiesBase::constructAndWriteSensitivtyPointField
+void ShapeSensitivitiesBase::constructAndWriteSensitivtyPointField
 (
     const autoPtr<List<Field<Type>>>& sensFieldPtr,
     const word& name
@@ -81,12 +81,12 @@ void shapeSensitivitiesBase::constructAndWriteSensitivtyPointField
         IOobject
         (
             name,
-            meshShape_.time().timeName(),
-            meshShape_,
+            mesh_.time().timeName(),
+            mesh_,
             IOobject::NO_READ,
             IOobject::NO_WRITE
         ),
-        pointMesh::New(meshShape_),
+        pointMesh::New(mesh_),
         dimensioned<Type>(dimless, Zero)
         //fixedValuePointPatchField<Type>::typeName
     );
@@ -109,7 +109,7 @@ void shapeSensitivitiesBase::constructAndWriteSensitivtyPointField
 
 template<class Type>
 tmp<GeometricField<Type, fvPatchField, volMesh>>
-shapeSensitivitiesBase::constructVolSensitivtyField
+ShapeSensitivitiesBase::constructVolSensitivtyField
 (
     const autoPtr
     <
@@ -125,12 +125,12 @@ shapeSensitivitiesBase::constructVolSensitivtyField
                 IOobject
                 (
                     name,
-                    meshShape_.time().timeName(),
-                    meshShape_,
+                    mesh_.time().timeName(),
+                    mesh_,
                     IOobject::NO_READ,
                     IOobject::NO_WRITE
                 ),
-                meshShape_,
+                mesh_,
                 pTraits<Type>::zero
             )
         );
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.C
new file mode 100644
index 0000000000000000000000000000000000000000..3d1b1dfd898fe2c79c0d970e7469660330d96c07
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.C
@@ -0,0 +1,384 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2020, 2022 PCOpt/NTUA
+    Copyright (C) 2013-2020, 2022 FOSS GP
+    Copyright (C) 2019-2022 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 <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "sensitivitySurface.H"
+#include "volPointInterpolationAdjoint.H"
+#include "faMatrices.H"
+#include "famSup.H"
+#include "famLaplacian.H"
+#include "volSurfaceMapping.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+defineTypeNameAndDebug(sensitivitySurface, 0);
+addToRunTimeSelectionTable
+(
+    adjointSensitivity,
+    sensitivitySurface,
+    dictionary
+);
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+label sensitivitySurface::computeFaceDerivativesSize
+(
+    const bool computeVectorFieldSize
+)
+{
+    label size(0);
+    for (const label patchI : sensitivityPatchIDs_)
+    {
+        const fvPatch& patch = mesh_.boundary()[patchI];
+        const label patchSize = patch.size();
+        size += label(computeVectorFieldSize ? 3*patchSize : patchSize);
+    }
+    return size;
+}
+
+
+void sensitivitySurface::smoothSensitivities()
+{
+    // Read in parameters
+    const label iters(dict().getOrDefault<label>("iters", 500));
+    const scalar tolerance(dict().getOrDefault<scalar>("tolerance", 1.e-06));
+    autoPtr<faMesh> aMeshPtr(nullptr);
+
+    IOobject faceLabels
+    (
+        "faceLabels",
+        mesh_.time().findInstance
+        (
+            mesh_.dbDir()/faMesh::meshSubDir,
+            "faceLabels",
+            IOobject::READ_IF_PRESENT
+        ),
+        faMesh::meshSubDir,
+        mesh_,
+        IOobject::READ_IF_PRESENT,
+        IOobject::NO_WRITE
+    );
+
+    // If the faMesh already exists, read it
+    if (faceLabels.typeHeaderOk<labelIOList>(false))
+    {
+        Info<< "Reading the already constructed faMesh" << endl;
+        aMeshPtr.reset(new faMesh(mesh_));
+    }
+    else
+    {
+        // Dictionary used to construct the faMesh
+        dictionary faMeshDefinition;
+
+        IOobject faMeshDefinitionDict
+        (
+            "faMeshDefinition",
+            mesh_.time().caseSystem(),
+            mesh_,
+            IOobject::MUST_READ,
+            IOobject::NO_WRITE
+        );
+
+        // If the faMeshDefinitionDict exists, use it to construct the mesh
+        if (faMeshDefinitionDict.typeHeaderOk<IOdictionary>(false))
+        {
+            Info<< "Reading faMeshDefinition from system " << endl;
+            faMeshDefinition = IOdictionary(faMeshDefinitionDict);
+        }
+        // Otherwise, faMesh is generated from all patches on which we compute
+        // sensitivities
+        else
+        {
+            Info<< "Constructing faMeshDefinition from sensitivity patches"
+                << endl;
+            wordList polyMeshPatches(sensitivityPatchIDs_.size());
+            label i(0);
+            for (const label patchID : sensitivityPatchIDs_)
+            {
+                polyMeshPatches[i++] = mesh_.boundary()[patchID].name();
+            }
+            faMeshDefinition.add<wordList>("polyMeshPatches", polyMeshPatches);
+            (void)faMeshDefinition.subDictOrAdd("boundary");
+            Info<< faMeshDefinition << endl;
+        }
+
+        // Construct faMesh
+        aMeshPtr.reset(new faMesh(mesh_, faMeshDefinition));
+    }
+    faMesh& aMesh = aMeshPtr.ref();
+
+    // Physical radius of the smoothing, provided either directly or computed
+    // based on the average 'length' of boundary faces
+    const scalar Rphysical
+        (dict().getOrDefault<scalar>("radius", computeRadius(aMesh)));
+    DebugInfo
+        << "Physical radius of the sensitivity smoothing "
+        << Rphysical << nl << endl;
+
+    // Radius used as the diffusivity in the Helmholtz filter, computed as a
+    // function of the physical radius
+    const dimensionedScalar RpdeSqr
+    (
+        "RpdeSqr", dimArea, sqr(Rphysical/(2.*::sqrt(3.)))
+    );
+
+    dimensionedScalar one("1", dimless, 1.);
+
+    // Mapping engine
+    volSurfaceMapping vsm(aMesh);
+
+    // Source term in faMatrix needs to be an areaField
+    areaVectorField sens
+    (
+        IOobject
+        (
+            "sens",
+            mesh_.time().timeName(),
+            mesh_,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        aMesh,
+        dimensionedVector(dimless, Zero),
+        faPatchFieldBase::zeroGradientType()
+    );
+
+    // Copy sensitivities to area field
+    sens.primitiveFieldRef() =
+        vsm.mapToSurface<vector>(wallFaceSensVecPtr_());
+
+    // Initialisation of the smoothed sensitivities field based on the original
+    // sensitivities
+    areaVectorField smoothedSens("smoothedSens", sens);
+    for (label iter = 0; iter < iters; ++iter)
+    {
+        Info<< "Sensitivity smoothing iteration " << iter << endl;
+
+        faVectorMatrix smoothEqn
+        (
+            fam::Sp(one, smoothedSens)
+          - fam::laplacian(RpdeSqr, smoothedSens)
+         ==
+            sens
+        );
+
+        smoothEqn.relax();
+
+        const scalar residual(mag(smoothEqn.solve().initialResidual()));
+
+        DebugInfo
+            << "Max smoothSens " << gMax(mag(smoothedSens)()) << endl;
+
+        // Print execution time
+        mesh_.time().printExecutionTime(Info);
+
+        // Check convergence
+        if (residual < tolerance)
+        {
+            Info<< "\n***Reached smoothing equation convergence limit, "
+                   "iteration " << iter << "***\n\n";
+            break;
+        }
+    }
+
+    // Transfer smooth sensitivity field to wallFaceSensVecPtr_ for defining
+    // derivatives_
+    vsm.mapToVolume(smoothedSens, wallFaceSensVecPtr_());
+
+    // Write normal, regularised sensitivities to file
+    volScalarField volSmoothedSens
+    (
+        IOobject
+        (
+            "smoothedSurfaceSens" + suffix_,
+            mesh_.time().timeName(),
+            mesh_,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        mesh_,
+        dimensionedScalar(dimless, Zero)
+    );
+    areaVectorField nf(aMesh.faceAreaNormals());
+    nf.normalise();
+    areaScalarField smoothedSensNormal(smoothedSens & nf);
+    vsm.mapToVolume(smoothedSensNormal, volSmoothedSens.boundaryFieldRef());
+    volSmoothedSens.write();
+}
+
+
+scalar sensitivitySurface::computeRadius(const faMesh& aMesh)
+{
+    scalar averageArea(gAverage(aMesh.S().field()));
+    const Vector<label>& geometricD = mesh_.geometricD();
+    const boundBox& bounds = mesh_.bounds();
+    forAll(geometricD, iDir)
+    {
+        if (geometricD[iDir] == -1)
+        {
+            averageArea /= bounds.span()[iDir];
+        }
+    }
+    scalar mult = dict().getOrDefault<scalar>("meanRadiusMultiplier", 10);
+
+    return mult*pow(averageArea, scalar(1)/scalar(mesh_.nGeometricD() - 1));
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+sensitivitySurface::sensitivitySurface
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    adjointSolver& adjointSolver
+)
+:
+    sensitivitySurfacePoints(mesh, dict, adjointSolver),
+    smoothSensitivities_(dict.getOrDefault("smoothSensitivities", false)),
+    returnVectorField_
+       (dict.getOrDefault<bool>("returnVectorField", true))
+    //finalResultIncludesArea_
+    //   (dict.getOrDefault<bool>("finalResultIncludesArea", false))
+{
+    // Allocate boundary field pointers
+    wallFaceSensVecPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
+    wallFaceSensNormalPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
+    wallFaceSensNormalVecPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
+
+    derivatives_.setSize(computeFaceDerivativesSize(returnVectorField_), Zero);
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void sensitivitySurface::read()
+{
+    sensitivitySurfacePoints::read();
+    smoothSensitivities_ = dict().getOrDefault("smoothSensitivities", false);
+    returnVectorField_ =
+        dict().getOrDefault<bool>("returnVectorField", true);
+    //finalResultIncludesArea_ =
+    //   dict().getOrDefault<bool>("finalResultIncludesArea", false);
+}
+
+
+void sensitivitySurface::assembleSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
+{
+    // Compute point-based sensitivities
+    sensitivitySurfacePoints::assembleSensitivities(designVars);
+
+    // Transfer point sensitivities to point field
+    vectorField pointSens(mesh_.nPoints(), Zero);
+    for (const label patchI : sensitivityPatchIDs_)
+    {
+        const polyPatch& pp = mesh_.boundaryMesh()[patchI];
+        const labelList& meshPoints = pp.meshPoints();
+        forAll(meshPoints, ppi)
+        {
+            pointSens[meshPoints[ppi]] = wallPointSensVecPtr_()[patchI][ppi];
+        }
+    }
+
+    // vectorField face-sensitivities
+    vectorField faceVecSens(computeFaceDerivativesSize(false), Zero);
+
+    // Map sensitivities from points to faces
+    volPointInterpolationAdjoint interpolation(mesh_);
+    interpolation.interpolateSensitivitiesField
+        (pointSens, faceVecSens, sensitivityPatchIDs_);
+
+    // Transfer non-regularised sensitivities to wallFaceSens* fields and write
+    label nPassedFaces(0);
+    for (const label patchI : sensitivityPatchIDs_)
+    {
+        const fvPatch& patch = mesh_.boundary()[patchI];
+        tmp<vectorField> nf = patch.nf();
+        wallFaceSensVecPtr_()[patchI] =
+            SubField<vector>(faceVecSens, patch.size(), nPassedFaces)
+           /patch.magSf();
+        wallFaceSensNormalPtr_()[patchI] = wallFaceSensVecPtr_()[patchI] & nf();
+        wallFaceSensNormalVecPtr_()[patchI] =
+            wallFaceSensNormalPtr_()[patchI]*nf;
+        nPassedFaces += patch.size();
+    }
+    write();
+
+    // Regularise sensitivities if necessary
+    if (smoothSensitivities_)
+    {
+        smoothSensitivities();
+    }
+
+    // Make sure we have the correct sensitivities size
+    derivatives_.setSize(computeFaceDerivativesSize(returnVectorField_), Zero);
+    nPassedFaces = 0;
+    for (const label patchI : sensitivityPatchIDs_)
+    {
+        const fvPatch& patch = mesh_.boundary()[patchI];
+        const vectorField nf(patch.nf());
+        if (returnVectorField_)
+        {
+            const Vector<label>& sd = mesh_.solutionD();
+            forAll(patch, fI)
+            {
+                const label gfI = nPassedFaces + fI;
+                const vector& fSens = wallFaceSensVecPtr_()[patchI][fI];
+                derivatives_[3*gfI    ] = scalar(sd[0] == -1 ? 0 : fSens.x());
+                derivatives_[3*gfI + 1] = scalar(sd[1] == -1 ? 0 : fSens.y());
+                derivatives_[3*gfI + 2] = scalar(sd[2] == -1 ? 0 : fSens.z());
+            }
+        }
+        else
+        {
+            forAll(patch, fI)
+            {
+                derivatives_[nPassedFaces + fI]
+                    = wallFaceSensVecPtr_()[patchI][fI] & nf[fI];
+            }
+        }
+        nPassedFaces += patch.size();
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.H
similarity index 52%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.H
index a1939835dee0dc040b10595ef1ffd459eb5afbc9..1b1571837ac0e77a7115467a472f1aa35039eb3d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -26,7 +26,7 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::incompressible::sensitivitySurface
+    Foam::sensitivitySurface
 
 Description
     Calculation of adjoint based sensitivities at wall faces
@@ -60,11 +60,7 @@ SourceFiles
 #ifndef sensitivitySurfaceIncompressible_H
 #define sensitivitySurfaceIncompressible_H
 
-#include "adjointSensitivityIncompressible.H"
-#include "shapeSensitivitiesBase.H"
-#include "adjointEikonalSolverIncompressible.H"
-#include "adjointMeshMovementSolverIncompressible.H"
-#include "deltaBoundary.H"
+#include "sensitivitySurfacePoints.H"
 #include "faMesh.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -72,75 +68,31 @@ SourceFiles
 namespace Foam
 {
 
-namespace incompressible
-{
-
 /*---------------------------------------------------------------------------*\
-                      Class sensitivitySurface Declaration
+                  Class sensitivitySurface Declaration
 \*---------------------------------------------------------------------------*/
 
 class sensitivitySurface
 :
-    public adjointSensitivity,
-    public shapeSensitivitiesBase
+    public sensitivitySurfacePoints
 {
 protected:
 
     // Protected data
 
-
-        //- Include surface area in sens computation
-        bool includeSurfaceArea_;
-
-        //- Include the adjoint pressure term in sens computation
-        bool includePressureTerm_;
-
-        //- Include the term containing the grad of the stress at the boundary
-        bool includeGradStressTerm_;
-
-        //- Include the transpose part of the adjoint stresses
-        bool includeTransposeStresses_;
-
-        //- Use snGrad in the transpose part of the adjoint stresses
-        bool useSnGradInTranposeStresses_;
-
-        //- Include the term from the deviatoric part of the stresses
-        bool includeDivTerm_;
-
-        //- Include distance variation in sens computation
-        bool includeDistance_;
-
-        //- Include mesh movement variation in sens computation
-        bool includeMeshMovement_;
-
-        //- Include terms directly emerging from the objective function
-        bool includeObjective_;
-
-        //- Write geometric info for use by external programs
-        bool writeGeometricInfo_;
-
-        //- Smooth sensitivity derivatives based on the computation of the
-        //- 'Sobolev gradient'
+        //- Smooth sensitivity derivatives based on a surface Laplace solver
         bool smoothSensitivities_;
 
-        autoPtr<adjointEikonalSolver> eikonalSolver_;
+        //- Return the complete vector of sensitivities
+        bool returnVectorField_;
 
-        autoPtr<adjointMeshMovementSolver> meshMovementSolver_;
-
-        // Export face normal and face centre for use by external users
-        autoPtr<volVectorField> nfOnPatchPtr_;
-        autoPtr<volVectorField> SfOnPatchPtr_;
-        autoPtr<volVectorField> CfOnPatchPtr_;
+        //bool finalResultIncludesArea_;
 
 
     // Protected Member Functions
 
-        //- Add sensitivities from dSd/db and dnf/db computed at points and
-        //- mapped to faces
-        void addGeometricSens();
-
-        //- Set suffix name for sensitivity fields
-        void setSuffixName();
+        //- Compute the size of the return field
+        label computeFaceDerivativesSize(const bool computeVectorFieldSize);
 
         //- Smooth sensitivity derivatives based on the computation of the
         //- 'Sobolev gradient'
@@ -175,7 +127,7 @@ public:
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
+            adjointSolver& adjointSolver
         );
 
 
@@ -188,55 +140,20 @@ public:
         //- Read controls and update solver pointers if necessary
         void read();
 
-       //- Read dict if changed
-       virtual bool readDict(const dictionary& dict);
-
-       //- Compute the number of faces on sensitivityPatchIDs_
-       void computeDerivativesSize();
-
-       //- Accumulate sensitivity integrands
-       virtual void accumulateIntegrand(const scalar dt);
-
-       //- Assemble sensitivities
-       virtual void assembleSensitivities();
-
-       //- Zero sensitivity fields and their constituents
-       virtual void clearSensitivities();
-
-       //- Get adjoint eikonal solver
-       autoPtr<adjointEikonalSolver>& getAdjointEikonalSolver();
-
-       //- Write sensitivity maps
-       virtual void write(const word& baseName = word::null);
-
-       // Inline getters and setters
-
-           //- Get access to the includeObjective bool
-           inline bool getIncludeObjective() const;
-
-           //- Get access to the includeSurfaceArea bool
-           inline bool getIncludeSurfaceArea() const;
-
-           //- Set includeObjective bool
-           inline void setIncludeObjective(const bool includeObjective);
-
-           //- Set includeSurfaceArea bool
-           inline void setIncludeSurfaceArea(const bool includeSurfaceArea);
-
+        //- Assemble sensitivities
+        virtual void assembleSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        );
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-#include "sensitivitySurfaceIncompressibleI.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
 #endif
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.C
new file mode 100644
index 0000000000000000000000000000000000000000..15cc2fc5c61125040fd9baa801ef6adebb976010
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.C
@@ -0,0 +1,560 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2020, 2022 PCOpt/NTUA
+    Copyright (C) 2013-2020, 2022 FOSS GP
+    Copyright (C) 2019-2022 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 <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "sensitivitySurfacePoints.H"
+#include "deltaBoundary.H"
+#include "designVariables.H"
+#include "syncTools.H"
+#include "symmetryFvPatch.H"
+#include "symmetryPlaneFvPatch.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+defineTypeNameAndDebug(sensitivitySurfacePoints, 1);
+addToRunTimeSelectionTable
+(
+    adjointSensitivity,
+    sensitivitySurfacePoints,
+    dictionary
+);
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+labelHashSet sensitivitySurfacePoints::populateExtendedIDs() const
+{
+    // Populate extendedPatchIDs
+    label pI(0);
+    labelList extendedPatchIDs(mesh_.boundary().size(), -1);
+    forAll(mesh_.boundary(), patchI)
+    {
+        const fvPatch& pp = mesh_.boundary()[patchI];
+        bool isSymmetry
+            (isA<symmetryFvPatch>(pp) || isA<symmetryPlaneFvPatch>(pp));
+        if (!isA<coupledFvPatch>(pp) && !isA<emptyFvPatch>(pp) && !isSymmetry)
+        {
+            extendedPatchIDs[pI++] = patchI;
+        }
+    }
+    extendedPatchIDs.setSize(pI);
+    return labelHashSet(extendedPatchIDs);
+}
+
+
+void sensitivitySurfacePoints::setSuffixName()
+{
+    word suffix(adjointMeshMovementSolver_ ? "ESI" : "SI");
+    suffix = suffix + word(dict().getOrDefault<word>("suffix", word::null));
+    setSuffix(adjointSolver_.solverName() + suffix);
+}
+
+
+void sensitivitySurfacePoints::finalisePointSensitivities()
+{
+    // List with mesh faces. Global addressing
+    const faceList& faces = mesh_.faces();
+
+    // Geometry differentiation engine
+    deltaBoundary dBoundary(mesh_);
+
+    for (const label patchI : extendedPatchIDs_)
+    {
+        const fvPatch& patch = mesh_.boundary()[patchI];
+        vectorField nf(patch.nf());
+
+        // Point sens result for patch
+        vectorField& pointPatchSens = wallPointSensVecPtr_()[patchI];
+
+        // Face sens for patch
+        vectorField facePatchSens = dxdbMult_()[patchI];
+        if (dxdbDirectMult_)
+        {
+            facePatchSens += dxdbDirectMult_()[patchI];
+        }
+        if (bcDxDbMult_)
+        {
+            facePatchSens += bcDxDbMult_()[patchI];
+        }
+
+        // Correspondance of local point addressing to global point addressing
+        const labelList& meshPoints = patch.patch().meshPoints();
+
+        // Each local patch point belongs to these local patch faces
+        // (local numbering)
+        const labelListList& patchPointFaces = patch.patch().pointFaces();
+
+        // Index of first face in patch
+        const label patchStartIndex = patch.start();
+
+        // Loop over patch points.
+        // Collect contributions from each boundary face this point belongs to
+        forAll(meshPoints, ppI)
+        {
+            const labelList& pointFaces = patchPointFaces[ppI];
+            forAll(pointFaces, pfI)
+            {
+                label localFaceIndex = pointFaces[pfI];
+                label globalFaceIndex = patchStartIndex + localFaceIndex;
+                const face& faceI = faces[globalFaceIndex];
+
+                // Point coordinates. All indices in global numbering
+                pointField p(faceI.points(mesh_.points()));
+                tensorField p_d(faceI.size(), Zero);
+                forAll(faceI, facePointI)
+                {
+                    if (faceI[facePointI] == meshPoints[ppI])
+                    {
+                        p_d[facePointI] = tensor::I;
+                    }
+                }
+                tensorField deltaNormals =
+                    dBoundary.makeFaceCentresAndAreas_d(p, p_d);
+
+                if (isSymmetryPoint_[meshPoints[ppI]])
+                {
+                    const vector& n = symmPointNormal_[meshPoints[ppI]];
+                    deltaNormals =
+                      //0.5*(deltaNormals + transform(I - 2.0*sqr(n), deltaNormals));
+                        (deltaNormals + transform(I - 2.0*sqr(n), deltaNormals));
+                }
+
+                // Element [0] is the variation in the face center
+                // (dxFace/dxPoint)
+                const tensor& deltaCf = deltaNormals[0];
+                pointPatchSens[ppI] += facePatchSens[localFaceIndex] & deltaCf;
+
+                // Term multiplying d(Sf)/d(point displacement) and
+                // d(nf)/d(point displacement)
+                //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                // Element [1] is the variation in the (dimensional) normal
+                if (dSfdbMult_)
+                {
+                    const tensor& deltaSf = deltaNormals[1];
+                    pointPatchSens[ppI] +=
+                        dSfdbMult_()[patchI][localFaceIndex] & deltaSf;
+                }
+
+                // Element [2] is the variation in the unit normal
+                if (dnfdbMult_)
+                {
+                    const tensor& deltaNf = deltaNormals[2];
+                    pointPatchSens[ppI] +=
+                        dnfdbMult_()[patchI][localFaceIndex] & deltaNf;
+                }
+            }
+        }
+    }
+}
+
+
+void sensitivitySurfacePoints::constructGlobalPointNormalsAndAreas
+(
+    vectorField& pointNormals,
+    scalarField& pointMagSf
+)
+{
+    for (const label patchI : extendedPatchIDs_)
+    {
+        const fvPatch& patch = mesh_.boundary()[patchI];
+        const scalarField& magSf = patch.magSf();
+        vectorField nf(patch.nf());
+
+        // Correspondance of local point addressing to global point addressing
+        const labelList& meshPoints = patch.patch().meshPoints();
+
+        // Each local patch point belongs to these local patch faces
+        // (local numbering)
+        const labelListList& patchPointFaces = patch.patch().pointFaces();
+
+        // Loop over patch points
+        forAll(meshPoints, ppI)
+        {
+            const labelList& pointFaces = patchPointFaces[ppI];
+            forAll(pointFaces, pfI)
+            {
+                const label localFaceIndex = pointFaces[pfI];
+
+                // Accumulate information for point normals
+                pointNormals[meshPoints[ppI]] += nf[localFaceIndex];
+                pointMagSf[meshPoints[ppI]] += magSf[localFaceIndex];
+            }
+        }
+    }
+
+    syncTools::syncPointList
+    (
+        mesh_,
+        pointNormals,
+        plusEqOp<vector>(),
+        vector::zero
+    );
+    syncTools::syncPointList
+    (
+        mesh_,
+        pointMagSf,
+        plusEqOp<scalar>(),
+        scalar(0)
+    );
+
+    if (writeGeometricInfo_)
+    {
+        pointScalarField MagSf
+        (
+            IOobject
+            (
+                "pointMagSf",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            pointMesh::New(mesh_),
+            dimensionedScalar(dimless, Zero)
+        );
+        pointVectorField Nf
+        (
+            IOobject
+            (
+                "pointNf",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            pointMesh::New(mesh_),
+            dimensionedVector(dimless, Zero)
+        );
+        MagSf.primitiveFieldRef() = pointMagSf;
+        Nf.primitiveFieldRef() = pointNormals;
+        Nf.primitiveFieldRef().normalise();
+        MagSf.write();
+        Nf.write();
+    }
+}
+
+
+void sensitivitySurfacePoints::computePointDerivativesSize()
+{
+    // Allocate appropriate space for sensitivities
+    label nTotalPoints(0);
+    for (const label patchI : sensitivityPatchIDs_)
+    {
+        nTotalPoints += mesh_.boundaryMesh()[patchI].nPoints();
+    }
+
+    // Derivatives for all (x,y,z) components of the displacement
+    derivatives_ = scalarField(3*nTotalPoints, Zero);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+sensitivitySurfacePoints::sensitivitySurfacePoints
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    adjointSolver& adjointSolver
+)
+:
+    sensitivityShapeESI(mesh, dict, adjointSolver),
+    writeGeometricInfo_(false),
+    includeSurfaceArea_(false),
+    isSymmetryPoint_(mesh.nPoints(), false),
+    symmPointNormal_(mesh.nPoints(), Zero),
+    extendedPatchIDs_(populateExtendedIDs())
+{
+    if (debug)
+    {
+        Info<< "Extended sensitivity patches " << nl;
+        for (const label patchI : extendedPatchIDs_)
+        {
+            Info<< mesh_.boundary()[patchI].name() << endl;
+        }
+    }
+    read();
+    setSuffixName();
+
+    // Allocate boundary field pointer
+    wallPointSensVecPtr_.reset(createZeroBoundaryPointFieldPtr<vector>(mesh_));
+    wallPointSensNormalPtr_.reset
+    (
+        createZeroBoundaryPointFieldPtr<scalar>(mesh_)
+    );
+    wallPointSensNormalVecPtr_.reset
+    (
+        createZeroBoundaryPointFieldPtr<vector>(mesh_)
+    );
+
+    computePointDerivativesSize();
+
+    // Populate symmetry patches
+    forAll(mesh_.boundary(), patchI)
+    {
+        const fvPatch& pp = mesh_.boundary()[patchI];
+        bool isSymmetry
+            (isA<symmetryFvPatch>(pp) || isA<symmetryPlaneFvPatch>(pp));
+        if (isSymmetry)
+        {
+            const labelList& meshPoints = pp.patch().meshPoints();
+            const vectorField& pointNormals = pp.patch().pointNormals();
+            forAll(meshPoints, pI)
+            {
+                const label pointi = meshPoints[pI];
+                isSymmetryPoint_[pointi] = true;
+                symmPointNormal_[pointi] = pointNormals[pI];
+            }
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void sensitivitySurfacePoints::read()
+{
+    writeGeometricInfo_ =
+        dict().getOrDefault<bool>("writeGeometricInfo", false);
+    // Point sensitivities do not include the surface area by default
+    includeSurfaceArea_ =
+        dict().getOrDefault<bool>("includeSurfaceArea", false);
+}
+
+
+bool sensitivitySurfacePoints::readDict(const dictionary& dict)
+{
+    if (sensitivityShapeESI::readDict(dict))
+    {
+        read();
+        return true;
+    }
+
+    return false;
+}
+
+
+const Foam::labelHashSet&
+Foam::sensitivitySurfacePoints::geometryVariationIntegrationPatches() const
+{
+    return extendedPatchIDs_;
+}
+
+
+void sensitivitySurfacePoints::assembleSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
+{
+    // Make sure we have the proper size for the sensitivities
+    computePointDerivativesSize();
+
+    // Assemble the multipliers of dxdbFace, as in the ESI approach
+    computeDxDbMult();
+
+    // Geometric (or "direct") sensitivities are better computed directly on
+    // the points. Compute them and add the ones that depend on dxFace/dxPoint
+    finalisePointSensitivities();
+
+    // polyPatch::pointNormals will give the wrong result for points
+    // belonging to multiple patches or patch-processorPatch intersections.
+    // Keeping a mesh-wide field to allow easy reduction using syncTools.
+    // A bit expensive? Better way?
+    vectorField pointNormals(mesh_.nPoints(), Zero);
+    scalarField pointMagSf(mesh_.nPoints(), Zero);
+    constructGlobalPointNormalsAndAreas(pointNormals, pointMagSf);
+
+    // Do parallel communications to avoid wrong values at processor boundaries
+    // Global field for accumulation
+    vectorField pointSensGlobal(mesh_.nPoints(), Zero);
+    for (const label patchI : extendedPatchIDs_)
+    {
+        const labelList& meshPoints = mesh_.boundaryMesh()[patchI].meshPoints();
+        forAll(meshPoints, ppI)
+        {
+            const label globaPointI = meshPoints[ppI];
+            pointSensGlobal[globaPointI] += wallPointSensVecPtr_()[patchI][ppI];
+        }
+    }
+
+    /*
+    // Remove components normal to symmetry planes
+    forAll(mesh_.boundary(), patchI)
+    {
+        const fvPatch& patch = mesh_.boundary()[patchI];
+        if (isA<symmetryFvPatch>(patch) || isA<symmetryPlaneFvPatch>(patch))
+        {
+            // Deliberately using local point normals instead of the global ones,
+            // to get the direction normal to the symmetry plane itself
+            const vectorField& pn = patch.patch().pointNormals();
+            const labelList& meshPoints = patch.patch().meshPoints();
+            forAll(meshPoints, pI)
+            {
+                const label gpI = meshPoints[pI];
+                pointSensGlobal[gpI] -= wallPointSensVecPtr_()[patchI][pI];
+                pointSensGlobal[gpI] -=
+                    (pointSensGlobal[gpI] & pn[pI])*pn[pI];
+                pointSensGlobal[gpI] *= 2;
+            }
+        }
+    }
+    */
+
+    // Accumulate dJ/dx_i
+    syncTools::syncPointList
+    (
+        mesh_,
+        pointSensGlobal,
+        plusEqOp<vector>(),
+        vector::zero
+    );
+
+    // Transfer back to local fields
+    for (const label patchI : extendedPatchIDs_)
+    {
+        const labelList& meshPoints =
+            mesh_.boundaryMesh()[patchI].meshPoints();
+        wallPointSensVecPtr_()[patchI].map(pointSensGlobal, meshPoints);
+    }
+
+    // Compute normal sens and append to return field
+    label nPassedDVs(0);
+    const Vector<label>& sd = mesh_.solutionD();
+    for (const label patchI : sensitivityPatchIDs_)
+    {
+        const polyPatch& patch = mesh_.boundaryMesh()[patchI];
+        //if (patch.size()>0)
+        {
+            const labelList& meshPoints = patch.meshPoints();
+
+            // Avoid storing unit point normals in the global list since we
+            // might divide multiple times with the number of faces belonging
+            // to the point. Instead do the division locally, per patch use
+            vectorField patchPointNormals(pointNormals, meshPoints);
+            patchPointNormals.normalise();
+            if (!includeSurfaceArea_)
+            {
+                wallPointSensVecPtr_()[patchI] /=
+                    scalarField(pointMagSf, meshPoints);
+            }
+            wallPointSensNormalPtr_()[patchI] =
+                wallPointSensVecPtr_()[patchI] & patchPointNormals;
+            wallPointSensNormalVecPtr_()[patchI] =
+                wallPointSensNormalPtr_()[patchI] *patchPointNormals;
+
+            forAll(patch.localPoints(), pi)
+            {
+                const label gpi = nPassedDVs + pi;
+                const vector& pSens = wallPointSensVecPtr_()[patchI][pi];
+                derivatives_[3*gpi    ] = scalar(sd[0] == -1 ? 0 : pSens.x());
+                derivatives_[3*gpi + 1] = scalar(sd[1] == -1 ? 0 : pSens.y());
+                derivatives_[3*gpi + 2] = scalar(sd[2] == -1 ? 0 : pSens.z());
+            }
+            nPassedDVs += patch.nPoints();
+        }
+    }
+
+    // Write derivative fields
+    write();
+
+    // Get processed sensitivities from designVariables, if present
+    if (designVars)
+    {
+        adjointSensitivity::assembleSensitivities(designVars);
+    }
+}
+
+
+void sensitivitySurfacePoints::write(const word& baseName)
+{
+    adjointSensitivity::write();
+    ShapeSensitivitiesBase::write();
+
+    if (writeGeometricInfo_)
+    {
+        volVectorField nfOnPatch
+        (
+            IOobject
+            (
+                "nfOnPatch",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::AUTO_WRITE
+            ),
+            mesh_,
+            Zero
+        );
+
+        volVectorField SfOnPatch
+        (
+            IOobject
+            (
+                "SfOnPatch",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::AUTO_WRITE
+            ),
+            mesh_,
+            Zero
+        );
+
+        volVectorField CfOnPatch
+        (
+            IOobject
+            (
+                "CfOnPatch",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::AUTO_WRITE
+            ),
+            mesh_,
+            Zero
+        );
+        for (const label patchI : sensitivityPatchIDs_)
+        {
+            const fvPatch& patch = mesh_.boundary()[patchI];
+            nfOnPatch.boundaryFieldRef()[patchI] = patch.nf();
+            SfOnPatch.boundaryFieldRef()[patchI] = patch.Sf();
+            CfOnPatch.boundaryFieldRef()[patchI] = patch.Cf();
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.H
similarity index 55%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.H
index bbadffb4c606c885cb17a940b07b5958782a0eed..d432d98bfe2c66c584339aea614699fee1ad0e14 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.H
@@ -26,99 +26,62 @@ License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
 Class
-    Foam::incompressible::sensitivitySurfacePoints
+    Foam::sensitivitySurfacePoints
 
 Description
-    Calculation of adjoint based sensitivities at wall points
+    Calculation of adjoint-based sensitivities at wall points using the
+    E-SI formulation
 
 SourceFiles
     sensitivitySurfacePoints.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef sensitivitySurfacePointsIncompressible_H
-#define sensitivitySurfacePointsIncompressible_H
+#ifndef sensitivitySurfacePoints_H
+#define sensitivitySurfacePoints_H
 
-#include "adjointSensitivityIncompressible.H"
-#include "shapeSensitivitiesBase.H"
-#include "adjointEikonalSolverIncompressible.H"
-#include "adjointMeshMovementSolverIncompressible.H"
-#include "deltaBoundary.H"
+#include "sensitivityShapeESI.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
 /*---------------------------------------------------------------------------*\
                   Class sensitivitySurfacePoints Declaration
 \*---------------------------------------------------------------------------*/
 
 class sensitivitySurfacePoints
 :
-    public adjointSensitivity,
-    public shapeSensitivitiesBase
+    public sensitivityShapeESI
 {
 protected:
 
     // Protected data
 
+        //- Write geometric info for use by external programs
+        bool writeGeometricInfo_;
 
         //- Include surface area in sens computation
         bool includeSurfaceArea_;
 
-        //- Include the adjoint pressure term in sens computation
-        bool includePressureTerm_;
-
-        //- Include the term containing the grad of the stress at the boundary
-        bool includeGradStressTerm_;
-
-        //- Include the transpose part of the adjoint stresses
-        bool includeTransposeStresses_;
-
-        //- Use snGrad in the transpose part of the adjoint stresses
-        bool useSnGradInTranposeStresses_;
-
-        //- Include the term from the deviatoric part of the stresses
-        bool includeDivTerm_;
-
-        //- Include distance variation in sens computation
-        bool includeDistance_;
-
-        //- Include mesh movement variation in sens computation
-        bool includeMeshMovement_;
-
-        //- Include terms directly emerging from the objective function
-        bool includeObjective_;
-
-        autoPtr<adjointEikonalSolver> eikonalSolver_;
+        //- Is point belonging to a symmetry{Plane}
+        boolList isSymmetryPoint_;
 
-        autoPtr<adjointMeshMovementSolver> meshMovementSolver_;
+        //- Local point normal per symmetry point
+        vectorField symmPointNormal_;;
 
-        //- The face-based part of the sensitivities
-        //  i.e. terms that multiply dxFace/dxPoint.
-        //  Sensitivities DO include locale surface area, to get
-        //  the correct weighting from the contributions of various faces.
-        //  Normalized at the end.
-        autoPtr<boundaryVectorField> wallFaceSens_;
-
-        //- Multipliers of d(Sf)/db and d(nf)/db
-        autoPtr<boundaryVectorField> dSfdbMult_;
-        autoPtr<boundaryVectorField> dnfdbMult_;
+        //- Extended patchIDs
+        //  Sensitivities from patches adjacent to the sensitivityPatchIDs_
+        //  should also be taken into consideration in order to compute the
+        //  correct values at points in their interfaces
+        labelHashSet extendedPatchIDs_;
 
 
     // Protected Member Functions
 
-        //- Read controls and update solver pointers if necessary
-        void read();
-
-        //- Add terms related to post-processing PDEs
-        //- (i.e. adjoint Eikonal, adjoint mesh movement)
-        //- and add local face area
-        void finaliseFaceMultiplier();
+        //- Set suffix name for sensitivity fields
+        labelHashSet populateExtendedIDs() const;
 
         //- Converts face sensitivities to point sensitivities and adds the
         //- ones directly computed in points (i.e. dSf/db and dnf/db).
@@ -134,6 +97,9 @@ protected:
         //- Set suffix name for sensitivity fields
         void setSuffixName();
 
+        //- Allocate the proper size for the point-based sensitivities
+        void computePointDerivativesSize();
+
 
 private:
 
@@ -159,7 +125,7 @@ public:
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
+            adjointSolver& adjointSolver
         );
 
 
@@ -169,25 +135,29 @@ public:
 
     // Member Functions
 
-       //- Read dict if changed
-       virtual bool readDict(const dictionary& dict);
+        //- Read controls and update solver pointers if necessary
+        void read();
 
-       //- Accumulate sensitivity integrands
-       virtual void accumulateIntegrand(const scalar dt);
+        //- Read dict if changed
+        virtual bool readDict(const dictionary& dict);
 
-       //- Assemble sensitivities
-       virtual void assembleSensitivities();
+        //- Return set of patches on which to compute direct sensitivities
+        virtual const labelHashSet& geometryVariationIntegrationPatches() const;
 
-       //- Zero sensitivity fields and their constituents
-       virtual void clearSensitivities();
+        //- Assemble sensitivities
+        virtual void assembleSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        );
 
-       virtual void write(const word& baseName = word::null);
+        //- Write sensitivity fields.
+        //  If valid, copies boundaryFields to pointFields and writes them.
+        virtual void write(const word& baseName = word::null);
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.C
deleted file mode 100644
index c41eda00134d5ee99af6b7e6859d5197288cc0e7..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.C
+++ /dev/null
@@ -1,183 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
-    Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "FIBaseIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-#include "fvOptions.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(FIBase, 0);
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void FIBase::read()
-{
-    includeDistance_ =
-        dict_.getOrDefault<bool>
-        (
-            "includeDistance",
-            adjointVars_.adjointTurbulence().ref().includeDistance()
-        );
-
-    // Allocate distance solver if needed
-    if (includeDistance_ && !eikonalSolver_)
-    {
-        eikonalSolver_.reset
-        (
-            new adjointEikonalSolver
-            (
-                mesh_,
-                dict_,
-                primalVars_.RASModelVariables(),
-                adjointVars_,
-                sensitivityPatchIDs_
-            )
-        );
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-FIBase::FIBase
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    shapeSensitivities(mesh, dict, adjointSolver),
-    gradDxDbMult_
-    (
-        IOobject
-        (
-            "gradDxDbMult",
-            mesh_.time().timeName(),
-            mesh_,
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        mesh_,
-        dimensionedTensor(sqr(dimLength)/pow3(dimTime), Zero)
-    ),
-    divDxDbMult_(mesh_.nCells(), Zero),
-    optionsDxDbMult_(mesh_.nCells(), Zero),
-
-    includeDistance_(false),
-    eikonalSolver_(nullptr)
-{
-    read();
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-bool FIBase::readDict(const dictionary& dict)
-{
-    if (sensitivity::readDict(dict))
-    {
-        if (eikonalSolver_)
-        {
-            eikonalSolver_().readDict(dict);
-        }
-
-        return true;
-    }
-
-    return false;
-}
-
-
-void FIBase::accumulateIntegrand(const scalar dt)
-{
-    // Accumulate multiplier of grad(dxdb)
-    gradDxDbMult_ += computeGradDxDbMultiplier()().T()*dt;
-
-    // Accumulate multiplier of div(dxdb)
-    PtrList<objective>& functions(objectiveManager_.getObjectiveFunctions());
-    for (objective& func : functions)
-    {
-        if (func.hasDivDxDbMult())
-        {
-            divDxDbMult_ +=
-                func.weight()*func.divDxDbMultiplier().primitiveField()*dt;
-        }
-    }
-
-    // Terms from fvOptions
-    fv::options::New(this->mesh_).postProcessSens
-    (
-        optionsDxDbMult_, adjointVars_.solverName()
-    );
-
-    // Accumulate source for the adjoint to the eikonal equation
-    if (includeDistance_)
-    {
-        eikonalSolver_->accumulateIntegrand(dt);
-    }
-
-    // Accumulate direct sensitivities
-    accumulateDirectSensitivityIntegrand(dt);
-
-    // Accumulate sensitivities due to boundary conditions
-    accumulateBCSensitivityIntegrand(dt);
-}
-
-
-void FIBase::clearSensitivities()
-{
-    gradDxDbMult_ = dimensionedTensor(gradDxDbMult_.dimensions(), Zero);
-    divDxDbMult_ = Zero;
-    optionsDxDbMult_ = vector::zero;
-
-    if (includeDistance_)
-    {
-        eikonalSolver_->reset();
-    }
-
-    shapeSensitivities::clearSensitivities();
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.C
deleted file mode 100644
index ee26064a7effa7ecdaf2e7d7238603013ab9b530..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.C
+++ /dev/null
@@ -1,165 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "SIBaseIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(SIBase, 0);
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void SIBase::read()
-{
-    surfaceSensitivity_.read();
-    includeObjective_ =
-        dict().getOrDefault<bool>("includeObjectiveContribution", true);
-    writeSensitivityMap_ =
-        dict().getOrDefault<bool>("writeSensitivityMap", false);
-
-    // If includeObjective is set to true both here and in the surface
-    // sensitivities, set the one in the latter to false to avoid double
-    // contributions
-    bool surfSensIncludeObjective(surfaceSensitivity_.getIncludeObjective());
-    if (includeObjective_ && surfSensIncludeObjective)
-    {
-        WarningInFunction
-            << "includeObjectiveContribution set to true in both "
-            << "surfaceSensitivities and the parameterization options" << nl
-            << "This will lead to double contributions " << nl
-            << "Disabling the former"
-            << endl;
-        surfaceSensitivity_.setIncludeObjective(false);
-    }
-
-    // Make sure surface area is included in the sensitivity map
-    surfaceSensitivity_.setIncludeSurfaceArea(true);
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-SIBase::SIBase
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    shapeSensitivities(mesh, dict, adjointSolver),
-    surfaceSensitivity_
-    (
-        mesh,
-        // Ideally, subOrEmptyDict would be used.
-        // Since we need a recursive search in shapeSensitivities though
-        // and the dict returned by subOrEmptyDict (if found)
-        // does not know its parent, optionalSubDict is used
-        dict.optionalSubDict("surfaceSensitivities"),
-        adjointSolver
-    ),
-    includeObjective_(true),
-    writeSensitivityMap_(true)
-{
-    read();
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-bool SIBase::readDict(const dictionary& dict)
-{
-    if (sensitivity::readDict(dict))
-    {
-        surfaceSensitivity_.readDict
-        (
-            dict.optionalSubDict("surfaceSensitivities")
-        );
-
-        return true;
-    }
-
-    return false;
-}
-
-
-void SIBase::accumulateIntegrand(const scalar dt)
-{
-    // Accumulate multiplier of dxFace/db
-    surfaceSensitivity_.accumulateIntegrand(dt);
-
-    // Accumulate direct sensitivities
-    if (includeObjective_)
-    {
-        accumulateDirectSensitivityIntegrand(dt);
-    }
-
-    // Accumulate sensitivities due to boundary conditions
-    accumulateBCSensitivityIntegrand(dt);
-}
-
-
-void SIBase::clearSensitivities()
-{
-    surfaceSensitivity_.clearSensitivities();
-    shapeSensitivities::clearSensitivities();
-}
-
-
-const sensitivitySurface& SIBase::getSurfaceSensitivities() const
-{
-    return surfaceSensitivity_;
-}
-
-
-void SIBase::write(const word& baseName)
-{
-    shapeSensitivities::write(baseName);
-    if (writeSensitivityMap_)
-    {
-        surfaceSensitivity_.write(baseName);
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.H
deleted file mode 100644
index eebb436e9ca9347dafd45c0e910a62434f36ce18..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.H
+++ /dev/null
@@ -1,219 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-Class
-    Foam::incompressible::adjointSensitivity
-
-Description
-    Abstract base class for adjoint-based sensitivities in incompressible flows
-
-    Reference:
-    \verbatim
-        For the FI and ESI formulations
-            Kavvadias, I., Papoutsis-Kiachagias, E., & Giannakoglou, K. (2015).
-            On the proper treatment of grid sensitivities in continuous adjoint
-            methods for shape optimization.
-            Journal of Computational Physics, 301, 1–18.
-            http://doi.org/10.1016/j.jcp.2015.08.012
-
-        For the SI formulation
-            Papoutsis-Kiachagias, E. M., & Giannakoglou, K. C. (2014).
-            Continuous Adjoint Methods for Turbulent Flows, Applied to Shape
-            and Topology Optimization: Industrial Applications.
-            Archives of Computational Methods in Engineering, 23(2), 255–299.
-            http://doi.org/10.1007/s11831-014-9141-9
-    \endverbatim
-
-SourceFiles
-    adjointSensitivity.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef adjointSensitivityIncompressible_H
-#define adjointSensitivityIncompressible_H
-
-#include "sensitivity.H"
-#include "incompressibleVars.H"
-#include "incompressibleAdjointVars.H"
-#include "wallFvPatch.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// Forward declaration
-class incompressibleAdjointSolver;
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                     Class adjointSensitivity Declaration
-\*---------------------------------------------------------------------------*/
-
-class adjointSensitivity
-:
-    public sensitivity
-{
-protected:
-
-    // Protected data
-
-        scalarField derivatives_;
-        incompressibleAdjointSolver& adjointSolver_;
-        const incompressibleVars& primalVars_;
-        incompressibleAdjointVars& adjointVars_;
-        objectiveManager& objectiveManager_;
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        adjointSensitivity(const adjointSensitivity&) = delete;
-
-        //- No copy assignment
-        void operator=(const adjointSensitivity&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("adjointSensitivity");
-
-
-    // Declare run-time constructor selection table
-
-        declareRunTimeSelectionTable
-        (
-            autoPtr,
-            adjointSensitivity,
-            dictionary,
-            (
-                const fvMesh& mesh,
-                const dictionary& dict,
-                incompressibleAdjointSolver& adjointSolver
-            ),
-            (
-                mesh,
-                dict,
-                adjointSolver
-            )
-        );
-
-
-    // Constructors
-
-        //- Construct from components
-        adjointSensitivity
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-    // Selectors
-
-        //- Return a reference to the selected turbulence model
-        static autoPtr<adjointSensitivity> New
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-
-    //- Destructor
-    virtual ~adjointSensitivity() = default;
-
-
-    // Member Functions
-
-        //- Get primal variables
-        inline const incompressibleVars& primalVars() const
-        {
-            return primalVars_;
-        }
-
-        //- Get adjoint variables
-        inline const incompressibleAdjointVars& adjointVars() const
-        {
-            return adjointVars_;
-        }
-
-        //- Get adjoint solver
-        inline const incompressibleAdjointSolver& adjointSolver() const
-        {
-            return adjointSolver_;
-        }
-
-        //- Accumulate sensitivity integrands
-        //  Corresponds to the flow and adjoint part of the sensitivities
-        virtual void accumulateIntegrand(const scalar dt) = 0;
-
-        //- Assemble sensitivities
-        //  Adds the geometric part of the sensitivities
-        virtual void assembleSensitivities() = 0;
-
-        //- Calculates and returns sensitivity fields.
-        //  Used with optimisation libraries
-        virtual const scalarField& calculateSensitivities();
-
-        //- Returns the sensitivity fields
-        //  Assumes it has already been updated/computed
-        const scalarField& getSensitivities() const;
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
-
-        //- Write sensitivity fields.
-        //  If valid, copies boundaryFields to volFields and writes them.
-        //  Virtual to be reimplemented by control points-based methods
-        //  (Bezier, RBF) which do not need to write fields
-        virtual void write(const word& baseName = word::null);
-
-        //- Compute the volTensorField multiplying grad(dxdb) for
-        //- the volume-based approach to compute shape sensitivity derivatives
-        tmp<volTensorField> computeGradDxDbMultiplier();
-
-        //- Compute source term for adjoint mesh movement equation
-        tmp<volVectorField> adjointMeshMovementSource();
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.C
deleted file mode 100644
index 2a50491bc1fb581d661dd80754af67a02b54de5e..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.C
+++ /dev/null
@@ -1,247 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "sensitivityBezierIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-#include "IOmanip.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(sensitivityBezier, 0);
-addToRunTimeSelectionTable
-(
-    adjointSensitivity,
-    sensitivityBezier,
-    dictionary
-);
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-sensitivityBezier::sensitivityBezier
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    SIBase(mesh, dict, adjointSolver),
-    //Bezier_(mesh, dict), // AJH Read locally?
-    Bezier_(mesh, mesh.lookupObject<IOdictionary>("optimisationDict")),
-    sens_(Bezier_.nBezier(), Zero),
-    flowSens_(Bezier_.nBezier(), Zero),
-    dSdbSens_(Bezier_.nBezier(), Zero),
-    dndbSens_(Bezier_.nBezier(), Zero),
-    dxdbDirectSens_(Bezier_.nBezier(), Zero),
-    bcSens_(Bezier_.nBezier(), Zero),
-    derivativesFolder_("optimisation"/type() + "Derivatives")
-{
-    derivatives_ = scalarField(3*Bezier_.nBezier(), Zero);
-    // Create folder to store sensitivities
-    mkDir(derivativesFolder_);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void sensitivityBezier::assembleSensitivities()
-{
-    // Assemble the sensitivity map
-    // Solves for the post-processing equations and adds their contribution to
-    // the sensitivity map
-    surfaceSensitivity_.assembleSensitivities();
-
-    forAll(sens_, iCP)
-    {
-        // Face-based summation. More robust since the result is independent of
-        // the number of processors (does not hold for a point-based summation)
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            // Interpolate parameterization info to faces
-            tmp<tensorField> tdxidXj = Bezier_.dxdbFace(patchI, iCP);
-            const tensorField& dxidXj = tdxidXj();
-
-            // Patch sensitivity map
-            const vectorField& patchSensMap =
-                surfaceSensitivity_.getWallFaceSensVecBoundary()[patchI];
-            flowSens_[iCP] += gSum(patchSensMap & dxidXj);
-
-            if (includeObjective_)
-            {
-                // Contribution from objective function
-                // term from delta( n dS ) / delta b and
-                // term from delta( n    ) / delta b
-                //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                tmp<tensorField> tdSdb
-                (
-                    Bezier_.dndbBasedSensitivities(patchI, iCP)
-                );
-                const tensorField& dSdb = tdSdb();
-                dSdbSens_[iCP] += gSum(dSfdbMult_()[patchI] & dSdb);
-
-                tmp<tensorField> tdndb
-                (
-                    Bezier_.dndbBasedSensitivities(patchI, iCP, false)
-                );
-                const tensorField& dndb = tdndb();
-                dndbSens_[iCP] += gSum((dnfdbMult_()[patchI] & dndb));
-
-                // Contribution from objective function
-                // term from delta( x ) / delta b
-                // Only for objectives directly including
-                // x, like moments
-                //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                dxdbDirectSens_[iCP] +=
-                    gSum((dxdbDirectMult_()[patchI] & dxidXj));
-            }
-
-            // Sensitivities from boundary conditions
-            bcSens_[iCP] += gSum(bcDxDbMult_()[patchI] & dxidXj);
-        }
-    }
-    sens_ = flowSens_ + dSdbSens_ + dndbSens_ + dxdbDirectSens_ + bcSens_;
-
-    // Transform sensitivities to scalarField in order to cooperate with
-    // updateMethod
-    label nBezier = Bezier_.nBezier();
-    forAll(sens_, cpI)
-    {
-        derivatives_[cpI] = sens_[cpI].x();
-        derivatives_[cpI + nBezier] = sens_[cpI].y();
-        derivatives_[cpI + 2*nBezier] = sens_[cpI].z();
-        const boolList& confineXmovement = Bezier_.confineXmovement();
-        const boolList& confineYmovement = Bezier_.confineYmovement();
-        const boolList& confineZmovement = Bezier_.confineZmovement();
-        if (confineXmovement[cpI])
-        {
-            derivatives_[cpI] *= scalar(0);
-            flowSens_[cpI].x() = Zero;
-            dSdbSens_[cpI].x() = Zero;
-            dndbSens_[cpI].x() = Zero;
-            dxdbDirectSens_[cpI].x() = Zero;
-            bcSens_[cpI].x() = Zero;
-        }
-        if (confineYmovement[cpI])
-        {
-            derivatives_[cpI + nBezier] *= scalar(0);
-            flowSens_[cpI].y() = Zero;
-            dSdbSens_[cpI].y() = Zero;
-            dndbSens_[cpI].y() = Zero;
-            dxdbDirectSens_[cpI].y() = Zero;
-            bcSens_[cpI].y() = Zero;
-        }
-        if (confineZmovement[cpI])
-        {
-            derivatives_[cpI + 2*nBezier] *= scalar(0);
-            flowSens_[cpI].z() = Zero;
-            dSdbSens_[cpI].z() = Zero;
-            dndbSens_[cpI].z() = Zero;
-            dxdbDirectSens_[cpI].z() = Zero;
-            bcSens_[cpI].z() = Zero;
-        }
-    }
-}
-
-
-void sensitivityBezier::clearSensitivities()
-{
-    sens_ = Zero;
-    flowSens_ = Zero;
-    dSdbSens_ = Zero;
-    dndbSens_ = Zero;
-    dxdbDirectSens_ = Zero;
-    bcSens_ = Zero;
-
-    SIBase::clearSensitivities();
-}
-
-
-void sensitivityBezier::write(const word& baseName)
-{
-    Info<< "Writing control point sensitivities to file" << endl;
-    // Write sensitivity map
-    SIBase::write(baseName);
-    // Write control point sensitivities
-    if (Pstream::master())
-    {
-        OFstream derivFile
-        (
-            derivativesFolder_/
-                 baseName + adjointVars_.solverName() + mesh_.time().timeName()
-        );
-        unsigned int widthDV = max(int(name(sens_.size()).size()), int(3));
-        unsigned int width = IOstream::defaultPrecision() + 7;
-        derivFile
-            << setw(widthDV) << "#dv" << " "
-            << setw(width) << "total" << " "
-            << setw(width) << "flow" << " "
-            << setw(width) << "dSdb" << " "
-            << setw(width) << "dndb" << " "
-            << setw(width) << "dxdbDirect" << " "
-            << setw(width) << "dvdb" << endl;
-        label nDV = derivatives_.size();
-        label nBezier = Bezier_.nBezier();
-        const boolListList& confineMovement = Bezier_.confineMovement();
-        label lastActive(-1);
-        for (label iDV = 0; iDV < nDV; iDV++)
-        {
-            label iCP = iDV%nBezier;
-            label idir = iDV/nBezier;
-            if (!confineMovement[idir][iCP])
-            {
-                if (iDV!=lastActive + 1) derivFile << "\n";
-                lastActive = iDV;
-                derivFile
-                    << setw(widthDV) << iDV << " "
-                    << setw(width) << derivatives_[iDV] << " "
-                    << setw(width) << flowSens_[iCP].component(idir) << " "
-                    << setw(width) << dSdbSens_[iCP].component(idir) << " "
-                    << setw(width) << dndbSens_[iCP].component(idir) << " "
-                    << setw(width) << dxdbDirectSens_[iCP].component(idir) << " "
-                    << setw(width) << bcSens_[iCP].component(idir)
-                    << endl;
-            }
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.H
deleted file mode 100644
index 0422edadeb3dfbc5bdc940ac053be7f16e348024..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.H
+++ /dev/null
@@ -1,136 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-Class
-    Foam::incompressible::sensitivityBezier
-
-Description
-    Calculation of adjoint based sensitivities for Bezier control points
-
-SourceFiles
-    sensitivityBezier.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef Foam_sensitivityBezierIncompressible_H
-#define Foam_sensitivityBezierIncompressible_H
-
-#include "primitiveFieldsFwd.H"
-#include "volFieldsFwd.H"
-#include "pointFieldsFwd.H"
-#include "surfaceFieldsFwd.H"
-#include "volPointInterpolation.H"
-#include "SIBaseIncompressible.H"
-#include "primitivePatchInterpolation.H"
-#include "deltaBoundary.H"
-#include "Bezier.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class sensitivityBezier Declaration
-\*---------------------------------------------------------------------------*/
-
-class sensitivityBezier
-:
-    public SIBase
-{
-protected:
-
-    // Protected data
-
-        Bezier Bezier_;
-
-        vectorField sens_;
-        vectorField flowSens_;
-        vectorField dSdbSens_;
-        vectorField dndbSens_;
-        vectorField dxdbDirectSens_;
-        vectorField bcSens_;
-
-        fileName derivativesFolder_;
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        sensitivityBezier(const sensitivityBezier&) = delete;
-
-        //- No copy assignment
-        void operator=(const sensitivityBezier&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("Bezier");
-
-
-    // Constructors
-
-        //- Construct from components
-        sensitivityBezier
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-
-    //- Destructor
-    virtual ~sensitivityBezier() = default;
-
-
-    // Member Functions
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities();
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
-
-        //- Write sensitivities to file
-        virtual void write(const word& baseName = word::null);
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.C
deleted file mode 100644
index aa0411d9a935af1bcbfb5b41b6547dad9c9d91ab..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.C
+++ /dev/null
@@ -1,368 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "sensitivityBezierFIIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-#include "IOmanip.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(sensitivityBezierFI, 0);
-addToRunTimeSelectionTable
-(
-    adjointSensitivity, sensitivityBezierFI, dictionary
-);
-
-
-// * * * * * * * * * * * Private  Member Functions  * * * * * * * * * * * * * //
-
-void sensitivityBezierFI::read()
-{
-    // Laplace solution controls
-    const dictionary dxdbDict = dict_.subOrEmptyDict("dxdbSolver");
-    meshMovementIters_ = dxdbDict.getOrDefault<label>("iters", 1000);
-    meshMovementResidualLimit_ =
-        dxdbDict.getOrDefault<scalar>("tolerance", 1.e-07);
-
-    // Read variables related to the adjoint eikonal solver
-    FIBase::read();
-}
-
-
-tmp<volVectorField> sensitivityBezierFI::solveMeshMovementEqn
-(
-    const label iCP,
-    const label idir
-)
-{
-    read();
-    tmp<volVectorField> tm(new volVectorField("m", dxdb_));
-    volVectorField& m = tm.ref();
-
-    // SOLVE FOR DXDB
-    //~~~~~~~~~~~~~~~~
-    // set boundary conditions
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        // interpolate parameterization info to faces
-        tmp<vectorField> tdxidXjFace = Bezier_.dxdbFace(patchI, iCP, idir);
-        const vectorField& dxidXjFace = tdxidXjFace();
-
-        m.boundaryFieldRef()[patchI] == dxidXjFace;
-    }
-
-    // iterate the adjoint to the eikonal equation
-    for (label iter = 0; iter < meshMovementIters_; iter++)
-    {
-        Info<< "Mesh Movement Propagation(direction, CP), ("
-            << idir << ", " << iCP << "), Iteration : "<< iter << endl;
-
-        fvVectorMatrix mEqn
-        (
-            fvm::laplacian(m)
-        );
-
-        // Scalar residual = max(mEqn.solve().initialResidual());
-        scalar residual = mag(mEqn.solve().initialResidual());
-
-        Info<< "Max dxdb " << gMax(mag(m)()) << endl;
-
-        mesh_.time().printExecutionTime(Info);
-
-        // Check convergence
-        if (residual < meshMovementResidualLimit_)
-        {
-            Info<< "\n***Reached dxdb convergence limit, iteration " << iter
-                << "***\n\n";
-            break;
-        }
-    }
-
-    return tm;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-sensitivityBezierFI::sensitivityBezierFI
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    FIBase(mesh, dict, adjointSolver),
-    //Bezier_(mesh, dict), // AJH Read locally?
-    Bezier_(mesh, mesh.lookupObject<IOdictionary>("optimisationDict")),
-    flowSens_(3*Bezier_.nBezier(), Zero),
-    dSdbSens_(3*Bezier_.nBezier(), Zero),
-    dndbSens_(3*Bezier_.nBezier(), Zero),
-    dxdbDirectSens_(3*Bezier_.nBezier(), Zero),
-    dVdbSens_(3*Bezier_.nBezier(), Zero),
-    distanceSens_(3*Bezier_.nBezier(), Zero),
-    optionsSens_(3*Bezier_.nBezier(), Zero),
-    bcSens_(3*Bezier_.nBezier(), Zero),
-
-    derivativesFolder_("optimisation"/type() + "Derivatives"),
-
-    meshMovementIters_(-1),
-    meshMovementResidualLimit_(1.e-7),
-    dxdb_
-    (
-        variablesSet::autoCreateMeshMovementField
-        (
-            mesh,
-            "mTilda",
-            dimensionSet(dimLength)
-        )
-    )
-{
-    read();
-
-    derivatives_ = scalarField(3*Bezier_.nBezier(), Zero),
-    // Create folder to store sensitivities
-    mkDir(derivativesFolder_);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void sensitivityBezierFI::assembleSensitivities()
-{
-    // Adjoint to the eikonal equation
-    autoPtr<volTensorField> distanceSensPtr(nullptr);
-    if (includeDistance_)
-    {
-        // Solver equation
-        eikonalSolver_->solve();
-
-        // Allocate memory and compute grad(dxdb) multiplier
-        distanceSensPtr.reset
-        (
-            createZeroFieldPtr<tensor>
-            (
-                mesh_,
-                "distanceSensPtr",
-                dimensionSet(0, 2, -3, 0, 0, 0, 0)
-            )
-        );
-        distanceSensPtr() = eikonalSolver_->getFISensitivityTerm()().T();
-    }
-
-    const label nBezier = Bezier_.nBezier();
-    const label nDVs = 3*nBezier;
-    for (label iDV = 0; iDV < nDVs; iDV++)
-    {
-        label iCP = iDV%nBezier;
-        label idir = iDV/nBezier;
-        if
-        (
-            (idir == 0 && Bezier_.confineXmovement()[iCP])
-         || (idir == 1 && Bezier_.confineYmovement()[iCP])
-         || (idir == 2 && Bezier_.confineZmovement()[iCP])
-        )
-        {
-            continue;
-        }
-        else
-        {
-            // Flow term
-            // ~~~~~~~~~~~
-            // compute dxdb and its grad
-            tmp<volVectorField> tm = solveMeshMovementEqn(iCP, idir);
-            const volVectorField& m = tm();
-            volTensorField gradDxDb(fvc::grad(m, "grad(dxdb)"));
-
-            flowSens_[iDV] =
-                gSum
-                (
-                    (gradDxDb.primitiveField() && gradDxDbMult_.primitiveField())
-                  * mesh_.V()
-                );
-
-            for (const label patchI : sensitivityPatchIDs_)
-            {
-                // Contribution from objective function
-                // term from delta(n dS)/delta b and
-                // term from delta(n)/delta b
-                //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                tmp<vectorField> tdSdb =
-                    Bezier_.dndbBasedSensitivities(patchI, iCP, idir);
-                const vectorField& dSdb = tdSdb();
-                tmp<vectorField> tdndb =
-                    Bezier_.dndbBasedSensitivities(patchI, iCP, idir, false);
-                const vectorField& dndb = tdndb();
-                dSdbSens_[iDV] += gSum(dSfdbMult_()[patchI] & dSdb);
-                dndbSens_[iDV] += gSum(dnfdbMult_()[patchI] & dndb);
-
-                // Contribution from objective function
-                // term from delta( x ) / delta b
-                // Only for objectives directly including
-                // x, like moments
-                //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                tmp<vectorField> tdxdbFace =
-                    Bezier_.dxdbFace(patchI, iCP, idir);
-                const vectorField& dxdbFace = tdxdbFace();
-                dxdbDirectSens_[iDV] +=
-                    gSum(dxdbDirectMult_()[patchI] & dxdbFace);
-
-                // Contribution from boundary conditions
-                bcSens_[iDV] += gSum(bcDxDbMult_()[patchI] & dxdbFace);
-            }
-
-            // Contribution from delta (V) / delta b
-            // For objectives defined as volume integrals only
-            //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-            dVdbSens_[iDV] =
-                gSum
-                (
-                   divDxDbMult_
-                 * fvc::div(m)().primitiveField()
-                 * mesh_.V()
-                );
-
-            // Distance dependent term
-            //~~~~~~~~~~~~~~~~~~~~~~~~~
-            if (includeDistance_)
-            {
-                distanceSens_[iDV] =
-                    gSum
-                    (
-                        (
-                            distanceSensPtr().primitiveField()
-                         && gradDxDb.primitiveField()
-                        )
-                       *mesh_.V()
-                    );
-            }
-
-            // Terms from fvOptions
-            optionsSens_[iDV] +=
-                gSum((optionsDxDbMult_ & m.primitiveField())*mesh_.V());
-        }
-
-        // Sum contributions
-        derivatives_ =
-            flowSens_
-          + dSdbSens_
-          + dndbSens_
-          + dxdbDirectSens_
-          + dVdbSens_
-          + distanceSens_
-          + optionsSens_
-          + bcSens_;
-    }
-}
-
-
-void sensitivityBezierFI::clearSensitivities()
-{
-    flowSens_ = Zero;
-    dSdbSens_ = Zero;
-    dndbSens_ = Zero;
-    dxdbDirectSens_ = Zero;
-    dVdbSens_ = Zero;
-    distanceSens_ = Zero;
-    optionsSens_ = Zero;
-    bcSens_ = Zero;
-
-    FIBase::clearSensitivities();
-}
-
-
-void sensitivityBezierFI::write(const word& baseName)
-{
-    Info<< "Writing control point sensitivities to file" << endl;
-    if (Pstream::master())
-    {
-        OFstream derivFile
-        (
-            derivativesFolder_/
-                baseName + adjointVars_.solverName() + mesh_.time().timeName()
-        );
-        unsigned int widthDV = max(int(name(flowSens_.size()).size()), int(3));
-        unsigned int width = IOstream::defaultPrecision() + 7;
-        derivFile
-            << setw(widthDV) << "#dv"        << " "
-            << setw(width)   << "total"      << " "
-            << setw(width)   << "flow"       << " "
-            << setw(width)   << "dSdb"       << " "
-            << setw(width)   << "dndb"       << " "
-            << setw(width)   << "dxdbDirect" << " "
-            << setw(width)   << "dVdb"       << " "
-            << setw(width)   << "distance"   << " "
-            << setw(width)   << "options"    << " "
-            << setw(width)   << "dvdb"       << endl;
-        const label nDVs = derivatives_.size();
-        const label nBezier = Bezier_.nBezier();
-        const boolListList& confineMovement = Bezier_.confineMovement();
-        label lastActive(-1);
-
-        for (label iDV = 0; iDV < nDVs; iDV++)
-        {
-            const label iCP(iDV%nBezier);
-            const label idir(iDV/nBezier);
-            if (!confineMovement[idir][iCP])
-            {
-                if (iDV!=lastActive + 1)
-                {
-                    derivFile << "\n";
-                }
-                lastActive = iDV;
-                derivFile
-                   << setw(widthDV) << iDV << " "
-                   << setw(width) << derivatives_[iDV] << " "
-                   << setw(width) << flowSens_[iDV] << " "
-                   << setw(width) << dSdbSens_[iDV] << " "
-                   << setw(width) << dndbSens_[iDV] << " "
-                   << setw(width) << dxdbDirectSens_[iDV] << " "
-                   << setw(width) << dVdbSens_[iDV] << " "
-                   << setw(width) << distanceSens_[iDV] << " "
-                   << setw(width) << optionsSens_[iDV] << " "
-                   << setw(width) << bcSens_[iDV] << endl;
-            }
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.H
deleted file mode 100644
index 56df11726d28960ee0f201fb70367d0552bb8763..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.H
+++ /dev/null
@@ -1,168 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-Class
-    Foam::incompressible::sensitivityBezierFI
-
-Description
-    Calculation of adjoint based sensitivities for Bezier control points
-    using the FI appoach
-
-SourceFiles
-    sensitivityBezierFI.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef sensitivityBezierFIIncompressible_H
-#define sensitivityBezierFIIncompressible_H
-
-#include "primitiveFieldsFwd.H"
-#include "volFieldsFwd.H"
-#include "pointFieldsFwd.H"
-#include "surfaceFieldsFwd.H"
-#include "volPointInterpolation.H"
-#include "FIBaseIncompressible.H"
-#include "primitivePatchInterpolation.H"
-#include "deltaBoundary.H"
-#include "Bezier.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                     Class sensitivityBezierFI Declaration
-\*---------------------------------------------------------------------------*/
-
-class sensitivityBezierFI
-:
-    public FIBase
-{
-protected:
-
-    // Protected data
-        Bezier Bezier_;
-
-        //- Flow related term
-        scalarField flowSens_;
-
-        //- Term depending on delta(n dS)/delta b
-        scalarField dSdbSens_;
-
-        //- Term depending on delta(n)/delta b
-        scalarField dndbSens_;
-
-        //- Term depending on delta(x)/delta b for objectives that directly
-        //- depend on x
-        scalarField dxdbDirectSens_;
-
-        //- Term depending on delta(V)/delta b
-        scalarField dVdbSens_;
-
-        //- Term depending on distance differentiation
-        scalarField distanceSens_;
-
-        //- Term depending on fvOptions
-        scalarField optionsSens_;
-
-        //- Term depending on the differenation of boundary conditions
-        scalarField bcSens_;
-
-        fileName derivativesFolder_;
-
-        label meshMovementIters_;
-        scalar meshMovementResidualLimit_;
-        volVectorField dxdb_;
-
-        void read();
-
-        tmp<volVectorField> solveMeshMovementEqn
-        (
-            const label iCP,
-            const label idir
-        );
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        sensitivityBezierFI(const sensitivityBezierFI&) = delete;
-
-        //- No copy assignment
-        void operator=(const sensitivityBezierFI&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("BezierFI");
-
-
-    // Constructors
-
-        //- Construct from components
-        sensitivityBezierFI
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-
-    //- Destructor
-    virtual ~sensitivityBezierFI() = default;
-
-
-    // Member Functions
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities();
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
-
-        //- Write sensitivities to file
-        virtual void write(const word& baseName = word::null);
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.C
deleted file mode 100644
index 3b93e7cbcfad24a337cf22a3bf8e75aa5789f071..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.C
+++ /dev/null
@@ -1,946 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
-    Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "sensitivitySurfaceIncompressible.H"
-#include "incompressibleAdjointSolver.H"
-#include "primitivePatchInterpolation.H"
-#include "syncTools.H"
-#include "addToRunTimeSelectionTable.H"
-#include "faMatrices.H"
-#include "famSup.H"
-#include "famLaplacian.H"
-#include "volSurfaceMapping.H"
-#include "fixedValueFaPatchFields.H"
-#include "zeroGradientFaPatchFields.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(sensitivitySurface, 1);
-addToRunTimeSelectionTable
-(
-    adjointSensitivity,
-    sensitivitySurface,
-    dictionary
-);
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void sensitivitySurface::addGeometricSens()
-{
-    if (includeObjective_)
-    {
-        // Grab objective refs
-        PtrList<objective>& functions
-            (objectiveManager_.getObjectiveFunctions());
-        // Compute sens for all points in parameterized patches.
-        // Interfacing points will be accumulated later
-        autoPtr<pointBoundaryVectorField> pointSensdSdb
-        (
-            createZeroBoundaryPointFieldPtr<vector>(mesh_)
-        );
-        autoPtr<pointBoundaryVectorField> pointSensdndb
-        (
-            createZeroBoundaryPointFieldPtr<vector>(mesh_)
-        );
-        // Geometric (or "direct") sensitivities are better computed directly
-        // on the points
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            const vectorField nf(patch.nf());
-
-            // point sens result for patch
-            vectorField& patchdSdb = pointSensdSdb()[patchI];
-            vectorField& patchdndb = pointSensdndb()[patchI];
-
-            vectorField dSdbMultiplierTot(patch.size(), Zero);
-            vectorField dndbMultiplierTot(patch.size(), Zero);
-            for (auto& fun : functions)
-            {
-                dSdbMultiplierTot += fun.weight()*fun.dSdbMultiplier(patchI);
-                dndbMultiplierTot += fun.weight()*fun.dndbMultiplier(patchI);
-            }
-            // Correspondence of local point addressing to global point
-            // addressing
-            const labelList& meshPoints = patch.patch().meshPoints();
-            //  List with mesh faces. Global addressing
-            const faceList& faces = mesh_.faces();
-            //  Each local patch point belongs to these local patch faces
-            //  (local numbering)
-            const labelListList& patchPointFaces = patch.patch().pointFaces();
-            //  index of first face in patch
-            const label patchStartIndex = patch.start();
-            //  geometry differentiation engine
-            deltaBoundary dBoundary(mesh_);
-            //  Loop over patch points.
-            //  Collect contributions from each boundary face this point
-            //  belongs to
-            forAll(meshPoints, ppI)
-            {
-                const labelList& pointFaces = patchPointFaces[ppI];
-                for (label localFaceIndex : pointFaces)
-                {
-                    label globalFaceIndex = patchStartIndex + localFaceIndex;
-                    const face& faceI = faces[globalFaceIndex];
-                    // Point coordinates. All indices in global numbering
-                    const pointField p(faceI.points(mesh_.points()));
-                    tensorField p_d(faceI.size(), Zero);
-                    forAll(faceI, facePointI)
-                    {
-                        if (faceI[facePointI] == meshPoints[ppI])
-                        {
-                            p_d[facePointI] = tensor::I;
-                        }
-                    }
-                    const tensorField deltaNormals
-                    (
-                        dBoundary.makeFaceCentresAndAreas_d(p, p_d)
-                    );
-
-                    // Element [1] is the variation in the (dimensional) normal
-                    const tensor& deltaSf = deltaNormals[1];
-                    patchdSdb[ppI] +=
-                        dSdbMultiplierTot[localFaceIndex] & deltaSf;
-
-                    // Element [2] is the variation in the unit normal
-                    const tensor& deltaNf = deltaNormals[2];
-                    patchdndb[ppI] +=
-                        dndbMultiplierTot[localFaceIndex] & deltaNf;
-                }
-            }
-        }
-        // Do parallel communications to avoid wrong values at processor
-        // boundaries
-        vectorField dSdbGlobal(mesh_.nPoints(), Zero);
-        vectorField dndbGlobal(mesh_.nPoints(), Zero);
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const labelList& meshPoints =
-                mesh_.boundaryMesh()[patchI].meshPoints();
-            forAll(meshPoints, ppI)
-            {
-                const label globaPointI = meshPoints[ppI];
-                dSdbGlobal[globaPointI] += pointSensdSdb()[patchI][ppI];
-                dndbGlobal[globaPointI] += pointSensdndb()[patchI][ppI];
-            }
-        }
-        // Accumulate over processors
-        syncTools::syncPointList
-        (
-            mesh_, dSdbGlobal, plusEqOp<vector>(), vector::zero
-        );
-        syncTools::syncPointList
-        (
-            mesh_, dndbGlobal, plusEqOp<vector>(), vector::zero
-        );
-        // Transfer back to local fields and map to faces
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            const labelList& meshPoints = patch.patch().meshPoints();
-            const scalarField& magSf = patch.magSf();
-            pointSensdSdb()[patchI].map(dSdbGlobal, meshPoints);
-            pointSensdndb()[patchI].map(dndbGlobal, meshPoints);
-            // Map dSf/dx and dnf/dx term from points to faces.  Ideally, all
-            // sensitivities should be computed at points rather than faces.
-            PrimitivePatchInterpolation<polyPatch> patchInter(patch.patch());
-            vectorField dSdbFace
-            (
-                patchInter.pointToFaceInterpolate(pointSensdSdb()[patchI])
-            );
-            // dSdb already contains the face area. Divide with it to make it
-            // compatible with the rest of the terms
-            dSdbFace /= magSf;
-
-            tmp<vectorField> tdndbFace =
-                patchInter.pointToFaceInterpolate(pointSensdndb()[patchI]);
-            const vectorField& dndbFace = tdndbFace();
-
-            // Add to sensitivity fields
-            wallFaceSensVecPtr_()[patchI] += dSdbFace + dndbFace;
-        }
-    }
-}
-
-
-void sensitivitySurface::setSuffixName()
-{
-    word suffix(dict().getOrDefault<word>("suffix", word::null));
-    // Determine suffix for fields holding the sens
-    if (includeMeshMovement_)
-    {
-        shapeSensitivitiesBase::setSuffix
-        (
-            adjointVars_.solverName() + "ESI" + suffix
-        );
-    }
-    else
-    {
-        shapeSensitivitiesBase::setSuffix
-        (
-            adjointVars_.solverName() + "SI" + suffix
-        );
-    }
-}
-
-
-void sensitivitySurface::smoothSensitivities()
-{
-    // Read in parameters
-    const label iters(dict().getOrDefault<label>("iters", 500));
-    const scalar tolerance(dict().getOrDefault<scalar>("tolerance", 1.e-06));
-    autoPtr<faMesh> aMeshPtr(nullptr);
-
-    IOobject faceLabels
-    (
-        "faceLabels",
-        mesh_.time().findInstance
-        (
-            mesh_.dbDir()/faMesh::meshSubDir,
-            "faceLabels",
-            IOobject::READ_IF_PRESENT
-        ),
-        faMesh::meshSubDir,
-        mesh_,
-        IOobject::READ_IF_PRESENT,
-        IOobject::NO_WRITE
-    );
-
-    // If the faMesh already exists, read it
-    if (faceLabels.typeHeaderOk<labelIOList>(false))
-    {
-        Info<< "Reading the already constructed faMesh" << endl;
-        aMeshPtr.reset(new faMesh(mesh_));
-    }
-    else
-    {
-        // Dictionary used to construct the faMesh
-        dictionary faMeshDefinition;
-
-        IOobject faMeshDefinitionDict
-        (
-            "faMeshDefinition",
-            mesh_.time().caseSystem(),
-            mesh_,
-            IOobject::MUST_READ,
-            IOobject::NO_WRITE
-        );
-
-        // If the faMeshDefinitionDict exists, use it to construct the mesh
-        if (faMeshDefinitionDict.typeHeaderOk<IOdictionary>(false))
-        {
-            Info<< "Reading faMeshDefinition from system " << endl;
-            faMeshDefinition = IOdictionary(faMeshDefinitionDict);
-        }
-        // Otherwise, faMesh is generated from all patches on which we compute
-        // sensitivities
-        else
-        {
-            Info<< "Constructing faMeshDefinition from sensitivity patches"
-                << endl;
-            wordList polyMeshPatches(sensitivityPatchIDs_.size());
-            label i(0);
-            for (const label patchID : sensitivityPatchIDs_)
-            {
-                polyMeshPatches[i++] = mesh_.boundary()[patchID].name();
-            }
-            faMeshDefinition.add<wordList>("polyMeshPatches", polyMeshPatches);
-            (void)faMeshDefinition.subDictOrAdd("boundary");
-        }
-
-        // Construct faMesh
-        aMeshPtr.reset(new faMesh(mesh_, faMeshDefinition));
-    }
-    faMesh& aMesh = aMeshPtr.ref();
-
-    // Physical radius of the smoothing, provided either directly or computed
-    // based on the average 'length' of boundary faces
-    const scalar Rphysical
-        (dict().getOrDefault<scalar>("radius", computeRadius(aMesh)));
-    DebugInfo
-        << "Physical radius of the sensitivity smoothing "
-        << Rphysical << nl << endl;
-
-    // Radius used as the diffusivity in the Helmholtz filter, computed as a
-    // function of the physical radius
-    const dimensionedScalar RpdeSqr
-    (
-        "RpdeSqr", dimArea, sqr(Rphysical/(2.*::sqrt(3.)))
-    );
-
-    dimensionedScalar one("1", dimless, 1.);
-
-    // Mapping engine
-    const volSurfaceMapping vsm(aMesh);
-
-    // Source term in faMatrix needs to be an areaField
-    areaScalarField sens
-    (
-        IOobject
-        (
-            "sens",
-            mesh_.time().timeName(),
-            mesh_,
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        aMesh,
-        dimensionedScalar(dimless, Zero),
-        faPatchFieldBase::zeroGradientType()
-    );
-
-    // Copy sensitivities to area field
-    sens.primitiveFieldRef() =
-        vsm.mapToSurface<scalar>(wallFaceSensNormalPtr_());
-
-    // Initialisation of the smoothed sensitivities field based on the original
-    // sensitivities
-    areaScalarField smoothedSens("smoothedSens", sens);
-    for (label iter = 0; iter < iters; ++iter)
-    {
-        Info<< "Sensitivity smoothing iteration " << iter << endl;
-
-        faScalarMatrix smoothEqn
-        (
-            fam::Sp(one, smoothedSens)
-          - fam::laplacian(RpdeSqr, smoothedSens)
-         ==
-            sens
-        );
-
-        smoothEqn.relax();
-
-        const scalar residual(mag(smoothEqn.solve().initialResidual()));
-
-        DebugInfo
-            << "Max smoothSens " << gMax(mag(smoothedSens)()) << endl;
-
-        // Print execution time
-        mesh_.time().printExecutionTime(Info);
-
-        // Check convergence
-        if (residual < tolerance)
-        {
-            Info<< "\n***Reached smoothing equation convergence limit, "
-                   "iteration " << iter << "***\n\n";
-            break;
-        }
-    }
-
-    // Field used to write the smoothed sensitivity field to file
-    volScalarField volSmoothedSens
-    (
-        IOobject
-        (
-            "smoothedSurfaceSens" + surfaceFieldSuffix_,
-            mesh_.time().timeName(),
-            mesh_,
-            IOobject::NO_READ,
-            IOobject::NO_WRITE
-        ),
-        mesh_,
-        dimensionedScalar(dimless, Zero)
-    );
-
-    // Transfer result back to volField and write
-    vsm.mapToVolume(smoothedSens, volSmoothedSens.boundaryFieldRef());
-    volSmoothedSens.write();
-}
-
-
-scalar sensitivitySurface::computeRadius(const faMesh& aMesh)
-{
-    scalar averageArea(gAverage(aMesh.S().field()));
-    const Vector<label>& geometricD = mesh_.geometricD();
-    const boundBox& bounds = mesh_.bounds();
-    forAll(geometricD, iDir)
-    {
-        if (geometricD[iDir] == -1)
-        {
-            averageArea /= bounds.span()[iDir];
-        }
-    }
-    scalar mult = dict().getOrDefault<scalar>("meanRadiusMultiplier", 10);
-
-    return mult*pow(averageArea, scalar(1)/scalar(mesh_.nGeometricD() - 1));
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-sensitivitySurface::sensitivitySurface
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    adjointSensitivity(mesh, dict, adjointSolver),
-    shapeSensitivitiesBase(mesh, dict),
-    includeSurfaceArea_(false),
-    includePressureTerm_(false),
-    includeGradStressTerm_(false),
-    includeTransposeStresses_(false),
-    useSnGradInTranposeStresses_(false),
-    includeDivTerm_(false),
-    includeDistance_(false),
-    includeMeshMovement_(false),
-    includeObjective_(false),
-    writeGeometricInfo_(false),
-    smoothSensitivities_(false),
-    eikonalSolver_(nullptr),
-    meshMovementSolver_(nullptr),
-
-    nfOnPatchPtr_(nullptr),
-    SfOnPatchPtr_(nullptr),
-    CfOnPatchPtr_(nullptr)
-{
-    read();
-    setSuffixName();
-
-    // Allocate boundary field pointer
-    wallFaceSensVecPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-    wallFaceSensNormalPtr_.reset(createZeroBoundaryPtr<scalar>(mesh_));
-    wallFaceSensNormalVecPtr_.reset(createZeroBoundaryPtr<vector>(mesh_));
-
-    // Allocate fields to contain geometric info
-    if (writeGeometricInfo_)
-    {
-        nfOnPatchPtr_.reset
-        (
-            new volVectorField
-            (
-                IOobject
-                (
-                    "nfOnPatch",
-                    mesh.time().timeName(),
-                    mesh,
-                    IOobject::NO_READ,
-                    IOobject::AUTO_WRITE
-                ),
-                mesh,
-                vector::zero
-            )
-        );
-
-        SfOnPatchPtr_.reset
-        (
-            new volVectorField
-            (
-                IOobject
-                (
-                    "SfOnPatch",
-                    mesh.time().timeName(),
-                    mesh,
-                    IOobject::NO_READ,
-                    IOobject::AUTO_WRITE
-                ),
-                mesh,
-                vector::zero
-            )
-        );
-
-        CfOnPatchPtr_.reset
-        (
-            new volVectorField
-            (
-                IOobject
-                (
-                    "CfOnPatch",
-                    mesh.time().timeName(),
-                    mesh,
-                    IOobject::NO_READ,
-                    IOobject::AUTO_WRITE
-                ),
-                mesh,
-                vector::zero
-            )
-        );
-    }
-
-    // Allocate appropriate space for the sensitivity field
-    computeDerivativesSize();
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void sensitivitySurface::read()
-{
-    includeSurfaceArea_ =
-        dict().getOrDefault<bool>("includeSurfaceArea", true);
-    includePressureTerm_ =
-        dict().getOrDefault<bool>("includePressure", true);
-    includeGradStressTerm_ =
-        dict().getOrDefault<bool>("includeGradStressTerm", true);
-    includeTransposeStresses_ =
-        dict().getOrDefault<bool>("includeTransposeStresses", true);
-    useSnGradInTranposeStresses_ =
-        dict().getOrDefault<bool>("useSnGradInTranposeStresses", false);
-    includeDivTerm_ = dict().getOrDefault<bool>("includeDivTerm", false);
-    includeDistance_ =
-        dict().getOrDefault<bool>
-        (
-            "includeDistance",
-            adjointVars_.adjointTurbulence().ref().includeDistance()
-        );
-    includeMeshMovement_ =
-        dict().getOrDefault<bool>("includeMeshMovement", true);
-    includeObjective_ =
-        dict().getOrDefault<bool>("includeObjectiveContribution", true);
-    writeGeometricInfo_ =
-        dict().getOrDefault<bool>("writeGeometricInfo", false);
-    smoothSensitivities_ =
-        dict().getOrDefault<bool>("smoothSensitivities", false);
-
-    // Allocate new solvers if necessary
-    if (includeDistance_ && !eikonalSolver_)
-    {
-        eikonalSolver_.reset
-        (
-            new adjointEikonalSolver
-            (
-                mesh_,
-                dict_,
-                primalVars_.RASModelVariables(),
-                adjointVars_,
-                sensitivityPatchIDs_
-            )
-        );
-    }
-    if (includeMeshMovement_ && !meshMovementSolver_)
-    {
-        meshMovementSolver_.reset
-        (
-            new adjointMeshMovementSolver
-            (
-                mesh_,
-                dict_,
-                *this,
-                sensitivityPatchIDs_,
-                eikonalSolver_
-            )
-        );
-    }
-}
-
-
-bool sensitivitySurface::readDict(const dictionary& dict)
-{
-    if (sensitivity::readDict(dict))
-    {
-        if (eikonalSolver_)
-        {
-            eikonalSolver_().readDict(dict);
-        }
-
-        if (meshMovementSolver_)
-        {
-            meshMovementSolver_().readDict(dict);
-        }
-
-        return true;
-    }
-
-    return false;
-}
-
-
-void sensitivitySurface::computeDerivativesSize()
-{
-    label nFaces(0);
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        nFaces += mesh_.boundary()[patchI].size();
-    }
-    derivatives_.setSize(nFaces);
-}
-
-
-void sensitivitySurface::accumulateIntegrand(const scalar dt)
-{
-    // Grab references
-    const volScalarField& p = primalVars_.p();
-    const volVectorField& U = primalVars_.U();
-
-    const volScalarField& pa = adjointVars_.pa();
-    const volVectorField& Ua = adjointVars_.Ua();
-    autoPtr<incompressibleAdjoint::adjointRASModel>& adjointTurbulence =
-        adjointVars_.adjointTurbulence();
-
-    // Accumulate source for additional post-processing PDEs, if necessary
-    if (includeDistance_)
-    {
-        eikonalSolver_->accumulateIntegrand(dt);
-    }
-
-    if (includeMeshMovement_)
-    {
-        meshMovementSolver_->accumulateIntegrand(dt);
-    }
-
-    // Terms from the adjoint turbulence model
-    const boundaryVectorField& adjointTMsensitivities =
-        adjointTurbulence->wallShapeSensitivities();
-
-    DebugInfo
-        << "    Calculating adjoint sensitivity. " << endl;
-
-    tmp<volScalarField> tnuEff = adjointTurbulence->nuEff();
-    const volScalarField& nuEff = tnuEff.ref();
-
-    // Sensitivities do not include locale surface area by default.
-    // The part of the sensitivities that multiplies dxFace/db follows
-
-    // Deal with the stress part first since it's the most awkward in terms
-    // of memory managment
-    if (includeGradStressTerm_)
-    {
-        // Terms corresponding to contributions from converting delta
-        // to thetas are added through the corresponding adjoint
-        // boundary conditions instead of grabbing contributions from
-        // the objective function.  Useful to have a unified
-        // formulation for low- and high-re meshes
-
-        tmp<volVectorField> tgradp = fvc::grad(p);
-        const volVectorField& gradp = tgradp.ref();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            tmp<vectorField> tnf = patch.nf();
-            const fvPatchVectorField& Uab = Ua.boundaryField()[patchI];
-            wallFaceSensVecPtr_()[patchI] -=
-                (Uab & tnf)*gradp.boundaryField()[patchI]*dt;
-        }
-        tgradp.clear();
-
-        // We only need to modify the boundaryField of gradU locally.
-        // If grad(U) is cached then
-        // a. The .ref() call fails since the tmp is initialised from a
-        //    const ref
-        // b. we would be changing grad(U) for all other places in the code
-        //    that need it
-        // So, always allocate new memory and avoid registering the new field
-        tmp<volTensorField> tgradU =
-            volTensorField::New("gradULocal", fvc::grad(U));
-        volTensorField::Boundary& gradUbf = tgradU.ref().boundaryFieldRef();
-
-        // Explicitly correct the boundary gradient to get rid of the
-        // tangential component
-        forAll(mesh_.boundary(), patchI)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            if (isA<wallFvPatch>(patch))
-            {
-                tmp<vectorField> tnf = mesh_.boundary()[patchI].nf();
-                gradUbf[patchI] = tnf*U.boundaryField()[patchI].snGrad();
-            }
-        }
-
-        tmp<volSymmTensorField> tstress = nuEff*twoSymm(tgradU);
-        const volSymmTensorField& stress = tstress.cref();
-        autoPtr<volVectorField> ptemp
-            (Foam::createZeroFieldPtr<vector>(mesh_, "temp", sqr(dimVelocity)));
-        volVectorField& temp = ptemp.ref();
-        for (label idir = 0; idir < pTraits<vector>::nComponents; ++idir)
-        {
-            unzipRow(stress, idir, temp);
-            volTensorField gradStressDir(fvc::grad(temp));
-            for (const label patchI : sensitivityPatchIDs_)
-            {
-                const fvPatch& patch = mesh_.boundary()[patchI];
-                tmp<vectorField> tnf = patch.nf();
-                const fvPatchVectorField& Uab = Ua.boundaryField()[patchI];
-                wallFaceSensVecPtr_()[patchI] +=
-                    (
-                        Uab.component(idir)
-                       *(gradStressDir.boundaryField()[patchI] & tnf)
-                    )*dt;
-            }
-        }
-    }
-
-    // Transpose part of the adjoint stresses
-    // Dealt with separately to deallocate gradUa as soon as possible
-    if (includeTransposeStresses_)
-    {
-        tmp<volTensorField> tgradUa = fvc::grad(Ua);
-        const volTensorField::Boundary& gradUabf =
-            tgradUa.cref().boundaryField();
-
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            tmp<vectorField> tnf = patch.nf();
-            const vectorField& nf = tnf();
-            vectorField gradUaNf
-                (
-                    useSnGradInTranposeStresses_
-                  ? (Ua.boundaryField()[patchI].snGrad() & nf)*nf
-                  : (gradUabf[patchI] & nf)
-                );
-            wallFaceSensVecPtr_()[patchI] -=
-                nuEff.boundaryField()[patchI]
-               *(gradUaNf & U.boundaryField()[patchI].snGrad())*nf;
-        }
-    }
-
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = mesh_.boundary()[patchI];
-        tmp<vectorField> tnf = patch.nf();
-        const vectorField& nf = tnf();
-
-        // Includes spurious tangential gradU part. Deprecated
-        /*
-        vectorField stressAndPressureTerm =
-              (
-                - (
-                       Ua.boundaryField()[patchI].snGrad()
-                    + (gradUa.boundaryField()[patchI] & nf)
-                  ) * nuEff.boundaryField()[patchI]
-                + pa.boundaryField()[patchI] *nf
-              ) & gradU.boundaryField()[patchI].T();
-        */
-
-        // Adjoint stress term
-        vectorField stressTerm
-        (
-          - (
-                Ua.boundaryField()[patchI].snGrad()
-              & U.boundaryField()[patchI].snGrad()
-            )
-          * nuEff.boundaryField()[patchI]
-          * nf
-        );
-
-        if (includeDivTerm_)
-        {
-            stressTerm +=
-                scalar(1./3.)*nuEff.boundaryField()[patchI]
-              * (
-                    ((Ua.boundaryField()[patchI].snGrad() &nf)*nf)
-                  & U.boundaryField()[patchI].snGrad()
-                )
-              * nf;
-        }
-
-        // Adjoint pressure terms
-        vectorField pressureTerm(patch.size(), Zero);
-        if (includePressureTerm_)
-        {
-            pressureTerm =
-            (
-                (nf*pa.boundaryField()[patchI])
-                & U.boundaryField()[patchI].snGrad()
-            )* nf;
-        }
-
-        PtrList<objective>& functions
-            (objectiveManager_.getObjectiveFunctions());
-
-        // Term from objectives including x directly (e.g. moments)
-        vectorField dxdbMultiplierTot(pressureTerm.size(), Zero);
-        if (includeObjective_)
-        {
-            forAll(functions, funcI)
-            {
-                dxdbMultiplierTot +=
-                    functions[funcI].weight()
-                  * (
-                        functions[funcI].dxdbDirectMultiplier(patchI)
-                    );
-            }
-        }
-
-        // Fill in sensitivity fields
-        wallFaceSensVecPtr_()[patchI] +=
-        (
-            stressTerm
-          + pressureTerm
-          + adjointTMsensitivities[patchI]
-          + dxdbMultiplierTot
-        )*dt;
-    }
-
-    // Add terms from physics other than the typical incompressible flow eqns
-    adjointSolver_.additionalSensitivityMapTerms
-        (wallFaceSensVecPtr_(), sensitivityPatchIDs_, dt);
-
-    // Add the sensitivity part corresponding to changes of the normal vector
-    // Computed at points and mapped to faces
-    addGeometricSens();
-}
-
-
-void sensitivitySurface::assembleSensitivities()
-{
-    // Update geometric fields for use by external users
-    if (writeGeometricInfo_)
-    {
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            tmp<vectorField> tnf = patch.nf();
-            const vectorField& nf = tnf();
-            const vectorField& Sf = patch.Sf();
-            const vectorField& Cf = patch.Cf();
-
-            nfOnPatchPtr_().boundaryFieldRef()[patchI] = nf;
-            SfOnPatchPtr_().boundaryFieldRef()[patchI] = Sf;
-            CfOnPatchPtr_().boundaryFieldRef()[patchI] = Cf;
-        }
-    }
-
-    // Solve extra equations if necessary
-    // Solved using accumulated sources over time
-    autoPtr<boundaryVectorField> distanceSensPtr(nullptr);
-    if (includeDistance_)
-    {
-        eikonalSolver_->solve();
-        distanceSensPtr.reset(createZeroBoundaryPtr<vector>(mesh_));
-        const boundaryVectorField& sens =
-            eikonalSolver_->distanceSensitivities();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            distanceSensPtr()[patchI] = sens[patchI];
-        }
-    }
-
-    autoPtr<boundaryVectorField> meshMovementSensPtr(nullptr);
-    if (includeMeshMovement_)
-    {
-        meshMovementSolver_->solve();
-        meshMovementSensPtr.reset(createZeroBoundaryPtr<vector>(mesh_));
-        const boundaryVectorField& sens =
-            meshMovementSolver_->meshMovementSensitivities();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            meshMovementSensPtr()[patchI] = sens[patchI];
-        }
-    }
-
-
-    // Project to normal face vector
-    label nPassedFaces(0);
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = mesh_.boundary()[patchI];
-        tmp<vectorField> tnf(patch.nf());
-        const vectorField& nf = tnf();
-
-        // Distance related terms
-        if (includeDistance_)
-        {
-            wallFaceSensVecPtr_()[patchI] += distanceSensPtr()[patchI];
-        }
-
-        // Mesh movement related terms
-        if (includeMeshMovement_)
-        {
-            wallFaceSensVecPtr_()[patchI] += meshMovementSensPtr()[patchI];
-        }
-
-        if (includeSurfaceArea_)
-        {
-            wallFaceSensVecPtr_()[patchI] *= patch.magSf();
-        }
-
-        wallFaceSensNormalPtr_()[patchI] = wallFaceSensVecPtr_()[patchI] & nf;
-        wallFaceSensNormalVecPtr_()[patchI] =
-            wallFaceSensNormalPtr_()[patchI] * nf;
-
-        forAll(patch, fI)
-        {
-            derivatives_[nPassedFaces + fI]
-                = wallFaceSensNormalPtr_()[patchI][fI];
-        }
-        nPassedFaces += patch.size();
-    }
-
-    // Smooth sensitivities if needed
-    if (smoothSensitivities_)
-    {
-        smoothSensitivities();
-    }
-}
-
-
-void sensitivitySurface::clearSensitivities()
-{
-    // Reset terms in post-processing PDEs
-    if (includeDistance_)
-    {
-        eikonalSolver_->reset();
-    }
-    if (includeMeshMovement_)
-    {
-        meshMovementSolver_->reset();
-    }
-    // Reset sensitivity fields
-    adjointSensitivity::clearSensitivities();
-    shapeSensitivitiesBase::clearSensitivities();
-}
-
-
-autoPtr<adjointEikonalSolver>& sensitivitySurface::getAdjointEikonalSolver()
-{
-    return eikonalSolver_;
-}
-
-
-void sensitivitySurface::write(const word& baseName)
-{
-    setSuffixName();
-    adjointSensitivity::write();
-    shapeSensitivitiesBase::write();
-
-    if (writeGeometricInfo_)
-    {
-        nfOnPatchPtr_().write();
-        SfOnPatchPtr_().write();
-        CfOnPatchPtr_().write();
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-} // End namespace incompressible
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.C
deleted file mode 100644
index 74e6455ea38cfb46a8eb22ec6a244fd2ba8f8d38..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.C
+++ /dev/null
@@ -1,773 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2020, 2022 FOSS GP
-    Copyright (C) 2019-2022 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "sensitivitySurfacePointsIncompressible.H"
-#include "incompressibleAdjointSolver.H"
-#include "addToRunTimeSelectionTable.H"
-#include "syncTools.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(sensitivitySurfacePoints, 0);
-addToRunTimeSelectionTable
-(
-    adjointSensitivity,
-    sensitivitySurfacePoints,
-    dictionary
-);
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void sensitivitySurfacePoints::read()
-{
-    includeSurfaceArea_ =
-        dict().getOrDefault<bool>("includeSurfaceArea", false);
-    includePressureTerm_ =
-        dict().getOrDefault<bool>("includePressure", true);
-    includeGradStressTerm_ =
-        dict().getOrDefault<bool>("includeGradStressTerm", true);
-    includeTransposeStresses_ =
-        dict().getOrDefault<bool>("includeTransposeStresses", true);
-    useSnGradInTranposeStresses_ =
-        dict().getOrDefault<bool>("useSnGradInTranposeStresses", false);
-    includeDivTerm_ =
-        dict().getOrDefault<bool>("includeDivTerm", false);
-    includeDistance_ =
-        dict().getOrDefault<bool>
-        (
-            "includeDistance",
-            adjointVars_.adjointTurbulence().ref().includeDistance()
-        );
-    includeMeshMovement_ =
-        dict().getOrDefault<bool>("includeMeshMovement", true);
-    includeObjective_ =
-        dict().getOrDefault<bool>("includeObjectiveContribution", true);
-
-    // Allocate new solvers if necessary
-    if (includeDistance_ && !eikonalSolver_)
-    {
-        eikonalSolver_.reset
-        (
-            new adjointEikonalSolver
-            (
-                mesh_,
-                dict(),
-                primalVars_.RASModelVariables(),
-                adjointVars_,
-                sensitivityPatchIDs_
-            )
-        );
-    }
-
-    if (includeMeshMovement_ && !meshMovementSolver_)
-    {
-        meshMovementSolver_.reset
-        (
-            new adjointMeshMovementSolver
-            (
-                mesh_,
-                dict(),
-                *this,
-                sensitivityPatchIDs_,
-                eikonalSolver_
-            )
-        );
-    }
-}
-
-
-void sensitivitySurfacePoints::finaliseFaceMultiplier()
-{
-    // Solve extra equations if necessary
-    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    autoPtr<boundaryVectorField> distanceSensPtr(nullptr);
-    if (includeDistance_)
-    {
-        eikonalSolver_->solve();
-        distanceSensPtr.reset(createZeroBoundaryPtr<vector>(mesh_));
-        const boundaryVectorField& sens =
-            eikonalSolver_->distanceSensitivities();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            distanceSensPtr()[patchI] = sens[patchI];
-        }
-    }
-
-    autoPtr<boundaryVectorField> meshMovementSensPtr(nullptr);
-    if (includeMeshMovement_)
-    {
-        meshMovementSolver_->solve();
-        meshMovementSensPtr.reset(createZeroBoundaryPtr<vector>(mesh_));
-        const boundaryVectorField& sens =
-            meshMovementSolver_->meshMovementSensitivities();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            meshMovementSensPtr()[patchI] = sens[patchI];
-        }
-    }
-
-    // Add to other terms multiplying dxFace/dxPoints
-    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = mesh_.boundary()[patchI];
-        tmp<vectorField> tnf = patch.nf();
-        const scalarField& magSf = patch.magSf();
-        // Distance related terms
-        if (includeDistance_)
-        {
-            wallFaceSens_()[patchI] += distanceSensPtr()[patchI];
-        }
-
-        // Mesh movement related terms
-        if (includeMeshMovement_)
-        {
-            wallFaceSens_()[patchI] += meshMovementSensPtr()[patchI];
-        }
-
-        // Add local face area
-        //~~~~~~~~~~~~~~~~~~~~
-        // Sensitivities DO include locale surface area, to get
-        // the correct weighting from the contributions of various faces.
-        // Normalized at the end.
-        // dSfdbMult already includes the local area. No need to re-multiply
-        wallFaceSens_()[patchI] *= magSf;
-        dnfdbMult_()[patchI] *= magSf;
-    }
-}
-
-
-void sensitivitySurfacePoints::finalisePointSensitivities()
-{
-    // Geometric (or "direct") sensitivities are better computed directly on
-    // the points. Compute them and add the ones that depend on dxFace/dxPoint
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = mesh_.boundary()[patchI];
-        vectorField nf(patch.nf());
-
-        // Point sens result for patch
-        vectorField& pointPatchSens = wallPointSensVecPtr_()[patchI];
-
-        // Face sens for patch
-        const vectorField& facePatchSens = wallFaceSens_()[patchI];
-
-        // Geometry variances
-        const vectorField& dSfdbMultPatch = dSfdbMult_()[patchI];
-        const vectorField& dnfdbMultPatch = dnfdbMult_()[patchI];
-
-        // Correspondance of local point addressing to global point addressing
-        const labelList& meshPoints = patch.patch().meshPoints();
-
-        // List with mesh faces. Global addressing
-        const faceList& faces = mesh_.faces();
-
-        // Each local patch point belongs to these local patch faces
-        // (local numbering)
-        const labelListList& patchPointFaces = patch.patch().pointFaces();
-
-        // Index of first face in patch
-        const label patchStartIndex = patch.start();
-
-        // Geometry differentiation engine
-        deltaBoundary dBoundary(mesh_);
-
-        // Loop over patch points.
-        // Collect contributions from each boundary face this point belongs to
-        forAll(meshPoints, ppI)
-        {
-            const labelList& pointFaces = patchPointFaces[ppI];
-            forAll(pointFaces, pfI)
-            {
-                label localFaceIndex = pointFaces[pfI];
-                label globalFaceIndex = patchStartIndex + localFaceIndex;
-                const face& faceI = faces[globalFaceIndex];
-
-                // Point coordinates. All indices in global numbering
-                pointField p(faceI.points(mesh_.points()));
-                tensorField p_d(faceI.size(), Zero);
-                forAll(faceI, facePointI)
-                {
-                    if (faceI[facePointI] == meshPoints[ppI])
-                    {
-                        p_d[facePointI] = tensor::I;
-                    }
-                }
-                tensorField deltaNormals =
-                    dBoundary.makeFaceCentresAndAreas_d(p, p_d);
-
-                // Element [0] is the variation in the face center
-                // (dxFace/dxPoint)
-                const tensor& deltaCf = deltaNormals[0];
-                pointPatchSens[ppI] += facePatchSens[localFaceIndex] & deltaCf;
-
-                // Term multiplying d(Sf)/d(point displacement) and
-                // d(nf)/d(point displacement)
-                //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-                if (includeObjective_)
-                {
-                    // Element [1] is the variation in the (dimensional) normal
-                    const tensor& deltaSf = deltaNormals[1];
-                    pointPatchSens[ppI] +=
-                        dSfdbMultPatch[localFaceIndex] & deltaSf;
-
-                    // Element [2] is the variation in the unit normal
-                    const tensor& deltaNf = deltaNormals[2];
-                    pointPatchSens[ppI] +=
-                        dnfdbMultPatch[localFaceIndex] & deltaNf;
-                }
-            }
-        }
-    }
-}
-
-
-void sensitivitySurfacePoints::constructGlobalPointNormalsAndAreas
-(
-    vectorField& pointNormals,
-    scalarField& pointMagSf
-)
-{
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = mesh_.boundary()[patchI];
-        const scalarField& magSf = patch.magSf();
-        vectorField nf(patch.nf());
-
-        // Correspondance of local point addressing to global point addressing
-        const labelList& meshPoints = patch.patch().meshPoints();
-
-        // Each local patch point belongs to these local patch faces
-        // (local numbering)
-        const labelListList& patchPointFaces = patch.patch().pointFaces();
-
-        // Loop over patch points
-        forAll(meshPoints, ppI)
-        {
-            const labelList& pointFaces = patchPointFaces[ppI];
-            forAll(pointFaces, pfI)
-            {
-                const label localFaceIndex = pointFaces[pfI];
-
-                // Accumulate information for point normals
-                pointNormals[meshPoints[ppI]] += nf[localFaceIndex];
-                pointMagSf[meshPoints[ppI]] += magSf[localFaceIndex];
-            }
-        }
-    }
-
-    syncTools::syncPointList
-    (
-        mesh_,
-        pointNormals,
-        plusEqOp<vector>(),
-        vector::zero
-    );
-    syncTools::syncPointList
-    (
-        mesh_,
-        pointMagSf,
-        plusEqOp<scalar>(),
-        scalar(0)
-    );
-}
-
-
-void sensitivitySurfacePoints::setSuffixName()
-{
-    word suffix(dict().getOrDefault<word>("suffix", word::null));
-    // Determine suffix for fields holding the sens
-    if (includeMeshMovement_)
-    {
-        shapeSensitivitiesBase::setSuffix
-        (
-            adjointVars_.solverName() + "ESI" + suffix
-        );
-    }
-    else
-    {
-        shapeSensitivitiesBase::setSuffix
-        (
-            adjointVars_.solverName() + "SI" + suffix
-        );
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-sensitivitySurfacePoints::sensitivitySurfacePoints
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    adjointSensitivity(mesh, dict, adjointSolver),
-    shapeSensitivitiesBase(mesh, dict),
-    includeSurfaceArea_(false),
-    includePressureTerm_(false),
-    includeGradStressTerm_(false),
-    includeTransposeStresses_(false),
-    useSnGradInTranposeStresses_(false),
-    includeDivTerm_(false),
-    includeDistance_(false),
-    includeMeshMovement_(false),
-    includeObjective_(false),
-    eikonalSolver_(nullptr),
-    meshMovementSolver_(nullptr),
-    wallFaceSens_(createZeroBoundaryPtr<vector>(mesh_)),
-    dSfdbMult_(createZeroBoundaryPtr<vector>(mesh_)),
-    dnfdbMult_(createZeroBoundaryPtr<vector>(mesh_))
-
-{
-    read();
-
-    // Allocate boundary field pointer
-    wallPointSensVecPtr_.reset(createZeroBoundaryPointFieldPtr<vector>(mesh_));
-    wallPointSensNormalPtr_.reset
-    (
-        createZeroBoundaryPointFieldPtr<scalar>(mesh_)
-    );
-    wallPointSensNormalVecPtr_.reset
-    (
-        createZeroBoundaryPointFieldPtr<vector>(mesh_)
-    );
-
-    // Allocate appropriate space for sensitivities
-    label nTotalPoints(0);
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        nTotalPoints += mesh_.boundaryMesh()[patchI].nPoints();
-    }
-    reduce(nTotalPoints, sumOp<label>());
-
-    // Derivatives for all (x,y,z) components of the displacement are kept
-    derivatives_ = scalarField(3*nTotalPoints, Zero);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-bool sensitivitySurfacePoints::readDict(const dictionary& dict)
-{
-    if (sensitivity::readDict(dict))
-    {
-        if (eikonalSolver_)
-        {
-            eikonalSolver_().readDict(dict);
-        }
-
-        if (meshMovementSolver_)
-        {
-            meshMovementSolver_().readDict(dict);
-        }
-
-        return true;
-    }
-
-    return false;
-}
-
-
-void sensitivitySurfacePoints::accumulateIntegrand(const scalar dt)
-{
-    // Grab references
-    const volScalarField& p = primalVars_.p();
-    const volVectorField& U = primalVars_.U();
-
-    const volScalarField& pa = adjointVars_.pa();
-    const volVectorField& Ua = adjointVars_.Ua();
-    autoPtr<incompressibleAdjoint::adjointRASModel>& adjointTurbulence =
-        adjointVars_.adjointTurbulence();
-
-    // Solve extra equations if necessary
-    if (includeDistance_)
-    {
-        eikonalSolver_->accumulateIntegrand(dt);
-    }
-
-    if (includeMeshMovement_)
-    {
-        meshMovementSolver_->accumulateIntegrand(dt);
-    }
-
-    // Terms from the adjoint turbulence model
-    const boundaryVectorField& adjointTMsensitivities =
-        adjointTurbulence->wallShapeSensitivities();
-
-    // Objective references
-    PtrList<objective>& functions(objectiveManager_.getObjectiveFunctions());
-
-    DebugInfo
-        << "    Calculating adjoint sensitivity. " << endl;
-
-    tmp<volScalarField> tnuEff = adjointTurbulence->nuEff();
-    const volScalarField& nuEff = tnuEff.ref();
-
-    // Deal with the stress part first since it's the most awkward in terms
-    // of memory managment
-    if (includeGradStressTerm_)
-    {
-        // Terms corresponding to contributions from converting delta
-        // to thetas are added through the corresponding adjoint
-        // boundary conditions instead of grabbing contributions from
-        // the objective function.  Useful to have a unified
-        // formulation for low- and high-re meshes
-
-        tmp<volVectorField> tgradp = fvc::grad(p);
-        const volVectorField& gradp = tgradp.ref();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            tmp<vectorField> tnf = patch.nf();
-            const fvPatchVectorField& Uab = Ua.boundaryField()[patchI];
-            wallFaceSens_()[patchI] -=
-                (Uab & tnf)*gradp.boundaryField()[patchI]*dt;
-        }
-        tgradp.clear();
-
-        // We only need to modify the boundaryField of gradU locally.
-        // If grad(U) is cached then
-        // a. The .ref() call fails since the tmp is initialised from a
-        //    const ref
-        // b. we would be changing grad(U) for all other places in the code
-        //    that need it
-        // So, always allocate new memory and avoid registering the new field
-        tmp<volTensorField> tgradU =
-            volTensorField::New("gradULocal", fvc::grad(U));
-        volTensorField::Boundary& gradUbf = tgradU.ref().boundaryFieldRef();
-
-        // Explicitly correct the boundary gradient to get rid of the
-        // tangential component
-        forAll(mesh_.boundary(), patchI)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            if (isA<wallFvPatch>(patch))
-            {
-                tmp<vectorField> tnf = mesh_.boundary()[patchI].nf();
-                gradUbf[patchI] = tnf*U.boundaryField()[patchI].snGrad();
-            }
-        }
-
-        tmp<volSymmTensorField> tstress = nuEff*twoSymm(tgradU);
-        const volSymmTensorField& stress = tstress.cref();
-        autoPtr<volVectorField> ptemp
-            (Foam::createZeroFieldPtr<vector>(mesh_, "temp", sqr(dimVelocity)));
-        volVectorField& temp = ptemp.ref();
-        for (label idir = 0; idir < pTraits<vector>::nComponents; ++idir)
-        {
-            unzipRow(stress, idir, temp);
-            volTensorField gradStressDir(fvc::grad(temp));
-            for (const label patchI : sensitivityPatchIDs_)
-            {
-                const fvPatch& patch = mesh_.boundary()[patchI];
-                tmp<vectorField> tnf = patch.nf();
-                const fvPatchVectorField& Uab = Ua.boundaryField()[patchI];
-                wallFaceSens_()[patchI] +=
-                    (
-                        Uab.component(idir)
-                       *(gradStressDir.boundaryField()[patchI] & tnf)
-                    )*dt;
-            }
-        }
-    }
-
-    // Transpose part of the adjoint stresses
-    // Dealt with separately to deallocate gradUa as soon as possible
-    if (includeTransposeStresses_)
-    {
-        tmp<volTensorField> tgradUa = fvc::grad(Ua);
-        const volTensorField::Boundary& gradUabf =
-            tgradUa.cref().boundaryField();
-        for (const label patchI : sensitivityPatchIDs_)
-        {
-            const fvPatch& patch = mesh_.boundary()[patchI];
-            tmp<vectorField> tnf = patch.nf();
-            const vectorField& nf = tnf();
-            vectorField gradUaNf
-                (
-                    useSnGradInTranposeStresses_
-                  ? (Ua.boundaryField()[patchI].snGrad() & nf)*nf
-                  : (gradUabf[patchI] & nf)
-                );
-            wallFaceSens_()[patchI] -=
-                nuEff.boundaryField()[patchI]
-               *(gradUaNf & U.boundaryField()[patchI].snGrad())*tnf;
-        }
-    }
-
-    // The face-based part of the sensitivities, i.e. terms that multiply
-    // dxFace/dxPoint.
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = mesh_.boundary()[patchI];
-        tmp<vectorField> tnf = patch.nf();
-        const vectorField& nf = tnf();
-
-        // Adjoint stress term
-        // vectorField stressTerm
-        //     (
-        //        -(nf & DUa.boundaryField()[patchI])
-        //        *nuEff.boundaryField()[patchI]
-        //       & gradU.boundaryField()[patchI].T();
-        //     )
-
-        vectorField stressTerm
-        (
-          - (
-                Ua.boundaryField()[patchI].snGrad()
-              & U.boundaryField()[patchI].snGrad()
-            )
-          * nuEff.boundaryField()[patchI]
-          * nf
-        );
-
-        if (includeDivTerm_)
-        {
-            stressTerm +=
-                scalar(1./3.)*nuEff.boundaryField()[patchI]
-              * (
-                    ((Ua.boundaryField()[patchI].snGrad() &nf)*nf)
-                  & U.boundaryField()[patchI].snGrad()
-                )
-               *nf;
-        }
-
-        // Adjoint pressure terms
-        vectorField pressureTerm(patch.size(), Zero);
-        if (includePressureTerm_)
-        {
-            pressureTerm =
-            (
-                (nf*pa.boundaryField()[patchI])
-              & U.boundaryField()[patchI].snGrad()
-            )
-           *nf;
-        }
-
-        vectorField dxdbMultiplierTot(patch.size(), Zero);
-        if (includeObjective_)
-        {
-            // Term from objectives multiplying dxdb
-            forAll(functions, funcI)
-            {
-                const scalar wei = functions[funcI].weight();
-                // dt added in wallFaceSens_
-                dxdbMultiplierTot +=
-                    wei*functions[funcI].dxdbDirectMultiplier(patchI);
-
-                // Fill in multipliers of d(Sf)/db and d(nf)/db
-                dSfdbMult_()[patchI] +=
-                    wei*dt*functions[funcI].dSdbMultiplier(patchI);
-                dnfdbMult_()[patchI] +=
-                    wei*dt*functions[funcI].dndbMultiplier(patchI);
-            }
-        }
-
-        // Fill in dxFace/dxPoint multiplier.
-        // Missing geometric contributions which are directly computed on the
-        // points
-        wallFaceSens_()[patchI] +=
-        (
-            stressTerm
-          + pressureTerm
-          + adjointTMsensitivities[patchI]
-          + dxdbMultiplierTot
-        )*dt;
-    }
-
-    // Add terms from physics other than the typical incompressible flow eqns
-    adjointSolver_.additionalSensitivityMapTerms
-        (wallFaceSens_(), sensitivityPatchIDs_, dt);
-}
-
-
-void sensitivitySurfacePoints::assembleSensitivities()
-{
-    // Add remaining parts to term multiplying dxFace/dxPoints
-    // Solves for post-processing PDEs
-    finaliseFaceMultiplier();
-
-    // Geometric (or "direct") sensitivities are better computed directly on
-    // the points. Compute them and add the ones that depend on dxFace/dxPoint
-    finalisePointSensitivities();
-
-    // polyPatch::pointNormals will give the wrong result for points
-    // belonging to multiple patches or patch-processorPatch intersections.
-    // Keeping a mesh-wide field to allow easy reduction using syncTools.
-    // A bit expensive? Better way?
-    vectorField pointNormals(mesh_.nPoints(), Zero);
-    scalarField pointMagSf(mesh_.nPoints(), Zero);
-    constructGlobalPointNormalsAndAreas(pointNormals, pointMagSf);
-
-    // Do parallel communications to avoid wrong values at processor boundaries
-    // Global field for accumulation
-    vectorField pointSensGlobal(mesh_.nPoints(), Zero);
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const labelList& meshPoints = mesh_.boundaryMesh()[patchI].meshPoints();
-        forAll(meshPoints, ppI)
-        {
-            const label globaPointI = meshPoints[ppI];
-            pointSensGlobal[globaPointI] +=
-                wallPointSensVecPtr_()[patchI][ppI];
-        }
-    }
-
-    // Accumulate dJ/dx_i
-    syncTools::syncPointList
-    (
-        mesh_,
-        pointSensGlobal,
-        plusEqOp<vector>(),
-        vector::zero
-    );
-
-    // Transfer back to local fields
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const labelList& meshPoints =
-            mesh_.boundaryMesh()[patchI].meshPoints();
-        wallPointSensVecPtr_()[patchI].map(pointSensGlobal, meshPoints);
-    }
-
-    // Compute normal sens and append to return field
-    label nPassedDVs(0);
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const polyPatch& patch = mesh_.boundaryMesh()[patchI];
-        List<scalarField> procPatchSens(Pstream::nProcs());
-        //if (patch.size()>0)
-        {
-            const labelList& meshPoints = patch.meshPoints();
-
-            // Avoid storing unit point normals in the global list since we
-            // might divide multiple times with the number of faces belonging
-            // to the point. Instead do the division locally, per patch use
-            vectorField patchPointNormals(pointNormals, meshPoints);
-            patchPointNormals /= mag(patchPointNormals) + VSMALL;
-            if (!includeSurfaceArea_)
-            {
-                wallPointSensVecPtr_()[patchI] /=
-                    scalarField(pointMagSf, meshPoints);
-            }
-            wallPointSensNormalPtr_()[patchI] =
-                wallPointSensVecPtr_()[patchI]
-              & patchPointNormals;
-            wallPointSensNormalVecPtr_()[patchI] =
-                wallPointSensNormalPtr_()[patchI]
-               *patchPointNormals;
-
-            // 1. Gather sens from all processors for this patch and communicate
-            // them back. Potentially large memory overhead but the rest of the
-            // code structure assumes that all procs know all sensitivity
-            // derivatives
-            //
-            // 2. Transfer vectorial sensitivities to scalarField.
-            // Needed since the normal point vector is wrongly computed at patch
-            // boundaries and cannot be used to reconstruct a vectorial movement
-            // from just its normal component
-            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-            procPatchSens[Pstream::myProcNo()].setSize
-            (
-                3*wallPointSensNormalVecPtr_()[patchI].size()
-            );
-            scalarField& patchScalarSens = procPatchSens[Pstream::myProcNo()];
-            forAll(wallPointSensNormalVecPtr_()[patchI], ptI)
-            {
-                patchScalarSens[3*ptI] =
-                    wallPointSensNormalVecPtr_()[patchI][ptI].x();
-                patchScalarSens[3*ptI + 1] =
-                    wallPointSensNormalVecPtr_()[patchI][ptI].y();
-                patchScalarSens[3*ptI + 2] =
-                    wallPointSensNormalVecPtr_()[patchI][ptI].z();
-            }
-            Pstream::allGatherList(procPatchSens);
-
-            forAll(procPatchSens, procI)
-            {
-                const scalarField& procSens = procPatchSens[procI];
-                forAll(procSens, dvI)
-                {
-                    derivatives_[nPassedDVs + dvI] = procSens[dvI];
-                }
-                nPassedDVs += procSens.size();
-            }
-        }
-    }
-}
-
-
-void sensitivitySurfacePoints::clearSensitivities()
-{
-    // Reset terms in post-processing PDEs
-    if (includeDistance_)
-    {
-        eikonalSolver_->reset();
-    }
-    if (includeMeshMovement_)
-    {
-        meshMovementSolver_->reset();
-    }
-
-    // Reset local fields to zero
-    wallFaceSens_() = vector::zero;
-    dSfdbMult_() = vector::zero;
-    dnfdbMult_() = vector::zero;
-
-    // Reset sensitivity fields
-    adjointSensitivity::clearSensitivities();
-    shapeSensitivitiesBase::clearSensitivities();
-}
-
-
-void sensitivitySurfacePoints::write(const word& baseName)
-{
-    setSuffixName();
-    adjointSensitivity::write();
-    shapeSensitivitiesBase::write();
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-} // End namespace incompressible
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.C
deleted file mode 100644
index bcd0e1729d28788a1baac9fb9f3aa8a2fa1a2a8f..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.C
+++ /dev/null
@@ -1,346 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "sensitivityVolBSplinesIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-#include "IOmanip.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(sensitivityVolBSplines, 0);
-addToRunTimeSelectionTable
-(
-    adjointSensitivity,
-    sensitivityVolBSplines,
-    dictionary
-);
-
-// * * * * * * * * * * * Private  Member Functions  * * * * * * * * * * * * * //
-
-void sensitivityVolBSplines::computeObjectiveContributions()
-{
-    if (includeObjective_)
-    {
-        label passedCPs = 0;
-        PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-        forAll(boxes, iNURB)
-        {
-            label nb = boxes[iNURB].getControlPoints().size();
-            for (label cpI = 0; cpI < nb; cpI++)
-            {
-                vector dSdbSensCP(Zero);
-                vector dndbSensCP(Zero);
-                for (const label patchI : sensitivityPatchIDs_)
-                {
-                    tensorField dSdb
-                    (
-                        boxes[iNURB].dndbBasedSensitivities(patchI, cpI)
-                    );
-                    dSdbSensCP += gSum(dSfdbMult_()[patchI] & dSdb);
-
-                    tensorField dndb
-                    (
-                        boxes[iNURB].dndbBasedSensitivities
-                        (
-                            patchI,
-                            cpI,
-                            false
-                        )
-                    );
-                    dndbSensCP += gSum((dnfdbMult_()[patchI] & dndb));
-                }
-                dSdbSens_[passedCPs + cpI] = dSdbSensCP;
-                dndbSens_[passedCPs + cpI] = dndbSensCP;
-            }
-            passedCPs += nb;
-        }
-        volBSplinesBase_.boundControlPointMovement(dSdbSens_);
-        volBSplinesBase_.boundControlPointMovement(dndbSens_);
-
-        passedCPs = 0;
-        forAll(boxes, iNURB)
-        {
-            vectorField sensDxDbDirect =
-                boxes[iNURB].computeControlPointSensitivities
-                (
-                    dxdbDirectMult_(),
-                    sensitivityPatchIDs_.toc()
-                );
-
-            // Transfer to global list
-            forAll(sensDxDbDirect, cpI)
-            {
-                dxdbDirectSens_[passedCPs + cpI] = sensDxDbDirect[cpI];
-            }
-            passedCPs += sensDxDbDirect.size();
-        }
-        volBSplinesBase_.boundControlPointMovement(dxdbDirectSens_);
-    }
-}
-
-
-void sensitivityVolBSplines::computeBCContributions()
-{
-    label passedCPs = 0;
-    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-    forAll(boxes, iNURB)
-    {
-        vectorField sensBcsDxDb =
-            boxes[iNURB].computeControlPointSensitivities
-            (
-                bcDxDbMult_(),
-                sensitivityPatchIDs_.toc()
-            );
-
-        // Transfer to global list
-        forAll(sensBcsDxDb, cpI)
-        {
-            bcSens_[passedCPs + cpI] = sensBcsDxDb[cpI];
-        }
-        passedCPs += sensBcsDxDb.size();
-    }
-    volBSplinesBase_.boundControlPointMovement(bcSens_);
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-sensitivityVolBSplines::sensitivityVolBSplines
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    SIBase(mesh, dict, adjointSolver),
-    volBSplinesBase_
-    (
-        const_cast<volBSplinesBase&>(volBSplinesBase::New(mesh))
-    ),
-
-    flowSens_(0),
-    dSdbSens_(0),
-    dndbSens_(0),
-    dxdbDirectSens_(0),
-    bcSens_(0),
-
-    derivativesFolder_("optimisation"/type() + "Derivatives")
-{
-    // No boundary field pointers need to be allocated
-    const label nCPs(volBSplinesBase_.getTotalControlPointsNumber());
-    derivatives_ = scalarField(3*nCPs, Zero);
-    flowSens_ = vectorField(nCPs, Zero);
-    dSdbSens_ = vectorField(nCPs, Zero);
-    dndbSens_ = vectorField(nCPs, Zero);
-    dxdbDirectSens_ = vectorField(nCPs, Zero);
-    bcSens_ = vectorField(nCPs, Zero);
-
-    // Create folder to store sensitivities
-    mkDir(derivativesFolder_);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void sensitivityVolBSplines::assembleSensitivities()
-{
-    // Assemble the sensitivity map
-    // Solves for the post-processing equations and adds their contribution to
-    // the sensitivity map
-    surfaceSensitivity_.assembleSensitivities();
-
-    // Finalise sensitivities including dxFace/db
-    const boundaryVectorField& faceSens =
-        surfaceSensitivity_.getWallFaceSensVecBoundary();
-
-    label passedCPs(0);
-    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-    forAll(boxes, iNURB)
-    {
-        vectorField sens =
-            boxes[iNURB].computeControlPointSensitivities
-            (
-                faceSens,
-                sensitivityPatchIDs_.toc()
-            );
-        // Transfer to global list
-        forAll(sens, cpI)
-        {
-            flowSens_[passedCPs + cpI] = sens[cpI];
-        }
-        passedCPs += sens.size();
-    }
-    volBSplinesBase_.boundControlPointMovement(flowSens_);
-
-    // Contribution from objective function
-    // Note:
-    // includeObjectiveContribution has to be set to false (false by default)
-    // in surfaceSensitivity, in order to avoid computing this term twice.
-    // Optionally avoided altogether if includeObjectiveContribution is set to
-    // false for sensitivityVolBSplines
-    computeObjectiveContributions();
-
-    computeBCContributions();
-
-    // Transform sensitivites to scalarField in order to cooperate with
-    // updateMethod
-    forAll(flowSens_, cpI)
-    {
-        derivatives_[3*cpI] =
-            flowSens_[cpI].x()
-          + dSdbSens_[cpI].x()
-          + dndbSens_[cpI].x()
-          + dxdbDirectSens_[cpI].x()
-          + bcSens_[cpI].x();
-        derivatives_[3*cpI + 1] =
-            flowSens_[cpI].y()
-          + dSdbSens_[cpI].y()
-          + dndbSens_[cpI].y()
-          + dxdbDirectSens_[cpI].y()
-          + bcSens_[cpI].y();
-        derivatives_[3*cpI + 2] =
-            flowSens_[cpI].z()
-          + dSdbSens_[cpI].z()
-          + dndbSens_[cpI].z()
-          + dxdbDirectSens_[cpI].z()
-          + bcSens_[cpI].z();
-    }
-}
-
-
-void sensitivityVolBSplines::clearSensitivities()
-{
-    flowSens_ = vector::zero;
-    dSdbSens_ = vector::zero;
-    dndbSens_ = vector::zero;
-    dxdbDirectSens_ = vector::zero;
-    bcSens_ = vector::zero;
-
-    SIBase::clearSensitivities();
-}
-
-
-void sensitivityVolBSplines::write(const word& baseName)
-{
-    Info<< "Writing control point sensitivities to file" << endl;
-    // Write sensitivity map
-    SIBase::write(baseName);
-    // Write control point sensitivities
-    if (Pstream::master())
-    {
-        OFstream derivFile
-        (
-             derivativesFolder_/
-                 baseName + adjointVars_.solverName() + mesh_.time().timeName()
-        );
-        unsigned int widthDV =
-            max(int(Foam::name(derivatives_.size()).size()), int(3));
-        unsigned int width = IOstream::defaultPrecision() + 7;
-        derivFile
-            << setw(widthDV) << "#cp" << " "
-            << setw(width) << "total::x"<< " "
-            << setw(width) << "total::y"<< " "
-            << setw(width) << "total::z"<< " "
-            << setw(width) << "flow::x" << " "
-            << setw(width) << "flow::y" << " "
-            << setw(width) << "flow::z" << " "
-            << setw(width) << "dSdb::x" << " "
-            << setw(width) << "dSdb::y" << " "
-            << setw(width) << "dSdb::z" << " "
-            << setw(width) << "dndb::x" << " "
-            << setw(width) << "dndb::y" << " "
-            << setw(width) << "dndb::z" << " "
-            << setw(width) << "dxdbDirect::x" << " "
-            << setw(width) << "dxdbDirect::y" << " "
-            << setw(width) << "dxdbDirect::z" << " "
-            << setw(width) << "dvdb::x" << " "
-            << setw(width) << "dvdb::y" << " "
-            << setw(width) << "dvdb::z" << endl;
-
-        label passedCPs(0);
-        label lastActive(-1);
-        PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-        forAll(boxes, iNURB)
-        {
-            label nb = boxes[iNURB].getControlPoints().size();
-            const boolList& activeCPs = boxes[iNURB].getActiveCPs();
-            for (label iCP = 0; iCP < nb; iCP++)
-            {
-                if (activeCPs[iCP])
-                {
-                    label globalCP = passedCPs + iCP;
-                    if (globalCP!=lastActive + 1)
-                    {
-                        derivFile << "\n";
-                    }
-                    lastActive = globalCP;
-
-                    derivFile
-                       << setw(widthDV) << globalCP << " "
-                       << setw(width) << derivatives_[3*globalCP]  << " "
-                       << setw(width) << derivatives_[3*globalCP + 1]  << " "
-                       << setw(width) << derivatives_[3*globalCP + 2]  << " "
-                       << setw(width) << flowSens_[globalCP].x() << " "
-                       << setw(width) << flowSens_[globalCP].y() << " "
-                       << setw(width) << flowSens_[globalCP].z() << " "
-                       << setw(width) << dSdbSens_[globalCP].x() << " "
-                       << setw(width) << dSdbSens_[globalCP].y() << " "
-                       << setw(width) << dSdbSens_[globalCP].z() << " "
-                       << setw(width) << dndbSens_[globalCP].x() << " "
-                       << setw(width) << dndbSens_[globalCP].y() << " "
-                       << setw(width) << dndbSens_[globalCP].z() << " "
-                       << setw(width) << dxdbDirectSens_[globalCP].x() << " "
-                       << setw(width) << dxdbDirectSens_[globalCP].y() << " "
-                       << setw(width) << dxdbDirectSens_[globalCP].z() << " "
-                       << setw(width) << bcSens_[globalCP].x() << " "
-                       << setw(width) << bcSens_[globalCP].y() << " "
-                       << setw(width) << bcSens_[globalCP].z()
-                       << endl;
-                }
-            }
-            passedCPs += nb;
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.H
deleted file mode 100644
index 6343fcf2da2ec4ddc5590f51b20a1f381e3a58b7..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.H
+++ /dev/null
@@ -1,148 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-Class
-    Foam::incompressible::sensitivityVolBSplines
-
-Description
-    Calculation of adjoint based sensitivities at vol B-Splines control points
-    using the SI or e-SI approach (determined by surface sensitivities)
-
-SourceFiles
-    sensitivityVolBSplines.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef sensitivityVolBSplinesIncompressible_H
-#define sensitivityVolBSplinesIncompressible_H
-
-#include "SIBaseIncompressible.H"
-#include "volBSplinesBase.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                   Class sensitivityVolBSplines Declaration
-\*---------------------------------------------------------------------------*/
-
-class sensitivityVolBSplines
-:
-    public SIBase
-{
-protected:
-
-    // Protected data
-
-        //- Reference to underlaying volumetric B-Splines morpher
-        volBSplinesBase& volBSplinesBase_;
-
-        //- Flow related term
-        vectorField flowSens_;
-
-        //- Term depending on delta(n dS)/delta b
-        vectorField dSdbSens_;
-
-        //- Term depending on delta (n)/delta b
-        vectorField dndbSens_;
-
-        //- Term depending on dxdb for objective functions directly depending
-        //- on x
-        vectorField dxdbDirectSens_;
-
-        //- Term dependng on the differentiation of boundary conditions
-        vectorField bcSens_;
-
-        fileName derivativesFolder_;
-
-
-    // Protected Member Functions
-
-        void computeObjectiveContributions();
-        void computeBCContributions();
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        sensitivityVolBSplines(const sensitivityVolBSplines&) = delete;
-
-        //- No copy assignment
-        void operator=(const sensitivityVolBSplines&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("volumetricBSplines");
-
-
-    // Constructors
-
-        //- Construct from components
-        sensitivityVolBSplines
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-
-    //- Destructor
-    virtual ~sensitivityVolBSplines() = default;
-
-
-    // Member Functions
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities();
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
-
-        //- Write sensitivities to file
-        virtual void write(const word& baseName = word::null);
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.C
deleted file mode 100644
index abd779ab9e8c8d60d10863fde542fa7ce3d950df..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.C
+++ /dev/null
@@ -1,409 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "sensitivityVolBSplinesFIIncompressible.H"
-#include "pointVolInterpolation.H"
-#include "IOmanip.H"
-#include "addToRunTimeSelectionTable.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(sensitivityVolBSplinesFI, 0);
-addToRunTimeSelectionTable
-(
-    adjointSensitivity,
-    sensitivityVolBSplinesFI,
-    dictionary
-);
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-sensitivityVolBSplinesFI::sensitivityVolBSplinesFI
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    FIBase(mesh, dict, adjointSolver),
-    volBSplinesBase_
-    (
-        const_cast<volBSplinesBase&>(volBSplinesBase::New(mesh))
-    ),
-    flowSens_(0),
-    dSdbSens_(0),
-    dndbSens_(0),
-    dxdbDirectSens_(0),
-    dVdbSens_(0),
-    distanceSens_(0),
-    optionsSens_(0),
-    bcSens_(0),
-
-    derivativesFolder_("optimisation"/type() + "Derivatives")
-{
-    // No boundary field pointers need to be allocated
-
-    label nCPs = volBSplinesBase_.getTotalControlPointsNumber();
-    derivatives_ = scalarField(3*nCPs, Zero);
-    flowSens_ = vectorField(nCPs, Zero);
-    dSdbSens_ = vectorField(nCPs, Zero);
-    dndbSens_ = vectorField(nCPs, Zero);
-    dxdbDirectSens_ = vectorField(nCPs, Zero);
-    dVdbSens_ = vectorField(nCPs, Zero);
-    distanceSens_ = vectorField(nCPs, Zero);
-    optionsSens_ = vectorField(nCPs, Zero);
-    bcSens_ = vectorField(nCPs, Zero);
-
-    // Create folder to store sensitivities
-    mkDir(derivativesFolder_);
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void sensitivityVolBSplinesFI::assembleSensitivities()
-{
-    /*
-    addProfiling
-    (
-        sensitivityVolBSplinesFI,
-        "sensitivityVolBSplinesFI::assembleSensitivities"
-    );
-    */
-    read();
-
-    // Interpolation engine
-    pointVolInterpolation volPointInter(pointMesh::New(mesh_), mesh_);
-
-    // Adjoint to the eikonal equation
-    autoPtr<volTensorField> distanceSensPtr(nullptr);
-    if (includeDistance_)
-    {
-        // Solver equation
-        eikonalSolver_->solve();
-
-        // Allocate memory and compute grad(dxdb) multiplier
-        distanceSensPtr.reset
-        (
-            createZeroFieldPtr<tensor>
-            (
-                mesh_,
-                "distanceSensPtr",
-                dimensionSet(0, 2, -3, 0, 0, 0, 0)
-            )
-        );
-        distanceSensPtr() = eikonalSolver_->getFISensitivityTerm()().T();
-    }
-
-    // Integration
-    label passedCPs(0);
-    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-    forAll(boxes, iNURB)
-    {
-        const label nb(boxes[iNURB].getControlPoints().size());
-        vectorField boxSensitivities(nb, Zero);
-
-        vectorField dxdbSens = boxes[iNURB].computeControlPointSensitivities
-        (
-            dxdbDirectMult_(),
-            sensitivityPatchIDs_.toc()
-        );
-
-        vectorField bcSens = boxes[iNURB].computeControlPointSensitivities
-        (
-            bcDxDbMult_(),
-            sensitivityPatchIDs_.toc()
-        );
-
-        for (label cpI = 0; cpI < nb; cpI++)
-        {
-            label globalCP = passedCPs + cpI;
-
-            // Parameterization info
-            tmp<volTensorField> tvolDxDbI
-            (
-                volPointInter.interpolate(boxes[iNURB].getDxDb(cpI))
-            );
-            const volTensorField& volDxDbI = tvolDxDbI();
-
-            // Chain rule used to get dx/db at cells
-            // Gives practically the same results at a much higher CPU cost
-            /*
-            tmp<volTensorField> tvolDxDbI(boxes[iNURB].getDxCellsDb(cpI));
-            volTensorField& volDxDbI = tvolDxDbI.ref();
-            */
-
-            const tensorField& gradDxDbMultInt = gradDxDbMult_.primitiveField();
-            for (label idir = 0; idir < pTraits<vector>::nComponents; ++idir)
-            {
-                // Gradient of parameterization info
-                auto ttemp =
-                    tmp<volVectorField>::New
-                    (
-                        IOobject
-                        (
-                            "dxdb",
-                            mesh_.time().timeName(),
-                            mesh_,
-                            IOobject::NO_READ,
-                            IOobject::NO_WRITE
-                        ),
-                        mesh_,
-                        dimensionedVector(dimless, Zero)
-                    );
-                volVectorField& temp = ttemp.ref();
-                unzipCol(volDxDbI, vector::components(idir), temp);
-
-                volTensorField gradDxDb(fvc::grad(temp));
-                // Volume integral terms
-                flowSens_[globalCP].component(idir) = gSum
-                (
-                    (gradDxDbMultInt && gradDxDb.primitiveField())
-                   *mesh_.V()
-                );
-
-                if (includeDistance_)
-                {
-                    const tensorField& distSensInt =
-                        distanceSensPtr().primitiveField();
-                    distanceSens_[globalCP].component(idir) =
-                        gSum
-                        (
-                            (distSensInt && gradDxDb.primitiveField())
-                            *mesh_.V()
-                        );
-                }
-            }
-
-            // Contribution from objective function term from
-            // delta( n dS ) / delta b and
-            // delta ( x )   / delta b
-            // for objectives directly depending on x
-            for (const label patchI : sensitivityPatchIDs_)
-            {
-                tensorField dSdb
-                (
-                    boxes[iNURB].dndbBasedSensitivities(patchI, cpI)
-                );
-                dSdbSens_[globalCP] += gSum(dSfdbMult_()[patchI] & dSdb);
-                tensorField dndb
-                (
-                    boxes[iNURB].dndbBasedSensitivities(patchI, cpI, false)
-                );
-                dndbSens_[globalCP] += gSum((dnfdbMult_()[patchI] & dndb));
-            }
-
-            // Contribution from delta (V) / delta b
-            // For objectives defined as volume integrals only
-            dVdbSens_[globalCP] +=
-                gSum
-                (
-                    divDxDbMult_
-                   *fvc::div(T(volDxDbI))().primitiveField()
-                   *mesh_.V()
-                );
-
-            // Terms from fvOptions
-            optionsSens_[globalCP] +=
-                gSum((optionsDxDbMult_ & volDxDbI.primitiveField())*mesh_.V());
-
-            // dxdbSens storage
-            dxdbDirectSens_[globalCP] = dxdbSens[cpI];
-
-            // bcSens storage
-            bcSens_[globalCP] = bcSens[cpI];
-
-            boxSensitivities[cpI] =
-                flowSens_[globalCP]
-              + dSdbSens_[globalCP]
-              + dndbSens_[globalCP]
-              + dVdbSens_[globalCP]
-              + distanceSens_[globalCP]
-              + dxdbDirectSens_[globalCP]
-              + optionsSens_[globalCP]
-              + bcSens_[globalCP];
-        }
-
-        // Zero sensitivities in non-active design variables
-        boxes[iNURB].boundControlPointMovement(boxSensitivities);
-
-        // Transfer sensitivities to global list
-        for (label cpI = 0; cpI < nb; cpI++)
-        {
-            label globalCP = passedCPs + cpI;
-            derivatives_[3*globalCP] = boxSensitivities[cpI].x();
-            derivatives_[3*globalCP + 1] = boxSensitivities[cpI].y();
-            derivatives_[3*globalCP + 2] = boxSensitivities[cpI].z();
-        }
-
-        // Increment number of passed sensitivities
-        passedCPs += nb;
-    }
-
-    // Zero non-active sensitivity components.
-    // For consistent output only, does not affect optimisation
-    volBSplinesBase_.boundControlPointMovement(flowSens_);
-    volBSplinesBase_.boundControlPointMovement(dSdbSens_);
-    volBSplinesBase_.boundControlPointMovement(dndbSens_);
-    volBSplinesBase_.boundControlPointMovement(dVdbSens_);
-    volBSplinesBase_.boundControlPointMovement(distanceSens_);
-    volBSplinesBase_.boundControlPointMovement(dxdbDirectSens_);
-    volBSplinesBase_.boundControlPointMovement(optionsSens_);
-    volBSplinesBase_.boundControlPointMovement(bcSens_);
-
-  //profiling::writeNow();
-}
-
-
-void sensitivityVolBSplinesFI::clearSensitivities()
-{
-    flowSens_ = vector::zero;
-    dSdbSens_ = vector::zero;
-    dndbSens_ = vector::zero;
-    dxdbDirectSens_ = vector::zero;
-    dVdbSens_ = vector::zero;
-    distanceSens_ = vector::zero;
-    optionsSens_ = vector::zero;
-    bcSens_ = vector::zero;
-
-    FIBase::clearSensitivities();
-}
-
-
-void sensitivityVolBSplinesFI::write(const word& baseName)
-{
-    Info<< "Writing control point sensitivities to file" << endl;
-    if (Pstream::master())
-    {
-        OFstream derivFile
-        (
-             derivativesFolder_/
-                baseName + adjointVars_.solverName() + mesh_.time().timeName()
-        );
-        unsigned int widthDV
-        (
-            max(int(Foam::name(flowSens_.size()).size()), int(3))
-        );
-        unsigned int width = IOstream::defaultPrecision() + 7;
-        derivFile
-            << setw(widthDV) << "#cp" << " "
-            << setw(width) << "total::x" << " "
-            << setw(width) << "total::y" << " "
-            << setw(width) << "total::z" << " "
-            << setw(width) << "flow::x" << " "
-            << setw(width) << "flow::y" << " "
-            << setw(width) << "flow::z" << " "
-            << setw(width) << "dSdb::x" << " "
-            << setw(width) << "dSdb::y" << " "
-            << setw(width) << "dSdb::z" << " "
-            << setw(width) << "dndb::x" << " "
-            << setw(width) << "dndb::y" << " "
-            << setw(width) << "dndb::z" << " "
-            << setw(width) << "dxdbDirect::x" << " "
-            << setw(width) << "dxdbDirect::y" << " "
-            << setw(width) << "dxdbDirect::z" << " "
-            << setw(width) << "dVdb::x" << " "
-            << setw(width) << "dVdb::y" << " "
-            << setw(width) << "dVdb::z" << " "
-            << setw(width) << "distance::x" << " "
-            << setw(width) << "distance::y" << " "
-            << setw(width) << "distance::z" << " "
-            << setw(width) << "options::x" << " "
-            << setw(width) << "options::y" << " "
-            << setw(width) << "options::z" << " "
-            << setw(width) << "dvdb::x" << " "
-            << setw(width) << "dvdb::y" << " "
-            << setw(width) << "dvdb::z" << endl;
-
-        label passedCPs(0);
-        label lastActive(-1);
-        PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-        forAll(boxes, iNURB)
-        {
-            label nb = boxes[iNURB].getControlPoints().size();
-            const boolList& activeCPs = boxes[iNURB].getActiveCPs();
-            for (label iCP = 0; iCP < nb; iCP++)
-            {
-                if (activeCPs[iCP])
-                {
-                    label globalCP = passedCPs + iCP;
-                    if (globalCP!=lastActive + 1) derivFile << "\n";
-                    lastActive = globalCP;
-
-                    derivFile
-                        << setw(widthDV) << globalCP << " "
-                        << setw(width) << derivatives_[3*globalCP] << " "
-                        << setw(width) << derivatives_[3*globalCP + 1] << " "
-                        << setw(width) << derivatives_[3*globalCP + 2] << " "
-                        << setw(width) << flowSens_[globalCP].x() << " "
-                        << setw(width) << flowSens_[globalCP].y() << " "
-                        << setw(width) << flowSens_[globalCP].z() << " "
-                        << setw(width) << dSdbSens_[globalCP].x() << " "
-                        << setw(width) << dSdbSens_[globalCP].y() << " "
-                        << setw(width) << dSdbSens_[globalCP].z() << " "
-                        << setw(width) << dndbSens_[globalCP].x() << " "
-                        << setw(width) << dndbSens_[globalCP].y() << " "
-                        << setw(width) << dndbSens_[globalCP].z() << " "
-                        << setw(width) << dxdbDirectSens_[globalCP].x() << " "
-                        << setw(width) << dxdbDirectSens_[globalCP].y() << " "
-                        << setw(width) << dxdbDirectSens_[globalCP].z() << " "
-                        << setw(width) << dVdbSens_[globalCP].x() << " "
-                        << setw(width) << dVdbSens_[globalCP].y() << " "
-                        << setw(width) << dVdbSens_[globalCP].z() << " "
-                        << setw(width) << distanceSens_[globalCP].x() << " "
-                        << setw(width) << distanceSens_[globalCP].y() << " "
-                        << setw(width) << distanceSens_[globalCP].z() << " "
-                        << setw(width) << optionsSens_[globalCP].x() << " "
-                        << setw(width) << optionsSens_[globalCP].y() << " "
-                        << setw(width) << optionsSens_[globalCP].z() << " "
-                        << setw(width) << bcSens_[globalCP].x() << " "
-                        << setw(width) << bcSens_[globalCP].y() << " "
-                        << setw(width) << bcSens_[globalCP].z() << endl;
-                }
-            }
-            passedCPs += nb;
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.H
deleted file mode 100644
index 7077df12baca023e6521991ae364001dda085461..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.H
+++ /dev/null
@@ -1,151 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-Class
-    Foam::incompressible::sensitivityVolBSplinesFI
-
-Description
-    Calculation of adjoint based sensitivities at vol B-Splines control points
-    using the FI approach.
-
-SourceFiles
-    sensitivityVolBSplinesFI.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef sensitivityVolBSplinesFIIncompressible_H
-#define sensitivityVolBSplinesFIIncompressible_H
-
-#include "FIBaseIncompressible.H"
-#include "volBSplinesBase.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                  Class sensitivityVolBSplinesFI Declaration
-\*---------------------------------------------------------------------------*/
-
-class sensitivityVolBSplinesFI
-:
-    public FIBase
-{
-protected:
-
-    // Protected data
-
-        //- Reference to underlaying volumetric B-Splines morpher
-        volBSplinesBase& volBSplinesBase_;
-
-        //- Flow related term
-        vectorField flowSens_;
-
-        //- Term depending on delta(n dS)/delta b
-        vectorField dSdbSens_;
-
-        //- Term depending on delta(n)/delta b
-        vectorField dndbSens_;
-
-        //- Term depending on delta(x)/delta b for objectives that directly
-        //- depend on x
-        vectorField dxdbDirectSens_;
-
-        //- Term depending on delta(V)/delta b
-        vectorField dVdbSens_;
-
-        //- Term depending on distance differentiation
-        vectorField distanceSens_;
-
-        //- Term depending on fvOptions
-        vectorField optionsSens_;
-
-        //- Term depending on the differentiation of boundary conditions
-        vectorField bcSens_;
-
-        fileName derivativesFolder_;
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        sensitivityVolBSplinesFI(const sensitivityVolBSplinesFI&) = delete;
-
-        //- No copy assignment
-        void operator=(const sensitivityVolBSplinesFI&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("volumetricBSplinesFI");
-
-
-    // Constructors
-
-        //- Construct from components
-        sensitivityVolBSplinesFI
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-
-    //- Destructor
-    virtual ~sensitivityVolBSplinesFI() = default;
-
-
-    // Member Functions
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities();
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
-
-        //- Write sensitivities to file
-        virtual void write(const word& baseName = word::null);
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.C
deleted file mode 100644
index 9f765dbae5ebba14e5f9d1d102341f3827cf2c4e..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.C
+++ /dev/null
@@ -1,176 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2020 PCOpt/NTUA
-    Copyright (C) 2020 FOSS GP
--------------------------------------------------------------------------------
-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 "shapeSensitivitiesIncompressible.H"
-#include "adjointBoundaryConditions.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(shapeSensitivities, 0);
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void shapeSensitivities::accumulateDirectSensitivityIntegrand(const scalar dt)
-{
-    // Accumulate direct sensitivities
-    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const scalarField magSfDt(mesh_.boundary()[patchI].magSf()*dt);
-        for (objective& func : functions)
-        {
-            const scalar wei(func.weight());
-            dSfdbMult_()[patchI] += wei*func.dSdbMultiplier(patchI)*dt;
-            dnfdbMult_()[patchI] += wei*func.dndbMultiplier(patchI)*magSfDt;
-            dxdbDirectMult_()[patchI] +=
-                wei*func.dxdbDirectMultiplier(patchI)*magSfDt;
-        }
-    }
-}
-
-
-void shapeSensitivities::accumulateBCSensitivityIntegrand(const scalar dt)
-{
-    // Avoid updating the event number to keep consistency with cases caching
-    // gradUa
-    auto& UaBoundary = adjointVars_.Ua().boundaryFieldRef(false);
-    tmp<boundaryVectorField> DvDbMult(dvdbMult());
-
-    // Accumulate sensitivities due to boundary conditions
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const scalarField magSfDt(mesh_.boundary()[patchI].magSf()*dt);
-        fvPatchVectorField& Uab = UaBoundary[patchI];
-        if (isA<adjointVectorBoundaryCondition>(Uab))
-        {
-            bcDxDbMult_()[patchI] +=
-            (
-                DvDbMult()[patchI]
-              & refCast<adjointVectorBoundaryCondition>(Uab).dxdbMult()
-            )*magSfDt;
-        }
-    }
-}
-
-
-tmp<boundaryVectorField> shapeSensitivities::dvdbMult() const
-{
-    tmp<boundaryVectorField>
-        tres(createZeroBoundaryPtr<vector>(meshShape_).ptr());
-    boundaryVectorField& res = tres.ref();
-
-    // Grab references
-    const volScalarField& pa = adjointVars_.pa();
-    const volVectorField& Ua = adjointVars_.Ua();
-    const autoPtr<incompressibleAdjoint::adjointRASModel>& adjointTurbulence =
-        adjointVars_.adjointTurbulence();
-
-    // Fields needed to calculate adjoint sensitivities
-    const autoPtr<incompressible::RASModelVariables>&
-       turbVars = primalVars_.RASModelVariables();
-    const singlePhaseTransportModel& lamTransp = primalVars_.laminarTransport();
-    volScalarField nuEff(lamTransp.nu() + turbVars->nutRef());
-    tmp<volTensorField> tgradUa = fvc::grad(Ua);
-    const volTensorField::Boundary& gradUabf = tgradUa.cref().boundaryField();
-
-    for (const label patchI : sensitivityPatchIDs_)
-    {
-        const fvPatch& patch = meshShape_.boundary()[patchI];
-        tmp<vectorField> tnf = patch.nf();
-        const vectorField& nf = tnf();
-
-        res[patchI] =
-            (
-                nuEff.boundaryField()[patchI]
-              * (
-                    Ua.boundaryField()[patchI].snGrad()
-                  + (gradUabf[patchI] & nf)
-                )
-            )
-          - (nf*pa.boundaryField()[patchI])
-          + adjointTurbulence().adjointMomentumBCSource()[patchI];
-    }
-
-    return tres;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-shapeSensitivities::shapeSensitivities
-(
-    const fvMesh& mesh,
-    const dictionary& dict,
-    incompressibleAdjointSolver& adjointSolver
-)
-:
-    adjointSensitivity(mesh, dict, adjointSolver),
-    shapeSensitivitiesBase(mesh, dict),
-    dSfdbMult_(createZeroBoundaryPtr<vector>(mesh_)),
-    dnfdbMult_(createZeroBoundaryPtr<vector>(mesh_)),
-    dxdbDirectMult_(createZeroBoundaryPtr<vector>(mesh_)),
-    bcDxDbMult_(createZeroBoundaryPtr<vector>(mesh_))
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void shapeSensitivities::clearSensitivities()
-{
-    dSfdbMult_() = vector::zero;
-    dnfdbMult_() = vector::zero;
-    dxdbDirectMult_() = vector::zero;
-    bcDxDbMult_() = vector::zero;
-
-    adjointSensitivity::clearSensitivities();
-    shapeSensitivitiesBase::clearSensitivities();
-}
-
-
-void shapeSensitivities::write(const word& baseName)
-{
-    adjointSensitivity::write(baseName);
-    shapeSensitivitiesBase::write();
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.H
deleted file mode 100644
index 100ea1748d8f6efeda6e58030d95941d67620cbf..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.H
+++ /dev/null
@@ -1,141 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2020 PCOpt/NTUA
-    Copyright (C) 2020 FOSS GP
--------------------------------------------------------------------------------
-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/>.
-
-Class
-    Foam::incompressible::shapeSensitivitiesBase
-
-Description
-    Base class supporting shape sensitivity derivatives for
-    incompressible flows
-
-SourceFiles
-    shapeSensitivitiesBase.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef shapeSensitivitiesIncompressible_H
-#define shapeSensitivitiesIncompressible_H
-
-#include "adjointSensitivityIncompressible.H"
-#include "shapeSensitivitiesBase.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class shapeSensitivities Declaration
-\*---------------------------------------------------------------------------*/
-
-class shapeSensitivities
-:
-    public adjointSensitivity,
-    public shapeSensitivitiesBase
-{
-protected:
-
-    // Protected data
-
-        //- Fields related to direct sensitivities
-        autoPtr<boundaryVectorField> dSfdbMult_;
-        autoPtr<boundaryVectorField> dnfdbMult_;
-        autoPtr<boundaryVectorField> dxdbDirectMult_;
-        autoPtr<boundaryVectorField> bcDxDbMult_;
-
-
-    // Protected Member Fuctions
-
-        //- Accumulate direct sensitivities
-        virtual void accumulateDirectSensitivityIntegrand(const scalar dt);
-
-        //- Accumulate sensitivities enamating from the boundary conditions
-        virtual void accumulateBCSensitivityIntegrand(const scalar dt);
-
-        //- Compute multiplier of dv_i/db
-        tmp<boundaryVectorField> dvdbMult() const;
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        shapeSensitivities(const shapeSensitivities&) = delete;
-
-        //- No copy assignment
-        void operator=(const shapeSensitivities&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("shapeSensitivities");
-
-
-    // Constructors
-
-        //- Construct from components
-        shapeSensitivities
-        (
-            const fvMesh& mesh,
-            const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
-        );
-
-
-    //- Destructor
-    virtual ~shapeSensitivities() = default;
-
-
-    // Member Functions
-
-        //- Accumulate sensitivity integrands
-        virtual void accumulateIntegrand(const scalar dt) = 0;
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities() = 0;
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
-
-        //- Write sensitivity fields.
-        virtual void write(const word& baseName = word::null);
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.C
index 6694ed50d76ccb990c0082a29be97edb70b90994..787ee0187698fb5573e1b232ff062a2cb58c4c84 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -48,18 +48,13 @@ Foam::sensitivity::sensitivity
 :
     mesh_(mesh),
     dict_(dict),
+    writeFieldSens_(dict.getOrDefault<bool>("writeFieldSens", false)),
     fieldSensPtr_(nullptr)
 {}
 
 
 // * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
 
-const Foam::dictionary& Foam::sensitivity::dict() const
-{
-    return dict_;
-}
-
-
 bool Foam::sensitivity::readDict(const dictionary& dict)
 {
     dict_ = dict;
@@ -68,15 +63,9 @@ bool Foam::sensitivity::readDict(const dictionary& dict)
 }
 
 
-void Foam::sensitivity::computeDerivativesSize()
-{
-    // Does nothing
-}
-
-
 void Foam::sensitivity::write(const word& baseName)
 {
-    if (fieldSensPtr_)
+    if (fieldSensPtr_ && writeFieldSens_)
     {
         fieldSensPtr_->write();
     }
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.H
index aa706a913b94a7f5c863f0464ed9e2e645d79ad4..9a4790c2cbbec73f161d61de41f111de1b69d368 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/sensitivity/sensitivity.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -57,6 +57,9 @@ SourceFiles
 namespace Foam
 {
 
+// Forward declaration
+class designVariables;
+
 /*---------------------------------------------------------------------------*\
                          Class sensitivity Declaration
 \*---------------------------------------------------------------------------*/
@@ -69,14 +72,13 @@ protected:
 
         const fvMesh& mesh_;
         dictionary dict_;
+        bool writeFieldSens_;
 
         // Field sensitivities. Topology optimisation
         //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         autoPtr<volScalarField> fieldSensPtr_;
 
 
-    // Protected Member Functions
-
 private:
 
     // Private Member Functions
@@ -108,27 +110,40 @@ public:
 
     // Member Functions
 
+        //- Return reference to mesh
+        inline const fvMesh& mesh() const
+        {
+            return mesh_;
+        }
+
         //- Return the construction dictionary
-        const dictionary& dict() const;
+        inline const dictionary& dict() const
+        {
+            return
+                dict_.optionalSubDict(mesh_.name()).
+                    optionalSubDict("sensitivities");
+        }
 
         //- Read dictionary if changed
         virtual bool readDict(const dictionary& dict);
 
-        //- Compute design variables number. Does nothing in the base
-        //  Used to get the correct design variables number when
-        //  setSensitivityPatchIDs are not set in the constructor
-        virtual void computeDerivativesSize();
+        //- Calculates and returns sensitivity field
+        virtual const scalarField& calculateSensitivities
+        (
+            autoPtr<designVariables>& designVars
+        ) = 0;
 
-        //- Calculates and returns sensitivity fields.
-        //  Used with optimisation libraries
-        virtual const scalarField& calculateSensitivities() = 0;
+        //- Get the fieldSensPtr
+        inline const autoPtr<volScalarField>& fieldSensPtr() const
+        {
+            return fieldSensPtr_;
+        }
 
         //- Write sensitivity fields.
         //  If valid, copies boundaryFields to volFields and writes them.
         //  Virtual to be reimplemented by control points-based methods
         //  (Bezier, RBF) which do not need to write fields
         virtual void write(const word& baseName = word::null);
-
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.C
new file mode 100644
index 0000000000000000000000000000000000000000..459b4ed2d669f1105477c8b8f1cb55fea2036bb5
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.C
@@ -0,0 +1,281 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "designVariables.H"
+#include "adjointSensitivity.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(designVariables, 0);
+    defineRunTimeSelectionTable(designVariables, designVariables);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::designVariables::readBounds
+(
+    autoPtr<scalar> lowerBoundPtr,
+    autoPtr<scalar> upperBoundPtr
+)
+{
+    // Read lower bounds for the design variables, if present
+    if (dict_.found("lowerBounds"))
+    {
+        scalarField lowerBounds(dict_.get<scalarField>("lowerBounds"));
+        if (lowerBounds.size() != getVars().size())
+        {
+            FatalErrorInFunction
+                << "Inconsistent dimensions for lowerBounds ("
+                << lowerBounds.size()
+                << ") and design variables ("
+                << getVars().size() << ")"
+                << exit(FatalError);
+        }
+        lowerBounds_.reset(new scalarField(lowerBounds));
+    }
+    else if (dict_.found("lowerBound"))
+    {
+        scalar lowerBound(dict_.get<scalar>("lowerBound"));
+        lowerBounds_.reset(new scalarField(getVars().size(), lowerBound));
+    }
+    else if (lowerBoundPtr.valid())
+    {
+        lowerBounds_.reset(new scalarField(getVars().size(), lowerBoundPtr()));
+    }
+
+    // Read upper bounds for the design variables, if present
+    if (dict_.found("upperBounds"))
+    {
+        scalarField upperBounds(dict_.get<scalarField>("upperBounds"));
+        if (upperBounds.size() != getVars().size())
+        {
+            FatalErrorInFunction
+                << "Inconsistent dimensions for upperBounds ("
+                << upperBounds.size()
+                << ") and design variables ("
+                << getVars().size() << ")"
+                << exit(FatalError);
+        }
+        upperBounds_.reset(new scalarField(upperBounds));
+    }
+    else if (dict_.found("upperBound"))
+    {
+        scalar upperBound(dict_.get<scalar>("upperBound"));
+        upperBounds_.reset(new scalarField(getVars().size(), upperBound));
+    }
+    else if (upperBoundPtr.valid())
+    {
+        upperBounds_.reset(new scalarField(getVars().size(), upperBoundPtr()));
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::designVariables::designVariables
+(
+    fvMesh& mesh,
+    const dictionary& dict
+)
+:
+    scalarField(0),
+    mesh_(mesh),
+    dict_(dict),
+    activeDesignVariables_(0),
+    oldDesignVariables_(nullptr),
+    maxInitChange_(nullptr),
+    lowerBounds_(nullptr),
+    upperBounds_(nullptr)
+{
+    // Read max initial change of design variables if present
+    if (dict.found("maxInitChange"))
+    {
+        maxInitChange_.reset(new scalar(dict_.get<scalar>("maxInitChange")));
+    }
+}
+
+
+Foam::designVariables::designVariables
+(
+    fvMesh& mesh,
+    const dictionary& dict,
+    const label size
+)
+:
+    scalarField(size, Zero),
+    mesh_(mesh),
+    dict_(dict),
+    activeDesignVariables_(0),
+    oldDesignVariables_(nullptr),
+    maxInitChange_(nullptr),
+    lowerBounds_(nullptr),
+    upperBounds_(nullptr)
+{
+    // Read max initial change of design variables if present
+    if (dict.found("maxInitChange"))
+    {
+        maxInitChange_.reset(new scalar(dict_.get<scalar>("maxInitChange")));
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::designVariables> Foam::designVariables::New
+(
+    fvMesh& mesh,
+    const dictionary& dict
+)
+{
+    if (!dict.found("type"))
+    {
+        return autoPtr<designVariables>(nullptr);
+    }
+
+    const word modelType(dict.get<word>("type"));
+
+    Info<< "designVariables type : " << modelType << endl;
+
+    auto cstrIter = designVariablesConstructorTablePtr_->cfind(modelType);
+
+    if (!cstrIter.found())
+    {
+        FatalErrorInLookup
+        (
+            "designVariables",
+            modelType,
+            *designVariablesConstructorTablePtr_
+        ) << exit(FatalError);
+    }
+
+    return autoPtr<designVariables>(cstrIter()(mesh, dict));
+}
+
+
+// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
+
+bool Foam::designVariables::readDict(const dictionary& dict)
+{
+    dict_ = dict;
+
+    if (dict.found("maxInitChange"))
+    {
+        maxInitChange_.reset(new scalar(dict_.get<scalar>("maxInitChange")));
+    }
+
+    return true;
+}
+
+
+const Foam::scalarField& Foam::designVariables::getVars() const
+{
+    return *this;
+}
+
+
+Foam::scalarField& Foam::designVariables::getVars()
+{
+    return *this;
+}
+
+
+void Foam::designVariables::storeDesignVariables()
+{
+    if (!oldDesignVariables_)
+    {
+        oldDesignVariables_.reset(new scalarField(getVars().size(), Zero));
+    }
+
+    oldDesignVariables_.ref() = getVars();
+}
+
+
+void Foam::designVariables::resetDesignVariables()
+{
+    DebugInfo
+        << "Reseting design variables" << endl;
+    getVars() = (oldDesignVariables_());
+}
+
+
+void Foam::designVariables::postProcessSens
+(
+    scalarField& objectiveSens,
+    PtrList<scalarField>& constraintSens,
+    const wordList& adjointSolversNames,
+    bool isMaster
+)
+{
+    // Does nothing in base
+}
+
+
+void Foam::designVariables::evolveNumber()
+{
+    // Does nothing in base
+}
+
+
+void Foam::designVariables::setInitialValues()
+{
+    // Does nothing in base
+}
+
+
+void Foam::designVariables::addFvOptions
+(
+    const PtrList<primalSolver>& primalSolver,
+    const PtrList<adjointSolverManager>& adjointSolverManagers
+)
+{
+    // Does nothing in base
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::designVariables::constraintValues()
+{
+    return tmp<scalarField>(nullptr);
+}
+
+
+Foam::PtrList<Foam::scalarField> Foam::designVariables::constraintDerivatives()
+{
+    return PtrList<scalarField>();
+}
+
+
+void Foam::designVariables::writeDesignVars()
+{
+    // Does nothing in base
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.H
new file mode 100644
index 0000000000000000000000000000000000000000..7a684a7a14ff6d3c13fd6ddd040a7f0607d391e4
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.H
@@ -0,0 +1,282 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+
+Class
+    Foam::designVariables
+
+Description
+    Abstract base class for defining design variables.
+
+SourceFiles
+    designVariables.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef designVariables_H
+#define designVariables_H
+
+#include "fvMesh.H"
+#include "volFieldsFwd.H"
+#include "volFields.H"
+#include "dictionary.H"
+#include "primalSolver.H"
+#include "adjointSolverManager.H"
+#include "adjointSensitivity.H"
+#include "runTimeSelectionTables.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+class adjointSensitivity;
+
+/*---------------------------------------------------------------------------*\
+                       Class designVariables Declaration
+\*---------------------------------------------------------------------------*/
+
+class designVariables
+:
+    public scalarField
+{
+protected:
+
+    // Protected data
+
+        fvMesh& mesh_;
+        dictionary dict_;
+
+        //- Which of the design variables will be updated
+        labelList activeDesignVariables_;
+
+        //- Copy of old design variables. Usefull when performing line-search
+        autoPtr<scalarField> oldDesignVariables_;
+
+        //- Maximum design variables' change in the first optimisation cycle
+        //  Used when eta is not used in updateMethod
+        autoPtr<scalar> maxInitChange_;
+
+        //- Lower bounds of the design variables
+        autoPtr<scalarField> lowerBounds_;
+
+        //- Upper bounds of the design variables
+        autoPtr<scalarField> upperBounds_;
+
+
+    // Protected Member Functions
+
+        //- Read bounds for design variables, if present
+        void readBounds
+        (
+            autoPtr<scalar> lowerBoundPtr = nullptr,
+            autoPtr<scalar> upperBoundPtr = nullptr
+        );
+
+
+private:
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        designVariables(const designVariables&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const designVariables&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("designVariables");
+
+
+    // Declare run-time constructor selection table
+
+        declareRunTimeNewSelectionTable
+        (
+            autoPtr,
+            designVariables,
+            designVariables,
+            (
+                fvMesh& mesh,
+                const dictionary& dict
+            ),
+            (mesh, dict)
+        );
+
+
+    // Constructors
+
+        //- Construct from dictionary
+        designVariables
+        (
+            fvMesh& mesh,
+            const dictionary& dict
+        );
+
+        //- Construct from dictionary and size
+        designVariables
+        (
+            fvMesh& mesh,
+            const dictionary& dict,
+            const label size
+        );
+
+
+    // Selectors
+
+        //- Return a reference to the selected design variables
+        static autoPtr<designVariables> New
+        (
+            fvMesh& mesh,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~designVariables() = default;
+
+
+    // Member Functions
+
+        //- Read dictionary if changed
+        virtual bool readDict(const dictionary& dict);
+
+        //- Get the design variables
+        //  Defaults to *this.
+        //  Virtual for potential overriding from derived classes
+        virtual const scalarField& getVars() const;
+
+        //- Get the design variables
+        //  Defaults to *this.
+        //  Virtual for potential overriding from derived classes
+        virtual scalarField& getVars();
+
+        //- Update design variables based on a given correction
+        //  Translates the scalarField of corrections to a meaningful
+        //  update of the design variables
+        virtual void update(scalarField& correction) = 0;
+
+        //- Store design variables, as the starting point for line search
+        virtual void storeDesignVariables();
+
+        //- Reset to the starting point of line search
+        virtual void resetDesignVariables();
+
+        //- Compute eta if not set in the first step
+        virtual scalar computeEta(scalarField& correction) = 0;
+
+        //- Whether to use global sum when computing matrix-vector products
+        //- in update methods
+        //  Depends on whether the design variables are common for all
+        //  processors (e.g. volumetric B-Splines control points) or distributed
+        //  across them (e.g. topology optimisation)
+        virtual bool globalSum() const = 0;
+
+        //- Return list of active design variables
+        inline const labelList& activeDesignVariables() const;
+
+        //- Check whether the max. initial change of the design variables has
+        //- been set
+        inline bool isMaxInitChangeSet() const;
+
+        //- Set maxInitChange
+        inline void setMaxInitChange(const scalar maxInitChange);
+
+        //- Trigger the recomputation of eta by updateMethod
+        inline virtual bool resetEta() const;
+
+        //- Get min bounds for the design variables
+        inline const autoPtr<scalarField>& lowerBounds() const;
+
+        //- Get max bounds for the design variables
+        inline const autoPtr<scalarField>& upperBounds() const;
+
+        //- Get min bounds for the design variables
+        inline scalarField& lowerBoundsRef();
+
+        //- Get max bounds for the design variables
+        inline scalarField& upperBoundsRef();
+
+        //- Post process sensitivities if needed
+        virtual void postProcessSens
+        (
+            scalarField& objectiveSens,
+            PtrList<scalarField>& constraintSens,
+            const wordList& adjointSolversNames,
+            bool isMaster
+        );
+
+        //- Assemble sensitivity derivatives, by combining the part related
+        //- to the primal and adjoint solution with the part related to the
+        //- design variables
+        virtual tmp<scalarField> assembleSensitivities
+        (
+            adjointSensitivity& sens
+        ) = 0;
+
+        //- For design variables with a dynamic character (i.e. changing
+        //- number), perform the evolution
+        virtual void evolveNumber();
+
+        //- Set initial values of the design variables
+        //  For design variables sets that need to be initialised after the
+        //  construction of the primal fields.
+        //  Does nothing in base
+        virtual void setInitialValues();
+
+        //- Add fvOptions depending on the design variables
+        virtual void addFvOptions
+        (
+            const PtrList<primalSolver>& primalSolver,
+            const PtrList<adjointSolverManager>& adjointSolverManagers
+        );
+
+        //- Design variables might add constraints related to themselves
+        //- (e.g. linear combinations of the design variables)
+        //- Return the values and gradients of these constraints
+        virtual tmp<scalarField> constraintValues();
+        virtual PtrList<scalarField> constraintDerivatives();
+
+        //- Write useful quantities to files
+        virtual void writeDesignVars();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#include "designVariablesI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariablesI.H
similarity index 61%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.C
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariablesI.H
index a6e1a6de7e25272cf546f93a8afb038b3f29ff7e..928bd54968ec22f0a4a38c38632f386d92634f17 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariablesI.H
@@ -5,9 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2021 PCOpt/NTUA
+    Copyright (C) 2021 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,48 +26,56 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include "optMeshMovementNULL.H"
-#include "addToRunTimeSelectionTable.H"
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+inline const Foam::labelList&
+Foam::designVariables::activeDesignVariables() const
+{
+    return activeDesignVariables_;
+}
 
-namespace Foam
+
+bool Foam::designVariables::isMaxInitChangeSet() const
 {
-    defineTypeNameAndDebug(optMeshMovementNULL, 0);
-    addToRunTimeSelectionTable
-    (
-        optMeshMovement,
-        optMeshMovementNULL,
-        dictionary
-    );
+    return maxInitChange_.valid();
 }
 
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+void Foam::designVariables::setMaxInitChange(const scalar maxInitChange)
+{
+    maxInitChange_.reset(new scalar(maxInitChange));
+}
 
-Foam::optMeshMovementNULL::optMeshMovementNULL
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    const labelList& patchIDs
-)
-:
-    optMeshMovement(mesh, dict, patchIDs)
-{}
 
+bool Foam::designVariables::resetEta() const
+{
+    return false;
+}
+
+
+const Foam::autoPtr<Foam::scalarField>&
+Foam::designVariables::lowerBounds() const
+{
+    return lowerBounds_;
+}
+
+
+const Foam::autoPtr<Foam::scalarField>&
+Foam::designVariables::upperBounds() const
+{
+    return upperBounds_;
+}
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-void Foam::optMeshMovementNULL::moveMesh()
+Foam::scalarField& Foam::designVariables::lowerBoundsRef()
 {
-    // Do nothing
+    return lowerBounds_.ref();
 }
 
 
-Foam::scalar
-Foam::optMeshMovementNULL::computeEta(const scalarField& correction)
+Foam::scalarField& Foam::designVariables::upperBoundsRef()
 {
-    return scalar(0);
+    return upperBounds_.ref();
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.C
new file mode 100644
index 0000000000000000000000000000000000000000..05e11c7614243c838bfa7a98e54c4e03b79d94f2
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.C
@@ -0,0 +1,289 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "BezierDesignVariables.H"
+#include "IOmanip.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(BezierDesignVariables, 0);
+    addToRunTimeSelectionTable
+    (
+        shapeDesignVariables,
+        BezierDesignVariables,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * * //
+
+void Foam::BezierDesignVariables::readBounds
+(
+    autoPtr<scalar> lowerBoundPtr,
+    autoPtr<scalar> upperBoundPtr
+)
+{
+    designVariables::readBounds(lowerBoundPtr, upperBoundPtr);
+
+    if (dict_.found("lowerCPBounds"))
+    {
+        vector lowerCPBounds(dict_.get<vector>("lowerCPBounds"));
+        lowerBounds_.reset(new scalarField(getVars().size(), Zero));
+        setBounds(lowerBounds_, lowerCPBounds);
+    }
+
+    if (dict_.found("upperCPBounds"))
+    {
+        vector upperCPBounds(dict_.get<vector>("upperCPBounds"));
+        upperBounds_.reset(new scalarField(getVars().size(), Zero));
+        setBounds(upperBounds_, upperCPBounds);
+    }
+}
+
+
+void Foam::BezierDesignVariables::setBounds
+(
+    autoPtr<scalarField>& bounds,
+    const vector& cpBounds
+)
+{
+    bounds.reset(new scalarField(getVars().size(), Zero));
+    const label nCPs(bezier_.nBezier());
+    for (label iCP = 0; iCP < nCPs; ++iCP)
+    {
+        bounds()[iCP] = cpBounds.x();
+        bounds()[nCPs + iCP] = cpBounds.y();
+        bounds()[2*nCPs + iCP] = cpBounds.z();
+    }
+}
+
+
+Foam::tmp<Foam::vectorField>
+Foam::BezierDesignVariables::computeBoundaryDisplacement
+(
+    const scalarField& correction
+)
+{
+    // Reset boundary movement field
+    dx_.primitiveFieldRef() = Zero;
+
+    // Compute boundary movement using the derivatives of grid nodes
+    // wrt to the Bezier control points and the correction
+    const label nCPs(bezier_.nBezier());
+    auto tcpMovement(tmp<vectorField>::New(nCPs, Zero));
+    vectorField& cpMovement = tcpMovement.ref();
+    const boolListList& confineMovement = bezier_.confineMovement();
+
+    forAll(cpMovement, cpI)
+    {
+        if (!confineMovement[0][cpI])
+        {
+            cpMovement[cpI].x() = correction[cpI];
+        }
+        if (!confineMovement[1][cpI])
+        {
+            cpMovement[cpI].y() = correction[nCPs + cpI];
+        }
+        if (!confineMovement[2][cpI])
+        {
+            cpMovement[cpI].z() = correction[2*nCPs + cpI];
+        }
+
+        dx_ += (bezier_.dxidXj()[cpI] & cpMovement[cpI]);
+    }
+
+    return tcpMovement;
+}
+
+
+void Foam::BezierDesignVariables::decomposeVarID
+(
+    label& cpI,
+    label& dir,
+    const label varID
+) const
+{
+    const label nBezier = bezier_.nBezier();
+    cpI = varID%nBezier;
+    dir = varID/nBezier;
+}
+
+
+// * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * * //
+
+Foam::BezierDesignVariables::BezierDesignVariables
+(
+    fvMesh& mesh,
+    const dictionary& dict
+)
+:
+    shapeDesignVariables(mesh, dict),
+    bezier_
+    (
+        mesh,
+        IOdictionary
+        (
+            IOobject
+            (
+                "optimisationDict",
+                mesh_.time().globalPath()/"system",
+                mesh,
+                IOobject::MUST_READ,
+                IOobject::NO_WRITE,
+                false
+            )
+        )
+    ),
+    dx_
+    (
+        IOobject
+        (
+            "dx",
+            mesh_.time().timeName(),
+            mesh_,
+            IOobject::NO_READ,
+            IOobject::NO_WRITE
+        ),
+        pointMesh::New(mesh_),
+        dimensionedVector(dimless, Zero)
+    )
+{
+    // Set the size of the design variables field
+    scalarField::setSize(3*bezier_.nBezier(), Zero);
+
+    // Set the active design variables
+    activeDesignVariables_ = bezier_.getActiveDesignVariables();
+
+    // Read bounds
+    readBounds();
+}
+
+
+// * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
+
+void Foam::BezierDesignVariables::update(scalarField& correction)
+{
+    // Translate the correction field to control point movements
+    computeBoundaryDisplacement(correction);
+
+    // Transfer movement to the displacementMethod
+    displMethodPtr_->setMotionField(dx_);
+
+    // Update the design variables
+    scalarField::operator+=(correction);
+
+    // Do the actual mesh movement
+    moveMesh();
+}
+
+
+Foam::scalar Foam::BezierDesignVariables::computeEta(scalarField& correction)
+{
+    // Transfer the correction field to control point movement
+    computeBoundaryDisplacement(correction);
+
+    const scalar maxDisplacement(max(mag(dx_)).value());
+
+    Info<< "maxAllowedDisplacement/maxDisplacement at the boundary\t"
+        << maxInitChange_() << "/" << maxDisplacement << endl;
+
+    const scalar eta = maxInitChange_()/maxDisplacement;
+    Info<< "Setting eta value to " << eta << endl;
+    correction *= eta;
+
+    return eta;
+}
+
+
+bool Foam::BezierDesignVariables::globalSum() const
+{
+    return false;
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::BezierDesignVariables::dxdbFace
+(
+    const label patchI,
+    const label varID
+) const
+{
+    label cpI(-1), dir(-1);
+    decomposeVarID(cpI, dir, varID);
+    return bezier_.dxdbFace(patchI, cpI, dir);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::BezierDesignVariables::dndb
+(
+    const label patchI,
+    const label varID
+) const
+{
+    label cpI(-1), dir(-1);
+    decomposeVarID(cpI, dir, varID);
+    return bezier_.dndbBasedSensitivities(patchI, cpI, dir, false);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::BezierDesignVariables::dSdb
+(
+    const label patchI,
+    const label varID
+) const
+{
+    label cpI(-1), dir(-1);
+    decomposeVarID(cpI, dir, varID);
+    return bezier_.dndbBasedSensitivities(patchI, cpI, dir, true);
+}
+
+
+Foam::tmp<Foam::volVectorField>
+Foam::BezierDesignVariables::dCdb(const label varID) const
+{
+    label cpI(-1), dir(-1);
+    decomposeVarID(cpI, dir, varID);
+    label patchI(-1);
+    // There is no mechanism in place to identify the parametertised patch.
+    // Look over all patches and grab one with a non-zero dxdb
+    for (const label pI : parametertisedPatches_)
+    {
+        tmp<vectorField> dxdbFace = bezier_.dxdbFace(pI, cpI, dir);
+        if (gSum(mag(dxdbFace)) > SMALL)
+        {
+            patchI = pI;
+        }
+    }
+    return solveMeshMovementEqn(patchI, varID);
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.H
new file mode 100644
index 0000000000000000000000000000000000000000..02a57434554e6f4816ea01cab84999408cd912f1
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.H
@@ -0,0 +1,172 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+Class
+    Foam::BezierDesignVariables
+
+Description
+    Bezier design variables for shape optimisation
+
+SourceFiles
+    BezierDesignVariables.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef BezierDesignVariables_H
+#define BezierDesignVariables_H
+
+#include "shapeDesignVariables.H"
+#include "Bezier.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                    Class BezierDesignVariables Decleration
+\*---------------------------------------------------------------------------*/
+
+class BezierDesignVariables
+:
+    public shapeDesignVariables
+{
+protected:
+
+    // Protected Data Members
+
+        //- The Bezier control points and auxiliary functions
+        Bezier bezier_;
+
+        //- Boundary movement due to the change in Bezier control points
+        pointVectorField dx_;
+
+
+    // Protected Member Functions
+
+        //- Read bounds for design variables, if present
+        void readBounds
+        (
+            autoPtr<scalar> lowerBoundPtr = nullptr,
+            autoPtr<scalar> upperBoundPtr = nullptr
+        );
+
+        //- Set uniform bounds for all control points
+        void setBounds(autoPtr<scalarField>& bounds, const vector& cpBounds);
+
+        //- Transform the correction of design variables to control points'
+        //- movement
+        tmp<vectorField> computeBoundaryDisplacement
+        (
+            const scalarField& correction
+        );
+
+        //- Decompose varID to cpID and direction
+        void decomposeVarID(label& cpI, label& dir, const label varID) const;
+
+
+private:
+
+    // Private Member Functions
+
+        //- No copy construct
+        BezierDesignVariables(const BezierDesignVariables&) = delete;
+
+        //- No copy assignment
+        void operator=(const BezierDesignVariables&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("Bezier");
+
+
+    // Constructors
+
+        //- Construct from components
+        BezierDesignVariables
+        (
+            fvMesh& mesh,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~BezierDesignVariables() = default;
+
+
+    // Member Functions
+
+        //- Update design variables based on a given correction
+        virtual void update(scalarField& correction);
+
+        //- Compute eta if not set in the first step
+        virtual scalar computeEta(scalarField& correction);
+
+        //- Whether to use global sum when computing matrix-vector products
+        //  in update methods
+        virtual bool globalSum() const;
+
+
+        // Fields related to sensitivity computations
+
+            //- Get dxdb for given design variable and patch
+            virtual tmp<vectorField> dxdbFace
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dndb for given design variable and patch
+            virtual tmp<vectorField> dndb
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dSdb for given design variable and patch
+            virtual tmp<vectorField> dSdb
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dCdb for given design variable.
+            //  Used for FI-based sensitivities
+            virtual tmp<volVectorField> dCdb(const label varID) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.C
new file mode 100644
index 0000000000000000000000000000000000000000..c1eca2034bcbe36eeb65d91bbb166cdc60f2a518
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.C
@@ -0,0 +1,510 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "shapeDesignVariables.H"
+#include "cellQuality.H"
+#include "createZeroField.H"
+#include "addToRunTimeSelectionTable.H"
+#include "volFieldsFwd.H"
+#include "adjointEikonalSolver.H"
+#include "IOmanip.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(shapeDesignVariables, 0);
+    defineRunTimeSelectionTable(shapeDesignVariables, dictionary);
+    addToRunTimeSelectionTable
+    (
+        designVariables,
+        shapeDesignVariables,
+        designVariables
+    );
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+Foam::label Foam::shapeDesignVariables::sensSize() const
+{
+    return size();
+}
+
+
+const Foam::labelList& Foam::shapeDesignVariables::activeSensitivities() const
+{
+    return activeDesignVariables_;
+}
+
+
+Foam::tmp<Foam::volVectorField>
+Foam::shapeDesignVariables::solveMeshMovementEqn
+(
+    const label patchI,
+    const label varID
+) const
+{
+    const dictionary dxdbDict = dict_.subOrEmptyDict("dxdbSolver");
+    const label iters = dxdbDict.getOrDefault<label>("iters", 1000);
+    const scalar tolerance =
+        dxdbDict.getOrDefault<scalar>("tolerance", 1.e-07);
+    tmp<volVectorField> tm
+    (
+        tmp<volVectorField>::New
+        (
+            variablesSet::autoCreateMeshMovementField
+            (
+                mesh_,
+                "m",
+                dimensionSet(dimLength)
+            )
+        )
+    );
+    volVectorField& m = tm.ref();
+
+    // Solve for dxdb
+    //~~~~~~~~~~~~~~~~
+    m.boundaryFieldRef()[patchI] == dxdbFace(patchI, varID);
+
+    // Iterate the direct differentiation of the grid displacement  equation
+    for (label iter = 0; iter < iters; ++iter)
+    {
+        Info<< "Mesh Movement Propagation for varID" << varID
+            << ", Iteration : "<< iter << endl;
+
+        fvVectorMatrix mEqn
+        (
+            fvm::laplacian(m)
+        );
+
+        scalar residual = mag(mEqn.solve().initialResidual());
+
+        DebugInfo
+            << "Max dxdb " << gMax(mag(m)()) << endl;
+
+        mesh_.time().printExecutionTime(Info);
+
+        // Check convergence
+        if (residual < tolerance)
+        {
+            Info<< "\n***Reached dxdb convergence limit, iteration " << iter
+                << "***\n\n";
+            break;
+        }
+    }
+
+    return tm;
+}
+
+
+void Foam::shapeDesignVariables::allocateSensFields()
+{
+    if (dxdbVolSens_.empty())
+    {
+        dxdbVolSens_.setSize(sensSize(), Zero);
+        dxdbSurfSens_.setSize(sensSize(), Zero);
+        dSdbSens_.setSize(sensSize(), Zero);
+        dndbSens_.setSize(sensSize(), Zero);
+        dxdbDirectSens_.setSize(sensSize(), Zero);
+        dVdbSens_.setSize(sensSize(), Zero);
+        distanceSens_.setSize(sensSize(), Zero);
+        optionsSens_.setSize(sensSize(), Zero);
+        bcSens_.setSize(sensSize(), Zero);
+    }
+}
+
+
+void Foam::shapeDesignVariables::zeroSensFields()
+{
+    dxdbVolSens_ = Zero;
+    dxdbSurfSens_ = Zero;
+    dSdbSens_ = Zero;
+    dndbSens_ = Zero;
+    dxdbDirectSens_ = Zero;
+    dVdbSens_ = Zero;
+    distanceSens_ = Zero;
+    optionsSens_ = Zero;
+    bcSens_ = Zero;
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::shapeDesignVariables::shapeDesignVariables
+(
+    fvMesh& mesh,
+    const dictionary& dict
+)
+:
+    designVariables(mesh, dict),
+    parametertisedPatches_
+    (
+        mesh_.boundaryMesh().patchSet(dict.get<wordRes>("patches"))
+    ),
+    displMethodPtr_
+    (
+        displacementMethod::New(mesh_, parametertisedPatches_.toc())
+    ),
+    pointsInit_(nullptr),
+    writeEachMesh_(dict.getOrDefault<bool>("writeEachMesh", true)),
+    dxdbVolSens_(),
+    dxdbSurfSens_(),
+    dSdbSens_(),
+    dndbSens_(),
+    dxdbDirectSens_(),
+    dVdbSens_(),
+    distanceSens_(),
+    optionsSens_(),
+    bcSens_(),
+    derivativesFolder_
+    (
+        word("optimisation")/word("derivatives")
+       /word(mesh.name() == polyMesh::defaultRegion ? word() : mesh.name())
+    )
+{
+    if (!parametertisedPatches_.size())
+    {
+        FatalErrorInFunction
+            << "None of the provided parameterised patches is valid"
+            << endl
+            << exit(FatalError);
+    }
+    mkDir(derivativesFolder_);
+}
+
+
+// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::shapeDesignVariables> Foam::shapeDesignVariables::New
+(
+    fvMesh& mesh,
+    const dictionary& dict
+)
+{
+    const word modelType(dict.get<word>("shapeType"));
+
+    Info<< "shapeDesignVariables type : " << modelType << endl;
+
+    auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelType);
+
+    if (!cstrIter.found())
+    {
+        FatalErrorInLookup
+        (
+            "shapeType",
+            modelType,
+            *dictionaryConstructorTablePtr_
+        ) << exit(FatalError);
+    }
+
+    return autoPtr<shapeDesignVariables>(cstrIter()(mesh, dict));
+}
+
+
+// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
+
+bool Foam::shapeDesignVariables::readDict(const dictionary& dict)
+{
+    if (designVariables::readDict(dict))
+    {
+        parametertisedPatches_ =
+            mesh_.boundaryMesh().patchSet(dict.get<wordRes>("patches"));
+        displMethodPtr_->setPatchIDs(parametertisedPatches_.toc());
+
+        writeEachMesh_ =
+            dict.getOrDefault<bool>("writeEachMesh", true);
+
+        return true;
+    }
+
+    return false;
+}
+
+
+void Foam::shapeDesignVariables::storeDesignVariables()
+{
+    designVariables::storeDesignVariables();
+
+    if (!pointsInit_)
+    {
+        pointsInit_.reset(new pointField(mesh_.nPoints(), Zero));
+    }
+    pointsInit_() = mesh_.points();
+}
+
+
+void Foam::shapeDesignVariables::resetDesignVariables()
+{
+    designVariables::resetDesignVariables();
+    mesh_.movePoints(pointsInit_());
+}
+
+
+void Foam::shapeDesignVariables::moveMesh()
+{
+    // Move mesh
+    displMethodPtr_->update();
+
+    if (writeEachMesh_)
+    {
+        Info<< "  Writing new mesh points for mesh region "
+            << mesh_.name() << endl;
+        pointIOField points
+        (
+            IOobject
+            (
+               "points",
+                mesh_.pointsInstance(),
+                mesh_.meshSubDir,
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE,
+                false
+            ),
+            mesh_.points()
+        );
+        points.write();
+    }
+
+    // Check mesh quality
+    mesh_.checkMesh(true);
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::shapeDesignVariables::assembleSensitivities
+(
+    adjointSensitivity& adjointSens
+)
+{
+    // Return field
+    tmp<scalarField> tsens(tmp<scalarField>::New(sensSize(), Zero));
+    scalarField& sens = tsens.ref();
+
+    // Reset sensitivity components to zero
+    allocateSensFields();
+    zeroSensFields();
+
+    // Grab multipliers from the adjoint sensitivities
+    const autoPtr<volTensorField>& gradDxDbMult = adjointSens.gradDxDbMult();
+    const autoPtr<scalarField>& divDxDbMult = adjointSens.divDxDbMult();
+    const autoPtr<boundaryVectorField>& dxdbMult = adjointSens.dxdbMult();
+    const autoPtr<boundaryVectorField>& dSdbMult = adjointSens.dSfdbMult();
+    const autoPtr<boundaryVectorField>& dndbMult = adjointSens.dnfdbMult();
+    const autoPtr<boundaryVectorField>& dxdbDirectMult =
+        adjointSens.dxdbDirectMult();
+    const autoPtr<boundaryVectorField>& bcDxDbmult = adjointSens.bcDxDbMult();
+    const autoPtr<vectorField>& optionsDxDbMult = adjointSens.optionsDxDbMult();
+    const volScalarField::Internal& V = mesh_.V();
+    autoPtr<adjointEikonalSolver>& eikonalSolver =
+        adjointSens.getAdjointEikonalSolver();
+
+    autoPtr<volTensorField> distanceSens(nullptr);
+    if (adjointSens.includeDistance())
+    {
+        distanceSens.reset
+        (
+            new volTensorField(eikonalSolver->getFISensitivityTerm())
+        );
+    }
+
+    // Loop over active design variables only
+    for (const label varI : activeSensitivities())
+    {
+        // FI approach, integrate terms including variations of the grid
+        // sensitivities
+        if (adjointSens.computeDxDbInternalField())
+        {
+            // Parameterization info
+            tmp<volVectorField> tvolDxDbI = dCdb(varI);
+            const volVectorField& volDxDbI = tvolDxDbI();
+            tmp<volTensorField> gradDxDb = fvc::grad(volDxDbI);
+
+            // Contributions from the adjoint-related part
+            dxdbVolSens_[varI] = gSum((gradDxDbMult() && gradDxDb())*V);
+
+            // Contributions from the distance related part
+            if (adjointSens.includeDistance())
+            {
+                distanceSens_[varI] = gSum((distanceSens() && gradDxDb)*V);
+            }
+            // Contributions from the multiplier of divDxDb
+            if (divDxDbMult)
+            {
+                dVdbSens_[varI] +=
+                    gSum(divDxDbMult()*fvc::div(volDxDbI)().primitiveField()*V);
+            }
+
+            // Contributions from fvOptions
+            optionsSens_[varI] +=
+                gSum((optionsDxDbMult() & volDxDbI.primitiveField())*V);
+        }
+
+        // Contribution from boundary terms
+        // Most of them (with the expection of dxdbMult) exist in both the
+        // FI and E-SI approaches
+        for (const label patchI : parametertisedPatches_)
+        {
+            if (dSdbMult)
+            {
+                tmp<vectorField> pdSdb = dSdb(patchI, varI);
+                dSdbSens_[varI] += gSum(dSdbMult()[patchI] & pdSdb);
+            }
+
+            if (dndbMult)
+            {
+                tmp<vectorField> pdndb = dndb(patchI, varI);
+                dndbSens_[varI] += gSum((dndbMult()[patchI] & pdndb));
+            }
+
+            tmp<vectorField> pdxdb = dxdbFace(patchI, varI);
+            // Main contribution in the E-SI approach
+            if (dxdbMult)
+            {
+                dxdbSurfSens_[varI] += gSum(dxdbMult()[patchI] & pdxdb());
+            }
+            if (dxdbDirectMult)
+            {
+                dxdbDirectSens_[varI] +=
+                    gSum((dxdbDirectMult()[patchI] & pdxdb()));
+            }
+            if (bcDxDbmult)
+            {
+                bcSens_[varI] += gSum((bcDxDbmult()[patchI] & pdxdb()));
+            }
+        }
+    }
+
+    sens =
+        dxdbVolSens_ + dxdbSurfSens_ + dSdbSens_ + dndbSens_ + dxdbDirectSens_
+      + dVdbSens_ + distanceSens_ + optionsSens_ + bcSens_;
+
+    writeSensitivities(sens, adjointSens);
+
+    return tsens;
+}
+
+
+void Foam::shapeDesignVariables::writeSensitivities
+(
+    const scalarField& sens,
+    const adjointSensitivity& adjointSens
+)
+{
+    OFstream derivFile
+    (
+        derivativesFolder_/
+            type() + adjointSens.getAdjointSolver().solverName()
+          + adjointSens.getSuffix() + mesh_.time().timeName()
+    );
+    unsigned int widthDV = max(int(name(dxdbVolSens_.size()).size()), int(6));
+    unsigned int width = IOstream::defaultPrecision() + 7;
+    derivFile
+        << setw(widthDV) << "#varID" << " "
+        << setw(width) << "total"<< " "
+        << setw(width) << "dxdbVol" << " "
+        << setw(width) << "dxdbSurf" << " "
+        << setw(width) << "dSdb" << " "
+        << setw(width) << "dndb" << " "
+        << setw(width) << "dxdbDirect" << " "
+        << setw(width) << "dVdb" << " "
+        << setw(width) << "distance" << " "
+        << setw(width) << "options" << " "
+        << setw(width) << "dvdb" << endl;
+
+    for (const label varI : activeSensitivities())
+    {
+        derivFile
+           << setw(widthDV) << varI << " "
+           << setw(width) << sens[varI] << " "
+           << setw(width) << dxdbVolSens_[varI] << " "
+           << setw(width) << dxdbSurfSens_[varI] << " "
+           << setw(width) << dSdbSens_[varI] << " "
+           << setw(width) << dndbSens_[varI] << " "
+           << setw(width) << dxdbDirectSens_[varI] << " "
+           << setw(width) << dVdbSens_[varI] << " "
+           << setw(width) << distanceSens_[varI] << " "
+           << setw(width) << optionsSens_[varI] << " "
+           << setw(width) << bcSens_[varI] << " "
+           << endl;
+    }
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::shapeDesignVariables::dxdbVol
+(
+    const label varID
+) const
+{
+    // Deliberately returning a zero-sized field
+    return tmp<vectorField>::New(0);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::shapeDesignVariables::dxdbFace
+(
+    const label patchI,
+    const label varID
+) const
+{
+    NotImplemented;
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::shapeDesignVariables::dndb
+(
+    const label patchI,
+    const label varID
+) const
+{
+    NotImplemented;
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::shapeDesignVariables::dSdb
+(
+    const label patchI,
+    const label varID
+) const
+{
+    NotImplemented;
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::volVectorField>
+Foam::shapeDesignVariables::dCdb(const label varID) const
+{
+    NotImplemented;
+    return nullptr;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.H
new file mode 100644
index 0000000000000000000000000000000000000000..66d1010c2ddfcc3aa0bd13853e33a429dd9d03a3
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.H
@@ -0,0 +1,296 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+Class
+    Foam::shapeDesignVariables
+
+Description
+    Abstract base class for defining design variables for shape optimisation.
+
+SourceFiles
+    shapeDesignVariables.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef shapeDesignVariables_H
+#define shapeDesignVariables_H
+
+#include "designVariables.H"
+#include "displacementMethod.H"
+#include "runTimeSelectionTables.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                     Class shapeDesignVariables Declaration
+\*---------------------------------------------------------------------------*/
+
+class shapeDesignVariables
+:
+    public designVariables
+{
+protected:
+
+    // Protected data
+
+        //- Patches to be moved by the design variables
+        labelHashSet parametertisedPatches_;
+
+        //- Mesh movement mechanism
+        autoPtr<displacementMethod> displMethodPtr_;
+
+        //- Store old points. Useful for line search
+        autoPtr<pointField> pointsInit_;
+
+        //- Write the mesh points irrespective of whether this is a write time
+        bool writeEachMesh_;
+
+        // Auxiliary fields keeping track of the various sensitiity components
+
+            //- Flow related term.
+            //  Term including grad(dxdb) in the volume integrals of the FI
+            //  formulation
+            scalarField dxdbVolSens_;
+
+            //- Flow related term.
+            //  Main term in the E-SI formulation. Is the surface intergral
+            //  emerging after performing the Gauss-divergence theorem on the
+            //  FI-based sensitivities
+            scalarField dxdbSurfSens_;
+
+            //- Term depending on delta(n dS)/delta b
+            scalarField dSdbSens_;
+
+            //- Term depending on delta(n)/delta b
+            scalarField dndbSens_;
+
+            //- Term depending on delta(x)/delta b for objectives that directly
+            //- depend on x
+            scalarField dxdbDirectSens_;
+
+            //- Term depending on delta(V)/delta b
+            scalarField dVdbSens_;
+
+            //- Term depending on distance differentiation
+            scalarField distanceSens_;
+
+            //- Term depending on fvOptions
+            scalarField optionsSens_;
+
+            //- Term depending on the differenation of boundary conditions
+            scalarField bcSens_;
+
+        //- Name of the sensitivity derivatives folder
+        fileName derivativesFolder_;
+
+
+    // Protected Member Functions
+
+        //- Size of the sensitivity derivatives.
+        //  Might be different than this->size() in some cases
+        virtual label sensSize() const;
+
+        //- Active variables for which to compute sensitivities
+        //  Might be different than this->activeDesignVariables_ in some cases
+        virtual const labelList& activeSensitivities() const;
+
+        //- Compute dxdb at the mesh cell centers by solving a Laplace PDE
+        virtual tmp<volVectorField> solveMeshMovementEqn
+        (
+            const label patchI,
+            const label varID
+        ) const;
+
+        //- Allocate the fields assosiated with the computation of sensitivities
+        //  Not allocated in the constructor since the size of the design
+        //  variables is usually not known there
+        void allocateSensFields();
+
+        //- Zero the fields assosiated with the computation of sensitivities
+        void zeroSensFields();
+
+
+private:
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        shapeDesignVariables(const shapeDesignVariables&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const shapeDesignVariables&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("shape");
+
+
+    // Declare run-time constructor selection table
+
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            shapeDesignVariables,
+            dictionary,
+            (
+                fvMesh& mesh,
+                const dictionary& dict
+            ),
+            (mesh, dict)
+        );
+
+
+    // Constructors
+
+        //- Construct from components
+        shapeDesignVariables
+        (
+            fvMesh& mesh,
+            const dictionary& dict
+        );
+
+
+    // Selectors
+
+        //- Construct and return the selected shapeDesignVariables
+        static autoPtr<shapeDesignVariables> New
+        (
+            fvMesh& mesh,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~shapeDesignVariables() = default;
+
+
+    // Member Functions
+
+        //- Read dictionary if changed
+        virtual bool readDict(const dictionary& dict);
+
+        //- Update design variables based on a given correction.
+        //  Translates the scalarField of corrections to a meaningful
+        //  update of the design variables
+        virtual void update(scalarField& correction) = 0;
+
+        //- Store design variables, as the starting point for line search
+        virtual void storeDesignVariables();
+
+        //- Reset to starting point of line search
+        virtual void resetDesignVariables();
+
+        //- Compute eta if not set in the first step
+        virtual scalar computeEta(scalarField& correction) = 0;
+
+        //- Whether to use global sum when computing matrix-vector products
+        //- in update methods
+        //  Depends on whether the design variables are common for all
+        //  processors (e.g. volumetric B-Splines control points) or distributed
+        //  across them (e.g. topology optimisation)
+        virtual bool globalSum() const = 0;
+
+        // Functions related to mesh movement
+
+            //- Move mesh based on displacementMethod
+            virtual void moveMesh();
+
+            //- Patches affected by the parameterisation
+            inline const labelHashSet& getPatches() const
+            {
+                return parametertisedPatches_;
+            }
+
+            //- Return displacementMethod
+            inline autoPtr<displacementMethod>& returnDisplacementMethod()
+            {
+                return displMethodPtr_;
+            }
+
+
+        // Fields related to sensitivity computations
+
+            //- Add part of sensitivity derivatives related to geometry
+            //- variations
+            virtual tmp<scalarField> assembleSensitivities
+            (
+                adjointSensitivity& adjointSens
+            );
+
+            //- Write final sensitivity derivatives to files
+            virtual void writeSensitivities
+            (
+                const scalarField& sens,
+                const adjointSensitivity& adjointSens
+            );
+
+            //- Get dxdb for all mesh points
+            virtual tmp<vectorField> dxdbVol
+            (
+                const label varID
+            ) const;
+
+            //- Get dxdb for a given design variable and patch
+            virtual tmp<vectorField> dxdbFace
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dndb for a given design variable and patch
+            virtual tmp<vectorField> dndb
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dSdb for a given design variable and patch
+            virtual tmp<vectorField> dSdb
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dCdb for a given design variable.
+            //  Used for FI-based sensitivities
+            virtual tmp<volVectorField> dCdb(const label varID) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.C
new file mode 100644
index 0000000000000000000000000000000000000000..377230d2424d89f163493bef2183251cdd4142e9
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.C
@@ -0,0 +1,220 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "morphingBoxConstraint.H"
+#include "volumetricBSplinesDesignVariables.H"
+#include "createZeroField.H"
+#include "IOmanip.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineRunTimeSelectionTable(morphingBoxConstraint, dictionary);
+    defineTypeNameAndDebug(morphingBoxConstraint, 0);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::morphingBoxConstraint::writeDVSensitivities
+(
+    const scalarField& sens,
+    const word& solverName
+)
+{
+    if (Pstream::master())
+    {
+        OFstream derivFile
+            (derivativesFolder_/solverName + mesh_.time().timeName());
+
+        unsigned int width = IOstream::defaultPrecision() + 7;
+        derivFile
+            << setw(width) << "#varID" << " "
+            << setw(width) << "adjointSensitivity"
+            << endl;
+
+        const labelList& activeVars = designVariables_.activeDesignVariables();
+        forAll(activeVars, varI)
+        {
+            const label activeVarI = activeVars[varI];
+            derivFile
+                << setw(width) << activeVarI << " "
+                << setw(width) << sens[activeVarI] << endl;
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::morphingBoxConstraint::morphingBoxConstraint
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    volumetricBSplinesDesignVariables& designVariables
+)
+:
+    mesh_(mesh),
+    dict_(dict),
+    designVariables_(designVariables),
+    volBSplinesBase_(designVariables.getVolBSplinesBase()),
+    initialCPs_(3*volBSplinesBase_.getTotalControlPointsNumber()),
+    initialiseVars_(true),
+    derivativesFolder_("optimisation"/type() + "Derivatives")
+{
+    // Store initial control points
+    const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxes();
+    label varID(0);
+    for (const NURBS3DVolume& boxI : boxes)
+    {
+        const vectorField& cps = boxI.getControlPoints();
+        for (const vector& cpI : cps)
+        {
+            initialCPs_[varID++] = cpI.x();
+            initialCPs_[varID++] = cpI.y();
+            initialCPs_[varID++] = cpI.z();
+        }
+    }
+
+    // Create sensitivities folder
+    mkDir(derivativesFolder_);
+}
+
+
+// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::morphingBoxConstraint> Foam::morphingBoxConstraint::New
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    volumetricBSplinesDesignVariables& designVariables
+)
+{
+    const word modelType(dict.getOrDefault<word>("constraintType", "none"));
+
+    Info<< "morphingBoxConstraint type : " << modelType << endl;
+
+    auto cstrIter = dictionaryConstructorTablePtr_->cfind(modelType);
+
+    if (!cstrIter.found())
+    {
+        FatalErrorInLookup
+        (
+            "constraintType",
+            modelType,
+            *dictionaryConstructorTablePtr_
+        ) << exit(FatalError);
+    }
+
+    return autoPtr<morphingBoxConstraint>
+    (
+        cstrIter()(mesh, dict, designVariables)
+    );
+}
+
+// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
+
+void Foam::morphingBoxConstraint::computeBounds
+(
+    autoPtr<scalarField>& lowerBounds,
+    autoPtr<scalarField>& upperBounds
+)
+{
+    if (lowerBounds || upperBounds)
+    {
+        NotImplemented;
+    }
+}
+
+
+void Foam::morphingBoxConstraint::updateBounds
+(
+    autoPtr<scalarField>& lowerBounds,
+    autoPtr<scalarField>& upperBounds
+)
+{
+    if (designVariables_.updateBounds() && (lowerBounds || upperBounds))
+    {
+        NotImplemented;
+    }
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::morphingBoxConstraint::postProcessSens
+(
+    const scalarField& controlPointSens,
+    const word& adjointSolverName
+)
+{
+    // Sensitivities w.r.t. the design variables
+    auto tdvSens
+        (tmp<scalarField>::New(designVariables_.scalarField::size(), Zero));
+    scalarField& dvSens = tdvSens.ref();
+    computeDVsSensitivities(dvSens, controlPointSens);
+    writeDVSensitivities(dvSens, adjointSolverName);
+
+    return tdvSens;
+}
+
+
+Foam::scalar Foam::morphingBoxConstraint::computeEta
+(
+    scalarField& correction,
+    const scalar maxInitChange
+)
+{
+    vectorField cpMovement(designVariables_.controlPointMovement(correction));
+    const scalar maxDisplacement
+    (
+        volBSplinesBase_.computeMaxBoundaryDisplacement
+        (
+            cpMovement,
+            designVariables_.getPatches().toc()
+        )
+    );
+
+    Info<< "maxAllowedDisplacement/maxDisplacement of boundary\t"
+        << maxInitChange << "/" << maxDisplacement << endl;
+    const scalar eta(maxInitChange/ maxDisplacement);
+
+    Info<< "Setting eta value to " << eta << endl;
+    correction *= eta;
+
+    return eta;
+}
+
+
+bool Foam::morphingBoxConstraint::writeData(Ostream& os) const
+{
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.H
new file mode 100644
index 0000000000000000000000000000000000000000..ec4d6f87a7a607c615f1e8cc9442678639ac0578
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.H
@@ -0,0 +1,239 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+Class
+    Foam::morphingBoxConstraint
+
+Description
+    Abstract base class for defining constraints for the control points of
+    volumetric B-Splines morphing boxes
+
+SourceFiles
+    morphingBoxConstraint.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef morphingBoxConstraint_H
+#define morphingBoxConstraint_H
+
+#include "volBSplinesBase.H"
+#include "runTimeSelectionTables.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+// Forward declaration
+class volumetricBSplinesDesignVariables;
+
+/*---------------------------------------------------------------------------*\
+                    Class morphingBoxConstraint Declaration
+\*---------------------------------------------------------------------------*/
+
+class morphingBoxConstraint
+{
+protected:
+
+    // Protected data
+
+        //- Mesh reference
+        const fvMesh& mesh_;
+
+        //- Volumetric B-Splines variables dict
+        const dictionary dict_;
+
+        //- Reference to underlaying volumetric B-Splines morpher
+        volumetricBSplinesDesignVariables& designVariables_;
+
+        //- Easy access to the volBSplinesBase resting in the designVariables_
+        volBSplinesBase& volBSplinesBase_;
+
+        //- Initial CPs stored in scalarField
+        scalarField initialCPs_;
+
+        //- Initialise the design variables
+        bool initialiseVars_;
+
+        //- Folder holding the twist sensitivities
+        fileName derivativesFolder_;
+
+
+    // Protected Member Functions
+
+        //- Compute sensitivities wrt the design variables (chain rule)
+        void virtual computeDVsSensitivities
+        (
+            scalarField& dvSens,
+            const scalarField& cpSens
+        ) = 0;
+
+        //- Write sensitivities w.r.t. the design variables
+        void virtual writeDVSensitivities
+        (
+            const scalarField& sens,
+            const word& name
+        );
+
+
+private:
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        morphingBoxConstraint(const morphingBoxConstraint&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const morphingBoxConstraint&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("morphingBoxConstraint");
+
+
+    // Declare run-time constructor selection table
+
+        declareRunTimeSelectionTable
+        (
+            autoPtr,
+            morphingBoxConstraint,
+            dictionary,
+            (
+                const fvMesh& mesh,
+                const dictionary& dict,
+                volumetricBSplinesDesignVariables& designVariables
+            ),
+            (mesh, dict, designVariables)
+        );
+
+
+    // Constructors
+
+        //- Construct from components
+        morphingBoxConstraint
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            volumetricBSplinesDesignVariables& designVariables
+        );
+
+
+    // Selectors
+
+        //- Construct and return the selected morphingBoxConstraint
+        static autoPtr<morphingBoxConstraint> New
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            volumetricBSplinesDesignVariables& designVariables
+        );
+
+
+    //- Destructor
+    virtual ~morphingBoxConstraint() = default;
+
+
+    // Member Functions
+
+        //- Compute the active design variables based on the IDs of the
+        //- active control point coordinates
+        virtual labelList computeActiveDesignVariables
+        (
+            const labelList& activeCPCoors
+        ) = 0;
+
+        //- Transform bounds from control points to design variables
+        //  WIP
+        virtual void computeBounds
+        (
+            autoPtr<scalarField>& lowerBounds,
+            autoPtr<scalarField>& upperBounds
+        );
+
+        //- Update the bounds of the design variables
+        //  WIP
+        virtual void updateBounds
+        (
+            autoPtr<scalarField>& lowerBounds,
+            autoPtr<scalarField>& upperBounds
+        );
+
+        //- Convert design variables to control points, stored in a scalarField
+        virtual tmp<scalarField> designVariablesToControlPoints
+        (
+            const scalarField& designVariables
+        ) = 0;
+
+        //- Return the design variables corresponding to the given control
+        //- points
+        virtual tmp<scalarField> controlPointsToDesignVariables
+        (
+            const scalarField& cps
+        ) = 0;
+
+        //- Convert the correction of the design variables to the correction of
+        //- the control points
+        virtual tmp<scalarField> correctionCPs
+        (
+            const scalarField& correction
+        ) = 0;
+
+        //- Chain rule from control points to design variables
+        virtual tmp<scalarField> postProcessSens
+        (
+            const scalarField& controlPointSens,
+            const word& adjointSolverName
+        );
+
+        //- Compute eta if not set in the first step
+        virtual scalar computeEta
+        (
+            scalarField& correction,
+            const scalar maxInitChange
+        );
+
+        //- Initialise the design variables?
+        inline bool initialiseVars() const
+        {
+            return initialiseVars_;
+        }
+
+        //- Append useful information to the design variables IOdictionary
+        virtual bool writeData(Ostream& os) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.C
new file mode 100644
index 0000000000000000000000000000000000000000..7c43cf9e4576ed61672fad3bc27dd9ff707c4382
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.C
@@ -0,0 +1,286 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 PCOpt/NTUA
+    Copyright (C) 2021 FOSS GP
+-------------------------------------------------------------------------------
+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 "noConstraint.H"
+#include "volumetricBSplinesDesignVariables.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(noConstraint, 1);
+    addToRunTimeSelectionTable
+    (
+        morphingBoxConstraint,
+        noConstraint,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::noConstraint::computeDVsSensitivities
+(
+    scalarField& dvSens,
+    const scalarField& cpSens
+)
+{
+    dvSens = cpSens;
+}
+
+
+void Foam::noConstraint::updateInternalBounds
+(
+    autoPtr<scalarField>& lowerBounds,
+    autoPtr<scalarField>& upperBounds,
+    const NURBS3DVolume& boxI,
+    const label passed
+)
+{
+    const vectorField& cps = boxI.getControlPoints();
+    const Vector<label> nCPsDir = boxI.nCPsPerDirection();
+    // Internal points
+    for (label k = 1; k < nCPsDir[2] - 1; ++k)
+    {
+        for (label j = 1; j < nCPsDir[1] - 1; ++j)
+        {
+            for (label i = 1; i < nCPsDir[0] - 1; ++i)
+            {
+                label cpID(boxI.getCPID(i, j, k));
+                for (label idir = 0; idir < 3; ++idir)
+                {
+                    label iIncr(idir == 0);
+                    label jIncr(idir == 1);
+                    label kIncr(idir == 2);
+                    label prevCP
+                        (boxI.getCPID(i - iIncr, j - jIncr, k - kIncr));
+                    label nextCP
+                        (boxI.getCPID(i + iIncr, j + jIncr, k + kIncr));
+                    lowerBounds()[3*cpID + idir + passed] =
+                        0.5
+                       *(
+                            cps[prevCP].component(idir)
+                          + cps[cpID].component(idir)
+                        );
+                    upperBounds()[3*cpID + idir + passed] =
+                        0.5
+                       *(
+                            cps[nextCP].component(idir)
+                          + cps[cpID].component(idir)
+                        );
+                }
+            }
+        }
+    }
+}
+
+
+void Foam::noConstraint::updateBoundaryBounds
+(
+    autoPtr<scalarField>& lowerBounds,
+    autoPtr<scalarField>& upperBounds,
+    const NURBS3DVolume& boxI,
+    const label passed
+)
+{
+    const vectorField& cps = boxI.getControlPoints();
+    const Vector<label> nCPsDir = boxI.nCPsPerDirection();
+    // Loop over boundaries in all directions of the box
+    for (label ibound = 0; ibound < 3; ++ibound)
+    {
+        // Start of iterators in the three directions
+        Vector<label> minID(1, 1, 1);
+        // End of iterators in the three directions
+        Vector<label> maxID(nCPsDir[0] - 2, nCPsDir[1] - 2, nCPsDir[2] - 2);
+        // Increment of iterators in the three directions
+        Vector<label> incr(1, 1, 1);
+
+        // Adjust looping in the direction we are examining
+        minID[ibound] = 0;
+        maxID[ibound] = nCPsDir[ibound];
+        incr[ibound] = nCPsDir[ibound] - 1;
+        Vector<label> indices(Zero);
+        label& i = indices[0];
+        label& j = indices[1];
+        label& k = indices[2];
+
+        for (k = minID[2]; k < maxID[2]; k += incr[2])
+        {
+            for (j = minID[1]; j < maxID[1]; j += incr[1])
+            {
+                for (i = minID[0]; i < maxID[0]; i += incr[0])
+                {
+                    label cpID(boxI.getCPID(i, j, k));
+                    for (label dir = 0; dir < 3; ++dir)
+                    {
+                        Vector<label> incrMinus(dir == 0, dir == 1, dir == 2);
+                        Vector<label> incrPlus(dir == 0, dir == 1, dir == 2);
+                        // Adjust increment for the ibound direction
+                        incrMinus[ibound] =
+                            label
+                            (
+                                incrMinus[ibound]
+                             && indices[ibound] == nCPsDir[ibound] - 1
+                            );
+                        incrPlus[ibound] =
+                            label(incrMinus[ibound] && indices[ibound] == 0);
+                        label prevCP =
+                            boxI.getCPID
+                            (
+                                i - incrMinus[0],
+                                j - incrMinus[1],
+                                k - incrMinus[2]
+                            );
+                        label nextCP =
+                            boxI.getCPID
+                            (
+                                i + incrPlus[0],
+                                j + incrPlus[1],
+                                k + incrPlus[2]
+                            );
+                        if (incrMinus[ibound])
+                        {
+                            lowerBounds()[3*cpID + dir + passed] =
+                                0.5
+                               *(
+                                    cps[prevCP].component(dir)
+                                  + cps[cpID].component(dir)
+                                );
+                        }
+                        if (incrPlus[ibound])
+                        {
+                            upperBounds()[3*cpID + dir + passed] =
+                                0.5
+                               *(
+                                    cps[nextCP].component(dir)
+                                  + cps[cpID].component(dir)
+                                );
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::noConstraint::noConstraint
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    volumetricBSplinesDesignVariables& designVariables
+)
+:
+    morphingBoxConstraint(mesh, dict, designVariables)
+{
+    designVariables_.setSize(3*volBSplinesBase_.getTotalControlPointsNumber());
+}
+
+
+// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
+
+void Foam::noConstraint::computeBounds
+(
+    autoPtr<scalarField>& lowerBounds,
+    autoPtr<scalarField>& upperBounds
+)
+{
+    // Does nothing
+}
+
+
+void Foam::noConstraint::updateBounds
+(
+    autoPtr<scalarField>& lowerBounds,
+    autoPtr<scalarField>& upperBounds
+)
+{
+    if (designVariables_.nonOverlappingCPs() && designVariables_.updateBounds())
+    {
+        DebugInfo
+            << "Updating bounds for the design variables " << endl;
+        const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
+        label passed(0);
+        for (const NURBS3DVolume& boxI : boxes)
+        {
+            // Bounds for internal control points
+            updateInternalBounds(lowerBounds, upperBounds, boxI, passed);
+            // Bounds for boundary points.
+            // Assumes that the boundary edges remain fixed
+            updateBoundaryBounds(lowerBounds, upperBounds, boxI, passed);
+            passed += 3*boxI.getControlPoints().size();
+        }
+        DebugInfo
+            << "lower bounds " << lowerBounds() << endl;
+        DebugInfo
+            << "upper bounds " << upperBounds() << endl;
+    }
+}
+
+
+Foam::labelList Foam::noConstraint::computeActiveDesignVariables
+(
+    const labelList& activeCPCoors
+)
+{
+    return activeCPCoors;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::noConstraint::designVariablesToControlPoints
+(
+    const scalarField& designVariables
+)
+{
+    return designVariables;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::noConstraint::controlPointsToDesignVariables
+(
+    const scalarField& cps
+)
+{
+    return cps;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::noConstraint::correctionCPs
+(
+    const scalarField& correction
+)
+{
+    return correction;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.H
new file mode 100644
index 0000000000000000000000000000000000000000..1fe0113573a4240c16791a8c2ed469eb35136f38
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.H
@@ -0,0 +1,175 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 PCOpt/NTUA
+    Copyright (C) 2021 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+Class
+    Foam::noConstraint
+
+Description
+    Applies no constraints to the control points.
+    Enforces the non-overlapping bounds, if present.
+
+SourceFiles
+    noConstraint.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef noConstraint_H
+#define noConstraint_H
+
+#include "morphingBoxConstraint.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                         Class noConstraint Declaration
+\*---------------------------------------------------------------------------*/
+
+class noConstraint
+:
+    public morphingBoxConstraint
+{
+
+protected:
+
+    // Protected Member Functions
+
+        //- Compute sensitivities wrt the design variables (chain rule)
+        void virtual computeDVsSensitivities
+        (
+            scalarField& dvSens,
+            const scalarField& cpSens
+        );
+
+        //- Update the bounds of the internal control points
+        void updateInternalBounds
+        (
+            autoPtr<scalarField>& lowerBounds,
+            autoPtr<scalarField>& upperBounds,
+            const NURBS3DVolume& boxI,
+            const label passed
+        );
+
+        //- Update the bounds of the boundary control points
+        void updateBoundaryBounds
+        (
+            autoPtr<scalarField>& lowerBounds,
+            autoPtr<scalarField>& upperBounds,
+            const NURBS3DVolume& boxI,
+            const label passed
+        );
+
+
+private:
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        noConstraint(const noConstraint&) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const noConstraint&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("none");
+
+
+    // Constructors
+
+        //- Construct from components
+        noConstraint
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            volumetricBSplinesDesignVariables& designVariables
+        );
+
+
+    //- Destructor
+    virtual ~noConstraint() = default;
+
+
+    // Member Functions
+
+        //- Compute the active design variables based on the IDs of the
+        //- active control point coordinates
+        virtual labelList computeActiveDesignVariables
+        (
+            const labelList& activeCPCoors
+        );
+
+        //- Transform bounds from control points to design variables
+        //  Does nothing in this case
+        virtual void computeBounds
+        (
+            autoPtr<scalarField>& lowerBounds,
+            autoPtr<scalarField>& upperBounds
+        );
+
+        //- Update the bounds of the design variables
+        //  Will update the bound  values if nonOverlappingCPs is active
+        virtual void updateBounds
+        (
+            autoPtr<scalarField>& lowerBounds,
+            autoPtr<scalarField>& upperBounds
+        );
+
+        //- Convert design variables to control points, stored in a scalarField
+        virtual tmp<scalarField> designVariablesToControlPoints
+        (
+            const scalarField& designVariables
+        );
+
+        //- Return the design variables corresponding to the given control
+        //- points
+        virtual tmp<scalarField> controlPointsToDesignVariables
+        (
+            const scalarField& cps
+        );
+
+        //- Convert the correction of the design variables to the correction of
+        //- the control points
+        virtual tmp<scalarField> correctionCPs
+        (
+            const scalarField& correctionDVs
+        );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.C
new file mode 100644
index 0000000000000000000000000000000000000000..d20d3e08b7de961d0c97d661481faa536ccab182
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.C
@@ -0,0 +1,600 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "volumetricBSplinesDesignVariables.H"
+#include "pointVolInterpolation.H"
+#include "displacementMethodvolumetricBSplinesMotionSolver.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(volumetricBSplinesDesignVariables, 0);
+    addToRunTimeSelectionTable
+    (
+        shapeDesignVariables,
+        volumetricBSplinesDesignVariables,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * * //
+
+Foam::label Foam::volumetricBSplinesDesignVariables::sensSize() const
+{
+    return 3*volBSplinesBase_.getTotalControlPointsNumber();
+}
+
+
+const Foam::labelList&
+Foam::volumetricBSplinesDesignVariables::activeSensitivities() const
+{
+    return volBSplinesBase_.getActiveDesignVariables();
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::setActiveDesignVariables()
+{
+    // Active design variables pertaining to the CPs numbering
+    labelList activeVarsInCPs = volBSplinesBase_.getActiveDesignVariables();
+
+    // Convert the aforementioned list to the numbering of the actual design
+    // variables
+    activeDesignVariables_ =
+        constraint_().computeActiveDesignVariables(activeVarsInCPs);
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::designVariablesToControlPoints()
+{
+    const scalarField& dvs = *this;
+
+    // Convert design variables to CPs coordinates, stored in a scalarField
+    scalarField cpsScalar(constraint_().designVariablesToControlPoints(dvs));
+
+    // Convert the scalarField to vectorFields and transfer to morphing boxes
+    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
+    label varID(0);
+    for (NURBS3DVolume& boxI : boxes)
+    {
+        vectorField cps(boxI.getControlPoints().size(), Zero);
+        for (vector& cpI : cps)
+        {
+            cpI.x() = cpsScalar[varID++];
+            cpI.y() = cpsScalar[varID++];
+            cpI.z() = cpsScalar[varID++];
+        }
+        boxI.setControlPoints(cps);
+    }
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::controlPointsToDesignVariables()
+{
+    // Store CP coordinates to a scalarField
+    scalarField cpsScalar(3*volBSplinesBase_.getTotalControlPointsNumber());
+    const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxes();
+    label varID(0);
+    for (const NURBS3DVolume& boxI : boxes)
+    {
+        const vectorField& cps = boxI.getControlPoints();
+        for (const vector& cpI : cps)
+        {
+            cpsScalar[varID++] = cpI.x();
+            cpsScalar[varID++] = cpI.y();
+            cpsScalar[varID++] = cpI.z();
+        }
+    }
+
+    // Convert this scalarField to the design variables
+    scalarField::operator=
+        (constraint_().controlPointsToDesignVariables(cpsScalar));
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::controlPointsToDesignVariables
+(
+    const vectorField& controlPoints
+)
+{
+    // Store CP coordinates to a scalarField
+    scalarField cpsScalar(3*volBSplinesBase_.getTotalControlPointsNumber());
+    const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxes();
+    label varID(0);
+    for (const NURBS3DVolume& boxI : boxes)
+    {
+        const label nCPs(boxI.getControlPoints().size());
+        for (label cpI = 0; cpI < nCPs; ++cpI)
+        {
+            cpsScalar[varID++] = controlPoints[cpI].x();
+            cpsScalar[varID++] = controlPoints[cpI].y();
+            cpsScalar[varID++] = controlPoints[cpI].z();
+        }
+    }
+
+    // Convert this scalarField to the design variables
+    scalarField::operator=
+        (constraint_().controlPointsToDesignVariables(cpsScalar));
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::readBounds
+(
+    autoPtr<scalar> lowerBoundPtr,
+    autoPtr<scalar> upperBoundPtr
+)
+{
+    designVariables::readBounds(lowerBoundPtr, upperBoundPtr);
+    readBounds(lowerBounds_, "lower", -1);
+    readBounds(upperBounds_, "upper",  1);
+
+    // Update bounds based on the constraints - WIP
+    constraint_().computeBounds(lowerBounds_, upperBounds_);
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::readBounds
+(
+    autoPtr<scalarField>& bounds,
+    const word& boundsName,
+    const label sign
+)
+{
+    // Read global bounds for the control points
+    if (dict_.found(boundsName + "CPBounds"))
+    {
+        bounds.reset(new scalarField(getVars().size()));
+
+        vector CPBounds(dict_.get<vector>(boundsName + "CPBounds"));
+        const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
+        label varID(0);
+        for (const NURBS3DVolume& boxI : boxes)
+        {
+            const label nCPs(boxI.getControlPoints().size());
+            for (label iCP = 0; iCP < nCPs; ++iCP)
+            {
+                bounds()[varID++] = CPBounds.x();
+                bounds()[varID++] = CPBounds.y();
+                bounds()[varID++] = CPBounds.z();
+            }
+        }
+    }
+    // Read in bounds from the designVariables dictionary if present.
+    // If nonOverlappingCPs is used, the current CPs are used to determine the
+    // bounds of the CPs. If we continue from a previous solution, the current
+    // CPs are different from the initial ones and, hence, different bounds
+    // will be computed for the continuation run. Instead, read the bounds
+    // from the designVariables dict, if present
+    else if (localIOdictionary::found(boundsName + "Bounds"))
+    {
+        DebugInfo
+            << "Reading " << boundsName << "Bounds from dict " << endl;
+        bounds.reset
+            (new scalarField(boundsName + "Bounds", *this, getVars().size()));
+
+    }
+    else if (nonOverlappingCPs_)
+    {
+        DebugInfo
+            << "Setting " << boundsName << "Bounds from nonOverlappingCPs"
+            << endl;
+        bounds.reset(new scalarField(getVars().size()));
+        const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
+        label varID(0);
+        for (const NURBS3DVolume& boxI : boxes)
+        {
+            const vectorField& cps = boxI.getControlPoints();
+            const Vector<label> nCPsDir = boxI.nCPsPerDirection();
+            vector dists(Zero);
+            for (label idir = 0; idir < 3; ++idir)
+            {
+                dists[idir] =
+                    (max(cps.component(idir)) - min(cps.component(idir)))
+                   /scalar(nCPsDir[idir] - 1);
+            }
+            const label nCPs(boxI.getControlPoints().size());
+            for (label iCP = 0; iCP < nCPs; ++iCP)
+            {
+                const vector& cp = cps[iCP];
+                bounds()[varID++] = cp.x() + sign*0.5*dists.x();
+                bounds()[varID++] = cp.y() + sign*0.5*dists.y();
+                bounds()[varID++] = cp.z() + sign*0.5*dists.z();
+            }
+        }
+    }
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::writeBounds
+(
+    const scalarField& bounds,
+    const word& name
+) const
+{
+    if (Pstream::master())
+    {
+        const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
+        label passed(0);
+        for (const NURBS3DVolume& boxI : boxes)
+        {
+            OFstream file
+            (
+                word("optimisation")/word("controlPoints")/boxI.name()
+              + name + mesh_.time().timeName() + ".csv"
+            );
+            // Write header
+            file<< "\"Points : 0\", \"Points : 1\", \"Points : 2\","
+                << "\"i\", \"j\", \"k\""<< endl;
+
+            const vectorField& cps = boxI.getControlPoints();
+            const label nCPsU = boxI.basisU().nCPs();
+            const label nCPsV = boxI.basisV().nCPs();
+            forAll(cps, cpI)
+            {
+                const label k = cpI/label(nCPsU*nCPsV);
+                const label j = (cpI - k*nCPsU*nCPsV)/nCPsU;
+                const label i = (cpI - k*nCPsU*nCPsV - j*nCPsU);
+
+                file<< bounds[3*cpI + passed] << ", "
+                    << bounds[3*cpI + 1 + passed] << ", "
+                    << bounds[3*cpI + 2 + passed] << ", "
+                    << i << ", "
+                    << j << ", "
+                    << k << endl;
+            }
+            passed += 3*cps.size();
+        }
+    }
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::setDisplacement
+(
+    const vectorField& cpMovement
+)
+{
+    displacementMethod& dm = displMethodPtr_.ref();
+    // Are volumetric B-Splines also used to move the mesh ?
+    if (isA<displacementMethodvolumetricBSplinesMotionSolver>(dm))
+    {
+        // Communicate the control points movement to the displacement method
+        displMethodPtr_->setControlField(cpMovement);
+    }
+    else
+    {
+        // This will also update the control point positions
+        tmp<vectorField> tnewPoints =
+            volBSplinesBase_.computeBoundaryDisplacement
+            (
+                cpMovement,
+                parametertisedPatches_.toc()
+            );
+        const vectorField& newPoints = tnewPoints();
+
+        pointVectorField dx
+        (
+            IOobject
+            (
+                "dx",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE,
+                false
+            ),
+            pointMesh::New(mesh_),
+            dimensionedVector(dimless, Zero)
+        );
+
+        for (const label pI : parametertisedPatches_)
+        {
+            dx.boundaryField()[pI].setInInternalField
+            (
+                dx.primitiveFieldRef(),
+                vectorField(newPoints, mesh_.boundaryMesh()[pI].meshPoints())
+            );
+        }
+
+        // Set boundary movement of motion solver
+        displMethodPtr_->setMotionField(dx);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::volumetricBSplinesDesignVariables::volumetricBSplinesDesignVariables
+(
+    fvMesh& mesh,
+    const dictionary& dict
+)
+:
+    shapeDesignVariables(mesh, dict),
+    localIOdictionary
+    (
+        IOobject
+        (
+            "volumetricBSplinesDesignVariables",
+            mesh.time().timeName(),
+            fileName("uniform"),
+            mesh,
+            IOobject::READ_IF_PRESENT,
+            IOobject::AUTO_WRITE
+        ),
+        word::null
+    ),
+    volBSplinesBase_(const_cast<volBSplinesBase&>(volBSplinesBase::New(mesh))),
+    nonOverlappingCPs_(dict_.getOrDefault<bool>("nonOverlappingCPs", false)),
+    updateBounds_(dict_.getOrDefault<bool>("updateBounds", true)),
+    constraint_(morphingBoxConstraint::New(mesh, dict, *this))
+{
+    // Read in design variables if present or initialise them
+    if (localIOdictionary::found("designVariables"))
+    {
+        scalarField::operator=
+            (scalarField("designVariables", *this, scalarField::size()));
+    }
+    else if (constraint_().initialiseVars())
+    {
+        controlPointsToDesignVariables();
+    }
+
+    // Set the active design variables
+    setActiveDesignVariables();
+
+    // Read bounds for design variables, if present
+    readBounds();
+}
+
+
+// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
+
+Foam::tmp<Foam::vectorField>
+Foam::volumetricBSplinesDesignVariables::controlPointMovement
+(
+    const scalarField& correction
+)
+{
+    auto tcpMovement
+    (
+        tmp<vectorField>::New
+        (
+            volBSplinesBase_.getTotalControlPointsNumber(),
+            Zero
+        )
+    );
+    vectorField& cpMovement = tcpMovement.ref();
+
+    // Convert the correction pertaining to the design variables to a
+    // scalarField correction for the control points
+    const scalarField correctionCPs(constraint_().correctionCPs(correction));
+
+    // scalarField to vectorField conversion
+    forAll(cpMovement, iCP)
+    {
+        cpMovement[iCP].x() = correctionCPs[3*iCP];
+        cpMovement[iCP].y() = correctionCPs[3*iCP + 1];
+        cpMovement[iCP].z() = correctionCPs[3*iCP + 2];
+    }
+    volBSplinesBase_.boundControlPointMovement(cpMovement);
+
+    return tcpMovement;
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::update(scalarField& correction)
+{
+    // Get controlPoint movement from correction
+    tmp<vectorField> tcpMovement = controlPointMovement(correction);
+    const vectorField& cpMovement = tcpMovement();
+
+    // Set the field driving the displacement method
+    setDisplacement(cpMovement);
+
+    // Do the actual mesh movement
+    // Updates also the control point positions
+    moveMesh();
+
+    // Update the design variables
+    scalarField::operator+=(correction);
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::resetDesignVariables()
+{
+    shapeDesignVariables::resetDesignVariables();
+    designVariablesToControlPoints();
+}
+
+
+Foam::scalar
+Foam::volumetricBSplinesDesignVariables::computeEta(scalarField& correction)
+{
+    return constraint_().computeEta(correction, maxInitChange_());
+}
+
+
+bool Foam::volumetricBSplinesDesignVariables::globalSum() const
+{
+    return false;
+}
+
+
+Foam::tmp<Foam::scalarField>
+Foam::volumetricBSplinesDesignVariables::assembleSensitivities
+(
+    adjointSensitivity& adjointSens
+)
+{
+    return
+        constraint_().postProcessSens
+        (
+            shapeDesignVariables::assembleSensitivities(adjointSens)(),
+            adjointSens.getAdjointSolver().solverName()
+        );
+}
+
+
+void Foam::volumetricBSplinesDesignVariables::evolveNumber()
+{
+    constraint_().updateBounds(lowerBounds_, upperBounds_);
+}
+
+
+bool Foam::volumetricBSplinesDesignVariables::writeData(Ostream& os) const
+{
+    scalarField::writeEntry("designVariables", os);
+    if (lowerBounds_)
+    {
+        lowerBounds_().writeEntry("lowerBounds", os);
+        writeBounds(lowerBounds_(), "lowerBounds");
+    }
+    if (upperBounds_)
+    {
+        upperBounds_().writeEntry("upperBounds", os);
+        writeBounds(upperBounds_(), "upperBounds");
+    }
+    return constraint_().writeData(os);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::volumetricBSplinesDesignVariables::dxdbVol
+(
+    const label varID
+) const
+{
+    const displacementMethod& dm = displMethodPtr_();
+    if (isA<displacementMethodvolumetricBSplinesMotionSolver>(dm))
+    {
+        Vector<label> decomposed = volBSplinesBase_.decomposeDV(varID);
+        const label boxI = decomposed.x();
+        const label cpILocal = decomposed.y();
+        const label dir = decomposed.z();
+
+        pointTensorField dxdb(volBSplinesBase_.boxRef(boxI).getDxDb(cpILocal));
+        return unzipCol(dxdb, dir);
+    }
+    return tmp<vectorField>::New(0);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::volumetricBSplinesDesignVariables::dxdbFace
+(
+    const label patchI,
+    const label varID
+) const
+{
+    Vector<label> decomposed = volBSplinesBase_.decomposeDV(varID);
+    const label boxI = decomposed.x();
+    const label cpILocal = decomposed.y();
+    const label dir = decomposed.z();
+
+    tensorField dxdb
+        (volBSplinesBase_.boxRef(boxI).patchDxDbFace(patchI, cpILocal));
+    return unzipCol(dxdb, dir);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::volumetricBSplinesDesignVariables::dndb
+(
+    const label patchI,
+    const label varID
+) const
+{
+    Vector<label> decomposed = volBSplinesBase_.decomposeDV(varID);
+    const label boxI = decomposed.x();
+    const label cpILocal = decomposed.y();
+    const label dir = decomposed.z();
+
+    tensorField dndb
+    (
+        volBSplinesBase_.boxRef(boxI).
+            dndbBasedSensitivities(patchI, cpILocal, false)
+    );
+    return unzipCol(dndb, dir);
+}
+
+
+Foam::tmp<Foam::vectorField> Foam::volumetricBSplinesDesignVariables::dSdb
+(
+    const label patchI,
+    const label varID
+) const
+{
+    Vector<label> decomposed = volBSplinesBase_.decomposeDV(varID);
+    const label boxI = decomposed.x();
+    const label cpILocal = decomposed.y();
+    const label dir = decomposed.z();
+
+    tensorField dndb
+    (
+        volBSplinesBase_.boxRef(boxI).dndbBasedSensitivities(patchI, cpILocal)
+    );
+    return unzipCol(dndb, dir);
+}
+
+
+Foam::tmp<Foam::volVectorField> Foam::volumetricBSplinesDesignVariables::dCdb
+(
+    const label varID
+) const
+{
+    Vector<label> decomposed = volBSplinesBase_.decomposeDV(varID);
+    const label boxI = decomposed.x();
+    const label cpILocal = decomposed.y();
+    const label dir = decomposed.z();
+    NURBS3DVolume& box = volBSplinesBase_.boxRef(boxI);
+    pointVolInterpolation volPointInter(pointMesh::New(mesh_), mesh_);
+    // WIP: we compute the entire dxdb tensor corresponding to the contol point
+    // and then extract the desired direction. This is quite expensive.
+    // Specific functions returning what we want should be implemented in
+    // NURBS3DVolume to reduce the cost
+    tmp<volTensorField> dxdb = volPointInter.interpolate(box.getDxDb(cpILocal));
+    auto tdxdbDir =
+        tmp<volVectorField>::New
+        (
+            IOobject
+            (
+                "dxdbDir",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh_,
+            dimensionedVector(dimless, Zero)
+        );
+    volVectorField& dxdbDir = tdxdbDir.ref();
+    unzipCol(dxdb(), vector::components(dir), dxdbDir);
+    return tdxdbDir;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.H
new file mode 100644
index 0000000000000000000000000000000000000000..8e033f21549d855f5f0fb67a12ac08eeecbda96e
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.H
@@ -0,0 +1,263 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+Class
+    Foam::volumetricBSplinesDesignVariables
+
+Description
+    Volumetric B-Splines design variables for shape optimisation
+
+SourceFiles
+    volumetricBSplinesDesignVariables.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef volumetricBSplinesDesignVariables_H
+#define volumetricBSplinesDesignVariables_H
+
+#include "shapeDesignVariables.H"
+#include "volBSplinesBase.H"
+#include "morphingBoxConstraint.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+              Class volumetricBSplinesDesignVariables Declaration
+\*---------------------------------------------------------------------------*/
+
+class volumetricBSplinesDesignVariables
+:
+    public shapeDesignVariables,
+    public localIOdictionary
+{
+protected:
+
+    // Protected data
+
+        //- Reference to underlaying volumetric B-Splines morpher
+        volBSplinesBase& volBSplinesBase_;
+
+        //- Should the control points be non-overlapping
+        bool nonOverlappingCPs_;
+
+        //- Should the bounds of the non-overlapping control points be updated
+        //- in each optimisation cycle
+        bool updateBounds_;
+
+        //- Constraint imposed on the movement of the design variables.
+        //  Can be used to e.g. impose a uniform movement of the control points
+        //  in one direction, etc.
+        autoPtr<morphingBoxConstraint> constraint_;
+
+
+    // Protected Member Functions
+
+        //- Size of the active control points
+        virtual label sensSize() const;
+
+        //- Components of the active control points
+        virtual const labelList& activeSensitivities() const;
+
+        //- Set IDs of active design variables
+        //  Might be different than what volBSplinesBase_ returns, if
+        //  constraint_ changes the number of design variables
+        void setActiveDesignVariables();
+
+        //- Set control points based on current design variables values
+        void designVariablesToControlPoints();
+
+        //- Set the design variables based on the current control points
+        void controlPointsToDesignVariables();
+
+        //- Set the design variables based on the given control points
+        void controlPointsToDesignVariables(const vectorField& controlPoints);
+
+        //- Read bounds for design variables, if present
+        void readBounds
+        (
+            autoPtr<scalar> lowerBoundPtr = nullptr,
+            autoPtr<scalar> upperBoundPtr = nullptr
+        );
+
+        //- Read one set of bounds (lower, upper)
+        void readBounds
+        (
+            autoPtr<scalarField>& bounds,
+            const word& boundsName,
+            const label sign
+        );
+
+        //- Write current bounds to file
+        void writeBounds(const scalarField& bounds, const word& name) const;
+
+        //- Set the field driving the displacement method.
+        //  Can be either the movement of the control points or the boundary
+        //  displacement, depending on the method used to move the mesh
+        void setDisplacement(const vectorField& cpMovement);
+
+
+private:
+
+    // Private Member Functions
+
+        //- Disallow default bitwise copy construct
+        volumetricBSplinesDesignVariables
+        (
+            const volumetricBSplinesDesignVariables&
+        ) = delete;
+
+        //- Disallow default bitwise assignment
+        void operator=(const volumetricBSplinesDesignVariables&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("volumetricBSplines");
+
+
+    // Constructors
+
+        //- Construct from components
+        volumetricBSplinesDesignVariables
+        (
+            fvMesh& mesh,
+            const dictionary& dict
+        );
+
+
+    //- Destructor
+    virtual ~volumetricBSplinesDesignVariables() = default;
+
+
+    // Member Functions
+
+        //- Are control points non-overlapping
+        inline bool nonOverlappingCPs() const
+        {
+            return nonOverlappingCPs_;
+        }
+
+        //- Are bounds to be updated after each optmisation cycle
+        inline bool updateBounds() const
+        {
+            return updateBounds_;
+        }
+
+        //- Constant access to the volBSplinesBase object
+        inline const volBSplinesBase& getVolBSplinesBase() const
+        {
+            return volBSplinesBase_;
+        }
+
+        //- Non-constant access to the volBSplinesBase object
+        inline volBSplinesBase& getVolBSplinesBase()
+        {
+            return volBSplinesBase_;
+        }
+
+        //- Transform correction of design variables to control points movement
+        tmp<vectorField> controlPointMovement
+        (
+            const scalarField& correction
+        );
+
+        //- Update design variables based on a given correction
+        virtual void update(scalarField& correction);
+
+        //- Reset to starting point of line search
+        virtual void resetDesignVariables();
+
+        //- Compute eta if not set in the first step
+        virtual scalar computeEta(scalarField& correction);
+
+        //- Whether to use global sum when computing matrix-vector products
+        //- in update methods
+        virtual bool globalSum() const;
+
+        //- Assemble the sensitivity derivatives, by also applying possible
+        //- constraints
+        virtual tmp<scalarField> assembleSensitivities
+        (
+            adjointSensitivity& adjointSens
+        );
+
+        //- For design variables with a dynamic character (i.e. changing
+        //  number), perform the evolution.
+        //  Hijacked here to evolve the bounds of the design variables
+        //  after the end of each optimisation cycle
+        virtual void evolveNumber();
+
+        //- Write fields to support continuation
+        virtual bool writeData(Ostream& os) const;
+
+        // Fields related to sensitivity computations
+
+            //- Get dxdb for all mesh points
+            virtual tmp<vectorField> dxdbVol
+            (
+                const label varID
+            ) const;
+
+            //- Get dxdb for given design variable and patch
+            virtual tmp<vectorField> dxdbFace
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dndb for given design variable and patch
+            virtual tmp<vectorField> dndb
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dSdb for given design variable and patch
+            virtual tmp<vectorField> dSdb
+            (
+                const label patchI,
+                const label varID
+            ) const;
+
+            //- Get dCdb for given design variable.
+            //  Used for FI-based sensitivities
+            virtual tmp<volVectorField> dCdb(const label varID) const;
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.C
index 20f2efc38d0314feaa9c2b9a33a41e0370b93ca3..0ec06af702861190f32909f44c688f43aa64f2a8 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -34,7 +34,7 @@ License
 
 namespace Foam
 {
-    defineTypeNameAndDebug(ArmijoConditions, 0);
+    defineTypeNameAndDebug(ArmijoConditions, 1);
     addToRunTimeSelectionTable
     (
         lineSearch,
@@ -49,10 +49,11 @@ namespace Foam
 Foam::ArmijoConditions::ArmijoConditions
 (
     const dictionary& dict,
-    const Time& time
+    const Time& time,
+    updateMethod& UpdateMethod
 )
 :
-    lineSearch(dict, time),
+    lineSearch(dict, time, UpdateMethod),
     c1_(coeffsDict().getOrDefault<scalar>("c1", 1.e-4))
 {}
 
@@ -61,9 +62,15 @@ Foam::ArmijoConditions::ArmijoConditions
 
 bool Foam::ArmijoConditions::converged()
 {
-    Info<< "New merit function value " << newMeritValue_ << endl;
-    Info<< "Old merit function value " << oldMeritValue_ << endl;
-    Info<< "Extrapolated merit function value "
+    DebugInfo
+        << "New merit function value " << newMeritValue_ << endl;
+    DebugInfo
+        << "Old merit function value " << oldMeritValue_ << endl;
+    DebugInfo
+        << "c1, step, directionalDeriv "
+        << c1_ << " " << step_ << " " <<directionalDeriv_
+        << endl;
+    DebugInfo<< "Extrapolated merit function value "
         << oldMeritValue_ + c1_*step_*directionalDeriv_ << endl;
     return newMeritValue_ < oldMeritValue_ + c1_*step_*directionalDeriv_;
 }
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.H
index 3e1890e045514f77e35ff94a23d20e7d51eb1f77..2c16c84e95843a03a87c7eb632d11470f8a36850 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/ArmijoConditions/ArmijoConditions.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -80,25 +80,15 @@ public:
     TypeName("ArmijoConditions");
 
 
-    // Declare run-time constructor selection table
-
-        declareRunTimeSelectionTable
-        (
-            autoPtr,
-            ArmijoConditions,
-            dictionary,
-            (
-                const dictionary& dict,
-                const Time& time
-            ),
-            (dict)
-        );
-
-
     // Constructors
 
         //- Construct from components
-        ArmijoConditions(const dictionary& dict, const Time& time);
+        ArmijoConditions
+        (
+            const dictionary& dict,
+            const Time& time,
+            updateMethod& UpdatheMethod
+        );
 
 
     // Destructor
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.C
index 0701dae6eb1a8b77609cab2f2c46b3aed387d13c..8ae94392d36ad4b2b5068838307847d57d73b64e 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -49,7 +49,12 @@ const Foam::dictionary& Foam::lineSearch::coeffsDict()
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::lineSearch::lineSearch(const dictionary& dict, const Time& time)
+Foam::lineSearch::lineSearch
+(
+    const dictionary& dict,
+    const Time& time,
+    updateMethod& UpdateMethod
+)
 :
     dict_(dict),
     lineSearchDict_
@@ -77,6 +82,7 @@ Foam::lineSearch::lineSearch(const dictionary& dict, const Time& time)
     minStep_(dict.getOrDefault<scalar>("minStep", 0.3)),
     step_(Zero),
     iter_(lineSearchDict_.getOrDefault<label>("iter", 0)),
+    innerIter_(0),
     maxIters_(dict.getOrDefault<label>("maxIters", 4)),
     extrapolateInitialStep_
     (
@@ -86,7 +92,8 @@ Foam::lineSearch::lineSearch(const dictionary& dict, const Time& time)
             false
         )
     ),
-    stepUpdate_(stepUpdate::New(dict))
+    stepUpdate_(stepUpdate::New(dict)),
+    updateMethod_(UpdateMethod)
 {}
 
 
@@ -95,7 +102,8 @@ Foam::lineSearch::lineSearch(const dictionary& dict, const Time& time)
 Foam::autoPtr<Foam::lineSearch> Foam::lineSearch::New
 (
     const dictionary& dict,
-    const Time& time
+    const Time& time,
+    updateMethod& UpdateMethod
 )
 {
     autoPtr<lineSearch> lineSrch(nullptr);
@@ -119,7 +127,7 @@ Foam::autoPtr<Foam::lineSearch> Foam::lineSearch::New
             ) << exit(FatalIOError);
         }
 
-        lineSrch.reset((ctorPtr(dict, time)).ptr());
+        lineSrch.reset((ctorPtr(dict, time, UpdateMethod)).ptr());
     }
     else
     {
@@ -140,9 +148,9 @@ void Foam::lineSearch::setDeriv(const scalar deriv)
 }
 
 
-void Foam::lineSearch::setDirection(const scalarField& direction)
+void Foam::lineSearch::setNewDeriv(const scalar deriv)
 {
-    direction_ = direction;
+    // Does nothing in base
 }
 
 
@@ -162,6 +170,7 @@ void Foam::lineSearch::setOldMeritValue(const scalar value)
 
 void Foam::lineSearch::reset()
 {
+    innerIter_ = 0;
     if (extrapolateInitialStep_ && iter_ != 0)
     {
         // step_ = 2*(oldMeritValue_-prevMeritValue_)/directionalDeriv_;
@@ -181,35 +190,57 @@ void Foam::lineSearch::reset()
 }
 
 
-Foam::label Foam::lineSearch::maxIters() const
+void Foam::lineSearch::updateStep(const scalar newStep)
 {
-    return maxIters_;
+    step_ = newStep;
 }
 
 
-Foam::scalar Foam::lineSearch::step() const
+void Foam::lineSearch::updateCorrection(scalarField& correction)
 {
-    return step_;
+    correction *= step_;
 }
 
 
-void Foam::lineSearch::updateStep(const scalar newStep)
+bool Foam::lineSearch::loop()
 {
-    step_ = newStep;
+    const bool isRunning = innerIter_ < maxIters_;
+
+    if (isRunning)
+    {
+        ++innerIter_;
+    }
+
+    return isRunning;
+}
+
+
+bool Foam::lineSearch::computeGradient() const
+{
+    return false;
+}
+
+
+void Foam::lineSearch::postUpdate()
+{
+    this->operator++();
 }
 
 
 Foam::lineSearch& Foam::lineSearch::operator++()
 {
-    iter_++;
+    ++iter_;
     prevMeritDeriv_ = directionalDeriv_;
     lineSearchDict_.add<scalar>("prevMeritDeriv", prevMeritDeriv_, true);
     lineSearchDict_.add<label>("iter", iter_, true);
-    lineSearchDict_.regIOobject::writeObject
-    (
-        IOstreamOption(IOstreamOption::ASCII),
-        true
-    );
+    if (lineSearchDict_.time().writeTime())
+    {
+        lineSearchDict_.regIOobject::writeObject
+        (
+            IOstreamOption(IOstreamOption::ASCII),
+            true
+        );
+    }
 
     return *this;
 }
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.H
index 0904559bd83c34506c3074cfb1e8fe55c41473ba..52cf061cfd03bad258d29eafae9591a5266a4a71 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/lineSearch/lineSearch.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -44,6 +44,7 @@ SourceFiles
 #include "IOdictionary.H"
 #include "scalarField.H"
 #include "stepUpdate.H"
+#include "updateMethod.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -90,9 +91,12 @@ protected:
         //- Correction multiplier
         scalar step_;
 
-        //- Inner line search iteration
+        //- Optimisation cycle
         label iter_;
 
+        //- Inner line search iteration
+        label innerIter_;
+
         //- Maximum line search iterations
         label maxIters_;
 
@@ -104,6 +108,9 @@ protected:
         //- Mechanism to update method if line search conditions are not set
         autoPtr<stepUpdate> stepUpdate_;
 
+        //- Reference to the update method related to the line search
+        updateMethod& updateMethod_;
+
 
     // Protected Member Functions
 
@@ -137,16 +144,22 @@ public:
             dictionary,
             (
                 const dictionary& dict,
-                const Time& time
+                const Time& time,
+                updateMethod& UpdateMethod
             ),
-            (dict, time)
+            (dict, time, UpdateMethod)
         );
 
 
     // Constructors
 
         //- Construct from components
-        lineSearch(const dictionary& dict, const Time& time);
+        lineSearch
+        (
+            const dictionary& dict,
+            const Time& time,
+            updateMethod& UpdateMethod
+        );
 
     // Selectors
 
@@ -154,7 +167,8 @@ public:
         static autoPtr<lineSearch> New
         (
             const dictionary& dict,
-            const Time& time
+            const Time& time,
+            updateMethod& UpdateMethod
         );
 
 
@@ -164,46 +178,80 @@ public:
 
     // Member Functions
 
-       //- Set objective derivative
-       virtual void setDeriv(const scalar deriv);
+        //- Set directional derivative
+        virtual void setDeriv(const scalar deriv);
+
+        //- Set new directional derivative.
+        //  Does nothing in base. Only used by methods that require the
+        //  gradient information to be computed at the new point
+        virtual void setNewDeriv(const scalar deriv);
+
+        //- Set direction
+        inline void setDirection(const scalarField& direction)
+        {
+            direction_ = direction;
+        }
+
+        //- Set new objective value
+        void setNewMeritValue(const scalar value);
+
+        //- Set old objective value
+        void setOldMeritValue(const scalar value);
+
+        //- Reset step to initial value
+        virtual void reset();
+
+        //- Get inner line search iteration
+        inline label innerIter() const
+        {
+            return innerIter_;
+        }
 
-       //- Set direction
-       void setDirection(const scalarField& direction);
+        //- Get max number of iterations
+        inline label maxIters() const
+        {
+            return maxIters_;
+        }
 
-       //- Set new objective value
-       void setNewMeritValue(const scalar value);
+        //- Get current step
+        inline scalar step() const
+        {
+            return step_;
+        }
 
-       //- Set old objective value
-       void setOldMeritValue(const scalar value);
+        //- Return the correction of the design variables
+        virtual bool converged() = 0;
 
-       //- Reset step to initial value
-       virtual void reset();
+        //- Update the line search step based on the specific line search
+        //- strategy, e.g. bisection, quadratic fit, etc.
+        virtual void updateStep() = 0;
 
-       //- Get max number of iterations
-       label maxIters() const;
+        //- Update the step using the supplied value
+        virtual void updateStep(const scalar newStep);
 
-       //- Get current step
-       scalar step() const;
+        //- Update the correction.
+        //  Multiplies with step in base
+        virtual void updateCorrection(scalarField& correction);
 
-       //- Return the correction of the design variables
-       virtual bool converged() = 0;
+        //- Return true if lineSearch should continue and if so increment inner
+        //  iteration
+        virtual bool loop();
 
-       //- Update the line search step based on the specific line search
-       //- strategy, e.g. bisection, quadratic fit, etc.
-       virtual void updateStep() = 0;
+        //- Does line search need to update the gradient?
+        virtual bool computeGradient() const;
 
-       //- Update the step using the supplied value
-       virtual void updateStep(const scalar newStep);
+        //- Execute steps at the end of the line search iterations
+        virtual void postUpdate();
 
 
     // Member operators
 
-       //- Increment iteration number and store merit value corresponding to
-       //- the previous optimisation cycle
-       virtual lineSearch& operator++();
+        //- Increment iteration number and store merit value corresponding to
+        //- the previous optimisation cycle
+        virtual lineSearch& operator++();
 
-       //- Postfix increment. Necessary for compilation
-       virtual lineSearch& operator++(int);
+        //- Postfix increment. Necessary for compilation
+        virtual lineSearch& operator++(int);
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/stepUpdate/quadratic/quadratic.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/stepUpdate/quadratic/quadratic.C
index 7a73727b5576e3842908ac749c56dd489efd9e56..6e4722a9262790c8fba18bc4c583048751924c0b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/stepUpdate/quadratic/quadratic.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/lineSearch/stepUpdate/quadratic/quadratic.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -60,10 +60,10 @@ Foam::quadratic::quadratic(const dictionary& dict)
 
 void Foam::quadratic::updateStep(scalar& step)
 {
-    Info<< "f(0)" << firstMeritValue_ << endl;
-    Info<< "f(a0)" << secondMeritValue_ << endl;
-    Info<< "df(0)" << meritDerivative_ << endl;
-    Info<< "a0 " <<  step << endl;
+    Info<< "First merit value,  f(0)  = " << firstMeritValue_ << endl;
+    Info<< "Second merit value, f(a0) = " << secondMeritValue_ << endl;
+    Info<< "Merit derivative,   df(0) = " << meritDerivative_ << endl;
+    Info<< "Previous step,      a0    = " <<  step << endl;
     scalar denom = 1./(step*step);
     scalar coeff1 =
         (secondMeritValue_ - meritDerivative_*step - firstMeritValue_)
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.C
deleted file mode 100644
index f4e156f457b76c7019816515ed2cf63a30198cfc..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.C
+++ /dev/null
@@ -1,208 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019-2021 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "optMeshMovement.H"
-#include "cellQuality.H"
-#include "createZeroField.H"
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-    defineTypeNameAndDebug(optMeshMovement, 0);
-    defineRunTimeSelectionTable(optMeshMovement, dictionary);
-}
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-Foam::scalar Foam::optMeshMovement::getMaxAllowedDisplacement() const
-{
-    if (!maxAllowedDisplacement_)
-    {
-        FatalErrorInFunction
-            << "maxAllowedDisplacement requested but not set" << nl
-            << exit(FatalError);
-    }
-
-    return maxAllowedDisplacement_();
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::optMeshMovement::optMeshMovement
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    const labelList& patchIDs
-)
-:
-    maxAllowedDisplacement_(nullptr),
-    mesh_(mesh),
-    dict_(dict),
-    correction_(0),
-    patchIDs_(patchIDs),
-    pointsInit_(mesh.points()),
-    displMethodPtr_(displacementMethod::New(mesh_, patchIDs_)),
-    writeMeshQualityMetrics_
-    (
-        dict.getOrDefault("writeMeshQualityMetrics", false)
-    )
-{
-    // Set maxAllowedDisplacement if provided
-    if (dict.found("maxAllowedDisplacement"))
-    {
-        maxAllowedDisplacement_.reset
-        (
-            new scalar(dict.get<scalar>("maxAllowedDisplacement"))
-        );
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
-
-Foam::autoPtr<Foam::optMeshMovement> Foam::optMeshMovement::New
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    const labelList& patchIDs
-)
-{
-    const word modelType(dict.get<word>("type"));
-
-    Info<< "optMeshMovement type : " << modelType << endl;
-
-    auto* ctorPtr = dictionaryConstructorTable(modelType);
-
-    if (!ctorPtr)
-    {
-        FatalIOErrorInLookup
-        (
-            dict,
-            "type",
-            modelType,
-            *dictionaryConstructorTablePtr_
-        ) << exit(FatalIOError);
-    }
-
-    return autoPtr<optMeshMovement>(ctorPtr(mesh, dict, patchIDs));
-}
-
-
-// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
-
-void Foam::optMeshMovement::setCorrection(const scalarField& correction)
-{
-    correction_ = correction;
-}
-
-
-void Foam::optMeshMovement::moveMesh()
-{
-    // Move mesh
-    displMethodPtr_->update();
-
-    // Check mesh quality
-    mesh_.checkMesh(true);
-
-    // If needed, plot mesh quality metrics
-    writeMeshQualityMetrics();
-}
-
-
-Foam::autoPtr<Foam::displacementMethod>&
-Foam::optMeshMovement::returnDisplacementMethod()
-{
-    return displMethodPtr_;
-}
-
-
-const Foam::labelList& Foam::optMeshMovement::getPatchIDs()
-{
-    return patchIDs_;
-}
-
-
-void Foam::optMeshMovement::writeMeshQualityMetrics()
-{
-    if (writeMeshQualityMetrics_)
-    {
-        cellQuality cellQualityEngine(mesh_);
-        tmp<scalarField> cellNonOrtho(cellQualityEngine.nonOrthogonality());
-        tmp<scalarField> cellSkewness(cellQualityEngine.skewness());
-        Info<< "Average, Max cell non - orthogonality "
-            << gAverage(cellNonOrtho())
-            << " " << gMax(cellNonOrtho()) << endl;
-        Info<< "Average, Max cell skewness " << gAverage(cellSkewness())
-            << " " << gMax(cellSkewness()) << endl;
-        autoPtr<volScalarField> nonOrthoPtr
-        (
-           createZeroFieldPtr<scalar>(mesh_, "nonOrtho", dimless)
-        );
-        autoPtr<volScalarField> skewnessPtr
-        (
-           createZeroFieldPtr<scalar>(mesh_, "skewness", dimless)
-        );
-        nonOrthoPtr().primitiveFieldRef() = cellNonOrtho();
-        skewnessPtr().primitiveFieldRef() = cellSkewness();
-        nonOrthoPtr().write();
-        skewnessPtr().write();
-    }
-}
-
-
-void Foam::optMeshMovement::storeDesignVariables()
-{
-    pointsInit_ = mesh_.points();
-}
-
-
-void Foam::optMeshMovement::resetDesignVariables()
-{
-    Info<< "optMeshMovement:: resetting mesh points" << endl;
-    mesh_.movePoints(pointsInit_);
-}
-
-
-bool Foam::optMeshMovement::maxAllowedDisplacementSet() const
-{
-    return bool(maxAllowedDisplacement_);
-}
-
-
-Foam::labelList Foam::optMeshMovement::getActiveDesignVariables() const
-{
-    NotImplemented;
-    return labelList(0);
-}
-
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.H
deleted file mode 100644
index 5d96fc65c5555f554d861c938409f499f6260297..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.H
+++ /dev/null
@@ -1,201 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-
-Class
-    Foam::optMeshMovement
-
-Description
-    Abstract base class for translating an update of the design variables
-    into mesh movement
-
-SourceFiles
-    optMeshMovement.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef optMeshMovement_H
-#define optMeshMovement_H
-
-#include "displacementMethod.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-                       Class optMeshMovement Declaration
-\*---------------------------------------------------------------------------*/
-
-class optMeshMovement
-{
-private:
-
-    // Private Member Functions
-
-        //- Max allowed boundary displacement for the first optimisation cycle
-        autoPtr<scalar> maxAllowedDisplacement_;
-
-
-    // Private Member Functions
-
-        //- No copy construct
-        optMeshMovement(const optMeshMovement&) = delete;
-
-        //- No copy assignment
-        void operator=(const optMeshMovement&) = delete;
-
-
-protected:
-
-    // Protected data
-
-        fvMesh& mesh_;
-        const dictionary& dict_;
-
-        //- Correction of design variables
-        scalarField correction_;
-
-        //- IDs of patches to be moved
-        labelList patchIDs_;
-
-        //- Fall back points in case line-search is used
-        vectorField pointsInit_;
-
-        //- Mesh movement engine and interface for applying mesh movement
-        //- boundary conditions
-        autoPtr<displacementMethod> displMethodPtr_;
-
-        //- Whether to write the mesh quality metrics to files each time the
-        //- mesh is updated
-        bool writeMeshQualityMetrics_;
-
-
-    // Protected Member Functions
-
-        //- Get maxAllowedDisplacement, is set
-        scalar getMaxAllowedDisplacement() const;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("optMeshMovement");
-
-
-    // Declare run-time constructor selection table
-
-        declareRunTimeSelectionTable
-        (
-            autoPtr,
-            optMeshMovement,
-            dictionary,
-            (
-                fvMesh& mesh,
-                const dictionary& dict,
-                const labelList& patchIDs
-            ),
-            (
-                mesh,
-                dict,
-                patchIDs
-            )
-        );
-
-
-    // Constructors
-
-        //- Construct from components
-        optMeshMovement
-        (
-            fvMesh& mesh,
-            const dictionary& dict,
-            const labelList& patchIDs
-        );
-
-
-    // Selectors
-
-        static autoPtr<optMeshMovement> New
-        (
-            fvMesh& mesh,
-            const dictionary& dict,
-            const labelList& patchIDs
-        );
-
-
-    //- Destructor
-    virtual ~optMeshMovement() = default;
-
-
-    // Member Functions
-
-       //- Set design variable correction
-       void setCorrection(const scalarField& correction);
-
-       //- Calculates mesh movemnt based on the correction of the design
-       //- variables
-       virtual void moveMesh();
-
-       //- Return displacementMethod
-       autoPtr<displacementMethod>& returnDisplacementMethod();
-
-       //- Return patchIDs
-       const labelList& getPatchIDs();
-
-       //- Write mesh quality metrics
-       void writeMeshQualityMetrics();
-
-       //- Store design variables and mesh, to act as the starting point of
-       //- line search
-       virtual void storeDesignVariables();
-
-       //- Reset to starting point of line search
-       virtual void resetDesignVariables();
-
-       //- Compute eta value based on max displacement
-       virtual scalar computeEta(const scalarField& correction) = 0;
-
-       //- Whether maxAllowedDisplacement has been set
-       bool maxAllowedDisplacementSet() const;
-
-       //- Return active design variables.
-       //  Implemented only for certain parametetisations
-       virtual labelList getActiveDesignVariables() const;
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.C
deleted file mode 100644
index 11c0e384480d487e92bd4b5e3cb8307db5a7b2be..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.C
+++ /dev/null
@@ -1,160 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "optMeshMovementBezier.H"
-#include "addToRunTimeSelectionTable.H"
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-    defineTypeNameAndDebug(optMeshMovementBezier, 0);
-    addToRunTimeSelectionTable
-    (
-        optMeshMovement,
-        optMeshMovementBezier,
-        dictionary
-    );
-}
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void Foam::optMeshMovementBezier::computeBoundaryMovement
-(
-    const scalarField& correction
-)
-{
-    // Re-initialize movement to zero
-    dx_.primitiveFieldRef() = vector::zero;
-
-    // Compute boundary mesh movement using derivatives of the control points
-    // and parameterization information
-    const label nBezier = Bezier_.nBezier();
-    const boolList& confineXmovement = Bezier_.confineXmovement();
-    const boolList& confineYmovement = Bezier_.confineYmovement();
-    const boolList& confineZmovement = Bezier_.confineZmovement();
-    vectorField actualMovement(nBezier, Zero);
-    for (label iCP = 0; iCP < nBezier; iCP++)
-    {
-        // Confine x movement
-        if (!confineXmovement[iCP])
-        {
-            actualMovement[iCP].x() = correction[iCP];
-        }
-        // Confine y movement
-        if (!confineYmovement[iCP])
-        {
-            actualMovement[iCP].y() = correction[iCP + nBezier];
-        }
-        // Confine z movement
-        if (!confineZmovement[iCP])
-        {
-            actualMovement[iCP].z() = correction[iCP + 2*nBezier];
-        }
-        dx_ += Bezier_.dxidXj()[iCP] & actualMovement[iCP];
-    }
-
-    // Add to cumulative control point change (wrong in the first optimisation
-    // cycle if initial eta not set)
-    cumulativeChange_ += actualMovement;
-    Info<< "Cumulative control point change " << cumulativeChange_ << endl;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::optMeshMovementBezier::optMeshMovementBezier
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    const labelList& patchIDs
-)
-:
-    optMeshMovement(mesh, dict, patchIDs),
-    Bezier_(mesh, mesh.lookupObject<IOdictionary>("optimisationDict")),
-    dx_
-    (
-        IOobject
-        (
-            "dx",
-            mesh_.time().timeName(),
-            mesh_,
-            IOobject::NO_READ,
-            IOobject::NO_WRITE,
-            IOobject::NO_REGISTER
-        ),
-        pointMesh::New(mesh),
-        dimensionedVector(dimless, Zero)
-    ),
-    cumulativeChange_(Bezier_.nBezier(), Zero)
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::optMeshMovementBezier::moveMesh()
-{
-    // Update the boundary movement
-    computeBoundaryMovement(correction_);
-
-    // Set boundary movement of motion solver
-    displMethodPtr_->setMotionField(dx_);
-
-    // Move the mesh and check quality
-    optMeshMovement::moveMesh();
-}
-
-
-Foam::scalar
-Foam::optMeshMovementBezier::computeEta(const scalarField& correction)
-{
-    // Set unscaled correction
-    computeBoundaryMovement(correction);
-
-    // Get maximum boundary movement
-    const scalar maxDisplacement = gMax(mag(dx_.primitiveField()));
-
-    // Compute eta value
-    Info<< "maxAllowedDisplacement/maxDisplacement \t"
-        << getMaxAllowedDisplacement() << "/" << maxDisplacement << endl;
-    const scalar eta = getMaxAllowedDisplacement()/maxDisplacement;
-    Info<< "Setting eta value to " << eta << endl;
-
-    return eta;
-}
-
-
-Foam::labelList Foam::optMeshMovementBezier::getActiveDesignVariables() const
-{
-    return Bezier_.getActiveDesignVariables();
-}
-
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.C
deleted file mode 100644
index 0a494c137ec0e53f748b5a25a78829d39dd62d5c..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.C
+++ /dev/null
@@ -1,173 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "optMeshMovementVolumetricBSplines.H"
-#include "addToRunTimeSelectionTable.H"
-
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-    defineTypeNameAndDebug(optMeshMovementVolumetricBSplines, 0);
-    addToRunTimeSelectionTable
-    (
-        optMeshMovement,
-        optMeshMovementVolumetricBSplines,
-        dictionary
-    );
-}
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-Foam::vectorField Foam::optMeshMovementVolumetricBSplines::controlPointMovement
-(
-    const scalarField& correction
-)
-{
-    const label nControlPoints(correction.size()/3);
-    vectorField cpMovement(nControlPoints, Zero);
-
-    for (label iCP = 0; iCP < nControlPoints; ++iCP)
-    {
-        cpMovement[iCP].x() = correction[3*iCP];
-        cpMovement[iCP].y() = correction[3*iCP + 1];
-        cpMovement[iCP].z() = correction[3*iCP + 2];
-    }
-    displMethodPtr_->boundControlField(cpMovement);
-
-    return cpMovement;
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::optMeshMovementVolumetricBSplines::optMeshMovementVolumetricBSplines
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    const labelList& patchIDs
-)
-:
-    optMeshMovement(mesh, dict, patchIDs),
-    volBSplinesBase_
-    (
-        const_cast<volBSplinesBase&>(volBSplinesBase::New(mesh))
-    ),
-    cpsInit_(volBSplinesBase_.getNumberOfBoxes())
-{
-    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-
-    forAll(boxes, boxI)
-    {
-        cpsInit_[boxI].setSize
-        (
-            boxes[boxI].getControlPoints().size()
-        );
-        cpsInit_[boxI] = boxes[boxI].getControlPoints();
-    }
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::optMeshMovementVolumetricBSplines::moveMesh()
-{
-    // Get controlPoint movement from correction
-    vectorField cpMovement = controlPointMovement(correction_);
-
-    // Set movement of the B-Splines control points
-    displMethodPtr_->setControlField(cpMovement);
-
-    // Move the mesh and check quality
-    optMeshMovement::moveMesh();
-}
-
-
-void Foam::optMeshMovementVolumetricBSplines::storeDesignVariables()
-{
-    optMeshMovement::storeDesignVariables();
-    const PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxes();
-    forAll(boxes, boxI)
-    {
-        cpsInit_[boxI] = boxes[boxI].getControlPoints();
-    }
-}
-
-
-void Foam::optMeshMovementVolumetricBSplines::resetDesignVariables()
-{
-    // Reset mesh points
-    optMeshMovement::resetDesignVariables();
-
-    DebugInfo
-        << "optMeshMovementVolumetricBSplines:: resetting control points"
-        << endl;
-
-    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-    forAll(boxes, boxI)
-    {
-        boxes[boxI].setControlPoints(cpsInit_[boxI]);
-    }
-}
-
-
-Foam::scalar Foam::optMeshMovementVolumetricBSplines::computeEta
-(
-    const scalarField& correction
-)
-{
-    const vectorField cpMovement(controlPointMovement(correction));
-    const scalar maxDisplacement
-    (
-        volBSplinesBase_.computeMaxBoundaryDisplacement
-        (
-            cpMovement,
-            patchIDs_
-        )
-    );
-
-    Info<< "maxAllowedDisplacement/maxDisplacement of boundary\t"
-        << getMaxAllowedDisplacement() << "/" << maxDisplacement << endl;
-    scalar eta = getMaxAllowedDisplacement() / maxDisplacement;
-    Info<< "Setting eta value to " << eta << endl;
-
-    return eta;
-}
-
-
-Foam::labelList
-Foam::optMeshMovementVolumetricBSplines::getActiveDesignVariables() const
-{
-    return volBSplinesBase_.getActiveDesignVariables();
-}
-
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.C
deleted file mode 100644
index cdde7b1a575f6635c3fb10f750af476b9169ac22..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.C
+++ /dev/null
@@ -1,190 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "optMeshMovementVolumetricBSplinesExternalMotionSolver.H"
-#include "addToRunTimeSelectionTable.H"
-
-
-// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
-
-namespace Foam
-{
-    defineTypeNameAndDebug
-    (
-        optMeshMovementVolumetricBSplinesExternalMotionSolver,
-        0
-    );
-    addToRunTimeSelectionTable
-    (
-        optMeshMovement,
-        optMeshMovementVolumetricBSplinesExternalMotionSolver,
-        dictionary
-    );
-}
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void Foam::optMeshMovementVolumetricBSplinesExternalMotionSolver::
-computeBoundaryMovement
-(
-    const scalarField& correction
-)
-{
-    const label nCPs(volBSplinesBase_.getTotalControlPointsNumber());
-    dx_.primitiveFieldRef() = vector::zero;
-    cpMovement_ = vector::zero;
-
-    for (label iCP = 0; iCP < nCPs; iCP++)
-    {
-        cpMovement_[iCP].x() = correction[3*iCP];
-        cpMovement_[iCP].y() = correction[3*iCP + 1];
-        cpMovement_[iCP].z() = correction[3*iCP + 2];
-    }
-
-    // Bound control point movement for non-active CPs
-    volBSplinesBase_.boundControlPointMovement(cpMovement_);
-
-    // Compute boundary movement
-    label passedCPs(0);
-    PtrList<NURBS3DVolume>& boxes = volBSplinesBase_.boxesRef();
-    forAll(boxes, iNURB)
-    {
-        const label nb = boxes[iNURB].getControlPoints().size();
-        for (label cpI = 0; cpI < nb; ++cpI)
-        {
-            const label globalCP = passedCPs + cpI;
-            forAll(patchIDs_, pI)
-            {
-                const label patchI = patchIDs_[pI];
-                vectorField boundaryMovement
-                (
-                    boxes[iNURB].patchDxDb(patchI, cpI)
-                  & cpMovement_[globalCP]
-                );
-                dx_.boundaryField()[patchI].addToInternalField
-                (
-                    dx_.primitiveFieldRef(),
-                    boundaryMovement
-                );
-            }
-        }
-
-        // Increment number of passed sensitivities
-        passedCPs += nb;
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-Foam::optMeshMovementVolumetricBSplinesExternalMotionSolver::
-optMeshMovementVolumetricBSplinesExternalMotionSolver
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    const labelList& patchIDs
-)
-:
-    optMeshMovement(mesh, dict, patchIDs),
-    volBSplinesBase_
-    (
-        const_cast<volBSplinesBase&>(volBSplinesBase::New(mesh))
-    ),
-    dx_
-    (
-        IOobject
-        (
-            "dx",
-            mesh.time().timeName(),
-            mesh,
-            IOobject::NO_READ,
-            IOobject::NO_WRITE,
-            IOobject::NO_REGISTER
-        ),
-        pointMesh::New(mesh),
-        dimensionedVector(dimless, Zero)
-    ),
-    cpMovement_(volBSplinesBase_.getTotalControlPointsNumber(), Zero)
-{}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::optMeshMovementVolumetricBSplinesExternalMotionSolver::moveMesh()
-{
-    // Compute boundary movement
-    computeBoundaryMovement(correction_);
-
-    // Set boundary movement of motion solver
-    displMethodPtr_->setMotionField(dx_);
-
-    // Positions of control points have not changed since only the boundary dx
-    // has been computed.
-    // Use correction to update them
-    volBSplinesBase_.moveControlPoints(cpMovement_);
-
-    // Write control points to files
-    volBSplinesBase_.writeControlPoints();
-
-    // Move the mesh and check quality
-    optMeshMovement::moveMesh();
-}
-
-
-Foam::scalar
-Foam::optMeshMovementVolumetricBSplinesExternalMotionSolver::computeEta
-(
-    const scalarField& correction
-)
-{
-    computeBoundaryMovement(correction);
-
-    // Get maximum boundary movement
-    scalar maxDisplacement = gMax(mag(dx_.primitiveField()));
-
-    // Compute eta value
-    Info<< "maxAllowedDisplacement/maxDisplacement \t"
-        << getMaxAllowedDisplacement() << "/" << maxDisplacement << endl;
-    scalar eta = getMaxAllowedDisplacement() / maxDisplacement;
-    Info<< "Setting eta value to " << eta << endl;
-
-    return eta;
-}
-
-
-Foam::labelList
-Foam::optMeshMovementVolumetricBSplinesExternalMotionSolver::getActiveDesignVariables()
-const
-{
-    return volBSplinesBase_.getActiveDesignVariables();
-}
-
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.H
deleted file mode 100644
index 57d8e976be6a5428296357475ab3179cde7c5030..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.H
+++ /dev/null
@@ -1,138 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-Class
-    Foam::optMeshMovementVolumetricBSplinesExternalMotionSolver
-
-Description
-    Converts NURBS volume control points update to actual mesh movement.
-    Internal points are moved based on a motionSolver other than
-    volumetricBSplinesExternalMotionSolver.
-
-SourceFiles
-    optMeshMovementVolumetricBSplinesExternalMotionSolver.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef optMeshMovementVolumetricBSplinesExternalMotionSolver_H
-#define optMeshMovementVolumetricBSplinesExternalMotionSolver_H
-
-#include "optMeshMovement.H"
-#include "volBSplinesBase.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-/*---------------------------------------------------------------------------*\
-    Class optMeshMovementVolumetricBSplinesExternalMotionSolver Declaration
-\*---------------------------------------------------------------------------*/
-
-class optMeshMovementVolumetricBSplinesExternalMotionSolver
-:
-    public optMeshMovement
-{
-protected:
-
-    // Protected data
-
-        //- Reference to underlaying volumetric B-Splines morpher
-        volBSplinesBase& volBSplinesBase_;
-
-        //- Boundary movement due to change of NURBS control points
-        pointVectorField dx_;
-
-        //- Movement of control points
-        vectorField cpMovement_;
-
-
-    // Protected Member Functions
-
-        void computeBoundaryMovement(const scalarField& correction);
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        optMeshMovementVolumetricBSplinesExternalMotionSolver
-        (
-            const optMeshMovementVolumetricBSplinesExternalMotionSolver&
-        ) = delete;
-
-        //- No copy assignment
-        void operator=
-        (
-            const optMeshMovementVolumetricBSplinesExternalMotionSolver&
-        ) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("volumetricBSplinesExternalMotionSolver");
-
-
-    // Constructors
-
-        //- Construct from components
-        optMeshMovementVolumetricBSplinesExternalMotionSolver
-        (
-            fvMesh& mesh,
-            const dictionary& dict,
-            const labelList& patchIDs
-        );
-
-
-    //- Destructor
-    virtual ~optMeshMovementVolumetricBSplinesExternalMotionSolver() = default;
-
-
-    // Member Functions
-
-       //- Calculates surface mesh movement
-       void moveMesh();
-
-       //- Compute eta value based on max displacement
-       virtual scalar computeEta(const scalarField& correction);
-
-       //- Return active design variables
-       virtual labelList getActiveDesignVariables() const;
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.C
new file mode 100644
index 0000000000000000000000000000000000000000..eecea7e7226dac17f56984521ff028ff3a0a62bf
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.C
@@ -0,0 +1,573 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+    Copyright (C) 2019-2021 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 <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "adjointSolverManager.H"
+#include "designVariablesUpdate.H"
+#include "constrainedOptimisationMethod.H"
+#include "IOmanip.H"
+#include "runTimeSelectionTables.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(designVariablesUpdate, 0);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+Foam::label Foam::designVariablesUpdate::nConstraints
+(
+    PtrList<adjointSolverManager>& adjointSolverManagers
+) const
+{
+    // Figure out number of adjoint solvers corresponding to constraints.
+    // Looks in all operating points
+    label nConstraints(0);
+    for (const adjointSolverManager& adjManagerI : adjointSolvManagers_)
+    {
+        nConstraints += adjManagerI.nConstraints();
+    }
+    // Add constraints that might emerge from the design variables
+    tmp<scalarField> designVarsConstraints = designVars_().constraintValues();
+    if (designVarsConstraints)
+    {
+        nConstraints += designVarsConstraints().size();
+    }
+    return nConstraints;
+}
+
+
+Foam::label Foam::designVariablesUpdate::nAdjointSolvers() const
+{
+    label n(0);
+    for (const adjointSolverManager& adjSolvManager : adjointSolvManagers_)
+    {
+        n += adjSolvManager.nAdjointSolvers();
+    }
+    return n;
+}
+
+
+void Foam::designVariablesUpdate::writeCPUcostHeader()
+{
+    unsigned int width(IOstream::defaultPrecision() + 5);
+    CPUcostFile_
+        << setw(width) << "#Cycle" << " "
+        << setw(width) << "LineSearchIters" << " "
+        << setw(width) << "CycleCPUcost" << " "
+        << setw(width) << "CyclePrimalSolutions" << " "
+        << setw(width) << "CycleAdjointSolutions" << " "
+        << setw(width) << "TotalCPUcost" << " "
+        << setw(width) << "TotalPrimalSolutions" << " "
+        << setw(width) << "TotalAdjointSolutions" << endl;
+}
+
+
+void Foam::designVariablesUpdate::writeToCostFile(bool zeroAdjointSolns)
+{
+    unsigned int width(IOstream::defaultPrecision() + 5);
+    label cyclePrimalSolutions(nPrimalsPerCycle_);
+    label cycleAdjointSolutions(nAdjointsPerCycle_);
+    label lineSearchIters(1);
+    if (lineSearch_)
+    {
+        lineSearchIters = lineSearch_().innerIter();
+        cyclePrimalSolutions *= lineSearchIters;
+        if (lineSearch_().computeGradient())
+        {
+            cycleAdjointSolutions *= lineSearchIters;
+        }
+    }
+    if (zeroAdjointSolns)
+    {
+        cycleAdjointSolutions = 0;
+    }
+    nPrimalSolutions_ += cyclePrimalSolutions;
+    nAdjointSolutions_ += cycleAdjointSolutions;
+    const scalar elapsedCpuTime = mesh_.time().elapsedCpuTime();
+    const scalar cycleCost = elapsedCpuTime - CPUcost_;
+    CPUcost_ = elapsedCpuTime;
+
+    CPUcostFile_
+        << setw(width) << mesh_.time().timeName() << " "
+        << setw(width) << lineSearchIters << " "
+        << setw(width) << cycleCost  << " "
+        << setw(width) << cyclePrimalSolutions << " "
+        << setw(width) << cycleAdjointSolutions << " "
+        << setw(width) << CPUcost_ << " "
+        << setw(width) << nPrimalSolutions_ << " "
+        << setw(width) << nAdjointSolutions_ << endl;
+
+}
+
+
+void Foam::designVariablesUpdate::checkConvergence
+(
+    const scalarField& oldCorrection
+)
+{
+    bool converged(false);
+    // Design variables convergence check
+    if (designVarsThreshold_)
+    {
+        const labelList& activeVarIDs =
+            designVars_->activeDesignVariables();
+        const scalarField correction(oldCorrection, activeVarIDs);
+        const scalarField activeVars(designVars_->getVars(), activeVarIDs);
+        const scalar scaledCorrection =
+            gMax(mag(correction)/(mag(activeVars) + SMALL));
+        DebugInfo
+            << "Current correction " << correction << nl
+            << "Active vars " << activeVars << endl;
+        Info<< "Max. scaled correction of the design variables = "
+            << scaledCorrection
+            << endl;
+        if (scaledCorrection < designVarsThreshold_())
+        {
+            Info<< tab << "Design variables have converged " << endl;
+            converged = true;
+        }
+    }
+    // Objective convergence check
+    if (objectiveThreshold_)
+    {
+        const scalar newObjective = computeObjectiveValue();
+        const scalar oldObjective = updateMethod_->getObjectiveValueOld();
+        const scalar relativeUpdate =
+            mag(newObjective - oldObjective)/(mag(oldObjective) + SMALL);
+        Info<< "Relative change of the objective value = "
+            << relativeUpdate
+            << endl;
+        if (relativeUpdate < objectiveThreshold_())
+        {
+            Info<< tab << "Objective function has converged " << endl;
+            converged = true;
+        }
+    }
+    // Feasibility check
+    const scalarField& constraints = updateMethod_->getConstraintValues();
+    const scalar feasibility = sum(pos(constraints)*constraints);
+    Info<< "Feasibility = " << feasibility << endl;
+    if (converged && feasibility < feasibilityThreshold_)
+    {
+        Info<< "Stopping criteria met and all constraints satisfied." << nl
+            << "Optimisation has converged, stopping ..." << nl << nl
+            << "End" << nl
+            << endl;
+        // Force writing of all objective and constraint functions, to get
+        // the converged results to files
+        for (adjointSolverManager& am : adjointSolvManagers_)
+        {
+            for (adjointSolver& as : am.adjointSolvers())
+            {
+                // Use dummy weighted objective
+                as.getObjectiveManager().writeObjectives();
+            }
+        }
+        writeToCostFile(true);
+        if (UPstream::parRun())
+        {
+            UPstream::exit(0);
+        }
+        else
+        {
+            std::exit(0);
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::designVariablesUpdate::designVariablesUpdate
+(
+    fvMesh& mesh,
+    const dictionary& dict,
+    PtrList<adjointSolverManager>& adjointSolverManagers,
+    autoPtr<designVariables>& designVars
+)
+:
+    mesh_(mesh),
+    dict_(dict),
+    adjointSolvManagers_(adjointSolverManagers),
+    designVars_(designVars),
+    updateMethod_
+    (
+        updateMethod::New
+        (
+            mesh_,
+            dict_.subDict("updateMethod"),
+            designVars_,
+            nConstraints(adjointSolverManagers)
+        )
+    ),
+    lineSearch_
+    (
+        lineSearch::New
+        (
+            dict_.subDict("updateMethod").subOrEmptyDict("lineSearch"),
+            mesh.time(),
+            updateMethod_.ref()
+        )
+    ),
+    CPUcostFile_(mesh_.time().globalPath()/"optimisation"/"CPUcost"),
+    nPrimalsPerCycle_(adjointSolverManagers.size()),
+    nAdjointsPerCycle_(nAdjointSolvers()),
+    nPrimalSolutions_(nPrimalsPerCycle_),
+    nAdjointSolutions_(nAdjointsPerCycle_),
+    CPUcost_(0),
+    designVarsThreshold_(nullptr),
+    objectiveThreshold_(nullptr),
+    convergenceCriteriaDefined_(false),
+    feasibilityThreshold_
+    (
+        dict.subOrEmptyDict("convergence").
+            getOrDefault<scalar>("feasibilityThreshold", 1.e-06)
+    )
+{
+    dictionary convergenceDict = dict.subOrEmptyDict("convergence");
+    if (convergenceDict.found("designVariables"))
+    {
+        designVarsThreshold_.reset
+            (new scalar(convergenceDict.get<scalar>("designVariables")));
+    }
+    if (convergenceDict.found("objective"))
+    {
+        objectiveThreshold_.reset
+            (new scalar(convergenceDict.get<scalar>("objective")));
+    }
+    convergenceCriteriaDefined_ = designVarsThreshold_ || objectiveThreshold_;
+    // Check whether eta of maxInitChange are set
+    if (!designVars_().isMaxInitChangeSet() && !updateMethod_().initialEtaSet())
+    {
+        FatalErrorInFunction
+            << "Neither eta (updateMethod) or maxInitChange (designVariables) "
+            << "is set."
+            << exit(FatalError);
+    }
+
+    label nConstr(nConstraints(adjointSolvManagers_));
+    // Sanity checks for combinations of number of constraints and
+    // optimisation methods
+    if
+    (
+        nConstr
+     && !isA<constrainedOptimisationMethod>(updateMethod_())
+    )
+    {
+        const auto& cnstrTable =
+            *(constrainedOptimisationMethod::dictionaryConstructorTablePtr_);
+
+        // Has constraints but is not a constraint optimisation method
+        FatalErrorInFunction
+            << "Found " << nConstr << " adjoint solvers corresponding to "
+            << "constraints but the optimisation method ("
+            << updateMethod_().type()
+            << ") is not a constrainedOptimisationMethod." << nl
+            << "Available constrainedOptimisationMethods:" << nl
+            << cnstrTable.sortedToc()
+            << exit(FatalError);
+    }
+    else if
+    (
+        !nConstr
+     && isA<constrainedOptimisationMethod>(updateMethod_())
+    )
+    {
+        // Does not have constraints but is a constrained optimisation method
+        WarningInFunction
+            << "Did not find any adjoint solvers corresponding to "
+            << "constraints but the optimisation method ("
+            << updateMethod_().type()
+            << ") is a constrainedOptimisationMethod." << nl << nl
+            << "This can cause some constraintOptimisationMethods to misbehave."
+            << nl << nl
+            << "Either the isConstraint bool is not set in one of the adjoint "
+            << "solvers or you should consider using an updateMethod "
+            << "that is not a constrainedOptimisationMethod"
+            << nl << endl;
+    }
+
+    if (designVarsThreshold_)
+    {
+        Info<< "Optimisation will run until the max. scaled correction "
+            << "of the design variables is < " << designVarsThreshold_()
+            << endl;
+    }
+    if (objectiveThreshold_)
+    {
+        Info<< "Optimisation will run until the relative update of the "
+            << "objective function is < " << objectiveThreshold_()
+            << endl;
+    }
+    if (!convergenceCriteriaDefined_)
+    {
+        Info<< "No convergence criterion defined for optimsation" << nl
+            << "It will run for " << mesh_.time().endTime().value()
+            << " optimisation cycles " << nl << endl;
+    }
+    Info<< "Feasibility threshold is " << feasibilityThreshold_ << endl;
+
+    // Write header of the cost file
+    writeCPUcostHeader();
+}
+
+
+// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
+
+void Foam::designVariablesUpdate::update()
+{
+    // Compute update of the design variables
+    tmp<scalarField> tcorrection(computeDirection());
+    scalarField& correction = tcorrection.ref();
+
+    // Set the old value of the objective function
+    setOldObjectiveValue();
+
+    // Update the design variables
+    designVars_->update(correction);
+
+    // If direction has been scaled (say by setting the initial eta), the
+    // old correction has to be updated
+    postUpdate(correction);
+}
+
+
+void Foam::designVariablesUpdate::update(const scalarField& direction)
+{
+    // Multiply with line search step, if necessary
+    scalarField correction(direction);
+    if (lineSearch_.valid())
+    {
+        lineSearch_->updateCorrection(correction);
+    }
+
+    // Update the design variables
+    designVars_->update(correction);
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::designVariablesUpdate::computeDirection()
+{
+    updateGradientsAndValues();
+    updateMethod_->computeCorrection();
+    scalarField& correction = updateMethod_->returnCorrection();
+
+    // Compute eta if needed
+    if (!updateMethod_->initialEtaSet() || designVars_->resetEta())
+    {
+        const scalar eta(designVars_->computeEta(correction));
+        updateMethod_->modifyStep(eta);
+        updateMethod_->initialEtaSet() = true;
+    }
+
+    // Intentionally copies result to new field
+    return tmp<scalarField>::New(correction);
+}
+
+
+void Foam::designVariablesUpdate::updateGradientsAndValues()
+{
+    scalarField objectiveSens;
+    PtrList<scalarField> constraintSens;
+    scalar objectiveValue(Zero);
+    DynamicList<scalar> constraintValues;
+
+    for (adjointSolverManager& adjSolvManager : adjointSolvManagers_)
+    {
+        const scalar opWeight = adjSolvManager.operatingPointWeight();
+
+        // Aggregate sensitivities of solvers corresponding to objectives
+        // (i.e. not constraints)
+        tmp<scalarField> tadjointSolverManagerSens =
+            adjSolvManager.aggregateSensitivities();
+
+        // Aggregate objective values of solvers corresponding to objectives
+        // (i.e. not constraints)
+        objectiveValue += opWeight*adjSolvManager.objectiveValue();
+
+        // Get constraint sensitivities
+        PtrList<scalarField> adjointSolverManagerConstSens =
+            adjSolvManager.constraintSensitivities();
+
+        // Post process sensitivities if needed.
+        // Done here since each adjointSolverManager might post-process
+        // its sensitivities in a different way
+        designVars_->postProcessSens
+        (
+            tadjointSolverManagerSens.ref(),
+            adjointSolverManagerConstSens,
+            adjSolvManager.adjointSolversNames(),
+            adjSolvManager.isMaster()
+        );
+
+        if (objectiveSens.empty())
+        {
+            objectiveSens.setSize(tadjointSolverManagerSens().size(), Zero);
+        }
+
+        // Accumulate sensitivities
+        objectiveSens += opWeight*tadjointSolverManagerSens();
+        forAll(adjointSolverManagerConstSens, sI)
+        {
+            constraintSens.
+                push_back(adjointSolverManagerConstSens.set(sI, nullptr));
+        }
+        constraintValues.push_back(adjSolvManager.constraintValues());
+    }
+    // Add contraint values and gradients from the design variables
+    tmp<scalarField> designVarsConstValues = designVars_->constraintValues();
+    PtrList<scalarField> designVarsConstDerivs =
+        designVars_->constraintDerivatives();
+    if (designVarsConstValues && designVarsConstDerivs.size())
+    {
+        if (designVarsConstValues().size() != designVarsConstDerivs.size())
+        {
+            FatalErrorInFunction
+                << "Size of design variables constraints and derivatives differ"
+                << endl
+                << exit(FatalError);
+        }
+        constraintValues.push_back(designVarsConstValues());
+        constraintSens.push_back(std::move(designVarsConstDerivs));
+    }
+
+    // Update objective/constraint values/gradients, known by the update method
+    updateMethod_->setObjectiveDeriv(objectiveSens);
+    updateMethod_->setConstraintDeriv(constraintSens);
+    updateMethod_->setObjectiveValue(objectiveValue);
+    updateMethod_->setConstraintValues
+        (scalarField(std::move(constraintValues)));
+}
+
+
+Foam::scalar Foam::designVariablesUpdate::computeObjectiveValue()
+{
+    scalar objectiveValue(Zero);
+    for (adjointSolverManager& adjSolvManager : adjointSolvManagers_)
+    {
+        const scalar opWeight = adjSolvManager.operatingPointWeight();
+        objectiveValue += opWeight*adjSolvManager.objectiveValue();
+    }
+    return objectiveValue;
+}
+
+
+void Foam::designVariablesUpdate::setOldObjectiveValue()
+{
+    updateMethod_->setObjectiveValueOld(computeObjectiveValue());
+}
+
+
+Foam::scalar Foam::designVariablesUpdate::computeMeritFunction()
+{
+    // Compute new objective and constraint values and update the ones
+    // in updateMethod
+    scalar objectiveValue(Zero);
+    DynamicList<scalar> constraintValues;
+
+    for (adjointSolverManager& adjSolvManager : adjointSolvManagers_)
+    {
+        const scalar opWeight = adjSolvManager.operatingPointWeight();
+
+        objectiveValue += opWeight*adjSolvManager.objectiveValue();
+        constraintValues.push_back(adjSolvManager.constraintValues());
+    }
+
+    // Add constraints directly imposed to the design variables
+    tmp<scalarField> designVarsConstValues = designVars_->constraintValues();
+    if (designVarsConstValues)
+    {
+        constraintValues.push_back(designVarsConstValues());
+    }
+    updateMethod_->setObjectiveValue(objectiveValue);
+    updateMethod_->setConstraintValues
+        (scalarField(std::move(constraintValues)));
+
+    return updateMethod_->computeMeritFunction();
+}
+
+
+Foam::scalar Foam::designVariablesUpdate::meritFunctionDirectionalDerivative()
+{
+    return updateMethod_->meritFunctionDirectionalDerivative();
+}
+
+
+void Foam::designVariablesUpdate::updateOldCorrection
+(
+    const scalarField& oldCorrection
+)
+{
+    updateMethod_->updateOldCorrection(oldCorrection);
+}
+
+
+void Foam::designVariablesUpdate::write()
+{
+    updateMethod_->writeAuxiliaryData();
+    designVars_->writeDesignVars();
+    writeToCostFile();
+}
+
+
+void Foam::designVariablesUpdate::postUpdate(const scalarField& oldCorrection)
+{
+    updateOldCorrection(oldCorrection);
+    write();
+    designVars_->evolveNumber();
+    if (lineSearch_.valid())
+    {
+        lineSearch_().postUpdate();
+        // Append additional empty line at the end of the instantaneous
+        // objective file to indicate the end of the block corresponding to
+        // this optimisation cycle
+        for (adjointSolverManager& am : adjointSolvManagers_)
+        {
+            for (adjointSolver& as : am.adjointSolvers())
+            {
+                PtrList<objective>& objectives =
+                    as.getObjectiveManager().getObjectiveFunctions();
+                for (objective& obj : objectives)
+                {
+                    obj.writeInstantaneousSeparator();
+                }
+            }
+        }
+    }
+    if (convergenceCriteriaDefined_)
+    {
+        checkConvergence(oldCorrection);
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.H
new file mode 100644
index 0000000000000000000000000000000000000000..2ed9edddf2b0f9268c6bef087663840fc15595b9
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.H
@@ -0,0 +1,241 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
+
+
+Class
+    Foam::designVariablesUpdate
+
+Description
+    A class encapsulating functionality neccessary to perform an optimisation
+    loop, such as updating the design variables,  checking the
+    sufficient reduction/adhetion of objective and constraint values in each
+    optimisation cycle by performing a line-search and checking the overall
+    convergence of the optimisation loop, if the corresponding criteria are
+    provided.
+
+    Kept separate from optimisationManager to isolate functionality required
+    only when the update of the design variables is performed.
+
+SourceFiles
+    designVariablesUpdate.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef designVariablesUpdate_H
+#define designVariablesUpdate_H
+
+#include "adjointSolverManager.H"
+#include "designVariables.H"
+#include "updateMethod.H"
+#include "lineSearch.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                      Class designVariablesUpdate Declaration
+\*---------------------------------------------------------------------------*/
+
+class designVariablesUpdate
+{
+protected:
+
+    // Protected data
+
+        fvMesh& mesh_;
+        const dictionary dict_;
+        PtrList<adjointSolverManager>& adjointSolvManagers_;
+        autoPtr<designVariables>& designVars_;
+
+        //- Method to update the design variables based on the provided
+        //- sensitivity derivatives
+        autoPtr<updateMethod> updateMethod_;
+
+        //- Line search mechanism to approximate the update step length
+        autoPtr<lineSearch> lineSearch_;
+
+        // Variables related to the computation of the CPU cost
+
+            //- Output file
+            OFstream CPUcostFile_;
+
+            //- Primal solutions per optimisation cycle
+            label nPrimalsPerCycle_;
+
+            //- Adjoint solutions per optimisation cycle
+            label nAdjointsPerCycle_;
+
+            //- Primal evaluations performed so far
+            label nPrimalSolutions_;
+
+            //- Adjoint evaluations performed so far
+            label nAdjointSolutions_;
+
+            //- CPU cost (in seconds)
+            scalar CPUcost_;
+
+        //  Convergence criteria
+
+            //- The maximum of the correction/designVariables values
+            //- must be lower that this threshold to consider the run converged
+            autoPtr<scalar> designVarsThreshold_;
+
+            //- The relative update of the objective value w.r.t. to its last
+            //- value should be smaller than this value to considered the run
+            //- converged
+            autoPtr<scalar> objectiveThreshold_;
+
+            //- Is at least a single convergence criterion defined
+            bool convergenceCriteriaDefined_;
+
+            //- In case of a constrained optimisation, the sum of positive
+            //- constraints should be lower than this value to consider the
+            //- run converged (i.e. this tolerates some deviation from
+            //- satisfying all constraints)
+            scalar feasibilityThreshold_;
+
+
+    // Protected Member Functions
+
+        //- Get the number of adjoint solvers that correspond to constraints
+        label nConstraints
+        (
+            PtrList<adjointSolverManager>& adjointSolverManagers
+        ) const;
+
+        //- Get total number of adjoint solvers
+        label nAdjointSolvers() const;
+
+        //- Write CPU cost header
+        void writeCPUcostHeader();
+
+        //- Write to cost file
+        void writeToCostFile(bool zeroAdjointSolns = false);
+
+        //- Check if the optimisation loop has converged based on the provided
+        //- criteria
+        //  May terminate the program
+        void checkConvergence(const scalarField& oldCorrection);
+
+
+private:
+
+    // Private Member Functions
+
+        //- No copy construct
+        designVariablesUpdate(const designVariablesUpdate&) = delete;
+
+        //- No copy assignment
+        void operator=(const designVariablesUpdate&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("designVariablesUpdate");
+
+
+    // Constructors
+
+        //- Construct from components
+        designVariablesUpdate
+        (
+            fvMesh& mesh,
+            const dictionary& dict,
+            PtrList<adjointSolverManager>& adjointSolverManagers,
+            autoPtr<designVariables>& designVars
+        );
+
+    //- Destructor
+    virtual ~designVariablesUpdate() = default;
+
+
+    // Member Functions
+
+        //- Update design variables
+        void update();
+
+        //- Update design variables based on a given correction
+        void update(const scalarField& correction);
+
+        //- Compute update direction
+        tmp<scalarField> computeDirection();
+
+        //- Compute cumulative objective and constraint gradients
+        void updateGradientsAndValues();
+
+        //- Sum objective values from all adjointSolverManagers
+        scalar computeObjectiveValue();
+
+        //- Set the old objective value known by the updateMethod
+        //  Used to check convergence of the optimisation loop
+        void setOldObjectiveValue();
+
+        //- Compute the merit function of the optimisation problem.
+        //  Could be different than the objective function in case of
+        //  constraint optimisation
+        scalar computeMeritFunction();
+
+        //- Derivative of the merit function
+        scalar meritFunctionDirectionalDerivative();
+
+        //- Update old correction. Needed for quasi-Newton Methods
+        void updateOldCorrection(const scalarField&);
+
+        //- Write useful quantities to files
+        void write();
+
+        //- Steps to be executed after the susccessfull update of the design
+        //- varibles, i.e. the last step of line search or the simple update
+        //- in the fixedStep approach
+        void postUpdate(const scalarField& oldCorrection);
+
+        //- Get access to design variables
+        inline autoPtr<designVariables>& getDesignVariables()
+        {
+            return designVars_;
+        }
+
+        //- Get a reference to the line search object
+        inline autoPtr<lineSearch>& getLineSearch()
+        {
+            return lineSearch_;
+        }
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.C
index caae3b7b09945b08aef3a07018c06d595a6269f8..6073e1e7d740ff9b11c1f51439dca3625f6446c7 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -27,6 +27,8 @@ License
 
 \*---------------------------------------------------------------------------*/
 
+#include "adjointSolverManager.H"
+#include "dictionary.H"
 #include "optimisationManager.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -38,28 +40,164 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-Foam::optimisationManager::optimisationManager(fvMesh& mesh)
-:
-    IOdictionary
-    (
-        IOobject
-        (
-            "optimisationDict",
-            mesh.time().system(),
-            mesh,
-            IOobject::MUST_READ_IF_MODIFIED,
-            IOobject::NO_WRITE,
-            IOobject::REGISTER
-        )
-    ),
-    mesh_(mesh),
-    time_(const_cast<Time&>(mesh.time())),
-    primalSolvers_(),
-    adjointSolverManagers_(),
-    managerType_(get<word>("optimisationManager")),
-    optType_(nullptr)
+void Foam::optimisationManager::resetTime
+(
+    const dimensionedScalar startTime,
+    const label startTimeIndex,
+    const scalar endTime
+)
+{
+    // Does nothing in base
+}
+
+
+void Foam::optimisationManager::lineSearchUpdate()
+{
+    // Compute direction of update
+    tmp<scalarField> tdirection = dvUpdate_->computeDirection();
+    const scalarField& direction = tdirection.ref();
+
+    // Grab reference to line search
+    autoPtr<lineSearch>& lineSrch = dvUpdate_->getLineSearch();
+
+    // Store starting point
+    dvUpdate_->getDesignVariables()->storeDesignVariables();
+
+    // Compute merit function before update
+    scalar meritFunction = dvUpdate_->computeMeritFunction();
+    lineSrch->setOldMeritValue(meritFunction);
+    dvUpdate_->setOldObjectiveValue();
+
+    // Get merit function derivative
+    scalar dirDerivative =
+        dvUpdate_->meritFunctionDirectionalDerivative();
+    lineSrch->setDeriv(dirDerivative);
+    lineSrch->setDirection(direction);
+
+    // Reset initial step.
+    // Might be interpolated from previous optimisation cycles
+    lineSrch->reset();
+
+    // Perform line search
+    while (lineSrch->loop())
+    {
+        Info<< "\n- - - - - - - - - - - - - - -"  << endl;
+        Info<< "Line search iteration "   << lineSrch->innerIter() << endl;
+        Info<< "- - - - - - - - - - - - - - -\n"  << endl;
+
+        // Update design variables. Multiplication with line search step
+        // happens inside the update(direction) function
+        moveDesignVariables(direction);
+
+        const dimensionedScalar startTime = mesh_.time();
+        const label startTimeIndex = mesh_.time().timeIndex();
+        const scalar primalEndTime = mesh_.time().endTime().value();
+        // Solve all primal equations
+        solvePrimalEquations();
+
+        // Compute and set new merit function
+        meritFunction = dvUpdate_->computeMeritFunction();
+        lineSrch->setNewMeritValue(meritFunction);
+
+        if (lineSrch->computeGradient())
+        {
+            // Reset adjoint sensitivities in all adjoint solver managers
+            clearSensitivities();
+
+            // Solve all adjoint equations
+            solveAdjointEquations();
+
+            // Update objective and gradient information known by updateMethod
+            dvUpdate_->updateGradientsAndValues();
+
+            // Update the directional derivative
+            dirDerivative =
+                dvUpdate_->meritFunctionDirectionalDerivative();
+            lineSrch->setNewDeriv(dirDerivative);
+        }
+
+        if (lineSrch->converged())
+        {
+            // If line search criteria have been met, proceed
+            Info<< "Line search converged in " << lineSrch->innerIter()
+                << " iterations." << endl;
+            scalarField scaledCorrection(lineSrch->step()*direction);
+            dvUpdate_->postUpdate(scaledCorrection);
+            break;
+        }
+        else
+        {
+            // If maximum number of iteration has been reached, continue
+            if (lineSrch->innerIter() == lineSrch->maxIters())
+            {
+                Info<< "Line search reached max. number of iterations.\n"
+                    << "Proceeding to the next optimisation cycle" << endl;
+                scalarField scaledCorrection(lineSrch->step()*direction);
+                dvUpdate_->postUpdate(scaledCorrection);
+            }
+            // Reset to initial design variables and update step
+            else
+            {
+                //- Reset time if necessary
+                this->resetTime(startTime, startTimeIndex, primalEndTime);
+                dvUpdate_->getDesignVariables()->resetDesignVariables();
+                lineSrch->updateStep();
+            }
+        }
+    }
+
+    // If line search did not need to get the new gradient, do it now in order
+    // to have it for the next optimisation cycle
+    if (!lineSrch->computeGradient())
+    {
+        // Reset adjoint sensitivities in all adjoint solver managers
+        clearSensitivities();
+
+        // Solve all adjoint equations
+        solveAdjointEquations();
+    }
+}
+
+
+void Foam::optimisationManager::fixedStepUpdate()
+{
+    // Update design variables
+    if (shouldUpdateDesignVariables_)
+    {
+        moveDesignVariables();
+    }
+
+    // Solve primal equations
+    solvePrimalEquations();
+
+    // Reset adjoint sensitivities in all adjoint solver managers
+    clearSensitivities();
+
+    // Solve all adjoint equations
+    solveAdjointEquations();
+}
+
+
+void Foam::optimisationManager::moveDesignVariables()
+{
+    // Update design variables
+    dvUpdate_->update();
+}
+
+
+void Foam::optimisationManager::moveDesignVariables
+(
+    const scalarField& direction
+)
+{
+    // Update design variables
+    dvUpdate_->update(direction);
+}
+
+
+void Foam::optimisationManager::initialize()
 {
     dictionary& primalSolversDict = subDict("primalSolvers");
     const wordList& primalSolverNames = primalSolversDict.toc();
@@ -79,20 +217,42 @@ Foam::optimisationManager::optimisationManager(fvMesh& mesh)
             solveri,
             primalSolver::New
             (
-                mesh,
+                mesh_,
                 managerType_,
-                solverDict
+                solverDict,
+                primalSolverNames[solveri]
             )
         );
     }
 
     // Construct adjointSolverManagers
     const dictionary& adjointManagersDict = subDict("adjointManagers");
-    const wordList& adjointManagerNames = adjointManagersDict.toc();
+    const wordList adjointManagerNames = adjointManagersDict.toc();
     adjointSolverManagers_.setSize(adjointManagerNames.size());
 
-    label nAdjointSolvers(0);
-    bool overrideUseSolverName(adjointSolverManagers_.size() > 1);
+    // Determine the number of adjoint solvers which are not null (i.e. need to
+    // allocate adjoint fields). Used to determine whether the adjoint field
+    // names should be appended by the solver name
+    label nNotNullAdjointSolvers(0);
+    for (const word& adjManager : adjointManagerNames)
+    {
+        const dictionary& adjSolversDict =
+            adjointManagersDict.subDict(adjManager).subDict("adjointSolvers");
+        const wordList adjointSolverNames = adjSolversDict.toc();
+        for (const word& adjSolver : adjointSolverNames)
+        {
+            if (adjSolversDict.subDict(adjSolver).get<word>("type") != "null")
+            {
+                ++nNotNullAdjointSolvers;
+            }
+        }
+    }
+    Info<< "Found "
+        << nNotNullAdjointSolvers
+        << " adjoint solvers that allocate fields"
+        << endl;
+    bool overrideUseSolverName(nNotNullAdjointSolvers > 1);
+
     forAll(adjointSolverManagers_, manageri)
     {
         adjointSolverManagers_.set
@@ -100,13 +260,13 @@ Foam::optimisationManager::optimisationManager(fvMesh& mesh)
             manageri,
             new adjointSolverManager
             (
-                mesh,
+                mesh_,
+                designVars_,
                 managerType_,
                 adjointManagersDict.subDict(adjointManagerNames[manageri]),
                 overrideUseSolverName
             )
         );
-        nAdjointSolvers += adjointSolverManagers_[manageri].nAdjointSolvers();
     }
 
     // Sanity checks on the naming convention
@@ -126,7 +286,7 @@ Foam::optimisationManager::optimisationManager(fvMesh& mesh)
         }
     }
 
-    if (nAdjointSolvers > 1)
+    if (nNotNullAdjointSolvers > 1)
     {
         for (const adjointSolverManager& amI : adjointSolverManagers_)
         {
@@ -145,9 +305,49 @@ Foam::optimisationManager::optimisationManager(fvMesh& mesh)
             }
         }
     }
+    if (designVars_)
+    {
+        designVars_().addFvOptions(primalSolvers_, adjointSolverManagers_);
+    }
 }
 
 
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::optimisationManager::optimisationManager(fvMesh& mesh)
+:
+    IOdictionary
+    (
+        IOobject
+        (
+            "optimisationDict",
+            mesh.time().system(),
+            mesh,
+            IOobject::MUST_READ_IF_MODIFIED,
+            IOobject::NO_WRITE,
+            IOobject::REGISTER
+        )
+    ),
+    mesh_(mesh),
+    time_(const_cast<Time&>(mesh.time())),
+    designVars_
+    (
+        this->subOrEmptyDict("optimisation").isDict("designVariables") ?
+        designVariables::New
+        (
+            mesh_,
+            subDict("optimisation").subDict("designVariables")
+        ) :
+        nullptr
+    ),
+    primalSolvers_(),
+    adjointSolverManagers_(),
+    managerType_(get<word>("optimisationManager")),
+    dvUpdate_(nullptr),
+    shouldUpdateDesignVariables_(true)
+{}
+
+
 // * * * * * * * * * * * * * * * * Selectors  * * * * * * * * * * * * * * //
 
 Foam::autoPtr<Foam::optimisationManager> Foam::optimisationManager::New
@@ -208,6 +408,12 @@ bool Foam::optimisationManager::read()
             man.readDict(adjointManagersDict.subDict(man.managerName()));
         }
 
+        if (designVars_)
+        {
+            designVars_->readDict
+                (subDict("optimisation").subDict("designVariables"));
+        }
+
         return true;
     }
 
@@ -215,16 +421,18 @@ bool Foam::optimisationManager::read()
 }
 
 
-Foam::PtrList<Foam::primalSolver>& Foam::optimisationManager::primalSolvers()
-{
-    return primalSolvers_;
-}
-
-
-Foam::PtrList<Foam::adjointSolverManager>&
-Foam::optimisationManager::adjointSolverManagers()
+void Foam::optimisationManager::updateDesignVariables()
 {
-    return adjointSolverManagers_;
+    // Update design variables using either a line-search scheme or
+    // a fixed-step update
+    if (dvUpdate_->getLineSearch())
+    {
+        lineSearchUpdate();
+    }
+    else
+    {
+        fixedStepUpdate();
+    }
 }
 
 
@@ -258,6 +466,15 @@ void Foam::optimisationManager::computeSensitivities()
 }
 
 
+void Foam::optimisationManager::clearSensitivities()
+{
+    for (adjointSolverManager& adjSolvManager : adjointSolverManagers_)
+    {
+        adjSolvManager.clearSensitivities();
+    }
+}
+
+
 void Foam::optimisationManager::updatePrimalBasedQuantities()
 {
     forAll(adjointSolverManagers_, amI)
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.H
index ecd752586a843c06d426631748c2b729dcabaa5c..8fe371432608a4d5fab3b6eea6821a9d348832a4 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/optimisationManager.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -42,8 +42,9 @@ SourceFiles
 
 #include "runTimeSelectionTables.H"
 #include "IOdictionary.H"
-#include "optimisationTypeIncompressible.H"
 #include "primalSolver.H"
+#include "designVariables.H"
+#include "designVariablesUpdate.H"
 #include "adjointSolverManager.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -63,12 +64,63 @@ protected:
 
     // Protected data
 
+        //- Reference to the mesh
         fvMesh& mesh_;
+
+        //- Reference to the time
         Time& time_;
+
+        //- Design variables of the optimisation problem.
+        //  Constructed before the adjoint solvers, since
+        //  some objective functions depend on their values
+        autoPtr<designVariables> designVars_;
+
+        //- List of primal solvers to be included in the optimisation
         PtrList<primalSolver> primalSolvers_;
+
+        //- List of adjoint solver managers to be included in the optimisation
         PtrList<adjointSolverManager> adjointSolverManagers_;
+
+        //- Type of the optimisation manager (singleRun, (un)steadyOptimisation)
         const word managerType_;
-        autoPtr<incompressible::optimisationType> optType_;
+
+        //- Helper class managing parts of the optimisation.
+        //  Allocated only when an actual optimisation is conducted.
+        autoPtr<designVariablesUpdate> dvUpdate_;
+
+        //- Switch defining if the design variables should be updated or not.
+        Switch shouldUpdateDesignVariables_;
+
+
+    // Protected Member Functions
+
+        //- Reset time
+        //  Does nothing in base, useful in unsteady adjoint
+        virtual void resetTime
+        (
+            const dimensionedScalar startTime,
+            const label startTimeIndex,
+            const scalar endTime
+        );
+
+        //- Update design variables
+        virtual void moveDesignVariables();
+
+        //- Update design variables. Multiplication with line search step
+        //  happens inside the update(direction) function
+        virtual void moveDesignVariables
+        (
+            const scalarField& direction
+        );
+
+        //- Update design variables using a line-search
+        void lineSearchUpdate();
+
+        //- Update design variables using a fixed step
+        void fixedStepUpdate();
+
+        //- Initialization. Construct primal and adjoint solvers
+        virtual void initialize();
 
 
 private:
@@ -120,44 +172,73 @@ public:
 
     // Member Functions
 
-        virtual PtrList<primalSolver>& primalSolvers();
+        // Access
+
+            //- Get the primal solvers
+            inline PtrList<primalSolver>& primalSolvers()
+            {
+                return primalSolvers_;
+            }
+
+            //- Get the adjoint solver managers
+            inline PtrList<adjointSolverManager>& adjointSolverManagers()
+            {
+                return adjointSolverManagers_;
+            }
+
+            //- Get the design variables
+            inline autoPtr<designVariables>& getDesignVariables()
+            {
+                return designVars_;
+            }
+
+            //- Get the mechanism supporting the update of the design variables
+            inline autoPtr<designVariablesUpdate>& getOptimisationType()
+            {
+                return dvUpdate_;
+            }
+
+
+        // Evolution
 
-        virtual PtrList<adjointSolverManager>& adjointSolverManagers();
+            //- Changes in case of run-time update of optimisationDict
+            virtual bool read();
 
-        virtual bool read();
+            //- Prefix increment
+            virtual optimisationManager& operator++() = 0;
 
-        //- Prefix increment,
-        virtual optimisationManager& operator++() = 0;
+            //- Postfix increment, this is identical to the prefix increment
+            virtual optimisationManager& operator++(int) = 0;
 
-        //- Postfix increment, this is identical to the prefix increment
-        virtual optimisationManager& operator++(int) = 0;
+            //- Return true if end of optimisation run.
+            //  Also, updates the design variables if needed
+            virtual bool checkEndOfLoopAndUpdate() = 0;
 
-        //- Return true if end of optimisation run.
-        //  Also, updates the design variables if needed
-        virtual bool checkEndOfLoopAndUpdate() = 0;
+            //- Return true if end of optimisation run
+            virtual bool end() = 0;
 
-        //- Return true if end of optimisation run
-        virtual bool end() = 0;
+            //- Whether to update the design variables
+            virtual bool update() = 0;
 
-        //- Whether to update the design variables
-        virtual bool update() = 0;
+            //- Update design variables.
+            //  Might employ a line search to find a correction satisfying the
+            //  step convergence criteria
+            virtual void updateDesignVariables();
 
-        //- Update design variables.
-        //  Might employ a line search to find a correction satisfying the step
-        //  convergence criteria
-        virtual void updateDesignVariables() = 0;
+            //- Solve all primal equations
+            virtual void solvePrimalEquations();
 
-        //- Solve all primal equations
-        virtual void solvePrimalEquations();
+            //- Solve all adjoint equations
+            virtual void solveAdjointEquations();
 
-        //- Solve all adjoint equations
-        virtual void solveAdjointEquations();
+            //- Compute all adjoint sensitivities
+            virtual void computeSensitivities();
 
-        //- Compute all adjoint sensitivities
-        virtual void computeSensitivities();
+            //- Clear all adjoint sensitivities
+            virtual void clearSensitivities();
 
-        //- Solve all primal equations
-        virtual void updatePrimalBasedQuantities();
+            //- Solve all primal equations
+            virtual void updatePrimalBasedQuantities();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/singleRun/singleRun.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/singleRun/singleRun.C
index eca6e739ecdfbf0611e4f78122d610558194083e..d841dd434d0a74c3607c7141a620c9117440abcd 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/singleRun/singleRun.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/singleRun/singleRun.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -45,7 +45,13 @@ Foam::singleRun::singleRun(fvMesh& mesh)
 :
     optimisationManager(mesh),
     cycles_(Zero)
-{}
+{
+    initialize();
+    if (designVars_)
+    {
+        designVars_().setInitialValues();
+    }
+}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.C
index 50a2507689dfa5bfad07ff11dd1b8af5ff1fd4d0..d6a0c9e8669ccfce785747ae60703a40ff37e5ad 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -44,137 +44,37 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-void Foam::steadyOptimisation::updateOptTypeSource()
-{
-    forAll(primalSolvers_, pI)
-    {
-        primalSolvers_[pI].updateOptTypeSource(optType_->sourcePtr());
-    }
-
-    forAll(adjointSolverManagers_, asmI)
-    {
-        PtrList<adjointSolver>& adjointSolvers =
-            adjointSolverManagers_[asmI].adjointSolvers();
-
-        forAll(adjointSolvers, aI)
-        {
-            adjointSolvers[aI].updateOptTypeSource(optType_->sourcePtr());
-        }
-    }
-}
-
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void Foam::steadyOptimisation::lineSearchUpdate()
-{
-    // Compute direction of update
-    tmp<scalarField> tdirection = optType_->computeDirection();
-    scalarField& direction = tdirection.ref();
-
-    // Grab reference to line search
-    autoPtr<lineSearch>& lineSrch = optType_->getLineSearch();
-
-    // Store starting point
-    optType_->storeDesignVariables();
-
-    // Compute merit function before update
-    scalar meritFunction = optType_->computeMeritFunction();
-    lineSrch->setOldMeritValue(meritFunction);
-
-    // Get merit function derivative
-    const scalar dirDerivative =
-        optType_->meritFunctionDirectionalDerivative();
-    lineSrch->setDeriv(dirDerivative);
-    lineSrch->setDirection(direction);
-
-    // Reset initial step.
-    // Might be interpolated from previous optimisation cycles
-    lineSrch->reset();
-
-    // Perform line search
-    for (label iter = 0; iter < lineSrch->maxIters(); ++iter)
-    {
-        Info<< "\n- - - - - - - - - - - - - - -"  << endl;
-        Info<< "Line search iteration "   << iter << endl;
-        Info<< "- - - - - - - - - - - - - - -\n"  << endl;
-
-        // Update design variables. Multiplication with line search step
-        // happens inside the update(direction) function
-        optType_->update(direction);
-
-        // Solve all primal equations
-        solvePrimalEquations();
-
-        // Compute and set new merit function
-        meritFunction = optType_->computeMeritFunction();
-        lineSrch->setNewMeritValue(meritFunction);
-
-        if (lineSrch->converged())
-        {
-            // If line search criteria have been met, proceed
-            Info<< "Line search converged in " << iter + 1
-                << " iterations." << endl;
-            scalarField scaledCorrection(lineSrch->step()*direction);
-            optType_->updateOldCorrection(scaledCorrection);
-            optType_->write();
-            lineSrch()++;
-            break;
-        }
-        else
-        {
-            // If maximum number of iteration has been reached, continue
-            if (iter == lineSrch->maxIters() - 1)
-            {
-                Info<< "Line search reached max. number of iterations.\n"
-                    << "Proceeding to the next optimisation cycle" << endl;
-                scalarField scaledCorrection(lineSrch->step()*direction);
-                optType_->updateOldCorrection(scaledCorrection);
-                optType_->write();
-                lineSrch()++;
-            }
-            // Reset to initial design variables and update step
-            else
-            {
-                optType_->resetDesignVariables();
-                lineSrch->updateStep();
-            }
-        }
-    }
-}
-
-
-void Foam::steadyOptimisation::fixedStepUpdate()
-{
-    // Update design variables
-    optType_->update();
-
-    // Solve primal equations
-    solvePrimalEquations();
-}
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::steadyOptimisation::steadyOptimisation(fvMesh& mesh)
 :
     optimisationManager(mesh)
 {
-    optType_.reset
+    initialize();
+    // Force the construction of the design variables, if they are not
+    // already present
+    if (!designVars_)
+    {
+        designVars_.reset
+        (
+            designVariables::New
+            (
+                mesh_,
+                subDict("optimisation").subDict("designVariables")
+            ).ptr()
+        );
+    }
+    designVars_().setInitialValues();
+    dvUpdate_.reset
     (
-        incompressible::optimisationType::New
+        new designVariablesUpdate
         (
             mesh,
             subDict("optimisation"),
-            adjointSolverManagers_
-        ).ptr()
+            adjointSolverManagers_,
+            designVars_
+        )
     );
-
-    // Update source ptrs in all solvers to look at the source held in optType
-    // Possible problem if mesh is adapted
-    updateOptTypeSource();
 }
 
 
@@ -203,7 +103,7 @@ bool Foam::steadyOptimisation::checkEndOfLoopAndUpdate()
 {
     if (update())
     {
-        optType_->update();
+        dvUpdate_->update();
     }
     return end();
 }
@@ -221,25 +121,4 @@ bool Foam::steadyOptimisation::update()
 }
 
 
-void Foam::steadyOptimisation::updateDesignVariables()
-{
-    // Update design variables using either a line-search scheme or
-    // a fixed-step update
-    if (optType_->getLineSearch())
-    {
-        lineSearchUpdate();
-    }
-    else
-    {
-        fixedStepUpdate();
-    }
-
-    // Reset adjoint sensitivities in all adjoint solver managers
-    for (adjointSolverManager& adjSolverManager : adjointSolverManagers_)
-    {
-        adjSolverManager.clearSensitivities();
-    }
-}
-
-
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.H
index 0fad239c9c9b806f220d55b85be0a560c557b768..7ae292ee2e4faa693f313a22a078c43fcc199f7b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/steadyOptimisation/steadyOptimisation.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -60,9 +60,6 @@ private:
 
     // Private Member Functions
 
-        //- Update optimisationType source for all primal and adjoint solvers
-        void updateOptTypeSource();
-
         //- No copy construct
         steadyOptimisation(const steadyOptimisation&) = delete;
 
@@ -114,10 +111,6 @@ public:
 
         //- Whether to update the design variables
         virtual bool update();
-
-        //- Do a line search to find a correction satisfying the step
-        //- convergence criteria
-        virtual void updateDesignVariables();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.C
deleted file mode 100644
index ccbf08f466c9f7ab55af8b3a58b5ac083546d537..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.C
+++ /dev/null
@@ -1,344 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019-2021 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "optimisationTypeIncompressible.H"
-#include "constrainedOptimisationMethod.H"
-#include "runTimeSelectionTables.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(optimisationType, 0);
-defineRunTimeSelectionTable(optimisationType, dictionary);
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-optimisationType::optimisationType
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    PtrList<adjointSolverManager>& adjointSolverManagers
-)
-:
-    mesh_(mesh),
-    dict_(dict),
-    adjointSolvManagers_(adjointSolverManagers),
-    updateMethod_
-    (
-        updateMethod::New(mesh_, dict_.subDict("updateMethod"))
-    ),
-    sourcePtr_(nullptr),
-    lineSearch_
-    (
-        lineSearch::New
-        (
-            dict_.subDict("updateMethod").subOrEmptyDict("lineSearch"),
-            mesh.time()
-        )
-    )
-{
-    // Figure out number of adjoint solvers corresponding to constraints.
-    // Looks in all operating poitns
-    label nConstraints(0);
-    for (const adjointSolverManager& adjManagerI : adjointSolvManagers_)
-    {
-        nConstraints += adjManagerI.nConstraints();
-    }
-
-    // Sanity checks for combinations of number of constraints and
-    // optimisation methods
-    if
-    (
-        nConstraints
-     && !isA<constrainedOptimisationMethod>(updateMethod_())
-    )
-    {
-        const auto& cnstrTable =
-            *(constrainedOptimisationMethod::dictionaryConstructorTablePtr_);
-
-        // Has constraints but is not a constraint optimisation method
-        FatalErrorInFunction
-            << "Found " << nConstraints << " adjoint solvers corresponding to "
-            << "constraints but the optimisation method ("
-            << updateMethod_().type()
-            << ") is not a constrainedOptimisationMethod." << nl
-            << "Available constrainedOptimisationMethods:" << nl
-            << cnstrTable.sortedToc()
-            << exit(FatalError);
-    }
-    else if
-    (
-        !nConstraints
-     && isA<constrainedOptimisationMethod>(updateMethod_())
-    )
-    {
-        // Does not have constraints but is a constrained optimisation method
-        WarningInFunction
-            << "Did not find any adjoint solvers corresponding to "
-            << "constraints but the optimisation method ("
-            << updateMethod_().type()
-            << ") is a constrainedOptimisationMethod." << nl << nl
-            << "This can cause some constraintOptimisationMethods to misbehave."
-            << nl << nl
-            << "Either the isConstraint bool is not set in one of the adjoint "
-            << "solvers or you should consider using an updateMethod "
-            << "that is not a constrainedOptimisationMethod"
-            << nl << endl;
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
-
-autoPtr<optimisationType> optimisationType::New
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    PtrList<adjointSolverManager>& adjointSolverManagers
-)
-{
-    const word modelType(dict.subDict("optimisationType").get<word>("type"));
-
-    Info<< "optimisationType type : " << modelType << endl;
-
-    auto* ctorPtr = dictionaryConstructorTable(modelType);
-
-    if (!ctorPtr)
-    {
-        FatalIOErrorInLookup
-        (
-            dict,
-            "optimisationType",
-            modelType,
-            *dictionaryConstructorTablePtr_
-        ) << exit(FatalIOError);
-    }
-
-    return autoPtr<optimisationType>
-    (
-        ctorPtr(mesh, dict, adjointSolverManagers)
-    );
-}
-
-
-// * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
-
-void optimisationType::update()
-{
-    // Compute update of the design variables
-    tmp<scalarField> tcorrection(computeDirection());
-    scalarField& correction = tcorrection.ref();
-
-    // Update design variables given the correction
-    update(correction);
-
-    // If direction has been scaled (say by setting the initial eta), the
-    // old correction has to be updated
-    updateOldCorrection(correction);
-    write();
-}
-
-
-void optimisationType::update(scalarField& direction)
-{
-    // Multiply with line search step, if necessary
-    scalarField correction(direction);
-    if (lineSearch_)
-    {
-        correction *= lineSearch_->step();
-    }
-
-    // Update the design variables
-    updateDesignVariables(correction);
-}
-
-
-tmp<scalarField> optimisationType::computeDirection()
-{
-    // Sum contributions for sensitivities and objective/constraint values
-    scalarField objectiveSens;
-    PtrList<scalarField> constraintSens;
-    scalar objectiveValue(Zero);
-    scalarField constraintValues;
-
-    updateGradientsAndValues
-    (
-        objectiveSens,
-        constraintSens,
-        objectiveValue,
-        constraintValues
-    );
-
-    // Based on the sensitivities, return design variables correction
-    updateMethod_->setObjectiveDeriv(objectiveSens);
-    updateMethod_->setConstraintDeriv(constraintSens);
-    updateMethod_->setObjectiveValue(objectiveValue);
-    updateMethod_->setConstraintValues(constraintValues);
-    tmp<scalarField> tcorrection
-    (
-        new scalarField(objectiveSens.size(), Zero)
-    );
-    scalarField& correction = tcorrection.ref();
-    correction = updateMethod_->returnCorrection();
-
-    // Compute eta if needed
-    computeEta(correction);
-
-    return tcorrection;
-}
-
-
-void optimisationType::updateGradientsAndValues
-(
-    scalarField& objectiveSens,
-    PtrList<scalarField>& constraintSens,
-    scalar& objectiveValue,
-    scalarField& constraintValues
-)
-{
-    for (adjointSolverManager& adjSolvManager : adjointSolvManagers_)
-    {
-        const scalar opWeight = adjSolvManager.operatingPointWeight();
-
-        // Allocate objective sens size if necessary
-        tmp<scalarField> tadjointSolverManagerSens =
-            adjSolvManager.aggregateSensitivities();
-
-        if (objectiveSens.empty())
-        {
-            objectiveSens.setSize(tadjointSolverManagerSens().size(), Zero);
-        }
-
-        objectiveSens += opWeight*tadjointSolverManagerSens();
-        objectiveValue += opWeight*adjSolvManager.objectiveValue();
-
-        // Allocate constraint sens size if necessary
-        PtrList<scalarField> adjointSolverManagerConstSens =
-            adjSolvManager.constraintSensitivities();
-
-        tmp<scalarField> cValues = adjSolvManager.constraintValues();
-
-        if (constraintSens.empty())
-        {
-            constraintSens.setSize(adjointSolverManagerConstSens.size());
-            forAll(constraintSens, cI)
-            {
-                constraintSens.set
-                (
-                    cI,
-                    new scalarField
-                    (
-                        adjointSolverManagerConstSens[cI].size(),
-                        Zero
-                    )
-                );
-                constraintValues.setSize(cValues().size());
-                constraintValues = Zero;
-            }
-        }
-
-        forAll(constraintSens, cI)
-        {
-            constraintSens[cI] += opWeight*adjointSolverManagerConstSens[cI];
-        }
-        constraintValues += opWeight*cValues();
-    }
-}
-
-
-scalar optimisationType::computeMeritFunction()
-{
-    // Compute new objective and constraint values and update the ones
-    // in updateMethod
-    scalar objectiveValue(Zero);
-    scalarField constraintValues;
-
-    for (adjointSolverManager& adjSolvManager : adjointSolvManagers_)
-    {
-        const scalar opWeight = adjSolvManager.operatingPointWeight();
-
-        objectiveValue += opWeight*adjSolvManager.objectiveValue();
-        tmp<scalarField> cValues = adjSolvManager.constraintValues();
-
-        if (constraintValues.empty())
-        {
-            constraintValues.setSize(cValues().size(), Zero);
-        }
-        constraintValues += opWeight*cValues();
-    }
-    updateMethod_->setObjectiveValue(objectiveValue);
-    updateMethod_->setConstraintValues(constraintValues);
-
-    return updateMethod_->computeMeritFunction();
-}
-
-
-scalar optimisationType::meritFunctionDirectionalDerivative()
-{
-    return updateMethod_->meritFunctionDirectionalDerivative();
-}
-
-
-void optimisationType::updateOldCorrection(const scalarField& oldCorrection)
-{
-    updateMethod_->updateOldCorrection(oldCorrection);
-}
-
-
-void optimisationType::write()
-{
-    updateMethod_->write();
-}
-
-
-const autoPtr<volScalarField>& optimisationType::sourcePtr()
-{
-    return sourcePtr_;
-}
-
-
-autoPtr<lineSearch>& optimisationType::getLineSearch()
-{
-    return lineSearch_;
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.H
deleted file mode 100644
index 1c1a75619fb239e515bfadc3cb1f570457fe4fb7..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.H
+++ /dev/null
@@ -1,193 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-
-Class
-    Foam::incompressible::optimisationType
-
-Description
-    Abstract base class for optimisation methods
-
-SourceFiles
-    optimisationType.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef optimisationTypeIncompressible_H
-#define optimisationTypeIncompressible_H
-
-#include "adjointSolverManager.H"
-#include "updateMethod.H"
-#include "lineSearch.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class optimisationType Declaration
-\*---------------------------------------------------------------------------*/
-
-class optimisationType
-{
-protected:
-
-    // Protected data
-
-        fvMesh& mesh_;
-        const dictionary dict_;
-        PtrList<adjointSolverManager>& adjointSolvManagers_;
-        autoPtr<updateMethod> updateMethod_;
-        autoPtr<volScalarField> sourcePtr_;
-        autoPtr<lineSearch> lineSearch_;
-
-        //- Update the design variables given their correction
-        virtual void updateDesignVariables(scalarField& correction) = 0;
-
-        //- Compute eta if not set in the first step
-        virtual void computeEta(scalarField& correction) = 0;
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        optimisationType(const optimisationType&) = delete;
-
-        //- No copy assignment
-        void operator=(const optimisationType&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("optimisationType");
-
-
-    // Declare run-time constructor selection table
-
-        declareRunTimeSelectionTable
-        (
-            autoPtr,
-            optimisationType,
-            dictionary,
-            (
-                fvMesh& mesh,
-                const dictionary& dict,
-                PtrList<adjointSolverManager>& adjointSolverManagers
-            ),
-            (mesh, dict, adjointSolverManagers)
-        );
-
-
-
-    // Constructors
-
-        //- Construct from components
-        optimisationType
-        (
-            fvMesh& mesh,
-            const dictionary& dict,
-            PtrList<adjointSolverManager>& adjointSolverManagers
-        );
-
-    // Selectors
-
-        //- Return a reference to the selected turbulence model
-        static autoPtr<optimisationType> New
-        (
-            fvMesh& mesh,
-            const dictionary& dict,
-            PtrList<adjointSolverManager>& adjointSolverManagers
-        );
-
-
-    // Destructor
-
-        virtual ~optimisationType() = default;
-
-        //- Update design variables
-        virtual void update();
-
-        //- Update design variables based on a given correction
-        virtual void update(scalarField& correction);
-
-        //- Store design variables, as the starting point for line search
-        virtual void storeDesignVariables() = 0;
-
-        //- Reset to starting point of line search
-        virtual void resetDesignVariables() = 0;
-
-        //- Compute update direction
-        virtual tmp<scalarField> computeDirection();
-
-        //- Compute cumulative objective and constraint gradients
-        virtual void updateGradientsAndValues
-        (
-            scalarField& objectiveSens,
-            PtrList<scalarField>& constraintSens,
-            scalar& objectiveValue,
-            scalarField& constraintValues
-        );
-
-        //- Compute the merit function of the optimisation problem.
-        //  Could be different than the objective function in case of
-        //  constraint optimisation
-        virtual scalar computeMeritFunction();
-
-        //- Derivative of the merit function
-        virtual scalar meritFunctionDirectionalDerivative();
-
-        //- Update old correction. Needed for quasi-Newton Methods
-        virtual void updateOldCorrection(const scalarField&);
-
-        //- Write useful quantities to files
-        virtual void write();
-
-        //- Get source term
-        const autoPtr<volScalarField>& sourcePtr();
-
-        //- Get a reference to the line search object
-        autoPtr<lineSearch>& getLineSearch();
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.C
deleted file mode 100644
index 7a71d5ea6bc861bf4a0369ed05857c208de88909..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.C
+++ /dev/null
@@ -1,207 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
-
-\*---------------------------------------------------------------------------*/
-
-#include "shapeOptimisationIncompressible.H"
-#include "addToRunTimeSelectionTable.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-defineTypeNameAndDebug(shapeOptimisation, 0);
-addToRunTimeSelectionTable
-(
-    optimisationType,
-    shapeOptimisation,
-    dictionary
-);
-
-// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
-
-void shapeOptimisation::updateDesignVariables(scalarField& correction)
-{
-    // Communicate the movement to optMeshMovement
-    optMeshMovement_->setCorrection(correction);
-
-    if (updateGeometry_)
-    {
-        // Update the mesh
-        optMeshMovement_->moveMesh();
-
-        if (writeEachMesh_)
-        {
-            Info<< "  Writing new mesh points " << endl;
-            pointIOField points
-            (
-                IOobject
-                (
-                   "points",
-                    mesh_.pointsInstance(),
-                    mesh_.meshSubDir,
-                    mesh_,
-                    IOobject::NO_READ,
-                    IOobject::NO_WRITE,
-                    IOobject::NO_REGISTER
-                ),
-                mesh_.points()
-            );
-            points.write();
-        }
-    }
-}
-
-
-void shapeOptimisation::computeEta
-(
-    scalarField& correction
-)
-{
-    if (!updateMethod_->initialEtaSet())
-    {
-        // In the unlikely event that eta is not set and the line search step
-        // is not 1, multiply with it
-        // if (lineSearch_) correction *= lineSearch_->step();
-
-        // Compute eta based on desirable mesh movement size
-        scalar eta = optMeshMovement_->computeEta(correction);
-        correction *= eta;
-
-        // Update eta known by the optimisation method and inform it that is
-        // has been set
-        updateMethod_->setStep(eta);
-        updateMethod_->initialEtaSet() = true;
-
-        // If a backtracking should be made at the first optimisation cycle,
-        // the direction of the subsequent line searches of the same cycle
-        // should also be scaled with the newly computed eta. We do this by
-        // changing the line search step. This will happen only at the first
-        // optimisation cycle since the updated value of eta will be included
-        // in the line search direction in all subsequent optimisation cycles
-        //correction *= eta;
-    }
-}
-
-
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
-
-shapeOptimisation::shapeOptimisation
-(
-    fvMesh& mesh,
-    const dictionary& dict,
-    PtrList<adjointSolverManager>& adjointSolverManagers
-)
-:
-    optimisationType(mesh, dict, adjointSolverManagers),
-    optMeshMovement_(nullptr),
-    writeEachMesh_
-    (
-        dict.subDict("optimisationType").
-            getOrDefault<bool>("writeEachMesh", false)
-    ),
-    updateGeometry_
-    (
-        dict.subDict("optimisationType").
-            getOrDefault<bool>("updateGeometry", true)
-    )
-{
-    // Note: to be updated
-    labelHashSet patches
-    (
-        mesh_.boundaryMesh().patchSet
-        (
-            dict_.subDict("sensitivities").get<wordRes>("patches")
-        )
-    );
-    if (patches.empty())
-    {
-        WarningInFunction
-            << "There is no patch on which to compute sensitivities. "
-            << "Check optimisationDict \n"
-            << endl;
-    }
-    labelList sensitivityPatchIDs = patches.toc();
-    optMeshMovement_.reset
-    (
-        optMeshMovement::New
-        (
-            mesh_,
-            dict_.subDict("meshMovement"),
-            sensitivityPatchIDs
-        ).ptr()
-    );
-
-    // Sanity checks: at least one of eta or maxAllowedDisplacement must be set
-    if
-    (
-        !updateMethod_->initialEtaSet()
-     && !optMeshMovement_().maxAllowedDisplacementSet()
-    )
-    {
-        FatalErrorInFunction
-            << "Neither eta (updateMethod) "
-            << "nor maxAllowedDisplacement (meshMovement) have been set"
-            << nl
-            << exit(FatalError);
-    }
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void shapeOptimisation::storeDesignVariables()
-{
-    optMeshMovement_->storeDesignVariables();
-}
-
-
-void shapeOptimisation::resetDesignVariables()
-{
-    optMeshMovement_->resetDesignVariables();
-}
-
-
-void shapeOptimisation::write()
-{
-    optimisationType::write();
-    updateMethod_->writeCorrection();
-}
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.H
deleted file mode 100644
index 421feaba671710687f9422582921254b701a11a1..0000000000000000000000000000000000000000
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.H
+++ /dev/null
@@ -1,140 +0,0 @@
-/*---------------------------------------------------------------------------*\
-  =========                 |
-  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
-   \\    /   O peration     |
-    \\  /    A nd           | www.openfoam.com
-     \\/     M anipulation  |
--------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
-
-
-
-Description
-    Shape optimisation support library
-
-Class
-    Foam::incompressible::shapeOptimisation
-
-Description
-    Calculates shape sensitivities using the adjoint approach,
-    computes boundaryMesh movement and propagates it to the volume mesh
-
-SourceFiles
-    shapeOptimisation.C
-
-\*---------------------------------------------------------------------------*/
-
-#ifndef shapeOptimisationIncompressible_H
-#define shapeOptimisationIncompressible_H
-
-#include "optimisationTypeIncompressible.H"
-#include "optMeshMovement.H"
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-namespace incompressible
-{
-
-/*---------------------------------------------------------------------------*\
-                      Class shapeOptimisation Declaration
-\*---------------------------------------------------------------------------*/
-
-class shapeOptimisation
-:
-    public optimisationType
-{
-protected:
-
-    // Protected data
-
-        autoPtr<optMeshMovement> optMeshMovement_;
-
-        bool writeEachMesh_;
-        bool updateGeometry_;
-
-
-    // Protected Member Functions
-
-        //- Update the design variables given their correction
-        virtual void updateDesignVariables(scalarField& correction);
-
-        //- Compute eta if not set in the first step
-        virtual void computeEta(scalarField& correction);
-
-
-private:
-
-    // Private Member Functions
-
-        //- No copy construct
-        shapeOptimisation(const shapeOptimisation&) = delete;
-
-        //- No copy assignment
-        void operator=(const shapeOptimisation&) = delete;
-
-
-public:
-
-    //- Runtime type information
-    TypeName("shapeOptimisation");
-
-
-    // Constructors
-
-        //- Construct from components
-        shapeOptimisation
-        (
-            fvMesh& mesh,
-            const dictionary& dict,
-            PtrList<adjointSolverManager>& adjointSolverManagers
-        );
-
-
-    //- Destructor
-    virtual ~shapeOptimisation() = default;
-
-
-    // Member Functions
-
-        //- Store design variables, as the starting point for line search
-        virtual void storeDesignVariables();
-
-        //- Store design variables, as the starting point for line search
-        virtual void resetDesignVariables();
-
-        //- Write useful quantities to files
-        virtual void write();
-};
-
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-} // End namespace incompressible
-} // End namespace Foam
-
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
-
-#endif
-
-// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.C
index b451f6b66017e511bef51d3ac2ee4217931f69b8..05aa86d2b7e3d1bea0abf0321d0515c74db6e8a7 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -46,24 +46,6 @@ namespace Foam
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-void Foam::BFGS::allocateMatrices()
-{
-    // Set active design variables, if necessary
-    if (activeDesignVars_.empty())
-    {
-        activeDesignVars_ = identity(objectiveDerivatives_.size());
-    }
-
-    // Set previous HessianInv to be a diagonal matrix
-    SquareMatrix<scalar> temp(activeDesignVars_.size(), I);
-
-    // Allocate correct size and content to HessianInv matrices
-    // has a max. capability of approximately 34000 variables.
-    HessianInvOld_ = temp;
-    HessianInv_ = temp;
-}
-
-
 void Foam::BFGS::updateHessian()
 {
     // Vectors needed to construct the inverse HessianInv matrix
@@ -73,14 +55,22 @@ void Foam::BFGS::updateHessian()
     s.map(correctionOld_, activeDesignVars_);
 
     scalar ys = globalSum(s*y);
-
     if (counter_ == 1 && scaleFirstHessian_)
     {
-        scalar scaleFactor = ys/globalSum(y*y);
-        Info<< "Scaling Hessian with factor " << scaleFactor << endl;
-        forAll(activeDesignVars_, varI)
+        if (ys > scalar(0))
         {
-            HessianInvOld_[varI][varI] *= scaleFactor;
+            scalar scaleFactor = ys/globalSum(y*y);
+            Info<< "Scaling Hessian with factor " << scaleFactor << endl;
+            forAll(activeDesignVars_, varI)
+            {
+                Hessian_()[varI][varI] *= scaleFactor;
+            }
+        }
+        else
+        {
+            WarningInFunction
+                << "y*s is negative. Skipping the scaling of the first Hessian"
+                << endl;
         }
     }
 
@@ -95,13 +85,12 @@ void Foam::BFGS::updateHessian()
         << "Hessian curvature index " << ys << endl;
 
     // Construct the inverse HessianInv
-    HessianInv_ =
-        HessianInvOld_
-      + (ys + globalSum(leftMult(y, HessianInvOld_)*y))/sqr(ys)*outerProd(s, s)
+    Hessian_() +=
+        (ys + globalSum(leftMult(y, Hessian_())*y))/sqr(ys)*outerProd(s, s)
       - (scalar(1)/ys)*
         (
-           outerProd(rightMult(HessianInvOld_, y), s)
-         + outerProd(s, leftMult(y, HessianInvOld_))
+           outerProd(rightMult(Hessian_(), y), s)
+         + outerProd(s, leftMult(y, Hessian_()))
         );
 }
 
@@ -113,9 +102,12 @@ void Foam::BFGS::update()
     if (counter_ < nSteepestDescent_)
     {
         Info<< "Using steepest descent to update design variables" << endl;
-        correction_ = -eta_*objectiveDerivatives_;
+        for (const label varI : activeDesignVars_)
+        {
+            correction_[varI] = -eta_*objectiveDerivatives_[varI];
+        }
     }
-    // else use BFGS formula to update the design variables
+    // Else use BFGS formula to update the design variables
     else
     {
         // Compute correction for active design variables
@@ -123,7 +115,7 @@ void Foam::BFGS::update()
         activeDerivs.map(objectiveDerivatives_, activeDesignVars_);
         scalarField activeCorrection
         (
-            -etaHessian_*rightMult(HessianInv_, activeDerivs)
+            -etaHessian_*rightMult(Hessian_(), activeDerivs)
         );
 
         // Transfer correction to the global list
@@ -137,29 +129,6 @@ void Foam::BFGS::update()
     // Store fields for the next iteration
     derivativesOld_ = objectiveDerivatives_;
     correctionOld_ = correction_;
-    HessianInvOld_ = HessianInv_;
-}
-
-
-void Foam::BFGS::readFromDict()
-{
-    if (optMethodIODict_.headerOk())
-    {
-        optMethodIODict_.readEntry("HessianInvOld", HessianInvOld_);
-        optMethodIODict_.readEntry("derivativesOld", derivativesOld_);
-        optMethodIODict_.readEntry("correctionOld", correctionOld_);
-        optMethodIODict_.readEntry("counter", counter_);
-        optMethodIODict_.readEntry("eta", eta_);
-
-        const label n(HessianInvOld_.n());
-        HessianInv_ = SquareMatrix<scalar>(n, Zero);
-        correction_ = scalarField(correctionOld_.size(), Zero);
-
-        if (activeDesignVars_.empty())
-        {
-            activeDesignVars_ = identity(n);
-        }
-    }
 }
 
 
@@ -168,88 +137,19 @@ void Foam::BFGS::readFromDict()
 Foam::BFGS::BFGS
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
 :
-    updateMethod(mesh, dict),
-    etaHessian_
-    (
-        coeffsDict().getOrDefault<scalar>("etaHessian", 1)
-    ),
-    nSteepestDescent_
-    (
-        coeffsDict().getOrDefault<label>("nSteepestDescent", 1)
-    ),
-    activeDesignVars_(0),
-    scaleFirstHessian_
-    (
-        coeffsDict().getOrDefault<bool>("scaleFirstHessian", false)
-    ),
+    quasiNewton(mesh, dict, designVars, nConstraints, type),
     curvatureThreshold_
     (
-        coeffsDict().getOrDefault<scalar>("curvatureThreshold", 1e-10)
-    ),
-    // Construct null matrix since we dont know the dimension yet
-    HessianInv_(),
-    HessianInvOld_(),
-    derivativesOld_(0),
-    correctionOld_(0),
-    counter_(Zero)
-{
-    if
-    (
-        !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
+        coeffsDict(type).getOrDefault<scalar>("curvatureThreshold", 1e-10)
     )
-    {
-        // If not, all available design variables will be used. Number is not
-        // know at the moment
-        Info<< "\t Did not find explicit definition of active design variables."
-            << " Treating all available ones as active" << endl;
-    }
-
-    // Read old hessian, correction and derivatives, if present
-    readFromDict();
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::BFGS::computeCorrection()
-{
-    if (counter_ == 0)
-    {
-        allocateMatrices();
-    }
-    else
-    {
-        updateHessian();
-    }
-
-    update();
-    ++counter_;
-}
-
-
-void Foam::BFGS::updateOldCorrection(const scalarField& oldCorrection)
 {
-    updateMethod::updateOldCorrection(oldCorrection);
-    correctionOld_ = oldCorrection;
-}
-
-
-void Foam::BFGS::write()
-{
-    optMethodIODict_.add<SquareMatrix<scalar>>
-    (
-        "HessianInvOld",
-        HessianInvOld_,
-        true
-    );
-    optMethodIODict_.add<scalarField>("derivativesOld", derivativesOld_, true);
-    optMethodIODict_.add<scalarField>("correctionOld", correctionOld_, true);
-    optMethodIODict_.add<label>("counter", counter_, true);
-
-    updateMethod::write();
+    allocateHessian();
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.H
index 4995f7f502e52c4f89cbb511f1a8e3b9ae7d3af1..9daa6cd15d93f8733cbf61e425ca853998b30e29 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/BFGS/BFGS.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -30,7 +30,8 @@ Class
     Foam::BFGS
 
 Description
-    The quasi-Newton BFGS formula
+    The quasi-Newton BFGS formula.
+    quasiNewton::Hessian corresponds to the inverse Hessian matrix in BFGS
 
 SourceFiles
     BFGS.C
@@ -40,8 +41,7 @@ SourceFiles
 #ifndef BFGS_H
 #define BFGS_H
 
-#include "updateMethod.H"
-#include "scalarMatrices.H"
+#include "quasiNewton.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,7 +54,7 @@ namespace Foam
 
 class BFGS
 :
-    public updateMethod
+    public quasiNewton
 {
 private:
 
@@ -71,48 +71,17 @@ protected:
 
     // Protected data
 
-        //- Step for the Newton method
-        scalar etaHessian_;
-
-        //- Number of first steepest descent steps
-        label nSteepestDescent_;
-
-        //- Map to active design variables
-        labelList activeDesignVars_;
-
-        //- Scale the iniitial unitary Hessian approximation
-        bool scaleFirstHessian_;
-
         //- Curvature threshold
         scalar curvatureThreshold_;
 
-        //- The Hessian inverse. Should have the size of the active design
-        //- variables
-        SquareMatrix<scalar> HessianInv_;
-
-        //- The previous Hessian inverse
-        SquareMatrix<scalar> HessianInvOld_;
-
-        //- The previous derivatives
-        scalarField derivativesOld_;
-
-        //- The previous correction
-        scalarField correctionOld_;
 
-        //- Optimisation cycle counter
-        label counter_;
-
-        //- Allocate matrices in the first optimisation cycle
-        void allocateMatrices();
+    // Protected Member Functions
 
         //- Update approximation of the inverse Hessian
-        void updateHessian();
+        virtual void updateHessian();
 
         //- Update design variables
-        void update();
-
-        //- Read old info from dict
-        void readFromDict();
+        virtual void update();
 
 
 public:
@@ -124,24 +93,18 @@ public:
     // Constructors
 
         //- Construct from components
-        BFGS(const fvMesh& mesh, const dictionary& dict);
+        BFGS
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
     virtual ~BFGS() = default;
-
-
-    // Member Functions
-
-       //- Compute design variables correction
-       void computeCorrection();
-
-       //- Update old correction. Useful for quasi-Newton methods coupled with
-       //- line search
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
-
-       //- Write old info to dict
-       virtual void write();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.C
index 6f38c121317d03e2cb1d5b3f63d954116d5c9f3b..4d1e2aa32f72079dbdd3d105c6b8036d05bdd5dd 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -46,24 +46,6 @@ namespace Foam
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-void Foam::DBFGS::allocateMatrices()
-{
-    // Set active design variables, if necessary
-    if (activeDesignVars_.empty())
-    {
-        activeDesignVars_ = identity(objectiveDerivatives_.size());
-    }
-
-    // Set previous Hessian to be a diagonal matrix
-    SquareMatrix<scalar> temp(activeDesignVars_.size(), I);
-
-    // Allocate correct size and content to Hessian matrices
-    // has a max. capability of approximately 34000 variables.
-    HessianOld_ = temp;
-    Hessian_ = temp;
-}
-
-
 void Foam::DBFGS::updateHessian()
 {
     // Vectors needed to construct the inverse Hessian matrix
@@ -73,18 +55,26 @@ void Foam::DBFGS::updateHessian()
     s.map(correctionOld_, activeDesignVars_);
 
     scalar ys = globalSum(s*y);
-
     if (counter_ == 1 && scaleFirstHessian_)
     {
-        scalar scaleFactor = ys/globalSum(y*y);
-        Info<< "Scaling Hessian with factor " << scaleFactor << endl;
-        forAll(activeDesignVars_, varI)
+        if (ys > scalar(0))
         {
-            HessianOld_[varI][varI] /= scaleFactor;
+            scalar scaleFactor = ys/globalSum(y*y);
+            Info<< "Scaling Hessian with factor " << scaleFactor << endl;
+            forAll(activeDesignVars_, varI)
+            {
+                Hessian_()[varI][varI] /= scaleFactor;
+            }
+        }
+        else
+        {
+            WarningInFunction
+                << "y*s is negative. Skipping the scaling of the first Hessian"
+                << endl;
         }
     }
 
-    scalar sBs = globalSum(leftMult(s, HessianOld_)*s);
+    scalar sBs = globalSum(leftMult(s, Hessian_())*s);
 
     // Check curvature condition and apply dampening is necessary
     scalar theta(1);
@@ -98,26 +88,28 @@ void Foam::DBFGS::updateHessian()
     DebugInfo
         << "Hessian curvature index " << ys << endl;
 
-    scalarField r(theta*y + (scalar(1)-theta)*rightMult(HessianOld_, s));
+    scalarField r(theta*y + (scalar(1)-theta)*rightMult(Hessian_(), s));
 
     // Construct the inverse Hessian
-    Hessian_ =
-        HessianOld_
-      - outerProd(rightMult(HessianOld_, s), leftMult(s/sBs, HessianOld_))
+    Hessian_() +=
+      - outerProd(rightMult(Hessian_(), s), leftMult(s/sBs, Hessian_()))
       + outerProd(r, r/globalSum(s*r));
 }
 
 
 void Foam::DBFGS::update()
 {
-    SquareMatrix<scalar> HessianInv = inv(Hessian_);
+    SquareMatrix<scalar> HessianInv = inv(Hessian_());
 
     // In the first few iterations, use steepest descent but update the Hessian
     // matrix
     if (counter_ < nSteepestDescent_)
     {
         Info<< "Using steepest descent to update design variables" << endl;
-        correction_ = -eta_*objectiveDerivatives_;
+        for (const label varI : activeDesignVars_)
+        {
+            correction_[varI] = -eta_*objectiveDerivatives_[varI];
+        }
     }
     // Else use DBFGS formula to update design variables
     else
@@ -141,29 +133,6 @@ void Foam::DBFGS::update()
     // Store fields for the next iteration
     derivativesOld_ = objectiveDerivatives_;
     correctionOld_ = correction_;
-    HessianOld_ = Hessian_;
-}
-
-
-void Foam::DBFGS::readFromDict()
-{
-    if (optMethodIODict_.headerOk())
-    {
-        optMethodIODict_.readEntry("HessianOld",  HessianOld_);
-        optMethodIODict_.readEntry("derivativesOld", derivativesOld_);
-        optMethodIODict_.readEntry("correctionOld", correctionOld_);
-        optMethodIODict_.readEntry("counter", counter_);
-        optMethodIODict_.readEntry("eta", eta_);
-
-        label n = HessianOld_.n();
-        Hessian_ = SquareMatrix<scalar>(n, Zero);
-        correction_ = scalarField(correctionOld_.size(), Zero);
-
-        if (activeDesignVars_.empty())
-        {
-            activeDesignVars_ = identity(n);
-        }
-    }
 }
 
 
@@ -172,86 +141,20 @@ void Foam::DBFGS::readFromDict()
 Foam::DBFGS::DBFGS
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
 :
-    updateMethod(mesh, dict),
-
-    // Construct null matrix since we dont know the dimension yet
-    etaHessian_
-    (
-        coeffsDict().getOrDefault<scalar>("etaHessian", 1)
-    ),
-    nSteepestDescent_
-    (
-        coeffsDict().getOrDefault<label>("nSteepestDescent", 1)
-    ),
-    activeDesignVars_(0),
-    scaleFirstHessian_
-    (
-        coeffsDict().getOrDefault<bool>("scaleFirstHessian", false)
-    ),
+    quasiNewton(mesh, dict, designVars, nConstraints, type),
     curvatureThreshold_
     (
-        coeffsDict().getOrDefault<scalar>("curvatureThreshold", 1e-10)
+        coeffsDict(type).getOrDefault<scalar>("curvatureThreshold", 1e-10)
     ),
-    Hessian_(),
-    HessianOld_(),
-    derivativesOld_(0),
-    correctionOld_(0),
-    counter_(0),
-    gamma_(coeffsDict().getOrDefault<scalar>("gamma", 0.2))
-
-{
-    if
-    (
-        !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
-    )
-    {
-        // If not, all available design variables will be used. Number is not
-        // know at the moment
-        Info<< "\t Did not find explicit definition of active design variables."
-               " Treating all available ones as active " << endl;
-    }
-
-    // read old hessian, correction and derivatives, if present
-    readFromDict();
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::DBFGS::computeCorrection()
-{
-    if (counter_ == 0)
-    {
-        allocateMatrices();
-    }
-    else
-    {
-        updateHessian();
-    }
-
-    update();
-    ++counter_;
-}
-
-
-void Foam::DBFGS::updateOldCorrection(const scalarField& oldCorrection)
+    gamma_(coeffsDict(type).getOrDefault<scalar>("gamma", 0.2))
 {
-    updateMethod::updateOldCorrection(oldCorrection);
-    correctionOld_ = oldCorrection;
-}
-
-
-void Foam::DBFGS::write()
-{
-    optMethodIODict_.add<SquareMatrix<scalar>>("HessianOld", HessianOld_, true);
-    optMethodIODict_.add<scalarField>("derivativesOld", derivativesOld_, true);
-    optMethodIODict_.add<scalarField>("correctionOld", correctionOld_, true);
-    optMethodIODict_.add<label>("counter", counter_, true);
-
-    updateMethod::write();
+    allocateHessian();
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.H
index 44d171fd7f1c4a0b05f4c1247c23f25a5dd4db69..55983504b105b366e0b6c318b726876c5646aa58 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/DBFGS/DBFGS.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -40,8 +40,7 @@ SourceFiles
 #ifndef DBFGS_H
 #define DBFGS_H
 
-#include "updateMethod.H"
-#include "scalarMatrices.H"
+#include "quasiNewton.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,59 +53,27 @@ namespace Foam
 
 class DBFGS
 :
-    public updateMethod
+    public quasiNewton
 {
 protected:
 
     // Protected data
 
-        //- Step for the Newton method
-        scalar etaHessian_;
-
-        //- Number of first steepest descent steps
-        label nSteepestDescent_;
-
-        //- Map to active design variables
-        labelList activeDesignVars_;
-
-        //- Scale the initial unitary Hessian approximation
-        bool scaleFirstHessian_;
-
         //- Curvature threshold
         scalar curvatureThreshold_;
 
-        //- The Hessian. Should have the size of the active design variables
-        SquareMatrix<scalar> Hessian_;
-
-        //- The previous Hessian
-        SquareMatrix<scalar> HessianOld_;
-
-        //- The previous derivatives
-        scalarField derivativesOld_;
-
-        //- The previous correction
-        scalarField correctionOld_;
-
-        //- Optimisation cycle counter
-        label counter_;
-
         //- Threshold for damping
         scalar gamma_;
 
 
     // Protected Member Functions
 
-        //- Allocate matrices in the first optimisation cycle
-        void allocateMatrices();
-
         //- Update approximation of the inverse Hessian
         void updateHessian();
 
         //- Update design variables
         void update();
 
-        //- Read old info from dict
-        void readFromDict();
 
 private:
 
@@ -124,27 +91,22 @@ public:
     //- Runtime type information
     TypeName("DBFGS");
 
+
     // Constructors
 
         //- Construct from components
-        DBFGS(const fvMesh& mesh, const dictionary& dict);
+        DBFGS
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
     virtual ~DBFGS() = default;
-
-
-    // Member Functions
-
-       //- Compute design variables correction
-       void computeCorrection();
-
-       //- Update old correction. Useful for quasi-Newton methods coupled with
-       //- line search
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
-
-       //- Write old info to dict
-       virtual void write();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.C
new file mode 100644
index 0000000000000000000000000000000000000000..9e0eb0d66784f883a13c892464955b57bfde0429
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.C
@@ -0,0 +1,1048 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020-2021 PCOpt/NTUA
+    Copyright (C) 2020-2021 FOSS GP
+-------------------------------------------------------------------------------
+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 "ISQP.H"
+#include "IOmanip.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(ISQP, 0);
+    addToRunTimeSelectionTable
+    (
+        updateMethod,
+        ISQP,
+        dictionary
+    );
+    addToRunTimeSelectionTable
+    (
+        constrainedOptimisationMethod,
+        ISQP,
+        dictionary
+    );
+}
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+void Foam::ISQP::updateSizes()
+{
+    const label n = activeDesignVars_.size();
+    if (n != deltaP_.size())
+    {
+        // Correction fields
+        p_.setSize(n, Zero);
+        deltaP_.setSize(n, Zero);
+
+        // Lagrange multipliers and slack variables for bound constraints
+        if (includeBoundConstraints_)
+        {
+            lTilda_().setSize(n, Zero);
+            ls_().setSize(n, Zero);
+            uTilda_().setSize(n, Zero);
+            us_().setSize(n, Zero);
+
+            deltaLTilda_().setSize(n, Zero);
+            deltaLs_().setSize(n, Zero);
+            deltaUTilda_().setSize(n, Zero);
+            deltaUs_().setSize(n, Zero);
+        }
+
+        // Fields used to compute the Hessian
+        for (label i = 0; i < nPrevSteps_; ++i)
+        {
+            y_[i].setSize(n, Zero);
+            s_[i].setSize(n, Zero);
+        }
+    }
+}
+
+
+void Foam::ISQP::allocateBoundMultipliers()
+{
+    if (includeBoundConstraints_)
+    {
+        // Number of constraints
+        const label n(activeDesignVars_.size());
+
+        if (!lTilda_)
+        {
+            lTilda_.reset(autoPtr<scalarField>::New(n, Zero));
+        }
+        ls_.reset(autoPtr<scalarField>::New(n, Zero));
+        if (!uTilda_)
+        {
+            uTilda_.reset(autoPtr<scalarField>::New(n, Zero));
+        }
+        us_.reset(autoPtr<scalarField>::New(n, Zero));
+
+        deltaLTilda_.reset(autoPtr<scalarField>::New(n, Zero));
+        deltaLs_.reset(autoPtr<scalarField>::New(n, Zero));
+        deltaUTilda_.reset(autoPtr<scalarField>::New(n, Zero));
+        deltaUs_.reset(autoPtr<scalarField>::New(n, Zero));
+    }
+}
+
+
+void Foam::ISQP::allocateLagrangeMultipliers()
+{
+    // Number of constraints
+    const label m(nConstraints_);
+    // Allocate the extra variables ensuring the feasibility
+    if (includeExtraVars_)
+    {
+        extraVars_.reset(autoPtr<scalarField>::New(m, 1));
+        z_.reset(autoPtr<scalarField>::New(m, max(1, 0.5*c_)));
+
+        deltaExtraVars_.reset(autoPtr<scalarField>::New(m, Zero));
+        deltaZ_.reset(autoPtr<scalarField>::New(m, Zero));
+    }
+
+    doAllocateLagrangeMultipliers_ = false;
+}
+
+
+void Foam::ISQP::updateYS()
+{
+    // Compute the Lagranian and old Lagrangian derivatives
+    scalarField LagrangianDerivativesOld(derivativesOld_);
+    forAll(constraintDerivatives_, cI)
+    {
+        LagrangianDerivatives_ += lamdas_[cI]*constraintDerivatives_[cI];
+        LagrangianDerivativesOld += lamdas_[cI]*constraintDerivativesOld_[cI];
+    }
+
+    if (includeBoundConstraints_)
+    {
+        forAll(activeDesignVars_, aI)
+        {
+            const label varI(activeDesignVars_[aI]);
+            const scalar contr(uTilda_()[aI] - lTilda_()[aI]);
+            LagrangianDerivatives_[varI] += contr;
+            LagrangianDerivativesOld[varI] += contr;
+        }
+    }
+
+    // Update vectors associated to the inverse Hessian matrix
+    updateVectors(LagrangianDerivatives_, LagrangianDerivativesOld);
+}
+
+
+void Foam::ISQP::initialize()
+{
+    const scalarField x(designVars_().getVars(), activeDesignVars_);
+
+    // Quantities related to design variables
+    p_ = Zero;
+    if (includeBoundConstraints_)
+    {
+        lTilda_() = scalar(1);
+        uTilda_() = scalar(1);
+        ls_() = scalar(1);
+        us_() = scalar(1);
+    }
+
+    // Quantities related to constraints
+    lamdas_ = scalar(1);
+    gs_ = scalar(1);
+
+    if (includeExtraVars_)
+    {
+        extraVars_() = scalar(1);
+        z_() = max(1, 0.5*c_);
+    }
+
+    // Reset eps
+    eps_ = 1;
+}
+
+
+void Foam::ISQP::zeroUpdates()
+{
+    deltaP_ = Zero;
+    deltaLamda_ = Zero;
+    deltaGs_ = Zero;
+
+    if (includeBoundConstraints_)
+    {
+        deltaLTilda_() = Zero;
+        deltaLs_() = Zero;
+        deltaUTilda_() = Zero;
+        deltaUs_() = Zero;
+    }
+
+    if (includeExtraVars_)
+    {
+        deltaExtraVars_() = Zero;
+        deltaZ_() = Zero;
+    }
+}
+
+
+void Foam::ISQP::solveDeltaPEqn()
+{
+    // Explicit part of the right hand side of the deltaX equation
+    scalarField FDx(-resFL());
+    if (includeBoundConstraints_)
+    {
+        FDx +=
+            (uTilda_()*resFus() + resFuTilda())/us_()
+          - (lTilda_()*resFls() + resFlTilda())/ls_();
+    }
+    scalarField AMult(resFlamda()/lamdas_ - resFGs());
+    scalarField mult(gs_/lamdas_);
+    if (includeExtraVars_)
+    {
+        mult += extraVars_()/z_();
+        AMult -= (extraVars_()*resFExtraVars() + resFz())/z_();
+    }
+    AMult /= mult;
+    forAll(FDx, aI)
+    {
+        const label varI(activeDesignVars_[aI]);
+        forAll(constraintDerivatives_, cI)
+        {
+            FDx[aI] += constraintDerivatives_[cI][varI]*AMult[cI];
+        }
+    }
+    CGforDeltaP(FDx);
+    /*
+  //Info<< "FDx::" << FDx << endl;
+  //Info<< "invHFL::" << invHFL() << endl;
+
+    // Loop to obtain deltaX. Part of the LHS is treated explicitly, to
+    // avoid solving a (potentially) dense system with the size of the design
+    // variables
+    scalarField rhs(computeRHSForDeltaX(FDx));
+    scalar res(sqrt(globalSum(magSqr(deltaP_ - rhs))));
+    scalar resInit(res);
+    label iter(0);
+    do
+    {
+        deltaP_ = rhs;
+        rhs = computeRHSForDeltaX(FDx);
+        res = sqrt(globalSum(magSqr(deltaP_ - rhs)));
+        DebugInfo
+            << "Solving for deltaX, Initial Residual " << resInit
+            << ", Final Residual " << res << endl;
+        resInit = res;
+    } while (iter++ < maxDxIters_ && res > 1.e-07);
+//  Info<< "deltaX solution " << deltaP_ << endl;
+//  */
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::computeRHSForDeltaX
+(
+    const scalarField& FDx
+)
+{
+    tmp<scalarField> trhs(tmp<scalarField>::New(-FDx));
+    scalarField& rhs = trhs.ref();
+
+    // Compute (Gs)^(-1)*Λ*A*Dp
+    scalarField GsLADp(cValues_.size(), Zero);
+    forAll(constraintDerivatives_, cI)
+    {
+        const scalarField& cDerivsI = constraintDerivatives_[cI];
+        GsLADp[cI] +=
+            globalSum(scalarField(cDerivsI, activeDesignVars_)*deltaP_);
+    }
+    GsLADp *= lamdas_/gs_;
+
+    // Multiply with A^T
+    forAll(rhs, aI)
+    {
+        const label varI(activeDesignVars_[aI]);
+        forAll(constraintDerivatives_, cI)
+        {
+            rhs[aI] += constraintDerivatives_[cI][varI]*GsLADp[cI];
+        }
+    }
+
+    // Contributions from bounds
+    if (includeBoundConstraints_)
+    {
+        rhs += (lTilda_()/ls_() + uTilda_()/us_())*deltaP_;
+    }
+
+    rhs = -invHessianVectorProduct(rhs);
+
+    rhs = 0.95*deltaP_ + 0.05*rhs;
+    return trhs;
+}
+
+
+void Foam::ISQP::CGforDeltaP(const scalarField& FDx)
+{
+    scalarField precond(deltaPDiagPreconditioner());
+    scalarField r(FDx - DeltaPMatrixVectorProduct(deltaP_));
+    scalarField z(precond*r);
+    scalarField p(z);
+    scalar res(sqrt(globalSum(r*r)));
+    scalar resInit(res);
+    scalar rz(globalSum(r*z));
+    scalar rzOld(rz);
+    label iter(0);
+    do
+    {
+        scalarField Ap(DeltaPMatrixVectorProduct(p));
+        scalar a = rz/globalSum(p*Ap);
+        deltaP_ += a*p;
+        r -= a*Ap;
+        res = sqrt(globalSum(r*r));
+        z = precond*r;
+        rz = globalSum(r*z);
+        scalar beta = rz/rzOld;
+        p = z + beta*p;
+        rzOld = rz;
+    } while (iter++ < maxDxIters_ && res > 1.e-09);
+    DebugInfo
+        << "CG, Solving for deltaP, Initial Residual " << resInit
+        << ", Final Residual " << res
+        << ", No Iterations " << iter << endl;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::deltaPDiagPreconditioner()
+{
+    tmp<scalarField> tpreconditioner(HessianDiag());
+  //tmp<scalarField> tpreconditioner(SR1HessianDiag());
+    scalarField& preconditioner = tpreconditioner.ref();
+
+    // Part related to the constraints
+    forAll(constraintDerivatives_, cI)
+    {
+        scalarField cDerivs(constraintDerivatives_[cI], activeDesignVars_);
+        scalar mult(gs_[cI]/lamdas_[cI]);
+        if (includeExtraVars_)
+        {
+            mult += extraVars_()[cI]/z_()[cI];
+        }
+        preconditioner += sqr(cDerivs)/mult;
+    }
+
+    if (includeBoundConstraints_)
+    {
+        preconditioner += lTilda_()/ls_() + uTilda_()/us_();
+    }
+
+    preconditioner = 1./preconditioner;
+
+    return tpreconditioner;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::DeltaPMatrixVectorProduct
+(
+    const scalarField& vector
+)
+{
+    tmp<scalarField> tAp(HessianVectorProduct(vector));
+  //tmp<scalarField> tAp(SR1HessianVectorProduct(vector));
+    scalarField& Ap = tAp.ref();
+    scalarField GsLAv(cValues_.size(), Zero);
+    forAll(constraintDerivatives_, cI)
+    {
+        const scalarField& cDerivsI = constraintDerivatives_[cI];
+        GsLAv[cI] =
+            globalSum(scalarField(cDerivsI, activeDesignVars_)*vector);
+    }
+    scalarField mult(gs_/lamdas_);
+    if (includeExtraVars_)
+    {
+        mult += extraVars_()/z_();
+    }
+    GsLAv /= mult;
+
+    // Multiply with A^T
+    forAll(Ap, aI)
+    {
+        const label varI(activeDesignVars_[aI]);
+        forAll(constraintDerivatives_, cI)
+        {
+            Ap[aI] += constraintDerivatives_[cI][varI]*GsLAv[cI];
+        }
+    }
+
+    // Contributions from bounds
+    if (includeBoundConstraints_)
+    {
+        Ap += (lTilda_()/ls_() + uTilda_()/us_())*vector;
+    }
+
+
+    return tAp;
+}
+
+
+void Foam::ISQP::computeNewtonDirection()
+{
+    // Zero the updates computed in the previous optimisation cycle
+    //zeroUpdates();
+
+    // Solve equation for deltaP_. The expensive part of the step. Everything
+    // else can be computed based on this
+    solveDeltaPEqn();
+
+    // deltaLamda
+    forAll(constraintDerivatives_, cI)
+    {
+        const scalarField& cDerivsI = constraintDerivatives_[cI];
+        deltaLamda_[cI]  =
+            globalSum(scalarField(cDerivsI, activeDesignVars_)*deltaP_);
+    }
+    scalarField mult(gs_/lamdas_);
+    if (includeExtraVars_)
+    {
+        mult += extraVars_()/z_();
+        deltaLamda_ += (resFz() + extraVars_()*resFExtraVars())/z_();
+    }
+    deltaLamda_ += resFGs() - resFlamda()/lamdas_;
+    deltaLamda_ /= mult;
+
+    // deltaGs
+    deltaGs_ = -(gs_*deltaLamda_ + resFlamda())/lamdas_;
+
+    if (includeBoundConstraints_)
+    {
+        // deltaLs
+        deltaLs_() = deltaP_ + resFls();
+
+        // deltaUs
+        deltaUs_() = -deltaP_ + resFus();
+
+        // deltaLTilda
+        deltaLTilda_() = -(lTilda_()*deltaLs_() + resFlTilda())/ls_();
+
+        // deltaUTilda
+        deltaUTilda_() = -(uTilda_()*deltaUs_() + resFuTilda())/us_();
+    }
+
+    if (includeExtraVars_)
+    {
+        deltaZ_() = -deltaLamda_ + resFExtraVars();
+        deltaExtraVars_() = - (extraVars_()*deltaZ_() + resFz())/z_();
+    }
+}
+
+
+Foam::scalar Foam::ISQP::lineSearch()
+{
+    const label n(p_.size());
+    const label m(cValues_.size());
+    scalar step(1.);
+
+    if (includeBoundConstraints_)
+    {
+        for (label i = 0; i < n; ++i)
+        {
+            adjustStep(step, ls_()[i], deltaLs_()[i]);
+            adjustStep(step, us_()[i], deltaUs_()[i]);
+            adjustStep(step, lTilda_()[i], deltaLTilda_()[i]);
+            adjustStep(step, uTilda_()[i], deltaUTilda_()[i]);
+        }
+    }
+
+    // Perform bound checks and adjust step accordingly
+    for (label i = 0; i < m; ++i)
+    {
+        adjustStep(step, lamdas_[i], deltaLamda_[i]);
+        adjustStep(step, gs_[i], deltaGs_[i]);
+        if (includeExtraVars_)
+        {
+            adjustStep(step, extraVars_()[i], deltaExtraVars_()[i]);
+            adjustStep(step, z_()[i], deltaZ_()[i]);
+        }
+    }
+
+    // Each processor might have computed a different step, if design variables
+    // are distributed. Get the global minimum
+    if (globalSum_)
+    {
+        reduce(step, minOp<scalar>());
+    }
+
+    step = min(1, step);
+
+    if (debug > 1)
+    {
+        Info<< "Step before line search is " << step << endl;
+    }
+
+    // Old residual
+    scalar normResOld = sqrt(globalSum(magSqr(computeResiduals())));
+    scalar maxRes(GREAT);
+
+    for (label i = 0; i < maxLineSearchIters_ ; ++i)
+    {
+        // Update the solution with given step
+        updateSolution(step);
+
+        // Compute new residuals and their max value
+        scalarField resNew(computeResiduals());
+        scalar normResNew  = sqrt(globalSum(magSqr(resNew)));
+        maxRes = gMax(mag(resNew));
+
+        if (normResNew < normResOld)
+        {
+            DebugInfo
+                << "Initial residual = " << normResOld << ", "
+                << "Final residual = " << normResNew << ", "
+                << "No of LineSearch Iterations = " << i + 1
+                << endl;
+            break;
+        }
+        else
+        {
+            // Return solution to previous and reduce step
+            if (i != maxLineSearchIters_ - 1)
+            {
+                updateSolution(-step);
+                step *= 0.5;
+            }
+            else
+            {
+                Info<< tab << "Line search did not converge. "
+                    << "Procceding with the last compute step" << endl;
+            }
+        }
+    }
+
+    if (debug > 1)
+    {
+        Info<< "Step after line search is " << step << nl <<  endl;
+    }
+
+    return maxRes;
+}
+
+
+void Foam::ISQP::adjustStep
+(
+    scalar& step,
+    const scalar value,
+    const scalar update
+)
+{
+    if (0.99*value + step*update < scalar(0))
+    {
+        step = -0.99*value/update;
+    }
+}
+
+
+void Foam::ISQP::updateSolution(const scalar step)
+{
+    p_ += step*deltaP_;
+    lamdas_ += step*deltaLamda_;
+    gs_ += step*deltaGs_;
+    if (includeBoundConstraints_)
+    {
+        lTilda_() += step*deltaLTilda_();
+        ls_() += step*deltaLs_();
+        uTilda_() += step*deltaUTilda_();
+        us_() += step*deltaUs_();
+    }
+    if (includeExtraVars_)
+    {
+        extraVars_() += step*deltaExtraVars_();
+        z_() += step*deltaZ_();
+    }
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::computeResiduals()
+{
+    const label n(activeDesignVars_.size());
+    const label m(cValues_.size());
+    label size(includeBoundConstraints_ ? 5*n + 2*m : n + 2*m);
+    if (includeExtraVars_)
+    {
+        size += 2*m;
+    }
+    tmp<scalarField> tres(tmp<scalarField>::New(size, Zero));
+    scalarField& res = tres.ref();
+
+    label iRes(0);
+
+    // Gradient of the Lagrangian
+    res.rmap(resFL()(), identity(n));
+    iRes = n;
+
+    // Inequality constraints slacks
+    res.rmap(resFGs()(), identity(m, iRes));
+    iRes += m;
+
+    // Inequality constraints complementarity slackness
+    res.rmap(resFlamda()(), identity(m, iRes));
+    iRes += m;
+
+    if (includeBoundConstraints_)
+    {
+        // Lower bounds slacks
+        res.rmap(resFls()(), identity(n, iRes));
+        iRes += n;
+
+        // Upper bounds slacks
+        res.rmap(resFus()(), identity(n, iRes));
+        iRes += n;
+
+        // Lower bounds complementarity slackness
+        res.rmap(resFlTilda()(), identity(n, iRes));
+        iRes += n;
+
+        // Upper bounds complementarity slackness
+        res.rmap(resFuTilda()(), identity(n, iRes));
+        iRes += n;
+    }
+
+    if (includeExtraVars_)
+    {
+        // Lagragian derivative wrt the extra variables
+        res.rmap(resFExtraVars()(), identity(m, iRes));
+        iRes += m;
+
+        // Lagrange multipliers for the extra variables positiveness
+        res.rmap(resFz(), identity(m, iRes));
+        iRes += m;
+    }
+
+    return tres;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFL()
+{
+    tmp<scalarField> tgradL
+        (tmp<scalarField>::New(objectiveDerivatives_, activeDesignVars_));
+    scalarField& gradL = tgradL.ref();
+
+    scalarField Hp(HessianVectorProduct(p_));
+  //scalarField Hp = SR1HessianVectorProduct(p_);
+    gradL += Hp;
+
+    if (debug > 2)
+    {
+        scalarField H1Hp(invHessianVectorProduct(Hp));
+        Info << "Diff H1Hp - p " << gSum(mag(H1Hp - p_)) << endl;
+    }
+
+    forAll(constraintDerivatives_, cI)
+    {
+        gradL +=
+            lamdas_[cI]
+           *scalarField(constraintDerivatives_[cI], activeDesignVars_);
+    }
+
+    if (includeBoundConstraints_)
+    {
+        gradL += uTilda_() - lTilda_();
+    }
+
+    return tgradL;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::invHFL()
+{
+    tmp<scalarField> tinvHFL
+        (tmp<scalarField>::New(objectiveDerivatives_, activeDesignVars_));
+    scalarField& invHFL = tinvHFL.ref();
+
+    forAll(constraintDerivatives_, cI)
+    {
+        invHFL +=
+            lamdas_[cI]
+           *scalarField(constraintDerivatives_[cI], activeDesignVars_);
+    }
+
+    if (includeBoundConstraints_)
+    {
+        invHFL += uTilda_() - lTilda_();
+    }
+
+    invHFL = invHessianVectorProduct(invHFL);
+    invHFL += p_;
+
+    return tinvHFL;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFGs()
+{
+    tmp<scalarField> tFGs(tmp<scalarField>::New(gs_ + cValues_));
+    scalarField& FGs = tFGs.ref();
+
+    forAll(constraintDerivatives_, cI)
+    {
+        FGs[cI] +=
+            globalSum
+            (
+                scalarField(constraintDerivatives_[cI], activeDesignVars_)*p_
+            );
+    }
+
+    if (includeExtraVars_)
+    {
+        FGs -= extraVars_();
+    }
+
+    return tFGs;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFlamda()
+{
+    return (lamdas_*gs_ - eps_);
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFls()
+{
+    if (includeBoundConstraints_)
+    {
+        const scalarField x(designVars_().getVars(), activeDesignVars_);
+        const scalarField xMin
+            (designVars_().lowerBounds()(), activeDesignVars_);
+
+        return (x + p_ - xMin - ls_());
+    }
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFus()
+{
+    if (includeBoundConstraints_)
+    {
+        const scalarField x(designVars_().getVars(), activeDesignVars_);
+        const scalarField xMax
+            (designVars_().upperBounds()(), activeDesignVars_);
+
+        return (xMax - x - p_ - us_());
+    }
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFlTilda()
+{
+    if (includeBoundConstraints_)
+    {
+        return (lTilda_()*ls_() - eps_);
+    }
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFuTilda()
+{
+    if (includeBoundConstraints_)
+    {
+        return (uTilda_()*us_() - eps_);
+    }
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFExtraVars()
+{
+    if (includeExtraVars_)
+    {
+        return (c_ - lamdas_ - z_());
+    }
+    return nullptr;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::ISQP::resFz()
+{
+    if (includeExtraVars_)
+    {
+        return (z_()*extraVars_() - eps_);
+    }
+    return nullptr;
+}
+
+
+void Foam::ISQP::solveSubproblem()
+{
+    zeroUpdates();
+    if (includeBoundConstraints_ || !cValues_.empty())
+    {
+        scalar resMax(gMax(mag(computeResiduals())));
+        label iter(0);
+        do
+        {
+            DebugInfo
+                << "Newton iter " << iter << nl << endl;
+
+            // Decrease eps
+            if (resMax < 0.9*eps_)
+            {
+                eps_ *= 0.1;
+            }
+
+            // Computes Newton direction for the subproblem
+            computeNewtonDirection();
+
+            // Perform line search and return max residual of the solution
+            // satisfying the bound constraints and the residual reduction.
+            // Upates solution.
+            resMax = lineSearch();
+            DebugInfo
+                << "max residual = " << resMax << ", "
+                << "eps = " << eps_ << nl << endl;
+        } while
+        (
+            iter++ < maxNewtonIters_ && (eps_ > epsMin_ || resMax > 0.9*eps_)
+        );
+        if (iter == maxNewtonIters_)
+        {
+            WarningInFunction
+                << "Iterative solution of the QP problem did not converge"
+                << endl;
+        }
+        if (debug)
+        {
+            scalarField vars(designVars_().getVars(), activeDesignVars_);
+            scalarField newVars(vars + p_);
+            Info<< "Min of updated vars " << gMin(newVars) << endl;
+            Info<< "Max of updated vars " << gMax(newVars) << endl;
+
+            Info<< "Min of lamda " << gMin(lamdas_) << endl;
+            Info<< "Max of gs " << gMax(gs_) << endl;
+            Info<< "Max of lamda*gs " << gMax(lamdas_*gs_) << endl;
+
+            if (includeBoundConstraints_)
+            {
+                Info<< "Min of lTilda " << gMin(lTilda_()) << endl;
+                Info<< "Min of uTilda " << gMin(uTilda_()) << endl;
+                Info<< "Min of ls " << gMin(ls_()) << endl;
+                Info<< "Min of us " << gMin(us_()) << endl;
+                Info<< "Max of lTilda*ls " << gMax(lTilda_()*ls_()) << endl;
+                Info<< "Max of uTilda*us " << gMax(uTilda_()*us_()) << endl;
+            }
+            if (includeExtraVars_)
+            {
+                Info<< "Min of extraVars " << gMin(extraVars_()) << endl;
+                Info<< "Max of extraVars*z " << gMax(extraVars_()*z_()) << endl;
+            }
+        }
+    }
+    else
+    {
+        computeNewtonDirection();
+        lineSearch();
+    }
+
+    // Pass update to correction field
+    correction_.rmap(p_, activeDesignVars_);
+    if (!counter_)
+    {
+        correction_ *= eta_;
+    }
+    else
+    {
+        correction_ *= etaHessian_;
+    }
+}
+
+
+void Foam::ISQP::storeOldFields()
+{
+    derivativesOld_ = objectiveDerivatives_;
+    if (constraintDerivativesOld_.empty())
+    {
+        constraintDerivativesOld_.setSize(constraintDerivatives_.size());
+    }
+    forAll(constraintDerivativesOld_, cI)
+    {
+        constraintDerivativesOld_[cI] = constraintDerivatives_[cI];
+    }
+    correctionOld_ = correction_;
+}
+
+
+Foam::scalar Foam::ISQP::meritFunctionConstraintPart() const
+{
+    // Assumes that all constraints are known by all processors
+    // What about constraints directly imposed on distributed design variables?
+    // These should be met in each iteration of the algorithm, so,
+    // most probably, there is no problem
+    return sum(pos(cValues_)*cValues_);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::ISQP::ISQP
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
+)
+:
+    LBFGS(mesh, dict, designVars, nConstraints, type),
+    SQPBase(mesh, dict, designVars, *this, type),
+
+    doAllocateLagrangeMultipliers_(true),
+    includeBoundConstraints_
+    (
+        designVars->upperBounds() && designVars->lowerBounds()
+    ),
+    includeExtraVars_
+    (
+        coeffsDict(type).getOrDefault<bool>("includeExtraVars", false)
+    ),
+    p_(activeDesignVars_.size(), Zero),
+    gs_(nConstraints_, 1),
+    lTilda_
+    (
+        includeBoundConstraints_ && found("lTilda") ?
+        new scalarField("lTilda", *this, activeDesignVars_.size()) :
+        nullptr
+    ),
+    ls_(nullptr),
+    uTilda_
+    (
+        includeBoundConstraints_ && found("uTilda") ?
+        new scalarField("uTilda", *this, activeDesignVars_.size()) :
+        nullptr
+    ),
+    us_(nullptr),
+    extraVars_(nullptr),
+    z_(nullptr),
+    c_(coeffsDict(type).getOrDefault<scalar>("c", 100)),
+    deltaP_(activeDesignVars_.size(), Zero),
+    deltaLamda_(nConstraints_, Zero),
+    deltaGs_(nConstraints_, Zero),
+    deltaLTilda_(nullptr),
+    deltaLs_(nullptr),
+    deltaUTilda_(nullptr),
+    deltaUs_(nullptr),
+    deltaExtraVars_(nullptr),
+    deltaZ_(nullptr),
+    eps_(1),
+    epsMin_(coeffsDict(type).getOrDefault<scalar>("epsMin", 1.e-07)),
+    maxNewtonIters_(coeffsDict(type).getOrDefault<label>("maxIters", 1000)),
+    maxLineSearchIters_
+    (
+        coeffsDict(type).getOrDefault<label>("maxLineSearchIters", 10)
+    ),
+    maxDxIters_(coeffsDict(type).getOrDefault<label>("maxDpIters", 1000)),
+    meritFunctionFile_(nullptr)
+{
+    // Always apply damping of s in ISQP
+    useYDamping_ = true;
+    useSDamping_ = false;
+
+    // Allocate multipliers and slack variables for the bound constraints
+    allocateBoundMultipliers();
+    allocateLagrangeMultipliers();
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::ISQP::computeCorrection()
+{
+    // Update sizes of fields related to the active design variables
+    updateSizes();
+
+    // The first iteration uses a unitary Hessian. No need to update
+    LagrangianDerivatives_ = objectiveDerivatives_;
+    if (counter_)
+    {
+        updateYS();
+    }
+
+    // Initiaze variables
+    initialize();
+
+    // Solve subproblem using a Newton optimiser
+    solveSubproblem();
+
+    // Store fields for the next iteration and write them to file
+    storeOldFields();
+
+    // Increase counter
+    ++counter_;
+}
+
+
+Foam::scalar Foam::ISQP::computeMeritFunction()
+{
+    mu_ = max(pos(cValues_)*lamdas_) + delta_;
+    scalar L = objectiveValue_ + mu_*sum(pos(cValues_)*cValues_);
+
+    return L;
+}
+
+
+Foam::scalar Foam::ISQP::meritFunctionDirectionalDerivative()
+{
+    scalar deriv =
+        globalSum(objectiveDerivatives_*correction_)
+      - mu_*sum(pos(cValues_)*cValues_);
+
+    return deriv;
+}
+
+
+void Foam::ISQP::updateOldCorrection(const scalarField& oldCorrection)
+{
+    updateMethod::updateOldCorrection(oldCorrection);
+    correctionOld_ = oldCorrection;
+}
+
+
+bool Foam::ISQP::writeData(Ostream& os) const
+{
+    if (includeBoundConstraints_)
+    {
+        uTilda_().writeEntry("uTilda", os);
+        lTilda_().writeEntry("lTilda", os);
+    }
+
+    return LBFGS::writeData(os) && SQPBase::addToFile(os);
+}
+
+
+bool Foam::ISQP::writeAuxiliaryData()
+{
+    return SQPBase::writeMeritFunction(*this);
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.H
new file mode 100644
index 0000000000000000000000000000000000000000..56336c41cbe78dab9ea2f5cd2c34d2d59b7c6c84
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.H
@@ -0,0 +1,345 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2020-2021 PCOpt/NTUA
+    Copyright (C) 2020-2021 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+
+Class
+    Foam::ISQP
+
+Description
+    An L-BFGS-based SQP algorithm for computing the update of the design
+    variables in the presence of inequality constraints. The QP problem is
+    solved using the interior point method (hence the I in the classe name,
+    which is not standard in the terminology used in the literature). The
+    (potentially dense) linear system formulated by the interior point method
+    is solved using Conjugate Gradient with a diagonal preconditioner, using
+    matrix-vector products to avoid storing the LHS matrix.
+
+    Bound constraints on the design variables will also be included, if set by
+    the designVariables known by the updateMethod. If the QP problem is
+    infeasible, the algorithm can still be used by setting includeExtraVars_
+    to true, to allow a computation of an update, despite not being able to
+    satisfy all the constraints of the QP problem.
+
+
+SourceFiles
+    ISQP.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef ISQP_H
+#define ISQP_H
+
+#include "LBFGS.H"
+#include "SQPBase.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                             Class ISQP Declaration
+\*---------------------------------------------------------------------------*/
+
+class ISQP
+:
+    public LBFGS,
+    public SQPBase
+{
+protected:
+
+    // Protected data
+
+        //- Should Lagrange multipliers be allocated
+        bool doAllocateLagrangeMultipliers_;
+
+        //- Are bound constraints included?
+        bool includeBoundConstraints_;
+
+        //- Are additional design variables included?
+        //  These are introduced to find relaxed solutions, even if the
+        //  original problem does not have any feasible points
+        bool includeExtraVars_;
+
+        //- The set of design variables being updated during the subproblem.
+        //  Size is that of the active design variables.
+        //  Correction will end up being the difference of this and the old
+        //  design variables.
+        scalarField p_;
+
+        // Lagrange multipliers and slack variables
+
+            //- Lagrange multipliers for the inequality constraints
+            //  Inheritated from SQPBase
+            //scalarField lamdas_;
+
+            //- Slack variables for the inequality constraints
+            scalarField gs_;
+
+            //- Lagrange multipliers for the lower bound constraints
+            autoPtr<scalarField> lTilda_;
+
+            //- Slack variables the lower bound constraints
+            autoPtr<scalarField> ls_;
+
+            //- Lagrange multipliers for the upper bound constraints
+            autoPtr<scalarField> uTilda_;
+
+            //- Slack variables the upper bound constraints
+            autoPtr<scalarField> us_;
+
+            //- Extra variables for finding solutions even in infeasible
+            //- problems
+            autoPtr<scalarField> extraVars_;
+
+            //- Lagrange multipliers for positive extra variables
+            autoPtr<scalarField> z_;
+
+            //- Multiplier of the additional variables y in the Lagrangian, to
+            //- make them 'expensive'
+            scalar c_;
+
+
+        // Fields holding updates of the design, Lagrange and slack variables
+
+            scalarField deltaP_;
+            scalarField deltaLamda_;
+            scalarField deltaGs_;
+            autoPtr<scalarField> deltaLTilda_;
+            autoPtr<scalarField> deltaLs_;
+            autoPtr<scalarField> deltaUTilda_;
+            autoPtr<scalarField> deltaUs_;
+            autoPtr<scalarField> deltaExtraVars_;
+            autoPtr<scalarField> deltaZ_;
+
+
+        //- Infinitesimal quantity
+        //  Updated during the inner iterations of the subproblem
+        scalar eps_;
+
+        //- Final eps quantity to be reached during the solution of the
+        //- subproblem
+        scalar epsMin_;
+
+        //- Maxmimum number of Newton iterations for the subproblem
+        label maxNewtonIters_;
+
+        //- Maxmimum number of line search iterations for each iteration of the
+        //- subproblem
+        label maxLineSearchIters_;
+
+        //- Maxmimum number of iterations for the deltaX equation
+        label maxDxIters_;
+
+        //- File including the l1 merit function
+        autoPtr<OFstream> meritFunctionFile_;
+
+
+    // Protected Member Functions
+
+        //- Update sizes of fields related to the active design variables
+        void updateSizes();
+
+        //- Allocate multipliers for the bound constraints
+        void allocateBoundMultipliers();
+
+        //- Allocate Lagrange multipliers for the inequality constraints
+        void allocateLagrangeMultipliers();
+
+        //- Update the vectors accosiated with the Hessian matrix
+        void updateYS();
+
+        //- Allocate fields related to constraints
+        void initialize();
+
+
+        // Functions related to the solution of the primal-dual subproblem
+
+            //- Solve subproblem using a Newton optimiser
+            void solveSubproblem();
+
+            //- Compute direction for the Newton problem
+            void computeNewtonDirection();
+
+            //- Zero the updates computed in the previous optimisation cycle
+            void zeroUpdates();
+
+            //- Solve the equation for deltaX, which is the expensive part of
+            //- the Newtopn step.
+            //  All other corrections can be computed based on this
+            void solveDeltaPEqn();
+
+            //- Compute the RHS for the deltaX equation
+            tmp<scalarField> computeRHSForDeltaX(const scalarField& FDx);
+
+            //- CG algorithm for the solution of the deltaP eqn
+            void CGforDeltaP(const scalarField& FDx);
+
+            //- Diagonal preconditioner of the deltaP eqn
+            tmp<scalarField> deltaPDiagPreconditioner();
+
+            //- Procudt of the LHS of the deltaP eqn with a vector
+            tmp<scalarField> DeltaPMatrixVectorProduct
+            (
+                const scalarField& vector
+            );
+
+            //- Perform line search and return max residual corresponding to
+            //- the updated solution
+            scalar lineSearch();
+
+            //- Adjust step to satisfy cireteria
+            void adjustStep
+            (
+                scalar& step,
+                const scalar value,
+                const scalar update
+            );
+
+            //- Update the current solution using the known direction and the
+            //- given step length
+            void updateSolution(const scalar step);
+
+
+        // Residuals of the various KKT conditions
+
+            //- Compute and return residuals based on the current solution
+            tmp<scalarField> computeResiduals();
+
+            //- Residual of the gradient of the Lagrangian
+            //  Size is that of the active design variables
+            tmp<scalarField> resFL();
+
+            //- Product of the inverse Hessian with the residual of the
+            //- gradient of the Lagrangian.
+            //  Avoid the formation of the Hessian matrix.
+            //  Size is that of the active design variables.
+            tmp<scalarField> invHFL();
+
+            //- Residual of the inequality constraint slackness
+            tmp<scalarField> resFGs();
+
+            //- Residual of the complementarity slackness for the
+            //- inequality constraints
+            tmp<scalarField> resFlamda();
+
+            //- Residual of the lower bounds slackness
+            tmp<scalarField> resFls();
+
+            //- Residual of the upper bounds slackness
+            tmp<scalarField> resFus();
+
+            //- Residual of the complementarity slackness for the
+            //- lower bound constraints
+            tmp<scalarField> resFlTilda();
+
+            //- Residual of the complementarity slackness for the
+            //- upper bound constraints
+            tmp<scalarField> resFuTilda();
+
+            //- Residual of the Lagrangian derivative wrt the extra variables
+            tmp<scalarField> resFExtraVars();
+
+            //- Residual of the complementarity slackness for the
+            //- extra variables
+            tmp<scalarField> resFz();
+
+
+        //- Store old fields needed for the next iter
+        void storeOldFields();
+
+        //- Get the part the merit function that depends on the constraints
+        virtual scalar meritFunctionConstraintPart() const;
+
+
+private:
+
+    // Private Member Functions
+
+        //- No copy construct
+        ISQP(const ISQP&) = delete;
+
+        //- No copy assignment
+        void operator=(const ISQP&) = delete;
+
+
+public:
+
+    //- Runtime type information
+    TypeName("ISQP");
+
+
+    // Constructors
+
+        //- Construct from components
+        ISQP
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
+
+
+    //- Destructor
+    virtual ~ISQP() = default;
+
+
+    // Member Functions
+
+        //- Compute design variables correction
+        void computeCorrection();
+
+        //- Compute merit function. Could be different than the objective
+        //- in the presence of constraints
+        virtual scalar computeMeritFunction();
+
+        //- Derivative of the merit function. Could be different than the
+        //- objective derivative in the presence of constraints
+        virtual scalar meritFunctionDirectionalDerivative();
+
+        //- Update old correction. Useful for quasi-Newton methods coupled with
+        //- line search
+        virtual void updateOldCorrection(const scalarField& oldCorrection);
+
+        //- Write useful quantities to files
+        virtual bool writeData(Ostream& os) const;
+
+        //- Write merit function information
+        virtual bool writeAuxiliaryData();
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.C
index 2aa335053f5f3f7956a7970a0e02234db65d218b..6d82c97299f799fb96dd4e0e5b2ce84940b06b01 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -34,7 +34,7 @@ License
 
 namespace Foam
 {
-    defineTypeNameAndDebug(LBFGS, 0);
+    defineTypeNameAndDebug(LBFGS, 1);
     addToRunTimeSelectionTable
     (
         updateMethod,
@@ -46,20 +46,27 @@ namespace Foam
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-void Foam::LBFGS::allocateMatrices()
+void Foam::LBFGS::allocateVectors()
 {
-    // Set active design variables, if necessary
-    if (activeDesignVars_.empty())
-    {
-        activeDesignVars_ = identity(objectiveDerivatives_.size());
-    }
-
-    // Allocate vectors
     label nVars(activeDesignVars_.size());
-    for (label i = 0; i < nPrevSteps_; i++)
+    for (label i = 0; i < nPrevSteps_; ++i)
     {
-        y_.set(i, new scalarField(nVars, Zero));
-        s_.set(i, new scalarField(nVars, Zero));
+        if (!y_.get(i))
+        {
+            y_.set(i, new scalarField(nVars, Zero));
+        }
+        if (!s_.get(i))
+        {
+            s_.set(i, new scalarField(nVars, Zero));
+        }
+        if (found("y" + Foam::name(i)))
+        {
+            y_[i] = scalarField("y" + Foam::name(i), *this, nVars);
+        }
+        if (found("s" + Foam::name(i)))
+        {
+            s_[i] = scalarField("s" + Foam::name(i), *this, nVars);
+        }
     }
 }
 
@@ -87,187 +94,553 @@ void Foam::LBFGS::pivotFields(PtrList<scalarField>& list, const scalarField& f)
 }
 
 
-void Foam::LBFGS::updateVectors()
+void Foam::LBFGS::updateVectors
+(
+    const scalarField& derivatives,
+    const scalarField& derivativesOld
+)
 {
-    // Update list of y. Can only be done here since objectiveDerivatives_
-    // was not known at the end of the previous loop
-    scalarField yRecent
-        (objectiveDerivatives_ - derivativesOld_, activeDesignVars_);
-    pivotFields(y_, yRecent);
+    // Sanity checks
+    if
+    (
+        (derivatives.size() != derivativesOld.size())
+     || (derivatives.size() != designVars_().getVars().size())
+    )
+    {
+        FatalErrorInFunction
+            << "Sizes of input derivatives and design variables do not match"
+            << exit(FatalError);
+    }
+
+    // Update list of y. Can only be done here since derivatives
+    // were not known at the end of the previous cycle
+    scalarField yRecent(derivatives - derivativesOld, activeDesignVars_);
     // Update list of s.
     // correction_ holds the previous correction
     scalarField sActive(correctionOld_, activeDesignVars_);
+    applyDamping(yRecent, sActive);
+
+    pivotFields(y_, yRecent);
     pivotFields(s_, sActive);
+}
 
+
+void Foam::LBFGS::applyDamping(scalarField& y, scalarField& s)
+{
+    const scalar sy(globalSum(s*y));
+    if (useSDamping_)
+    {
+        const scalarField Hy(invHessianVectorProduct(y, counter_ - 1));
+        const scalar yHy(globalSum(y*Hy));
+        scalar theta(1);
+        if (sy < 0.2*yHy)
+        {
+            WarningInFunction
+                << "y*s is below threshold. Using damped form" << nl
+                << "sy, yHy " << sy << " " << yHy << endl;
+
+            theta = 0.8*yHy/(yHy - sy);
+        }
+        s = theta*s + (1 - theta)*Hy;
+    }
+    else if (useYDamping_)
+    {
+        const scalarField Bs(HessianVectorProduct(s, counter_ - 1));
+        const scalar sBs(globalSum(s*Bs));
+        scalar theta(1);
+        if (sy < 0.2*sBs)
+        {
+            WarningInFunction
+                << "y*s is below threshold. Using damped form" << nl
+                << "sy, sBs " << sy << " " << sBs << endl;
+
+            theta = 0.8*sBs/(sBs - sy);
+        }
+        y = theta*y + (1 - theta)*Bs;
+    }
     DebugInfo
-        << "y fields" << nl << y_ << endl;
-    DebugInfo
-        << "s fields" << nl << s_ << endl;
+        << "Curvature index (sy) is " << sy << endl;
 }
 
 
-void Foam::LBFGS::steepestDescentUpdate()
+Foam::tmp<Foam::scalarField>
+Foam::LBFGS::invHessianVectorProduct(const scalarField& vector)
 {
-    Info<< "Using steepest descent to update design variables" << endl;
-    correction_ = -eta_*objectiveDerivatives_;
+    return invHessianVectorProduct(vector, counter_);
 }
 
 
-void Foam::LBFGS::LBFGSUpdate()
+Foam::tmp<Foam::scalarField>
+Foam::LBFGS::invHessianVectorProduct
+(
+    const scalarField& vector,
+    const label counter
+)
 {
-    // L-BFGS two loop recursion
-    //~~~~~~~~~~~~~~~~~~~~~~~~~~
-    label nSteps(min(counter_, nPrevSteps_));
-    label nLast(nSteps - 1);
-    scalarField q(objectiveDerivatives_, activeDesignVars_);
-    scalarField a(nSteps, Zero);
-    scalarField r(nSteps, Zero);
-    for (label i = nLast; i > -1; --i)
+    // Sanity checks
+    tmp<scalarField> tq(tmp<scalarField>::New(activeDesignVars_.size(), Zero));
+    scalarField& q = tq.ref();
+    if (vector.size() == designVars_().getVars().size())
     {
-        r[i] = 1./globalSum(y_[i]*s_[i]);
-        a[i] = r[i]*globalSum(s_[i]*q);
-        q -= a[i]*y_[i];
+        q.map(vector, activeDesignVars_);
     }
-
-    scalar gamma =
-        globalSum(y_[nLast]*s_[nLast])/globalSum(y_[nLast]*y_[nLast]);
-    q *= gamma;
-
-    scalarField b(activeDesignVars_.size(), Zero);
-    for (label i = 0; i < nSteps; ++i)
+    else if (vector.size() == activeDesignVars_.size())
     {
-        b = r[i]*globalSum(y_[i]*q);
-        q += s_[i]*(a[i] -b);
+        q = vector;
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Size of input vector is equal to neither the number of "
+            << " design variabes nor that of the active design variables"
+            << exit(FatalError);
     }
 
-    // Update correction
-    forAll(activeDesignVars_, varI)
+    if (counter != 0)
     {
-        correction_[activeDesignVars_[varI]] = -etaHessian_*q[varI];
+        // L-BFGS two loop recursion
+        //~~~~~~~~~~~~~~~~~~~~~~~~~~
+        label nSteps(min(counter, nPrevSteps_));
+        label nLast(nSteps - 1);
+        scalarField a(nSteps, 0.);
+        scalarField r(nSteps, 0.);
+        for (label i = nLast; i > -1; --i)
+        {
+          //Info << "Y " << y_[i] << endl;
+          //Info << "S " << s_[i] << endl;
+            r[i] = 1./globalSum(y_[i]*s_[i]);
+            a[i] = r[i]*globalSum(s_[i]*q);
+            q -= a[i]*y_[i];
+        }
+
+        scalar gamma =
+            globalSum(y_[nLast]*s_[nLast])/globalSum(y_[nLast]*y_[nLast]);
+        q *= gamma;
+
+        scalarField b(activeDesignVars_.size(), Zero);
+        for (label i = 0; i < nSteps; ++i)
+        {
+            b = r[i]*globalSum(y_[i]*q);
+            q += s_[i]*(a[i] - b);
+        }
     }
+
+    return tq;
 }
 
 
-void Foam::LBFGS::update()
+Foam::tmp<Foam::scalarField>
+Foam::LBFGS::HessianVectorProduct(const scalarField& vector)
 {
-    // In the first few iterations, use steepest descent but update the Hessian
-    // matrix
-    if (counter_ < nSteepestDescent_)
+    return HessianVectorProduct(vector, counter_);
+}
+
+
+Foam::tmp<Foam::scalarField>
+Foam::LBFGS::HessianVectorProduct
+(
+    const scalarField& vector,
+    const label counter
+)
+{
+    // Sanity checks
+    tmp<scalarField> tq(tmp<scalarField>::New(activeDesignVars_.size(), Zero));
+    scalarField& q = tq.ref();
+
+    scalarField source;
+    if (vector.size() == designVars_().getVars().size())
     {
-        steepestDescentUpdate();
+        source = scalarField(vector, activeDesignVars_);
+    }
+    else if (vector.size() == activeDesignVars_.size())
+    {
+        source = vector;
     }
-    // else use LBFGS formula to update the design variables
     else
     {
-        LBFGSUpdate();
+        FatalErrorInFunction
+            << "Size of input vector is equal to neither the number of "
+            << " design variabes nor that of the active design variables"
+            << exit(FatalError);
     }
 
-    // Store fields for the next iteration
-    derivativesOld_ = objectiveDerivatives_;
-    correctionOld_ = correction_;
+    if (counter != 0)
+    {
+        const label nSteps(min(counter, nPrevSteps_));
+        const label nLast(nSteps - 1);
+        const scalar delta =
+            globalSum(y_[nLast]*y_[nLast])/globalSum(y_[nLast]*s_[nLast]);
+
+        // Product of the last matrix on the right with the input vector
+        scalarField SKsource(2*nSteps, Zero);
+        for(label i = 0; i < nSteps; ++i)
+        {
+            SKsource[i] = delta*globalSum(s_[i]*source);
+            SKsource[i + nSteps] = globalSum(y_[i]*source);
+        }
+
+        // Form the middle matrix to be inverted
+        SquareMatrix<scalar> M(2*nSteps, 2*nSteps, Zero);
+        for (label i = 0; i < nSteps; ++i)
+        {
+            // Lower diagonal part
+            M[nSteps + i][nSteps + i] = - globalSum(s_[i]*y_[i]);
+            // Upper left part
+            for (label j = 0; j < nSteps; ++j)
+            {
+                M[i][j] = delta*globalSum(s_[i]*s_[j]);
+            }
+        }
+
+        // Upper right and lower left parts
+        for (label j = 0; j < nSteps; ++j)
+        {
+            for (label i = j + 1; i < nSteps; ++i)
+            {
+                scalar value = globalSum(s_[i]*y_[j]);
+                M[i][j + nSteps] = value;
+                M[j + nSteps][i] = value;
+            }
+        }
+        SquareMatrix<scalar> invM(inv(M));
+
+        // Product of the inverted middle matrix with the right vector
+        scalarField invMSource(rightMult(invM, SKsource));
+
+        // Left vector multiplication with the rest of contributions
+        // vag: parallel comms
+        forAll(q, i)
+        {
+            for (label j = 0; j < nSteps; ++j)
+            {
+                q[i] -=
+                    delta*s_[j][i]*invMSource[j]
+                  + y_[j][i]*invMSource[j + nSteps];
+            }
+        }
+
+        q += delta*source;
+    }
+    else
+    {
+        q = source;
+    }
+
+    return tq;
 }
 
 
-void Foam::LBFGS::readFromDict()
+Foam::tmp<Foam::scalarField> Foam::LBFGS::HessianDiag()
 {
-    if (optMethodIODict_.headerOk())
+    // Sanity checks
+    const label n(activeDesignVars_.size());
+    tmp<scalarField> tdiag(tmp<scalarField>::New(n, 1));
+    scalarField& diag = tdiag.ref();
+
+    if (counter_ != 0)
     {
-        optMethodIODict_.readEntry("y", y_);
-        optMethodIODict_.readEntry("s", s_);
-        optMethodIODict_.readEntry("derivativesOld", derivativesOld_);
-        optMethodIODict_.readEntry("counter", counter_);
-        optMethodIODict_.readEntry("eta", eta_);
-        optMethodIODict_.readEntry("correctionOld", correctionOld_);
+        const label nSteps(min(counter_, nPrevSteps_));
+        const label nLast(nSteps - 1);
+        const scalar delta =
+            globalSum(y_[nLast]*y_[nLast])/globalSum(y_[nLast]*s_[nLast]);
+        diag *= delta;
+
+        // Form the middle matrix to be inverted
+        SquareMatrix<scalar> M(2*nSteps, 2*nSteps, Zero);
+        for (label i = 0; i < nSteps; ++i)
+        {
+            // Lower diagonal part
+            M[nSteps + i][nSteps + i] = - globalSum(s_[i]*y_[i]);
+            // Upper left part
+            for (label j = 0; j < nSteps; ++j)
+            {
+                M[i][j] = delta*globalSum(s_[i]*s_[j]);
+            }
+        }
+
+        // Upper right and lower left parts
+        for (label j = 0; j < nSteps; ++j)
+        {
+            for (label i = j + 1; i < nSteps; ++i)
+            {
+                scalar value = globalSum(s_[i]*y_[j]);
+                M[i][j + nSteps] = value;
+                M[j + nSteps][i] = value;
+            }
+        }
 
-        correction_ = scalarField(correctionOld_.size(), Zero);
+        // Invert the matrix
+        SquareMatrix<scalar> invM(inv(M));
 
-        if (activeDesignVars_.empty())
+        // Product of the inverse of the middle matrix with the right vector
+        List<scalarField> MR(2*nSteps, scalarField(n, Zero));
+        for(label k = 0; k < n; ++k)
         {
-            activeDesignVars_ = identity(derivativesOld_.size());
+            for(label i = 0; i < 2*nSteps; ++i)
+            {
+                for(label j = 0; j < nSteps; ++j)
+                {
+                    MR[i][k] +=
+                        invM[i][j]*delta*s_[j][k]
+                      + invM[i][j + nSteps]*y_[j][k];
+                }
+            }
+        }
+
+        // Part of the Hessian diagonal computed by the multiplication
+        // of the above matrix with the left matrix of the recursive Hessian
+        // reconstruction
+        for(label k = 0; k < n; ++k)
+        {
+            for(label j = 0; j < nSteps; ++j)
+            {
+                diag[k] -=
+                    delta*s_[j][k]*MR[j][k] + y_[j][k]*MR[j + nSteps][k];
+            }
         }
     }
+
+    return tdiag;
 }
 
 
-// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+Foam::tmp<Foam::scalarField>
+Foam::LBFGS::SR1HessianVectorProduct(const scalarField& vector)
+{
+    return SR1HessianVectorProduct(vector, counter_);
+}
 
-Foam::LBFGS::LBFGS
+
+Foam::tmp<Foam::scalarField>
+Foam::LBFGS::SR1HessianVectorProduct
 (
-    const fvMesh& mesh,
-    const dictionary& dict
+    const scalarField& vector,
+    const label counter
 )
-:
-    updateMethod(mesh, dict),
+{
+    // Sanity checks
+    tmp<scalarField> tq(tmp<scalarField>::New(activeDesignVars_.size(), Zero));
+    scalarField& q = tq.ref();
 
-    // Construct null matrix since we dont know the dimension yet
-    etaHessian_
-    (
-        coeffsDict().getOrDefault<scalar>("etaHessian", 1)
-    ),
-    nSteepestDescent_
-    (
-        coeffsDict().getOrDefault<label>("nSteepestDescent", 1)
-    ),
-    activeDesignVars_(0),
-    nPrevSteps_
-    (
-        coeffsDict().getOrDefault<label>("nPrevSteps", 10)
-    ),
-    y_(nPrevSteps_),
-    s_(nPrevSteps_),
-    derivativesOld_(0),
-    counter_(Zero)
+    scalarField source;
+    if (vector.size() == designVars_().getVars().size())
+    {
+        source = scalarField(vector, activeDesignVars_);
+    }
+    else if (vector.size() == activeDesignVars_.size())
+    {
+        source = vector;
+    }
+    else
+    {
+        FatalErrorInFunction
+            << "Size of input vector is equal to neither the number of "
+            << " design variabes nor that of the active design variables"
+            << exit(FatalError);
+    }
+
+    if (counter != 0)
+    {
+        const label nSteps(min(counter, nPrevSteps_));
+        const label nLast(nSteps - 1);
+        const scalar delta =
+            globalSum(y_[nLast]*y_[nLast])/globalSum(y_[nLast]*s_[nLast]);
+
+        // Product of the last matrix on the right with the input vector
+        scalarField YBSsource(nSteps, Zero);
+        for(label i = 0; i < nSteps; ++i)
+        {
+            YBSsource[i] = globalSum((y_[i] - delta*s_[i])*source);
+        }
+
+        // Form the middle matrix to be inverted
+        SquareMatrix<scalar> M(nSteps, nSteps, Zero);
+        for (label i = 0; i < nSteps; ++i)
+        {
+            // D part
+            M[i][i] += globalSum(s_[i]*y_[i]);
+            // (S^T)BS part
+            for (label j = 0; j < nSteps; ++j)
+            {
+                M[i][j] -= delta*globalSum(s_[i]*s_[j]);
+            }
+        }
+
+        // Upper right and lower left parts
+        for (label j = 0; j < nSteps; ++j)
+        {
+            for (label i = j + 1; i < nSteps; ++i)
+            {
+                scalar value = globalSum(s_[i]*y_[j]);
+                M[i][j] += value;
+                M[j][i] += value;
+            }
+        }
+        SquareMatrix<scalar> invM(inv(M));
+
+        // Product of the inverted middle matrix with the right vector
+        scalarField invMSource(rightMult(invM, YBSsource));
+
+        // Left vector multiplication with the rest of contributions
+        // vag: parallel comms
+        forAll(q, i)
+        {
+            for (label j = 0; j < nSteps; ++j)
+            {
+                q[i] += (y_[j][i] - delta*s_[j][i])*invMSource[j];
+            }
+        }
+
+        q += delta*source;
+    }
+    else
+    {
+        q = source;
+    }
+
+    return tq;
+}
+
+
+Foam::tmp<Foam::scalarField> Foam::LBFGS::SR1HessianDiag()
 {
-    if
-    (
-        !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
-    )
+    // Sanity checks
+    const label n(activeDesignVars_.size());
+    tmp<scalarField> tdiag(tmp<scalarField>::New(n, 1));
+    scalarField& diag = tdiag.ref();
+
+    if (counter_ != 0)
     {
-        // If not, all available design variables will be used. Number is not
-        // know at the moment
-        Info<< "\t Did not find explicit definition of active design variables. "
-            << "Treating all available ones as active " << endl;
+        const label nSteps(min(counter_, nPrevSteps_));
+        const label nLast(nSteps - 1);
+        const scalar delta =
+            globalSum(y_[nLast]*y_[nLast])/globalSum(y_[nLast]*s_[nLast]);
+        diag *= delta;
+
+        // Form the middle matrix to be inverted
+        SquareMatrix<scalar> M(nSteps, nSteps, Zero);
+        for (label i = 0; i < nSteps; ++i)
+        {
+            // D part
+            M[i][i] += globalSum(s_[i]*y_[i]);
+            // (S^T)BS part
+            for (label j = 0; j < nSteps; ++j)
+            {
+                M[i][j] -= delta*globalSum(s_[i]*s_[j]);
+            }
+        }
+
+        // Upper right and lower left parts
+        for (label j = 0; j < nSteps; ++j)
+        {
+            for (label i = j + 1; i < nSteps; ++i)
+            {
+                scalar value = globalSum(s_[i]*y_[j]);
+                M[i][j] += value;
+                M[j][i] += value;
+            }
+        }
+        SquareMatrix<scalar> invM(inv(M));
+
+        // Product of the inverse of the middle matrix with the right vector
+        List<scalarField> MR(nSteps, scalarField(n, Zero));
+        for(label k = 0; k < n; ++k)
+        {
+            for(label i = 0; i < nSteps; ++i)
+            {
+                for(label j = 0; j < nSteps; ++j)
+                {
+                    MR[i][k] += invM[i][j]*(y_[j][k] - delta*s_[j][k]);
+                }
+            }
+        }
+
+        // Part of the Hessian diagonal computed by the multiplication
+        // of the above matrix with the left matrix of the recursive Hessian
+        // reconstruction
+        for(label k = 0; k < n; ++k)
+        {
+            for(label j = 0; j < nSteps; ++j)
+            {
+                diag[k] += (y_[j][k] - delta*s_[j][k])*MR[j][k];
+            }
+        }
     }
 
-    // Read old Hessian, correction and derivatives, if present
-    readFromDict();
+    return tdiag;
 }
 
 
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+void Foam::LBFGS::updateHessian()
+{
+    updateVectors(objectiveDerivatives_, derivativesOld_);
+}
+
 
-void Foam::LBFGS::computeCorrection()
+void Foam::LBFGS::update()
 {
-    if (counter_ == 0)
+    // In the first few iterations, use steepest descent but update the Hessian
+    // matrix
+    if (counter_ < nSteepestDescent_)
     {
-        allocateMatrices();
+        Info<< "Using steepest descent to update design variables" << endl;
+        for (const label varI : activeDesignVars_)
+        {
+            correction_[varI] = -eta_*objectiveDerivatives_[varI];
+        }
     }
+    // else use LBFGS formula to update the design variables
     else
     {
-        updateVectors();
+        scalarField q(invHessianVectorProduct(objectiveDerivatives_));
+        forAll(activeDesignVars_, varI)
+        {
+            correction_[activeDesignVars_[varI]] = -etaHessian_*q[varI];
+        }
     }
 
-    update();
-    ++counter_;
+    // Store fields for the next iteration
+    derivativesOld_ = objectiveDerivatives_;
+    correctionOld_ = correction_;
 }
 
 
-void Foam::LBFGS::updateOldCorrection(const scalarField& oldCorrection)
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::LBFGS::LBFGS
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
+)
+:
+    quasiNewton(mesh, dict, designVars, nConstraints, type),
+    nPrevSteps_(coeffsDict(type).getOrDefault<label>("nPrevSteps", 10)),
+    y_(nPrevSteps_),
+    s_(nPrevSteps_),
+    useSDamping_(coeffsDict(type).getOrDefault<bool>("useSDamping", false)),
+    useYDamping_(coeffsDict(type).getOrDefault<bool>("useYDamping", false))
 {
-    updateMethod::updateOldCorrection(oldCorrection);
-    correctionOld_ = oldCorrection;
+    // Allocate the correct sizes for y and s
+    allocateVectors();
 }
 
 
-void Foam::LBFGS::write()
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::LBFGS::writeData(Ostream& os) const
 {
-    optMethodIODict_.add<PtrList<scalarField>>("y", y_, true);
-    optMethodIODict_.add<PtrList<scalarField>>("s", s_, true);
-    optMethodIODict_.add<scalarField>("derivativesOld", derivativesOld_, true);
-    optMethodIODict_.add<scalarField>("correctionOld", correctionOld_, true);
-    optMethodIODict_.add<label>("counter", counter_, true);
+    // Write each component of y and s as a separate field so as to allow for
+    // reading them also in binary, since PtrList does not support this
+    forAll(y_, i)
+    {
+        y_[i].writeEntry(word("y" + Foam::name(i)), os);
+        s_[i].writeEntry(word("s" + Foam::name(i)), os);
+    }
 
-    updateMethod::write();
+    return quasiNewton::writeData(os);
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.H
index 7bffc387e86c7ba18e015eedd3ab2bcb1e99a321..2f6db818889879853d589083fee07a28e2d0db82 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/LBFGS/LBFGS.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -41,8 +41,7 @@ SourceFiles
 #ifndef LBFGS_H
 #define LBFGS_H
 
-#include "updateMethod.H"
-#include "scalarMatrices.H"
+#include "quasiNewton.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -50,12 +49,12 @@ namespace Foam
 {
 
 /*---------------------------------------------------------------------------*\
-                             Class LBFGS Declaration
+                            Class LBFGS Declaration
 \*---------------------------------------------------------------------------*/
 
 class LBFGS
 :
-    public updateMethod
+    public quasiNewton
 {
 private:
 
@@ -72,15 +71,6 @@ protected:
 
     // Protected data
 
-        //- Step for the Newton method
-        scalar etaHessian_;
-
-        //- Number of first steepest descent steps
-        label nSteepestDescent_;
-
-        //- Map to active design variables
-        labelList activeDesignVars_;
-
         //- Number of old corrections and grad differences kept
         label nPrevSteps_;
 
@@ -90,36 +80,107 @@ protected:
         //- The previous correction. Holds nPrevSteps_ fields
         PtrList<scalarField> s_;
 
-        //- The previous derivatives
-        scalarField derivativesOld_;
+        //- Use damping for s to ensure positive-definitiveness
+        bool useSDamping_;
 
-        //- The previous correction
-        scalarField correctionOld_;
+        //- Use damping for s to ensure positive-definitiveness
+        bool useYDamping_;
 
-        //- Optimisation cycle counter
-        label counter_;
+
+    // Protected Member Functions
 
         //- Allocate matrices in the first optimisation cycle
-        void allocateMatrices();
+        void allocateVectors();
 
         //- Move pointers in PtrList to the left and replace last element with
         //- given field
         void pivotFields(PtrList<scalarField>& list, const scalarField& f);
 
         //- Update y and s vectors
-        void updateVectors();
+        void updateVectors
+        (
+            const scalarField& derivatives,
+            const scalarField& derivativesOld
+        );
+
+        //- Use the damped version of s to ensure positive-definitiveness
+        //  Usefull in conjunction with SQP
+        void applyDamping
+        (
+            scalarField& y,
+            scalarField& s
+        );
+
+        //- Update the Hessian matrix by updating the base vectors
+        virtual void updateHessian();
 
         //- Update design variables
-        void update();
-
-        //- Update based on steepest descent
-        void steepestDescentUpdate();
-
-        //- Update based on LBFGS
-        void LBFGSUpdate();
-
-        //- Read old info from dict
-        void readFromDict();
+        virtual void update();
+
+        //- Compute the inverse Hessian - vector product
+        //  Input should have the size of all design variables or the active
+        //  ones, output is the size of the active design variables
+        virtual tmp<scalarField> invHessianVectorProduct
+        (
+            const scalarField& vector
+        );
+
+        //- Same as previous one, but with an explicit counter provided
+        tmp<scalarField> invHessianVectorProduct
+        (
+            const scalarField& vector,
+            const label counter
+        );
+
+        //- Compute the Hessian - vector product
+        //  Requires the solution of a system of equations twice the size of
+        //  the bases. This should be OK since the latter is small.
+        //  Input should have the size of all design variables or the active
+        //  ones, output is the size of the active design variables
+        virtual tmp<scalarField> HessianVectorProduct
+        (
+            const scalarField& vector
+        );
+
+        //- Same as previous one, but with an explicit counter provided
+        tmp<scalarField> HessianVectorProduct
+        (
+            const scalarField& vector,
+            const label counter
+        );
+
+        //- Return the diagonal of the Hessian.
+        //  Requires the solution of a system of equations twice the size of
+        //  the bases. This should be OK since the latter is small.
+        //  Useful for preconditioning
+        tmp<scalarField> HessianDiag();
+
+
+        // Similar functions, but using the SR1 formula instead of BFGS.
+        // Should become a separate class at some point
+
+            //- Compute the Hessian - vector product
+            //  Requires the solution of a system of equations twice the size
+            //  of the bases. This should be OK since the latter is small.
+            //  Input should have the size of all design variables or the
+            //  active ones, output is the size of the active design variables
+            virtual tmp<scalarField> SR1HessianVectorProduct
+            (
+                const scalarField& vector
+            );
+
+            //- Same as previous one, but with an explicit counter provided
+            tmp<scalarField> SR1HessianVectorProduct
+            (
+                const scalarField& vector,
+                const label counter
+            );
+
+            //- Return the diagonal of the Hessian.
+            //  Requires the solution of a system of equations twice the size
+            //  of the bases. This should be OK since the latter is small.
+            //  Useful for preconditioning
+            tmp<scalarField> SR1HessianDiag();
 
 
 public:
@@ -131,7 +192,14 @@ public:
     // Constructors
 
         //- Construct from components
-        LBFGS(const fvMesh& mesh, const dictionary& dict);
+        LBFGS
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
@@ -140,15 +208,8 @@ public:
 
     // Member Functions
 
-       //- Compute design variables correction
-       void computeCorrection();
-
-       //- Update old correction. Useful for quasi-Newton methods coupled with
-       //- line search
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
-
-       //- Write old info to dict
-       virtual void write();
+       //- Write useful quantities to files
+       virtual bool writeData(Ostream& os) const;
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.C
index 7626776fed34873a30faf807ec2e90db5a7c45af..03562ad4a68b148429441d78d43cac634fbb9a05 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -53,38 +53,12 @@ namespace Foam
 
 // * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
 
-void Foam::SQP::allocateMatrices()
-{
-    // Set active design variables, if necessary
-    if (activeDesignVars_.empty())
-    {
-        activeDesignVars_ = identity(objectiveDerivatives_.size());
-    }
-
-    // Set previous Hessian to be a diagonal matrix
-    SquareMatrix<scalar> temp(activeDesignVars_.size(), I);
-
-    // Allocate correct size and content to Hessian matrices
-    // Has a max. capability of approximately 34000 variables.
-    HessianOld_ = temp;
-    Hessian_ = temp;
-
-    // Set size of Lagrange multipliers
-    lamdas_.setSize(constraintDerivatives_.size());
-    lamdas_ = Zero;
-
-    // Set corerction size
-    correction_.setSize(objectiveDerivatives_.size());
-    correction_ = Zero;
-}
-
-
 void Foam::SQP::updateHessian()
 {
     // Vectors needed to construct the (inverse) Hessian matrix
     scalarField y(activeDesignVars_.size(), Zero);
     scalarField s(activeDesignVars_.size(), Zero);
-    scalarField LagrangianDerivativesOld = objectiveDerivativesOld_;
+    scalarField LagrangianDerivativesOld = derivativesOld_;
     forAll(constraintDerivatives_, cI)
     {
         LagrangianDerivatives_ -= lamdas_[cI] * constraintDerivatives_[cI];
@@ -102,7 +76,7 @@ void Foam::SQP::updateHessian()
             Info<< "Scaling Hessian with factor " << scaleFactor << endl;
             forAll(activeDesignVars_, varI)
             {
-                HessianOld_[varI][varI] /= scaleFactor;
+                Hessian_()[varI][varI] /= scaleFactor;
             }
         }
         else
@@ -112,7 +86,7 @@ void Foam::SQP::updateHessian()
                 << endl;
         }
     }
-    scalar sBs = globalSum(leftMult(s, HessianOld_)*s);
+    scalar sBs = globalSum(leftMult(s, Hessian_())*s);
 
     // Check curvature condition
     scalar theta(1);
@@ -122,28 +96,28 @@ void Foam::SQP::updateHessian()
             << " y*s is below threshold. Using damped form" << endl;
         theta = (1 - dumpingThreshold_)*sBs/(sBs - ys);
     }
-    scalarField r(theta*y + (scalar(1) - theta)*rightMult(HessianOld_, s));
+    scalarField r(theta*y + (scalar(1) - theta)*rightMult(Hessian_(), s));
     DebugInfo
         << "Unmodified Hessian curvature index " << ys << endl;
     DebugInfo
         << "Modified Hessian curvature index " << globalSum(r*s) << endl;
 
     // Update the Hessian
-    Hessian_ =
-        HessianOld_
-      - outerProd(rightMult(HessianOld_, s), leftMult(s/sBs, HessianOld_))
+    Hessian_() +=
+      - outerProd(rightMult(Hessian_(), s), leftMult(s/sBs, Hessian_()))
       + outerProd(r, r/globalSum(s*r));
 }
 
 
-void Foam::SQP::computeLagrangeMultipliersAndCorrect()
+void Foam::SQP::update()
 {
-    SquareMatrix<scalar> HessianInv = inv(Hessian_);  //also denoted below as W
+    // Also denoted below as W
+    SquareMatrix<scalar> HessianInv = inv(Hessian_());
     if (debug > 1)
     {
-        Info<< "Hessian " << Hessian_ << endl;
+        Info<< "Hessian " << Hessian_() << endl;
         Info<< "HessianInv " << HessianInv << endl;
-        label n = Hessian_.n();
+        label n = Hessian_().n();
         SquareMatrix<scalar> test(n, Zero);
         for (label k = 0; k < n; k++)
         {
@@ -152,7 +126,7 @@ void Foam::SQP::computeLagrangeMultipliersAndCorrect()
                 scalar elem(Zero);
                 for (label i = 0; i < n; i++)
                 {
-                    elem += Hessian_[k][i] * HessianInv[i][l];
+                    elem += Hessian_()[k][i] * HessianInv[i][l];
                 }
                 test[k][l]=elem;
             }
@@ -229,7 +203,7 @@ void Foam::SQP::computeLagrangeMultipliersAndCorrect()
 
 void Foam::SQP::storeOldFields()
 {
-    objectiveDerivativesOld_ = objectiveDerivatives_;
+    derivativesOld_ = objectiveDerivatives_;
     if (constraintDerivativesOld_.empty())
     {
         constraintDerivativesOld_.setSize(constraintDerivatives_.size());
@@ -239,99 +213,38 @@ void Foam::SQP::storeOldFields()
         constraintDerivativesOld_[cI] = constraintDerivatives_[cI];
     }
     correctionOld_ = correction_;
-    HessianOld_ = Hessian_;
 }
 
 
-void Foam::SQP::readFromDict()
+Foam::scalar Foam::SQP::meritFunctionConstraintPart() const
 {
-    if (optMethodIODict_.headerOk())
-    {
-        optMethodIODict_.readEntry("Hessian", Hessian_);
-        optMethodIODict_.readEntry("HessianOld", HessianOld_);
-        optMethodIODict_.readEntry
-        (
-            "objectiveDerivativesOld",
-            objectiveDerivativesOld_
-        );
-        optMethodIODict_.readEntry
-        (
-            "constraintDerivativesOld",
-            constraintDerivativesOld_
-        );
-        optMethodIODict_.readEntry("correctionOld", correctionOld_);
-        optMethodIODict_.readEntry("lamdas", lamdas_);
-        optMethodIODict_.readEntry("counter", counter_);
-        optMethodIODict_.readEntry("eta", eta_);
-
-        correction_ = scalarField(correctionOld_.size(), Zero);
-
-        if (activeDesignVars_.empty())
-        {
-            activeDesignVars_ = identity(correction_.size());
-        }
-    }
+    // Assumes that all constraints are known by all processors
+    // What about constraints directly imposed on distributed design variables?
+    // These should be met in each iteration of the algorithm, so,
+    // most probably, there is no problem
+    return sum(mag(cValues_));
 }
 
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::SQP::SQP(const fvMesh& mesh, const dictionary& dict)
+Foam::SQP::SQP
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
+)
 :
-    constrainedOptimisationMethod(mesh, dict),
-
-    etaHessian_
-    (
-        coeffsDict().getOrDefault<scalar>("etaHessian", 1)
-    ),
-    activeDesignVars_(0),
-    scaleFirstHessian_
-    (
-        coeffsDict().getOrDefault<bool>("scaleFirstHessian", false)
-    ),
+    quasiNewton(mesh, dict, designVars, nConstraints, type),
+    SQPBase(mesh, dict, designVars, *this, type),
     dumpingThreshold_
     (
-        coeffsDict().getOrDefault<scalar>("dumpingThreshold", 0.2)
-    ),
-    LagrangianDerivatives_(0),
-    Hessian_(),  // construct null matrix since we dont know the dimension yet
-    HessianOld_(),
-    objectiveDerivativesOld_(0),
-    constraintDerivativesOld_(0),
-    correctionOld_(0),
-    lamdas_(0),
-    counter_(0),
-    objFunctionFolder_
-    (
-        mesh_.time().globalPath()/"optimisation"/"objective"/
-        mesh_.time().timeName()
-    ),
-    meritFunctionFile_(nullptr),
-    mu_(Zero),
-    delta_
-    (
-        coeffsDict().getOrDefault<scalar>("delta", 0.1)
+        coeffsDict(type).getOrDefault<scalar>("dumpingThreshold", 0.2)
     )
 {
-    if
-    (
-        !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
-    )
-    {
-        // If not, all available design variables will be used. Number is not
-        // know at the moment
-        Info<< "\t Did not find explicit definition of active design "
-            << "variables. Treating all available ones as active " << endl;
-    }
-
-    // Create folder to merit function
-    if (Pstream::master())
-    {
-        mkDir(objFunctionFolder_);
-    }
-
-    // Read old hessian, correction and derivatives, if present
-    readFromDict();
+    allocateHessian();
 }
 
 
@@ -339,26 +252,11 @@ Foam::SQP::SQP(const fvMesh& mesh, const dictionary& dict)
 
 void Foam::SQP::computeCorrection()
 {
-    // Allocate correct sizes in first update
-    if (counter_ == 0)
-    {
-        allocateMatrices();
-    }
-
-    // The first iteration uses a unitary Hessian. No need to update
     LagrangianDerivatives_ = objectiveDerivatives_;
-    if (counter_ != 0)
-    {
-        updateHessian();
-    }
-
-    // Update lamdas and desing vars
-    computeLagrangeMultipliersAndCorrect();
+    quasiNewton::computeCorrection();
 
     // Store fields for the next iteration and write them to file
     storeOldFields();
-
-    counter_++;
 }
 
 
@@ -389,83 +287,15 @@ Foam::scalar Foam::SQP::meritFunctionDirectionalDerivative()
 }
 
 
-void Foam::SQP::updateOldCorrection(const scalarField& oldCorrection)
+bool Foam::SQP::writeData(Ostream& os) const
 {
-    updateMethod::updateOldCorrection(oldCorrection);
-    correctionOld_ = oldCorrection;
+    return quasiNewton::writeData(os) && SQPBase::addToFile(os);
 }
 
 
-void Foam::SQP::write()
+bool Foam::SQP::writeAuxiliaryData()
 {
-    // Write updateMethod dictionary
-    optMethodIODict_.add<SquareMatrix<scalar>>("Hessian", Hessian_, true);
-    optMethodIODict_.add<SquareMatrix<scalar>>("HessianOld", HessianOld_, true);
-    optMethodIODict_.
-        add<scalarField>
-        (
-            "objectiveDerivativesOld", objectiveDerivativesOld_, true
-        );
-    optMethodIODict_.
-        add<List<scalarField>>
-        (
-            "constraintDerivativesOld", constraintDerivativesOld_, true
-        );
-    optMethodIODict_.add<scalarField>("correctionOld", correctionOld_, true);
-    optMethodIODict_.add<scalarField>("lamdas", lamdas_, true);
-    optMethodIODict_.add<label>("counter", counter_, true);
-
-    updateMethod::write();
-
-    // Write merit function
-    scalar constraintPart = sum(mag(cValues_));
-    scalar merit = objectiveValue_ + mu_*constraintPart;
-    if (Pstream::master())
-    {
-        unsigned int width = IOstream::defaultPrecision() + 6;
-        unsigned int constraintsSize = lamdas_.size();
-        constraintsSize = constraintsSize*(width + 1) + 2;
-
-        // Open file and write header
-        if (!meritFunctionFile_)
-        {
-            meritFunctionFile_.reset
-            (
-                new OFstream(objFunctionFolder_/word("meritFunction"))
-            );
-
-            meritFunctionFile_()
-                << setw(1) << "#" << " "
-                << setw(width) << "merit" << " "
-                << setw(width) << "J" << " "
-                << setw(constraintsSize) << "lamdas" << " "
-                << setw(constraintsSize) << "constraints" << " "
-                << setw(width) << "mu" << " "
-                << setw(width) << "constraintContr" << endl;
-
-        }
-
-        meritFunctionFile_()
-            << setw(1) << mesh_.time().value() -1 << " "
-            << setw(width) << merit << " "
-            << setw(width) << objectiveValue_ << " "
-            << setw(1) << "(";
-
-        forAll(lamdas_, cI)
-        {
-            meritFunctionFile_()
-                << setw(width) << lamdas_[cI] << setw(1) << " ";
-        }
-        meritFunctionFile_() << setw(3) << ")(";
-        forAll(cValues_, cI)
-        {
-            meritFunctionFile_()
-                << setw(width) << cValues_[cI] << setw(1) << " ";
-        }
-        meritFunctionFile_() << setw(2) << ") ";
-        meritFunctionFile_() << setw(width) << mu_ << " ";
-        meritFunctionFile_() << setw(width) << constraintPart << endl;
-    }
+    return SQPBase::writeMeritFunction(*this);
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.H
index 191770765c0791bdbdf7f38897972a2f29c2d3d7..bc491a6bb50149078282dbb98be35712063590aa 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQP/SQP.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -40,8 +40,8 @@ SourceFiles
 #ifndef SQP_H
 #define SQP_H
 
-#include "constrainedOptimisationMethod.H"
-#include "OFstream.H"
+#include "quasiNewton.H"
+#include "SQPBase.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,61 +54,16 @@ namespace Foam
 
 class SQP
 :
-    public constrainedOptimisationMethod
+    public quasiNewton,
+    public SQPBase
 {
 protected:
 
     // Protected data
 
-        //- Step for the Newton method
-        scalar etaHessian_;
-
-        //- Map to active design variables
-        labelList activeDesignVars_;
-
-        //- Scale the initial unitary Hessian approximation
-        bool scaleFirstHessian_;
-
         //- Curvature threshold
         scalar dumpingThreshold_;
 
-        //- Derivatives of the Lagrangian function
-        scalarField LagrangianDerivatives_;
-
-        //- The Hessian inverse. Should have the size of the active design
-        //- variables
-        SquareMatrix<scalar> Hessian_;
-
-        //- The previous Hessian inverse
-        SquareMatrix<scalar> HessianOld_;
-
-        //- The previous objective derivatives
-        scalarField objectiveDerivativesOld_;
-
-        //- The previous constraint derivatives
-        List<scalarField> constraintDerivativesOld_;
-
-        //- The previous correction
-        scalarField correctionOld_;
-
-        //- Lagrange multipliers
-        scalarField lamdas_;
-
-        //- Optimisation cycle count
-        label counter_;
-
-        //- Name of the objective folder
-        fileName objFunctionFolder_;
-
-        //- File including the l1 merit function
-        autoPtr<OFstream> meritFunctionFile_;
-
-        //- Penalty value for the merit function
-        scalar mu_;
-
-        //- Safety factor
-        scalar delta_;
-
 
 private:
 
@@ -120,24 +75,17 @@ private:
         //- No copy assignment
         void operator=(const SQP&) = delete;
 
-        //- Make folder holding the Lagrangian file
-        void makeFolder();
-
-        //- Allocate fields and matrices when size of design variables
-        //- is known
-        void allocateMatrices();
-
         //- Update the Hessian matrix
-        void updateHessian();
+        virtual void updateHessian();
 
         //- Compute new lamdas and update correction
-        void computeLagrangeMultipliersAndCorrect();
+        virtual void update();
 
         //- Store old fields needed for the next iter
         void storeOldFields();
 
-        //- Read old values from dict, if present
-        void readFromDict();
+        //- Get the part the merit function that depends on the constraints
+        virtual scalar meritFunctionConstraintPart() const;
 
 
 public:
@@ -149,7 +97,14 @@ public:
     // Constructors
 
         //- Construct from components
-        SQP(const fvMesh& mesh, const dictionary& dict);
+        SQP
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
@@ -158,23 +113,22 @@ public:
 
     // Member Functions
 
-       //- Compute design variables correction
-       void computeCorrection();
+        //- Compute design variables correction
+        void computeCorrection();
 
-       //- Compute merit function. Could be different than the objective
-       //- in the presence of constraints
-       virtual scalar computeMeritFunction();
+        //- Compute merit function. Could be different than the objective
+        //- in the presence of constraints
+        virtual scalar computeMeritFunction();
 
-       //- Derivative of the merit function. Could be different than the
-       //- objective derivative in the presence of constraints
-       virtual scalar meritFunctionDirectionalDerivative();
+        //- Derivative of the merit function. Could be different than the
+        //- objective derivative in the presence of constraints
+        virtual scalar meritFunctionDirectionalDerivative();
 
-       //- Update old correction. Useful for quasi-Newton methods coupled with
-       //- line search
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
+        //- Write useful quantities to files
+        virtual bool writeData(Ostream& os) const;
 
-       //- Write useful quantities to files
-       virtual void write();
+        //- Write merit function information
+        virtual bool writeAuxiliaryData();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.C
new file mode 100644
index 0000000000000000000000000000000000000000..fdd380f9f5857892533248c1a2e6e82b4c1cccac
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.C
@@ -0,0 +1,178 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
+-------------------------------------------------------------------------------
+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 "SQPBase.H"
+#include "IOmanip.H"
+#include "updateMethod.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(SQPBase, 1);
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::SQPBase::SQPBase
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const updateMethod& UpdateMethod,
+    const word& type
+)
+:
+    constrainedOptimisationMethod
+    (
+        mesh,
+        dict,
+        designVars,
+        UpdateMethod.nConstraints(),
+        type
+    ),
+    LagrangianDerivatives_(designVars().getVars().size(), Zero),
+    constraintDerivativesOld_
+    (
+        UpdateMethod.nConstraints(),
+        scalarField(LagrangianDerivatives_.size(), Zero)
+    ),
+    lamdas_
+    (
+        UpdateMethod.found("lamdas") ?
+        scalarField("lamdas", UpdateMethod, UpdateMethod.nConstraints()) :
+        scalarField(UpdateMethod.nConstraints(), Zero)
+    ),
+    objFunctionFolder_
+    (
+        mesh.time().globalPath()/"optimisation"/"objective"/
+        mesh.time().timeName()
+    ),
+    meritFunctionFile_(nullptr),
+    mu_(Zero),
+    delta_
+    (
+        UpdateMethod.coeffsDict(type).getOrDefault<scalar>("delta", 0.1)
+    )
+{
+    // Read in old constraint derivatives if present
+    forAll(lamdas_, cI)
+    {
+        if (UpdateMethod.found("constraintDerivativesOld" + Foam::name(cI)))
+        {
+            constraintDerivativesOld_[cI] =
+                scalarField
+                (
+                    "constraintDerivativesOld" + Foam::name(cI),
+                    UpdateMethod,
+                    LagrangianDerivatives_.size()
+                );
+        }
+    }
+    // Create folder to merit function
+    if (Pstream::master())
+    {
+        mkDir(objFunctionFolder_);
+    }
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::SQPBase::addToFile(Ostream& os) const
+{
+    forAll(constraintDerivativesOld_, cI)
+    {
+        constraintDerivativesOld_[cI].
+            writeEntry("constraintDerivativesOld" + Foam::name(cI), os);
+    }
+    lamdas_.writeEntry("lamdas", os);
+
+    return true;
+}
+
+
+bool Foam::SQPBase::writeMeritFunction(const updateMethod& UpdateMethod)
+{
+    scalar objectivePart = UpdateMethod.getObjectiveValue();
+    scalar constraintPart = mu_*meritFunctionConstraintPart();
+    scalar merit = objectivePart + constraintPart;
+    const scalarField& cValues = UpdateMethod.getConstraintValues();
+    if (Pstream::master())
+    {
+        unsigned int width = IOstream::defaultPrecision() + 6;
+        unsigned int constraintsSize = lamdas_.size();
+        constraintsSize = constraintsSize*(width + 1) + 2;
+
+        // Open file and write header
+        if (!meritFunctionFile_)
+        {
+            meritFunctionFile_.reset
+            (
+                new OFstream(objFunctionFolder_/word("meritFunction"))
+            );
+
+            meritFunctionFile_()
+                << setw(1) << "#" << " "
+                << setw(width) << "merit" << " "
+                << setw(width) << "J" << " "
+                << setw(constraintsSize) << "lamdas" << " "
+                << setw(constraintsSize) << "constraints" << " "
+                << setw(width) << "mu" << " "
+                << setw(width) << "constraintContr" << endl;
+
+        }
+
+        meritFunctionFile_()
+            << setw(1) << UpdateMethod.getCycle() << " "
+            << setw(width) << merit << " "
+            << setw(width) << objectivePart << " "
+            << setw(1) << "(";
+
+        forAll(lamdas_, cI)
+        {
+            meritFunctionFile_()
+                << setw(width) << lamdas_[cI] << setw(1) << " ";
+        }
+        meritFunctionFile_() << setw(3) << ")(";
+        forAll(cValues, cI)
+        {
+            meritFunctionFile_()
+                << setw(width) << cValues[cI] << setw(1) << " ";
+        }
+        meritFunctionFile_() << setw(2) << ") ";
+        meritFunctionFile_() << setw(width) << mu_ << " ";
+        meritFunctionFile_() << setw(width) << constraintPart << endl;
+    }
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.H
similarity index 56%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.H
index a2e69d75b1732a64f5e120b2de260b755c6629c5..73595d3bcb52e298005dd35328693ad483eaf712 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.H
@@ -5,9 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -25,64 +24,68 @@ License
     You should have received a copy of the GNU General Public License
     along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
 
+
 Class
-    Foam::incompressible::FIBase
+    Foam::SQPBase
 
 Description
-    Base class for Field Integral-based sensitivity derivatives
+    Base class for Sequantial Quadratic Programming (SQP) methods
 
 SourceFiles
-    FIBase.C
+    SQPBase.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef FIBaseIncompressible_H
-#define FIBaseIncompressible_H
+#ifndef SQPBase_H
+#define SQPBase_H
 
-#include "adjointSensitivityIncompressible.H"
-#include "shapeSensitivitiesIncompressible.H"
-#include "adjointEikonalSolverIncompressible.H"
+#include "constrainedOptimisationMethod.H"
+#include "quasiNewton.H"
+#include "OFstream.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
 /*---------------------------------------------------------------------------*\
-                            Class FIBase Declaration
+                           Class SQPBase Declaration
 \*---------------------------------------------------------------------------*/
 
-class FIBase
+class SQPBase
 :
-    public shapeSensitivities
+    public constrainedOptimisationMethod
 {
 protected:
 
     // Protected data
 
-        //- grad(dx/db) multiplier
-        volTensorField gradDxDbMult_;
+        //- Derivatives of the Lagrangian function
+        scalarField LagrangianDerivatives_;
+
+        //- The previous constraint derivatives
+        List<scalarField> constraintDerivativesOld_;
 
-        //- div(dx/db) multiplier
-        scalarField divDxDbMult_;
+        //- Lagrange multipliers
+        scalarField lamdas_;
 
-        //- dx/db multiplier coming from fvOptions
-        vectorField optionsDxDbMult_;
+        //- Name of the objective folder
+        fileName objFunctionFolder_;
 
-        //- Include distance variation in sens computation
-        bool includeDistance_;
+        //- File including the l1 merit function
+        autoPtr<OFstream> meritFunctionFile_;
 
-        //- Adjoint eikonal equation solver
-        autoPtr<adjointEikonalSolver> eikonalSolver_;
+        //- Penalty value for the merit function
+        scalar mu_;
+
+        //- Safety factor
+        scalar delta_;
 
 
     // Protected Member Functions
 
-        //- Read options and update solver pointers if necessary
-        void read();
+        //- Get the part the merit function that depends on the constraints
+        virtual scalar meritFunctionConstraintPart() const = 0;
 
 
 private:
@@ -90,52 +93,50 @@ private:
     // Private Member Functions
 
         //- No copy construct
-        FIBase(const FIBase&) = delete;
+        SQPBase(const SQPBase&) = delete;
 
         //- No copy assignment
-        void operator=(const FIBase&) = delete;
+        void operator=(const SQPBase&) = delete;
+
+        //- Make folder holding the Lagrangian file
+        void makeFolder();
 
 
 public:
 
     //- Runtime type information
-    TypeName("volumetricBSplinesFI");
+    TypeName("SQPBase");
 
 
     // Constructors
 
         //- Construct from components
-        FIBase
+        SQPBase
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
+            autoPtr<designVariables>& designVars,
+            const updateMethod& UpdateMethod,
+            const word& type
         );
 
 
     //- Destructor
-    virtual ~FIBase() = default;
+    virtual ~SQPBase() = default;
 
 
     // Member Functions
 
-        //- Read dict if changed
-        virtual bool readDict(const dictionary& dict);
-
-        //- Accumulate sensitivity integrands
-        virtual void accumulateIntegrand(const scalar dt);
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities() = 0;
+        //- Write continuation info
+        virtual bool addToFile(Ostream& os) const;
 
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
+        //- Write info about the merit function
+        virtual bool writeMeritFunction(const updateMethod& UpdateMethod);
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.C
index 6cba4953fe3dd102181eaccde7e8ed3a8a785ebd..74bec23639267c7b4f2c880099d8e062fccbc469 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -46,28 +46,6 @@ namespace Foam
 
 // * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
 
-void Foam::SR1::allocateMatrices()
-{
-    // Set active design variables, if necessary
-    if (activeDesignVars_.empty())
-    {
-        activeDesignVars_ = identity(objectiveDerivatives_.size());
-    }
-
-    // Set previous HessianInv to be a diagonal matrix
-    SquareMatrix<scalar> temp(activeDesignVars_.size(), Zero);
-    forAll(activeDesignVars_, i)
-    {
-        temp[i][i] = scalar(1);
-    }
-
-    // Allocate correct size and content to HessianInv matrices
-    // has a max. capability of approximately 34000 variables.
-    HessianInvOld_ = temp;
-    HessianInv_ = temp;
-}
-
-
 void Foam::SR1::updateHessian()
 {
     // Vectors needed to construct the inverse HessianInv matrix
@@ -76,25 +54,22 @@ void Foam::SR1::updateHessian()
     y.map(objectiveDerivatives_ - derivativesOld_, activeDesignVars_);
     s.map(correctionOld_, activeDesignVars_);
 
-    scalarField temp(s - rightMult(HessianInvOld_, y));
+    scalarField temp(s - rightMult(Hessian_(), y));
 
     // Construct the inverse HessianInv
     scalar tempMag = sqrt(globalSum(sqr(temp)));
     scalar yMag = sqrt(globalSum(sqr(y)));
-    scalar HessYMag = sqrt(globalSum(sqr(rightMult(HessianInvOld_, y))));
+    scalar HessYMag = sqrt(globalSum(sqr(rightMult(Hessian_(), y))));
 
     // Stability check
     if (tempMag > ratioThreshold_ * yMag * HessYMag)
     {
-        HessianInv_ =
-            HessianInvOld_
-          + (scalar(1)/(globalSum(temp*y)))*outerProd(temp, temp);
+        Hessian_() += (scalar(1)/(globalSum(temp*y)))*outerProd(temp, temp);
     }
     else
     {
         WarningInFunction
             << "Denominator of update too small. Keeping old Hessian" << endl;
-        HessianInv_ = HessianInvOld_;
     }
 }
 
@@ -105,8 +80,11 @@ void Foam::SR1::update()
     // matrix
     if (counter_ < nSteepestDescent_)
     {
-        Info<< "Using steepest descent to update design variables ... " << endl;
-        correction_ = -eta_*objectiveDerivatives_;
+        Info<< "Using steepest descent to update design variables" << endl;
+        for (const label varI : activeDesignVars_)
+        {
+            correction_[varI] = -eta_*objectiveDerivatives_[varI];
+        }
     }
     else
     {
@@ -114,7 +92,7 @@ void Foam::SR1::update()
         activeDerivs.map(objectiveDerivatives_, activeDesignVars_);
         scalarField activeCorrection
         (
-            -etaHessian_*rightMult(HessianInv_, activeDerivs)
+            -etaHessian_*rightMult(Hessian_(), activeDerivs)
         );
 
         // Transfer correction to the global list
@@ -128,112 +106,27 @@ void Foam::SR1::update()
     // Store fields for the next iteration
     derivativesOld_ = objectiveDerivatives_;
     correctionOld_ = correction_;
-    HessianInvOld_ = HessianInv_;
-}
-
-
-void Foam::SR1::readFromDict()
-{
-    if (optMethodIODict_.headerOk())
-    {
-        optMethodIODict_.readEntry("HessianInvOld", HessianInvOld_);
-        optMethodIODict_.readEntry("derivativesOld", derivativesOld_);
-        optMethodIODict_.readEntry("correctionOld", correctionOld_);
-        optMethodIODict_.readEntry("counter", counter_);
-        optMethodIODict_.readEntry("eta", eta_);
-
-        const label n(HessianInvOld_.n());
-        HessianInv_ = SquareMatrix<scalar>(n, Zero);
-        correction_ = scalarField(correctionOld_.size(), Zero);
-
-        if (activeDesignVars_.empty())
-        {
-            activeDesignVars_ = identity(n);
-        }
-    }
 }
 
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::SR1::SR1(const fvMesh& mesh, const dictionary& dict)
+Foam::SR1::SR1
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
+)
 :
-    updateMethod(mesh, dict),
-
-    // Construct null matrix since we dont know the dimension yet
-    etaHessian_
-    (
-        coeffsDict().getOrDefault<scalar>("etaHessian", 1)
-    ),
-    nSteepestDescent_
-    (
-        coeffsDict().getOrDefault<label>("nSteepestDescent", 1)
-    ),
+    quasiNewton(mesh, dict, designVars, nConstraints, type),
     ratioThreshold_
     (
-        coeffsDict().getOrDefault<scalar>("ratioThreshold", 1e-08)
-    ),
-    activeDesignVars_(0),
-    HessianInv_(),
-    HessianInvOld_(),
-    derivativesOld_(0),
-    correctionOld_(0),
-    counter_(0)
-{
-    if
-    (
-        !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
+        coeffsDict(type).getOrDefault<scalar>("ratioThreshold", 1e-08)
     )
-    {
-        // If not, all available design variables will be used. Number is not
-        // know at the moment
-        Info<< "\t Didn't find explicit definition of active design variables. "
-            << "Treating all available ones as active " << endl;
-    }
-
-    // Read old hessian, correction and derivatives, if present
-    readFromDict();
-}
-
-
-// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
-
-void Foam::SR1::computeCorrection()
 {
-    if (counter_ == 0)
-    {
-        allocateMatrices();
-    }
-    else
-    {
-        updateHessian();
-    }
-
-    update();
-    ++counter_;
-}
-
-
-void Foam::SR1::updateOldCorrection(const scalarField& oldCorrection)
-{
-    updateMethod::updateOldCorrection(oldCorrection);
-    correctionOld_ = oldCorrection;
-}
-
-
-void Foam::SR1::write()
-{
-    optMethodIODict_.add<SquareMatrix<scalar>>
-    (
-        "HessianInvOld",
-        HessianInvOld_,
-        true
-    );
-    optMethodIODict_.add<scalarField>("derivativesOld", derivativesOld_, true);
-    optMethodIODict_.add<scalarField>("correctionOld", correctionOld_, true);
-    optMethodIODict_.add<label>("counter", counter_, true);
-
-    updateMethod::write();
+    allocateHessian();
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.H
index fa8fa1d5f05edf8a0ec578f241f1231f5901b5a8..c350798a3e36de7006d37e4a2ee8e56819a706cc 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SR1/SR1.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -30,7 +30,8 @@ Class
     Foam::SR1
 
 Description
-    The quasi-Newton Symmetric Rank One formula
+    The quasi-Newton Symmetric Rank One formula.
+    The quasiNewton::Hessian corresponds to Hessian inverse for SR1
 
 SourceFiles
     SR1.C
@@ -40,8 +41,7 @@ SourceFiles
 #ifndef SR1_H
 #define SR1_H
 
-#include "updateMethod.H"
-#include "scalarMatrices.H"
+#include "quasiNewton.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,54 +54,23 @@ namespace Foam
 
 class SR1
 :
-    public updateMethod
+    public quasiNewton
 {
 protected:
 
     // Protected data
 
-        //- Step for the Newton method
-        scalar etaHessian_;
-
-        //- Number of first steepest descent steps
-        label nSteepestDescent_;
-
         //- For stability check
         scalar ratioThreshold_;
 
-        //- Map to active design variables
-        labelList activeDesignVars_;
-
-        //- The Hessian inverse. Should have the size of the active design
-        //- variables
-        SquareMatrix<scalar> HessianInv_;
-
-        //- The previous Hessian inverse
-        SquareMatrix<scalar> HessianInvOld_;
-
-        //- The previous derivatives
-        scalarField derivativesOld_;
-
-        //- The previous correction
-        scalarField correctionOld_;
-
-        //- Optimisation counter
-        label counter_;
-
 
-    // Protected functions
-
-        //- Allocate matrices in the first optimisation cycle
-        void allocateMatrices();
+    // Protected Member Functions
 
         //- Update approximation of the inverse Hessian
-        void updateHessian();
+        virtual void updateHessian();
 
         //- Update design variables
-        void update();
-
-        //- Read old info from dict
-        void readFromDict();
+        virtual void update();
 
 
 private:
@@ -124,24 +93,18 @@ public:
     // Constructors
 
         //- Construct from components
-        SR1(const fvMesh& mesh, const dictionary& dict);
+        SR1
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
     virtual ~SR1() = default;
-
-
-    // Member Functions
-
-       //- Compute design variables correction
-       void computeCorrection();
-
-       //- Update old correction. Useful for quasi-Newton methods coupled with
-       //- line search
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
-
-       //- Write old info to dict
-       virtual void write();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.C
index bba5598e5cc5a08061324792dcda01f38bb8b412..fc0e9a9e467177881e2d2a8e28ef17bc497565aa 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -44,72 +44,23 @@ namespace Foam
 }
 
 
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
-
-void Foam::conjugateGradient::allocateFields()
-{
-    // Set active design variables, if necessary
-    if (activeDesignVars_.empty())
-    {
-        activeDesignVars_ = identity(objectiveDerivatives_.size());
-    }
-
-    // Allocate old fields
-    dxOld_ = scalarField(activeDesignVars_.size(), Zero);
-    sOld_ = scalarField(activeDesignVars_.size(), Zero);
-}
-
-
-void Foam::conjugateGradient::readFromDict()
-{
-    if (optMethodIODict_.headerOk())
-    {
-        optMethodIODict_.readEntry("dxOld", dxOld_);
-        optMethodIODict_.readEntry("sOld", sOld_);
-        optMethodIODict_.readEntry("counter", counter_);
-        optMethodIODict_.readEntry("eta", eta_);
-
-        label nDVs = optMethodIODict_.get<label>("nDVs");
-        correction_ = scalarField(nDVs, Zero);
-
-        if (activeDesignVars_.empty())
-        {
-            activeDesignVars_ = identity(nDVs);
-        }
-    }
-}
-
-
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::conjugateGradient::conjugateGradient
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
 :
-    updateMethod(mesh, dict),
+    updateMethod(mesh, dict, designVars, nConstraints, type),
 
-    activeDesignVars_(0),
-    dxOld_(0),
-    sOld_(0),
-    counter_(0),
-    betaType_
-    (
-        coeffsDict().getOrDefault<word>("betaType", "FletcherReeves")
-    )
+    dxOld_(readOrZeroField("dxOld", activeDesignVars_.size())),
+    sOld_(readOrZeroField("sOld", activeDesignVars_.size())),
+    betaType_(coeffsDict(type).getOrDefault<word>("betaType", "FletcherReeves"))
 {
-    if
-    (
-        !coeffsDict().readIfPresent("activeDesignVariables", activeDesignVars_)
-    )
-    {
-        // If not, all available design variables will be used.
-        // Number is not know at the moment
-        Info<< "\t Did not find explicit definition of active design variables. "
-            << "Treating all available ones as active " << endl;
-    }
-
     // Check if beta type is valid
     if
     (
@@ -124,9 +75,6 @@ Foam::conjugateGradient::conjugateGradient
            << nl << nl
            << exit(FatalError);
     }
-
-    // Read old dx and s, if present
-    readFromDict();
 }
 
 
@@ -136,17 +84,18 @@ void Foam::conjugateGradient::computeCorrection()
 {
     if (counter_ == 0)
     {
-        allocateFields();
-
         Info<< "Using steepest descent for the first iteration" << endl;
-        correction_ = -eta_*objectiveDerivatives_;
+        for (const label varI : activeDesignVars_)
+        {
+            correction_[varI] = -eta_*objectiveDerivatives_[varI];
+        }
 
         dxOld_.map(-objectiveDerivatives_, activeDesignVars_);
         sOld_ = dxOld_;
     }
     else
     {
-        scalarField dx = scalarField(activeDesignVars_.size(), Zero);
+        scalarField dx(activeDesignVars_.size(), Zero);
         dx.map(-objectiveDerivatives_, activeDesignVars_);
 
         scalar beta(Zero);
@@ -200,14 +149,12 @@ void Foam::conjugateGradient::updateOldCorrection
 }
 
 
-void Foam::conjugateGradient::write()
+bool Foam::conjugateGradient::writeData(Ostream& os) const
 {
-    optMethodIODict_.add<scalarField>("dxOld", dxOld_, true);
-    optMethodIODict_.add<scalarField>("sOld", sOld_, true);
-    optMethodIODict_.add<label>("counter", counter_, true);
-    optMethodIODict_.add<label>("nDVs", objectiveDerivatives_.size(), true);
+    dxOld_.writeEntry("dxOld", os);
+    sOld_.writeEntry("sOld", os);
 
-    updateMethod::write();
+    return updateMethod::writeData(os);
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.H
index 5c42ecdcfd53b0df62e0ff3fcffb623dc2bc5c49..5954ea74fd0d0715b869b1f5a7dc6fad8fa5a346 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/conjugateGradient/conjugateGradient.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -63,22 +63,11 @@ protected:
 
     // Protected data
 
-        labelList activeDesignVars_;
         scalarField dxOld_;
         scalarField sOld_;
-        label counter_;
         word betaType_;
 
 
-    // Protected Member Functions
-
-        //- Allocate matrices in the first optimisation cycle
-        void allocateFields();
-
-        //- Read old info from dict
-        void readFromDict();
-
-
 private:
 
     // Private Member Functions
@@ -99,7 +88,14 @@ public:
     // Constructors
 
         //- Construct from components
-        conjugateGradient(const fvMesh& mesh, const dictionary& dict);
+        conjugateGradient
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
@@ -108,14 +104,14 @@ public:
 
     // Member Functions
 
-       //- Compute design variables correction
-       void computeCorrection();
+        //- Compute design variables correction
+        void computeCorrection();
 
-       //- Update old correction. For use when eta has been changed externally
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
+        //- Update old correction. For use when eta has been changed externally
+        virtual void updateOldCorrection(const scalarField& oldCorrection);
 
-       //- Write old info to dict
-       virtual void write();
+        //- Write useful quantities to files
+        virtual bool writeData(Ostream& os) const;
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.C
index b8c058644f86584fa95d5c193b22efc2237c371a..9e75e19eab16c5dd6f468b97ac9aa05c479fe4e9 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -43,10 +43,11 @@ namespace Foam
 Foam::constrainedOptimisationMethod::constrainedOptimisationMethod
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
-:
-    updateMethod(mesh, dict)
 {}
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.H
index 278f44d77def9dd2a3ac73f47f9b3115226b4772..de4059fc2264694f77c5d84ee1341ee8deb6b5e5 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constrainedOptimisationMethod/constrainedOptimisationMethod.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -42,7 +42,10 @@ SourceFiles
 #ifndef constrainedOptimisationMethod_H
 #define constrainedOptimisationMethod_H
 
-#include "updateMethod.H"
+#include "runTimeSelectionTables.H"
+#include "fvMesh.H"
+#include "dictionary.H"
+#include "designVariables.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -54,8 +57,6 @@ namespace Foam
 \*---------------------------------------------------------------------------*/
 
 class constrainedOptimisationMethod
-:
-    public updateMethod
 {
 private:
 
@@ -87,9 +88,12 @@ public:
             dictionary,
             (
                 const fvMesh& mesh,
-                const dictionary& dict
+                const dictionary& dict,
+                autoPtr<designVariables>& designVars,
+                const label nConstraints,
+                const word& type
             ),
-            (mesh, dict)
+            (mesh, dict, designVars, nConstraints, type)
         );
 
 
@@ -99,18 +103,15 @@ public:
         constrainedOptimisationMethod
         (
             const fvMesh& mesh,
-            const dictionary& dict
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
         );
 
 
     //- Destructor
     virtual ~constrainedOptimisationMethod() = default;
-
-
-    // Member Functions
-
-       //- Return the correction of the design variables
-       virtual void computeCorrection()=0;
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.C
index 25cb7f27c7fbc04b35400966b7318a1a3546ffb3..16de65310364e4d9095be718b625d03a133654f9 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -55,14 +55,16 @@ namespace Foam
 Foam::constraintProjection::constraintProjection
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
 :
-    constrainedOptimisationMethod(mesh, dict),
-    useCorrection_
-    (
-        coeffsDict().getOrDefault<bool>("useCorrection", true)
-    )
+    constrainedOptimisationMethod(mesh, dict, designVars, nConstraints, type),
+    updateMethod(mesh, dict, designVars, nConstraints, type),
+    useCorrection_(coeffsDict(type).getOrDefault<bool>("useCorrection", true)),
+    delta_(coeffsDict(type).getOrDefault<scalar>("delta", 0.1))
 {}
 
 
@@ -73,7 +75,6 @@ void Foam::constraintProjection::computeCorrection()
     // Reset to zero
     const label n = objectiveDerivatives_.size();
     const label m = constraintDerivatives_.size();
-    correction_ = scalarField(n, Zero);
 
     // Matrix with constraint derivatives and its inverse
     scalarSquareMatrix MMT(m, Zero);
@@ -114,13 +115,24 @@ void Foam::constraintProjection::computeCorrection()
     }
 
     // Final correction
-    correction_ = objectiveDerivatives_ - constraintContribution;
-    correction_ *= -eta_;
+    scalarField correction(objectiveDerivatives_ - constraintContribution);
+    correction *= -eta_;
     if (useCorrection_)
     {
-        correction_ -= nonLinearContribution;
+        correction -= nonLinearContribution;
+    }
+
+    for (const label varI : activeDesignVars_)
+    {
+        correction_[varI] = correction[varI];
     }
 }
 
 
+Foam::scalar Foam::constraintProjection::computeMeritFunction()
+{
+    return objectiveValue_ + delta_*sum(mag(cValues_));
+}
+
+
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.H
index 0bea873fb951355c2329148c816ca61686c2ad23..b054665698dfc588d4698fd481192521b5f5cb4b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/constraintProjection/constraintProjection.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -44,6 +44,7 @@ SourceFiles
 #define constraintProjection_H
 
 #include "constrainedOptimisationMethod.H"
+#include "updateMethod.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -56,7 +57,8 @@ namespace Foam
 
 class constraintProjection
 :
-    public constrainedOptimisationMethod
+    public constrainedOptimisationMethod,
+    public updateMethod
 {
 protected:
 
@@ -65,6 +67,9 @@ protected:
         //- Correct for non-linearities
         bool useCorrection_;
 
+        //- Weight of the aggregated constraint values in the merit function
+        scalar delta_;
+
 
 private:
 
@@ -86,7 +91,14 @@ public:
     // Constructors
 
         //- Construct from components
-        constraintProjection(const fvMesh& mesh, const dictionary& dict);
+        constraintProjection
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
@@ -95,8 +107,12 @@ public:
 
     // Member Functions
 
-       //- Compute design variables correction
-       void computeCorrection();
+        //- Compute design variables correction
+        void computeCorrection();
+
+        //- Compute merit function. Could be different than the objective
+        //- in the presence of constraints
+        virtual scalar computeMeritFunction();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.C
new file mode 100644
index 0000000000000000000000000000000000000000..a6c356367540e76cd673c1667397f9fec80811b5
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.C
@@ -0,0 +1,129 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "quasiNewton.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(quasiNewton, 0);
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::quasiNewton::allocateHessian()
+{
+    Hessian_.reset(new SquareMatrix<scalar>(activeDesignVars_.size(), I));
+    // Read in Hessian Matrix or initialiase
+    const label nDVs(designVars_().activeDesignVariables().size());
+    forAll(designVars_().activeDesignVariables(), iDV)
+    {
+        if (found("Hessian" + Foam::name(iDV)))
+        {
+            Hessian_().subColumn(iDV) =
+                scalarField("Hessian" + Foam::name(iDV), *this, nDVs);
+        }
+    }
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::quasiNewton::quasiNewton
+(
+    const fvMesh& mesh,
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+        const word& type
+)
+:
+    updateMethod(mesh, dict, designVars, nConstraints, type),
+    etaHessian_(coeffsDict(type).getOrDefault<scalar>("etaHessian", 1)),
+    nSteepestDescent_
+    (
+        coeffsDict(type).getOrDefault<label>("nSteepestDescent", 1)
+    ),
+    scaleFirstHessian_
+    (
+        coeffsDict(type).getOrDefault<bool>("scaleFirstHessian", false)
+    ),
+    Hessian_(nullptr),
+    derivativesOld_
+    (
+        readOrZeroField("derivativesOld", objectiveDerivatives_.size())
+    ),
+    correctionOld_(readOrZeroField("correctionOld", correction_.size()))
+{}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+void Foam::quasiNewton::computeCorrection()
+{
+    // The first iteration uses a unitary Hessian. No need to update
+    if (counter_ != 0)
+    {
+        updateHessian();
+    }
+
+    update();
+    ++counter_;
+}
+
+
+void Foam::quasiNewton::updateOldCorrection(const scalarField& oldCorrection)
+{
+    updateMethod::updateOldCorrection(oldCorrection);
+    correctionOld_ = oldCorrection;
+}
+
+
+bool Foam::quasiNewton::writeData(Ostream& os) const
+{
+    if (Hessian_)
+    {
+        // Matrices cannot be written/read in binary.
+        // Circumvent this by writing separate columns as scalarFields
+        forAll(designVars_().activeDesignVariables(), iDV)
+        {
+            Hessian_().subColumn(iDV).operator Field<scalar>().
+                writeEntry("Hessian" + Foam::name(iDV), os);
+        }
+    }
+    derivativesOld_.writeEntry("derivativesOld", os);
+    correctionOld_.writeEntry("correctionOld", os);
+
+    return updateMethod::writeData(os);
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.H
similarity index 54%
rename from src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.H
rename to src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.H
index 0420c38383fc2d44ab3e1cff7142a17996d2e292..6d2358690b12ea743d2f18815fd709cb2b905090 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.H
@@ -5,9 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,59 +26,70 @@ License
 
 
 Class
-    Foam::incompressible::SIBase
+    Foam::quasiNewton
 
 Description
-    Base class for Surface Integral-based sensitivity derivatives
+    Base class for quasi-Newton methods
 
 SourceFiles
-    SIBase.C
+    quasiNewton.C
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef SIBaseIncompressible_H
-#define SIBaseIncompressible_H
+#ifndef quasiNewton_H
+#define quasiNewton_H
 
-#include "shapeSensitivitiesIncompressible.H"
-#include "sensitivitySurfaceIncompressible.H"
+#include "updateMethod.H"
+#include "scalarMatrices.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 namespace Foam
 {
 
-namespace incompressible
-{
-
 /*---------------------------------------------------------------------------*\
-                            Class SIBase Declaration
+                         Class quasiNewton Declaration
 \*---------------------------------------------------------------------------*/
 
-class SIBase
+class quasiNewton
 :
-    public shapeSensitivities
+    public updateMethod
 {
 protected:
 
     // Protected data
 
-        //- Surface sensitivities
-        sensitivitySurface surfaceSensitivity_;
+        //- Step for the Newton method
+        scalar etaHessian_;
+
+        //- Number of first steepest descent steps
+        label nSteepestDescent_;
+
+        //- Scale the initial unitary Hessian approximation
+        bool scaleFirstHessian_;
+
+        //- The Hessian or its inverse, depending on the deriving class.
+        //  Has the size of the active design variables.
+        //  autoPtr to avoid allocation by limited memory variants
+        autoPtr<SquareMatrix<scalar>> Hessian_;
 
-        //- Whether to include direct sensitivities or not.
-        //  Used to avoid double contributions from both here and the
-        //  sensitivitySurface object which might have already accounted for
-        //  them
-        bool includeObjective_;
+        //- The previous derivatives
+        scalarField derivativesOld_;
 
-        //- Write sensitivity map upon write
-        bool writeSensitivityMap_;
+        //- The previous correction
+        scalarField correctionOld_;
 
 
     // Protected Member Functions
 
-        //- Read options from dict
-        void read();
+        //- Update approximation of the inverse Hessian
+        virtual void updateHessian() = 0;
+
+        //- Update design variables
+        virtual void update() = 0;
+
+        //- Allocate the Hessian matrix
+        void allocateHessian();
 
 
 private:
@@ -87,58 +97,51 @@ private:
     // Private Member Functions
 
         //- No copy construct
-        SIBase(const SIBase&) = delete;
+        quasiNewton(const quasiNewton&) = delete;
 
         //- No copy assignment
-        void operator=(const SIBase&) = delete;
+        void operator=(const quasiNewton&) = delete;
 
 
 public:
 
     //- Runtime type information
-    TypeName("volumetricBSplinesFI");
+    TypeName("quasiNewton");
 
 
     // Constructors
 
         //- Construct from components
-        SIBase
+        quasiNewton
         (
             const fvMesh& mesh,
             const dictionary& dict,
-            incompressibleAdjointSolver& adjointSolver
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
         );
 
 
     //- Destructor
-    virtual ~SIBase() = default;
+    virtual ~quasiNewton() = default;
 
 
     // Member Functions
 
-        //- Read dict if changed
-        virtual bool readDict(const dictionary& dict);
-
-        //- Accumulate sensitivity integrands
-        virtual void accumulateIntegrand(const scalar dt);
-
-        //- Assemble sensitivities
-        virtual void assembleSensitivities() = 0;
-
-        //- Zero sensitivity fields and their constituents
-        virtual void clearSensitivities();
+       //- Compute design variables correction
+       void computeCorrection();
 
-        //- Return reference to underlaying surface sensitivities
-        const sensitivitySurface& getSurfaceSensitivities() const;
+       //- Update old correction. Useful for quasi-Newton methods coupled with
+       //- line search
+       virtual void updateOldCorrection(const scalarField& oldCorrection);
 
-        //- Write sensitivity map
-        virtual void write(const word& baseName = word::null);
+       //- Write useful quantities to files
+       virtual bool writeData(Ostream& os) const;
 };
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
-} // End namespace incompressible
 } // End namespace Foam
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.C
index 9251b948aa9b9e10511aadba65cfa572c54b738a..89964254984d33653694e99b7f30c7e078976b93 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2021 PCOpt/NTUA
+    Copyright (C) 2013-2021 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -49,10 +49,13 @@ namespace Foam
 Foam::steepestDescent::steepestDescent
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
 :
-    updateMethod(mesh, dict)
+    updateMethod(mesh, dict, designVars, nConstraints, type)
 {}
 
 
@@ -60,7 +63,10 @@ Foam::steepestDescent::steepestDescent
 
 void Foam::steepestDescent::computeCorrection()
 {
-    correction_ = -eta_*objectiveDerivatives_;
+    for (const label varI : activeDesignVars_)
+    {
+        correction_[varI] = -eta_*objectiveDerivatives_[varI];
+    }
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.H
index 46599167954ae3ff24c21d0427ccb59491700c21..f8dc2e4b9c487ac2ebef051f681cffd5f0145a9d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/steepestDescent/steepestDescent.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -74,7 +74,14 @@ public:
     // Constructors
 
         //- Construct from components
-        steepestDescent(const fvMesh& mesh, const dictionary& dict);
+        steepestDescent
+        (
+            const fvMesh& mesh,
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
+        );
 
 
     //- Destructor
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.C b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.C
index fd550bbab356723005083f818cca00bb0378d902..84a7ad4f7fe6a868db812d48c5e7903606370a04 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.C
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2022 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -195,48 +195,63 @@ Foam::scalar Foam::updateMethod::globalSum(tmp<scalarField>& tfield)
 }
 
 
+Foam::label Foam::updateMethod::globalSum(const label size)
+{
+    label res(0);
+    if (globalSum_)
+    {
+        res = returnReduce(size, sumOp<label>());
+    }
+    else
+    {
+        res = size;
+    }
+    return res;
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::updateMethod::updateMethod
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints,
+    const word& type
 )
 :
-    mesh_(mesh),
-    dict_(dict),
-    optMethodIODict_
+    localIOdictionary
     (
         IOobject
         (
             "updateMethodDict",
-            mesh_.time().timeName(),
+            mesh.time().timeName(),
             "uniform",
-            mesh_,
+            mesh,
             IOobject::READ_IF_PRESENT,
-            IOobject::NO_WRITE
-        )
+            IOobject::AUTO_WRITE
+        ),
+        word::null // avoid type checking
     ),
-    objectiveDerivatives_(0),
+    mesh_(mesh),
+    dict_(dict),
+    designVars_(designVars),
+    nConstraints_(nConstraints),
+    activeDesignVars_(designVars().activeDesignVariables()),
+    objectiveDerivatives_(designVars().getVars().size(), Zero),
     constraintDerivatives_(0),
     objectiveValue_(0),
+    objectiveValueOld_(0),
     cValues_(0),
-    correction_(0),
+    correction_(readOrZeroField("correction", designVars().getVars().size())),
     cumulativeCorrection_(0),
     eta_(1),
+    counter_(getOrDefault<label>("counter", Zero)),
     initialEtaSet_(false),
     correctionFolder_(mesh_.time().globalPath()/"optimisation"/"correction"),
-    globalSum_
-    (
-        dict.getOrDefault<bool>("globalSum", false)
-    )
+    globalSum_(designVars_->globalSum())
 {
-    // Create folder to store corrections
-    if (Pstream::master())
-    {
-        mkDir(correctionFolder_);
-    }
-
     // Set initial eta, if present. It might be set either in the
     // optimisationDict or in the specific dictionary dedicated to the
     // updateMethod
@@ -244,16 +259,25 @@ Foam::updateMethod::updateMethod
     {
         initialEtaSet_ = true;
     }
-    else if (optMethodIODict_.readIfPresent("eta", eta_))
+    else if (this->readIfPresent("eta", eta_))
     {
         initialEtaSet_ = true;
     }
 }
 
 
-Foam::dictionary Foam::updateMethod::coeffsDict()
+Foam::tmp<Foam::scalarField> Foam::updateMethod::readOrZeroField
+(
+    const word& name,
+    const label size
+)
 {
-    return dict_.subOrEmptyDict(type());
+    return tmp<scalarField>
+    (
+        found(name) ?
+        new scalarField(name, *this, size) :
+        new scalarField(size, Zero)
+    );
 }
 
 
@@ -262,7 +286,9 @@ Foam::dictionary Foam::updateMethod::coeffsDict()
 Foam::autoPtr<Foam::updateMethod> Foam::updateMethod::New
 (
     const fvMesh& mesh,
-    const dictionary& dict
+    const dictionary& dict,
+    autoPtr<designVariables>& designVars,
+    const label nConstraints
 )
 {
     const word modelType(dict.get<word>("method"));
@@ -282,12 +308,19 @@ Foam::autoPtr<Foam::updateMethod> Foam::updateMethod::New
         ) << exit(FatalIOError);
     }
 
-    return autoPtr<updateMethod>(ctorPtr(mesh, dict));
+    return autoPtr<updateMethod>
+        (ctorPtr(mesh, dict, designVars, nConstraints, modelType));
 }
 
 
 // * * * * * * * * * * * * * * *  Member Functions   * * * * * * * * * * * * //
 
+Foam::dictionary Foam::updateMethod::coeffsDict(const word& type) const
+{
+    return dict_.optionalSubDict(type);
+}
+
+
 void Foam::updateMethod::setObjectiveDeriv(const scalarField& derivs)
 {
     objectiveDerivatives_ = derivs;
@@ -309,27 +342,74 @@ void Foam::updateMethod::setObjectiveValue(const scalar value)
 }
 
 
+void Foam::updateMethod::setObjectiveValueOld(const scalar value)
+{
+    objectiveValueOld_ = value;
+}
+
+
 void Foam::updateMethod::setConstraintValues(const scalarField& values)
 {
     cValues_ = values;
 }
 
 
+Foam::scalar Foam::updateMethod::getObjectiveValue() const
+{
+    return objectiveValue_;
+}
+
+
+Foam::scalar Foam::updateMethod::getObjectiveValueOld() const
+{
+    return objectiveValueOld_;
+}
+
+
+const Foam::scalarField& Foam::updateMethod::getConstraintValues() const
+{
+    return cValues_;
+}
+
+
+Foam::label Foam::updateMethod::getCycle() const
+{
+    return counter_;
+}
+
+
 void Foam::updateMethod::setStep(const scalar eta)
 {
     eta_ = eta;
 }
 
 
+void Foam::updateMethod::modifyStep(const scalar multiplier)
+{
+    eta_ *= multiplier;
+}
+
+
 void Foam::updateMethod::setGlobalSum(const bool useGlobalSum)
 {
     globalSum_ = useGlobalSum;
 }
 
 
+void Foam::updateMethod::setConstaintsNumber(const label nConstraints)
+{
+    nConstraints_ = nConstraints;
+}
+
+
+Foam::label Foam::updateMethod::nConstraints() const
+{
+    return nConstraints_;
+}
+
+
 Foam::scalarField& Foam::updateMethod::returnCorrection()
 {
-    computeCorrection();
     return correction_;
 }
 
@@ -395,27 +475,25 @@ void Foam::updateMethod::updateOldCorrection
 }
 
 
-void Foam::updateMethod::write()
+bool Foam::updateMethod::writeData(Ostream& os) const
 {
     // Insert eta if set
     if (initialEtaSet_)
     {
-        optMethodIODict_.add<scalar>("eta", eta_, true);
+        os.writeEntry("eta", eta_);
     }
 
-    optMethodIODict_.add<scalarField>("correction", correction_, true);
+    os.writeEntry("counter", counter_);
+    correction_.writeEntry("correction", os);
 
-    // Write IOdictionary
-    // Always write in ASCII format.
-    // Even when choosing to write in binary through controlDict,
-    // the content is written in ASCII format but with a binary header.
-    // This creates problems when the content is read back in
-    // (e.g. continuation)
-    optMethodIODict_.regIOobject::writeObject
-    (
-        IOstreamOption(IOstreamOption::ASCII, mesh_.time().writeCompression()),
-        true
-    );
+    return true;
+}
+
+
+bool Foam::updateMethod::writeAuxiliaryData()
+{
+    // Does nothing in base
+    return true;
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.H b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.H
index 0cec839afd7c2d02961df9790b485dfc2bf6ed09..49ee8c61c622e2fbd33c813c0a06f9de2d926065 100644
--- a/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.H
+++ b/src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/updateMethod/updateMethod.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -39,9 +39,9 @@ SourceFiles
 #ifndef updateMethod_H
 #define updateMethod_H
 
+#include "localIOdictionary.H"
+#include "designVariables.H"
 #include "runTimeSelectionTables.H"
-#include "IOdictionary.H"
-#include "fvMesh.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -53,6 +53,8 @@ namespace Foam
 \*---------------------------------------------------------------------------*/
 
 class updateMethod
+:
+    public localIOdictionary
 {
 protected:
 
@@ -62,8 +64,14 @@ protected:
 
         const dictionary dict_;
 
-        //- Used to output values useful for continuation runs
-        IOdictionary optMethodIODict_;
+        //- Design variables
+        autoPtr<designVariables>& designVars_;
+
+        //- Number of constraints
+        label nConstraints_;
+
+        //- Map to active design variables
+        const labelList& activeDesignVars_;
 
         //- Derivatives of the objective functions
         scalarField objectiveDerivatives_;
@@ -74,6 +82,10 @@ protected:
         //- Objective value
         scalar objectiveValue_;
 
+        //- Old objective value
+        //  Used for convergence checking
+        scalar objectiveValueOld_;
+
         //- Constraint values
         scalarField cValues_;
 
@@ -87,6 +99,9 @@ protected:
         //- Step multiplying the correction
         scalar eta_;
 
+        //- Optimisation cycle count
+        label counter_;
+
         //- Is initially set?
         bool initialEtaSet_;
 
@@ -128,8 +143,12 @@ protected:
         //- Compute either global or local sum, based on globalSum flag
         scalar globalSum(tmp<scalarField>& tfield);
 
-        //- Return optional dictionary with parameters specific to each method
-        dictionary coeffsDict();
+        //- Compute either global or local sum, based on globalSum flag
+        label globalSum(const label);
+
+        //- Helper function to either read a scalarField of certain size
+        //- from a dictionary, or construct a zero field
+        tmp<scalarField> readOrZeroField(const word& name, const label size);
 
 
 private:
@@ -158,9 +177,12 @@ public:
             dictionary,
             (
                 const fvMesh& mesh,
-                const dictionary& dict
+                const dictionary& dict,
+                autoPtr<designVariables>& designVars,
+                const label nConstraints,
+                const word& type
             ),
-            (mesh, dict)
+            (mesh, dict, designVars, nConstraints, type)
         );
 
 
@@ -170,7 +192,10 @@ public:
         updateMethod
         (
             const fvMesh& mesh,
-            const dictionary& dict
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints,
+            const word& type
         );
 
 
@@ -180,7 +205,9 @@ public:
         static autoPtr<updateMethod> New
         (
             const fvMesh& mesh,
-            const dictionary& dict
+            const dictionary& dict,
+            autoPtr<designVariables>& designVars,
+            const label nConstraints
         );
 
 
@@ -190,54 +217,84 @@ public:
 
     // Member Functions
 
-       //- Set objective derivative
-       void setObjectiveDeriv(const scalarField& derivs);
+        //- Return optional dictionary with parameters specific to each method
+        dictionary coeffsDict(const word& type) const;
+
+        //- Set objective derivative
+        void setObjectiveDeriv(const scalarField& derivs);
+
+        //- Set constraints derivative
+        void setConstraintDeriv(const PtrList<scalarField>& derivs);
+
+        //- Set objective value
+        void setObjectiveValue(const scalar value);
+
+        //- Set old objective value
+        void setObjectiveValueOld(const scalar value);
+
+        //- Set values of constraints
+        void setConstraintValues(const scalarField& values);
+
+        //- Get objective value
+        scalar getObjectiveValue() const;
+
+        //- Get old objective value
+        scalar getObjectiveValueOld() const;
+
+        //- Get values of constraints
+        const scalarField& getConstraintValues() const;
+
+        //- Get optimisation cycle
+        label getCycle() const;
+
+        //- Set step for optimisation methods
+        void setStep(const scalar eta);
 
-       //- Set constraints derivative
-       void setConstraintDeriv(const PtrList<scalarField>& derivs);
+        //- Multiply step
+        void modifyStep(const scalar multiplier);
 
-       //- Set constraints derivative
-       void setObjectiveValue(const scalar value);
+        //- Set globalSum variable.
+        //  Should be set by the optimisationManager owning the updateMethod
+        void setGlobalSum(const bool useGlobalSum);
 
-       //- Set constraints derivative
-       void setConstraintValues(const scalarField& values);
+        //- Set the number of constraints
+        void setConstaintsNumber(const label nConstraints);
 
-       //- Set step for optimisation methods
-       void setStep(const scalar eta);
+        //- Get the number of constraints
+        label nConstraints() const;
 
-       //- Set globalSum variable.
-       //  Should be set by the optimisationType owining the updateMethod
-       void setGlobalSum(const bool useGlobalSum);
+        //- Return the correction of the design variables
+        virtual void computeCorrection() = 0;
 
-       //- Return the correction of the design variables
-       virtual void computeCorrection()=0;
+        //- Return the correction of the design variables
+        //const scalarField& returnCorrection() const;
 
-       //- Return the correction of the design variables
-       //const scalarField& returnCorrection() const;
+        //- Return the correction of the design variables
+        scalarField& returnCorrection();
 
-       //- Return the correction of the design variables
-       scalarField& returnCorrection();
+        void writeCorrection();
 
-       void writeCorrection();
+        //- Compute merit function. Could be different than the objective
+        //- in the presence of constraints
+        virtual scalar computeMeritFunction();
 
-       //- Compute merit function. Could be different than the objective
-       //- in the presence of constraints
-       virtual scalar computeMeritFunction();
+        //- Directional derivative of the merit function, in the direction of
+        //- the correction. Could be different than the objective directional
+        //- derivative in the presence of constraints
+        virtual scalar meritFunctionDirectionalDerivative();
 
-       //- Directional derivative of the merit function, in the direction of
-       //- the correction. Could be different than the objective directional
-       //- derivative in the presence of constraints
-       virtual scalar meritFunctionDirectionalDerivative();
+        //- Return whether initial eta was set
+        bool& initialEtaSet();
 
-       //- Return whether initial eta was set
-       bool& initialEtaSet();
+        //- Update old correction. Useful for quasi-newton methods coupled with
+        //- line search
+        virtual void updateOldCorrection(const scalarField& oldCorrection);
 
-       //- Update old correction. useful for quasi-newton methods coupled with
-       //- line search
-       virtual void updateOldCorrection(const scalarField& oldCorrection);
+        //- Write continuation data under uniform
+        virtual bool writeData(Ostream& os) const;
 
-       //- Write useful quantities to files
-       virtual void write();
+        //- Write non-continuation data, usually under the optimisation folder
+        virtual bool writeAuxiliaryData();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/Bezier/Bezier.C b/src/optimisation/adjointOptimisation/adjoint/parameterization/Bezier/Bezier.C
index 6653c2ed483e3e7045f2182e00bc4c44dc917972..457af29dfebe9c62fc3398f9b4897676c3fc6ea4 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/Bezier/Bezier.C
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/Bezier/Bezier.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2019, 2022 FOSS GP
+    Copyright (C) 2007-2019, 2022-2023 PCOpt/NTUA
+    Copyright (C) 2013-2019, 2022-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -91,7 +91,7 @@ Bezier::Bezier(const fvMesh& mesh, const dictionary& dict)
     {
         for (label iCP = 0; iCP < nBezier_; ++iCP)
         {
-            if (confineMovement_[iDir][iCP])
+            if (!confineMovement_[iDir][iCP])
             {
                 activeDesignVariables_[iActive++] = iDir*nBezier_ + iCP;
             }
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.C b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.C
index 89839e07b65c30bd899e6c69a3f0c15ce5711ef4..fc43049d1f9771305e9ca8298a9fa91a6e0658d7 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.C
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2022 PCOpt/NTUA
-    Copyright (C) 2013-2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -350,7 +350,7 @@ bool Foam::NURBS3DVolume::bound
     vector& vec,
     scalar minValue,
     scalar maxValue
-)
+) const
 {
     bool boundPoint(false);
     // Lower value bounding
@@ -427,6 +427,70 @@ void Foam::NURBS3DVolume::determineActiveDesignVariablesAndPoints()
             activeControlPoints_[cpI] = false;
         }
     }
+
+    // Deactivate design varibles if they affect no mesh point
+    confineInertControlPoints();
+}
+
+
+void Foam::NURBS3DVolume::confineInertControlPoints()
+{
+    // Loop over all active control points and check whether they parameterize
+    // at least one mesh point of the ones laying within the morphing box
+    const labelList& map = getMap();
+    const pointVectorField& parametricCoors = getParametricCoordinates();
+
+    const scalarField& knotsU = basisU_.knots();
+    const scalarField& knotsV = basisV_.knots();
+    const scalarField& knotsW = basisW_.knots();
+
+    const label pU = basisU_.degree();
+    const label pV = basisV_.degree();
+    const label pW = basisW_.degree();
+    forAll(activeControlPoints_, cpI)
+    {
+        if (activeControlPoints_[cpI])
+        {
+            // Get i, j, k corresponding to this control point
+            label i(-1),  j(-1), k(-1);
+            getIJK(i, j, k, cpI);
+
+            const scalar uMin(knotsU[i]);
+            const scalar uMax(knotsU[i + pU + 1]);
+            const scalar vMin(knotsV[j]);
+            const scalar vMax(knotsV[j + pV + 1]);
+            const scalar wMin(knotsW[k]);
+            const scalar wMax(knotsW[k + pW + 1]);
+            bool foundParamPt(false);
+            for (const label paramI : map)
+            {
+                const vector& paramCoors = parametricCoors()[paramI];
+                if
+                (
+                    paramCoors.x() >= uMin && paramCoors.x() < uMax
+                 && paramCoors.y() >= vMin && paramCoors.y() < vMax
+                 && paramCoors.z() >= wMin && paramCoors.z() < wMax
+                )
+                {
+                    foundParamPt = true;
+                    break;
+                }
+            }
+            reduce(foundParamPt, orOp<bool>());
+            if (!foundParamPt)
+            {
+                activeControlPoints_[cpI] = false;
+                activeDesignVariables_[3*cpI] = false;
+                activeDesignVariables_[3*cpI + 1] = false;
+                activeDesignVariables_[3*cpI + 2] = false;
+                Info<< "Disabling control " << cpI
+                    << " and variables "
+                    << "(" << 3*cpI << ", " << 3*cpI+1 << ", " << 3*cpI+2 << ")"
+                    << " since they does not parameterize any mesh point"
+                    << endl;
+            }
+        }
+    }
 }
 
 
@@ -765,7 +829,6 @@ Foam::NURBS3DVolume::NURBS3DVolume
     {
         controlPointsDefinition::New(*this);
     }
-    determineActiveDesignVariablesAndPoints();
 }
 
 
@@ -801,6 +864,114 @@ Foam::autoPtr<Foam::NURBS3DVolume> Foam::NURBS3DVolume::New
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+
+Foam::tmp<Foam::vectorField> Foam::NURBS3DVolume::computeParametricCoordinates
+(
+    const vectorField& points
+) const
+{
+    tmp<vectorField> tparamCoors(tmp<vectorField>::New(points.size(), Zero));
+    vectorField& paramCoors = tparamCoors.ref();
+    // Initialize parametric coordinates based on min/max of control points
+    scalar minX1 = min(cps_.component(0));
+    scalar maxX1 = max(cps_.component(0));
+    scalar minX2 = min(cps_.component(1));
+    scalar maxX2 = max(cps_.component(1));
+    scalar minX3 = min(cps_.component(2));
+    scalar maxX3 = max(cps_.component(2));
+
+    scalar oneOverDenomX(1./(maxX1 - minX1));
+    scalar oneOverDenomY(1./(maxX2 - minX2));
+    scalar oneOverDenomZ(1./(maxX3 - minX3));
+
+    forAll(points, pI)
+    {
+        paramCoors[pI].x() = (points[pI].x() - minX1)*oneOverDenomX;
+        paramCoors[pI].y() = (points[pI].y() - minX2)*oneOverDenomY;
+        paramCoors[pI].z() = (points[pI].z() - minX3)*oneOverDenomZ;
+    }
+
+    // Indices of points that failed to converge
+    // (i.e. are bounded for nMaxBound iters)
+    boolList dropOffPoints(points.size(), false);
+    label nDropedPoints(0);
+
+    // Initial cartesian coordinates
+    tmp<vectorField> tsplinesBasedCoors(coordinates(paramCoors));
+    vectorField& splinesBasedCoors = tsplinesBasedCoors.ref();
+
+    // Newton-Raphson loop to compute parametric coordinates
+    // based on cartesian coordinates and the known control points
+    Info<< "Mapping of mesh points to parametric space for box " << name_
+        << " ..." << endl;
+    // Do loop on a point-basis and check residual of each point equation
+    label maxIterNeeded(0);
+    forAll(points, pI)
+    {
+        label iter(0);
+        label nBoundIters(0);
+        vector res(GREAT, GREAT, GREAT);
+        do
+        {
+            vector& uVec = paramCoors[pI];
+            vector& coorPointI = splinesBasedCoors[pI];
+            uVec += ((inv(JacobianUVW(uVec))) & (points[pI] - coorPointI));
+            // Bounding might be needed for the first iterations
+            // If multiple bounds happen, point is outside of the control
+            // boxes and should be discarded
+            if (bound(uVec))
+            {
+                ++nBoundIters;
+            }
+            if (nBoundIters > nMaxBound_)
+            {
+                dropOffPoints[pI] = true;
+                ++nDropedPoints;
+                break;
+            }
+            // Update current cartesian coordinates based on parametric ones
+            coorPointI = coordinates(uVec);
+            // Compute residual
+            res = cmptMag(points[pI] - coorPointI);
+        }
+        while
+        (
+            (iter++ < maxIter_)
+         && (
+                   res.component(0) > tolerance_
+                || res.component(1) > tolerance_
+                || res.component(2) > tolerance_
+            )
+        );
+        if (iter > maxIter_)
+        {
+            WarningInFunction
+                << "Mapping to parametric space for point " << pI
+                << " failed." << endl
+                << "Residual after " << maxIter_ + 1 << " iterations : "
+                << res << endl
+                << "parametric coordinates " <<  paramCoors[pI]
+                <<  endl
+                << "Local system coordinates " <<  points[pI] <<  endl
+                << "Threshold residual per direction : " << tolerance_
+                << endl;
+        }
+        maxIterNeeded = max(maxIterNeeded, iter);
+    }
+    reduce(maxIterNeeded, maxOp<label>());
+
+    label nParameterizedPoints = points.size() - nDropedPoints;
+
+    reduce(nDropedPoints, sumOp<label>());
+    reduce(nParameterizedPoints, sumOp<label>());
+    Info<< "Found " << nDropedPoints
+        << " to discard from morphing boxes" << endl;
+    Info<< "Keeping " << nParameterizedPoints
+        << " parameterized points in boxes"  << endl;
+    return tparamCoors;
+}
+
+
 Foam::vector Foam::NURBS3DVolume::volumeDerivativeU
 (
     const scalar u,
@@ -1502,6 +1673,23 @@ Foam::label Foam::NURBS3DVolume::getCPID
 }
 
 
+void Foam::NURBS3DVolume::getIJK
+(
+    label&  i,
+    label&  j,
+    label&  k,
+    const label cpID
+) const
+{
+    const label nCPsU = basisU_.nCPs();
+    const label nCPsV = basisV_.nCPs();
+    k = cpID/(nCPsU*nCPsV);
+    const label inKplaneID = (cpID - k*(nCPsU*nCPsV));
+    j = inKplaneID/nCPsU;
+    i = inKplaneID - j*nCPsU;
+}
+
+
 void Foam::NURBS3DVolume::setControlPoints(const vectorField& newCps)
 {
     if (cps_.size() != newCps.size())
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.H b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.H
index b9e6fbd2b169b5dbd01238630f3719a8eeafde39..4d61349a14a5ed79d68cda2014369a977b9eab17 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.H
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolume.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -170,7 +170,7 @@ protected:
             vector& vec,
             scalar minValue = 1e-7,
             scalar maxValue = 0.999999
-        );
+        ) const;
 
         //- Create lists with active design variables and control points
         void determineActiveDesignVariablesAndPoints();
@@ -184,6 +184,9 @@ protected:
         //- Confine movement in all control points for user-defined directions
         void confineControlPointsDirections();
 
+        //- Deactivate control points if they affect no mesh point
+        void confineInertControlPoints();
+
         //- Confine all three movements for a prescribed control point
         void confineControlPoint(const label cpI);
 
@@ -262,6 +265,13 @@ public:
 
     // Member Functions
 
+        //- Compute parametric coordinates for a given set of points
+        //  (not necessarily the mesh ones)
+        tmp<vectorField> computeParametricCoordinates
+        (
+            const vectorField& points
+        ) const;
+
         // Derivatives wrt parametric coordinates
 
             //- Volume point derivative wrt u at point u,v,w
@@ -384,6 +394,9 @@ public:
             //- Get control point ID from its I-J-K coordinates
             label getCPID(const label i, const label j, const label k) const;
 
+            //- Get I-J-K coordinates from control point ID
+            void getIJK(label& i, label& j, label& k, const label cpID) const;
+
             //- Set new control points
             //  New values should be on the coordinates system original CPs
             //  were defined
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolumeI.H b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolumeI.H
index 80872f97cd08493e7f6d61ab6ef4e13fb69b127c..8609f43c05431957f69441f39dc224f53ef76778 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolumeI.H
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/NURBS3DVolume/NURBS3DVolumeI.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -97,6 +97,13 @@ const Foam::NURBSbasis& Foam::NURBS3DVolume::basisW() const
 }
 
 
+inline Foam::Vector<Foam::label>
+Foam::NURBS3DVolume::nCPsPerDirection() const
+{
+    return Vector<label>(basisU_.nCPs(), basisV_.nCPs(), basisW_.nCPs());
+}
+
+
 const Foam::fvMesh& Foam::NURBS3DVolume::mesh() const
 {
     return mesh_;
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cartesian/NURBS3DVolumeCartesian.C b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cartesian/NURBS3DVolumeCartesian.C
index 1ab353df04721110ac6be5ce85e6d87a570bbca5..5753398ae67ac4e7e6b47540216d991f1e5ce2b7 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cartesian/NURBS3DVolumeCartesian.C
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cartesian/NURBS3DVolumeCartesian.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -89,6 +89,7 @@ void Foam::NURBS3DVolumeCartesian::updateLocalCoordinateSystem
     if (computeParamCoors)
     {
         getParametricCoordinates();
+        determineActiveDesignVariablesAndPoints();
     }
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.C b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.C
index c132d15c3e6386a5d6d05c1a5663eadb4a756a22..31c10725aa4a1b797da4998e2ffe4d8d0c2c0cc3 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.C
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -137,6 +137,7 @@ Foam::NURBS3DVolumeCylindrical::NURBS3DVolumeCylindrical
     if (computeParamCoors)
     {
         getParametricCoordinates();
+        determineActiveDesignVariablesAndPoints();
     }
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.H b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.H
index 2719ccd2332f2f211025d908c8366b0b423fbbfd..52f0730b12248c13cb5e5ddc1c7235fd055fe0a5 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.H
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/cylindrical/NURBS3DVolumeCylindrical.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -106,6 +106,15 @@ public:
 
     //- Destructor
     virtual ~NURBS3DVolumeCylindrical() = default;
+
+
+    // Access
+
+        //- Get the origin vector
+        inline const vector& getOrigin() const
+        {
+            return origin_;
+        }
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.C b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.C
index c5515ba8436f0d99ef8ffb7cbeed9923b93efcdf..4ff3278479ba6b193774eda72d3e993dd8bdc5ae 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.C
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -139,13 +139,13 @@ const vectorField& volBSplinesBase::getControlPoints(const label& iNURB) const
 
 vectorField volBSplinesBase::getAllControlPoints() const
 {
-    vectorField totalCPs(0);
+    DynamicList<vector> totalCPs(0);
     forAll(volume_, iNURB)
     {
-        totalCPs.append(volume_[iNURB].getControlPoints());
+        totalCPs.push_back(volume_[iNURB].getControlPoints());
     }
 
-    return totalCPs;
+    return vectorField(std::move(totalCPs));
 }
 
 
@@ -182,6 +182,12 @@ labelList volBSplinesBase::getStartCpID() const
 }
 
 
+labelList volBSplinesBase::getStartVarID() const
+{
+    return 3*getStartCpID();
+}
+
+
 label volBSplinesBase::findBoxID(const label cpI) const
 {
     const labelList startCPID(getStartCpID());
@@ -200,6 +206,33 @@ label volBSplinesBase::findBoxID(const label cpI) const
 }
 
 
+Vector<label> volBSplinesBase::decomposeDV(const label varID) const
+{
+    Vector<label> decomposed;
+    labelList startVarID = getStartVarID();
+    label boxID(-1);
+    for (label iBox = 0; iBox < startVarID.size() - 1 ; ++iBox)
+    {
+        if (varID >= startVarID[iBox] && varID < startVarID[iBox + 1])
+        {
+            boxID = iBox;
+            break;
+        }
+    }
+    const label localVarID = varID - startVarID[boxID];
+    decomposed.x() = boxID;
+    decomposed.y() = localVarID/3;
+    decomposed.z() = localVarID%3;
+    DebugInfo
+        << "varID " << varID
+        << " belongs to box " << decomposed.x()
+        << " cpLocal " << decomposed.y()
+        << " dir " << decomposed.z()
+        << endl;
+    return decomposed;
+}
+
+
 const Foam::labelList& volBSplinesBase::getActiveDesignVariables() const
 {
     return activeDesignVariables_;
@@ -243,6 +276,43 @@ Foam::scalar Foam::volBSplinesBase::computeMaxBoundaryDisplacement
 }
 
 
+Foam::tmp<vectorField> Foam::volBSplinesBase::computeBoundaryDisplacement
+(
+    const vectorField& controlPointsMovement,
+    const labelList& patchesToBeMoved
+)
+{
+    auto tdisplacement(tmp<vectorField>::New(mesh_.nPoints(), Zero));
+    vectorField& displacement = tdisplacement.ref();
+
+    label pastControlPoints(0);
+    forAll(volume_, iNURB)
+    {
+        const label nb(volume_[iNURB].getControlPoints().size());
+        vectorField localControlPointsMovement(nb, Zero);
+
+        // Set localControlPointsMovement
+        forAll(localControlPointsMovement, iCPM)
+        {
+            localControlPointsMovement[iCPM] =
+                controlPointsMovement[pastControlPoints + iCPM];
+        }
+
+        displacement +=
+            volume_[iNURB].computeNewBoundaryPoints
+            (
+                localControlPointsMovement,
+                patchesToBeMoved
+            )
+          - mesh_.points();
+
+        pastControlPoints += nb;
+    }
+
+    return tdisplacement;
+}
+
+
 void Foam::volBSplinesBase::boundControlPointMovement
 (
     vectorField& controlPointsMovement
diff --git a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.H b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.H
index 6855f097976411cb889473ce678129fa07625325..93e03929f00b37d123dc221a5ec0413053eef6f9 100644
--- a/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.H
+++ b/src/optimisation/adjointOptimisation/adjoint/parameterization/NURBS/NURBS3DVolume/volBSplinesBase/volBSplinesBase.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -129,9 +129,15 @@ public:
         //- Get start CP ID for each box
         labelList getStartCpID() const;
 
+        //- Get start CP ID for each box
+        labelList getStartVarID() const;
+
         //- Find box of certain control point
         label findBoxID(const label cpI) const;
 
+        //- From design variable ID, return boxID, cpID and direction
+        Vector<label> decomposeDV(const label dvI) const;
+
         //- Get active design variables
         const labelList& getActiveDesignVariables() const;
 
@@ -143,6 +149,13 @@ public:
             const labelList& patchesToBeMoved
         );
 
+        //- Get the updated boundary points only
+        tmp<vectorField> computeBoundaryDisplacement
+        (
+            const vectorField& controlPointsMovement,
+            const labelList& patchesToBeMoved
+        );
+
         //- Bound control points movement
         void boundControlPointMovement
         (
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.C b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.C
index cbe65c281a7bb921dce0ff10e0380fee3b91edb7..ed51b74da3c895920dd3cbaf188c848ac93fc888 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -28,6 +28,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "adjointSolverManager.H"
+#include "primalSolver.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -42,6 +43,7 @@ namespace Foam
 Foam::adjointSolverManager::adjointSolverManager
 (
     fvMesh& mesh,
+    autoPtr<designVariables>& designVars,
     const word& managerType,
     const dictionary& dict,
     bool overrideUseSolverName
@@ -62,14 +64,18 @@ Foam::adjointSolverManager::adjointSolverManager
     mesh_(mesh),
     dict_(dict),
     managerName_(dict.dictName()),
+    managerType_(managerType),
     primalSolverName_(dict.get<word>("primalSolver")),
     adjointSolvers_(0),
     objectiveSolverIDs_(0),
-    constraintSolverIDs_(0),
+    oneSidedConstraintSolverIDs_(0),
+    doubleSidedConstraintSolverIDs_(0),
     operatingPointWeight_
     (
         dict.getOrDefault<scalar>("operatingPointWeight", 1)
-    )
+    ),
+    nActiveAdjointSolvers_(0),
+    designVars_(designVars)
 {
     dictionary& adjointSolversDict =
         const_cast<dictionary&>(dict.subDict("adjointSolvers"));
@@ -77,14 +83,16 @@ Foam::adjointSolverManager::adjointSolverManager
     const wordList adjSolverNames = adjointSolversDict.toc();
     adjointSolvers_.setSize(adjSolverNames.size());
     objectiveSolverIDs_.setSize(adjSolverNames.size());
-    constraintSolverIDs_.setSize(adjSolverNames.size());
+    oneSidedConstraintSolverIDs_.setSize(adjSolverNames.size());
+    doubleSidedConstraintSolverIDs_.setSize(adjSolverNames.size());
     label nObjectives(0);
-    label nConstraints(0);
+    label nOneSidedConstraints(0);
+    label nDoubleSidedConstraints(0);
     forAll(adjSolverNames, namei)
     {
         dictionary& solverDict =
             adjointSolversDict.subDict(adjSolverNames[namei]);
-        if (overrideUseSolverName || adjointSolvers_.size() > 1)
+        if (overrideUseSolverName)
         {
             solverDict.add<bool>("useSolverNameForFields", true);
         }
@@ -96,13 +104,21 @@ Foam::adjointSolverManager::adjointSolverManager
                 mesh_,
                 managerType,
                 solverDict,
-                primalSolverName_
+                primalSolverName_,
+                adjSolverNames[namei]
             )
         );
-
-        if (adjointSolvers_[namei].isConstraint())
+        if (adjointSolvers_[namei].active())
+        {
+            nActiveAdjointSolvers_++;
+        }
+        if (adjointSolvers_[namei].isDoubleSidedConstraint())
         {
-            constraintSolverIDs_[nConstraints++] = namei;
+            doubleSidedConstraintSolverIDs_[nDoubleSidedConstraints++] = namei;
+        }
+        else if (adjointSolvers_[namei].isConstraint())
+        {
+            oneSidedConstraintSolverIDs_[nOneSidedConstraints++] = namei;
         }
         else
         {
@@ -110,10 +126,17 @@ Foam::adjointSolverManager::adjointSolverManager
         }
     }
     objectiveSolverIDs_.setSize(nObjectives);
-    constraintSolverIDs_.setSize(nConstraints);
+    oneSidedConstraintSolverIDs_.setSize(nOneSidedConstraints);
+    doubleSidedConstraintSolverIDs_.setSize(nDoubleSidedConstraints);
+
+    Info<< "Found " << nOneSidedConstraints
+        << " adjoint solvers acting as single-sided constraints" << endl;
 
-    Info<< "Found " << nConstraints
-        << " adjoint solvers acting as constraints" << endl;
+    Info<< "Found " << nDoubleSidedConstraints
+        << " adjoint solvers acting as double-sided constraints" << endl;
+
+    Info<< "Found " << nActiveAdjointSolvers_
+        << " active adjoint solvers" << endl;
 
     // Having more than one non-aggregated objectives per operating point
     // is needlessly expensive. Issue a warning
@@ -122,7 +145,7 @@ Foam::adjointSolverManager::adjointSolverManager
         WarningInFunction
             << "Number of adjoint solvers corresponding to objectives "
             << "is greater than 1 (" << objectiveSolverIDs_.size() << ")" << nl
-            << "Consider aggregating your objectives to one" << endl;
+            << "Consider aggregating your objectives to one\n" << endl;
     }
 }
 
@@ -177,15 +200,66 @@ Foam::adjointSolverManager::adjointSolvers()
 }
 
 
+Foam::wordList Foam::adjointSolverManager::adjointSolversNames() const
+{
+    wordList names(adjointSolvers_.size());
+    forAll(adjointSolvers_, sI)
+    {
+        names[sI]  = adjointSolvers_[sI].name();
+    }
+    return names;
+}
+
+
 Foam::scalar Foam::adjointSolverManager::operatingPointWeight() const
 {
     return operatingPointWeight_;
 }
 
 
+Foam::label Foam::adjointSolverManager::nActiveAdjointSolvers() const
+{
+    return nActiveAdjointSolvers_;
+}
+
+
+Foam::label Foam::adjointSolverManager::nActiveAdjointSolvers
+(
+    const dictionary& dict
+)
+{
+    const dictionary& adjointSolversDict = dict.subDict("adjointSolvers");
+    const wordList adjSolverNames = adjointSolversDict.toc();
+    label n(0);
+    Switch active(true);
+    forAll(adjSolverNames, namei)
+    {
+        active = adjointSolversDict.subDict(adjSolverNames[namei]).
+            getOrDefault<bool>("active", true);
+        if (active)
+        {
+            n++;
+        }
+    }
+    return n;
+}
+
+
 Foam::label Foam::adjointSolverManager::nConstraints() const
 {
-    return constraintSolverIDs_.size();
+    return nOneSidedConstraints() + 2*nDoubleSidedConstraints();
+}
+
+
+Foam::label Foam::adjointSolverManager::nOneSidedConstraints() const
+{
+    return oneSidedConstraintSolverIDs_.size();
+}
+
+
+Foam::label Foam::adjointSolverManager::nDoubleSidedConstraints() const
+{
+    return doubleSidedConstraintSolverIDs_.size();
 }
 
 
@@ -197,17 +271,29 @@ Foam::label Foam::adjointSolverManager::nObjectives() const
 
 Foam::label Foam::adjointSolverManager::nAdjointSolvers() const
 {
-    return nConstraints() + nObjectives();
+    return nOneSidedConstraints() + nDoubleSidedConstraints() + nObjectives();
 }
 
 
 void Foam::adjointSolverManager::solveAdjointEquations()
 {
+    //  Solve all adjoint equations of this adjointSolverManager
     for (adjointSolver& solver : adjointSolvers_)
     {
+        // Update all primal-based quantities needed by the adjoint equations
+        solver.updatePrimalBasedQuantities();
+
         // Solve the adjoint equations taking into consideration the weighted
         // contribution of possibly multiple objectives
         solver.solve();
+
+        // Compute sensitivities and force writing to the adjoint dictionary
+        // if this an output time
+        solver.computeObjectiveSensitivities(designVars_);
+        if (mesh_.time().writeTime())
+        {
+            solver.regIOobject::write(true);
+        }
     }
 }
 
@@ -223,7 +309,7 @@ Foam::adjointSolverManager::aggregateSensitivities()
     {
         // Sum contributions
         const scalarField& solverSens =
-            adjointSolvers_[solveri].getObjectiveSensitivities();
+            adjointSolvers_[solveri].getObjectiveSensitivities(designVars_);
 
         if (sens.empty())
         {
@@ -239,17 +325,28 @@ Foam::adjointSolverManager::aggregateSensitivities()
 Foam::PtrList<Foam::scalarField>
 Foam::adjointSolverManager::constraintSensitivities()
 {
-    PtrList<scalarField> constraintSens(constraintSolverIDs_.size());
-    forAll(constraintSens, cI)
+    PtrList<scalarField> constraintSens(nConstraints());
+    // Only one-sided constraints
+    label cI(0);
+    for (const label consI : oneSidedConstraintSolverIDs_)
     {
-        label consI = constraintSolverIDs_[cI];
         constraintSens.set
         (
-            cI,
-            new scalarField(adjointSolvers_[consI].getObjectiveSensitivities())
+            cI++,
+            new scalarField
+                (adjointSolvers_[consI].getObjectiveSensitivities(designVars_))
         );
     }
 
+    // Two-sided constraints. Negated left-most side sensitivities
+    for (const label consI : doubleSidedConstraintSolverIDs_)
+    {
+        scalarField sens
+            (adjointSolvers_[consI].getObjectiveSensitivities(designVars_));
+        constraintSens.set(cI++, new scalarField(  sens));
+        constraintSens.set(cI++, new scalarField(- sens));
+    }
+
     return constraintSens;
 }
 
@@ -258,7 +355,7 @@ void Foam::adjointSolverManager::computeAllSensitivities()
 {
     for (adjointSolver& adjSolver : adjointSolvers_)
     {
-        adjSolver.computeObjectiveSensitivities();
+        adjSolver.computeObjectiveSensitivities(designVars_);
     }
 }
 
@@ -288,16 +385,24 @@ Foam::scalar Foam::adjointSolverManager::objectiveValue()
 
 Foam::tmp<Foam::scalarField> Foam::adjointSolverManager::constraintValues()
 {
-    tmp<scalarField> tconstraintValues
-    (
-        new scalarField(constraintSolverIDs_.size(), Zero)
-    );
+    auto tconstraintValues(tmp<scalarField>::New(nConstraints(), Zero));
     scalarField& constraintValues = tconstraintValues.ref();
-    forAll(constraintValues, cI)
+    label cI(0);
+    // One-sided constraints only
+    for (const label consI : oneSidedConstraintSolverIDs_)
     {
         objectiveManager& objManager =
-            adjointSolvers_[constraintSolverIDs_[cI]].getObjectiveManager();
-        constraintValues[cI] = objManager.print();
+            adjointSolvers_[consI].getObjectiveManager();
+        constraintValues[cI++] = objManager.print();
+    }
+    // Double-sided constraints
+    // Objective value of the left-most side is negated
+    for (const label consI : doubleSidedConstraintSolverIDs_)
+    {
+        objectiveManager& objManager =
+            adjointSolvers_[consI].getObjectiveManager();
+        constraintValues[cI++] = objManager.print(false);
+        constraintValues[cI++] = objManager.print(true);
     }
 
     return tconstraintValues;
@@ -316,4 +421,10 @@ void Foam::adjointSolverManager::updatePrimalBasedQuantities(const word& name)
 }
 
 
+bool Foam::adjointSolverManager::isMaster() const
+{
+    return mesh_.lookupObject<primalSolver>(primalSolverName_).isMaster();
+}
+
+
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.H b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.H
index 5ebb2bcbc6adec1b4c2de237baffa74d6b277c20..7ab01a1fc3f644846ccd056c7a166534335bb64b 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolverManager/adjointSolverManager.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -77,16 +77,24 @@ protected:
 
         const word managerName_;
 
+        const word managerType_;
+
         const word primalSolverName_;
 
         PtrList<adjointSolver> adjointSolvers_;
 
         labelList objectiveSolverIDs_;
 
-        labelList constraintSolverIDs_;
+        labelList oneSidedConstraintSolverIDs_;
+
+        labelList doubleSidedConstraintSolverIDs_;
 
         scalar operatingPointWeight_;
 
+        label nActiveAdjointSolvers_;
+
+        autoPtr<designVariables>& designVars_;
+
 
 public:
 
@@ -99,6 +107,7 @@ public:
         adjointSolverManager
         (
             fvMesh& mesh,
+            autoPtr<designVariables>& designVars,
             const word& managerType,
             const dictionary& dict,
             bool overrideUseSolverName
@@ -111,7 +120,7 @@ public:
 
     // Member Functions
 
-        virtual bool readDict(const dictionary& dict);
+        bool readDict(const dictionary& dict);
 
 
         // Access
@@ -131,12 +140,33 @@ public:
             //- Non-const access to adjoint solvers
             PtrList<adjointSolver>& adjointSolvers();
 
+            //- Return the names of all adjointSolvers
+            wordList adjointSolversNames() const;
+
             //- Const access to adjoint solvers
             scalar operatingPointWeight() const;
 
-            //- Number of adjoint solvers corresponding to constraints
+            //- Return number of active adjoint solvers, either corresponding
+            //  to objectives or constraints
+            label nActiveAdjointSolvers() const;
+
+            //- Static function returning the number of active adjoint
+            //  solvers reading dict
+            static label nActiveAdjointSolvers(const dictionary& dict);
+
+            //- Number of constraints
+            //  Is the sum of one-sided and double-sided constraints, the
+            //  latter multiplied by two
             label nConstraints() const;
 
+            //- Number of adjoint solvers corresponding to one-sided
+            //- constraints
+            label nOneSidedConstraints() const;
+
+            //- Number of adjoint solvers corresponding to double-sided
+            //- constraints
+            label nDoubleSidedConstraints() const;
+
             //- Number of adjoint solvers corresponding to objectives
             label nObjectives() const;
 
@@ -148,13 +178,13 @@ public:
 
             //- Update objective function-related values and solve adjoint
             //- equations
-            virtual void solveAdjointEquations();
+            void solveAdjointEquations();
 
             //- Aggregate sensitivities from various adjoint solvers
-            virtual tmp<scalarField> aggregateSensitivities();
+            tmp<scalarField> aggregateSensitivities();
 
             //- Get constraint sensitivities. One scalarField per constraint
-            virtual PtrList<scalarField> constraintSensitivities();
+            PtrList<scalarField> constraintSensitivities();
 
             //- Compute sensitivities for all adjoint solvers
             //- (both objective- and constraint-related ones)
@@ -167,12 +197,17 @@ public:
             scalar objectiveValue();
 
             //- Get constraint values
-            virtual tmp<scalarField> constraintValues();
+            tmp<scalarField> constraintValues();
 
             //- Update fields related to primal solution.
             //  For instance, primal fields of adjoint turbulence models
             void updatePrimalBasedQuantities(const word& name);
 
+            //- Whether the primal solver corresponding to the
+            //- adjointSolverManager is the master one, in case of coupled
+            //- solvers
+            bool isMaster() const;
+
 
         // IO
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.C b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.C
index e212b596dc8975f9de1af1eaebb9add2fceb6447..9510d1977d0617d428bdb38a095d0066a0275ffb 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -28,6 +28,8 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "adjointSolver.H"
+#include "adjointSensitivity.H"
+#include "designVariables.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -38,6 +40,39 @@ namespace Foam
 }
 
 
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::adjointSolver::allocateSensitivities()
+{
+    if (computeSensitivities_)
+    {
+        adjointSensitivity_.reset
+        (
+            adjointSensitivity::New(mesh_, designVarsDict(), *this).ptr()
+        );
+    }
+}
+
+
+Foam::dictionary Foam::adjointSolver::designVarsDict() const
+{
+    // Re-read optimisationDict here to cover multi-region cases
+    return
+        IOdictionary
+        (
+            IOobject
+            (
+                "optimisationDict",
+                mesh_.time().globalPath()/"system",
+                mesh_,
+                IOobject::MUST_READ,
+                IOobject::NO_WRITE,
+                false
+            )
+        ).subDict("optimisation").subDict("designVariables");
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::adjointSolver::adjointSolver
@@ -45,31 +80,38 @@ Foam::adjointSolver::adjointSolver
     fvMesh& mesh,
     const word& managerType,
     const dictionary& dict,
-    const word& primalSolverName
+    const word& primalSolverName,
+    const word& solverName
 )
 :
-    solver(mesh, managerType, dict),
+    solver(mesh, managerType, dict, solverName),
     primalSolverName_(primalSolverName),
-    objectiveManagerPtr_
+    objectiveManager_
     (
-        objectiveManager::New
-        (
-            mesh,
-            dict.subDict("objectives"),
-            solverName_,
-            primalSolverName
-        )
+        mesh,
+        dict.subDict("objectives"),
+        solverName_,
+        primalSolverName
     ),
     sensitivities_(nullptr),
     computeSensitivities_
     (
         dict.getOrDefault<bool>("computeSensitivities", true)
     ),
-    isConstraint_(dict.getOrDefault<bool>("isConstraint", false))
+    isConstraint_(dict.getOrDefault<bool>("isConstraint", false)),
+    isDoubleSidedConstraint_
+        (dict.getOrDefault<bool>("isDoubleSidedConstraint", false)),
+    adjointSensitivity_(nullptr)
 {
+    // Force solver to not be a (single-sided) contraint if flagged as
+    // double-sided
+    if (isDoubleSidedConstraint_)
+    {
+        isConstraint_ = false;
+    }
     // Update objective-related quantities to get correct derivatives
     // in case of continuation
-    objectiveManagerPtr_().update();
+    objectiveManager_.update();
 }
 
 
@@ -80,7 +122,8 @@ Foam::autoPtr<Foam::adjointSolver> Foam::adjointSolver::New
     fvMesh& mesh,
     const word& managerType,
     const dictionary& dict,
-    const word& primalSolverName
+    const word& primalSolverName,
+    const word& solverName
 )
 {
     const word solverType(dict.get<word>("type"));
@@ -100,29 +143,13 @@ Foam::autoPtr<Foam::adjointSolver> Foam::adjointSolver::New
 
     return autoPtr<adjointSolver>
     (
-        ctorPtr(mesh, managerType, dict, primalSolverName)
+        ctorPtr(mesh, managerType, dict, primalSolverName, solverName)
     );
 }
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-const Foam::primalSolver& Foam::adjointSolver::getPrimalSolver() const
-{
-    return mesh_.lookupObject<primalSolver>(primalSolverName_);
-}
-
-
-Foam::primalSolver& Foam::adjointSolver::getPrimalSolver()
-{
-    return
-        const_cast<primalSolver&>
-        (
-            mesh_.lookupObject<primalSolver>(primalSolverName_)
-        );
-}
-
-
 bool Foam::adjointSolver::readDict(const dictionary& dict)
 {
     if (solver::readDict(dict))
@@ -130,7 +157,12 @@ bool Foam::adjointSolver::readDict(const dictionary& dict)
         computeSensitivities_ =
             dict.getOrDefault<bool>("computeSensitivities", true);
 
-        objectiveManagerPtr_->readDict(dict.subDict("objectives"));
+        objectiveManager_.readDict(dict.subDict("objectives"));
+
+        if (adjointSensitivity_)
+        {
+            adjointSensitivity_().readDict(designVarsDict());
+        }
 
         return true;
     }
@@ -139,38 +171,94 @@ bool Foam::adjointSolver::readDict(const dictionary& dict)
 }
 
 
-const Foam::objectiveManager& Foam::adjointSolver::getObjectiveManager() const
+bool Foam::adjointSolver::includeDistance() const
+{
+    return false;
+}
+
+
+Foam::dimensionSet Foam::adjointSolver::daDimensions() const
+{
+    NotImplemented;
+    return dimless;
+}
+
+
+Foam::dimensionSet Foam::adjointSolver::maDimensions() const
+{
+    NotImplemented;
+    return dimless;
+}
+
+
+Foam::tmp<Foam::volScalarField> Foam::adjointSolver::adjointEikonalSource()
 {
-    return objectiveManagerPtr_();
+    return nullptr;
 }
 
 
-Foam::objectiveManager& Foam::adjointSolver::getObjectiveManager()
+Foam::tmp<Foam::volScalarField> Foam::adjointSolver::yWall() const
 {
-    return objectiveManagerPtr_();
+    return nullptr;
 }
 
 
-void Foam::adjointSolver::postLoop()
+void Foam::adjointSolver::computeObjectiveSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
 {
-    addProfiling(adjointSolver, "adjointSolver::postLoop");
-    computeObjectiveSensitivities();
-    // The solver dictionary has been already written after the termination
-    // of the adjoint loop. Force re-writing it to include the sensitivities
-    // as well
-    regIOobject::write(true);
+    if (computeSensitivities_)
+    {
+        preCalculateSensitivities();
+        const scalarField& sens =
+            adjointSensitivity_->calculateSensitivities(designVars);
+        if (!sensitivities_)
+        {
+            sensitivities_.reset(new scalarField(sens.size(), Zero));
+        }
+        sensitivities_.ref() = sens;
+    }
+    else
+    {
+        sensitivities_.reset(new scalarField());
+    }
 }
 
 
-bool Foam::adjointSolver::isConstraint()
+const Foam::scalarField& Foam::adjointSolver::getObjectiveSensitivities
+(
+    autoPtr<designVariables>& designVars
+)
 {
-    return isConstraint_;
+    if (!sensitivities_)
+    {
+        // Read sensitivities from file in case of continuation
+        // Done here and not in allocateSensitivities since the size of the
+        // design variables and, hence, the sensitivities is not known there
+        if (dictionary::found("sensitivities"))
+        {
+            sensitivities_ =
+                tmp<scalarField>::New
+                    ("sensitivities", *this, designVars().size());
+        }
+        else
+        {
+            computeObjectiveSensitivities(designVars);
+        }
+    }
+
+    return sensitivities_();
 }
 
 
 void Foam::adjointSolver::clearSensitivities()
 {
-    sensitivities_.clear();
+    if (computeSensitivities_)
+    {
+        adjointSensitivity_->clearSensitivities();
+        sensitivities_.clear();
+    }
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.H b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.H
index f3a7c8e765441e8b3c2d95c9ff14c89ea0bad6bd..6a1e228a3e2d8889c2253269e7747a870eb175c0 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -42,8 +42,8 @@ Description
 #include "IOdictionary.H"
 #include "solver.H"
 #include "objectiveManager.H"
-#include "sensitivity.H"
 #include "primalSolver.H"
+#include "adjointSensitivity.H"
 #include "runTimeSelectionTables.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -51,6 +51,8 @@ Description
 namespace Foam
 {
 
+class designVariables;
+
 /*---------------------------------------------------------------------------*\
                         Class adjointSolver Declaration
 \*---------------------------------------------------------------------------*/
@@ -78,7 +80,7 @@ protected:
         const word primalSolverName_;
 
         //- Object to manage objective functions
-        autoPtr<objectiveManager> objectiveManagerPtr_;
+        objectiveManager objectiveManager_;
 
         //- Sensitivities field
         tmp<scalarField> sensitivities_;
@@ -89,6 +91,29 @@ protected:
         //- Is the adjoint solver used to tackle a constraint
         bool isConstraint_;
 
+        //- Is the adjoint solver used to tackle a double-sided constraint
+        bool isDoubleSidedConstraint_;
+
+        //- Sensitivity Derivatives engine
+        autoPtr<adjointSensitivity> adjointSensitivity_;
+
+
+    // Protected Member Functions
+
+        //- Actions to be performed before calculating sensitivities
+        //  Does noting in base
+        virtual void preCalculateSensitivities()
+        {}
+
+        //- Allocate the sensitivity derivatives
+        //  Since parts of the sensitivities depend on virtual functions
+        //  implemented within derived classes, the actual allocation should
+        //  happen there
+        void allocateSensitivities();
+
+        //- Return the dictionary corresponding to the design variables
+        dictionary designVarsDict() const;
+
 
 public:
 
@@ -109,9 +134,10 @@ public:
                 fvMesh& mesh,
                 const word& managerType,
                 const dictionary& dict,
-                const word& primalSolverName
+                const word& primalSolverName,
+                const word& solverName
             ),
-            (mesh, managerType, dict, primalSolverName)
+            (mesh, managerType, dict, primalSolverName, solverName)
         );
 
 
@@ -123,7 +149,8 @@ public:
             fvMesh& mesh,
             const word& managerType,
             const dictionary& dict,
-            const word& primalSolverName
+            const word& primalSolverName,
+            const word& solverName
         );
 
 
@@ -135,7 +162,8 @@ public:
             fvMesh& mesh,
             const word& managerType,
             const dictionary& dict,
-            const word& primalSolverName
+            const word& primalSolverName,
+            const word& solverName
         );
 
 
@@ -150,47 +178,63 @@ public:
             virtual bool readDict(const dictionary& dict);
 
             //- Return the primal solver name
-            const word& primalSolverName() const
-            {
-                return primalSolverName_;
-            }
+            inline const word& primalSolverName() const;
 
             //- Return a const-reference to the primal solver
             //- corresponding to this adjoint solver
-            const primalSolver& getPrimalSolver() const;
+            inline const primalSolver& getPrimalSolver() const;
 
             //- Return a non const-reference to the primal solver
             //- corresponding to this adjoint solver
-            primalSolver& getPrimalSolver();
+            inline primalSolver& getPrimalSolver();
 
             //- Return a const reference to the objective manager
-            const objectiveManager& getObjectiveManager() const;
+            inline const objectiveManager& getObjectiveManager() const;
 
             //- Return a reference to the objective manager
-            objectiveManager& getObjectiveManager();
+            inline objectiveManager& getObjectiveManager();
 
+            //- Is the solving referring to a constraint
+            inline bool isConstraint();
 
-        // Evolution
+            //- Is the solving referring to a double-sided constraint
+            inline bool isDoubleSidedConstraint();
 
-            //- Functions to be called after loop
-            //  Computes adjoint sensitivities
-            virtual void postLoop();
+            //- Does the adjoint to an equation computing distances need to
+            //- taken into consideration
+            virtual bool includeDistance() const;
 
-            //- Compute sensitivities of the underlaying objectives
-            virtual void computeObjectiveSensitivities() = 0;
+            //- Return the dimensions of the adjoint distance field
+            virtual dimensionSet daDimensions() const;
 
-            //- Is the solving referring to a constraint
-            virtual bool isConstraint();
+            //- Return the dimensions of the adjoint grid displacement variable
+            virtual dimensionSet maDimensions() const;
+
+            //- Return the source the adjoint eikonal equation
+            virtual tmp<volScalarField> adjointEikonalSource();
+
+            //- Return the distance field, to be used in the solution of the
+            //- adjoint eikonal PDE
+            virtual tmp<volScalarField> yWall() const;
+
+
+        // Evolution
+
+            //- Compute sensitivities of the underlaying objectives
+            virtual void computeObjectiveSensitivities
+            (
+                autoPtr<designVariables>& designVars
+            );
 
             //- Grab a reference to the computed sensitivities
-            virtual const scalarField& getObjectiveSensitivities() = 0;
+            virtual const scalarField& getObjectiveSensitivities
+            (
+                autoPtr<designVariables>& designVars
+            );
 
             //- Clears the sensitivity field known by the adjoint solver
             virtual void clearSensitivities();
 
-            //- Return the base sensitivity object
-            virtual sensitivity& getSensitivityBase() = 0;
-
             //- Update primal based quantities, e.g. the primal fields
             //- in adjoint turbulence models
             //  Does nothing in the base
@@ -198,6 +242,67 @@ public:
 
             //- Write the sensitivity derivatives
             virtual bool writeData(Ostream& os) const;
+
+            // Functions related to the computation of sensitivity derivatives.
+            // All functions get the field to accumulate their contribution on
+            // as an argument and should be implemented by the derived classes
+
+                // Shape optimisation
+
+                    //- Compute the multiplier for grad(dxdb)
+                    //  Used in shape sensitivity derivatives, computed with
+                    //  the FI and E-SI approaches
+                    virtual void accumulateGradDxDbMultiplier
+                    (
+                        volTensorField& gradDxDbMult,
+                        const scalar dt
+                    )
+                    {}
+
+                    //- Compute the multiplier for div(dxdb)
+                    //  Used in shape sensitivity derivatives, computed with
+                    //  the FI and E-SI approaches
+                    virtual void accumulateDivDxDbMultiplier
+                    (
+                        autoPtr<scalarField>& divDxDbMult,
+                        const scalar dt
+                    )
+                    {}
+
+                    //- Accumulate the multipliers of geometric quantities
+                    //- defined at the boundary, usually through an objective
+                    //- or constraint function
+                    virtual void accumulateGeometryVariationsMultipliers
+                    (
+                        autoPtr<boundaryVectorField>& dSfdbMult,
+                        autoPtr<boundaryVectorField>& dnfdbMult,
+                        autoPtr<boundaryVectorField>& dxdbDirectMult,
+                        autoPtr<pointBoundaryVectorField>& pointDxDirectDbMult,
+                        const labelHashSet& sensitivityPatchIDs,
+                        const scalar dt
+                    )
+                    {}
+
+                    //- Contributions from boundary functions that inlcude
+                    //- geometric aspects in them and change when the geometry
+                    //- is displaced, e.g. rotationWallVelocity
+                    virtual void accumulateBCSensitivityIntegrand
+                    (
+                        autoPtr<boundaryVectorField>& bcDxDbMult,
+                        const labelHashSet& sensitivityPatchIDs,
+                        const scalar dt
+                    )
+                    {}
+
+                    //- Contributions from fvOptions that inlcude
+                    //- geometric aspects in them and change when the geometry
+                    //- is displaced, e.g. MRF
+                    virtual void accumulateOptionsDxDbMultiplier
+                    (
+                        vectorField& optionsDxDbMult,
+                        const scalar dt
+                    )
+                    {}
 };
 
 
@@ -207,6 +312,10 @@ public:
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
+#include "adjointSolverI.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
 #endif
 
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolverI.H b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolverI.H
new file mode 100644
index 0000000000000000000000000000000000000000..cc02748fb1fd72f799af4af7889a4bb0b1b88475
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolverI.H
@@ -0,0 +1,78 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 PCOpt/NTUA
+    Copyright (C) 2021 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+inline const Foam::word& Foam::adjointSolver::primalSolverName() const
+{
+    return primalSolverName_;
+}
+
+
+inline const Foam::primalSolver& Foam::adjointSolver::getPrimalSolver() const
+{
+    return mesh_.lookupObject<primalSolver>(primalSolverName_);
+}
+
+
+inline Foam::primalSolver& Foam::adjointSolver::getPrimalSolver()
+{
+    return
+        const_cast<primalSolver&>
+        (
+            mesh_.lookupObject<primalSolver>(primalSolverName_)
+        );
+}
+
+
+inline const Foam::objectiveManager&
+Foam::adjointSolver::getObjectiveManager() const
+{
+    return objectiveManager_;
+}
+
+
+inline Foam::objectiveManager& Foam::adjointSolver::getObjectiveManager()
+{
+    return objectiveManager_;
+}
+
+
+inline bool Foam::adjointSolver::isConstraint()
+{
+    return isConstraint_;
+}
+
+
+inline bool Foam::adjointSolver::isDoubleSidedConstraint()
+{
+    return isDoubleSidedConstraint_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.C b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.C
index 926071a5b6d0bd339aafae8a86c2a0281beba50a..1b93b635c386c4dee95a86d1994b6cde3bc62dcb 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -58,7 +58,7 @@ Foam::incompressibleAdjointVars& Foam::adjointSimple::allocateVars()
         (
             mesh_,
             solverControl_(),
-            objectiveManagerPtr_(),
+            objectiveManager_,
             primalVars_
         )
     );
@@ -66,20 +66,6 @@ Foam::incompressibleAdjointVars& Foam::adjointSimple::allocateVars()
 }
 
 
-void Foam::adjointSimple::addExtraSchemes()
-{
-    if (adjointVars_.useSolverNameForFields())
-    {
-        WarningInFunction
-            << "useSolverNameForFields is set to true for adjointSolver "
-            << solverName() << nl << tab
-            << "Appending variable names with the solver name" << nl << tab
-            << "Please adjust the necessary entries in fvSchemes and fvSolution"
-            << nl << endl;
-    }
-}
-
-
 void Foam::adjointSimple::continuityErrors()
 {
     const surfaceScalarField& phia = adjointVars_.phiaInst();
@@ -99,6 +85,12 @@ void Foam::adjointSimple::continuityErrors()
 }
 
 
+void Foam::adjointSimple::preCalculateSensitivities()
+{
+    adjointSensitivity_->accumulateIntegrand(scalar(1));
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::adjointSimple::adjointSimple
@@ -106,14 +98,21 @@ Foam::adjointSimple::adjointSimple
     fvMesh& mesh,
     const word& managerType,
     const dictionary& dict,
-    const word& primalSolverName
+    const word& primalSolverName,
+    const word& solverName
 )
 :
-    incompressibleAdjointSolver(mesh, managerType, dict, primalSolverName),
+    incompressibleAdjointSolver
+    (
+        mesh,
+        managerType,
+        dict,
+        primalSolverName,
+        solverName
+    ),
     solverControl_(SIMPLEControl::New(mesh, managerType, *this)),
     adjointVars_(allocateVars()),
-    cumulativeContErr_(Zero),
-    adjointSensitivity_(nullptr)
+    cumulativeContErr_(Zero)
 {
     ATCModel_.reset
     (
@@ -126,7 +125,6 @@ Foam::adjointSimple::adjointSimple
         ).ptr()
     );
 
-    addExtraSchemes();
     setRefCell
     (
         adjointVars_.paInst(),
@@ -134,67 +132,21 @@ Foam::adjointSimple::adjointSimple
         solverControl_().pRefCell(),
         solverControl_().pRefValue()
     );
-
-    if (computeSensitivities_)
-    {
-        const IOdictionary& optDict =
-            mesh.lookupObject<IOdictionary>("optimisationDict");
-
-        adjointSensitivity_.reset
-        (
-            incompressible::adjointSensitivity::New
-            (
-                mesh,
-                optDict.subDict("optimisation").subDict("sensitivities"),
-                *this
-            ).ptr()
-        );
-        // Read stored sensitivities, if they exist
-        // Need to know the size of the sensitivity field, retrieved after the
-        // allocation of the corresponding object
-        if (dictionary::found("sensitivities"))
-        {
-            sensitivities_ =
-                tmp<scalarField>::New
-                (
-                    "sensitivities",
-                    *this,
-                    adjointSensitivity_().getSensitivities().size()
-                );
-        }
-    }
+    allocateSensitivities();
 }
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::adjointSimple::readDict(const dictionary& dict)
+void Foam::adjointSimple::solveIter()
 {
-    if (incompressibleAdjointSolver::readDict(dict))
+    solverControl_().incrementIter();
+    if (solverControl_().performIter())
     {
-        if (adjointSensitivity_)
-        {
-            const IOdictionary& optDict =
-                mesh_.lookupObject<IOdictionary>("optimisationDict");
-
-            adjointSensitivity_().readDict
-            (
-                optDict.subDict("optimisation").subDict("sensitivities")
-            );
-        }
-
-        return true;
+        preIter();
+        mainIter();
+        postIter();
     }
-
-    return false;
-}
-
-
-void Foam::adjointSimple::solveIter()
-{
-    preIter();
-    mainIter();
-    postIter();
 }
 
 
@@ -236,7 +188,7 @@ void Foam::adjointSimple::mainIter()
     UaEqn.boundaryManipulate(Ua.boundaryFieldRef());
 
     // Add sources from volume-based objectives
-    objectiveManagerPtr_().addUaEqnSource(UaEqn);
+    objectiveManager_.addSource(UaEqn);
 
     // Add ATC term
     ATCModel_->addATC(UaEqn);
@@ -321,8 +273,8 @@ void Foam::adjointSimple::mainIter()
     {
         dimensionedScalar maxUa = gMax(mag(Ua)());
         dimensionedScalar maxpa = gMax(mag(pa)());
-        Info<< "Max mag of adjoint velocity = " << maxUa.value() << endl;
-        Info<< "Max mag of adjoint pressure = " << maxpa.value() << endl;
+        Info<< "Max mag (" << Ua.name() << ") = " << maxUa.value() << endl;
+        Info<< "Max mag (" << pa.name() << ") = " << maxpa.value() << endl;
     }
 }
 
@@ -362,67 +314,12 @@ bool Foam::adjointSimple::loop()
 
 void Foam::adjointSimple::preLoop()
 {
-    // Reset mean fields before solving
+    // Reset initial and mean fields before solving
+    adjointVars_.restoreInitValues();
     adjointVars_.resetMeanFields();
 }
 
 
-void Foam::adjointSimple::computeObjectiveSensitivities()
-{
-    if (computeSensitivities_)
-    {
-        adjointSensitivity_->accumulateIntegrand(scalar(1));
-        const scalarField& sens = adjointSensitivity_->calculateSensitivities();
-        if (!sensitivities_)
-        {
-            sensitivities_.reset(new scalarField(sens.size(), Zero));
-        }
-        sensitivities_.ref() = sens;
-    }
-    else
-    {
-        sensitivities_.reset(new scalarField());
-    }
-}
-
-
-const Foam::scalarField& Foam::adjointSimple::getObjectiveSensitivities()
-{
-    if (!sensitivities_)
-    {
-        computeObjectiveSensitivities();
-    }
-
-    return sensitivities_();
-}
-
-
-void Foam::adjointSimple::clearSensitivities()
-{
-    if (computeSensitivities_)
-    {
-        adjointSensitivity_->clearSensitivities();
-        adjointSolver::clearSensitivities();
-    }
-}
-
-
-Foam::sensitivity& Foam::adjointSimple::getSensitivityBase()
-{
-    if (!adjointSensitivity_)
-    {
-        FatalErrorInFunction
-            << "Sensitivity object not allocated" << nl
-            << "Turn computeSensitivities on in "
-            << solverName_
-            << nl << nl
-            << exit(FatalError);
-    }
-
-    return adjointSensitivity_();
-}
-
-
 void Foam::adjointSimple::addMomentumSource(fvVectorMatrix& matrix)
 {
     // Does nothing
@@ -440,7 +337,48 @@ void Foam::adjointSimple::updatePrimalBasedQuantities()
     incompressibleAdjointSolver::updatePrimalBasedQuantities();
 
     // Update objective function related quantities
-    objectiveManagerPtr_->updateAndWrite();
+    objectiveManager_.updateAndWrite();
+}
+
+
+void Foam::adjointSimple::addTopOFvOptions() const
+{
+    // Determine number of variables related to the adjoint turbulence model
+    autoPtr<incompressibleAdjoint::adjointRASModel>& adjointTurbulence =
+        adjointVars_.adjointTurbulence();
+    const wordList& turbVarNames =
+        adjointTurbulence().getAdjointTMVariablesBaseNames();
+    label nTurbVars(turbVarNames.size());
+    if (adjointTurbulence().includeDistance())
+    {
+        nTurbVars++;
+    }
+
+    // Determine names of fields to be added to the dictionary
+    wordList names(1 + nTurbVars);
+    label varID(0);
+    names[varID++] = adjointVars_.UaInst().name();
+    for (const word& turbName : turbVarNames)
+    {
+        names[varID++] = turbName;
+    }
+    if (adjointTurbulence().includeDistance())
+    {
+        names[varID++] =
+            word(useSolverNameForFields() ? "da" + solverName_ : "da");
+    }
+
+    // Add entries to dictionary
+    const word dictName("topOSource" + solverName_);
+    dictionary optionDict(dictName);
+    optionDict.add<word>("type", "topOSource");
+    optionDict.add<wordList>("names", names);
+    optionDict.add<word>("function", "linear");
+    optionDict.add<word>("interpolationField", "beta");
+
+    // Construct and append fvOption
+    fv::optionList& fvOptions(fv::options::New(this->mesh_));
+    fvOptions.push_back(fv::option::New(dictName, optionDict, mesh_));
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.H b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.H
index 145e85b53e4c7adffd852b063a3c53ca34687596..3a6f74005265073076e898f25ce0afe37c08ec6d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/adjointSimple/adjointSimple.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -49,9 +49,6 @@ Description
 
 #include "incompressibleAdjointSolver.H"
 #include "SIMPLEControl.H"
-#include "incompressibleVars.H"
-#include "incompressibleAdjointVars.H"
-#include "adjointSensitivityIncompressible.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -93,9 +90,6 @@ protected:
         //- Cumulative continuity error
         scalar cumulativeContErr_;
 
-        //- Sensitivity Derivatives engine
-        autoPtr<incompressible::adjointSensitivity> adjointSensitivity_;
-
 
     // Protected Member Functions
 
@@ -103,16 +97,12 @@ protected:
         //- for convenience in the rest of the class.
         incompressibleAdjointVars& allocateVars();
 
-        //- In case variable names are different than the base ones,
-        //- add extra schemes and relaxation factors to the appropriate dicts
-        //  Note: 160813: Changes in the baseline solution and fvSchemes classes
-        //  have to be made in order to add schemes in the dict at run time.
-        //  Not supported for now
-        void addExtraSchemes();
-
         //- Compute continuity errors
         void continuityErrors();
 
+        //- Accumulate the sensitivities integrand before calculating them
+        virtual void preCalculateSensitivities();
+
 
 public:
 
@@ -130,7 +120,8 @@ public:
             fvMesh& mesh,
             const word& managerType,
             const dictionary& dict,
-            const word& primalSolverName
+            const word& primalSolverName,
+            const word& solverName
         );
 
 
@@ -140,10 +131,6 @@ public:
 
     // Member Functions
 
-        //- Read dict if updated
-        virtual bool readDict(const dictionary& dict);
-
-
         // Evolution
 
             //- Execute one iteration of the solution algorithm
@@ -167,19 +154,6 @@ public:
             //- Functions to be called before loop
             virtual void preLoop();
 
-            //- Compute sensitivities of the underlaying objectives
-            virtual void computeObjectiveSensitivities();
-
-            //- Grab a reference to the computed sensitivities
-            virtual const scalarField& getObjectiveSensitivities();
-
-            //- Clears the sensitivity field known by the adjoint solver
-            //- and zeros sensitivities constituents
-            virtual void clearSensitivities();
-
-            //- Return the base sensitivity object
-            virtual sensitivity& getSensitivityBase();
-
 
             // Source terms to be added to the adjoint equations
 
@@ -189,6 +163,7 @@ public:
                 //- Source terms for the continuity equation
                 virtual void addPressureSource(fvScalarMatrix& matrix);
 
+
             //- Update primal based quantities
             //- related to the objective functions.
             //  Also writes the objective function values to files.
@@ -197,6 +172,9 @@ public:
             //  objectives of non-converged linearSearch iterations
             virtual void updatePrimalBasedQuantities();
 
+            //- Add fvOptions for TopO
+            virtual void addTopOFvOptions() const;
+
             //- Write average iteration
             virtual bool writeData(Ostream& os) const;
 };
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.C b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.C
index c5691c4477c2f291dceaaffbc0f70161f779486e..16296e88fb32b41bb2f583d0182ed4d305f76cff 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -30,6 +30,8 @@ License
 #include "incompressibleAdjointSolver.H"
 #include "incompressiblePrimalSolver.H"
 #include "wallFvPatch.H"
+#include "adjointBoundaryConditions.H"
+#include "adjointBoundaryConditionsFwd.H"
 #include "addToRunTimeSelectionTable.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@@ -47,6 +49,38 @@ namespace Foam
 }
 
 
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+bool Foam::incompressibleAdjointSolver::hasBCdxdbMult
+(
+    const labelHashSet& sensitivityPatchIDs
+)
+{
+    if (hasBCdxdbMult_.bad())
+    {
+        const volVectorField& Ua = getAdjointVars().Ua();
+        hasBCdxdbMult_ = false;
+        for (const label patchI : sensitivityPatchIDs)
+        {
+            const fvPatchVectorField& Uab = Ua.boundaryField()[patchI];
+            if (isA<adjointVectorBoundaryCondition>(Uab))
+            {
+                adjointVectorBoundaryCondition& abc =
+                    refCast<adjointVectorBoundaryCondition>
+                        (const_cast<fvPatchVectorField&>(Uab));
+                tmp<tensorField> dxdbMult = abc.dxdbMult();
+                if (dxdbMult)
+                {
+                    hasBCdxdbMult_ = true;
+                    break;
+                }
+            }
+        }
+    }
+    return hasBCdxdbMult_;
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::incompressibleAdjointSolver::incompressibleAdjointSolver
@@ -54,16 +88,18 @@ Foam::incompressibleAdjointSolver::incompressibleAdjointSolver
     fvMesh& mesh,
     const word& managerType,
     const dictionary& dict,
-    const word& primalSolverName
+    const word& primalSolverName,
+    const word& solverName
 )
 :
-    adjointSolver(mesh, managerType, dict, primalSolverName),
+    adjointSolver(mesh, managerType, dict, primalSolverName, solverName),
     primalVars_
     (
         mesh.lookupObjectRef<incompressiblePrimalSolver>(primalSolverName).
             getIncoVars()
     ),
-    ATCModel_(nullptr)
+    ATCModel_(nullptr),
+    hasBCdxdbMult_(Switch::INVALID)
 {}
 
 
@@ -75,7 +111,8 @@ Foam::incompressibleAdjointSolver::New
     fvMesh& mesh,
     const word& managerType,
     const dictionary& dict,
-    const word& primalSolverName
+    const word& primalSolverName,
+    const word& solverName
 )
 {
     const word solverType(dict.get<word>("solver"));
@@ -95,29 +132,13 @@ Foam::incompressibleAdjointSolver::New
     return
         autoPtr<incompressibleAdjointSolver>
         (
-            ctorPtr(mesh, managerType, dict, primalSolverName)
+            ctorPtr(mesh, managerType, dict, primalSolverName, solverName)
         );
 }
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-bool Foam::incompressibleAdjointSolver::readDict(const dictionary& dict)
-{
-    if (adjointSolver::readDict(dict))
-    {
-        return true;
-    }
-
-    return false;
-}
-
-bool Foam::incompressibleAdjointSolver::useSolverNameForFields() const
-{
-    return getAdjointVars().useSolverNameForFields();
-}
-
-
 const Foam::incompressibleVars&
 Foam::incompressibleAdjointSolver::getPrimalVars() const
 {
@@ -152,6 +173,38 @@ Foam::incompressibleAdjointSolver::getATCModel() const
 }
 
 
+bool Foam::incompressibleAdjointSolver::includeDistance() const
+{
+    return getAdjointVars().adjointTurbulence()->includeDistance();
+}
+
+
+Foam::dimensionSet Foam::incompressibleAdjointSolver::daDimensions() const
+{
+    return sqr(dimLength)/pow3(dimTime);
+}
+
+
+Foam::dimensionSet Foam::incompressibleAdjointSolver::maDimensions() const
+{
+    return pow3(dimLength/dimTime);
+}
+
+
+Foam::tmp<Foam::volScalarField>
+Foam::incompressibleAdjointSolver::adjointEikonalSource()
+{
+    return getAdjointVars().adjointTurbulence()->distanceSensitivities();
+}
+
+
+Foam::tmp<Foam::volScalarField>
+Foam::incompressibleAdjointSolver::yWall() const
+{
+    return getPrimalVars().RASModelVariables()->d();
+}
+
+
 Foam::autoPtr<Foam::ATCModel>& Foam::incompressibleAdjointSolver::getATCModel()
 {
     return ATCModel_;
@@ -169,14 +222,17 @@ void Foam::incompressibleAdjointSolver::updatePrimalBasedQuantities()
 }
 
 
-Foam::tmp<Foam::volTensorField>
-Foam::incompressibleAdjointSolver::computeGradDxDbMultiplier()
+void Foam::incompressibleAdjointSolver::accumulateGradDxDbMultiplier
+(
+    volTensorField& gradDxDbMult,
+    const scalar dt
+)
 {
     /*
     addProfiling
     (
         incompressibleAdjointSolver,
-        "incompressibleAdjointSolver::computeGradDxDbMultiplier"
+        "incompressibleAdjointSolver::accumulateGradDxDbMultiplier"
     );
     */
     autoPtr<incompressibleAdjoint::adjointRASModel>& adjointRAS
@@ -215,53 +271,32 @@ Foam::incompressibleAdjointSolver::computeGradDxDbMultiplier()
 
     tmp<volScalarField> tnuEff = adjointRAS->nuEff();
     tmp<volSymmTensorField> stress = tnuEff()*twoSymm(gradU);
-    // Note:
-    // term4 (Ua & grad(stress)) is numerically tricky.  Its div leads to third
-    // order spatial derivs in E-SI based computations. Applying the product
-    // derivative rule (putting Ua inside the grad) gives better results in
-    // NACA0012, SA, WF.  However, the original formulation should be kept at
-    // the boundary in order to respect the Ua boundary conditions (necessary
-    // for E-SI to give the same sens as FI).  A mixed approach is hence
-    // followed
-
-    // Term 3, used also to allocated the return field
     tmp<volTensorField> tgradUa = fvc::grad(Ua);
-    auto tflowTerm =
+
+    // Return field
+    auto tflowTerm
+    (
         tmp<volTensorField>::New
         (
-            "flowTerm",
-          - tnuEff*(gradU & twoSymm(tgradUa()))
-        );
+            IOobject
+            (
+                "flowTerm",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh_,
+            dimensionedTensor(sqr(dimLength)/pow3(dimTime), Zero),
+            fvPatchFieldBase::zeroGradientType()
+        )
+    );
     volTensorField& flowTerm = tflowTerm.ref();
-    // Term 4, only for the internal field
-    flowTerm.ref() +=
-        (
-          - (tgradUa & stress())
-          + fvc::grad(Ua & stress())
-        )().internalField();
+    // Term 3
+    flowTerm = - tnuEff*(gradU & twoSymm(tgradUa()));
+    // Term 4
+    flowTerm += fvc::grad(Ua & stress()) - (tgradUa & stress());
 
-    // Boundary conditions from term 4
-    for (label idir = 0; idir < pTraits<vector>::nComponents; ++idir)
-    {
-        autoPtr<volVectorField> stressDirPtr
-        (
-            createZeroFieldPtr<vector>
-                (mesh_, "stressDir", stress().dimensions())
-        );
-        // Components need to be in the [0-5] range since stress is a
-        // volSymmTensorField
-        unzipRow(stress(), idir, stressDirPtr());
-        volTensorField gradStressDir(fvc::grad(stressDirPtr()));
-        forAll(mesh_.boundary(), pI)
-        {
-            if (!isA<coupledFvPatch>(mesh_.boundary()[pI]))
-            {
-                flowTerm.boundaryFieldRef()[pI] +=
-                    Ua.component(idir)().boundaryField()[pI]
-                   *gradStressDir.boundaryField()[pI];
-            }
-        }
-    }
     // Release memory
     stress.clear();
 
@@ -279,8 +314,7 @@ Foam::incompressibleAdjointSolver::computeGradDxDbMultiplier()
     flowTerm += T(adjointRAS->FISensitivityTerm());
 
     // Term 7, term from objective functions
-    PtrList<objective>& functions
-        (objectiveManagerPtr_->getObjectiveFunctions());
+    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
 
     for (objective& objI : functions)
     {
@@ -292,20 +326,137 @@ Foam::incompressibleAdjointSolver::computeGradDxDbMultiplier()
 
     flowTerm.correctBoundaryConditions();
 
+    gradDxDbMult += flowTerm.T()*dt;
   //profiling::writeNow();
+}
+
+
+void Foam::incompressibleAdjointSolver::accumulateDivDxDbMultiplier
+(
+    autoPtr<scalarField>& divDxDbMult,
+    const scalar dt
+)
+{
+    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
+    for (objective& func : functions)
+    {
+        if (func.hasDivDxDbMult())
+        {
+            divDxDbMult() +=
+                func.weight()*func.divDxDbMultiplier().primitiveField()*dt;
+        }
+    }
+}
+
+
+void Foam::incompressibleAdjointSolver::accumulateGeometryVariationsMultipliers
+(
+    autoPtr<boundaryVectorField>& dSfdbMult,
+    autoPtr<boundaryVectorField>& dnfdbMult,
+    autoPtr<boundaryVectorField>& dxdbDirectMult,
+    autoPtr<pointBoundaryVectorField>& pointDxDbDirectMult,
+    const labelHashSet& sensitivityPatchIDs,
+    const scalar dt
+)
+{
+    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
+    for (const label patchI : sensitivityPatchIDs)
+    {
+        const scalarField magSfDt(mesh_.boundary()[patchI].magSf()*dt);
+        for (objective& func : functions)
+        {
+            const scalar wei = func.weight();
+            if (func.hasdSdbMult())
+            {
+                dSfdbMult()[patchI] += wei*func.dSdbMultiplier(patchI)*dt;
+            }
+            if (func.hasdndbMult())
+            {
+                dnfdbMult()[patchI] += wei*func.dndbMultiplier(patchI)*magSfDt;
+            }
+            if (func.hasdxdbDirectMult())
+            {
+                dxdbDirectMult()[patchI] +=
+                    wei*func.dxdbDirectMultiplier(patchI)*magSfDt;
+            }
+        }
+    }
+}
 
-    return (tflowTerm);
+
+void Foam::incompressibleAdjointSolver::accumulateBCSensitivityIntegrand
+(
+    autoPtr<boundaryVectorField>& bcDxDbMult,
+    const labelHashSet& sensitivityPatchIDs,
+    const scalar dt
+)
+{
+    if (!hasBCdxdbMult(sensitivityPatchIDs))
+    {
+        return;
+    }
+
+    // Grab references
+    const volVectorField& Ua = getAdjointVars().Ua();
+    const autoPtr<incompressibleAdjoint::adjointRASModel>& adjointTurbulence =
+        getAdjointVars().adjointTurbulence();
+
+    // Fields needed to calculate adjoint sensitivities
+    const autoPtr<incompressible::RASModelVariables>&
+       turbVars = primalVars_.RASModelVariables();
+    const singlePhaseTransportModel& lamTransp = primalVars_.laminarTransport();
+    volScalarField nuEff(lamTransp.nu() + turbVars->nut());
+    tmp<volTensorField> tgradUa = fvc::grad(Ua);
+    const volTensorField::Boundary& gradUabf = tgradUa.cref().boundaryField();
+
+    // Avoid updating the event number to keep consistency with cases caching
+    // gradUa
+    auto& UaBoundary = getAdjointVars().Ua().boundaryFieldRef(false);
+    auto& nuEffBoundary = nuEff.boundaryField();
+
+    for (const label patchI : sensitivityPatchIDs)
+    {
+        fvPatchVectorField& Uab = UaBoundary[patchI];
+        if (isA<adjointVectorBoundaryCondition>(Uab))
+        {
+            const fvPatch& patch = mesh_.boundary()[patchI];
+            tmp<vectorField> tnf = patch.nf();
+            const scalarField& magSf = patch.magSf();
+
+            tmp<vectorField> DvDbMult =
+                nuEffBoundary[patchI]*(Uab.snGrad() + (gradUabf[patchI] & tnf))
+    //        - (nf*pa.boundaryField()[patchI])
+              + adjointTurbulence().adjointMomentumBCSource()[patchI];
+            bcDxDbMult()[patchI] +=
+                (
+                    DvDbMult
+                  & refCast<adjointVectorBoundaryCondition>(Uab).dxdbMult()
+                )*magSf*dt;
+        }
+    }
 }
 
 
-void Foam::incompressibleAdjointSolver::additionalSensitivityMapTerms
+void Foam::incompressibleAdjointSolver::accumulateOptionsDxDbMultiplier
 (
-    boundaryVectorField& sensitivityMap,
-    const labelHashSet& patchIDs,
+    vectorField& optionsDxDbMult,
     const scalar dt
 )
 {
-    // Does nothing in base
+    // Terms from fvOptions - missing contributions from turbulence models
+    const incompressibleAdjointVars& av = getAdjointVars();
+    vectorField temp(mesh_.nCells(), Zero);
+    fv::options::New(this->mesh_).postProcessSens
+    (
+        temp, av.UaInst().name(), av.solverName()
+    );
+    optionsDxDbMult += temp*dt;
+    temp = Zero;
+    fv::options::New(this->mesh_).postProcessSens
+    (
+        temp, av.paInst().name(), av.solverName()
+    );
+    optionsDxDbMult += temp*dt;
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.H b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.H
index de5e821cab336b05e8f69b633bfd63d9d6150ee7..72523176e91fd1c858abdf92120ec0cad6f408dc 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -79,6 +79,18 @@ protected:
         //- Adjoint Transpose Convection options
         autoPtr<ATCModel> ATCModel_;
 
+        //- Auxiliary bool to avoid a potentially expensive
+        //- part of the sensitivity computation
+        //  As a Switch with delayed evaluation since adjointBoundaryConditions
+        //  have not been allocated at the time of construction
+        Switch hasBCdxdbMult_;
+
+
+    // Protected Member Functions
+
+        //- Compute, if necessary, and return the hasBCdxdbMult_ bool
+        bool hasBCdxdbMult(const labelHashSet& sensitivityPatchIDs);
+
 
 public:
 
@@ -100,9 +112,10 @@ public:
                 fvMesh& mesh,
                 const word& managerType,
                 const dictionary& dict,
-                const word& primalSolverName
+                const word& primalSolverName,
+                const word& solverName
             ),
-            (mesh, managerType, dict, primalSolverName)
+            (mesh, managerType, dict, primalSolverName, solverName)
         );
 
 
@@ -114,7 +127,8 @@ public:
             fvMesh& mesh,
             const word& managerType,
             const dictionary& dict,
-            const word& primalSolverName
+            const word& primalSolverName,
+            const word& solverName
         );
 
 
@@ -126,7 +140,8 @@ public:
             fvMesh& mesh,
             const word& managerType,
             const dictionary& dict,
-            const word& primalSolverName
+            const word& primalSolverName,
+            const word& solverName
         );
 
 
@@ -136,13 +151,6 @@ public:
 
     // Member Functions
 
-        //- Read dict if updated
-        virtual bool readDict(const dictionary& dict);
-
-        //- Should solver name be appended to fields
-        virtual bool useSolverNameForFields() const;
-
-
         // Access
 
             //- Access to the incompressible primal variables set
@@ -160,6 +168,22 @@ public:
             //- Access to the ATC model
             autoPtr<ATCModel>& getATCModel();
 
+            //- Should the adjoint to the eikonal equation be solved
+            virtual bool includeDistance() const;
+
+            //- Return the dimensions of the adjoint distance field
+            virtual dimensionSet daDimensions() const;
+
+            //- Return the dimensions of the adjoint grid displacement variable
+            virtual dimensionSet maDimensions() const;
+
+            //- Return the source the adjoint eikonal equation
+            virtual tmp<volScalarField> adjointEikonalSource();
+
+            //- Return the distance field, to be used in the solution of the
+            //- adjoint eikonal PDE
+            virtual tmp<volScalarField> yWall() const;
+
 
         // Evolution
 
@@ -170,22 +194,62 @@ public:
 
         // Sensitivity related functions
 
-            //- Compute the multiplier for grad(dxdb)
-            //  Used in shape sensitivity derivatives, computed with the FI
-            //  and E-SI approaches
-            virtual tmp<volTensorField> computeGradDxDbMultiplier();
-
-            //- Terms to be added to the sensitivity map, depending
-            //- on the adjoint solver
-            //  The typical terms associated with the typical incompressible
-            //  flow equations are included in the sensitivity classes.
-            //  This is to be used whenever additional physics is added
-            virtual void additionalSensitivityMapTerms
-            (
-                boundaryVectorField& sensitivityMap,
-                const labelHashSet& patchIDs,
-                const scalar dt
-            );
+            // Functions related to the computation of sensitivity derivatives
+            // All functions get the field to accumulate their contibution on
+            // as an argument. Should be implemented by the derived classes
+            // if the physics there adds terms to the sensitivity derivatives
+
+                // Shape optimisation
+
+                    //- Compute the multiplier for grad(dxdb)
+                    //  Used in shape sensitivity derivatives, computed with
+                    //  the FI and E-SI approaches
+                    virtual void accumulateGradDxDbMultiplier
+                    (
+                        volTensorField& gradDxDbMult,
+                        const scalar dt
+                    );
+
+                    //- Compute the multiplier for div(dxdb)
+                    //  Used in shape sensitivity derivatives, computed with
+                    //  the FI and E-SI approaches
+                    virtual void accumulateDivDxDbMultiplier
+                    (
+                        autoPtr<scalarField>& divDxDbMult,
+                        const scalar dt
+                    );
+
+                    //- Accumulate the multipliers of geometric quantities
+                    //- defined at the boundary, usually through an objective
+                    //- or constraint function
+                    virtual void accumulateGeometryVariationsMultipliers
+                    (
+                        autoPtr<boundaryVectorField>& dSfdbMult,
+                        autoPtr<boundaryVectorField>& dnfdbMult,
+                        autoPtr<boundaryVectorField>& dxdbDirectMult,
+                        autoPtr<pointBoundaryVectorField>& pointDxDirectDbMult,
+                        const labelHashSet& sensitivityPatchIDs,
+                        const scalar dt
+                    );
+
+                    //- Contributions from boundary functions that inlcude
+                    //- geometric aspects in them and change when the geometry
+                    //- is displaced, e.g. rotationWallVelocity
+                    virtual void accumulateBCSensitivityIntegrand
+                    (
+                        autoPtr<boundaryVectorField>& bcDxDbMult,
+                        const labelHashSet& sensitivityPatchIDs,
+                        const scalar dt
+                    );
+
+                    //- Contributions from fvOptions that inlcude
+                    //- geometric aspects in them and change when the geometry
+                    //- is displaced, e.g. MRF
+                    virtual void accumulateOptionsDxDbMultiplier
+                    (
+                        vectorField& optionsDxDbMult,
+                        const scalar dt
+                    );
 
 
         // IO
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolverTemplates.C b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolverTemplates.C
new file mode 100644
index 0000000000000000000000000000000000000000..ebadf74550f356772ecfdfd16c3772f8e279075d
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolverTemplates.C
@@ -0,0 +1,58 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2022-2023 PCOpt/NTUA
+    Copyright (C) 2022-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+template<class Type>
+tmp<surfaceInterpolationScheme<Type>>
+Foam::incompressibleAdjointSolver::convectionScheme
+(
+    const word& varName,
+    word phiName
+) const
+{
+    const surfaceScalarField& phi = primalVars_.phi();
+    if (phiName == word::null)
+    {
+        phiName = primalVars_.phiInst().name();
+    }
+    word divEntry("div(" + phiName + ',' + varName +')');
+    ITstream& divScheme = mesh_.divScheme(divEntry);
+    // Skip the first entry which might be 'bounded' or 'Gauss'.
+    // If it is 'bounded', skip the second entry as well
+    word discarded(divScheme);
+    if (discarded == "bounded")
+    {
+        discarded = word(divScheme);
+    }
+    return surfaceInterpolationScheme<Type>::New(mesh_, phi, divScheme);
+}
+
+
+// ************************************************************************* //
+
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.C b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.C
new file mode 100644
index 0000000000000000000000000000000000000000..ae115b2c5e6b857277033af39f957d8cc1dc9afa
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.C
@@ -0,0 +1,238 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2023 PCOpt/NTUA
+    Copyright (C) 2023 FOSS GP
+-------------------------------------------------------------------------------
+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 "adjointNull.H"
+#include "findRefCell.H"
+#include "constrainHbyA.H"
+#include "adjustPhi.H"
+#include "addToRunTimeSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+    defineTypeNameAndDebug(adjointNull, 0);
+    addToRunTimeSelectionTable
+    (
+        adjointSolver,
+        adjointNull,
+        adjointSolver
+    );
+}
+
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void Foam::adjointNull::preCalculateSensitivities()
+{
+    adjointSensitivity_->accumulateIntegrand(scalar(1));
+}
+
+
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
+
+Foam::adjointNull::adjointNull
+(
+    fvMesh& mesh,
+    const word& managerType,
+    const dictionary& dict,
+    const word& primalSolverName,
+    const word& solverName
+)
+:
+    adjointSolver
+    (
+        mesh,
+        managerType,
+        dict,
+        primalSolverName,
+        solverName
+    )
+{
+    allocateSensitivities();
+}
+
+
+// * * * * * * * * * * * * * * * * * Selectors * * * * * * * * * * * * * * * //
+
+Foam::autoPtr<Foam::adjointNull> Foam::adjointNull::New
+(
+    fvMesh& mesh,
+    const word& managerType,
+    const dictionary& dict,
+    const word& primalSolverName,
+    const word& solverName
+)
+{
+    return autoPtr<adjointNull>
+    (
+        new adjointNull
+        (
+            mesh,
+            managerType,
+            dict,
+            primalSolverName,
+            solverName
+        )
+    );
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+const Foam::word Foam::adjointNull::simulationType() const
+{
+    return word("null");
+}
+
+
+Foam::dimensionSet Foam::adjointNull::maDimensions() const
+{
+    return pow3(dimLength/dimTime);
+}
+
+
+void Foam::adjointNull::solveIter()
+{
+    // Does nothing
+}
+
+
+void Foam::adjointNull::solve()
+{
+    // Does nothing
+}
+
+
+bool Foam::adjointNull::loop()
+{
+    return false;
+}
+
+
+void Foam::adjointNull::updatePrimalBasedQuantities()
+{
+    // Update objective function related quantities
+    objectiveManager_.updateAndWrite();
+}
+
+
+void Foam::adjointNull::accumulateGradDxDbMultiplier
+(
+    volTensorField& gradDxDbMult,
+    const scalar dt
+)
+{
+    tmp<volTensorField> tsens
+    (
+        tmp<volTensorField>::New
+        (
+            IOobject
+            (
+                "flowTerm",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh_,
+            dimensionedTensor(sqr(dimLength)/pow3(dimTime), Zero),
+            fvPatchFieldBase::zeroGradientType()
+        )
+    );
+    volTensorField& sens = tsens.ref();
+
+    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
+
+    for (objective& objI : functions)
+    {
+        if (objI.hasGradDxDbMult())
+        {
+            sens += objI.weight()*objI.gradDxDbMultiplier();
+        }
+    }
+    sens.correctBoundaryConditions();
+
+    gradDxDbMult += sens.T()*dt;
+}
+
+
+void Foam::adjointNull::accumulateDivDxDbMultiplier
+(
+    autoPtr<scalarField>& divDxDbMult,
+    const scalar dt
+)
+{
+    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
+    for (objective& func : functions)
+    {
+        if (func.hasDivDxDbMult())
+        {
+            divDxDbMult() +=
+                func.weight()*func.divDxDbMultiplier().primitiveField()*dt;
+        }
+    }
+}
+
+
+void Foam::adjointNull::accumulateGeometryVariationsMultipliers
+(
+    autoPtr<boundaryVectorField>& dSfdbMult,
+    autoPtr<boundaryVectorField>& dnfdbMult,
+    autoPtr<boundaryVectorField>& dxdbDirectMult,
+    autoPtr<pointBoundaryVectorField>& pointDxDbDirectMult,
+    const labelHashSet& sensitivityPatchIDs,
+    const scalar dt
+)
+{
+    PtrList<objective>& functions = objectiveManager_.getObjectiveFunctions();
+    for (const label patchI : sensitivityPatchIDs)
+    {
+        const scalarField magSfDt(mesh_.boundary()[patchI].magSf()*dt);
+        for (objective& func : functions)
+        {
+            const scalar wei(func.weight());
+            if (func.hasdSdbMult())
+            {
+                dSfdbMult()[patchI] += wei*func.dSdbMultiplier(patchI)*dt;
+            }
+            if (func.hasdndbMult())
+            {
+                dnfdbMult()[patchI] += wei*func.dndbMultiplier(patchI)*magSfDt;
+            }
+            if (func.hasdxdbDirectMult())
+            {
+                dxdbDirectMult()[patchI] +=
+                    wei*func.dxdbDirectMultiplier(patchI)*magSfDt;
+            }
+        }
+    }
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.H b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.H
new file mode 100644
index 0000000000000000000000000000000000000000..aa2a0bdf099d5242c4c637ccfc91b04a9c76a59b
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.H
@@ -0,0 +1,186 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2023 PCOpt/NTUA
+    Copyright (C) 2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+Class
+    Foam::adjointNull
+
+Description
+    Dummy adjoint solver.
+    Used to add the derivatives of geometric constraints which do not require
+    the solution of adjoint PDEs
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef adjointNull_H
+#define adjointNull_H
+
+#include "adjointSolver.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class adjointNull Declaration
+\*---------------------------------------------------------------------------*/
+
+class adjointNull
+:
+    public adjointSolver
+{
+
+private:
+
+    // Private Member Functions
+
+        //- No copy construct
+        adjointNull(const adjointNull&) = delete;
+
+        //- No copy assignment
+        void operator=(const adjointNull&) = delete;
+
+
+protected:
+
+    // Protected data
+
+        //- Accumulate the sensitivities integrand before calculating them
+        virtual void preCalculateSensitivities();
+
+
+public:
+
+    // Static Data Members
+
+        //- Run-time type information
+        TypeName("null");
+
+
+    // Constructors
+
+        //- Construct from mesh and dictionary
+        adjointNull
+        (
+            fvMesh& mesh,
+            const word& managerType,
+            const dictionary& dict,
+            const word& primalSolverName,
+            const word& solverName
+        );
+
+
+    // Selectors
+
+        //- Return a reference to the selected turbulence model
+        static autoPtr<adjointNull> New
+        (
+            fvMesh& mesh,
+            const word& managerType,
+            const dictionary& dict,
+            const word& primalSolverName,
+            const word& solverName
+        );
+
+
+    //- Destructor
+    virtual ~adjointNull() = default;
+
+
+    // Member Functions
+
+        // Access
+
+            //- Return the type of simulation this solver pertains to
+            virtual const word simulationType() const;
+
+            //- Return the dimensions of the adjoint grid displacement variable
+            virtual dimensionSet maDimensions() const;
+
+
+        // Evolution
+
+            //- Execute one iteration of the solution algorithm
+            virtual void solveIter();
+
+            //- Main control loop
+            virtual void solve();
+
+            //- Looper (advances iters, time step)
+            virtual bool loop();
+
+            //- Update primal based quantities related to the objective
+            //- functions.
+            //  Also writes the objective function values to files.
+            virtual void updatePrimalBasedQuantities();
+
+            // Functions related to the computation of sensitivity derivatives
+
+                // Shape optimisation
+
+                    //- Compute the multiplier for grad(dxdb)
+                    //  Used in shape sensitivity derivatives, computed with
+                    //  the FI and E-SI approaches
+                    virtual void accumulateGradDxDbMultiplier
+                    (
+                        volTensorField& gradDxDbMult,
+                        const scalar dt
+                    );
+
+                    //- Compute the multiplier for div(dxdb)
+                    //  Used in shape sensitivity derivatives, computed with
+                    //  the FI and E-SI approaches
+                    virtual void accumulateDivDxDbMultiplier
+                    (
+                        autoPtr<scalarField>& divDxDbMult,
+                        const scalar dt
+                    );
+
+                    //- Accumulate the multipliers of geometric quantities
+                    //- defined at the boundary, usually through an objective
+                    //- or constraint function
+                    virtual void accumulateGeometryVariationsMultipliers
+                    (
+                        autoPtr<boundaryVectorField>& dSfdbMult,
+                        autoPtr<boundaryVectorField>& dnfdbMult,
+                        autoPtr<boundaryVectorField>& dxdbDirectMult,
+                        autoPtr<pointBoundaryVectorField>& pointDxDirectDbMult,
+                        const labelHashSet& sensitivityPatchIDs,
+                        const scalar dt
+                    );
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.C b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.C
index 575ea54196322434d298e27361ca3574a258e198..67b44b9bdbd1afde316d5e4dad5e9eb23baf35cb 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -61,10 +61,17 @@ Foam::RASTurbulenceModel::RASTurbulenceModel
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 :
-    incompressiblePrimalSolver(mesh, managerType, dict),
+    incompressiblePrimalSolver
+    (
+        mesh,
+        managerType,
+        dict,
+        solverName
+    ),
     solverControl_(SIMPLEControl::New(mesh, managerType, *this)),
     incoVars_(allocateVars())
 {
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.H b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.H
index b4dd92895af71b28a3352ebde41f39089755b25a..2090f90c8929efec015bf6ef01508fea62c9860d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/RASTurbulenceModel/RASTurbulenceModel.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -98,7 +98,8 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.C b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.C
index 5fa3f120c8415f2421cf3d6709bb225800459cc3..447c9a1880634cc7f5faf79b6ea04735b5053d36 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -55,10 +55,11 @@ Foam::incompressiblePrimalSolver::incompressiblePrimalSolver
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 :
-    primalSolver(mesh, managerType, dict),
+    primalSolver(mesh, managerType, dict, solverName),
     phiReconstructionTol_
     (
         dict.subOrEmptyDict("fieldReconstruction").
@@ -79,7 +80,8 @@ Foam::incompressiblePrimalSolver::New
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 {
     const word solverType(dict.get<word>("solver"));
@@ -99,7 +101,7 @@ Foam::incompressiblePrimalSolver::New
     return
         autoPtr<incompressiblePrimalSolver>
         (
-            ctorPtr(mesh, managerType, dict)
+            ctorPtr(mesh, managerType, dict, solverName)
         );
 }
 
@@ -144,12 +146,6 @@ Foam::incompressiblePrimalSolver::getObjectiveFunctions() const
 }
 
 
-bool Foam::incompressiblePrimalSolver::useSolverNameForFields() const
-{
-    return vars_().useSolverNameForFields();
-}
-
-
 const Foam::incompressibleVars&
 Foam::incompressiblePrimalSolver::getIncoVars() const
 {
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.H b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.H
index 9506e59fb5b82767ffc71f7f7d0c38f1dbe6f962..dcfc780640b764cc0a3e969b2a06430c59dfb12f 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/incompressiblePrimalSolver/incompressiblePrimalSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -95,9 +95,10 @@ public:
             (
                 fvMesh& mesh,
                 const word& managerType,
-                const dictionary& dict
+                const dictionary& dict,
+                const word& solverName
             ),
-            (mesh, managerType, dict)
+            (mesh, managerType, dict, solverName)
         );
 
 
@@ -108,7 +109,8 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
@@ -119,7 +121,8 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
@@ -132,9 +135,6 @@ public:
         //- Read dict if updated
         virtual bool readDict(const dictionary& dict);
 
-        //- Should solver name be appended to fields
-        bool useSolverNameForFields() const;
-
 
         // Access
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.C b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.C
index 90a3343709402298f4ea9bc2b788485d4ca3f82c..06cd7955285727c9515d985e9257c9b4a1c0c666 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -92,13 +92,20 @@ Foam::simple::simple
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 :
-    incompressiblePrimalSolver(mesh, managerType, dict),
+    incompressiblePrimalSolver
+    (
+        mesh,
+        managerType,
+        dict,
+        solverName
+    ),
     solverControl_(SIMPLEControl::New(mesh, managerType, *this)),
     incoVars_(allocateVars()),
-    MRF_(mesh, word(useSolverNameForFields() ? solverName() : word::null)),
+    MRF_(mesh, word(useSolverNameForFields() ? solverName_ : word::null)),
     cumulativeContErr_(Zero),
     objectives_(0)
 {
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.H b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.H
index 335df8280fc79f9045f4903c3a87f31973efd144..2635e0ba7c3944078a35f216469e9d5484467263 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/incompressible/simple/simple.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -117,7 +117,8 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.C b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.C
index 1f4c9e43f17f129866154b4cdfe0aa401ac46dce..8ea643c630072fac9968ac308af3968d3ed7c685 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.C
@@ -46,10 +46,11 @@ Foam::primalSolver::primalSolver
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 :
-    solver(mesh, managerType, dict)
+    solver(mesh, managerType, dict, solverName)
 {}
 
 
@@ -59,7 +60,8 @@ Foam::autoPtr<Foam::primalSolver> Foam::primalSolver::New
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 {
     const word solverType(dict.get<word>("type"));
@@ -77,7 +79,8 @@ Foam::autoPtr<Foam::primalSolver> Foam::primalSolver::New
         ) << exit(FatalIOError);
     }
 
-    return autoPtr<primalSolver>(ctorPtr(mesh, managerType, dict));
+    return
+        autoPtr<primalSolver>(ctorPtr(mesh, managerType, dict, solverName));
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.H b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.H
index 2a859ff1a7539907a5c3e099d32b1ef130d261c9..c65a80d3112238e7b9bec1cc08efab2eebbc3c30 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/primalSolvers/primalSolver/primalSolver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -84,9 +84,10 @@ public:
             (
                 fvMesh& mesh,
                 const word& managerType,
-                const dictionary& dict
+                const dictionary& dict,
+                const word& solverName
             ),
-            (mesh, managerType, dict)
+            (mesh, managerType, dict, solverName)
         );
 
 
@@ -97,7 +98,8 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
@@ -108,7 +110,8 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.C b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.C
index 223ecde22b2e0370ec954ff4530a61ca4ecae469..2b8bb4714cd6bb41608b84d043be86381d28ab38 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -42,14 +42,15 @@ Foam::solver::solver
 (
     fvMesh& mesh,
     const word& managerType,
-    const dictionary& dict
+    const dictionary& dict,
+    const word& solverName
 )
 :
     localIOdictionary
     (
         IOobject
         (
-            dict.dictName(),
+            solverName,
             mesh.time().timeName(),
             fileName("uniform")/fileName("solvers"),
             mesh,
@@ -62,21 +63,15 @@ Foam::solver::solver
     mesh_(mesh),
     managerType_(managerType),
     dict_(dict),
-    solverName_(dict.dictName()),
-    active_(dict.getOrDefault("active", true)),
-    optTypeSource_(nullptr),
+    solverName_(solverName),
+    active_(dict.getOrDefault<bool>("active", true)),
+    isMaster_(dict.getOrDefault<bool>("isMaster", true)),
+    useSolverNameForFields_
+        (dict_.getOrDefault<bool>("useSolverNameForFields", false)),
     vars_(nullptr)
 {}
 
 
-// * * * * * * * * * * * * * * * * Destructor  * * * * * * * * * * * * * * * //
-
-Foam::solver::~solver()
-{
-    optTypeSource_ = 0;
-}
-
-
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
 bool Foam::solver::readDict(const dictionary& dict)
@@ -85,7 +80,7 @@ bool Foam::solver::readDict(const dictionary& dict)
 
     // Note: Slightly dangerous to change active_ while the solver is
     // running. At the very least, this should trigger writing before stopping.
-    // Additional problems if we have an adjontSolver corresponding to a
+    // Additional problems if we have an adjointSolver corresponding to a
     // constraint. To be revisited
     //active_ = dict.getOrDefault<bool>("active", true);
 
@@ -93,41 +88,6 @@ bool Foam::solver::readDict(const dictionary& dict)
 }
 
 
-const Foam::fvMesh& Foam::solver::mesh() const
-{
-    return mesh_;
-}
-
-const Foam::word& Foam::solver::solverName() const
-{
-    return solverName_;
-}
-
-
-bool Foam::solver::active()
-{
-    return active_;
-}
-
-
-const Foam::dictionary& Foam::solver::dict() const
-{
-    return dict_;
-}
-
-
-const Foam::variablesSet& Foam::solver::getVariablesSet() const
-{
-    return vars_();
-}
-
-
-Foam::variablesSet& Foam::solver::getVariablesSet()
-{
-    return vars_();
-}
-
-
 void Foam::solver::restoreInitValues()
 {
     // Does nothing in the base class
@@ -146,16 +106,9 @@ void Foam::solver::postLoop()
 }
 
 
-void Foam::solver::updateOptTypeSource
-(
-    const autoPtr<volScalarField>& optSourcePtr
-)
+void Foam::solver::addTopOFvOptions() const
 {
-    if (optSourcePtr)
-    {
-        const volScalarField& optSource = optSourcePtr();
-        optTypeSource_ = &optSource;
-    }
+    // Does nothing in the base class
 }
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.H b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.H
index c67d3d3fb9999aed798eebb534eb6d0dc80ab432..9e72c7e8b5c32f4fdbeed8983bbe8ee9e579bc96 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solver.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -29,7 +29,7 @@ Class
     Foam::solver
 
 Description
-    Base class for solution control classes
+    Base solver class
 
 \*---------------------------------------------------------------------------*/
 
@@ -72,11 +72,12 @@ protected:
         //- Solve equations?
         bool active_;
 
-        //- Pointer to a source term coming from the optimisationType
-        //- (e.g. porosity from topologyOptimisation)
-        //  Will never allocate new memory, so no need to be deleted
-        //  in the destructor
-        const volScalarField* optTypeSource_;
+        //- Is the solver the master one
+        //  Used in cases where a solver needs to work in tandem with another
+        bool isMaster_;
+
+        //- Append the variables related to the solver with it name
+        bool useSolverNameForFields_;
 
         //- Base variableSet pointer.
         //  To be allocated in derived classes
@@ -108,12 +109,13 @@ public:
         (
             fvMesh& mesh,
             const word& managerType,
-            const dictionary& dict
+            const dictionary& dict,
+            const word& solverName
         );
 
 
     //- Destructor
-    virtual ~solver();
+    virtual ~solver() = default;
 
 
     // Member Functions
@@ -124,25 +126,36 @@ public:
         // Access
 
             //- Return the solver mesh
-            const fvMesh& mesh() const;
+            inline const fvMesh& mesh() const;
 
             //- Return the solver name
-            const word& solverName() const;
+            inline const word& solverName() const;
 
             //- Use solver name as a suffix to the involved fields
-            virtual bool useSolverNameForFields() const = 0;
+            inline bool useSolverNameForFields() const;
+
+            //- Given a variable name, return a name that is possibly appended
+            //- by the solverName, depending on useSolverNameForFields
+            inline word extendedVariableName(const word& varName) const;
 
             //- Return state of solver
-            virtual bool active();
+            inline bool active();
 
             //- Return the solver dictionary
-            virtual const dictionary& dict() const;
+            inline const dictionary& dict() const;
 
             //- Return constant reference to variableSet used by the solver
-            const variablesSet& getVariablesSet() const;
+            inline const variablesSet& getVariablesSet() const;
 
             //- Return non-constant reference to variableSet used by the solver
-            variablesSet& getVariablesSet();
+            inline variablesSet& getVariablesSet();
+
+            //- Return the manager type
+            inline const word& managerType() const;
+
+            //- Whether the solver is the master one, in case of coupled
+            //- solvers
+            inline bool isMaster() const;
 
 
         // Evolution
@@ -165,12 +178,6 @@ public:
             //- Functions to be called after loop
             virtual void postLoop();
 
-            //- Update source term related to optimisationType
-            void updateOptTypeSource
-            (
-                const autoPtr<volScalarField>& optSourcePtr
-            );
-
             //- Main control loop.
             //  Gets a list of function pointers to be called at the end of
             //  each solver iteration
@@ -181,9 +188,9 @@ public:
                 List<void (Type::*)()>& funcs
             );
 
-            //- Add source from optimisationType to underlaying equation
-            template<class Type>
-            void addOptimisationTypeSource(fvMatrix<Type>& matrix) const;
+            //- Add topO fvOptions
+            //  Does nothing in base
+            virtual void addTopOFvOptions() const;
 
 
         // IO
@@ -217,6 +224,7 @@ public:
 #ifdef NoRepository
 #   include "solverTemplates.C"
 #endif
+#include "solverI.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverI.H b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverI.H
new file mode 100644
index 0000000000000000000000000000000000000000..1b17b69ac86eb00ad1939d453d74975032ecf5f9
--- /dev/null
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverI.H
@@ -0,0 +1,91 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021-2023 PCOpt/NTUA
+    Copyright (C) 2021-2023 FOSS GP
+-------------------------------------------------------------------------------
+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/>.
+
+\*---------------------------------------------------------------------------*/
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+const Foam::fvMesh& Foam::solver::mesh() const
+{
+    return mesh_;
+}
+
+
+const Foam::word& Foam::solver::solverName() const
+{
+    return solverName_;
+}
+
+
+bool Foam::solver::useSolverNameForFields() const
+{
+    return useSolverNameForFields_;
+}
+
+
+Foam::word Foam::solver::extendedVariableName(const word& varName) const
+{
+    return (useSolverNameForFields_ ? word(varName + solverName_) : varName);
+}
+
+
+bool Foam::solver::active()
+{
+    return active_;
+}
+
+
+const Foam::dictionary& Foam::solver::dict() const
+{
+    return dict_;
+}
+
+
+const Foam::variablesSet& Foam::solver::getVariablesSet() const
+{
+    return vars_();
+}
+
+
+Foam::variablesSet& Foam::solver::getVariablesSet()
+{
+    return vars_();
+}
+
+
+const Foam::word& Foam::solver::managerType() const
+{
+    return managerType_;
+}
+
+
+bool Foam::solver::isMaster() const
+{
+    return isMaster_;
+}
+
+
+// ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverTemplates.C b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverTemplates.C
index 94fce6a37c78489a7ac61fa3fe01c5c976cbbfc3..ea45841b2b6825585bc86e114fdde0ea69eb1786 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverTemplates.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverTemplates.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -55,20 +55,4 @@ void Foam::solver::solveWithArgs
 }
 
 
-template<class Type>
-void Foam::solver::addOptimisationTypeSource
-(
-    fvMatrix<Type>& matrix
-) const
-{
-    // If source has been allocated, add source * variable
-    if (optTypeSource_)
-    {
-        const GeometricField<Type, fvPatchField, volMesh>& psi = matrix.psi();
-
-        matrix += fvm::Sp(*optTypeSource_, psi);
-    }
-}
-
-
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.C b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.C
index 6059ba45c7ac5b43b41b74aaa4feda52097a1a1c..9edba23b8fa42203f58f345cd4e612df472ec76d 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.C
@@ -122,4 +122,22 @@ void Foam::SIMPLEControl::checkMeanSolution() const
 }
 
 
+bool Foam::SIMPLEControl::converged()
+{
+    return simpleControl::criteriaSatisfied();
+}
+
+
+bool Foam::SIMPLEControl::performIter()
+{
+    return true;
+}
+
+
+void Foam::SIMPLEControl::incrementIter()
+{
+    // Does nothing in base
+}
+
+
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.H b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.H
index 02a4b0de483bf442e7e0c3f9b908efa36a3207d8..8543b2b1b551011b3d48fa32ceea5941aa237cab 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/SIMPLEControl/SIMPLEControl.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -161,6 +161,15 @@ public:
 
             //- Loop
             virtual bool loop() = 0;
+
+            //- Is the solver converged
+            virtual bool converged();
+
+            //- Perform this iteration?
+            virtual bool performIter();
+
+            //- Increment iteration counter
+            virtual void incrementIter();
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/optimisation/SIMPLEControlOpt.C b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/optimisation/SIMPLEControlOpt.C
index 81336289bcf80d4bac9d1b8a7d33e27e9f5cd58e..c9feeab19c8f6753f1274f6aca93857f429fe0f9 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/optimisation/SIMPLEControlOpt.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/optimisation/SIMPLEControlOpt.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -125,75 +125,97 @@ bool Foam::SIMPLEControlOpt::loop()
 
     Time& runTime = const_cast<Time&>(mesh_.time());
 
-    // Sub-cycle time if this is the first iter
-    if (!subCycledTimePtr_)
-    {
-        subCycledTimePtr_.reset(new subCycleTime(runTime, nIters()));
-        Info<< "Solving equations for solver "
-            << solver_.solverName() << "\n" << endl;
-        deltaTSubSycle_ = runTime.deltaTValue();
-
-        // Reset iteration count to zero
-        iter_ = 0;
-    }
-
-    // Increase index
-    subCycledTimePtr_()++;
-    iter_ = subCycledTimePtr_().index();
-
     bool doNextIter(true);
 
-    if (criteriaSatisfied())
+    if (nIters() > 0)
     {
-        Info<< nl
-            << solver_.solverName()
-            << " solution converged in "
-            << subCycledTimePtr_->index() << " iterations" << nl << endl;
-
-        subCycledTimePtr_->endSubCycle();
-        subCycledTimePtr_.clear();
-
-        // Write solution before continuing to next solver
-        runTime.write();
-        solver_.write();
-
-        // Check whether mean fields have not been computed due to an
-        // unexpectedly early convergence
-        checkMeanSolution();
-
-        doNextIter = false;
+        // Sub-cycle time if this is the first iter
+        if (!subCycledTimePtr_)
+        {
+            subCycledTimePtr_.reset(new subCycleTime(runTime, nIters()));
+            Info<< "Solving equations for solver "
+                << solver_.solverName() << "\n" << endl;
+            deltaTSubSycle_ = runTime.deltaTValue();
+
+            // Reset iteration count to zero
+            iter_ = 0;
+
+            // Reset previous time index of fvMesh::data, to avoid the rare
+            // occurance of a solver satisfying the convergence criteria at the
+            // first iteration, which then causes all subsequent optimisation
+            // cycles to be seen as converged, irrespective of the residual
+            // level, since the data::prevTimeIndex_ is not updated
+            //mesh_.data::setPreviousTimeIndex(0);
+        }
+
+        // Increase index
+        subCycledTimePtr_()++;
+        iter_ = subCycledTimePtr_().index();
+
+
+        if (criteriaSatisfied())
+        {
+            Info<< nl
+                << solver_.solverName()
+                << " solution converged in "
+                << subCycledTimePtr_->index() << " iterations" << nl << endl;
+
+            subCycledTimePtr_->endSubCycle();
+            subCycledTimePtr_.clear();
+
+            // Write solution before continuing to next solver
+            runTime.write();
+            solver_.write();
+
+            // Check whether mean fields have not been computed due to an
+            // unexpectedly early convergence
+            checkMeanSolution();
+
+            doNextIter = false;
+        }
+        else if (subCycledTimePtr_->end())
+        {
+            Info<< nl
+                << solver_.solverName()
+                << " solution reached max. number of iterations "
+                << subCycledTimePtr_().nSubCycles() << nl << endl;
+
+            subCycledTimePtr_->endSubCycle();
+            subCycledTimePtr_.clear();
+
+            // Write solution before continuing to next solver
+            runTime.write();
+            solver_.write();
+
+            doNextIter = false;
+        }
+        else
+        {
+            // Since dicts are not updated when Time is sub-cycled,
+            // do it manually here
+            runTime.readModifiedObjects();
+            resetDeltaT();
+
+            DebugInfo
+                << "Iteration " << subCycledTimePtr_().index()
+                << "|" << subCycledTimePtr_().nSubCycles() << endl;
+
+            simpleControl::storePrevIterFields();
+
+            doNextIter = true;
+        }
     }
-    else if (subCycledTimePtr_->end())
+    else
     {
-        Info<< nl
+        WarningInFunction
+            << "Number of iterations is non-positive (" << nIters() << ")."
+            << nl
+            << "Skipping the solution of the equations corresponding to solver "
             << solver_.solverName()
-            << " solution reached max. number of iterations "
-            << subCycledTimePtr_().nSubCycles() << nl << endl;
-
-        subCycledTimePtr_->endSubCycle();
-        subCycledTimePtr_.clear();
-
-        // Write solution before continuing to next solver
-        runTime.write();
-        solver_.write();
+            << nl << endl;
 
         doNextIter = false;
     }
-    else
-    {
-        // Since dicts are not updated when Time is sub-cycled,
-        // do it manually here
-        runTime.readModifiedObjects();
-        resetDeltaT();
-
-        DebugInfo
-            << "Iteration " << subCycledTimePtr_().index()
-            << "|" << subCycledTimePtr_().nSubCycles() << endl;
-
-        storePrevIterFields();
-
-        doNextIter = true;
-    }
 
     return doNextIter;
 }
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/singleRun/SIMPLEControlSingleRun.C b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/singleRun/SIMPLEControlSingleRun.C
index 8469547624b4433bf1adca7ccfc87a2e3e14dcba..5d1227122914fed917751a91ddb88fcf7c4e159e 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/singleRun/SIMPLEControlSingleRun.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/SIMPLEControl/singleRun/SIMPLEControlSingleRun.C
@@ -118,7 +118,7 @@ bool Foam::SIMPLEControlSingleRun::write(const bool valid) const
 void Foam::SIMPLEControlSingleRun::writeNow()
 {
     Time& time = const_cast<Time&>(mesh_.time());
-    // Avoid writing fields if already in an outputTime iter
+    // Avoid writing fields if already in an writeTime iter
     // since results will be written by the solver class either way
     if (!time.writeTime())
     {
@@ -156,7 +156,7 @@ bool Foam::SIMPLEControlSingleRun::loop()
     else
     {
         initialised_ = true;
-        storePrevIterFields();
+        simpleControl::storePrevIterFields();
     }
 
     bool isRunning = runTime.loop();
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControl.H b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControl.H
index 29fa740981028f6bb24e65476d1b3c46de7162a1..f8d5f3f046c2ddd1d457d303a70c5ea936011917 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControl.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControl.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -162,6 +162,9 @@ public:
 
             //- Whether averaging is enabled or not
             inline bool average() const;
+
+        //- Return reference to the underlaying solver
+        inline const solver& getSolver() const;
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControlI.H b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControlI.H
index 8817320a53d9fc6d796b110163708bc356c8a69e..8d37f646e0ab9041cb2652cb861dca5825fd93a8 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControlI.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/solverControl/solverControl/solverControlI.H
@@ -110,4 +110,10 @@ inline bool Foam::solverControl::average() const
 }
 
 
+inline const Foam::solver& Foam::solverControl::getSolver() const
+{
+    return solver_;
+}
+
+
 // ************************************************************************* //
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.C b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.C
index 6bf4cbc2dfc4a9001b97ceb1ef10f6c57dea7b41..19695a6371e34ebe91cf22976e429b6053f16b02 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -67,6 +67,19 @@ incompressibleAdjointVars::incompressibleAdjointVars
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+void incompressibleAdjointVars::restoreInitValues()
+{
+    if (solverControl_.storeInitValues())
+    {
+        Info<< "Restoring adjoint field to initial ones" << endl;
+        paInst() == dimensionedScalar(paInst().dimensions(), Zero);
+        UaInst() == dimensionedVector(UaInst().dimensions(), Zero);
+        phiaInst() == dimensionedScalar(phiaInst().dimensions(), Zero);
+        adjointTurbulence_().restoreInitValues();
+    }
+}
+
+
 void incompressibleAdjointVars::resetMeanFields()
 {
     if (solverControl_.average())
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.H b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.H
index 8bb60a23ebfcf47d96dded9aa4f11ffad774dbc1..fcfa216a91469d095016520c83dfb580ba47042f 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjoint/incompressibleAdjointVars.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -68,10 +68,10 @@ protected:
     // Protected Member Functions
 
         //- No copy construct
-        incompressibleAdjointVars(const incompressibleAdjointVars&);
+        incompressibleAdjointVars(const incompressibleAdjointVars&) = delete;
 
         //- No copy assignment
-        void operator=(const incompressibleAdjointVars&);
+        void operator=(const incompressibleAdjointVars&) = delete;
 
 
 public:
@@ -96,7 +96,7 @@ public:
 
 
     //- Destructor
-    virtual ~incompressibleAdjointVars(){};
+    virtual ~incompressibleAdjointVars() = default;
 
 
     // Member Functions
@@ -111,6 +111,9 @@ public:
             inline autoPtr<incompressibleAdjoint::adjointRASModel>&
                 adjointTurbulence();
 
+            //- Restore field values to the initial ones
+            void restoreInitValues();
+
             //- Reset mean fields to zero
             void resetMeanFields();
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.C b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.C
index 7a23aba9254ffa9f3bbc863822bea61e6b39398c..2b03684a2d755b35fb8e26a3bea92c5a0c58c46e 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.C
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.C
@@ -57,6 +57,7 @@ void incompressibleAdjointMeanFlowVars::setFields()
     mesh_.setFluxRequired(paPtr_->name());
 }
 
+
 void incompressibleAdjointMeanFlowVars::setMeanFields()
 {
     // Allocate mean fields
diff --git a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.H b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.H
index 9e523c61e2e86ed9af73114aeae26de363bdba04..87a0815bd5188df9308b555b75f2b9674ff4391c 100644
--- a/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.H
+++ b/src/optimisation/adjointOptimisation/adjoint/solvers/variablesSet/incompressibleAdjointMeanFlow/incompressibleAdjointMeanFlowVars.H
@@ -61,10 +61,10 @@ private:
         incompressibleAdjointMeanFlowVars
         (
             const incompressibleAdjointMeanFlowVars&
-        );
+        ) = delete;
 
         //- No copy assignment
-        void operator=(const incompressibleAdjointMeanFlowVars&);
+        void operator=(const incompressibleAdjointMeanFlowVars&) = delete;
 
 
 protected:
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.C
index f9e1d85bf9b4d09e928b9683f4d75448632f77ba..50f7c226daed88fc418a94647bc72675f41faac7 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -61,6 +61,26 @@ void adjointRASModel::printCoeffs()
 }
 
 
+void adjointRASModel::restoreInitValues()
+{
+    const solverControl& solControl = adjointVars_.getSolverControl();
+    if (solControl.storeInitValues())
+    {
+        if (adjointTMVariable1Ptr_)
+        {
+            volScalarField& var1 = adjointTMVariable1Ptr_.ref();
+            var1 == dimensionedScalar(var1.dimensions(), Zero);
+        }
+
+        if (adjointTMVariable2Ptr_)
+        {
+            volScalarField& var2 = adjointTMVariable2Ptr_.ref();
+            var2 == dimensionedScalar(var2.dimensions(), Zero);
+        }
+    }
+}
+
+
 void adjointRASModel::setMeanFields()
 {
     const solverControl& solControl = adjointVars_.getSolverControl();
@@ -352,7 +372,7 @@ autoPtr<volScalarField>& adjointRASModel::getAdjointTMVariable2InstPtr()
 }
 
 
-const wordList& adjointRASModel::getAdjointTMVariablesBaseNames()
+const wordList& adjointRASModel::getAdjointTMVariablesBaseNames() const
 {
     return adjointTMVariablesBaseNames_;
 }
@@ -374,7 +394,7 @@ tmp<volScalarField> adjointRASModel::nutJacobianTMVar1() const
             mesh_,
             dimensionedScalar
             (
-               nut().dimensions()/adjointTMVariable1Ptr_().dimensions(),
+               dimViscosity/adjointTMVariable1Ptr_().dimensions(),
                Zero
             )
         );
@@ -397,7 +417,7 @@ tmp<volScalarField> adjointRASModel::nutJacobianTMVar2() const
             mesh_,
             dimensionedScalar
             (
-                nut().dimensions()/adjointTMVariable2Ptr_().dimensions(),
+                dimViscosity/adjointTMVariable2Ptr_().dimensions(),
                 Zero
             )
         );
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.H
index ba9fff366fc56050fbd363be32a4cfeb4a5013ee..7f6dc022321da27ce49aeacde84f88b899235a86 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointRASModel/adjointRASModel.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2020 PCOpt/NTUA
-    Copyright (C) 2013-2020 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -263,7 +263,7 @@ public:
 
         //- Return reference to the adjoint turbulence model variables base
         //- names
-        const wordList& getAdjointTMVariablesBaseNames();
+        const wordList& getAdjointTMVariablesBaseNames() const;
 
         //- Return the effective stress tensor including the laminar stress
         virtual tmp<volSymmTensorField> devReff() const = 0;
@@ -314,12 +314,12 @@ public:
         virtual const boundaryVectorField& adjointMomentumBCSource() const = 0;
 
         //- Sensitivity terms for shape optimisation, emerging from
-        //  the turbulence model differentiation.
+        //- the turbulence model differentiation.
         //  Misses dxdb, to be added by the classes assembling the sensitivities
         virtual const boundaryVectorField& wallShapeSensitivities() = 0;
 
         //- Sensitivity terms for flow control, emerging from the
-        //  turbulence model differentiation
+        //- turbulence model differentiation
         virtual const boundaryVectorField& wallFloCoSensitivities() = 0;
 
         //- Sensitivity terms resulting from the differentiation of the
@@ -340,6 +340,9 @@ public:
         //- Set flag of changed primal solution to true
         void setChangedPrimalSolution();
 
+        //- Restore field values to the initial ones
+        void restoreInitValues();
+
         //- Reset mean fields to zero
         void resetMeanFields();
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.C
index 3ea09efc90b0dfe5a2c1f68298f290b2758f6f8b..3718c444025f021ca4c2311c6f9b9ec55130d351 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2023 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -35,6 +35,7 @@ License
 #include "boundaryAdjointContribution.H"
 #include "coupledFvPatch.H"
 #include "ATCModel.H"
+#include "fvOptions.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -148,7 +149,7 @@ const volScalarField& adjointSpalartAllmaras::nuTilda() const
 }
 
 
-const volScalarField& adjointSpalartAllmaras::nut() const
+const volScalarField& adjointSpalartAllmaras::nutRef() const
 {
     return primalVars_.RASModelVariables()().nutRef();
 }
@@ -861,7 +862,7 @@ tmp<volScalarField> adjointSpalartAllmaras::distanceSensitivities()
     typedef nutUSpaldingWallFunctionFvPatchScalarField
         SAwallFunctionPatchField;
 
-    const volScalarField::Boundary& nutBoundary = nut().boundaryField();
+    const volScalarField::Boundary& nutBoundary = nutRef().boundaryField();
     const scalarField& V = mesh_.V().field();
 
     tmp<volScalarField> tnuEff = nuEff();
@@ -945,7 +946,7 @@ tmp<volScalarField> adjointSpalartAllmaras::distanceSensitivities()
 
 tmp<volTensorField> adjointSpalartAllmaras::FISensitivityTerm()
 {
-    const volVectorField& U  = primalVars_.U();
+    const volVectorField& U = primalVars_.U();
 
     tmp<volTensorField> tgradU = fvc::grad(U);
     const volTensorField& gradU = tgradU.cref();
@@ -997,10 +998,26 @@ tmp<volTensorField> adjointSpalartAllmaras::FISensitivityTerm()
     volScalarField dfw_dOmega
         (this->dfw_dOmega(Stilda_, dfw_dr, dStilda_dOmega));
 
-    return
+    // Assemply of the return field
+    auto tFISens
+    (
         tmp<volTensorField>::New
         (
-            "volSensTerm",
+            IOobject
+            (
+                type() + "flowTerm",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh_,
+            dimensionedTensor(sqr(dimLength)/pow3(dimTime), Zero),
+            fvPatchFieldBase::zeroGradientType()
+        )
+    );
+    volTensorField& FISens = tFISens.ref();
+    FISens =
             // jk, cm formulation for the TM model convection
           - (nuaTilda()*(U*gradNuTilda))
             // jk, symmetric in theory
@@ -1013,8 +1030,11 @@ tmp<volTensorField> adjointSpalartAllmaras::FISensitivityTerm()
               - Cb1_*nuTilda()*dStilda_dOmega
               + Cw1_*sqr(nuTilda()/y_)*dfw_dOmega
             )
-           *nuaTilda()*deltaOmega // jk
-        );
+           *nuaTilda()*deltaOmega; // jk
+    FISens.correctBoundaryConditions();
+
+    return tFISens;
+}
 }
 
 
@@ -1050,6 +1070,8 @@ void adjointSpalartAllmaras::correct()
 
     nuaTilda().storePrevIter();
 
+    fv::options& fvOptions(fv::options::New(this->mesh_));
+
     tmp<fvScalarMatrix> nuaTildaEqn
     (
         fvm::ddt(nuaTilda())
@@ -1065,23 +1087,26 @@ void adjointSpalartAllmaras::correct()
         //always a positive contribution to the lhs. no need for SuSp
       - fvm::Sp(Cw1_*fw_*nuTilda()/sqr(y_), nuaTilda())
       - Cdnut_*gradUaR
+      + fvOptions(nuaTilda())
     );
 
     // Add sources from the objective functions
-    objectiveManager_.addTMEqn1Source(nuaTildaEqn.ref());
+    objectiveManager_.addSource(nuaTildaEqn.ref());
 
     nuaTildaEqn.ref().relax();
+    fvOptions.constrain(nuaTildaEqn.ref());
     solve(nuaTildaEqn);
     nuaTilda().correctBoundaryConditions();
     nuaTilda().relax();
 
     if (adjointVars_.getSolverControl().printMaxMags())
     {
-        scalar maxDeltaNuaTilda =
-            gMax(mag(nuaTilda() - nuaTilda().prevIter())());
+        dimensionedScalar maxDeltaNuaTilda =
+            max(mag(nuaTilda() - nuaTilda().prevIter())());
         dimensionedScalar maxNuaTilda = max(mag(nuaTilda()));
         Info<< "Max mag of nuaTilda = " << maxNuaTilda.value() << endl;
-        Info<< "Max mag of delta nuaTilda = " << maxDeltaNuaTilda << endl;
+        Info<< "Max mag of delta nuaTilda = " << maxDeltaNuaTilda.value()
+            << endl;
     }
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.H
index 91606032411581530284e13890e30f4a336b68fa..b774d0254f9275b378813550abfafa1d65559b72 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointSpalartAllmaras/adjointSpalartAllmaras.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -175,7 +175,7 @@ protected:
             //- References to the primal turbulence model variables
             const volScalarField& nuTilda() const;
 
-            const volScalarField& nut() const;
+            const volScalarField& nutRef() const;
 
 
         // Adjoint Spalart - Allmaras
@@ -282,6 +282,12 @@ protected:
                 return adjointTMVariable1Ptr_();
             };
 
+            //- Constant access to the adjoint Spalart - Allmaras field
+            inline const volScalarField& nuaTilda() const
+            {
+                return adjointTMVariable1Ptr_();
+            };
+
             //- Update the constant primal-related fields
             void updatePrimalRelatedFields();
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointkOmegaSST/adjointkOmegaSST.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointkOmegaSST/adjointkOmegaSST.C
index 862cea770d408e2fd214543925878cbd4daef2c3..760a9730c35edaffb37109bdb105ae3a9a2a0861 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointkOmegaSST/adjointkOmegaSST.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointRAS/adjointkOmegaSST/adjointkOmegaSST.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2014-2022 PCOpt/NTUA
-    Copyright (C) 2014-2022 FOSS GP
+    Copyright (C) 2014-2023 PCOpt/NTUA
+    Copyright (C) 2014-2023 FOSS GP
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -2173,7 +2173,7 @@ void adjointkOmegaSST::correct()
 
     // Sources from the objective should be added after the boundary
     // manipulation
-    objectiveManager_.addTMEqn2Source(waEqn.ref());
+    objectiveManager_.addSource(waEqn.ref());
     waEqn.ref().solve();
 
     // Adjoint Turbulent kinetic energy equation
@@ -2195,7 +2195,7 @@ void adjointkOmegaSST::correct()
     kaEqn.ref().boundaryManipulate(ka().boundaryFieldRef());
     addWallFunctionTerms(kaEqn.ref(), dR_dnut);
     // Add sources from the objective functions
-    objectiveManager_.addTMEqn1Source(kaEqn.ref());
+    objectiveManager_.addSource(kaEqn.ref());
 
     kaEqn.ref().solve();
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointTurbulenceModel/adjointTurbulenceModel.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointTurbulenceModel/adjointTurbulenceModel.H
index 04adc50c37e51e887e3e06dbb9ab3c7715b6a5a6..ebabdf33667f5f0d5d6e4c7d03b485d329d50276 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointTurbulenceModel/adjointTurbulenceModel.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/incompressibleAdjoint/adjointTurbulenceModel/adjointTurbulenceModel.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2021 PCOpt/NTUA
-    Copyright (C) 2013-2021 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -159,9 +159,9 @@ public:
         }
 
         //- Return the turbulence viscosity
-        virtual const volScalarField& nut() const
+        virtual const tmp<volScalarField> nut() const
         {
-            return primalVars_.RASModelVariables()().nutRef();
+            return primalVars_.RASModelVariables()().nut();
         }
 
         //- Return the effective viscosity
@@ -178,9 +178,8 @@ public:
                 tmp<volScalarField>::New
                 (
                     "nuEff",
-                    lamTrans.nu()() + turbVars().nutRef()
+                    lamTrans.nu() + turbVars().nut()
                 );
-            //return primalVars_.turbulence()().nuEff();
         }
 
         //- Return the effective viscosity on a given patch
@@ -193,11 +192,7 @@ public:
             const autoPtr<incompressible::RASModelVariables>&
                 turbVars = primalVars_.RASModelVariables();
 
-            return
-                (
-                    lamTrans.nu()().boundaryField()[patchI]
-                  + turbVars().nutRef().boundaryField()[patchI]
-                );
+            return (lamTrans.nu(patchI) + turbVars().nut(patchI));
         }
 
         //- Return the effective stress tensor including the laminar stress
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.C
index e74372f42561e35c44e36e59eedfa388ca9faad5..a16f7677e7d164056a5a3ac1cb0152cb3f5a5895 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2023 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -373,17 +373,17 @@ void RASModelVariables::resetMeanFields()
         Info<< "Resetting mean turbulent fields to zero" << endl;
 
         // Reset fields to zero
-        if (hasTMVar1())
+        if (TMVar1MeanPtr_)
         {
             TMVar1MeanPtr_.ref() ==
                 dimensionedScalar(TMVar1Inst().dimensions(), Zero);
         }
-        if (hasTMVar2())
+        if (TMVar2MeanPtr_)
         {
             TMVar2MeanPtr_.ref() ==
                 dimensionedScalar(TMVar2Inst().dimensions(), Zero);
         }
-        if (hasNut())
+        if (nutMeanPtr_)
         {
             nutMeanPtr_.ref() ==
                 dimensionedScalar(nutRefInst().dimensions(), Zero);
@@ -436,7 +436,7 @@ tmp<volSymmTensorField> RASModelVariables::devReff
             IOobject::NO_READ,
             IOobject::NO_WRITE
         ),
-      - (laminarTransport.nu() + nutRef())*devTwoSymm(fvc::grad(U))
+      - (laminarTransport.nu() + nut())*devTwoSymm(fvc::grad(U))
     );
 }
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.H
index 21313d3e5aac24ab42a2368f7699609f7ecf257e..2d48ffa006ac4722041aab78ef3fe4db25009488 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariables.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2019, 2022 FOSS GP
+    Copyright (C) 2007-2019, 2022-2023 PCOpt/NTUA
+    Copyright (C) 2013-2019, 2022-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -176,10 +176,6 @@ public:
         inline const word& nutBaseName() const;
 
         //- Bools to identify which turbulent fields are present
-        //  Apart from the distance pointer, all other pointers are
-        //  allocated even if the the corresponding field does not exist.
-        //  Hence, the pointer itself cannot be used to determine the
-        //  existance of the field
         inline virtual bool hasTMVar1() const;
         inline virtual bool hasTMVar2() const;
         inline virtual bool hasNut() const;
@@ -194,10 +190,16 @@ public:
         inline       volScalarField& TMVar2();
         inline const volScalarField& nutRef() const;
         inline       volScalarField& nutRef();
+        inline tmp<volScalarField> nut() const;
         inline const volScalarField& d() const;
         inline       volScalarField& d();
 
-        //- return references to instantaneous turbulence fields
+        inline tmp<scalarField> TMVar1(const label patchi) const;
+        inline tmp<scalarField> TMVar2(const label patchi) const;
+        inline tmp<scalarField> nut(const label patchi) const;
+        inline tmp<fvPatchScalarField> nutPatchField(const label patchi) const;
+
+        //- Return references to instantaneous turbulence fields
         inline const volScalarField& TMVar1Inst() const;
         inline       volScalarField& TMVar1Inst();
         inline const volScalarField& TMVar2Inst() const;
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariablesI.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariablesI.H
index 784f0ef2c887cb8491d59cfda3572ecb48278f93..ccbc5285b8d8a5a7279bec2d7ec188c491c2e952 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariablesI.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/RASModelVariables/RASModelVariablesI.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -141,6 +141,30 @@ inline volScalarField& RASModelVariables::nutRef()
 }
 
 
+inline tmp<volScalarField> RASModelVariables::nut() const
+{
+    if (hasNut())
+    {
+        return tmp<volScalarField>(nutRef());
+    }
+
+    return
+        tmp<volScalarField>::New
+        (
+            IOobject
+            (
+                "dummylaminarNut",
+                mesh_.time().timeName(),
+                mesh_,
+                IOobject::NO_READ,
+                IOobject::NO_WRITE
+            ),
+            mesh_,
+            dimensionedScalar(dimViscosity, Zero)
+        );
+}
+
+
 inline const volScalarField& RASModelVariables::d() const
 {
     return distPtr_.cref();
@@ -153,6 +177,53 @@ inline volScalarField& RASModelVariables::d()
 }
 
 
+inline tmp<scalarField> RASModelVariables::TMVar1(const label patchi) const
+{
+    if (hasTMVar1())
+    {
+        return TMVar1().boundaryField()[patchi];
+    }
+
+    return tmp<scalarField>::New(mesh_.boundary()[patchi].size(), Zero);
+}
+
+
+inline tmp<scalarField> RASModelVariables::TMVar2(const label patchi) const
+{
+    if (hasTMVar2())
+    {
+        return TMVar2().boundaryField()[patchi];
+    }
+
+    return tmp<scalarField>::New(mesh_.boundary()[patchi].size(), Zero);
+}
+
+
+inline tmp<scalarField> RASModelVariables::nut(const label patchi) const
+{
+    if (hasNut())
+    {
+        return nutRef().boundaryField()[patchi];
+    }
+
+    return tmp<scalarField>::New(mesh_.boundary()[patchi].size(), Zero);
+}
+
+
+inline tmp<fvPatchScalarField>
+RASModelVariables::nutPatchField(const label patchi) const
+{
+    if (hasNut())
+    {
+        return nutRef().boundaryField()[patchi];
+    }
+
+    // Using dummy internalField
+    return
+        tmp<fvPatchScalarField>::New(mesh_.boundary()[patchi], mesh_.V(), Zero);
+}
+
+
 inline const volScalarField& RASModelVariables::TMVar1Inst() const
 {
     return TMVar1Ptr_.cref();
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.C
index 7ffd13e1e11bae1d291d2c3eceac140fe12f3f1b..9e17b38e59263943a96edf29f24b38948ee4398a 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -58,24 +58,6 @@ SpalartAllmaras::SpalartAllmaras
     TMVar1BaseName_ = "nuTilda";
 
     TMVar1Ptr_.ref(mesh_.lookupObjectRef<volScalarField>(TMVar1BaseName_));
-
-    TMVar2Ptr_.reset
-    (
-        new volScalarField
-        (
-            IOobject
-            (
-                "dummySpalartAllmarasVar2",
-                mesh.time().timeName(),
-                mesh,
-                IOobject::NO_READ,
-                IOobject::NO_WRITE
-            ),
-            mesh,
-            dimensionedScalar(dimless, Zero)
-        )
-    );
-
     nutPtr_.ref(mesh_.lookupObjectRef<volScalarField>(nutBaseName_));
 
     // The wall dist name can vary depending on how wallDist was
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.H
index f61317f65938021e07be32b4541c2297e8ac5ac1..8d30eaaf27fb9c8bd4352e298a7cf22ca1d7a513 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/SpalartAllmaras/SpalartAllmaras.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2019, 2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -80,21 +80,6 @@ public:
 
 
     // Member Functions
-
-        // Bools to identify which turbulent fields are present
-        inline virtual bool hasTMVar1() const
-        {
-            return true;
-        }
-        inline virtual bool hasTMVar2() const
-        {
-            return false;
-        }
-        inline virtual bool hasNut() const
-        {
-            return true;
-        }
-
         //- return nut Jacobian wrt the TM vars
         virtual tmp<volScalarField> nutJacobianVar1
         (
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.C
index a05a88cd4937cd9dde8dcd770ef760d2bae4cd7b..bd4f6f29e82906cbda6e1ca8d30947cf785b8018 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -44,6 +44,35 @@ namespace RASVariables
 defineTypeNameAndDebug(kEpsilon, 0);
 addToRunTimeSelectionTable(RASModelVariables, kEpsilon, dictionary);
 
+
+// * * * * * * * * * * * * Protected Member Functions  * * * * * * * * * * * //
+
+void kEpsilon::allocateMeanFields()
+{
+    RASModelVariables::allocateMeanFields();
+    if (solverControl_.average())
+    {
+        GMean_.reset
+        (
+            new volScalarField::Internal
+            (
+                IOobject
+                (
+                    "GMean",
+                    mesh_.time().timeName(),
+                    mesh_,
+                    IOobject::READ_IF_PRESENT,
+                    IOobject::AUTO_WRITE
+                ),
+                mesh_,
+                dimensionedScalar(dimArea/pow3(dimTime), Zero)
+            )
+        );
+    }
+}
+
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 kEpsilon::kEpsilon
@@ -66,6 +95,74 @@ kEpsilon::kEpsilon
 }
 
 
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+tmp<volScalarField::Internal> kEpsilon::computeG()
+{
+    const turbulenceModel& turbModel = mesh_.lookupObject<turbulenceModel>
+    (
+         IOobject::groupName
+         (
+             turbulenceModel::propertiesName,
+             TMVar2().internalField().group()
+         )
+    );
+    // Recompute G and modify values next to the walls
+    // Ideally, grad(U) should be cached to avoid the overhead
+    const volVectorField& U = turbModel.U();
+    tmp<volTensorField> tgradU = fvc::grad(U);
+    volScalarField::Internal GbyNu0
+    (
+        IOobject::scopedName(this->type(), "GbyNu"),
+        (tgradU() && devTwoSymm(tgradU()))
+    );
+
+    // NB: leave tmp registered (for correctBoundaryConditions)
+    auto tG =
+        tmp<volScalarField::Internal>::New
+        (
+            turbModel.GName(),
+            nutRefInst()*GbyNu0
+        );
+    // Use correctBoundaryConditions instead of updateCoeffs to avoid
+    // messing with updateCoeffs in the next iteration of omegaEqn
+    TMVar2Inst().correctBoundaryConditions();
+
+    return tG;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+tmp<volScalarField::Internal> kEpsilon::G()
+{
+    if (solverControl_.useAveragedFields())
+    {
+        DebugInfo
+            << "Using GMean" << endl;
+        return tmp<volScalarField::Internal>(GMean_());
+    }
+    DebugInfo
+        << "Using instantaneous G" << endl;
+    return computeG();
+}
+
+
+void kEpsilon::computeMeanFields()
+{
+    RASModelVariables::computeMeanFields();
+    if (solverControl_.doAverageIter())
+    {
+        const label iAverageIter = solverControl_.averageIter();
+        scalar avIter(iAverageIter);
+        scalar oneOverItP1 = 1./(avIter + 1);
+        scalar mult = avIter*oneOverItP1;
+        GMean_() = GMean_()*mult + computeG()*oneOverItP1;
+    }
+}
+
+
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 } // End namespace RASVariables
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.H
index 510f0cc7cf95fe573fa773e6b2d05519482b46fd..e0877a762c94e171132e685606df7717b3ee9a53 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kEpsilon/kEpsilon.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2019, 2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -59,6 +59,21 @@ class kEpsilon
     public RASModelVariables
 {
 
+protected:
+
+    // Protected data
+
+        //- Average of the production term
+        //  Avaraged separetely due the bi-zonal treatment next to the wall
+        autoPtr<volScalarField::Internal> GMean_;
+
+
+    // Protected Member functions
+
+        virtual void allocateMeanFields();
+        tmp<volScalarField::Internal> computeG();
+
+
 public:
 
     //- Runtime type information
@@ -81,19 +96,12 @@ public:
 
     // Member Functions
 
-        // Bools to identify which turbulent fields are present
-        inline virtual bool hasTMVar1() const
-        {
-            return true;
-        }
-        inline virtual bool hasTMVar2() const
-        {
-            return true;
-        }
-        inline virtual bool hasNut() const
-        {
-            return true;
-        }
+        //- Return the turbulence production term
+        virtual tmp<volScalarField::Internal> G();
+
+        //- Compute mean fields on the fly
+        virtual void computeMeanFields();
+
 };
 
 
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.C
index b4ba121b47ebcc0a42c09577e00b5c20c24c34ef..cb02c4b7b6075dd1441632e26c7442f629956ade 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.C
@@ -97,6 +97,8 @@ kOmegaSST::kOmegaSST
 }
 
 
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
 tmp<volScalarField::Internal> kOmegaSST::computeG()
 {
     const turbulenceModel& turbModel = mesh_.lookupObject<turbulenceModel>
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.H b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.H
index f6721bf042dfb89c35b8ce206e2532619fbabd2d..911b6915e6d7a4e1a553a1f037a791fe73880892 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.H
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/kOmegaSST/kOmegaSST.H
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019, 2022 PCOpt/NTUA
-    Copyright (C) 2013-2019, 2022 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -102,20 +102,6 @@ public:
         //- Compute mean fields on the fly
         virtual void computeMeanFields();
 
-        // Bools to identify which turbulent fields are present
-        inline virtual bool hasTMVar1() const
-        {
-            return true;
-        }
-        inline virtual bool hasTMVar2() const
-        {
-            return true;
-        }
-        inline virtual bool hasNut() const
-        {
-            return true;
-        }
-
         //- Correct boundary conditions of turbulent fields
         virtual void correctBoundaryConditions
         (
diff --git a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/laminar/laminar.C b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/laminar/laminar.C
index 3560e42a166ba312e6bf394b3e6b6cc850ded557..388a145f90c60264775c59c07a6546593ce51f92 100644
--- a/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/laminar/laminar.C
+++ b/src/optimisation/adjointOptimisation/adjoint/turbulenceModels/turbulenceModelVariables/RAS/laminar/laminar.C
@@ -5,8 +5,8 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2007-2019 PCOpt/NTUA
-    Copyright (C) 2013-2019 FOSS GP
+    Copyright (C) 2007-2023 PCOpt/NTUA
+    Copyright (C) 2013-2023 FOSS GP
     Copyright (C) 2019-2020 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
@@ -53,60 +53,7 @@ laminar::laminar
 )
 :
     RASModelVariables(mesh, SolverControl)
-{
-    TMVar1Ptr_.reset
-    (
-        new volScalarField
-        (
-            IOobject
-            (
-                "dummylaminarVar1",
-                mesh.time().timeName(),
-                mesh,
-                IOobject::NO_READ,
-                IOobject::NO_WRITE
-            ),
-            mesh,
-            dimensionedScalar(dimless, Zero)
-        )
-    );
-
-    TMVar2Ptr_.reset
-    (
-        new volScalarField
-        (
-            IOobject
-            (
-                "dummylaminarVar2",
-                mesh.time().timeName(),
-                mesh,
-                IOobject::NO_READ,
-                IOobject::NO_WRITE
-            ),
-            mesh,
-            dimensionedScalar(dimless, Zero)
-        )
-    );
-
-    nutPtr_.reset
-    (
-        new volScalarField
-        (
-            IOobject
-            (
-                "dummylaminarNut",
-                mesh.time().timeName(),
-                mesh,
-                IOobject::NO_READ,
-                IOobject::NO_WRITE
-            ),
-            mesh,
-            dimensionedScalar(sqr(dimLength)/dimTime, Zero)
-        )
-    );
-
-    allocateInitValues();
-}
+{}
 
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/motorBike/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/motorBike/system/optimisationDict
index 4b831c0f71dc0e97b5fa68032d52b9ad2b535ce5..c18f84b1236048fea6c54ed5c2a6d8cbbe9d5ac2 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/motorBike/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/motorBike/system/optimisationDict
@@ -106,63 +106,65 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        // Used to compute a number of variants of the sensitivity map
-        // at once
-        type            multiple;
-        patches         (motorBikeGroup);
-        sensTypes
-        {
-            pointBased
-            {
-                type               surfacePoints;
-                patches            (motorBikeGroup);
-                includeSurfaceArea false;
-                adjointEikonalSolver
-                {
-                    tolerance 1.e-5;
-                    iters     1000;
-                    epsilon   0.1;
-                }
-            }
+        sensitivityType multiple;
+        sensitivityTypes 
+        (
+            pointBased 
             faceBased-unsmoothed
-            {
-                type                 surface;
-                patches              (motorBikeGroup);
-                includeSurfaceArea   false;
-            }
             faceBased-RMult_2
-            {
-                type                 surface;
-                patches              (motorBikeGroup);
-                includeSurfaceArea   false;
-                smoothSensitivities  true;
-                meanRadiusMultiplier 2;
-                suffix               Rmult2; // suffix of the sensitivity map output files
-                iters                2000;
-            }
             faceBased-RMult_5
-            {
-                type                 surface;
-                patches              (motorBikeGroup);
-                includeSurfaceArea   false;
-                smoothSensitivities  true;
-                meanRadiusMultiplier 5;
-                suffix               Rmult5; // suffix of the sensitivity map output files
-                iters                2000;
-            }
             faceBased-RMult_10
+        );
+        patches          (motorBikeGroup);
+        pointBased
+        {
+            sensitivityType    surfacePoints;
+            patches            (motorBikeGroup);
+            adjointEikonalSolver
             {
-                type                 surface;
-                patches              (motorBikeGroup);
-                includeSurfaceArea   false;
-                smoothSensitivities  true;
-                meanRadiusMultiplier 10;
-                suffix               Rmult10; // suffix of the sensitivity map output files
-                iters                2000;
+                tolerance 1.e-5;
+                iters     1000;
+                epsilon   0.1;
             }
         }
+        faceBased-unsmoothed
+        {
+            sensitivityType      surface;
+            patches              (motorBikeGroup);
+            includeSurfaceArea   true;
+        }
+        faceBased-RMult_2
+        {
+            sensitivityType      surface;
+            patches              (motorBikeGroup);
+            includeSurfaceArea   true;
+            smoothSensitivities  true;
+            meanRadiusMultiplier 2;
+            suffix               Rmult2; // suffix of the sensitivity map output files
+            iters                1000;
+        }
+        faceBased-RMult_5
+        {
+            sensitivityType      surface;
+            patches              (motorBikeGroup);
+            includeSurfaceArea   true;
+            smoothSensitivities  true;
+            meanRadiusMultiplier 5;
+            suffix               Rmult5; // suffix of the sensitivity map output files
+            iters                1000;
+        }
+        faceBased-RMult_10
+        {
+            sensitivityType      surface;
+            patches              (motorBikeGroup);
+            includeSurfaceArea   true;
+            smoothSensitivities  true;
+            meanRadiusMultiplier 10;
+            suffix               Rmult10; // suffix of the sensitivity map output files
+            iters                1000;
+        }
     }
 }
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/drag/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/drag/system/optimisationDict
index 31b902b7d9fed647b74227fabfdb259456c4d4e1..a6325beff8d9fb32bcdff1ae14508b2dafbd2ec3 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/drag/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/drag/system/optimisationDict
@@ -95,9 +95,9 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            surfacePoints;
+        sensitivityType surfacePoints;
         patches         (pressure suction);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/lift/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/lift/system/optimisationDict
index dee8234a763a59caf3983760b5d9f46c0e8973d4..4ba2394ce16fa1d7c2907176221f0ee183099cf6 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/lift/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/lift/system/optimisationDict
@@ -95,9 +95,9 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            surfacePoints;
+        sensitivityType surfacePoints;
         patches         (pressure suction);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/moment/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/moment/system/optimisationDict
index 59a3b2268c29ad1cbaf58b8484ebada5b90df12b..3bdf8d043d9e002658232fd43a5b5ff2b31659ac 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/moment/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/laminar/moment/system/optimisationDict
@@ -97,9 +97,9 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            surfacePoints;
+        sensitivityType surfacePoints;
         patches         (pressure suction);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftFullSetup/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftFullSetup/system/optimisationDict
index bf0f6f3427d7bc810f8a23c76b65a0ee4dd2ec20..57c2ef4345f0c89c3c80075a907adc9ea5833858 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftFullSetup/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftFullSetup/system/optimisationDict
@@ -103,14 +103,13 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type              surfacePoints;
-        patches           (pressure suction);
+        sensitivityType surfacePoints;
+        patches         (pressure suction);
         includeSurfaceArea  false;
         includeDistance     true;
         includeMeshMovement true;
-        includeObjectiveContribution true;
         writeAllSurfaceFiles         true;
         adjointMeshMovementSolver
         {
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftMinimumSetup/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftMinimumSetup/system/optimisationDict
index b4b5f2512b1c0836c175290b761002ca742038a8..9f1e1b53b82165cfa0b71b079ebd7f978bd70a1c 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftMinimumSetup/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/naca0012/turbulent/liftMinimumSetup/system/optimisationDict
@@ -97,10 +97,10 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type              surfacePoints;
-        patches           (pressure suction);
+        sensitivityType surfacePoints;
+        patches         (pressure suction);
     }
 }
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/laminar/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/laminar/system/optimisationDict
index 220f2f7c5782963fd56e571a58dc7fd810d10319..5b66688d05e71d8fed9ab210f2db5d7f5ee3b99d 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/laminar/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/laminar/system/optimisationDict
@@ -91,9 +91,9 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            surfacePoints;
+        sensitivityType surfacePoints;
         patches         (lower upper);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/highRe/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/highRe/system/optimisationDict
index 398f6fe2a2aeaade745d45cafda3dc856be93b39..97618bf8a1013808cff42e96f95cbe17c308a0dc 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/highRe/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/highRe/system/optimisationDict
@@ -91,9 +91,9 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            surfacePoints;
+        sensitivityType surfacePoints;
         patches         (lower upper);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/multiPoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/multiPoint/system/optimisationDict
index a60e75897ea8b4e2a9cddf466b580c2e626141f3..4ec44012d0fc086ee8cbd00bd4b700f749a965db 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/multiPoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/multiPoint/system/optimisationDict
@@ -163,10 +163,10 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type                surfacePoints;
-        patches             (lower upper);
+        sensitivityType surfacePoints;
+        patches         (lower upper);
     }
 }
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/singlePoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/singlePoint/system/optimisationDict
index 398f6fe2a2aeaade745d45cafda3dc856be93b39..97618bf8a1013808cff42e96f95cbe17c308a0dc 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/singlePoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/sensitivityMaps/sbend/turbulent/lowRe/singlePoint/system/optimisationDict
@@ -91,9 +91,9 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            surfacePoints;
+        sensitivityType surfacePoints;
         patches         (lower upper);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRate/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRate/system/optimisationDict
index b273bf66d08ef4da89e5a89d899f5825bb033bb3..dcc9b7654c56e3431f81c354735b129167846f56 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRate/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRate/system/optimisationDict
@@ -93,25 +93,18 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (upperWall lowerWall middleWall);
+        maxInitChange   1.e-3;
     }
     updateMethod
     {
         method  conjugateGradient;
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 1.e-3;
-    }
 }
 
 // ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRatePartition/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRatePartition/system/optimisationDict
index 04e72416da950d7328ebda8cfc1ab5c9b26f5c22..453d844e547f0c9869af3d34b6118b41104731e7 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRatePartition/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/flowRatePartition/system/optimisationDict
@@ -97,25 +97,18 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (upperWall lowerWall middleWall);
+        maxInitChange   1.e-3;
     }
     updateMethod
     {
         method  conjugateGradient;
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 1.e-3;
-    }
 }
 
 // ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/uniformityPatch/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/uniformityPatch/system/optimisationDict
index cd43525df3bf00d46d0a4b4c40e03321ff9c1936..6be392101b5be4dc7d3e889c2c910da11d840b89 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/uniformityPatch/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/fork-uneven/uniformityPatch/system/optimisationDict
@@ -93,25 +93,18 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (upperWall lowerWall middleWall);
+        maxInitChange   1.e-3;
     }
     updateMethod
     {
         method  conjugateGradient;
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 1.e-3;
-    }
 }
 
 // ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/motorBike/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/motorBike/system/optimisationDict
index e27a4dbf616497ffb7885d3e227ac4a8898d7bdc..f9df2a73dd745d2693cfebb886b50a1ea57a0a72 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/motorBike/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/motorBike/system/optimisationDict
@@ -106,15 +106,13 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-    sensitivities
-    {
-        type     volumetricBSplinesFI;
-        patches  (motorBikeGroup);
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (motorBikeGroup);
+        maxInitChange   2.e-3;
     }
     updateMethod
     {
@@ -125,11 +123,6 @@ optimisation
         }
         //eta 1; //optional
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/kOmegaSST/lift/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/kOmegaSST/lift/system/optimisationDict
index 708f7c9e409e70195654d6d2788ca1242b10110c..6c99208ebbd700a0e1ddb9b3310bc51b564c1a3f 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/kOmegaSST/lift/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/kOmegaSST/lift/system/optimisationDict
@@ -97,9 +97,11 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (pressure suction);
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/constant/dynamicMeshDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/constant/dynamicMeshDict
new file mode 100644
index 0000000000000000000000000000000000000000..a6e30fa7e51907f31386178c34c007359e333f60
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/constant/dynamicMeshDict
@@ -0,0 +1,27 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2206                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      dynamicMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+solver laplacianMotionSolver;
+
+laplacianMotionSolverCoeffs
+{
+    diffusivity uniform;
+    iters       1000;
+    tolerance   1.e-06;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/system/optimisationDict
index 1d6ddd34a630bf053a56920a66653c15ab3b4d76..5af1e7b95e5fa59ac519dccb7e29c05b5fbcf4b0 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/system/optimisationDict
@@ -95,31 +95,28 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            multiple; // used to compute many kinds of sensitivities at the same time
+        type            shape;
+        shapeType       Bezier;
+        sensitivityType multiple;
+        sensitivityTypes (FI ESI SI);
         patches         (pressure suction);
-        sensTypes
+        ESI
         {
-            ESI
-            {
-                type            Bezier;
-                patches         (pressure suction);
-            }
-            SI
-            {
-                type            Bezier;
-                patches         (pressure suction);
-                surfaceSensitivities
-                {
-                    includeMeshMovement false;
-                }
-            }
-            FI
-            {
-                type            BezierFI;
-                patches         (pressure suction);
-            }
+            sensitivityType shapeESI;
+            patches         (pressure suction);
+        }
+        SI
+        {
+            sensitivityType shapeESI;
+            patches         (pressure suction);
+            includeMeshMovement false;
+        }
+        FI
+        {
+            sensitivityType shapeFI;
+            patches         (pressure suction);
         }
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/lift/opt/constraintProjection/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/lift/opt/constraintProjection/system/optimisationDict
index 4b5e2335bb5adbfd1a2bd9d7d50390cc31d90442..77303dd4909af7968e5077b545ea6588e3f42b4e 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/lift/opt/constraintProjection/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/lift/opt/constraintProjection/system/optimisationDict
@@ -94,15 +94,14 @@ adjointManagers
                 // choose adjoint solver
                 //----------------------
                 active                 true;
-                type                   incompressible;
-                solver                 adjointSimple;
+                type                   null;
                 isConstraint           true;
 
                 // manage objectives
                 //------------------
                 objectives
                 {
-                    type                incompressible;
+                    type                geometric;
                     objectiveNames
                     {
                         vol
@@ -113,25 +112,6 @@ adjointManagers
                         }
                     }
                 }
-
-                // ATC treatment
-                //--------------
-                ATCModel
-                {
-                    ATCModel        standard;
-                }
-
-                // solution control
-                //------------------
-                solutionControls
-                {
-                    nIters 3000;
-                    residualControl
-                    {
-                        "pa.*"       1.e-7;
-                        "Ua.*"       1.e-7;
-                    }
-                }
             }
         }
     }
@@ -139,25 +119,18 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
-    {
-        type            shapeOptimisation;
-        writeEachMesh   true;
-    }
-    sensitivities
+    designVariables
     {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (pressure suction);
+        maxInitChange   5.e-3;
     }
     updateMethod
     {
         method          constraintProjection;
     }
-    meshMovement
-    {
-        type            volumetricBSplines;
-        maxAllowedDisplacement 5.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/constant/dynamicMeshDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/constant/dynamicMeshDict
new file mode 100644
index 0000000000000000000000000000000000000000..a6e30fa7e51907f31386178c34c007359e333f60
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/constant/dynamicMeshDict
@@ -0,0 +1,27 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2206                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      dynamicMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+solver laplacianMotionSolver;
+
+laplacianMotionSolverCoeffs
+{
+    diffusivity uniform;
+    iters       1000;
+    tolerance   1.e-06;
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/system/optimisationDict
index c18f88e42ee61ff9f06b8b2f10a1410950b72766..bcc6a46540474907de0193dd4657220dcb649e39 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/system/optimisationDict
@@ -97,36 +97,28 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type                multiple; // used to compute many kinds of sensitivities at the same time
-        patches             (pressure suction);
-        sensTypes
+        type            shape;
+        shapeType       Bezier;
+        sensitivityType multiple;
+        sensitivityTypes (FI ESI SI);
+        patches         (pressure suction);
+        ESI
         {
-            ESI
-            {
-                type                Bezier;
-                patches             (pressure suction);
-            }
-            SI
-            {
-                type                Bezier;
-                patches             (pressure suction);
-                surfaceSensitivities
-                {
-                    includeMeshMovement false;
-                }
-            }
-            FI
-            {
-                type                BezierFI;
-                patches             (pressure suction);
-            }
-            surface
-            {
-                type                surface;
-                patches             (pressure suction);
-            }
+            sensitivityType shapeESI;
+            patches         (pressure suction);
+        }
+        SI
+        {
+            sensitivityType shapeESI;
+            patches         (pressure suction);
+            includeMeshMovement false;
+        }
+        FI
+        {
+            sensitivityType shapeFI;
+            patches         (pressure suction);
         }
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/U b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/U
new file mode 100644
index 0000000000000000000000000000000000000000..3f09bacf52a660a4b0637ac44d1082449222b99c
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/U
@@ -0,0 +1,44 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       volVectorField;
+    object      U;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+dimensions      [ 0 1 -1 0 0 0 0 ];
+
+internalField   uniform ( 5.996344962 0.20939698 0 );
+
+boundaryField
+{
+    frontBack
+    {
+        type            empty;
+    }
+    suction
+    {
+        type            fixedValue;
+        value           uniform ( 0 0 0 );
+    }
+    pressure
+    {
+        type            fixedValue;
+        value           uniform ( 0 0 0 );
+    }
+    inlet
+    {
+        type            freestream;
+        freestreamValue uniform ( 5.996344962 0.20939698 0 );
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/Ua b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/Ua
new file mode 100644
index 0000000000000000000000000000000000000000..c374f446e8bdb808d0b6fdfab812a19e04d47fc7
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/Ua
@@ -0,0 +1,44 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       volVectorField;
+    object      Ua;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+dimensions      [ 0 1 -1 0 0 0 0 ];
+
+internalField   uniform ( 0 0 0 );
+
+boundaryField
+{
+    frontBack
+    {
+        type            empty;
+    }
+    suction
+    {
+        type            adjointWallVelocity;
+        value           uniform ( 0 0 0 );
+    }
+    pressure
+    {
+        type            adjointWallVelocity;
+        value           uniform ( 0 0 0 );
+    }
+    inlet
+    {
+        type            adjointFarFieldVelocity;
+        value           uniform ( 0 0 0 );
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/p b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/p
new file mode 100644
index 0000000000000000000000000000000000000000..8a4f76887c62de68e4e44f51f9f10f0f3f084f7b
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/p
@@ -0,0 +1,43 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       volScalarField;
+    object      p;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+dimensions      [ 0 2 -2 0 0 0 0 ];
+
+internalField   uniform 0;
+
+boundaryField
+{
+    frontBack
+    {
+        type            empty;
+    }
+    suction
+    {
+        type            zeroGradient;
+    }
+    pressure
+    {
+        type            zeroGradient;
+    }
+    inlet
+    {
+        type            outletInlet;
+        outletValue     uniform 0;
+        value           uniform 0;
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/pa b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/pa
new file mode 100644
index 0000000000000000000000000000000000000000..d496048b595d2215dc30ecf8c304e7471048290f
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/pa
@@ -0,0 +1,42 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       volScalarField;
+    object      pa;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+dimensions      [ 0 2 -2 0 0 0 0 ];
+
+internalField   uniform 0;
+
+boundaryField
+{
+    frontBack
+    {
+        type            empty;
+    }
+    suction
+    {
+        type            zeroGradient;
+    }
+    pressure
+    {
+        type            zeroGradient;
+    }
+    inlet
+    {
+        type            adjointFarFieldPressure;
+        value           uniform 0;
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allclean b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allclean
new file mode 100755
index 0000000000000000000000000000000000000000..87ffba857ae48b5bf6e337ded76ab9b0fe85cf68
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allclean
@@ -0,0 +1,9 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/CleanFunctions      # Tutorial clean functions
+#------------------------------------------------------------------------------
+
+cleanCase
+rm -r constant/PARSEC 2> /dev/null
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allrun b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allrun
new file mode 100755
index 0000000000000000000000000000000000000000..b0961695d23414bf1405da19155288c34497b927
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allrun
@@ -0,0 +1,12 @@
+#!/bin/sh
+cd "${0%/*}" || exit                                # Run from this directory
+. ${WM_PROJECT_DIR:?}/bin/tools/RunFunctions        # Tutorial run functions
+#------------------------------------------------------------------------------
+
+resourcesDir=$FOAM_TUTORIALS/incompressible/adjointOptimisationFoam/resources
+
+\cp -r $resourcesDir/meshes/naca0012/polyMesh constant
+runApplication decomposePar
+runParallel $(getApplication)
+
+#------------------------------------------------------------------------------
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/adjointRASProperties b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/adjointRASProperties
new file mode 100644
index 0000000000000000000000000000000000000000..5238b1310a8b19f32c4a90fe5a0fd74a32cc3fbb
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/adjointRASProperties
@@ -0,0 +1,23 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      RASProperties;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+adjointRASModel   adjointLaminar;
+
+adjointTurbulence on;
+
+printCoeffs       off;
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/dynamicMeshDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/dynamicMeshDict
new file mode 100644
index 0000000000000000000000000000000000000000..52f5e65511b0dccbaf2225d8bf78e89622642ea2
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/dynamicMeshDict
@@ -0,0 +1,45 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      dynamicMeshDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+solver volumetricBSplinesMotionSolver;
+
+volumetricBSplinesMotionSolverCoeffs
+{
+    duct
+    {
+        type    cartesian;
+        nCPsU   8;
+        nCPsV   6;
+        nCPsW   3;
+        degreeU 3;
+        degreeV 3;
+        degreeW 2;
+
+        controlPointsDefinition axisAligned;
+        lowerCpBounds           ( 0.1 -0.25  -0.1);
+        upperCpBounds           ( 0.9  0.25   1.1);
+
+        confineUMovement false;
+        confineVMovement false;
+        confineWMovement true;
+        confineBoundaryControlPoints true;
+
+        confineUMinCPs ( (true true true) (true true true) );
+        confineUMaxCPs ( (true true true) (true true true) );
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/transportProperties b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/transportProperties
new file mode 100644
index 0000000000000000000000000000000000000000..5af049eb760a6f571e7fe62045b7dc8945ed83a1
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/transportProperties
@@ -0,0 +1,21 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      transportProperties;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+transportModel  Newtonian;
+
+nu              nu [ 0 2 -1 0 0 0 0 ] 6.e-03;
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/turbulenceProperties b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/turbulenceProperties
new file mode 100644
index 0000000000000000000000000000000000000000..3d9b82f999ca91cee6ba89333c3a46c740e34946
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/turbulenceProperties
@@ -0,0 +1,19 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      turbulenceProperties;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+simulationType  laminar;
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/plot.gp b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/plot.gp
new file mode 100644
index 0000000000000000000000000000000000000000..6fb006f0722aa7db8616337d2b76710fc560c33f
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/plot.gp
@@ -0,0 +1,15 @@
+set multiplot layout 2,2
+set grid
+
+set xl 'Optimization cycle'
+
+set yl 'C_d/C_{d,init}'
+p 'optimisation/objective/0/dragas1' u 1:3 w lp notitle
+
+set yl 'C_l'
+set yr [0.05266:0.05366]
+p 'optimisation/objective/0/liftlift' u 1:2 w lp notitle, 0.05340147181439196 t 'upper bound', 0.05320147181439196 t 'lower bound'
+
+set autoscale y
+set yl '(V-Vinit)/Vinit'
+p 'optimisation/objective/0/volvol' u 1:2 w lp notitle, -0.15 t 'lower bound'
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/controlDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/controlDict
new file mode 100644
index 0000000000000000000000000000000000000000..56098a48357050a33833ff39cf4002c63e04a2c6
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/controlDict
@@ -0,0 +1,47 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      controlDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+application     adjointOptimisationFoam;
+
+startFrom       latestTime;
+
+startTime       0;
+
+stopAt          endTime;
+
+endTime         90;
+
+deltaT          1;
+
+writeControl    timeStep;
+
+writeInterval   10;
+
+purgeWrite      0;
+
+writeFormat     binary;
+
+writePrecision  16;
+
+writeCompression false;
+
+timeFormat      general;
+
+timePrecision   6;
+
+runTimeModifiable yes;
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/decomposeParDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/decomposeParDict
new file mode 100644
index 0000000000000000000000000000000000000000..ce2f8888c0e0a47c87695d3ff4b3d2f9d66ee142
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/decomposeParDict
@@ -0,0 +1,26 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      decomposeParDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+numberOfSubdomains 4;
+
+method             hierarchical;
+
+coeffs
+{
+    n              (2 2 1);
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSchemes b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSchemes
new file mode 100644
index 0000000000000000000000000000000000000000..1f3bb745fe04e9e3a930487b597f027c51707b61
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSchemes
@@ -0,0 +1,52 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      fvSchemes;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+ddtSchemes
+{
+    default         steadyState;
+}
+
+gradSchemes
+{
+    default          Gauss linear;
+    gradUConv        cellLimited Gauss linear 0.5;
+}
+
+divSchemes
+{
+    default                    Gauss linear;
+    div(phi,U)         bounded Gauss linearUpwind gradUConv;
+    div(-phi,Uaas1)    bounded Gauss linearUpwind gradUaConv;
+    div(-phi,Ualift)   bounded Gauss linearUpwind gradUaConv;
+    div(-phi,Uamoment) bounded Gauss linearUpwind gradUaConv;
+}
+
+laplacianSchemes
+{
+    default         Gauss linear corrected;
+}
+
+interpolationSchemes
+{
+    default         linear;
+}
+
+snGradSchemes
+{
+    default         corrected;
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSolution b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSolution
new file mode 100644
index 0000000000000000000000000000000000000000..ee43e55a5cfa4b0519c64aede2004a57eeceb625
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSolution
@@ -0,0 +1,61 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      fvSolution;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+SIMPLE
+{
+    nNonOrthogonalCorrectors 0;
+}
+
+solvers
+{
+    "p.*|pa.*"
+    {
+        solver           PCG;
+        preconditioner   DIC;
+        tolerance        1e-14;
+        relTol           0.01;
+    };
+    "m|ma"
+    {
+        solver           PCG;
+        preconditioner   DIC;
+        tolerance        1e-14;
+        relTol           0.01;
+    };
+    "U.*|Ua.*"
+    {
+        solver           PBiCGStab;
+        preconditioner   DILU;
+        tolerance        1e-14;
+        relTol           0.1;
+    }
+}
+
+relaxationFactors
+{
+    fields
+    {
+        "p.*"    0.8;
+        "pa.*"   0.8;
+    }
+    equations
+    {
+        "U.*"    0.9;
+        "Ua.*"   0.9;
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/optimisationDict
new file mode 100644
index 0000000000000000000000000000000000000000..54cb109b59e2f5f921d6753c1fb8c5a3e829b8bf
--- /dev/null
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/optimisationDict
@@ -0,0 +1,209 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2306                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      optimisationDict;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+optimisationManager    steadyOptimisation;
+
+primalSolvers
+{
+    p1
+    {
+        active                 true;
+        type                   incompressible;
+        solver                 simple;
+        consistent             true;
+        solutionControls
+        {
+            nIters 3000;
+            nInitialIters 10000;
+            consistent             true;
+            residualControl
+            {
+                "p.*"       1.e-7;
+                "U.*"       1.e-7;
+            }
+        }
+    }
+}
+
+adjointManagers
+{
+    am1
+    {
+        primalSolver             p1;
+        adjointSolvers
+        {
+            as1
+            {
+                // choose adjoint solver
+                //----------------------
+                active                 true;
+                type                   incompressible;
+                solver                 adjointSimple;
+                consistent             true;
+
+                // manage objectives
+                //------------------
+                objectives
+                {
+                    type            incompressible;
+                    objectiveNames
+                    {
+                        drag
+                        {
+                            weight          1.;
+                            type            force;
+                            patches         (pressure suction);
+                            direction       (0.9993908270189999 0.034899496703 0);
+                            Aref            2.;
+                            rhoInf          1.225;
+                            UInf            6;
+                            normalize       true;
+                        }
+                    }
+                }
+
+                // ATC treatment
+                //--------------
+                ATCModel
+                {
+                    ATCModel        standard;
+                }
+
+                // solution control
+                //------------------
+                solutionControls
+                {
+                    nIters        3000;
+                    nInitialIters 10000;
+                    consistent             true;
+                    residualControl
+                    {
+                        "pa.*"       1.e-7;
+                        "Ua.*"       1.e-7;
+                    }
+                }
+            }
+            lift
+            {
+                // choose adjoint solver
+                //----------------------
+                active                  true;
+                type                    incompressible;
+                solver                  adjointSimple;
+                isDoubleSidedConstraint true;
+                consistent              true;
+
+                // manage objectives
+                //------------------
+                objectives
+                {
+                    type            incompressible;
+                    objectiveNames
+                    {
+                        lift
+                        {
+                            weight          1000.;
+                            type            force;
+                            patches         (pressure suction);
+                            direction       (-0.034899496703 0.9993908270189999 0);
+                            Aref            2.;
+                            rhoInf          1.225;
+                            UInf            6;
+                            target          0.05340147181439196;
+                            targetLeft      0.05320147181439196;
+                        }
+                    }
+                }
+
+                // ATC treatment
+                //--------------
+                ATCModel
+                {
+                    ATCModel        standard;
+                }
+
+                // solution control
+                //------------------
+                solutionControls
+                {
+                    nIters        3000;
+                    nInitialIters 10000;
+                    consistent             true;
+                    residualControl
+                    {
+                        "pa.*"       1.e-7;
+                        "Ua.*"       1.e-7;
+                    }
+                }
+            }
+            vol
+            {
+                // choose adjoint solver
+                //----------------------
+                active                 true;
+                type                   null;
+                isConstraint           true;
+
+                // manage objectives
+                //------------------
+                objectives
+                {
+                    type                geometric;
+                    objectiveNames
+                    {
+                        vol
+                        {
+                            weight          -1;
+                            type            partialVolume;
+                            patches         (pressure suction);
+                            target          -0.15;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+optimisation
+{
+    convergence
+    {
+        objective       1.e-04;
+    }
+    designVariables
+    {
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (pressure suction);
+        maxInitChange   5.e-3;
+        nonOverlappingCPs true;
+    }
+    updateMethod
+    {
+        method          ISQP;
+        delta           1;
+        lineSearch
+        {
+            type ArmijoConditions;
+            stepUpdateType quadratic;
+            minRatio 0.05;
+        }
+    }
+}
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/constrained/SQP/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/constrained/SQP/system/optimisationDict
index 881ac144c5fba29206a912eff1a3dcfae45220c6..f6cb8b0389c9395d48b22d18e7037aaa139ca5e3 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/constrained/SQP/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/constrained/SQP/system/optimisationDict
@@ -88,14 +88,13 @@ adjointManagers
                 // choose adjoint solver
                 //----------------------
                 active                 true;
-                type                   incompressible;
-                solver                 adjointSimple;
+                type                   null;
                 isConstraint           true;
                 // manage objectives
                 //------------------
                 objectives
                 {
-                    type                incompressible;
+                    type                geometric;
 
                     objectiveNames
                     {
@@ -107,25 +106,6 @@ adjointManagers
                         }
                     }
                 }
-
-                // ATC treatment
-                //--------------
-                ATCModel
-                {
-                    ATCModel        standard;
-                }
-
-                // solution control
-                //------------------
-                solutionControls
-                {
-                    nIters 3000;
-                    residualControl
-                    {
-                        "pa.*"       1.e-7;
-                        "Ua.*"       1.e-7;
-                    }
-                }
             }
         }
     }
@@ -133,16 +113,13 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
-    {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-
-    sensitivities
+    designVariables
     {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
     updateMethod
@@ -155,12 +132,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS-transformBox/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS-transformBox/system/optimisationDict
index b072281b84c6d00f6f277617870d78ec2b947ccc..bad6497e5e4464a0ce6f168f07cc8535fcf91b2a 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS-transformBox/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS-transformBox/system/optimisationDict
@@ -91,16 +91,13 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
     updateMethod
@@ -113,12 +110,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS/system/optimisationDict
index b072281b84c6d00f6f277617870d78ec2b947ccc..bad6497e5e4464a0ce6f168f07cc8535fcf91b2a 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/BFGS/system/optimisationDict
@@ -91,16 +91,13 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
     updateMethod
@@ -113,12 +110,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/SD/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/SD/system/optimisationDict
index cddc8885b167b041a6a9a3ef9a7dccb799c02cb8..860e5996e30c691bf1def5e44f4c2bc76c16e464 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/SD/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/losses/SD/system/optimisationDict
@@ -90,25 +90,18 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (lower upper);
+        maxInitChange   2.e-3;
     }
     updateMethod
     {
         method  steepestDescent;
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/uniformityCellZone/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/uniformityCellZone/system/optimisationDict
index 51028cddc98e8a33f0bb9198ae756299b4b588b4..c356a980f8341d57fd5cce797845737f6bced262 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/uniformityCellZone/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/opt/unconstrained/uniformityCellZone/system/optimisationDict
@@ -91,16 +91,13 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
     updateMethod
@@ -113,12 +110,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/primalAdjoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/primalAdjoint/system/optimisationDict
index ba6f484792acda27fb9fab1ffe55f16384bd175a..30ef057dc41fa598eec67ba9f877f6afbe91986a 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/primalAdjoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/laminar/primalAdjoint/system/optimisationDict
@@ -92,50 +92,28 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            multiple; // used to compute many kinds of sensitivities at the same time
-        patches         (lower upper);
-        sensTypes
+        type             shape;
+        shapeType        Bezier;
+        sensitivityType  multiple;
+        sensitivityTypes (FI ESI SI);
+        patches          (lower upper);
+        FI
         {
-            FIVolSplines
-            {
-                type                volumetricBSplinesFI;
-                patches             (lower upper);
-            }
-            ESIVolSplines
-            {
-                type                volumetricBSplines;
-                patches             (lower upper);
-            }
-            SIVolSplines
-            {
-                type                volumetricBSplines;
-                patches             (lower upper);
-                surfaceSensitivities
-                {
-                    includeMeshMovement false;
-                }
-            }
-            FIBezier
-            {
-                type                BezierFI;
-                patches             (lower upper);
-            }
-            ESIBezier
-            {
-                type                Bezier;
-                patches             (lower upper);
-            }
-            SIBezier
-            {
-                type                Bezier;
-                patches             (lower upper);
-                surfaceSensitivities
-                {
-                    includeMeshMovement false;
-                }
-            }
+            sensitivityType     shapeFI;
+            patches             (lower upper);
+        }
+        ESI
+        {
+            sensitivityType     shapeESI;
+            patches             (lower upper);
+        }
+        SI
+        {
+            sensitivityType     shapeESI;
+            patches             (lower upper);
+            includeMeshMovement false;
         }
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/controlDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/controlDict
index d9ac5b98f9db4276180f796e519f7f56f7b4bad9..b4a97b7ec7fd971780b5fb585582b49ff062f21f 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/controlDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/controlDict
@@ -1,10 +1,11 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2306                                 |
+|  \\    /   O peration     | Version:  2306                                  |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 FoamFile
 {
     version         2;
@@ -12,7 +13,6 @@ FoamFile
     class           dictionary;
     object          controlDict;
 }
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 application     adjointOptimisationFoam;
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/optimisationDict
index 38b87855c1716832717092ee958ede11d256ba1c..7871c43408da4551e3468566aac8d54c4e0d8eae 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-continuation/system/optimisationDict
@@ -92,17 +92,15 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
-    sensitivities
-    {
-        type                volumetricBSplinesFI;
-        patches             (lower upper);
-    }
     updateMethod
     {
         method BFGS;
@@ -110,27 +108,8 @@ optimisation
         {
             etaHessian        0.8;
             scaleFirstHessian true;
-            /*
-            activeDesignVariables
-            (
-                141	142 144	145 147	148
-                150	151 153	154 168	169
-                171	172 174	175 177	178
-                180	181 195	196 198	199
-                201	202 204	205 207	208
-                222	223 225	226 228	229
-                231	232 234	235 249	250
-                252	253 255	256 258	259
-                261	262
-            );
-            */
         }
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-oneGo/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-oneGo/system/optimisationDict
index 38b87855c1716832717092ee958ede11d256ba1c..7871c43408da4551e3468566aac8d54c4e0d8eae 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-oneGo/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS-oneGo/system/optimisationDict
@@ -92,17 +92,15 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
-    sensitivities
-    {
-        type                volumetricBSplinesFI;
-        patches             (lower upper);
-    }
     updateMethod
     {
         method BFGS;
@@ -110,27 +108,8 @@ optimisation
         {
             etaHessian        0.8;
             scaleFirstHessian true;
-            /*
-            activeDesignVariables
-            (
-                141	142 144	145 147	148
-                150	151 153	154 168	169
-                171	172 174	175 177	178
-                180	181 195	196 198	199
-                201	202 204	205 207	208
-                222	223 225	226 228	229
-                231	232 234	235 249	250
-                252	253 255	256 258	259
-                261	262
-            );
-            */
         }
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/multiPoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/multiPoint/system/optimisationDict
index 14bca2ce560fcf1e1a619fdbd7ecd5cea2302ec2..734f4f21753a5a45e8a4090fe63f5b15920b12e1 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/multiPoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/multiPoint/system/optimisationDict
@@ -163,17 +163,15 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
-    sensitivities
-    {
-        type                volumetricBSplinesFI;
-        patches             (lower upper);
-    }
     updateMethod
     {
         method BFGS;
@@ -184,11 +182,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-    meshMovement
-    {
-        type                volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op1/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op1/system/optimisationDict
index bd10cc6a18f39ec5652b2cf2ba772dd20beb9851..558152e13cf053adb94dc9c009f19f07a8ac40dc 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op1/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op1/system/optimisationDict
@@ -92,17 +92,15 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
-    sensitivities
-    {
-        type                volumetricBSplinesFI;
-        patches             (lower upper);
-    }
     updateMethod
     {
         method BFGS;
@@ -113,11 +111,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-    meshMovement
-    {
-        type                volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op2/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op2/system/optimisationDict
index b072281b84c6d00f6f277617870d78ec2b947ccc..bad6497e5e4464a0ce6f168f07cc8535fcf91b2a 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op2/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/losses/BFGS/op2/system/optimisationDict
@@ -91,16 +91,13 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type             shapeOptimisation;
-        writeEachMesh    true;
-    }
-
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
     updateMethod
@@ -113,12 +110,6 @@ optimisation
             scaleFirstHessian true;
         }
     }
-
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/nutSqr/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/nutSqr/system/optimisationDict
index 2fb015f5e3123b6a846b218a9e59182b343224d7..ef3990dc4d5c82cfe40cb557ff1f217eb9b874f2 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/nutSqr/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/nutSqr/system/optimisationDict
@@ -93,17 +93,15 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type                    shapeOptimisation;
-        writeEachMesh           true;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
-    sensitivities
-    {
-        type                   volumetricBSplinesFI;
-        patches                (lower upper);
-    }
     updateMethod
     {
         method BFGS;
@@ -113,11 +111,7 @@ optimisation
             scaleFirstHessian  true;
         }
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
+
 // ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/powerDissipation/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/powerDissipation/system/optimisationDict
index 395fb48ee60640899feaff5e54d32dc2c83ce015..41f30823737ec0bae66aa8192f234223b602f90c 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/powerDissipation/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/opt/powerDissipation/system/optimisationDict
@@ -93,17 +93,15 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type                    shapeOptimisation;
-        writeEachMesh           true;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
+        patches         (lower upper);
+        maxInitChange   2.e-3;
     }
 
-    sensitivities
-    {
-        type                   volumetricBSplinesFI;
-        patches                (lower upper);
-    }
     updateMethod
     {
         method BFGS;
@@ -113,11 +111,6 @@ optimisation
             scaleFirstHessian  true;
         }
     }
-    meshMovement
-    {
-        type                   volumetricBSplines;
-        maxAllowedDisplacement 2.e-3;
-    }
 }
 
 // ************************************************************************* //
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjoint/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjoint/system/optimisationDict
index ba6f484792acda27fb9fab1ffe55f16384bd175a..30ef057dc41fa598eec67ba9f877f6afbe91986a 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjoint/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjoint/system/optimisationDict
@@ -92,50 +92,28 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            multiple; // used to compute many kinds of sensitivities at the same time
-        patches         (lower upper);
-        sensTypes
+        type             shape;
+        shapeType        Bezier;
+        sensitivityType  multiple;
+        sensitivityTypes (FI ESI SI);
+        patches          (lower upper);
+        FI
         {
-            FIVolSplines
-            {
-                type                volumetricBSplinesFI;
-                patches             (lower upper);
-            }
-            ESIVolSplines
-            {
-                type                volumetricBSplines;
-                patches             (lower upper);
-            }
-            SIVolSplines
-            {
-                type                volumetricBSplines;
-                patches             (lower upper);
-                surfaceSensitivities
-                {
-                    includeMeshMovement false;
-                }
-            }
-            FIBezier
-            {
-                type                BezierFI;
-                patches             (lower upper);
-            }
-            ESIBezier
-            {
-                type                Bezier;
-                patches             (lower upper);
-            }
-            SIBezier
-            {
-                type                Bezier;
-                patches             (lower upper);
-                surfaceSensitivities
-                {
-                    includeMeshMovement false;
-                }
-            }
+            sensitivityType     shapeFI;
+            patches             (lower upper);
+        }
+        ESI
+        {
+            sensitivityType     shapeESI;
+            patches             (lower upper);
+        }
+        SI
+        {
+            sensitivityType     shapeESI;
+            patches             (lower upper);
+            includeMeshMovement false;
         }
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjointFullSetup/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjointFullSetup/system/optimisationDict
index 03f7f9d4796e83eea7ed8b8880ffb1e42b99924f..aa22acfe8361588e660c6f0dae69a89d02464e06 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjointFullSetup/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/SA/primalAdjointFullSetup/system/optimisationDict
@@ -94,132 +94,28 @@ adjointManagers
 
 optimisation
 {
-    sensitivities
+    designVariables
     {
-        type            multiple; // used to compute many kinds of sensitivities at the same time
-        patches         (lower upper);
-        sensTypes
+        type             shape;
+        shapeType        Bezier;
+        sensitivityType  multiple;
+        sensitivityTypes (FI ESI SI);
+        patches          (lower upper);
+        FI
         {
-            FIVolSplines
-            {
-                type                volumetricBSplinesFI;
-                patches             (lower upper);
-                includeDistance     true;
-                adjointEikonalSolver
-                {
-                    iters  1000;
-                    tolerance 1.e-6;
-                }
-            }
-            ESIVolSplines
-            {
-                type                         volumetricBSplines;
-                patches                      (lower upper);
-                includeObjectiveContribution true; // one of this or the equivalent flag in
-                                                   // surfaceSensitivities has to be set to true
-                                                   // with this being the prefered one
-                surfaceSensitivities
-                {
-                    patches                      (lower upper);
-                    includeSurfaceArea           true;
-                    includeMeshMovement          true;
-                    includeDistance              true;
-                    includeObjectiveContribution false;
-
-                    // adjointEikonal and adjointMeshMovement solvers should be always nested
-                    // within the dictionary of the sensitivity type they correspond to.
-                    // For (E)SI based sensitivities, this means the surfaceSensitivities dict
-                    // Default values are provided, so the dictionaries can be skipped
-                    adjointEikonalSolver
-                    {
-                        iters  1000;
-                        tolerance 1.e-6;
-                    }
-                    adjointMeshMovementSolver
-                    {
-                        iters  10000;
-                        tolerance 1.e-6;
-                    }
-                }
-            }
-            SIVolSplines
-            {
-                type                         volumetricBSplines;
-                patches                      (lower upper);
-                includeObjectiveContribution true; // same comment as above
-                surfaceSensitivities
-                {
-                    patches                      (lower upper);
-                    includeSurfaceArea           true;
-                    includeMeshMovement          false;
-                    includeDistance              true;
-                    includeObjectiveContribution false;
-                    adjointEikonalSolver
-                    {
-                        iters  1000;
-                        tolerance 1.e-6;
-                    }
-                }
-            }
-            FIBezier
-            {
-                type            BezierFI;
-                includeDistance true;
-                patches         (lower upper);
-                dxdbSolver
-                {
-                    iters           1000;
-                    tolerance       1.e-6;
-                }
-                adjointEikonalSolver
-                {
-                    iters  1000;
-                    tolerance 1.e-6;
-                }
-            }
-            ESIBezier
-            {
-                type            Bezier;
-                includeObjectiveContribution true; // same comment as above
-                surfaceSensitivities
-                {
-                    patches             (lower upper);
-                    includeSurfaceArea  true;
-                    includeMeshMovement true;
-                    includeDistance     true;
-                    includeObjectiveContribution false;
-                    adjointEikonalSolver
-                    {
-                        iters  1000;
-                        tolerance 1.e-6;
-                    }
-                    adjointMeshMovementSolver
-                    {
-                        iters  10000;
-                        tolerance 1.e-6;
-                    }
-                }
-                patches         (lower upper);
-            }
-            SIBezier
-            {
-                type                Bezier;
-                includeObjectiveContribution true; // same comment as above
-                surfaceSensitivities
-                {
-                    patches             (lower upper);
-                    includeSurfaceArea  true;
-                    includeMeshMovement false;
-                    includeDistance     true;
-                    includeObjectiveContribution false;
-                    adjointEikonalSolver
-                    {
-                        iters  1000;
-                        tolerance 1.e-6;
-                    }
-                }
-                patches             (lower upper);
-            }
+            sensitivityType     shapeFI;
+            patches             (lower upper);
+        }
+        ESI
+        {
+            sensitivityType     shapeESI;
+            patches             (lower upper);
+        }
+        SI
+        {
+            sensitivityType     shapeESI;
+            patches             (lower upper);
+            includeMeshMovement false;
         }
     }
 }
diff --git a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/kOmegaSST/opt/system/optimisationDict b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/kOmegaSST/opt/system/optimisationDict
index 4adb513df426ada0cf931188e41c7a8289fbac97..ef4ba5751e52c5f216262c09edc4cbff665fb431 100644
--- a/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/kOmegaSST/opt/system/optimisationDict
+++ b/tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/sbend/turbulent/kOmegaSST/opt/system/optimisationDict
@@ -95,25 +95,18 @@ adjointManagers
 
 optimisation
 {
-    optimisationType
+    designVariables
     {
-        type            shapeOptimisation;
-        writeEachMesh   true;
-    }
-    sensitivities
-    {
-        type            volumetricBSplinesFI;
+        type            shape;
+        shapeType       volumetricBSplines;
+        sensitivityType shapeFI;
         patches         ( lower upper );
+        maxInitChange   0.002;
     }
     updateMethod
     {
         method BFGS;
     }
-    meshMovement
-    {
-        type            volumetricBSplines;
-        maxAllowedDisplacement 0.002;
-    }
 }
 
 // ************************************************************************* //