diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6c600cd624e1da76d5e4113e0a6456265c125fd9..1abd4e1cd6b8ebe181c8bed224f602fe76a945d4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ project(gmsh_gpu_dg)
 include(CheckLanguage)
 
 
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 set(CMAKE_CUDA_ARCHITECTURES 35)
 set(CMAKE_CUDA_HOST_COMPILER "/usr/bin/g++-6")
@@ -135,7 +135,7 @@ endif ()
 ######################################################################
 ## Compiler optimization options
 
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-zero-as-null-pointer-constant")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-zero-as-null-pointer-constant -Wno-global-constructors -Wno-padded -Wno-shorten-64-to-32 -Wno-sign-conversion -Wno-old-style-cast -Wno-sign-compare -Wno-c99-extensions -Wno-extra-semi-stmt")
 
 set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
 set(CMAKE_CXX_FLAGS_RELEASE "-O3 -g -DNDEBUG")
@@ -230,5 +230,11 @@ endif()
 include_directories(contrib/sgr)
 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
 
-add_executable(gmsh_gpu_dg main.cpp gmsh_io.cpp reference_element.cpp physical_element.cpp)
+add_executable(gmsh_gpu_dg main.cpp gmsh_io.cpp reference_element.cpp physical_element.cpp entity.cpp kernels_cpu.cpp)
 target_link_libraries(gmsh_gpu_dg ${LINK_LIBS})
+
+add_executable(test test.cpp gmsh_io.cpp reference_element.cpp physical_element.cpp entity.cpp kernels_cpu.cpp)
+target_link_libraries(test ${LINK_LIBS})
+
+add_executable(dump_orient dump_orient.cpp)
+target_link_libraries(dump_orient ${LINK_LIBS})
diff --git a/dump_orient.cpp b/dump_orient.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c941697d58f9e5177245f2c4b805986279d2fe79
--- /dev/null
+++ b/dump_orient.cpp
@@ -0,0 +1,123 @@
+#include <iostream>
+#include <cassert>
+
+#include "gmsh.h"
+
+namespace g     = gmsh;
+namespace go    = gmsh::option;
+namespace gm    = gmsh::model;
+namespace gmm   = gmsh::model::mesh;
+namespace gmo   = gmsh::model::occ;
+namespace gmg   = gmsh::model::geo;
+namespace gv    = gmsh::view;
+
+using gvp_t = gmsh::vectorpair;
+
+#define SPHERE_RADIUS 0.2
+#define DIMENSION(x) x
+
+static void
+make_geometry(int order)
+{
+    gm::add("difftest");
+
+    std::vector<std::pair<int,int>> objects;
+    objects.push_back(
+        std::make_pair(3, gmo::addBox(0.0, 0.0, 0.0, 1.0, 1.0, 1.0) )
+        );
+    objects.push_back(
+        std::make_pair(3, gmo::addSphere(0.5, 0.5, 0.5, SPHERE_RADIUS) )
+        );
+
+    std::vector<std::pair<int, int>> tools;
+    gmsh::vectorpair odt;
+    std::vector<gmsh::vectorpair> odtm;
+    gmo::fragment(objects, tools, odt, odtm);
+
+    gmo::synchronize();
+
+    gvp_t vp;
+    gm::getEntities(vp);
+    gmm::setSize(vp, 0.5);
+
+    gmm::generate( DIMENSION(3) );
+    gmm::setOrder( order );
+}
+
+int main(void)
+{
+    gmsh::initialize();
+
+    int gorder = 2;
+
+    make_geometry(gorder);
+
+    gvp_t gmsh_entities;
+    gm::getEntities(gmsh_entities, DIMENSION(3));
+
+    for (auto [dim, tag] : gmsh_entities)
+    {
+        std::vector<int> elemTypes;
+        gmm::getElementTypes(elemTypes, dim, tag);
+        
+        assert(elemTypes.size() == 1);
+        if (elemTypes.size() != 1)
+            throw std::invalid_argument("Only one element type per entity is allowed");
+
+        for (auto& elemType : elemTypes)
+        {
+            std::vector<size_t> elemTags;
+            std::vector<size_t> elemNodesTags;
+            gmm::getElementsByType(elemType, elemTags, elemNodesTags, tag);
+
+            std::vector<int> orientations;
+            gmm::getBasisFunctionsOrientationForElements(elemType, "H1Legendre3",
+                orientations, tag);
+
+            std::vector<std::size_t> face_nodes;
+            gmm::getElementFaceNodes(elemType, 3, face_nodes, tag); // 3 is for triangles
+            int fe_tag = gm::addDiscreteEntity(dim-1);
+            int eleType2D = gmsh::model::mesh::getElementType("triangle", gorder);
+            gmsh::model::mesh::addElementsByType(fe_tag, eleType2D, {}, face_nodes);
+
+            std::vector<int> orientations2D;
+            gmm::getBasisFunctionsOrientationForElements(eleType2D, "H1Legendre3",
+                orientations2D, fe_tag);
+
+            std::cout << "3D: " << orientations.size() << std::endl;
+            std::cout << "2D: " << orientations2D.size() << std::endl;
+            assert(orientations2D.size() == 4*orientations.size());
+
+            using op = std::pair<int, std::array<int, 4>>;
+            std::vector<op> ordermap;
+
+            for (size_t i = 0; i < orientations.size(); i++)
+            {
+                op p;
+                p.first = orientations[i];
+                std::array<int, 4> or2d;
+
+                for (size_t j = 0; j < 4; j++)
+                    or2d[j] = orientations2D[4*i+j];
+                
+                p.second = or2d;
+                ordermap.push_back(p);
+            }
+
+            auto comp = [](const op& a, const op& b) -> bool { return a.first < b.first; };
+
+            std::sort(ordermap.begin(), ordermap.end(), comp);
+
+            for (auto& om : ordermap)
+            {
+                std::cout << om.first << ": ";
+                for (auto& o : om.second)
+                    std::cout << o << " ";
+                std::cout << std::endl;
+            }
+        }
+    }
+
+    gmsh::finalize();
+    return 0;
+}
diff --git a/entity.cpp b/entity.cpp
index 86b2aae57065a2d8f540c87ffc89918ee519f71a..e8cad719a1ae7b246350f72e90a8c764957a6178 100644
--- a/entity.cpp
+++ b/entity.cpp
@@ -1,49 +1,362 @@
 #include <iostream>
-
+#include <cassert>
 #include "sgr.hpp"
 
 #include "gmsh_io.h"
 #include "entity.h"
 
-entity::entity(int pdim, int ptag, int pelemType, int porder)
-    : g_dim(pdim), g_tag(ptag), g_elemType(pelemType), order(porder)
+
+entity::entity(const entity_params& ep)
+    : dim(ep.dim), tag(ep.tag), elemType(ep.etype), g_order(ep.gorder),
+      a_order(ep.aorder), cur_elem_ordering(entity_ordering::GMSH)
 {
-    /* Get all the elements belonging to this group */
-    gmm::getElementsByType(g_elemType, g_elemTags, g_elemNodesTags, g_tag);
-    auto num_elems = g_elemTags.size();
+    /* Prepare reference elements */
+    reference_elements_factory ref(ep);
+    reference_cells = ref.get_elements();
 
-#ifdef ENABLE_DEBUG_PRINT
-    std::cout << yellowfg << "entity::entity(): elemType " << g_elemType
-              << ", elems: " << num_elems << std::endl;
-    std::cout << quadrature_name(2*order) << " " << basis_func_name(order) << " "
-              << basis_grad_name(order) << reset << std::endl;
-#endif
-    /* Get integration points and weights on refelem */
-    gmm::getIntegrationPoints(g_elemType, quadrature_name(2*order), ips, iws);
-    assert(ips.size() == 3*iws.size());
+    physical_elements_factory pef(ep);
+    physical_cells = pef.get_elements();
+
+    std::vector<std::size_t> face_nodes;
+    gmm::getElementFaceNodes(elemType, 3, face_nodes, tag); // 3 is for triangles
+    int tag_2D = gm::addDiscreteEntity(dim-1);
+    elemType_2D = gmsh::model::mesh::getElementType("triangle", g_order);
+    gmsh::model::mesh::addElementsByType(tag_2D, elemType_2D, {}, face_nodes);
+
+    entity_params ep_2D;
+    ep_2D.dim       = 2;
+    ep_2D.tag       = tag_2D;
+    ep_2D.etype     = elemType_2D;
+    ep_2D.gorder    = g_order; 
+    ep_2D.aorder    = a_order; 
+
+    reference_elements_factory ref_2D(ep_2D);
+    reference_faces = ref_2D.get_elements();
+
+    physical_elements_factory pef_2D(ep_2D);
+    physical_faces = pef_2D.get_elements();
+}
+
+double
+entity::measure(void) const
+{
+    auto meas = 0.0;
+    for (auto& pe : physical_cells)
+        meas += pe.measure();
+    
+    return meas;
+}
+
+vecxd
+entity::project(const scalar_function& function) const
+{
+    auto num_bf = reference_cells[0].num_basis_functions();
+
+    vecxd ret = vecxd::Zero( physical_cells.size() * num_bf );
+    for (size_t iT = 0; iT < physical_cells.size(); iT++)
+    {
+        const auto& pe = physical_cells[iT];
+        //assert( pe.orientation() == 0 );
+        assert( pe.orientation() < reference_cells.size() );
+        const auto& re = reference_cells[pe.orientation()];
+        const auto pqps = pe.integration_points();
+        const auto dets = pe.determinants();
+
+        assert(pqps.size() == dets.size());
+
+        matxd mass = matxd::Zero(num_bf, num_bf);
+        vecxd rhs = vecxd::Zero(num_bf);
+        for (size_t iQp = 0; iQp < pqps.size(); iQp++)
+        {
+            const auto& pqp = pqps[iQp];
+            const vecxd phi = re.basis_functions(iQp);
+            mass += pqp.weight() * phi * phi.transpose();
+            rhs += pqp.weight() * function(pqp.point()) * phi; 
+        }
+
+        ret.segment(iT*num_bf, num_bf) = mass.ldlt().solve(rhs);
+    }
+
+    return ret;
+}
+
+const reference_element&
+entity::refelem(const physical_element& pe) const
+{
+    assert(pe.orientation() < reference_cells.size());
+    return reference_cells[pe.orientation()];
+}
+
+size_t
+entity::num_orientations(void) const
+{
+    return reference_cells.size();
+}
+
+size_t
+entity::num_elements(void) const
+{
+    return physical_cells.size();
+}
+
+std::vector<size_t>
+entity::num_elements_per_orientation(void) const
+{
+    std::vector<size_t> ret( num_orientations(), 0 );
+    for (auto& pe : physical_cells)
+    {
+        assert( pe.orientation() < ret.size() );
+        ret[ pe.orientation() ]++;
+    }
+
+    return ret;
+}
+
+const physical_element&
+entity::elem(size_t iT)
+{
+    assert(iT < physical_cells.size());
+    return physical_cells[iT];
+}
 
-    gmm::getJacobians(g_elemType, ips, jacs, dets, pts, g_tag);
+matxd
+entity::mass_matrix(size_t iT)
+{
+    const auto& pe = physical_cells[iT];
+    assert( pe.orientation() < reference_cells.size() );
+    const auto& re = reference_cells[pe.orientation()];
+    const auto rqps = re.integration_points();
+    const auto dets = pe.determinants();
+    auto num_bf = re.num_basis_functions();
 
-    assert(jacs.size() == 9*dets.size());
-    assert(pts.size() == 3*dets.size());
-    assert(dets.size() == num_elems*iws.size());
+        matxd mass = matxd::Zero(num_bf, num_bf);
 
-    int bf_nc;
-    gmm::getBasisFunctions(g_elemType, ips, basis_func_name(order), bf_nc, basisFunctions, g_num_bfun_orient);
-    assert(bf_nc == 1);
-    gmm::getBasisFunctionsOrientationForElements(g_elemType, basis_func_name(order), basisFunctionsOrientation);
+        for (size_t iQp = 0; iQp < rqps.size(); iQp++)
+        {
+            const auto& qp = rqps[iQp];
+            const vecxd phi = re.basis_functions(iQp);
+            mass += dets[iQp] * qp.weight() * phi * phi.transpose();
+        }
+    
+    return mass;
+}
 
-    gmm::getBasisFunctions(g_elemType, ips, basis_grad_name(order), bf_nc, basisGradients, g_num_bgrad_orient);
-    assert(bf_nc == 3);
-    gmm::getBasisFunctionsOrientationForElements(g_elemType, basis_grad_name(order), basisGradientsOrientation);
+void
+entity::sort_by_orientation(void)
+{
+    /* Sort by orientation and keep GMSH ordering within the same orientation */
+    auto comp = [](const physical_element& e1, const physical_element& e2) -> bool {
+        auto oe1 = e1.orientation();
+        auto oe2 = e2.orientation();
+        auto op1 = e1.original_position();
+        auto op2 = e2.original_position();
 
-    assert(g_num_bfun_orient == g_num_bgrad_orient);
+        if ( oe1 < oe2 and op1 < op2 )
+            return true;
 
-    gmm::getBarycenters(g_elemType, g_tag, false, false, barycenters);
-    assert(barycenters.size() == 3*num_elems);
+        return false;
+    };
 
-#ifdef ENABLE_DEBUG_PRINT
-    std::cout << yellowfg << "Orientations: " << g_num_bfun_orient << " " << g_num_bgrad_orient << reset << std::endl;
+    std::sort(physical_cells.begin(), physical_cells.end(), comp);
+    cur_elem_ordering = entity_ordering::BY_ORIENTATION;
+}
+
+void
+entity::sort_by_gmsh(void)
+{
+    /* Sort by GMSH original ordering */
+    auto comp = [](const physical_element& e1, const physical_element& e2) -> bool {
+        auto op1 = e1.original_position();
+        auto op2 = e2.original_position();
+
+        if ( op1 < op2 )
+            return true;
+
+        return false;
+    };
+
+    std::sort(physical_cells.begin(), physical_cells.end(), comp);
+    cur_elem_ordering = entity_ordering::GMSH;
+}
+
+int
+entity::gmsh_tag(void) const
+{
+    return tag;
+}
+
+int
+entity::gmsh_elem_type(void) const
+{
+    return elemType;
+}
+
+entity_ordering
+entity::current_elem_ordering(void) const
+{
+    return cur_elem_ordering;
+}
+
+
+void
+entity::populate_differentiation_matrices(entity_data_cpu<1>& ed) const
+{
+    /* In the case of geometric order = 1, the inverse of the
+     * mass matrix is embedded in the differentiation matrix */
+
+    auto dm_rows = ed.num_orientations * ed.num_bf;
+    auto dm_cols = 3 * ed.num_bf;
+    ed.differentiation_matrix = matxd::Zero(dm_rows, dm_cols);
+
+    for (size_t iO = 0; iO < ed.num_orientations; iO++)
+    {
+        matxd dm = matxd::Zero(ed.num_bf, 3*ed.num_bf);
+
+        auto& re = reference_cells[iO];
+        auto qps = re.integration_points();
+        auto num_qp = qps.size();
+        for (size_t iQp = 0; iQp < num_qp; iQp++)
+        {
+            auto w = qps[iQp].weight();
+            vecxd phi = re.basis_functions(iQp);
+            matxd dphi = re.basis_gradients(iQp);
+            vecxd du_phi = dphi.col(0);
+            vecxd dv_phi = dphi.col(1);
+            vecxd dw_phi = dphi.col(2);
+
+            dm.block(0,           0, ed.num_bf, ed.num_bf) += w * phi * du_phi.transpose();
+            dm.block(0,   ed.num_bf, ed.num_bf, ed.num_bf) += w * phi * dv_phi.transpose();
+            dm.block(0, 2*ed.num_bf, ed.num_bf, ed.num_bf) += w * phi * dw_phi.transpose();
+        }
+
+        matxd mass = re.mass_matrix();
+        Eigen::LDLT<matxd> mass_ldlt(mass);
+        if (mass_ldlt.info() != Eigen::Success )
+            throw std::invalid_argument("LDLT failed");
+
+        size_t base_row = iO * ed.num_bf;
+        ed.differentiation_matrix.block(base_row, 0, ed.num_bf, 3*ed.num_bf) =
+            mass_ldlt.solve(dm);
+    }
+}
+
+void
+entity::populate_jacobians(entity_data_cpu<1>& ed) const
+{
+    auto num_elems = num_elements();
+
+    ed.jacobians = matxd::Zero(3*num_elems, 3);
+    for (size_t iT = 0; iT < num_elems; iT++)
+    {
+        auto& pe = physical_cells[iT];
+        ed.jacobians.block(3*iT, 0, 3, 3) = pe.jacobian().inverse().transpose();
+    }
+}
+
+void
+entity::populate_entity_data(entity_data_cpu<1>& ed) const
+{
+    assert(g_order == 1);
+    assert(reference_cells.size() > 0);
+    ed.num_elems = num_elements_per_orientation();
+    ed.num_orientations = num_orientations();
+    ed.num_bf = reference_cells[0].num_basis_functions();
+    ed.dofs_base = 0;
+
+    populate_differentiation_matrices(ed);
+    populate_jacobians(ed);
+
+#if 0
+    if (new_re.m_geometric_order == 1)
+    {
+        new_re.m_
+
+    }
+    else
+    {
+        new_re.m_mass_matrices = matxd::Zero(num_bf*num_qp, 3*num_bf);
+        for (size_t iQp = 0; iQp < num_qp; iQp++)
+        {
+            auto w = new_re.m_quadpoints[iQp].weight();
+            vecxd phi = new_re.m_basis_functions.segment(num_bf*iQp, num_bf);
+            new_re.m_mass_matrices.block(num_bf*iQp, 0, num_bf, num_bf) =
+                w * phi * phi.transpose();
+        }
+    }
 #endif
 }
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+std::pair<std::vector<point_3d>, std::vector<quadrature_point_3d>>
+integrate(const entity& e, int order)
+{
+    auto tag = e.gmsh_tag();
+    auto elemType = e.gmsh_elem_type();
+
+    std::vector<double>     ips;
+    std::vector<double>     iws;
+    gmm::getIntegrationPoints(elemType, quadrature_name(order), ips, iws);
+
+    auto num_qp = iws.size();
+
+    std::vector<double>     ppts;
+    std::vector<double>     jacs;
+    std::vector<double>     dets;
+    gmm::getJacobians(elemType, ips, jacs, dets, ppts, tag);
+
+    std::vector<point_3d> retr;
+
+    for (size_t iQp = 0; iQp < num_qp; iQp++)
+    {
+        double u = ips[3*iQp + 0];
+        double v = ips[3*iQp + 1];
+        double w = ips[3*iQp + 2];
+        point_3d p(u,v,w);
+        retr.push_back(p);
+    }
+
+    std::vector<quadrature_point_3d> retp;
+    retp.reserve(dets.size());
+
+    for (auto& pe : e)
+    {
+        auto g_pos = pe.original_position();
+        auto dets_base = g_pos*num_qp;
+        auto ppts_base = 3*g_pos*num_qp;
+
+        for (size_t iQp = 0; iQp < num_qp; iQp++)
+        {
+            double w = dets[dets_base+iQp]*iws[iQp];
+            double x = ppts[ppts_base + 3*iQp + 0];
+            double y = ppts[ppts_base + 3*iQp + 1];
+            double z = ppts[ppts_base + 3*iQp + 2];
+            point_3d p(x,y,z);
+            quadrature_point_3d q(p,w);
+            retp.push_back(q);
+        }
+    }
+    return std::make_pair(retr, retp);
+}
+
diff --git a/entity.h b/entity.h
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..71d3ab964bf786846ca851f0d1a27c6fc07560ec 100644
--- a/entity.h
+++ b/entity.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <iostream>
+
+#include "reference_element.h"
+#include "physical_element.h"
+
+#include "entity_data.h"
+
+using scalar_function = std::function<double(const point_3d&)>;
+
+enum class entity_ordering {
+    GMSH,
+    BY_ORIENTATION
+};
+
+class entity
+{
+    int     dim;
+    int     tag;
+    int     elemType;
+    int     elemType_2D;
+    int     g_order;
+    int     a_order;
+
+    entity_ordering                     cur_elem_ordering;
+
+    std::vector<reference_element>      reference_cells;
+    std::vector<physical_element>       physical_cells;
+
+    std::vector<reference_element>      reference_faces;
+    std::vector<physical_element>       physical_faces;
+
+    void populate_differentiation_matrices(entity_data_cpu<1>&) const;
+    void populate_jacobians(entity_data_cpu<1>&) const;
+
+public:
+    entity() = delete;
+    entity(const entity&) = delete;
+    entity(entity&&) = default;
+    entity& operator=(const entity&) = delete;
+    entity& operator=(entity&&) = delete;
+
+    entity(const entity_params& ep);
+
+    double                      measure(void) const;
+    vecxd                       project(const scalar_function&) const;
+
+    size_t                      num_elements(void) const;
+    size_t                      num_orientations(void) const;
+    std::vector<size_t>         num_elements_per_orientation(void) const;
+
+    const reference_element&    refelem(const physical_element&) const;
+    const physical_element&     elem(size_t);
+
+    matxd                       mass_matrix(size_t);
+    
+    void                        sort_by_orientation(void);
+    void                        sort_by_gmsh(void);
+
+    int                         gmsh_tag(void) const;
+    int                         gmsh_elem_type(void) const;
+    entity_ordering             current_elem_ordering(void) const;
+
+    void                        populate_entity_data(entity_data_cpu<1>&) const;
+
+
+    std::vector<physical_element>::const_iterator  begin() const { return physical_cells.begin(); }
+    std::vector<physical_element>::const_iterator    end() const { return physical_cells.end(); }
+
+};
+
+std::pair<std::vector<point_3d>, std::vector<quadrature_point_3d>>
+integrate(const entity& e, int order);
+
diff --git a/entity_data.h b/entity_data.h
new file mode 100644
index 0000000000000000000000000000000000000000..130ce394efb3290982793e9f9191e3883f725aa0
--- /dev/null
+++ b/entity_data.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "eigen.h"
+
+template<size_t GK>
+struct entity_data_cpu;
+
+template<>
+struct entity_data_cpu<1>
+{
+    std::vector<size_t> num_elems;          /* No. of elements in the entity */
+    size_t              num_orientations;   /* No. of bf. orientations */
+    size_t              num_bf;             /* No. of dofs per elem */
+    size_t              num_fluxes;         /* No. of flux dofs per elem */
+    size_t              num_faces;          /* No. of faces */
+    size_t              dofs_base;
+
+    matxd               mass_matrix;
+    matxd               differentiation_matrix;
+    matxd               lifting_matrix;
+    matxd               jacobians;
+};
+
diff --git a/gmsh_io.cpp b/gmsh_io.cpp
index ef5af96157df81584b0b3304b0adf9c280398980..3b4defe325d301b297bdfed5e7f9217522170884 100644
--- a/gmsh_io.cpp
+++ b/gmsh_io.cpp
@@ -3,6 +3,7 @@
 #include <cassert>
 
 #include "gmsh_io.h"
+#include "entity.h"
 
 std::string
 quadrature_name(int order)
@@ -15,7 +16,9 @@ quadrature_name(int order)
 std::string
 basis_func_name(int order)
 {
+    (void) order;
     std::stringstream ss;
+    //ss << "Lagrange";
     ss << "H1Legendre" << order;
     return ss.str();
 }
@@ -23,7 +26,9 @@ basis_func_name(int order)
 std::string
 basis_grad_name(int order)
 {
+    (void) order;
     std::stringstream ss;
+    //ss << "GradLagrange";
     ss << "GradH1Legendre" << order;
     return ss.str();
 }
@@ -31,42 +36,93 @@ basis_grad_name(int order)
 model::model()
     : geometric_order(1), approximation_order(1)
 {
-    read_gmsh_entities();
+    remesh(geometric_order);
 }
 
 model::model(int pgo, int pao)
     : geometric_order(pgo), approximation_order(pao)
 {
-    assert(geometric_order >= 1 and geometric_order <= 3);
+    assert(geometric_order > 0);
     assert(approximation_order > 0);
 
-    read_gmsh_entities();
+    remesh(geometric_order);
 }
 
 model::model(const char *filename)
     : geometric_order(1), approximation_order(1)
 {
     gmsh::open( std::string(filename) );
-    read_gmsh_entities();
+    remesh(geometric_order);
 }
 
 model::model(const char *filename, int pgo, int pao)
     : geometric_order(pgo), approximation_order(pao)
 {
-    assert(geometric_order >= 1 and geometric_order <= 3);
+    assert(geometric_order > 0);
     assert(approximation_order > 0);
 
     gmsh::open( std::string(filename) );
-    read_gmsh_entities();
+    remesh(geometric_order);
+}
+
+model::~model()
+{
+    gmsh::clear();
+}
+
+void
+model::remesh()
+{
+    gmm::generate( DIMENSION(3) );
+    gmm::setOrder( geometric_order );
+    entities.clear();
+    import_gmsh_entities();
+}
+
+void
+model::remesh(int pgo)
+{
+    geometric_order = pgo;
+    remesh();
+}
+
+std::vector<entity>::const_iterator
+model::begin() const
+{
+    return entities.begin();
+}
+
+std::vector<entity>::const_iterator
+model::end() const
+{
+    return entities.begin();
+}
+
+std::vector<entity>::iterator
+model::begin()
+{
+    return entities.begin();
+}
+
+std::vector<entity>::iterator
+model::end()
+{
+    return entities.begin();
+}
+
+entity&
+model::entity_at(size_t pos)
+{
+    return entities.at(pos);
 }
 
 void
-model::read_gmsh_entities(void)
+model::import_gmsh_entities(void)
 {
-    gvp_t entities;
-    gm::getEntities(entities, DIMENSION(3));
+    gvp_t gmsh_entities;
+    gm::getEntities(gmsh_entities, DIMENSION(3));
 
-    for (auto [dim, tag] : entities)
+    for (auto [dim, tag] : gmsh_entities)
     {
         std::vector<int> elemTypes;
         gmm::getElementTypes(elemTypes, dim, tag);
@@ -77,7 +133,9 @@ model::read_gmsh_entities(void)
 
         for (auto& elemType : elemTypes)
         {
-            
+            entity e({.dim = dim, .tag = tag, .etype = elemType,
+                      .aorder = approximation_order, .gorder = geometric_order});
+            entities.push_back( std::move(e) );
         }
     }
 }
diff --git a/gmsh_io.h b/gmsh_io.h
index 4e542960e8e64d894df152d5d8fdbfca242796e0..5c18fd0cbd2e49ec4b50b350929bfe1852071a04 100644
--- a/gmsh_io.h
+++ b/gmsh_io.h
@@ -4,7 +4,7 @@
 
 #include <gmsh.h>
 
-#include "reference_element.h"
+#include "entity.h"
 
 namespace g     = gmsh;
 namespace go    = gmsh::option;
@@ -28,12 +28,29 @@ class model
     int                             geometric_order;
     int                             approximation_order;
 
-    void    read_gmsh_entities(void);
+
+    std::vector<entity>             entities;
+
+
+    void    import_gmsh_entities(void);
 
 public:
     model();
     model(int, int);
     model(const char *);
     model(const char *, int, int);
+    ~model();
+
+    void    remesh(void);
+    void    remesh(int);
+
+    std::vector<entity>::const_iterator begin() const;
+    std::vector<entity>::const_iterator end() const;
+    std::vector<entity>::iterator begin();
+    std::vector<entity>::iterator end();
+
+    entity&     entity_at(size_t);
+    entity      entity_at(size_t) const;
+
 };
 
diff --git a/kernels_cpu.cpp b/kernels_cpu.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..11dc87d0b96a526521213c567177dfc9199f504f
--- /dev/null
+++ b/kernels_cpu.cpp
@@ -0,0 +1,46 @@
+#include "types.h"
+#include "entity_data.h"
+
+void compute_field_derivatives(const entity_data_cpu<1>& ed,
+    const vecxd& f, vecxd& df_dx, vecxd& df_dy, vecxd& df_dz)
+{
+    /* The elements must be ordered by orientation -> entity::sort_by_orientation() */
+    size_t orient_elem_base = 0;
+    for (size_t iO = 0; iO < ed.num_orientations; iO++)
+    {
+        size_t num_elems = ed.num_elems[iO];
+        size_t orient_base = orient_elem_base * ed.num_bf;
+
+        for (size_t iT = 0; iT < num_elems; iT++)
+        {
+            size_t ent_iT = orient_elem_base + iT;
+            size_t dofofs = ed.dofs_base + orient_base + iT*ed.num_bf;
+
+            for (size_t odof = 0; odof < ed.num_bf; odof++)
+            {
+                double acc_df_dx = 0.0;
+                double acc_df_dy = 0.0;
+                double acc_df_dz = 0.0;
+
+                for (size_t refsys_dir = 0; refsys_dir < 3; refsys_dir++)
+                {
+                    for (size_t idof = 0; idof < ed.num_bf; idof++)
+                    {
+                        auto dm_row = iO*ed.num_bf + odof;
+                        auto dm_col = refsys_dir*ed.num_bf + idof;
+                        double D = ed.differentiation_matrix(dm_row, dm_col);
+                        acc_df_dx += ed.jacobians(3*ent_iT + 0, refsys_dir) * D * f(dofofs+idof);
+                        acc_df_dy += ed.jacobians(3*ent_iT + 1, refsys_dir) * D * f(dofofs+idof);
+                        acc_df_dz += ed.jacobians(3*ent_iT + 2, refsys_dir) * D * f(dofofs+idof);
+                    }
+                }
+
+                df_dx(dofofs+odof) = acc_df_dx;
+                df_dy(dofofs+odof) = acc_df_dy;
+                df_dz(dofofs+odof) = acc_df_dz;
+            }
+        }
+
+        orient_elem_base += num_elems;
+    }
+}
\ No newline at end of file
diff --git a/kernels_cpu.h b/kernels_cpu.h
new file mode 100644
index 0000000000000000000000000000000000000000..08b5abd085b5ec60a12e318892543f1581902d9e
--- /dev/null
+++ b/kernels_cpu.h
@@ -0,0 +1,5 @@
+#pragma once
+
+void compute_field_derivatives(const entity_data_cpu<1>& ed,
+    const vecxd& f, vecxd& df_dx, vecxd& df_dy, vecxd& df_dz);
+
diff --git a/main.cpp b/main.cpp
index ac49ce9f3ed2c3b0b3ff7f1078003de64ba67f1c..ed7832b1dbcbcf633eb09ed96013632993bdaaec 100644
--- a/main.cpp
+++ b/main.cpp
@@ -3,6 +3,7 @@
 #include <unistd.h>
 
 #include "gmsh_io.h"
+#include "silo_output.hpp"
 
 static void usage(const char *progname)
 {
@@ -13,9 +14,11 @@ static void usage(const char *progname)
 int main(int argc, char *argv[])
 {
     const char *model_filename = nullptr;
+    int geometric_order = 1;
+    int approximation_order = 1;
 
     int ch;
-    while ( (ch = getopt(argc, argv, "m:")) != -1 )
+    while ( (ch = getopt(argc, argv, "m:o:O:")) != -1 )
     {
         switch(ch)
         {
@@ -23,12 +26,25 @@ int main(int argc, char *argv[])
             model_filename = optarg;
             break;
 
+        case 'o':
+            approximation_order = atoi(optarg);
+            /* TODO: check approximation order */
+            break;
+
+        case 'O':
+            geometric_order = atoi(optarg);
+            /* TODO: check geometric order */
+            break;
+
         default:
             usage(argv[0]);
             return 1;
         }
     }
 
+    argc -= optind;
+    argv += optind;
+
     if (!model_filename)
     {
         std::cout << "Model filename (-m) not specified" << std::endl;
@@ -37,9 +53,13 @@ int main(int argc, char *argv[])
 
     gmsh::initialize(argc, argv);
 
-    
+    geometric_order = approximation_order;
+    model mod(model_filename, geometric_order, approximation_order);
 
-    model mod;
+    silo silo;
+    silo.create_db("test.silo");
+    silo.import_mesh_from_gmsh();
+    silo.write_mesh();
 
     gmsh::finalize();
     return 0;
diff --git a/physical_element.cpp b/physical_element.cpp
index c4ad9c0ef181c8559c6139ab0f63415ef7eab6d3..a8b34ea87482438d440eac4db615962ebc9fe795 100644
--- a/physical_element.cpp
+++ b/physical_element.cpp
@@ -1,23 +1,113 @@
+#include <iostream>
+#include <cassert>
 
 #include "physical_element.h"
 #include "gmsh_io.h"
+#include "sgr.hpp"
 
 physical_element::physical_element()
 {}
 
-physical_elements_factory::physical_elements_factory(int pdim, int ptag, int pElemType)
-    : dim(pdim), tag(ptag), elemType(pElemType)
+size_t
+physical_element::original_position(void) const
 {
-    
+    return m_original_position;
 }
 
+int
+physical_element::dimension(void) const
+{
+    return m_dimension;
+}
+
+int
+physical_element::orientation(void) const
+{
+    return m_orientation;
+}
+
+double
+physical_element::measure(void) const
+{
+    return m_measure;
+}
+
+vec_quadpt_3d
+physical_element::integration_points(void) const
+{
+    return m_phys_quadpoints;
+}
+
+/* Return only one determinant: this is used for geometric order 1
+ * where the determinants and the jacobians are constant */
+double
+physical_element::determinant(void) const
+{
+    assert(m_geometric_order == 1);
+    return m_determinants[0];
+}
+
+/* Return all the determinants in all the integration points: this
+ * is for elements with geometric order > 1 */
+vec_double
+physical_element::determinants(void) const
+{
+    assert(m_geometric_order >= 1);
+    return m_determinants;
+}
+
+/* Return only one jacobian: this is used for geometric order 1
+ * where the determinants and the jacobians are constant */
+mat3d
+physical_element::jacobian(void) const
+{
+    assert(m_geometric_order == 1);
+    return m_jacobians[0];
+}
+
+/* Return all the jacobians in all the integration points: this
+ * is for elements with geometric order > 1 */
+vec_mat3d
+physical_element::jacobians(void) const
+{
+    assert(m_geometric_order >= 1);
+    return m_jacobians;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+physical_elements_factory::physical_elements_factory(const entity_params& ep)
+    : dim(ep.dim), tag(ep.tag), elemType(ep.etype), geom_order(ep.gorder),
+      approx_order(ep.aorder)
+{}
+
 std::vector<physical_element>
 physical_elements_factory::get_elements()
 {
-    auto order = 2;
+    std::vector<size_t> elemTags;
+    std::vector<size_t> elemNodesTags;
+    gmm::getElementsByType(elemType, elemTags, elemNodesTags, tag);
+    auto num_elems = elemTags.size();
+    auto num_nodes = elemNodesTags.size()/elemTags.size();
+    assert( elemTags.size()*num_nodes == elemNodesTags.size() );
+
     std::vector<double>     ips;
     std::vector<double>     iws;
-    gmm::getIntegrationPoints(elemType, quadrature_name(2*order), ips, iws);
+    gmm::getIntegrationPoints(elemType, quadrature_name(2*approx_order), ips, iws);
 
     auto num_gf = iws.size();
 
@@ -26,50 +116,70 @@ physical_elements_factory::get_elements()
     std::vector<double>     dets;
     gmm::getJacobians(elemType, ips, jacs, dets, ppts, tag);
 
-    auto num_elems = dets.size()/num_gf;
     assert( num_gf * num_elems == dets.size() );
 
+    std::vector<int> orientations;
+    gmm::getBasisFunctionsOrientationForElements(elemType, basis_func_name(approx_order),
+        orientations, tag);
+    assert(orientations.size() == num_elems);
+
     std::vector<physical_element> ret;
     ret.reserve(num_elems);
 
     for (size_t elem = 0; elem < num_elems; elem++)
     {
         physical_element new_pe;
+        new_pe.m_geometric_order = geom_order;
+        new_pe.m_approximation_order = approx_order;
         new_pe.m_original_position = elem;
         new_pe.m_dimension = dim;
         new_pe.m_parent_entity_tag = tag;
+        new_pe.m_orientation = orientations[elem];
+        new_pe.m_element_tag = elemTags[elem];
+        new_pe.m_measure = 0.0;
+
+        new_pe.m_node_tags.resize(num_nodes);
+        for (size_t i = 0; i < num_nodes; i++)
+            new_pe.m_node_tags[i] = elemNodesTags[num_nodes*elem+i];
+
+        auto elem_base = elem*num_gf;
 
         for (size_t gf = 0; gf < num_gf; gf++)
-        {
+        {   
+            auto gf_offset = elem_base + gf;
+ 
             const auto JSIZE = 3*3; /* Jacobian matrix size */
-            auto jacs_base = elem*num_gf*JSIZE + gf*JSIZE;
+            auto jacs_base = gf_offset*JSIZE;
             assert(jacs_base+8 < jacs.size());
 
             /* GMSH returns jacobians by column */
             Eigen::Matrix3d jac;
-            jac(0,0) = jacs[jacs_base+0];
-            jac(0,1) = jacs[jacs_base+1];
-            jac(0,2) = jacs[jacs_base+2];
-            jac(1,0) = jacs[jacs_base+3];
-            jac(1,1) = jacs[jacs_base+4];
-            jac(1,2) = jacs[jacs_base+5];
-            jac(2,0) = jacs[jacs_base+6];
-            jac(2,1) = jacs[jacs_base+7];
-            jac(2,2) = jacs[jacs_base+8];
+            jac(0,0) = jacs[jacs_base+0];   /* dx/du */
+            jac(1,0) = jacs[jacs_base+1];   /* dy/du */
+            jac(2,0) = jacs[jacs_base+2];   /* dz/du */
+            jac(0,1) = jacs[jacs_base+3];   /* dx/dv */
+            jac(1,1) = jacs[jacs_base+4];   /* dy/dv */
+            jac(2,1) = jacs[jacs_base+5];   /* dz/dv */
+            jac(0,2) = jacs[jacs_base+6];   /* dx/dw */
+            jac(1,2) = jacs[jacs_base+7];   /* dy/dw */
+            jac(2,2) = jacs[jacs_base+8];   /* dz/dw */
             new_pe.m_jacobians.push_back(jac);
 
-            auto dets_ofs = elem*num_gf + gf;
+            auto dets_ofs = gf_offset;
             assert( dets_ofs < dets.size() );
             new_pe.m_determinants.push_back(dets[dets_ofs]);
+            new_pe.m_measure += dets[dets_ofs]*iws[gf];
 
             const auto PSIZE = 3;
-            auto pts_base = elem*num_gf*PSIZE + gf*PSIZE;
+            auto pts_base = gf_offset*PSIZE;
             assert(pts_base + 2 < ppts.size());
             point_3d pt(ppts[pts_base+0], ppts[pts_base+1], ppts[pts_base+2]);
-            new_pe.m_phys_quadpoints.push_back(pt);
+            quadrature_point_3d qpt(pt, iws[gf]*dets[dets_ofs]);
+            new_pe.m_phys_quadpoints.push_back(qpt);
         }
+
+        ret.push_back( std::move(new_pe) );
     }
 
-    return std::vector<physical_element>();
+    return ret;
 }
-
diff --git a/physical_element.h b/physical_element.h
index 8a977a2aedd9c662a3b904ede63eadf9b94fd318..867bec1045bd7e420c8789feb85df0bb244344ab 100644
--- a/physical_element.h
+++ b/physical_element.h
@@ -1,29 +1,46 @@
 #pragma once
 
 #include "point.hpp"
-#include "eigen.h"
-
-using vec_int = std::vector<int>;
-using vec_double = std::vector<double>;
-using vec_point_3d = std::vector<point_3d>;
-using vec_mat3d = eigen_compatible_stdvector<Eigen::Matrix3d>;
+#include "types.h"
 
 class physical_element
 {
-    size_t          m_original_position;
-    int             m_dimension;
-    int             m_orientation;
-    int             m_parent_entity_tag;
-    int             m_element_tag;
-    vec_int         m_node_tags;
-    vec_double      m_determinants;
-    vec_mat3d       m_jacobians;
-    vec_point_3d    m_phys_quadpoints;
-    point_3d        m_barycenter;
+    size_t          m_original_position;    /* Position in GMSH ordering (relative to entity) */
+    int             m_dimension;            /* Geometric dimension */
+    int             m_orientation;          /* GMSH orientation */
+    int             m_parent_entity_tag;    /* Tag of the parent entity */
+    size_t          m_element_tag;          /* Tag of the element */
+    vec_size_t      m_node_tags;            /* Tags of the element nodes */
+    vec_double      m_determinants;         /* SIGNED determinants in the quadrature points */
+    vec_mat3d       m_jacobians;            /* Jacobians in the quadrature points */
+    vec_quadpt_3d   m_phys_quadpoints;      /* Quadrature points in the physical element */
+    point_3d        m_barycenter;           /* Element barycenter */
+    double          m_measure;              /* Element measure */
+    int             m_geometric_order;
+    int             m_approximation_order;
 
 public:
     physical_element();
 
+    size_t          original_position(void) const;
+    int             dimension(void) const;
+    int             orientation(void) const;
+    int             parent_entity_tag(void) const;
+    int             element_tag(void) const;
+    vec_int         node_tags(void) const;
+
+    double          determinant(void) const;
+    vec_double      determinants(void) const;
+
+    mat3d           jacobian(void) const;
+    vec_mat3d       jacobians(void) const;
+
+    vec_quadpt_3d   integration_points(void) const;
+    point_3d        barycenter(void) const;
+    double          measure(void) const;
+
+    /* This class is not intended to be constructed
+     * by the user but by the appropriate factory */
     friend class physical_elements_factory;
 };
 
@@ -32,10 +49,24 @@ class physical_elements_factory
     int         dim;
     int         tag;
     int         elemType;
+    int         geom_order;
+    int         approx_order;
 
 public:
-    physical_elements_factory(int, int, int);
+    physical_elements_factory(const entity_params& ep);
 
     std::vector<physical_element> get_elements();
 };
 
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/point.hpp b/point.hpp
index 5a53b129536e80fe7713c46f2c52d8678a97f774..03662cbd4d6dff47db40cb5298e4a52727c189ec 100644
--- a/point.hpp
+++ b/point.hpp
@@ -217,6 +217,7 @@ class quadrature_point
     point<T, DIM>   p;
     T               w;
 
+public:
     quadrature_point()
     {}
 
diff --git a/reference_element.cpp b/reference_element.cpp
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b91ae6aeed23b6cd6833420544a1b247fae211d1 100644
--- a/reference_element.cpp
+++ b/reference_element.cpp
@@ -0,0 +1,198 @@
+#include "gmsh_io.h"
+#include "reference_element.h"
+
+reference_element::reference_element()
+{}
+
+size_t
+reference_element::num_basis_functions(void) const
+{
+    return m_num_bf;
+}
+
+size_t
+reference_element::num_integration_points(void) const
+{
+    return m_quadpoints.size();
+}
+
+vecxd
+reference_element::basis_functions(size_t iQp) const
+{
+    assert(iQp < num_integration_points());
+    return m_basis_functions.segment(m_num_bf*iQp, m_num_bf);
+}
+
+matxd
+reference_element::basis_gradients(size_t iQp) const
+{
+    assert(iQp < num_integration_points());
+    return m_basis_gradients.block(m_num_bf * iQp, 0, m_num_bf, 3);
+}
+
+vecxd
+reference_element::basis_functions(const point_3d& p) const
+{
+    std::vector<double> ip;
+    ip.push_back(p.x());
+    ip.push_back(p.y());
+    ip.push_back(p.z());
+
+    int bf_nc, bf_no;
+    std::vector<double> basisFunctions;
+    gmm::getBasisFunctions(m_gmsh_elem_type, ip, basis_func_name(m_approx_order), bf_nc,
+        basisFunctions, bf_no);
+
+    assert( basisFunctions.size() == m_num_bf*bf_no );
+    auto bf_ofs = m_num_bf * m_orientation;
+
+    vecxd ret = vecxd::Zero(m_num_bf);
+    for (size_t i = 0; i < m_num_bf; i++)
+        ret(i) = basisFunctions[bf_ofs + i];
+
+    return ret;
+}
+
+matxd
+reference_element::mass_matrix(void) const
+{
+    assert(m_geometric_order == 1);
+    return m_mass_matrix;
+}
+
+matxd
+reference_element::mass_matrix(size_t iQp) const
+{
+    assert(m_geometric_order > 1);
+    return m_mass_matrices.block(m_num_bf*iQp, 0, m_num_bf, m_num_bf);
+}
+
+matxd
+reference_element::mass_matrices(void) const
+{
+    assert(m_geometric_order > 1);
+    return m_mass_matrices;
+}
+
+vec_quadpt_3d
+reference_element::integration_points(void) const
+{
+    return m_quadpoints;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+reference_elements_factory::reference_elements_factory(const entity_params& ep)
+    : dim(ep.dim), tag(ep.tag), elemType(ep.etype), geom_order(ep.gorder),
+      approx_order(ep.aorder)
+{}
+
+std::vector<reference_element>
+reference_elements_factory::get_elements()
+{
+    /* Get integration points and weights on refelem */
+    std::vector<double>     ips;
+    std::vector<double>     iws;
+    gmm::getIntegrationPoints(elemType, quadrature_name(2*approx_order), ips, iws);
+    assert(ips.size() == 3*iws.size());
+
+    auto num_qp = iws.size();
+
+    int bf_nc, num_bfun_orient;
+    std::vector<double> basisFunctions;
+    gmm::getBasisFunctions(elemType, ips, basis_func_name(approx_order), bf_nc,
+        basisFunctions, num_bfun_orient);
+    assert(bf_nc == 1);
+    
+    auto num_bf = basisFunctions.size() / (num_qp * num_bfun_orient);
+    assert( num_bf * num_qp * num_bfun_orient == basisFunctions.size() );
+
+    int num_bgrad_orient;
+    std::vector<double> basisGradients;
+    gmm::getBasisFunctions(elemType, ips, basis_grad_name(approx_order), bf_nc,
+        basisGradients, num_bgrad_orient);
+    assert(bf_nc == 3);
+
+    assert(num_bfun_orient == num_bgrad_orient);
+
+    std::vector<reference_element> ret;
+
+    for (size_t iO = 0; iO < num_bfun_orient; iO++)
+    {
+        /* Create one reference element per orientation */
+        reference_element new_re;
+        new_re.m_approx_order = approx_order;
+        new_re.m_geometric_order = geom_order;
+        new_re.m_dimension = dim;
+        new_re.m_orientation = iO;
+        new_re.m_parent_entity_tag = tag;
+        new_re.m_basis_functions = vecxd::Zero(num_qp*num_bf);
+        new_re.m_basis_gradients = matxd::Zero(num_bf*num_qp, 3);
+        new_re.m_num_bf = num_bf;
+        new_re.m_gmsh_elem_type = elemType;
+
+        auto bf_base = num_bf * num_qp * iO;
+        auto bg_base = 3 * bf_base;
+        for (size_t iQp = 0; iQp < num_qp; iQp++)
+        {
+            for (size_t ibf = 0; ibf < num_bf; ibf++)
+            {
+                new_re.m_basis_functions(num_bf*iQp + ibf) =
+                    basisFunctions[bf_base + num_bf*iQp + ibf];
+                new_re.m_basis_gradients(num_bf*iQp + ibf, 0) =
+                    basisGradients[bg_base + 3*num_bf*iQp + 3*ibf + 0];
+                new_re.m_basis_gradients(num_bf*iQp + ibf, 1) =
+                    basisGradients[bg_base + 3*num_bf*iQp + 3*ibf + 1];
+                new_re.m_basis_gradients(num_bf*iQp + ibf, 2) =
+                    basisGradients[bg_base + 3*num_bf*iQp + 3*ibf + 2];
+            }
+        }
+
+        for (size_t iQp = 0; iQp < num_qp; iQp++)
+        {
+            auto weight = iws[iQp];
+            auto qpt_u = ips[3*iQp+0];
+            auto qpt_v = ips[3*iQp+1];
+            auto qpt_w = ips[3*iQp+2];
+            auto pt = point_3d(qpt_u, qpt_v, qpt_w);
+            new_re.m_quadpoints.push_back( quadrature_point_3d(pt, weight) );       
+        }
+
+        if (new_re.m_geometric_order == 1)
+        {
+            new_re.m_mass_matrix = matxd::Zero(num_bf, num_bf);
+            for (size_t iQp = 0; iQp < num_qp; iQp++)
+            {
+                auto w = new_re.m_quadpoints[iQp].weight();
+                vecxd phi = new_re.m_basis_functions.segment(num_bf*iQp, num_bf);
+                new_re.m_mass_matrix += w * phi * phi.transpose();
+            }
+        }
+        else
+        {
+            new_re.m_mass_matrices = matxd::Zero(num_bf*num_qp, num_bf);
+            for (size_t iQp = 0; iQp < num_qp; iQp++)
+            {
+                auto w = new_re.m_quadpoints[iQp].weight();
+                vecxd phi = new_re.m_basis_functions.segment(num_bf*iQp, num_bf);
+                new_re.m_mass_matrices.block(num_bf*iQp, 0, num_bf, num_bf) =
+                    w * phi * phi.transpose();
+            }
+        }
+
+        ret.push_back(std::move(new_re));
+    }
+
+    return ret;
+}
+
diff --git a/reference_element.h b/reference_element.h
index 990a29db63914cd9ce5358733cf5a874563feca0..446b94c499efde0e52f945d8c9842491756e30c6 100644
--- a/reference_element.h
+++ b/reference_element.h
@@ -1,8 +1,70 @@
 #pragma once
 
-class reference_cell
-{};
+#include "types.h"
 
-class reference_face
-{};
+/*
+static size_t orientations[] = {
+    1, 0, 1, 4,     1, 0, 0, 2,     0, 0, 1, 5,
+    1, 1, 0, 0,     0, 1, 1, 3,     0, 1, 0, 1,
+    3, 2, 1, 4,     3, 2, 0, 2,     2, 0, 3, 5,
+    1, 3, 2, 0,     2, 1, 3, 3,     0, 3, 2, 1,
+    5, 2, 3, 4,     3, 4, 2, 2,     4, 2, 3, 5,
+    3, 5, 2, 0,     2, 3, 5, 3,     2, 3, 4, 1,
+    5, 4, 5, 4,     5, 4, 4, 2,     4, 4, 5, 5,
+    5, 5, 4, 0,     4, 5, 5, 3,     4, 5, 4, 1
+};
+
+#define FACE_ORIENTATION(co, fn) ( orientations[4*co + fn] )
+*/
+
+class reference_element
+{
+    int                 m_approx_order;
+    int                 m_geometric_order;
+    int                 m_dimension;
+    int                 m_orientation;
+    int                 m_gmsh_elem_type;
+    int                 m_parent_entity_tag;
+    size_t              m_num_bf;
+    vecxd               m_basis_functions;
+    matxd               m_basis_gradients;
+    matxd               m_mass_matrix;
+    matxd               m_mass_matrices;
+    vec_quadpt_3d       m_quadpoints;
+
+public:
+    reference_element();
+
+    size_t          num_basis_functions(void) const;
+    size_t          num_integration_points(void) const;
+
+    vec_quadpt_3d   integration_points(void) const;
+
+    vecxd           basis_functions(size_t iQp) const;
+    matxd           basis_gradients(size_t iQp) const;
+
+    vecxd           basis_functions(const point_3d&) const;
+
+    matxd           mass_matrix(void) const;
+    matxd           mass_matrix(size_t iQp) const;
+    matxd           mass_matrices(void) const;
+
+    /* This class is not intended to be constructed
+     * by the user but by the appropriate factory */
+    friend class reference_elements_factory;
+};
+
+class reference_elements_factory
+{
+    int         dim;
+    int         tag;
+    int         elemType;
+    int         geom_order;
+    int         approx_order;
+
+public:
+    reference_elements_factory(const entity_params& ep);
+
+    std::vector<reference_element> get_elements();
+};
 
diff --git a/silo_output.hpp b/silo_output.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..daac379a41959cb29f7a51c59c5ed85d081153c9
--- /dev/null
+++ b/silo_output.hpp
@@ -0,0 +1,182 @@
+#pragma once
+
+#include <cassert>
+#include <silo.h>
+#include "gmsh.h"
+
+#define INVALID_OFS ((size_t) (~0))
+
+class silo
+{
+    std::vector<size_t>     node_tag2ofs;
+    std::vector<double>     node_coords_x;
+    std::vector<double>     node_coords_y;
+    std::vector<double>     node_coords_z;
+
+    std::vector<int>        nodelist;
+    int                     num_cells;
+
+    DBfile                  *m_siloDb;
+
+    void gmsh_get_nodes()
+    {
+        std::vector<size_t>     nodeTags;
+        std::vector<double>     coords;
+        std::vector<double>     paraCoords;
+
+        gmsh::model::mesh::getNodes(nodeTags, coords, paraCoords, -1, -1, true, false);
+
+        auto maxtag_pos = std::max_element(nodeTags.begin(), nodeTags.end());
+        
+        size_t maxtag = 0;
+        if (maxtag_pos != nodeTags.end())
+            maxtag = (*maxtag_pos)+1;
+
+        node_coords_x.resize( nodeTags.size() );
+        node_coords_y.resize( nodeTags.size() );
+        node_coords_z.resize( nodeTags.size() );
+
+        for (size_t i = 0; i < nodeTags.size(); i++)
+        {
+            node_coords_x[i] = coords[3*i+0];
+            node_coords_y[i] = coords[3*i+1];
+            node_coords_z[i] = coords[3*i+2];
+        }
+
+        node_tag2ofs.resize(maxtag, INVALID_OFS);
+        for (size_t i = 0; i < nodeTags.size(); i++)
+            node_tag2ofs.at( nodeTags[i] ) = i;
+    }
+
+    void gmsh_get_elements()
+    {
+        gmsh::vectorpair entities;
+        num_cells = 0;
+        gmsh::model::getEntities(entities, 3);
+        for (auto [dim, tag] : entities)
+        {
+            std::vector<int> elemTypes;
+            gmsh::model::mesh::getElementTypes(elemTypes, dim, tag);
+            for (auto& elemType : elemTypes)
+            {
+                std::vector<size_t> elemTags;
+                std::vector<size_t> elemNodeTags;
+                gmsh::model::mesh::getElementsByType(elemType, elemTags, elemNodeTags, tag);
+                auto nodesPerElem = elemNodeTags.size()/elemTags.size();
+                assert( elemTags.size() * nodesPerElem == elemNodeTags.size() );
+            
+                for (size_t i = 0; i < elemTags.size(); i++)
+                {
+                    auto base = nodesPerElem * i;
+
+                    auto node0_tag = elemNodeTags[base + 0];
+                    assert(node0_tag < node_tag2ofs.size());
+                    auto node0_ofs = node_tag2ofs[node0_tag];
+                    assert(node0_ofs != INVALID_OFS);
+                    nodelist.push_back(node0_ofs + 1);
+
+                    auto node1_tag = elemNodeTags[base + 1];
+                    assert(node1_tag < node_tag2ofs.size());
+                    auto node1_ofs = node_tag2ofs[node1_tag];
+                    assert(node1_ofs != INVALID_OFS);
+                    nodelist.push_back(node1_ofs + 1);
+
+                    auto node2_tag = elemNodeTags[base + 2];
+                    assert(node2_tag < node_tag2ofs.size());
+                    auto node2_ofs = node_tag2ofs[node2_tag];
+                    assert(node2_ofs != INVALID_OFS);
+                    nodelist.push_back(node2_ofs + 1);
+
+                    auto node3_tag = elemNodeTags[base + 3];
+                    assert(node3_tag < node_tag2ofs.size());
+                    auto node3_ofs = node_tag2ofs[node3_tag];
+                    assert(node3_ofs != INVALID_OFS);
+                    nodelist.push_back(node3_ofs + 1);
+                }
+
+                num_cells += elemTags.size();
+            }
+        }
+        std::cout << nodelist.size() << " " << num_cells << std::endl;
+
+        assert(nodelist.size() == 4*num_cells);
+    }
+
+public:
+    silo()
+    {}
+
+    void import_mesh_from_gmsh()
+    {
+        gmsh_get_nodes();
+        gmsh_get_elements();        
+    }
+
+    bool create_db(const std::string& dbname)
+    {
+        m_siloDb = DBCreate(dbname.c_str(), DB_CLOBBER, DB_LOCAL, NULL, DB_HDF5);
+        if (m_siloDb)
+            return true;
+
+        std::cout << "SILO: Error creating database" << std::endl;
+        return false;
+    }
+
+    bool write_mesh(double time = -1.0, int cycle = 0)
+    {
+        DBoptlist   *optlist = nullptr;
+
+        if (time >= 0)
+        {
+            optlist = DBMakeOptlist(2);
+            DBAddOption(optlist, DBOPT_CYCLE, &cycle);
+            DBAddOption(optlist, DBOPT_DTIME, &time);
+        }
+
+        double *coords[] = {node_coords_x.data(), node_coords_y.data(), node_coords_z.data()};
+
+        int lnodelist = nodelist.size();
+
+        int shapetype[] = { DB_ZONETYPE_TET };
+        int shapesize[] = {4};
+        int shapecounts[] = { num_cells };
+        int nshapetypes = 1;
+        int nnodes = node_coords_x.size();
+        int nzones = num_cells;
+        int ndims = 3;
+
+        DBPutZonelist2(m_siloDb, "zonelist", nzones, ndims,
+            nodelist.data(), lnodelist, 1, 0, 0, shapetype, shapesize,
+            shapecounts, nshapetypes, NULL);
+
+        DBPutUcdmesh(m_siloDb, "mesh", ndims, NULL, coords, nnodes, nzones,
+            "zonelist", NULL, DB_DOUBLE, optlist);
+
+        if (optlist)
+            DBFreeOptlist(optlist);
+
+        return true;
+    }
+
+    bool write_zonal_variable(const std::string& name, const std::vector<double>& data)
+    {
+        if (data.size() != num_cells)
+        {
+            std::cout << "Invalid number of cells" << std::endl;
+            return false;
+        }
+
+        DBPutUcdvar1(m_siloDb, name.c_str(), "mesh", data.data(), data.size(), NULL,
+                     0, DB_DOUBLE, DB_ZONECENT, NULL);
+        
+        return true;
+    }
+
+    void close_db()
+    {
+        if (m_siloDb)
+            DBClose(m_siloDb);
+
+        m_siloDb = nullptr;
+    }
+};
diff --git a/test.cpp b/test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4b162a87fc8beeead3061fb39d5df004363a74f3
--- /dev/null
+++ b/test.cpp
@@ -0,0 +1,345 @@
+#include <iostream>
+
+#include <unistd.h>
+
+#include "test.h"
+#include "gmsh_io.h"
+#include "silo_output.hpp"
+#include "sgr.hpp"
+#include "entity_data.h"
+#include "kernels_cpu.h"
+
+using namespace sgr;
+
+#define SPHERE_RADIUS 0.2
+
+static void
+resize_mesh(double mesh_h)
+{
+    gvp_t vp;
+    gm::getEntities(vp);
+    gmm::setSize(vp, mesh_h);
+}
+
+static void
+make_geometry(int order, double mesh_h)
+{
+    gm::add("difftest");
+
+    std::vector<std::pair<int,int>> objects;
+    objects.push_back(
+        std::make_pair(3, gmo::addBox(0.0, 0.0, 0.0, 1.0, 1.0, 1.0) )
+        );
+    //objects.push_back(
+    //    std::make_pair(3, gmo::addSphere(0.5, 0.5, 0.5, SPHERE_RADIUS) )
+    //    );
+
+    //std::vector<std::pair<int, int>> tools;
+    //gmsh::vectorpair odt;
+    //std::vector<gmsh::vectorpair> odtm;
+    //gmo::fragment(objects, tools, odt, odtm);
+
+    gmo::synchronize();
+
+    resize_mesh(mesh_h);
+}
+
+
+int test_basics(int geometric_order, int approximation_order)
+{
+    model mod(geometric_order, approximation_order);
+
+    auto idx = geometric_order - 1;
+
+    std::cout << cyanfg << "Testing geometric order " << geometric_order;
+    std::cout << ", approximation order = " << approximation_order << nofg;
+    std::cout << std::endl;
+
+    double vol_e0 = (4.0/3.0)*M_PI*SPHERE_RADIUS*SPHERE_RADIUS*SPHERE_RADIUS;
+    double vol_e1 = 1.0 - vol_e0;
+
+    /*****************************************/
+    std::cout << "Volumes by reference quadrature points and determinants" << std::endl;
+    std::vector<double> t1_vals { 0.07, 0.00145, 0.00021, 1.83e-5 };
+    COMPARE_VALUES_RELATIVE("Entity 0 vol r+d", mod.entity_at(0).measure(), vol_e0, t1_vals[idx]);
+    std::vector<double> t2_vals { 0.024, 5.02e-5, 7.003e-06, 6.35e-7 };
+    COMPARE_VALUES_RELATIVE("Entity 1 vol r+d", mod.entity_at(1).measure(), vol_e1, t2_vals[idx]);
+
+    /*****************************************/
+    std::cout << "Volumes by physical quadrature points" << std::endl;
+    double vol_e0_pqp = 0.0;
+    auto& e0 = mod.entity_at(0);
+    for (const auto& e : e0)
+    {
+        const auto& pqps = e.integration_points();
+        for (const auto& qp : pqps)
+            vol_e0_pqp += qp.weight(); 
+    }
+    COMPARE_VALUES_RELATIVE("Entity 0 vol pqp", vol_e0_pqp, vol_e0, t1_vals[idx]);
+
+    double vol_e1_pqp = 0.0;
+    auto& e1 = mod.entity_at(1);
+    for (const auto& e : e1)
+    {
+        const auto& pqps = e.integration_points();
+        for (const auto& qp : pqps)
+            vol_e1_pqp += qp.weight(); 
+    }
+    COMPARE_VALUES_RELATIVE("Entity 1 vol pqp", vol_e1_pqp, vol_e1, t2_vals[idx]);
+
+    /*****************************************/
+    std::cout << "Volumes by jacobians" << std::endl;
+    double vol_e0_j = 0.0;
+    for (const auto& e : e0)
+    {
+        const auto& js = e.jacobians();
+        auto& re = e0.refelem(e);
+        auto rqps = re.integration_points();
+        auto dets = e.determinants();
+        for (size_t i = 0; i < js.size(); i++)
+        {
+            double dj = js[i].determinant();
+            double dg = dets[i];
+            auto relerr = dj/dg - 1;
+            assert( std::abs(relerr) < 3e-14 );
+            vol_e0_j += dj*rqps[i].weight();
+        }
+    }
+    COMPARE_VALUES_RELATIVE("Entity 0 vol j", vol_e0_j, vol_e0, t1_vals[idx]);
+
+    double vol_e1_j = 0.0;
+    for (const auto& e : e1)
+    {
+        const auto& js = e.jacobians();
+        auto& re = e1.refelem(e);
+        auto rqps = re.integration_points();
+        auto dets = e.determinants();
+        for (size_t i = 0; i < js.size(); i++)
+        {
+            double dj = js[i].determinant();
+            double dg = dets[i];
+            auto relerr = dj/dg - 1;
+            assert( std::abs(relerr) < 3e-14 );
+            vol_e1_j += dj*rqps[i].weight();
+        }
+    }
+    COMPARE_VALUES_RELATIVE("Entity 1 vol j", vol_e1_j, vol_e1, t2_vals[idx]);
+
+    /*****************************************/
+    std::cout << "Volumes by integrating f(x) = 1 over the domain" << std::endl;
+    double vol_e0_mass = 0.0;
+    auto f = [](const point_3d& pt) -> double { return 1; };
+    vecxd f_e0 = e0.project(f);
+    for (size_t iT = 0; iT < e0.num_elements(); iT++)
+    {
+        auto& pe = e0.elem(iT);
+        auto& re = e0.refelem(pe);
+        auto num_bf = re.num_basis_functions();
+        matxd mass = e0.mass_matrix(iT);
+        vecxd f_pe = f_e0.segment(num_bf*iT, num_bf);
+        vol_e0_mass += f_pe.transpose() * mass * f_pe;
+    }
+    COMPARE_VALUES_RELATIVE("Entity 0 vol mass", vol_e0_mass, vol_e0, t1_vals[idx]);
+
+    double vol_e1_mass = 0.0;
+    vecxd f_e1 = e1.project(f);
+    for (size_t iT = 0; iT < e1.num_elements(); iT++)
+    {
+        auto& pe = e1.elem(iT);
+        auto& re = e1.refelem(pe);
+        auto num_bf = re.num_basis_functions();
+        matxd mass = e1.mass_matrix(iT);
+        vecxd f_pe = f_e1.segment(num_bf*iT, num_bf);
+        vol_e1_mass += f_pe.transpose() * mass * f_pe;
+    }
+    COMPARE_VALUES_RELATIVE("Entity 1 vol mass", vol_e1_mass, vol_e1, t2_vals[idx]);
+
+    return 0;
+}
+
+#define OVERINTEGRATE
+int test_mass_convergence(int geometric_order, int approximation_order)
+{
+    std::vector<double> sizes({ 0.32, 0.16, 0.08, 0.04 });
+    std::vector<double> errors;
+
+    std::cout << cyanfg << "Testing geometric order " << geometric_order;
+    std::cout << ", approximation order = " << approximation_order << nofg;
+    std::cout << std::endl;
+
+    auto test_f = [](const point_3d& pt) -> double {
+        return std::sin(M_PI*pt.x())*std::sin(M_PI*pt.y())*std::sin(M_PI*pt.z()); 
+    };
+
+    for (size_t i = 0; i < sizes.size(); i++)
+    {
+        double h = sizes[i];
+        make_geometry(0,h);
+        model mod(geometric_order, approximation_order);
+
+        auto& e0 = mod.entity_at(0);
+        e0.sort_by_orientation();
+        vecxd test_f_e0 = e0.project(test_f);
+        double proj_error_e0 = 0.0;
+#ifdef OVERINTEGRATE
+        auto [rps, pqps] = integrate(e0, 2*approximation_order+1);
+        auto num_qp = rps.size();
+#endif
+        for (size_t iT = 0; iT < e0.num_elements(); iT++)
+        {
+            auto& pe = e0.elem(iT);
+            auto& re = e0.refelem(pe);
+            auto num_bf = re.num_basis_functions();
+
+            vecxd pvals = test_f_e0.segment(iT*num_bf, num_bf);
+#ifdef OVERINTEGRATE
+            for (size_t iQp = 0; iQp < num_qp; iQp++)
+            {
+                vecxd phi = re.basis_functions( rps[iQp] );
+                auto pqpofs = num_qp*iT + iQp; 
+                double diff = (pvals.dot(phi) - test_f(pqps[pqpofs].point()));
+                proj_error_e0 += std::abs(pqps[pqpofs].weight())*diff*diff;
+            }
+#else
+            auto pqps = pe.integration_points();
+            for (size_t iQp = 0; iQp < pqps.size(); iQp++)
+            {
+                vecxd phi = re.basis_functions( iQp );
+                double diff = (pvals.dot(phi) - test_f(pqps[iQp].point()));
+                proj_error_e0 += std::abs(pqps[iQp].weight())*diff*diff;
+            }
+#endif
+        }
+
+        errors.push_back( std::sqrt(proj_error_e0) );
+    }
+
+    for (auto& error : errors)
+        std::cout << error << " ";
+    std::cout << std::endl;
+
+    double rate = 0.0;
+    for (size_t i = 1; i < errors.size(); i++)
+        std::cout << (rate = std::log2(errors[i-1]/errors[i])) << " ";
+    std::cout << std::endl;
+
+    COMPARE_VALUES_ABSOLUTE("Projection", rate, double(approximation_order+1), 0.2);
+
+    return 0;
+}
+
+int test_differentiation_convergence(int geometric_order, int approximation_order)
+{
+    std::vector<double> sizes({ 0.32, 0.16, 0.08, 0.04 });
+    std::vector<double> errors;
+
+    std::cout << cyanfg << "Testing geometric order " << geometric_order;
+    std::cout << ", approximation order = " << approximation_order << nofg;
+    std::cout << std::endl;
+
+    auto f = [](const point_3d& pt) -> double {
+        return std::sin(M_PI*pt.x())*std::sin(M_PI*pt.y())*std::sin(M_PI*pt.z()); 
+    };
+    auto df_dx = [](const point_3d& pt) -> double {
+        return M_PI*std::cos(M_PI*pt.x())*std::sin(M_PI*pt.y())*std::sin(M_PI*pt.z()); 
+    };
+    auto df_dy = [](const point_3d& pt) -> double {
+        return M_PI*std::sin(M_PI*pt.x())*std::cos(M_PI*pt.y())*std::sin(M_PI*pt.z()); 
+    };
+    auto df_dz = [](const point_3d& pt) -> double {
+        return M_PI*std::sin(M_PI*pt.x())*std::sin(M_PI*pt.y())*std::cos(M_PI*pt.z()); 
+    };
+
+    for (size_t i = 0; i < sizes.size(); i++)
+    {
+        double h = sizes[i];
+        make_geometry(0,h);
+        model mod(geometric_order, approximation_order);
+
+        auto& e0 = mod.entity_at(0);
+        e0.sort_by_orientation();
+        vecxd Pf_e0 = e0.project(f);
+        vecxd df_dx_e0 = vecxd::Zero( Pf_e0.rows() );
+        vecxd df_dy_e0 = vecxd::Zero( Pf_e0.rows() );
+        vecxd df_dz_e0 = vecxd::Zero( Pf_e0.rows() );
+
+        entity_data_cpu<1> ed;
+        e0.populate_entity_data(ed);
+
+        compute_field_derivatives(ed, Pf_e0, df_dx_e0, df_dy_e0, df_dz_e0);
+
+        vecxd Pdf_dx_e0 = e0.project(df_dx);
+        vecxd Pdf_dy_e0 = e0.project(df_dy);
+        vecxd Pdf_dz_e0 = e0.project(df_dz);
+
+        double err_x = 0.0;
+        double err_y = 0.0;
+        double err_z = 0.0;
+
+        for (size_t iT = 0; iT < e0.num_elements(); iT++)
+        {
+            auto& pe = e0.elem(iT);
+            auto& re = e0.refelem(pe);
+            matxd mass = e0.mass_matrix(iT);
+            auto num_bf = re.num_basis_functions();
+            vecxd diff_x = Pdf_dx_e0.segment(iT*num_bf, num_bf) - df_dx_e0.segment(iT*num_bf, num_bf); 
+            vecxd diff_y = Pdf_dy_e0.segment(iT*num_bf, num_bf) - df_dy_e0.segment(iT*num_bf, num_bf); 
+            vecxd diff_z = Pdf_dz_e0.segment(iT*num_bf, num_bf) - df_dz_e0.segment(iT*num_bf, num_bf); 
+        
+            err_x += diff_x.dot(mass*diff_x);
+            err_y += diff_y.dot(mass*diff_y);
+            err_z += diff_z.dot(mass*diff_z);
+        }
+
+        std::cout << std::sqrt(err_x) << " " << std::sqrt(err_y) << " " << std::sqrt(err_z) << std::endl;
+    }
+
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    gmsh::initialize();
+    gmsh::option::setNumber("General.Terminal", 0);
+    gmsh::option::setNumber("Mesh.Algorithm", 1);
+    gmsh::option::setNumber("Mesh.Algorithm3D", 1);
+
+    int failed_tests = 0;
+
+    /*
+    for (size_t go = 1; go < 5; go++)
+    {
+        for (size_t ao = go; ao < 5; ao++)
+        {
+            make_geometry(0, 0.1);
+            failed_tests += test_basics(go, ao);
+        }
+    }
+    */
+   
+    /*
+    for (size_t go = 1; go < 5; go++)
+    {
+        for (size_t ao = go; ao < 5; ao++)
+            failed_tests += test_mass_convergence(go, ao);
+    }
+    */
+
+    for (size_t ao = 1; ao < 5; ao++)
+        failed_tests += test_differentiation_convergence(1, ao);
+
+    /*****************************************/
+    
+    //COMPARE_VALUES_RELATIVE("Entity 0 vol mass", vol_e0_mass, vol_e0, t1_vals[order-1]);
+
+    #if 0
+
+
+
+    #endif
+
+    gmsh::finalize();
+    return failed_tests;
+}
+
+
diff --git a/test.h b/test.h
new file mode 100644
index 0000000000000000000000000000000000000000..7a9dd89706de43d661379dba383df2b931b6154f
--- /dev/null
+++ b/test.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#define COMPARE_VALUES_ABSOLUTE(name, val, ref, tol)                    \
+    std::cout << "Test: " << name << "...";                             \
+    if ( double real_tol = std::abs(val - ref); real_tol < tol )        \
+    { std::cout << greenfg << "PASS" << nofg << " (val = " << val;      \
+      std::cout << ", ref = " << ref << ", tol = " << tol;              \
+      std::cout << ", real_tol = " << real_tol << std::endl; }          \
+    else                                                                \
+    { std::cout << redfg << "FAIL" << nofg << " (val = " << val;        \
+      std::cout << ", ref = " << ref << ", tol = " << tol;              \
+      std::cout << ", real_tol = " << real_tol << std::endl; return 1; }
+
+#define COMPARE_VALUES_RELATIVE(name, val, ref, tol)                                \
+    std::cout << "Test: " << name << "...";                                         \
+    if ( double real_tol = (std::abs(val - ref)/std::abs(ref)); real_tol < tol )    \
+    { std::cout << greenfg << "PASS" << nofg << " (val = " << val;                  \
+      std::cout << ", ref = " << ref << ", tol = " << tol;                          \
+      std::cout << ", real_tol = " << real_tol << std::endl; }                      \
+    else                                                                            \
+    { std::cout << redfg << "FAIL" << nofg << " (val = " << val;        \
+      std::cout << ", ref = " << ref << ", tol = " << tol;              \
+      std::cout << ", real_tol = " << real_tol << std::endl; return 1; }
+
diff --git a/types.h b/types.h
new file mode 100644
index 0000000000000000000000000000000000000000..64116d921fca3de6269b269b3d125f4cffc05f21
--- /dev/null
+++ b/types.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "point.hpp"
+#include "eigen.h"
+
+using vec_int = std::vector<int>;
+using vec_size_t = std::vector<size_t>;
+using vec_double = std::vector<double>;
+using vec_point_3d = std::vector<point_3d>;
+using vec_quadpt_3d = std::vector<quadrature_point_3d>;
+using mat3d = Eigen::Matrix<double, 3, 3, Eigen::RowMajor>;
+using vec_mat3d = eigen_compatible_stdvector<mat3d>;
+using vecxd = Eigen::Matrix<double, Eigen::Dynamic, 1>;
+using matxd = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
+
+struct entity_params
+{
+    int     dim;
+    int     tag;
+    int     etype;
+    int     gorder;
+    int     aorder;
+};
+