diff --git a/dune/gfe/assemblers/CMakeLists.txt b/dune/gfe/assemblers/CMakeLists.txt
index e0dfa540445ee4ad122b247b682505a90a70846a..2a6d415715361df50741cecb6e5a2df1fd250545 100644
--- a/dune/gfe/assemblers/CMakeLists.txt
+++ b/dune/gfe/assemblers/CMakeLists.txt
@@ -14,7 +14,6 @@ install(FILES
         localgeodesicfestiffness.hh
         localintegralenergy.hh
         mixedgfeassembler.hh
-        mixedlocalgfeadolcstiffness.hh
         nonplanarcosseratshellenergy.hh
         simofoxenergy.hh
         sumenergy.hh
diff --git a/dune/gfe/assemblers/localenergy.hh b/dune/gfe/assemblers/localenergy.hh
index e188993fbead5ee7fc97c0a715f926d3a815550d..9056e5b75acf7d9124d432a7d42565848c8e4f1a 100644
--- a/dune/gfe/assemblers/localenergy.hh
+++ b/dune/gfe/assemblers/localenergy.hh
@@ -18,6 +18,8 @@ namespace Dune {
     template <class TargetSpace>
     struct LocalEnergyTypes
     {
+      constexpr static bool isProductManifold = false;
+
       using Coefficients = std::vector<TargetSpace>;
       using CompositeCoefficients = TupleVector<std::vector<TargetSpace> >;
     };
@@ -27,6 +29,8 @@ namespace Dune {
     template <class ... Factors>
     struct LocalEnergyTypes<ProductManifold<Factors...> >
     {
+      constexpr static bool isProductManifold = true;
+
       using Coefficients = std::vector<ProductManifold<Factors...> >;
       using CompositeCoefficients = TupleVector<std::vector<Factors>... >;
     };
diff --git a/dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh b/dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh
index 93b6723a66aac4fde8402cac012b7023b58e97cc..25eea316e33ae726fdf2e897b0300322dc9eb292 100644
--- a/dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh
+++ b/dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh
@@ -1,11 +1,9 @@
 #ifndef DUNE_GFE_LOCAL_GEODESIC_FE_ADOL_C_STIFFNESS_HH
 #define DUNE_GFE_LOCAL_GEODESIC_FE_ADOL_C_STIFFNESS_HH
 
-#include <adolc/adouble.h>            // use of active doubles
-#include <adolc/drivers/drivers.h>    // use of "Easy to Use" drivers
-// gradient(.) and hessian(.)
-#include <adolc/interfaces.h>    // use of "Easy to Use" drivers
-#include <adolc/taping.h>             // use of taping
+#include <adolc/adouble.h>
+#include <adolc/taping.h>
+#include <adolc/drivers/drivers.h>
 
 #include <dune/fufem/utilities/adolcnamespaceinjections.hh>
 
@@ -14,6 +12,7 @@
 
 #include <dune/gfe/assemblers/localenergy.hh>
 #include <dune/gfe/assemblers/localgeodesicfestiffness.hh>
+#include <dune/gfe/spaces/productmanifold.hh>
 
 /** \brief Assembles energy gradient and Hessian with ADOL-C (automatic differentiation)
  */
@@ -25,42 +24,33 @@ class LocalGeodesicFEADOLCStiffness
   typedef typename Basis::GridView GridView;
   typedef typename GridView::ctype DT;
   typedef typename TargetSpace::ctype RT;
-  typedef typename GridView::template Codim<0>::Entity Entity;
+  constexpr static int gridDim = GridView::dimension;
 
-  typedef typename TargetSpace::template rebind<adouble>::other ATargetSpace;
+  // The 'active' target spaces, i.e., the number type is replaced by adouble
+  using ATargetSpace = typename TargetSpace::template rebind<adouble>::other;
 
-  // some other sizes
-  constexpr static int gridDim = GridView::dimension;
 
 public:
 
   //! Dimension of a tangent space
   constexpr static int blocksize = TargetSpace::TangentVector::dimension;
 
-  //! Dimension of the embedding space
-  constexpr static int embeddedBlocksize = TargetSpace::EmbeddedTangentVector::dimension;
-
   LocalGeodesicFEADOLCStiffness(const Dune::GFE::LocalEnergy<Basis, ATargetSpace>* energy, bool adolcScalarMode = false)
     : localEnergy_(energy),
     adolcScalarMode_(adolcScalarMode)
   {}
 
   /** \brief Compute the energy at the current configuration */
-  virtual RT energy (const typename Basis::LocalView& localView,
-                     const std::vector<TargetSpace>& localSolution) const override;
+  virtual RT energy(const typename Basis::LocalView& localView,
+                    const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::Coefficients& coefficients) const override;
 
   /** \brief ProductManifolds: Compute the energy from coefficients in separate containers
    * for each factor
    */
   virtual RT energy (const typename Basis::LocalView& localView,
-                     const typename Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::CompositeCoefficients& coefficients) const override
-  {
-    DUNE_THROW(Dune::NotImplemented, "!");
-  }
+                     const typename Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::CompositeCoefficients& coefficients) const override;
 
   /** \brief Assemble the element gradient of the energy functional
-
-     This uses the automatic differentiation toolbox ADOL_C.
    */
   virtual void assembleGradient(const typename Basis::LocalView& localView,
                                 const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::Coefficients& coefficients,
@@ -80,10 +70,7 @@ public:
   virtual void assembleGradientAndHessian(const typename Basis::LocalView& localView,
                                           const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeCoefficients& coefficients,
                                           typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeGradient& localGradient,
-                                          typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeHessian& localHessian) const override
-  {
-    DUNE_THROW(Dune::NotImplemented, "!");
-  }
+                                          typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeHessian& localHessian) const override;
 
   const Dune::GFE::LocalEnergy<Basis, ATargetSpace>* localEnergy_;
   const bool adolcScalarMode_;
@@ -94,7 +81,7 @@ template <class Basis, class TargetSpace>
 typename LocalGeodesicFEADOLCStiffness<Basis, TargetSpace>::RT
 LocalGeodesicFEADOLCStiffness<Basis, TargetSpace>::
 energy(const typename Basis::LocalView& localView,
-       const std::vector<TargetSpace>& localSolution) const
+       const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::Coefficients& localSolution) const
 {
   double pureEnergy;
   int rank = localView.globalBasis().gridView().comm().rank();
@@ -137,6 +124,82 @@ energy(const typename Basis::LocalView& localView,
 }
 
 
+template <class Basis, class TargetSpace>
+typename LocalGeodesicFEADOLCStiffness<Basis, TargetSpace>::RT
+LocalGeodesicFEADOLCStiffness<Basis, TargetSpace>::
+energy(const typename Basis::LocalView& localView,
+       const typename Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::CompositeCoefficients& localConfiguration) const
+{
+  using namespace Dune::Indices;
+
+  int rank = Dune::MPIHelper::getCommunication().rank();
+
+  if constexpr (not Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::isProductManifold)
+  {
+    // TODO: The following fails in the clang-11 C++20 CI jobs
+    //static_assert(localConfiguration.size()==1);
+    return energy(localView, localConfiguration[_0]);
+  }
+  else
+  {
+    using TargetSpace0 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_0])>;
+    using TargetSpace1 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_1])>;
+
+    using ATargetSpace0 = typename TargetSpace0::template rebind<adouble>::other;
+    using ATargetSpace1 = typename TargetSpace1::template rebind<adouble>::other;
+
+    Dune::TupleVector<std::vector<ATargetSpace0>, std::vector<ATargetSpace1> > localAConfiguration;
+    localAConfiguration[_0].resize(localConfiguration[_0].size());
+    localAConfiguration[_1].resize(localConfiguration[_1].size());
+
+    double pureEnergy;
+    trace_on(rank);
+
+    adouble energy = 0;
+
+    // The following loop is not quite intuitive: we copy the localSolution into an
+    // array of FieldVector<double>, go from there to FieldVector<adouble> and
+    // only then to ATargetSpace.
+    // Rationale: The constructor/assignment-from-vector of TargetSpace frequently
+    // contains a projection onto the manifold from the surrounding Euclidean space.
+    // ADOL-C needs a function on the whole Euclidean space, hence that projection
+    // is part of the function and needs to be taped.
+
+    // The following variable cannot be declared inside of the loop, or ADOL-C will report wrong results
+    // (Presumably because several independent variables use the same memory location.)
+    std::vector<typename ATargetSpace0::CoordinateType> aRaw0(localConfiguration[_0].size());
+    for (size_t i=0; i<localConfiguration[_0].size(); i++) {
+      typename TargetSpace0::CoordinateType raw = localConfiguration[_0][i].globalCoordinates();
+      for (size_t j=0; j<raw.size(); j++)
+        aRaw0[i][j] <<= raw[j];
+      localAConfiguration[_0][i] = aRaw0[i];    // may contain a projection onto M -- needs to be done in adouble
+    }
+
+    std::vector<typename ATargetSpace1::CoordinateType> aRaw1(localConfiguration[_1].size());
+    for (size_t i=0; i<localConfiguration[_1].size(); i++) {
+      typename TargetSpace1::CoordinateType raw = localConfiguration[_1][i].globalCoordinates();
+      for (size_t j=0; j<raw.size(); j++)
+        aRaw1[i][j] <<= raw[j];
+      localAConfiguration[_1][i] = aRaw1[i];    // may contain a projection onto M -- needs to be done in adouble
+    }
+
+    try {
+
+      energy = localEnergy_->energy(localView, localAConfiguration);
+
+    } catch (Dune::Exception &e) {
+      trace_off();
+      throw e;
+    }
+    energy >>= pureEnergy;
+
+    trace_off();
+
+    return pureEnergy;
+  }
+}
+
+
 template <class Basis, class TargetSpace>
 void LocalGeodesicFEADOLCStiffness<Basis, TargetSpace>::
 assembleGradient(const typename Basis::LocalView& localView,
@@ -149,6 +212,7 @@ assembleGradient(const typename Basis::LocalView& localView,
   int rank = localView.globalBasis().gridView().comm().rank();
 
   // Compute the actual gradient
+  constexpr static auto embeddedBlocksize = TargetSpace::EmbeddedTangentVector::dimension;
   size_t nDofs = localSolution.size();
   size_t nDoubles = nDofs*embeddedBlocksize;
   std::vector<double> xp(nDoubles);
@@ -200,6 +264,7 @@ assembleGradientAndHessian(const typename Basis::LocalView& localView,
   /////////////////////////////////////////////////////////////////
 
   // Compute the actual gradient
+  constexpr static auto embeddedBlocksize = TargetSpace::EmbeddedTangentVector::dimension;
   size_t nDofs = localSolution.size();
   size_t nDoubles = nDofs*embeddedBlocksize;
   std::vector<double> xp(nDoubles);
@@ -370,4 +435,379 @@ assembleGradientAndHessian(const typename Basis::LocalView& localView,
   }
 }
 
+
+/////////////////////////////////////////////////////////////////////////////////////
+//   Compute gradient and Hessian together
+//   To compute the Hessian we need to compute the gradient anyway, so we may
+//   as well return it.  This saves assembly time.
+/////////////////////////////////////////////////////////////////////////////////////
+template <class Basis, class TargetSpace>
+void LocalGeodesicFEADOLCStiffness<Basis, TargetSpace>::
+assembleGradientAndHessian(const typename Basis::LocalView& localView,
+                           const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeCoefficients& localConfiguration,
+                           typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeGradient& localGradient,
+                           typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeHessian& localHessian) const
+{
+  using namespace Dune::Indices;
+
+  if constexpr (not Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::isProductManifold)
+  {
+    // TODO: The following fails in the clang-11 C++20 CI jobs
+    //static_assert(localConfiguration.size()==1);
+    return assembleGradientAndHessian(localView,
+                                      localConfiguration[_0],
+                                      localGradient[_0],
+                                      localHessian[_0][_0]);
+  }
+  else
+  {
+    int rank = Dune::MPIHelper::getCommunication().rank();
+
+    // Tape energy computation.  We may not have to do this every time, but it's comparatively cheap.
+    energy(localView, localConfiguration);
+
+    /////////////////////////////////////////////////////////////////
+    // Compute the gradient.  It is needed to transform the Hessian
+    // into the correct coordinates.
+    /////////////////////////////////////////////////////////////////
+
+    // TODO: The following fails in the clang-11 C++20 CI jobs
+    //static_assert(localConfiguration.size()==2, "ADOL-C assembly only implemented for product spaces with two factors");
+
+    //! Dimension of the embedding space
+    using TargetSpace0 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_0])>;
+    using TargetSpace1 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_1])>;
+
+    constexpr static int blocksize0 = TargetSpace0::TangentVector::dimension;
+    constexpr static int blocksize1 = TargetSpace1::TangentVector::dimension;
+
+    constexpr static int embeddedBlocksize0 = TargetSpace0::EmbeddedTangentVector::dimension;
+    constexpr static int embeddedBlocksize1 = TargetSpace1::EmbeddedTangentVector::dimension;
+
+    // Compute the actual gradient
+    size_t nDofs0 = localConfiguration[_0].size();
+    size_t nDofs1 = localConfiguration[_1].size();
+    size_t nDoubles = nDofs0*embeddedBlocksize0 + nDofs1*embeddedBlocksize1;
+
+    std::vector<double> xp(nDoubles);
+    int idx=0;
+    for (size_t i=0; i<localConfiguration[_0].size(); i++)
+      for (size_t j=0; j<embeddedBlocksize0; j++)
+        xp[idx++] = localConfiguration[_0][i].globalCoordinates()[j];
+
+    for (size_t i=0; i<localConfiguration[_1].size(); i++)
+      for (size_t j=0; j<embeddedBlocksize1; j++)
+        xp[idx++] = localConfiguration[_1][i].globalCoordinates()[j];
+
+    // Compute gradient
+    std::vector<double> g(nDoubles);
+    gradient(rank,nDoubles,xp.data(),g.data());
+
+    // Copy into Dune type
+    std::vector<typename TargetSpace0::EmbeddedTangentVector> localEmbeddedGradient0(localConfiguration[_0].size());
+    std::vector<typename TargetSpace1::EmbeddedTangentVector> localEmbeddedGradient1(localConfiguration[_1].size());
+
+    idx=0;
+    for (size_t i=0; i<localConfiguration[_0].size(); i++) {
+      for (size_t j=0; j<embeddedBlocksize0; j++)
+        localEmbeddedGradient0[i][j] = g[idx++];
+
+      // Express gradient in local coordinate system
+      localConfiguration[_0][i].orthonormalFrame().mv(localEmbeddedGradient0[i],localGradient[_0][i]);
+    }
+
+    for (size_t i=0; i<localConfiguration[_1].size(); i++) {
+      for (size_t j=0; j<embeddedBlocksize1; j++)
+        localEmbeddedGradient1[i][j] = g[idx++];
+
+      // Express gradient in local coordinate system
+      localConfiguration[_1][i].orthonormalFrame().mv(localEmbeddedGradient1[i],localGradient[_1][i]);
+    }
+
+    /////////////////////////////////////////////////////////////////
+    // Compute Hessian
+    /////////////////////////////////////////////////////////////////
+
+    // We compute the Hessian of the energy functional using the ADOL-C system.
+    // Since ADOL-C does not know about nonlinear spaces, what we get is actually
+    // the Hessian of a prolongation of the energy functional into the surrounding
+    // Euclidean space.  To obtain the Riemannian Hessian from this we apply the
+    // formula described in Absil, Mahoney, Trumpf, "An extrinsic look at the Riemannian Hessian".
+    // This formula consists of two steps:
+    // 1) Remove all entries of the Hessian pertaining to the normal space of the
+    //    manifold.  In the aforementioned paper this is done by projection onto the
+    //    tangent space.  Since we want a matrix that is really smaller (but full rank again),
+    //    we can achieve the same effect by multiplying the embedded Hessian from the left
+    //    and from the right by the matrix of orthonormal frames.
+    // 2) Add a correction involving the Weingarten map.
+    //
+    // This works, and is easy to implement using the ADOL-C "hessian" driver.
+    // However, here we implement a small shortcut.  Computing the embedded Hessian and
+    // multiplying one side by the orthonormal frame is the same as evaluating the Hessian
+    // (seen as an operator from R^n to R^n) in the directions of the vectors of the
+    // orthonormal frame.  By luck, ADOL-C can compute the evaluations of the Hessian in
+    // a given direction directly (in fact, this is also how the "hessian" driver works).
+    // Since there are less frame vectors than the dimension of the embedding space,
+    // this reinterpretation allows to reduce the number of calls to ADOL-C.
+    // In my Cosserat shell tests this reduced assembly time by about 10%.
+
+    std::vector<Dune::FieldMatrix<RT,blocksize0,embeddedBlocksize0> > orthonormalFrame0(nDofs0);
+
+    for (size_t i=0; i<nDofs0; i++)
+      orthonormalFrame0[i] = localConfiguration[_0][i].orthonormalFrame();
+
+    std::vector<Dune::FieldMatrix<RT,blocksize1,embeddedBlocksize1> > orthonormalFrame1(nDofs1);
+
+    for (size_t i=0; i<nDofs1; i++)
+      orthonormalFrame1[i] = localConfiguration[_1][i].orthonormalFrame();
+
+    Dune::Matrix<Dune::FieldMatrix<double,blocksize0, embeddedBlocksize0> > embeddedHessian00(nDofs0,nDofs0);
+    Dune::Matrix<Dune::FieldMatrix<double,blocksize0, embeddedBlocksize1> > embeddedHessian01(nDofs0,nDofs1);
+    Dune::Matrix<Dune::FieldMatrix<double,blocksize1, embeddedBlocksize0> > embeddedHessian10(nDofs1,nDofs0);
+    Dune::Matrix<Dune::FieldMatrix<double,blocksize1, embeddedBlocksize1> > embeddedHessian11(nDofs1,nDofs1);
+
+    if(adolcScalarMode_) {
+      std::vector<double> v(nDoubles);
+      std::vector<double> w(nDoubles);
+
+      std::fill(v.begin(), v.end(), 0.0);
+
+      size_t nDoubles0 = nDofs0*embeddedBlocksize0;   // nDoubles = nDoubles0 + nDoubles1
+      size_t nDoubles1 = nDofs1*embeddedBlocksize1;
+
+      std::fill(v.begin(), v.end(), 0.0);
+
+      for (size_t i=0; i<nDofs0 + nDofs1; i++) {
+
+        // Evaluate Hessian in the direction of each vector of the orthonormal frame
+        if (i < nDofs0) {     //Upper half
+          auto i0 = i;
+          for (int ii0=0; ii0<blocksize0; ii0++) {
+            for (size_t k0=0; k0<embeddedBlocksize0; k0++) {
+              v[i0*embeddedBlocksize0 + k0] = orthonormalFrame0[i0][ii0][k0];
+            }
+            int rc= 3;
+            MINDEC(rc, hess_vec(rank, nDoubles, xp.data(), v.data(), w.data()));
+            if (rc < 0)
+              DUNE_THROW(Dune::Exception, "ADOL-C has returned with error code " << rc << "!");
+
+            for (size_t j0=0; j0<nDoubles0; j0++)         //Upper left
+              embeddedHessian00[i0][j0/embeddedBlocksize0][ii0][j0%embeddedBlocksize0] = w[j0];
+
+            for (size_t j1=0; j1<nDoubles1; j1++)         //Upper right
+              embeddedHessian01[i0][j1/embeddedBlocksize1][ii0][j1%embeddedBlocksize1] = w[nDoubles0 + j1];
+
+            // Make v the null vector again
+            std::fill(&v[i0*embeddedBlocksize0], &v[(i0+1)*embeddedBlocksize0], 0.0);
+          }
+        } else  {     // i = nDofs0 ... nDofs0 + nDofs1 //lower half
+          auto i1 = i - nDofs0;
+          for (int ii1=0; ii1<blocksize1; ii1++) {
+            for (size_t k1=0; k1<embeddedBlocksize1; k1++) {
+              v[nDoubles0 + i1*embeddedBlocksize1 + k1] = orthonormalFrame1[i1][ii1][k1];
+            }
+            int rc= 3;
+            MINDEC(rc, hess_vec(rank, nDoubles, xp.data(), v.data(), w.data()));
+            if (rc < 0)
+              DUNE_THROW(Dune::Exception, "ADOL-C has returned with error code " << rc << "!");
+
+            for (size_t j0=0; j0<nDoubles0; j0++)         // Upper left
+              embeddedHessian10[i1][j0/embeddedBlocksize0][ii1][j0%embeddedBlocksize0] = w[j0];
+
+            for (size_t j1=0; j1<nDoubles1; j1++)         //Upper right
+              embeddedHessian11[i1][j1/embeddedBlocksize1][ii1][j1%embeddedBlocksize1] = w[nDoubles0 + j1];
+
+            // Make v the null vector again
+            std::fill(&v[nDoubles0 + i1*embeddedBlocksize1], &v[nDoubles0 + (i1+1)*embeddedBlocksize1], 0.0);
+          }
+        }
+      }
+
+    } else { //ADOL-C vector mode}
+      int n = nDoubles;
+      int nDirections = nDofs0 * blocksize0 + nDofs1 * blocksize1;
+      double* tangent[nDoubles];
+      for(size_t i=0; i<nDoubles; i++)
+        tangent[i] = (double*)malloc(nDirections*sizeof(double));
+
+      double* rawHessian[nDoubles];
+      for(size_t i=0; i<nDoubles; i++)
+        rawHessian[i] = (double*)malloc(nDirections*sizeof(double));
+
+      // Initialize directions field with zeros
+      for (int j=0; j<nDirections; j++)
+        for (int i=0; i<n; i++)
+          tangent[i][j] = 0.0;
+
+      for (size_t j=0; j<nDofs0*blocksize0; j++)
+        for (int i=0; i<embeddedBlocksize0; i++)
+          tangent[(j/blocksize0)*embeddedBlocksize0+i][j] = orthonormalFrame0[j/blocksize0][j%blocksize0][i];
+
+      for (size_t j=0; j<nDofs1*blocksize1; j++)
+        for (int i=0; i<embeddedBlocksize1; i++)
+          tangent[nDofs0*embeddedBlocksize0 + (j/blocksize1)*embeddedBlocksize1+i][nDofs0*blocksize0 + j] = orthonormalFrame1[j/blocksize1][j%blocksize1][i];
+
+      hess_mat(rank,nDoubles,nDirections,xp.data(),tangent,rawHessian);
+
+      // Copy Hessian into Dune data type
+      size_t offset0 = nDofs0*embeddedBlocksize0;
+      size_t offset1 = nDofs0*blocksize0;
+
+      // upper left block
+      for(size_t i=0; i<nDofs0*embeddedBlocksize0; i++)
+        for (size_t j=0; j<nDofs0*blocksize0; j++)
+          embeddedHessian00[j/blocksize0][i/embeddedBlocksize0][j%blocksize0][i%embeddedBlocksize0] = rawHessian[i][j];
+
+      // upper right block
+      for(size_t i=0; i<nDofs1*embeddedBlocksize1; i++)
+        for (size_t j=0; j<nDofs0*blocksize0; j++)
+          embeddedHessian01[j/blocksize0][i/embeddedBlocksize1][j%blocksize0][i%embeddedBlocksize1] = rawHessian[offset0+i][j];
+
+      // lower left block
+      for(size_t i=0; i<nDofs0*embeddedBlocksize0; i++)
+        for (size_t j=0; j<nDofs1*blocksize1; j++)
+          embeddedHessian10[j/blocksize1][i/embeddedBlocksize0][j%blocksize1][i%embeddedBlocksize0] = rawHessian[i][offset1+j];
+
+      // lower right block
+      for(size_t i=0; i<nDofs1*embeddedBlocksize1; i++)
+        for (size_t j=0; j<nDofs1*blocksize1; j++)
+          embeddedHessian11[j/blocksize1][i/embeddedBlocksize1][j%blocksize1][i%embeddedBlocksize1] = rawHessian[offset0+i][offset1+j];
+
+      for(size_t i=0; i<nDoubles; i++) {
+        free(rawHessian[i]);
+        free(tangent[i]);
+      }
+    }
+
+    // From this, compute the Hessian with respect to the manifold (which we assume here is embedded
+    // isometrically in a Euclidean space.
+    // For the detailed explanation of the following see: Absil, Mahoney, Trumpf, "An extrinsic look
+    // at the Riemannian Hessian".
+
+    using namespace Dune::Indices;
+
+    localHessian[_0][_0].setSize(nDofs0,nDofs0);
+
+    for (size_t col=0; col<nDofs0; col++)
+    {
+      for (size_t subCol=0; subCol<blocksize0; subCol++)
+      {
+        typename TargetSpace0::EmbeddedTangentVector z = orthonormalFrame0[col][subCol];
+
+        // P_x \partial^2 f z
+        for (size_t row=0; row<nDofs0; row++)
+        {
+          typename TargetSpace0::TangentVector semiEmbeddedProduct;
+          embeddedHessian00[row][col].mv(z,semiEmbeddedProduct);
+
+          for (int subRow=0; subRow<blocksize0; subRow++)
+            localHessian[_0][_0][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
+        }
+      }
+    }
+
+    localHessian[_0][_1].setSize(nDofs0,nDofs1);
+
+    for (size_t col=0; col<nDofs1; col++)
+    {
+      for (size_t subCol=0; subCol<blocksize1; subCol++)
+      {
+        typename TargetSpace1::EmbeddedTangentVector z = orthonormalFrame1[col][subCol];
+
+        // P_x \partial^2 f z
+        for (size_t row=0; row<nDofs0; row++)
+        {
+          typename TargetSpace0::TangentVector semiEmbeddedProduct;
+          embeddedHessian01[row][col].mv(z,semiEmbeddedProduct);
+
+          for (int subRow=0; subRow<blocksize0; subRow++)
+            localHessian[_0][_1][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
+        }
+      }
+    }
+
+    localHessian[_1][_0].setSize(nDofs1,nDofs0);
+
+    for (size_t col=0; col<nDofs0; col++)
+    {
+      for (size_t subCol=0; subCol<blocksize0; subCol++)
+      {
+        typename TargetSpace0::EmbeddedTangentVector z = orthonormalFrame0[col][subCol];
+
+        // P_x \partial^2 f z
+        for (size_t row=0; row<nDofs1; row++) {
+          typename TargetSpace1::TangentVector semiEmbeddedProduct;
+          embeddedHessian10[row][col].mv(z,semiEmbeddedProduct);
+
+          for (int subRow=0; subRow<blocksize1; subRow++)
+            localHessian[_1][_0][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
+        }
+      }
+    }
+
+    localHessian[_1][_1].setSize(nDofs1,nDofs1);
+
+    for (size_t col=0; col<nDofs1; col++)
+    {
+      for (size_t subCol=0; subCol<blocksize1; subCol++)
+      {
+        typename TargetSpace1::EmbeddedTangentVector z = orthonormalFrame1[col][subCol];
+
+        // P_x \partial^2 f z
+        for (size_t row=0; row<nDofs1; row++)
+        {
+          typename TargetSpace1::TangentVector semiEmbeddedProduct;
+          embeddedHessian11[row][col].mv(z,semiEmbeddedProduct);
+
+          for (int subRow=0; subRow<blocksize1; subRow++)
+            localHessian[_1][_1][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
+        }
+      }
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    //  Further correction due to non-planar configuration space
+    //  + \mathfrak{A}_x(z,P^\orth_x \partial f)
+    //////////////////////////////////////////////////////////////////////////////////////
+
+    // Project embedded gradient onto normal space
+    std::vector<typename TargetSpace0::EmbeddedTangentVector> projectedGradient0(nDofs0);
+    for (size_t i=0; i<nDofs0; i++)
+      projectedGradient0[i] = localConfiguration[_0][i].projectOntoNormalSpace(localEmbeddedGradient0[i]);
+
+    std::vector<typename TargetSpace1::EmbeddedTangentVector> projectedGradient1(nDofs1);
+    for (size_t i=0; i<nDofs1; i++)
+      projectedGradient1[i] = localConfiguration[_1][i].projectOntoNormalSpace(localEmbeddedGradient1[i]);
+
+    // The Weingarten map has only diagonal entries
+    for (size_t row=0; row<nDofs0; row++)
+    {
+      for (size_t subRow=0; subRow<blocksize0; subRow++)
+      {
+        typename TargetSpace0::EmbeddedTangentVector z = orthonormalFrame0[row][subRow];
+        typename TargetSpace0::EmbeddedTangentVector tmp1 = localConfiguration[_0][row].weingarten(z,projectedGradient0[row]);
+
+        typename TargetSpace0::TangentVector tmp2;
+        orthonormalFrame0[row].mv(tmp1,tmp2);
+
+        localHessian[_0][_0][row][row][subRow] += tmp2;
+      }
+    }
+
+    for (size_t row=0; row<nDofs1; row++)
+    {
+      for (size_t subRow=0; subRow<blocksize1; subRow++)
+      {
+        typename TargetSpace1::EmbeddedTangentVector z = orthonormalFrame1[row][subRow];
+        typename TargetSpace1::EmbeddedTangentVector tmp1 = localConfiguration[_1][row].weingarten(z,projectedGradient1[row]);
+
+        typename TargetSpace1::TangentVector tmp2;
+        orthonormalFrame1[row].mv(tmp1,tmp2);
+
+        localHessian[_1][_1][row][row][subRow] += tmp2;
+      }
+    }
+  }
+}
+
 #endif
diff --git a/dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh b/dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh
deleted file mode 100644
index 02d287d052374259080aa788e8687c3057ec8be9..0000000000000000000000000000000000000000
--- a/dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh
+++ /dev/null
@@ -1,516 +0,0 @@
-#ifndef DUNE_GFE_MIXEDLOCALGFEADOLCSTIFFNESS_HH
-#define DUNE_GFE_MIXEDLOCALGFEADOLCSTIFFNESS_HH
-
-#include <adolc/adouble.h>            // use of active doubles
-#include <adolc/drivers/drivers.h>    // use of "Easy to Use" drivers
-// gradient(.) and hessian(.)
-#include <adolc/interfaces.h>    // use of "Easy to Use" drivers
-#include <adolc/taping.h>             // use of taping
-
-#include <dune/fufem/utilities/adolcnamespaceinjections.hh>
-
-#include <dune/common/fmatrix.hh>
-#include <dune/istl/matrix.hh>
-
-#include <dune/gfe/assemblers/localgeodesicfestiffness.hh>
-#include <dune/gfe/spaces/productmanifold.hh>
-
-/** \brief Assembles energy gradient and Hessian with ADOL-C (automatic differentiation)
- */
-template<class Basis, class TargetSpace>
-class MixedLocalGFEADOLCStiffness
-  : public LocalGeodesicFEStiffness<Basis,TargetSpace>
-{
-  // grid types
-  typedef typename Basis::GridView GridView;
-  typedef typename GridView::ctype DT;
-  typedef typename TargetSpace::ctype RT;
-
-  // The 'active' target spaces, i.e., the number type is replaced by adouble
-  using ATargetSpace = typename TargetSpace::template rebind<adouble>::other;
-
-  // some other sizes
-  constexpr static int gridDim = GridView::dimension;
-
-public:
-
-  MixedLocalGFEADOLCStiffness(const Dune::GFE::LocalEnergy<Basis,ATargetSpace>* energy, bool adolcScalarMode = false)
-    : localEnergy_(energy),
-    adolcScalarMode_(adolcScalarMode)
-  {}
-
-  /** \brief Compute the energy at the current configuration */
-  virtual RT energy (const typename Basis::LocalView& localView,
-                     const typename Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::Coefficients& localConfiguration) const override
-  {
-    DUNE_THROW(Dune::NotImplemented, "!");
-  }
-
-
-  /** \brief Compute the energy at the current configuration */
-  virtual RT energy (const typename Basis::LocalView& localView,
-                     const typename Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::CompositeCoefficients& localConfiguration) const override;
-
-  /** \brief Assemble the element gradient of the energy functional */
-  void assembleGradient(const typename Basis::LocalView& localView,
-                        const std::vector<TargetSpace>& solution,
-                        std::vector<typename TargetSpace::TangentVector>& gradient) const override
-  {
-    DUNE_THROW(Dune::NotImplemented, "!");
-  }
-
-  virtual void assembleGradientAndHessian(const typename Basis::LocalView& localView,
-                                          const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::Coefficients& coefficients,
-                                          typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::Gradient& localGradient,
-                                          typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::Hessian& localHessian) const override
-  {
-    DUNE_THROW(Dune::NotImplemented, "!");
-  }
-
-  /** \brief Assemble the local stiffness matrix at the current position
-
-     This uses the automatic differentiation toolbox ADOL_C.
-   */
-  virtual void assembleGradientAndHessian(const typename Basis::LocalView& localView,
-                                          const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeCoefficients& localConfiguration,
-                                          typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeGradient& localGradient,
-                                          typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeHessian& localHessian) const override;
-
-  const Dune::GFE::LocalEnergy<Basis, ATargetSpace>* localEnergy_;
-  const bool adolcScalarMode_;
-};
-
-
-template <class Basis, class TargetSpace>
-typename MixedLocalGFEADOLCStiffness<Basis, TargetSpace>::RT
-MixedLocalGFEADOLCStiffness<Basis, TargetSpace>::
-energy(const typename Basis::LocalView& localView,
-       const typename Dune::GFE::Impl::LocalEnergyTypes<TargetSpace>::CompositeCoefficients& localConfiguration) const
-{
-  using namespace Dune::Indices;
-
-  int rank = Dune::MPIHelper::getCommunication().rank();
-
-  using TargetSpace0 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_0])>;
-  using TargetSpace1 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_1])>;
-
-  using ATargetSpace0 = typename TargetSpace0::template rebind<adouble>::other;
-  using ATargetSpace1 = typename TargetSpace1::template rebind<adouble>::other;
-
-  Dune::TupleVector<std::vector<ATargetSpace0>, std::vector<ATargetSpace1> > localAConfiguration;
-  localAConfiguration[_0].resize(localConfiguration[_0].size());
-  localAConfiguration[_1].resize(localConfiguration[_1].size());
-
-  double pureEnergy;
-  trace_on(rank);
-
-  adouble energy = 0;
-
-  // The following loop is not quite intuitive: we copy the localSolution into an
-  // array of FieldVector<double>, go from there to FieldVector<adouble> and
-  // only then to ATargetSpace.
-  // Rationale: The constructor/assignment-from-vector of TargetSpace frequently
-  // contains a projection onto the manifold from the surrounding Euclidean space.
-  // ADOL-C needs a function on the whole Euclidean space, hence that projection
-  // is part of the function and needs to be taped.
-
-  // The following variable cannot be declared inside of the loop, or ADOL-C will report wrong results
-  // (Presumably because several independent variables use the same memory location.)
-  std::vector<typename ATargetSpace0::CoordinateType> aRaw0(localConfiguration[_0].size());
-  for (size_t i=0; i<localConfiguration[_0].size(); i++) {
-    typename TargetSpace0::CoordinateType raw = localConfiguration[_0][i].globalCoordinates();
-    for (size_t j=0; j<raw.size(); j++)
-      aRaw0[i][j] <<= raw[j];
-    localAConfiguration[_0][i] = aRaw0[i];    // may contain a projection onto M -- needs to be done in adouble
-  }
-
-  std::vector<typename ATargetSpace1::CoordinateType> aRaw1(localConfiguration[_1].size());
-  for (size_t i=0; i<localConfiguration[_1].size(); i++) {
-    typename TargetSpace1::CoordinateType raw = localConfiguration[_1][i].globalCoordinates();
-    for (size_t j=0; j<raw.size(); j++)
-      aRaw1[i][j] <<= raw[j];
-    localAConfiguration[_1][i] = aRaw1[i];    // may contain a projection onto M -- needs to be done in adouble
-  }
-
-  try {
-
-    energy = localEnergy_->energy(localView, localAConfiguration);
-
-  } catch (Dune::Exception &e) {
-    trace_off();
-    throw e;
-  }
-  energy >>= pureEnergy;
-
-  trace_off();
-
-  return pureEnergy;
-}
-
-// ///////////////////////////////////////////////////////////
-//   Compute gradient and Hessian together
-//   To compute the Hessian we need to compute the gradient anyway, so we may
-//   as well return it.  This saves assembly time.
-// ///////////////////////////////////////////////////////////
-template <class Basis, class TargetSpace>
-void MixedLocalGFEADOLCStiffness<Basis, TargetSpace>::
-assembleGradientAndHessian(const typename Basis::LocalView& localView,
-                           const typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeCoefficients& localConfiguration,
-                           typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeGradient& localGradient,
-                           typename Dune::GFE::Impl::LocalStiffnessTypes<TargetSpace>::CompositeHessian& localHessian) const
-{
-  int rank = Dune::MPIHelper::getCommunication().rank();
-
-  // Tape energy computation.  We may not have to do this every time, but it's comparatively cheap.
-  using namespace Dune::Indices;
-
-  energy(localView, localConfiguration);
-
-  /////////////////////////////////////////////////////////////////
-  // Compute the gradient.  It is needed to transform the Hessian
-  // into the correct coordinates.
-  /////////////////////////////////////////////////////////////////
-
-  //! Dimension of the embedding space
-  using TargetSpace0 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_0])>;
-  using TargetSpace1 = std::decay_t<decltype(std::declval<TargetSpace>()[Dune::Indices::_1])>;
-
-  constexpr static int blocksize0 = TargetSpace0::TangentVector::dimension;
-  constexpr static int blocksize1 = TargetSpace1::TangentVector::dimension;
-
-  constexpr static int embeddedBlocksize0 = TargetSpace0::EmbeddedTangentVector::dimension;
-  constexpr static int embeddedBlocksize1 = TargetSpace1::EmbeddedTangentVector::dimension;
-
-  // Compute the actual gradient
-  size_t nDofs0 = localConfiguration[_0].size();
-  size_t nDofs1 = localConfiguration[_1].size();
-  size_t nDoubles = nDofs0*embeddedBlocksize0 + nDofs1*embeddedBlocksize1;
-
-  std::vector<double> xp(nDoubles);
-  int idx=0;
-  for (size_t i=0; i<localConfiguration[_0].size(); i++)
-    for (size_t j=0; j<embeddedBlocksize0; j++)
-      xp[idx++] = localConfiguration[_0][i].globalCoordinates()[j];
-
-  for (size_t i=0; i<localConfiguration[_1].size(); i++)
-    for (size_t j=0; j<embeddedBlocksize1; j++)
-      xp[idx++] = localConfiguration[_1][i].globalCoordinates()[j];
-
-  // Compute gradient
-  std::vector<double> g(nDoubles);
-  gradient(rank,nDoubles,xp.data(),g.data());                    // gradient evaluation
-
-  // Copy into Dune type
-  std::vector<typename TargetSpace0::EmbeddedTangentVector> localEmbeddedGradient0(localConfiguration[_0].size());
-  std::vector<typename TargetSpace1::EmbeddedTangentVector> localEmbeddedGradient1(localConfiguration[_1].size());
-
-  idx=0;
-  for (size_t i=0; i<localConfiguration[_0].size(); i++) {
-    for (size_t j=0; j<embeddedBlocksize0; j++)
-      localEmbeddedGradient0[i][j] = g[idx++];
-
-    // Express gradient in local coordinate system
-    localConfiguration[_0][i].orthonormalFrame().mv(localEmbeddedGradient0[i],localGradient[_0][i]);
-  }
-
-  for (size_t i=0; i<localConfiguration[_1].size(); i++) {
-    for (size_t j=0; j<embeddedBlocksize1; j++)
-      localEmbeddedGradient1[i][j] = g[idx++];
-
-    // Express gradient in local coordinate system
-    localConfiguration[_1][i].orthonormalFrame().mv(localEmbeddedGradient1[i],localGradient[_1][i]);
-  }
-
-  /////////////////////////////////////////////////////////////////
-  // Compute Hessian
-  /////////////////////////////////////////////////////////////////
-
-  // We compute the Hessian of the energy functional using the ADOL-C system.
-  // Since ADOL-C does not know about nonlinear spaces, what we get is actually
-  // the Hessian of a prolongation of the energy functional into the surrounding
-  // Euclidean space.  To obtain the Riemannian Hessian from this we apply the
-  // formula described in Absil, Mahoney, Trumpf, "An extrinsic look at the Riemannian Hessian".
-  // This formula consists of two steps:
-  // 1) Remove all entries of the Hessian pertaining to the normal space of the
-  //    manifold.  In the aforementioned paper this is done by projection onto the
-  //    tangent space.  Since we want a matrix that is really smaller (but full rank again),
-  //    we can achieve the same effect by multiplying the embedded Hessian from the left
-  //    and from the right by the matrix of orthonormal frames.
-  // 2) Add a correction involving the Weingarten map.
-  //
-  // This works, and is easy to implement using the ADOL-C "hessian" driver.
-  // However, here we implement a small shortcut.  Computing the embedded Hessian and
-  // multiplying one side by the orthonormal frame is the same as evaluating the Hessian
-  // (seen as an operator from R^n to R^n) in the directions of the vectors of the
-  // orthonormal frame.  By luck, ADOL-C can compute the evaluations of the Hessian in
-  // a given direction directly (in fact, this is also how the "hessian" driver works).
-  // Since there are less frame vectors than the dimension of the embedding space,
-  // this reinterpretation allows to reduce the number of calls to ADOL-C.
-  // In my Cosserat shell tests this reduced assembly time by about 10%.
-
-  std::vector<Dune::FieldMatrix<RT,blocksize0,embeddedBlocksize0> > orthonormalFrame0(nDofs0);
-
-  for (size_t i=0; i<nDofs0; i++)
-    orthonormalFrame0[i] = localConfiguration[_0][i].orthonormalFrame();
-
-  std::vector<Dune::FieldMatrix<RT,blocksize1,embeddedBlocksize1> > orthonormalFrame1(nDofs1);
-
-  for (size_t i=0; i<nDofs1; i++)
-    orthonormalFrame1[i] = localConfiguration[_1][i].orthonormalFrame();
-
-  Dune::Matrix<Dune::FieldMatrix<double,blocksize0, embeddedBlocksize0> > embeddedHessian00(nDofs0,nDofs0);
-  Dune::Matrix<Dune::FieldMatrix<double,blocksize0, embeddedBlocksize1> > embeddedHessian01(nDofs0,nDofs1);
-  Dune::Matrix<Dune::FieldMatrix<double,blocksize1, embeddedBlocksize0> > embeddedHessian10(nDofs1,nDofs0);
-  Dune::Matrix<Dune::FieldMatrix<double,blocksize1, embeddedBlocksize1> > embeddedHessian11(nDofs1,nDofs1);
-
-  if(adolcScalarMode_) {
-    std::vector<double> v(nDoubles);
-    std::vector<double> w(nDoubles);
-
-    std::fill(v.begin(), v.end(), 0.0);
-
-    size_t nDoubles0 = nDofs0*embeddedBlocksize0;     // nDoubles = nDoubles0 + nDoubles1
-    size_t nDoubles1 = nDofs1*embeddedBlocksize1;
-
-    std::fill(v.begin(), v.end(), 0.0);
-
-    for (size_t i=0; i<nDofs0 + nDofs1; i++) {
-
-      // Evaluate Hessian in the direction of each vector of the orthonormal frame
-      if (i < nDofs0) {       //Upper half
-        auto i0 = i;
-        for (int ii0=0; ii0<blocksize0; ii0++) {
-          for (size_t k0=0; k0<embeddedBlocksize0; k0++) {
-            v[i0*embeddedBlocksize0 + k0] = orthonormalFrame0[i0][ii0][k0];
-          }
-          int rc= 3;
-          MINDEC(rc, hess_vec(rank, nDoubles, xp.data(), v.data(), w.data()));
-          if (rc < 0)
-            DUNE_THROW(Dune::Exception, "ADOL-C has returned with error code " << rc << "!");
-
-          for (size_t j0=0; j0<nDoubles0; j0++)           //Upper left
-            embeddedHessian00[i0][j0/embeddedBlocksize0][ii0][j0%embeddedBlocksize0] = w[j0];
-
-          for (size_t j1=0; j1<nDoubles1; j1++)           //Upper right
-            embeddedHessian01[i0][j1/embeddedBlocksize1][ii0][j1%embeddedBlocksize1] = w[nDoubles0 + j1];
-
-          // Make v the null vector again
-          std::fill(&v[i0*embeddedBlocksize0], &v[(i0+1)*embeddedBlocksize0], 0.0);
-        }
-      } else  {       // i = nDofs0 ... nDofs0 + nDofs1 //lower half
-        auto i1 = i - nDofs0;
-        for (int ii1=0; ii1<blocksize1; ii1++) {
-          for (size_t k1=0; k1<embeddedBlocksize1; k1++) {
-            v[nDoubles0 + i1*embeddedBlocksize1 + k1] = orthonormalFrame1[i1][ii1][k1];
-          }
-          int rc= 3;
-          MINDEC(rc, hess_vec(rank, nDoubles, xp.data(), v.data(), w.data()));
-          if (rc < 0)
-            DUNE_THROW(Dune::Exception, "ADOL-C has returned with error code " << rc << "!");
-
-          for (size_t j0=0; j0<nDoubles0; j0++)           // Upper left
-            embeddedHessian10[i1][j0/embeddedBlocksize0][ii1][j0%embeddedBlocksize0] = w[j0];
-
-          for (size_t j1=0; j1<nDoubles1; j1++)           //Upper right
-            embeddedHessian11[i1][j1/embeddedBlocksize1][ii1][j1%embeddedBlocksize1] = w[nDoubles0 + j1];
-
-          // Make v the null vector again
-          std::fill(&v[nDoubles0 + i1*embeddedBlocksize1], &v[nDoubles0 + (i1+1)*embeddedBlocksize1], 0.0);
-        }
-      }
-    }
-
-  } else {   //ADOL-C vector mode}
-    int n = nDoubles;
-    int nDirections = nDofs0 * blocksize0 + nDofs1 * blocksize1;
-    double* tangent[nDoubles];
-    for(size_t i=0; i<nDoubles; i++)
-      tangent[i] = (double*)malloc(nDirections*sizeof(double));
-
-    double* rawHessian[nDoubles];
-    for(size_t i=0; i<nDoubles; i++)
-      rawHessian[i] = (double*)malloc(nDirections*sizeof(double));
-
-    // Initialize directions field with zeros
-    for (int j=0; j<nDirections; j++)
-      for (int i=0; i<n; i++)
-        tangent[i][j] = 0.0;
-
-    for (size_t j=0; j<nDofs0*blocksize0; j++)
-      for (int i=0; i<embeddedBlocksize0; i++)
-        tangent[(j/blocksize0)*embeddedBlocksize0+i][j] = orthonormalFrame0[j/blocksize0][j%blocksize0][i];
-
-    for (size_t j=0; j<nDofs1*blocksize1; j++)
-      for (int i=0; i<embeddedBlocksize1; i++)
-        tangent[nDofs0*embeddedBlocksize0 + (j/blocksize1)*embeddedBlocksize1+i][nDofs0*blocksize0 + j] = orthonormalFrame1[j/blocksize1][j%blocksize1][i];
-
-    hess_mat(rank,nDoubles,nDirections,xp.data(),tangent,rawHessian);
-
-    // Copy Hessian into Dune data type
-    size_t offset0 = nDofs0*embeddedBlocksize0;
-    size_t offset1 = nDofs0*blocksize0;
-
-    // upper left block
-    for(size_t i=0; i<nDofs0*embeddedBlocksize0; i++)
-      for (size_t j=0; j<nDofs0*blocksize0; j++)
-        embeddedHessian00[j/blocksize0][i/embeddedBlocksize0][j%blocksize0][i%embeddedBlocksize0] = rawHessian[i][j];
-
-    // upper right block
-    for(size_t i=0; i<nDofs1*embeddedBlocksize1; i++)
-      for (size_t j=0; j<nDofs0*blocksize0; j++)
-        embeddedHessian01[j/blocksize0][i/embeddedBlocksize1][j%blocksize0][i%embeddedBlocksize1] = rawHessian[offset0+i][j];
-
-    // lower left block
-    for(size_t i=0; i<nDofs0*embeddedBlocksize0; i++)
-      for (size_t j=0; j<nDofs1*blocksize1; j++)
-        embeddedHessian10[j/blocksize1][i/embeddedBlocksize0][j%blocksize1][i%embeddedBlocksize0] = rawHessian[i][offset1+j];
-
-    // lower right block
-    for(size_t i=0; i<nDofs1*embeddedBlocksize1; i++)
-      for (size_t j=0; j<nDofs1*blocksize1; j++)
-        embeddedHessian11[j/blocksize1][i/embeddedBlocksize1][j%blocksize1][i%embeddedBlocksize1] = rawHessian[offset0+i][offset1+j];
-
-    for(size_t i=0; i<nDoubles; i++) {
-      free(rawHessian[i]);
-      free(tangent[i]);
-    }
-  }
-
-  // From this, compute the Hessian with respect to the manifold (which we assume here is embedded
-  // isometrically in a Euclidean space.
-  // For the detailed explanation of the following see: Absil, Mahoney, Trumpf, "An extrinsic look
-  // at the Riemannian Hessian".
-
-  using namespace Dune::Indices;
-
-  localHessian[_0][_0].setSize(nDofs0,nDofs0);
-
-  for (size_t col=0; col<nDofs0; col++) {
-
-    for (size_t subCol=0; subCol<blocksize0; subCol++) {
-
-      typename TargetSpace0::EmbeddedTangentVector z = orthonormalFrame0[col][subCol];
-
-      // P_x \partial^2 f z
-      for (size_t row=0; row<nDofs0; row++) {
-        typename TargetSpace0::TangentVector semiEmbeddedProduct;
-        embeddedHessian00[row][col].mv(z,semiEmbeddedProduct);
-
-        for (int subRow=0; subRow<blocksize0; subRow++)
-          localHessian[_0][_0][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
-      }
-
-    }
-
-  }
-
-  localHessian[_0][_1].setSize(nDofs0,nDofs1);
-
-  for (size_t col=0; col<nDofs1; col++) {
-
-    for (size_t subCol=0; subCol<blocksize1; subCol++) {
-
-      typename TargetSpace1::EmbeddedTangentVector z = orthonormalFrame1[col][subCol];
-
-      // P_x \partial^2 f z
-      for (size_t row=0; row<nDofs0; row++) {
-        typename TargetSpace0::TangentVector semiEmbeddedProduct;
-        embeddedHessian01[row][col].mv(z,semiEmbeddedProduct);
-
-        for (int subRow=0; subRow<blocksize0; subRow++)
-          localHessian[_0][_1][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
-      }
-
-    }
-
-  }
-
-  localHessian[_1][_0].setSize(nDofs1,nDofs0);
-
-  for (size_t col=0; col<nDofs0; col++) {
-
-    for (size_t subCol=0; subCol<blocksize0; subCol++) {
-
-      typename TargetSpace0::EmbeddedTangentVector z = orthonormalFrame0[col][subCol];
-
-      // P_x \partial^2 f z
-      for (size_t row=0; row<nDofs1; row++) {
-        typename TargetSpace1::TangentVector semiEmbeddedProduct;
-        embeddedHessian10[row][col].mv(z,semiEmbeddedProduct);
-
-        for (int subRow=0; subRow<blocksize1; subRow++)
-          localHessian[_1][_0][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
-      }
-
-    }
-
-  }
-
-  localHessian[_1][_1].setSize(nDofs1,nDofs1);
-
-  for (size_t col=0; col<nDofs1; col++) {
-
-    for (size_t subCol=0; subCol<blocksize1; subCol++) {
-
-      typename TargetSpace1::EmbeddedTangentVector z = orthonormalFrame1[col][subCol];
-
-      // P_x \partial^2 f z
-      for (size_t row=0; row<nDofs1; row++) {
-        typename TargetSpace1::TangentVector semiEmbeddedProduct;
-        embeddedHessian11[row][col].mv(z,semiEmbeddedProduct);
-
-        for (int subRow=0; subRow<blocksize1; subRow++)
-          localHessian[_1][_1][row][col][subRow][subCol] = semiEmbeddedProduct[subRow];
-      }
-
-    }
-
-  }
-
-  //////////////////////////////////////////////////////////////////////////////////////
-  //  Further correction due to non-planar configuration space
-  //  + \mathfrak{A}_x(z,P^\orth_x \partial f)
-  //////////////////////////////////////////////////////////////////////////////////////
-
-  // Project embedded gradient onto normal space
-  std::vector<typename TargetSpace0::EmbeddedTangentVector> projectedGradient0(nDofs0);
-  for (size_t i=0; i<nDofs0; i++)
-    projectedGradient0[i] = localConfiguration[_0][i].projectOntoNormalSpace(localEmbeddedGradient0[i]);
-
-  std::vector<typename TargetSpace1::EmbeddedTangentVector> projectedGradient1(nDofs1);
-  for (size_t i=0; i<nDofs1; i++)
-    projectedGradient1[i] = localConfiguration[_1][i].projectOntoNormalSpace(localEmbeddedGradient1[i]);
-
-  // The Weingarten map has only diagonal entries
-  for (size_t row=0; row<nDofs0; row++) {
-
-    for (size_t subRow=0; subRow<blocksize0; subRow++) {
-
-      typename TargetSpace0::EmbeddedTangentVector z = orthonormalFrame0[row][subRow];
-      typename TargetSpace0::EmbeddedTangentVector tmp1 = localConfiguration[_0][row].weingarten(z,projectedGradient0[row]);
-
-      typename TargetSpace0::TangentVector tmp2;
-      orthonormalFrame0[row].mv(tmp1,tmp2);
-
-      localHessian[_0][_0][row][row][subRow] += tmp2;
-    }
-
-  }
-
-  for (size_t row=0; row<nDofs1; row++) {
-
-    for (size_t subRow=0; subRow<blocksize1; subRow++) {
-
-      typename TargetSpace1::EmbeddedTangentVector z = orthonormalFrame1[row][subRow];
-      typename TargetSpace1::EmbeddedTangentVector tmp1 = localConfiguration[_1][row].weingarten(z,projectedGradient1[row]);
-
-      typename TargetSpace1::TangentVector tmp2;
-      orthonormalFrame1[row].mv(tmp1,tmp2);
-
-      localHessian[_1][_1][row][row][subRow] += tmp2;
-    }
-
-  }
-}
-
-#endif
diff --git a/src/cosserat-continuum.cc b/src/cosserat-continuum.cc
index 6220cd3101c2408623e8cb42ba9bbc6c211a275f..ee4f3bd2335f24f577613a8605ecc0cc6762cca1 100644
--- a/src/cosserat-continuum.cc
+++ b/src/cosserat-continuum.cc
@@ -56,7 +56,7 @@
 
 #include <dune/gfe/localgeodesicfefunction.hh>
 #include <dune/gfe/localprojectedfefunction.hh>
-#include <dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh>
+#include <dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh>
 #include <dune/gfe/assemblers/cosseratenergystiffness.hh>
 #include <dune/gfe/assemblers/nonplanarcosseratshellenergy.hh>
 #include <dune/gfe/cosseratvtkwriter.hh>
@@ -498,8 +498,8 @@ int main (int argc, char *argv[]) try
                                                                                   neumannFunction,
                                                                                   volumeLoad);
 
-      MixedLocalGFEADOLCStiffness<CompositeBasis,TargetSpace> localGFEADOLCStiffness(&localCosseratEnergy,
-                                                                                     adolcScalarMode);
+      LocalGeodesicFEADOLCStiffness<CompositeBasis,TargetSpace> localGFEADOLCStiffness(&localCosseratEnergy,
+                                                                                       adolcScalarMode);
       MixedGFEAssembler<CompositeBasis,
           RealTuple<double,3>,
           Rotation<double,3> > mixedAssembler(compositeBasis, &localGFEADOLCStiffness);
@@ -588,7 +588,7 @@ int main (int argc, char *argv[]) try
       }
 #endif
     } else {     //dim != dimworld
-      using StiffnessType = MixedLocalGFEADOLCStiffness<CompositeBasis, TargetSpace>;
+      using StiffnessType = LocalGeodesicFEADOLCStiffness<CompositeBasis, TargetSpace>;
       std::shared_ptr<StiffnessType> localGFEStiffness;
 
 #if HAVE_DUNE_CURVEDGEOMETRY && WORLD_DIM == 3 && GRID_DIM == 2
diff --git a/src/film-on-substrate.cc b/src/film-on-substrate.cc
index 6faf4932cad44c68babe8976594d91803d1f4658..da1337cfe0c26d0105a9fc5253f01dc953ef8ea4 100644
--- a/src/film-on-substrate.cc
+++ b/src/film-on-substrate.cc
@@ -58,7 +58,7 @@
 #include <dune/gfe/cosseratvtkwriter.hh>
 #include <dune/gfe/assemblers/localintegralenergy.hh>
 #include <dune/gfe/assemblers/mixedgfeassembler.hh>
-#include <dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh>
+#include <dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh>
 #include <dune/gfe/neumannenergy.hh>
 #include <dune/gfe/assemblers/surfacecosseratenergy.hh>
 #include <dune/gfe/assemblers/sumenergy.hh>
@@ -525,7 +525,7 @@ int main (int argc, char *argv[]) try
     sumEnergy.addLocalEnergy(elasticEnergy);
     sumEnergy.addLocalEnergy(surfaceCosseratEnergy);
 
-    MixedLocalGFEADOLCStiffness<CompositeBasis,RBM> localGFEADOLCStiffness(&sumEnergy);
+    LocalGeodesicFEADOLCStiffness<CompositeBasis,RBM> localGFEADOLCStiffness(&sumEnergy);
     MixedGFEAssembler<CompositeBasis,
         RealTuple<double,dim>,
         Rotation<double,dim> > mixedAssembler(compositeBasis, &localGFEADOLCStiffness);
diff --git a/src/simofoxshell.cc b/src/simofoxshell.cc
index 431732b7dd302e5a5f518773a98cb51579d1330d..8ffc5c704d6c4ce0b681de3a59faad83d4525a21 100644
--- a/src/simofoxshell.cc
+++ b/src/simofoxshell.cc
@@ -25,7 +25,7 @@
 #include <dune/fufem/functiontools/boundarydofs.hh>
 #include <dune/fufem/dunepython.hh>
 
-#include <dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh>
+#include <dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh>
 #include <dune/gfe/assemblers/simofoxenergy.hh>
 #include <dune/gfe/embeddedglobalgfefunction.hh>
 #include <dune/gfe/assemblers/mixedgfeassembler.hh>
@@ -322,7 +322,7 @@ int main(int argc, char *argv[]) try
 
     using TargetSpace = Dune::GFE::ProductManifold<RealTuple<double,3>,UnitVector<double,3> >;
 
-    MixedLocalGFEADOLCStiffness<decltype(compositeBasis),
+    LocalGeodesicFEADOLCStiffness<decltype(compositeBasis),
         TargetSpace> localGFEADOLCStiffness(&simoFoxEnergyADOLCLocalStiffness);
 
     MixedGFEAssembler<decltype(compositeBasis),
diff --git a/test/cosseratcontinuumtest.cc b/test/cosseratcontinuumtest.cc
index cd1fd2f4d1037d0600bbe1612ddc7854f4fda362..3c034b6e2cd916f9b2e9fc8903da1b50fb1299b7 100644
--- a/test/cosseratcontinuumtest.cc
+++ b/test/cosseratcontinuumtest.cc
@@ -22,7 +22,7 @@
 
 #include <dune/fufem/boundarypatch.hh>
 
-#include <dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh>
+#include <dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh>
 #include <dune/gfe/assemblers/mixedgfeassembler.hh>
 #include <dune/gfe/assemblers/sumenergy.hh>
 #include <dune/gfe/assemblers/localintegralenergy.hh>
@@ -207,7 +207,7 @@ int main (int argc, char *argv[])
   sumEnergy.addLocalEnergy(bulkCosseratEnergy);
   sumEnergy.addLocalEnergy(neumannEnergy);
 
-  MixedLocalGFEADOLCStiffness<CompositeBasis,
+  LocalGeodesicFEADOLCStiffness<CompositeBasis,
       GFE::ProductManifold<RealTuple<double,dim>,Rotation<double,dim> > > localGFEADOLCStiffness(&sumEnergy);
   MixedGFEAssembler<CompositeBasis,
       RealTuple<double,dim>,
diff --git a/test/geodesicfeassemblerwrappertest.cc b/test/geodesicfeassemblerwrappertest.cc
index a3f1ad32486a33049b40b3ecb3af8a243b9a61da..3a0117a4343326b4947658a7da937e11a782a5ee 100644
--- a/test/geodesicfeassemblerwrappertest.cc
+++ b/test/geodesicfeassemblerwrappertest.cc
@@ -28,7 +28,6 @@
 #include <dune/gfe/assemblers/localgeodesicfeadolcstiffness.hh>
 #include <dune/gfe/localprojectedfefunction.hh>
 #include <dune/gfe/assemblers/mixedgfeassembler.hh>
-#include <dune/gfe/assemblers/mixedlocalgfeadolcstiffness.hh>
 #include <dune/gfe/riemanniantrsolver.hh>
 #include <dune/gfe/spaces/productmanifold.hh>
 #include <dune/gfe/spaces/realtuple.hh>
@@ -146,7 +145,7 @@ int main (int argc, char *argv[])
                                                                                      &neumannBoundary,
                                                                                      neumannFunction,
                                                                                      nullptr);
-  MixedLocalGFEADOLCStiffness<CompositeBasis,
+  LocalGeodesicFEADOLCStiffness<CompositeBasis,
       GFE::ProductManifold<RealTuple<double,dim>,Rotation<double,dim> > > mixedLocalGFEADOLCStiffness(&cosseratEnergy);
   MixedGFEAssembler<CompositeBasis,
       RealTuple<double,dim>,