diff --git a/ProcessLib/StokesFlow/CreateLocalAssemblers.h b/ProcessLib/StokesFlow/CreateLocalAssemblers.h
new file mode 100644
index 0000000000000000000000000000000000000000..9d3215354d370ba2a6e5761f299decd0d0b7c0fe
--- /dev/null
+++ b/ProcessLib/StokesFlow/CreateLocalAssemblers.h
@@ -0,0 +1,87 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "LocalDataInitializer.h"
+
+#include "BaseLib/Logging.h"
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+
+namespace ProcessLib
+{
+namespace StokesFlow
+{
+namespace detail
+{
+template <int GlobalDim,
+          template <typename, typename, typename, int>
+          class LocalAssemblerImplementation,
+          typename LocalAssemblerInterface, typename... ExtraCtorArgs>
+void createLocalAssemblers(
+    NumLib::LocalToGlobalIndexMap const& dof_table,
+    const unsigned shapefunction_order,
+    std::vector<MeshLib::Element*> const& mesh_elements,
+    std::vector<std::unique_ptr<LocalAssemblerInterface>>& local_assemblers,
+    ExtraCtorArgs&&... extra_ctor_args)
+{
+    // Shape matrices initializer
+    using LocalDataInitializer =
+        LocalDataInitializer<LocalAssemblerInterface,
+                             LocalAssemblerImplementation, GlobalDim,
+                             ExtraCtorArgs...>;
+
+    DBUG("Create local assemblers.");
+    // Populate the vector of local assemblers.
+    local_assemblers.resize(mesh_elements.size());
+
+    LocalDataInitializer initializer(dof_table, shapefunction_order);
+
+    DBUG("Calling local assembler builder for all mesh elements.");
+    GlobalExecutor::transformDereferenced(
+        initializer, mesh_elements, local_assemblers,
+        std::forward<ExtraCtorArgs>(extra_ctor_args)...);
+}
+
+}  // namespace detail
+
+/*! Creates local assemblers for each element of the given \c mesh.
+ *
+ * \tparam LocalAssemblerImplementation the individual local assembler type
+ * \tparam LocalAssemblerInterface the general local assembler interface
+ * \tparam ExtraCtorArgs types of additional constructor arguments.
+ *         Those arguments will be passed to the constructor of
+ *         \c LocalAssemblerImplementation.
+ *
+ * The first two template parameters cannot be deduced from the arguments.
+ * Therefore they always have to be provided manually.
+ */
+template <int GlobalDim,
+          template <typename, typename, typename, int>
+          class LocalAssemblerImplementation,
+          typename LocalAssemblerInterface, typename... ExtraCtorArgs>
+void createLocalAssemblers(
+    const unsigned /*dimension*/,
+    std::vector<MeshLib::Element*> const& mesh_elements,
+    NumLib::LocalToGlobalIndexMap const& dof_table,
+    const unsigned shapefunction_order,
+    std::vector<std::unique_ptr<LocalAssemblerInterface>>& local_assemblers,
+    ExtraCtorArgs&&... extra_ctor_args)
+{
+    DBUG("Create local assemblers.");
+
+    detail::createLocalAssemblers<GlobalDim, LocalAssemblerImplementation>(
+        dof_table, shapefunction_order, mesh_elements, local_assemblers,
+        std::forward<ExtraCtorArgs>(extra_ctor_args)...);
+}
+
+}  // namespace StokesFlow
+}  // namespace ProcessLib
diff --git a/ProcessLib/StokesFlow/LocalDataInitializer.h b/ProcessLib/StokesFlow/LocalDataInitializer.h
new file mode 100644
index 0000000000000000000000000000000000000000..caaaee96fce7b5f40cacd9a8ff2ca76c1612aa53
--- /dev/null
+++ b/ProcessLib/StokesFlow/LocalDataInitializer.h
@@ -0,0 +1,287 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <typeindex>
+#include <typeinfo>
+#include <unordered_map>
+
+#include "MeshLib/Elements/Elements.h"
+#include "NumLib/DOF/LocalToGlobalIndexMap.h"
+#include "NumLib/Fem/FiniteElement/LowerDimShapeTable.h"
+#include "NumLib/Fem/Integration/GaussLegendreIntegrationPolicy.h"
+
+#ifndef OGS_MAX_ELEMENT_DIM
+static_assert(false, "The macro OGS_MAX_ELEMENT_DIM is undefined.");
+#endif
+
+#ifndef OGS_MAX_ELEMENT_ORDER
+static_assert(false, "The macro OGS_MAX_ELEMENT_ORDER is undefined.");
+#endif
+
+// The following macros decide which element types will be compiled, i.e.
+// which element types will be available for use in simulations.
+
+#ifdef OGS_ENABLE_ELEMENT_SIMPLEX
+#define ENABLED_ELEMENT_TYPE_SIMPLEX 1u
+#else
+#define ENABLED_ELEMENT_TYPE_SIMPLEX 0u
+#endif
+
+#ifdef OGS_ENABLE_ELEMENT_CUBOID
+#define ENABLED_ELEMENT_TYPE_CUBOID 1u << 1
+#else
+#define ENABLED_ELEMENT_TYPE_CUBOID 0u
+#endif
+
+#ifdef OGS_ENABLE_ELEMENT_PRISM
+#define ENABLED_ELEMENT_TYPE_PRISM 1u << 2
+#else
+#define ENABLED_ELEMENT_TYPE_PRISM 0u
+#endif
+
+#ifdef OGS_ENABLE_ELEMENT_PYRAMID
+#define ENABLED_ELEMENT_TYPE_PYRAMID 1u << 3
+#else
+#define ENABLED_ELEMENT_TYPE_PYRAMID 0u
+#endif
+
+// Dependent element types.
+// Faces of tets, pyramids and prisms are triangles
+#define ENABLED_ELEMENT_TYPE_TRI                                       \
+    ((ENABLED_ELEMENT_TYPE_SIMPLEX) | (ENABLED_ELEMENT_TYPE_PYRAMID) | \
+     (ENABLED_ELEMENT_TYPE_PRISM))
+// Faces of hexes, pyramids and prisms are quads
+#define ENABLED_ELEMENT_TYPE_QUAD                                     \
+    ((ENABLED_ELEMENT_TYPE_CUBOID) | (ENABLED_ELEMENT_TYPE_PYRAMID) | \
+     (ENABLED_ELEMENT_TYPE_PRISM))
+
+// All enabled element types
+#define OGS_ENABLED_ELEMENTS                                          \
+    ((ENABLED_ELEMENT_TYPE_SIMPLEX) | (ENABLED_ELEMENT_TYPE_CUBOID) | \
+     (ENABLED_ELEMENT_TYPE_PYRAMID) | (ENABLED_ELEMENT_TYPE_PRISM))
+
+// Include only what is needed (Well, the conditions are not sharp).
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeTet10.h"
+#include "NumLib/Fem/ShapeFunction/ShapeTet4.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeTri3.h"
+#include "NumLib/Fem/ShapeFunction/ShapeTri6.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeHex20.h"
+#include "NumLib/Fem/ShapeFunction/ShapeHex8.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0
+#include "NumLib/Fem/ShapeFunction/ShapeQuad4.h"
+#include "NumLib/Fem/ShapeFunction/ShapeQuad8.h"
+#include "NumLib/Fem/ShapeFunction/ShapeQuad9.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0
+#include "NumLib/Fem/ShapeFunction/ShapePrism15.h"
+#include "NumLib/Fem/ShapeFunction/ShapePrism6.h"
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0
+#include "NumLib/Fem/ShapeFunction/ShapePyra13.h"
+#include "NumLib/Fem/ShapeFunction/ShapePyra5.h"
+#endif
+
+namespace ProcessLib::StokesFlow
+{
+/// The LocalDataInitializer is a functor creating a local assembler data with
+/// corresponding to the mesh element type shape functions and calling
+/// initialization of the new local assembler data.
+/// For example for MeshLib::Line a local assembler data with template argument
+/// NumLib::ShapeLine2 is created.
+///
+/// \attention This is modified version of the ProcessLib::LocalDataInitializer
+/// class which does not include line or point elements. For the shape functions
+/// of order 2 (used for fluid velocity in StokesFlow) a shape function of order
+/// 1 will be used for the pressure.
+template <typename LocalAssemblerInterface,
+          template <typename, typename, typename, int> class LocalAssemblerData,
+          unsigned GlobalDim, typename... ConstructorArgs>
+class LocalDataInitializer final
+{
+public:
+    using LADataIntfPtr = std::unique_ptr<LocalAssemblerInterface>;
+
+    LocalDataInitializer(NumLib::LocalToGlobalIndexMap const& dof_table,
+                         const unsigned shapefunction_order)
+        : _dof_table(dof_table)
+    {
+        if (shapefunction_order != 2)
+        {
+            OGS_FATAL(
+                "The given shape function order {:d} is not supported. The "
+                "shape function order shall be specified to 2.",
+                shapefunction_order);
+        }
+
+        // /// Quads and Hexahedra ///////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_QUAD) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 2
+            _builder[std::type_index(typeid(MeshLib::Quad8))] =
+                makeLocalAssemblerBuilder<NumLib::ShapeQuad8>();
+            _builder[std::type_index(typeid(MeshLib::Quad9))] =
+                makeLocalAssemblerBuilder<NumLib::ShapeQuad9>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_CUBOID) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+            _builder[std::type_index(typeid(MeshLib::Hex20))] =
+                makeLocalAssemblerBuilder<NumLib::ShapeHex20>();
+#endif
+
+            // /// Simplices ////////////////////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_TRI) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 2 && OGS_MAX_ELEMENT_ORDER >= 2
+            _builder[std::type_index(typeid(MeshLib::Tri6))] =
+                makeLocalAssemblerBuilder<NumLib::ShapeTri6>();
+#endif
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_SIMPLEX) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+            _builder[std::type_index(typeid(MeshLib::Tet10))] =
+                makeLocalAssemblerBuilder<NumLib::ShapeTet10>();
+#endif
+
+            // /// Prisms ////////////////////////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PRISM) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+            _builder[std::type_index(typeid(MeshLib::Prism15))] =
+                makeLocalAssemblerBuilder<NumLib::ShapePrism15>();
+#endif
+
+            // /// Pyramids //////////////////////////////////////////////////
+
+#if (OGS_ENABLED_ELEMENTS & ENABLED_ELEMENT_TYPE_PYRAMID) != 0 && \
+    OGS_MAX_ELEMENT_DIM >= 3 && OGS_MAX_ELEMENT_ORDER >= 2
+            _builder[std::type_index(typeid(MeshLib::Pyramid13))] =
+                makeLocalAssemblerBuilder<NumLib::ShapePyra13>();
+#endif
+    }
+
+    /// Returns data pointer to the newly created local assembler data.
+    ///
+    /// \attention
+    /// The index \c id is not necessarily the mesh item's id. Especially when
+    /// having multiple meshes it will differ from the latter.
+    LADataIntfPtr operator()(std::size_t const id,
+                             MeshLib::Element const& mesh_item,
+                             ConstructorArgs&&... args) const
+    {
+        auto const type_idx = std::type_index(typeid(mesh_item));
+        auto const it = _builder.find(type_idx);
+
+        if (it != _builder.end())
+        {
+            auto const num_local_dof = _dof_table.getNumberOfElementDOF(id);
+            return it->second(mesh_item, num_local_dof,
+                              std::forward<ConstructorArgs>(args)...);
+        }
+        OGS_FATAL(
+            "You are trying to build a local assembler for an unknown mesh "
+            "element type ({:s})."
+            " Maybe you have disabled this mesh element type in your build "
+            "configuration.",
+            type_idx.name());
+    }
+
+private:
+    using LADataBuilder =
+        std::function<LADataIntfPtr(MeshLib::Element const& e,
+                                    std::size_t const local_matrix_size,
+                                    ConstructorArgs&&...)>;
+
+    template <typename ShapeFunction>
+    using IntegrationMethod = typename NumLib::GaussLegendreIntegrationPolicy<
+        typename ShapeFunction::MeshElement>::IntegrationMethod;
+
+    template <typename ShapeFunctionDisplacement,
+              typename ShapeFunctionPressure>
+    using LAData =
+        LocalAssemblerData<ShapeFunctionDisplacement, ShapeFunctionPressure,
+                           IntegrationMethod<ShapeFunctionDisplacement>,
+                           GlobalDim>;
+
+    /// A helper forwarding to the correct version of makeLocalAssemblerBuilder
+    /// depending whether the global dimension is less than the shape function's
+    /// dimension or not.
+    template <typename ShapeFunction>
+    static LADataBuilder makeLocalAssemblerBuilder()
+    {
+        return makeLocalAssemblerBuilder<ShapeFunction>(
+            static_cast<std::integral_constant<
+                bool, (GlobalDim >= ShapeFunction::DIM)>*>(nullptr));
+    }
+
+    /// Mapping of element types to local assembler constructors.
+    std::unordered_map<std::type_index, LADataBuilder> _builder;
+
+    NumLib::LocalToGlobalIndexMap const& _dof_table;
+
+    // local assembler builder implementations.
+private:
+    /// Generates a function that creates a new LocalAssembler of type
+    /// LAData<ShapeFunctionDisplacement, ShapeFunctionPressure>. Only functions
+    /// with shape function's dimension less or equal to the global dimension
+    /// are instantiated, e.g.  following combinations of shape functions and
+    /// global dimensions: (Line2, 1), (Line2, 2), (Line2, 3), (Hex20, 3) but
+    /// not (Hex20, 2) or (Hex20, 1).
+    template <typename ShapeFunction>
+    static LADataBuilder makeLocalAssemblerBuilder(std::true_type* /*unused*/)
+    {
+        assert(ShapeFunction::ORDER == 2);
+
+        using LowerOrderShapeFunction =
+            typename NumLib::LowerDim<ShapeFunction>::type;
+        return [](MeshLib::Element const& e,
+                  std::size_t const local_matrix_size,
+                  ConstructorArgs&&... args) {
+            return LADataIntfPtr{
+                new LAData<ShapeFunction, LowerOrderShapeFunction>{
+                    e, local_matrix_size,
+                    std::forward<ConstructorArgs>(args)...}};
+        };
+    }
+
+    /// Returns nullptr for shape functions whose dimensions are less than the
+    /// global dimension.
+    template <typename ShapeFunction>
+    static LADataBuilder makeLocalAssemblerBuilder(std::false_type* /*unused*/)
+    {
+        return nullptr;
+    }
+};
+
+}  // namespace ProcessLib::StokesFlow
+
+#undef ENABLED_ELEMENT_TYPE_SIMPLEX
+#undef ENABLED_ELEMENT_TYPE_CUBOID
+#undef ENABLED_ELEMENT_TYPE_PYRAMID
+#undef ENABLED_ELEMENT_TYPE_PRISM
+#undef ENABLED_ELEMENT_TYPE_TRI
+#undef ENABLED_ELEMENT_TYPE_QUAD
+#undef OGS_ENABLED_ELEMENTS
diff --git a/ProcessLib/StokesFlow/StokesFlowFEM.h b/ProcessLib/StokesFlow/StokesFlowFEM.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd3a9f3ab4611e739f494dca071faea957230c5f
--- /dev/null
+++ b/ProcessLib/StokesFlow/StokesFlowFEM.h
@@ -0,0 +1,257 @@
+/**
+ * \file
+ * \copyright
+ * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
+ *            Distributed under a Modified BSD License.
+ *              See accompanying file LICENSE.txt or
+ *              http://www.opengeosys.org/project/license
+ *
+ */
+
+#pragma once
+
+#include <Eigen/Dense>
+#include <vector>
+
+#include "IntegrationPointData.h"
+#include "LocalAssemblerInterface.h"
+#include "StokesFlowProcessData.h"
+
+#include "MaterialLib/MPL/MaterialSpatialDistributionMap.h"
+#include "MaterialLib/MPL/Medium.h"
+#include "MaterialLib/MPL/Property.h"
+#include "MathLib/LinAlg/Eigen/EigenMapTools.h"
+#include "NumLib/Fem/InitShapeMatrices.h"
+#include "NumLib/Fem/ShapeMatrixPolicy.h"
+#include "NumLib/Function/Interpolation.h"
+
+namespace ProcessLib::StokesFlow
+{
+template <typename ShapeFunctionLiquidVelocity, typename ShapeFunctionPressure,
+          typename IntegrationMethod, int GlobalDim>
+class LocalAssemblerData : public StokesFlowLocalAssemblerInterface
+{
+    static const int liquid_velocity_index = 0;
+    static const int pressure_index =
+        ShapeFunctionLiquidVelocity::NPOINTS * GlobalDim;
+
+    static const int liquid_velocity_size =
+        ShapeFunctionLiquidVelocity::NPOINTS * GlobalDim;
+    static const int pressure_size = ShapeFunctionPressure::NPOINTS;
+
+    using ShapeMatrixTypeLiquidVelocity =
+        ShapeMatrixPolicyType<ShapeFunctionLiquidVelocity, GlobalDim>;
+    using ShapeMatrixTypePressure =
+        ShapeMatrixPolicyType<ShapeFunctionPressure, GlobalDim>;
+
+    using NodalVectorType = typename ShapeMatrixTypePressure::NodalVectorType;
+
+public:
+    LocalAssemblerData(MeshLib::Element const& element,
+                       std::size_t const /*local_matrix_size*/,
+                       bool const is_axially_symmetric,
+                       unsigned const integration_order,
+                       StokesFlowProcessData const& process_data)
+        : _element(element),
+          _is_axially_symmetric(is_axially_symmetric),
+          _integration_method(integration_order),
+          _process_data(process_data)
+    {
+        unsigned const n_integration_points =
+            _integration_method.getNumberOfPoints();
+        _ip_data.reserve(n_integration_points);
+
+        auto const shape_matrices_v =
+            NumLib::initShapeMatrices<ShapeFunctionLiquidVelocity,
+                                      ShapeMatrixTypeLiquidVelocity, GlobalDim>(
+                element, is_axially_symmetric, _integration_method);
+
+        auto const shape_matrices_p =
+            NumLib::initShapeMatrices<ShapeFunctionPressure,
+                                      ShapeMatrixTypePressure, GlobalDim>(
+                element, is_axially_symmetric, _integration_method);
+
+        for (unsigned ip = 0; ip < n_integration_points; ip++)
+        {
+            _ip_data.emplace_back(
+                shape_matrices_v[ip].N, shape_matrices_v[ip].dNdx,
+                shape_matrices_p[ip].N, shape_matrices_p[ip].dNdx,
+                _integration_method.getWeightedPoint(ip).getWeight() *
+                    shape_matrices_v[ip].integralMeasure *
+                    shape_matrices_v[ip].detJ);
+
+            auto& ip_data = _ip_data[ip];
+
+            ip_data.N_v_op = ShapeMatrixTypeLiquidVelocity::template MatrixType<
+                GlobalDim, liquid_velocity_size>::Zero(GlobalDim,
+                                                       liquid_velocity_size);
+
+            ip_data.dNdx_v_op =
+                ShapeMatrixTypeLiquidVelocity::template MatrixType<
+                    GlobalDim * GlobalDim,
+                    liquid_velocity_size>::Zero(GlobalDim * GlobalDim,
+                                                liquid_velocity_size);
+
+            for (int i = 0; i < GlobalDim; ++i)
+            {
+                ip_data.N_v_op
+                    .template block<1, liquid_velocity_size / GlobalDim>(
+                        i, i * liquid_velocity_size / GlobalDim)
+                    .noalias() = shape_matrices_v[ip].N;
+
+                ip_data.dNdx_v_op
+                    .template block<GlobalDim,
+                                    liquid_velocity_size / GlobalDim>(
+                        i * GlobalDim, i * liquid_velocity_size / GlobalDim)
+                    .noalias() = shape_matrices_v[ip].dNdx;
+            }
+        }
+    }
+
+    void assemble(double const t, double const dt,
+                  std::vector<double> const& local_x,
+                  std::vector<double> const& /*local_xdot*/,
+                  std::vector<double>& /*local_M_data*/,
+                  std::vector<double>& local_K_data,
+                  std::vector<double>& local_b_data) override
+    {
+        auto const local_matrix_size = liquid_velocity_size + pressure_size;
+        assert(local_x.size() == local_matrix_size);
+
+        auto local_p = Eigen::Map<const NodalVectorType>(
+            &local_x[pressure_index], pressure_size);
+
+        auto local_K = MathLib::createZeroedMatrix<
+            typename ShapeMatrixTypeLiquidVelocity::template MatrixType<
+                local_matrix_size, local_matrix_size>>(
+            local_K_data, local_matrix_size, local_matrix_size);
+        auto local_b = MathLib::createZeroedVector<
+            typename ShapeMatrixTypeLiquidVelocity::template VectorType<
+                local_matrix_size>>(local_b_data, local_matrix_size);
+
+        // Get block matrices
+        // Kvv
+        auto Kvv =
+            local_K.template block<liquid_velocity_size, liquid_velocity_size>(
+                liquid_velocity_index, liquid_velocity_index);
+        // Kvp
+        auto Kvp = local_K.template block<liquid_velocity_size, pressure_size>(
+            liquid_velocity_index, pressure_index);
+        // kpv
+        auto Kpv = local_K.template block<pressure_size, liquid_velocity_size>(
+            pressure_index, liquid_velocity_index);
+        // bv
+        auto bv = local_b.template segment<liquid_velocity_size>(
+            liquid_velocity_index);
+
+        unsigned const n_integration_points =
+            _integration_method.getNumberOfPoints();
+
+        ParameterLib::SpatialPosition pos;
+        pos.setElementID(_element.getID());
+
+        auto const& b = _process_data.specific_body_force;
+
+        MaterialPropertyLib::VariableArray vars;
+
+        // create unit vector
+        assert(GlobalDim == 2);
+        auto const identity_mat =
+            Eigen::Matrix<double, GlobalDim, GlobalDim>::Identity().eval();
+        auto const unit_vector = Eigen::Map<const Eigen::RowVectorXd>(
+                                     identity_mat.data(), identity_mat.size())
+                                     .eval();
+
+        // Get material properties
+        auto const& medium =
+            *_process_data.media_map->getMedium(_element.getID());
+        auto const& phase = medium.phase("AqueousLiquid");
+
+        for (unsigned ip(0); ip < n_integration_points; ++ip)
+        {
+            pos.setIntegrationPoint(ip);
+
+            auto& ip_data = _ip_data[ip];
+
+            auto const& N_p = ip_data.N_p;
+            auto const& N_v_op = ip_data.N_v_op;
+
+            auto const& dNdx_v = ip_data.dNdx_v;
+            auto const& dNdx_v_op = ip_data.dNdx_v_op;
+            auto const& dNdx_p = ip_data.dNdx_p;
+
+            auto const& w = ip_data.integration_weight;
+
+            double p_int_pt = 0.0;
+
+            NumLib::shapeFunctionInterpolate(local_p, N_p, p_int_pt);
+
+            vars[static_cast<int>(
+                MaterialPropertyLib::Variable::phase_pressure)] = p_int_pt;
+
+            // Use the viscosity model to compute the viscosity
+            auto const mu = phase[MaterialPropertyLib::PropertyType::viscosity]
+                                .template value<double>(vars, pos, t, dt);
+
+            // Kvv
+            for (int i = 0; i < GlobalDim; ++i)
+            {
+                Kvv.template block<liquid_velocity_size / GlobalDim,
+                                   liquid_velocity_size / GlobalDim>(
+                       i * liquid_velocity_size / GlobalDim,
+                       i * liquid_velocity_size / GlobalDim)
+                    .noalias() += w * dNdx_v.transpose() * mu * dNdx_v;
+            }
+
+            // Kvp
+            Kvp.noalias() += w * N_v_op.transpose() * dNdx_p;
+
+            // Kpv
+            Kpv.noalias() += w * N_p.transpose() * unit_vector * dNdx_v_op;
+
+            // bv
+            bv.noalias() += w * N_v_op.transpose() * b;
+        }
+    }
+
+    Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix(
+        const unsigned integration_point) const override
+    {
+        auto const& N_p = _ip_data[integration_point].N_p;
+
+        // assumes N_p is stored contiguously in memory
+        return Eigen::Map<const Eigen::RowVectorXd>(N_p.data(), N_p.size());
+    }
+
+    void computeSecondaryVariableConcrete(
+        double const /*t*/,
+        double const /*dt*/,
+        Eigen::VectorXd const& local_x,
+        Eigen::VectorXd const& /*local_x_dot*/) override
+    {
+        auto const local_p =
+            local_x.template segment<pressure_size>(pressure_index);
+
+        NumLib::interpolateToHigherOrderNodes<
+            ShapeFunctionPressure,
+            typename ShapeFunctionLiquidVelocity::MeshElement, GlobalDim>(
+            _element, _is_axially_symmetric, local_p,
+            *_process_data.pressure_interpolated);
+    }
+
+private:
+    MeshLib::Element const& _element;
+    bool const _is_axially_symmetric;
+    IntegrationMethod const _integration_method;
+    StokesFlowProcessData const& _process_data;
+
+    std::vector<IntegrationPointData<ShapeMatrixTypeLiquidVelocity,
+                                     ShapeMatrixTypePressure, GlobalDim,
+                                     ShapeFunctionLiquidVelocity::NPOINTS>,
+                Eigen::aligned_allocator<IntegrationPointData<
+                    ShapeMatrixTypeLiquidVelocity, ShapeMatrixTypePressure,
+                    GlobalDim, ShapeFunctionLiquidVelocity::NPOINTS>>>
+        _ip_data;
+};
+
+}  // namespace ProcessLib::StokesFlow