From 9a89fcc0ed267e863d34287841775040d50bbd5b Mon Sep 17 00:00:00 2001
From: Vaggelis Papoutsis <vaggelisp@gmail.com>
Date: Tue, 18 Jul 2023 17:07:55 +0300
Subject: [PATCH] ENH: overhaul of the adjoint optimisation library

Parts of the adjoint optimisation library were re-designed to generalise
the way sensitivity derivatives (SDs) are computed and to allow easier
extension to primal problems other than the ones governed by
incompressible flows. In specific:
- the adjoint solver now holds virtual functions returning the part of
  SDs that depends only on the primal and the adjoint fields.
- a new class named designVariables was introduced which, apart from
  defining the design variables of the optimisation problem and
  providing hooks for updating them in an optimisation loop, provides
  the part of the SDs that affects directly the flow residuals (e.g.
  geometric variations in shape optimisation, derivatives of source
  terms in topology optimisation, etc). The final assembly of the SDs
  happens here, with the updated sensitivity class acting as an
  intermediate.

With the new structure, when the primal problem changes (for instance,
passive scalars are included), the same design variables and sensitivity
classes can be re-used for all physics, with additional contributions to
the SDs being limited (and contained) to the new adjoint solver to be
implemented. The old code structure would require new SD classes for
each additional primal problem.

As a side-effect, setting up a case has arguably become a bit easier and
more intuitive.

Additional changes include:
---------------------------

- Changes in the formulation and computation of shape sensitivity derivatives
  using the E-SI approach. The latter is now derived directly from the
  FI approach, with proper discretization for the terms and boundary
  conditions that emerge from applying the Gauss divergence theorem used
  to transition from FI to E-SI. When E-SI and FI are based on the same
  Laplace grid displacement model, they are now numerically equivalent
  (the previous formulation proved the theoretical equivalence of the
  two approaches but numerical results could differ, depending on the
  case).
- Sensitivity maps at faces are now computed based (and are deriving
  from) sensitivity maps at points, with a constistent point-to-face
  interpolation (requires the differentiation of volPointInterpolation).
- The objective class now allocates only the member pointers that
  correspond to the non-zero derivatives of the objective w.r.t. the
  flow and geometric quantities, leading to a reduced memory footprint.
  Additionally, contributions from volume-based objectives to the
  adjoint equations have been re-worked, removing the need for
  objectiveManager to be virtual.
- In constrained optimisation, an adjoint solver needs to be present for
  each constraint function. For geometric constraints though, no adjoint
  equations need to solved. This is now accounted for through the null
  adjoint solver and the geometric objectives which do not allocate
  adjoint fields for this kind of constraints, reducing memory
  requirements and file clutter.
- Refactoring of the updateMethod to collaborate with the new
  designVariables. Additionally, all updateMethods can now read and
  write restart data in binary, facilitating exact continuation.
  Furthermore, code shared by various quasi-Newton methods (BFGS, DBFGS,
  LBFGS, SR1) has been organised in the namesake class. Over and above,
  an SQP variant capable of tackling inequality constraints has been
  added (ISQP, with I indicating that the QP problem in the presence of
  inequality constraints is solved through an interior point method).
  Inequality constraints can be one-sided (constraint < upper-value)
  or double-sided (lower-value < constraint < upper-value).
- Bounds can now be defined for the design variables.
  For volumetricBSplines in specific, these can be computed as the
  mid-points of the control points and their neighbouring ones. This
  usually leads to better-defined optimisation problems and reduces the
  chances of an invalid mesh during optimisation.
- Convergence criteria can now be defined for the optimisation loop
  which will stop if the relative objective function reduction over
  the last objective value is lower than a given threshold and
  constraints are satisfied within a give tolerance. If no criteria are
  defined, the optimisation will run for the max. given number of cycles
  provided in controlDict.
- Added a new grid displacement method based on the p-Laplacian
  equation, which seems to outperform other PDE-based approaches.

TUT: updated the shape optimisation tutorials and added a new one
showcasing the use of double-sided constraints, ISQP, applying
no-overlapping constraints to volumetric B-Splines control points
and defining convergence criteria for the optimisation loop.
---
 .../adjointOptimisationFoam.C                 |   10 +-
 .../computeSensitivities.C                    |    3 +-
 .../writeActiveDesignVariables/Make/files     |    3 -
 .../writeActiveDesignVariables/Make/options   |   14 -
 .../writeActiveDesignVariables.C              |  108 --
 .../adjoint/ATCModel/ATCModel/ATCModel.C      |    5 +-
 .../zeroATCcells/faceCells/faceCells.C        |   12 +-
 .../adjointOptimisation/adjoint/Make/files    |   62 +-
 .../adjointOptimisation/adjoint/Make/options  |    3 +
 .../adjointBoundaryCondition.C                |    7 +-
 .../adjointWallVelocityFvPatchVectorField.C   |   11 +-
 .../boundaryAdjointContribution.C             |   12 +-
 .../boundaryAdjointContribution.H             |   24 +-
 .../boundaryAdjointContributionTemplates.C}   |   18 +-
 ...oundaryAdjointContributionIncompressible.C |   39 +-
 ...oundaryAdjointContributionIncompressible.H |   23 +-
 .../displacementMethod/displacementMethod.C   |    6 +
 .../displacementMethod/displacementMethod.H   |   58 +-
 ...displacementMethodelasticityMotionSolver.C |   37 +-
 ...displacementMethodelasticityMotionSolver.H |    7 +-
 .../displacementMethodlaplacianMotionSolver.C |   60 +-
 .../displacementMethodlaplacianMotionSolver.H |    7 +-
 ...displacementMethodpLaplacianMotionSolver.C |  191 +++
 ...isplacementMethodpLaplacianMotionSolver.H} |   76 +-
 .../elasticityMotionSolver.C                  |   81 +-
 .../elasticityMotionSolver.H                  |   12 +-
 .../laplacianMotionSolver.C                   |   85 +-
 .../laplacianMotionSolver.H                   |   25 +-
 .../pLaplacianMotionSolver.C                  |  219 ++++
 .../pLaplacianMotionSolver.H                  |  178 +++
 .../pLaplacianMotionSolverI.H}                |   40 +-
 .../volPointInterpolateAdjoint.C              |  345 ++++++
 .../volPointInterpolationAdjoint.C            |  391 ++++++
 .../volPointInterpolationAdjoint.H            |  193 +++
 .../{objectiveManager => }/objectiveManager.C |  123 +-
 .../{objectiveManager => }/objectiveManager.H |   56 +-
 .../objectiveManagerIncompressible.C          |  132 ---
 .../objectiveGeometric/objectiveGeometric.C   |  119 ++
 .../objectiveGeometric/objectiveGeometric.H}  |   85 +-
 .../objectivePartialVolume.C                  |   24 +-
 .../objectivePartialVolume.H                  |   13 +-
 .../objectiveForce/objectiveForce.C           |   10 +-
 .../objectiveIncompressible.C                 |  332 +-----
 .../objectiveIncompressible.H                 |   69 +-
 .../objectiveIncompressibleI.H                |  187 ++-
 .../objectiveMoment/objectiveMoment.C         |    2 +-
 .../objectiveNutSqr/objectiveNutSqr.C         |   68 +-
 .../objectiveNutSqr/objectiveNutSqr.H         |   21 +-
 .../objectivePowerDissipation.C               |   57 +-
 .../objectivePowerDissipation.H               |   13 +-
 .../objectivePtLosses/objectivePtLosses.C     |    2 +-
 .../objectiveUniformityCellZone.C             |   22 +-
 .../objectiveUniformityPatch.C                |    2 +-
 .../adjoint/objectives/objective/objective.C  |  270 ++---
 .../adjoint/objectives/objective/objective.H  |  110 +-
 .../adjoint/objectives/objective/objectiveI.H |  147 ++-
 .../adjointEikonalSolver.C}                   |  131 ++-
 .../adjointEikonalSolver.H}                   |   29 +-
 .../adjointSensitivity/adjointSensitivity.C}  |  152 +--
 .../adjointSensitivity/adjointSensitivity.H   |  299 +++++
 .../adjointSensitivity/adjointSensitivityI.H  |   96 ++
 .../multiple/sensitivityMultiple.C}           |   35 +-
 .../multiple/sensitivityMultiple.H}           |   21 +-
 .../shape/ESI/sensitivityShapeESI.C           |  168 +++
 .../shape/ESI/sensitivityShapeESI.H}          |   74 +-
 .../shape/FI/sensitivityShapeFI.C             |   88 ++
 .../shape/FI/sensitivityShapeFI.H}            |   50 +-
 .../adjointMeshMovementSolver.C}              |  164 +--
 .../adjointMeshMovementSolver.H}              |  104 +-
 .../ShapeSensitivitiesBase.C}                 |  362 ++++--
 .../ShapeSensitivitiesBase.H}                 |  115 +-
 .../ShapeSensitivitiesBaseTemplates.C}        |   26 +-
 .../shape/surface/sensitivitySurface.C        |  384 ++++++
 .../shape/surface/sensitivitySurface.H}       |  119 +-
 .../surfacePoints/sensitivitySurfacePoints.C  |  560 +++++++++
 .../surfacePoints/sensitivitySurfacePoints.H} |  106 +-
 .../FIBase/FIBaseIncompressible.C             |  183 ---
 .../SIBase/SIBaseIncompressible.C             |  165 ---
 .../adjointSensitivityIncompressible.H        |  219 ----
 .../sensitivityBezierIncompressible.C         |  247 ----
 .../sensitivityBezierIncompressible.H         |  136 ---
 .../sensitivityBezierFIIncompressible.C       |  368 ------
 .../sensitivityBezierFIIncompressible.H       |  168 ---
 .../sensitivitySurfaceIncompressible.C        |  946 ---------------
 .../sensitivitySurfacePointsIncompressible.C  |  773 ------------
 .../sensitivityVolBSplinesIncompressible.C    |  346 ------
 .../sensitivityVolBSplinesIncompressible.H    |  148 ---
 .../sensitivityVolBSplinesFIIncompressible.C  |  409 -------
 .../sensitivityVolBSplinesFIIncompressible.H  |  151 ---
 .../shapeSensitivitiesIncompressible.C        |  176 ---
 .../shapeSensitivitiesIncompressible.H        |  141 ---
 .../sensitivity/sensitivity.C                 |   19 +-
 .../sensitivity/sensitivity.H                 |   41 +-
 .../designVariables/designVariables.C         |  281 +++++
 .../designVariables/designVariables.H         |  282 +++++
 .../designVariables/designVariablesI.H}       |   67 +-
 .../shape/Bezier/BezierDesignVariables.C      |  289 +++++
 .../shape/Bezier/BezierDesignVariables.H      |  172 +++
 .../shapeDesignVariables.C                    |  510 ++++++++
 .../shapeDesignVariables.H                    |  296 +++++
 .../morphingBoxConstraint.C                   |  220 ++++
 .../morphingBoxConstraint.H                   |  239 ++++
 .../none/noConstraint.C                       |  286 +++++
 .../none/noConstraint.H                       |  175 +++
 .../volumetricBSplinesDesignVariables.C       |  600 ++++++++++
 .../volumetricBSplinesDesignVariables.H       |  263 +++++
 .../ArmijoConditions/ArmijoConditions.C       |   23 +-
 .../ArmijoConditions/ArmijoConditions.H       |   26 +-
 .../lineSearch/lineSearch/lineSearch.C        |   71 +-
 .../lineSearch/lineSearch/lineSearch.H        |  114 +-
 .../stepUpdate/quadratic/quadratic.C          |   12 +-
 .../optMeshMovement/optMeshMovement.C         |  208 ----
 .../optMeshMovement/optMeshMovement.H         |  201 ----
 .../optMeshMovementBezier.C                   |  160 ---
 .../optMeshMovementVolumetricBSplines.C       |  173 ---
 ...ntVolumetricBSplinesExternalMotionSolver.C |  190 ---
 ...ntVolumetricBSplinesExternalMotionSolver.H |  138 ---
 .../designVariablesUpdate.C                   |  573 +++++++++
 .../designVariablesUpdate.H                   |  241 ++++
 .../optimisationManager/optimisationManager.C |  297 ++++-
 .../optimisationManager/optimisationManager.H |  141 ++-
 .../optimisationManager/singleRun/singleRun.C |   12 +-
 .../steadyOptimisation/steadyOptimisation.C   |  167 +--
 .../steadyOptimisation/steadyOptimisation.H   |   11 +-
 .../optimisationTypeIncompressible.C          |  344 ------
 .../optimisationTypeIncompressible.H          |  193 ---
 .../shapeOptimisationIncompressible.C         |  207 ----
 .../shapeOptimisationIncompressible.H         |  140 ---
 .../optimisation/updateMethod/BFGS/BFGS.C     |  164 +--
 .../optimisation/updateMethod/BFGS/BFGS.H     |   71 +-
 .../optimisation/updateMethod/DBFGS/DBFGS.C   |  161 +--
 .../optimisation/updateMethod/DBFGS/DBFGS.H   |   64 +-
 .../optimisation/updateMethod/ISQP/ISQP.C     | 1048 +++++++++++++++++
 .../optimisation/updateMethod/ISQP/ISQP.H     |  345 ++++++
 .../optimisation/updateMethod/LBFGS/LBFGS.C   |  617 ++++++++--
 .../optimisation/updateMethod/LBFGS/LBFGS.H   |  147 ++-
 .../optimisation/updateMethod/SQP/SQP.C       |  246 +---
 .../optimisation/updateMethod/SQP/SQP.H       |  106 +-
 .../updateMethod/SQPBase/SQPBase.C            |  178 +++
 .../SQPBase/SQPBase.H}                        |   93 +-
 .../optimisation/updateMethod/SR1/SR1.C       |  151 +--
 .../optimisation/updateMethod/SR1/SR1.H       |   71 +-
 .../conjugateGradient/conjugateGradient.C     |   91 +-
 .../conjugateGradient/conjugateGradient.H     |   36 +-
 .../constrainedOptimisationMethod.C           |   11 +-
 .../constrainedOptimisationMethod.H           |   29 +-
 .../constraintProjection.C                    |   36 +-
 .../constraintProjection.H                    |   28 +-
 .../updateMethod/quasiNewton/quasiNewton.C    |  129 ++
 .../quasiNewton/quasiNewton.H}                |  101 +-
 .../steepestDescent/steepestDescent.C         |   16 +-
 .../steepestDescent/steepestDescent.H         |   13 +-
 .../updateMethod/updateMethod/updateMethod.C  |  162 ++-
 .../updateMethod/updateMethod/updateMethod.H  |  149 ++-
 .../adjoint/parameterization/Bezier/Bezier.C  |    6 +-
 .../NURBS3DVolume/NURBS3DVolume.C             |  196 ++-
 .../NURBS3DVolume/NURBS3DVolume.H             |   19 +-
 .../NURBS3DVolume/NURBS3DVolumeI.H            |   11 +-
 .../cartesian/NURBS3DVolumeCartesian.C        |    5 +-
 .../cylindrical/NURBS3DVolumeCylindrical.C    |    5 +-
 .../cylindrical/NURBS3DVolumeCylindrical.H    |   13 +-
 .../volBSplinesBase/volBSplinesBase.C         |   80 +-
 .../volBSplinesBase/volBSplinesBase.H         |   17 +-
 .../adjointSolverManager.C                    |  173 ++-
 .../adjointSolverManager.H                    |   53 +-
 .../adjointSolver/adjointSolver.C             |  182 ++-
 .../adjointSolver/adjointSolver.H             |  165 ++-
 .../adjointSolver/adjointSolverI.H            |   78 ++
 .../adjointSimple/adjointSimple.C             |  210 ++--
 .../adjointSimple/adjointSimple.H             |   44 +-
 .../incompressibleAdjointSolver.C             |  301 +++--
 .../incompressibleAdjointSolver.H             |  122 +-
 .../incompressibleAdjointSolverTemplates.C    |   58 +
 .../solvers/adjointSolvers/null/adjointNull.C |  238 ++++
 .../solvers/adjointSolvers/null/adjointNull.H |  186 +++
 .../RASTurbulenceModel/RASTurbulenceModel.C   |   15 +-
 .../RASTurbulenceModel/RASTurbulenceModel.H   |    7 +-
 .../incompressiblePrimalSolver.C              |   20 +-
 .../incompressiblePrimalSolver.H              |   18 +-
 .../incompressible/simple/simple.C            |   17 +-
 .../incompressible/simple/simple.H            |    7 +-
 .../primalSolvers/primalSolver/primalSolver.C |   11 +-
 .../primalSolvers/primalSolver/primalSolver.H |   15 +-
 .../adjoint/solvers/solver/solver.C           |   73 +-
 .../adjoint/solvers/solver/solver.H           |   60 +-
 .../adjoint/solvers/solver/solverI.H          |   91 ++
 .../adjoint/solvers/solver/solverTemplates.C  |   20 +-
 .../SIMPLEControl/SIMPLEControl.C             |   18 +
 .../SIMPLEControl/SIMPLEControl.H             |   13 +-
 .../optimisation/SIMPLEControlOpt.C           |  146 ++-
 .../singleRun/SIMPLEControlSingleRun.C        |    4 +-
 .../solverControl/solverControl.H             |    7 +-
 .../solverControl/solverControlI.H            |    6 +
 .../incompressibleAdjointVars.C               |   17 +-
 .../incompressibleAdjointVars.H               |   13 +-
 .../incompressibleAdjointMeanFlowVars.C       |    1 +
 .../incompressibleAdjointMeanFlowVars.H       |    4 +-
 .../adjointRASModel/adjointRASModel.C         |   30 +-
 .../adjointRASModel/adjointRASModel.H         |   13 +-
 .../adjointSpalartAllmaras.C                  |   51 +-
 .../adjointSpalartAllmaras.H                  |   12 +-
 .../adjointkOmegaSST/adjointkOmegaSST.C       |    8 +-
 .../adjointTurbulenceModel.H                  |   17 +-
 .../RAS/RASModelVariables/RASModelVariables.C |   12 +-
 .../RAS/RASModelVariables/RASModelVariables.H |   16 +-
 .../RASModelVariables/RASModelVariablesI.H    |   75 +-
 .../RAS/SpalartAllmaras/SpalartAllmaras.C     |   22 +-
 .../RAS/SpalartAllmaras/SpalartAllmaras.H     |   19 +-
 .../RAS/kEpsilon/kEpsilon.C                   |  101 +-
 .../RAS/kEpsilon/kEpsilon.H                   |   38 +-
 .../RAS/kOmegaSST/kOmegaSST.C                 |    2 +
 .../RAS/kOmegaSST/kOmegaSST.H                 |   18 +-
 .../RAS/laminar/laminar.C                     |   59 +-
 .../motorBike/system/optimisationDict         |  100 +-
 .../laminar/drag/system/optimisationDict      |    4 +-
 .../laminar/lift/system/optimisationDict      |    4 +-
 .../laminar/moment/system/optimisationDict    |    4 +-
 .../liftFullSetup/system/optimisationDict     |    7 +-
 .../liftMinimumSetup/system/optimisationDict  |    6 +-
 .../sbend/laminar/system/optimisationDict     |    4 +-
 .../turbulent/highRe/system/optimisationDict  |    4 +-
 .../lowRe/multiPoint/system/optimisationDict  |    6 +-
 .../lowRe/singlePoint/system/optimisationDict |    4 +-
 .../flowRate/system/optimisationDict          |   17 +-
 .../flowRatePartition/system/optimisationDict |   17 +-
 .../uniformityPatch/system/optimisationDict   |   17 +-
 .../motorBike/system/optimisationDict         |   19 +-
 .../kOmegaSST/lift/system/optimisationDict    |    6 +-
 .../primalAdjoint/constant/dynamicMeshDict    |   27 +
 .../primalAdjoint/system/optimisationDict     |   41 +-
 .../system/optimisationDict                   |   41 +-
 .../primalAdjoint/constant/dynamicMeshDict    |   27 +
 .../primalAdjoint/system/optimisationDict     |   48 +-
 .../naca0012/laminar/multipleConstraints/0/U  |   44 +
 .../naca0012/laminar/multipleConstraints/0/Ua |   44 +
 .../naca0012/laminar/multipleConstraints/0/p  |   43 +
 .../naca0012/laminar/multipleConstraints/0/pa |   42 +
 .../laminar/multipleConstraints/Allclean      |    9 +
 .../laminar/multipleConstraints/Allrun        |   12 +
 .../constant/adjointRASProperties             |   23 +
 .../constant/dynamicMeshDict                  |   45 +
 .../constant/transportProperties              |   21 +
 .../constant/turbulenceProperties             |   19 +
 .../laminar/multipleConstraints/plot.gp       |   15 +
 .../multipleConstraints/system/controlDict    |   47 +
 .../system/decomposeParDict                   |   26 +
 .../multipleConstraints/system/fvSchemes      |   52 +
 .../multipleConstraints/system/fvSolution     |   61 +
 .../system/optimisationDict                   |  209 ++++
 .../constrained/SQP/system/optimisationDict   |   43 +-
 .../BFGS-transformBox/system/optimisationDict |   19 +-
 .../losses/BFGS/system/optimisationDict       |   19 +-
 .../losses/SD/system/optimisationDict         |   17 +-
 .../system/optimisationDict                   |   19 +-
 .../primalAdjoint/system/optimisationDict     |   62 +-
 .../BFGS-continuation/system/controlDict      |    4 +-
 .../BFGS-continuation/system/optimisationDict |   33 +-
 .../losses/BFGS-oneGo/system/optimisationDict |   33 +-
 .../BFGS/multiPoint/system/optimisationDict   |   19 +-
 .../losses/BFGS/op1/system/optimisationDict   |   19 +-
 .../losses/BFGS/op2/system/optimisationDict   |   19 +-
 .../SA/opt/nutSqr/system/optimisationDict     |   20 +-
 .../powerDissipation/system/optimisationDict  |   19 +-
 .../SA/primalAdjoint/system/optimisationDict  |   62 +-
 .../system/optimisationDict                   |  144 +--
 .../kOmegaSST/opt/system/optimisationDict     |   17 +-
 266 files changed, 16802 insertions(+), 11652 deletions(-)
 delete mode 100644 applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/files
 delete mode 100644 applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/Make/options
 delete mode 100644 applications/utilities/preProcessing/optimisation/writeActiveDesignVariables/writeActiveDesignVariables.C
 rename src/optimisation/adjointOptimisation/adjoint/boundaryAdjointContributions/{boundaryAdjointContributionIncompressible/boundaryAdjointContributionIncompressibleTemplates.C => boundaryAdjointContribution/boundaryAdjointContributionTemplates.C} (79%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.C
 rename src/optimisation/adjointOptimisation/adjoint/{optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.H => displacementMethod/displacementMethodpLaplacianMotionSolver/displacementMethodpLaplacianMotionSolver.H} (54%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolver.H
 rename src/optimisation/adjointOptimisation/adjoint/{optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressibleI.H => dynamicMesh/motionSolver/pLaplacianMotionSolver/pLaplacianMotionSolverI.H} (65%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolateAdjoint.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/interpolation/volPointInterpolation/volPointInterpolationAdjoint.H
 rename src/optimisation/adjointOptimisation/adjoint/objectiveManager/{objectiveManager => }/objectiveManager.C (82%)
 rename src/optimisation/adjointOptimisation/adjoint/objectiveManager/{objectiveManager => }/objectiveManager.H (75%)
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/objectives/geometric/objectiveGeometric/objectiveGeometric.C
 rename src/optimisation/adjointOptimisation/adjoint/{objectiveManager/objectiveManagerIncompressible/objectiveManagerIncompressible.H => objectives/geometric/objectiveGeometric/objectiveGeometric.H} (58%)
 rename src/optimisation/adjointOptimisation/adjoint/objectives/{incompressible => geometric}/objectivePartialVolume/objectivePartialVolume.C (88%)
 rename src/optimisation/adjointOptimisation/adjoint/objectives/{incompressible => geometric}/objectivePartialVolume/objectivePartialVolume.H (91%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.C => adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.C} (71%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/adjointEikonalSolver/adjointEikonalSolverIncompressible.H => adjointSensitivity/adjointEikonalSolver/adjointEikonalSolver.H} (91%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/adjointSensitivity/adjointSensitivityIncompressible.C => adjointSensitivity/adjointSensitivity/adjointSensitivity.C} (58%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivity.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/adjointSensitivity/adjointSensitivityI.H
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.C => adjointSensitivity/multiple/sensitivityMultiple.C} (82%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/sensitivityMultiple/sensitivityMultipleIncompressible.H => adjointSensitivity/multiple/sensitivityMultiple.H} (89%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.C
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/{optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.H => adjointSensitivity/adjointSensitivity/shape/ESI/sensitivityShapeESI.H} (59%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.C
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/{optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.H => adjointSensitivity/adjointSensitivity/shape/FI/sensitivityShapeFI.H} (66%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.C => adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.C} (53%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/adjointMeshMovementSolver/adjointMeshMovementSolverIncompressible.H => adjointSensitivity/shape/adjointMeshMovementSolver/adjointMeshMovementSolver.H} (58%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{shapeSensitivitiesBase/shapeSensitivitiesBase.C => adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.C} (50%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{shapeSensitivitiesBase/shapeSensitivitiesBase.H => adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBase.H} (67%)
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{shapeSensitivitiesBase/shapeSensitivitiesBaseTemplates.C => adjointSensitivity/shape/shapeSensitivityBase/ShapeSensitivitiesBaseTemplates.C} (89%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surface/sensitivitySurface.C
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.H => adjointSensitivity/shape/surface/sensitivitySurface.H} (52%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.C
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/{incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.H => adjointSensitivity/shape/surfacePoints/sensitivitySurfacePoints.H} (55%)
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/adjointSensitivity/adjointSensitivityIncompressible.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezier/sensitivityBezierIncompressible.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityBezierFI/sensitivityBezierFIIncompressible.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurface/sensitivitySurfaceIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivitySurfacePoints/sensitivitySurfacePointsIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplines/sensitivityVolBSplinesIncompressible.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/sensitivityVolBSplinesFI/sensitivityVolBSplinesFIIncompressible.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/adjointSensitivity/incompressible/shapeSensitivities/shapeSensitivitiesIncompressible.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/designVariables/designVariables.H
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/{optMeshMovement/optMeshMovementNULL/optMeshMovementNULL.C => designVariables/designVariables/designVariablesI.H} (61%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/Bezier/BezierDesignVariables.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/shapeDesignVariables/shapeDesignVariables.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/morphingBoxConstaint/morphingBoxConstraint.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/morphingBoxConstraints/none/noConstraint.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/designVariables/shape/volumetricBSplines/volumetricBSplinesDesignVariables.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovement/optMeshMovement.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementBezier/optMeshMovementBezier.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplines/optMeshMovementVolumetricBSplines.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optMeshMovement/optMeshMovementVolumetricBSplinesExternalMotionSolver/optMeshMovementVolumetricBSplinesExternalMotionSolver.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationManager/optimisationManager/designVariablesUpdate/designVariablesUpdate.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/optimisationType/optimisationTypeIncompressible.H
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.C
 delete mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/optimisationType/incompressible/shapeOptimisation/shapeOptimisationIncompressible.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/ISQP/ISQP.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/SQPBase/SQPBase.C
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/{adjointSensitivity/incompressible/FIBase/FIBaseIncompressible.H => updateMethod/SQPBase/SQPBase.H} (56%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/optimisation/updateMethod/quasiNewton/quasiNewton.C
 rename src/optimisation/adjointOptimisation/adjoint/optimisation/{adjointSensitivity/incompressible/SIBase/SIBaseIncompressible.H => updateMethod/quasiNewton/quasiNewton.H} (54%)
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/adjointSolver/adjointSolverI.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/incompressible/incompressibleAdjointSolver/incompressibleAdjointSolverTemplates.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.C
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/solvers/adjointSolvers/null/adjointNull.H
 create mode 100644 src/optimisation/adjointOptimisation/adjoint/solvers/solver/solverI.H
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/drag/primalAdjoint/constant/dynamicMeshDict
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/moment/primalAdjoint/constant/dynamicMeshDict
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/U
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/Ua
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/p
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/0/pa
 create mode 100755 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allclean
 create mode 100755 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/Allrun
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/adjointRASProperties
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/dynamicMeshDict
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/transportProperties
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/constant/turbulenceProperties
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/plot.gp
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/controlDict
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/decomposeParDict
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSchemes
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/fvSolution
 create mode 100644 tutorials/incompressible/adjointOptimisationFoam/shapeOptimisation/naca0012/laminar/multipleConstraints/system/optimisationDict

diff --git a/applications/solvers/incompressible/adjointOptimisationFoam/adjointOptimisationFoam.C b/applications/solvers/incompressible/adjointOptimisationFoam/adjointOptimisationFoam.C
index 7d29d484978..ddf2c809abf 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 b7ced6e309f..b1ec73c7553 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 c6e8c8fb9df..00000000000
--- 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 f5b51c5ca77..00000000000
--- 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 b87b0bb837a..00000000000
--- 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 d6791326aab..9a3bb69b9fe 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 9f1d346ed74..714af8c9219 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 c389a144036..8a1b1662f60 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 f3041856b6b..0853278b17d 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 9523408d2d2..d7f399de4fc 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 0172840bb03..0b59ba17700 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 004ea4b0ae7..a8220f2639d 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 b9e52b82946..fc42c5fb81f 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 0fd968f7765..dd6503e8a32 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 3d1d19f474c..86f780e6b1c 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 d77cec9b254..b4a4569bcc8 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 d0da1a72118..32d5a41fd45 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 0521fe4edcc..84f30d1c5d2 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 f26dc3b132b..8aa927342b6 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 50b8ec4df82..6077cbc4d85 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 873aff3d798..dfdcdb54447 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 083ab095893..e1d73c39c23 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 00000000000..d73d0d270b3
--- /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 5c39b97d7ff..dc599353593 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 663f7e3ea9a..3778a5cce99 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 f6c2f0dc058..2fd11babf30 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 fa7039ff592..d53daf1d13e 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 f2cf0471d11..66f397c57e6 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 00000000000..6b03ef4a5de
--- /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 00000000000..37baaa7041e
--- /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 c6274f3b442..3aba688f930 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 00000000000..e174ef78889
--- /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 00000000000..63e07ae5266
--- /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 00000000000..78225457b8b
--- /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 f3d2819ba5a..39b36884316 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 0aeacc6b484..951e53c1163 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 44aaa1a0a73..00000000000
--- 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 00000000000..548329f9a95
--- /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 ff13355b09f..29e14cd11e1 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 5df2db8b9de..f131efa4180 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 4aef943fc85..bc9527a5d7b 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 393f792ca11..d201396112c 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 9c54784d847..8fb87d88e64 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 eb2b89fae94..8af411bf18e 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 a1f72dafb37..3e3b05324dc 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 52765b47e18..f1dd11d622a 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 debd1ae1a02..96e868c0861 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 a429c6d6a9e..4b3b8752583 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 ae7f98112a9..2a910db9a25 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 4f2379d113e..6f744de07b7 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 20ee9e921f5..fef3492b957 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 62fcd785042..7314896024b 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 f1a73419517..c6f5bd91deb 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 76c4fb4e3a6..eca940fdf7a 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 49f3d46343d..7d0270d8cc4 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 4c11fa047d4..5cb1f9d5e98 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 bdffd2682eb..782f4c1e783 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 7bfd9b5e29c..3613265d4dc 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 0cd4e7565f0..326c096f58b 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 00000000000..a476777a096
--- /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 00000000000..a84fa65405e
--- /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 2945d88272b..f616b5aaeb1 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 09a14d66edc..c882c7afe76 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 00000000000..0e3c15ab24d
--- /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 94d19f2fa4a..d3f2ca8cc17 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 00000000000..d68f332896c
--- /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 eaccf4d71cd..7edbc1a3b85 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 663db50998d..ea19da52b74 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 814777256a6..1764cae2e11 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 4206d7b787a..b788697ca1b 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 5389e6de82a..e923ed5f4fc 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 ef77ae31432..fe12e31d730 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 00000000000..3d1b1dfd898
--- /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 a1939835dee..1b1571837ac 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 00000000000..15cc2fc5c61
--- /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 bbadffb4c60..d432d98bfe2 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 c41eda00134..00000000000
--- 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 ee26064a7ef..00000000000
--- 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 eebb436e9ca..00000000000
--- 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 2a50491bc1f..00000000000
--- 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 0422edadeb3..00000000000
--- 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 aa0411d9a93..00000000000
--- 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 56df11726d2..00000000000
--- 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 3b93e7cbcfa..00000000000
--- 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 74e6455ea38..00000000000
--- 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 bcd0e1729d2..00000000000
--- 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 6343fcf2da2..00000000000
--- 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 abd779ab9e8..00000000000
--- 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 7077df12bac..00000000000
--- 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 9f765dbae5e..00000000000
--- 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 100ea1748d8..00000000000
--- 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 6694ed50d76..787ee018769 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 aa706a913b9..9a4790c2cbb 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 00000000000..459b4ed2d66
--- /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 00000000000..7a684a7a14f
--- /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 a6e1a6de7e2..928bd54968e 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 00000000000..05e11c76142
--- /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 00000000000..02a57434554
--- /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 00000000000..c1eca2034bc
--- /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 00000000000..66d1010c2dd
--- /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 00000000000..377230d2424
--- /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 00000000000..ec4d6f87a7a
--- /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 00000000000..7c43cf9e457
--- /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 00000000000..1fe0113573a
--- /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 00000000000..d20d3e08b7d
--- /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 00000000000..8e033f21549
--- /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 20f2efc38d0..0ec06af7028 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 3e1890e0455..2c16c84e958 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 0701dae6eb1..8ae94392d36 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 0904559bd83..52cf061cfd0 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 7a73727b557..6e4722a9262 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 f4e156f457b..00000000000
--- 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 5d96fc65c55..00000000000
--- 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 11c0e384480..00000000000
--- 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 0a494c137ec..00000000000
--- 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 cdde7b1a575..00000000000
--- 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 57d8e976be6..00000000000
--- 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 00000000000..eecea7e7226
--- /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 00000000000..2ed9edddf2b
--- /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 caae3b7b099..6073e1e7d74 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 ecd752586a8..8fe37143260 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 eca6e739ecd..d841dd434d0 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 50a2507689d..d6a0c9e8669 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 0fad239c9c9..7ae292ee2e4 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 ccbf08f466c..00000000000
--- 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 1c1a75619fb..00000000000
--- 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 7a71d5ea6bc..00000000000
--- 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 421feaba671..00000000000
--- 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 b451f6b6601..05aa86d2b7e 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 4995f7f502e..9daa6cd15d9 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 6f38c121317..4d1e2aa32f7 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 44d171fd7f1..55983504b10 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 00000000000..9e0eb0d6678
--- /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 00000000000..56336c41cbe
--- /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 2aa335053f5..6d82c97299f 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 7bffc387e86..2f6db818889 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 7626776fed3..03562ad4a68 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 191770765c0..bc491a6bb50 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 00000000000..fdd380f9f58
--- /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 a2e69d75b17..73595d3bcb5 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 6cba4953fe3..74bec236392 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 fa8fa1d5f05..c350798a3e3 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 bba5598e5cc..fc0e9a9e467 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 5c42ecdcfd5..5954ea74fd0 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 b8c058644f8..9e75e19eab1 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 278f44d77de..de4059fc226 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 25cb7f27c7f..16de6531036 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 0bea873fb95..b054665698d 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 00000000000..a6c35636754
--- /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 0420c38383f..6d2358690b1 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 9251b948aa9..89964254984 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 46599167954..f8dc2e4b9c4 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 fd550bbab35..84a7ad4f7fe 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 0cec839afd7..49ee8c61c62 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 6653c2ed483..457af29dfeb 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 89839e07b65..fc43049d1f9 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 b9e6fbd2b16..4d61349a14a 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 80872f97cd0..8609f43c054 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 1ab353df047..5753398ae67 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 c132d15c3e6..31c10725aa4 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 2719ccd2332..52f0730b122 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 c5515ba8436..4ff3278479b 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 6855f097976..93e03929f00 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 cbe65c281a7..ed51b74da3c 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 5ebb2bcbc6a..7ab01a1fc3f 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 e212b596dc8..9510d1977d0 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 f3a7c8e7654..6a1e228a3e2 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 00000000000..cc02748fb1f
--- /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 926071a5b6d..1b93b635c38 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 145e85b53e4..3a6f7400526 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 c5691c4477c..16296e88fb3 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 de5e821cab3..72523176e91 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 00000000000..ebadf74550f
--- /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 00000000000..ae115b2c5e6
--- /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 00000000000..aa2a0bdf099
--- /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 575ea541963..67b44b9bdbd 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 b4dd92895af..2090f90c892 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 5fa3f120c84..447c9a18806 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 9506e59fb5b..dcfc780640b 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 90a33437094..06cd7955285 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 335df8280fc..2635e0ba7c3 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 1f4c9e43f17..8ea643c6300 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 2a859ff1a75..c65a80d3112 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 223ecde22b2..2b8bb4714cd 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 c67d3d3fb99..9e72c7e8b5c 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 00000000000..1b17b69ac86
--- /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 94fce6a37c7..ea45841b2b6 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 6059ba45c7a..9edba23b8fa 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 02a4b0de483..8543b2b1b55 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 81336289bcf..c9feeab19c8 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 8469547624b..5d122712291 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 29fa7409810..f8d5f3f046c 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 8817320a53d..8d37f646e0a 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 6bf4cbc2dfc..19695a6371e 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 8bb60a23ebf..fcfa216a914 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 7a23aba9254..2b03684a2d7 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 9e523c61e2e..87a0815bd51 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 f9e1d85bf9b..50f7c226dae 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 ba9fff366fc..7f6dc022321 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 3ea09efc90b..3718c444025 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 91606032411..b774d0254f9 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 862cea770d4..760a9730c35 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 04adc50c37e..ebabdf33667 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 e74372f4256..a16f7677e7d 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 21313d3e5aa..2d48ffa006a 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 784f0ef2c88..ccbc5285b8d 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 7ffd13e1e11..9e17b38e592 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 f61317f6593..8d30eaaf27f 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 a05a88cd493..bd4f6f29e82 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 510f0cc7cf9..e0877a762c9 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 b4ba121b47e..cb02c4b7b60 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 f6721bf042d..911b6915e6d 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 3560e42a166..388a145f90c 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 4b831c0f71d..c18f84b1236 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 31b902b7d9f..a6325beff8d 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 dee8234a763..4ba2394ce16 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 59a3b2268c2..3bdf8d043d9 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 bf0f6f3427d..57c2ef4345f 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 b4b5f2512b1..9f1e1b53b82 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 220f2f7c578..5b66688d05e 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 398f6fe2a2a..97618bf8a10 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 a60e75897ea..4ec44012d0f 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 398f6fe2a2a..97618bf8a10 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 b273bf66d08..dcc9b7654c5 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 04e72416da9..453d844e547 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 cd43525df3b..6be392101b5 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 e27a4dbf616..f9df2a73dd7 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 708f7c9e409..6c99208ebbd 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 00000000000..a6e30fa7e51
--- /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 1d6ddd34a63..5af1e7b95e5 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 4b5e2335bb5..77303dd4909 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 00000000000..a6e30fa7e51
--- /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 c18f88e42ee..bcc6a465404 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 00000000000..3f09bacf52a
--- /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 00000000000..c374f446e8b
--- /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 00000000000..8a4f76887c6
--- /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 00000000000..d496048b595
--- /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 00000000000..87ffba857ae
--- /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 00000000000..b0961695d23
--- /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 00000000000..5238b1310a8
--- /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 00000000000..52f5e65511b
--- /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 00000000000..5af049eb760
--- /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 00000000000..3d9b82f999c
--- /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 00000000000..6fb006f0722
--- /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 00000000000..56098a48357
--- /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 00000000000..ce2f8888c0e
--- /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 00000000000..1f3bb745fe0
--- /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 00000000000..ee43e55a5cf
--- /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 00000000000..54cb109b59e
--- /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 881ac144c5f..f6cb8b0389c 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 b072281b84c..bad6497e5e4 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 b072281b84c..bad6497e5e4 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 cddc8885b16..860e5996e30 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 51028cddc98..c356a980f83 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 ba6f484792a..30ef057dc41 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 d9ac5b98f9d..b4a97b7ec7f 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 38b87855c17..7871c43408d 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 38b87855c17..7871c43408d 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 14bca2ce560..734f4f21753 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 bd10cc6a18f..558152e13cf 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 b072281b84c..bad6497e5e4 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 2fb015f5e31..ef3990dc4d5 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 395fb48ee60..41f30823737 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 ba6f484792a..30ef057dc41 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 03f7f9d4796..aa22acfe836 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 4adb513df42..ef4ba5751e5 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;
-    }
 }
 
 // ************************************************************************* //
-- 
GitLab