diff --git a/Jenkinsfile b/Jenkinsfile
index 1ace2a79..a7e7eb3a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,119 +1,120 @@
 pipeline {
     parameters {string(defaultValue: '', description: 'api-token', name: 'API_TOKEN')
                 string(defaultValue: '', description: 'Token for readthedocs', name: 'RTD_TOKEN')
                 string(defaultValue: '', description: 'buildable phid', name: 'BUILD_TARGET_PHID')
                 string(defaultValue: '', description: 'Commit id', name: 'COMMIT_ID')
                 string(defaultValue: '', description: 'Diff id', name: 'DIFF_ID')
                 string(defaultValue: 'PHID-PROJ-gbo56hpf2y5bi7t5jusk', description: 'ID of the project', name: 'PROJECT_ID')
     }
 
     environment {
         PHABRICATOR_HOST = 'https://c4science.ch/api/'
         PYTHONPATH = sh returnStdout: true, script: 'echo ${WORKSPACE}/tests/ci/script/'
     }
 
     agent {
         dockerfile { additionalBuildArgs '--tag tamaas-environment'}
     }
 
     stages {
         stage('SCM Checkout') {
             steps {
                 checkout scm: [
                     $class: 'GitSCM',
                     branches: scm.branches,
                     extensions: [[
                         $class: 'SubmoduleOption',
                         recursiveSubmodules: true,
                     ]],
                     userRemoteConfigs: scm.userRemoteConfigs
                 ]
             }
         }
 
         stage('Configure') {
             steps {
                 sh '''#!/usr/bin/env bash
                       echo "py_exec = \'python3\'" > build-setup.conf
                       echo "build_python = \'true\'" >> build-setup.conf
                       echo "build_tests = \'true\'" >> build-setup.conf
                       echo "use_googletest = \'true\'" >> build-setup.conf
-                      echo "use_mpi = \'true\'" >> build-setup.conf'''
+                      echo "use_mpi = \'true\'" >> build-setup.conf
+                      echo "verbose = \'true\'" >> build-setup.conf'''
             }
         }
 
         stage('Compile') {
             steps {
                 sh '''#!/usr/bin/env bash
                       set -o pipefail
                       rm -rf .sconf_temp .sconsign.dblite build-release
                       scons | tee compilation.txt'''
             }
 
             post {
                 failure {
                     uploadArtifact('compilation.txt', 'Compilation')
                 }
             }
         }
 
         stage('Run tests') {
             steps {
                 sh 'PYTHONPATH=$PWD/build-release/python/ python3 -m pytest --durations=0 --junitxml=results.xml build-release/tests'
             }
         }
     }
 
     post {
         always {
             createArtifact("results.xml")
             junit 'results.xml'
             step([$class: 'XUnitBuilder',
                   thresholds: [
                     [$class: 'SkippedThreshold', failureThreshold: '0'],
                     [$class: 'FailedThreshold', failureThreshold: '0']],
                   tools: [[$class: 'GoogleTestType', pattern: 'build-release/tests/gtest_results.xml']]])
         }
 
         success {
             trigger_rtd()
             passed_hbm()
         }
 
         failure {
             failed_hbm()
             emailext(
                 body: '''${SCRIPT, template="groovy-html.template"}''',
                 mimeType: 'text/html',
                 subject: "[Jenkins] ${currentBuild.fullDisplayName} Failed",
                 recipientProviders: [[$class: 'CulpritsRecipientProvider']],
                 to: 'lucas.frerot@protonmail.com',
                 attachLog: true,
                 compressLog: true)
         }
     }
 }
 
 def createArtifact(filename) {
     sh "./tests/ci/scripts/hbm send-uri -k 'Jenkins URI' -u ${BUILD_URL} -l 'View Jenkins result'"
     sh "./tests/ci/scripts/hbm send-junit-results -f ${filename}"
 }
 
 def uploadArtifact(artifact, name) {
     sh "./test/ci/scripts/hbm upload-file -f ${artifact} -n \"${name}\" -v ${PROJECT_ID}"
 }
 
 def failed_hbm() {
     sh "./tests/ci/scripts/hbm failed"
 }
 
 def passed_hbm() {
     sh "./tests/ci/scripts/hbm passed"
 }
 
 def trigger_rtd() {
     sh """
        set -x
        curl -X POST -d "token=${RTD_TOKEN}" https://readthedocs.org/api/v2/webhook/tamaas/106141/
        """
 }
diff --git a/SConstruct b/SConstruct
index 4c109182..b83e4ff4 100644
--- a/SConstruct
+++ b/SConstruct
@@ -1,475 +1,478 @@
 # -*- mode:python; coding: utf-8 -*-
 # vim: set ft=python:
 
 # @file
 # LICENSE
 #
 # Copyright (©) 2016-2021 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/>.
 
 # ------------------------------------------------------------------------------
 # Imports
 # ------------------------------------------------------------------------------
 from __future__ import print_function
 
 import sys
 import os
 from subprocess import check_output
 
 # Import below not strictly necessary, but good for pep8
 from SCons.Script import (
     EnsurePythonVersion,
     EnsureSConsVersion,
     Help,
     Environment,
     Variables,
     EnumVariable,
     PathVariable,
     BoolVariable,
     ListVariable,
     Split,
     Export,
     Dir,
 )
 
 from SCons.Errors import StopError
 from SCons import __version__ as scons_version
 
 from version import get_git_subst
 from detect import (
     FindFFTW,
     FindBoost,
     FindThrust,
     FindCuda,
     FindExpolit,
     FindPybind11
 )
 
 from INFOS import TAMAAS_INFOS
 
 # ------------------------------------------------------------------------------
 
 EnsurePythonVersion(2, 7)
 EnsureSConsVersion(2, 4)
 
 
 # ------------------------------------------------------------------------------
 def detect_dependencies(env):
     "Detect all dependencies"
     fftw_comp = {
         'omp': ['omp'],
         'threads': ['threads'],
         'none': [],
     }
 
     fftw_components = fftw_comp[env['fftw_threads']]
     if main_env['use_mpi']:
         fftw_components.append('mpi')
 
     FindFFTW(env, fftw_components, precision=env['real_type'])
     FindBoost(env, ['boost/preprocessor/seq.hpp'])
     FindExpolit(env)
 
     thrust_var = 'THRUST_ROOT'
 
     # Take cuda version of thrust if available
     if 'CUDA_ROOT' in env['ENV']:
         thrust_var = 'CUDA_ROOT'
 
     FindThrust(env, env['backend'], thrust_var)
 
     # Activate cuda if needed
     if env['backend'] == 'cuda':
         FindCuda(env)
 
     if env['build_python']:
         FindPybind11(env)
 
 
 def subdir(env, dir):
     "Building a sub-directory"
     return env.SConscript(env.File('SConscript', dir),
                           variant_dir=env.Dir(dir, env['build_dir']),
                           duplicate=True)
 
 
 # ------------------------------------------------------------------------------
 # Main compilation
 # ------------------------------------------------------------------------------
 
 # Compilation colors
 colors = {
     'cyan': '\033[96m',
     'purple': '\033[95m',
     'blue': '\033[94m',
     'green': '\033[92m',
     'yellow': '\033[93m',
     'gray': '\033[38;5;8m',
     'orange': '\033[38;5;208m',
     'red': '\033[91m',
     'end': '\033[0m'
 }
 
 # Inherit all environment variables (for CXX detection, etc.)
 main_env = Environment(
     ENV=os.environ,
 )
 
 # Set tamaas information
 for k, v in TAMAAS_INFOS.items():
     main_env[k] = v
 
 main_env['COLOR_DICT'] = colors
 main_env.AddMethod(subdir, 'SubDirectory')
 
 # Build variables
 vars = Variables('build-setup.conf')
 vars.AddVariables(
     EnumVariable('build_type', 'Build type', 'release',
                  allowed_values=('release', 'profiling', 'debug'),
                  ignorecase=2),
     EnumVariable('backend', 'Thrust backend', 'omp',
                  allowed_values=('cpp', 'omp', 'tbb'),
                  ignorecase=2),
     EnumVariable('fftw_threads', 'Threads FFTW library preference', 'omp',
                  allowed_values=('omp', 'threads', 'none'),
                  ignorecase=2),
     EnumVariable('sanitizer', 'Sanitizer type', 'none',
                  allowed_values=('none', 'memory', 'leaks', 'address'),
                  ignorecase=2),
 
     PathVariable('prefix',
                  'Prefix where to install', '/usr/local'),
 
     # Dependencies paths
     PathVariable('FFTW_ROOT',
                  'FFTW custom path', os.getenv('FFTW_ROOT', ''),
                  PathVariable.PathAccept),
     PathVariable('THRUST_ROOT',
                  'Thrust custom path', os.getenv('THRUST_ROOT', ''),
                  PathVariable.PathAccept),
     PathVariable('BOOST_ROOT',
                  'Boost custom path', os.getenv('BOOST_ROOT', ''),
                  PathVariable.PathAccept),
     PathVariable('CUDA_ROOT',
                  'Cuda custom path', os.getenv('CUDA_ROOT', ''),
                  PathVariable.PathAccept),
     # Dependencies provided as submodule get different default
     PathVariable('GTEST_ROOT',
                  'Googletest custom path',
                  os.getenv('GTEST_ROOT', '#third-party/googletest/googletest'),
                  PathVariable.PathAccept),
     PathVariable('PYBIND11_ROOT',
                  'Pybind11 custom path',
                  os.getenv('PYBIND11_ROOT', '#third-party/pybind11/include'),
                  PathVariable.PathAccept),
     PathVariable('EXPOLIT_ROOT',
                  'Expolit custom path',
                  os.getenv('EXPOLIT_ROOT', '#third-party/expolit/include'),
                  PathVariable.PathAccept),
 
     # Executables
     ('CXX', 'Compiler', os.getenv('CXX', 'g++')),
     ('MPICXX', 'MPI Compiler wrapper', os.getenv('MPICXX', 'mpicxx')),
     ('py_exec', 'Python executable', 'python3'),
 
     # Compiler flags
     ('CXXFLAGS', 'C++ compiler flags', os.getenv('CXXFLAGS', "")),
 
     # Cosmetic
     BoolVariable('verbose', 'Activate verbosity', False),
     BoolVariable('color', 'Color the non-verbose compilation output', False),
 
     # Tamaas components
     BoolVariable('build_doc', 'Build documentation', False),
     BoolVariable('build_tests', 'Build test suite', False),
     BoolVariable('build_python', 'Build python wrapper', True),
 
     # Documentation
     ListVariable('doc_builders', 'Generated documentation formats',
                  default='html', names=Split("html man")),  # TODO include latex
 
     # Dependencies
     BoolVariable('use_googletest', 'Build tests using GTest', False),
     BoolVariable('use_mpi', 'Builds multi-process parallelism', False),
 
     # Distribution options
     BoolVariable('strip_info', 'Strip binary of added information', False),
     BoolVariable('build_static_lib', "Build a static libTamaas", False),
 
     # Type variables
     EnumVariable('real_type', 'Type for real precision variables', 'double',
                  allowed_values=('double', 'long double')),
     EnumVariable('integer_type', 'Type for integer variables', 'int',
                  allowed_values=('int', 'long')),
 )
 
 # Set variables of environment
 vars.Update(main_env)
 help_text = vars.GenerateHelpText(main_env)
 help_text += """
 Commands:
     scons [build] [options]...                            Compile Tamaas (and additional modules/tests)
     scons install [prefix=/your/prefix] [options]...      Install Tamaas to prefix
     scons dev                                             Install symlink to Tamaas python module (useful to development purposes)
     scons test                                            Run tests with pytest
     scons doc                                             Compile documentation with Doxygen and Sphinx+Breathe
     scons archive                                         Create a gzipped archive from source
 """  # noqa
 Help(help_text)
 
 # Save all options, not just those that differ from default
 with open('build-setup.conf', 'w') as setup:
     for option in vars.options:
         setup.write("# " + option.help.replace('\n', '\n# ') + "\n")
         setup.write("{} = '{}'\n".format(option.key, main_env[option.key]))
 
 main_env['should_configure'] = \
     not main_env.GetOption('clean') and not main_env.GetOption('help')
 
 build_type = main_env['build_type']
 build_dir = 'build-${build_type}'
 main_env['build_dir'] = main_env.Dir(build_dir)
 
 
 # Setting up the python name with version
 if main_env['build_python']:
     args = (main_env.subst("${py_exec} -c").split()
             + ["from distutils.sysconfig import get_python_version;"
             "print(get_python_version())"])
     main_env['py_version'] = bytes(check_output(args)).decode()
 
 # Printing some build infos
 if main_env['should_configure']:
     print('-- SCons {} (Python {}.{})'.format(scons_version,
                                               sys.version_info.major,
                                               sys.version_info.minor))
     print(main_env.subst("-- Build type: ${build_type}\n"
                          "-- Thrust backend: ${backend}\n"
                          "-- FFTW threads: ${fftw_threads}\n"
                          "-- MPI: ${use_mpi}\n"
                          "-- Build directory: ${build_dir}\n"
                          "-- Python version (bindings): $py_version"))
 
 
 verbose = main_env['verbose']
 
 # Remove colors if not set
 if not main_env['color']:
     for key in colors:
         colors[key] = ''
 
 if not verbose:
     main_env['CXXCOMSTR'] = main_env['SHCXXCOMSTR'] = \
         u'{0}[Compiling ($SHCXX)] {1}$SOURCE'.format(colors['green'],
                                                      colors['end'])
     main_env['LINKCOMSTR'] = main_env['SHLINKCOMSTR'] = \
         u'{0}[Linking] {1}$TARGET'.format(colors['purple'],
                                           colors['end'])
     main_env['ARCOMSTR'] = u'{}[Ar]{} $TARGET'.format(colors['purple'],
                                                       colors['end'])
     main_env['RANLIBCOMSTR'] = \
         u'{}[Randlib]{} $TARGET'.format(colors['purple'],
                                         colors['end'])
     main_env['PRINT_CMD_LINE_FUNC'] = pretty_cmd_print
+    main_env['INSTALLSTR'] = \
+        u'{}[Installing] {}$SOURCE to $TARGET'.format(colors['blue'],
+                                                      colors['end'])
 
 # Include paths
 main_env.AppendUnique(CPPPATH=['#/src',
                                '#/src/core',
                                '#/src/mpi',
                                '#/src/bem',
                                '#/src/surface',
                                '#/src/python',
                                '#/src/percolation',
                                '#/src/model',
                                '#/src/model/elasto_plastic',
                                '#/src/solvers',
                                '#/src/gpu',
                                '#/python'])
 
 # Changing the shared object extension
 main_env['SHOBJSUFFIX'] = '.o'
 
 # Back to gcc if cuda is activated
 if main_env['backend'] == "cuda" and "g++" not in main_env['CXX']:
     raise StopError('GCC should be used when compiling with CUDA')
 
 # OpenMP flags - compiler dependent
 omp_flags = {
     "g++": ["-fopenmp"],
     "clang++": ["-fopenmp"],
     "icpc": ["-qopenmp"]
 }
 
 
 def cxx_alias(cxx):
     for k in omp_flags.keys():
         if k in cxx:
             return k
 
     raise StopError('Unsupported compiler: ' + cxx)
 
 
 cxx = cxx_alias(main_env['CXX'])
 
 # Setting main compilation flags
 main_env['CXXFLAGS'] = Split(main_env['CXXFLAGS'])
 main_env['LINKFLAGS'] = main_env['CXXFLAGS']
 main_env.AppendUnique(
     CXXFLAGS=Split('-std=c++14 -Wall -Wextra -pedantic'),
     CPPDEFINES={
         'TAMAAS_LOOP_BACKEND': 'TAMAAS_LOOP_BACKEND_${backend.upper()}',
         'TAMAAS_FFTW_BACKEND': 'TAMAAS_FFTW_BACKEND_${fftw_threads.upper()}'
     },
 )
 
 # Adding OpenMP flags
 if main_env['backend'] == 'omp':
     main_env.AppendUnique(CXXFLAGS=omp_flags[cxx])
     main_env.AppendUnique(LINKFLAGS=omp_flags[cxx])
 else:
     main_env.AppendUnique(CXXFLAGS=['-Wno-unknown-pragmas'])
 
 # Correct bug in clang?
 if main_env['backend'] == 'omp' and cxx == "clang++":
     main_env.AppendUnique(LIBS=["atomic"])
 elif main_env['backend'] == 'tbb':
     main_env.AppendUnique(LIBS=['tbb'])
 
 # Manage MPI compiler
 if main_env['use_mpi']:
     main_env['CXX'] = '$MPICXX'
     main_env.AppendUnique(CPPDEFINES=['TAMAAS_USE_MPI'])
     main_env.AppendUnique(CXXFLAGS=['-Wno-cast-function-type'])
 
 # Flags and options
 if main_env['build_type'] == 'debug':
     main_env.AppendUnique(CPPDEFINES=['TAMAAS_DEBUG'])
 
 # Define the scalar types
 main_env.AppendUnique(CPPDEFINES={'TAMAAS_REAL_TYPE': '${real_type}',
                                   'TAMAAS_INT_TYPE': '${integer_type}'})
 
 # Compilation flags
 cxxflags_dict = {
     "debug": Split("-g -O0"),
     "profiling": Split("-g -O3 -fno-omit-frame-pointer"),
     "release": Split("-O3")
 }
 
 if main_env['sanitizer'] != 'none':
     if main_env['backend'] == 'cuda':
         raise StopError(
             "Sanitizers with cuda are not yet supported!")
     cxxflags_dict[build_type].append('-fsanitize=${sanitizer}')
 
 main_env.AppendUnique(CXXFLAGS=cxxflags_dict[build_type])
 main_env.AppendUnique(SHLINKFLAGS=cxxflags_dict[build_type])
 main_env.AppendUnique(LINKFLAGS=cxxflags_dict[build_type])
 
 if main_env['should_configure']:
     basic_checks(main_env)
     detect_dependencies(main_env)
 
 # Writing information file
 main_env.Tool('textfile')
 main_env['SUBST_DICT'] = get_git_subst()
 
 # Empty values if requested
 if main_env['strip_info']:
     for k in main_env['SUBST_DICT']:
         main_env['SUBST_DICT'][k] = ""
 
 # Substitution of environment file
 main_env['SUBST_DICT'].update({
     '@build_type@': '$build_type',
     '@build_dir@': '${build_dir.abspath}',
     '@build_version@': '$version',
     '@backend@': '$backend',
 })
 
 # Environment file content
 env_content = """export PYTHONPATH=@build_dir@/python:$$PYTHONPATH
 export LD_LIBRARY_PATH=@build_dir@/src:$$LD_LIBRARY_PATH
 """
 
 # Writing environment file
 env_file = main_env.Textfile(
     main_env.File('tamaas_environment.sh', main_env['build_dir']),
     env_content)
 
 
 # Default targets
 build_targets = ['build-cpp', env_file]
 install_targets = ['install-lib']
 
 if main_env._get_major_minor_revision(scons_version)[0] >= 4:
     main_env.Tool('compilation_db')
     main_env.CompilationDatabase(PRINT_CMD_LINE_FUNC=pretty_cmd_print)
 
 # Building Tamaas library
 Export('main_env')
 main_env.SubDirectory('src')
 
 # Building Tamaas extra components
 for dir in ['python', 'tests']:
     if main_env['build_{}'.format(dir)] and not main_env.GetOption('help'):
         main_env.SubDirectory(dir)
         build_targets.append('build-{}'.format(dir))
 
 # Building API + Sphinx documentation if requested
 if main_env['build_doc']:
     main_env.SubDirectory('doc')
     main_env.Alias('doc', 'build-doc')
     install_targets.append('install-doc')
 else:
     dummy_command(main_env, 'doc', 'Command "doc" does not do anything'
                   ' without documentation activated ("build_doc=True")')
 
 # Define dummy dev command when python is deactivated
 if not main_env['build_python']:
     dummy_command(main_env, 'dev', 'Command "dev" does not do anything'
                   + ' without python activated ("build_python=True")')
 else:
     install_targets.append('install-python')
 
 # Define dummy test command when tests are deactivated
 if not main_env['build_tests']:
     dummy_command(main_env, 'test', 'Command "test" does not do anything'
                   + ' without tests activated ("build_tests=True")')
 
 # Definition of target aliases, a.k.a. sub-commands
 main_env.Alias('build', build_targets)
 
 # Define proper install targets
 main_env.Alias('install', install_targets)
 
 # Default target is to build stuff
 main_env.Default('build')
 
 # Building a tar archive
 archive = main_env.Command(
     'tamaas-${version}.tar.gz',
     '',
     ('tar --exclude-vcs --exclude-vcs-ignores '
      '--exclude=third-party/googletest '
      '--exclude=third-party/pybind11 '
      '--exclude=joss '
      '--exclude=".*" '
      '-czf $TARGET {}'.format(Dir('.').name)),
     chdir='..',
 )
 main_env.Alias('archive', archive)
diff --git a/python/SConscript b/python/SConscript
index d31d2948..6d0da3bd 100644
--- a/python/SConscript
+++ b/python/SConscript
@@ -1,160 +1,163 @@
 # -*- mode:python; coding: utf-8 -*-
 # vim: set ft=python:
 
 # @file
 # LICENSE
 #
 # Copyright (©) 2016-2021 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/>.
 
 from __future__ import print_function
 
 from SCons.Script import Import, Split, Copy, Dir
 
 Import('main_env')
 
 # Pybind11 wrapper
 env_pybind = main_env.Clone(SHLIBPREFIX='')
 
 # Remove pedantic warnings
 cxx_flags = env_pybind['CXXFLAGS']
 try:
     del cxx_flags[cxx_flags.index('-pedantic')]
 except ValueError:
     pass
 
 env_pybind.Tool(pybind11)
 
 pybind_sources = Split("""
 tamaas_module.cpp
 wrap/core.cpp
 wrap/percolation.cpp
 wrap/surface.cpp
 wrap/model.cpp
 wrap/solvers.cpp
 wrap/compute.cpp
 wrap/mpi.cpp
 wrap/test_features.cpp
 """)
 
 # Setting paths to find libTamaas
 env_pybind.AppendUnique(LIBPATH=['../src'])
 
 # Link against a static libTamaas
 if env_pybind['build_static_lib']:
     env_pybind.PrependUnique(LIBS=['Tamaas'])  # keep other libs for link
     env_pybind['RPATH'] = ""  # no need for rpath w/ static lib
 
 # Link against a dynamic libTamaas
 else:
     env_pybind.AppendUnique(RPATH=[
         "'$$$$ORIGIN/../../src'",  # path to lib in build_dir
         "'$$$$ORIGIN/../../..'",  # path to lib in install prefix
     ])
 
     env_pybind['LIBS'] = ['Tamaas']  # discard other libs for link
 
 # Building the pybind library
 tamaas_wrap = env_pybind.Pybind11Module(
     target='tamaas/_tamaas',
     source=pybind_sources,
 )
 
 # For some reason link happens too early
 Import('libTamaas')
 env_pybind.Depends(tamaas_wrap, libTamaas)
 
 # Copying the __init__.py file with extra python classes
 copy_env = env_pybind.Clone()
 
 
 # Copying additional python files
 
 python_files = """
 __main__.py
 compute.py
 dumpers/__init__.py
 dumpers/_helper.py
 nonlinear_solvers/__init__.py
 """.split()
 
 targets = [tamaas_wrap]
 targets += [
     copy_env.Command(copy_env.File(f, 'tamaas'),
                      copy_env.File(f, '#python/tamaas'),
                      Copy("$TARGET", "$SOURCE"))
     for f in python_files
 ]
 
 targets.append(copy_env.Command('MANIFEST.in', '#python/MANIFEST.in',
                                 Copy("$TARGET", "$SOURCE")))
 
 
 subst_env = env_pybind.Clone(
     SUBST_DICT={
         '@version@': '$version',
         '@authors@': str(copy_env['authors']),
         '@email@': '$email',
         # TODO change when issue with unicode fixed
         # '@copyright@': '$copyright',
         # '@maintainer@': '$maintainer',
     }
 )
 subst_env.Tool('textfile')
 
 targets.append(subst_env.Substfile('setup.py.in'))
 targets.append(subst_env.Substfile('tamaas/__init__.py.in'))
 
 # Defining alias for python builds
 main_env.Alias('build-python', targets)
 
 # Checking if we can use pip to install (more convenient for end-user)
 install_env = main_env.Clone()
 conf = Configure(install_env,
                  custom_tests={'CheckPythonModule': CheckPythonModule})
 has_pip = conf.CheckPythonModule('pip')
 install_env = conf.Finish()
 
 # Current build directory
 install_env['PYDIR'] = Dir('.')
 
 # Setting command line for installation
 if has_pip:
     install_env['PYINSTALLCOM'] = '${py_exec} -m pip install -U $PYOPTIONS .'
     install_env['PYDEVELOPCOM'] = \
         '${py_exec} -m pip install $PYOPTIONS -e .[all]'
 else:
     install_env['PYINSTALLCOM'] = '${py_exec} setup.py install $PYOPTIONS'
     install_env['PYDEVELOPCOM'] = '${py_exec} setup.py develop $PYOPTIONS'
     install_env['py_version'] = get_python_version(install_env)
     install_env.PrependENVPath(
         'PYTHONPATH',
         install_env.subst('${prefix}/lib/python${py_version}/site-packages'))
 
 # Specify install target
+PYOPTIONS = ['${"" if verbose else "-q"}']
 python_install = install_env.Command(
     '.python_install_phony',
-    targets, install_env['PYINSTALLCOM'], PYOPTIONS='--prefix ${prefix}',
+    targets, install_env['PYINSTALLCOM'],
+    PYOPTIONS=['--prefix', '${prefix}'] + PYOPTIONS,
     chdir=install_env['PYDIR'])
 
 python_install_dev = install_env.Command(
     '.python_install_local_phony',
-    targets, install_env['PYDEVELOPCOM'], PYOPTIONS='--user',
+    targets, install_env['PYDEVELOPCOM'],
+    PYOPTIONS=['--user'] + PYOPTIONS,
     chdir=install_env['PYDIR'])
 
 # Defining aliases
 main_env.Alias('install-python', python_install)
 main_env.Alias('dev', python_install_dev)
diff --git a/site_scons/site_init.py b/site_scons/site_init.py
index 39205752..99b39e1c 100644
--- a/site_scons/site_init.py
+++ b/site_scons/site_init.py
@@ -1,179 +1,191 @@
 # -*- coding: utf-8 -*-
 
 # @file
 # LICENSE
 #
 # Copyright (©) 2016-2021 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/>.
 
 import subprocess
 
 from detect import FindPybind11
 from SCons.Script import Configure
 from SCons.Errors import StopError
 
 
 # ------------------------------------------------------------------------------
 def pybind11(env):
     """A tool to configure pybind11"""
     def execute(command):
         return [line for line in subprocess.check_output(
             command, universal_newlines=True).split('\n')
                 if line != ""]
 
     # Create a clone so we do not modify env
     clone = env.Clone()
 
     # Set variables for clone
     FindPybind11(clone)
     includes = clone['CPPPATH']
 
     # Extension of shared library for python
     try:
         extension = execute(
             env.subst('${py_exec}-config --extension-suffix').split())[0]
     except subprocess.CalledProcessError:
         extension = ".so"
 
     def pybind11_builder(env, target, source, **kwargs):
         """Create a pybind11 module"""
         clone = env.Clone()
         clone.AppendUnique(CPPPATH=includes)
         clone['SHLIBSUFFIX'] = extension
         clone.AppendUnique(SHLINKFLAGS=['-fvisibility=hidden'])
         return clone.SharedLibrary(target, source, **kwargs)
 
     # Add pseudo-builder to master environment
     env.AddMethod(pybind11_builder, 'Pybind11Module')
 
 
 # ------------------------------------------------------------------------------
 def pretty_cmd_print(command, target, source, env):
     colors = env['COLOR_DICT']
     if 'Copy' in command:
         color = colors['gray']
         action = 'Copying'
     elif 'Creating' in command:
         color = colors['yellow']
         action = 'Generating'
     elif 'database' in command:
         color = colors['yellow']
         action = 'Generating'
+    elif 'pytest' in command:
+        color = colors['green']
+        action = 'Running tests'
+        target[0] = target[0].dir
+    elif 'pip' in command:
+        color = colors['blue']
+        if 'user' in command:
+            action = 'Symlinking'
+            target[0] = target[0].dir
+        elif 'prefix' in command:
+            action = 'Installing'
+            target[0] = env.subst(target[0].dir.path + ' to ${prefix}')
     else:
         print(command)
         return
 
     print("{color}[{action}] {end}{target}".format(
         color=color,
         end=colors['end'],
         target=target[0],
         action=action
     ))
 
 
 # ------------------------------------------------------------------------------
 def CheckPythonModule(context, module):
     """Checks the existence of a python module"""
     context.Message('Checking for Python module {}... '.format(module))
     env = context.sconf.env
     command = [env.subst('${py_exec}'), '-c', 'import {}'.format(module)]
 
     context.Log('Executing external command: {}\n'.format(command))
 
     try:
         subprocess.check_output(command, stderr=subprocess.STDOUT)
         result = True
     except subprocess.CalledProcessError as e:
         result = False
         output = bytes(e.output)
         context.Log(output.decode() + '\n')
 
     context.Result(result)
     return result
 
 
 # ------------------------------------------------------------------------------
 def CheckCompilerFlag(context, flag):
     "Check compiler provides flag"
     context.Message('Checking compiler flag {}... '.format(
         context.sconf.env.subst(flag)))
     test_file = """
     int main() {
       return 0;
     }
     """
 
     env = context.sconf.env.Clone(CXXFLAGS=[flag],
                                   LINKFLAGS=[flag])
     context.sconf.env = env
 
     result = context.TryLink(test_file, '.cpp')
     context.Result(result)
     return result
 
 
 # ------------------------------------------------------------------------------
 def dummy_command(env, command, error_msg):
     """Creates a dummy scons command"""
     def print_error(*args, **kwargs):
         print(error_msg)
 
     def print_cmd(*args, **kwargs):
         pass
 
     comm = env.Command('#.phony_{}'.format(command),
                        '', print_error, PRINT_CMD_LINE_FUNC=print_cmd)
     env.Alias(command, comm)
 
 
 # ------------------------------------------------------------------------------
 def get_python_version(env):
     versions_script = """
 from __future__ import print_function
 from sysconfig import get_python_version
 print(get_python_version())"""
     version = subprocess.check_output([env['py_exec'], "-c", versions_script],
                                       universal_newlines=True).replace('\n',
                                                                        '')
     print(version)
     return version
 
 
 # ------------------------------------------------------------------------------
 def basic_checks(env):
     custom_tests = {
         'CheckCompilerFlag': CheckCompilerFlag,
     }
 
     conf = Configure(env, custom_tests=custom_tests)
 
     if not conf.CheckCXX():
         StopError('Could not find working C++ compiler')
 
     for flag in env['CXXFLAGS']:
         if not conf.CheckCompilerFlag(flag):
             StopError('Compiler does not support flag {}'.format(flag))
 
     def check_type(cpp_type, stl_header):
         if not conf.CheckType(cpp_type,
                               '#include <{}>'.format(stl_header),
                               'cpp'):
             StopError('Standard type {} is not available'.format(cpp_type))
 
     check_type('std::multiplies<void>', 'functional')
     check_type('std::unique_ptr<void>', 'memory')
 
     conf.Finish()
diff --git a/tests/SConscript b/tests/SConscript
index eeba2985..737f1efa 100644
--- a/tests/SConscript
+++ b/tests/SConscript
@@ -1,213 +1,215 @@
 # -*- mode:python; coding: utf-8 -*-
 # vim: set ft=python:
 
 # @file
 # LICENSE
 #
 # Copyright (©) 2016-2021 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/>.
 
 from __future__ import print_function
 
 from SCons.Script import Split, Copy, Dir, Import
 from detect import FindGTest, FindPybind11
 
 
 # ------------------------------------------------------------------------------
 def copyComStr(env, main):
     if 'SHCXXCOMSTR' in main:
         env['CXXCOMSTR'] = main['SHCXXCOMSTR']
     if 'SHLINKCOMSTR' in main:
         env['LINKCOMSTR'] = main['SHLINKCOMSTR']
 
 
 # ------------------------------------------------------------------------------
 def make_python_tests(env):
     """Copy python tests to build directory"""
     test_env = env.Clone()
 
     test_files = Split("""
     test_hertz.py
     test_westergaard.py
     test_patch_westergaard.py
     test_patch_plasticity.py
     test_surface.py
     test_hertz_disp.py
     test_hertz_kato.py
     test_saturated_pressure.py
     test_flood_fill.py
     test_integral_operators.py
     test_dumper.py
     test_tangential.py
     test_boussinesq_surface.py
     test_voigt.py
     test_memory.py
     test_epic.py
     fftfreq.py
     conftest.py
     pytest.ini
     """)
 
     if env['use_mpi']:
         test_files += ['test_mpi_routines.py', 'mpi_routines.py']
 
     src_dir = "#/tests"
 
     targets = [
         test_env.Command(file, test_env.File(file, src_dir),
                          Copy("$TARGET", "$SOURCE"))
         for file in test_files
     ]
 
     test_env = env.Clone(tools=[pybind11])
     # Helper module for integral operators
     test_env['SHLIBPREFIX'] = ''
     test_env.PrependUnique(LIBS=['Tamaas'])
     register = test_env.Pybind11Module(
         target="register_integral_operators",
         source=["register_integral_operators.cpp"])
 
     Import('libTamaas')
     test_env.Depends(register, libTamaas)
     targets.append(register)
 
     return targets
 
 
 # ------------------------------------------------------------------------------
 def compile_google_test(env, gtest_path):
     gtest_obj = env.Object('gtest.o',
                            [env.File("src/gtest-all.cc", gtest_path)])
     return env.StaticLibrary('gtest', gtest_obj)
 
 
 # ------------------------------------------------------------------------------
 def make_google_tests(env):
     gtest_dir = Dir(env['GTEST_ROOT'])
 
     gtest_env = env.Clone(CPPPATH=[gtest_dir],
                           CXXFLAGS=['-pthread', '-isystem',
                                     env.Dir('include', gtest_dir).path])
 
     FindGTest(gtest_env)
 
     libgtest = None
     # Hugly hack to detect if we need to compile gtest submodule
     if env['GTEST_ROOT'] == '#third-party/googletest/googletest':
         gtest_path = str(gtest_dir)
         libgtest = compile_google_test(gtest_env, gtest_path)
 
     env.AppendUnique(CXXFLAGS=gtest_env['CXXFLAGS'])
     env.PrependUnique(LIBS=['Tamaas', env.subst('python${py_version}')])
 
     google_test_files = Split("""
                               test_fft.cpp
                               test_grid.cpp
                               test_loop.cpp
                               test_model.cpp
                               test_static_types.cpp
                               test_integration.cpp
                               """)
 
     # Necessary for the tests that use pybind11 calls to python
     uses = []
     if env['build_python']:
         google_test_files.append('test_fftfreq.cpp')
         uses = ['TAMAAS_USE_PYTHON']
 
     if env['use_mpi']:
         google_test_files.append('test_mpi.cpp')
 
     defines = env['CPPDEFINES']
     if type(defines) is not list:
         defines = [defines]
 
     gtest_main = env.Object("tamaas_gtest_main.o", 'tamaas_gtest_main.cc',
                             CPPDEFINES=defines + uses)
     gtest_all = env.Program('test_gtest_all', google_test_files + [gtest_main],
                             LIBS=(env['LIBS'] + ['gtest']))
     Import('libTamaas')
     env.Depends(gtest_all, libTamaas)
     env.Depends(gtest_all, libgtest)
 
     return [gtest_all]
 
 
 # ------------------------------------------------------------------------------
 def make_bare_tests(env):
     rough = env.Program("test_rough_surface.cpp")
     Import('libTamaas')
     env.Depends(rough, libTamaas)
 
     return [rough]
 
 
 # ------------------------------------------------------------------------------
 Import('main_env')
 
 # Setup of test environment
 test_env = main_env.Clone()
 test_env.AppendUnique(
     LIBPATH=['.', '../src'],
     RPATH=["'$$$$ORIGIN/../src'"]
 )
 test_env.PrependUnique(LIBS=['Tamaas'])
 
 # Building tests that do not require any third party
 targets = make_bare_tests(test_env)
 
 # Build tests that required python bindings
 if test_env['build_python']:
     FindPybind11(test_env)
     test_env.Tool(pybind11)
     test_env.ParseConfig("${py_exec}-config --ldflags")
     test_env['CCFLAGS'] = []
 
     targets += make_python_tests(test_env)
 
 # Building google tests
 if test_env['use_googletest']:
     targets += make_google_tests(test_env)
     targets.append(test_env.Command('test_gtest.py', '#tests/test_gtest.py',
                                     Copy('$TARGET', '$SOURCE')))
 
 # Target alias to build tests
 main_env.Alias('build-tests', targets)
 
 # Check if pytest is installed
 conf = Configure(test_env,
                  custom_tests={'CheckPythonModule': CheckPythonModule})
 
 has_pytest = conf.CheckPythonModule('pytest')
 conf.Finish()
 
 # Define a command to execute tests
 if has_pytest:
     pytest_env = test_env.Clone()
     test_env['pythonpath'] = '${build_dir}/python'
     test_env['ld_library_path'] = '${build_dir}/src'
     pytest_env.PrependENVPath('PYTHONPATH', '${pythonpath.abspath}')
     pytest_env.PrependENVPath('LD_LIBRARY_PATH', '${ld_library_path.abspath}')
     # Setting a moderate thread number
     pytest_env['ENV']['OMP_NUM_THREADS'] = "1"
-    test_target = pytest_env.Command('.phony_test', targets,
-                                     '${py_exec} -m pytest ${build_dir}/tests')
+    pytest_env['PYTESTOPTS'] = ['${"-v" if verbose else "-q"}']
+    test_target = pytest_env.Command(
+        '.phony_test', targets,
+        '${py_exec} -m pytest $PYTESTOPTS ${TARGET.dir}')
     main_env.Alias('test', test_target)
 
 else:
     # We still define a target here so that `scons test` still works
     dummy_command(main_env, 'test',
                   'Cannot run tests: pytest is not installed')
diff --git a/tests/conftest.py b/tests/conftest.py
index f1fb9f78..1c9337d8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,309 +1,302 @@
 # -*- coding: utf-8 -*-
 # @file
 # LICENSE
 #
 # Copyright (©) 2016-2021 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/>.
 
 from __future__ import division, print_function
 
 import subprocess
 import sys
 from functools import reduce
 from operator import mul as multiply
 
 import numpy as np
 import pytest
 
 from numpy.linalg import norm
 
 try:
     import tamaas as tm
 except ModuleNotFoundError as e:
     print(e, file=sys.stderr)
     print("Use 'scons test' to run tests", file=sys.stderr)
     sys.exit(1)
 
 
 type_list = [tm.model_type.basic_1d,
              tm.model_type.basic_2d,
              tm.model_type.surface_1d,
              tm.model_type.surface_2d,
              tm.model_type.volume_1d,
              tm.model_type.volume_2d]
 
 
 def get_libtamaas_file():
     try:
         ldd_out = bytes(subprocess.check_output(['ldd', tm._tamaas.__file__]))
         for line in filter(lambda x: 'Tamaas' in x,
                            ldd_out.decode().split('\n')):
             path = line.split(' => ')[1]
             return path.split(' ')[0]
         return "static link"
 
     except subprocess.CalledProcessError:
         return "N/A"
 
 
 def pytest_report_header(config):
     print('tamaas build type: {}\nmodule file: {}\nwrapper: {}\nlibTamaas: {}'
           .format(tm.TamaasInfo.build_type,
                   tm.__file__,
                   tm._tamaas.__file__,
                   get_libtamaas_file()))
 
 
 def pytest_addoption(parser):
     pass
     # try:
     #     parser.addoption(
     #         '--with-mpi', action="store_true", default=False,
     #         help="Run MPI tests, this should be paired with mpirun."
     #     )
     # except ValueError:
     #     pass
 
 
 def mtidfn(val):
     return str(val).split('.')[-1]
 
 
 def pkridfn(val):
     return {tm.PolonskyKeerRey.pressure: "pkr_pressure",
             tm.PolonskyKeerRey.gap: "pkr_gap"}[val]
 
 
 def profile(func, domain, N, modes, amplitude):
     coords = [np.linspace(0, d, n, endpoint=False, dtype=tm.dtype)
               for n, d in zip(N, domain)]
     coords = np.meshgrid(*coords, indexing='ij')
     sines = [func(2 * np.pi * x * m) for x, m in zip(coords, modes)]
     return reduce(multiply, [amplitude] + sines)
 
 
-@pytest.fixture(scope="package", autouse=True)
-def tamaas_fixture():
-    tm.initialize()
-    yield None
-    tm.finalize()
-
-
 class HertzFixture:
     def __init__(self, n, load):
         self.domain_size = 1
         self.n = n
         self.load = load
         self.curvature = 0.1
         self.radius = 1. / self.curvature
         self.e_star = 1.
         self.a = (3 * load / (4 * self.curvature * self.e_star))**(1. / 3.)
 
         self.x = np.linspace(-self.domain_size / 2.,
                              self.domain_size / 2.,
                              self.n, dtype=tm.dtype)
         self.y = self.x.copy()
         self.x, self.y = np.meshgrid(self.x, self.y)
 
         self._computeSurface()
         self._computePressure()
         self._computeDisplacement()
 
     def _computeDisplacement(self):
         r = np.sqrt(self.x**2 + self.y**2)
         self.displacement = np.zeros_like(r)
         contact = r < self.a
         self.displacement[contact] = self.surface[contact]
         self.displacement[~contact] = \
             (self.surface[~contact]
              + self.a / (np.pi * self.radius) * np.sqrt(r[~contact]**2
                                                         - self.a**2)
              + (r[~contact]**2 - 2 * self.a**2) / (np.pi * self.radius)
              * np.arccos(self.a / r[~contact]))
 
     def _computePressure(self):
         r = np.sqrt(self.x**2 + self.y**2)
         self.pressure = np.zeros_like(r)
         contact = np.where(r < self.a)
         self.pressure[contact] = \
             2 * self.e_star / (np.pi * self.radius) \
             * np.sqrt(self.a**2 - r[contact]**2)
 
     def _computeSurface(self):
         self.surface = -1. / (2 * self.radius) * (self.x**2 + self.y**2)
 
 
 @pytest.fixture(scope="package")
 def hertz():
     return HertzFixture(1024, 0.00001)
 
 
 @pytest.fixture(scope="package")
 def hertz_coarse():
     return HertzFixture(512, 0.0001)
 
 
 @pytest.fixture(scope="package",
                 params=[tm.PolonskyKeerRey.pressure],
                 ids=pkridfn)
 def pkr(hertz, request):
     model = tm.ModelFactory.createModel(tm.model_type.basic_2d,
                                         [hertz.domain_size, hertz.domain_size],
                                         [hertz.n, hertz.n])
     model.E, model.nu = hertz.e_star, 0
     solver = tm.PolonskyKeerRey(model, hertz.surface, 1e-12,
                                 request.param,
                                 request.param)
     solver.solve(hertz.load)
 
     return model, hertz
 
 
 class WestergaardFixture:
     def __init__(self, n, load):
         self.domain_size = 1.
         self.lamda = 1.
         self.delta = 0.1
         self.e_star = 1.
 
         self.n = n
 
         self.p_star = np.pi * self.e_star * self.delta / self.lamda
 
         self.load = load * self.p_star
 
         self.a = self.lamda / np.pi \
             * np.arcsin(np.sqrt(self.load / self.p_star))
 
         self.x = np.linspace(-self.domain_size / 2.,
                              self.domain_size / 2.,
                              self.n, endpoint=False, dtype=tm.dtype)
 
         self._computeSurface()
         self._computePressure()
         self._computeDisplacement()
 
     def _computeSurface(self):
         self.surface = self.delta * np.cos(2 * np.pi * self.x / self.lamda)
 
     def _computePressure(self):
         self.pressure = np.zeros_like(self.surface)
         contact = np.where(np.abs(self.x) < self.a)
         self.pressure[contact] = 2 * self.load \
             * (np.cos(np.pi * self.x[contact] / self.lamda)
                / np.sin(np.pi * self.a / self.lamda)**2) \
             * np.sqrt(np.sin(np.pi * self.a / self.lamda)**2 -
                       np.sin(np.pi * self.x[contact] / self.lamda)**2)
 
     def _computeDisplacement(self):
         psi = np.pi * np.abs(self.x) / self.lamda
         psi_a = np.pi * self.a / self.lamda
 
         with np.errstate(invalid='ignore'):  # get some warnings out of the way
             self.displacement = (np.cos(2*psi) + 2 * np.sin(psi)
                                  * np.sqrt(np.sin(psi)**2 - np.sin(psi_a)**2)
                                  - 2 * np.sin(psi_a)**2 *
                                  np.log((np.sin(psi) +
                                          np.sqrt(np.sin(psi)**2
                                                  - np.sin(psi_a)**2))
                                         / np.sin(psi_a)))
 
         contact = np.where(np.abs(self.x) < self.a)
         self.displacement[contact] = np.cos(2*psi[contact])
         self.displacement *= self.load * self.lamda / (np.pi * self.e_star
                                                        * np.sin(psi_a)**2)
 
 
 @pytest.fixture(scope="package")
 def westergaard():
     return WestergaardFixture(19683, 0.1)
 
 
 class PatchWestergaard:
     def __init__(self, model_type):
         dim = tm.type_traits[model_type].dimension
         bdim = tm.type_traits[model_type].boundary_dimension
 
         domain = [1.] * dim
         size = [6] * dim
 
         self.modes = np.random.randint(1, 3, (bdim,))
 
         self.model = tm.ModelFactory.createModel(model_type, domain, size)
         self.model.E = 3.
         self.model.nu = 0.
 
         self.pressure = profile(np.cos, self.model.boundary_system_size,
                                 self.model.boundary_shape, self.modes, 1)
         self.solution = profile(
             np.cos, self.model.boundary_system_size,
             self.model.boundary_shape, self.modes,
             1 / (np.pi * self.model.E_star * norm(self.modes)))
 
 
 @pytest.fixture(scope="package",
                 params=set(type_list) - {tm.model_type.volume_1d},
                 ids=mtidfn)
 def patch_westergaard(request):
     return PatchWestergaard(request.param)
 
 
 class UniformPlasticity:
     def __init__(self, model_type, domain, sizes):
         self.n = sizes
         self.domain = domain
         self.model = tm.ModelFactory.createModel(model_type, domain,
                                                  sizes)
         self.E_h = 0.1
         self.sigma_y = 0.01
         self.residual = tm.ModelFactory.createResidual(self.model,
                                                        sigma_y=self.sigma_y,
                                                        hardening=self.E_h)
         self.model.E = 1.
         self.model.nu = 0.
 
     def solution(self, p):
         E, nu = self.model.E, self.model.nu
         E_h, sigma_y = self.E_h, self.sigma_y
         mu = E / (2 * (1 + nu))
 
         strain = -1 / (mu + E_h) * (p * (3 * mu + E_h) / (2 * mu)
                                     - np.sign(p) * sigma_y)
 
         dep = (2 * mu * np.abs(strain) - sigma_y) / (3 * mu + E_h)
         plastic_strain = np.sign(strain) / 2 * dep * np.array([
             -1, -1, 2, 0, 0, 0,
         ], dtype=tm.dtype)
 
         stress = 2 * mu * (np.array([
             0, 0, strain, 0, 0, 0
         ], dtype=tm.dtype) - plastic_strain)
 
         return {
             "stress": stress,
             "plastic_strain": plastic_strain,
             "cumulated_plastic_strain": dep
         }, {
             "stress": mu,
             "plastic_strain": 1,
             "cumulated_plastic_strain": 1
         }
 
 
 @pytest.fixture(params=[tm.model_type.volume_2d],
                 ids=mtidfn)
 def patch_isotropic_plasticity(request):
     return UniformPlasticity(request.param, [1., 1., 1.], [4, 4, 4])