diff --git a/include/GooseFEM/Mesh.h b/include/GooseFEM/Mesh.h
index 5f99eff..9927c2a 100644
--- a/include/GooseFEM/Mesh.h
+++ b/include/GooseFEM/Mesh.h
@@ -1,114 +1,128 @@
 /*
 
 (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM
 
 */
 
 #ifndef GOOSEFEM_MESH_H
 #define GOOSEFEM_MESH_H
 
 #include "config.h"
 
 namespace GooseFEM {
 namespace Mesh {
 
 enum class ElementType {
     Quad4,
     Hex8,
     Tri3 };
 
 inline ElementType defaultElementType(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn);
 
 // Renumber to lowest possible index. For example [0,3,4,2] -> [0,2,3,1]
 
 class Renumber {
 public:
     // constructors
     Renumber() = default;
     Renumber(const xt::xarray<size_t>& dofs);
 
     // get renumbered DOFs (same as "Renumber::apply(dofs)")
     xt::xtensor<size_t, 2> get(const xt::xtensor<size_t, 2>& dofs) const;
 
     // apply renumbering to other set
     template <class T>
     T apply(const T& list) const;
 
     // get the list needed to renumber, e.g.:
     //   dofs_renumbered(i,j) = index(dofs(i,j))
     xt::xtensor<size_t, 1> index() const;
 
 private:
     xt::xtensor<size_t, 1> m_renum;
 };
 
 // Reorder to lowest possible index, in specific order.
 //
 // For example for "Reorder({iiu,iip})" after reordering:
 //
 //   iiu = xt::range<size_t>(nnu);
 //   iip = xt::range<size_t>(nnp) + nnu;
 
 class Reorder {
 public:
     // constructors
     Reorder() = default;
     Reorder(const std::initializer_list<xt::xtensor<size_t, 1>> args);
 
     // get reordered DOFs (same as "Reorder::apply(dofs)")
     xt::xtensor<size_t, 2> get(const xt::xtensor<size_t, 2>& dofs) const;
 
     // apply renumbering to other set
     template <class T>
     T apply(const T& list) const;
 
     // get the list needed to reorder, e.g.:
     // dofs_reordered(i,j) = index(dofs(i,j))
     xt::xtensor<size_t, 1> index() const;
 
 private:
     xt::xtensor<size_t, 1> m_renum;
 };
 
 // list with DOF-numbers in sequential order
 inline xt::xtensor<size_t, 2> dofs(size_t nnode, size_t ndim);
 
 // renumber to lowest possible index (see "GooseFEM::Mesh::Renumber")
 inline xt::xtensor<size_t, 2> renumber(const xt::xtensor<size_t, 2>& dofs);
 
 // number of elements connected to each node
 inline xt::xtensor<size_t, 1> coordination(const xt::xtensor<size_t, 2>& conn);
 
 // elements connected to each node
 inline std::vector<std::vector<size_t>> elem2node(
     const xt::xtensor<size_t, 2>& conn,
     bool sorted=true); // ensure the output to be sorted
 
 // return size of each element edge
 inline xt::xtensor<double, 2> edgesize(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn,
     ElementType type);
 
 inline xt::xtensor<double, 2> edgesize(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn); // extract element-type based on shape of "conn"
 
 // Coordinates of the center of each element
 inline xt::xtensor<double, 2> centers(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn,
     ElementType type);
 
 inline xt::xtensor<double, 2> centers(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn); // extract element-type based on shape of "conn"
 
+// Convert an element-map to a node-map.
+// Input: new_elvar = elvar[elem_map]
+// Return: new_nodevar = nodevar[node_map]
+inline xt::xtensor<size_t, 1> elemmap2nodemap(
+    const xt::xtensor<size_t, 1>& elem_map,
+    const xt::xtensor<double, 2>& coor,
+    const xt::xtensor<size_t, 2>& conn); // extract element-type based on shape of "conn"
+
+inline xt::xtensor<size_t, 1> elemmap2nodemap(
+    const xt::xtensor<size_t, 1>& elem_map,
+    const xt::xtensor<double, 2>& coor,
+    const xt::xtensor<size_t, 2>& conn,
+    ElementType type);
+
 } // namespace Mesh
 } // namespace GooseFEM
 
 #include "Mesh.hpp"
 
 #endif
diff --git a/include/GooseFEM/Mesh.hpp b/include/GooseFEM/Mesh.hpp
index 43c516f..407acaf 100644
--- a/include/GooseFEM/Mesh.hpp
+++ b/include/GooseFEM/Mesh.hpp
@@ -1,247 +1,284 @@
 /*
 
 (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM
 
 */
 
 #ifndef GOOSEFEM_MESH_HPP
 #define GOOSEFEM_MESH_HPP
 
 #include "Mesh.h"
 
 namespace GooseFEM {
 namespace Mesh {
 
 inline ElementType defaultElementType(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn)
 {
     if (coor.shape(1) == 2ul && conn.shape(1) == 3ul) {
         return ElementType::Tri3;
     }
     if (coor.shape(1) == 2ul && conn.shape(1) == 4ul) {
         return ElementType::Quad4;
     }
     if (coor.shape(1) == 3ul && conn.shape(1) == 8ul) {
         return ElementType::Hex8;
     }
 
     throw std::runtime_error("Element-type not implemented");
 }
 
 inline Renumber::Renumber(const xt::xarray<size_t>& dofs)
 {
     size_t n = xt::amax(dofs)() + 1;
     size_t i = 0;
 
     xt::xtensor<size_t, 1> unique = xt::unique(dofs);
 
     m_renum = xt::empty<size_t>({n});
 
     for (auto& j : unique) {
         m_renum(j) = i;
         ++i;
     }
 }
 
 // ret(i,j) = renum(list(i,j))
 template <class T>
 T Renumber::apply(const T& list) const
 {
     T ret = T::from_shape(list.shape());
 
     auto jt = ret.begin();
 
     for (auto it = list.begin(); it != list.end(); ++it, ++jt) {
         *jt = m_renum(*it);
     }
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 2> Renumber::get(const xt::xtensor<size_t, 2>& dofs) const
 {
     return this->apply(dofs);
 }
 
 inline xt::xtensor<size_t, 1> Renumber::index() const
 {
     return m_renum;
 }
 
 inline Reorder::Reorder(const std::initializer_list<xt::xtensor<size_t, 1>> args)
 {
     size_t n = 0;
     size_t i = 0;
 
     for (auto& arg : args) {
         if (arg.size() == 0) {
             continue;
         }
         n = std::max(n, xt::amax(arg)() + 1);
     }
 
     #ifdef GOOSEFEM_ENABLE_ASSERT
     for (auto& arg : args) {
         GOOSEFEM_ASSERT(xt::unique(arg) == xt::sort(arg));
     }
     #endif
 
     m_renum = xt::empty<size_t>({n});
 
     for (auto& arg : args) {
         for (auto& j : arg) {
             m_renum(j) = i;
             ++i;
         }
     }
 }
 
 inline xt::xtensor<size_t, 2> Reorder::get(const xt::xtensor<size_t, 2>& dofs) const
 {
     return this->apply(dofs);
 }
 
 inline xt::xtensor<size_t, 1> Reorder::index() const
 {
     return m_renum;
 }
 
 // apply renumbering, e.g. for a matrix:
 //
 //   ret(i,j) = renum(list(i,j))
 
 template <class T>
 T Reorder::apply(const T& list) const
 {
     T ret = T::from_shape(list.shape());
 
     auto jt = ret.begin();
 
     for (auto it = list.begin(); it != list.end(); ++it, ++jt) {
         *jt = m_renum(*it);
     }
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 2> renumber(const xt::xtensor<size_t, 2>& dofs)
 {
     return Renumber(dofs).get(dofs);
 }
 
 inline xt::xtensor<size_t, 2> dofs(size_t nnode, size_t ndim)
 {
     return xt::reshape_view(xt::arange<size_t>(nnode * ndim), {nnode, ndim});
 }
 
 inline xt::xtensor<size_t, 1> coordination(const xt::xtensor<size_t, 2>& conn)
 {
     size_t nnode = xt::amax(conn)() + 1;
 
     xt::xtensor<size_t, 1> N = xt::zeros<size_t>({nnode});
 
     for (auto it = conn.begin(); it != conn.end(); ++it) {
         N(*it) += 1;
     }
 
     return N;
 }
 
 inline std::vector<std::vector<size_t>> elem2node(const xt::xtensor<size_t, 2>& conn, bool sorted)
 {
     auto N = coordination(conn);
     auto nnode = N.size();
 
     std::vector<std::vector<size_t>> ret(nnode);
     for (size_t i = 0; i < nnode; ++i) {
         ret[i].reserve(N(i));
     }
 
     for (size_t e = 0; e < conn.shape(0); ++e) {
         for (size_t m = 0; m < conn.shape(1); ++m) {
             ret[conn(e, m)].push_back(e);
         }
     }
 
     if (sorted) {
         for (auto& row : ret) {
             std::sort(row.begin(), row.end());
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 2> edgesize(
     const xt::xtensor<double, 2>& coor, const xt::xtensor<size_t, 2>& conn, ElementType type)
 {
     GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0));
 
     if (type == ElementType::Quad4) {
         GOOSEFEM_ASSERT(coor.shape(1) == 2ul);
         GOOSEFEM_ASSERT(conn.shape(1) == 4ul);
         xt::xtensor<size_t, 1> n0 = xt::view(conn, xt::all(), 0);
         xt::xtensor<size_t, 1> n1 = xt::view(conn, xt::all(), 1);
         xt::xtensor<size_t, 1> n2 = xt::view(conn, xt::all(), 2);
         xt::xtensor<size_t, 1> n3 = xt::view(conn, xt::all(), 3);
         xt::xtensor<double, 1> x0 = xt::view(coor, xt::keep(n0), 0);
         xt::xtensor<double, 1> x1 = xt::view(coor, xt::keep(n1), 0);
         xt::xtensor<double, 1> x2 = xt::view(coor, xt::keep(n2), 0);
         xt::xtensor<double, 1> x3 = xt::view(coor, xt::keep(n3), 0);
         xt::xtensor<double, 1> y0 = xt::view(coor, xt::keep(n0), 1);
         xt::xtensor<double, 1> y1 = xt::view(coor, xt::keep(n1), 1);
         xt::xtensor<double, 1> y2 = xt::view(coor, xt::keep(n2), 1);
         xt::xtensor<double, 1> y3 = xt::view(coor, xt::keep(n3), 1);
         xt::xtensor<double, 2> ret = xt::empty<double>(conn.shape());
         xt::view(ret, xt::all(), 0) = xt::sqrt(xt::pow(x1 - x0, 2.0) + xt::pow(y1 - y0, 2.0));
         xt::view(ret, xt::all(), 1) = xt::sqrt(xt::pow(x2 - x1, 2.0) + xt::pow(y2 - y1, 2.0));
         xt::view(ret, xt::all(), 2) = xt::sqrt(xt::pow(x3 - x2, 2.0) + xt::pow(y3 - y2, 2.0));
         xt::view(ret, xt::all(), 3) = xt::sqrt(xt::pow(x0 - x3, 2.0) + xt::pow(y0 - y3, 2.0));
         return ret;
     }
 
     throw std::runtime_error("Element-type not implemented");
 }
 
 inline xt::xtensor<double, 2> edgesize(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn)
 {
     return edgesize(coor, conn, defaultElementType(coor, conn));
 }
 
 inline xt::xtensor<double, 2> centers(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn,
     ElementType type)
 {
     GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0));
     xt::xtensor<double, 2> ret = xt::zeros<double>({conn.shape(0), coor.shape(1)});
 
     if (type == ElementType::Quad4) {
         GOOSEFEM_ASSERT(coor.shape(1) == 2);
         GOOSEFEM_ASSERT(conn.shape(1) == 4);
         for (size_t i = 0; i < 4; ++i) {
             auto n = xt::view(conn, xt::all(), i);
             ret += xt::view(coor, xt::keep(n), xt::all());
         }
         ret /= 4.0;
         return ret;
     }
 
     throw std::runtime_error("Element-type not implemented");
 }
 
 inline xt::xtensor<double, 2> centers(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn)
 {
     return centers(coor, conn, defaultElementType(coor, conn));
 }
 
+inline xt::xtensor<size_t, 1> elemmap2nodemap(
+    const xt::xtensor<size_t, 1>& elem_map,
+    const xt::xtensor<double, 2>& coor,
+    const xt::xtensor<size_t, 2>& conn,
+    ElementType type)
+{
+    GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0));
+    GOOSEFEM_ASSERT(elem_map.size() == conn.shape(0));
+    size_t N = coor.shape(0);
+
+    xt::xtensor<size_t, 1> ret = N * xt::ones<size_t>({N});
+
+    if (type == ElementType::Quad4) {
+        GOOSEFEM_ASSERT(coor.shape(1) == 2);
+        GOOSEFEM_ASSERT(conn.shape(1) == 4);
+
+        for (size_t i = 0; i < 4; ++i) {
+            xt::xtensor<size_t, 1> t = N * xt::ones<size_t>({N});
+            auto old_nd = xt::view(conn, xt::all(), i);
+            auto new_nd = xt::view(conn, xt::keep(elem_map), i);
+            xt::view(t, xt::keep(old_nd)) = new_nd;
+            ret = xt::where(xt::equal(ret, N), t, ret);
+        }
+
+        return ret;
+    }
+
+    throw std::runtime_error("Element-type not implemented");
+}
+
+inline xt::xtensor<size_t, 1> elemmap2nodemap(
+    const xt::xtensor<size_t, 1>& elem_map,
+    const xt::xtensor<double, 2>& coor,
+    const xt::xtensor<size_t, 2>& conn)
+{
+    return elemmap2nodemap(elem_map, coor, conn, defaultElementType(coor, conn));
+}
 
 } // namespace Mesh
 } // namespace GooseFEM
 
 #endif
diff --git a/include/GooseFEM/MeshQuad4.h b/include/GooseFEM/MeshQuad4.h
index 1494415..52a614a 100644
--- a/include/GooseFEM/MeshQuad4.h
+++ b/include/GooseFEM/MeshQuad4.h
@@ -1,258 +1,258 @@
 /*
 
 (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM
 
 */
 
 #ifndef GOOSEFEM_MESHQUAD4_H
 #define GOOSEFEM_MESHQUAD4_H
 
 #include "config.h"
 
 namespace GooseFEM {
 namespace Mesh {
 namespace Quad4 {
 
 namespace Map {
     class FineLayer2Regular;
 } // namespace Map
 
 class Regular {
 public:
     Regular() = default;
     Regular(size_t nelx, size_t nely, double h = 1.0);
 
     // size
     size_t nelem() const; // number of elements
     size_t nnode() const; // number of nodes
     size_t nne() const;   // number of nodes-per-element
     size_t ndim() const;  // number of dimensions
     size_t nelx() const;  // number of elements in x-direction
     size_t nely() const;  // number of elements in y-direction
     double h() const;     // edge size
 
     // type
     ElementType getElementType() const;
 
     // mesh
     xt::xtensor<double, 2> coor() const; // nodal positions [nnode, ndim]
     xt::xtensor<size_t, 2> conn() const; // connectivity [nelem, nne]
 
     // boundary nodes: edges
     xt::xtensor<size_t, 1> nodesBottomEdge() const;
     xt::xtensor<size_t, 1> nodesTopEdge() const;
     xt::xtensor<size_t, 1> nodesLeftEdge() const;
     xt::xtensor<size_t, 1> nodesRightEdge() const;
 
     // boundary nodes: edges, without corners
     xt::xtensor<size_t, 1> nodesBottomOpenEdge() const;
     xt::xtensor<size_t, 1> nodesTopOpenEdge() const;
     xt::xtensor<size_t, 1> nodesLeftOpenEdge() const;
     xt::xtensor<size_t, 1> nodesRightOpenEdge() const;
 
     // boundary nodes: corners (including aliases)
     size_t nodesBottomLeftCorner() const;
     size_t nodesBottomRightCorner() const;
     size_t nodesTopLeftCorner() const;
     size_t nodesTopRightCorner() const;
     size_t nodesLeftBottomCorner() const;
     size_t nodesLeftTopCorner() const;
     size_t nodesRightBottomCorner() const;
     size_t nodesRightTopCorner() const;
 
     // DOF-numbers for each component of each node (sequential)
     xt::xtensor<size_t, 2> dofs() const;
 
     // DOF-numbers for the case that the periodicity if fully eliminated
     xt::xtensor<size_t, 2> dofsPeriodic() const;
 
     // periodic node pairs [:,2]: (independent, dependent)
     xt::xtensor<size_t, 2> nodesPeriodic() const;
 
     // front-bottom-left node, used as reference for periodicity
     size_t nodesOrigin() const;
 
     // element numbers as matrix
     xt::xtensor<size_t, 2> elementMatrix() const;
 
 private:
     double m_h;                     // elementary element edge-size (in all directions)
     size_t m_nelx;                  // number of elements in x-direction (length == "m_nelx * m_h")
     size_t m_nely;                  // number of elements in y-direction (length == "m_nely * m_h")
     size_t m_nelem;                 // number of elements
     size_t m_nnode;                 // number of nodes
     static const size_t m_nne = 4;  // number of nodes-per-element
     static const size_t m_ndim = 2; // number of dimensions
 };
 
 class FineLayer {
 public:
     FineLayer() = default;
     FineLayer(size_t nelx, size_t nely, double h = 1.0, size_t nfine = 1);
 
     // Reconstruct class for given coordinates / connectivity
     FineLayer(const xt::xtensor<double, 2>& coor, const xt::xtensor<size_t, 2>& conn);
 
     // size
     size_t nelem() const; // number of elements
     size_t nnode() const; // number of nodes
     size_t nne() const;   // number of nodes-per-element
     size_t ndim() const;  // number of dimensions
     size_t nelx() const;  // number of elements in x-direction
     size_t nely() const;  // number of elements in y-direction
     double h() const;     // edge size
 
     // type
     ElementType getElementType() const;
 
     // mesh
     xt::xtensor<double, 2> coor() const; // nodal positions [nnode, ndim]
     xt::xtensor<size_t, 2> conn() const; // connectivity [nelem, nne]
 
     // element sets
     xt::xtensor<size_t, 1> elementsMiddleLayer() const; // elements in the middle (fine) layer
 
     // boundary nodes: edges
     xt::xtensor<size_t, 1> nodesBottomEdge() const;
     xt::xtensor<size_t, 1> nodesTopEdge() const;
     xt::xtensor<size_t, 1> nodesLeftEdge() const;
     xt::xtensor<size_t, 1> nodesRightEdge() const;
 
     // boundary nodes: edges, without corners
     xt::xtensor<size_t, 1> nodesBottomOpenEdge() const;
     xt::xtensor<size_t, 1> nodesTopOpenEdge() const;
     xt::xtensor<size_t, 1> nodesLeftOpenEdge() const;
     xt::xtensor<size_t, 1> nodesRightOpenEdge() const;
 
     // boundary nodes: corners (including aliases)
     size_t nodesBottomLeftCorner() const;
     size_t nodesBottomRightCorner() const;
     size_t nodesTopLeftCorner() const;
     size_t nodesTopRightCorner() const;
     size_t nodesLeftBottomCorner() const;
     size_t nodesLeftTopCorner() const;
     size_t nodesRightBottomCorner() const;
     size_t nodesRightTopCorner() const;
 
     // DOF-numbers for each component of each node (sequential)
     xt::xtensor<size_t, 2> dofs() const;
 
     // DOF-numbers for the case that the periodicity if fully eliminated
     xt::xtensor<size_t, 2> dofsPeriodic() const;
 
     // periodic node pairs [:,2]: (independent, dependent)
     xt::xtensor<size_t, 2> nodesPeriodic() const;
 
     // front-bottom-left node, used as reference for periodicity
     size_t nodesOrigin() const;
 
-    // mapping to 'roll' periodically,
-    // return element mapping, to get the new connectivity use "comm[mesh.roll(), :]"
-    xt::xtensor<size_t, 1> roll(size_t n, size_t axis = 0);
+    // mapping to 'roll' periodically in the x-direction,
+    // returns element mapping, such that: new_elemvar = elemvar[elem_map]
+    xt::xtensor<size_t, 1> roll(size_t n);
 
 private:
     double m_h;                         // elementary element edge-size (in all directions)
     double m_Lx;                        // mesh size in "x"
     size_t m_nelem;                     // number of elements
     size_t m_nnode;                     // number of nodes
     static const size_t m_nne = 4;      // number of nodes-per-element
     static const size_t m_ndim = 2;     // number of dimensions
     xt::xtensor<size_t, 1> m_nelx;      // number of elements in "x" (*)
     xt::xtensor<size_t, 1> m_nnd;       // total number of nodes in the main node layer (**)
     xt::xtensor<size_t, 1> m_nhx;       // element size in x-direction (*)
     xt::xtensor<size_t, 1> m_nhy;       // element size in y-direction (*)
     xt::xtensor<int, 1> m_refine;       // refine direction (-1:no refine, 0:"x" (*)
     xt::xtensor<size_t, 1> m_startElem; // start element (*)
     xt::xtensor<size_t, 1> m_startNode; // start node (**)
     // (*) per element layer in "y"
     // (**) per node layer in "y"
 
     void init(size_t nelx, size_t nely, double h, size_t nfine = 1);
     void map(const xt::xtensor<double, 2>& coor, const xt::xtensor<size_t, 2>& conn);
 
     friend class GooseFEM::Mesh::Quad4::Map::FineLayer2Regular;
 };
 
 namespace Map {
 
 // Return "FineLayer"-class responsible for generating a connectivity
 // Throws if conversion is not possible
 GooseFEM::Mesh::Quad4::FineLayer FineLayer(
     const xt::xtensor<double, 2>& coor,
     const xt::xtensor<size_t, 2>& conn);
 
 class RefineRegular {
 public:
     // constructor
     RefineRegular() = default;
     RefineRegular(const GooseFEM::Mesh::Quad4::Regular& mesh, size_t nx, size_t ny);
 
     // return the one of the two meshes
     GooseFEM::Mesh::Quad4::Regular getCoarseMesh() const;
     GooseFEM::Mesh::Quad4::Regular getFineMesh() const;
 
     // elements of the Fine mesh per element of the Coarse mesh
     xt::xtensor<size_t, 2> getMap() const;
 
     // map field
     xt::xtensor<double, 2> mapToCoarse(const xt::xtensor<double, 1>& data) const; // scalar per el
     xt::xtensor<double, 2> mapToCoarse(const xt::xtensor<double, 2>& data) const; // scalar per intpnt
     xt::xtensor<double, 4> mapToCoarse(const xt::xtensor<double, 4>& data) const; // tensor per intpnt
 
     // map field
     xt::xtensor<double, 1> mapToFine(const xt::xtensor<double, 1>& data) const; // scalar per el
     xt::xtensor<double, 2> mapToFine(const xt::xtensor<double, 2>& data) const; // scalar per intpnt
     xt::xtensor<double, 4> mapToFine(const xt::xtensor<double, 4>& data) const; // tensor per intpnt
 
 private:
     // the meshes
     GooseFEM::Mesh::Quad4::Regular m_coarse;
     GooseFEM::Mesh::Quad4::Regular m_fine;
 
     // mapping
     xt::xtensor<size_t, 1> m_fine2coarse;
     xt::xtensor<size_t, 1> m_fine2coarse_index;
     xt::xtensor<size_t, 2> m_coarse2fine;
 };
 
 class FineLayer2Regular {
 public:
     // constructor
     FineLayer2Regular() = default;
     FineLayer2Regular(const GooseFEM::Mesh::Quad4::FineLayer& mesh);
 
     // return either of the meshes
     GooseFEM::Mesh::Quad4::Regular getRegularMesh() const;
     GooseFEM::Mesh::Quad4::FineLayer getFineLayerMesh() const;
 
     // elements of the Regular mesh per element of the FineLayer mesh
     // and the fraction by which the overlap is
     std::vector<std::vector<size_t>> getMap() const;
     std::vector<std::vector<double>> getMapFraction() const;
 
     // map field
     xt::xtensor<double, 1> mapToRegular(const xt::xtensor<double, 1>& data) const; // scalar per el
     xt::xtensor<double, 2> mapToRegular(const xt::xtensor<double, 2>& data) const; // scalar per intpnt
     xt::xtensor<double, 4> mapToRegular(const xt::xtensor<double, 4>& data) const; // tensor per intpnt
 
 private:
     // the "FineLayer" mesh to map
     GooseFEM::Mesh::Quad4::FineLayer m_finelayer;
 
     // the new "Regular" mesh to which to map
     GooseFEM::Mesh::Quad4::Regular m_regular;
 
     // mapping
     std::vector<std::vector<size_t>> m_elem_regular;
     std::vector<std::vector<double>> m_frac_regular;
 };
 
 } // namespace Map
 
 } // namespace Quad4
 } // namespace Mesh
 } // namespace GooseFEM
 
 #include "MeshQuad4.hpp"
 
 #endif
diff --git a/include/GooseFEM/MeshQuad4.hpp b/include/GooseFEM/MeshQuad4.hpp
index b6d151b..77557a5 100644
--- a/include/GooseFEM/MeshQuad4.hpp
+++ b/include/GooseFEM/MeshQuad4.hpp
@@ -1,1411 +1,1410 @@
 /*
 
 (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM
 
 */
 
 #ifndef GOOSEFEM_MESHQUAD4_HPP
 #define GOOSEFEM_MESHQUAD4_HPP
 
 #include "MeshQuad4.h"
 
 namespace GooseFEM {
 namespace Mesh {
 namespace Quad4 {
 
 inline Regular::Regular(size_t nelx, size_t nely, double h) : m_h(h), m_nelx(nelx), m_nely(nely)
 {
     GOOSEFEM_ASSERT(m_nelx >= 1ul);
     GOOSEFEM_ASSERT(m_nely >= 1ul);
 
     m_nnode = (m_nelx + 1) * (m_nely + 1);
     m_nelem = m_nelx * m_nely;
 }
 
 inline size_t Regular::nelem() const
 {
     return m_nelem;
 }
 
 inline size_t Regular::nnode() const
 {
     return m_nnode;
 }
 
 inline size_t Regular::nne() const
 {
     return m_nne;
 }
 
 inline size_t Regular::ndim() const
 {
     return m_ndim;
 }
 
 inline size_t Regular::nelx() const
 {
     return m_nelx;
 }
 
 inline size_t Regular::nely() const
 {
     return m_nely;
 }
 
 inline double Regular::h() const
 {
     return m_h;
 }
 
 inline ElementType Regular::getElementType() const
 {
     return ElementType::Quad4;
 }
 
 inline xt::xtensor<double, 2> Regular::coor() const
 {
     xt::xtensor<double, 2> ret = xt::empty<double>({m_nnode, m_ndim});
 
     xt::xtensor<double, 1> x =
         xt::linspace<double>(0.0, m_h * static_cast<double>(m_nelx), m_nelx + 1);
     xt::xtensor<double, 1> y =
         xt::linspace<double>(0.0, m_h * static_cast<double>(m_nely), m_nely + 1);
 
     size_t inode = 0;
 
     for (size_t iy = 0; iy < m_nely + 1; ++iy) {
         for (size_t ix = 0; ix < m_nelx + 1; ++ix) {
             ret(inode, 0) = x(ix);
             ret(inode, 1) = y(iy);
             ++inode;
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 2> Regular::conn() const
 {
     xt::xtensor<size_t, 2> ret = xt::empty<size_t>({m_nelem, m_nne});
 
     size_t ielem = 0;
 
     for (size_t iy = 0; iy < m_nely; ++iy) {
         for (size_t ix = 0; ix < m_nelx; ++ix) {
             ret(ielem, 0) = (iy) * (m_nelx + 1) + (ix);
             ret(ielem, 1) = (iy) * (m_nelx + 1) + (ix + 1);
             ret(ielem, 3) = (iy + 1) * (m_nelx + 1) + (ix);
             ret(ielem, 2) = (iy + 1) * (m_nelx + 1) + (ix + 1);
             ++ielem;
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesBottomEdge() const
 {
     return xt::arange<size_t>(m_nelx + 1);
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesTopEdge() const
 {
     return xt::arange<size_t>(m_nelx + 1) + m_nely * (m_nelx + 1);
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesLeftEdge() const
 {
     return xt::arange<size_t>(m_nely + 1) * (m_nelx + 1);
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesRightEdge() const
 {
     return xt::arange<size_t>(m_nely + 1) * (m_nelx + 1) + m_nelx;
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesBottomOpenEdge() const
 {
     return xt::arange<size_t>(1, m_nelx);
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesTopOpenEdge() const
 {
     return xt::arange<size_t>(1, m_nelx) + m_nely * (m_nelx + 1);
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesLeftOpenEdge() const
 {
     return xt::arange<size_t>(1, m_nely) * (m_nelx + 1);
 }
 
 inline xt::xtensor<size_t, 1> Regular::nodesRightOpenEdge() const
 {
     return xt::arange<size_t>(1, m_nely) * (m_nelx + 1) + m_nelx;
 }
 
 inline size_t Regular::nodesBottomLeftCorner() const
 {
     return 0;
 }
 
 inline size_t Regular::nodesBottomRightCorner() const
 {
     return m_nelx;
 }
 
 inline size_t Regular::nodesTopLeftCorner() const
 {
     return m_nely * (m_nelx + 1);
 }
 
 inline size_t Regular::nodesTopRightCorner() const
 {
     return m_nely * (m_nelx + 1) + m_nelx;
 }
 
 inline size_t Regular::nodesLeftBottomCorner() const
 {
     return nodesBottomLeftCorner();
 }
 
 inline size_t Regular::nodesLeftTopCorner() const
 {
     return nodesTopLeftCorner();
 }
 
 inline size_t Regular::nodesRightBottomCorner() const
 {
     return nodesBottomRightCorner();
 }
 
 inline size_t Regular::nodesRightTopCorner() const
 {
     return nodesTopRightCorner();
 }
 
 inline xt::xtensor<size_t, 2> Regular::nodesPeriodic() const
 {
     xt::xtensor<size_t, 1> bot = nodesBottomOpenEdge();
     xt::xtensor<size_t, 1> top = nodesTopOpenEdge();
     xt::xtensor<size_t, 1> lft = nodesLeftOpenEdge();
     xt::xtensor<size_t, 1> rgt = nodesRightOpenEdge();
     std::array<size_t, 2> shape = {bot.size() + lft.size() + 3ul, 2ul};
     xt::xtensor<size_t, 2> ret = xt::empty<size_t>(shape);
 
     ret(0, 0) = nodesBottomLeftCorner();
     ret(0, 1) = nodesBottomRightCorner();
 
     ret(1, 0) = nodesBottomLeftCorner();
     ret(1, 1) = nodesTopRightCorner();
 
     ret(2, 0) = nodesBottomLeftCorner();
     ret(2, 1) = nodesTopLeftCorner();
 
     size_t i = 3;
 
     xt::view(ret, xt::range(i, i + bot.size()), 0) = bot;
     xt::view(ret, xt::range(i, i + bot.size()), 1) = top;
 
     i += bot.size();
 
     xt::view(ret, xt::range(i, i + lft.size()), 0) = lft;
     xt::view(ret, xt::range(i, i + lft.size()), 1) = rgt;
 
     return ret;
 }
 
 inline size_t Regular::nodesOrigin() const
 {
     return nodesBottomLeftCorner();
 }
 
 inline xt::xtensor<size_t, 2> Regular::dofs() const
 {
     return GooseFEM::Mesh::dofs(m_nnode, m_ndim);
 }
 
 inline xt::xtensor<size_t, 2> Regular::dofsPeriodic() const
 {
     xt::xtensor<size_t, 2> ret = GooseFEM::Mesh::dofs(m_nnode, m_ndim);
     xt::xtensor<size_t, 2> nodePer = nodesPeriodic();
     xt::xtensor<size_t, 1> independent = xt::view(nodePer, xt::all(), 0);
     xt::xtensor<size_t, 1> dependent = xt::view(nodePer, xt::all(), 1);
 
     for (size_t j = 0; j < m_ndim; ++j) {
         xt::view(ret, xt::keep(dependent), j) = xt::view(ret, xt::keep(independent), j);
     }
 
     return GooseFEM::Mesh::renumber(ret);
 }
 
 inline xt::xtensor<size_t, 2> Regular::elementMatrix() const
 {
     return xt::arange<size_t>(m_nelem).reshape({m_nely, m_nelx});
 }
 
 inline FineLayer::FineLayer(size_t nelx, size_t nely, double h, size_t nfine)
 {
     this->init(nelx, nely, h, nfine);
 }
 
 inline FineLayer::FineLayer(const xt::xtensor<double, 2>& coor, const xt::xtensor<size_t, 2>& conn)
 {
     this->map(coor, conn);
 }
 
 inline void FineLayer::init(size_t nelx, size_t nely, double h, size_t nfine)
 {
     GOOSEFEM_ASSERT(nelx >= 1ul);
     GOOSEFEM_ASSERT(nely >= 1ul);
 
     m_h = h;
     m_Lx = m_h * static_cast<double>(nelx);
 
     // compute element size in y-direction (use symmetry, compute upper half)
 
     // temporary variables
     size_t nmin, ntot;
     xt::xtensor<size_t, 1> nhx = xt::ones<size_t>({nely});
     xt::xtensor<size_t, 1> nhy = xt::ones<size_t>({nely});
     xt::xtensor<int, 1> refine = -1 * xt::ones<int>({nely});
 
     // minimum height in y-direction (half of the height because of symmetry)
     if (nely % 2 == 0) {
         nmin = nely / 2;
     }
     else {
         nmin = (nely + 1) / 2;
     }
 
     // minimum number of fine layers in y-direction (minimum 1, middle layer part of this half)
     if (nfine % 2 == 0) {
         nfine = nfine / 2 + 1;
     }
     else {
         nfine = (nfine + 1) / 2;
     }
 
     if (nfine < 1) {
         nfine = 1;
     }
 
     if (nfine > nmin) {
         nfine = nmin;
     }
 
     // loop over element layers in y-direction, try to coarsen using these rules:
     // (1) element size in y-direction <= distance to origin in y-direction
     // (2) element size in x-direction should fit the total number of elements in x-direction
     // (3) a certain number of layers have the minimum size "1" (are fine)
     for (size_t iy = nfine;;) {
         // initialize current size in y-direction
         if (iy == nfine) {
             ntot = nfine;
         }
         // check to stop
         if (iy >= nely || ntot >= nmin) {
             nely = iy;
             break;
         }
 
         // rules (1,2) satisfied: coarsen in x-direction
         if (3 * nhy(iy) <= ntot && nelx % (3 * nhx(iy)) == 0 && ntot + nhy(iy) < nmin) {
             refine(iy) = 0;
             nhy(iy) *= 2;
             auto vnhy = xt::view(nhy, xt::range(iy + 1, _));
             auto vnhx = xt::view(nhx, xt::range(iy, _));
             vnhy *= 3;
             vnhx *= 3;
         }
 
         // update the number of elements in y-direction
         ntot += nhy(iy);
         // proceed to next element layer in y-direction
         ++iy;
         // check to stop
         if (iy >= nely || ntot >= nmin) {
             nely = iy;
             break;
         }
     }
 
     // symmetrize, compute full information
 
     // allocate mesh constructor parameters
     m_nhx = xt::empty<size_t>({nely * 2 - 1});
     m_nhy = xt::empty<size_t>({nely * 2 - 1});
     m_refine = xt::empty<int>({nely * 2 - 1});
     m_nelx = xt::empty<size_t>({nely * 2 - 1});
     m_nnd = xt::empty<size_t>({nely * 2});
     m_startElem = xt::empty<size_t>({nely * 2 - 1});
     m_startNode = xt::empty<size_t>({nely * 2});
 
     // fill
     // - lower half
     for (size_t iy = 0; iy < nely; ++iy) {
         m_nhx(iy) = nhx(nely - iy - 1);
         m_nhy(iy) = nhy(nely - iy - 1);
         m_refine(iy) = refine(nely - iy - 1);
     }
     // - upper half
     for (size_t iy = 0; iy < nely - 1; ++iy) {
         m_nhx(iy + nely) = nhx(iy + 1);
         m_nhy(iy + nely) = nhy(iy + 1);
         m_refine(iy + nely) = refine(iy + 1);
     }
 
     // update size
     nely = m_nhx.size();
 
     // compute the number of elements per element layer in y-direction
     for (size_t iy = 0; iy < nely; ++iy) {
         m_nelx(iy) = nelx / m_nhx(iy);
     }
 
     // compute the number of nodes per node layer in y-direction
     for (size_t iy = 0; iy < (nely + 1) / 2; ++iy) {
         m_nnd(iy) = m_nelx(iy) + 1;
     }
     for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) {
         m_nnd(iy + 1) = m_nelx(iy) + 1;
     }
 
     // compute mesh dimensions
 
     // initialize
     m_nnode = 0;
     m_nelem = 0;
     m_startNode(0) = 0;
 
     // loop over element layers (bottom -> middle, elements become finer)
     for (size_t i = 0; i < (nely - 1) / 2; ++i) {
         // - store the first element of the layer
         m_startElem(i) = m_nelem;
         // - add the nodes of this layer
         if (m_refine(i) == 0) {
             m_nnode += (3 * m_nelx(i) + 1);
         }
         else {
             m_nnode += (m_nelx(i) + 1);
         }
         // - add the elements of this layer
         if (m_refine(i) == 0) {
             m_nelem += (4 * m_nelx(i));
         }
         else {
             m_nelem += (m_nelx(i));
         }
         // - store the starting node of the next layer
         m_startNode(i + 1) = m_nnode;
     }
 
     // loop over element layers (middle -> top, elements become coarser)
     for (size_t i = (nely - 1) / 2; i < nely; ++i) {
         // - store the first element of the layer
         m_startElem(i) = m_nelem;
         // - add the nodes of this layer
         if (m_refine(i) == 0) {
             m_nnode += (5 * m_nelx(i) + 1);
         }
         else {
             m_nnode += (m_nelx(i) + 1);
         }
         // - add the elements of this layer
         if (m_refine(i) == 0) {
             m_nelem += (4 * m_nelx(i));
         }
         else {
             m_nelem += (m_nelx(i));
         }
         // - store the starting node of the next layer
         m_startNode(i + 1) = m_nnode;
     }
     // - add the top row of nodes
     m_nnode += m_nelx(nely - 1) + 1;
 }
 
 inline size_t FineLayer::nelem() const
 {
     return m_nelem;
 }
 
 inline size_t FineLayer::nnode() const
 {
     return m_nnode;
 }
 
 inline size_t FineLayer::nne() const
 {
     return m_nne;
 }
 
 inline size_t FineLayer::ndim() const
 {
     return m_ndim;
 }
 
 inline size_t FineLayer::nelx() const
 {
     return xt::amax(m_nelx)();
 }
 
 inline size_t FineLayer::nely() const
 {
     return xt::sum(m_nhy)();
 }
 
 inline double FineLayer::h() const
 {
     return m_h;
 }
 
 inline ElementType FineLayer::getElementType() const
 {
     return ElementType::Quad4;
 }
 
 inline xt::xtensor<double, 2> FineLayer::coor() const
 {
     // allocate output
     xt::xtensor<double, 2> ret = xt::empty<double>({m_nnode, m_ndim});
 
     // current node, number of element layers
     size_t inode = 0;
     size_t nely = static_cast<size_t>(m_nhy.size());
 
     // y-position of each main node layer (i.e. excluding node layers for refinement/coarsening)
     // - allocate
     xt::xtensor<double, 1> y = xt::empty<double>({nely + 1});
     // - initialize
     y(0) = 0.0;
     // - compute
     for (size_t iy = 1; iy < nely + 1; ++iy) {
         y(iy) = y(iy - 1) + m_nhy(iy - 1) * m_h;
     }
 
     // loop over element layers (bottom -> middle) : add bottom layer (+ refinement layer) of nodes
 
     for (size_t iy = 0;; ++iy) {
         // get positions along the x- and z-axis
         xt::xtensor<double, 1> x = xt::linspace<double>(0.0, m_Lx, m_nelx(iy) + 1);
 
         // add nodes of the bottom layer of this element
         for (size_t ix = 0; ix < m_nelx(iy) + 1; ++ix) {
             ret(inode, 0) = x(ix);
             ret(inode, 1) = y(iy);
             ++inode;
         }
 
         // stop at middle layer
         if (iy == (nely - 1) / 2) {
             break;
         }
 
         // add extra nodes of the intermediate layer, for refinement in x-direction
         if (m_refine(iy) == 0) {
             // - get position offset in x- and y-direction
             double dx = m_h * static_cast<double>(m_nhx(iy) / 3);
             double dy = m_h * static_cast<double>(m_nhy(iy) / 2);
             // - add nodes of the intermediate layer
             for (size_t ix = 0; ix < m_nelx(iy); ++ix) {
                 for (size_t j = 0; j < 2; ++j) {
                     ret(inode, 0) = x(ix) + dx * static_cast<double>(j + 1);
                     ret(inode, 1) = y(iy) + dy;
                     ++inode;
                 }
             }
         }
     }
 
     // loop over element layers (middle -> top) : add (refinement layer +) top layer of nodes
 
     for (size_t iy = (nely - 1) / 2; iy < nely; ++iy) {
         // get positions along the x- and z-axis
         xt::xtensor<double, 1> x = xt::linspace<double>(0.0, m_Lx, m_nelx(iy) + 1);
 
         // add extra nodes of the intermediate layer, for refinement in x-direction
         if (m_refine(iy) == 0) {
             // - get position offset in x- and y-direction
             double dx = m_h * static_cast<double>(m_nhx(iy) / 3);
             double dy = m_h * static_cast<double>(m_nhy(iy) / 2);
             // - add nodes of the intermediate layer
             for (size_t ix = 0; ix < m_nelx(iy); ++ix) {
                 for (size_t j = 0; j < 2; ++j) {
                     ret(inode, 0) = x(ix) + dx * static_cast<double>(j + 1);
                     ret(inode, 1) = y(iy) + dy;
                     ++inode;
                 }
             }
         }
 
         // add nodes of the top layer of this element
         for (size_t ix = 0; ix < m_nelx(iy) + 1; ++ix) {
             ret(inode, 0) = x(ix);
             ret(inode, 1) = y(iy + 1);
             ++inode;
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 2> FineLayer::conn() const
 {
     // allocate output
     xt::xtensor<size_t, 2> ret = xt::empty<size_t>({m_nelem, m_nne});
 
     // current element, number of element layers, starting nodes of each node layer
     size_t ielem = 0;
     size_t nely = static_cast<size_t>(m_nhy.size());
     size_t bot, mid, top;
 
     // loop over all element layers
     for (size_t iy = 0; iy < nely; ++iy) {
         // - get: starting nodes of bottom(, middle) and top layer
         bot = m_startNode(iy);
         mid = m_startNode(iy) + m_nnd(iy);
         top = m_startNode(iy + 1);
 
         // - define connectivity: no coarsening/refinement
         if (m_refine(iy) == -1) {
             for (size_t ix = 0; ix < m_nelx(iy); ++ix) {
                 ret(ielem, 0) = bot + (ix);
                 ret(ielem, 1) = bot + (ix + 1);
                 ret(ielem, 2) = top + (ix + 1);
                 ret(ielem, 3) = top + (ix);
                 ielem++;
             }
         }
 
         // - define connectivity: refinement along the x-direction (below the middle layer)
         else if (m_refine(iy) == 0 && iy <= (nely - 1) / 2) {
             for (size_t ix = 0; ix < m_nelx(iy); ++ix) {
                 // -- bottom element
                 ret(ielem, 0) = bot + (ix);
                 ret(ielem, 1) = bot + (ix + 1);
                 ret(ielem, 2) = mid + (2 * ix + 1);
                 ret(ielem, 3) = mid + (2 * ix);
                 ielem++;
                 // -- top-right element
                 ret(ielem, 0) = bot + (ix + 1);
                 ret(ielem, 1) = top + (3 * ix + 3);
                 ret(ielem, 2) = top + (3 * ix + 2);
                 ret(ielem, 3) = mid + (2 * ix + 1);
                 ielem++;
                 // -- top-center element
                 ret(ielem, 0) = mid + (2 * ix);
                 ret(ielem, 1) = mid + (2 * ix + 1);
                 ret(ielem, 2) = top + (3 * ix + 2);
                 ret(ielem, 3) = top + (3 * ix + 1);
                 ielem++;
                 // -- top-left element
                 ret(ielem, 0) = bot + (ix);
                 ret(ielem, 1) = mid + (2 * ix);
                 ret(ielem, 2) = top + (3 * ix + 1);
                 ret(ielem, 3) = top + (3 * ix);
                 ielem++;
             }
         }
 
         // - define connectivity: coarsening along the x-direction (above the middle layer)
         else if (m_refine(iy) == 0 && iy > (nely - 1) / 2) {
             for (size_t ix = 0; ix < m_nelx(iy); ++ix) {
                 // -- lower-left element
                 ret(ielem, 0) = bot + (3 * ix);
                 ret(ielem, 1) = bot + (3 * ix + 1);
                 ret(ielem, 2) = mid + (2 * ix);
                 ret(ielem, 3) = top + (ix);
                 ielem++;
                 // -- lower-center element
                 ret(ielem, 0) = bot + (3 * ix + 1);
                 ret(ielem, 1) = bot + (3 * ix + 2);
                 ret(ielem, 2) = mid + (2 * ix + 1);
                 ret(ielem, 3) = mid + (2 * ix);
                 ielem++;
                 // -- lower-right element
                 ret(ielem, 0) = bot + (3 * ix + 2);
                 ret(ielem, 1) = bot + (3 * ix + 3);
                 ret(ielem, 2) = top + (ix + 1);
                 ret(ielem, 3) = mid + (2 * ix + 1);
                 ielem++;
                 // -- upper element
                 ret(ielem, 0) = mid + (2 * ix);
                 ret(ielem, 1) = mid + (2 * ix + 1);
                 ret(ielem, 2) = top + (ix + 1);
                 ret(ielem, 3) = top + (ix);
                 ielem++;
             }
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::elementsMiddleLayer() const
 {
     size_t nely = m_nhy.size();
     size_t iy = (nely - 1) / 2;
     return m_startElem(iy) + xt::arange<size_t>(m_nelx(iy));
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesBottomEdge() const
 {
     return m_startNode(0) + xt::arange<size_t>(m_nelx(0) + 1);
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesTopEdge() const
 {
     size_t nely = m_nhy.size();
     return m_startNode(nely) + xt::arange<size_t>(m_nelx(nely - 1) + 1);
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesLeftEdge() const
 {
     size_t nely = m_nhy.size();
 
     xt::xtensor<size_t, 1> ret = xt::empty<size_t>({nely + 1});
 
     size_t i = 0;
     size_t j = (nely + 1) / 2;
     size_t k = (nely - 1) / 2;
     size_t l = nely;
 
     xt::view(ret, xt::range(i, j)) = xt::view(m_startNode, xt::range(i, j));
     xt::view(ret, xt::range(k + 1, l + 1)) = xt::view(m_startNode, xt::range(k + 1, l + 1));
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesRightEdge() const
 {
     size_t nely = m_nhy.size();
 
     xt::xtensor<size_t, 1> ret = xt::empty<size_t>({nely + 1});
 
     size_t i = 0;
     size_t j = (nely + 1) / 2;
     size_t k = (nely - 1) / 2;
     size_t l = nely;
 
     xt::view(ret, xt::range(i, j)) =
         xt::view(m_startNode, xt::range(i, j)) + xt::view(m_nelx, xt::range(i, j));
 
     xt::view(ret, xt::range(k + 1, l + 1)) =
         xt::view(m_startNode, xt::range(k + 1, l + 1)) + xt::view(m_nelx, xt::range(k, l));
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesBottomOpenEdge() const
 {
     return m_startNode(0) + xt::arange<size_t>(1, m_nelx(0));
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesTopOpenEdge() const
 {
     size_t nely = m_nhy.size();
 
     return m_startNode(nely) + xt::arange<size_t>(1, m_nelx(nely - 1));
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesLeftOpenEdge() const
 {
     size_t nely = m_nhy.size();
 
     xt::xtensor<size_t, 1> ret = xt::empty<size_t>({nely - 1});
 
     size_t i = 0;
     size_t j = (nely + 1) / 2;
     size_t k = (nely - 1) / 2;
     size_t l = nely;
 
     xt::view(ret, xt::range(i, j - 1)) = xt::view(m_startNode, xt::range(i + 1, j));
     xt::view(ret, xt::range(k, l - 1)) = xt::view(m_startNode, xt::range(k + 1, l));
 
     return ret;
 }
 
 inline xt::xtensor<size_t, 1> FineLayer::nodesRightOpenEdge() const
 {
     size_t nely = m_nhy.size();
 
     xt::xtensor<size_t, 1> ret = xt::empty<size_t>({nely - 1});
 
     size_t i = 0;
     size_t j = (nely + 1) / 2;
     size_t k = (nely - 1) / 2;
     size_t l = nely;
 
     xt::view(ret, xt::range(i, j - 1)) =
         xt::view(m_startNode, xt::range(i + 1, j)) + xt::view(m_nelx, xt::range(i + 1, j));
 
     xt::view(ret, xt::range(k, l - 1)) =
         xt::view(m_startNode, xt::range(k + 1, l)) + xt::view(m_nelx, xt::range(k, l - 1));
 
     return ret;
 }
 
 inline size_t FineLayer::nodesBottomLeftCorner() const
 {
     return m_startNode(0);
 }
 
 inline size_t FineLayer::nodesBottomRightCorner() const
 {
     return m_startNode(0) + m_nelx(0);
 }
 
 inline size_t FineLayer::nodesTopLeftCorner() const
 {
     size_t nely = m_nhy.size();
 
     return m_startNode(nely);
 }
 
 inline size_t FineLayer::nodesTopRightCorner() const
 {
     size_t nely = m_nhy.size();
 
     return m_startNode(nely) + m_nelx(nely - 1);
 }
 
 inline size_t FineLayer::nodesLeftBottomCorner() const
 {
     return nodesBottomLeftCorner();
 }
 
 inline size_t FineLayer::nodesRightBottomCorner() const
 {
     return nodesBottomRightCorner();
 }
 
 inline size_t FineLayer::nodesLeftTopCorner() const
 {
     return nodesTopLeftCorner();
 }
 
 inline size_t FineLayer::nodesRightTopCorner() const
 {
     return nodesTopRightCorner();
 }
 
 inline xt::xtensor<size_t, 2> FineLayer::nodesPeriodic() const
 {
     xt::xtensor<size_t, 1> bot = nodesBottomOpenEdge();
     xt::xtensor<size_t, 1> top = nodesTopOpenEdge();
     xt::xtensor<size_t, 1> lft = nodesLeftOpenEdge();
     xt::xtensor<size_t, 1> rgt = nodesRightOpenEdge();
     std::array<size_t, 2> shape = {bot.size() + lft.size() + 3ul, 2ul};
     xt::xtensor<size_t, 2> ret = xt::empty<size_t>(shape);
 
     ret(0, 0) = nodesBottomLeftCorner();
     ret(0, 1) = nodesBottomRightCorner();
 
     ret(1, 0) = nodesBottomLeftCorner();
     ret(1, 1) = nodesTopRightCorner();
 
     ret(2, 0) = nodesBottomLeftCorner();
     ret(2, 1) = nodesTopLeftCorner();
 
     size_t i = 3;
 
     xt::view(ret, xt::range(i, i + bot.size()), 0) = bot;
     xt::view(ret, xt::range(i, i + bot.size()), 1) = top;
 
     i += bot.size();
 
     xt::view(ret, xt::range(i, i + lft.size()), 0) = lft;
     xt::view(ret, xt::range(i, i + lft.size()), 1) = rgt;
 
     return ret;
 }
 
 inline size_t FineLayer::nodesOrigin() const
 {
     return nodesBottomLeftCorner();
 }
 
 inline xt::xtensor<size_t, 2> FineLayer::dofs() const
 {
     return GooseFEM::Mesh::dofs(m_nnode, m_ndim);
 }
 
 inline xt::xtensor<size_t, 2> FineLayer::dofsPeriodic() const
 {
     xt::xtensor<size_t, 2> ret = GooseFEM::Mesh::dofs(m_nnode, m_ndim);
     xt::xtensor<size_t, 2> nodePer = nodesPeriodic();
     xt::xtensor<size_t, 1> independent = xt::view(nodePer, xt::all(), 0);
     xt::xtensor<size_t, 1> dependent = xt::view(nodePer, xt::all(), 1);
 
     for (size_t j = 0; j < m_ndim; ++j) {
         xt::view(ret, xt::keep(dependent), j) = xt::view(ret, xt::keep(independent), j);
     }
 
     return GooseFEM::Mesh::renumber(ret);
 }
 
-inline xt::xtensor<size_t, 1> FineLayer::roll(size_t n, size_t axis)
+inline xt::xtensor<size_t, 1> FineLayer::roll(size_t n)
 {
-    GOOSEFEM_ASSERT(axis == 0);
     auto conn = this->conn();
     size_t nely = static_cast<size_t>(m_nhy.size());
     xt::xtensor<size_t, 1> ret = xt::empty<size_t>({m_nelem});
 
     // loop over all element layers
     for (size_t iy = 0; iy < nely; ++iy) {
 
         // no refinement
         size_t shift = n * (m_nelx(iy) / m_nelx(0));
         size_t nel = m_nelx(iy);
 
         // refinement
         if (m_refine(iy) != -1) {
             shift = n * (m_nelx(iy) / m_nelx(0)) * 4;
             nel = m_nelx(iy) * 4;
         }
 
         // element numbers of the layer, and roll them
         auto e = m_startElem(iy) + xt::arange<size_t>(nel);
         xt::view(ret, xt::range(m_startElem(iy), m_startElem(iy) + nel)) = xt::roll(e, shift);
     }
 
     return ret;
 }
 
 inline void FineLayer::map(const xt::xtensor<double, 2>& coor, const xt::xtensor<size_t, 2>& conn)
 {
     GOOSEFEM_ASSERT(coor.shape(1) == 2);
     GOOSEFEM_ASSERT(conn.shape(1) == 4);
     GOOSEFEM_ASSERT(conn.shape(0) > 0);
     GOOSEFEM_ASSERT(coor.shape(0) >= 4);
     GOOSEFEM_ASSERT(xt::amax(conn)() < coor.shape(0));
 
     auto n0 = xt::view(conn, xt::all(), 0);
     auto n1 = xt::view(conn, xt::all(), 1);
     auto n2 = xt::view(conn, xt::all(), 2);
     auto dx = xt::view(coor, xt::keep(n1), 0) - xt::view(coor, xt::keep(n0), 0);
     auto dy = xt::view(coor, xt::keep(n2), 1) - xt::view(coor, xt::keep(n1), 1);
     auto hx = xt::amin(xt::where(dx > 0.0, dx, std::numeric_limits<double>::max()))();
     auto hy = xt::amin(xt::where(dy > 0.0, dx, std::numeric_limits<double>::max()))();
 
     GOOSEFEM_CHECK(xt::allclose(hx, hy));
 
     if (conn.shape(0) == 1) {
         this->init(1, 1, hx);
         GOOSEFEM_CHECK(xt::all(xt::equal(this->conn(), conn)));
         GOOSEFEM_CHECK(xt::allclose(this->coor(), coor));
         return;
     }
 
     size_t emid = (conn.shape(0) - conn.shape(0) % 2) / 2;
     size_t eleft = emid;
     size_t eright = emid;
 
     while (conn(eleft, 0) == conn(eleft - 1, 1) && eleft > 0) {
         eleft--;
     }
 
     while (conn(eright, 1) == conn(eright + 1, 0) && eright < conn.shape(0) - 1) {
         eright++;
     }
 
     GOOSEFEM_CHECK(xt::allclose(coor(conn(eleft, 0), 0), 0.0));
     size_t nelx = eright - eleft + 1;
     size_t nely = static_cast<size_t>((coor(coor.shape(0) - 1, 1) - coor(0, 1)) / hx);
 
     this->init(nelx, nely, hx);
     GOOSEFEM_CHECK(xt::all(xt::equal(this->conn(), conn)));
     GOOSEFEM_CHECK(xt::allclose(this->coor(), coor));
     GOOSEFEM_CHECK(xt::all(xt::equal(this->elementsMiddleLayer(), eleft + xt::arange<size_t>(nelx))));
 }
 
 namespace Map {
 
 inline RefineRegular::RefineRegular(
     const GooseFEM::Mesh::Quad4::Regular& mesh, size_t nx, size_t ny)
     : m_coarse(mesh)
 {
     m_fine = Regular(nx * m_coarse.nelx(), ny * m_coarse.nely(), m_coarse.h());
 
     xt::xtensor<size_t, 2> elmat_coarse = m_coarse.elementMatrix();
     xt::xtensor<size_t, 2> elmat_fine = m_fine.elementMatrix();
 
     m_coarse2fine = xt::empty<size_t>({m_coarse.nelem(), nx * ny});
 
     for (size_t i = 0; i < elmat_coarse.shape(0); ++i) {
         for (size_t j = 0; j < elmat_coarse.shape(1); ++j) {
             xt::view(m_coarse2fine, elmat_coarse(i, j), xt::all()) = xt::flatten(xt::view(
                 elmat_fine, xt::range(i * ny, (i + 1) * ny), xt::range(j * nx, (j + 1) * nx)));
         }
     }
 }
 
 inline GooseFEM::Mesh::Quad4::Regular RefineRegular::getCoarseMesh() const
 {
     return m_coarse;
 }
 
 inline GooseFEM::Mesh::Quad4::Regular RefineRegular::getFineMesh() const
 {
     return m_fine;
 }
 
 inline xt::xtensor<size_t, 2> RefineRegular::getMap() const
 {
     return m_coarse2fine;
 }
 
 inline xt::xtensor<double, 2> RefineRegular::mapToCoarse(const xt::xtensor<double, 1>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.size());
 
     size_t m = m_coarse2fine.shape(0);
     size_t n = m_coarse2fine.shape(1);
 
     xt::xtensor<double, 2> ret = xt::empty<double>({m, n});
 
     for (size_t i = 0; i < m; ++i) {
         auto e = xt::view(m_coarse2fine, i, xt::all());
         xt::view(ret, i) = xt::view(data, xt::keep(e));
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 2> RefineRegular::mapToCoarse(const xt::xtensor<double, 2>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.size());
 
     size_t m = m_coarse2fine.shape(0);
     size_t n = m_coarse2fine.shape(1);
     size_t N = data.shape(1);
 
     xt::xtensor<double, 2> ret = xt::empty<double>({m, n * N});
 
     for (size_t i = 0; i < m; ++i) {
         auto e = xt::view(m_coarse2fine, i, xt::all());
         for (size_t q = 0; q < data.shape(1); ++q) {
             xt::view(ret, i, xt::range(q + 0, q + n * N, N)) = xt::view(data, xt::keep(e), q);
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 4> RefineRegular::mapToCoarse(const xt::xtensor<double, 4>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.size());
 
     size_t m = m_coarse2fine.shape(0);
     size_t n = m_coarse2fine.shape(1);
     size_t N = data.shape(1);
 
     xt::xtensor<double, 4> ret = xt::empty<double>({m, n * N, data.shape(2), data.shape(3)});
 
     for (size_t i = 0; i < m; ++i) {
         auto e = xt::view(m_coarse2fine, i, xt::all());
         for (size_t q = 0; q < data.shape(1); ++q) {
             xt::view(ret, i, xt::range(q + 0, q + n * N, N)) = xt::view(data, xt::keep(e), q);
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 1> RefineRegular::mapToFine(const xt::xtensor<double, 1>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.shape(0));
 
     xt::xtensor<double, 1> ret = xt::empty<double>({m_coarse2fine.size()});
 
     for (size_t i = 0; i < m_coarse2fine.shape(0); ++i) {
         auto e = xt::view(m_coarse2fine, i, xt::all());
         xt::view(ret, xt::keep(e)) = data(i);
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 2> RefineRegular::mapToFine(const xt::xtensor<double, 2>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.shape(0));
 
     xt::xtensor<double, 2> ret = xt::empty<double>({m_coarse2fine.size(), data.shape(1)});
 
     for (size_t i = 0; i < m_coarse2fine.shape(0); ++i) {
         auto e = xt::view(m_coarse2fine, i, xt::all());
         xt::view(ret, xt::keep(e)) = xt::view(data, i);
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 4> RefineRegular::mapToFine(const xt::xtensor<double, 4>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_coarse2fine.shape(0));
 
     xt::xtensor<double, 4> ret =
         xt::empty<double>({m_coarse2fine.size(), data.shape(1), data.shape(2), data.shape(3)});
 
     for (size_t i = 0; i < m_coarse2fine.shape(0); ++i) {
         auto e = xt::view(m_coarse2fine, i, xt::all());
         xt::view(ret, xt::keep(e)) = xt::view(data, i);
     }
 
     return ret;
 }
 
 inline FineLayer2Regular::FineLayer2Regular(const GooseFEM::Mesh::Quad4::FineLayer& mesh)
     : m_finelayer(mesh)
 {
     // ------------
     // Regular-mesh
     // ------------
 
     m_regular = GooseFEM::Mesh::Quad4::Regular(
         xt::amax(m_finelayer.m_nelx)(), xt::sum(m_finelayer.m_nhy)(), m_finelayer.m_h);
 
     // -------
     // mapping
     // -------
 
     // allocate mapping
     m_elem_regular.resize(m_finelayer.m_nelem);
     m_frac_regular.resize(m_finelayer.m_nelem);
 
     // alias
     xt::xtensor<size_t, 1> nhx = m_finelayer.m_nhx;
     xt::xtensor<size_t, 1> nhy = m_finelayer.m_nhy;
     xt::xtensor<size_t, 1> nelx = m_finelayer.m_nelx;
     xt::xtensor<size_t, 1> start = m_finelayer.m_startElem;
 
     // 'matrix' of element numbers of the Regular-mesh
     xt::xtensor<size_t, 2> elementMatrix = m_regular.elementMatrix();
 
     // cumulative number of element-rows of the Regular-mesh per layer of the FineLayer-mesh
     xt::xtensor<size_t, 1> cum_nhy =
         xt::concatenate(xt::xtuple(xt::zeros<size_t>({1}), xt::cumsum(nhy)));
 
     // number of element layers in y-direction of the FineLayer-mesh
     size_t nely = nhy.size();
 
     // loop over layers of the FineLayer-mesh
     for (size_t iy = 0; iy < nely; ++iy) {
         // element numbers of the Regular-mesh along this layer of the FineLayer-mesh
         auto el_new = xt::view(elementMatrix, xt::range(cum_nhy(iy), cum_nhy(iy + 1)), xt::all());
 
         // no coarsening/refinement
         // ------------------------
 
         if (m_finelayer.m_refine(iy) == -1) {
             // element numbers of the FineLayer-mesh along this layer
             xt::xtensor<size_t, 1> el_old = start(iy) + xt::arange<size_t>(nelx(iy));
 
             // loop along this layer of the FineLayer-mesh
             for (size_t ix = 0; ix < nelx(iy); ++ix) {
                 // get the element numbers of the Regular-mesh for this element of the
                 // FineLayer-mesh
                 auto block =
                     xt::view(el_new, xt::all(), xt::range(ix * nhx(iy), (ix + 1) * nhx(iy)));
 
                 // write to mapping
                 for (auto& i : block) {
                     m_elem_regular[el_old(ix)].push_back(i);
                     m_frac_regular[el_old(ix)].push_back(1.0);
                 }
             }
         }
 
         // refinement along the x-direction (below the middle layer)
         // ---------------------------------------------------------
 
         else if (m_finelayer.m_refine(iy) == 0 && iy <= (nely - 1) / 2) {
             // element numbers of the FineLayer-mesh along this layer
             // rows: coarse block, columns element numbers per block
             xt::xtensor<size_t, 2> el_old =
                 start(iy) + xt::arange<size_t>(nelx(iy) * 4ul).reshape({-1, 4});
 
             // loop along this layer of the FineLayer-mesh
             for (size_t ix = 0; ix < nelx(iy); ++ix) {
                 // get the element numbers of the Regular-mesh for this block of the FineLayer-mesh
                 auto block =
                     xt::view(el_new, xt::all(), xt::range(ix * nhx(iy), (ix + 1) * nhx(iy)));
 
                 // bottom: wide-to-narrow
                 {
                     for (size_t j = 0; j < nhy(iy) / 2; ++j) {
                         auto e = xt::view(block, j, xt::range(j, nhx(iy) - j));
 
                         m_elem_regular[el_old(ix, 0)].push_back(e(0));
                         m_frac_regular[el_old(ix, 0)].push_back(0.5);
 
                         for (size_t k = 1; k < e.size() - 1; ++k) {
                             m_elem_regular[el_old(ix, 0)].push_back(e(k));
                             m_frac_regular[el_old(ix, 0)].push_back(1.0);
                         }
 
                         m_elem_regular[el_old(ix, 0)].push_back(e(e.size() - 1));
                         m_frac_regular[el_old(ix, 0)].push_back(0.5);
                     }
                 }
 
                 // top: regular small element
                 {
                     auto e = xt::view(
                         block,
                         xt::range(nhy(iy) / 2, nhy(iy)),
                         xt::range(1 * nhx(iy) / 3, 2 * nhx(iy) / 3));
 
                     for (auto& i : e) {
                         m_elem_regular[el_old(ix, 2)].push_back(i);
                         m_frac_regular[el_old(ix, 2)].push_back(1.0);
                     }
                 }
 
                 // left
                 {
                     // left-bottom: narrow-to-wide
                     for (size_t j = 0; j < nhy(iy) / 2; ++j) {
                         auto e = xt::view(block, j, xt::range(0, j + 1));
 
                         for (size_t k = 0; k < e.size() - 1; ++k) {
                             m_elem_regular[el_old(ix, 3)].push_back(e(k));
                             m_frac_regular[el_old(ix, 3)].push_back(1.0);
                         }
 
                         m_elem_regular[el_old(ix, 3)].push_back(e(e.size() - 1));
                         m_frac_regular[el_old(ix, 3)].push_back(0.5);
                     }
 
                     // left-top: regular
                     {
                         auto e = xt::view(
                             block,
                             xt::range(nhy(iy) / 2, nhy(iy)),
                             xt::range(0 * nhx(iy) / 3, 1 * nhx(iy) / 3));
 
                         for (auto& i : e) {
                             m_elem_regular[el_old(ix, 3)].push_back(i);
                             m_frac_regular[el_old(ix, 3)].push_back(1.0);
                         }
                     }
                 }
 
                 // right
                 {
                     // right-bottom: narrow-to-wide
                     for (size_t j = 0; j < nhy(iy) / 2; ++j) {
                         auto e = xt::view(block, j, xt::range(nhx(iy) - j - 1, nhx(iy)));
 
                         m_elem_regular[el_old(ix, 1)].push_back(e(0));
                         m_frac_regular[el_old(ix, 1)].push_back(0.5);
 
                         for (size_t k = 1; k < e.size(); ++k) {
                             m_elem_regular[el_old(ix, 1)].push_back(e(k));
                             m_frac_regular[el_old(ix, 1)].push_back(1.0);
                         }
                     }
 
                     // right-top: regular
                     {
                         auto e = xt::view(
                             block,
                             xt::range(nhy(iy) / 2, nhy(iy)),
                             xt::range(2 * nhx(iy) / 3, 3 * nhx(iy) / 3));
 
                         for (auto& i : e) {
                             m_elem_regular[el_old(ix, 1)].push_back(i);
                             m_frac_regular[el_old(ix, 1)].push_back(1.0);
                         }
                     }
                 }
             }
         }
 
         // coarsening along the x-direction (above the middle layer)
         else if (m_finelayer.m_refine(iy) == 0 && iy > (nely - 1) / 2) {
             // element numbers of the FineLayer-mesh along this layer
             // rows: coarse block, columns element numbers per block
             xt::xtensor<size_t, 2> el_old =
                 start(iy) + xt::arange<size_t>(nelx(iy) * 4ul).reshape({-1, 4});
 
             // loop along this layer of the FineLayer-mesh
             for (size_t ix = 0; ix < nelx(iy); ++ix) {
                 // get the element numbers of the Regular-mesh for this block of the FineLayer-mesh
                 auto block =
                     xt::view(el_new, xt::all(), xt::range(ix * nhx(iy), (ix + 1) * nhx(iy)));
 
                 // top: narrow-to-wide
                 {
                     for (size_t j = 0; j < nhy(iy) / 2; ++j) {
                         auto e = xt::view(
                             block,
                             nhy(iy) / 2 + j,
                             xt::range(1 * nhx(iy) / 3 - j - 1, 2 * nhx(iy) / 3 + j + 1));
 
                         m_elem_regular[el_old(ix, 3)].push_back(e(0));
                         m_frac_regular[el_old(ix, 3)].push_back(0.5);
 
                         for (size_t k = 1; k < e.size() - 1; ++k) {
                             m_elem_regular[el_old(ix, 3)].push_back(e(k));
                             m_frac_regular[el_old(ix, 3)].push_back(1.0);
                         }
 
                         m_elem_regular[el_old(ix, 3)].push_back(e(e.size() - 1));
                         m_frac_regular[el_old(ix, 3)].push_back(0.5);
                     }
                 }
 
                 // bottom: regular small element
                 {
                     auto e = xt::view(
                         block,
                         xt::range(0, nhy(iy) / 2),
                         xt::range(1 * nhx(iy) / 3, 2 * nhx(iy) / 3));
 
                     for (auto& i : e) {
                         m_elem_regular[el_old(ix, 1)].push_back(i);
                         m_frac_regular[el_old(ix, 1)].push_back(1.0);
                     }
                 }
 
                 // left
                 {
                     // left-bottom: regular
                     {
                         auto e = xt::view(
                             block,
                             xt::range(0, nhy(iy) / 2),
                             xt::range(0 * nhx(iy) / 3, 1 * nhx(iy) / 3));
 
                         for (auto& i : e) {
                             m_elem_regular[el_old(ix, 0)].push_back(i);
                             m_frac_regular[el_old(ix, 0)].push_back(1.0);
                         }
                     }
 
                     // left-top: narrow-to-wide
                     for (size_t j = 0; j < nhy(iy) / 2; ++j) {
                         auto e =
                             xt::view(block, nhy(iy) / 2 + j, xt::range(0, 1 * nhx(iy) / 3 - j));
 
                         for (size_t k = 0; k < e.size() - 1; ++k) {
                             m_elem_regular[el_old(ix, 0)].push_back(e(k));
                             m_frac_regular[el_old(ix, 0)].push_back(1.0);
                         }
 
                         m_elem_regular[el_old(ix, 0)].push_back(e(e.size() - 1));
                         m_frac_regular[el_old(ix, 0)].push_back(0.5);
                     }
                 }
 
                 // right
                 {
                     // right-bottom: regular
                     {
                         auto e = xt::view(
                             block,
                             xt::range(0, nhy(iy) / 2),
                             xt::range(2 * nhx(iy) / 3, 3 * nhx(iy) / 3));
 
                         for (auto& i : e) {
                             m_elem_regular[el_old(ix, 2)].push_back(i);
                             m_frac_regular[el_old(ix, 2)].push_back(1.0);
                         }
                     }
 
                     // right-top: narrow-to-wide
                     for (size_t j = 0; j < nhy(iy) / 2; ++j) {
                         auto e = xt::view(
                             block, nhy(iy) / 2 + j, xt::range(2 * nhx(iy) / 3 + j, nhx(iy)));
 
                         m_elem_regular[el_old(ix, 2)].push_back(e(0));
                         m_frac_regular[el_old(ix, 2)].push_back(0.5);
 
                         for (size_t k = 1; k < e.size(); ++k) {
                             m_elem_regular[el_old(ix, 2)].push_back(e(k));
                             m_frac_regular[el_old(ix, 2)].push_back(1.0);
                         }
                     }
                 }
             }
         }
     }
 }
 
 inline GooseFEM::Mesh::Quad4::Regular FineLayer2Regular::getRegularMesh() const
 {
     return m_regular;
 }
 
 inline GooseFEM::Mesh::Quad4::FineLayer FineLayer2Regular::getFineLayerMesh() const
 {
     return m_finelayer;
 }
 
 inline std::vector<std::vector<size_t>> FineLayer2Regular::getMap() const
 {
     return m_elem_regular;
 }
 
 inline std::vector<std::vector<double>> FineLayer2Regular::getMapFraction() const
 {
     return m_frac_regular;
 }
 
 inline xt::xtensor<double, 1>
 FineLayer2Regular::mapToRegular(const xt::xtensor<double, 1>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_finelayer.nelem());
 
     xt::xtensor<double, 1> ret = xt::zeros<double>({m_regular.nelem()});
 
     for (size_t e = 0; e < m_finelayer.nelem(); ++e) {
         for (size_t i = 0; i < m_elem_regular[e].size(); ++i) {
             ret(m_elem_regular[e][i]) += m_frac_regular[e][i] * data(e);
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 2>
 FineLayer2Regular::mapToRegular(const xt::xtensor<double, 2>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_finelayer.nelem());
 
     xt::xtensor<double, 2> ret = xt::zeros<double>({m_regular.nelem(), data.shape(1)});
 
     for (size_t e = 0; e < m_finelayer.nelem(); ++e) {
         for (size_t i = 0; i < m_elem_regular[e].size(); ++i) {
             xt::view(ret, m_elem_regular[e][i]) += m_frac_regular[e][i] * xt::view(data, e);
         }
     }
 
     return ret;
 }
 
 inline xt::xtensor<double, 4>
 FineLayer2Regular::mapToRegular(const xt::xtensor<double, 4>& data) const
 {
     GOOSEFEM_ASSERT(data.shape(0) == m_finelayer.nelem());
 
     xt::xtensor<double, 4> ret =
         xt::zeros<double>({m_regular.nelem(), data.shape(1), data.shape(2), data.shape(3)});
 
     for (size_t e = 0; e < m_finelayer.nelem(); ++e) {
         for (size_t i = 0; i < m_elem_regular[e].size(); ++i) {
             xt::view(ret, m_elem_regular[e][i]) += m_frac_regular[e][i] * xt::view(data, e);
         }
     }
 
     return ret;
 }
 
 } // namespace Map
 
 } // namespace Quad4
 } // namespace Mesh
 } // namespace GooseFEM
 
 #endif
diff --git a/python/Mesh.hpp b/python/Mesh.hpp
index 5fed2d7..93f69d0 100644
--- a/python/Mesh.hpp
+++ b/python/Mesh.hpp
@@ -1,114 +1,157 @@
 /* =================================================================================================
 
 (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM
 
 ================================================================================================= */
 
 #include <GooseFEM/GooseFEM.h>
 #include <pybind11/pybind11.h>
 #include <pyxtensor/pyxtensor.hpp>
 
 namespace py = pybind11;
 
 void init_Mesh(py::module& m)
 {
 
     py::enum_<GooseFEM::Mesh::ElementType>(m, "ElementType", "ElementType")
         .value("Tri3", GooseFEM::Mesh::ElementType::Tri3)
         .value("Quad4", GooseFEM::Mesh::ElementType::Quad4)
         .value("Hex8", GooseFEM::Mesh::ElementType::Hex8)
         .export_values();
 
     py::class_<GooseFEM::Mesh::Renumber>(m, "Renumber")
 
         .def(
             py::init<const xt::xarray<size_t>&>(),
             "Class to renumber to the lowest possible indices. Use ``Renumber(...).get()`` to get "
             "the renumbered result",
             py::arg("dofs"))
 
         .def("get", &GooseFEM::Mesh::Renumber::get, "Get result of renumbering")
 
         .def(
             "index",
             &GooseFEM::Mesh::Renumber::index,
             "Get index list to apply renumbering. Apply renumbering using ``index[dofs]``")
 
         .def(
             "__repr__", [](const GooseFEM::Mesh::Renumber&) { return "<GooseFEM.Mesh.Renumber>"; });
 
     py::class_<GooseFEM::Mesh::Reorder>(m, "Reorder")
 
         .def(py::init([](xt::xtensor<size_t, 1>& a) { return new GooseFEM::Mesh::Reorder({a}); }))
 
         .def(py::init([](xt::xtensor<size_t, 1>& a, xt::xtensor<size_t, 1>& b) {
             return new GooseFEM::Mesh::Reorder({a, b});
         }))
 
         .def(py::init(
             [](xt::xtensor<size_t, 1>& a, xt::xtensor<size_t, 1>& b, xt::xtensor<size_t, 1>& c) {
                 return new GooseFEM::Mesh::Reorder({a, b, c});
             }))
 
         .def(py::init([](xt::xtensor<size_t, 1>& a,
                          xt::xtensor<size_t, 1>& b,
                          xt::xtensor<size_t, 1>& c,
                          xt::xtensor<size_t, 1>& d) {
             return new GooseFEM::Mesh::Reorder({a, b, c, d});
         }))
 
         .def("get", &GooseFEM::Mesh::Reorder::get, "Reorder matrix (e.g. ``dofs``)")
 
         .def(
             "index",
             &GooseFEM::Mesh::Reorder::index,
             "Get index list to apply renumbering. Apply renumbering using ``index[dofs]``")
 
         .def("__repr__", [](const GooseFEM::Mesh::Reorder&) { return "<GooseFEM.Mesh.Reorder>"; });
 
     m.def(
         "dofs",
         &GooseFEM::Mesh::dofs,
         "List with DOF-numbers (in sequential order)",
         py::arg("nnode"),
         py::arg("ndim"));
 
     m.def(
         "renumber",
         &GooseFEM::Mesh::renumber,
         "Renumber to lowest possible indices. Use ``GooseFEM.Mesh.Renumber`` for advanced "
         "functionality",
         py::arg("dofs"));
 
     m.def(
         "coordination",
         &GooseFEM::Mesh::coordination,
         "Coordination number of each node (number of elements connected to each node)",
         py::arg("conn"));
 
     m.def(
         "elem2node",
         &GooseFEM::Mesh::elem2node,
         "Element-numbers connected to each node",
         py::arg("conn"),
         py::arg("sorted") = true);
 
     m.def(
         "edgesize",
         py::overload_cast<const xt::xtensor<double, 2>&, const xt::xtensor<size_t, 2>&>(
             &GooseFEM::Mesh::edgesize),
         "Get the edge size of all elements",
         py::arg("coor"),
         py::arg("conn"));
 
     m.def(
         "edgesize",
         py::overload_cast<
             const xt::xtensor<double, 2>&,
             const xt::xtensor<size_t, 2>&,
             GooseFEM::Mesh::ElementType>(&GooseFEM::Mesh::edgesize),
         "Get the edge size of all elements",
         py::arg("coor"),
         py::arg("conn"),
         py::arg("type"));
+
+    m.def(
+        "centers",
+        py::overload_cast<const xt::xtensor<double, 2>&, const xt::xtensor<size_t, 2>&>(
+            &GooseFEM::Mesh::centers),
+        "Coordinates of the center of each element",
+        py::arg("coor"),
+        py::arg("conn"));
+
+    m.def(
+        "centers",
+        py::overload_cast<
+            const xt::xtensor<double, 2>&,
+            const xt::xtensor<size_t, 2>&,
+            GooseFEM::Mesh::ElementType>(&GooseFEM::Mesh::centers),
+        "Coordinates of the center of each element",
+        py::arg("coor"),
+        py::arg("conn"),
+        py::arg("type"));
+
+    m.def(
+        "elemmap2nodemap",
+        py::overload_cast<
+            const xt::xtensor<size_t, 1>&,
+            const xt::xtensor<double, 2>&,
+            const xt::xtensor<size_t, 2>&>(&GooseFEM::Mesh::elemmap2nodemap),
+        "Convert an element-map to a node-map",
+        py::arg("elem_map"),
+        py::arg("coor"),
+        py::arg("conn"));
+
+    m.def(
+        "elemmap2nodemap",
+        py::overload_cast<
+            const xt::xtensor<size_t, 1>&,
+            const xt::xtensor<double, 2>&,
+            const xt::xtensor<size_t, 2>&,
+            GooseFEM::Mesh::ElementType>(&GooseFEM::Mesh::elemmap2nodemap),
+        "Convert an element-map to a node-map",
+        py::arg("elem_map"),
+        py::arg("coor"),
+        py::arg("conn"),
+        py::arg("type"));
 }
diff --git a/python/MeshQuad4.hpp b/python/MeshQuad4.hpp
index 8cdb385..26a0985 100644
--- a/python/MeshQuad4.hpp
+++ b/python/MeshQuad4.hpp
@@ -1,261 +1,263 @@
 /* =================================================================================================
 
 (c - GPLv3) T.W.J. de Geus (Tom) | tom@geus.me | www.geus.me | github.com/tdegeus/GooseFEM
 
 ================================================================================================= */
 
 #include <GooseFEM/GooseFEM.h>
 #include <pybind11/pybind11.h>
 #include <pyxtensor/pyxtensor.hpp>
 
 namespace py = pybind11;
 
 void init_MeshQuad4(py::module& m)
 {
 
     py::class_<GooseFEM::Mesh::Quad4::Regular>(m, "Regular")
 
         .def(
             py::init<size_t, size_t, double>(),
             "Regular mesh: 'nx' pixels in horizontal direction, 'ny' in vertical direction, edge "
             "size 'h'",
             py::arg("nx"),
             py::arg("ny"),
             py::arg("h") = 1.)
 
         .def("coor", &GooseFEM::Mesh::Quad4::Regular::coor)
 
         .def("conn", &GooseFEM::Mesh::Quad4::Regular::conn)
 
         .def("nelem", &GooseFEM::Mesh::Quad4::Regular::nelem)
 
         .def("nnode", &GooseFEM::Mesh::Quad4::Regular::nnode)
 
         .def("nne", &GooseFEM::Mesh::Quad4::Regular::nne)
 
         .def("ndim", &GooseFEM::Mesh::Quad4::Regular::ndim)
 
         .def("nelx", &GooseFEM::Mesh::Quad4::Regular::nelx)
 
         .def("nely", &GooseFEM::Mesh::Quad4::Regular::nely)
 
         .def("h", &GooseFEM::Mesh::Quad4::Regular::h)
 
         .def("getElementType", &GooseFEM::Mesh::Quad4::Regular::getElementType)
 
         .def("nodesBottomEdge", &GooseFEM::Mesh::Quad4::Regular::nodesBottomEdge)
 
         .def("nodesTopEdge", &GooseFEM::Mesh::Quad4::Regular::nodesTopEdge)
 
         .def("nodesLeftEdge", &GooseFEM::Mesh::Quad4::Regular::nodesLeftEdge)
 
         .def("nodesRightEdge", &GooseFEM::Mesh::Quad4::Regular::nodesRightEdge)
 
         .def("nodesBottomOpenEdge", &GooseFEM::Mesh::Quad4::Regular::nodesBottomOpenEdge)
 
         .def("nodesTopOpenEdge", &GooseFEM::Mesh::Quad4::Regular::nodesTopOpenEdge)
 
         .def("nodesLeftOpenEdge", &GooseFEM::Mesh::Quad4::Regular::nodesLeftOpenEdge)
 
         .def("nodesRightOpenEdge", &GooseFEM::Mesh::Quad4::Regular::nodesRightOpenEdge)
 
         .def("nodesBottomLeftCorner", &GooseFEM::Mesh::Quad4::Regular::nodesBottomLeftCorner)
 
         .def("nodesBottomRightCorner", &GooseFEM::Mesh::Quad4::Regular::nodesBottomRightCorner)
 
         .def("nodesTopLeftCorner", &GooseFEM::Mesh::Quad4::Regular::nodesTopLeftCorner)
 
         .def("nodesTopRightCorner", &GooseFEM::Mesh::Quad4::Regular::nodesTopRightCorner)
 
         .def("nodesLeftBottomCorner", &GooseFEM::Mesh::Quad4::Regular::nodesLeftBottomCorner)
 
         .def("nodesLeftTopCorner", &GooseFEM::Mesh::Quad4::Regular::nodesLeftTopCorner)
 
         .def("nodesRightBottomCorner", &GooseFEM::Mesh::Quad4::Regular::nodesRightBottomCorner)
 
         .def("nodesRightTopCorner", &GooseFEM::Mesh::Quad4::Regular::nodesRightTopCorner)
 
         .def("dofs", &GooseFEM::Mesh::Quad4::Regular::dofs)
 
         .def("nodesPeriodic", &GooseFEM::Mesh::Quad4::Regular::nodesPeriodic)
 
         .def("nodesOrigin", &GooseFEM::Mesh::Quad4::Regular::nodesOrigin)
 
         .def("dofsPeriodic", &GooseFEM::Mesh::Quad4::Regular::dofsPeriodic)
 
         .def("elementMatrix", &GooseFEM::Mesh::Quad4::Regular::elementMatrix)
 
         .def("__repr__", [](const GooseFEM::Mesh::Quad4::Regular&) {
             return "<GooseFEM.Mesh.Quad4.Regular>";
         });
 
     py::class_<GooseFEM::Mesh::Quad4::FineLayer>(m, "FineLayer")
 
         .def(
             py::init<size_t, size_t, double, size_t>(),
             "FineLayer mesh: 'nx' pixels in horizontal direction (length 'Lx'), idem in vertical "
             "direction",
             py::arg("nx"),
             py::arg("ny"),
             py::arg("h") = 1.,
             py::arg("nfine") = 1)
 
         .def(
             py::init<const xt::xtensor<double, 2>&, const xt::xtensor<size_t, 2>&>(),
             "Map connectivity to generating FineLayer-object.",
             py::arg("coor"),
             py::arg("conn"))
 
         .def("coor", &GooseFEM::Mesh::Quad4::FineLayer::coor)
 
         .def("conn", &GooseFEM::Mesh::Quad4::FineLayer::conn)
 
         .def("nelem", &GooseFEM::Mesh::Quad4::FineLayer::nelem)
 
         .def("nnode", &GooseFEM::Mesh::Quad4::FineLayer::nnode)
 
         .def("nne", &GooseFEM::Mesh::Quad4::FineLayer::nne)
 
         .def("ndim", &GooseFEM::Mesh::Quad4::FineLayer::ndim)
 
         .def("nelx", &GooseFEM::Mesh::Quad4::FineLayer::nelx)
 
         .def("nely", &GooseFEM::Mesh::Quad4::FineLayer::nely)
 
         .def("h", &GooseFEM::Mesh::Quad4::FineLayer::h)
 
         .def("getElementType", &GooseFEM::Mesh::Quad4::FineLayer::getElementType)
 
         .def("elementsMiddleLayer", &GooseFEM::Mesh::Quad4::FineLayer::elementsMiddleLayer)
 
         .def("nodesBottomEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesBottomEdge)
 
         .def("nodesTopEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesTopEdge)
 
         .def("nodesLeftEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesLeftEdge)
 
         .def("nodesRightEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesRightEdge)
 
         .def("nodesBottomOpenEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesBottomOpenEdge)
 
         .def("nodesTopOpenEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesTopOpenEdge)
 
         .def("nodesLeftOpenEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesLeftOpenEdge)
 
         .def("nodesRightOpenEdge", &GooseFEM::Mesh::Quad4::FineLayer::nodesRightOpenEdge)
 
         .def("nodesBottomLeftCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesBottomLeftCorner)
 
         .def("nodesBottomRightCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesBottomRightCorner)
 
         .def("nodesTopLeftCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesTopLeftCorner)
 
         .def("nodesTopRightCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesTopRightCorner)
 
         .def("nodesLeftBottomCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesLeftBottomCorner)
 
         .def("nodesLeftTopCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesLeftTopCorner)
 
         .def("nodesRightBottomCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesRightBottomCorner)
 
         .def("nodesRightTopCorner", &GooseFEM::Mesh::Quad4::FineLayer::nodesRightTopCorner)
 
         .def("dofs", &GooseFEM::Mesh::Quad4::FineLayer::dofs)
 
         .def("nodesPeriodic", &GooseFEM::Mesh::Quad4::FineLayer::nodesPeriodic)
 
         .def("nodesOrigin", &GooseFEM::Mesh::Quad4::FineLayer::nodesOrigin)
 
         .def("dofsPeriodic", &GooseFEM::Mesh::Quad4::FineLayer::dofsPeriodic)
 
+        .def("roll", &GooseFEM::Mesh::Quad4::FineLayer::roll)
+
         .def("__repr__", [](const GooseFEM::Mesh::Quad4::FineLayer&) {
             return "<GooseFEM.Mesh.Quad4.FineLayer>";
         });
 }
 
 void init_MeshQuad4Map(py::module& m)
 {
 
     py::class_<GooseFEM::Mesh::Quad4::Map::RefineRegular>(m, "RefineRegular")
 
         .def(
             py::init<const GooseFEM::Mesh::Quad4::Regular&, size_t, size_t>(),
             "Refine a regular mesh",
             py::arg("mesh"),
             py::arg("nx"),
             py::arg("ny"))
 
         .def("getCoarseMesh", &GooseFEM::Mesh::Quad4::Map::RefineRegular::getCoarseMesh)
 
         .def("getFineMesh", &GooseFEM::Mesh::Quad4::Map::RefineRegular::getFineMesh)
 
         .def("getMap", &GooseFEM::Mesh::Quad4::Map::RefineRegular::getMap)
 
         .def(
             "mapToCoarse",
             py::overload_cast<const xt::xtensor<double, 1>&>(
                 &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToCoarse, py::const_))
 
         .def(
             "mapToCoarse",
             py::overload_cast<const xt::xtensor<double, 2>&>(
                 &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToCoarse, py::const_))
 
         .def(
             "mapToCoarse",
             py::overload_cast<const xt::xtensor<double, 4>&>(
                 &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToCoarse, py::const_))
 
         .def(
             "mapToFine",
             py::overload_cast<const xt::xtensor<double, 1>&>(
                 &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_))
 
         .def(
             "mapToFine",
             py::overload_cast<const xt::xtensor<double, 2>&>(
                 &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_))
 
         .def(
             "mapToFine",
             py::overload_cast<const xt::xtensor<double, 4>&>(
                 &GooseFEM::Mesh::Quad4::Map::RefineRegular::mapToFine, py::const_))
 
         .def("__repr__", [](const GooseFEM::Mesh::Quad4::Map::RefineRegular&) {
             return "<GooseFEM.Mesh.Quad4.Map.RefineRegular>";
         });
 
     py::class_<GooseFEM::Mesh::Quad4::Map::FineLayer2Regular>(m, "FineLayer2Regular")
 
         .def(
             py::init<const GooseFEM::Mesh::Quad4::FineLayer&>(),
             "Map a FineLayer mesh to a Regular mesh",
             py::arg("mesh"))
 
         .def("getRegularMesh", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::getRegularMesh)
 
         .def("getFineLayerMesh", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::getFineLayerMesh)
 
         .def("getMap", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::getMap)
 
         .def("getMapFraction", &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::getMapFraction)
 
         .def(
             "mapToRegular",
             py::overload_cast<const xt::xtensor<double, 1>&>(
                 &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_))
 
         .def(
             "mapToRegular",
             py::overload_cast<const xt::xtensor<double, 2>&>(
                 &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_))
 
         .def(
             "mapToRegular",
             py::overload_cast<const xt::xtensor<double, 4>&>(
                 &GooseFEM::Mesh::Quad4::Map::FineLayer2Regular::mapToRegular, py::const_))
 
         .def("__repr__", [](const GooseFEM::Mesh::Quad4::Map::FineLayer2Regular&) {
             return "<GooseFEM.Mesh.Quad4.Map.FineLayer2Regular>";
         });
 }
diff --git a/test/basic/Mesh.cpp b/test/basic/Mesh.cpp
index b6f126b..b51fe14 100644
--- a/test/basic/Mesh.cpp
+++ b/test/basic/Mesh.cpp
@@ -1,62 +1,278 @@
 
 #include <catch2/catch.hpp>
 #include <xtensor/xrandom.hpp>
 #include <xtensor/xmath.hpp>
 #include <GooseFEM/GooseFEM.h>
 
 #define ISCLOSE(a,b) REQUIRE_THAT((a), Catch::WithinAbs((b), 1.e-12));
 
 TEST_CASE("GooseFEM::Mesh", "Mesh.h")
 {
     SECTION("edgesize")
     {
         GooseFEM::Mesh::Quad4::Regular mesh(2, 2, 10.0);
         auto s = GooseFEM::Mesh::edgesize(mesh.coor(), mesh.conn());
         auto t = GooseFEM::Mesh::edgesize(mesh.coor(), mesh.conn(), mesh.getElementType());
         REQUIRE(xt::allclose(s, 10.0));
         REQUIRE(xt::allclose(t, 10.0));
     }
 
     SECTION("coordination")
     {
         GooseFEM::Mesh::Quad4::Regular mesh(3, 3);
         auto N = GooseFEM::Mesh::coordination(mesh.conn());
         xt::xtensor<size_t, 1> ret = {1, 2, 2, 1, 2, 4, 4, 2, 2, 4, 4, 2, 1, 2, 2, 1};
         REQUIRE(xt::all(xt::equal(N, ret)));
     }
 
     SECTION("centers")
     {
         GooseFEM::Mesh::Quad4::Regular mesh(2, 2, 2.0);
         xt::xtensor<double, 2> c = {
             {1.0, 1.0},
             {3.0, 1.0},
             {1.0, 3.0},
             {3.0, 3.0}};
 
         REQUIRE(xt::allclose(GooseFEM::Mesh::centers(mesh.coor(), mesh.conn()), c));
     }
 
     SECTION("elem2node")
     {
         GooseFEM::Mesh::Quad4::Regular mesh(3, 3);
         auto tonode = GooseFEM::Mesh::elem2node(mesh.conn());
         REQUIRE(tonode.size() == 16);
         REQUIRE(tonode[0] == std::vector<size_t>{0});
         REQUIRE(tonode[1] == std::vector<size_t>{0, 1});
         REQUIRE(tonode[2] == std::vector<size_t>{1, 2});
         REQUIRE(tonode[3] == std::vector<size_t>{2});
         REQUIRE(tonode[4] == std::vector<size_t>{0, 3});
         REQUIRE(tonode[5] == std::vector<size_t>{0, 1, 3, 4});
         REQUIRE(tonode[6] == std::vector<size_t>{1, 2, 4, 5});
         REQUIRE(tonode[7] == std::vector<size_t>{2, 5});
         REQUIRE(tonode[8] == std::vector<size_t>{3, 6});
         REQUIRE(tonode[9] == std::vector<size_t>{3, 4, 6, 7});
         REQUIRE(tonode[10] == std::vector<size_t>{4, 5, 7, 8});
         REQUIRE(tonode[11] == std::vector<size_t>{5, 8});
         REQUIRE(tonode[12] == std::vector<size_t>{6});
         REQUIRE(tonode[13] == std::vector<size_t>{6, 7});
         REQUIRE(tonode[14] == std::vector<size_t>{7, 8});
         REQUIRE(tonode[15] == std::vector<size_t>{8});
     }
+
+    SECTION("elemmap2nodemap")
+    {
+        GooseFEM::Mesh::Quad4::Regular mesh(3, 3);
+
+        xt::xtensor<size_t, 1> elmap0 = {
+            0, 1, 2,
+            3, 4, 5,
+            6, 7, 8
+        };
+        xt::xtensor<size_t, 1> elmap1 = {
+            2, 0, 1,
+            5, 3, 4,
+            8, 6, 7
+        };
+        xt::xtensor<size_t, 1> elmap2 = {
+            1, 2, 0,
+            4, 5, 3,
+            7, 8, 6
+        };
+
+        xt::xtensor<size_t, 1> nodemap0 = {
+             0,  1,  2,  3,
+             4,  5,  6,  7,
+             8,  9, 10, 11,
+            12, 13, 14, 15
+        };
+        xt::xtensor<size_t, 1> nodemap1 = {
+              2,  0,  1,  2,
+              6,  4,  5,  6,
+             10,  8,  9, 10,
+             14, 15, 13, 14
+        };
+        xt::xtensor<size_t, 1> nodemap2 = {
+             1,  2,  0,  1,
+             5,  6,  4,  5,
+             9, 10,  8,  9,
+            13, 14, 15, 13
+        };
+
+        REQUIRE(xt::all(xt::equal(GooseFEM::Mesh::elemmap2nodemap(elmap0, mesh.coor(), mesh.conn()), nodemap0)));
+        REQUIRE(xt::all(xt::equal(GooseFEM::Mesh::elemmap2nodemap(elmap1, mesh.coor(), mesh.conn()), nodemap1)));
+        REQUIRE(xt::all(xt::equal(GooseFEM::Mesh::elemmap2nodemap(elmap2, mesh.coor(), mesh.conn()), nodemap2)));
+    }
+
+    SECTION("elemmap2nodemap - example 1")
+    {
+        GooseFEM::Mesh::Quad4::FineLayer mesh(3, 3);
+
+        xt::xtensor<int, 1> elemval = {
+            1, 0, 0,
+            1, 0, 0,
+            1, 0, 0
+        };
+
+        xt::xtensor<int, 1> elemval_r1 = {
+            0, 1, 0,
+            0, 1, 0,
+            0, 1, 0
+        };
+
+        xt::xtensor<int, 1> elemval_r2 = {
+            0, 0, 1,
+            0, 0, 1,
+            0, 0, 1
+        };
+
+        xt::xtensor<int, 1> nodeval = {
+            1, 0, 0, 1,
+            1, 0, 0, 1,
+            1, 0, 0, 1,
+            1, 0, 0, 1
+        };
+
+        xt::xtensor<int, 1> nodeval_r1 = {
+            0, 1, 0, 0,
+            0, 1, 0, 0,
+            0, 1, 0, 0,
+            0, 1, 0, 0
+        };
+
+        xt::xtensor<int, 1> nodeval_r2 = {
+            0, 0, 1, 0,
+            0, 0, 1, 0,
+            0, 0, 1, 0,
+            0, 0, 1, 0
+        };
+
+        {
+            auto elemmap = mesh.roll(0);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(1);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r1, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r1, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(2);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r2, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r2, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(3);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(4);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r1, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r1, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(5);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r2, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r2, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+    }
+
+    SECTION("elemmap2nodemap - example 2")
+    {
+        GooseFEM::Mesh::Quad4::FineLayer mesh(3, 3);
+
+        xt::xtensor<int, 1> elemval = {
+            1, 0, 0,
+            0, 1, 0,
+            0, 0, 1
+        };
+
+        xt::xtensor<int, 1> elemval_r1 = {
+            0, 1, 0,
+            0, 0, 1,
+            1, 0, 0
+        };
+
+        xt::xtensor<int, 1> elemval_r2 = {
+            0, 0, 1,
+            1, 0, 0,
+            0, 1, 0
+        };
+
+        xt::xtensor<int, 1> nodeval = {
+            1, 0, 0, 1,
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            1, 0, 0, 1
+        };
+
+        xt::xtensor<int, 1> nodeval_r1 = {
+            0, 1, 0, 0,
+            0, 0, 1, 0,
+            1, 0, 0, 1,
+            0, 1, 0, 0
+        };
+
+        xt::xtensor<int, 1> nodeval_r2 = {
+            0, 0, 1, 0,
+            1, 0, 0, 1,
+            0, 1, 0, 0,
+            0, 0, 1, 0
+        };
+
+        {
+            auto elemmap = mesh.roll(0);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(1);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r1, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r1, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(2);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r2, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r2, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(3);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(4);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r1, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r1, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+
+        {
+            auto elemmap = mesh.roll(5);
+            auto nodemap = GooseFEM::Mesh::elemmap2nodemap(elemmap, mesh.coor(), mesh.conn());
+            REQUIRE(xt::all(xt::equal(elemval_r2, xt::view(elemval, xt::keep(elemmap)))));
+            REQUIRE(xt::all(xt::equal(nodeval_r2, xt::view(nodeval, xt::keep(nodemap)))));
+        }
+    }
 }