diff --git a/CMake/FindTBB.cmake b/CMake/FindTBB.cmake
index 71565cc7a05e3165877c0234a4d4cd2e038693a2..6f17524cea41116149174a1995750f01e1254ffc 100644
--- a/CMake/FindTBB.cmake
+++ b/CMake/FindTBB.cmake
@@ -78,7 +78,7 @@ SET(TBB_CXX_FLAGS_DEBUG "-DTBB_USE_THREADING_TOOLS=1 -DTBB_USE_DEBUG=1")
 
 # --- List of libraries to be found ---
 
-IF(DEFINED ENV{ONEAPI_ROOT})
+IF(DEFINED ENV{ONEAPI_ROOT} AND MSVC)
     set(_TBB_TBBLIB_NAME    "tbb12")
 ELSE()
     set(_TBB_TBBLIB_NAME    "tbb")
@@ -245,37 +245,37 @@ unset(_TBB_INCLUDE_DIRS CACHE)
 # Versions
 # ----------------------------------------------------------------------------  
  
-SET(_TBB_VERSION_INTERFACE ${TBB_VERSION_INTERFACE})
-IF( (NOT TBB_VERSION_INTERFACE) AND TBB_INCLUDE_DIRS AND NOT DEFINED ENV{ONEAPI_ROOT})
-    FILE(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _FILE)
-    set(_TBB_VERSION_MAJOR 0)
-    set(_TBB_VERSION_MINOR 0)
-    STRING(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" _TBB_VERSION_INTERFACE "${_FILE}")
+# Find file containing version info and read the version
+IF(EXISTS "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h")
+    FILE(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _FILE) # older TBB
     STRING(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*"     "\\1" _TBB_VERSION_MAJOR     "${_FILE}")
     STRING(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*"     "\\1" _TBB_VERSION_MINOR     "${_FILE}")
-    set(_TBB_VERSION_STRING "${_TBB_VERSION_MAJOR}.${_TBB_VERSION_MINOR}")
-    unset(_FILE)
+    SET(TBB_VERSION ${_TBB_VERSION_MAJOR}.${_TBB_VERSION_MINOR})
+ELSEIF(EXISTS "${TBB_INCLUDE_DIRS}/oneapi/tbb/version.h")
+    FILE(READ "${TBB_INCLUDE_DIRS}/oneapi/tbb/version.h" _FILE) # newer oneTBB
+    STRING(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*"     "\\1" _TBB_VERSION_MAJOR     "${_FILE}")
+    STRING(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*"     "\\1" _TBB_VERSION_MINOR     "${_FILE}")
+    STRING(REGEX REPLACE ".*#define TBB_VERSION_PATCH ([0-9]+).*"     "\\1" _TBB_VERSION_PATCH     "${_FILE}")
+    SET(TBB_VERSION ${_TBB_VERSION_MAJOR}.${_TBB_VERSION_MINOR}.${_TBB_VERSION_PATCH})
+ELSE()
+    MESSAGE(STATUS "TBB_LIBRARIES: ${TBB_LIBRARIES}")
+    MESSAGE(STATUS "TBB_INCLUDE_DIRS: ${TBB_INCLUDE_DIRS}")
+    MESSAGE(FATAL_ERROR "Cannot find version file in ${TBB_INCLUDE_DIRS}/tbb.")
 ENDIF()
-IF(NOT TBB_VERSION_INTERFACE)
-    SET(TBB_VERSION_INTERFACE ${_TBB_VERSION_INTERFACE} CACHE STRING "" FORCE)
-    SET(TBB_VERSION_MAJOR ${_TBB_VERSION_MAJOR} CACHE INTERNAL "" FORCE)
-    SET(TBB_VERSION_MINOR ${_TBB_VERSION_MINOR} CACHE INTERNAL "" FORCE)
-    SET(TBB_VERSION_STRING ${_TBB_VERSION_STRING} CACHE STRING "" FORCE)
-    mark_as_advanced(TBB_VERSION_INTERFACE)
-    mark_as_advanced(TBB_VERSION_MAJOR)
-    mark_as_advanced(TBB_VERSION_MINOR)
-    mark_as_advanced(TBB_VERSION_STRING)
+# If file has been found, also read the interface version
+STRING(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1" _TBB_INTERFACE_VERSION "${_FILE}")
+SET(TBB_INTERFACE_VERSION ${_TBB_INTERFACE_VERSION} CACHE STRING "" FORCE)
+IF (_VERB)
+    MESSAGE(STATUS "TBB version: ${TBB_VERSION} (${TBB_INTERFACE_VERSION})")
 ENDIF()
 # ----------------------------------------------------------------------------   
 
 include(FindPackageHandleStandardArgs)
 # handle the QUIETLY and REQUIRED arguments and set TBB_FOUND to TRUE
 # if all listed variables are TRUE
-find_package_handle_standard_args(TBB DEFAULT_MSG 
-                TBB_LIBRARIES TBB_INCLUDE_DIRS
-                TBB_LIB_PATH TBB_LIB_PATH_DEBUG)
-
-#MESSAGE(STATUS "TBB_FOUND = ${TBB_FOUND}")
+find_package_handle_standard_args(TBB
+                                  REQUIRED_VARS TBB_LIBRARIES TBB_INCLUDE_DIRS TBB_LIB_PATH TBB_LIB_PATH_DEBUG
+                                  VERSION_VAR TBB_VERSION)
 # ----------------------------------------------------------------------------   
 
 unset(_VERB)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b5da42d071ae94d6989d1bb93a7538f3467114a..8bb8eca85ec84335a03f532aede76d2d355420f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,11 +15,7 @@
 # ----------------------------------------------------------------------------
 PROJECT(amfe)
 # ----------------------------------------------------------------------------
-CMAKE_MINIMUM_REQUIRED(VERSION 3.1)
-IF(${CMAKE_VERSION} VERSION_GREATER "3.14.0") # we might want to update the project and use the NEW behavior here
-    cmake_policy(SET CMP0078 OLD)
-    cmake_policy(SET CMP0086 OLD)
-ENDIF()
+CMAKE_MINIMUM_REQUIRED(VERSION 3.14)
 
 # -- I/O
 # Lib/Exe dir
@@ -66,17 +62,12 @@ OPTION(USE_MUMPS       "Compile with MUMPS"              ON)
 
 # -- DEPENDENCIES
 # Python
-IF (CMAKE_VERSION VERSION_LESS 3.12.0)
-    FIND_PACKAGE(PythonInterp 3.6 REQUIRED)
-    FIND_PACKAGE(PythonLibs 3.6 REQUIRED)
-ELSE()
-    FIND_PACKAGE(Python3 COMPONENTS Interpreter Development)
-    # use Python3_ROOT_DIR if wrong python found (e.g. anaconda)
-    SET(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
-    SET(PYTHON_LIBRARIES ${Python3_LIBRARIES})
-    SET(PYTHON_INCLUDE_PATH ${Python3_INCLUDE_DIRS}) 
-    SET(PYTHONLIBS_VERSION_STRING ${Python3_VERSION})     
-ENDIF()
+# use Python3_ROOT_DIR if wrong python found (e.g. anaconda)
+FIND_PACKAGE(Python3 COMPONENTS Interpreter Development)
+SET(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
+SET(PYTHON_LIBRARIES ${Python3_LIBRARIES})
+SET(PYTHON_INCLUDE_PATH ${Python3_INCLUDE_DIRS}) 
+SET(PYTHONLIBS_VERSION_STRING ${Python3_VERSION})     
 MESSAGE(STATUS "PYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE}")
 MESSAGE(STATUS "PYTHON_LIBRARIES:FILEPATH=${PYTHON_LIBRARIES}")
 MESSAGE(STATUS "PYTHON_INCLUDE_PATH:FILEPATH=${PYTHON_INCLUDE_PATH}")
diff --git a/fwk/_src/CMakeLists.txt b/fwk/_src/CMakeLists.txt
index 78c8658478fbd983c3d1625165f0e6957d27a216..fa26e7c3cce7349259a4e954a2738d4f5fccb86a 100644
--- a/fwk/_src/CMakeLists.txt
+++ b/fwk/_src/CMakeLists.txt
@@ -19,29 +19,16 @@ INCLUDE(${SWIG_USE_FILE})
 FILE(GLOB SRCS *.h *.cpp *.inl *.swg)
 FILE(GLOB ISRCS *.i)
 
-SET(CMAKE_SWIG_FLAGS "")
 SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES CPLUSPLUS ON)
+SET(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} "-interface" "_fwkw") # avoids "import _module_d" with MSVC/Debug
+SWIG_ADD_LIBRARY(fwkw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
+SET_PROPERTY(TARGET fwkw PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON)
+MACRO_DebugPostfix(fwkw)
 
-SET(SWINCFLAGS
--I${PROJECT_SOURCE_DIR}/fwk/src
-)
-SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES SWIG_FLAGS "${SWINCFLAGS}")
-
-if (${CMAKE_VERSION} VERSION_LESS "3.8.0")
-    SWIG_ADD_MODULE(fwkw python ${ISRCS} ${SRCS})
-else()
-    SWIG_ADD_LIBRARY(fwkw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
-endif()
-MACRO_DebugPostfix(_fwkw)
-
-TARGET_INCLUDE_DIRECTORIES(_fwkw PRIVATE ${PROJECT_SOURCE_DIR}/fwk/_src
-                                         ${PYTHON_INCLUDE_PATH}
-)
-
-SWIG_LINK_LIBRARIES(fwkw
-                    fwk ${PYTHON_LIBRARIES}
-)
+TARGET_INCLUDE_DIRECTORIES(fwkw PRIVATE ${PROJECT_SOURCE_DIR}/fwk/_src
+                                        ${PYTHON_INCLUDE_PATH})
+TARGET_LINK_LIBRARIES(fwkw PRIVATE fwk ${PYTHON_LIBRARIES})
 
 INSTALL(FILES "${EXECUTABLE_OUTPUT_PATH}/\${BUILD_TYPE}/fwkw.py"
         DESTINATION ${CMAKE_INSTALL_PREFIX})
-INSTALL(TARGETS _fwkw DESTINATION ${CMAKE_INSTALL_PREFIX})
+INSTALL(TARGETS fwkw DESTINATION ${CMAKE_INSTALL_PREFIX})
diff --git a/fwk/wutils.py b/fwk/wutils.py
index d7e2a7a0a6c80da61aa796c66e5f787bed6bd36d..d5baa08e243c06eb43a2344f9314a93f4463408c 100644
--- a/fwk/wutils.py
+++ b/fwk/wutils.py
@@ -1,13 +1,13 @@
 # -*- coding: utf-8 -*-
 
 # Copyright 2020 University of Liège
-# 
+#
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# 
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -96,7 +96,7 @@ def findbins(modname, dirB=['build','wavesB'], verb=False): # changer wavesB en
         print("[findbins] looking for linux/mingw libs")
     libpath.append( os.path.join(spath, 'bin') ) # also for win-mingw
 
-	# check the paths one by one
+    # check the paths one by one
     for p in libpath:
         if os.path.isdir(p):
             if verb:
@@ -133,40 +133,35 @@ def initDLL():
 
 # ------------------------------------------------------------------------------
 
-def initMKL(nthreads, verb=False):
+def initMKL(verb=False):
+    """Initialize the environment for MKL
+       Since we use the SDL for MKL, we need to configure it at runtime.
+       This is MUST be performed before any calls to MKL.
+       Could be improved by performing the init from the C++ and actually checking that the threading layer is the one we want, instead of using env var.
+    """
     import os
-    # we try to have full control over the threading environment
-    # => set MKL/OMP variables if (and only if) they are not set!
-    # number of "default" threads for MKL (available MKL domains: ALL, BLAS, VML, PARDISO)
-    if not 'MKL_NUM_THREADS' in os.environ:
-        os.environ['MKL_NUM_THREADS'] = '%d' % nthreads
-        os.environ['MKL_DOMAIN_NUM_THREADS'] = 'MKL_DOMAIN_ALL=%d' % nthreads
-    # threading interface
-    if not 'MKL_THREADING_LAYER' in os.environ:
-        if nthreads == 1:
-            os.environ['MKL_THREADING_LAYER'] = 'SEQUENTIAL'
-        else:
-            os.environ['MKL_THREADING_LAYER'] = 'TBB'
-    # interface (32 or 64 bits)
+    # Define threading interface
     if not 'MKL_INTERFACE_LAYER' in os.environ:
         os.environ['MKL_INTERFACE_LAYER'] = 'LP64' # 32 bits (default), ILP64 for 64 bits
-    # we do not want to use OpenMP
-    if not 'OMP_NUM_THREADS' in os.environ:
-        os.environ['OMP_NUM_THREADS'] = '1'
+    # Define threading layer
+    if not 'MKL_THREADING_LAYER' in os.environ:
+        os.environ['MKL_THREADING_LAYER'] = 'TBB' # sequential computation will be performed with 1 TBB thread
+    # Force number of OMP threads to 1, TBB threads are controlled from inside the C++ code
+    if not 'MKL_NUM_THREADS' in os.environ:
+        os.environ['MKL_NUM_THREADS'] = '1'
+        os.environ['MKL_DOMAIN_NUM_THREADS'] = 'MKL_DOMAIN_ALL=1'
+    # Force number of OMP threads to remain unchanged
     if not 'MKL_DYNAMIC' in os.environ:
         os.environ['MKL_DYNAMIC'] = 'FALSE'
-    if not 'OMP_DYNAMIC' in os.environ:
-        os.environ['OMP_DYNAMIC'] = 'FALSE'
-    # display variables
-    print(f'Envrionment initialized for MKL with {nthreads} threads.')
+    # Display variables
     if verb:
-        for s in ['OMP_', 'MKL', 'KMP']:
-            print('* %s environment:' % s)        
+        for s in ['OMP', 'MKL', 'KMP']:
+            print('* %s environment:' % s)
             for key, value in os.environ.items():
                 if s in key:
                     print('    %s = %s' % (key, value))
     print('')
-  
+
 # ------------------------------------------------------------------------------
 
 def setupwdir(testname):
@@ -188,7 +183,7 @@ def setupwdir(testname):
         name = "noname"
     import os, os.path
 
-    # builds the name of the workspace folder       
+    # builds the name of the workspace folder
     #print "__file__=",__file__
     dir1 = os.path.abspath(os.path.dirname(__file__)+os.sep+"..")+os.sep
     print("dir1=",dir1)
@@ -198,7 +193,7 @@ def setupwdir(testname):
     resdir = testname[len(common):].replace(os.sep,"_")
     resdir = os.path.splitext(resdir)[0] # remove ".py"
     if siz>1:
-        # append the mpi size if mpi is used 
+        # append the mpi size if mpi is used
         # (so that the same test can be run concurrently with
         # sevral mpi sizes in ctest)
         resdir += "_mpi%d" % siz
@@ -220,10 +215,10 @@ def setupwdir(testname):
                     os.remove(fpth)
                 elif os.path.isdir(fpth):
                     shutil.rmtree(fpth)
-        
+
     if siz>1: # if multiple process => send sync to slaves.
         comm.Barrier()
-    
+
     # we can now safely go into the new folder
     os.chdir(wdir)
 
@@ -283,7 +278,7 @@ def run():
 
     # parse args
     args = parseargs()
-    initMKL(args.k, verb=True) # initialize threading layer and number of threads
+    initMKL(verb=True) # initialize MKL environment (threading interface, layer and number of threads)
     if args.fpe:
         fwk.enableFpe() # enables floating point exceptions at runtime
         print(ccolors.ANSI_YELLOW + 'Floating point exceptions will cause numpy to overflow!' + ccolors.ANSI_RESET)
@@ -305,4 +300,5 @@ def run():
     print("starting test", testname)
     print("time:", time.strftime("%c"))
     print("hostname:", socket.gethostname())
-    exec(open(testname, 'r', encoding='utf8').read(), globals(), globals()) # exec at global level!!
+    script = open(testname, 'r', encoding='utf-8').read()
+    exec(compile(script, testname, 'exec'), {'__file__': testname, '__name__':'__main__'}) # compile to get clean backtrace
diff --git a/init.py b/init.py
index d6bb154c89a0d4bc6e06f5fb34ca775c16b8e303..1d1037f1a20bdaa95e7094b807f50f1d98fed800 100644
--- a/init.py
+++ b/init.py
@@ -2,3 +2,6 @@
 # Add this dir to the python path so that sub-dir can be imported as sub-module
 import os.path, sys
 sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+# Initialize MKL environment
+from .fwk import wutils
+wutils.initMKL()
diff --git a/tbox/_src/CMakeLists.txt b/tbox/_src/CMakeLists.txt
index 1dea57d843faf64c93ee41ba2a9ce4ceee12ea56..9e309715d7fd92b4cd209b1afd2e37159a8b2435 100644
--- a/tbox/_src/CMakeLists.txt
+++ b/tbox/_src/CMakeLists.txt
@@ -19,33 +19,17 @@ INCLUDE(${SWIG_USE_FILE})
 FILE(GLOB SRCS *.h *.cpp *.inl *.swg)
 FILE(GLOB ISRCS *.i)
 
-SET(CMAKE_SWIG_FLAGS "")
 SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES CPLUSPLUS ON)
+SET(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} "-interface" "_tboxw") # avoids "import _module_d" with MSVC/Debug
+SWIG_ADD_LIBRARY(tboxw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
+SET_PROPERTY(TARGET tboxw PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON)
+MACRO_DebugPostfix(tboxw)
 
-SET(SWINCFLAGS 
--I${PROJECT_SOURCE_DIR}/tbox/src
--I${PROJECT_SOURCE_DIR}/tbox/_src
--I${PROJECT_SOURCE_DIR}/fwk/src
--I${PROJECT_SOURCE_DIR}/fwk/_src
-)
-SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES SWIG_FLAGS "${SWINCFLAGS}")
-
-if (${CMAKE_VERSION} VERSION_LESS "3.8.0")
-    SWIG_ADD_MODULE(tboxw python ${ISRCS} ${SRCS})
-else()
-    SWIG_ADD_LIBRARY(tboxw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
-endif()
-MACRO_DebugPostfix(_tboxw)
-
-TARGET_INCLUDE_DIRECTORIES(_tboxw PRIVATE ${PROJECT_SOURCE_DIR}/fwk/_src
-                                          ${PROJECT_SOURCE_DIR}/tbox/_src
-                                          ${PYTHON_INCLUDE_PATH}
-)
-
-SWIG_LINK_LIBRARIES(tboxw
-                    tbox fwk ${PYTHON_LIBRARIES}
-)
+TARGET_INCLUDE_DIRECTORIES(tboxw PRIVATE ${PROJECT_SOURCE_DIR}/tbox/_src
+                                         ${PROJECT_SOURCE_DIR}/fwk/_src
+                                         ${PYTHON_INCLUDE_PATH})
+TARGET_LINK_LIBRARIES(tboxw PRIVATE tbox fwk ${PYTHON_LIBRARIES})
 
 INSTALL(FILES "${EXECUTABLE_OUTPUT_PATH}/\${BUILD_TYPE}/tboxw.py"
         DESTINATION ${CMAKE_INSTALL_PREFIX})
-INSTALL(TARGETS _tboxw DESTINATION ${CMAKE_INSTALL_PREFIX})
+INSTALL(TARGETS tboxw DESTINATION ${CMAKE_INSTALL_PREFIX})
diff --git a/tbox/_src/tboxw.h b/tbox/_src/tboxw.h
index b9feacdafc52341b820365f648c374ef1b68ced9..3da0640093557e341d636782a8ea0c232b2ca019 100644
--- a/tbox/_src/tboxw.h
+++ b/tbox/_src/tboxw.h
@@ -25,6 +25,7 @@
 #include "wCacheQuad4.h"
 #include "wCacheTetra4.h"
 #include "wCacheTri3.h"
+#include "wDss.h"
 #include "wElement.h"
 #include "wFct0.h"
 #include "wFct1.h"
diff --git a/tbox/_src/tboxw.i b/tbox/_src/tboxw.i
index 36e9d5a8d884fece0dcaf5ea7e41263788d6aaad..135b6a94aeca4184098dd2ad2ef54ffaefd6aa7d 100644
--- a/tbox/_src/tboxw.i
+++ b/tbox/_src/tboxw.i
@@ -97,9 +97,7 @@ namespace std {
 
 %include "wNode.h"
 %nodefault tbox::Element; // Element is pure virtual but swig doesn't know it.
-%warnfilter(509); // Shadowed overloaded method MATTYPE. Code compiling and running.
 %include "wElement.h"
-%warnfilter(+509);
 %include "wTag.h"
 %include "wQuad4.h"
 %include "wTetra4.h"
@@ -143,7 +141,6 @@ namespace std {
 %warnfilter(+509);
 %shared_ptr(tbox::MshDeform);
 %immutable tbox::MshDeform::msh; // read-only variable (no setter)
-%immutable tbox::MshDeform::nthreads;
 %include "wMshDeform.h" // included after std templates because it needs them
 %shared_ptr(tbox::MshCrack);
 %include "wMshCrack.h"
@@ -156,6 +153,8 @@ namespace std {
 %include "wLinearSolver.h"
 %shared_ptr(tbox::Pardiso);
 %include "wPardiso.h"
+%shared_ptr(tbox::Dss);
+%include "wDss.h"
 %shared_ptr(tbox::Mumps);
 %include "wMumps.h"
 %shared_ptr(tbox::SparseLu);
diff --git a/tbox/src/CMakeLists.txt b/tbox/src/CMakeLists.txt
index c2ccf55085a1e5a9e4382099f7145a9ad2441206..9aedbbc125b0d24212fb4774f66ad4a4923ee6cb 100644
--- a/tbox/src/CMakeLists.txt
+++ b/tbox/src/CMakeLists.txt
@@ -25,12 +25,12 @@ TARGET_INCLUDE_DIRECTORIES(tbox PUBLIC ${PROJECT_SOURCE_DIR}/tbox/src)
 FIND_PACKAGE(TBB REQUIRED)
 SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${TBB_CXX_FLAGS_DEBUG}")
 TARGET_INCLUDE_DIRECTORIES(tbox PUBLIC ${TBB_INCLUDE_DIRS})
-IF(TBB_VERSION_INTERFACE LESS 11004)
+IF(TBB_INTERFACE_VERSION LESS 11004)
     TARGET_COMPILE_DEFINITIONS(tbox PUBLIC TBB_PREVIEW_GLOBAL_CONTROL)
 ENDIF()
 TARGET_LINK_LIBRARIES(tbox  ${TBB_LIBRARIES})
 
-# -- MKL/BLAS/LAPACK --
+# -- MKL --
 # Do not forget to set your environement variables using: (default path)
 # - "C:\Program Files (x86)\Intel\Composer XE\mkl\bin\intel64\mklvars_intel64.bat" (windows, check that VS is in x64 mode)
 # - source /opt/intel/mkl/bin/mklvars.sh intel64 (linux)
@@ -45,40 +45,17 @@ IF(USE_MKL)
     MESSAGE(STATUS "MKL_LIBRARIES=${MKL_LIBRARIES}")
     IF(MKL_INCLUDE_DIRS AND MKL_LIBRARIES)
         MESSAGE(STATUS "Found Intel MKL")
-        SET(FOUND_MKL TRUE)
-        SET(LAPACK_INCLUDE_DIRS ${MKL_INCLUDE_DIRS})
-        SET(LAPACK_LIBRARIES ${MKL_LIBRARIES})
+        TARGET_INCLUDE_DIRECTORIES(tbox PUBLIC ${MKL_INCLUDE_DIRS})
+        TARGET_LINK_LIBRARIES(tbox ${MKL_LIBRARIES})
     ELSE()
         MESSAGE(FATAL_ERROR "Intel MKL not found!")
     ENDIF()
-# Find BLAS
-ELSE()
-    IF(${BLA_VENDOR} MATCHES "OpenBlas")
-       FIND_LIBRARY(LAPACK_LIBRARIES openblas)
-    ELSE()
-       FIND_PACKAGE(LAPACK REQUIRED) #FIND_LAPACK calls FIND_BLAS
-    ENDIF()
-    IF(NOT LAPACK_LIBRARIES)
-        MESSAGE(FATAL_ERROR "BLAS/LAPACK not found!")
-    ENDIF()
-    MESSAGE(STATUS "LAPACK_LIBRARIES=${LAPACK_LIBRARIES}")  
-    MESSAGE(STATUS "LAPACK_LINKER_FLAGS=${LAPACK_LINKER_FLAGS}")  
-    MESSAGE(STATUS "BLA_VENDOR=${BLA_VENDOR}")  
-    MESSAGE(STATUS "BLA_STATIC=${BLA_STATIC}")
 ENDIF()
 
 # -- Eigen --
 FIND_PACKAGE(EIGEN 3.3.4 REQUIRED)
-TARGET_INCLUDE_DIRECTORIES(tbox PUBLIC ${EIGEN_INCLUDE_DIRS} ${LAPACK_INCLUDE_DIRS})
+TARGET_INCLUDE_DIRECTORIES(tbox PUBLIC ${EIGEN_INCLUDE_DIRS})
 TARGET_COMPILE_DEFINITIONS(tbox PUBLIC EIGEN_DONT_PARALLELIZE)
-IF(FOUND_MKL)
-    MESSAGE(STATUS "Linking Eigen with MKL")
-    TARGET_COMPILE_DEFINITIONS(tbox PUBLIC EIGEN_USE_MKL_ALL)
-ELSE()
-    MESSAGE(STATUS "Linking Eigen with BLAS ${BLA_VENDOR}")
-    TARGET_COMPILE_DEFINITIONS(tbox PUBLIC EIGEN_USE_BLAS)
-ENDIF()
-TARGET_LINK_LIBRARIES(tbox ${LAPACK_LIBRARIES})
 
 # -- MUMPS --
 IF (USE_MUMPS)
diff --git a/tbox/src/tbox.h b/tbox/src/tbox.h
index b6814d4c6444cc2afffd02c09a043b438db500b7..d13650219ec96bdffc6662d3759f9a2c19ce4ebd 100644
--- a/tbox/src/tbox.h
+++ b/tbox/src/tbox.h
@@ -99,6 +99,7 @@ class Linesearch;
 // Linear solvers
 class LinearSolver;
 class Pardiso;
+class Dss;
 class Mumps;
 class SparseLu;
 class Gmres;
diff --git a/tbox/src/wDss.cpp b/tbox/src/wDss.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3aaf5235356874006cedd45b5d30a5b8c50df7c5
--- /dev/null
+++ b/tbox/src/wDss.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2020 University of Liège
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wDss.h"
+#ifdef USE_MKL
+
+using namespace tbox;
+
+Dss::Dss() : LinearSolver()
+{
+    handle = NULL;
+    MKL_INT opt = MKL_DSS_MSG_LVL_INFO + MKL_DSS_TERM_LVL_FATAL + MKL_DSS_ZERO_BASED_INDEXING; // continue exec until non-recoverable (fatal) error
+    print(dss_create(handle, opt));
+}
+Dss::~Dss()
+{
+    if (handle)
+    {
+        MKL_INT opt = MKL_DSS_DEFAULTS;
+        print(dss_delete(handle, opt));
+        handle = NULL;
+    }
+}
+
+void Dss::analyze(Eigen::SparseMatrix<double> const &A)
+{
+    // Check that matrix A is square
+    if (A.rows() != A.cols())
+        throw std::runtime_error("tbox::Dss: Matrix A is not square!\n");
+    // Convert to CRS
+    Eigen::SparseMatrix<double, Eigen::RowMajor> AA = A;
+    if (!AA.isCompressed())
+        AA.makeCompressed();
+
+    // Build data structure
+    MKL_INT opt = MKL_DSS_NON_SYMMETRIC;          // consider MKL_DSS_SYMMETRIC and MKL_DSS_SYMMETRIC_STRUCTURE
+    MKL_INT n = static_cast<int>(AA.rows());      // matrix size (number of rows and columns)
+    MKL_INT nz = static_cast<int>(AA.nonZeros()); // number of non-zero entries
+    const MKL_INT *rStart = AA.outerIndexPtr();   // starting index of non-zero entry for a row
+    const MKL_INT *cIndex = AA.innerIndexPtr();   // index of column of non-zero entry
+    print(dss_define_structure(handle, opt, rStart, n, n, cIndex, nz));
+
+    // Define reordering
+    opt = MKL_DSS_AUTO_ORDER;
+    print(dss_reorder(handle, opt, 0));
+}
+void Dss::factorize(Eigen::SparseMatrix<double> const &A)
+{
+    Eigen::SparseMatrix<double, Eigen::RowMajor> AA = A;
+    MKL_INT opt = MKL_DSS_INDEFINITE;
+    const double *val = AA.valuePtr();
+    print(dss_factor_real(handle, opt, val));
+}
+void Dss::solve(Eigen::Map<Eigen::VectorXd> const &b, Eigen::Map<Eigen::VectorXd> &x)
+{
+    MKL_INT opt = MKL_DSS_DEFAULTS;
+    MKL_INT n = 1; // number of rhs
+    const double *rhs = b.data();
+    double *sol = x.data();
+    print(dss_solve_real(handle, opt, rhs, n, sol));
+}
+void Dss::compute(Eigen::SparseMatrix<double> const &A, Eigen::Map<Eigen::VectorXd> const &b, Eigen::Map<Eigen::VectorXd> &x)
+{
+    this->analyze(A);
+    this->factorize(A);
+    this->solve(b, x);
+}
+
+/**
+ * @brief Print status code
+ */
+void Dss::print(int cod)
+{
+    if (cod != MKL_DSS_SUCCESS)
+    {
+        std::stringstream err;
+        err << "tbox::Dss: DSS returned code " << cod << "\n";
+        throw std::runtime_error(err.str());
+    }
+}
+
+void Dss::write(std::ostream &out) const
+{
+    out << "Intel MKL DSS (PARDISO)" << std::endl;
+}
+
+#endif // USE_MKL
diff --git a/tbox/src/wDss.h b/tbox/src/wDss.h
new file mode 100644
index 0000000000000000000000000000000000000000..da42e4ac37db46fd320c6bef2e4a8814db7278d2
--- /dev/null
+++ b/tbox/src/wDss.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 University of Liège
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WDSS_H
+#define WDSS_H
+
+#include "tbox.h"
+#ifdef USE_MKL
+
+#include "wLinearSolver.h"
+#include <mkl_dss.h>
+#include <Eigen/Sparse>
+#include <limits>
+
+namespace tbox
+{
+/**
+ * @brief Intel DSS (PARDISO) interface
+ * @authors Adrien Crovato
+ */
+class TBOX_API Dss : public LinearSolver
+{
+    void *handle; // DSS handle
+
+    void print(int cod);
+
+public:
+    Dss();
+    ~Dss();
+    virtual LSolType type() const override { return LSolType::DSS; }
+
+#ifndef SWIG
+    virtual void analyze(Eigen::SparseMatrix<double> const &A) override;
+    virtual void factorize(Eigen::SparseMatrix<double> const &A) override;
+    virtual void solve(Eigen::Map<Eigen::VectorXd> const &b, Eigen::Map<Eigen::VectorXd> &x) override;
+    virtual void compute(Eigen::SparseMatrix<double> const &A, Eigen::Map<Eigen::VectorXd> const &b, Eigen::Map<Eigen::VectorXd> &x) override;
+
+    virtual double getError() override { return std::numeric_limits<double>::epsilon(); }
+    virtual int getIterations() override { return 1; }
+
+    virtual void write(std::ostream &out) const override;
+#endif
+};
+
+} // namespace tbox
+
+#endif // USE_MKL
+
+#endif // WDSS_H
\ No newline at end of file
diff --git a/tbox/src/wElement.cpp b/tbox/src/wElement.cpp
index f2755ff8077b5d5bdde23d0ec6d4f60e3b3dddba..38b1c789a9dd1102ce549ce6e6924bdcdbe1c32e 100644
--- a/tbox/src/wElement.cpp
+++ b/tbox/src/wElement.cpp
@@ -26,26 +26,26 @@ namespace tbox
 {
 
 TBOX_API std::ostream &
-operator<<(std::ostream &out, ELTYPE const &obj)
+operator<<(std::ostream &out, ElType const &obj)
 {
     switch (obj)
     {
-    case ELTYPE::LINE2:
+    case ElType::LINE2:
         out << "LINE2";
         break;
-    case ELTYPE::TRI3:
+    case ElType::TRI3:
         out << "TRI3";
         break;
-    case ELTYPE::QUAD4:
+    case ElType::QUAD4:
         out << "QUAD4";
         break;
-    case ELTYPE::TETRA4:
+    case ElType::TETRA4:
         out << "TETRA4";
         break;
-    case ELTYPE::HEX8:
+    case ElType::HEX8:
         out << "HEX8";
         break;
-    case ELTYPE::POINT1:
+    case ElType::POINT1:
         out << "POINT1";
         break;
     default:
diff --git a/tbox/src/wElement.h b/tbox/src/wElement.h
index 3c02267a31885b6d3f8bcc961503387628244f38..000c0fc8f7b1e8b4f0fd9a16b1833616b7a4293b 100644
--- a/tbox/src/wElement.h
+++ b/tbox/src/wElement.h
@@ -29,7 +29,7 @@ namespace tbox
  * @brief element types (from gmsh)
  * @authors Romain Boman
  */
-enum class ELTYPE
+enum class ElType
 {
     UNKNOWN = 0,
     LINE2 = 1,
@@ -40,7 +40,7 @@ enum class ELTYPE
     POINT1 = 15
 };
 #ifndef SWIG
-TBOX_API std::ostream &operator<<(std::ostream &out, ELTYPE const &obj);
+TBOX_API std::ostream &operator<<(std::ostream &out, ElType const &obj);
 #endif
 
 /**
@@ -81,9 +81,9 @@ public:
     Element(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
     virtual ~Element() {}
 #endif
-    virtual ELTYPE type() const
+    virtual ElType type() const
     {
-        return ELTYPE::UNKNOWN;
+        return ElType::UNKNOWN;
     }
 
 #ifndef SWIG
diff --git a/tbox/src/wGmres.cpp b/tbox/src/wGmres.cpp
index b708c6e54fb045932d808cc5ddcbd9ef39158fa2..40a7db3c275cf368865dd5bc15e948e7f8a3a378 100644
--- a/tbox/src/wGmres.cpp
+++ b/tbox/src/wGmres.cpp
@@ -17,14 +17,14 @@
 #include "wGmres.h"
 using namespace tbox;
 
-Gmres::Gmres() : LinearSolver()
+Gmres::Gmres(int _fill, double _tsh, int _rst, double _tol, int _mit) : LinearSolver(), fill(_fill), tsh(_tsh), rst(_rst), tol(_tol), mit(_mit)
 {
-    // set the preconditioner and solver parameters to "good" starting values
-    solver.preconditioner().setFillfactor(1);
-    solver.preconditioner().setDroptol(1e-6);
-    solver.set_restart(50);
-    solver.setTolerance(1e-8);
-    solver.setMaxIterations(1000);
+    // set the preconditioner and solver parameters
+    solver.preconditioner().setFillfactor(fill);
+    solver.preconditioner().setDroptol(tsh);
+    solver.set_restart(rst);
+    solver.setTolerance(tol);
+    solver.setMaxIterations(mit);
 }
 
 void Gmres::analyze(Eigen::SparseMatrix<double> const &A)
@@ -45,7 +45,13 @@ void Gmres::compute(Eigen::SparseMatrix<double> const &A, Eigen::Map<Eigen::Vect
     x = solver.solve(b);
 }
 
+void Gmres::setTolerance(double t)
+{
+    tol = t;
+    solver.setTolerance(t);
+}
+
 void Gmres::write(std::ostream &out) const
 {
-    out << "GMRES (Eigen)" << std::endl;
+    out << "Eigen GMRES(" << rst << "," << static_cast<int>(log10(tol)) << ")/ILUT(" << fill << "," << static_cast<int>(log10(tsh)) << ")" << std::endl;
 }
diff --git a/tbox/src/wGmres.h b/tbox/src/wGmres.h
index b0a4a44ca14328533837ca4d3ace03afc91b33fe..928bf34fdceb9fe4854d675350c158ee9e5015d6 100644
--- a/tbox/src/wGmres.h
+++ b/tbox/src/wGmres.h
@@ -30,11 +30,18 @@ namespace tbox
  */
 class TBOX_API Gmres : public LinearSolver
 {
+    int fill;   // fill-in factor for ILUT
+    double tsh; // drop tolerance threshold for ILUT
+    int rst;    // number of iterations before restart for GMRES
+    double tol; // relative tolerance for GMRES
+    int mit;    // maximum number of iterations for GMRES
     Eigen::GMRES<Eigen::SparseMatrix<double>, Eigen::IncompleteLUT<double>> solver;
 
 public:
-    Gmres();
+    Gmres(int _fill = 1, double _tsh = 1e-6, int _rst = 30, double _tol = 1e-8, int _mit = 1000);
+    Gmres(const Gmres &gmres) : Gmres(gmres.fill, gmres.tsh, gmres.rst, gmres.tol, gmres.mit) {}
     virtual ~Gmres() {}
+    virtual LSolType type() const override { return LSolType::GMRES_ILUT; }
 
 #ifndef SWIG
     virtual void analyze(Eigen::SparseMatrix<double> const &A) override;
@@ -43,19 +50,12 @@ public:
     virtual void compute(Eigen::SparseMatrix<double> const &A, Eigen::Map<Eigen::VectorXd> const &b, Eigen::Map<Eigen::VectorXd> &x) override;
 
     virtual double getError() override { return solver.error(); }
-    virtual int getIterations() override { return solver.iterations(); }
+    virtual int getIterations() override { return static_cast<int>(solver.iterations()); }
+
+    void setTolerance(double t);
 
     virtual void write(std::ostream &out) const override;
 #endif
-
-    void setFillFactor(int f)
-    {
-        solver.preconditioner().setFillfactor(f);
-    }
-    void setDropTol(double t) { solver.preconditioner().setDroptol(t); }
-    void setTolerance(double t) { solver.setTolerance(t); }
-    void setRestart(int r) { solver.set_restart(r); }
-    void setMaxIterations(int n) { solver.setMaxIterations(n); }
 };
 
 } // namespace tbox
diff --git a/tbox/src/wGmshExport.h b/tbox/src/wGmshExport.h
index eefcf4470d05730ceeaa7aba8552ae93c06c15bd..dc74bf3ebb68693991f9e7f82bba716344b45f9c 100644
--- a/tbox/src/wGmshExport.h
+++ b/tbox/src/wGmshExport.h
@@ -34,6 +34,7 @@ class TBOX_API GmshExport : public MshExport
 public:
     GmshExport(std::shared_ptr<MshData> _msh);
     virtual ~GmshExport();
+    virtual WrtType type() const override { return WrtType::GMSH; }
 
     virtual void save(std::string const &fname) const override;
 #ifndef SWIG
@@ -45,4 +46,4 @@ public:
 
 } // namespace tbox
 
-#endif //WGMSHUTILS_H
+#endif // WGMSHEXPORT_H
diff --git a/tbox/src/wGmshImport.cpp b/tbox/src/wGmshImport.cpp
index 3ea091dcafff117000b8284cbb216c7821ff2c80..4a03444fa641e20bca9cbc0cdfdc8308846b875b 100644
--- a/tbox/src/wGmshImport.cpp
+++ b/tbox/src/wGmshImport.cpp
@@ -256,7 +256,7 @@ void GmshImport::readMshElements(FILE *file, int myrank)
                     belongsToPart = true;
             }
         }
-        if (static_cast<ELTYPE>(type) == ELTYPE::POINT1)
+        if (static_cast<ElType>(type) == ElType::POINT1)
             belongsToPart = true;
 
         bool skipline = true;
@@ -265,32 +265,32 @@ void GmshImport::readMshElements(FILE *file, int myrank)
         {
             skipline = false;
             std::vector<Node *> nods;
-            if (static_cast<ELTYPE>(type) == ELTYPE::HEX8) // 8-node hexa
+            if (static_cast<ElType>(type) == ElType::HEX8) // 8-node hexa
             {
                 readElNods(file, 8, nods);
                 msh->elems.push_back(new Hex8(no, ptag, etag, prts, nods));
             }
-            else if (static_cast<ELTYPE>(type) == ELTYPE::QUAD4) // 4-node quad
+            else if (static_cast<ElType>(type) == ElType::QUAD4) // 4-node quad
             {
                 readElNods(file, 4, nods);
                 msh->elems.push_back(new Quad4(no, ptag, etag, prts, nods));
             }
-            else if (static_cast<ELTYPE>(type) == ELTYPE::TETRA4) // 4-node tetra
+            else if (static_cast<ElType>(type) == ElType::TETRA4) // 4-node tetra
             {
                 readElNods(file, 4, nods);
                 msh->elems.push_back(new Tetra4(no, ptag, etag, prts, nods));
             }
-            else if (static_cast<ELTYPE>(type) == ELTYPE::LINE2) // 2-node line
+            else if (static_cast<ElType>(type) == ElType::LINE2) // 2-node line
             {
                 readElNods(file, 2, nods);
                 msh->elems.push_back(new Line2(no, ptag, etag, prts, nods));
             }
-            else if (static_cast<ELTYPE>(type) == ELTYPE::POINT1) // 1-node point
+            else if (static_cast<ElType>(type) == ElType::POINT1) // 1-node point
             {
                 readElNods(file, 1, nods);
                 msh->elems.push_back(new Point1(no, ptag, etag, prts, nods));
             }
-            else if (static_cast<ELTYPE>(type) == ELTYPE::TRI3) // 3-node triangle
+            else if (static_cast<ElType>(type) == ElType::TRI3) // 3-node triangle
             {
                 readElNods(file, 3, nods);
                 msh->elems.push_back(new Tri3(no, ptag, etag, prts, nods));
diff --git a/tbox/src/wGroup.cpp b/tbox/src/wGroup.cpp
index c26634fd859ab247d3e102ea628a242f8ff84203..b271543f2441c8da0d23f96aea35a90753eaa434 100644
--- a/tbox/src/wGroup.cpp
+++ b/tbox/src/wGroup.cpp
@@ -28,7 +28,7 @@ Group::Group(std::shared_ptr<MshData> _msh, int no) : wSharedObject(), msh(_msh)
     if (it == msh->ptags.end())
     {
         std::stringstream out;
-        out << "Physical Group #" << no << " not found among physical tags";
+        out << "Group: Physical Group #" << no << " not found among physical tags";
         throw std::out_of_range(out.str());
     }
     tag = it->second;
@@ -40,8 +40,8 @@ Group::Group(std::shared_ptr<MshData> _msh, std::string const &name) : wSharedOb
     if (it == msh->ntags.end())
     {
         std::stringstream out;
-        out << "Physical Group \"" << name << "\" not found among physical tags\n";
-        out << "[info] available tags:\n\t";
+        out << "Group: Physical Group \"" << name << "\" not found among physical tags\n";
+        out << "[info] available tags: ";
         for (auto it = msh->ntags.begin(); it != msh->ntags.end(); ++it)
         {
             auto itn(it);
diff --git a/tbox/src/wHex8.h b/tbox/src/wHex8.h
index 391b73dce4b71ae7b5866f7d2b26dfc8bf1fe19b..fb3e427b5cc27efdd7e51f709c7cbffe75b9083e 100644
--- a/tbox/src/wHex8.h
+++ b/tbox/src/wHex8.h
@@ -43,7 +43,7 @@ protected:
 public:
     Hex8(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
     virtual ~Hex8() {}
-    virtual ELTYPE type() const override { return ELTYPE::HEX8; }
+    virtual ElType type() const override { return ElType::HEX8; }
 
 #ifndef SWIG
     virtual void initValues(bool isvol, int ordr = 2) override;
diff --git a/tbox/src/wLine2.h b/tbox/src/wLine2.h
index 6b076608d104fc2ffe5877ee05ed9a69c918d68a..d87928f5898f76558d3161d4493790a26d7343b6 100644
--- a/tbox/src/wLine2.h
+++ b/tbox/src/wLine2.h
@@ -49,7 +49,7 @@ protected:
 public:
     Line2(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
     virtual ~Line2() {}
-    virtual ELTYPE type() const override { return ELTYPE::LINE2; }
+    virtual ElType type() const override { return ElType::LINE2; }
 
 #ifndef SWIG
     virtual void initValues(bool isvol, int ordr = 2) override;
diff --git a/tbox/src/wLinearSolver.h b/tbox/src/wLinearSolver.h
index 3e93813a4db7b1899b03c5fd6cfb80b81c44bfd6..b717b8c8c538a943dd73439da5df0e56aeda50c3 100644
--- a/tbox/src/wLinearSolver.h
+++ b/tbox/src/wLinearSolver.h
@@ -24,6 +24,20 @@
 namespace tbox
 {
 
+/**
+ * @brief Solver type
+ * @authors Adrien Crovato
+ */
+enum class LSolType
+{
+    UNDEFINED = 0,
+    SPARSE_LU = 1,
+    GMRES_ILUT = 2,
+    PARDISO = 3,
+    DSS = 4,
+    MUMPS = 5
+};
+
 /**
  * @brief Base interface class for linear solvers
  * @authors Adrien Crovato
@@ -33,6 +47,7 @@ class TBOX_API LinearSolver : public fwk::wSharedObject
 public:
     LinearSolver() {}
     virtual ~LinearSolver() {}
+    virtual LSolType type() const { return LSolType::UNDEFINED; }
 
 #ifndef SWIG
     virtual void analyze(Eigen::SparseMatrix<double> const &A);
diff --git a/tbox/src/wLinesearch.cpp b/tbox/src/wLinesearch.cpp
index 3bf8e33032bce9b003ac5c70f189202e267541b5..8254d77f9e6780993fe0a79158efcf55029c1d70 100644
--- a/tbox/src/wLinesearch.cpp
+++ b/tbox/src/wLinesearch.cpp
@@ -111,7 +111,7 @@ double FleuryLS::run()
     double phit = fct.eval(h);
     fevalIt = 2;
 
-    if (verbose > 2)
+    if (verbose > 3)
     {
         std::cout << "Starting Backeting \n";
         std::cout << "\t" << std::setprecision(16) << "phi1(" << 0 << ") = " << phi1 << '\n';
@@ -128,14 +128,14 @@ double FleuryLS::run()
         {
             if (h > hU)
             {
-                if (verbose > 2)
+                if (verbose > 3)
                 {
                     std::cout << "Positive slope detected (in ascending bracketing #1)!\n";
                 }
 
                 if (revAllowed)
                 {
-                    if (verbose > 2)
+                    if (verbose > 3)
                     {
                         std::cout << "Reverse Direction of Line Search (in ascending bracketing #1)!"
                                   << "\n";
@@ -146,7 +146,7 @@ double FleuryLS::run()
                 }
                 else
                 {
-                    if (verbose > 2)
+                    if (verbose > 3)
                     {
                         std::cout << "Error in ascending bracketing #1:\n"
                                   << std::setprecision(16) << "a1=" << 0 << " a2=" << h << " a3 =" << 2.0 * h << '\n'
@@ -164,7 +164,7 @@ double FleuryLS::run()
             phit = fct.eval(2 * h);
             fevalIt++;
 
-            if (verbose > 2)
+            if (verbose > 3)
             {
                 std::cout << "Ascending bracketing iteration # " << nB << ": \n";
                 std::cout << "\t"
@@ -185,14 +185,14 @@ double FleuryLS::run()
         {
             if (h < hL)
             {
-                if (verbose > 2)
+                if (verbose > 3)
                 {
                     std::cout << "Positive slope detected (in descending bracketing #2)!\n";
                 }
 
                 if (revAllowed)
                 {
-                    if (verbose > 2)
+                    if (verbose > 3)
                     {
                         std::cout << "Reverse Direction of Line Search (in descending bracketing #2) !"
                                   << "\n";
@@ -203,7 +203,7 @@ double FleuryLS::run()
                 }
                 else
                 {
-                    if (verbose > 2)
+                    if (verbose > 3)
                     {
                         std::cout << "Error in descending bracketing #2:\n"
                                   << std::setprecision(16) << "a1=" << 0 << " a2=" << h << " a3 =" << 2.0 * h << '\n'
@@ -220,7 +220,7 @@ double FleuryLS::run()
             phi3 = phit;
             phit = fct.eval(h / 2);
             fevalIt++;
-            if (verbose > 2)
+            if (verbose > 3)
             {
                 std::cout << "Descending bracketing iteration # " << nB << ": \n";
                 std::cout << "\t"
@@ -241,7 +241,7 @@ double FleuryLS::run()
     double a3 = 2 * h;
     double a4 = h * (4 * phi2 - 3 * phi1 - phi3) / (4 * phi2 - 2 * phi1 - 2 * phi3);
 
-    if (verbose > 2)
+    if (verbose > 3)
     {
         std::cout << "Backeting succeeds in " << nB << " iterations : \n";
         std::cout << "\t" << std::setprecision(16) << "phi1(" << a1 << ") = " << phi1 << '\n';
@@ -276,7 +276,7 @@ double FleuryLS::run()
             throw std::runtime_error(msg.str());
         }
 
-        if (verbose > 2)
+        if (verbose > 3)
         {
             std::cout << "Line search iteration # " << nLS << ": \n";
             std::cout << "\t" << std::setprecision(16) << "phi1(" << a1 << ") = " << phi1 << '\n';
@@ -319,7 +319,7 @@ double FleuryLS::run()
 
         A = (a2 - a3) * phi1 + (a3 - a1) * phi2 + (a1 - a2) * phi3;
 
-        if (verbose > 2)
+        if (verbose > 3)
         {
             std::cout << "\t" << std::setprecision(16) << "|A| = " << fabs(A) << "><" << tol << '\n';
         }
@@ -337,7 +337,7 @@ double FleuryLS::run()
              (A);
     }
 
-    if (verbose > 2)
+    if (verbose > 3)
     {
         std::cout << "Line search succeeds in " << nLS << " iterations : \n";
         std::cout << "\t" << std::setprecision(16) << "|A| = " << fabs(A) << '\n';
diff --git a/tbox/src/wMshConvert.cpp b/tbox/src/wMshConvert.cpp
index 013fff0c07c8539820a1d14ae2fd54138aafa852..dd48f05554736b46383083f81a0a29fa934adb95 100644
--- a/tbox/src/wMshConvert.cpp
+++ b/tbox/src/wMshConvert.cpp
@@ -22,6 +22,7 @@
 #include <fstream>
 #include <set>
 #include <algorithm>
+#include <limits>
 
 using namespace tbox;
 
diff --git a/tbox/src/wMshCrack.cpp b/tbox/src/wMshCrack.cpp
index 9e11b8a8f62a14dbb8d5b511bc299795a40d08bd..6ab84c00b74ff50cec732b8b0cf502bb039f68a0 100644
--- a/tbox/src/wMshCrack.cpp
+++ b/tbox/src/wMshCrack.cpp
@@ -28,7 +28,7 @@ using namespace tbox;
 
 MshCrack::MshCrack(std::shared_ptr<MshData> _msh, int _nDim) : wSharedObject(), msh(_msh), nDim(_nDim)
 {
-    hasCrack = true; // so that we prevent automatic data structure creation if addCrack() is not called
+    hasCrack = true; // so that we prevent automatic data structure creation if setCrack() is not called
     crckGrp = nullptr;
     exclGrp = nullptr;
 }
@@ -37,58 +37,84 @@ MshCrack::~MshCrack()
     // Delete temporary groups
     delete crckGrp;
     delete exclGrp;
-    for (size_t i = 0; i < bndGrps.size(); ++i)
-        delete bndGrps[i];
-    for (size_t i = 0; i < bndGrp.size(); ++i)
-        delete bndGrp[i];
+    for (size_t i = 0; i < sBndGrps.size(); ++i)
+        delete sBndGrps[i];
+    for (size_t i = 0; i < sBndGrp.size(); ++i)
+        delete sBndGrp[i];
+    for (size_t i = 0; i < vBndGrps.size(); ++i)
+        delete vBndGrps[i];
+    for (size_t i = 0; i < vBndGrp.size(); ++i)
+        delete vBndGrp[i];
     std::cout << "~MshCrack()\n";
 }
 
 /**
  * @brief Create temporary group for crack
  */
-void MshCrack::setCrack(std::string const &crckName)
+void MshCrack::setCrack(std::string const &name)
 {
     // Check if a crack needs to be made
-    auto it = msh->ntags.find(crckName + '_');
+    auto it = msh->ntags.find(name + '_');
     if (it != msh->ntags.end())
-        std::cout << "Groups " << crckName << " and " << crckName << "_ are already existing. MshCrack will not be run!\n";
+        std::cout << "Groups " << name << " and " << name << "_ are already existing. MshCrack will not be run\n";
     else
     {
         hasCrack = false;
-        crckGrp = new Group(msh, crckName);
+        crckGrp = new Group(msh, name);
+        if (crckGrp->tag->dim != nDim - 1)
+        {
+            std::stringstream err;
+            err << "MshCrack::setCrack: cannot create a crack of dimension " << crckGrp->tag->dim << ", dimension must be " << nDim - 1 << "!\n";
+            throw std::runtime_error(err.str());
+        }
     }
 }
 
 /**
  * @brief Create temporary group for excluded nodes on crack
  */
-void MshCrack::setExcluded(std::string const &exclName)
+void MshCrack::setExcluded(std::string const &name)
 {
     if (!hasCrack)
-        exclGrp = new Group(msh, exclName);
+        exclGrp = new Group(msh, name);
 }
 
 /**
  * @brief Create temporary groups for crack boundaries
  */
-void MshCrack::addBoundaries(std::vector<std::string> const &bndName)
+void MshCrack::addBoundary(std::string const &name)
 {
     if (!hasCrack)
     {
-        for (auto name : bndName)
+        // Check if the boundaries on both sides of the crack are explicitely provided in two different tags
+        // if so, store them
+        try
         {
-            // Check if the boundaries are explicitely separated...
-            try
+            Groups *gs = new Groups(msh, std::vector<std::string>{name, name + '_'});
+            if (gs->groups[0]->tag->dim == nDim)
+                vBndGrps.push_back(gs);
+            else if (gs->groups[0]->tag->dim == nDim - 1)
+                sBndGrps.push_back(gs);
+            else
             {
-                Groups *gs = new Groups(msh, std::vector<std::string>{name, name + '_'});
-                bndGrps.push_back(gs);
+                std::stringstream err;
+                err << "MshCrack::addBoundary: cannot add a boundary of dimension " << gs->groups[0]->tag->dim << ", dimension must be either " << nDim << " or " << nDim - 1 << "!\n";
+                throw std::runtime_error(err.str());
             }
-            // ... if not, an algorithm will later try to to separate the elements according to the side of the crack they are touching
-            catch (const std::out_of_range &)
+        }
+        // else, the boundaries are contained in a single tag, an algorithm will later try to separate the elements according to the side of the crack they are on
+        catch (const std::out_of_range &)
+        {
+            Group *g = new Group(msh, name);
+            if (g->tag->dim == nDim)
+                vBndGrp.push_back(g);
+            else if (g->tag->dim == nDim - 1)
+                sBndGrp.push_back(g);
+            else
             {
-                Group *g = new Group(msh, name);
-                bndGrp.push_back(g);
+                std::stringstream err;
+                err << "MshCrack::addBoundary: cannot add a boundary of dimension " << g->tag->dim << ", dimension must be either " << nDim << " or " << nDim - 1 << "!\n";
+                throw std::runtime_error(err.str());
             }
         }
     }
@@ -100,15 +126,14 @@ void MshCrack::addBoundaries(std::vector<std::string> const &bndName)
 void MshCrack::run()
 {
     if (hasCrack)
-        std::cout << "MshCrack not run!\n";
+        std::cout << "MshCrack not run\n";
     else
     {
         // Check that crack is bounded
-        if (bndGrps.empty() && bndGrp.empty())
-            throw std::runtime_error("MshCrack::run crack has no boundary (use MshCrack::addBoundary()!\n");
+        if ((sBndGrps.empty() && sBndGrp.empty()) || (vBndGrps.empty() && vBndGrp.empty()))
+            throw std::runtime_error("MshCrack::run, crack has no (surface and/or volume) boundary, use MshCrack::addBoundary()!\n");
         // Open the crack and modify data structure
         openCrack();
-        modifyBoundaries_();
         modifyBoundaries();
         std::cout << "Crack added on " << *(crckGrp->tag) << "(" << crckGrp->tag->name << "_ created)" << std::endl;
     }
@@ -177,14 +202,14 @@ void MshCrack::openCrack()
                 nods[j] = e->nodes[e->nodes.size() - j - 1];
             }
         }
-        if (e->type() == ELTYPE::LINE2 && nDim == 2)
+        if (e->type() == ElType::LINE2 && nDim == 2)
             msh->elems.push_back(new Line2(static_cast<int>(i), tagp, tage, e->parts, nods));
-        else if (e->type() == ELTYPE::TRI3 && nDim == 3)
+        else if (e->type() == ElType::TRI3 && nDim == 3)
             msh->elems.push_back(new Tri3(static_cast<int>(i), tagp, tage, e->parts, nods));
         else
         {
             std::stringstream err;
-            err << "Msh::Crack: could not create element of type " << e->type() << " for dimension " << nDim << "!\n";
+            err << "MshCrack::run: could not create element of type " << e->type() << " for dimension " << nDim << "!\n";
             throw std::runtime_error(err.str());
         }
         i++;
@@ -192,15 +217,123 @@ void MshCrack::openCrack()
 }
 
 /**
- * @brief Swap the elements nodes on one side of the crack and merge physical tags
+ * @brief Modify the elements on the negative side of the crack
+ *
+ * 1) Identify boundary surface elements on negative side of crack, and store their nodes
+ *   - easy case: elements are contained in different tags
+ *   - hard case: elements are contained in a single tag, they are identified by comparing the vector joining them to the crack to the normal vector of the crack (NOT ROBUST)
+ * 2) Identify boundary volume elements on negative side of crack
+ *   - easy case: elements are contained in different tags
+ *   - hard case: elements are contained in a single tag, they are identified by
+ *     - either checking if one of their nodes belong to the surface
+ *     - or, comparing the vector joining them to the crack to the normal vector of the crack
+ * 3) Swap the nodes of (surface and volume) elements located on the negative side of the crack
+ * 4) Merge the tags identifying the (surface and volume) elements on the negative side into the tags identifying the positive side
  */
-void MshCrack::modifyBoundaries_()
+void MshCrack::modifyBoundaries()
 {
-    for (auto gs : bndGrps)
+    // Store crack nodes, and compute CGs and normals if sides are contained in a single tag
+    std::unordered_set<Node *> crkNods, crkNodsAll;
+    std::vector<Eigen::Vector3d> crkCG(crckGrp->tag->elems.size()), crkN(crckGrp->tag->elems.size());
+    if (!sBndGrp.empty() || !vBndGrp.empty())
+    {
+        // nodes of crack except excluded
+        for (auto p : nodMap)
+            crkNods.insert(p.first);
+        // all nodes of crack
+        for (auto e : crckGrp->tag->elems)
+            crkNodsAll.insert(e->nodes.begin(), e->nodes.end());
+        // cg and unit normal vector
+        for (size_t i = 0; i < crckGrp->tag->elems.size(); ++i)
+        {
+            crckGrp->tag->elems[i]->computeCg();
+            crkCG[i] = crckGrp->tag->elems[i]->cg;
+            crckGrp->tag->elems[i]->computeNormal();
+            crkN[i] = crckGrp->tag->elems[i]->normal;
+        }
+    }
+
+    // Identify surface elements on negative side, and store their nodes (to find volume elements if they are contained in a single tag)
+    std::vector<Element *> _surEl;
+    std::unordered_set<Node *> _surNd;
+    // Each side is contained in a different tag
+    for (auto gs : sBndGrps)
     {
-        // Modify element nodes touching the crack
         for (auto e : gs->groups[1]->tag->elems)
-            swapNodes(e);
+        {
+            _surEl.push_back(e);
+            // only keep nodes that are not on crack (since they are on positive side)
+            for (auto n : e->nodes)
+                if (!crkNodsAll.count(n))
+                    _surNd.insert(n);
+        }
+    }
+    // Both sides are contained in a single tag
+    for (auto g : sBndGrp)
+    {
+        // find elements having at least one node on the crack, and store if on negative side
+        std::unordered_set<Element *> onCrk = findTouching(g->tag->elems, crkNods);
+        for (auto e : onCrk)
+        {
+            // find mininal distance bewteen CGs
+            e->computeCg();
+            size_t idx = findClosest(crkCG, e->cg);
+            // if dot product between crack-to-volume vector and crack normal is negative, element is on negative side
+            if ((e->cg - crkCG[idx]).dot(crkN[idx]) < 0)
+            {
+                _surEl.push_back(e);
+                // only keep nodes that are not on crack (since they are on positive side)
+                for (auto n : e->nodes)
+                    if (!crkNodsAll.count(n))
+                        _surNd.insert(n);
+            }
+        }
+    }
+
+    // Identify volume elements touching surface elements on negative side
+    std::vector<Element *> _volEl;
+    // Each side is contained in a different tag
+    for (auto gs : vBndGrps)
+        for (auto e : gs->groups[1]->tag->elems)
+            _volEl.push_back(e);
+    // Both sides are contained in a single tag
+    for (auto g : vBndGrp)
+    {
+        // find elements having at least one node on the crack
+        std::unordered_set<Element *> onCrkOnly = findTouching(g->tag->elems, crkNods);
+        // find elements having at least one node on the negative side of the surface
+        std::unordered_set<Element *> onSurf = findTouching(std::vector<Element *>(onCrkOnly.begin(), onCrkOnly.end()), _surNd);
+        // remove these elements from the first set, as they are necessarily on the negative side
+        _volEl.insert(_volEl.end(), onSurf.begin(), onSurf.end());
+        for (auto e : onSurf)
+            onCrkOnly.erase(e);
+        // check if remaining elements are on the negative side
+        for (auto e : onCrkOnly)
+        {
+            // find mininal distance bewteen CGs
+            e->computeCg();
+            size_t idx = findClosest(crkCG, e->cg);
+            // if dot product between crack-to-volume vector and crack normal is negative, element is on negative side
+            if ((e->cg - crkCG[idx]).dot(crkN[idx]) < 0)
+                _volEl.push_back(e);
+        }
+    }
+
+    // Swap nodes of surface and volume elements on negative side
+    swapNodes(_surEl);
+    swapNodes(_volEl);
+    // Merge tag identifying negative side into positive side tag
+    mergeTags(sBndGrps);
+    mergeTags(vBndGrps);
+}
+
+/**
+ * @brief Merge physical tags and modify data structure accordingly
+ */
+void MshCrack::mergeTags(std::vector<Groups *> &grps)
+{
+    for (auto gs : grps)
+    {
         // Merge tags
         Tag *refTag = gs->groups[0]->tag;
         Tag *oldTag = gs->groups[1]->tag;
@@ -217,74 +350,58 @@ void MshCrack::modifyBoundaries_()
 }
 
 /**
- * @brief Detect the elements on one side of the crack and swap their nodes
+ * @brief Find elements touching a surface (ie, that have at least a node in the provided set)
  */
-void MshCrack::modifyBoundaries()
+std::unordered_set<Element *> MshCrack::findTouching(std::vector<Element *> const &elms, std::unordered_set<Node *> const &nods)
 {
-    // compute crack CGs and normals
-    std::vector<Eigen::Vector3d> crkCG(crckGrp->tag->elems.size()), crkN(crckGrp->tag->elems.size());
-    for (size_t i = 0; i < crckGrp->tag->elems.size(); ++i)
+    std::unordered_set<Element *> found;
+    for (auto e : elms)
     {
-        crckGrp->tag->elems[i]->computeCg();
-        crkCG[i] = crckGrp->tag->elems[i]->cg;
-        crckGrp->tag->elems[i]->computeNormal();
-        crkN[i] = crckGrp->tag->elems[i]->normal;
-    }
-    // find elements on the opposite side of the crack and swap their nodes
-    for (auto g : bndGrp)
-    {
-        // find elements that have at least one node on the crack and compute their CG
-        std::map<Element *, Eigen::Vector3d> onCrk;
-        for (auto e : g->tag->elems)
-        {
-            for (auto n : e->nodes)
-            {
-                if (nodMap.find(n) != nodMap.end())
-                {
-                    //Eigen::Vector3d cg(0, 0, 0);
-                    //for (auto n_ : e->nodes)
-                    //    cg += n_->pos;
-                    //cg /= e->nodes.size();
-                    //onCrk[e] = cg;
-                    e->computeCg();
-                    onCrk[e] = e->cg;
-                    break;
-                }
-            }
-        }
-        // find closest element, check side and swap nodes
-        for (auto p : onCrk)
+        for (auto n : e->nodes)
         {
-            // find mininal distance bewteen CGs
-            size_t idx = 0;
-            std::vector<Eigen::Vector3d> deltaCG(crkCG.size());
-            for (size_t i = 0; i < crkCG.size(); ++i)
+            if (nods.count(n))
             {
-                deltaCG[i] = p.second - crkCG[i];
-                if (deltaCG[i].norm() < deltaCG[idx].norm())
-                    idx = i;
+                found.insert(e);
+                break;
             }
-            // check sign of dot product
-            if (deltaCG[idx].dot(crkN[idx]) < 0)
-                swapNodes(p.first);
         }
     }
+    return found;
 }
 
 /**
- * @brief Replace nodes of elements on opposite side of crack by opposite nodes
+ * @brief Find closest element and returns its index
  */
-void MshCrack::swapNodes(Element *e)
+size_t MshCrack::findClosest(std::vector<Eigen::Vector3d> const &cga, Eigen::Vector3d const &cgb)
 {
-    for (size_t i = 0; i < e->nodes.size(); ++i)
+    size_t idx = 0;
+    std::vector<Eigen::Vector3d> delta(cga.size());
+    for (size_t i = 0; i < cga.size(); ++i)
     {
-        try
-        {
-            e->nodes[i] = nodMap.at(e->nodes[i]);
-        }
-        catch (const std::out_of_range &)
+        delta[i] = cgb - cga[i];
+        if (delta[i].norm() < delta[idx].norm())
+            idx = i;
+    }
+    return idx;
+}
+
+/**
+ * @brief Replace nodes of boundary elements on negative side of crack by negative crack nodes
+ */
+void MshCrack::swapNodes(std::vector<Element *> &elms)
+{
+    for (auto e : elms)
+    {
+        for (size_t i = 0; i < e->nodes.size(); ++i)
         {
-            //std::cout << e->nodes[i]->no << "not found in map!\n";
+            try
+            {
+                e->nodes[i] = nodMap.at(e->nodes[i]);
+            }
+            catch (const std::out_of_range &)
+            {
+                // std::cout << e->nodes[i]->no << "not found in map!\n";
+            }
         }
     }
 }
@@ -292,4 +409,4 @@ void MshCrack::swapNodes(Element *e)
 void MshCrack::write(std::ostream &out) const
 {
     out << "MshCrack on " << msh << std::endl;
-}
\ No newline at end of file
+}
diff --git a/tbox/src/wMshCrack.h b/tbox/src/wMshCrack.h
index dcce73d0dd8de221f3ca0fa3c3c22a34ccbb5a48..92338f7c85b1f56aa76763d35794b897b0b3894e 100644
--- a/tbox/src/wMshCrack.h
+++ b/tbox/src/wMshCrack.h
@@ -19,9 +19,10 @@
 
 #include "tbox.h"
 #include "wObject.h"
-#include "wMshExport.h"
+#include <Eigen/Dense>
 #include <vector>
 #include <map>
+#include <unordered_set>
 #include <memory>
 
 namespace tbox
@@ -30,9 +31,10 @@ namespace tbox
 /**
  * @brief Duplicate nodes and elements in a physical group and make mesh consistent
  *
- * Similar to plugin "crack" from gmsh but allows more control. Currently coded
+ * Similar to plugin "crack" from gmsh but allows more control. Currently implemented
  * only for Line2 and Tri3.
  * @authors Adrien Crovato
+ * @todo Automatic identification of boundary surface elements on negative side of crack is not robust (might fail for concave boundaries)
  */
 class TBOX_API MshCrack : public fwk::wSharedObject
 {
@@ -43,21 +45,25 @@ private:
     std::map<Node *, Node *> nodMap; ///< map between nodes on both side of crack
     Group *crckGrp;                  ///< physical group of crack
     Group *exclGrp;                  ///< physical group of nodes not to be duplicated
-    std::vector<Groups *> bndGrps;   ///< list of pair of physical groups touching crack
-    std::vector<Group *> bndGrp;     ///< list of physical groups touching crack
+    std::vector<Groups *> sBndGrps;  ///< list of pair of surface physical groups touching crack
+    std::vector<Groups *> vBndGrps;  ///< list of pair of volume physical groups touching crack
+    std::vector<Group *> sBndGrp;    ///< list of surface physical groups touching crack
+    std::vector<Group *> vBndGrp;    ///< list of volume physical groups touching crack
 
     void openCrack();
-    void modifyBoundaries_();
     void modifyBoundaries();
-    void swapNodes(Element *e);
+    std::unordered_set<Element *> findTouching(std::vector<Element *> const &elms, std::unordered_set<Node *> const &nods);
+    size_t findClosest(std::vector<Eigen::Vector3d> const &cga, Eigen::Vector3d const &cgb);
+    void mergeTags(std::vector<Groups *> &grps);
+    void swapNodes(std::vector<Element *> &elms);
 
 public:
     MshCrack(std::shared_ptr<MshData> _msh, int _nDim);
     virtual ~MshCrack();
 
-    void setCrack(std::string const &crckName);
-    void addBoundaries(std::vector<std::string> const &bndName);
-    void setExcluded(std::string const &exclName);
+    void setCrack(std::string const &name);
+    void setExcluded(std::string const &name);
+    void addBoundary(std::string const &name);
     void run();
 
 #ifndef SWIG
@@ -67,4 +73,4 @@ public:
 
 } // namespace tbox
 
-#endif //WMSHCRACK_H
\ No newline at end of file
+#endif // WMSHCRACK_H
\ No newline at end of file
diff --git a/tbox/src/wMshDeform.cpp b/tbox/src/wMshDeform.cpp
index bb740c91b662f7693d089fa5fae9ddb349cb1386..075a4a50e3a6d9438657c5fbaa8e8b5b7a60621e 100644
--- a/tbox/src/wMshDeform.cpp
+++ b/tbox/src/wMshDeform.cpp
@@ -16,6 +16,7 @@
 
 #include "wMshDeform.h"
 #include "wMshData.h"
+#include "wLinearSolver.h"
 #include "wGroup.h"
 #include "wGroups.h"
 #include "wTag.h"
@@ -23,19 +24,17 @@
 #include "wElement.h"
 #include "wCache.h"
 #include "wGauss.h"
-#include "wGmres.h"
 
 #include <tbb/global_control.h>
-#include <tbb/parallel_for_each.h>
-#include <tbb/spin_mutex.h>
 #include <deque>
 
 using namespace tbox;
 
 MshDeform::MshDeform(std::shared_ptr<MshData> _msh,
-                     int _nDim, int nthrds) : wSharedObject(), nDim(_nDim),
-                                              field(false), fixed(false), moving(false),
-                                              msh(_msh), nthreads(nthrds)
+                     std::shared_ptr<tbox::LinearSolver> _linsol,
+                     int _nDim, int nthrds, int vrb) : wSharedObject(),
+                                                       nDim(_nDim), nthreads(nthrds), verbose(vrb),
+                                                       msh(_msh), linsol(_linsol)
 {
     // Check problem dimension
     if (nDim != 2 && nDim != 3)
@@ -49,6 +48,7 @@ MshDeform::MshDeform(std::shared_ptr<MshData> _msh,
 
     // Display configuration
     std::cout << "--- Mesh morpher (linear elasticity) ---\n"
+              << "Inner solver: " << *linsol
               << "Number of threads: " << nthreads << "\n"
               << std::endl;
 }
@@ -58,37 +58,25 @@ MshDeform::MshDeform(std::shared_ptr<MshData> _msh,
  */
 void MshDeform::setField(std::string const &fldName)
 {
-    // Create temporary field group
-    fldGrp = new Group(msh, fldName);
-    field = true;
+    fldGrp = new Group(msh, fldName); // temporary field group
 }
 
 /**
  * @brief Add fixed boundaries
  */
-void MshDeform::addFixed(std::vector<std::string> const &fxdBndName)
+void MshDeform::addFixed(std::string const &fxdBndName)
 {
-    // Create temporary boundary groups
-    for (size_t i = 0; i < fxdBndName.size(); ++i)
-    {
-        Group *g = new Group(msh, fxdBndName[i]);
-        fxdBndGrp.push_back(g);
-    }
-    fixed = true;
+    Group *g = new Group(msh, fxdBndName); // temporary boundary group
+    fxdBndGrp.push_back(g);
 }
 
 /**
  * @brief Add moving boundaries
  */
-void MshDeform::addMoving(std::vector<std::string> const &movBndName)
+void MshDeform::addMoving(std::string const &movBndName)
 {
-    // Create temporary boundary groups
-    for (size_t i = 0; i < movBndName.size(); ++i)
-    {
-        Group *g = new Group(msh, movBndName[i]);
-        movBndGrp.push_back(g);
-    }
-    moving = true;
+    Group *g = new Group(msh, movBndName); // temporary boundary group
+    movBndGrp.push_back(g);
 }
 
 /**
@@ -96,23 +84,19 @@ void MshDeform::addMoving(std::vector<std::string> const &movBndName)
  */
 void MshDeform::setSymmetry(std::string const &symBndName, size_t _blck)
 {
-    // Get blocked direction
+    // Get blocked direction and create group
     blck = _blck;
     if (blck != 0 && blck != 1 && blck != 2)
-        throw std::runtime_error("tbox::MshDeform::setSymmetry: blck should be 0, 1 or 2 (for x, y and z direction resp.)!\n");
-    // Create temporary boundary groups
-    symBndGrp = new Group(msh, symBndName);
+        throw std::runtime_error("MshDeform::setSymmetry: blck should be 0, 1 or 2 (for x, y and z direction resp.)!\n");
+    symBndGrp = new Group(msh, symBndName); // temporary field group
 }
 
 /**
  * @brief Add internal boundaries
  */
-void MshDeform::addInternal(std::vector<std::string> const &intBndName)
+void MshDeform::addInternal(std::string const &intBndName, std::string const &intBndName_)
 {
-    // Create temporary internal link
-    if (intBndName.size() != 2)
-        throw std::runtime_error("MshDeform: input intBndName must be a pair of internal boundaries!\n");
-    Groups *gs = new Groups(msh, intBndName);
+    Groups *gs = new Groups(msh, std::vector<std::string>{intBndName, intBndName_}); // temporary internal link group
     intBndGrps.push_back(gs);
 }
 
@@ -122,12 +106,12 @@ void MshDeform::addInternal(std::vector<std::string> const &intBndName)
 void MshDeform::initialize()
 {
     // Sanity checks
-    if (!field)
-        throw std::runtime_error("MshDeform: field has not been found! Use setField() to add a field.\n");
-    else if (!fixed)
-        throw std::runtime_error("MshDeform: fixed boundaries have not been found! Use addFixed() to add at least one boundary.\n");
-    else if (!moving)
-        throw std::runtime_error("MshDeform: moving boundaries have not been found! Use addMoving() to add at least one boundary.\n");
+    if (fldGrp == nullptr)
+        throw std::runtime_error("MshDeform::initialize: field has not been found! Use setField() to add a field.\n");
+    if (fxdBndGrp.empty())
+        throw std::runtime_error("MshDeform::initialize: fixed boundaries have not been found! Use addFixed() to add at least one boundary.\n");
+    if (movBndGrp.empty())
+        throw std::runtime_error("MshDeform::initialize: moving boundaries have not been found! Use addMoving() to add at least one boundary.\n");
 
     // Store the field elements
     for (auto e : fldGrp->tag->elems)
@@ -185,7 +169,7 @@ void MshDeform::initialize()
         uniqueSort(nodes1);
         // Create the nodes pair
         if (nodes0.size() != nodes1.size())
-            throw std::runtime_error("MshDeform: internal boudnaries must have the same number of nodes!");
+            throw std::runtime_error("MshDeform::initialize: internal boudnaries must have the same number of nodes!");
         for (auto n0 : nodes0)
         {
             for (auto n1 : nodes1)
@@ -301,14 +285,12 @@ Eigen::MatrixXd MshDeform::buildK(tbox::Element const &e, Eigen::MatrixXd const
  */
 void MshDeform::build(Eigen::SparseMatrix<double, Eigen::RowMajor> &K)
 {
-    // Multithread
-    tbb::spin_mutex mutex;
-
     // List of triplets to build stifness matrix
     std::deque<Eigen::Triplet<double>> T;
 
     // Build stiffness matrix
-    tbb::parallel_for_each(fldElems.begin(), fldElems.end(), [&](Element *e) {
+    for (Element const *e : fldElems)
+    {
         // Hooke tensor
         Eigen::MatrixXd H;
         if (nDim == 2)
@@ -345,7 +327,6 @@ void MshDeform::build(Eigen::SparseMatrix<double, Eigen::RowMajor> &K)
         // Elementary stiffness matrix
         Eigen::MatrixXd Ke = this->buildK(*e, H);
         // Assembly
-        tbb::spin_mutex::scoped_lock lock(mutex);
         for (size_t i = 0; i < e->nodes.size(); ++i)
         {
             for (int m = 0; m < nDim; ++m)
@@ -361,7 +342,7 @@ void MshDeform::build(Eigen::SparseMatrix<double, Eigen::RowMajor> &K)
                 }
             }
         }
-    });
+    }
     // Periodic boundary condition step 2
     // -> for each node pair(a, b): write "pos_a - pos_b" = 0 on row a
     for (auto p : intBndPair)
@@ -446,7 +427,7 @@ void MshDeform::build(Eigen::VectorXd &f)
  */
 void MshDeform::deform()
 {
-    // Multithread
+    // Multithread (required for linear solver)
     tbb::global_control control(tbb::global_control::max_allowed_parallelism, nthreads);
 
     // Stiffness matrix
@@ -455,28 +436,24 @@ void MshDeform::deform()
     Eigen::VectorXd f = Eigen::VectorXd::Zero(nDim * mshSize), q = Eigen::VectorXd::Zero(nDim * mshSize);
 
     // Build stiffness matrix and RHS
-    std::cout << "- Deforming mesh:\n"
-              << "assembling matrices... " << std::flush;
+    if (verbose > 0)
+        std::cout << "- Deforming mesh:\n"
+                  << "assembling matrices... " << std::flush;
     this->build(K);
     this->build(f);
-    std::cout << "done" << std::endl;
+    if (verbose > 0)
+        std::cout << "done" << std::endl;
     // Solve the SoE
-    std ::cout << "solving equations... " << std::flush;
-    Gmres gmres;
-    // set the preconditioner
-    gmres.setFillFactor(2);
-    gmres.setDropTol(1e-6);
-    // set the gmres
-    gmres.setTolerance(1e-8);
-    gmres.setRestart(50);
-    gmres.setMaxIterations(500);
-    // solve
+    if (verbose > 0)
+        std ::cout << "solving equations... " << std::flush;
     Eigen::Map<Eigen::VectorXd> f_(f.data(), f.size()), q_(q.data(), q.size());
-    gmres.compute(K, f_, q_);
-    std::cout << "done (#it: " << gmres.getIterations() << ", error: " << std::scientific << gmres.getError() << std::fixed << ")" << std::endl;
+    linsol->compute(K, f_, q_);
+    if (verbose > 0)
+        std::cout << "done (#it: " << linsol->getIterations() << ", error: " << std::scientific << linsol->getError() << std::fixed << ")" << std::endl;
 
     // Update position
-    std ::cout << "updating mesh... " << std::flush;
+    if (verbose > 0)
+        std ::cout << "updating mesh... " << std::flush;
     for (auto n : msh->nodes)
     {
         for (int m = 0; m < nDim; m++)
@@ -497,8 +474,9 @@ void MshDeform::deform()
         e->update();
     for (auto e : intBndElems)
         e->update();
-    std::cout << "done\n"
-              << std::endl;
+    if (verbose > 0)
+        std::cout << "done\n"
+                  << std::endl;
 }
 
 /**
diff --git a/tbox/src/wMshDeform.h b/tbox/src/wMshDeform.h
index e968058397e19a090196e78dadbe758523a59ad0..3d08c5709556b13364a09afbe17728232aff9dcc 100644
--- a/tbox/src/wMshDeform.h
+++ b/tbox/src/wMshDeform.h
@@ -36,10 +36,9 @@ class TBOX_API MshDeform : public fwk::wSharedObject
 private:
     size_t mshSize;                                    ///< number of nodes in the mesh
     int nDim;                                          ///< dimension of the problem (2 or 3)
+    int nthreads;                                      ///< number of threads
+    int verbose;                                       ///< verbosity level
     std::vector<int> rows;                             ///< unknown local index
-    bool field;                                        ///< flag to check if field has been added
-    bool fixed;                                        ///< flag to check if at least one fixed boundary has been added
-    bool moving;                                       ///< flag to check if at least one moving boundary has been added
     Group *fldGrp;                                     ///< temporary group for field
     std::vector<Group *> fxdBndGrp;                    ///< temporary group for fixed boundaries
     std::vector<Group *> movBndGrp;                    ///< temporary group for moving boundaries
@@ -60,17 +59,17 @@ private:
     Eigen::MatrixXd buildK(tbox::Element const &e, Eigen::MatrixXd const &H);
 
 public:
-    std::shared_ptr<MshData> msh;
-    int nthreads;
+    std::shared_ptr<MshData> msh;               ///< mesh
+    std::shared_ptr<tbox::LinearSolver> linsol; ///< linear (inner) solver
 
-    MshDeform(std::shared_ptr<MshData> _msh, int _nDim, int nthrds = 1);
+    MshDeform(std::shared_ptr<MshData> _msh, std::shared_ptr<tbox::LinearSolver> _linsol, int _nDim, int nthrds = 1, int vrb = 1);
     virtual ~MshDeform() { std::cout << "~MshDeform()\n"; }
 
     void setField(std::string const &fldName);
-    void addMoving(std::vector<std::string> const &fxdBndName);
-    void addFixed(std::vector<std::string> const &movBndName);
+    void addMoving(std::string const &fxdBndName);
+    void addFixed(std::string const &movBndName);
     void setSymmetry(std::string const &symBndName, size_t _blck);
-    void addInternal(std::vector<std::string> const &intBndName);
+    void addInternal(std::string const &intBndName, std::string const &intBndName_);
     void initialize();
 
     void savePos();
@@ -84,4 +83,4 @@ public:
 
 } // namespace tbox
 
-#endif //WMSHDEFORM_H
\ No newline at end of file
+#endif // WMSHDEFORM_H
\ No newline at end of file
diff --git a/tbox/src/wMshExport.h b/tbox/src/wMshExport.h
index 50627aa1df3ce090c589b2853ced287dbaa5449d..19d6e3a056fac6a8092d599a5c1cc5715576d858 100644
--- a/tbox/src/wMshExport.h
+++ b/tbox/src/wMshExport.h
@@ -25,6 +25,17 @@
 namespace tbox
 {
 
+/**
+ * @brief Writer type
+ * @authors Adrien Crovato
+ */
+enum class WrtType
+{
+    UNDEFINED = 0,
+    GMSH = 1,
+    VTK = 2
+};
+
 /**
  * @brief Base class to write mesh
  * @authors Adrien Crovato, Romain Boman
@@ -38,6 +49,7 @@ public:
 
     MshExport(std::shared_ptr<MshData> _msh);
     virtual ~MshExport() {}
+    virtual WrtType type() const { return WrtType::UNDEFINED; }
 
     virtual void save(std::string const &fname) const;
 #ifndef SWIG
@@ -47,4 +59,4 @@ public:
 
 } // namespace tbox
 
-#endif //WMSHEXPORT_H
+#endif // WMSHEXPORT_H
diff --git a/tbox/src/wMumps.h b/tbox/src/wMumps.h
index 479d29f551fc5717078f53ff3269516d115a9191..535aa6f3a77f757b016c09a67eac15f1bdd230e3 100644
--- a/tbox/src/wMumps.h
+++ b/tbox/src/wMumps.h
@@ -53,6 +53,7 @@ private:
 public:
     Mumps();
     ~Mumps();
+    virtual LSolType type() const override { return LSolType::MUMPS; }
 
 #ifndef SWIG
     virtual void analyze(Eigen::SparseMatrix<double> const &A) override;
diff --git a/tbox/src/wPardiso.cpp b/tbox/src/wPardiso.cpp
index 01a589036b406527c7995d4d20c940fb83871dd8..dc9b8201f5213ff70e544d98c650c563a8f12def 100644
--- a/tbox/src/wPardiso.cpp
+++ b/tbox/src/wPardiso.cpp
@@ -50,7 +50,7 @@ void Pardiso::setOption(int k, int v)
 
 void Pardiso::write(std::ostream &out) const
 {
-    out << "Pardiso (MKL/Eigen)" << std::endl;
+    out << "Eigen Intel MKL PARDISO" << std::endl;
 }
 
 #endif // USE_MKL
diff --git a/tbox/src/wPardiso.h b/tbox/src/wPardiso.h
index 5936fe69e72896615cf63cfddcc1185c53351cd5..3e6c57657b4cded842547860c4c336036443ba66 100644
--- a/tbox/src/wPardiso.h
+++ b/tbox/src/wPardiso.h
@@ -38,6 +38,7 @@ class TBOX_API Pardiso : public LinearSolver
 public:
     Pardiso() : LinearSolver() {}
     virtual ~Pardiso() {}
+    virtual LSolType type() const override { return LSolType::PARDISO; }
 
 #ifndef SWIG
     virtual void analyze(Eigen::SparseMatrix<double> const &A) override;
diff --git a/tbox/src/wPoint1.h b/tbox/src/wPoint1.h
index fc78eb37711c4af8828be1100b83465ded5e8121..b905db0db099214b61ce81a55147316d8b716465 100644
--- a/tbox/src/wPoint1.h
+++ b/tbox/src/wPoint1.h
@@ -31,7 +31,7 @@ class TBOX_API Point1 : public Element
 {
 public:
     Point1(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
-    virtual ELTYPE type() const override { return ELTYPE::POINT1; }
+    virtual ElType type() const override { return ElType::POINT1; }
 
 private:
     static CachePoint1 &getCache(int n);
diff --git a/tbox/src/wQuad4.h b/tbox/src/wQuad4.h
index 75cde412625a5cca2d1fb921bf209eef3e942006..e50f1213e4b8872815ab6fc8392ff4fa6a5f6ec2 100644
--- a/tbox/src/wQuad4.h
+++ b/tbox/src/wQuad4.h
@@ -47,7 +47,7 @@ protected:
 public:
     Quad4(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
     virtual ~Quad4() {}
-    virtual ELTYPE type() const override { return ELTYPE::QUAD4; }
+    virtual ElType type() const override { return ElType::QUAD4; }
 
 #ifndef SWIG
     virtual void initValues(bool isvol, int ordr = 2) override;
diff --git a/tbox/src/wSparseLu.cpp b/tbox/src/wSparseLu.cpp
index e22a5271e90e83c8a00158b501a11a2b0ba30c3c..50bfb308e5a083ff0fd8a55c0e2905b1fd3de2e7 100644
--- a/tbox/src/wSparseLu.cpp
+++ b/tbox/src/wSparseLu.cpp
@@ -37,5 +37,5 @@ void SparseLu::compute(Eigen::SparseMatrix<double> const &A, Eigen::Map<Eigen::V
 
 void SparseLu::write(std::ostream &out) const
 {
-    out << "SparseLU (Eigen)" << std::endl;
+    out << "Eigen SparseLU" << std::endl;
 }
diff --git a/tbox/src/wSparseLu.h b/tbox/src/wSparseLu.h
index 323a5d628b90e522114ed53e33f46f1c29ae9345..84e7abef39b0d3a6cc661840f635f4cbc0f23ed3 100644
--- a/tbox/src/wSparseLu.h
+++ b/tbox/src/wSparseLu.h
@@ -38,6 +38,7 @@ class TBOX_API SparseLu : public LinearSolver
 public:
     SparseLu() : LinearSolver() {}
     virtual ~SparseLu() {}
+    virtual LSolType type() const override { return LSolType::SPARSE_LU; }
 
 #ifndef SWIG
     virtual void analyze(Eigen::SparseMatrix<double> const &A) override;
diff --git a/tbox/src/wTag.cpp b/tbox/src/wTag.cpp
index e0089e7924db7deb0f498ecc93f7661c818fbdc7..acc815132f80b31b1f1f9a18a272448b0c7274bb 100644
--- a/tbox/src/wTag.cpp
+++ b/tbox/src/wTag.cpp
@@ -26,7 +26,7 @@ void Tag::write(std::ostream &out) const
     out << "D) named \"" << name << "\" (" << elems.size() << " elements) ";
 
     // count each type
-    std::map<ELTYPE, int> types;
+    std::map<ElType, int> types;
     for (auto e : elems)
         types[e->type()] += 1;
 
diff --git a/tbox/src/wTetra4.h b/tbox/src/wTetra4.h
index 371dcba8ae2b81b546be302805081cc297c253f0..af669380e3faba104dfe7cb4aab4b133e822bce5 100644
--- a/tbox/src/wTetra4.h
+++ b/tbox/src/wTetra4.h
@@ -45,7 +45,7 @@ protected:
 public:
     Tetra4(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
     virtual ~Tetra4() {}
-    virtual ELTYPE type() const override { return ELTYPE::TETRA4; }
+    virtual ElType type() const override { return ElType::TETRA4; }
 
 #ifndef SWIG
     virtual void initValues(bool isvol, int ordr = 2) override;
diff --git a/tbox/src/wTri3.h b/tbox/src/wTri3.h
index c042c4c5a93094652fadab0e549d35c29fb4c9c3..6dab1db7f1147760c94a5a7fd000710f75b5cf24 100644
--- a/tbox/src/wTri3.h
+++ b/tbox/src/wTri3.h
@@ -51,7 +51,7 @@ protected:
 public:
     Tri3(int n, Tag *_ptag, Tag *_etag, std::vector<Tag *> &_parts, std::vector<Node *> &nods);
     virtual ~Tri3() {}
-    virtual ELTYPE type() const override { return ELTYPE::TRI3; }
+    virtual ElType type() const override { return ElType::TRI3; }
 
 #ifndef SWIG
     virtual void initValues(bool isvol, int ordr = 2) override;
diff --git a/tbox/src/wUnitTest.cpp b/tbox/src/wUnitTest.cpp
index 7b886c29e77e50893e9d5c64991e1b34e538b72d..cc91f80460c03b11a97ac21f17223b3fd3ff41df 100644
--- a/tbox/src/wUnitTest.cpp
+++ b/tbox/src/wUnitTest.cpp
@@ -82,7 +82,7 @@ void UnitTest::mshDeform(MshDeform *mshDef, int d)
 {
     // Initialize memory (volume element only)
     for (auto e : mshDef->msh->elems)
-        if ((d == 2 && e->type() == ELTYPE::TRI3) || (d == 3 && e->type() == ELTYPE::TETRA4))
+        if ((d == 2 && e->type() == ElType::TRI3) || (d == 3 && e->type() == ElType::TETRA4))
             e->initValues(true);
     // Call deform
     mshDef->deform();
diff --git a/tbox/tests/meshDeformation.py b/tbox/tests/meshDeformation.py
index bfd2010f4832468f3b44e9bdc7b69be0a025d0bd..78f8353c8fee407ef33e4a7b55153b33366fcc60 100644
--- a/tbox/tests/meshDeformation.py
+++ b/tbox/tests/meshDeformation.py
@@ -39,10 +39,10 @@ def main():
     msh = gmsh.MeshLoader("models/beam.geo",__file__).execute(**pars)
     gmshWriter = tbox.GmshExport(msh)
     # create mesh deformation handler
-    mshDef = tbox.MshDeform(msh, 2, nthrds=parseargs().k)
+    mshDef = tbox.MshDeform(msh, tbox.SparseLu(), 2, nthrds=parseargs().k)
     mshDef.setField("internalField")
-    mshDef.addFixed(["clamp"])
-    mshDef.addMoving(["tip"])
+    mshDef.addFixed("clamp")
+    mshDef.addMoving("tip")
     mshDef.initialize()
 
     # deform the mesh (dummy translation of "tip" nodes)
diff --git a/tbox/tests/meshDeformation3.py b/tbox/tests/meshDeformation3.py
index 7c0599d019ff8bd58e94602b6361368f66b0face..08e67b6b30f5bd0a453a2785a0a0aab61e658f28 100644
--- a/tbox/tests/meshDeformation3.py
+++ b/tbox/tests/meshDeformation3.py
@@ -38,10 +38,10 @@ def main():
     msh = gmsh.MeshLoader("models/beam3.geo",__file__).execute(**pars)
     gmshWriter = tbox.GmshExport(msh)
     # create mesh deformation handler
-    mshDef = tbox.MshDeform(msh, 3, nthrds=parseargs().k)
+    mshDef = tbox.MshDeform(msh, tbox.SparseLu(), 3, nthrds=parseargs().k)
     mshDef.setField("field")
-    mshDef.addFixed(["clamp"])
-    mshDef.addMoving(["tip"])
+    mshDef.addFixed("clamp")
+    mshDef.addMoving("tip")
     mshDef.initialize()
 
     # deform the mesh (dummy translation of "tip" nodes)
diff --git a/tbox/utils.py b/tbox/utils.py
index 7d32f8dc690809a80540655a25e8fec2f554b97d..73ec554d506fbd70468392746b5890de2e38da2c 100644
--- a/tbox/utils.py
+++ b/tbox/utils.py
@@ -2,13 +2,13 @@
 # -*- coding: utf-8 -*-
 
 # Copyright 2020 University of Liège
-# 
+#
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# 
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,7 +23,6 @@ Adrien Crovato
 
 def myrange(start, stop, step):
     """Extended range with custom step
-    Adrien Crovato
     """
     vec = []
     i = start
@@ -32,33 +31,34 @@ def myrange(start, stop, step):
         i += step
     return vec
 
-def plot(x, y, xlbl, ylbl, title, invert=False):
+def plot(x, y, cfg):
     """Plot using matplotlib
-    Adrien Crovato
-    @todo extend to pass unlimited number of arguments
     """
     from fwk.wutils import parseargs
     args = parseargs()
     if not args.nogui:
         import matplotlib.pyplot as plt
         # define font
-        font = {'family': 'serif',
-                'color': 'darkred',
-                'weight': 'normal',
-                'size': 16,}
+        font = {'family': 'serif', 'color': 'darkred', 'weight': 'normal', 'size': 16}
         # plot results
-        plt.plot(x, y, 'b-',lw=2)   
-        plt.xlabel(xlbl, fontdict=font)
-        plt.ylabel(ylbl, fontdict=font)
-        plt.title(title, fontdict=font)
-        if invert:
+        plt.plot(x, y, 'b-', lw=2)
+        if 'xlabel' in cfg:
+            plt.xlabel(cfg['xlabel'], fontdict=font)
+        if 'ylabel' in cfg:
+            plt.ylabel(cfg['ylabel'], fontdict=font)
+        if 'title' in cfg:
+            plt.title(cfg['title'], fontdict=font)
+        if 'xlim' in cfg:
+            plt.xlim(cfg['xlim'])
+        if 'ylim' in cfg:
+            plt.ylim(cfg['ylim'])
+        if 'invert' in cfg and cfg['invert']:
             plt.gca().invert_yaxis()
-        #plt.grid(True)
+        # display
         plt.show()
 
 def sort(id, data):
     """Sort data array against id (line connectivity list)
-    Adrien Crovato
     """
     import numpy as np
     # sort id vector
@@ -76,7 +76,6 @@ def sort(id, data):
 
 def read(filename):
     """Read from file and store in data array
-    Adrien Croavto
     """
     import io
     import numpy as np
@@ -89,7 +88,6 @@ def read(filename):
 
 def write(data, name, frmt, dlm, hdr, cmts):
     """Write data array to file
-    Adrien Crovato
     """
     import numpy as np
     print('writing data file ' + name + '.')
diff --git a/tbox/viewer.py b/tbox/viewer.py
index ddabbc90fe01ec8dbc3c357e47f3d141c87dc3c1..c5f42697cfbbd50caac3dac6327197f64fb66acb 100644
--- a/tbox/viewer.py
+++ b/tbox/viewer.py
@@ -2,13 +2,13 @@
 # -*- coding: utf-8 -*-
 
 # Copyright 2020 University of Liège
-# 
+#
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# 
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -35,10 +35,13 @@ class GUI:
         if not os.path.isfile(optfile):
             self.defopt(optfile)
         cmd="gmsh %s %s %s" % (mshfile, posfile, optfile)
-        os.system(cmd)        
+        os.system(cmd)
 
     def defopt(self,fname='defaultflowOpt.opt'):
         f = open(fname,'w')
         f.write("General.Orthographic = 0;\n")
+        f.write('View[0].IntervalsType = 3;\n')
+        f.write('For i In {1:PostProcessing.NbViews-1}\n')
+        f.write('  View[i].Visible = 0;\n')
+        f.write('EndFor\n')
         f.close()
-    
diff --git a/tboxVtk/_src/CMakeLists.txt b/tboxVtk/_src/CMakeLists.txt
index f1ad6f73a27613af52c9e94bbd1f84d55285a4f0..0011635748f147cd815dc45199cc4ad1a955ad05 100644
--- a/tboxVtk/_src/CMakeLists.txt
+++ b/tboxVtk/_src/CMakeLists.txt
@@ -19,35 +19,18 @@ INCLUDE(${SWIG_USE_FILE})
 FILE(GLOB SRCS *.h *.cpp *.inl *.swg)
 FILE(GLOB ISRCS *.i)
 
-SET(CMAKE_SWIG_FLAGS "")
 SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES CPLUSPLUS ON)
+SET(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} "-interface" "_tboxVtkw") # avoids "import _module_d" with MSVC/Debug
+SWIG_ADD_LIBRARY(tboxVtkw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
+SET_PROPERTY(TARGET tboxVtkw PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON)
+MACRO_DebugPostfix(tboxVtkw)
 
-SET(SWINCFLAGS 
--I${PROJECT_SOURCE_DIR}/tboxVtk/src
--I${PROJECT_SOURCE_DIR}/tbox/src
--I${PROJECT_SOURCE_DIR}/tbox/_src
--I${PROJECT_SOURCE_DIR}/fwk/src
--I${PROJECT_SOURCE_DIR}/fwk/_src
-)
-SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES SWIG_FLAGS "${SWINCFLAGS}")
-
-if (${CMAKE_VERSION} VERSION_LESS "3.8.0")
-    SWIG_ADD_MODULE(tboxVtkw python ${ISRCS} ${SRCS})
-else()
-    SWIG_ADD_LIBRARY(tboxVtkw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
-endif()
-MACRO_DebugPostfix(_tboxVtkw)
-
-TARGET_INCLUDE_DIRECTORIES(_tboxVtkw PRIVATE ${PROJECT_SOURCE_DIR}/tboxVtk/_src
-                                          ${PROJECT_SOURCE_DIR}/fwk/_src
-                                          ${PROJECT_SOURCE_DIR}/tbox/_src
-                                          ${PYTHON_INCLUDE_PATH}
-)
-
-SWIG_LINK_LIBRARIES(tboxVtkw
-                    tboxVtk tbox fwk ${PYTHON_LIBRARIES}
-)
+TARGET_INCLUDE_DIRECTORIES(tboxVtkw PRIVATE ${PROJECT_SOURCE_DIR}/tboxVtk/_src
+                                            ${PROJECT_SOURCE_DIR}/tbox/_src
+                                            ${PROJECT_SOURCE_DIR}/fwk/_src
+                                            ${PYTHON_INCLUDE_PATH})
+TARGET_LINK_LIBRARIES(tboxVtkw PRIVATE tboxVtk tbox fwk ${PYTHON_LIBRARIES})
 
 INSTALL(FILES "${EXECUTABLE_OUTPUT_PATH}/\${BUILD_TYPE}/tboxVtkw.py"
         DESTINATION ${CMAKE_INSTALL_PREFIX})
-INSTALL(TARGETS _tboxVtkw DESTINATION ${CMAKE_INSTALL_PREFIX})
+INSTALL(TARGETS tboxVtkw DESTINATION ${CMAKE_INSTALL_PREFIX})
diff --git a/tboxVtk/src/CMakeLists.txt b/tboxVtk/src/CMakeLists.txt
index 2d38e5c6013bd1b04b3e5e32b1f2cdbb03524005..288a3a4a674d94056a5c2ae8924bbbe1047596d7 100644
--- a/tboxVtk/src/CMakeLists.txt
+++ b/tboxVtk/src/CMakeLists.txt
@@ -27,14 +27,18 @@ TARGET_INCLUDE_DIRECTORIES(tboxVtk PUBLIC ${PROJECT_SOURCE_DIR}/tboxVtk/src)
 
 
 # 1. first, look for all available VTK libraries
-FIND_PACKAGE(VTK REQUIRED NO_MODULE)
+FIND_PACKAGE(VTK REQUIRED NO_MODULE QUIET)
 IF(${VTK_VERSION} VERSION_LESS 6.0)
 	MESSAGE(FATAL_ERROR "VTK>=6 required!")
 ENDIF()
-MESSAGE(STATUS "VTK_LIBRARIES1 = ${VTK_LIBRARIES}") # display all VTK libs
+#MESSAGE(STATUS "VTK_LIBRARIES1 = ${VTK_LIBRARIES}") # display all VTK libs
 
 # list of required components
-SET(REQVTKCMP vtkIOXML)
+IF(${VTK_VERSION} VERSION_LESS 9.0)
+	SET(REQVTKCMP vtkIOXML)
+ELSE()
+	SET(REQVTKCMP IOXML)
+ENDIF()
 
 # check whether MPI has been compiled
 IF(TARGET vtkParallelMPI) # NOTE: this test also works with a previous single-COMPONENT search
@@ -47,16 +51,19 @@ ENDIF()
 
 # 2. second, perform a new search with a limited number of components
 # NOTE: you may just disable the following line to link against ALL vtk libraries!
-FIND_PACKAGE(VTK REQUIRED COMPONENTS ${REQVTKCMP} NO_MODULE)
+FIND_PACKAGE(VTK REQUIRED COMPONENTS ${REQVTKCMP} NO_MODULE QUIET)
+IF(${VTK_FOUND})
+	MESSAGE(STATUS "Found VTK ${VTK_VERSION} at \"${VTK_PREFIX_PATH}\" (components: ${VTK_LIBRARIES})")
+ENDIF()
 
 # debug
-MESSAGE(STATUS "VTK_DEFINITIONS = ${VTK_DEFINITIONS}")
-MESSAGE(STATUS "VTK_REQUIRED_CXX_FLAGS = ${VTK_REQUIRED_CXX_FLAGS}")
-MESSAGE(STATUS "VTK_REQUIRED_EXE_LINKER_FLAGS = ${VTK_REQUIRED_EXE_LINKER_FLAGS}")
-MESSAGE(STATUS "VTK_REQUIRED_SHARED_LINKER_FLAGS = ${VTK_REQUIRED_SHARED_LINKER_FLAGS}")
-MESSAGE(STATUS "VTK_USE_FILE = ${VTK_USE_FILE}")
-MESSAGE(STATUS "VTK_INCLUDE_DIR = ${VTK_INCLUDE_DIR}")
-MESSAGE(STATUS "VTK_LIBRARIES = ${VTK_LIBRARIES}")
+#MESSAGE(STATUS "VTK_DEFINITIONS = ${VTK_DEFINITIONS}")
+#MESSAGE(STATUS "VTK_REQUIRED_CXX_FLAGS = ${VTK_REQUIRED_CXX_FLAGS}")
+#MESSAGE(STATUS "VTK_REQUIRED_EXE_LINKER_FLAGS = ${VTK_REQUIRED_EXE_LINKER_FLAGS}")
+#MESSAGE(STATUS "VTK_REQUIRED_SHARED_LINKER_FLAGS = ${VTK_REQUIRED_SHARED_LINKER_FLAGS}")
+#MESSAGE(STATUS "VTK_USE_FILE = ${VTK_USE_FILE}")
+#MESSAGE(STATUS "VTK_INCLUDE_DIR = ${VTK_INCLUDE_DIR}")
+#MESSAGE(STATUS "VTK_LIBRARIES = ${VTK_LIBRARIES}")
 
 TARGET_INCLUDE_DIRECTORIES(tboxVtk PUBLIC ${VTK_INCLUDE_DIRS})
 TARGET_COMPILE_DEFINITIONS(tboxVtk PUBLIC ${VTK_DEFINITIONS})
diff --git a/tboxVtk/src/wVtkExport.cpp b/tboxVtk/src/wVtkExport.cpp
index 322eff828e5a4f08080f0a15fecf2c83e8caad22..26d1750fae91e2d73c5059d475dca71a092f5d92 100644
--- a/tboxVtk/src/wVtkExport.cpp
+++ b/tboxVtk/src/wVtkExport.cpp
@@ -190,42 +190,42 @@ void VtkExport::create()
     // Create elements
     for (auto e : msh->elems)
     {
-        if (e->type() == ELTYPE::HEX8)
+        if (e->type() == ElType::HEX8)
         {
             vtkHexaP cell = vtkHexaP::New();
             for (size_t i = 0; i < e->nodes.size(); ++i)
                 cell->GetPointIds()->SetId(i, e->nodes[i]->no - 1);
             grid->InsertNextCell(cell->GetCellType(), cell->GetPointIds());
         }
-        else if (e->type() == ELTYPE::TETRA4)
+        else if (e->type() == ElType::TETRA4)
         {
             vtkTetraP cell = vtkTetraP::New();
             for (size_t i = 0; i < e->nodes.size(); ++i)
                 cell->GetPointIds()->SetId(i, e->nodes[i]->no - 1);
             grid->InsertNextCell(cell->GetCellType(), cell->GetPointIds());
         }
-        else if (e->type() == ELTYPE::QUAD4)
+        else if (e->type() == ElType::QUAD4)
         {
             vtkQuadP cell = vtkQuadP::New();
             for (size_t i = 0; i < e->nodes.size(); ++i)
                 cell->GetPointIds()->SetId(i, e->nodes[i]->no - 1);
             grid->InsertNextCell(cell->GetCellType(), cell->GetPointIds());
         }
-        else if (e->type() == ELTYPE::TRI3)
+        else if (e->type() == ElType::TRI3)
         {
             vtkTriP cell = vtkTriP::New();
             for (size_t i = 0; i < e->nodes.size(); ++i)
                 cell->GetPointIds()->SetId(i, e->nodes[i]->no - 1);
             grid->InsertNextCell(cell->GetCellType(), cell->GetPointIds());
         }
-        else if (e->type() == ELTYPE::LINE2)
+        else if (e->type() == ElType::LINE2)
         {
             vtkLineP cell = vtkLineP::New();
             for (size_t i = 0; i < e->nodes.size(); ++i)
                 cell->GetPointIds()->SetId(i, e->nodes[i]->no - 1);
             grid->InsertNextCell(cell->GetCellType(), cell->GetPointIds());
         }
-        else if (e->type() == ELTYPE::POINT1)
+        else if (e->type() == ElType::POINT1)
         {
             vtkVtxP cell = vtkVtxP::New();
             for (size_t i = 0; i < e->nodes.size(); ++i)
diff --git a/tboxVtk/src/wVtkExport.h b/tboxVtk/src/wVtkExport.h
index 8dea4d0fdfd78c8a35c0ee1db8991a4e7ce4932b..eca8b73cb475956294e8631e6621c12d852c1cda 100644
--- a/tboxVtk/src/wVtkExport.h
+++ b/tboxVtk/src/wVtkExport.h
@@ -44,6 +44,7 @@ class TBOXVTK_API VtkExport : public tbox::MshExport
 public:
     VtkExport(std::shared_ptr<tbox::MshData> _msh);
     virtual ~VtkExport();
+    virtual tbox::WrtType type() const override { return tbox::WrtType::VTK; }
 
     virtual void save(std::string const &fname) const override;
 #ifndef SWIG
@@ -55,4 +56,4 @@ public:
 
 } // namespace tboxVtk
 
-#endif //WGMSHUTILS_H
+#endif // WVTKEXPORT_H