diff --git a/NumLib/Extrapolation/ExtrapolatableElement.h b/NumLib/Extrapolation/ExtrapolatableElement.h
new file mode 100644
index 0000000000000000000000000000000000000000..6a666abe8cb4aa3555a09aace876ec725d5a2710
--- /dev/null
+++ b/NumLib/Extrapolation/ExtrapolatableElement.h
@@ -0,0 +1,35 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef NUMLIB_EXTRAPOLATION_EXTRAPOLATABLEELEMENT_H
+#define NUMLIB_EXTRAPOLATION_EXTRAPOLATABLEELEMENT_H
+
+#include <Eigen/Core>
+
+namespace NumLib
+{
+/*! Interface for providing shape matrices and integration point values for
+ *  extrapolation,
+ *
+ * Local assemblers that want to have some integration point values extrapolated
+ * using an Extrapolator have to implement this interface.
+ */
+class ExtrapolatableElement
+{
+public:
+    //! Provides the shape matrix at the given integration point.
+    virtual Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        const unsigned integration_point) const = 0;
+
+    virtual ~ExtrapolatableElement() = default;
+};
+
+}  // namespace NumLib
+
+#endif  // NUMLIB_EXTRAPOLATION_EXTRAPOLATABLEELEMENT_H
diff --git a/NumLib/Extrapolation/ExtrapolatableElementCollection.h b/NumLib/Extrapolation/ExtrapolatableElementCollection.h
new file mode 100644
index 0000000000000000000000000000000000000000..432fab4bc1097728611580cc5337001ebe60a327
--- /dev/null
+++ b/NumLib/Extrapolation/ExtrapolatableElementCollection.h
@@ -0,0 +1,134 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef NUMLIB_EXTRAPOLATION_EXTRAPOLATABLEELEMENTCOLLECTION_H
+#define NUMLIB_EXTRAPOLATION_EXTRAPOLATABLEELEMENTCOLLECTION_H
+
+#include "ExtrapolatableElement.h"
+
+namespace NumLib
+{
+/*! Adaptor to get information needed by an Extrapolator from an "arbitrary"
+ * collection of elements (e.g., local assemblers).
+ *
+ * This is an interface class; suitable implementations have to be provided for
+ * concrete collections of extrapolatable elements.
+ */
+class ExtrapolatableElementCollection
+{
+public:
+    //! Returns the shape matrix of the element with the given \c id at the
+    //! given \c integration_point.
+    virtual Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        std::size_t const id, unsigned const integration_point) const = 0;
+
+    /*! Returns integration point values of some property of a specific element.
+     *
+     * \param id ID of the element of which the property is queried.
+     * \param cache Can be used to compute a property on-the-fly.
+     *
+     * \remark
+     * \parblock
+     * Since this method returns a reference, integration point values can not
+     * be computed locally and just returned.
+     *
+     * Instead, the vector \c cache can be used to store some derived quantities
+     * that should be used as integration point values. They can be written to
+     * the \c cache and a reference to the \c cache can then be returned by the
+     * implementation of this method. Ordinary integration point values can be
+     * returned directly (if they are stored in the local assembler), and the
+     * \c cache can be left untouched.
+     *
+     * For usage examples see the processes already implemented or the unit test
+     * in TestExtrapolation.cpp
+     * \endparblock
+     */
+    virtual std::vector<double> const& getIntegrationPointValues(
+        std::size_t const id, std::vector<double>& cache) const = 0;
+
+    //! Returns the number of elements whose properties shall be extrapolated.
+    virtual std::size_t size() const = 0;
+
+    virtual ~ExtrapolatableElementCollection() = default;
+};
+
+template <typename LocalAssemblerCollection>
+class ExtrapolatableLocalAssemblerCollection
+    : public ExtrapolatableElementCollection
+{
+public:
+    //! LocalAssemblerCollection contains many LocalAssembler's.
+    using LocalAssembler = typename std::decay<decltype(
+        *std::declval<LocalAssemblerCollection>()[static_cast<std::size_t>(
+            0)])>::type;
+
+    static_assert(std::is_base_of<ExtrapolatableElement, LocalAssembler>::value,
+                  "Local assemblers used for extrapolation must be derived "
+                  "from ExtrapolatableElement.");
+
+    /*! A method providing integration point values of some property.
+     *
+     * The method must return reference to a vector containing the integration
+     * point values.
+     *
+     * For further information about the \c cache parameter see
+     * ExtrapolatableElementCollection::getIntegrationPointValues().
+     */
+    using IntegrationPointValuesMethod = std::vector<double> const& (
+        LocalAssembler::*)(std::vector<double>& cache) const;
+
+    /*! Constructs a new instance.
+     *
+     * \param local_assemblers a collection of local assemblers whose
+     * integration point values shall be extrapolated.
+     * \param integration_point_values_method a LocalAssembler method returning
+     * integration point values of some property for that local assembler.
+     */
+    ExtrapolatableLocalAssemblerCollection(
+        LocalAssemblerCollection const& local_assemblers,
+        IntegrationPointValuesMethod integration_point_values_method)
+        : _local_assemblers(local_assemblers)
+        , _integration_point_values_method(integration_point_values_method)
+    {
+    }
+
+    Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        std::size_t const id, unsigned const integration_point) const override
+    {
+        ExtrapolatableElement const& loc_asm = *_local_assemblers[id];
+        return loc_asm.getShapeMatrix(integration_point);
+    }
+
+    std::vector<double> const& getIntegrationPointValues(
+        std::size_t const id, std::vector<double>& cache) const override
+    {
+        auto const& loc_asm = *_local_assemblers[id];
+        return (loc_asm.*_integration_point_values_method)(cache);
+    }
+
+    std::size_t size() const override { return _local_assemblers.size(); }
+private:
+    LocalAssemblerCollection const& _local_assemblers;
+    IntegrationPointValuesMethod const _integration_point_values_method;
+};
+
+//! Creates an ExtrapolatableLocalAssemblerCollection, which can be used to
+//! provide information to an Extrapolator.
+template <typename LocalAssemblerCollection,
+          typename IntegrationPointValuesMethod>
+ExtrapolatableLocalAssemblerCollection<LocalAssemblerCollection>
+makeExtrapolatable(LocalAssemblerCollection const& local_assemblers,
+                   IntegrationPointValuesMethod integration_point_values_method)
+{
+    return ExtrapolatableLocalAssemblerCollection<LocalAssemblerCollection>{
+        local_assemblers, integration_point_values_method};
+}
+}  // namespace NumLib
+
+#endif  // NUMLIB_EXTRAPOLATION_EXTRAPOLATABLEELEMENTCOLLECTION_H
diff --git a/NumLib/Extrapolation/Extrapolator.h b/NumLib/Extrapolation/Extrapolator.h
index 1ac340fa0a75ce0e1498522463c9b05d0e9418b7..cee424b32b95a49bb7d4f98330023660decbd3c3 100644
--- a/NumLib/Extrapolation/Extrapolator.h
+++ b/NumLib/Extrapolation/Extrapolator.h
@@ -14,64 +14,19 @@
 
 #include <Eigen/Eigen>
 #include "NumLib/NumericsConfig.h"
+#include "ExtrapolatableElementCollection.h"
 
 namespace NumLib
 {
 
-/*! Interface for providing shape matrices and integration point values for
- *  extrapolation,
- *
- * Local assemblers that want to have some integration point values extrapolated
- * using Extrapolator have to implement this interface.
- *
- * \tparam PropertyTag  type of the property used to query a specific kind of
- *                      integration point value, usually an enum.
- */
-template <typename PropertyTag>
-class Extrapolatable
-{
-public:
-    //! Provides the shape matrix at the given integration point.
-    virtual Eigen::Map<const Eigen::RowVectorXd>
-    getShapeMatrix(const unsigned integration_point) const = 0;
-
-    /*! Provides integration point values for the given property.
-     *
-     * \remark
-     * The vector \c cache can be used to store some derived quantities that
-     * should be used as integration point values. They can be written to the
-     * \c cache and a reference to the \c cache can then be returned by the
-     * implementation of this method.
-     * Ordinary integration point values can be returned directly (if they are
-     * stored in the local assembler), and the \c cache cen be left untouched.
-     *
-     * \returns a reference to a vector containing the integration point values.
-     */
-    virtual std::vector<double> const&
-    getIntegrationPointValues(PropertyTag const property,
-                              std::vector<double>& cache) const = 0;
-
-    virtual ~Extrapolatable() = default;
-};
-
-/*! Interface for classes that extrapolate integration point values to nodal
- *  values.
- *
- * \tparam PropertyTag    type of the property used to query a specific kind of
- *                        integration point value, usually an enum.
- * \tparam LocalAssembler type of the local assembler being queried for
- *                        integration point values.
- */
-template<typename PropertyTag, typename LocalAssembler>
+//! Interface for classes that extrapolate integration point values to nodal
+//! values.
 class Extrapolator
 {
 public:
-    //! Vector of local assemblers, the same as is used in the FEM process.
-    using LocalAssemblers = std::vector<std::unique_ptr<LocalAssembler>>;
-
     //! Extrapolates the given \c property from the given local assemblers.
     virtual void extrapolate(
-            LocalAssemblers const& loc_asms, PropertyTag const property) = 0;
+            ExtrapolatableElementCollection const& extrapolatables) = 0;
 
     /*! Computes residuals from the extrapolation of the given \c property.
      *
@@ -80,7 +35,7 @@ public:
      * \pre extrapolate() must have been called before with the same arguments.
      */
     virtual void calculateResiduals(
-            LocalAssemblers const& loc_asms, PropertyTag const property) = 0;
+            ExtrapolatableElementCollection const& extrapolatables) = 0;
 
     //! Returns the extrapolated nodal values.
     //! \todo Maybe write directly to a MeshProperty.
diff --git a/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator-impl.h b/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator-impl.h
deleted file mode 100644
index 5e03eece5e3ba05e9c49ec5d82bf2ad31b270eba..0000000000000000000000000000000000000000
--- a/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator-impl.h
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * \copyright
- * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
- *            Distributed under a Modified BSD License.
- *              See accompanying file LICENSE.txt or
- *              http://www.opengeosys.org/project/license
- *
- */
-
-#include <functional>
-
-#include <logog/include/logog.hpp>
-#include <Eigen/Core>
-
-#include "MathLib/LinAlg/LinAlg.h"
-#include "NumLib/Assembler/SerialExecutor.h"
-#include "MathLib/LinAlg/MatrixVectorTraits.h"
-#include "NumLib/Function/Interpolation.h"
-#include "LocalLinearLeastSquaresExtrapolator.h"
-
-namespace NumLib
-{
-
-template<typename PropertyTag, typename LocalAssembler>
-void
-LocalLinearLeastSquaresExtrapolator<PropertyTag, LocalAssembler>::
-extrapolate(LocalAssemblers const& local_assemblers, PropertyTag const property)
-{
-    _nodal_values.setZero();
-
-    // counts the writes to each nodal value, i.e., the summands in order to
-    // compute the average afterwards
-    auto counts =
-        MathLib::MatrixVectorTraits<GlobalVector>::newInstance(_nodal_values);
-    counts->setZero(); // TODO BLAS?
-
-    using Self = LocalLinearLeastSquaresExtrapolator<
-        PropertyTag, LocalAssembler>;
-
-    NumLib::SerialExecutor::executeMemberDereferenced(
-        *this, &Self::extrapolateElement, local_assemblers, property, *counts);
-
-    MathLib::LinAlg::componentwiseDivide(_nodal_values, _nodal_values, *counts);
-}
-
-template<typename PropertyTag, typename LocalAssembler>
-void
-LocalLinearLeastSquaresExtrapolator<PropertyTag, LocalAssembler>::
-calculateResiduals(LocalAssemblers const& local_assemblers,
-                   PropertyTag const property)
-{
-    assert(static_cast<std::size_t>(_residuals.size()) == local_assemblers.size());
-
-    using Self = LocalLinearLeastSquaresExtrapolator<
-        PropertyTag, LocalAssembler>;
-
-    NumLib::SerialExecutor::executeMemberDereferenced(
-        *this, &Self::calculateResiudalElement, local_assemblers, property);
-}
-
-template<typename PropertyTag, typename LocalAssembler>
-void
-LocalLinearLeastSquaresExtrapolator<PropertyTag, LocalAssembler>::
-extrapolateElement(std::size_t const element_index,
-                   LocalAssembler const& loc_asm, PropertyTag const property,
-                   GlobalVector& counts)
-{
-    auto const& integration_point_values = loc_asm.getIntegrationPointValues(
-            property, _integration_point_values_cache);
-
-    // number of nodes in the element
-    const auto nn = loc_asm.getShapeMatrix(0).cols();
-    // number of integration points in the element
-    const auto ni = integration_point_values.size();
-
-    assert(ni >= static_cast<decltype(ni)>(nn) &&
-           "Least squares is not possible if there are more nodes than"
-           "integration points.");
-
-    auto& N = _local_matrix_cache; // TODO make that local?
-    N.resize(ni, nn); // TODO: might reallocate very often
-
-    for (auto int_pt=decltype(ni){0}; int_pt<ni; ++int_pt)
-    {
-        auto const& shp_mat = loc_asm.getShapeMatrix(int_pt);
-        assert(shp_mat.cols() == nn);
-
-        // copy shape matrix to extrapolation matrix columnwise
-        N.block(int_pt, 0, 1, nn) = shp_mat;
-    }
-
-    // TODO make gp_vals an Eigen::VectorXd const& ?
-    Eigen::Map<const Eigen::VectorXd> const integration_point_values_vec(
-                integration_point_values.data(), integration_point_values.size());
-
-    // TODO
-    // optimization: Store decomposition of N*N^T or N^T for reuse?
-    //   cf. http://eigen.tuxfamily.org/dox/classEigen_1_1FullPivLU.html
-    //   cf. http://eigen.tuxfamily.org/dox/classEigen_1_1LLT.html
-    //   cf. http://eigen.tuxfamily.org/dox/classEigen_1_1LDLT.html
-    //   cf. https://eigen.tuxfamily.org/dox/classEigen_1_1HouseholderQR.html
-    // Extrapolate several values at once?
-
-    // do the least squares computation using QR decomposition.
-    Eigen::VectorXd tmp = N.householderQr().solve(integration_point_values_vec);
-
-    // option: do the least squares computation using LDLT decomposition.
-    // Eigen::VectorXd tmp =
-    //         (N*N.transpose()).ldlt().solve(N*integration_point_values_vec);
-
-    // TODO: for now always zeroth component is used
-    auto const& global_indices = _local_to_global(element_index, 0).rows;
-
-    _nodal_values.add(global_indices, tmp); // TODO does that give rise to PETSc problems?
-    counts.add(global_indices, std::vector<double>(global_indices.size(), 1.0));
-}
-
-template<typename PropertyTag, typename LocalAssembler>
-void
-LocalLinearLeastSquaresExtrapolator<PropertyTag, LocalAssembler>::
-calculateResiudalElement(std::size_t const element_index,
-                         LocalAssembler const& loc_asm, PropertyTag const property)
-{
-    auto const& gp_vals = loc_asm.getIntegrationPointValues(
-            property, _integration_point_values_cache);
-    const unsigned ni = gp_vals.size();        // number of gauss points
-
-    // TODO: for now always zeroth component is used
-    const auto& global_indices = _local_to_global(element_index, 0).rows;
-
-    // filter nodal values of the current element
-    std::vector<double> nodal_vals_element;
-    nodal_vals_element.resize(global_indices.size());
-    for (unsigned i=0; i<global_indices.size(); ++i) {
-        // TODO PETSc negative indices?
-        nodal_vals_element[i] = _nodal_values[global_indices[i]];
-    }
-
-    double residual = 0.0;
-    double gp_val_extrapol = 0.0;
-
-    for (unsigned gp=0; gp<ni; ++gp)
-    {
-        NumLib::shapeFunctionInterpolate(
-            nodal_vals_element, loc_asm.getShapeMatrix(gp), gp_val_extrapol);
-        auto const& ax_m_b = gp_val_extrapol - gp_vals[gp];
-        residual += ax_m_b * ax_m_b;
-    }
-
-    _residuals.set(element_index, std::sqrt(residual / ni));
-}
-
-} // namespace ProcessLib
diff --git a/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.cpp b/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8979905afd346c007e4892d2dc8d35073a39dd42
--- /dev/null
+++ b/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.cpp
@@ -0,0 +1,173 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#include "LocalLinearLeastSquaresExtrapolator.h"
+
+#include <functional>
+
+#include <Eigen/Core>
+#include <logog/include/logog.hpp>
+
+#include "MathLib/LinAlg/LinAlg.h"
+#include "MathLib/LinAlg/MatrixVectorTraits.h"
+#include "NumLib/Assembler/SerialExecutor.h"
+#include "NumLib/Function/Interpolation.h"
+#include "ExtrapolatableElementCollection.h"
+
+namespace NumLib
+{
+LocalLinearLeastSquaresExtrapolator::LocalLinearLeastSquaresExtrapolator(
+    NumLib::LocalToGlobalIndexMap const& dof_table)
+    : _nodal_values(NumLib::GlobalVectorProvider::provider.getVector(
+          MathLib::MatrixSpecifications(dof_table.dofSizeWithoutGhosts(),
+                                        dof_table.dofSizeWithoutGhosts(),
+                                        &dof_table.getGhostIndices(),
+                                        nullptr)))
+#ifndef USE_PETSC
+    , _residuals(dof_table.size())
+#else
+    , _residuals(dof_table.size(), false)
+#endif
+    , _local_to_global(dof_table)
+{
+    /* Note in case the following assertion fails:
+     * If you copied the extrapolation code, for your processes from
+     * somewhere, note that the code from the groundwater flow process might
+     * not suit your needs: It is a special case and is therefore most
+     * likely too simplistic. You better adapt the extrapolation code from
+     * some more advanced process, like the TES process.
+     */
+    assert(dof_table.getNumberOfComponents() == 1 &&
+           "The d.o.f. table passed must be for one variable that has "
+           "only one component!");
+}
+
+void LocalLinearLeastSquaresExtrapolator::extrapolate(
+    ExtrapolatableElementCollection const& extrapolatables)
+{
+    _nodal_values.setZero();
+
+    // counts the writes to each nodal value, i.e., the summands in order to
+    // compute the average afterwards
+    auto counts =
+        MathLib::MatrixVectorTraits<GlobalVector>::newInstance(_nodal_values);
+    counts->setZero();  // TODO BLAS?
+
+    auto const size = extrapolatables.size();
+    for (std::size_t i=0; i<size; ++i) {
+        extrapolateElement(i, extrapolatables, *counts);
+    }
+
+    MathLib::LinAlg::componentwiseDivide(_nodal_values, _nodal_values, *counts);
+}
+
+void LocalLinearLeastSquaresExtrapolator::calculateResiduals(
+    ExtrapolatableElementCollection const& extrapolatables)
+{
+    assert(static_cast<std::size_t>(_residuals.size()) ==
+           extrapolatables.size());
+
+    auto const size = extrapolatables.size();
+    for (std::size_t i=0; i<size; ++i) {
+        calculateResidualElement(i, extrapolatables);
+    }
+}
+
+void LocalLinearLeastSquaresExtrapolator::extrapolateElement(
+    std::size_t const element_index,
+    ExtrapolatableElementCollection const& extrapolatables,
+    GlobalVector& counts)
+{
+    auto const& integration_point_values =
+        extrapolatables.getIntegrationPointValues(
+            element_index, _integration_point_values_cache);
+
+    // number of nodes in the element
+    const auto nn = extrapolatables.getShapeMatrix(element_index, 0).cols();
+    // number of integration points in the element
+    const auto ni = integration_point_values.size();
+
+    assert(ni >= static_cast<decltype(ni)>(nn) &&
+           "Least squares is not possible if there are more nodes than"
+           "integration points.");
+
+    auto& N = _local_matrix_cache; // TODO make that local?
+    N.resize(ni, nn); // TODO: might reallocate very often
+
+    for (auto int_pt = decltype(ni){0}; int_pt < ni; ++int_pt) {
+        auto const& shp_mat =
+            extrapolatables.getShapeMatrix(element_index, int_pt);
+        assert(shp_mat.cols() == nn);
+
+        // copy shape matrix to extrapolation matrix columnwise
+        N.block(int_pt, 0, 1, nn) = shp_mat;
+    }
+
+    // TODO make gp_vals an Eigen::VectorXd const& ?
+    Eigen::Map<const Eigen::VectorXd> const integration_point_values_vec(
+        integration_point_values.data(), integration_point_values.size());
+
+    // TODO
+    // optimization: Store decomposition of N*N^T or N^T for reuse?
+    //   cf. http://eigen.tuxfamily.org/dox/classEigen_1_1FullPivLU.html
+    //   cf. http://eigen.tuxfamily.org/dox/classEigen_1_1LLT.html
+    //   cf. http://eigen.tuxfamily.org/dox/classEigen_1_1LDLT.html
+    //   cf. https://eigen.tuxfamily.org/dox/classEigen_1_1HouseholderQR.html
+    // Extrapolate several values at once?
+
+    // do the least squares computation using QR decomposition.
+    Eigen::VectorXd tmp = N.householderQr().solve(integration_point_values_vec);
+
+    // option: do the least squares computation using LDLT decomposition.
+    // Eigen::VectorXd tmp =
+    //         (N*N.transpose()).ldlt().solve(N*integration_point_values_vec);
+
+    // TODO: for now always zeroth component is used
+    auto const& global_indices = _local_to_global(element_index, 0).rows;
+
+    _nodal_values.add(global_indices,
+                      tmp);  // TODO does that give rise to PETSc problems?
+    counts.add(global_indices, std::vector<double>(global_indices.size(), 1.0));
+}
+
+void LocalLinearLeastSquaresExtrapolator::calculateResidualElement(
+    std::size_t const element_index,
+    ExtrapolatableElementCollection const& extrapolatables)
+{
+    auto const& gp_vals = extrapolatables.getIntegrationPointValues(
+        element_index, _integration_point_values_cache);
+    const unsigned ni = gp_vals.size();  // number of gauss points
+
+    // TODO: for now always zeroth component is used
+    const auto& global_indices = _local_to_global(element_index, 0).rows;
+
+    // filter nodal values of the current element
+    std::vector<double> nodal_vals_element;
+    nodal_vals_element.resize(global_indices.size());
+    for (unsigned i = 0; i < global_indices.size(); ++i) {
+        // TODO PETSc negative indices?
+        nodal_vals_element[i] = _nodal_values[global_indices[i]];
+    }
+
+    double residual = 0.0;
+    double gp_val_extrapol = 0.0;
+
+    for (unsigned gp = 0; gp < ni; ++gp) {
+        NumLib::shapeFunctionInterpolate(
+            nodal_vals_element,
+            extrapolatables.getShapeMatrix(element_index, gp),
+            gp_val_extrapol);
+        auto const& ax_m_b = gp_val_extrapol - gp_vals[gp];
+        residual += ax_m_b * ax_m_b;
+    }
+
+    _residuals.set(element_index, std::sqrt(residual / ni));
+}
+
+}  // namespace NumLib
diff --git a/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h b/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h
index df3f0ffb093eb3ec788929846ff4e95cf72f7c85..52766bd8bda6b5c761a336876302d71845ab56ec 100644
--- a/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h
+++ b/NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h
@@ -35,50 +35,20 @@ namespace NumLib
  * system.
  * \endparblock
  */
-template <typename PropertyTag, typename LocalAssembler>
-class LocalLinearLeastSquaresExtrapolator
-    : public Extrapolator<PropertyTag, LocalAssembler>
+class LocalLinearLeastSquaresExtrapolator : public Extrapolator
 {
 public:
-    using LocalAssemblers =
-        typename Extrapolator<PropertyTag, LocalAssembler>::LocalAssemblers;
-
-    /*! Constructs a new instance
+    /*! Constructs a new instance.
      *
      * \note
-     * The \c dof_table of \c matrix_specs must be set, and it must point to a
-     * dof_table for one single component variable.
+     * The \c dof_table must point to a d.o.f. table for one single-component
+     * variable.
      */
     explicit LocalLinearLeastSquaresExtrapolator(
-        MathLib::MatrixSpecifications const& matrix_specs,
-        NumLib::LocalToGlobalIndexMap const& dof_table)
-        : _nodal_values(
-              NumLib::GlobalVectorProvider::provider.getVector(
-                  matrix_specs))
-#ifndef USE_PETSC
-          ,
-          _residuals(dof_table.size())
-#else
-          ,
-          _residuals(dof_table.size(), false)
-#endif
-          ,
-          _local_to_global(dof_table)
-    {
-        /* Note in case the following assertion fails.
-         * If you copied the extrapolation code, for your processes from
-         * somewhere, note that the code from the groundwater flow process might
-         * not suit your needs: It is a special case and is therefore most
-         * likely too simplistic. You better adapt the extrapolation code from
-         * some more advanced process, like the TES process.
-         */
-        assert(dof_table.getNumberOfComponents() == 1 &&
-               "The d.o.f. table passed must be for one variable that has "
-               "only one component!");
-    }
+        NumLib::LocalToGlobalIndexMap const& dof_table);
 
-    void extrapolate(LocalAssemblers const& local_assemblers,
-                     PropertyTag const property) override;
+    void extrapolate(
+            ExtrapolatableElementCollection const& extrapolatables) override;
 
     /*! \copydoc Extrapolator::calculateResiduals()
      *
@@ -87,8 +57,8 @@ public:
      * extrapolation results when interpolated back to the integration points
      * again.
      */
-    void calculateResiduals(LocalAssemblers const& local_assemblers,
-                            PropertyTag const property) override;
+    void calculateResiduals(
+            ExtrapolatableElementCollection const& extrapolatables) override;
 
     GlobalVector const& getNodalValues() const override
     {
@@ -108,14 +78,15 @@ public:
 
 private:
     //! Extrapolate one element.
-    void extrapolateElement(std::size_t const element_index,
-                            LocalAssembler const& local_assembler,
-                            PropertyTag const property, GlobalVector& counts);
+    void extrapolateElement(
+        std::size_t const element_index,
+        ExtrapolatableElementCollection const& extrapolatables,
+        GlobalVector& counts);
 
     //! Compute the residuals for one element
-    void calculateResiudalElement(std::size_t const element_index,
-                                  LocalAssembler const& local_assembler,
-                                  PropertyTag const property);
+    void calculateResidualElement(
+        std::size_t const element_index,
+        ExtrapolatableElementCollection const& extrapolatables);
 
     GlobalVector& _nodal_values;  //!< extrapolated nodal values
     GlobalVector _residuals;      //!< extrapolation residuals
@@ -129,8 +100,7 @@ private:
     //! Avoids frequent reallocations.
     std::vector<double> _integration_point_values_cache;
 };
-}
 
-#include "LocalLinearLeastSquaresExtrapolator-impl.h"
+}  // namespace NumLib
 
 #endif  // NUMLIB_LOCAL_LLSQ_EXTRAPOLATOR_H
diff --git a/ProcessLib/ExtrapolatorData.h b/ProcessLib/ExtrapolatorData.h
new file mode 100644
index 0000000000000000000000000000000000000000..15e550b73ba423b049543b8ba8cbeb21d6c3ca4a
--- /dev/null
+++ b/ProcessLib/ExtrapolatorData.h
@@ -0,0 +1,102 @@
+/**
+ * \copyright
+ * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#ifndef PROCESSLIB_EXTRAPOLATORDATA_H
+#define PROCESSLIB_EXTRAPOLATORDATA_H
+
+#include <memory>
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+#include "NumLib/Extrapolation/Extrapolator.h"
+
+namespace ProcessLib
+{
+/*! Helper struct containing an extrapolator and a single component DOF table.
+ *
+ * Storage for the DOF table is managed optionally.
+ *
+ * \todo Later on this struct shall be moved, e.g., be merged with the process
+ * output class.
+ */
+class ExtrapolatorData
+{
+public:
+    ExtrapolatorData() = default;
+
+    /*! Constructs a new instance.
+     *
+     * \param extrapolator the extrapolator managed by the instance being
+     * created
+     * \param dof_table_single_component the d.o.f. table used by the \c
+     * extrapolator
+     * \param manage_storage If true the memory of \c dof_table_single_component
+     * will be freed by the destructor of this class.
+     */
+    ExtrapolatorData(
+        std::unique_ptr<NumLib::Extrapolator>&& extrapolator,
+        NumLib::LocalToGlobalIndexMap const* const dof_table_single_component,
+        bool const manage_storage)
+        : _extrapolator(std::move(extrapolator))
+        , _dof_table_single_component(dof_table_single_component)
+        , _manage_storage(manage_storage)
+    {
+    }
+
+    ExtrapolatorData(ExtrapolatorData&& other)
+        : _extrapolator(std::move(other._extrapolator))
+        , _dof_table_single_component(other._dof_table_single_component)
+        , _manage_storage(other._manage_storage)
+    {
+        other._manage_storage = false;
+        other._dof_table_single_component = nullptr;
+    }
+
+    ExtrapolatorData& operator=(ExtrapolatorData&& other)
+    {
+        cleanup();
+        _manage_storage = other._manage_storage;
+        _dof_table_single_component = other._dof_table_single_component;
+        _extrapolator = std::move(other._extrapolator);
+        other._dof_table_single_component = nullptr;
+        other._manage_storage = false;
+        return *this;
+    }
+
+    NumLib::LocalToGlobalIndexMap const& getDOFTable() const
+    {
+        return *_dof_table_single_component;
+    }
+    NumLib::Extrapolator& getExtrapolator() const { return *_extrapolator; }
+
+    ~ExtrapolatorData() { cleanup(); }
+
+private:
+    //! Deletes the d.o.f table if it is allowed to do so.
+    void cleanup()
+    {
+        if (_manage_storage) {
+            delete _dof_table_single_component;
+            _dof_table_single_component = nullptr;
+        }
+    }
+
+    //! Extrapolator managed by the ExtrapolatorData instance.
+    std::unique_ptr<NumLib::Extrapolator> _extrapolator;
+
+    //! D.o.f. table used by the extrapolator.
+    NumLib::LocalToGlobalIndexMap const* _dof_table_single_component = nullptr;
+
+    //! If true, free storage of the d.o.f. table in the ExtrapolatorData
+    //! destructor.
+    bool _manage_storage = false;
+};
+
+} // namespace ProcessLib
+
+
+#endif // PROCESSLIB_EXTRAPOLATORDATA_H
diff --git a/ProcessLib/GroundwaterFlow/GroundwaterFlowFEM.h b/ProcessLib/GroundwaterFlow/GroundwaterFlowFEM.h
index 6f309631377062dae369d2b72ca48164944cbf04..0eaef58f200ba5215d6e0c50b57642600b06a262 100644
--- a/ProcessLib/GroundwaterFlow/GroundwaterFlowFEM.h
+++ b/ProcessLib/GroundwaterFlow/GroundwaterFlowFEM.h
@@ -12,6 +12,7 @@
 
 #include <vector>
 
+#include "NumLib/Extrapolation/ExtrapolatableElement.h"
 #include "NumLib/Fem/FiniteElement/TemplateIsoparametric.h"
 #include "NumLib/Fem/ShapeMatrixPolicy.h"
 #include "ProcessLib/LocalAssemblerInterface.h"
@@ -26,18 +27,22 @@ namespace ProcessLib
 namespace GroundwaterFlow
 {
 
-enum class IntegrationPointValue {
-    DarcyVelocityX,
-    DarcyVelocityY,
-    DarcyVelocityZ
-};
-
 const unsigned NUM_NODAL_DOF = 1;
 
 class GroundwaterFlowLocalAssemblerInterface
         : public ProcessLib::LocalAssemblerInterface
-        , public NumLib::Extrapolatable<IntegrationPointValue>
-{};
+        , public NumLib::ExtrapolatableElement
+{
+public:
+    virtual std::vector<double> const& getIntPtDarcyVelocityX(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getIntPtDarcyVelocityY(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getIntPtDarcyVelocityZ(
+        std::vector<double>& /*cache*/) const = 0;
+};
 
 template <typename ShapeFunction,
          typename IntegrationMethod,
@@ -118,22 +123,24 @@ public:
     }
 
     std::vector<double> const&
-    getIntegrationPointValues(IntegrationPointValue const property,
-                              std::vector<double>& /*cache*/) const override
+    getIntPtDarcyVelocityX(std::vector<double>& /*cache*/) const override
     {
-        switch (property)
-        {
-        case IntegrationPointValue::DarcyVelocityX:
-            return _darcy_velocities[0];
-        case IntegrationPointValue::DarcyVelocityY:
-            assert(GlobalDim > 1);
-            return _darcy_velocities[1];
-        case IntegrationPointValue::DarcyVelocityZ:
-            assert(GlobalDim > 2);
-            return _darcy_velocities[2];
-        }
+        assert(_darcy_velocities.size() > 1);
+        return _darcy_velocities[0];
+    }
+
+    std::vector<double> const&
+    getIntPtDarcyVelocityY(std::vector<double>& /*cache*/) const override
+    {
+        assert(_darcy_velocities.size() > 1);
+        return _darcy_velocities[1];
+    }
 
-        OGS_FATAL("");
+    std::vector<double> const&
+    getIntPtDarcyVelocityZ(std::vector<double>& /*cache*/) const override
+    {
+        assert(_darcy_velocities.size() > 2);
+        return _darcy_velocities[2];
     }
 
 private:
diff --git a/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.cpp b/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.cpp
index e25c277069c31cd3c1e787efa2944df7be25888f..6aa0b2a37c7814eb3cb8f7468c6c62dec7d11a13 100644
--- a/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.cpp
+++ b/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.cpp
@@ -53,28 +53,25 @@ void GroundwaterFlowProcess::initializeConcreteProcess(
         mesh.getDimension(), mesh.getElements(), dof_table, integration_order,
         _local_assemblers, _process_data);
 
-    // TODO Later on the DOF table can change during the simulation!
-    _extrapolator.reset(new ExtrapolatorImplementation(
-        Base::getMatrixSpecifications(), *Base::_local_to_global_index_map));
-
-    Base::_secondary_variables.addSecondaryVariable(
+    _secondary_variables.addSecondaryVariable(
         "darcy_velocity_x", 1,
-        makeExtrapolator(IntegrationPointValue::DarcyVelocityX, *_extrapolator,
-                         _local_assemblers));
+        makeExtrapolator(
+            getExtrapolator(), _local_assemblers,
+            &GroundwaterFlowLocalAssemblerInterface::getIntPtDarcyVelocityX));
 
-    if (mesh.getDimension() > 1)
-    {
-        Base::_secondary_variables.addSecondaryVariable(
+    if (mesh.getDimension() > 1) {
+        _secondary_variables.addSecondaryVariable(
             "darcy_velocity_y", 1,
-            makeExtrapolator(IntegrationPointValue::DarcyVelocityY,
-                             *_extrapolator, _local_assemblers));
+            makeExtrapolator(getExtrapolator(), _local_assemblers,
+                             &GroundwaterFlowLocalAssemblerInterface::
+                                 getIntPtDarcyVelocityY));
     }
-    if (mesh.getDimension() > 2)
-    {
-        Base::_secondary_variables.addSecondaryVariable(
+    if (mesh.getDimension() > 2) {
+        _secondary_variables.addSecondaryVariable(
             "darcy_velocity_z", 1,
-            makeExtrapolator(IntegrationPointValue::DarcyVelocityZ,
-                             *_extrapolator, _local_assemblers));
+            makeExtrapolator(getExtrapolator(), _local_assemblers,
+                             &GroundwaterFlowLocalAssemblerInterface::
+                                 getIntPtDarcyVelocityZ));
     }
 }
 
diff --git a/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.h b/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.h
index adaaf66d597dcaac9aee5336b5b892d4e445ee45..1c5254d2f2b708bf8af7c8f996155bc2bf22a8c5 100644
--- a/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.h
+++ b/ProcessLib/GroundwaterFlow/GroundwaterFlowProcess.h
@@ -42,13 +42,6 @@ public:
     //! @}
 
 private:
-    using ExtrapolatorInterface =
-        NumLib::Extrapolator<IntegrationPointValue,
-                             GroundwaterFlowLocalAssemblerInterface>;
-    using ExtrapolatorImplementation =
-        NumLib::LocalLinearLeastSquaresExtrapolator<
-            IntegrationPointValue, GroundwaterFlowLocalAssemblerInterface>;
-
     void initializeConcreteProcess(
         NumLib::LocalToGlobalIndexMap const& dof_table,
         MeshLib::Mesh const& mesh,
@@ -62,8 +55,6 @@ private:
 
     std::vector<std::unique_ptr<GroundwaterFlowLocalAssemblerInterface>>
         _local_assemblers;
-
-    std::unique_ptr<ExtrapolatorInterface> _extrapolator;
 };
 
 }   // namespace GroundwaterFlow
diff --git a/ProcessLib/Process.cpp b/ProcessLib/Process.cpp
index 10258da6e888cbb9a8c5a9834583c641abbe6b03..f0cb228849450fca0d3041b3135d42ba7a644477 100644
--- a/ProcessLib/Process.cpp
+++ b/ProcessLib/Process.cpp
@@ -10,6 +10,7 @@
 #include "Process.h"
 
 #include "NumLib/DOF/ComputeSparsityPattern.h"
+#include "NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h"
 #include "DirichletBc.h"
 #include "NeumannBc.h"
 #include "NeumannBcAssembler.h"
@@ -52,6 +53,9 @@ void Process::initialize()
     DBUG("Compute sparsity pattern");
     computeSparsityPattern();
 
+    DBUG("Initialize the extrapolator");
+    initializeExtrapolator();
+
     initializeConcreteProcess(*_local_to_global_index_map, _mesh,
                               _integration_order);
 
@@ -168,6 +172,42 @@ void Process::constructDofTable()
         std::move(all_mesh_subsets), NumLib::ComponentOrder::BY_LOCATION));
 }
 
+void Process::initializeExtrapolator()
+{
+    NumLib::LocalToGlobalIndexMap const* dof_table_single_component;
+    bool manage_storage;
+
+    if (_local_to_global_index_map->getNumberOfComponents() == 1)
+    {
+        // For single-variable-single-component processes reuse the existing DOF
+        // table.
+        dof_table_single_component = _local_to_global_index_map.get();
+        manage_storage = false;
+    }
+    else
+    {
+        // Otherwise construct a new DOF table.
+        std::vector<std::unique_ptr<MeshLib::MeshSubsets>>
+            all_mesh_subsets_single_component;
+        all_mesh_subsets_single_component.emplace_back(
+            new MeshLib::MeshSubsets(_mesh_subset_all_nodes.get()));
+
+        dof_table_single_component = new NumLib::LocalToGlobalIndexMap(
+            std::move(all_mesh_subsets_single_component),
+            // by location order is needed for output
+            NumLib::ComponentOrder::BY_LOCATION);
+        manage_storage = true;
+    }
+
+    std::unique_ptr<NumLib::Extrapolator> extrapolator(
+        new NumLib::LocalLinearLeastSquaresExtrapolator(
+            *dof_table_single_component));
+
+    // TODO Later on the DOF table can change during the simulation!
+    _extrapolator_data = ExtrapolatorData(
+        std::move(extrapolator), dof_table_single_component, manage_storage);
+}
+
 void Process::setInitialConditions(ProcessVariable const& variable,
                                    int const variable_id,
                                    int const component_id,
diff --git a/ProcessLib/Process.h b/ProcessLib/Process.h
index 72d4009202b733b08518e5116292c273fcb5d3f9..ccedc3ae2df699f732db2e0d0e8e40d9c3fde951 100644
--- a/ProcessLib/Process.h
+++ b/ProcessLib/Process.h
@@ -14,6 +14,7 @@
 #include "NumLib/ODESolver/ODESystem.h"
 #include "NumLib/ODESolver/TimeDiscretization.h"
 
+#include "ExtrapolatorData.h"
 #include "Parameter.h"
 #include "ProcessOutput.h"
 #include "SecondaryVariable.h"
@@ -85,6 +86,17 @@ public:
         return *_time_discretization;
     }
 
+protected:
+    NumLib::Extrapolator& getExtrapolator() const
+    {
+        return _extrapolator_data.getExtrapolator();
+    }
+
+    NumLib::LocalToGlobalIndexMap const& getSingleComponentDOFTable() const
+    {
+        return _extrapolator_data.getDOFTable();
+    }
+
 private:
     /// Process specific initialization called by initialize().
     virtual void initializeConcreteProcess(
@@ -104,6 +116,8 @@ private:
 
     void constructDofTable();
 
+    void initializeExtrapolator();
+
     /// Sets the initial condition values in the solution vector x for a given
     /// process variable and component.
     void setInitialConditions(ProcessVariable const& variable,
@@ -142,6 +156,8 @@ private:
 
     /// Variables used by this process.
     std::vector<std::reference_wrapper<ProcessVariable>> _process_variables;
+
+    ExtrapolatorData _extrapolator_data;
 };
 
 /// Find process variables in \c variables whose names match the settings under
diff --git a/ProcessLib/SecondaryVariable.h b/ProcessLib/SecondaryVariable.h
index bf99e2e4c3fb4dfef1072c3be335b9caac102063..f845c1c244c5ac7f9fc9e1f4d2abd3c7c003204b 100644
--- a/ProcessLib/SecondaryVariable.h
+++ b/ProcessLib/SecondaryVariable.h
@@ -13,8 +13,12 @@
 #include "BaseLib/ConfigTree.h"
 #include "BaseLib/uniqueInsert.h"
 #include "NumLib/Extrapolation/Extrapolator.h"
+#include "NumLib/Extrapolation/ExtrapolatableElementCollection.h"
 
-namespace NumLib { class LocalToGlobalIndexMap; }
+namespace NumLib
+{
+class LocalToGlobalIndexMap;
+}
 
 namespace ProcessLib
 {
@@ -156,44 +160,50 @@ private:
     std::set<std::string> _all_secondary_variables;
 };
 
-
-//! Creates an object that computes a secondary variable via extrapolation
-//! of integration point values.
-template<typename PropertyEnum, typename LocalAssembler>
-SecondaryVariableFunctions
-makeExtrapolator(PropertyEnum const property,
-                 NumLib::Extrapolator<PropertyEnum, LocalAssembler>&
-                 extrapolator,
-                 typename NumLib::Extrapolator<PropertyEnum,
-                     LocalAssembler>::LocalAssemblers const& local_assemblers)
+/*! Creates an object that computes a secondary variable via extrapolation of
+ * integration point values.
+ *
+ * \param extrapolator The extrapolator used for extrapolation.
+ * \param local_assemblers The collection of local assemblers whose integration
+ * point values will be extrapolated.
+ * \param integration_point_values_method The member function of the local
+ * assembler returning/computing the integration point values of the specific
+ * property being extrapolated.
+ */
+template <typename LocalAssemblerCollection>
+SecondaryVariableFunctions makeExtrapolator(
+    NumLib::Extrapolator& extrapolator,
+    LocalAssemblerCollection const& local_assemblers,
+    typename NumLib::ExtrapolatableLocalAssemblerCollection<
+        LocalAssemblerCollection>::IntegrationPointValuesMethod
+        integration_point_values_method)
 {
-    static_assert(std::is_base_of<
-         NumLib::Extrapolatable<PropertyEnum>, LocalAssembler>::value,
-        "The passed local assembler type (i.e. the local assembler interface) must"
-        " derive from NumLib::Extrapolatable<>.");
-
-    auto const eval_field = [property, &extrapolator, &local_assemblers](
-            GlobalVector const& /*x*/,
-            NumLib::LocalToGlobalIndexMap const& /*dof_table*/,
-            std::unique_ptr<GlobalVector>& /*result_cache*/
-            ) -> GlobalVector const&
-    {
-        extrapolator.extrapolate(local_assemblers, property);
+    auto const eval_field = [&extrapolator, &local_assemblers,
+                             integration_point_values_method](
+        GlobalVector const& /*x*/,
+        NumLib::LocalToGlobalIndexMap const& /*dof_table*/,
+        std::unique_ptr<GlobalVector> & /*result_cache*/
+        ) -> GlobalVector const& {
+        auto const extrapolatables = NumLib::makeExtrapolatable(
+            local_assemblers, integration_point_values_method);
+        extrapolator.extrapolate(extrapolatables);
         return extrapolator.getNodalValues();
     };
 
-    auto const eval_residuals = [property, &extrapolator, &local_assemblers](
-            GlobalVector const& /*x*/,
-            NumLib::LocalToGlobalIndexMap const& /*dof_table*/,
-            std::unique_ptr<GlobalVector>& /*result_cache*/
-            ) -> GlobalVector const&
-    {
-        extrapolator.calculateResiduals(local_assemblers, property);
+    auto const eval_residuals = [&extrapolator, &local_assemblers,
+                                 integration_point_values_method](
+        GlobalVector const& /*x*/,
+        NumLib::LocalToGlobalIndexMap const& /*dof_table*/,
+        std::unique_ptr<GlobalVector> & /*result_cache*/
+        ) -> GlobalVector const& {
+        auto const extrapolatables = NumLib::makeExtrapolatable(
+            local_assemblers, integration_point_values_method);
+        extrapolator.calculateResiduals(extrapolatables);
         return extrapolator.getElementResiduals();
     };
-    return { eval_field, eval_residuals };
+    return {eval_field, eval_residuals};
 }
 
-} // namespace ProcessLib
+}  // namespace ProcessLib
 
 #endif // PROCESSLIB_SECONDARY_VARIABLE_H
diff --git a/ProcessLib/TES/TESLocalAssembler-impl.h b/ProcessLib/TES/TESLocalAssembler-impl.h
index 331f67b63523159a21b2fcf18215b9d90e4061e7..4eae782c384713e7674833344b99cfab7446d6c2 100644
--- a/ProcessLib/TES/TESLocalAssembler-impl.h
+++ b/ProcessLib/TES/TESLocalAssembler-impl.h
@@ -194,10 +194,81 @@ template <typename ShapeFunction_, typename IntegrationMethod_,
           unsigned GlobalDim>
 std::vector<double> const& TESLocalAssembler<
     ShapeFunction_, IntegrationMethod_,
-    GlobalDim>::getIntegrationPointValues(TESIntPtVariables const var,
-                                          std::vector<double>& cache) const
+    GlobalDim>::getIntPtSolidDensity(std::vector<double>& /*cache*/) const
 {
-    return _d.getIntegrationPointValues(var, cache);
+    return _d.getData().solid_density;
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          unsigned GlobalDim>
+std::vector<double> const& TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_,
+    GlobalDim>::getIntPtLoading(std::vector<double>& cache) const
+{
+    auto const rho_SR = _d.getData().solid_density;
+    auto const rho_SR_dry = _d.getAssemblyParameters().rho_SR_dry;
+
+    cache.clear();
+    cache.reserve(rho_SR.size());
+
+    for (auto const rho : rho_SR) {
+        cache.push_back(rho/rho_SR_dry - 1.0);
+    }
+
+    return cache;
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          unsigned GlobalDim>
+std::vector<double> const&
+TESLocalAssembler<ShapeFunction_, IntegrationMethod_, GlobalDim>::
+    getIntPtReactionDampingFactor(std::vector<double>& cache) const
+{
+    auto const fac = _d.getData().reaction_adaptor->getReactionDampingFactor();
+    auto const num_integration_points = _d.getData().solid_density.size();
+
+    cache.clear();
+    cache.resize(num_integration_points, fac);
+
+    return cache;
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          unsigned GlobalDim>
+std::vector<double> const& TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_,
+    GlobalDim>::getIntPtReactionRate(std::vector<double>& /*cache*/) const
+{
+    return _d.getData().reaction_rate;
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          unsigned GlobalDim>
+std::vector<double> const& TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_,
+    GlobalDim>::getIntPtDarcyVelocityX(std::vector<double>& /*cache*/) const
+{
+    return _d.getData().velocity[0];
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          unsigned GlobalDim>
+std::vector<double> const& TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_,
+    GlobalDim>::getIntPtDarcyVelocityY(std::vector<double>& /*cache*/) const
+{
+    assert(_d.getData().velocity.size() > 1);
+    return _d.getData().velocity[1];
+}
+
+template <typename ShapeFunction_, typename IntegrationMethod_,
+          unsigned GlobalDim>
+std::vector<double> const& TESLocalAssembler<
+    ShapeFunction_, IntegrationMethod_,
+    GlobalDim>::getIntPtDarcyVelocityZ(std::vector<double>& /*cache*/) const
+{
+    assert(_d.getData().velocity.size() > 2);
+    return _d.getData().velocity[2];
 }
 
 template <typename ShapeFunction_, typename IntegrationMethod_,
diff --git a/ProcessLib/TES/TESLocalAssembler.h b/ProcessLib/TES/TESLocalAssembler.h
index 37ce25c30459fb2f945a8eac62ec0086c0589fb6..1826b2c6f4cc2fec5448b5559c80aa37d3c914b5 100644
--- a/ProcessLib/TES/TESLocalAssembler.h
+++ b/ProcessLib/TES/TESLocalAssembler.h
@@ -14,24 +14,44 @@
 #include <vector>
 
 #include "ProcessLib/LocalAssemblerInterface.h"
+#include "NumLib/Extrapolation/ExtrapolatableElement.h"
 #include "TESAssemblyParams.h"
 #include "TESLocalAssemblerInner-fwd.h"
 
-#include "NumLib/Extrapolation/Extrapolator.h"
-
 namespace ProcessLib
 {
 namespace TES
 {
 class TESLocalAssemblerInterface
     : public ProcessLib::LocalAssemblerInterface,
-      public NumLib::Extrapolatable<TESIntPtVariables>
+      public NumLib::ExtrapolatableElement
 {
 public:
     virtual ~TESLocalAssemblerInterface() = default;
 
     virtual bool checkBounds(std::vector<double> const& local_x,
                              std::vector<double> const& local_x_prev_ts) = 0;
+
+    virtual std::vector<double> const& getIntPtSolidDensity(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getIntPtLoading(
+        std::vector<double>& cache) const = 0;
+
+    virtual std::vector<double> const& getIntPtReactionDampingFactor(
+        std::vector<double>& cache) const = 0;
+
+    virtual std::vector<double> const& getIntPtReactionRate(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getIntPtDarcyVelocityX(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getIntPtDarcyVelocityY(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getIntPtDarcyVelocityZ(
+        std::vector<double>& /*cache*/) const = 0;
 };
 
 template <typename ShapeFunction_, typename IntegrationMethod_,
@@ -66,9 +86,26 @@ public:
     bool checkBounds(std::vector<double> const& local_x,
                      std::vector<double> const& local_x_prev_ts) override;
 
-    std::vector<double> const& getIntegrationPointValues(
-        TESIntPtVariables const var, std::vector<double>& cache) const override;
+    std::vector<double> const& getIntPtSolidDensity(
+        std::vector<double>& /*cache*/) const override;
+
+    std::vector<double> const& getIntPtLoading(
+        std::vector<double>& cache) const override;
+
+    std::vector<double> const& getIntPtReactionDampingFactor(
+        std::vector<double>& cache) const override;
+
+    std::vector<double> const& getIntPtReactionRate(
+        std::vector<double>& /*cache*/) const override;
+
+    std::vector<double> const& getIntPtDarcyVelocityX(
+        std::vector<double>& /*cache*/) const override;
+
+    std::vector<double> const& getIntPtDarcyVelocityY(
+        std::vector<double>& /*cache*/) const override;
 
+    std::vector<double> const& getIntPtDarcyVelocityZ(
+        std::vector<double>& /*cache*/) const override;
 private:
     std::vector<ShapeMatrices> _shape_matrices;
 
diff --git a/ProcessLib/TES/TESLocalAssemblerInner-impl.h b/ProcessLib/TES/TESLocalAssemblerInner-impl.h
index d587920df981851367fd1f4e838c8fb2412595d5..b0d63166cd60ad493793a48d369cc3de3a234d52 100644
--- a/ProcessLib/TES/TESLocalAssemblerInner-impl.h
+++ b/ProcessLib/TES/TESLocalAssemblerInner-impl.h
@@ -218,57 +218,6 @@ void TESLocalAssemblerInner<Traits>::preEachAssembleIntegrationPoint(
     _d.rho_GR = fluid_density(_d.p, _d.T, _d.vapour_mass_fraction);
 }
 
-template <typename Traits>
-std::vector<double> const&
-TESLocalAssemblerInner<Traits>::getIntegrationPointValues(
-    TESIntPtVariables const var, std::vector<double>& cache) const
-{
-    switch (var)
-    {
-        case TESIntPtVariables::REACTION_RATE:
-            return _d.reaction_rate;
-        case TESIntPtVariables::SOLID_DENSITY:
-            return _d.solid_density;
-        case TESIntPtVariables::VELOCITY_X:
-            return _d.velocity[0];
-        case TESIntPtVariables::VELOCITY_Y:
-            assert(_d.velocity.size() >= 2);
-            return _d.velocity[1];
-        case TESIntPtVariables::VELOCITY_Z:
-            assert(_d.velocity.size() >= 3);
-            return _d.velocity[2];
-
-        case TESIntPtVariables::LOADING:
-        {
-            auto& Cs = cache;
-            Cs.clear();
-            Cs.reserve(_d.solid_density.size());
-
-            for (auto rho_SR : _d.solid_density)
-            {
-                Cs.push_back(rho_SR / _d.ap.rho_SR_dry - 1.0);
-            }
-
-            return Cs;
-        }
-
-        // TODO that's an element value, ain't it?
-        case TESIntPtVariables::REACTION_DAMPING_FACTOR:
-        {
-            auto const num_integration_points = _d.solid_density.size();
-            auto& alphas = cache;
-            alphas.clear();
-            alphas.resize(num_integration_points,
-                          _d.reaction_adaptor->getReactionDampingFactor());
-
-            return alphas;
-        }
-    }
-
-    cache.clear();
-    return cache;
-}
-
 template <typename Traits>
 void TESLocalAssemblerInner<Traits>::assembleIntegrationPoint(
     unsigned integration_point,
diff --git a/ProcessLib/TES/TESLocalAssemblerInner.h b/ProcessLib/TES/TESLocalAssemblerInner.h
index 3e36cb4718059fb80bb4ffd989008ced34fcb05d..77d4638fc507c22d697a7cc89dee6f0bc5b39587 100644
--- a/ProcessLib/TES/TESLocalAssemblerInner.h
+++ b/ProcessLib/TES/TESLocalAssemblerInner.h
@@ -23,17 +23,6 @@ namespace ProcessLib
 {
 namespace TES
 {
-enum class TESIntPtVariables : unsigned
-{
-    SOLID_DENSITY,
-    REACTION_RATE,
-    VELOCITY_X,
-    VELOCITY_Y,
-    VELOCITY_Z,
-    LOADING,
-    REACTION_DAMPING_FACTOR
-};
-
 template <typename Traits>
 class TESLocalAssemblerInner
 {
@@ -56,9 +45,6 @@ public:
 
     void preEachAssemble();
 
-    std::vector<double> const& getIntegrationPointValues(
-        TESIntPtVariables const var, std::vector<double>& cache) const;
-
     // TODO better encapsulation
     AssemblyParams const& getAssemblyParameters() const { return _d.ap; }
     TESFEMReactionAdaptor const& getReactionAdaptor() const
diff --git a/ProcessLib/TES/TESProcess.cpp b/ProcessLib/TES/TESProcess.cpp
index b35eb3c189645aa58fc0ca75b8874d2bc1bbbd69..f2c373a56acf549d040aabda67c71392b6906d6b 100644
--- a/ProcessLib/TES/TESProcess.cpp
+++ b/ProcessLib/TES/TESProcess.cpp
@@ -175,28 +175,6 @@ void TESProcess::initializeConcreteProcess(
         mesh.getDimension(), mesh.getElements(), dof_table, integration_order,
         _local_assemblers, _assembly_params);
 
-    // TODO move the two data members somewhere else.
-    // for extrapolation of secondary variables
-    std::vector<std::unique_ptr<MeshLib::MeshSubsets>>
-        all_mesh_subsets_single_component;
-    all_mesh_subsets_single_component.emplace_back(
-        new MeshLib::MeshSubsets(this->_mesh_subset_all_nodes.get()));
-    _local_to_global_index_map_single_component.reset(
-        new NumLib::LocalToGlobalIndexMap(
-            std::move(all_mesh_subsets_single_component),
-            // by location order is needed for output
-            NumLib::ComponentOrder::BY_LOCATION));
-
-    {
-        auto const& l = *_local_to_global_index_map_single_component;
-        _extrapolator.reset(new ExtrapolatorImplementation(
-            MathLib::MatrixSpecifications(l.dofSizeWithoutGhosts(),
-                                          l.dofSizeWithoutGhosts(),
-                                          &l.getGhostIndices(),
-                                          nullptr),
-            l));
-    }
-
     // secondary variables
     auto add2nd = [&](std::string const& var_name, unsigned const n_components,
                       SecondaryVariableFunctions&& fcts) {
@@ -204,22 +182,29 @@ void TESProcess::initializeConcreteProcess(
                                                         std::move(fcts));
     };
     auto makeEx =
-        [&](TESIntPtVariables var) -> SecondaryVariableFunctions {
-        return ProcessLib::makeExtrapolator(var, *_extrapolator,
-                                            _local_assemblers);
+        [&](std::vector<double> const& (TESLocalAssemblerInterface::*method)(
+            std::vector<double>&)const) -> SecondaryVariableFunctions {
+        return ProcessLib::makeExtrapolator(getExtrapolator(),
+                                            _local_assemblers, method);
     };
 
-    add2nd("solid_density", 1, makeEx(TESIntPtVariables::SOLID_DENSITY));
-    add2nd("reaction_rate", 1, makeEx(TESIntPtVariables::REACTION_RATE));
-    add2nd("velocity_x", 1, makeEx(TESIntPtVariables::VELOCITY_X));
+    add2nd("solid_density", 1,
+           makeEx(&TESLocalAssemblerInterface::getIntPtSolidDensity));
+    add2nd("reaction_rate", 1,
+           makeEx(&TESLocalAssemblerInterface::getIntPtReactionRate));
+
+    add2nd("velocity_x", 1,
+           makeEx(&TESLocalAssemblerInterface::getIntPtDarcyVelocityX));
     if (mesh.getDimension() >= 2)
-        add2nd("velocity_y", 1, makeEx(TESIntPtVariables::VELOCITY_Y));
+        add2nd("velocity_y", 1,
+               makeEx(&TESLocalAssemblerInterface::getIntPtDarcyVelocityY));
     if (mesh.getDimension() >= 3)
-        add2nd("velocity_z", 1, makeEx(TESIntPtVariables::VELOCITY_Z));
+        add2nd("velocity_z", 1,
+               makeEx(&TESLocalAssemblerInterface::getIntPtDarcyVelocityZ));
 
-    add2nd("loading", 1, makeEx(TESIntPtVariables::LOADING));
+    add2nd("loading", 1, makeEx(&TESLocalAssemblerInterface::getIntPtLoading));
     add2nd("reaction_damping_factor", 1,
-           makeEx(TESIntPtVariables::REACTION_DAMPING_FACTOR));
+           makeEx(&TESLocalAssemblerInterface::getIntPtReactionDampingFactor));
 
     namespace PH = std::placeholders;
     using Self = TESProcess;
@@ -338,7 +323,7 @@ TESProcess::computeVapourPartialPressure(
 {
     assert(&dof_table == this->_local_to_global_index_map.get());
 
-    auto const& dof_table_single = *_local_to_global_index_map_single_component;
+    auto const& dof_table_single = getSingleComponentDOFTable();
     result_cache = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(
         {dof_table_single.dofSizeWithoutGhosts(),
          dof_table_single.dofSizeWithoutGhosts(),
@@ -371,7 +356,7 @@ TESProcess::computeRelativeHumidity(
 {
     assert(&dof_table == this->_local_to_global_index_map.get());
 
-    auto const& dof_table_single = *_local_to_global_index_map_single_component;
+    auto const& dof_table_single = getSingleComponentDOFTable();
     result_cache = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(
         {dof_table_single.dofSizeWithoutGhosts(),
          dof_table_single.dofSizeWithoutGhosts(),
@@ -409,7 +394,7 @@ TESProcess::computeEquilibriumLoading(
 {
     assert(&dof_table == this->_local_to_global_index_map.get());
 
-    auto const& dof_table_single = *_local_to_global_index_map_single_component;
+    auto const& dof_table_single = getSingleComponentDOFTable();
     result_cache = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(
         {dof_table_single.dofSizeWithoutGhosts(),
          dof_table_single.dofSizeWithoutGhosts(),
diff --git a/ProcessLib/TES/TESProcess.h b/ProcessLib/TES/TESProcess.h
index 987f84be056ade216626e93881447609a1217989..ec60ca4c03c5441557253149e5eb70a65037216a 100644
--- a/ProcessLib/TES/TESProcess.h
+++ b/ProcessLib/TES/TESProcess.h
@@ -31,8 +31,6 @@ namespace TES
 {
 class TESProcess final : public Process
 {
-    using BP = Process;  //!< "Base Process"
-
 public:
     TESProcess(
         MeshLib::Mesh& mesh,
@@ -51,13 +49,8 @@ public:
     NumLib::IterationResult postIteration(GlobalVector const& x) override;
 
     bool isLinear() const override { return false; }
-private:
-    using ExtrapolatorInterface =
-        NumLib::Extrapolator<TESIntPtVariables, TESLocalAssemblerInterface>;
-    using ExtrapolatorImplementation =
-        NumLib::LocalLinearLeastSquaresExtrapolator<
-            TESIntPtVariables, TESLocalAssemblerInterface>;
 
+private:
     void initializeConcreteProcess(
         NumLib::LocalToGlobalIndexMap const& dof_table,
         MeshLib::Mesh const& mesh, unsigned const integration_order) override;
@@ -85,11 +78,6 @@ private:
 
     AssemblyParams _assembly_params;
 
-    std::unique_ptr<NumLib::LocalToGlobalIndexMap>
-        _local_to_global_index_map_single_component;
-
-    std::unique_ptr<ExtrapolatorInterface> _extrapolator;
-
     // used for checkBounds()
     std::unique_ptr<GlobalVector> _x_previous_timestep;
 };
diff --git a/Tests/NumLib/TestExtrapolation.cpp b/Tests/NumLib/TestExtrapolation.cpp
index b76cb3b76847dccf38d8479c4feab4298f2ee0f4..c33916bfc9c807daa1c6d9d33c388c1ce241e277 100644
--- a/Tests/NumLib/TestExtrapolation.cpp
+++ b/Tests/NumLib/TestExtrapolation.cpp
@@ -10,26 +10,26 @@
 #include <random>
 #include <gtest/gtest.h>
 
-#include "NumLib/DOF/MatrixProviderUser.h"
 #include "MathLib/LinAlg/MatrixVectorTraits.h"
 #include "MathLib/LinAlg/UnifiedMatrixSetters.h"
-
 #include "MathLib/LinAlg/LinAlg.h"
 
 #include "MeshLib/MeshGenerators/MeshGenerator.h"
 
 #include "NumLib/DOF/DOFTableUtil.h"
+#include "NumLib/DOF/MatrixProviderUser.h"
+#include "NumLib/Extrapolation/ExtrapolatableElementCollection.h"
 #include "NumLib/Extrapolation/Extrapolator.h"
 #include "NumLib/Extrapolation/LocalLinearLeastSquaresExtrapolator.h"
 #include "NumLib/Fem/FiniteElement/TemplateIsoparametric.h"
 #include "NumLib/Fem/ShapeMatrixPolicy.h"
-
+#include "NumLib/Function/Interpolation.h"
 #include "NumLib/NumericsConfig.h"
+
 #include "ProcessLib/Utils/LocalDataInitializer.h"
 #include "ProcessLib/Utils/CreateLocalAssemblers.h"
 #include "ProcessLib/Utils/InitShapeMatrices.h"
 
-
 namespace
 {
 
@@ -63,26 +63,25 @@ void fillVectorRandomly(Vector& x)
 
 }
 
-enum class IntegrationPointValue
-{
-    StoredQuantity, // some quantity acutally stored in the local assembler
-    DerivedQuantity // a quantity computed for each integration point on-the-fly
-};
-
-class LocalAssemblerDataInterface
-        : public NumLib::Extrapolatable<IntegrationPointValue>
+class LocalAssemblerDataInterface : public NumLib::ExtrapolatableElement
 {
 public:
     virtual void interpolateNodalValuesToIntegrationPoints(
-            std::vector<double> const& local_nodal_values,
-            IntegrationPointValue const property) = 0;
+        std::vector<double> const& local_nodal_values) = 0;
+
+    virtual std::vector<double> const& getStoredQuantity(
+        std::vector<double>& /*cache*/) const = 0;
+
+    virtual std::vector<double> const& getDerivedQuantity(
+        std::vector<double>& cache) const = 0;
 };
 
-template<typename ShapeFunction,
-         typename IntegrationMethod,
-         unsigned GlobalDim>
-class LocalAssemblerData
-        : public LocalAssemblerDataInterface
+using IntegrationPointValuesMethod = std::vector<double> const& (
+    LocalAssemblerDataInterface::*)(std::vector<double>&)const;
+
+template <typename ShapeFunction, typename IntegrationMethod,
+          unsigned GlobalDim>
+class LocalAssemblerData : public LocalAssemblerDataInterface
 {
     using ShapeMatricesType = ShapeMatrixPolicyType<ShapeFunction, GlobalDim>;
     using ShapeMatrices = typename ShapeMatricesType::ShapeMatrices;
@@ -93,9 +92,11 @@ public:
                        unsigned const integration_order)
         : _shape_matrices(
               ProcessLib::initShapeMatrices<ShapeFunction, ShapeMatricesType,
-                  IntegrationMethod, GlobalDim>(e, integration_order))
+                                            IntegrationMethod, GlobalDim>(
+                  e, integration_order))
         , _int_pt_values(_shape_matrices.size())
-    {}
+    {
+    }
 
     Eigen::Map<const Eigen::RowVectorXd>
     getShapeMatrix(const unsigned integration_point) const override
@@ -106,42 +107,31 @@ public:
         return Eigen::Map<const Eigen::RowVectorXd>(N.data(), N.size());
     }
 
-    std::vector<double> const&
-    getIntegrationPointValues(IntegrationPointValue const property,
-                              std::vector<double>& cache) const override
+    std::vector<double> const& getStoredQuantity(
+        std::vector<double>& /*cache*/) const override
     {
-        switch (property)
-        {
-        case IntegrationPointValue::StoredQuantity:
-            return _int_pt_values;
-        case IntegrationPointValue::DerivedQuantity:
-            cache.clear();
-            for (auto value : _int_pt_values)
-                cache.push_back(2.0 * value);
-            return cache;
-        }
+        return _int_pt_values;
+    }
 
-        OGS_FATAL("");
+    std::vector<double> const& getDerivedQuantity(
+        std::vector<double>& cache) const override
+    {
+        cache.clear();
+        for (auto value : _int_pt_values)
+            cache.push_back(2.0 * value);
+        return cache;
     }
 
     void interpolateNodalValuesToIntegrationPoints(
-            std::vector<double> const& local_nodal_values,
-            IntegrationPointValue const property) override
+        std::vector<double> const& local_nodal_values) override
     {
-        switch (property)
-        {
-        case IntegrationPointValue::StoredQuantity:
-            ::interpolateNodalValuesToIntegrationPoints(
-                        local_nodal_values, _shape_matrices, _int_pt_values);
-            break;
-        case IntegrationPointValue::DerivedQuantity:
-            break;
-        }
+        ::interpolateNodalValuesToIntegrationPoints(
+            local_nodal_values, _shape_matrices, _int_pt_values);
     }
 
 private:
     std::vector<ShapeMatrices> _shape_matrices;
-    std::vector<double>        _int_pt_values;
+    std::vector<double> _int_pt_values;
 };
 
 class TestProcess
@@ -149,11 +139,9 @@ class TestProcess
 public:
     using LocalAssembler = LocalAssemblerDataInterface;
 
-    using ExtrapolatorInterface =
-        NumLib::Extrapolator<IntegrationPointValue, LocalAssembler>;
+    using ExtrapolatorInterface = NumLib::Extrapolator;
     using ExtrapolatorImplementation =
-        NumLib::LocalLinearLeastSquaresExtrapolator<
-            IntegrationPointValue, LocalAssembler>;
+        NumLib::LocalLinearLeastSquaresExtrapolator;
 
     TestProcess(MeshLib::Mesh const& mesh, unsigned const integration_order)
         : _integration_order(integration_order)
@@ -161,51 +149,44 @@ public:
     {
         std::vector<std::unique_ptr<MeshLib::MeshSubsets>> all_mesh_subsets;
         all_mesh_subsets.emplace_back(
-                    new MeshLib::MeshSubsets{&_mesh_subset_all_nodes});
+            new MeshLib::MeshSubsets{&_mesh_subset_all_nodes});
 
         _dof_table.reset(new NumLib::LocalToGlobalIndexMap(
-              std::move(all_mesh_subsets),
-              NumLib::ComponentOrder::BY_COMPONENT));
+            std::move(all_mesh_subsets), NumLib::ComponentOrder::BY_COMPONENT));
 
         // Passing _dof_table works, because this process has only one variable
         // and the variable has exactly one component.
-        _extrapolator.reset(new ExtrapolatorImplementation(
-            MathLib::MatrixSpecifications{_dof_table->dofSizeWithoutGhosts(),
-                                          _dof_table->dofSizeWithoutGhosts(),
-                                          &_dof_table->getGhostIndices(),
-                                          nullptr},
-            *_dof_table));
+        _extrapolator.reset(new ExtrapolatorImplementation(*_dof_table));
 
         createAssemblers(mesh);
     }
 
     void interpolateNodalValuesToIntegrationPoints(
-            GlobalVector const& global_nodal_values,
-            IntegrationPointValue const property) const
+        GlobalVector const& global_nodal_values) const
     {
         auto cb = [](std::size_t id, LocalAssembler& loc_asm,
                      NumLib::LocalToGlobalIndexMap const& dof_table,
-                     GlobalVector const& x,
-                     IntegrationPointValue const property) {
+                     GlobalVector const& x) {
             auto const indices = NumLib::getIndices(id, dof_table);
             auto const local_x = x.get(indices);
 
-            loc_asm.interpolateNodalValuesToIntegrationPoints(local_x,
-                                                              property);
+            loc_asm.interpolateNodalValuesToIntegrationPoints(local_x);
         };
 
         GlobalExecutor::executeDereferenced(cb, _local_assemblers, *_dof_table,
-                                            global_nodal_values, property);
+                                            global_nodal_values);
     }
 
-    std::pair<GlobalVector const*, GlobalVector const*>
-    extrapolate(IntegrationPointValue const property) const
+    std::pair<GlobalVector const*, GlobalVector const*> extrapolate(
+        IntegrationPointValuesMethod method) const
     {
-        _extrapolator->extrapolate(_local_assemblers, property);
-        _extrapolator->calculateResiduals(_local_assemblers, property);
+        auto const extrapolatables =
+            NumLib::makeExtrapolatable(_local_assemblers, method);
+        _extrapolator->extrapolate(extrapolatables);
+        _extrapolator->calculateResiduals(extrapolatables);
 
-        return { &_extrapolator->getNodalValues(),
-                 &_extrapolator->getElementResiduals() };
+        return {&_extrapolator->getNodalValues(),
+                &_extrapolator->getElementResiduals()};
     }
 
 private:
@@ -249,11 +230,8 @@ private:
     std::unique_ptr<ExtrapolatorInterface> _extrapolator;
 };
 
-
-void extrapolate(TestProcess const& pcs,
-                 IntegrationPointValue property,
-                 GlobalVector const&
-                 expected_extrapolated_global_nodal_values,
+void extrapolate(TestProcess const& pcs, IntegrationPointValuesMethod method,
+                 GlobalVector const& expected_extrapolated_global_nodal_values,
                  std::size_t const nnodes, std::size_t const nelements)
 {
     namespace LinAlg = MathLib::LinAlg;
@@ -261,8 +239,8 @@ void extrapolate(TestProcess const& pcs,
     auto const tolerance_dx  = 20.0 * std::numeric_limits<double>::epsilon();
     auto const tolerance_res =  5.0 * std::numeric_limits<double>::epsilon();
 
-    auto const  result   = pcs.extrapolate(property);
-    auto const& x_extra  = *result.first;
+    auto const result = pcs.extrapolate(method);
+    auto const& x_extra = *result.first;
     auto const& residual = *result.second;
 
     ASSERT_EQ(nnodes,    x_extra.size());
@@ -322,20 +300,20 @@ TEST(NumLib, DISABLED_Extrapolation)
 
         fillVectorRandomly(*x);
 
-        pcs.interpolateNodalValuesToIntegrationPoints(
-                    *x, IntegrationPointValue::StoredQuantity);
+        pcs.interpolateNodalValuesToIntegrationPoints(*x);
 
-        // test extrapolation of a quantity that is stored in the local assembler
-        extrapolate(pcs, IntegrationPointValue::StoredQuantity,
-                    *x, nnodes, nelements);
+        // test extrapolation of a quantity that is stored in the local
+        // assembler
+        extrapolate(pcs, &LocalAssemblerDataInterface::getStoredQuantity, *x,
+                    nnodes, nelements);
 
         // expect 2*x as extraplation result for derived quantity
         auto two_x = MathLib::MatrixVectorTraits<GlobalVector>::newInstance(*x);
         LinAlg::axpy(*two_x, 1.0, *x); // two_x = x + x
 
-        // test extrapolation of a quantity that is derived from some integration
-        // point values
-        extrapolate(pcs, IntegrationPointValue::DerivedQuantity,
+        // test extrapolation of a quantity that is derived from some
+        // integration point values
+        extrapolate(pcs, &LocalAssemblerDataInterface::getDerivedQuantity,
                     *two_x, nnodes, nelements);
     }
 }