diff --git a/Dockerfile b/Dockerfile
index d9dcee82..78283c11 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,29 +1,31 @@
 FROM debian:stable-slim
 
 MAINTAINER Lucas Frérot <lucas.frerot@epfl.ch>
 
 # Add contrib and non-free
 RUN sed -i 's/main/main contrib non-free/' /etc/apt/sources.list
 
 # Install any needed packages from ubuntu repos
 RUN apt-get -qq update && apt-get install -y \
     curl \
     gcc \
     git \
     libboost-dev \
     libpython3-dev \
     libthrust-dev \
     libfftw3-dev \
     python3 \
     python3-dev \
     python3-numpy \
     python3-pip \
     python3-scipy \
     python3-h5py \
+    python3-netcdf4 \
     python3-phabricator \
     python3-click \
     python3-yaml \
+    python3-matplotlib \
     scons \
   && rm -rf /var/lib/apt/lists/*
 
-RUN pip3 install pytest
+RUN pip3 install pytest uvw
diff --git a/README.md b/README.md
index dc4d2882..ed8e8922 100644
--- a/README.md
+++ b/README.md
@@ -1,187 +1,187 @@
 Tamaas --- A blazingly fast rough contact library
 =================================================
 
 Tamaas is a C++/Python library that implements a number of numerical methods
 based on integral equations to efficiently solve contact problems with rough
 surfaces. The word تماس (tamaas) means "contact" in Arabic and Farsi.
 
 ## Dependencies
 
 Here is a list of dependencies for Tamaas:
 
 - a //C++ compiler// with full //C++14// and //OpenMP// support
 - [SCons](https://scons.org/) (python build system)
 - [FFTW3](http://www.fftw.org/) compiled with //OpenMP// support
 - [boost](https://www.boost.org/) (preprocessor)
 - [thrust](https://github.com/thrust/thrust) (1.9.2+)
 - [python 3+](https://www.python.org/) (probably works with python 2, but it
   is not tested) with [numpy](https://numpy.org/)
 - [pybind11](https://github.com/pybind/pybind11) (included as submodule)
 - [expolit](https://c4science.ch/source/expolit/) (included as submodule)
 
 Optional dependencies are:
 
 - [scipy](https://scipy.org) (for nonlinear solvers)
 - [uvw](https://pypi.org/project/uvw/) (for dumpers)
 - [googletest](https://github.com/google/googletest) and
   [pytest](https://docs.pytest.org/en/latest/) (for tests)
 - [Doxygen](http://doxygen.nl/) and
   [Sphinx](https://www.sphinx-doc.org/en/stable/) (for documentation)
 
 Note that a Debian distribution should have the right packages for all these
 dependencies (they package the right version of thrust extracted from CUDA in
 `stretch-backports non-free` and `buster non-free`).
 
 ## Compiling
 
 You should first clone the git submodules that are dependencies to tamaas
 (expolit, pybind11 and googletest):
 
     git submodule update --init --recursive
 
 The build system uses SCons. In order to compile Tamaas with the default
 options:
 
     scons
 
 After compiling a first time, you can edit the compilation options in the file
 `build-setup.conf`, or alternatively supply the options directly in the command
 line:
 
     scons option=value [...]
 
 To get a list of //all// build options and their possible values, you can run
 `scons -h`. You can run `scons -H` to see the SCons-specific options (among them
 `-j n` executes the build with `n` threads and `-c` cleans the build). Note that
 the build is aware of the `CXX` and `CXXFLAGS` environment variables.
 
 ## Installing
 
 Before you can import tamaas in python, you need to install the python package
 in some way.
 
 ### Using pip
 
 You have two choices to install tamaas:
 
 - An out-of-repository installation to a given prefix (e.g. `/usr/local`, or a
   python virtual environment)
 - A development installation to `~/.local` which links to the build directory
 
 The former is simply achieved with:
 
     scons prefix=/your/prefix install
 
     # Equivalent to (if you build in release)
     install build-release/src/libTamaas.so* /your/prefix/lib
-    pip install --prefix /your/prefix build-release/python
+    pip3 install --prefix /your/prefix build-release/python
 
 Make sure however that `/your/prefix/lib` is in your `LD_LIBRARY_PATH` or is a
 standard location, otherwise you may get an `ImportError`. The second
 installation choice is equally simple:
 
     scons dev
 
     # Equivalent to
-    pip install --user -e build-release/python
+    pip3 install --user -e build-release/python
 
 This time, you should not have to worry about the position of shared libraries.
 The python modules knows where to find them in the build directory. Check that
 everything is working fine with:
 
     python3 -c 'import tamaas; print(tamaas)'
 
 ### Using environment variables (not recommended)
 
 You can source (e.g. in your `~/.bashrc` file) the file
 `build-release/tamaas_environment.sh` to modify the `PYTHONPATH` and
 `LD_LIBRARY_PATH` environment variables. This is however not recommended because
 these variables may conflict in a python virtual environment (i.e. if you use
 `virtualenv` with tamaas).
 
 ## Tests
 
 To run tests, execute `pytest build-<build_type>` if you have compiled Tamaas
 with tests activated (`scons build_tests=True use_googletest=True`).
 
 ## Documentation
 
 The latest documentation is available on
 [ReadTheDocs](https://tamaas.readthedocs.io/en/latest/)!
 
 To build the documentation, activate the `build_doc` option. Make sure you have
 [sphinx-rtd-theme](https://pypi.org/project/sphinx-rtd-theme/) and
 [breath](https://pypi.org/project/breathe/) installed. The compiled indexes for
 the doxygen C++ API and Sphinx documentation can be found in
 `doc/build/{doxygen,sphinx}/html/index.html`. Beware however that manually
 compiling documentation leads to a lot of warnings.
 
 ## Examples
 
 Example simulations can be found in the `examples/` directory. There is no
 guarantee that the examples in `examples/legacy/` all work however.
 
 - `rough_contact.py` shows a typical normal rough contact simulation
 - `adhesion.py` shows how you can derive some classes from Tamaas in python,
   here to implement a custom adhesion potential
 - `plasticity.py` computes an elastoplastic Hertz simulation and dumps the
   result in `examples/paraview/` in VTK format
 - `stresses.py` shows how you can compute stresses from a boundary traction
   distribution
 - the scripts in `pipe_tools` allow to execute elastic contact simulations
   without the need to code a custom script (see documentation for more details)
 
 ## Contributing
 
 Contributions to Tamaas are welcome! Please follow the guidelines below.
 
 ### Report an issue
 
 If you have an account on [c4science](https://c4science.ch), you can [submit an
 issue](https://c4science.ch/maniphest/task/edit/?owner=frerot&projectPHIDs=tamaas&view=public).
 All open issues are visible on the
 [workboard](https://c4science.ch/project/board/2036/), and the full list of
 issues is available [here](https://c4science.ch/maniphest/query/1jDBkIDDxCAP/).
 
 ### Submit a patch / pull-request
 
 C4Science runs [Phabricator](https://www.phacility.com/phabricator/) to host the
 code. The procedure to submit changes to repositories is described in this
 [guide](https://secure.phabricator.com/book/phabricator/article/arcanist_diff/).
 In a nutshell:
 
 ```lang=bash
 # Make changes
 git commit           # Any number of times
 arc diff             # Pushes all new commits for review
 # Wait for review...
 arc land             # Once the changes are accepted
 ```
 
 ## Citing
 
 Please cite Tamaas as:
 
 Frérot, L., Anciaux, G., Rey, V., Pham-Ba, S., J.-F., Molinari (2019). Tamaas, a
 high-performance library for periodic rough surface contact,
 [doi:10.5281/zenodo.3479237](https://doi.org/10.5281/zenodo.3479237).
 
 If you use the elastic-plastic contact capabilities of Tamaas, please cite:
 
 Frérot, L., Bonnet, M., Molinari, J.-F. & Anciaux, G. A Fourier-accelerated
 volume integral method for elastoplastic contact. Computer Methods in Applied
 Mechanics and Engineering 351, 951–976 (2019)
 [doi:10.1016/j.cma.2019.04.006](https://doi.org/10.1016/j.cma.2019.04.006).
 
 If you use the adhesive contact capabilities of Tamaas, please cite:
 
 Rey, V., Anciaux, G. & Molinari, J.-F. Normal adhesive contact on rough
 surfaces: efficient algorithm for FFT-based BEM resolution. Comput Mech 1–13
 (2017)
 [doi:10.1007/s00466-017-1392-5](https://doi.org/10.1007/s00466-017-1392-5).
 
 
 ## License
 
 Tamaas is distributed under the terms of the [GNU Affero General Public License
 v3.0](https://www.gnu.org/licenses/agpl.html).
diff --git a/examples/pipe_tools/contact b/examples/pipe_tools/contact
index dc1ff614..0346cecb 100755
--- a/examples/pipe_tools/contact
+++ b/examples/pipe_tools/contact
@@ -1,62 +1,62 @@
 #!/usr/bin/env python3
 # -*- mode: python; coding: utf-8 -*-
 # vim: set ft=python:
 """
 Read from stdin a surface profile and solve an elastic contact problem with
 load given as an argument.
 """
 
 import argparse
 import sys
 
 import tamaas as tm
 import numpy as np
 
 from tamaas.dumpers import NumpyDumper
 
 __author__ = "Lucas Frérot"
 __copyright__ = """Copyright (©) 2019, EPFL (École Polytechnique Fédérale de Lausanne),
 Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)"""
 __license__ = "AGPL"
 __email__ = "lucas.frerot@epfl.ch"
 
 tm.initialize()
 tm.set_log_level(tm.LogLevel.error)
 
 parser = argparse.ArgumentParser(
     description="Compute the elastic contact solution with a given surface")
 
 parser.add_argument("--input", "-i",
                     help="Rough surface file (default read from stdin)")
 parser.add_argument("--tol",
                     type=float,
                     default=1e-12,
                     help="Solver tolerance")
 parser.add_argument("load",
                     type=float,
                     help="Applied average pressure")
 
 args = parser.parse_args()
 
 if not args.input:
     input = sys.stdin
 else:
     input = args.input
 
-surface = np.loadtxt(input.buffer)
+surface = np.loadtxt(input)
 
 discretization = surface.shape
 system_size = [1., 1.]
 
 model = tm.ModelFactory.createModel(tm.model_type.basic_2d,
                                     system_size, discretization)
 solver = tm.PolonskyKeerRey(model, surface, args.tol,
                             tm.PolonskyKeerRey.pressure,
                             tm.PolonskyKeerRey.pressure)
 
 solver.solve(args.load)
 
 dumper = NumpyDumper('numpy', 'traction', 'displacement')
 dumper.dump_to_file(sys.stdout.buffer, model)
 
 tm.finalize()
diff --git a/examples/pipe_tools/surface b/examples/pipe_tools/surface
index 5e519e08..d993ab8d 100755
--- a/examples/pipe_tools/surface
+++ b/examples/pipe_tools/surface
@@ -1,74 +1,74 @@
 #!/usr/bin/env python3
 # -*- mode: python; coding: utf-8 -*-
 # vim: set ft=python:
 """
 Create a random self-affine rough surface from command line parameters.
 """
 
 import argparse
 import sys
 import time
 
 import tamaas as tm
 import numpy as np
 
 __author__ = "Lucas Frérot"
 __copyright__ = """Copyright (©) 2019, EPFL (École Polytechnique Fédérale de Lausanne),
 Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)"""
 __license__ = "AGPL"
 __email__ = "lucas.frerot@epfl.ch"
 
 tm.initialize()
 
 parser = argparse.ArgumentParser(
     description="Generate a self-affine rough surface"
 )
 
 parser.add_argument("--cutoffs", "-Q",
                     nargs=3,
                     type=int,
                     help="Long, rolloff and short wavelength cutoffs",
                     required=True)
 parser.add_argument("--sizes",
                     nargs=2,
                     type=int,
                     help="Number of points",
                     required=True)
 parser.add_argument("--hurst", "-H",
                     type=float,
                     help="Hurst exponent",
                     required=True)
 parser.add_argument("--rms",
                     type=float,
                     help="Root-mean-square of heights",
                     default=1.)
 parser.add_argument("--seed",
                     type=int,
                     help="Random seed",
                     default=int(time.time()))
 parser.add_argument("--output", "-o",
                     help="Output file name (compressed if .gz)")
 
 args = parser.parse_args()
 
 spectrum = tm.Isopowerlaw2D()
 spectrum.q0 = args.cutoffs[0]
 spectrum.q1 = args.cutoffs[1]
 spectrum.q2 = args.cutoffs[2]
 spectrum.hurst = args.hurst
 
 generator = tm.SurfaceGeneratorRandomPhase2D()
 generator.setSizes(args.sizes)
 generator.random_seed = args.seed
 generator.setSpectrum(spectrum)
 
 surface = generator.buildSurface()
 
 if not args.output:
     output = sys.stdout
 else:
     output = args.output
 
-np.savetxt(output.buffer, surface)
+np.savetxt(output, surface)
 
 tm.finalize()
diff --git a/examples/rough_surface.cc b/examples/rough_surface.cc
deleted file mode 100644
index 1623225b..00000000
--- a/examples/rough_surface.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- *  @file
- *  @section LICENSE
- *
- *  Copyright (©) 2016-19 EPFL (École Polytechnique Fédérale de Lausanne),
- *  Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU Affero General Public License as published
- *  by the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Affero General Public License for more details.
- *
- *  You should have received a copy of the GNU Affero General Public License
- *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
- *
- */
-/* -------------------------------------------------------------------------- */
-#include "tamaas.hh"
-#include "isopowerlaw.hh"
-#include "surface_generator_filter.hh"
-#include "polonsky_keer_rey.hh"
-#include "model_factory.hh"
-/* -------------------------------------------------------------------------- */
-
-using namespace tamaas;
-
-int main(int argc, char * argv[]) {
-  /// Surface parameters
-  UInt grid_size = 512;
-  const UInt k0 = 4, k1 = 4, k2 = 32;
-  const Real hurst = 0.8;
-  const Real rms = 0.1;
-
-  /// Initialize Tamaas
-  initialize();
-
-  /// Surface generation (2D)
-  SurfaceGeneratorFilter<2> sg;
-  sg.setSizes({grid_size, grid_size});
-  sg.setRandomSeed(0);
-
-  Isopowerlaw<2> iso;
-  iso.setQ0(k0);
-  iso.setQ1(k1);
-  iso.setQ2(k2);
-  iso.setHurst(hurst);
-
-  sg.setFilter(&iso);
-
-  std::cout << "Building rough surface with properties\n"
-	    << "    Q0 = " << k0 << '\n'
-	    << "    Q1 = " << k1 << '\n'
-	    << "    Q2 = " << k2 << '\n'
-	    << "    Hurst = " << hurst << '\n'
-	    << "    size = " << grid_size << "x" << grid_size << std::endl;
-
-  /// Making surface
-  GridBase<Real> & grid = sg.buildSurface();
-
-  std::cout << "Creating model" << std::endl;
-
-  /// Model initialization
-  Model* model = ModelFactory::createModel(model_type::basic_2d, {1., 1.},
-                                          {grid_size, grid_size});
-  Real load = 0.1;
-  Real precision = 1e-12;
-
-  std::cout << "Solving contact problem" << std::endl;
-
-  /// Solve normal contact problem
-  PolonskyKeerRey solver{*model, grid, precision, PolonskyKeerRey::pressure,
-                         PolonskyKeerRey::pressure};
-
-  solver.solve(load);
-
-  std::cout << "Finished" << std::endl;
-
-  /// Cleanup Tamaas
-  finalize();
-
-  return EXIT_SUCCESS;
-}