diff --git a/postprocessing/CMakeLists.txt b/postprocessing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6cb24ddeff853efd53419e1cf838ecfce8c0f455
--- /dev/null
+++ b/postprocessing/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(vtu-compare)
diff --git a/postprocessing/vtu-compare/CMakeLists.txt b/postprocessing/vtu-compare/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d2a981579b7c51052479c44586c47c42688a1891
--- /dev/null
+++ b/postprocessing/vtu-compare/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.8)
+
+project(vtu-compare)
+
+add_executable(${PROJECT_NAME}
+   main.cpp
+)
+
+# VTK
+find_package(VTK)
+include_directories(${VTK_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})
diff --git a/postprocessing/vtu-compare/main.cpp b/postprocessing/vtu-compare/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b790b0d495a92154194cf7e2831aa02c416bf5bd
--- /dev/null
+++ b/postprocessing/vtu-compare/main.cpp
@@ -0,0 +1,351 @@
+//#include <vtkXMLUnstructuredGrid.h>
+#include <vtkStructuredGrid.h>
+#include <vtkImageData.h>
+//#include <vtkXMLStructuredGridWriter.h>
+#include <vtkXMLUnstructuredGridReader.h>
+#include <vtkXMLImageDataReader.h>
+#include <vtkHexahedron.h>
+#include <vtkPointData.h>
+#include <vtkDoubleArray.h>
+#include <vtkPolyData.h>
+#include <vtkSmartPointer.h>
+#include <vtkPoints.h>
+#include <cmath>
+#include <vtkCellData.h>
+#include <vtkUnstructuredGrid.h>
+#include <vtkDataSetSurfaceFilter.h>
+
+#include <iostream>
+#include <iomanip>
+// the data for one timestep
+struct CellData
+{
+  std::vector<double> u;
+  std::vector<double> v;
+  std::vector<double> p;
+
+  std::vector< std::array<double, 3 > > cellCenters;
+
+  size_t size;
+
+  template<typename T>
+  void resize( const T s )
+  {
+    assert( s > -1 );
+    size = size_t(s);
+    u.resize( size );
+    v.resize( size );
+    p.resize( size );
+
+    cellCenters.resize( size );
+  }
+
+};
+
+
+struct ErrorsBase
+{
+  double l1;
+  double l2;
+  double linf;
+
+  ErrorsBase() : l1(0.), l2(0.), linf( 0.) {}
+};
+
+
+struct Errors
+{
+  ErrorsBase abs;
+  ErrorsBase rel;
+};
+
+void checkCellCenters(const CellData& cellDataA,
+                      const CellData& cellDataB )
+{
+//  if ( pda->Get )
+//vtkPolyData* asdf;
+//asdf->GetPo
+  if (cellDataA.size != cellDataB.size )
+  {
+    throw std::runtime_error("Datasets have different sizes!");
+  }
+  constexpr double prec = 1e-8;
+
+  double normLinf = std::numeric_limits<double>::min();
+  double normLinfB = std::numeric_limits<double>::min();
+
+  for (size_t i = 0; i< cellDataA.size; ++i)
+  {
+    const auto& pa = cellDataA.cellCenters[i];
+    const auto& pb = cellDataB.cellCenters[i];
+
+    double normLinfLocal = std::numeric_limits<double>::min();
+    for (int d = 0; d < 3; ++d)
+    {
+      normLinfLocal = std::max( std::fabs(pa[d] - pb[d]), normLinfLocal );
+      normLinfB = std::max( std::fabs(pb[d]), normLinfB);
+    }
+    normLinf = std::max( normLinfLocal, normLinf );
+    if ( normLinfLocal > prec )
+    {
+      std::cout << "Point " << i << "deviates!" << std::endl
+                <<"  Dataset A: " << pa[0] << ", " << pa[1] << ", " << pa[2] << std::endl
+                <<"  Dataset B: " << pb[0] << ", " << pb[1] << ", " << pb[2] << std::endl;
+    }
+  }
+  const double normLinfRel = normLinf / normLinfB;
+//  std::cout << "Coordinate deviations: " << std::endl
+//            << "  linf-norm: " << normLinf << " (abs), "
+//            << normLinfRel << "(rel)" << std::endl;
+
+  if ( normLinfRel > prec )
+  {
+    throw std::runtime_error("Coordinates deviate too much!");
+  }
+
+}
+
+vtkPolyData* getPolyDataFromFile( const std::string& filename )
+{
+  vtkSmartPointer<vtkXMLUnstructuredGridReader> reader = vtkSmartPointer<vtkXMLUnstructuredGridReader>::New();
+
+  reader->SetFileName(filename.c_str());
+  reader->Update();
+
+  const auto grid = reader->GetOutput();
+
+  vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter =
+      vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
+  surfaceFilter->SetInputData(grid);
+  surfaceFilter->Update();
+
+  return surfaceFilter->GetOutput();
+}
+
+void parseData(const std::string& filename,
+               CellData& cellData)
+{
+
+  vtkSmartPointer<vtkXMLUnstructuredGridReader> reader = vtkSmartPointer<vtkXMLUnstructuredGridReader>::New();
+
+  reader->SetFileName(filename.c_str());
+  reader->Update();
+
+  const auto grid = reader->GetOutput();
+
+  vtkSmartPointer<vtkDataSetSurfaceFilter> surfaceFilter =
+      vtkSmartPointer<vtkDataSetSurfaceFilter>::New();
+  surfaceFilter->SetInputData(grid);
+  surfaceFilter->Update();
+
+  surfaceFilter->GetOutput();
+  vtkPolyData* polydata = surfaceFilter->GetOutput();
+
+  std::cout << "Output has " << polydata->GetNumberOfPoints() << " points." << std::endl;
+
+
+//  FindAllData( polydata );
+
+
+  vtkIdType pressureId = -1;
+  vtkIdType velocityId = -1;
+  {
+    const vtkIdType numberOfCellArrays = polydata->GetCellData()->GetNumberOfArrays();
+    for(vtkIdType i = 0; i < numberOfCellArrays; i++)
+    {
+      // The following two lines are equivalent
+      //polydata->GetPointData()->GetArray(i)->GetName();
+      //polydata->GetPointData()->GetArrayName(i);
+      const std::string arrayName = polydata->GetCellData()->GetArrayName(i);
+      if ( arrayName == "p" )
+        pressureId = i;
+      else if ( arrayName == "velocity_liq (m/s)" )
+        velocityId = i;
+//      int dataTypeID = polydata->GetCellData()->GetArray(i)->GetDataType();
+//      std::cout << "Array " << i << ": " << polydata->GetCellData()->GetArrayName(i)
+//                << " (type: " << dataTypeID << ")" << std::endl;
+    }
+  }
+  assert( pressureId != -1 );
+  assert( velocityId != -1 );
+
+  //Parse data
+  {
+    const auto pressureArray = polydata->GetCellData()->GetArray(pressureId);
+    const auto velocityArray = polydata->GetCellData()->GetArray(velocityId);
+
+    const auto pressureArraySize = pressureArray->GetNumberOfTuples();
+    const auto velocityArraySize = velocityArray->GetNumberOfTuples();
+    assert( velocityArraySize == pressureArraySize );
+    cellData.resize( velocityArraySize );
+
+    std::array<double, 3> velocityVal;
+    std::array<double, 1> pressureVal;
+
+    for (vtkIdType i = 0; i < pressureArraySize; ++i)
+    {
+      pressureArray->GetTuple( i, pressureVal.data() );
+      velocityArray->GetTuple( i, velocityVal.data() );
+
+      cellData.p[i] = pressureVal[0];
+
+      cellData.u[i] = velocityVal[0];
+      cellData.v[i] = velocityVal[1];
+
+
+      std::array<double, 6> bounds;
+      polydata->GetCellBounds( i, bounds.data() );
+      for (int j = 0; j < 3; ++j) {
+        cellData.cellCenters[i][j] = bounds[j*2] + bounds[j*2+1];
+        cellData.cellCenters[i][j] *= 0.5;
+      }
+
+
+    }
+
+  }
+}
+
+
+Errors computePressureError(const CellData& dataSetA,
+                    const CellData& dataSetB)
+{
+  assert( dataSetA.size == dataSetB.size );
+  Errors errorsPressure;
+  ErrorsBase errP;
+
+  for (size_t i = 0; i < dataSetA.size; ++i)
+  {
+    errorsPressure.abs.l1 += std::fabs(dataSetA.p[i] - dataSetB.p[i]);
+
+    errP.l1 += std::fabs( dataSetB.p[i] );
+
+    errorsPressure.abs.l2 += std::pow(dataSetA.p[i] - dataSetB.p[i], 2);
+    errP.l2 += std::pow( dataSetB.p[i], 2 );
+
+    errorsPressure.abs.linf = std::max( std::fabs(dataSetA.p[i] - dataSetB.p[i] ),
+                                       errorsPressure.abs.linf );
+
+    errP.linf = std::max( std::fabs(dataSetB.p[i]), errP.linf );
+
+    constexpr double tol = 1e-10;
+    if ( std::fabs(dataSetA.p[i] - dataSetB.p[i] ) > tol )
+    {
+//      std::cout << "Large error " << std::fabs(dataSetA.p[i] - dataSetB.p[i] ) << " (p) at Point " << i << std::endl
+//                << "  Coords: " << dataSetA.cellCenters[i][0] << ", "
+//                << dataSetA.cellCenters[i][1] << ", "
+//                << dataSetA.cellCenters[i][2] << ", "
+//                << std::endl;
+    }
+  }
+  errorsPressure.abs.l2 = std::sqrt(errorsPressure.abs.l2);
+
+  errP.l2 = std::sqrt( errP.l2 );
+
+  errorsPressure.rel.l1 = errorsPressure.abs.l1 / errP.l1;
+  errorsPressure.rel.l2 = errorsPressure.abs.l2 / errP.l2;
+  errorsPressure.rel.linf = errorsPressure.abs.linf / errP.linf;
+
+  std::cout << "Errors: " << std::endl
+            << "  p: " << std::endl
+            << "     (abs)        (rel)       (norm dataset 2)" << std::endl
+            << "    " << errorsPressure.abs.l1 << " (l1), " << errorsPressure.rel.l1 << ", " << errP.l1 << std::endl
+            << "    " << errorsPressure.abs.l2 << " (l2), " << errorsPressure.rel.l2 << ", " << errP.l2 << std::endl
+            << "    " << errorsPressure.abs.linf << " (linf), " << errorsPressure.rel.linf << ", " << errP.linf << std::endl;
+
+  return errorsPressure;
+}
+
+Errors computeVelocityError(const CellData& dataSetA,
+                            const CellData& dataSetB)
+{
+  assert( dataSetA.size == dataSetB.size );
+  Errors errorsVelocity;
+  ErrorsBase errV;
+  for (size_t i = 0; i < dataSetA.size; ++i)
+  {
+    errorsVelocity.abs.l1 += std::fabs(dataSetA.u[i] - dataSetB.u[i])
+                             + std::fabs(dataSetA.v[i] - dataSetB.v[i]) ;
+    errV.l1 += std::fabs( dataSetB.u[i] ) + std::fabs( dataSetB.v[i] );
+
+    errorsVelocity.abs.l2 += std::pow(dataSetA.u[i] - dataSetB.u[i], 2)
+                             + std::pow(dataSetA.v[i] - dataSetB.v[i], 2) ;
+
+    errV.l2 += std::pow( dataSetB.u[i], 2 ) + std::pow( dataSetB.v[i], 2 );
+
+    const double u = std::fabs(dataSetA.u[i] - dataSetB.u[i]);
+    const double v = std::fabs(dataSetA.v[i] - dataSetB.v[i]);
+    const double uvMax = std::max( u, v );
+    errorsVelocity.abs.linf = std::max( uvMax, errorsVelocity.abs.linf );
+
+    errV.linf = std::max( std::fabs(dataSetB.u[i]), errV.linf );
+    errV.linf = std::max( std::fabs(dataSetB.v[i]), errV.linf );
+
+    constexpr double tol = 1e-10;
+    if ( uvMax > tol )
+    {
+//      std::cout << "Large error " << uvMax << " at Point " << i << std::endl
+//                << "  Coords: " << dataSetA.cellCenters[i][0] << ", "
+//                << dataSetA.cellCenters[i][1] << ", "
+//                << dataSetA.cellCenters[i][2] << ", "
+//                << std::endl;
+    }
+  }
+  errorsVelocity.abs.l2 = std::sqrt(errorsVelocity.abs.l2);
+
+  errV.l2 = std::sqrt( errV.l2 );
+
+  errorsVelocity.rel.l1 = errorsVelocity.abs.l1 / errV.l1;
+  errorsVelocity.rel.l2 = errorsVelocity.abs.l2 / errV.l2;
+  errorsVelocity.rel.linf = errorsVelocity.abs.linf / errV.linf;
+
+  std::cout << "Errors: " << std::endl
+            << "  vel: " << std::endl
+            << "     (abs)        (rel)       (norm dataset 2)" << std::endl
+            << "    " << errorsVelocity.abs.l1 << " (l1), " << errorsVelocity.rel.l1 << ", " << errV.l1 << std::endl
+            << "    " << errorsVelocity.abs.l2 << " (l2), " << errorsVelocity.rel.l2 << ", " << errV.l2 << std::endl
+            << "    " << errorsVelocity.abs.linf << " (linf), " << errorsVelocity.rel.linf << ", " << errV.linf << std::endl;
+
+  return errorsVelocity;
+}
+
+std::tuple<Errors, Errors> computeErrors(const CellData& dataA, const CellData& dataB)
+{
+  Errors pressureErrors;
+  Errors velocityErrors;
+  //Stokes error
+  {
+    checkCellCenters( dataA, dataB );
+    pressureErrors = computePressureError( dataA, dataB );
+    velocityErrors = computeVelocityError( dataA, dataB );
+  }
+
+
+  return std::make_tuple( pressureErrors, velocityErrors );
+}
+
+int main(int argc, char *argv[])
+{
+  // parse command line arguments
+  if (argc != 3)
+  {
+    std::cout << "usage: " << argv[0] << " <1. VTK filename> <2. VTK filename>" << std::endl;
+    exit(-1);
+  }
+
+  const std::string filename1(argv[1]);
+  const std::string filename2(argv[2]);
+
+  CellData parsedData1;
+  CellData parsedData2;
+
+  parseData( filename1, parsedData1 );
+  parseData( filename2, parsedData2 );
+
+  Errors pressureErrors;
+  Errors velocityErrors;
+  std::tie( pressureErrors, velocityErrors ) = computeErrors( parsedData1, parsedData2 );
+
+  return EXIT_SUCCESS;
+}
+