diff --git a/.gitignore b/.gitignore
index cb98282bfb904d03ac706cc8740ee1ad922c8851..9666458274aaceba1d7d4b2f914ec0ca2459d738 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ GTAGS
 /doc/lisp/.fltk/
 /doc/*.msh
 /doc/*.vtu
+/doc/checkpoint.h5
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 02c2d48ced6eb3f1e1c106f5d00e010b258fee3c..1a7ed48ce5254d358ee9c449843f879168c0e24b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,8 +6,8 @@ variables:
   CTEST_OUTPUT_ON_FAILURE: 1
 
 include:
-  - local: '/.gitlab-ci/clang10-mpi-debug.yml'
-  - local: '/.gitlab-ci/clang10-mpi-release.yml'
+  - local: '/.gitlab-ci/clang11-mpi-debug.yml'
+  - local: '/.gitlab-ci/clang11-mpi-release.yml'
   - local: '/.gitlab-ci/gcc10-mpi-coverage.yml'
   - local: '/.gitlab-ci/gcc10-mpi-release.yml'
   - local: '/.gitlab-ci/gcc10-seq-coverage.yml'
diff --git a/.gitlab-ci/clang10-mpi-debug.yml b/.gitlab-ci/clang10-mpi-debug.yml
deleted file mode 100644
index a757e02d46b81e60e981c0d3d11aaa8058c6fcab..0000000000000000000000000000000000000000
--- a/.gitlab-ci/clang10-mpi-debug.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-test:clang10-mpi-debug:
-  image: localhost:5000/ubuntu_clang10_mpi
-  stage: test
-  needs: []
-  script:
-    - mkdir -p build/clang10-debug-mpi
-    - cd build/clang10-debug-mpi
-    - CXX=clang++-10 CC=clang-10 cmake ../.. -DCMAKE_BUILD_TYPE=Debug
-    - make -j 4
-    - make check
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang10-debug-mpi"
-    paths:
-      - build/clang10-debug-mpi
-    untracked: true
diff --git a/.gitlab-ci/clang10-mpi-release.yml b/.gitlab-ci/clang10-mpi-release.yml
deleted file mode 100644
index 3172c1c2c5f869e1e44153956cfde696adb6b05d..0000000000000000000000000000000000000000
--- a/.gitlab-ci/clang10-mpi-release.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-test:clang10-mpi-release:
-  image: localhost:5000/ubuntu_clang10_mpi
-  stage: test
-  needs: []
-  script:
-    - mkdir -p build/clang10-release-mpi
-    - cd build/clang10-release-mpi
-    - CXX=clang++-10 CC=clang-10 cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCLANG_FORMAT=/usr/bin/clang-format-10
-    - make -j 4
-    - make check
-  cache:
-    key: "${CI_COMMIT_REF_SLUG}-clang10-release-mpi"
-    paths:
-      - build/clang10-release-mpi
-    untracked: true
diff --git a/.gitlab-ci/clang11-mpi-debug.yml b/.gitlab-ci/clang11-mpi-debug.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3e8ad009fe9d78f6a3b1d662c4dec253514b0641
--- /dev/null
+++ b/.gitlab-ci/clang11-mpi-debug.yml
@@ -0,0 +1,15 @@
+test:clang11-mpi-debug:
+  image: localhost:5000/ubuntu_jammy_clang11_mpi
+  stage: test
+  needs: []
+  script:
+    - mkdir -p build/clang11-debug-mpi
+    - cd build/clang11-debug-mpi
+    - CXX=clang++-11 CC=clang-11 cmake ../.. -DCMAKE_BUILD_TYPE=Debug
+    - make -j 4
+    - make check
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang11-debug-mpi"
+    paths:
+      - build/clang11-debug-mpi
+    untracked: true
diff --git a/.gitlab-ci/clang11-mpi-release.yml b/.gitlab-ci/clang11-mpi-release.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7c034bf86fb1062a4847c81f79275189f5a51e7b
--- /dev/null
+++ b/.gitlab-ci/clang11-mpi-release.yml
@@ -0,0 +1,15 @@
+test:clang11-mpi-release:
+  image: localhost:5000/ubuntu_jammy_clang11_mpi
+  stage: test
+  needs: []
+  script:
+    - mkdir -p build/clang11-release-mpi
+    - cd build/clang11-release-mpi
+    - CXX=clang++-11 CC=clang-11 cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCLANG_FORMAT=/usr/bin/clang-format-11
+    - make -j 4
+    - make check
+  cache:
+    key: "${CI_COMMIT_REF_SLUG}-clang11-release-mpi"
+    paths:
+      - build/clang11-release-mpi
+    untracked: true
diff --git a/.gitlab-ci/gcc10-mpi-coverage.yml b/.gitlab-ci/gcc10-mpi-coverage.yml
index 9881eb7d15161bb5c6cda4786b8e2b43cde77e50..e3b178d1a09872ca53a176a4f425fc045f4189ac 100644
--- a/.gitlab-ci/gcc10-mpi-coverage.yml
+++ b/.gitlab-ci/gcc10-mpi-coverage.yml
@@ -1,5 +1,5 @@
 coverage:gcc10-mpi-coverage:
-  image: localhost:5000/ubuntu_gcc10_mpi
+  image: localhost:5000/ubuntu_jammy_gcc10_mpi
   stage: coverage
   needs: []
   script:
diff --git a/.gitlab-ci/gcc10-mpi-release.yml b/.gitlab-ci/gcc10-mpi-release.yml
index 198fe1785140889fb65d54e3fbdc4f9fe8ba94af..ef5be1ee55aff915acf4767ba0f5c1a9af1161de 100644
--- a/.gitlab-ci/gcc10-mpi-release.yml
+++ b/.gitlab-ci/gcc10-mpi-release.yml
@@ -1,5 +1,5 @@
 test:gcc10-mpi-release:
-  image: localhost:5000/ubuntu_gcc10_mpi
+  image: localhost:5000/ubuntu_jammy_gcc10_mpi
   stage: test
   needs: []
   script:
diff --git a/.gitlab-ci/gcc10-seq-coverage.yml b/.gitlab-ci/gcc10-seq-coverage.yml
index 421d47af55a010a9f47480e0fe349bffc980e264..96c5a3b79cc7dbb6a11b57bf71715432be16e3c7 100644
--- a/.gitlab-ci/gcc10-seq-coverage.yml
+++ b/.gitlab-ci/gcc10-seq-coverage.yml
@@ -1,5 +1,5 @@
 coverage:gcc10-seq-coverage:
-  image: localhost:5000/ubuntu_gcc10
+  image: localhost:5000/ubuntu_jammy_gcc10
   stage: coverage
   needs: []
   script:
diff --git a/.gitlab-ci/gcc10-seq-release.yml b/.gitlab-ci/gcc10-seq-release.yml
index 8a34f11b0dea3b4df56a2e8883c8d08245ddac2c..298124599f0aeb166af9e4d8b8d2a00ffc694924 100644
--- a/.gitlab-ci/gcc10-seq-release.yml
+++ b/.gitlab-ci/gcc10-seq-release.yml
@@ -1,5 +1,5 @@
 test:gcc10-seq-release:
-  image: localhost:5000/ubuntu_gcc10
+  image: localhost:5000/ubuntu_jammy_gcc10
   stage: test
   needs: []
   script:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 87fdee39aa6621d671f249224b8ed0e1fa58de93..8047520391cb75d3f46acad5c87a720414f3c040 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required (VERSION 3.16)
+cmake_minimum_required (VERSION 3.19)
 
 # CMake utils
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
@@ -83,8 +83,8 @@ endif()
 #------------------------------------------------------
 
 # Checks if compiler version is compatible with Pugs sources
-set(GNU_CXX_MIN_VERSION "9.0.0")
-set(CLANG_CXX_MIN_VERSION "10.0.0")
+set(GNU_CXX_MIN_VERSION "10.0.0")
+set(CLANG_CXX_MIN_VERSION "11.0.0")
 
 #------------------------------------------------------
 # Change Kokkos namespace to avoid conflicts
@@ -226,6 +226,7 @@ set(PUGS_ENABLE_HDF5 AUTO CACHE STRING
 
 if (PUGS_ENABLE_HDF5 MATCHES "^(AUTO|ON)$")
   # May be risky. (make to show pugs build options)
+  set(HDF5_PREFER_PARALLEL TRUE)
   find_package(HDF5)
   if (HDF5_FOUND)
     # HighFive
@@ -234,7 +235,6 @@ if (PUGS_ENABLE_HDF5 MATCHES "^(AUTO|ON)$")
     set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
     set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
     set(HIGHFIVE_EXAMPLES OFF)     # no examples
-    set(HIGHFIVE_PARALLEL_HDF5 ON) # activate parallel HDF5
     add_subdirectory(${PUGS_SOURCE_DIR}/packages/HighFive/)
     set(HIGHFIVE_TARGET HighFive)
   endif()
@@ -244,6 +244,19 @@ else()
   unset(PUGS_HAS_HDF5)
 endif()
 
+#------------------------------------------------------
+# search for libslurm
+
+find_package(Slurm)
+
+set(PUGS_HAS_SLURM ${SLURM_FOUND})
+
+if (${SLURM_FOUND})
+  include_directories(SYSTEM "${SLURM_INCLUDE_DIR}")
+else()
+  set(SLURM_LIBRARY "")
+endif()
+
 #------------------------------------------------------
 # search for clang-format
 
@@ -521,7 +534,7 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
 
       COMMAND ${FASTCOV} --gcov "${GCOV_BIN}"
       --include "${PUGS_SOURCE_DIR}/src"
-      --exclude "${PUGS_SOURCE_DIR}/src/main.cpp" "${PUGS_SOURCE_DIR}/src/utils/BacktraceManager.*" "${PUGS_SOURCE_DIR}/src/utils/FPEManager.*" "${PUGS_SOURCE_DIR}/src/utils/SignalManager.*"
+      --exclude "${PUGS_SOURCE_DIR}/src/main.cpp" "${PUGS_SOURCE_DIR}/src/utils/BacktraceManager.*" "${PUGS_SOURCE_DIR}/src/utils/FPEManager.*" "${PUGS_SOURCE_DIR}/src/utils/SignalManager.*" "${PUGS_SOURCE_DIR}/src/utils/checkpointing/pugs_checkpoint_main.cpp"
       --lcov -o coverage.info -n
 
       COMMAND ${LCOV} --gcov "${GCOV_BIN}" --list coverage.info
@@ -597,6 +610,7 @@ target_link_libraries(
   PugsMesh
   PugsAlgebra
   PugsAnalysis
+  PugsCheckpointing
   PugsDev
   PugsUtils
   PugsLanguage
@@ -609,6 +623,42 @@ target_link_libraries(
   PugsUtils
   PugsOutput
   PugsLanguageUtils
+  PugsCheckpointing
+  Kokkos::kokkos
+  ${PETSC_LIBRARIES}
+  ${SLEPC_LIBRARIES}
+  ${PARMETIS_LIBRARIES}
+  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
+  ${KOKKOS_CXX_FLAGS}
+  ${OPENMP_LINK_FLAGS}
+  ${PUGS_STD_LINK_FLAGS}
+  ${HIGHFIVE_TARGET}
+  ${SLURM_LIBRARY}
+  stdc++fs
+  )
+
+# Checkpoint management tool
+add_executable(
+  pugs_checkpoint
+  src/utils/checkpointing/pugs_checkpoint_main.cpp
+  )
+
+target_link_libraries(
+  pugs_checkpoint
+  PugsCheckpointing
+  PugsUtils
+  PugsMesh
+  PugsOutput
+  PugsLanguage
+  PugsLanguageAST
+  PugsLanguageModules
+  PugsLanguageUtils
+  PugsScheme
+  PugsDev
+  PugsAnalysis
+  PugsAlgebra
+  PugsMesh
+  PugsOutput
   Kokkos::kokkos
   ${PETSC_LIBRARIES}
   ${SLEPC_LIBRARIES}
@@ -618,9 +668,11 @@ target_link_libraries(
   ${OPENMP_LINK_FLAGS}
   ${PUGS_STD_LINK_FLAGS}
   ${HIGHFIVE_TARGET}
+  ${SLURM_LIBRARY}
   stdc++fs
   )
 
+
 # -------------------- Documentation --------------------
 
 include(PugsDoc)
@@ -632,9 +684,11 @@ include(PugsDoxygen)
 # -------------------- Installation ---------------------
 install(TARGETS
   pugs
+  pugs_checkpoint
   PugsMesh
   PugsAlgebra
   PugsAnalysis
+  PugsCheckpointing
   PugsDev
   PugsUtils
   PugsLanguage
@@ -707,6 +761,12 @@ else()
   endif()
 endif()
 
+if (SLURM_FOUND)
+  message(" SLURM library: ${SLURM_LIBRARY}")
+else()
+  message(" SLURM library: not found!")
+endif()
+
 message("----------- utilities ----------")
 
 if(CLANG_FORMAT)
diff --git a/cmake/FindSlurm.cmake b/cmake/FindSlurm.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..4904687ed482e95feab17fdcf07602b699cb7793
--- /dev/null
+++ b/cmake/FindSlurm.cmake
@@ -0,0 +1,54 @@
+# Looking for Slurm
+
+find_package(PkgConfig)
+pkg_check_modules(PC_SLURM QUIET SLURM)
+
+find_path(SLURM_INCLUDE_DIR
+  NAMES
+    "slurm/slurm.h"
+    "slurm/spank.h"
+  PATHS
+    ENV SLURM
+    ENV SLURM_PATH
+    ENV SLURM_ROOT
+    ENV CPATH
+    ENV C_INCLUDE_PATH
+    ENV CPLUS_INCLUDE_PATH
+  PATH_SUFFIXES
+    "include"
+  DOC
+  "Path to the SLURM include directory")
+
+find_library(SLURM_LIBRARY
+  NAMES
+    slurm
+  PATHS
+    ENV SLURM
+    ENV SLURM_PATH
+    ENV SLURM_ROOT
+    ENV LD_LIBRARY_PATH
+    ENV LIBRARY_PATH
+    ENV PATH
+  PATH_SUFFIXES
+    "lib"
+    "lib64"
+  DOC
+    "Path to the SLURM shared library")
+
+ if(EXISTS "${SLURM_INCLUDE_DIR}/slurm/slurm.h")
+   message(STATUS "Found slurm.h in ${SLURM_INCLUDE_DIR}")
+   if("${SLURM_LIBRARY}" STREQUAL "SLURM_LIBDIR-NOTFOUND")
+     message(WARNING "Could not find slurm library.")
+   endif()
+   message(STATUS "Found slurm library ${SLURM_LIBRARY}")
+ else()
+   message(WARNING "Could not find slurm include dir.")
+ endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Slurm
+  FOUND_VAR
+    SLURM_FOUND
+  REQUIRED_VARS
+    SLURM_LIBRARY
+    SLURM_INCLUDE_DIR)
diff --git a/cmake/PugsDoc.cmake b/cmake/PugsDoc.cmake
index 7718046e21b5de20bc5f4a6aeb5c451ca9757890..318e694b2a883ff77bd638b9faf30ccc2e99e65b 100644
--- a/cmake/PugsDoc.cmake
+++ b/cmake/PugsDoc.cmake
@@ -37,6 +37,7 @@ if (EMACS AND GNUPLOT_FOUND AND GMSH)
     COMMAND
     ${CMAKE_COMMAND} -E env
     PUGS=${PUGS_BINARY_DIR}/pugs
+    PUGS_CHECKPOINT=${PUGS_BINARY_DIR}/pugs_checkpoint
     HOME=${PUGS_SOURCE_DIR}/doc/lisp
     PUGS_SOURCE_DIR=${PUGS_SOURCE_DIR}
     PUGS_BINARY_DIR=${PUGS_BINARY_DIR}
@@ -51,6 +52,7 @@ if (EMACS AND GNUPLOT_FOUND AND GMSH)
     COMMAND
     ${CMAKE_COMMAND} -E env
     PUGS=${PUGS_BINARY_DIR}/pugs
+    PUGS_CHECKPOINT=${PUGS_BINARY_DIR}/pugs_checkpoint
     HOME=${PUGS_SOURCE_DIR}/doc/lisp
     PUGS_SOURCE_DIR=${PUGS_SOURCE_DIR}
     PUGS_BINARY_DIR=${PUGS_BINARY_DIR}
@@ -59,6 +61,7 @@ if (EMACS AND GNUPLOT_FOUND AND GMSH)
     "${PUGS_SOURCE_DIR}/doc/userdoc.org"
     "${PUGS_SOURCE_DIR}/doc/lisp/userdoc-html.el"
     pugs
+    pugs_checkpoint
     pugsdoc-dir
     pugsdoc-download-elpa
     ${ORG_GENERATOR_FILES}
@@ -77,6 +80,7 @@ if (EMACS AND GNUPLOT_FOUND AND GMSH)
       COMMAND
       ${CMAKE_COMMAND} -E env
       PUGS=${PUGS_BINARY_DIR}/pugs
+      PUGS_CHECKPOINT=${PUGS_BINARY_DIR}/pugs_checkpoint
       HOME=${PUGS_SOURCE_DIR}/doc/lisp
       PUGS_SOURCE_DIR=${PUGS_SOURCE_DIR}
       PUGS_BINARY_DIR=${PUGS_BINARY_DIR}
@@ -87,6 +91,7 @@ if (EMACS AND GNUPLOT_FOUND AND GMSH)
       "${PUGS_SOURCE_DIR}/tools/pgs-pygments.sh"
       "${PUGS_SOURCE_DIR}/tools/pgs-pygments.py"
       pugs
+      pugs_checkpoint
       pugsdoc-dir
       pugsdoc-download-elpa
       ${ORG_GENERATOR_FILES}
diff --git a/doc/lisp/build-doc-config.el b/doc/lisp/build-doc-config.el
index 6f06400a6dc1176370550342ea7bfb04ac976c11..ee9826245dcf4ed0671070eb9d6b172ba7b34369 100644
--- a/doc/lisp/build-doc-config.el
+++ b/doc/lisp/build-doc-config.el
@@ -51,7 +51,7 @@
  '(org-latex-listings 'minted))
 
 (setq org-latex-minted-options
-      '(("linenos=true") ("breaklines")))
+      '(("linenos=true") ("breaklines") ("autogobble")))
 
 (defun org-export-output-file-name-modified (orig-fun extension &optional subtreep pub-dir)
   (unless pub-dir
diff --git a/doc/userdoc.org b/doc/userdoc.org
index 54ea305564884ff6363a456990762adfcc378669..243fbde6af57b702f741d211bc9cb734ebb73364 100644
--- a/doc/userdoc.org
+++ b/doc/userdoc.org
@@ -32,6 +32,12 @@
 #+LATEX_HEADER_EXTRA: \usepackage{mathpazo}
 #+LATEX_HEADER_EXTRA: \usepackage{inconsolata}
 
+#+LATEX_HEADER_EXTRA:  %Patch accsupp to avoid copying line numbers when copying from listing
+#+LATEX_HEADER_EXTRA: \usepackage{accsupp}
+#+LATEX_HEADER_EXTRA: \newcommand\emptyaccsupp[1]{\BeginAccSupp{ActualText={}}#1\EndAccSupp{}}
+#+LATEX_HEADER_EXTRA: \let\theHFancyVerbLine\theFancyVerbLine
+#+LATEX_HEADER_EXTRA: \def\theFancyVerbLine{\rmfamily\tiny\emptyaccsupp{\arabic{FancyVerbLine}}}
+
 #+LATEX_HEADER_EXTRA: \BeforeBeginEnvironment{tabular}{\rowcolors[]{2}{orange!5}{orange!10}}
 #+LATEX_HEADER_EXTRA: \BeforeBeginEnvironment{minted}{\begin{mdframed}[linecolor=blue,backgroundcolor=blue!10]}
 #+LATEX_HEADER_EXTRA: \AfterEndEnvironment{minted}{\end{mdframed}}
@@ -2541,11 +2547,200 @@ to files for instance) as we will see below.
 
 **** ~core~ provided functions
 
-***** ~exit: Z -> void~
+***** execution control functions
+
+Here are functions that allow to control the execution of the
+script. These can stop the execution if some conditions are met or
+create checkpoint that may be used to stop and then resume the
+execution.
+
+****** ~exit: Z -> void~
 
 This function interrupts the execution of the script. The integer (~Z~)
 value is the code that is returned when ~pugs~ exits.
 
+#+NAME: exit-function
+#+BEGIN_SRC pugs :exports both :results output
+  for (let i:N, i = 1; i < 10; ++i) {
+    cout << "i = " << i << "\n";
+    if (i==3) {
+      exit(0);
+    }
+  }
+#+END_SRC
+The output shows that the execution is interrupted (in a clean way) if
+the condition (here ~i == 3~) is met.
+#+RESULTS: exit-function
+
+****** ~stop: void -> B~
+
+This is an interactive function that is used to notify the code to
+stop its execution. It returns ~true~ if one of the following stopping
+condition is met.
+- If a ~stop~ file is created *after* the beginning of the execution in
+  the *execution directory*. This file can be created using the ~touch~
+  command for instance
+  #+BEGIN_SRC shell :exports source
+    touch stop
+  #+END_SRC
+- If ~pugs~ was compiled using ~Slurm~ support (this can be checked using
+  the command ~./pugs -v~) then during batch execution the ~stop~ function
+  will also return ~true~ as soon as the remaining execution time is
+  less than $150s$.
+
+In the following example the function ~stop~ returns always ~false~ since
+none of the above conditions are satisfied.
+#+NAME: stop-function
+#+BEGIN_SRC pugs :exports both :results output
+  for (let i:N, i = 1; i <= 5; ++i) {
+    cout << "i = " << i << "\n";
+    if (stop()) {
+      exit(0);
+    }
+  }
+#+END_SRC
+The output shows that the execution reaches ~i == 5~.
+#+RESULTS: stop-function
+
+****** ~checkpoint: void -> void~
+
+This function creates a checkpoint that can be used as a starting
+point for another execution. This function can be placed anywhere in
+the script and possibly multiple times. The checkpoint storage file is
+~checkpoint.h5~ and it is created in the current execution directory.
+
+*This functionality requires ~pugs~ to be compiled with ~HDF5~ support*.
+
+The following example creates two checkpoints.
+#+NAME: checkpoint-function
+#+BEGIN_SRC pugs :exports both :results output
+  let n:N, n = 4;
+  cout << "n = " << n << "\n";
+  checkpoint();
+  let x:R^3, x = [1, 2, 3];
+  cout << "x = " << x << "\n";
+  checkpoint();
+#+END_SRC
+The output displays the creation of two checkpoints in the file ~checkpoint.h5~.
+#+RESULTS: checkpoint-function
+The command
+#+BEGIN_SRC shell :exports code
+  pugs_checkpoint --info checkpoint.h5
+#+END_SRC
+displays a simple execution state for each checkpoint stored in the
+file in order to help user to choose the appropriate resuming
+checkpoint.
+#+NAME: checkpoint-info
+#+BEGIN_SRC shell :exports results :results output
+  ${PUGS_CHECKPOINT} --no-color --info checkpoint.h5
+#+END_SRC
+#+RESULTS: checkpoint-info
+One can notice that the ~x~ variable does not appear in the first
+checkpoint since it was not created at this point. Also two pointers
+are defined:
+- ~last_checkpoint~ that displays the more recent checkpoint,
+  and
+- ~resuming_checkpoint~ that indicates which checkpoint will be used when
+  resuming.
+
+Proceeding with this example, one can change the resuming checkpoint
+with the command
+#+BEGIN_SRC shell :exports code
+  pugs_checkpoint --resume-from 0 checkpoint.h5
+#+END_SRC
+which displays
+#+NAME: checkpoint-resume-from
+#+BEGIN_SRC shell :exports results :results output
+  ${PUGS_CHECKPOINT} --no-color --resume-from 0 checkpoint.h5
+#+END_SRC
+#+RESULTS: checkpoint-resume-from
+Running again
+#+BEGIN_SRC shell :exports code
+  pugs_checkpoint --info checkpoint.h5
+#+END_SRC
+now prints
+#+NAME: checkpoint-info0
+#+BEGIN_SRC shell :exports results :results output
+  ${PUGS_CHECKPOINT} --no-color --info checkpoint.h5
+#+END_SRC
+#+RESULTS: checkpoint-info0
+One notices that ~resuming_checkpoint~ now points to ~checkpoint_0~.
+
+It is now possible to resume the execution using the command
+#+BEGIN_SRC shell :exports code
+  pugs --resume checkpoint.h5
+#+END_SRC
+The output is now
+#+NAME: checkpoint-resume0
+#+BEGIN_SRC shell :exports results :results output
+  ${PUGS} --no-exec-stat --no-preamble --no-color --threads=1 --resume checkpoint.h5
+#+END_SRC
+#+RESULTS: checkpoint-resume0
+Observe that the random seed is reset to the value that was stored at
+checkpoint 0.
+
+Also each checkpoint whose number is greater than the resuming
+checkpoint (here 0) are removed when the next checkpoint (here 1) is
+written.
+
+#+BEGIN_note
+One may have noticed that the script file that is used for resuming is
+actually stored in the ~checkpoint.h5~ file, the script is not a command
+line argument.
+
+By now, it is not possible to *simply* modify the script while
+resuming. This is done in purpose since it remains unclear if such a
+dangerous functionality should be make easy. Indeed allowing
+script modifications may lead to undefined behaviors.
+#+END_note
+
+#+BEGIN_warning
+The number of ~MPI~ ranks *cannot* be changed when resuming.
+
+This is not likely to change since it is a very specific
+functionality.  However, a post-processing tool rewriting checkpoints
+may be developed to achieve it, but it is not a priority.
+#+END_warning
+
+****** ~checkpoint_and_exit: void -> void~
+
+This is an advanced version of the ~checkpoint~ function that cause a
+clean ~exit~ of the code after writing a checkpoint.
+
+The use case for this function is batch execution and is generally
+triggered by the ~stop~ function.
+
+We give here a simple example.
+#+NAME: checkpoint-exit-function
+#+BEGIN_SRC pugs :exports both :results output
+  for (let i:N, i = 1; i<=10; ++i) {
+    cout << "i = " << i << "\n";
+    if (i==5) {
+      checkpoint_and_exit();
+    }
+  }
+#+END_SRC
+The output is
+#+RESULTS: checkpoint-exit-function
+
+Then one can resume the execution as previously by
+#+BEGIN_SRC shell :exports code
+  pugs --resume checkpoint.h5
+#+END_SRC
+It gives the output
+The output is now
+#+NAME: checkpoint-resume1
+#+BEGIN_SRC shell :exports results :results output
+  ${PUGS} --no-exec-stat --no-preamble --no-color --threads=1 --resume checkpoint.h5
+#+END_SRC
+#+RESULTS: checkpoint-resume1
+
+# Clean-up
+#+BEGIN_SRC shell :exports results :results none
+  /bin/rm -f checkpoint.h5
+#+END_SRC
+
+
 ***** ~getAvailableModules: void -> string~
 
 This function that is used in the preamble of this section is a
diff --git a/packages/HighFive/.github/run_examples.sh b/packages/HighFive/.github/run_examples.sh
index 77861f38846c279d551ec355b481892b34d4f89a..9af9a6ef1ba930f00b6c4b3d75265a67a06cfe4d 100755
--- a/packages/HighFive/.github/run_examples.sh
+++ b/packages/HighFive/.github/run_examples.sh
@@ -1,6 +1,6 @@
 #! /usr/bin/env bash
 
-set -e
+set -eu
 
 if [[ $# -eq 0 ]]
 then
diff --git a/packages/HighFive/.github/workflows/ci.yml b/packages/HighFive/.github/workflows/ci.yml
index 8730ed4104d319675090a633f66b18041631269a..66069123da2df778caa0b3ad1c0f565a72ef7f36 100644
--- a/packages/HighFive/.github/workflows/ci.yml
+++ b/packages/HighFive/.github/workflows/ci.yml
@@ -7,12 +7,14 @@ concurrency:
 on:
   push:
     branches:
-      - ci_test
-      - release/**
+      - master
+      - main
+      - v2.x
   pull_request:
     branches:
       - master
-      - release/**
+      - main
+      - v2.x
     paths-ignore:
       - '**.md'
       - '**.rst'
@@ -41,22 +43,21 @@ jobs:
           - config:
               os: ubuntu-20.04
               pkgs: ''
-              flags: '-DHIGHFIVE_USE_BOOST:Bool=OFF'
           - config:
               os: ubuntu-20.04
               pkgs: 'libboost-all-dev libopencv-dev'
-              flags: '-DHIGHFIVE_USE_OPENCV:Bool=ON -GNinja'
+              flags: '-DHIGHFIVE_TEST_BOOST:Bool=ON -DHIGHFIVE_TEST_OPENCV:Bool=ON -GNinja'
           - config:
               os: ubuntu-latest
               pkgs: 'libboost-all-dev libeigen3-dev libopencv-dev'
-              flags: '-DHIGHFIVE_USE_EIGEN:Bool=ON -DHIGHFIVE_USE_OPENCV:Bool=ON -GNinja'
+              flags: '-DHIGHFIVE_TEST_BOOST:Bool=ON -DHIGHFIVE_TEST_EIGEN:Bool=ON -DHIGHFIVE_TEST_OPENCV:Bool=ON -GNinja'
           - config:
               os: ubuntu-20.04
               pkgs: 'libboost-all-dev'
-              flags: '-DCMAKE_CXX_STANDARD=17'
+              flags: '-DCMAKE_CXX_STANDARD=17 -DHIGHFIVE_TEST_BOOST:Bool=ON'
           - config:
               os: ubuntu-22.04
-              flags: '-DHIGHFIVE_USE_BOOST=Off -DCMAKE_CXX_STANDARD=20'
+              flags: '-DHIGHFIVE_TEST_BOOST=Off -DCMAKE_CXX_STANDARD=20 -DHIGHFIVE_HAS_CONCEPTS=On'
 
     steps:
     - uses: actions/checkout@v3
@@ -75,13 +76,17 @@ jobs:
 
     - name: Build
       run: |
-        CMAKE_OPTIONS=(-DHIGHFIVE_PARALLEL_HDF5:BOOL=ON ${{ matrix.config.flags }})
+        CMAKE_OPTIONS=(-DHDF5_PREFER_PARALLEL:BOOL=ON ${{ matrix.config.flags }})
         source $GITHUB_WORKSPACE/.github/build.sh
 
     - name: Test
       working-directory: ${{github.workspace}}/build
       run: |
         ctest -j2 --output-on-failure -C $BUILD_TYPE
+
+    - name: Test No HDF5 Diagnositics
+      working-directory: ${{github.workspace}}/build
+      run: |
         ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG
 
 
@@ -91,7 +96,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        hdf5_version : [ hdf5-1_8_23, hdf5-1_10_11, hdf5-1_12_3, hdf5-1_14_3 ]
+        hdf5_version : [ hdf5-1_8_23, hdf5-1_10_11, hdf5-1_12_3, hdf5_1.14.4.1 ]
 
     steps:
     - uses: actions/checkout@v3
@@ -122,6 +127,10 @@ jobs:
       working-directory: ${{github.workspace}}/build
       run: |
         ctest -j2 --output-on-failure -C $BUILD_TYPE
+
+    - name: Test No HDF5 Diagnositics
+      working-directory: ${{github.workspace}}/build
+      run: |
         ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG
 
 
@@ -155,13 +164,17 @@ jobs:
     - name: Build
       env: ${{matrix.env}}
       run: |
-        CMAKE_OPTIONS=(-GNinja)
+        CMAKE_OPTIONS=(-DHIGHFIVE_TEST_BOOST=ON -GNinja)
         source $GITHUB_WORKSPACE/.github/build.sh
 
     - name: Test
       working-directory: ${{github.workspace}}/build
       run: |
         ctest -j2 --output-on-failure -C $BUILD_TYPE
+
+    - name: Test No HDF5 Diagnositics
+      working-directory: ${{github.workspace}}/build
+      run: |
         ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG
 
     - name: Examples
@@ -195,8 +208,8 @@ jobs:
       run: |
         CMAKE_OPTIONS=(
           -GNinja
-          -DHIGHFIVE_USE_BOOST:BOOL=ON
-          -DHIGHFIVE_USE_EIGEN:BOOL=ON
+          -DHIGHFIVE_TEST_BOOST:BOOL=ON
+          -DHIGHFIVE_TEST_EIGEN:BOOL=ON
           -DHIGHFIVE_BUILD_DOCS:BOOL=FALSE
           -DHIGHFIVE_GLIBCXX_ASSERTIONS=${HIGHFIVE_GLIBCXX_ASSERTIONS:-OFF}
           -DHIGHFIVE_SANITIZER=${HIGHFIVE_SANITIZER:-OFF}
@@ -207,6 +220,10 @@ jobs:
       working-directory: ${{github.workspace}}/build
       run: |
         ctest -j2 --output-on-failure -C $BUILD_TYPE
+
+    - name: Test No HDF5 Diagnositics
+      working-directory: ${{github.workspace}}/build
+      run: |
         ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG
 
     - name: Examples
@@ -246,7 +263,7 @@ jobs:
         sudo apt-get -qq install libhdf5-openmpi-dev
 
     - name: "CMake Project Integration"
-      run: bash tests/test_project_integration.sh
+      run: bash tests/cmake_integration/test_cmake_integration.sh
 
 
   # Job checking the benchmarks work
@@ -276,9 +293,16 @@ jobs:
     runs-on: ${{matrix.os}}
     strategy:
       matrix:
-        os: [ "macOS-12" ]
+        os: [ "macOS-14" ]
         cxxstd: ["14", "17", "20"]
 
+        include:
+          - os: "macOS-12"
+            cxxstd: "14"
+
+          - os: "macOS-13"
+            cxxstd: "20"
+
     steps:
     - uses: actions/checkout@v3
       with:
@@ -291,12 +315,10 @@ jobs:
       run: |
         CMAKE_OPTIONS=(
           -GNinja
-          -DHIGHFIVE_USE_BOOST:BOOL=ON
-          -DHIGHFIVE_USE_EIGEN:BOOL=ON
-          -DHIGHFIVE_USE_XTENSOR:BOOL=ON
+          -DHIGHFIVE_TEST_BOOST:BOOL=ON
+          -DHIGHFIVE_TEST_EIGEN:BOOL=ON
+          -DHIGHFIVE_TEST_XTENSOR:BOOL=ON
           -DHIGHFIVE_BUILD_DOCS:BOOL=FALSE
-          -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON
-          -DCMAKE_CXX_FLAGS="-coverage -O0"
           -DCMAKE_CXX_STANDARD=${{matrix.cxxstd}}
         )
         source $GITHUB_WORKSPACE/.github/build.sh
@@ -305,6 +327,10 @@ jobs:
       working-directory: ${{github.workspace}}/build
       run: |
         ctest -j2 --output-on-failure -C $BUILD_TYPE
+
+    - name: Test No HDF5 Diagnositics
+      working-directory: ${{github.workspace}}/build
+      run: |
         ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG
 
     - name: Examples
@@ -318,19 +344,9 @@ jobs:
     runs-on:  ${{matrix.os}}
     strategy:
       matrix:
-        os: [ "windows-2022"]
-        vs-toolset: [ "v141", "v143" ]
+        os: [ "windows-2019", "windows-2022"]
         cxxstd: ["14", "17", "20"]
 
-        include:
-          - os: "windows-2019"
-            vs-toolset: "v142"
-            cxxstd: "14"
-
-          - os: "windows-2019"
-            vs-toolset: "v142"
-            cxxstd: "17"
-
     steps:
     - uses: actions/checkout@v3
       with:
@@ -345,13 +361,11 @@ jobs:
       shell: bash -l {0}
       run: |
         CMAKE_OPTIONS=(
-          -T ${{matrix.vs-toolset}}
           -DCMAKE_CXX_STANDARD=${{matrix.cxxstd}}
           -DHIGHFIVE_UNIT_TESTS=ON
-          -DHIGHFIVE_USE_BOOST:BOOL=ON
-          -DHIGHFIVE_USE_EIGEN:BOOL=ON
-          -DHIGHFIVE_USE_XTENSOR:BOOL=ON
-          -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON
+          -DHIGHFIVE_TEST_BOOST:BOOL=ON
+          -DHIGHFIVE_TEST_EIGEN:BOOL=ON
+          -DHIGHFIVE_TEST_XTENSOR:BOOL=ON
         )
         source $GITHUB_WORKSPACE/.github/build.sh
 
@@ -360,4 +374,9 @@ jobs:
       shell: bash -l {0}
       run: |
         ctest -j2 --output-on-failure -C $BUILD_TYPE
+
+    - name: Test No HDF5 Diagnositics
+      working-directory: ${{github.workspace}}/build
+      shell: bash -l {0}
+      run: |
         ! ctest --verbose -C $BUILD_TYPE | grep HDF5-DIAG
diff --git a/packages/HighFive/.github/workflows/clang_format.yml b/packages/HighFive/.github/workflows/clang_format.yml
index 56f2fd8d53b78f0d709ae0ccc937207f1ba57083..7b9be4909f266a82c4d7ac79a38380e9a5f511bc 100644
--- a/packages/HighFive/.github/workflows/clang_format.yml
+++ b/packages/HighFive/.github/workflows/clang_format.yml
@@ -8,10 +8,8 @@ on:
   pull_request:
     branches:
       - master
-    paths-ignore:
-      - '**.md'
-      - '**.rst'
-      - 'doc/**'
+      - main
+      - v2.x
 
 jobs:
   Code_Format:
diff --git a/packages/HighFive/.github/workflows/coverage.yml b/packages/HighFive/.github/workflows/coverage.yml
index b3f4a212bcc442cef74ade505681ff3a1e61ac28..e5f3ed5fddfe5b3a465bc440dcb93b0926039b55 100644
--- a/packages/HighFive/.github/workflows/coverage.yml
+++ b/packages/HighFive/.github/workflows/coverage.yml
@@ -8,12 +8,13 @@ on:
   push:
     branches:
       - master
-      - ci_test
-      - release/**
+      - main
+      - v2.x
   pull_request:
     branches:
       - master
-      - release/**
+      - main
+      - v2.x
     paths-ignore:
       - '**.md'
       - '**.rst'
@@ -45,10 +46,10 @@ jobs:
       run: |
         CMAKE_OPTIONS=(
           -GNinja
-          -DHIGHFIVE_USE_BOOST:BOOL=ON
-          -DHIGHFIVE_USE_EIGEN:BOOL=ON
-          -DHIGHFIVE_USE_OPENCV:BOOL=ON
-          #-DHIGHFIVE_USE_XTENSOR:BOOL=ON
+          -DHIGHFIVE_TEST_BOOST:BOOL=ON
+          -DHIGHFIVE_TEST_EIGEN:BOOL=ON
+          -DHIGHFIVE_TEST_OPENCV:BOOL=ON
+          #-DHIGHFIVE_TEST_XTENSOR:BOOL=ON
           -DHIGHFIVE_TEST_SINGLE_INCLUDES=ON
           -DHIGHFIVE_BUILD_DOCS:BOOL=FALSE
           -DCMAKE_CXX_FLAGS="-coverage -O0"
diff --git a/packages/HighFive/.github/workflows/gh-pages.yml b/packages/HighFive/.github/workflows/gh-pages.yml
index 2032f91abc042c9704b420b021e72abbf52241f3..101e7234d78d2f15bb9064f9d5199484ab00cba2 100644
--- a/packages/HighFive/.github/workflows/gh-pages.yml
+++ b/packages/HighFive/.github/workflows/gh-pages.yml
@@ -3,10 +3,12 @@ name: gh-pages
 on:
   push:
     branches:
-      - master
+      - v2.x
   pull_request:
     branches:
+      - v2.x
       - master
+      - main
 
 jobs:
 
@@ -39,7 +41,7 @@ jobs:
         cp -r doc/poster build/doc/html/
 
     - name: Deploy to GitHub Pages
-      if: ${{ success() && github.ref == 'refs/heads/master' && github.event_name == 'push' }}
+      if: ${{ success() && github.ref == 'refs/heads/v2.x' && github.event_name == 'push' }}
       uses: crazy-max/ghaction-github-pages@v2
       with:
         target_branch: gh-pages
diff --git a/packages/HighFive/.github/workflows/version_file.yml b/packages/HighFive/.github/workflows/version_file.yml
index 816137e955fe920c432cf985cbd3a43fa2e924ff..55cf0f6f53268149729f573af465ccace052346e 100644
--- a/packages/HighFive/.github/workflows/version_file.yml
+++ b/packages/HighFive/.github/workflows/version_file.yml
@@ -3,12 +3,14 @@ name: HighFive Check Version File
 on:
   push:
     branches:
-      - ci_test
-      - release/**
+      - master
+      - main
+      - v2.x
   pull_request:
     branches:
       - master
-      - release/**
+      - main
+      - v2.x
 
 jobs:
   CheckVersion:
@@ -27,7 +29,7 @@ jobs:
     - name: Build
       run: |
         # Will trigger `configure_file` for H5Version.hpp.
-        cmake -DHIGHFIVE_USE_BOOST=Off -B build .
+        cmake -B build .
 
     - name: Test
       run: |
diff --git a/packages/HighFive/.gitrepo b/packages/HighFive/.gitrepo
index 98988c87ecab65e9d9e9215ea6684fa53407fd3b..0512eb59369fa0287eb768cdb9215c29160d65a0 100644
--- a/packages/HighFive/.gitrepo
+++ b/packages/HighFive/.gitrepo
@@ -6,7 +6,7 @@
 [subrepo]
 	remote = git@github.com:BlueBrain/HighFive.git
 	branch = master
-	commit = d04789128123e1a643c6284e28087be1dafae2ee
-	parent = ce26d02b1f92356362dc4daede0babf8d4ac23b6
+	commit = 20fbd4c4d328bc18a342eeef5ffd29f17de084e0
+	parent = 35908346b20b524d4d2d9f8f6220800c27595413
 	method = merge
 	cmdver = 0.4.6
diff --git a/packages/HighFive/.travis.yml b/packages/HighFive/.travis.yml
deleted file mode 100644
index bc5d34081016e68a344a5eb34ee671075ae8f22e..0000000000000000000000000000000000000000
--- a/packages/HighFive/.travis.yml
+++ /dev/null
@@ -1,138 +0,0 @@
-# Adapted from various sources, including:
-# - Louis Dionne's Hana: https://github.com/ldionne/hana
-# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit
-# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3
-# - Gabi Melman spdlog: https://github.com/gabime/spdlog
-
-sudo: required
-language: cpp
-
-addons: &gcc7
-  apt:
-    packages:
-      - g++-7
-      - libboost-all-dev
-      - libhdf5-openmpi-dev
-      - libeigen3-dev
-      - ninja-build
-    sources:
-      - ubuntu-toolchain-r-test
-
-matrix:
-  include:
-    # Older linux (trusty) with default gcc
-    # Install serial hdf5 + build serial
-    - os: linux
-      dist: trusty
-      env:
-        - HIGHFIVE_USE_XTENSOR=False
-        - HIGHFIVE_USE_OPENCV=False
-        - HIGHFIVE_PARALLEL_HDF5=False
-        - IS_BASE_ENVIRON=1
-      addons:
-        apt:
-          packages:
-            - libboost-all-dev
-            - libeigen3-dev
-            - libhdf5-serial-dev
-            - ninja-build
-
-    # Linux gcc-7
-    # Install parallel hdf5 + build parallel
-    - os: linux
-      dist: xenial
-      env:
-        - GCC_VERSION=7
-        - HIGHFIVE_USE_XTENSOR=True
-        - HIGHFIVE_USE_OPENCV=False
-        - HIGHFIVE_PARALLEL_HDF5=True
-      addons: *gcc7
-
-    # Mac OSX XCode 10
-    - os: osx
-      osx_image: xcode10.3
-      env:
-        - HIGHFIVE_USE_XTENSOR=True
-        - HIGHFIVE_USE_OPENCV=True
-        - HIGHFIVE_PARALLEL_HDF5=False
-
-    # Windows
-    - os: windows
-      env:
-        - HIGHFIVE_USE_XTENSOR=True
-        - HIGHFIVE_USE_OPENCV=True
-        - HIGHFIVE_PARALLEL_HDF5=False
-
-env:
-  global:
-    - MINCONDA_VERSION="latest"
-    - MINCONDA_LINUX="Linux-x86_64"
-    - MINCONDA_OSX="MacOSX-x86_64"
-
-install:
-  - export HOMEBREW_NO_AUTO_UPDATE=1  # for reproducibility, dont autoupdate
-
-  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
-      MINCONDA_OS=$MINCONDA_LINUX;
-    elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
-      if [ "$BREW_USE_LATEST" ]; then
-        brew update;
-        brew install hdf5; brew upgrade hdf5;
-      fi;
-      brew install boost hdf5 eigen ninja;
-      MINCONDA_OS=$MINCONDA_OSX;
-    fi
-
-  - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then
-      export CMAKE_GENERATOR="Visual Studio 15 2017 Win64" ;
-      export TESTS_TARGET="RUN_TESTS";
-      choco install --yes miniconda3 ;
-      source C:/Tools/miniconda3/Scripts/activate ;
-    else
-      export CMAKE_GENERATOR="Ninja" ;
-      export TESTS_TARGET="test";
-      wget "http://repo.continuum.io/miniconda/Miniconda3-$MINCONDA_VERSION-$MINCONDA_OS.sh" -O miniconda.sh;
-      bash miniconda.sh -b -p $HOME/miniconda ;
-      source $HOME/miniconda/bin/activate;
-      hash -r ;
-    fi
-  - conda config --set always_yes yes --set changeps1 no
-  - conda update -q conda
-  - conda install -c conda-forge mamba
-  - if [[ "$HIGHFIVE_USE_XTENSOR" == "True" ]]; then
-      mamba install -c conda-forge xtl xsimd xtensor;
-    fi
-  - if [[ "$HIGHFIVE_USE_OPENCV" == "True" ]]; then
-      mamba install -c conda-forge libopencv opencv;
-    fi
-  - if [[ "$TRAVIS_OS_NAME" == "windows" ]]; then
-      mamba install -c conda-forge boost-cpp hdf5 eigen;
-    fi
-
-before_script:
-  - if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
-  - if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi
-  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CXX="clang++" CC="clang"; fi
-  - which $CXX
-  - which $CC
-  - $CXX --version
-  - cmake --version
-
-script:
-  - cd ${TRAVIS_BUILD_DIR}
-  - mkdir -p build && pushd build
-  - >
-    cmake --warn-uninitialized --debug-output
-    -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON
-    -DHIGHFIVE_TEST_SINGLE_INCLUDES:BOOL=ON
-    -DHIGHFIVE_PARALLEL_HDF5:BOOL=${HIGHFIVE_PARALLEL_HDF5}
-    -DHIGHFIVE_USE_EIGEN:BOOL=ON
-    -DHIGHFIVE_USE_XTENSOR:BOOL=${HIGHFIVE_USE_XTENSOR}
-    -DHIGHFIVE_USE_OPENCV:BOOL=${HIGHFIVE_USE_OPENCV}
-    -G "${CMAKE_GENERATOR}" ../
-  - cmake --build .
-  - CTEST_OUTPUT_ON_FAILURE=1 cmake --build . --target ${TESTS_TARGET}
-  - popd
-  - if [ $IS_BASE_ENVIRON ]; then
-      bash tests/test_project_integration.sh;
-    fi
diff --git a/packages/HighFive/.zenodo.json b/packages/HighFive/.zenodo.json
new file mode 100644
index 0000000000000000000000000000000000000000..0e96eedbdb17835eb97cd787704e4f6096a116c6
--- /dev/null
+++ b/packages/HighFive/.zenodo.json
@@ -0,0 +1,38 @@
+{
+    "creators": [
+        {
+            "affiliation": "",
+            "name": "Devresse, Adrien"
+        },
+        {
+            "affiliation": "",
+            "name": "Cornu, Nicolas"
+        },
+        {
+            "affiliation": "",
+            "name": "Grosheintz-Laval, Luc"
+        },
+        {
+            "affiliation": "",
+            "name": "Awile, Omar"
+        },
+        {
+            "affiliation": "",
+            "name": "de Geus, Tom"
+        },
+        {
+            "affiliation": "",
+            "name": "Pereira, Fernando"
+        },
+        {
+            "affiliation": "",
+            "name": "Wolf, Matthias"
+        },
+        {
+            "affiliation": "",
+            "name": "HighFive Contributors"
+        }
+    ],
+
+    "title": "HighFive - Header-only C++ HDF5 interface"
+}
diff --git a/packages/HighFive/CMake/HighFiveConfig.cmake.in b/packages/HighFive/CMake/HighFiveConfig.cmake.in
deleted file mode 100644
index 464a645d038f8221583fd5019eb81f05662fdef4..0000000000000000000000000000000000000000
--- a/packages/HighFive/CMake/HighFiveConfig.cmake.in
+++ /dev/null
@@ -1,74 +0,0 @@
-function(copy_interface_properties target source)
-  foreach(prop
-          INTERFACE_COMPILE_DEFINITIONS
-          INTERFACE_COMPILE_FEATURES
-          INTERFACE_COMPILE_OPTIONS
-          INTERFACE_INCLUDE_DIRECTORIES
-          INTERFACE_LINK_LIBRARIES
-          INTERFACE_SOURCES
-          INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
-    set_property(TARGET ${target} APPEND PROPERTY ${prop} $<TARGET_PROPERTY:${source},${prop}>)
-  endforeach()
-endfunction()
-
-if(TARGET HighFive)
-    return()
-endif()
-
-@PACKAGE_INIT@
-
-# Get HighFive targets
-include("${CMAKE_CURRENT_LIST_DIR}/HighFiveTargets.cmake")
-
-# Recreate combined HighFive
-add_library(HighFive INTERFACE IMPORTED)
-set_property(TARGET HighFive APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS MPI_NO_CPPBIND)  # No c++ bindings
-
-# Ensure we activate required C++ std
-if(NOT DEFINED CMAKE_CXX_STANDARD)
-  if(CMAKE_VERSION VERSION_LESS 3.8)
-    message(WARNING "HighFive requires minimum C++11. (C++14 for XTensor) \
-        You may need to set CMAKE_CXX_STANDARD in you project")
-  else()
-    # A client request for a higher std overrides this
-    target_compile_features(HighFive INTERFACE cxx_std_11)
-  endif()
-endif()
-
-# If the user sets this flag, all dependencies are preserved.
-# Useful in central deployments where dependencies are not prepared later
-set(HIGHFIVE_USE_INSTALL_DEPS @HIGHFIVE_USE_INSTALL_DEPS@ CACHE BOOL "Use original Highfive dependencies")
-if(HIGHFIVE_USE_INSTALL_DEPS)
-  # If enabled in the deploy config, request c++14
-  if(@HIGHFIVE_USE_XTENSOR@ AND NOT CMAKE_VERSION VERSION_LESS 3.8)
-    set_property(TARGET HighFive APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_std_14)
-  endif()
-  message(STATUS "HIGHFIVE @PROJECT_VERSION@: Using original dependencies (HIGHFIVE_USE_INSTALL_DEPS=YES)")
-  copy_interface_properties(HighFive HighFive_HighFive)
-  return()
-endif()
-
-# When not using the pre-built dependencies, give user options
-if(DEFINED HIGHFIVE_USE_BOOST)
-  set(HIGHFIVE_USE_BOOST ${HIGHFIVE_USE_BOOST} CACHE BOOL "Enable Boost Support")
-else()
-  set(HIGHFIVE_USE_BOOST @HIGHFIVE_USE_BOOST@ CACHE BOOL "Enable Boost Support")
-endif()
-set(HIGHFIVE_USE_EIGEN "${HIGHFIVE_USE_EIGEN}" CACHE BOOL "Enable Eigen testing")
-set(HIGHFIVE_USE_XTENSOR "${HIGHFIVE_USE_XTENSOR}" CACHE BOOL "Enable xtensor testing")
-set(HIGHFIVE_PARALLEL_HDF5 @HIGHFIVE_PARALLEL_HDF5@ CACHE BOOL "Enable Parallel HDF5 support")
-option(HIGHFIVE_VERBOSE "Enable verbose logging" @HIGHFIVE_VERBOSE@)
-
-if(HIGHFIVE_USE_XTENSOR AND NOT CMAKE_VERSION VERSION_LESS 3.8)
-  set_property(TARGET HighFive APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_std_14)
-endif()
-
-if(NOT HighFive_FIND_QUIETLY)
-  message(STATUS "HIGHFIVE @PROJECT_VERSION@: (Re)Detecting Highfive dependencies (HIGHFIVE_USE_INSTALL_DEPS=NO)")
-endif()
-include("${CMAKE_CURRENT_LIST_DIR}/HighFiveTargetDeps.cmake")
-foreach(dependency HighFive_libheaders libdeps)
-    copy_interface_properties(HighFive ${dependency})
-endforeach()
-
-check_required_components(HighFive)
diff --git a/packages/HighFive/CMake/HighFiveTargetDeps.cmake b/packages/HighFive/CMake/HighFiveTargetDeps.cmake
deleted file mode 100644
index 919b53544ea198e040d86d5620bb05f05ab5a7f2..0000000000000000000000000000000000000000
--- a/packages/HighFive/CMake/HighFiveTargetDeps.cmake
+++ /dev/null
@@ -1,122 +0,0 @@
-# Link against target system libs
-# -------------------------------
-
-if(NOT TARGET libdeps)
-
-  # Independent target to make it possible to have new dependencies each build
-  add_library(libdeps INTERFACE)
-
-  if(HIGHFIVE_VERBOSE)
-    target_compile_definitions(libdeps INTERFACE -DHIGHFIVE_LOG_LEVEL=0)
-  endif()
-
-  if(HIGHFIVE_GLIBCXX_ASSERTIONS)
-    target_compile_definitions(libdeps INTERFACE -D_GLIBCXX_ASSERTIONS)
-  endif()
-
-  if(HIGHFIVE_HAS_FRIEND_DECLARATIONS)
-    target_compile_definitions(libdeps INTERFACE -DHIGHFIVE_HAS_FRIEND_DECLARATIONS=1)
-  endif()
-
-  if(HIGHFIVE_SANITIZER)
-    target_compile_options(libdeps INTERFACE -fsanitize=${HIGHFIVE_SANITIZER})
-    target_link_options(libdeps INTERFACE -fsanitize=${HIGHFIVE_SANITIZER})
-  endif()
-
-  # HDF5
-  if(NOT DEFINED HDF5_C_LIBRARIES)
-    set(HDF5_PREFER_PARALLEL ${HIGHFIVE_PARALLEL_HDF5})
-    set(HDF5_USE_STATIC_LIBRARIES ${HIGHFIVE_STATIC_HDF5})
-    find_package(HDF5 REQUIRED)
-  endif()
-
-  if(HIGHFIVE_PARALLEL_HDF5 AND NOT HDF5_IS_PARALLEL)
-    message(WARNING "Parallel HDF5 requested but libhdf5 doesnt support it")
-  endif()
-
-  target_include_directories(libdeps SYSTEM INTERFACE ${HDF5_INCLUDE_DIRS})
-  target_link_libraries(libdeps INTERFACE ${HDF5_LIBRARIES})
-  target_compile_definitions(libdeps INTERFACE ${HDF5_DEFINITIONS})
-  target_compile_definitions(libdeps INTERFACE HIGHFIVE_HAS_CONCEPTS=$<BOOL:${HIGHFIVE_HAS_CONCEPTS}>)
-
-
-  # Boost
-  if(HIGHFIVE_USE_BOOST)
-    if(NOT DEFINED Boost_NO_BOOST_CMAKE)
-      # HighFive deactivated finding Boost via Boost's own CMake files
-      # in Oct 2016 (commit '25627b085'). Likely to appease one cluster.
-      # Boost's CMake support has since improved and likely this setting
-      # isn't needed anymore. It is kept for backwards compatibility.
-      # However, a rework of HighFive's CMake code should consider removing
-      # this default. Hard coding this to true has been reported to cause
-      # build failures.
-      set(Boost_NO_BOOST_CMAKE TRUE)
-    endif()
-    find_package(Boost REQUIRED COMPONENTS system serialization)
-    # Dont use imported targets yet, not avail before cmake 3.5
-    target_include_directories(libdeps SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
-    target_compile_definitions(libdeps INTERFACE BOOST_ALL_NO_LIB H5_USE_BOOST)
-  endif()
-
-  # Half
-  if(HIGHFIVE_USE_HALF_FLOAT)
-    find_file(FOUND_HALF half.hpp)
-    if (NOT FOUND_HALF)
-      message(FATAL_ERROR "Half-precision floating-point support requested but file half.hpp not found")
-    endif()
-    target_compile_definitions(libdeps INTERFACE H5_USE_HALF_FLOAT)
-  endif()
-
-  # Eigen
-  if(HIGHFIVE_USE_EIGEN)
-    if (NOT EIGEN3_INCLUDE_DIRS)
-      find_package(Eigen3 NO_MODULE)
-      if(Eigen3_FOUND)
-        message(STATUS "Found Eigen ${Eigen3_VERSION}: ${EIGEN3_INCLUDE_DIRS}")
-      else()
-        find_package(PkgConfig)
-        pkg_check_modules(EIGEN3 REQUIRED eigen3)
-      endif()
-    endif()
-    if (NOT EIGEN3_INCLUDE_DIRS)
-      message(FATAL_ERROR "Eigen was requested but could not be found")
-    endif()
-    target_include_directories(libdeps SYSTEM INTERFACE ${EIGEN3_INCLUDE_DIRS})
-    target_compile_definitions(libdeps INTERFACE H5_USE_EIGEN)
-  endif()
-
-  # xtensor
-  if(HIGHFIVE_USE_XTENSOR)
-    if (NOT xtensor_INCLUDE_DIRS)
-      find_package(xtensor REQUIRED)
-    endif()
-    if (NOT xtl_INCLUDE_DIRS)
-      find_package(xtl REQUIRED)
-    endif()
-    target_include_directories(libdeps SYSTEM INTERFACE ${xtensor_INCLUDE_DIRS} ${xtl_INCLUDE_DIRS})
-    target_compile_definitions(libdeps INTERFACE H5_USE_XTENSOR)
-  endif()
-
-  # OpenCV
-  if(HIGHFIVE_USE_OPENCV)
-    if (NOT OpenCV_INCLUDE_DIRS)
-      find_package(OpenCV REQUIRED)
-    endif()
-    target_include_directories(libdeps SYSTEM INTERFACE ${OpenCV_INCLUDE_DIRS})
-    target_link_libraries(libdeps INTERFACE ${OpenCV_LIBS})
-    target_compile_definitions(libdeps INTERFACE H5_USE_OPENCV)
-  endif()
-
-  # MPI
-  if(HIGHFIVE_PARALLEL_HDF5 OR HDF5_IS_PARALLEL)
-    find_package(MPI REQUIRED)
-    target_include_directories(libdeps SYSTEM INTERFACE ${MPI_CXX_INCLUDE_PATH})
-    target_link_libraries(libdeps INTERFACE ${MPI_CXX_LIBRARIES})
-    if(CMAKE_VERSION VERSION_LESS 3.13)
-      target_link_libraries(libdeps INTERFACE ${MPI_CXX_LINK_FLAGS})
-    else()
-      target_link_options(libdeps INTERFACE "SHELL:${MPI_CXX_LINK_FLAGS}")
-    endif()
-  endif()
-
-endif()
diff --git a/packages/HighFive/CMake/HighFiveTargetExport.cmake b/packages/HighFive/CMake/HighFiveTargetExport.cmake
deleted file mode 100644
index 9906f39513c385e9dba2dca5194453fe9b2eec16..0000000000000000000000000000000000000000
--- a/packages/HighFive/CMake/HighFiveTargetExport.cmake
+++ /dev/null
@@ -1,48 +0,0 @@
-
-# Define the HighFive INTERFACE library
-add_library(libheaders INTERFACE)
-
-target_include_directories(libheaders INTERFACE
-  "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
-  "$<INSTALL_INTERFACE:include>")
-
-# Combined HighFive
-add_library(HighFive INTERFACE)
-target_compile_definitions(HighFive INTERFACE MPI_NO_CPPBIND)  # No c++ bindings
-target_link_libraries(HighFive INTERFACE libheaders libdeps)
-
-
-# Generate ${PROJECT_NAME}Config.cmake
-
-include(CMakePackageConfigHelpers)
-configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/HighFiveConfig.cmake.in
-  ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
-  INSTALL_DESTINATION share/${PROJECT_NAME}/CMake)
-
-write_basic_package_version_file(
-    ${PROJECT_NAME}ConfigVersion.cmake
-    VERSION ${PROJECT_VERSION}
-    COMPATIBILITY AnyNewerVersion)
-
-install(FILES
-    CMake/HighFiveTargetDeps.cmake
-    ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake
-    ${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake
-  DESTINATION share/${PROJECT_NAME}/CMake)
-
-
-# Provides IMPORTED targets when using this project from build/install trees.
-
-# Specify targets to include in the HighFive Exports
-install(TARGETS HighFive libheaders libdeps
-        EXPORT HighFiveTargets)
-
-# Generate & install the Export for the INSTALL_INTERFACE
-install(EXPORT HighFiveTargets
-        NAMESPACE HighFive_
-        FILE HighFiveTargets.cmake
-        DESTINATION share/${PROJECT_NAME}/CMake)
-
-# Generate the Export for the BUILD_INTERACE (hardly used)
-export(EXPORT HighFiveTargets
-       FILE "${PROJECT_BINARY_DIR}/HighFiveTargets.cmake")
diff --git a/packages/HighFive/CMake/config/TestHelpers.cmake b/packages/HighFive/CMake/config/TestHelpers.cmake
deleted file mode 100644
index f3ca1cb74108a6a348f1c9739e83042c56096787..0000000000000000000000000000000000000000
--- a/packages/HighFive/CMake/config/TestHelpers.cmake
+++ /dev/null
@@ -1,113 +0,0 @@
-# TestHelpers.cmake
-# 
-# set of Convenience functions for unit testing with cmake
-#
-# License: BSD 3
-#
-# Copyright (c) 2016, Adrien Devresse
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
-
-
-
-##
-# enable or disable detection of SLURM and MPIEXEC
-option(AUTO_TEST_WITH_SLURM "automatically add srun as test prefix in a SLURM environment" TRUE)
-option(AUTO_TEST_WITH_MPIEXEC "automatically add mpiexec as test prefix in a MPICH2/OpenMPI environment" TRUE)
-
-###
-##
-## Basic SLURM support
-## the prefix "srun" is added to any test in the environment
-## For a slurm test execution, simply run "salloc [your_exec_parameters] ctest"
-##    
-##
-if(AUTO_TEST_WITH_SLURM)
-    if(NOT DEFINED SLURM_SRUN_COMMAND)
-        find_program(SLURM_SRUN_COMMAND
-                       NAMES "srun"
-                       HINTS "${SLURM_ROOT}/bin")
-    endif()
-    
-    if(SLURM_SRUN_COMMAND)
-        set(TEST_EXEC_PREFIX_DEFAULT "${SLURM_SRUN_COMMAND}")
-        set(TEST_MPI_EXEC_PREFIX_DEFAULT "${SLURM_SRUN_COMMAND}")
-        set(TEST_MPI_EXEC_BIN_DEFAULT "${SLURM_SRUN_COMMAND}")
-	set(TEST_WITH_SLURM ON)
-        message(STATUS " - AUTO_TEST_WITH_SLURM with slurm cmd ${TEST_EXEC_PREFIX_DEFAULT} ")
-        message(STATUS "  -- set test execution prefix to ${TEST_EXEC_PREFIX_DEFAULT} ")
-        message(STATUS "  -- set MPI test execution prefix to ${TEST_MPI_EXEC_PREFIX_DEFAULT} ")
-    endif()
-
-endif()
-
-###
-## Basic MPIExec support, will just forward mpiexec as prefix
-## 
-if(AUTO_TEST_WITH_MPIEXEC AND NOT TEST_WITH_SLURM)
-
-   if(NOT DEFINED MPIEXEC)
-        find_program(MPIEXEC
-                     NAMES "mpiexec"
-                     HINTS "${MPI_ROOT}/bin")
-   endif()
-
-
-   if(MPIEXEC)
-        set(TEST_MPI_EXEC_PREFIX_DEFAULT "${MPIEXEC}")
-        set(TEST_MPI_EXEC_BIN_DEFAULT "${MPIEXEC}")
-	set(TEST_WITH_MPIEXEC ON)
-        message(STATUS " - AUTO_TEST_WITH_MPIEXEC cmd ${MPIEXEC} ")
-        message(STATUS "  -- set MPI test execution prefix to ${TEST_MPI_EXEC_PREFIX_DEFAULT} ")
-
-   endif()
-
-endif()
-
-
-
-###
-##  MPI executor program path without arguments used for testing.
-##  default: srun or mpiexec if found
-##
-set(TEST_MPI_EXEC_BIN "${TEST_MPI_EXEC_BIN_DEFAULT}" CACHE STRING "path of the MPI executor (mpiexec, mpirun) for test execution")
-
-
-
-###
-## Test execution prefix. Override this variable for any execution prefix required in clustered environment
-## 
-## To specify manually a command with argument, e.g -DTEST_EXEC_PREFIX="/var/empty/bin/srun;-n;-4" for a srun execution
-## with 4 nodes
-##
-## default: srun if found
-##
-set(TEST_EXEC_PREFIX "${TEST_EXEC_PREFIX_DEFAULT}" CACHE STRING "prefix command for the test executions")
-
-
-
-###
-## Test execution prefix specific for MPI programs.
-## 
-## To specify manually a command with argument, use the cmake list syntax. e.g -DTEST_EXEC_PREFIX="/var/empty/bin/mpiexec;-n;-4" for an MPI execution
-## with 4 nodes
-##
-## default: srun or mpiexec if found
-##
-set(TEST_MPI_EXEC_PREFIX "${TEST_MPI_EXEC_PREFIX_DEFAULT}" CACHE STRING "prefix command for the MPI test executions")
-
-
-
-
-
-
-
diff --git a/packages/HighFive/CMakeLists.txt b/packages/HighFive/CMakeLists.txt
index 694960090842b6a8c27676a336fe4a40e1266b39..2358e417203756c2e13358d4ba5322f89009f1a5 100644
--- a/packages/HighFive/CMakeLists.txt
+++ b/packages/HighFive/CMakeLists.txt
@@ -1,43 +1,11 @@
-cmake_minimum_required(VERSION 3.1)
-if(${CMAKE_VERSION} VERSION_LESS 3.13)
-  cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
-else()
-  cmake_policy(VERSION 3.13)
-endif()
+cmake_minimum_required(VERSION 3.13)
+cmake_policy(VERSION 3.13)
 
-project(HighFive VERSION 2.9.0)
+project(HighFive VERSION 3.0.0)
 
-configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/highfive/H5Version.hpp.in
-               ${CMAKE_CURRENT_SOURCE_DIR}/include/highfive/H5Version.hpp)
-# INCLUDES
-list(APPEND CMAKE_MODULE_PATH
-  ${CMAKE_CURRENT_SOURCE_DIR}/CMake
-  ${CMAKE_CURRENT_SOURCE_DIR}/CMake/config)
-
-# OPTIONS
-# Compatibility within Highfive 2.x series
-set(USE_BOOST ON CACHE BOOL "Enable Boost Support")
-set(USE_EIGEN OFF CACHE BOOL "Enable Eigen testing")
-set(USE_XTENSOR OFF CACHE BOOL "Enable xtensor testing")
-set(USE_OPENCV OFF CACHE BOOL "Enable OpenCV testing")
-mark_as_advanced(USE_BOOST USE_EIGEN USE_XTENSOR)
-
-set(HIGHFIVE_UNIT_TESTS AUTO CACHE STRING "Enable unit tests (requires Catch2 to be present)")
-set_property(CACHE HIGHFIVE_UNIT_TESTS PROPERTY STRINGS AUTO ON OFF)
-
-option(HIGHFIVE_USE_BOOST "Enable Boost Support" ${USE_BOOST})
-option(HIGHFIVE_USE_HALF_FLOAT "Enable half-precision floats" ${USE_HALF_FLOAT})
-option(HIGHFIVE_USE_EIGEN "Enable Eigen testing" ${USE_EIGEN})
-option(HIGHFIVE_USE_OPENCV "Enable OpenCV testing" ${USE_OPENCV})
-option(HIGHFIVE_USE_XTENSOR "Enable xtensor testing" ${USE_XTENSOR})
-option(HIGHFIVE_EXAMPLES "Compile examples" ON)
-option(HIGHFIVE_PARALLEL_HDF5 "Enable Parallel HDF5 support" OFF)
-option(HIGHFIVE_STATIC_HDF5 "Staticly link to HDF5 library" OFF)
-option(HIGHFIVE_BUILD_DOCS "Enable documentation building" ON)
+# Configure HighFive
+# ------------------
 option(HIGHFIVE_VERBOSE "Set logging level to verbose." OFF)
-option(HIGHFIVE_GLIBCXX_ASSERTIONS "Enable bounds check for STL." OFF)
-option(HIGHFIVE_HAS_CONCEPTS "Print readable compiler errors w/ C++20 concepts" ON)
-option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF)
 
 # Controls if HighFive classes are friends of each other.
 #
@@ -66,69 +34,128 @@ option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF)
 option(HIGHFIVE_HAS_FRIEND_DECLARATIONS "Enable additional friend declarations. Certain compiler require this On, others Off." OFF)
 mark_as_advanced(HIGHFIVE_HAS_FRIEND_DECLARATIONS)
 
-set(HIGHFIVE_SANITIZER OFF CACHE STRING "Enable a group of sanitizers, requires compiler support. Supported: 'address' and 'undefined'.")
-mark_as_advanced(HIGHFIVE_SANITIZER)
+option(HIGHFIVE_FIND_HDF5 "Find and link with HDF5." On)
+
+# Configure Tests & Examples
+# --------------------------
+
+# Internal variable that controls the default value for building
+# optional things like tests, examples and docs.
+if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
+  set(HIGHFIVE_EXTRAS_DEFAULT ON)
+else()
+  set(HIGHFIVE_EXTRAS_DEFAULT OFF)
+endif()
+
+if (CMAKE_CXX_STANDARD GREATER_EQUAL 20)
+  include(CheckIncludeFileCXX)
+  CHECK_INCLUDE_FILE_CXX(span HIGHFIVE_TEST_SPAN_DEFAULT)
+else()
+  set(HIGHFIVE_TEST_SPAN_DEFAULT Off)
+endif()
 
-# In deployments we probably don't want/cant have dynamic dependencies
-option(HIGHFIVE_USE_INSTALL_DEPS "End applications by default use detected dependencies here" OFF)
-mark_as_advanced(HIGHFIVE_USE_INSTALL_DEPS)
+option(HIGHFIVE_UNIT_TESTS "Compile unit-tests" ${HIGHFIVE_EXTRAS_DEFAULT})
+option(HIGHFIVE_EXAMPLES "Compile examples" ${HIGHFIVE_EXTRAS_DEFAULT})
+option(HIGHFIVE_BUILD_DOCS "Build documentation" ${HIGHFIVE_EXTRAS_DEFAULT})
 
+option(HIGHFIVE_TEST_SPAN "Enable std::span testing, requires C++20" ${HIGHFIVE_TEST_SPAN_DEFAULT})
+option(HIGHFIVE_TEST_BOOST "Enable Boost testing" OFF)
+option(HIGHFIVE_TEST_EIGEN "Enable Eigen testing" OFF)
+option(HIGHFIVE_TEST_OPENCV "Enable OpenCV testing" OFF)
+option(HIGHFIVE_TEST_XTENSOR "Enable xtensor testing" OFF)
+option(HIGHFIVE_TEST_HALF_FLOAT "Enable half-precision floats" OFF)
+
+# TODO remove entirely.
+option(HIGHFIVE_HAS_CONCEPTS "Print readable compiler errors w/ C++20 concepts" OFF)
+
+set(HIGHFIVE_MAX_ERRORS 0 CACHE STRING "Maximum number of compiler errors.")
+option(HIGHFIVE_HAS_WERROR "Convert warnings to errors." OFF)
+option(HIGHFIVE_GLIBCXX_ASSERTIONS "Enable bounds check for STL." OFF)
+# TODO these some magic to get a drop down menu in ccmake
+set(HIGHFIVE_SANITIZER OFF CACHE STRING "Enable a group of sanitizers, requires compiler support. Supported: 'address' and 'undefined'.")
+mark_as_advanced(HIGHFIVE_SANITIZER)
 
 # Check compiler cxx_std requirements
 # -----------------------------------
-
-if(CMAKE_CXX_STANDARD EQUAL 98)
-    message(FATAL_ERROR "HighFive needs to be compiled with at least C++11")
-endif()
+set(HIGHFIVE_CXX_STANDARD_DEFAULT 14)
 
 if(NOT DEFINED CMAKE_CXX_STANDARD)
-    set(CMAKE_CXX_STANDARD 11)
+    set(CMAKE_CXX_STANDARD ${HIGHFIVE_CXX_STANDARD_DEFAULT})
     set(CMAKE_CXX_STANDARD_REQUIRED ON)
     set(CMAKE_CXX_EXTENSIONS OFF)
 endif()
 
-if(HIGHFIVE_USE_XTENSOR AND CMAKE_CXX_STANDARD LESS 14)
-    set(CMAKE_CXX_STANDARD 14)
-    set(CMAKE_CXX_STANDARD_REQUIRED ON)
+if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS ${HIGHFIVE_CXX_STANDARD_DEFAULT})
+  message(FATAL_ERROR "HighFive needs to be compiled with at least C++${HIGHFIVE_CXX_STANDARD_DEFAULT}")
 endif()
 
 add_compile_definitions(HIGHFIVE_CXX_STD=${CMAKE_CXX_STANDARD})
 
-# Search dependencies (hdf5, boost, eigen, xtensor, mpi) and build target highfive_deps
-include(${PROJECT_SOURCE_DIR}/CMake/HighFiveTargetDeps.cmake)
+# HighFive
+# --------
+add_library(HighFiveInclude INTERFACE)
+add_library(HighFive::Include ALIAS HighFiveInclude)
+set_target_properties(HighFiveInclude PROPERTIES EXPORT_NAME Include)
+
+target_include_directories(HighFiveInclude INTERFACE
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+  $<INSTALL_INTERFACE:include>
+)
 
-# Set-up HighFive to be used in 3rd party project using exports. Create a HighFive target
-include(${PROJECT_SOURCE_DIR}/CMake/HighFiveTargetExport.cmake)
+add_library(HighFive INTERFACE)
+add_library(HighFive::HighFive ALIAS HighFive)
+
+target_link_libraries(HighFive INTERFACE HighFive::Include)
+
+if(HIGHFIVE_FIND_HDF5)
+  find_package(HDF5 REQUIRED)
+  target_link_libraries(HighFive INTERFACE HDF5::HDF5)
+endif()
+
+if(HDF5_IS_PARALLEL)
+  find_package(MPI REQUIRED)
+  target_link_libraries(HighFive
+    INTERFACE
+    $<BUILD_INTERFACE:MPI::MPI_C>
+    $<BUILD_INTERFACE:MPI::MPI_CXX>
+  )
+endif()
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/highfive/H5Version.hpp.in
+               ${CMAKE_CURRENT_SOURCE_DIR}/include/highfive/H5Version.hpp)
+
+# Install
+# -------
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+  ${CMAKE_CURRENT_BINARY_DIR}/cmake/HighFiveConfigVersion.cmake
+  VERSION ${PACKAGE_VERSION}
+  COMPATIBILITY AnyNewerVersion
+)
 
-# Installation of headers (HighFive is only interface)
 install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
   DESTINATION "include"
   PATTERN "*.in" EXCLUDE)
 
+install(TARGETS HighFive HighFiveInclude EXPORT HighFiveTargets)
+install(EXPORT HighFiveTargets
+  FILE HighFiveTargets.cmake
+  NAMESPACE HighFive::
+  DESTINATION lib/cmake/HighFive
+)
+
+install(FILES
+  ${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveConfig.cmake
+  ${CMAKE_CURRENT_BINARY_DIR}/cmake/HighFiveConfigVersion.cmake
+  DESTINATION lib/cmake/HighFive
+)
+
 # Preparing local building (tests, examples)
 # ------------------------------------------
 
-# Disable test if Boost was expressly disabled, or if HighFive is a sub-project
-if (NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
-  if(HIGHFIVE_UNIT_TESTS AND NOT HighFive_FIND_QUIETLY)
-    message(WARNING "Unit tests have been DISABLED.")
-  endif()
-  set(HIGHFIVE_UNIT_TESTS FALSE)
-endif()
-
-if(HIGHFIVE_UNIT_TESTS)
-  if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/deps/catch2/CMakeLists.txt)
-    add_subdirectory(deps/catch2 EXCLUDE_FROM_ALL)
-    list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/deps/catch2/contrib)
-  else()
-    find_package(Catch2)
-    if(NOT HIGHFIVE_UNIT_TESTS STREQUAL "AUTO" AND HIGHFIVE_UNIT_TESTS AND NOT Catch2_FOUND)
-      message(FATAL_ERROR "Please provide a Catch2 installation or clone the submodule")
-    elseif(NOT Catch2_FOUND)
-      message(WARNING "No Catch2 installation was found; Disabling unit tests.")
-      set(HIGHFIVE_UNIT_TESTS OFF)
-    endif()
-  endif()
+if(HIGHFIVE_EXAMPLES OR HIGHFIVE_UNIT_TESTS)
+  include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveWarnings.cmake)
+  include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HighFiveOptionalDependencies.cmake)
 endif()
 
 if(HIGHFIVE_EXAMPLES)
@@ -136,6 +163,8 @@ if(HIGHFIVE_EXAMPLES)
 endif()
 
 if(HIGHFIVE_UNIT_TESTS)
+  add_subdirectory(deps/catch2 EXCLUDE_FROM_ALL)
+  list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/deps/catch2/contrib)
   enable_testing()
   add_subdirectory(tests/unit)
 endif()
diff --git a/packages/HighFive/README.md b/packages/HighFive/README.md
index bc0d2752ed71792b5bfa95116efc7822d3812d66..b8d71b357db079de7316c3565efd1bdd41e1bd82 100644
--- a/packages/HighFive/README.md
+++ b/packages/HighFive/README.md
@@ -1,14 +1,20 @@
+*Note:* In preparation of `v3` of HighFive, we've started merging breaking
+changes into the main branch. More information and opportunity to comment can
+be found at:
+https://github.com/BlueBrain/HighFive/issues/864
+
 # HighFive - HDF5 header-only C++ Library
 
 [![Doxygen -> gh-pages](https://github.com/BlueBrain/HighFive/workflows/gh-pages/badge.svg?branch=master)](https://BlueBrain.github.io/HighFive/actions/workflows/gh-pages.yml?query=branch%3Amaster)
 [![codecov](https://codecov.io/gh/BlueBrain/HighFive/branch/master/graph/badge.svg?token=UBKxHEn7RS)](https://codecov.io/gh/BlueBrain/HighFive)
 [![HighFive_Integration_tests](https://github.com/BlueBrain/HighFive-testing/actions/workflows/integration.yml/badge.svg)](https://github.com/BlueBrain/HighFive-testing/actions/workflows/integration.yml)
+[![Zenodo](https://zenodo.org/badge/47755262.svg)](https://zenodo.org/doi/10.5281/zenodo.10679422)
 
 Documentation: https://bluebrain.github.io/HighFive/
 
 ## Brief
 
-HighFive is a modern header-only C++11 friendly interface for libhdf5.
+HighFive is a modern header-only C++14 friendly interface for libhdf5.
 
 HighFive supports STL vector/string, Boost::UBLAS, Boost::Multi-array and Xtensor. It handles C++ from/to HDF5 with automatic type mapping.
 HighFive does not require additional libraries (see dependencies).
@@ -19,7 +25,7 @@ It integrates nicely with other CMake projects by defining (and exporting) a Hig
 - Simple C++-ish minimalist interface
 - No other dependency than libhdf5
 - Zero overhead
-- Support C++11
+- Support C++14
 
 ### Feature support
 - create/read/write files, datasets, attributes, groups, dataspaces.
@@ -34,16 +40,15 @@ It integrates nicely with other CMake projects by defining (and exporting) a Hig
 - etc... (see [ChangeLog](./CHANGELOG.md))
 
 ### Dependencies
-- hdf5 (dev)
-- hdf5-mpi (optional, opt-in with -D*HIGHFIVE_PARALLEL_HDF5*=ON)
-- boost >= 1.41 (recommended, opt-out with -D*HIGHFIVE_USE_BOOST*=OFF)
-- eigen3 (optional, opt-in with -D*HIGHFIVE_USE_EIGEN*=ON)
-- xtensor (optional, opt-in with -D*HIGHFIVE_USE_XTENSOR*=ON)
-- half (optional, opt-in with -D*HIGHFIVE_USE_HALF_FLOAT*=ON)
+- HDF5 or pHDF5, including headers
+- boost >= 1.41 (recommended)
+- eigen3 (optional)
+- xtensor (optional)
+- half (optional)
 
 ### Known flaws
 - HighFive is not thread-safe. At best it has the same limitations as the HDF5 library. However, HighFive objects modify their members without protecting these writes. Users have reported that HighFive is not thread-safe even when using the threadsafe HDF5 library, e.g., https://github.com/BlueBrain/HighFive/discussions/675.
-- Eigen support in core HighFive is broken. See https://github.com/BlueBrain/HighFive/issues/532. H5Easy is not
+- Eigen support in core HighFive was broken until v3.0. See https://github.com/BlueBrain/HighFive/issues/532. H5Easy was not
   affected.
 - The support of fixed length strings isn't ideal.
 
@@ -119,11 +124,11 @@ For several 'standard' use cases the [highfive/H5Easy.hpp](include/highfive/H5Ea
     - scalars (to/from an extendible DataSet),
     - strings,
     - vectors (of standard types),
-    - [Eigen::Matrix](http://eigen.tuxfamily.org) (optional, enable CMake option `HIGHFIVE_USE_EIGEN`),
+    - [Eigen::Matrix](http://eigen.tuxfamily.org) (optional),
     - [xt::xarray](https://github.com/QuantStack/xtensor) and [xt::xtensor](https://github.com/QuantStack/xtensor)
-      (optional, enable CMake option `HIGHFIVE_USE_XTENSOR`).
+      (optional).
     - [cv::Mat_](https://docs.opencv.org/master/df/dfc/classcv_1_1Mat__.html)
-      (optional, enable CMake option `HIGHFIVE_USE_OPENCV`).
+      (optional).
 
 * Getting in a single line:
 
@@ -145,16 +150,29 @@ int main() {
 }
 ```
 
-whereby the `int` type of this example can be replaced by any of the above types. See [easy_load_dump.cpp](src/examples/easy_load_dump.cpp) for more details.
+whereby the `int` type of this example can be replaced by any of the above
+types. See [easy_load_dump.cpp](src/examples/easy_load_dump.cpp) for more
+details.
 
-**Note:** Classes such as `H5Easy::File` are just short for the regular `HighFive` classes (in this case `HighFive::File`). They can thus be used interchangeably.
+**Note:** Classes such as `H5Easy::File` are just short for the regular
+`HighFive` classes (in this case `HighFive::File`). They can thus be used
+interchangeably.
 
 
 ## CMake integration
 There's two common paths of integrating HighFive into a CMake based project.
 The first is to "vendor" HighFive, the second is to install HighFive as a
-normal C++ library. Due to how HighFive CMake code works, sometimes following
-the third Bailout Approach is needed.
+normal C++ library. Since HighFive makes choices about how to integrate HDF5,
+sometimes following the third Bailout Approach is needed.
+
+Regular HDF5 CMake variables can be used. Interesting variables include:
+
+* `HDF5_USE_STATIC_LIBRARIES` to link statically against the HDF5 library.
+* `HDF5_PREFER_PARALLEL` to prefer pHDF5.
+* `HDF5_IS_PARALLEL` to check if HDF5 is parallel.
+
+Please consult `tests/cmake_integration` for examples of how to write libraries
+or applications using HighFive.
 
 ### Vendoring HighFive
 
@@ -163,86 +181,63 @@ project (typically as a git submodule), for example in `third_party/HighFive`.
 
 The projects `CMakeLists.txt` add the following lines
 ```cmake
-add_executable(foo foo.cpp)
-
-# You might want to turn off Boost support:
-if(NOT DEFINED HIGHFIVE_USE_BOOST)
-  set(HIGHFIVE_USE_BOOST Off)
-endif()
-
-# Include the subdirectory and use the target HighFive.
 add_subdirectory(third_party/HighFive)
 target_link_libraries(foo HighFive)
 ```
 
 **Note:** `add_subdirectory(third_party/HighFive)` will search and "link" HDF5
-and optional dependencies such as Boost.
+but wont search or link any optional dependencies such as Boost.
 
 ### Regular Installation of HighFive
 
-Alternatively you can install HighFive once and use it in several projects via
-`find_package()`. First one should clone the sources:
-```bash
-git clone --recursive https://github.com/BlueBrain/HighFive.git HighFive-src
-```
-By default CMake will install systemwide, which is likely not appropriate. The
-instruction below allow users to select a custom path where HighFive will be
-installed, e.g. `HIGHFIVE_INSTALL_PREFIX=${HOME}/third_party/HighFive` or some
-other location. The CMake invocations would be
-```bash
-cmake -DHIGHFIVE_EXAMPLES=Off \
-      -DHIGHFIVE_USE_BOOST=Off \
-      -DHIGHFIVE_UNIT_TESTS=Off \
-      -DCMAKE_INSTALL_PREFIX=${HIGHFIVE_INSTALL_PREFIX} \
-      -B HighFive-src/build \
-      HighFive-src
-
-cmake --build HighFive-src/build
-cmake --install HighFive-src/build
-```
-This will install (i.e. copy) the headers to
-`${HIGHFIVE_INSTALL_PREFIX}/include` and some CMake files into an appropriate
-subfolder of `${HIGHFIVE_INSTALL_PREFIX}`.
+Alternatively, HighFive can be install and "found" like regular software.
 
-The projects `CMakeLists.txt` should add the following:
+The project's `CMakeLists.txt` should add the following:
 ```cmake
-# ...
-add_executable(foo foo.cpp)
-
 find_package(HighFive REQUIRED)
 target_link_libraries(foo HighFive)
 ```
 
-**Note:** If HighFive hasn't been installed in a default location, CMake needs
-to be told where to find it which can be done by adding
-`-DCMAKE_PREFIX_PATH=${HIGHFIVE_INSTALL_PREFIX}` to the CMake command for
-building the project using HighFive. The variable `CMAKE_PREFIX_PATH` is a
-semi-colon `;` separated list of directories.
+**Note:** `find_package(HighFive)` will search for HDF5. "Linking" to
+`HighFive` includes linking with HDF5. The two commands will not search for or
+"link" to optional dependencies such as Boost.
 
-**Note:** `find_package(HighFive)` will search and "link" HDF5 and optional
-dependencies such as Boost.
+### Bailout Approach
 
-### The Bailout Approach
-Since both `add_subdirectory` and `find_package` will trigger finding HDF5 and
-other optional dependencies of HighFive as well as the `target_link_libraries`
-code for "linking" with the dependencies, things can go wrong.
+To prevent HighFive from searching or "linking" to HDF5 the project's
+`CMakeLists.txt` should contain the following:
 
-Fortunately, HighFive is a header only library and all that's needed is the
-headers. Preferably, the version obtained by installing HighFive, since those
-include `H5Version.hpp`. Let's assume they've been copied to
-`third_party/HighFive`. Then one could create a target:
+```cmake
+# Prevent HighFive CMake code from searching for HDF5:
+set(HIGHFIVE_FIND_HDF5 Off)
 
-```bash
-add_library(HighFive INTERFACE)
-target_include_directory(HighFive INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/HighFive/include)
+# Then "find" HighFive as usual:
+find_package(HighFive REQUIRED)
+# alternatively, when vendoring:
+# add_subdirectory(third_party/HighFive)
 
+# Finally, use the target `HighFive::Include` which
+# doesn't add a dependency on HDF5.
+target_link_libraries(foo HighFive::Include)
 
-add_executable(foo foo.cpp)
-target_link_libraries(foo HighFive)
+# Proceed to find and link HDF5 as required.
 ```
 
-One known case where this is required is when vendoring the optional
-dependencies of HighFive.
+### Optional Dependencies
+
+HighFive does not attempt to find or "link" to any optional dependencies, such
+as Boost, Eigen, etc. Any project using HighFive with any of the optional
+dependencies must include the respective header:
+```
+#include <highfive/boost.hpp>
+#include <highfive/eigen.hpp>
+```
+and add the required CMake code to find and link against the dependencies. For
+Boost the required lines might be
+```
+find_package(Boost REQUIRED)
+target_link_libraries(foo PUBLIC Boost::headers)
+```
 
 # Questions?
 
@@ -255,6 +250,10 @@ For bugs and issues please use [Issues](https://github.com/BlueBrain/HighFive/is
 # Funding & Acknowledgment
  
 The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government's ETH Board of the Swiss Federal Institutes of Technology.
+
+HighFive releases are uploaded to Zenodo. If you wish to cite HighFive in a
+scientific publication you can use the DOIs for the
+[Zenodo records](https://zenodo.org/doi/10.5281/zenodo.10679422).
  
 Copyright © 2015-2022 Blue Brain Project/EPFL
 
diff --git a/packages/HighFive/cmake/HighFiveConfig.cmake b/packages/HighFive/cmake/HighFiveConfig.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..33ce7b7b0ed225805954e8efedf3e2de0841778f
--- /dev/null
+++ b/packages/HighFive/cmake/HighFiveConfig.cmake
@@ -0,0 +1,19 @@
+include(CMakeFindDependencyMacro)
+
+if(NOT DEFINED HIGHFIVE_FIND_HDF5)
+  set(HIGHFIVE_FIND_HDF5 On)
+endif()
+
+if(HIGHFIVE_FIND_HDF5)
+  find_dependency(HDF5)
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/HighFiveTargets.cmake")
+
+if(HDF5_IS_PARALLEL)
+  find_dependency(MPI)
+  target_link_libraries(HighFive::HighFive INTERFACE MPI::MPI_C MPI::MPI_CXX)
+endif()
+
+add_library(HighFive ALIAS HighFive::HighFive)
+add_library(HighFiveInclude ALIAS HighFive::Include)
diff --git a/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake b/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..861b80641fa3deb9d6849ca19c9b7369ee45fa3c
--- /dev/null
+++ b/packages/HighFive/cmake/HighFiveOptionalDependencies.cmake
@@ -0,0 +1,56 @@
+if(NOT TARGET HighFiveBoostDependency)
+  add_library(HighFiveBoostDependency INTERFACE)
+  if(HIGHFIVE_TEST_BOOST)
+    find_package(Boost REQUIRED)
+    target_link_libraries(HighFiveBoostDependency INTERFACE Boost::headers)
+    # TODO check if we need Boost::disable_autolinking to cause:
+    # -DBOOST_ALL_NO_LIB (does something on MSVC).
+    target_compile_definitions(HighFiveBoostDependency INTERFACE HIGHFIVE_TEST_BOOST=1)
+  endif()
+endif()
+
+if(NOT TARGET HighFiveEigenDependency)
+  add_library(HighFiveEigenDependency INTERFACE)
+  if(HIGHFIVE_TEST_EIGEN)
+    find_package(Eigen3 REQUIRED NO_MODULE)
+    target_link_libraries(HighFiveEigenDependency INTERFACE Eigen3::Eigen)
+    target_compile_definitions(HighFiveEigenDependency INTERFACE HIGHFIVE_TEST_EIGEN=1)
+  endif()
+endif()
+
+if(NOT TARGET HighFiveXTensorDependency)
+  add_library(HighFiveXTensorDependency INTERFACE)
+  if(HIGHFIVE_TEST_XTENSOR)
+    find_package(xtensor REQUIRED)
+    target_link_libraries(HighFiveXTensorDependency INTERFACE xtensor)
+    target_compile_definitions(HighFiveXTensorDependency INTERFACE HIGHFIVE_TEST_XTENSOR=1)
+  endif()
+endif()
+
+if(NOT TARGET HighFiveOpenCVDependency)
+  add_library(HighFiveOpenCVDependency INTERFACE)
+  if(HIGHFIVE_TEST_OPENCV)
+    find_package(OpenCV REQUIRED)
+    target_include_directories(HighFiveOpenCVDependency SYSTEM INTERFACE ${OpenCV_INCLUDE_DIRS})
+    target_link_libraries(HighFiveOpenCVDependency INTERFACE ${OpenCV_LIBS})
+    target_compile_definitions(HighFiveOpenCVDependency INTERFACE HIGHFIVE_TEST_OPENCV=1)
+  endif()
+endif()
+
+if(NOT TARGET HighFiveSpanDependency)
+  add_library(HighFiveSpanDependency INTERFACE)
+  if(HIGHFIVE_TEST_SPAN)
+    target_compile_definitions(HighFiveSpanDependency INTERFACE HIGHFIVE_TEST_SPAN=1)
+  endif()
+endif()
+
+if(NOT TARGET HighFiveOptionalDependencies)
+  add_library(HighFiveOptionalDependencies INTERFACE)
+  target_link_libraries(HighFiveOptionalDependencies INTERFACE
+    HighFiveBoostDependency
+    HighFiveEigenDependency
+    HighFiveXTensorDependency
+    HighFiveOpenCVDependency
+    HighFiveSpanDependency
+  )
+endif()
diff --git a/packages/HighFive/CMake/HighFiveWarnings.cmake b/packages/HighFive/cmake/HighFiveWarnings.cmake
similarity index 61%
rename from packages/HighFive/CMake/HighFiveWarnings.cmake
rename to packages/HighFive/cmake/HighFiveWarnings.cmake
index 16896b6489367481efdf00142df7e3fee7d0ec18..a1dee19dcd42e37067497a87993ae1c701d4921c 100644
--- a/packages/HighFive/CMake/HighFiveWarnings.cmake
+++ b/packages/HighFive/cmake/HighFiveWarnings.cmake
@@ -6,6 +6,7 @@ if(TARGET HighFiveWarnings)
 endif()
 
 add_library(HighFiveWarnings INTERFACE)
+add_library(HighFiveFlags INTERFACE)
 
 if(CMAKE_CXX_COMPILER_ID MATCHES "Clang"
    OR CMAKE_CXX_COMPILER_ID MATCHES "GNU"
@@ -23,15 +24,20 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang"
             -Wconversion
             -Wsign-conversion
     )
+endif()
 
-    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Intel")
-        target_compile_options(HighFiveWarnings
-            INTERFACE
-                -Wpedantic
-                -Wcast-align
-                -Wdouble-promotion
-        )
-    endif()
+if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+    target_compile_options(HighFiveWarnings
+        INTERFACE
+            -Wpedantic
+            -Wcast-align
+            -Wdouble-promotion
+    )
+
+    target_compile_options(HighFiveWarnings
+        INTERFACE
+            -ftemplate-backtrace-limit=0
+    )
 
     if(HIGHFIVE_HAS_WERROR)
         target_compile_options(HighFiveWarnings
@@ -41,3 +47,12 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang"
         )
     endif()
 endif()
+
+if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
+    if(HIGHFIVE_MAX_ERRORS)
+        target_compile_options(HighFiveFlags
+            INTERFACE
+            -fmax-errors=${HIGHFIVE_MAX_ERRORS}
+        )
+    endif()
+endif()
diff --git a/packages/HighFive/deps/catch2 b/packages/HighFive/deps/catch2
new file mode 160000
index 0000000000000000000000000000000000000000..4e8d92bf02f7d1c8006a0e7a5ecabd8e62d98502
--- /dev/null
+++ b/packages/HighFive/deps/catch2
@@ -0,0 +1 @@
+Subproject commit 4e8d92bf02f7d1c8006a0e7a5ecabd8e62d98502
diff --git a/packages/HighFive/doc/developer_guide.md b/packages/HighFive/doc/developer_guide.md
index fc388f3b5f7449dfa110a74e170b951ffec2dd21..4537bef20bb87a6d78dbaa4d78d3ae2d387b4741 100644
--- a/packages/HighFive/doc/developer_guide.md
+++ b/packages/HighFive/doc/developer_guide.md
@@ -23,8 +23,13 @@ cmake --build build --parallel
 ctest --test-dir build
 ```
 
-You might want to turn off Boost `-DHIGHFIVE_USE_BOOST=Off` or turn on other
-optional dependencies.
+You might want to add:
+* `-DHIGHFIVE_TEST_BOOST=On` or other optional dependencies on,
+* `-DHIGHFIVE_MAX_ERRORS=3` to only show the first three errors.
+
+Generic CMake reminders:
+* `-DCMAKE_INSTALL_PREFIX` defines where HighFive will be installed,
+* `-DCMAKE_PREFIX_PATH` defines where `*Config.cmake` files are found.
 
 ## Contributing
 There's numerous HDF5 features that haven't been wrapped yet. HighFive is a
@@ -90,6 +95,7 @@ release. Once this is done perform a final round of updates:
 * Download the archive (`*.tar.gz`) and compute its SHA256.
 * Update BlueBrain Spack recipe to use the archive and not the Git commit.
 * Update the upstream Spack recipe.
+* A Zenodo record should be generated automatically, check if it's sensible.
 
 ## Writing Tests
 ### Generate Multi-Dimensional Test Data
@@ -159,7 +165,7 @@ void check_write(...) {
 ### Test Organization
 #### Multi-Dimensional Arrays
 All tests for reading/writing whole multi-dimensional arrays to datasets or
-attributes belong in `tests/unit/tests_high_five_multi_dimensional.cpp`. This
+attributes belong in `tests/unit/test_all_types.cpp`. This
 includes write/read cycles; checking all the generic edges cases, e.g. empty
 arrays and mismatching sizes; and checking non-reallocation.
 
@@ -189,6 +195,9 @@ Write-read cycles for scalar values should be implemented in
 Unit-tests related to checking that `DataType` API, go in
 `tests/unit/tests_high_data_type.cpp`.
 
+#### Empty Arrays
+Check related to empty arrays to in `tests/unit/test_empty_arrays.cpp`.
+
 #### Selections
 Anything selection related goes in `tests/unit/test_high_five_selection.cpp`.
 This includes things like `ElementSet` and `HyperSlab`.
@@ -198,7 +207,7 @@ Regular write-read cycles for strings are performed along with the other types,
 see above. This should cover compatibility of `std::string` with all
 containers. However, additional testing is required, e.g. character set,
 padding, fixed vs. variable length. These all go in
-`tests/unit/test_high_five_string.cpp`.
+`tests/unit/test_string.cpp`.
 
 #### Specific Tests For Optional Containers
 If containers, e.g. `Eigen::Matrix` require special checks those go in files
diff --git a/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css b/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css
index ac7f0608ec69a90204ed8a599c159d2c5c8054e4..a2715e268c5532e94abf3d4a2591395932438e22 100644
--- a/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css
+++ b/packages/HighFive/doc/doxygen-awesome-css/doxygen-awesome.css
@@ -313,7 +313,7 @@ body {
 body, table, div, p, dl, #nav-tree .label, .title,
 .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname,
 .SelectItem, #MSearchField, .navpath li.navelem a,
-.navpath li.navelem a:hover, p.reference, p.definition {
+.navpath li.navelem a:hover, p.reference, p.definition, div.toc li, div.toc h3 {
     font-family: var(--font-family);
 }
 
@@ -334,6 +334,7 @@ p.reference, p.definition {
 a:link, a:visited, a:hover, a:focus, a:active {
     color: var(--primary-color) !important;
     font-weight: 500;
+    background: none;
 }
 
 a.anchor {
@@ -806,6 +807,10 @@ html.dark-mode iframe#MSearchResults {
     line-height: var(--tree-item-height);
 }
 
+#nav-tree .item > a:focus {
+    outline: none;
+}
+
 #nav-sync {
     bottom: 12px;
     right: 12px;
@@ -843,6 +848,7 @@ html.dark-mode iframe#MSearchResults {
 
 #nav-tree .arrow {
     opacity: var(--side-nav-arrow-opacity);
+    background: none;
 }
 
 .arrow {
@@ -1040,7 +1046,7 @@ blockquote::after {
 blockquote p {
     margin: var(--spacing-small) 0 var(--spacing-medium) 0;
 }
-.paramname {
+.paramname, .paramname em {
     font-weight: 600;
     color: var(--primary-dark-color);
 }
@@ -1090,7 +1096,7 @@ div.contents .toc {
     border: 0;
     border-left: 1px solid var(--separator-color);
     border-radius: 0;
-    background-color: transparent;
+    background-color: var(--page-background-color);
     box-shadow: none;
     position: sticky;
     top: var(--toc-sticky-top);
@@ -1982,14 +1988,16 @@ hr {
 }
 
 .contents hr {
-    box-shadow: 100px 0 0 var(--separator-color),
-                -100px 0 0 var(--separator-color),
-                500px 0 0 var(--separator-color),
-                -500px 0 0 var(--separator-color),
-                1500px 0 0 var(--separator-color),
-                -1500px 0 0 var(--separator-color),
-                2000px 0 0 var(--separator-color),
-                -2000px 0 0 var(--separator-color);
+    box-shadow: 100px 0 var(--separator-color),
+                -100px 0 var(--separator-color),
+                500px 0 var(--separator-color),
+                -500px 0 var(--separator-color),
+                900px 0 var(--separator-color),
+                -900px 0 var(--separator-color),
+                1400px 0 var(--separator-color),
+                -1400px 0 var(--separator-color),
+                1900px 0 var(--separator-color),
+                -1900px 0 var(--separator-color);       
 }
 
 .contents img, .contents .center, .contents center, .contents div.image object {
@@ -2460,17 +2468,17 @@ h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.
  Optional tab feature
 */
 
-.tabbed ul {
+.tabbed > ul {
     padding-inline-start: 0px;
     margin: 0;
     padding: var(--spacing-small) 0;
 }
 
-.tabbed li {
+.tabbed > ul > li {
     display: none;
 }
 
-.tabbed li.selected {
+.tabbed > ul > li.selected {
     display: block;
 }
 
diff --git a/packages/HighFive/doc/installation.md b/packages/HighFive/doc/installation.md
index e9c5b2e6e1884e9e2ea7cc99a88335b2020b6040..3dfb3dc4b50f4f04f66e1f25322e33cd28066b54 100644
--- a/packages/HighFive/doc/installation.md
+++ b/packages/HighFive/doc/installation.md
@@ -211,7 +211,7 @@ this you type
 
 Okay, on to configure, compile and install. The CMake commands are
 
-    cmake -DCMAKE_INSTALL_PREFIX=build/install -DHIGHFIVE_USE_BOOST=Off -B build .
+    cmake -DCMAKE_INSTALL_PREFIX=build/install -B build .
     cmake --build build --parallel
     cmake --install build
 
diff --git a/packages/HighFive/doc/migration_guide.md b/packages/HighFive/doc/migration_guide.md
index e85002b150c5c002a9d896736946c0260429cc58..ee72e52c7ad00b3eec394727e4f7fa99b73a0dfb 100644
--- a/packages/HighFive/doc/migration_guide.md
+++ b/packages/HighFive/doc/migration_guide.md
@@ -14,3 +14,225 @@ replaced with an `std::vector<std::string>` (for example).
 
 If desired one can silence warnings by replacing `FixedLenStringArray` with
 `deprecated::FixedLenStringArray`.
+
+## Deprecation of `read(T*, ...)`.
+A "raw read" is when the user allocates sufficient bytes and provides HighFive
+with the pointer to the first byte. "Regular reads" take a detour via the
+inspector and might resize the container, etc.
+
+The issue is that HighFive `v2` had the following two overloads:
+```
+template<class T>
+DataSet::read(T& x, /* skipped */);
+
+template<class T>
+DataSet::read(T* x, /* skipped */);
+```
+and the analogous for `Attribute`.
+
+The issue is that the second overload will also match things like `T**` and
+`T[][]`. For example the following code used the removed overload:
+```
+double x[2][3];
+dset.read(x);
+```
+which is fine because is a contiguous sequence of doubles. It's equivalent to
+following `v3` code:
+```
+double x[n][m];
+dset.read_raw((double*) x);
+```
+
+### Accidental Raw Read
+We consider the example above to be accidentally using a raw read, when it
+could be performing a regular read. We suggest to not change the above, i.e.
+```
+double x[n][m];
+dset.read(x);
+```
+continues to be correct in `v3` and can check that the dimensions match. The
+inspector recognizes `double[n][m]` as a contiguous array of doubles.
+Therefore, it'll use the shallow-copy buffer and avoid the any additional
+allocations or copies.
+
+### Intentional Raw Read
+When genuinely performing a "raw read", one must replace `read` with
+`read_raw`. For example:
+
+```
+double* x = malloc(n*m * sizeof(double));
+dset.read_raw(x);
+```
+is correct in `v3`.
+
+## Change for `T**`, `T***`, etc.
+*The immediately preceding section is likely relevant.*
+
+In `v2` raw pointers could be used to indicate dimensionality. For example:
+```
+double* x = malloc(n*m * sizeof(double));
+auto dset = file.createDataSet("foo", DataSpace({n, m}), ...);
+
+dset.write((double**) x);
+dset.read((double**) x);
+```
+was valid and would write the flat array `x` into the two-dimensional dataset
+`"foo"`. This must be modernized as follows:
+```
+double* x = malloc(n*m * sizeof(double));
+auto dset = file.createDataSet("foo", DataSpace({n, m}), ...);
+
+dset.write_raw(x);
+dset.read_raw(x);
+```
+
+In `v3` the type `T**` will refer a pointer to a pointer (as usual). The
+following:
+```
+size_t n = 2, m = 3;
+double** x = malloc(n * sizeof(double*));
+for(size_t i = 0; i < n; ++i) {
+  x[i] = malloc(m * sizeof(double));
+}
+
+auto dset = file.createDataSet("foo", DataSpace({n, m}), ...);
+dset.write(x);
+dset.read(x);
+```
+is correct in `v3` but would probably segfault in `v2`.
+
+
+## Reworked CMake
+In `v3` we completely rewrote the CMake code of HighFive. Since HighFive is a
+header only library, it needs to perform two tasks:
+
+1. Copy the sources during installation.
+2. Export a target that sets `-I ${HIGHFIVE_DIR}` and links with HDF5.
+
+We've removed all flags for optional dependencies, such as
+`-DHIGHFIVE_USE_BOOST`. Instead user that want to read/write into/from
+optionally supported containers, include a header with the corresponding name
+and make sure to adjust their CMake code to link with the dependency.
+
+The C++ code should have:
+```
+#include <highfive/boost.hpp>
+
+// Code the reads or write `boost::multi_array`.
+```
+and the CMake code would have
+```
+add_executable(app)
+
+# These lines might work, but depend on how exactly the user intends to use
+# Boost. They are not specific to HighFive, but previously added automatically
+# (and sometimes correctly) by HighFive.
+find_package(Boost)
+target_link_libraries(add PUBLIC boost::boost)
+
+# For HighFive there's two options for adding `-I ${HIGHFIVE_DIR}` and the
+# flags for HDF5.
+#
+# Option 1: HighFive is install (systemwide) as a regular library:
+find_package(HighFive)
+target_link_libraries(app PUBLIC HighFive::HighFive)
+
+# Option 2: HighFive is vendored as part of the project:
+add_subdirectory(third_party/HighFive)
+target_link_libraries(app PUBLIC HighFive::HighFive)
+```
+
+There are extensive examples of project integration in `tests/cmake_integration`,
+including how those projects in turn can be included in other projects. If these
+examples don't help, please feel free to open an Issue.
+
+## Type change `DataSpace::DataSpaceType`.
+We've converted the `enum` `DataSpace::DataSpaceType` to an `enum class`. We've
+added static `constexpr` members `dataspace_null` and `dataspace_scalar` to
+`DataSpace`. This minimizes the risk of breaking user code.
+
+Note that objects of type `DataSpace::DataSpaceType` will no longer silently
+convert to an integer. Including the two constants
+`DataSpace::dataspace_{scalar,null}`.
+
+## Deprecation `FileDriver` and `MPIOFileDriver`.
+These have been deprecated to stick more closely with familiar HDF5 concepts.
+The `FileDriver` is synonymous to `FileAccessProps`; and `MPIOFileDriver` is
+the same as:
+```
+auto fapl = FileAccessProps{};
+fapl.add(MPIOFileAccess(mpi_comm, mpi_info));
+```
+
+We felt that the savings in typing effort weren't worth introducing the concept
+of a "file driver". Removing the concept hopefully makes it easier to add a
+better abstraction for the handling of the property lists, when we discover
+such an abstraction.
+
+## Removal of broadcasting
+HighFive v2 had a feature that a dataset (or attribute) of shape `[n, 1]` could
+be read into a one-dimensional array automatically.
+
+The feature is prone to accidentally not failing. Consider an array that shape
+`[n, m]` and in general both `n, m > 0`. Hence, one should always be reading
+into a two-dimensional array, even if `n == 1` or `m == 1`. However, due to
+broadcasting, if one of the dimensions (accidentally) happens to be one, then
+the checks wont fails. This isn't a bug, however, it can hide a bug. For
+example if the test happen to use `[n, 1]` datasets and a one-dimensional
+array.
+
+Broadcasting in HighFive was different from broadcasting in NumPy. For reading
+into one-dimensional data HighFive supports stripping all dimensions that are
+not `1`. When extending the feature to multi-dimensional arrays it gets tricky.
+We can't strip from both the front and back. If we allow stripping from both
+ends, arrays such as `[1, n, m]` read into `[n, m]` if `m > 1` but into `[1,
+n]` (instead of `[n, 1]`) if (coincidentally) `m == 1`. For HighFive because
+avoiding being forced to read `[n, 1]` into `std::vector<std::vector<T>>` is
+more important than `[1, n]`.  Flattening the former requires copying
+everything while the latter can be made flat by just accessing the first value.
+Therefore, HighFive had a preference to strip from the right, while NumPy adds
+`1`s to the front/left of the shape.
+
+In `v3` we've removed broadcasting. Instead users must use one of the two
+alternatives: squeezing and reshaping. The examples show will use datasets and
+reading, but it works the same for attributes and writing.
+
+### Squeezing
+Often we know that the `k`th dimension is `1`, e.g. a column is `[n, 1]` and a
+row is `[1, m]`. In this case it's convenient to state, remove dimension `k`.
+The syntax to simultaneously remove the dimensions `{0, 2}` is:
+
+```
+dset.squeezeMemSpace({0, 2}).read(array);
+```
+Which will read a dataset with dimensions `[1, n, 1]` into an array of shape
+`[n]`.
+
+### Reshape
+Sometimes it's easier to state what the new shape must be. For this we have the
+syntax:
+```
+dset.reshapeMemSpace(dims).read(array);
+```
+To declare that `array` should have dimensions `dims` even if
+`dset.getDimensions()` is something different.
+
+Example:
+```
+dset.reshapeMemSpace({dset.getElementCount()}).read(array);
+```
+to read into a one-dimensional array.
+
+### Scalars
+There's a safe case that seems needlessly strict to enforce: if the dataset is
+a multi-dimensional array with one element one should be able to read into
+(write from) a scalar.
+
+The reverse, i.e. reading a scalar value in the HDF5 file into a
+multi-dimensional array isn't supported, because if we want to support array
+with runtime-defined rank, we can't deduce the correct shape, e.g. `[1]` vs.
+`[1, 1, 1]`, when read into an array.
+
+# Removal of `Object*Props`.
+To out knowledge these could not be used meaningfully. Please create an issue
+if you relied on these.
diff --git a/packages/HighFive/include/highfive/H5Attribute.hpp b/packages/HighFive/include/highfive/H5Attribute.hpp
index 810d388ae8ef555ff2417c3e5cf56edef212f003..9fa3b63ba3bd306f84483f9f2ee8d11e77d6c380 100644
--- a/packages/HighFive/include/highfive/H5Attribute.hpp
+++ b/packages/HighFive/include/highfive/H5Attribute.hpp
@@ -13,6 +13,7 @@
 #include <H5Apublic.h>
 
 #include "H5DataType.hpp"
+#include "H5DataSpace.hpp"
 #include "H5Object.hpp"
 #include "bits/H5Friends.hpp"
 #include "bits/H5Path_traits.hpp"
@@ -70,7 +71,7 @@ class Attribute: public Object, public PathTraits<Attribute> {
     /// \since 1.0
     DataType getDataType() const;
 
-    /// \brief Get the DataSpace of the current Attribute.
+    /// \brief Get a copy of the DataSpace of the current Attribute.
     /// \code{.cpp}
     /// Attribute attr = dset.createAttribute<int>("foo", DataSpace(1, 2));
     /// auto dspace = attr.getSpace(); // This will be a DataSpace of dimension 1 * 2
@@ -78,8 +79,12 @@ class Attribute: public Object, public PathTraits<Attribute> {
     /// \since 1.0
     DataSpace getSpace() const;
 
-    /// \brief Get the DataSpace of the current Attribute.
-    /// \note This is an alias of getSpace().
+    /// \brief Get the memory DataSpace of the current Attribute.
+    ///
+    /// HDF5 attributes don't support selections. Therefore, there's no need
+    /// for a memory dataspace. However, HighFive supports allocating arrays
+    /// and checking dimensions, this requires the dimensions of the memspace.
+    ///
     /// \since 1.0
     DataSpace getMemSpace() const;
 
@@ -132,7 +137,7 @@ class Attribute: public Object, public PathTraits<Attribute> {
     /// \endcode
     /// \since 2.2.2
     template <typename T>
-    void read(T* array, const DataType& mem_datatype) const;
+    void read_raw(T* array, const DataType& mem_datatype) const;
 
     /// \brief Read the attribute into a buffer.
     /// Behaves like Attribute::read(T*, const DataType&) const but
@@ -154,7 +159,7 @@ class Attribute: public Object, public PathTraits<Attribute> {
     /// \endcode
     /// \since 2.2.2
     template <typename T>
-    void read(T* array) const;
+    void read_raw(T* array) const;
 
     /// \brief Write the value into the Attribute.
     ///
@@ -245,10 +250,34 @@ class Attribute: public Object, public PathTraits<Attribute> {
     // No empty attributes
     Attribute() = delete;
 
+    ///
+    /// \brief Return an `Attribute` with `axes` squeezed from the memspace.
+    ///
+    /// Returns an `Attribute` in which the memspace has been modified
+    /// to not include the axes listed in `axes`.
+    ///
+    /// Throws if any axis to be squeezes has a dimension other than `1`.
+    ///
+    /// \since 3.0
+    Attribute squeezeMemSpace(const std::vector<size_t>& axes) const;
+
+    ///
+    /// \brief Return a `Attribute` with a simple memspace with `dims`.
+    ///
+    /// Returns a `Attribute` in which the memspace has been modified
+    /// to be a simple dataspace with dimensions `dims`.
+    ///
+    /// Throws if the number of elements changes.
+    ///
+    /// \since 3.0
+    Attribute reshapeMemSpace(const std::vector<size_t>& dims) const;
+
   protected:
     using Object::Object;
 
   private:
+    DataSpace _mem_space;
+
 #if HIGHFIVE_HAS_FRIEND_DECLARATIONS
     template <typename Derivate>
     friend class ::HighFive::AnnotateTraits;
diff --git a/packages/HighFive/include/highfive/H5DataSet.hpp b/packages/HighFive/include/highfive/H5DataSet.hpp
index 0236f06c23054fa9e4d937ca87e78a260511cadc..566eb17ffe263e19b40d520afe1ce36775cb3de5 100644
--- a/packages/HighFive/include/highfive/H5DataSet.hpp
+++ b/packages/HighFive/include/highfive/H5DataSet.hpp
@@ -98,8 +98,6 @@ class DataSet: public Object,
         return details::get_plist<DataSetAccessProps>(*this, H5Dget_access_plist);
     }
 
-    /// \deprecated Default constructor creates unsafe uninitialized objects
-    H5_DEPRECATED("Default constructor creates unsafe uninitialized objects")
     DataSet() = default;
 
   protected:
diff --git a/packages/HighFive/include/highfive/H5DataSpace.hpp b/packages/HighFive/include/highfive/H5DataSpace.hpp
index 7c7c5860abf93239befb9c77d791e617c9c27136..463648507a9fea2c7ffd27cbca1230348b4c3385 100644
--- a/packages/HighFive/include/highfive/H5DataSpace.hpp
+++ b/packages/HighFive/include/highfive/H5DataSpace.hpp
@@ -45,12 +45,16 @@ class DataSpace: public Object {
     /// This enum is needed otherwise we will not be able to distringuish between both with normal
     /// constructors. Both have a dimension of 0.
     /// \since 1.3
-    enum DataspaceType {
+    enum class DataspaceType {
         dataspace_scalar,  ///< Value to create scalar DataSpace
         dataspace_null,    ///< Value to create null DataSpace
         // simple dataspace are handle directly from their dimensions
     };
 
+    // For backward compatibility: `DataSpace::dataspace_scalar`.
+    constexpr static DataspaceType dataspace_scalar = DataspaceType::dataspace_scalar;
+    constexpr static DataspaceType dataspace_null = DataspaceType::dataspace_null;
+
     /// \brief Create a DataSpace of N-dimensions from a std::vector<size_t>.
     /// \param dims Dimensions of the new DataSpace
     ///
diff --git a/packages/HighFive/include/highfive/H5DataType.hpp b/packages/HighFive/include/highfive/H5DataType.hpp
index b15f62165afcf5adc4254d79dc41acbb9889404c..985eb6dd457fea7037df20386a6e553e8ec3b4ee 100644
--- a/packages/HighFive/include/highfive/H5DataType.hpp
+++ b/packages/HighFive/include/highfive/H5DataType.hpp
@@ -340,133 +340,6 @@ DataType create_datatype();
 /// \brief Create a DataType instance representing type T and perform a sanity check on its size
 template <typename T>
 DataType create_and_check_datatype();
-
-
-namespace deprecated {
-///
-/// \brief A structure representing a set of fixed-length strings
-///
-/// Although fixed-len arrays can be created 'raw' without the need for
-/// this structure, to retrieve results efficiently it must be used.
-///
-/// \tparam N Size of the string in bytes, including the null character. Note,
-///           that all string must be null-terminated.
-///
-template <std::size_t N>
-class FixedLenStringArray {
-  public:
-    FixedLenStringArray() = default;
-
-    ///
-    /// \brief Create a FixedStringArray from a raw contiguous buffer.
-    ///
-    /// The argument `n_strings` specifies the number of strings.
-    ///
-    FixedLenStringArray(const char array[][N], std::size_t n_strings);
-
-    ///
-    /// \brief Create a FixedStringArray from a sequence of strings.
-    ///
-    /// Such conversion involves a copy, original vector is not modified
-    ///
-    explicit FixedLenStringArray(const std::vector<std::string>& vec);
-
-    FixedLenStringArray(const std::string* iter_begin, const std::string* iter_end);
-
-    FixedLenStringArray(const std::initializer_list<std::string>&);
-
-    ///
-    /// \brief Append an std::string to the buffer structure
-    ///
-    void push_back(const std::string&);
-
-    void push_back(const std::array<char, N>&);
-
-    ///
-    /// \brief Retrieve a string from the structure as std::string
-    ///
-    std::string getString(std::size_t index) const;
-
-    // Container interface
-    inline const char* operator[](std::size_t i) const noexcept {
-        return datavec[i].data();
-    }
-    inline const char* at(std::size_t i) const {
-        return datavec.at(i).data();
-    }
-    inline bool empty() const noexcept {
-        return datavec.empty();
-    }
-    inline std::size_t size() const noexcept {
-        return datavec.size();
-    }
-    inline void resize(std::size_t n) {
-        datavec.resize(n);
-    }
-    inline const char* front() const {
-        return datavec.front().data();
-    }
-    inline const char* back() const {
-        return datavec.back().data();
-    }
-    inline char* data() noexcept {
-        return datavec[0].data();
-    }
-    inline const char* data() const noexcept {
-        return datavec[0].data();
-    }
-
-  private:
-    using vector_t = typename std::vector<std::array<char, N>>;
-
-  public:
-    // Use the underlying iterator
-    using iterator = typename vector_t::iterator;
-    using const_iterator = typename vector_t::const_iterator;
-    using reverse_iterator = typename vector_t::reverse_iterator;
-    using const_reverse_iterator = typename vector_t::const_reverse_iterator;
-    using value_type = typename vector_t::value_type;
-
-    inline iterator begin() noexcept {
-        return datavec.begin();
-    }
-    inline iterator end() noexcept {
-        return datavec.end();
-    }
-    inline const_iterator begin() const noexcept {
-        return datavec.begin();
-    }
-    inline const_iterator cbegin() const noexcept {
-        return datavec.cbegin();
-    }
-    inline const_iterator end() const noexcept {
-        return datavec.end();
-    }
-    inline const_iterator cend() const noexcept {
-        return datavec.cend();
-    }
-    inline reverse_iterator rbegin() noexcept {
-        return datavec.rbegin();
-    }
-    inline reverse_iterator rend() noexcept {
-        return datavec.rend();
-    }
-    inline const_reverse_iterator rbegin() const noexcept {
-        return datavec.rbegin();
-    }
-    inline const_reverse_iterator rend() const noexcept {
-        return datavec.rend();
-    }
-
-  private:
-    vector_t datavec;
-};
-}  // namespace deprecated
-
-template <size_t N>
-using FixedLenStringArray H5_DEPRECATED_USING("Use 'std::vector<std::string>'.") =
-    deprecated::FixedLenStringArray<N>;
-
 }  // namespace HighFive
 
 
@@ -475,12 +348,15 @@ using FixedLenStringArray H5_DEPRECATED_USING("Use 'std::vector<std::string>'.")
 /// This macro has to be called outside of any namespace.
 ///
 /// \code{.cpp}
+/// namespace app {
 /// enum FooBar { FOO = 1, BAR = 2 };
 /// EnumType create_enum_foobar() {
 ///    return EnumType<FooBar>({{"FOO", FooBar::FOO},
 ///                             {"BAR", FooBar::BAR}});
 /// }
-/// HIGHFIVE_REGISTER_TYPE(FooBar, create_enum_foobar)
+/// }
+///
+/// HIGHFIVE_REGISTER_TYPE(FooBar, ::app::create_enum_foobar)
 /// \endcode
 #define HIGHFIVE_REGISTER_TYPE(type, function)                    \
     template <>                                                   \
diff --git a/packages/HighFive/include/highfive/H5Easy.hpp b/packages/HighFive/include/highfive/H5Easy.hpp
index e793fd8753179263fb235129d717739abeb362fd..013c44135edf2c49680d962f1bcf6aa07d44b9dd 100644
--- a/packages/HighFive/include/highfive/H5Easy.hpp
+++ b/packages/HighFive/include/highfive/H5Easy.hpp
@@ -30,6 +30,7 @@
 #ifdef H5_USE_XTENSOR
 #include <xtensor/xarray.hpp>
 #include <xtensor/xtensor.hpp>
+#include "xtensor.hpp"
 #endif
 
 // optionally enable Eigen plug-in and load the library
@@ -41,6 +42,7 @@
 
 #ifdef H5_USE_EIGEN
 #include <Eigen/Eigen>
+#include "eigen.hpp"
 #endif
 
 // optionally enable OpenCV plug-in and load the library
@@ -52,6 +54,7 @@
 
 #ifdef H5_USE_OPENCV
 #include <opencv2/opencv.hpp>
+#include "experimental/opencv.hpp"
 #endif
 
 #include "H5File.hpp"
@@ -393,8 +396,5 @@ inline T loadAttribute(const File& file, const std::string& path, const std::str
 
 #include "h5easy_bits/H5Easy_Eigen.hpp"
 #include "h5easy_bits/H5Easy_misc.hpp"
-#include "h5easy_bits/H5Easy_opencv.hpp"
 #include "h5easy_bits/H5Easy_public.hpp"
 #include "h5easy_bits/H5Easy_scalar.hpp"
-#include "h5easy_bits/H5Easy_vector.hpp"
-#include "h5easy_bits/H5Easy_xtensor.hpp"
diff --git a/packages/HighFive/include/highfive/H5File.hpp b/packages/HighFive/include/highfive/H5File.hpp
index 9b393e5a35ea28411d59a5d29b87e3f31bbbb23d..b134aaa49dc2131688acf7133c4589c2e5dba748 100644
--- a/packages/HighFive/include/highfive/H5File.hpp
+++ b/packages/HighFive/include/highfive/H5File.hpp
@@ -10,7 +10,6 @@
 
 #include <string>
 
-#include "H5FileDriver.hpp"
 #include "H5Object.hpp"
 #include "H5PropertyList.hpp"
 #include "bits/H5Annotate_traits.hpp"
@@ -71,7 +70,7 @@ class File: public Object, public NodeTraits<File>, public AnnotateTraits<File>
     ///
     /// \brief Return the name of the file
     ///
-    const std::string& getName() const noexcept;
+    const std::string& getName() const;
 
 
     /// \brief Object path of a File is always "/"
diff --git a/packages/HighFive/include/highfive/H5FileDriver.hpp b/packages/HighFive/include/highfive/H5FileDriver.hpp
deleted file mode 100644
index 2cd4813a36112407166a3815828ba0918e4b88b0..0000000000000000000000000000000000000000
--- a/packages/HighFive/include/highfive/H5FileDriver.hpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- *  Copyright (c), 2017, Adrien Devresse <adrien.devresse@epfl.ch>
- *
- *  Distributed under the Boost Software License, Version 1.0.
- *    (See accompanying file LICENSE_1_0.txt or copy at
- *          http://www.boost.org/LICENSE_1_0.txt)
- *
- */
-#pragma once
-
-#include "H5PropertyList.hpp"
-#include "bits/H5_definitions.hpp"
-
-namespace HighFive {
-
-/// \brief file driver base concept
-/// \deprecated Use FileAccessProps directly
-class H5_DEPRECATED("Use FileAccessProps directly") FileDriver: public FileAccessProps {};
-
-#ifdef H5_HAVE_PARALLEL
-/// \brief MPIIO Driver for Parallel HDF5
-/// \deprecated Add MPIOFileAccess directly to FileAccessProps
-class H5_DEPRECATED("Add MPIOFileAccess directly to FileAccessProps") MPIOFileDriver
-    : public FileAccessProps {
-  public:
-    inline MPIOFileDriver(MPI_Comm mpi_comm, MPI_Info mpi_info);
-};
-#endif
-
-}  // namespace HighFive
-
-#include "bits/H5FileDriver_misc.hpp"
diff --git a/packages/HighFive/include/highfive/H5Group.hpp b/packages/HighFive/include/highfive/H5Group.hpp
index 0a6a4cdae605012f25ca541a9d584f743af1c93b..eebfcbb3ee0d0789c7d1e87bc26491e6ac3530da 100644
--- a/packages/HighFive/include/highfive/H5Group.hpp
+++ b/packages/HighFive/include/highfive/H5Group.hpp
@@ -47,8 +47,6 @@ class Group: public Object,
   public:
     const static ObjectType type = ObjectType::Group;
 
-    /// \deprecated Default constructor creates unsafe uninitialized objects
-    H5_DEPRECATED("Default constructor creates unsafe uninitialized objects")
     Group() = default;
 
     std::pair<unsigned int, unsigned int> getEstimatedLinkInfo() const;
diff --git a/packages/HighFive/include/highfive/H5Object.hpp b/packages/HighFive/include/highfive/H5Object.hpp
index 4cf4e7de0882660df7a4ea111b4f6ce390d8cf17..b6058bf785cea512f0df56ff32f8cc6448c01841 100644
--- a/packages/HighFive/include/highfive/H5Object.hpp
+++ b/packages/HighFive/include/highfive/H5Object.hpp
@@ -31,34 +31,12 @@ enum class ObjectType {
     Other  // Internal/custom object type
 };
 
-namespace detail {
-/// \brief Internal hack to create an `Object` from an ID.
-///
-/// WARNING: Creating an Object from an ID has implications w.r.t. the lifetime of the object
-///          that got passed via its ID. Using this method careless opens up the suite of issues
-///          related to C-style resource management, including the analog of double free, dangling
-///          pointers, etc.
-///
-/// NOTE: This is not part of the API and only serves to work around a compiler issue in GCC which
-///       prevents us from using `friend`s instead. This function should only be used for internal
-///       purposes. The problematic construct is:
-///
-///           template<class Derived>
-///           friend class SomeCRTP<Derived>;
-///
-/// \private
-Object make_object(hid_t hid);
-}  // namespace detail
-
 
 class Object {
   public:
     // move constructor, reuse hid
     Object(Object&& other) noexcept;
 
-    // decrease reference counter
-    ~Object();
-
     ///
     /// \brief isValid
     /// \return true if current Object is a valid HDF5Object
@@ -99,13 +77,15 @@ class Object {
     // Init with an low-level object id
     explicit Object(hid_t);
 
+    // decrease reference counter
+    ~Object();
+
     // Copy-Assignment operator
     Object& operator=(const Object& other);
 
     hid_t _hid;
 
   private:
-    friend Object detail::make_object(hid_t);
     friend class Reference;
     friend class CompoundType;
 
diff --git a/packages/HighFive/include/highfive/H5PropertyList.hpp b/packages/HighFive/include/highfive/H5PropertyList.hpp
index 2368f5ca935580f9b67fc2a48c0045d66d114255..5d467dbb33db083af33b3987cbed50e5c67d7fbb 100644
--- a/packages/HighFive/include/highfive/H5PropertyList.hpp
+++ b/packages/HighFive/include/highfive/H5PropertyList.hpp
@@ -87,7 +87,6 @@ namespace HighFive {
 /// \brief Types of property lists
 ///
 enum class PropertyType : int {
-    OBJECT_CREATE,
     FILE_CREATE,
     FILE_ACCESS,
     DATASET_CREATE,
@@ -99,7 +98,6 @@ enum class PropertyType : int {
     DATATYPE_ACCESS,
     STRING_CREATE,
     ATTRIBUTE_CREATE,
-    OBJECT_COPY,
     LINK_CREATE,
     LINK_ACCESS,
 };
@@ -195,7 +193,6 @@ class PropertyList: public PropertyListBase {
     void _initializeIfNeeded();
 };
 
-using ObjectCreateProps = PropertyList<PropertyType::OBJECT_CREATE>;
 using FileCreateProps = PropertyList<PropertyType::FILE_CREATE>;
 using FileAccessProps = PropertyList<PropertyType::FILE_ACCESS>;
 using DataSetCreateProps = PropertyList<PropertyType::DATASET_CREATE>;
@@ -207,7 +204,6 @@ using DataTypeCreateProps = PropertyList<PropertyType::DATATYPE_CREATE>;
 using DataTypeAccessProps = PropertyList<PropertyType::DATATYPE_ACCESS>;
 using StringCreateProps = PropertyList<PropertyType::STRING_CREATE>;
 using AttributeCreateProps = PropertyList<PropertyType::ATTRIBUTE_CREATE>;
-using ObjectCopyProps = PropertyList<PropertyType::OBJECT_COPY>;
 using LinkCreateProps = PropertyList<PropertyType::LINK_CREATE>;
 using LinkAccessProps = PropertyList<PropertyType::LINK_ACCESS>;
 
@@ -606,7 +602,6 @@ class CreateIntermediateGroup {
   public:
     explicit CreateIntermediateGroup(bool create = true);
 
-    explicit CreateIntermediateGroup(const ObjectCreateProps& ocpl);
     explicit CreateIntermediateGroup(const LinkCreateProps& lcpl);
 
     bool isSet() const;
@@ -615,7 +610,6 @@ class CreateIntermediateGroup {
     void fromPropertyList(hid_t hid);
 
   private:
-    friend ObjectCreateProps;
     friend LinkCreateProps;
     void apply(hid_t hid) const;
     bool _create;
diff --git a/packages/HighFive/include/highfive/H5Selection.hpp b/packages/HighFive/include/highfive/H5Selection.hpp
index c00c66d52f99e67d55fee39c4b488a9b2cb110b6..27681fe7a3aa8aac2e393e1571e06fa970d4c048 100644
--- a/packages/HighFive/include/highfive/H5Selection.hpp
+++ b/packages/HighFive/include/highfive/H5Selection.hpp
@@ -30,21 +30,21 @@ class Selection: public SliceTraits<Selection> {
     /// \brief getSpace
     /// \return Dataspace associated with this selection
     ///
-    DataSpace getSpace() const noexcept;
+    DataSpace getSpace() const;
 
     ///
     /// \brief getMemSpace
     /// \return Dataspace associated with the memory representation of this
     /// selection
     ///
-    DataSpace getMemSpace() const noexcept;
+    DataSpace getMemSpace() const;
 
     ///
     /// \brief getDataSet
     /// \return parent dataset of this selection
     ///
-    DataSet& getDataset() noexcept;
-    const DataSet& getDataset() const noexcept;
+    DataSet& getDataset();
+    const DataSet& getDataset() const;
 
     ///
     /// \brief return the datatype of the selection
diff --git a/packages/HighFive/include/highfive/H5Version.hpp b/packages/HighFive/include/highfive/H5Version.hpp
index bca2c3a83066e9e666840473c355c5ade91f1b87..4ffb036452953562b86ff2962de9045ccf518482 100644
--- a/packages/HighFive/include/highfive/H5Version.hpp
+++ b/packages/HighFive/include/highfive/H5Version.hpp
@@ -8,8 +8,8 @@
  */
 #pragma once
 
-#define HIGHFIVE_VERSION_MAJOR 2
-#define HIGHFIVE_VERSION_MINOR 9
+#define HIGHFIVE_VERSION_MAJOR 3
+#define HIGHFIVE_VERSION_MINOR 0
 #define HIGHFIVE_VERSION_PATCH 0
 
 /** \brief Concatenated representation of the HighFive version.
@@ -24,10 +24,10 @@
  *  std::cout << STRINGIFY_VALUE(HIGHFIVE_VERSION) << "\n";
  *  \endcode
  */
-#define HIGHFIVE_VERSION 2.9.0
+#define HIGHFIVE_VERSION 3.0.0
 
 /** \brief String representation of the HighFive version.
  *
  *  \warning This macro only exists from 2.7.1 onwards.
  */
-#define HIGHFIVE_VERSION_STRING "2.9.0"
+#define HIGHFIVE_VERSION_STRING "3.0.0"
diff --git a/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp b/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp
index cc235b50074f90f948e8b4b22678e1ee44091274..7ad38699a8a160024b1a2e78334078dee54bbfdf 100644
--- a/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Attribute_misc.hpp
@@ -8,6 +8,7 @@
  */
 #pragma once
 
+#include <H5Ipublic.h>
 #include <algorithm>
 #include <functional>
 #include <numeric>
@@ -18,10 +19,13 @@
 
 #include "../H5DataSpace.hpp"
 #include "H5Converter_misc.hpp"
+#include "H5Inspector_misc.hpp"
 #include "H5ReadWrite_misc.hpp"
 #include "H5Utils.hpp"
 #include "h5a_wrapper.hpp"
 #include "h5d_wrapper.hpp"
+#include "squeeze.hpp"
+#include "assert_compatible_spaces.hpp"
 
 namespace HighFive {
 
@@ -31,6 +35,10 @@ inline std::string Attribute::getName() const {
 }
 
 inline size_t Attribute::getStorageSize() const {
+    if (!this->isValid()) {
+        throw AttributeException("Invalid call to `DataSet::getFile` for invalid object");
+    }
+
     return static_cast<size_t>(detail::h5a_get_storage_size(_hid));
 }
 
@@ -47,7 +55,7 @@ inline DataSpace Attribute::getSpace() const {
 }
 
 inline DataSpace Attribute::getMemSpace() const {
-    return getSpace();
+    return _mem_space.getId() == H5I_INVALID_HID ? getSpace() : _mem_space;
 }
 
 template <typename T>
@@ -66,24 +74,22 @@ inline void Attribute::read(T& array) const {
         [this]() -> std::string { return this->getName(); },
         details::BufferInfo<T>::Operation::read);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
-        ss << "Impossible to read Attribute of dimensions " << mem_space.getNumberDimensions()
-           << " into arrays of dimensions " << buffer_info.n_dimensions;
+        ss << "Impossible to read attribute of dimensions " << mem_space.getNumberDimensions()
+           << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to "
+           << buffer_info.getMaxRank() << "(max)";
         throw DataSpaceException(ss.str());
     }
     auto dims = mem_space.getDimensions();
 
     if (mem_space.getElementCount() == 0) {
-        auto effective_dims = details::squeezeDimensions(dims,
-                                                         details::inspector<T>::recursive_ndim);
-
-        details::inspector<T>::prepare(array, effective_dims);
+        details::inspector<T>::prepare(array, dims);
         return;
     }
 
     auto r = details::data_converter::get_reader<T>(dims, array, file_datatype);
-    read(r.getPointer(), buffer_info.data_type);
+    read_raw(r.getPointer(), buffer_info.data_type);
     // re-arrange results
     r.unserialize(array);
 
@@ -102,7 +108,7 @@ inline void Attribute::read(T& array) const {
 }
 
 template <typename T>
-inline void Attribute::read(T* array, const DataType& mem_datatype) const {
+inline void Attribute::read_raw(T* array, const DataType& mem_datatype) const {
     static_assert(!std::is_const<T>::value,
                   "read() requires a non-const structure to read data into");
 
@@ -110,16 +116,17 @@ inline void Attribute::read(T* array, const DataType& mem_datatype) const {
 }
 
 template <typename T>
-inline void Attribute::read(T* array) const {
+inline void Attribute::read_raw(T* array) const {
     using element_type = typename details::inspector<T>::base_type;
     const DataType& mem_datatype = create_and_check_datatype<element_type>();
 
-    read(array, mem_datatype);
+    read_raw(array, mem_datatype);
 }
 
 template <typename T>
 inline void Attribute::write(const T& buffer) {
     const DataSpace& mem_space = getMemSpace();
+    auto dims = mem_space.getDimensions();
 
     if (mem_space.getElementCount() == 0) {
         return;
@@ -132,13 +139,14 @@ inline void Attribute::write(const T& buffer) {
         [this]() -> std::string { return this->getName(); },
         details::BufferInfo<T>::Operation::write);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
-        ss << "Impossible to write buffer of dimensions " << buffer_info.n_dimensions
-           << " into dataset of dimensions " << mem_space.getNumberDimensions();
+        ss << "Impossible to write attribute of dimensions " << mem_space.getNumberDimensions()
+           << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to "
+           << buffer_info.getMaxRank() << "(max)";
         throw DataSpaceException(ss.str());
     }
-    auto w = details::data_converter::serialize<T>(buffer, file_datatype);
+    auto w = details::data_converter::serialize<T>(buffer, dims, file_datatype);
     write_raw(w.getPointer(), buffer_info.data_type);
 }
 
@@ -155,4 +163,21 @@ inline void Attribute::write_raw(const T* buffer) {
     write_raw(buffer, mem_datatype);
 }
 
+inline Attribute Attribute::squeezeMemSpace(const std::vector<size_t>& axes) const {
+    auto mem_dims = this->getMemSpace().getDimensions();
+    auto squeezed_dims = detail::squeeze(mem_dims, axes);
+
+    auto attr = *this;
+    attr._mem_space = DataSpace(squeezed_dims);
+    return attr;
+}
+
+inline Attribute Attribute::reshapeMemSpace(const std::vector<size_t>& new_dims) const {
+    detail::assert_compatible_spaces(this->getMemSpace(), new_dims);
+
+    auto attr = *this;
+    attr._mem_space = DataSpace(new_dims);
+    return attr;
+}
+
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp b/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp
index ed387702fac981ef9b1169d0adca99b25f6cd6b5..5fcbafb5e64308a4560ffa17be2b3b6bbe183fd6 100644
--- a/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Converter_misc.hpp
@@ -69,7 +69,7 @@ struct DeepCopyBuffer {
     using hdf5_type = typename inspector<type>::hdf5_type;
 
     explicit DeepCopyBuffer(const std::vector<size_t>& _dims)
-        : buffer(inspector<T>::getSize(_dims))
+        : buffer(compute_total_size(_dims))
         , dims(_dims) {}
 
     hdf5_type* getPointer() {
@@ -344,23 +344,27 @@ struct Writer<T, typename enable_shallow_copy<T>::type>: public ShallowCopyBuffe
     using super = ShallowCopyBuffer<T, true>;
 
   public:
-    explicit Writer(const T& val, const DataType& /* file_datatype */)
+    explicit Writer(const T& val,
+                    const std::vector<size_t>& /* dims */,
+                    const DataType& /* file_datatype */)
         : super(val){};
 };
 
 template <typename T>
 struct Writer<T, typename enable_deep_copy<T>::type>: public DeepCopyBuffer<T> {
-    explicit Writer(const T& val, const DataType& /* file_datatype */)
-        : DeepCopyBuffer<T>(inspector<T>::getDimensions(val)) {
-        inspector<T>::serialize(val, this->begin());
+    explicit Writer(const T& val,
+                    const std::vector<size_t>& _dims,
+                    const DataType& /* file_datatype */)
+        : DeepCopyBuffer<T>(_dims) {
+        inspector<T>::serialize(val, _dims, this->begin());
     }
 };
 
 template <typename T>
 struct Writer<T, typename enable_string_copy<T>::type>: public StringBuffer<T, BufferMode::Write> {
-    explicit Writer(const T& val, const DataType& _file_datatype)
-        : StringBuffer<T, BufferMode::Write>(inspector<T>::getDimensions(val), _file_datatype) {
-        inspector<T>::serialize(val, this->begin());
+    explicit Writer(const T& val, const std::vector<size_t>& _dims, const DataType& _file_datatype)
+        : StringBuffer<T, BufferMode::Write>(_dims, _file_datatype) {
+        inspector<T>::serialize(val, _dims, this->begin());
     }
 };
 
@@ -402,18 +406,17 @@ struct Reader<T, typename enable_string_copy<T>::type>: public StringBuffer<T, B
 struct data_converter {
     template <typename T>
     static Writer<T> serialize(const typename inspector<T>::type& val,
+                               const std::vector<size_t>& dims,
                                const DataType& file_datatype) {
-        return Writer<T>(val, file_datatype);
+        return Writer<T>(val, dims, file_datatype);
     }
 
     template <typename T>
     static Reader<T> get_reader(const std::vector<size_t>& dims,
                                 T& val,
                                 const DataType& file_datatype) {
-        // TODO Use bufferinfo for recursive_ndim
-        auto effective_dims = details::squeezeDimensions(dims, inspector<T>::recursive_ndim);
-        inspector<T>::prepare(val, effective_dims);
-        return Reader<T>(effective_dims, val, file_datatype);
+        inspector<T>::prepare(val, dims);
+        return Reader<T>(dims, val, file_datatype);
     }
 };
 
diff --git a/packages/HighFive/include/highfive/bits/H5DataSet_misc.hpp b/packages/HighFive/include/highfive/bits/H5DataSet_misc.hpp
index 4817fe00151f869dc6d352087a4d5cd713982365..45e530efc6284eeccbb985908303ca2316593e1e 100644
--- a/packages/HighFive/include/highfive/bits/H5DataSet_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5DataSet_misc.hpp
@@ -22,6 +22,10 @@
 namespace HighFive {
 
 inline uint64_t DataSet::getStorageSize() const {
+    if (!this->isValid()) {
+        throw DataSetException("Invalid call to `DataSet::getStorageSize` for invalid object");
+    }
+
     return detail::h5d_get_storage_size(_hid);
 }
 
diff --git a/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp b/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp
index 619e51e7189e47db7649100c63c1ef82837b8536..7977021880e6c6b8c2af001a742d0b3d0eab080b 100644
--- a/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5DataType_misc.hpp
@@ -206,13 +206,6 @@ class AtomicType<char[StrLen]>: public DataType {
         : DataType(create_string(StrLen)) {}
 };
 
-template <size_t StrLen>
-class AtomicType<deprecated::FixedLenStringArray<StrLen>>: public DataType {
-  public:
-    inline AtomicType()
-        : DataType(create_string(StrLen)) {}
-};
-
 template <typename T>
 class AtomicType<std::complex<T>>: public DataType {
   public:
@@ -233,57 +226,12 @@ inline EnumType<details::Boolean> create_enum_boolean() {
 // Other cases not supported. Fail early with a user message
 template <typename T>
 AtomicType<T>::AtomicType() {
-    static_assert(details::inspector<T>::recursive_ndim == 0,
-                  "Atomic types cant be arrays, except for char[] (fixed-length strings)");
-    static_assert(details::inspector<T>::recursive_ndim > 0, "Type not supported");
+    static_assert(
+        true,
+        "Missing specialization of AtomicType<T>. Therefore, type T is not supported by HighFive.");
 }
 
 
-namespace deprecated {
-template <std::size_t N>
-inline FixedLenStringArray<N>::FixedLenStringArray(const char array[][N], std::size_t length) {
-    datavec.resize(length);
-    std::memcpy(datavec[0].data(), array[0].data(), N * length);
-}
-
-template <std::size_t N>
-inline FixedLenStringArray<N>::FixedLenStringArray(const std::string* iter_begin,
-                                                   const std::string* iter_end) {
-    datavec.reserve(static_cast<std::size_t>(iter_end - iter_begin));
-    for (std::string const* it = iter_begin; it != iter_end; ++it) {
-        push_back(*it);
-    }
-}
-
-template <std::size_t N>
-inline FixedLenStringArray<N>::FixedLenStringArray(const std::vector<std::string>& vec)
-    : FixedLenStringArray(vec.data(), vec.data() + vec.size()) {}
-
-template <std::size_t N>
-inline FixedLenStringArray<N>::FixedLenStringArray(
-    const std::initializer_list<std::string>& init_list)
-    : FixedLenStringArray(init_list.begin(), init_list.end()) {}
-
-template <std::size_t N>
-inline void FixedLenStringArray<N>::push_back(const std::string& src) {
-    datavec.emplace_back();
-    const size_t length = std::min(N - 1, src.length());
-    std::memcpy(datavec.back().data(), src.c_str(), length);
-    datavec.back()[length] = 0;
-}
-
-template <std::size_t N>
-inline void FixedLenStringArray<N>::push_back(const std::array<char, N>& src) {
-    datavec.emplace_back();
-    std::copy(src.begin(), src.end(), datavec.back().data());
-}
-
-template <std::size_t N>
-inline std::string FixedLenStringArray<N>::getString(std::size_t i) const {
-    return std::string(datavec[i].data());
-}
-}  // namespace deprecated
-
 // Internal
 // Reference mapping
 template <>
@@ -520,7 +468,3 @@ inline DataType create_datatype<bool>() {
 }
 
 }  // namespace HighFive
-
-#ifdef H5_USE_HALF_FLOAT
-#include <highfive/half_float.hpp>
-#endif
diff --git a/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp b/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp
index ceae1e5314551f72179b7e757d7ccf6faf67a038..4382c14c1305fcf989f641ab99116b059116e37e 100644
--- a/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Dataspace_misc.hpp
@@ -131,9 +131,10 @@ inline DataSpace DataSpace::FromCharArrayStrings(const char (&)[N][Width]) {
 
 namespace details {
 
-/// dimension checks @internal
-inline bool checkDimensions(const DataSpace& mem_space, size_t n_dim_requested) {
-    return checkDimensions(mem_space.getDimensions(), n_dim_requested);
+inline bool checkDimensions(const DataSpace& mem_space,
+                            size_t min_dim_requested,
+                            size_t max_dim_requested) {
+    return checkDimensions(mem_space.getDimensions(), min_dim_requested, max_dim_requested);
 }
 
 }  // namespace details
diff --git a/packages/HighFive/include/highfive/bits/H5FileDriver_misc.hpp b/packages/HighFive/include/highfive/bits/H5FileDriver_misc.hpp
deleted file mode 100644
index a6331bd5a9fc86a1ff182db46206fa4805b40a7d..0000000000000000000000000000000000000000
--- a/packages/HighFive/include/highfive/bits/H5FileDriver_misc.hpp
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *  Copyright (c), 2017-2018, Adrien Devresse <adrien.devresse@epfl.ch>
- *                            Juan Hernando <juan.hernando@epfl.ch>
- *
- *  Distributed under the Boost Software License, Version 1.0.
- *    (See accompanying file LICENSE_1_0.txt or copy at
- *          http://www.boost.org/LICENSE_1_0.txt)
- *
- */
-#pragma once
-
-namespace HighFive {
-
-#ifdef H5_HAVE_PARALLEL
-inline MPIOFileDriver::MPIOFileDriver(MPI_Comm comm, MPI_Info info) {
-    add(MPIOFileAccess(comm, info));
-}
-#endif
-
-}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5File_misc.hpp b/packages/HighFive/include/highfive/bits/H5File_misc.hpp
index 52ae59516eb83065455cc4632acaa10ec8f64f2e..6013953b13e5433c3ef5c4dfeaf9eae4e1c96930 100644
--- a/packages/HighFive/include/highfive/bits/H5File_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5File_misc.hpp
@@ -82,7 +82,7 @@ inline File::File(const std::string& filename,
     _hid = detail::h5f_create(filename.c_str(), createMode, fcpl, fapl);
 }
 
-inline const std::string& File::getName() const noexcept {
+inline const std::string& File::getName() const {
     if (_filename.empty()) {
         _filename = details::get_name([this](char* buffer, size_t length) {
             return detail::h5f_get_name(getId(), buffer, length);
diff --git a/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp b/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp
index 434545a60e76f7840847bc838d1ca91af911b17c..24b547e217e63dcb50fcf389e8875b21de724f98 100644
--- a/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Inspector_decl.hpp
@@ -1,20 +1,12 @@
 #pragma once
 
-#include <cstddef>
-#include <numeric>
-#include <functional>
-#include <vector>
+#include "compute_total_size.hpp"
 
 namespace HighFive {
 
-inline size_t compute_total_size(const std::vector<size_t>& dims) {
-    return std::accumulate(dims.begin(), dims.end(), size_t{1u}, std::multiplies<size_t>());
-}
-
 template <typename T>
 using unqualified_t = typename std::remove_const<typename std::remove_reference<T>::type>::type;
 
-
 namespace details {
 
 template <typename T>
diff --git a/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp b/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp
index 7ae90d84f167e3757b5246787971929cbb6a73bd..59bf8542224e06ce0f58779251a3aeec1c4294f3 100644
--- a/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Inspector_misc.hpp
@@ -27,92 +27,20 @@
 namespace HighFive {
 namespace details {
 
-inline bool checkDimensions(const std::vector<size_t>& dims, size_t n_dim_requested) {
-    size_t n_dim_actual = dims.size();
-
-    // We should allow reading scalar from shapes like `(1, 1, 1)`.
-    if (n_dim_requested == 0) {
-        if (n_dim_actual == 0ul) {
-            return true;
-        }
-
-        return size_t(std::count(dims.begin(), dims.end(), 1ul)) == n_dim_actual;
-    }
-
-    // For non-scalar datasets, we can squeeze away singleton dimension, but
-    // we never add any.
-    if (n_dim_actual < n_dim_requested) {
-        return false;
-    }
-
-    // Special case for 1-dimensional arrays, which can squeeze `1`s from either
-    // side simultaneously if needed.
-    if (n_dim_requested == 1ul) {
-        return n_dim_actual >= 1ul &&
-               size_t(std::count(dims.begin(), dims.end(), 1ul)) >= n_dim_actual - 1ul;
+inline bool checkDimensions(const std::vector<size_t>& dims,
+                            size_t min_dim_requested,
+                            size_t max_dim_requested) {
+    if (min_dim_requested <= dims.size() && dims.size() <= max_dim_requested) {
+        return true;
     }
 
-    // All other cases strip front only. This avoid unstable behaviour when
-    // squeezing singleton dimensions.
-    size_t n_dim_excess = n_dim_actual - n_dim_requested;
 
-    bool squeeze_back = true;
-    for (size_t i = 1; i <= n_dim_excess; ++i) {
-        if (dims[n_dim_actual - i] != 1) {
-            squeeze_back = false;
-            break;
-        }
-    }
-
-    return squeeze_back;
+    // Scalar values still support broadcasting
+    // into arrays with one element.
+    size_t n_elements = compute_total_size(dims);
+    return n_elements == 1 && min_dim_requested == 0;
 }
 
-
-inline std::vector<size_t> squeezeDimensions(const std::vector<size_t>& dims,
-                                             size_t n_dim_requested) {
-    auto format_error_message = [&]() -> std::string {
-        return "Can't interpret dims = " + format_vector(dims) + " as " +
-               std::to_string(n_dim_requested) + "-dimensional.";
-    };
-
-    if (n_dim_requested == 0) {
-        if (!checkDimensions(dims, n_dim_requested)) {
-            throw std::invalid_argument(format_error_message());
-        }
-
-        return {1ul};
-    }
-
-    auto n_dim = dims.size();
-    if (n_dim < n_dim_requested) {
-        throw std::invalid_argument(format_error_message());
-    }
-
-    if (n_dim_requested == 1ul) {
-        size_t non_singleton_dim = size_t(-1);
-        for (size_t i = 0; i < n_dim; ++i) {
-            if (dims[i] != 1ul) {
-                if (non_singleton_dim == size_t(-1)) {
-                    non_singleton_dim = i;
-                } else {
-                    throw std::invalid_argument(format_error_message());
-                }
-            }
-        }
-
-        return {dims[std::min(non_singleton_dim, n_dim - 1)]};
-    }
-
-    size_t n_dim_excess = dims.size() - n_dim_requested;
-    for (size_t i = 1; i <= n_dim_excess; ++i) {
-        if (dims[n_dim - i] != 1) {
-            throw std::invalid_argument(format_error_message());
-        }
-    }
-
-    return std::vector<size_t>(dims.begin(),
-                               dims.end() - static_cast<std::ptrdiff_t>(n_dim_excess));
-}
 }  // namespace details
 
 
@@ -124,18 +52,18 @@ inspector<T> {
     // hdf5_type is the base read by hdf5 (c-type) (e.g. std::vector<std::string> => const char*)
     using hdf5_type
 
-    // Number of dimensions starting from here
-    static constexpr size_t recursive_ndim
     // Is the inner type trivially copyable for optimisation
     // If this value is true: data() is mandatory
-    // If this value is false: getSizeVal, getSize, serialize, unserialize are mandatory
+    // If this value is false: serialize, unserialize are mandatory
     static constexpr bool is_trivially_copyable
 
+    // Is this type trivially nestable, i.e. is type[n] a contiguous
+    // array of `base_type[N]`?
+    static constexpr bool is_trivially_nestable
+
     // Reading:
     // Allocate the value following dims (should be recursive)
     static void prepare(type& val, const std::vector<std::size_t> dims)
-    // Return the size of the vector pass to/from hdf5 from a vector of dims
-    static size_t getSize(const std::vector<size_t>& dims)
     // Return a pointer of the first value of val (for reading)
     static hdf5_type* data(type& val)
     // Take a serialized vector 'in', some dims and copy value to val (for reading)
@@ -143,12 +71,10 @@ inspector<T> {
 
 
     // Writing:
-    // Return the size of the vector pass to/from hdf5 from a value
-    static size_t getSizeVal(const type& val)
     // Return a point of the first value of val
     static const hdf5_type* data(const type& val)
     // Take a val and serialize it inside 'out'
-    static void serialize(const type& val, hdf5_type* out)
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* out)
     // Return an array of dimensions of the space needed for writing val
     static std::vector<size_t> getDimensions(const type& val)
 }
@@ -163,19 +89,18 @@ struct type_helper {
     using hdf5_type = base_type;
 
     static constexpr size_t ndim = 0;
-    static constexpr size_t recursive_ndim = ndim;
-    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<type>::value;
+    static constexpr size_t min_ndim = ndim;
+    static constexpr size_t max_ndim = ndim;
 
-    static std::vector<size_t> getDimensions(const type& /* val */) {
-        return {};
-    }
+    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<type>::value;
+    static constexpr bool is_trivially_nestable = is_trivially_copyable;
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
+    static size_t getRank(const type& /* val */) {
+        return ndim;
     }
 
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return compute_total_size(dims);
+    static std::vector<size_t> getDimensions(const type& /* val */) {
+        return {};
     }
 
     static void prepare(type& /* val */, const std::vector<size_t>& /* dims */) {}
@@ -190,7 +115,7 @@ struct type_helper {
         return &val;
     }
 
-    static void serialize(const type& val, hdf5_type* m) {
+    static void serialize(const type& val, const std::vector<size_t>& /* dims*/, hdf5_type* m) {
         static_assert(is_trivially_copyable, "The type is not trivially copyable");
         *m = val;
     }
@@ -217,6 +142,7 @@ struct inspector<bool>: type_helper<bool> {
     using hdf5_type = int8_t;
 
     static constexpr bool is_trivially_copyable = false;
+    static constexpr bool is_trivially_nestable = false;
 
     static hdf5_type* data(type& /* val */) {
         throw DataSpaceException("A boolean cannot be read directly.");
@@ -232,7 +158,7 @@ struct inspector<bool>: type_helper<bool> {
         val = vec[0] != 0 ? true : false;
     }
 
-    static void serialize(const type& val, hdf5_type* m) {
+    static void serialize(const type& val, const std::vector<size_t>& /* dims*/, hdf5_type* m) {
         *m = val ? 1 : 0;
     }
 };
@@ -250,7 +176,7 @@ struct inspector<std::string>: type_helper<std::string> {
     }
 
     template <class It>
-    static void serialize(const type& val, It m) {
+    static void serialize(const type& val, const std::vector<size_t>& /* dims*/, It m) {
         (*m).assign(val.data(), val.size(), StringPadding::NullTerminated);
     }
 
@@ -266,6 +192,7 @@ struct inspector<Reference>: type_helper<Reference> {
     using hdf5_type = hobj_ref_t;
 
     static constexpr bool is_trivially_copyable = false;
+    static constexpr bool is_trivially_nestable = false;
 
     static hdf5_type* data(type& /* val */) {
         throw DataSpaceException("A Reference cannot be read directly.");
@@ -275,7 +202,7 @@ struct inspector<Reference>: type_helper<Reference> {
         throw DataSpaceException("A Reference cannot be written directly.");
     }
 
-    static void serialize(const type& val, hdf5_type* m) {
+    static void serialize(const type& val, const std::vector<size_t>& /* dims*/, hdf5_type* m) {
         hobj_ref_t ref;
         val.create_ref(&ref);
         *m = ref;
@@ -288,61 +215,6 @@ struct inspector<Reference>: type_helper<Reference> {
     }
 };
 
-template <size_t N>
-struct inspector<deprecated::FixedLenStringArray<N>> {
-    using type = deprecated::FixedLenStringArray<N>;
-    using value_type = char*;
-    using base_type = deprecated::FixedLenStringArray<N>;
-    using hdf5_type = char;
-
-    static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim;
-    static constexpr bool is_trivially_copyable = false;
-
-    static std::vector<size_t> getDimensions(const type& val) {
-        return std::vector<size_t>{val.size()};
-    }
-
-    static size_t getSizeVal(const type& val) {
-        return N * compute_total_size(getDimensions(val));
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return N * compute_total_size(dims);
-    }
-
-    static void prepare(type& /* val */, const std::vector<size_t>& dims) {
-        if (dims[0] > N) {
-            std::ostringstream os;
-            os << "Size of FixedlenStringArray (" << N << ") is too small for dims (" << dims[0]
-               << ").";
-            throw DataSpaceException(os.str());
-        }
-    }
-
-    static hdf5_type* data(type& val) {
-        return val.data();
-    }
-
-    static const hdf5_type* data(const type& val) {
-        return val.data();
-    }
-
-    static void serialize(const type& val, hdf5_type* m) {
-        for (size_t i = 0; i < val.size(); ++i) {
-            std::memcpy(m + i * N, val[i], N);
-        }
-    }
-
-    static void unserialize(const hdf5_type* vec, const std::vector<size_t>& dims, type& val) {
-        for (size_t i = 0; i < dims[0]; ++i) {
-            std::array<char, N> s;
-            std::memcpy(s.data(), vec + (i * N), N);
-            val.push_back(s);
-        }
-    }
-};
-
 template <typename T>
 struct inspector<std::vector<T>> {
     using type = std::vector<T>;
@@ -351,16 +223,27 @@ struct inspector<std::vector<T>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        if (!val.empty()) {
+            return ndim + inspector<value_type>::getRank(val[0]);
+        } else {
+            return min_ndim;
+        }
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
-        std::vector<size_t> sizes(recursive_ndim, 1ul);
+        auto rank = getRank(val);
+        std::vector<size_t> sizes(rank, 1ul);
         sizes[0] = val.size();
         if (!val.empty()) {
             auto s = inspector<value_type>::getDimensions(val[0]);
-            assert(s.size() + ndim == sizes.size());
             for (size_t i = 0; i < s.size(); ++i) {
                 sizes[i + ndim] = s[i];
             }
@@ -368,14 +251,6 @@ struct inspector<std::vector<T>> {
         return sizes;
     }
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return compute_total_size(dims);
-    }
-
     static void prepare(type& val, const std::vector<size_t>& dims) {
         val.resize(dims[0]);
         std::vector<size_t> next_dims(dims.begin() + 1, dims.end());
@@ -393,11 +268,12 @@ struct inspector<std::vector<T>> {
     }
 
     template <class It>
-    static void serialize(const type& val, It m) {
+    static void serialize(const type& val, const std::vector<size_t>& dims, It m) {
         if (!val.empty()) {
-            size_t subsize = inspector<value_type>::getSizeVal(val[0]);
+            auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
+            size_t subsize = compute_total_size(subdims);
             for (auto&& e: val) {
-                inspector<value_type>::serialize(e, m);
+                inspector<value_type>::serialize(e, subdims, m);
                 m += subsize;
             }
         }
@@ -421,25 +297,21 @@ struct inspector<std::vector<bool>> {
     using hdf5_type = uint8_t;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim;
+    static constexpr size_t min_ndim = ndim;
+    static constexpr size_t max_ndim = ndim;
+
     static constexpr bool is_trivially_copyable = false;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& /* val */) {
+        return ndim;
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{val.size()};
         return sizes;
     }
 
-    static size_t getSizeVal(const type& val) {
-        return val.size();
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        if (dims.size() > 1) {
-            throw DataSpaceException("std::vector<bool> is only 1 dimension.");
-        }
-        return dims[0];
-    }
-
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims.size() > 1) {
             throw DataSpaceException("std::vector<bool> is only 1 dimension.");
@@ -455,7 +327,7 @@ struct inspector<std::vector<bool>> {
         throw DataSpaceException("A std::vector<bool> cannot be written directly.");
     }
 
-    static void serialize(const type& val, hdf5_type* m) {
+    static void serialize(const type& val, const std::vector<size_t>& /* dims*/, hdf5_type* m) {
         for (size_t i = 0; i < val.size(); ++i) {
             m[i] = val[i] ? 1 : 0;
         }
@@ -478,28 +350,25 @@ struct inspector<std::array<T, N>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  sizeof(type) == N * sizeof(T) &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = (sizeof(type) == N * sizeof(T)) &&
+                                                  is_trivially_copyable;
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val[0]);
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{N};
-        if (!val.empty()) {
-            auto s = inspector<value_type>::getDimensions(val[0]);
-            sizes.insert(sizes.end(), s.begin(), s.end());
-        }
+        auto s = inspector<value_type>::getDimensions(val[0]);
+        sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return compute_total_size(dims);
-    }
-
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims[0] > N) {
             std::ostringstream os;
@@ -522,10 +391,11 @@ struct inspector<std::array<T, N>> {
     }
 
     template <class It>
-    static void serialize(const type& val, It m) {
-        size_t subsize = inspector<value_type>::getSizeVal(val[0]);
+    static void serialize(const type& val, const std::vector<size_t>& dims, It m) {
+        auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
+        size_t subsize = compute_total_size(subdims);
         for (auto& e: val) {
-            inspector<value_type>::serialize(e, m);
+            inspector<value_type>::serialize(e, subdims, m);
             m += subsize;
         }
     }
@@ -546,6 +416,7 @@ struct inspector<std::array<T, N>> {
     }
 };
 
+
 // Cannot be use for reading
 template <typename T>
 struct inspector<T*> {
@@ -555,12 +426,19 @@ struct inspector<T*> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
-    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
 
-    static size_t getSizeVal(const type& /* val */) {
-        throw DataSpaceException("Not possible to have size of a T*");
+    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        if (val != nullptr) {
+            return ndim + inspector<value_type>::getRank(val[0]);
+        } else {
+            return min_ndim;
+        }
     }
 
     static std::vector<size_t> getDimensions(const type& /* val */) {
@@ -573,7 +451,9 @@ struct inspector<T*> {
 
     /* it works because there is only T[][][] currently
        we will fix it one day */
-    static void serialize(const type& /* val */, hdf5_type* /* m */) {
+    static void serialize(const type& /* val */,
+                          const std::vector<size_t>& /* dims*/,
+                          hdf5_type* /* m */) {
         throw DataSpaceException("Not possible to serialize a T*");
     }
 };
@@ -587,9 +467,12 @@ struct inspector<T[N]> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 1;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = is_trivially_copyable;
 
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims.size() < 1) {
@@ -606,16 +489,14 @@ struct inspector<T[N]> {
         }
     }
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val[0]);
     }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{N};
-        if (N > 0) {
-            auto s = inspector<value_type>::getDimensions(val[0]);
-            sizes.insert(sizes.end(), s.begin(), s.end());
-        }
+        auto s = inspector<value_type>::getDimensions(val[0]);
+        sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
@@ -629,21 +510,15 @@ struct inspector<T[N]> {
 
     /* it works because there is only T[][][] currently
        we will fix it one day */
-    static void serialize(const type& val, hdf5_type* m) {
-        size_t subsize = inspector<value_type>::getSizeVal(val[0]);
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
+        auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
+        size_t subsize = compute_total_size(subdims);
         for (size_t i = 0; i < N; ++i) {
-            inspector<value_type>::serialize(val[i], m + i * subsize);
+            inspector<value_type>::serialize(val[i], subdims, m + i * subsize);
         }
     }
 };
 
+
 }  // namespace details
 }  // namespace HighFive
-
-#ifdef H5_USE_BOOST
-#include <highfive/boost.hpp>
-#endif
-
-#ifdef H5_USE_EIGEN
-#include <highfive/eigen.hpp>
-#endif
diff --git a/packages/HighFive/include/highfive/bits/H5Node_traits.hpp b/packages/HighFive/include/highfive/bits/H5Node_traits.hpp
index 6f4a93ce6a963d6d3d4fb9e1f1c65fa57380dc12..8076bd9b40a3ceedb111696356bf85a5aa4c9ba3 100644
--- a/packages/HighFive/include/highfive/bits/H5Node_traits.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Node_traits.hpp
@@ -78,14 +78,6 @@ class NodeTraits {
                           bool parents = true);
 
 
-    template <std::size_t N>
-    H5_DEPRECATED("Use 'std::vector<std::string>'.")
-    DataSet createDataSet(const std::string& dataset_name,
-                          const deprecated::FixedLenStringArray<N>& data,
-                          const DataSetCreateProps& createProps = DataSetCreateProps::Default(),
-                          const DataSetAccessProps& accessProps = DataSetAccessProps::Default(),
-                          bool parents = true);
-
     ///
     /// \brief get an existing dataset in the current file
     /// \param dataset_name
@@ -225,9 +217,6 @@ class NodeTraits {
     // It makes behavior consistent among versions and by default transforms
     // errors to exceptions
     bool _exist(const std::string& node_name, bool raise_errors = true) const;
-
-    // Opens an arbitrary object to obtain info
-    Object _open(const std::string& node_name) const;
 };
 
 
diff --git a/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp
index a98600598ad7647598633378456230706ff99a5a..b26257779ee8ed7f508aee0232a70e8605d3d261 100644
--- a/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Node_traits_misc.hpp
@@ -80,19 +80,6 @@ inline DataSet NodeTraits<Derivate>::createDataSet(const std::string& dataset_na
     return ds;
 }
 
-template <typename Derivate>
-template <std::size_t N>
-inline DataSet NodeTraits<Derivate>::createDataSet(const std::string& dataset_name,
-                                                   const deprecated::FixedLenStringArray<N>& data,
-                                                   const DataSetCreateProps& createProps,
-                                                   const DataSetAccessProps& accessProps,
-                                                   bool parents) {
-    DataSet ds = createDataSet<char[N]>(
-        dataset_name, DataSpace(data.size()), createProps, accessProps, parents);
-    ds.write(data);
-    return ds;
-}
-
 template <typename Derivate>
 inline DataSet NodeTraits<Derivate>::getDataSet(const std::string& dataset_name,
                                                 const DataSetAccessProps& accessProps) const {
@@ -267,7 +254,12 @@ inline LinkType NodeTraits<Derivate>::getLinkType(const std::string& node_name)
 
 template <typename Derivate>
 inline ObjectType NodeTraits<Derivate>::getObjectType(const std::string& node_name) const {
-    return _open(node_name).getType();
+    const auto id = detail::h5o_open(static_cast<const Derivate*>(this)->getId(),
+                                     node_name.c_str(),
+                                     H5P_DEFAULT);
+    auto object_type = _convert_object_type(detail::h5i_get_type(id));
+    detail::h5o_close(id);
+    return object_type;
 }
 
 
@@ -327,13 +319,4 @@ inline void NodeTraits<Derivate>::createHardLink(const std::string& link_name,
 }
 
 
-template <typename Derivate>
-inline Object NodeTraits<Derivate>::_open(const std::string& node_name) const {
-    const auto id = detail::h5o_open(static_cast<const Derivate*>(this)->getId(),
-                                     node_name.c_str(),
-                                     H5P_DEFAULT);
-    return detail::make_object(id);
-}
-
-
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Object_misc.hpp b/packages/HighFive/include/highfive/bits/H5Object_misc.hpp
index c5a1f39998dab4d5a52e50792fff70b5fc001988..eefddc1ed6e00a3a8c1be595f7e77c8339d3dd58 100644
--- a/packages/HighFive/include/highfive/bits/H5Object_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Object_misc.hpp
@@ -15,12 +15,6 @@
 #include "h5i_wrapper.hpp"
 
 namespace HighFive {
-namespace detail {
-inline Object make_object(hid_t hid) {
-    return Object(hid);
-}
-}  // namespace detail
-
 
 inline Object::Object()
     : _hid(H5I_INVALID_HID) {}
diff --git a/packages/HighFive/include/highfive/bits/H5Path_traits.hpp b/packages/HighFive/include/highfive/bits/H5Path_traits.hpp
index 46a038c4ffa1f3a9e91ae617fb634b0b668a8c09..a58f96187451f023565a7e12ccc028a9eccf9471 100644
--- a/packages/HighFive/include/highfive/bits/H5Path_traits.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Path_traits.hpp
@@ -25,8 +25,7 @@ class PathTraits {
     ///
     /// \brief Return a reference to the File object this object belongs
     /// \return the File object ref
-    File& getFile() const noexcept;
-
+    File& getFile() const;
 
   protected:
     std::shared_ptr<File> _file_obj;  // keep a ref to file so we keep its ref count > 0
diff --git a/packages/HighFive/include/highfive/bits/H5Path_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Path_traits_misc.hpp
index acde06d1ee9061e0701f14af3ac6a8297f8b4df6..0893599c62f8406a29dea691b3a971c7cf404408 100644
--- a/packages/HighFive/include/highfive/bits/H5Path_traits_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Path_traits_misc.hpp
@@ -35,7 +35,11 @@ inline std::string PathTraits<Derivate>::getPath() const {
 }
 
 template <typename Derivate>
-inline File& PathTraits<Derivate>::getFile() const noexcept {
+inline File& PathTraits<Derivate>::getFile() const {
+    const auto& obj = static_cast<const Derivate&>(*this);
+    if (!obj.isValid()) {
+        throw ObjectException("Invalid call to `PathTraits::getFile` for invalid object");
+    }
     return *_file_obj;
 }
 
diff --git a/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp b/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp
index 1fa2101f222d901cb88e003612c97b64b2eafc1a..cfeb7685d134b4caa38f2de0e141e01597b88e72 100644
--- a/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5PropertyList_misc.hpp
@@ -17,8 +17,6 @@ inline hid_t convert_plist_type(PropertyType propertyType) {
     // The HP5_XXX are macros with function calls so we can't assign
     // them as the enum values
     switch (propertyType) {
-    case PropertyType::OBJECT_CREATE:
-        return H5P_OBJECT_CREATE;
     case PropertyType::FILE_CREATE:
         return H5P_FILE_CREATE;
     case PropertyType::FILE_ACCESS:
@@ -41,8 +39,6 @@ inline hid_t convert_plist_type(PropertyType propertyType) {
         return H5P_STRING_CREATE;
     case PropertyType::ATTRIBUTE_CREATE:
         return H5P_ATTRIBUTE_CREATE;
-    case PropertyType::OBJECT_COPY:
-        return H5P_OBJECT_COPY;
     case PropertyType::LINK_CREATE:
         return H5P_LINK_CREATE;
     case PropertyType::LINK_ACCESS:
@@ -390,11 +386,6 @@ inline double Caching::getW0() const {
 inline CreateIntermediateGroup::CreateIntermediateGroup(bool create)
     : _create(create) {}
 
-inline CreateIntermediateGroup::CreateIntermediateGroup(const ObjectCreateProps& ocpl) {
-    fromPropertyList(ocpl.getId());
-}
-
-
 inline void CreateIntermediateGroup::apply(const hid_t hid) const {
     detail::h5p_set_create_intermediate_group(hid, _create ? 1 : 0);
 }
diff --git a/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp b/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp
index 05bb49888cf8d75b14c8fbf41fe27dd067cd0dd8..e5c862bc5f97b36868d8bcc4c8ae1bf77eb23dec 100644
--- a/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5ReadWrite_misc.hpp
@@ -9,6 +9,7 @@
 #pragma once
 
 #include <H5Tpublic.h>
+#include "H5Inspector_misc.hpp"
 #include "H5Utils.hpp"
 
 namespace HighFive {
@@ -57,10 +58,14 @@ struct BufferInfo {
     template <class F>
     BufferInfo(const DataType& dtype, F getName, Operation _op);
 
+    size_t getRank(const T& array) const;
+    size_t getMinRank() const;
+    size_t getMaxRank() const;
+
     // member data for info depending on the destination dataset type
     const bool is_fixed_len_string;
-    const size_t n_dimensions;
     const DataType data_type;
+    const size_t rank_correction;
 };
 
 // details implementation
@@ -135,10 +140,9 @@ BufferInfo<T>::BufferInfo(const DataType& file_data_type, F getName, Operation _
     : op(_op)
     , is_fixed_len_string(file_data_type.isFixedLenStr())
     // In case we are using Fixed-len strings we need to subtract one dimension
-    , n_dimensions(details::inspector<type_no_const>::recursive_ndim -
-                   ((is_fixed_len_string && is_char_array) ? 1 : 0))
     , data_type(string_type_checker<char_array_t>::getDataType(create_datatype<elem_type>(),
-                                                               file_data_type)) {
+                                                               file_data_type))
+    , rank_correction((is_fixed_len_string && is_char_array) ? 1 : 0) {
     // We warn. In case they are really not convertible an exception will rise on read/write
     if (file_data_type.getClass() != data_type.getClass()) {
         HIGHFIVE_LOG_WARN(getName() + "\": data and hdf5 dataset have different types: " +
@@ -157,6 +161,21 @@ BufferInfo<T>::BufferInfo(const DataType& file_data_type, F getName, Operation _
     }
 }
 
+template <typename T>
+size_t BufferInfo<T>::getRank(const T& array) const {
+    return details::inspector<type_no_const>::getRank(array) - rank_correction;
+}
+
+template <typename T>
+size_t BufferInfo<T>::getMinRank() const {
+    return details::inspector<T>::min_ndim - rank_correction;
+}
+
+template <typename T>
+size_t BufferInfo<T>::getMaxRank() const {
+    return details::inspector<T>::max_ndim - rank_correction;
+}
+
 }  // namespace details
 
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp b/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp
index c35b7bbf32e318450a5a8ca5ebf18e02f7dacd60..d1c14e93085514f85510746d110170eee8d302ca 100644
--- a/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Selection_misc.hpp
@@ -17,19 +17,19 @@ inline Selection::Selection(const DataSpace& memspace,
     , _file_space(file_space)
     , _set(set) {}
 
-inline DataSpace Selection::getSpace() const noexcept {
+inline DataSpace Selection::getSpace() const {
     return _file_space;
 }
 
-inline DataSpace Selection::getMemSpace() const noexcept {
+inline DataSpace Selection::getMemSpace() const {
     return _mem_space;
 }
 
-inline DataSet& Selection::getDataset() noexcept {
+inline DataSet& Selection::getDataset() {
     return _set;
 }
 
-inline const DataSet& Selection::getDataset() const noexcept {
+inline const DataSet& Selection::getDataset() const {
     return _set;
 }
 
diff --git a/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp b/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp
index c753026c3d20a68cb8c3442c4e32ba69951e053f..6812a091449ee5aa7c3343a439b123171fa56f8c 100644
--- a/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Slice_traits.hpp
@@ -13,6 +13,7 @@
 
 #include "H5_definitions.hpp"
 #include "H5Utils.hpp"
+#include "convert_size_vector.hpp"
 
 #include "../H5PropertyList.hpp"
 #include "h5s_wrapper.hpp"
@@ -51,17 +52,6 @@ class ElementSet {
     friend class SliceTraits;
 };
 
-namespace detail {
-
-template <class To, class From>
-inline std::vector<To> convertSizeVector(const std::vector<From>& from) {
-    std::vector<To> to(from.size());
-    std::copy(from.cbegin(), from.cend(), to.begin());
-
-    return to;
-}
-}  // namespace detail
-
 inline std::vector<hsize_t> toHDF5SizeVector(const std::vector<size_t>& from) {
     return detail::convertSizeVector<hsize_t>(from);
 }
@@ -73,10 +63,10 @@ inline std::vector<size_t> toSTLSizeVector(const std::vector<hsize_t>& from) {
 struct RegularHyperSlab {
     RegularHyperSlab() = default;
 
-    RegularHyperSlab(std::vector<size_t> offset_,
-                     std::vector<size_t> count_ = {},
-                     std::vector<size_t> stride_ = {},
-                     std::vector<size_t> block_ = {})
+    RegularHyperSlab(const std::vector<size_t>& offset_,
+                     const std::vector<size_t>& count_ = {},
+                     const std::vector<size_t>& stride_ = {},
+                     const std::vector<size_t>& block_ = {})
         : offset(toHDF5SizeVector(offset_))
         , count(toHDF5SizeVector(count_))
         , stride(toHDF5SizeVector(stride_))
@@ -87,10 +77,10 @@ struct RegularHyperSlab {
                                           std::vector<hsize_t> stride_ = {},
                                           std::vector<hsize_t> block_ = {}) {
         RegularHyperSlab slab;
-        slab.offset = offset_;
-        slab.count = count_;
-        slab.stride = stride_;
-        slab.block = block_;
+        slab.offset = std::move(offset_);
+        slab.count = std::move(count_);
+        slab.stride = std::move(stride_);
+        slab.block = std::move(block_);
 
         return slab;
     }
@@ -241,6 +231,85 @@ class HyperSlab {
     std::vector<Select_> selects;
 };
 
+///
+/// \brief Selects the Cartesian product of slices.
+///
+/// Given a one-dimensional dataset one might want to select the union of
+/// multiple, non-overlapping slices. For example,
+///
+///     using Slice = std::array<size_t, 2>;
+///     using Slices = std::vector<Slice>;
+///     auto slices = Slices{{0, 2}, {4, 10}};
+///     dset.select(ProductSet(slices);
+///
+/// to select elements `0`, `1` and `4`, ..., `9` (inclusive).
+///
+/// For a two-dimensional array one which to select the row specified above,
+/// but only columns `2`, `3` and `4`:
+///
+///    dset.select(ProductSet(slices, Slice{2, 5}));
+///    // Analogues with the roles of columns and rows reversed:
+///    dset.select(ProductSet(Slice{2, 5}, slices));
+///
+/// One can generalize once more and allow the unions of slices in both x- and
+/// y-dimension:
+///
+///     auto yslices = Slices{{1, 5}, {7, 8}};
+///     auto xslices = Slices{{0, 3}, {6, 8}};
+///     dset.select(ProductSet(yslices, xslices));
+///
+/// which selects the following from a 11x8 dataset:
+///
+///     . . . . . . . .
+///     x x x . . . x x
+///     x x x . . . x x
+///     x x x . . . x x
+///     x x x . . . x x
+///     . . . . . . . .
+///     . . . . . . . .
+///     x x x . . . x x
+///     . . . . . . . .
+///     . . . . . . . .
+///     . . . . . . . .
+///
+/// Final twist, the selection along and axis may be discrete indices, from
+/// which a vector of, possibly single-element, slices can be constructed. The
+/// corresponding types are `std::vector<size_t>` and `size_t` for multiple or
+/// just a single values. Note, looping over rows or columns one-by-one can be
+/// a very serious performance problem. In particular,
+///
+///     // Avoid:
+///     for(auto i : indices) {
+///         dset.select(i).read<double>();
+///     }
+///
+///     // Use:
+///     std::vector<size_t> tmp(indices.begin(), indices.end());
+///     dset.select(tmp).read<std::vector<double>>();
+///
+/// obvious omit the copy if `indices` already has the correct type.
+///
+/// The solution works analogous in higher dimensions. A selection `sk` along
+/// axis `k` can be interpreted as a subset `S_k` of the natural numbers. The
+/// index `i` is in `S_k` if it's selected by `sk`.  The `ProductSet` of `s0`,
+/// ..., `sN` selects the Cartesian product `S_0 x ... x S_N`.
+///
+/// Note that the selections along each axis must be sorted and non-overlapping.
+///
+class ProductSet {
+  public:
+    template <class... Slices>
+    explicit ProductSet(const Slices&... slices);
+
+  private:
+    HyperSlab slab;
+    std::vector<size_t> shape;
+
+    template <typename Derivate>
+    friend class SliceTraits;
+};
+
+
 template <typename Derivate>
 class SliceTraits {
   public:
@@ -287,11 +356,14 @@ class SliceTraits {
     ///
     Selection select(const ElementSet& elements) const;
 
+    Selection select(const ProductSet& product_set) const;
+
     template <typename T>
     T read(const DataTransferProps& xfer_props = DataTransferProps()) const;
 
     ///
     /// Read the entire dataset into a buffer
+    ///
     /// An exception is raised is if the numbers of dimension of the buffer and
     /// of the dataset are different.
     ///
@@ -312,9 +384,9 @@ class SliceTraits {
     /// \param dtype: The type of the data, in case it cannot be automatically guessed
     /// \param xfer_props: Data Transfer properties
     template <typename T>
-    void read(T* array,
-              const DataType& dtype,
-              const DataTransferProps& xfer_props = DataTransferProps()) const;
+    void read_raw(T* array,
+                  const DataType& dtype,
+                  const DataTransferProps& xfer_props = DataTransferProps()) const;
 
     ///
     /// Read the entire dataset into a raw buffer
@@ -326,7 +398,8 @@ class SliceTraits {
     /// \param array: A buffer containing enough space for the data
     /// \param xfer_props: Data Transfer properties
     template <typename T>
-    void read(T* array, const DataTransferProps& xfer_props = DataTransferProps()) const;
+    void read_raw(T* array, const DataTransferProps& xfer_props = DataTransferProps()) const;
+
 
     ///
     /// Write the integrality N-dimension buffer to this dataset
@@ -366,6 +439,28 @@ class SliceTraits {
     ///
     template <typename T>
     void write_raw(const T* buffer, const DataTransferProps& xfer_props = DataTransferProps());
+
+    ///
+    /// \brief Return a `Selection` with `axes` squeezed from the memspace.
+    ///
+    /// Returns a selection in which the memspace has been modified
+    /// to not include the axes listed in `axes`.
+    ///
+    /// Throws if any axis to be squeezes has a dimension other than `1`.
+    ///
+    /// \since 3.0
+    Selection squeezeMemSpace(const std::vector<size_t>& axes) const;
+
+    ///
+    /// \brief Return a `Selection` with a simple memspace with `dims`.
+    ///
+    /// Returns a selection in which the memspace has been modified
+    /// to be a simple dataspace with dimensions `dims`.
+    ///
+    /// Throws if the number of elements changes.
+    ///
+    /// \since 3.0
+    Selection reshapeMemSpace(const std::vector<size_t>& dims) const;
 };
 
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp b/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp
index dd7e45b1d4682c3da46274695fb477bfceca4bfd..c1bc9c9ea1751217336dbf591767061859a8b673 100644
--- a/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Slice_traits_misc.hpp
@@ -20,6 +20,9 @@
 
 #include "H5ReadWrite_misc.hpp"
 #include "H5Converter_misc.hpp"
+#include "squeeze.hpp"
+#include "compute_total_size.hpp"
+#include "assert_compatible_spaces.hpp"
 
 namespace HighFive {
 
@@ -63,6 +66,153 @@ inline ElementSet::ElementSet(const std::vector<std::vector<std::size_t>>& eleme
     }
 }
 
+namespace detail {
+class HyperCube {
+  public:
+    HyperCube(size_t rank)
+        : offset(rank)
+        , count(rank) {}
+
+    void cross(const std::array<size_t, 2>& range, size_t axis) {
+        offset[axis] = range[0];
+        count[axis] = range[1] - range[0];
+    }
+
+    RegularHyperSlab asSlab() {
+        return RegularHyperSlab(offset, count);
+    }
+
+  private:
+    std::vector<size_t> offset;
+    std::vector<size_t> count;
+};
+
+inline void build_hyper_slab(HyperSlab& slab, size_t /* axis */, HyperCube& cube) {
+    slab |= cube.asSlab();
+}
+
+template <class... Slices>
+inline void build_hyper_slab(HyperSlab& slab,
+                             size_t axis,
+                             HyperCube& cube,
+                             const std::array<size_t, 2>& slice,
+                             const Slices&... higher_slices) {
+    cube.cross(slice, axis);
+    build_hyper_slab(slab, axis + 1, cube, higher_slices...);
+}
+
+template <class... Slices>
+inline void build_hyper_slab(HyperSlab& slab,
+                             size_t axis,
+                             HyperCube& cube,
+                             const std::vector<std::array<size_t, 2>>& slices,
+                             const Slices&... higher_slices) {
+    for (const auto& slice: slices) {
+        build_hyper_slab(slab, axis, cube, slice, higher_slices...);
+    }
+}
+
+template <class... Slices>
+inline void build_hyper_slab(HyperSlab& slab,
+                             size_t axis,
+                             HyperCube& cube,
+                             const std::vector<size_t>& ids,
+                             const Slices&... higher_slices) {
+    for (const auto& id: ids) {
+        auto slice = std::array<size_t, 2>{id, id + 1};
+        build_hyper_slab(slab, axis, cube, slice, higher_slices...);
+    }
+}
+
+template <class... Slices>
+inline void build_hyper_slab(HyperSlab& slab,
+                             size_t axis,
+                             HyperCube& cube,
+                             size_t id,
+                             const Slices&... higher_slices) {
+    auto slice = std::array<size_t, 2>{id, id + 1};
+    build_hyper_slab(slab, axis, cube, slice, higher_slices...);
+}
+
+inline void compute_squashed_shape(size_t /* axis */, std::vector<size_t>& /* shape */) {
+    // assert(axis == shape.size());
+}
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   const std::array<size_t, 2>& slice,
+                                   const Slices&... higher_slices);
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   const std::vector<size_t>& points,
+                                   const Slices&... higher_slices);
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   size_t point,
+                                   const Slices&... higher_slices);
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   const std::vector<std::array<size_t, 2>>& slices,
+                                   const Slices&... higher_slices);
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   const std::array<size_t, 2>& slice,
+                                   const Slices&... higher_slices) {
+    shape[axis] = slice[1] - slice[0];
+    compute_squashed_shape(axis + 1, shape, higher_slices...);
+}
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   const std::vector<size_t>& points,
+                                   const Slices&... higher_slices) {
+    shape[axis] = points.size();
+    compute_squashed_shape(axis + 1, shape, higher_slices...);
+}
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   const std::vector<std::array<size_t, 2>>& slices,
+                                   const Slices&... higher_slices) {
+    shape[axis] = 0;
+    for (const auto& slice: slices) {
+        shape[axis] += slice[1] - slice[0];
+    }
+    compute_squashed_shape(axis + 1, shape, higher_slices...);
+}
+
+template <class... Slices>
+inline void compute_squashed_shape(size_t axis,
+                                   std::vector<size_t>& shape,
+                                   size_t /* point */,
+                                   const Slices&... higher_slices) {
+    shape[axis] = 1;
+    compute_squashed_shape(axis + 1, shape, higher_slices...);
+}
+}  // namespace detail
+
+template <class... Slices>
+inline ProductSet::ProductSet(const Slices&... slices) {
+    auto rank = sizeof...(slices);
+    detail::HyperCube cube(rank);
+    detail::build_hyper_slab(slab, 0, cube, slices...);
+
+    shape = std::vector<size_t>(rank, size_t(0));
+    detail::compute_squashed_shape(0, shape, slices...);
+}
+
+
 template <typename Derivate>
 inline Selection SliceTraits<Derivate>::select(const HyperSlab& hyperslab,
                                                const DataSpace& memspace) const {
@@ -154,6 +304,11 @@ inline Selection SliceTraits<Derivate>::select(const ElementSet& elements) const
     return detail::make_selection(DataSpace(num_elements), space, details::get_dataset(slice));
 }
 
+template <typename Derivate>
+inline Selection SliceTraits<Derivate>::select(const ProductSet& product_set) const {
+    return this->select(product_set.slab, DataSpace(product_set.shape));
+}
+
 
 template <typename Derivate>
 template <typename T>
@@ -177,16 +332,17 @@ inline void SliceTraits<Derivate>::read(T& array, const DataTransferProps& xfer_
         [&slice]() -> std::string { return details::get_dataset(slice).getPath(); },
         details::BufferInfo<T>::Operation::read);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
         ss << "Impossible to read DataSet of dimensions " << mem_space.getNumberDimensions()
-           << " into arrays of dimensions " << buffer_info.n_dimensions;
+           << " into arrays of dimensions: " << buffer_info.getMinRank() << "(min) to "
+           << buffer_info.getMaxRank() << "(max)";
         throw DataSpaceException(ss.str());
     }
     auto dims = mem_space.getDimensions();
 
     auto r = details::data_converter::get_reader<T>(dims, array, file_datatype);
-    read(r.getPointer(), buffer_info.data_type, xfer_props);
+    read_raw(r.getPointer(), buffer_info.data_type, xfer_props);
     // re-arrange results
     r.unserialize(array);
 
@@ -210,9 +366,9 @@ inline void SliceTraits<Derivate>::read(T& array, const DataTransferProps& xfer_
 
 template <typename Derivate>
 template <typename T>
-inline void SliceTraits<Derivate>::read(T* array,
-                                        const DataType& mem_datatype,
-                                        const DataTransferProps& xfer_props) const {
+inline void SliceTraits<Derivate>::read_raw(T* array,
+                                            const DataType& mem_datatype,
+                                            const DataTransferProps& xfer_props) const {
     static_assert(!std::is_const<T>::value,
                   "read() requires a non-const structure to read data into");
 
@@ -226,13 +382,14 @@ inline void SliceTraits<Derivate>::read(T* array,
                      static_cast<void*>(array));
 }
 
+
 template <typename Derivate>
 template <typename T>
-inline void SliceTraits<Derivate>::read(T* array, const DataTransferProps& xfer_props) const {
+inline void SliceTraits<Derivate>::read_raw(T* array, const DataTransferProps& xfer_props) const {
     using element_type = typename details::inspector<T>::base_type;
     const DataType& mem_datatype = create_and_check_datatype<element_type>();
 
-    read(array, mem_datatype, xfer_props);
+    read_raw(array, mem_datatype, xfer_props);
 }
 
 
@@ -241,6 +398,7 @@ template <typename T>
 inline void SliceTraits<Derivate>::write(const T& buffer, const DataTransferProps& xfer_props) {
     const auto& slice = static_cast<const Derivate&>(*this);
     const DataSpace& mem_space = slice.getMemSpace();
+    auto dims = mem_space.getDimensions();
 
     auto file_datatype = slice.getDataType();
 
@@ -249,14 +407,14 @@ inline void SliceTraits<Derivate>::write(const T& buffer, const DataTransferProp
         [&slice]() -> std::string { return details::get_dataset(slice).getPath(); },
         details::BufferInfo<T>::Operation::write);
 
-    if (!details::checkDimensions(mem_space, buffer_info.n_dimensions)) {
+    if (!details::checkDimensions(mem_space, buffer_info.getMinRank(), buffer_info.getMaxRank())) {
         std::ostringstream ss;
-        ss << "Impossible to write buffer of dimensions "
-           << details::format_vector(mem_space.getDimensions())
-           << " into dataset with n = " << buffer_info.n_dimensions << " dimensions.";
+        ss << "Impossible to write buffer with dimensions n = " << buffer_info.getRank(buffer)
+           << "into dataset with dimensions " << details::format_vector(mem_space.getDimensions())
+           << ".";
         throw DataSpaceException(ss.str());
     }
-    auto w = details::data_converter::serialize<T>(buffer, file_datatype);
+    auto w = details::data_converter::serialize<T>(buffer, dims, file_datatype);
     write_raw(w.getPointer(), buffer_info.data_type, xfer_props);
 }
 
@@ -276,6 +434,7 @@ inline void SliceTraits<Derivate>::write_raw(const T* buffer,
                       static_cast<const void*>(buffer));
 }
 
+
 template <typename Derivate>
 template <typename T>
 inline void SliceTraits<Derivate>::write_raw(const T* buffer, const DataTransferProps& xfer_props) {
@@ -285,5 +444,34 @@ inline void SliceTraits<Derivate>::write_raw(const T* buffer, const DataTransfer
     write_raw(buffer, mem_datatype, xfer_props);
 }
 
+namespace detail {
+inline const DataSet& getDataSet(const Selection& selection) {
+    return selection.getDataset();
+}
+
+inline const DataSet& getDataSet(const DataSet& dataset) {
+    return dataset;
+}
+
+}  // namespace detail
+
+template <typename Derivate>
+inline Selection SliceTraits<Derivate>::squeezeMemSpace(const std::vector<size_t>& axes) const {
+    auto slice = static_cast<const Derivate&>(*this);
+    auto mem_dims = slice.getMemSpace().getDimensions();
+    auto squeezed_dims = detail::squeeze(mem_dims, axes);
+
+    return detail::make_selection(DataSpace(squeezed_dims),
+                                  slice.getSpace(),
+                                  detail::getDataSet(slice));
+}
+
+template <typename Derivate>
+inline Selection SliceTraits<Derivate>::reshapeMemSpace(const std::vector<size_t>& new_dims) const {
+    auto slice = static_cast<const Derivate&>(*this);
+
+    detail::assert_compatible_spaces(slice.getMemSpace(), new_dims);
+    return detail::make_selection(DataSpace(new_dims), slice.getSpace(), detail::getDataSet(slice));
+}
 
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/H5Utils.hpp b/packages/HighFive/include/highfive/bits/H5Utils.hpp
index b3f039e20d29377269a12cfe6126df7bb3027ed6..c005bd22bd5f84c3221bb7dea1ed04a22ed7e913 100644
--- a/packages/HighFive/include/highfive/bits/H5Utils.hpp
+++ b/packages/HighFive/include/highfive/bits/H5Utils.hpp
@@ -25,12 +25,6 @@
 
 namespace HighFive {
 
-namespace deprecated {
-// If ever used, recognize dimensions of FixedLenStringArray
-template <std::size_t N>
-class FixedLenStringArray;
-}  // namespace deprecated
-
 namespace details {
 // converter function for hsize_t -> size_t when hsize_t != size_t
 template <typename Size>
diff --git a/packages/HighFive/include/highfive/bits/H5_definitions.hpp b/packages/HighFive/include/highfive/bits/H5_definitions.hpp
index ad4b95af2a9af52f92e14196eff5247c848ab96e..56993c855e6cfe0a57d656725ce7d590c1576c6e 100644
--- a/packages/HighFive/include/highfive/bits/H5_definitions.hpp
+++ b/packages/HighFive/include/highfive/bits/H5_definitions.hpp
@@ -45,11 +45,6 @@ class AtomicType;
 template <typename Derivate>
 class AnnotateTraits;
 
-namespace deprecated {
-template <std::size_t N>
-class FixedLenStringArray;
-}
-
 template <typename Derivate>
 class NodeTraits;
 
diff --git a/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp b/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4be279d46ea5aec9a9159eb4345ba8f2ced2587
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/assert_compatible_spaces.hpp
@@ -0,0 +1,29 @@
+/*
+ *  Copyright (c), 2024, BlueBrain Project, EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+#pragma once
+
+#include <vector>
+#include "../H5Exception.hpp"
+#include "../H5DataSpace.hpp"
+
+namespace HighFive {
+namespace detail {
+
+inline void assert_compatible_spaces(const DataSpace& old, const std::vector<size_t>& dims) {
+    auto n_elements_old = old.getElementCount();
+    auto n_elements_new = dims.size() == 0 ? 1 : compute_total_size(dims);
+
+    if (n_elements_old != n_elements_new) {
+        throw Exception("Invalid parameter `new_dims` number of elements differ: " +
+                        std::to_string(n_elements_old) + " (old) vs. " +
+                        std::to_string(n_elements_new) + " (new)");
+    }
+}
+}  // namespace detail
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/compute_total_size.hpp b/packages/HighFive/include/highfive/bits/compute_total_size.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5be8a599984bcbd93dec73a772462fe852ebaceb
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/compute_total_size.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <cstddef>
+#include <numeric>
+#include <functional>
+#include <vector>
+
+namespace HighFive {
+
+inline size_t compute_total_size(const std::vector<size_t>& dims) {
+    return std::accumulate(dims.begin(), dims.end(), size_t{1u}, std::multiplies<size_t>());
+}
+
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/convert_size_vector.hpp b/packages/HighFive/include/highfive/bits/convert_size_vector.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..62a815ae57fae76d2ccc69e65d3cca5ef295de76
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/convert_size_vector.hpp
@@ -0,0 +1,31 @@
+/*
+ *  Copyright (c), 2017, Adrien Devresse <adrien.devresse@epfl.ch>
+ *  Copyright (c), 2017-2024, BlueBrain Project, EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+#pragma once
+
+#include <vector>
+
+namespace HighFive {
+namespace detail {
+
+template <class To, class From, class It = From const*>
+inline std::vector<To> convertSizeVector(const It& begin, const It& end) {
+    std::vector<To> to(static_cast<size_t>(end - begin));
+    std::copy(begin, end, to.begin());
+
+    return to;
+}
+
+template <class To, class From>
+inline std::vector<To> convertSizeVector(const std::vector<From>& from) {
+    return convertSizeVector<To, From>(from.cbegin(), from.cend());
+}
+
+}  // namespace detail
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp b/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp
index 75b91bb6af950232f27ad64a2315d021a01a1526..df97c3ca183ea20b252240c5adac93ba43ba4359 100644
--- a/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp
+++ b/packages/HighFive/include/highfive/bits/h5o_wrapper.hpp
@@ -15,5 +15,14 @@ inline hid_t h5o_open(hid_t loc_id, const char* name, hid_t lapl_id) {
     return hid;
 }
 
+inline herr_t h5o_close(hid_t id) {
+    herr_t err = H5Oclose(id);
+    if (err < 0) {
+        HDF5ErrMapper::ToException<ObjectException>("Unable to close object.");
+    }
+
+    return err;
+}
+
 }  // namespace detail
 }  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/bits/squeeze.hpp b/packages/HighFive/include/highfive/bits/squeeze.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4be610e34f91c07168911202b93caa8a8c410eb7
--- /dev/null
+++ b/packages/HighFive/include/highfive/bits/squeeze.hpp
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c), 2024, BlueBrain Project, EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+#pragma once
+
+#include <vector>
+#include "../H5Exception.hpp"
+
+namespace HighFive {
+namespace detail {
+
+/// \brief Squeeze `axes` from `dims`.
+///
+/// An axis can only be squeezed if it's dimension is `1`. The elements of
+/// `axes` must be in the range `0, ..., dims.size()` (exclusive) and don't
+/// have to be sorted.
+///
+/// Example:
+///   squeeze({1, 3, 2, 1}, {0, 3}) == {3, 2}
+inline std::vector<size_t> squeeze(const std::vector<size_t>& dims,
+                                   const std::vector<size_t>& axes) {
+    auto n_dims = dims.size();
+    auto mask = std::vector<bool>(n_dims, false);
+    for (size_t i = 0; i < axes.size(); ++i) {
+        if (axes[i] >= n_dims) {
+            throw Exception("Out of range: axes[" + std::to_string(i) +
+                            "] == " + std::to_string(axes[i]) + " >= " + std::to_string(n_dims));
+        }
+
+        mask[axes[i]] = true;
+    }
+
+    auto squeezed_dims = std::vector<size_t>{};
+    for (size_t i = 0; i < n_dims; ++i) {
+        if (!mask[i]) {
+            squeezed_dims.push_back(dims[i]);
+        } else {
+            if (dims[i] != 1) {
+                throw Exception("Squeezing non-unity axis: axes[" + std::to_string(i) +
+                                "] = " + std::to_string(axes[i]));
+            }
+        }
+    }
+
+    return squeezed_dims;
+}
+
+}  // namespace detail
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/boost.hpp b/packages/HighFive/include/highfive/boost.hpp
index 8992159a2df1fca5a4bcbefaed2fd5e3dfcf5b2e..33c1458df0735c4c7061196a7b417f93c6050139 100644
--- a/packages/HighFive/include/highfive/boost.hpp
+++ b/packages/HighFive/include/highfive/boost.hpp
@@ -1,12 +1,9 @@
 #pragma once
-#ifdef H5_USE_BOOST
 
 #include "bits/H5Inspector_decl.hpp"
 #include "H5Exception.hpp"
 
 #include <boost/multi_array.hpp>
-// starting Boost 1.64, serialization header must come before ublas
-#include <boost/serialization/vector.hpp>
 #include <boost/numeric/ublas/matrix.hpp>
 
 namespace HighFive {
@@ -20,28 +17,34 @@ struct inspector<boost::multi_array<T, Dims>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = Dims;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val.data()[0]);
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
-        std::vector<size_t> sizes;
+        auto rank = getRank(val);
+        std::vector<size_t> sizes(rank, 1ul);
         for (size_t i = 0; i < ndim; ++i) {
-            sizes.push_back(val.shape()[i]);
+            sizes[i] = val.shape()[i];
+        }
+        if (val.size() != 0) {
+            auto s = inspector<value_type>::getDimensions(val.data()[0]);
+            sizes.resize(ndim + s.size());
+            for (size_t i = 0; i < s.size(); ++i) {
+                sizes[ndim + i] = s[i];
+            }
         }
-        auto s = inspector<value_type>::getDimensions(val.data()[0]);
-        sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return compute_total_size(dims);
-    }
-
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims.size() < ndim) {
             std::ostringstream os;
@@ -62,25 +65,36 @@ struct inspector<boost::multi_array<T, Dims>> {
         }
     }
 
+    static void assert_c_order(const type& val) {
+        if (!(val.storage_order() == boost::c_storage_order())) {
+            throw DataTypeException("Only C storage order is supported for 'boost::multi_array'.");
+        }
+    }
+
     static hdf5_type* data(type& val) {
+        assert_c_order(val);
         return inspector<value_type>::data(*val.data());
     }
 
     static const hdf5_type* data(const type& val) {
+        assert_c_order(val);
         return inspector<value_type>::data(*val.data());
     }
 
     template <class It>
-    static void serialize(const type& val, It m) {
+    static void serialize(const type& val, const std::vector<size_t>& dims, It m) {
+        assert_c_order(val);
         size_t size = val.num_elements();
-        size_t subsize = inspector<value_type>::getSizeVal(*val.origin());
+        auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
+        size_t subsize = compute_total_size(subdims);
         for (size_t i = 0; i < size; ++i) {
-            inspector<value_type>::serialize(*(val.origin() + i), m + i * subsize);
+            inspector<value_type>::serialize(*(val.origin() + i), subdims, m + i * subsize);
         }
     }
 
     template <class It>
     static void unserialize(It vec_align, const std::vector<size_t>& dims, type& val) {
+        assert_c_order(val);
         std::vector<size_t> next_dims(dims.begin() + ndim, dims.end());
         size_t subsize = compute_total_size(next_dims);
         for (size_t i = 0; i < val.num_elements(); ++i) {
@@ -99,9 +113,16 @@ struct inspector<boost::numeric::ublas::matrix<T>> {
     using hdf5_type = typename inspector<value_type>::hdf5_type;
 
     static constexpr size_t ndim = 2;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
     static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
                                                   inspector<value_type>::is_trivially_copyable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val(0, 0));
+    }
 
     static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{val.size1(), val.size2()};
@@ -110,14 +131,6 @@ struct inspector<boost::numeric::ublas::matrix<T>> {
         return sizes;
     }
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return compute_total_size(dims);
-    }
-
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims.size() < ndim) {
             std::ostringstream os;
@@ -136,11 +149,12 @@ struct inspector<boost::numeric::ublas::matrix<T>> {
         return inspector<value_type>::data(val(0, 0));
     }
 
-    static void serialize(const type& val, hdf5_type* m) {
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
         size_t size = val.size1() * val.size2();
-        size_t subsize = inspector<value_type>::getSizeVal(val(0, 0));
+        auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
+        size_t subsize = compute_total_size(subdims);
         for (size_t i = 0; i < size; ++i) {
-            inspector<value_type>::serialize(*(&val(0, 0) + i), m + i * subsize);
+            inspector<value_type>::serialize(*(&val(0, 0) + i), subdims, m + i * subsize);
         }
     }
 
@@ -160,5 +174,3 @@ struct inspector<boost::numeric::ublas::matrix<T>> {
 
 }  // namespace details
 }  // namespace HighFive
-
-#endif
diff --git a/packages/HighFive/include/highfive/eigen.hpp b/packages/HighFive/include/highfive/eigen.hpp
index c47095ddeeea3698ae76366d7c72fb443450241d..462769e4bf25831c991a707a486d0b285bc9ff15 100644
--- a/packages/HighFive/include/highfive/eigen.hpp
+++ b/packages/HighFive/include/highfive/eigen.hpp
@@ -1,93 +1,161 @@
 #pragma once
-#ifdef H5_USE_EIGEN
 
 #include "bits/H5Inspector_decl.hpp"
 #include "H5Exception.hpp"
 
-#include <Eigen/Eigen>
-
+#include <Eigen/Core>
+#include <Eigen/Dense>
 
 namespace HighFive {
 namespace details {
 
-template <typename T, int M, int N>
-struct inspector<Eigen::Matrix<T, M, N>> {
-    using type = Eigen::Matrix<T, M, N>;
-    using value_type = T;
+template <class EigenType>
+struct eigen_inspector {
+    using type = EigenType;
+    using value_type = typename EigenType::Scalar;
     using base_type = typename inspector<value_type>::base_type;
     using hdf5_type = base_type;
 
-    static constexpr size_t ndim = 2;
-    static constexpr size_t recursive_ndim = ndim + inspector<value_type>::recursive_ndim;
-    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
-                                                  inspector<value_type>::is_trivially_copyable;
 
+    static_assert(int(EigenType::ColsAtCompileTime) == int(EigenType::MaxColsAtCompileTime),
+                  "Padding isn't supported.");
+    static_assert(int(EigenType::RowsAtCompileTime) == int(EigenType::MaxRowsAtCompileTime),
+                  "Padding isn't supported.");
 
-    static void assert_not_buggy(Eigen::Index nrows, Eigen::Index ncols) {
-        if (nrows > 1 && ncols > 1) {
-            throw std::runtime_error(
-                "HighFive has been broken for Eigen::Matrix. Please check "
-                "https://github.com/BlueBrain/HighFive/issues/532.");
-        }
+    static constexpr bool is_row_major() {
+        return EigenType::ColsAtCompileTime == 1 || EigenType::RowsAtCompileTime == 1 ||
+               EigenType::IsRowMajor;
     }
 
-    static std::vector<size_t> getDimensions(const type& val) {
-        assert_not_buggy(val.rows(), val.cols());
 
+    static constexpr size_t ndim = 2;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+    static constexpr bool is_trivially_copyable = is_row_major() &&
+                                                  std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        return ndim + inspector<value_type>::getRank(val.data()[0]);
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
         std::vector<size_t> sizes{static_cast<size_t>(val.rows()), static_cast<size_t>(val.cols())};
         auto s = inspector<value_type>::getDimensions(val.data()[0]);
         sizes.insert(sizes.end(), s.begin(), s.end());
         return sizes;
     }
 
-    static size_t getSizeVal(const type& val) {
-        return compute_total_size(getDimensions(val));
-    }
-
-    static size_t getSize(const std::vector<size_t>& dims) {
-        return compute_total_size(dims);
-    }
-
     static void prepare(type& val, const std::vector<size_t>& dims) {
         if (dims[0] != static_cast<size_t>(val.rows()) ||
             dims[1] != static_cast<size_t>(val.cols())) {
             val.resize(static_cast<typename type::Index>(dims[0]),
                        static_cast<typename type::Index>(dims[1]));
         }
-
-        assert_not_buggy(val.rows(), val.cols());
     }
 
     static hdf5_type* data(type& val) {
-        assert_not_buggy(val.rows(), val.cols());
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
+        }
+
         return inspector<value_type>::data(*val.data());
     }
 
     static const hdf5_type* data(const type& val) {
-        assert_not_buggy(val.rows(), val.cols());
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
+        }
+
         return inspector<value_type>::data(*val.data());
     }
 
-    static void serialize(const type& val, hdf5_type* m) {
-        assert_not_buggy(val.rows(), val.cols());
-        std::memcpy(m, val.data(), static_cast<size_t>(val.size()) * sizeof(hdf5_type));
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
+        Eigen::Index n_rows = val.rows();
+        Eigen::Index n_cols = val.cols();
+
+        auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
+        auto subsize = compute_total_size(subdims);
+        for (Eigen::Index i = 0; i < n_rows; ++i) {
+            for (Eigen::Index j = 0; j < n_cols; ++j) {
+                inspector<value_type>::serialize(val(i, j), dims, m);
+                m += subsize;
+            }
+        }
     }
 
     static void unserialize(const hdf5_type* vec_align,
                             const std::vector<size_t>& dims,
                             type& val) {
-        assert_not_buggy(val.rows(), val.cols());
         if (dims.size() < 2) {
             std::ostringstream os;
             os << "Impossible to pair DataSet with " << dims.size()
                << " dimensions into an eigen-matrix.";
             throw DataSpaceException(os.str());
         }
-        std::memcpy(val.data(), vec_align, compute_total_size(dims) * sizeof(hdf5_type));
+
+        auto n_rows = static_cast<Eigen::Index>(dims[0]);
+        auto n_cols = static_cast<Eigen::Index>(dims[1]);
+
+        auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
+        auto subsize = compute_total_size(subdims);
+        for (Eigen::Index i = 0; i < n_rows; ++i) {
+            for (Eigen::Index j = 0; j < n_cols; ++j) {
+                inspector<value_type>::unserialize(vec_align, subdims, val(i, j));
+                vec_align += subsize;
+            }
+        }
+    }
+};
+
+template <typename T, int M, int N, int Options>
+struct inspector<Eigen::Matrix<T, M, N, Options>>
+    : public eigen_inspector<Eigen::Matrix<T, M, N, Options>> {
+  private:
+    using super = eigen_inspector<Eigen::Matrix<T, M, N, Options>>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <typename T, int M, int N, int Options>
+struct inspector<Eigen::Array<T, M, N, Options>>
+    : public eigen_inspector<Eigen::Array<T, M, N, Options>> {
+  private:
+    using super = eigen_inspector<Eigen::Array<T, M, N, Options>>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+
+template <typename PlainObjectType, int MapOptions>
+struct inspector<Eigen::Map<PlainObjectType, MapOptions>>
+    : public eigen_inspector<Eigen::Map<PlainObjectType, MapOptions>> {
+  private:
+    using super = eigen_inspector<Eigen::Map<PlainObjectType, MapOptions>>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+
+    static void prepare(type& val, const std::vector<size_t>& dims) {
+        if (dims[0] != static_cast<size_t>(val.rows()) ||
+            dims[1] != static_cast<size_t>(val.cols())) {
+            throw DataSetException("Eigen::Map has invalid shape and can't be resized.");
+        }
     }
 };
 
+
 }  // namespace details
 }  // namespace HighFive
-
-#endif
diff --git a/packages/HighFive/include/highfive/experimental/opencv.hpp b/packages/HighFive/include/highfive/experimental/opencv.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..224160975afb4a2403aa79a7953f6335106bc1a1
--- /dev/null
+++ b/packages/HighFive/include/highfive/experimental/opencv.hpp
@@ -0,0 +1,149 @@
+#pragma once
+
+#include "../bits/H5Inspector_decl.hpp"
+#include "../H5Exception.hpp"
+
+#include <opencv2/opencv.hpp>
+
+#include "../bits/convert_size_vector.hpp"
+
+namespace HighFive {
+namespace details {
+
+
+template <class T>
+struct inspector<cv::Mat_<T>> {
+    using type = cv::Mat_<T>;
+    using value_type = T;
+    using base_type = typename inspector<value_type>::base_type;
+    using hdf5_type = base_type;
+
+    static void assert_row_major(const type& type) {
+        // Documentation claims that Mat_ is always row-major. However, it
+        // could be padded. The steps/strides are in bytes.
+        int rank = type.dims;
+        size_t ld = sizeof(T);
+        for (int i = rank - 1; i >= 0; --i) {
+            if (static_cast<size_t>(type.step[i]) != ld) {
+                throw DataSetException("Padded cv::Mat_ are not supported.");
+            }
+
+            ld *= static_cast<size_t>(type.size[i]);
+        }
+    }
+
+
+    static constexpr size_t min_ndim = 2 + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = 1024 + inspector<value_type>::max_ndim;
+
+    // HighFive doesn't support padded OpenCV arrays. Therefore, pretend
+    // that they themselves are trivially copyable. And error out if the
+    // assumption is violated.
+    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        if (val.empty()) {
+            return min_ndim;
+
+        } else {
+            return static_cast<size_t>(val.dims) +
+                   inspector<value_type>::getRank(getAnyElement(val));
+        }
+    }
+
+    static const T& getAnyElement(const type& val) {
+        return *reinterpret_cast<T const*>(val.data);
+    }
+
+    static T& getAnyElement(type& val) {
+        return *reinterpret_cast<T*>(val.data);
+    }
+
+    static size_t getLocalRank(const type& val) {
+        return static_cast<size_t>(val.dims);
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
+        auto local_rank = getLocalRank(val);
+        auto rank = getRank(val);
+        std::vector<size_t> dims(rank, 1ul);
+
+        if (val.empty()) {
+            dims[0] = 0ul;
+            dims[1] = 1ul;
+            return dims;
+        }
+
+        for (size_t i = 0; i < local_rank; ++i) {
+            dims[i] = static_cast<size_t>(val.size[static_cast<int>(i)]);
+        }
+
+        auto s = inspector<value_type>::getDimensions(getAnyElement(val));
+        std::copy(s.cbegin(), s.cend(), dims.begin() + static_cast<int>(local_rank));
+        return dims;
+    }
+
+    static void prepare(type& val, const std::vector<size_t>& dims) {
+        auto subdims = detail::convertSizeVector<int>(dims);
+        val.create(static_cast<int>(subdims.size()), subdims.data());
+    }
+
+    static hdf5_type* data(type& val) {
+        assert_row_major(val);
+
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
+        }
+
+        if (val.empty()) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static const hdf5_type* data(const type& val) {
+        assert_row_major(val);
+
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<Eigen::Matrix<...>>::data`.");
+        }
+
+        if (val.empty()) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
+        if (val.empty()) {
+            return;
+        }
+
+        auto local_rank = val.dims;
+        auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end());
+        auto subsize = compute_total_size(subdims);
+        for (auto it = val.begin(); it != val.end(); ++it) {
+            inspector<value_type>::serialize(*it, subdims, m);
+            m += subsize;
+        }
+    }
+
+    static void unserialize(const hdf5_type* vec_align,
+                            const std::vector<size_t>& dims,
+                            type& val) {
+        auto local_rank = val.dims;
+        auto subdims = std::vector<size_t>(dims.begin() + local_rank, dims.end());
+        auto subsize = compute_total_size(subdims);
+        for (auto it = val.begin(); it != val.end(); ++it) {
+            inspector<value_type>::unserialize(vec_align, subdims, *it);
+            vec_align += subsize;
+        }
+    }
+};
+
+}  // namespace details
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_Eigen.hpp b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_Eigen.hpp
index 5b5d3b9a5f45e72e832eba821e3ab993b2997250..794725436d5a63703e07a1680c3152d2d4e31419 100644
--- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_Eigen.hpp
+++ b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_Eigen.hpp
@@ -14,66 +14,48 @@
 
 #ifdef H5_USE_EIGEN
 
+#include "../eigen.hpp"
+
 namespace H5Easy {
 
 namespace detail {
 
 template <typename T>
 struct io_impl<T, typename std::enable_if<std::is_base_of<Eigen::DenseBase<T>, T>::value>::type> {
-    // abbreviate row-major <-> col-major conversions
-    template <typename S>
-    struct types {
-        using row_major = Eigen::Ref<
-            const Eigen::Array<typename std::decay<T>::type::Scalar,
-                               std::decay<T>::type::RowsAtCompileTime,
-                               std::decay<T>::type::ColsAtCompileTime,
-                               std::decay<T>::type::ColsAtCompileTime == 1 ? Eigen::ColMajor
-                                                                           : Eigen::RowMajor,
-                               std::decay<T>::type::MaxRowsAtCompileTime,
-                               std::decay<T>::type::MaxColsAtCompileTime>,
-            0,
-            Eigen::InnerStride<1>>;
-
-        using col_major =
-            Eigen::Map<Eigen::Array<typename std::decay<T>::type::Scalar,
-                                    std::decay<T>::type::RowsAtCompileTime,
-                                    std::decay<T>::type::ColsAtCompileTime,
-                                    std::decay<T>::type::ColsAtCompileTime == 1 ? Eigen::ColMajor
-                                                                                : Eigen::RowMajor,
-                                    std::decay<T>::type::MaxRowsAtCompileTime,
-                                    std::decay<T>::type::MaxColsAtCompileTime>>;
-    };
-
-    // return the shape of Eigen::DenseBase<T> object as size 1 or 2 "std::vector<size_t>"
-    inline static std::vector<size_t> shape(const T& data) {
+    using EigenIndex = Eigen::DenseIndex;
+
+    // When creating a dataset for an Eigen object, the shape of the dataset is
+    // 1D for vectors. (legacy reasons)
+    inline static std::vector<size_t> file_shape(const T& data) {
         if (std::decay<T>::type::RowsAtCompileTime == 1) {
             return {static_cast<size_t>(data.cols())};
         }
         if (std::decay<T>::type::ColsAtCompileTime == 1) {
             return {static_cast<size_t>(data.rows())};
         }
-        return {static_cast<size_t>(data.rows()), static_cast<size_t>(data.cols())};
+        return inspector<T>::getDimensions(data);
     }
 
-    using EigenIndex = Eigen::DenseIndex;
+    // The shape of an Eigen object as used in HighFive core.
+    inline static std::vector<size_t> mem_shape(const T& data) {
+        return inspector<T>::getDimensions(data);
+    }
 
-    // get the shape of a "DataSet" as size 2 "std::vector<Eigen::Index>"
+    // The shape of an Eigen object as used in HighFive core.
     template <class D>
-    inline static std::vector<EigenIndex> shape(const File& file,
+    inline static std::vector<size_t> mem_shape(const File& file,
                                                 const std::string& path,
-                                                const D& dataset,
-                                                int RowsAtCompileTime) {
+                                                const D& dataset) {
         std::vector<size_t> dims = dataset.getDimensions();
 
-        if (dims.size() == 1 && RowsAtCompileTime == 1) {
-            return std::vector<EigenIndex>{1u, static_cast<EigenIndex>(dims[0])};
+        if (dims.size() == 1 && T::RowsAtCompileTime == 1) {
+            return std::vector<size_t>{1, dims[0]};
         }
-        if (dims.size() == 1) {
-            return std::vector<EigenIndex>{static_cast<EigenIndex>(dims[0]), 1u};
+        if (dims.size() == 1 && T::ColsAtCompileTime == 1) {
+            return std::vector<size_t>{dims[0], 1};
         }
         if (dims.size() == 2) {
-            return std::vector<EigenIndex>{static_cast<EigenIndex>(dims[0]),
-                                           static_cast<EigenIndex>(dims[1])};
+            return dims;
         }
 
         throw detail::error(file, path, "H5Easy::load: Inconsistent rank");
@@ -83,11 +65,12 @@ struct io_impl<T, typename std::enable_if<std::is_base_of<Eigen::DenseBase<T>, T
                                const std::string& path,
                                const T& data,
                                const DumpOptions& options) {
-        using row_major_type = typename types<T>::row_major;
         using value_type = typename std::decay<T>::type::Scalar;
-        row_major_type row_major(data);
-        DataSet dataset = initDataset<value_type>(file, path, shape(data), options);
-        dataset.write_raw(row_major.data());
+
+        std::vector<size_t> file_dims = file_shape(data);
+        std::vector<size_t> mem_dims = mem_shape(data);
+        DataSet dataset = initDataset<value_type>(file, path, file_dims, options);
+        dataset.reshapeMemSpace(mem_dims).write(data);
         if (options.flush()) {
             file.flush();
         }
@@ -96,14 +79,8 @@ struct io_impl<T, typename std::enable_if<std::is_base_of<Eigen::DenseBase<T>, T
 
     inline static T load(const File& file, const std::string& path) {
         DataSet dataset = file.getDataSet(path);
-        std::vector<typename T::Index> dims = shape(file, path, dataset, T::RowsAtCompileTime);
-        T data(dims[0], dims[1]);
-        dataset.read(data.data());
-        if (data.IsVectorAtCompileTime || data.IsRowMajor) {
-            return data;
-        }
-        using col_major = typename types<T>::col_major;
-        return col_major(data.data(), dims[0], dims[1]);
+        std::vector<size_t> dims = mem_shape(file, path, dataset);
+        return dataset.reshapeMemSpace(dims).template read<T>();
     }
 
     inline static Attribute dumpAttribute(File& file,
@@ -111,11 +88,12 @@ struct io_impl<T, typename std::enable_if<std::is_base_of<Eigen::DenseBase<T>, T
                                           const std::string& key,
                                           const T& data,
                                           const DumpOptions& options) {
-        using row_major_type = typename types<T>::row_major;
         using value_type = typename std::decay<T>::type::Scalar;
-        row_major_type row_major(data);
-        Attribute attribute = initAttribute<value_type>(file, path, key, shape(data), options);
-        attribute.write_raw(row_major.data());
+
+        std::vector<size_t> file_dims = file_shape(data);
+        std::vector<size_t> mem_dims = mem_shape(data);
+        Attribute attribute = initAttribute<value_type>(file, path, key, file_dims, options);
+        attribute.reshapeMemSpace(mem_dims).write(data);
         if (options.flush()) {
             file.flush();
         }
@@ -128,14 +106,8 @@ struct io_impl<T, typename std::enable_if<std::is_base_of<Eigen::DenseBase<T>, T
         DataSet dataset = file.getDataSet(path);
         Attribute attribute = dataset.getAttribute(key);
         DataSpace dataspace = attribute.getSpace();
-        std::vector<typename T::Index> dims = shape(file, path, dataspace, T::RowsAtCompileTime);
-        T data(dims[0], dims[1]);
-        attribute.read(data.data());
-        if (data.IsVectorAtCompileTime || data.IsRowMajor) {
-            return data;
-        }
-        using col_major = typename types<T>::col_major;
-        return col_major(data.data(), dims[0], dims[1]);
+        std::vector<size_t> dims = mem_shape(file, path, dataspace);
+        return attribute.reshapeMemSpace(dims).template read<T>();
     }
 };
 
diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_opencv.hpp b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_opencv.hpp
deleted file mode 100644
index b640cd854362f3bb55af6edf26888d466a2f27ce..0000000000000000000000000000000000000000
--- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_opencv.hpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- *  Copyright (c), 2017, Adrien Devresse <adrien.devresse@epfl.ch>
- *
- *  Distributed under the Boost Software License, Version 1.0.
- *    (See accompanying file LICENSE_1_0.txt or copy at
- *          http://www.boost.org/LICENSE_1_0.txt)
- *
- */
-#pragma once
-
-#include "../H5Easy.hpp"
-#include "H5Easy_misc.hpp"
-#include "H5Easy_scalar.hpp"
-
-#ifdef H5_USE_OPENCV
-
-namespace H5Easy {
-
-namespace detail {
-
-template <class T>
-struct is_opencv: std::false_type {};
-template <class T>
-struct is_opencv<cv::Mat_<T>>: std::true_type {};
-
-template <typename T>
-struct io_impl<T, typename std::enable_if<is_opencv<T>::value>::type> {
-    inline static std::vector<size_t> shape(const T& data) {
-        return std::vector<size_t>{static_cast<size_t>(data.rows), static_cast<size_t>(data.cols)};
-    }
-
-    inline static std::vector<int> shape(const File& file,
-                                         const std::string& path,
-                                         std::vector<size_t> dims) {
-        if (dims.size() == 1) {
-            return std::vector<int>{static_cast<int>(dims[0]), 1ul};
-        }
-        if (dims.size() == 2) {
-            return std::vector<int>{static_cast<int>(dims[0]), static_cast<int>(dims[1])};
-        }
-
-        throw detail::error(file, path, "H5Easy::load: Inconsistent rank");
-    }
-
-    inline static DataSet dump(File& file,
-                               const std::string& path,
-                               const T& data,
-                               const DumpOptions& options) {
-        using value_type = typename T::value_type;
-        DataSet dataset = initDataset<value_type>(file, path, shape(data), options);
-        std::vector<value_type> v(data.begin(), data.end());
-        dataset.write_raw(v.data());
-        if (options.flush()) {
-            file.flush();
-        }
-        return dataset;
-    }
-
-    inline static T load(const File& file, const std::string& path) {
-        using value_type = typename T::value_type;
-        DataSet dataset = file.getDataSet(path);
-        std::vector<int> dims = shape(file, path, dataset.getDimensions());
-        T data(dims[0], dims[1]);
-        dataset.read(reinterpret_cast<value_type*>(data.data));
-        return data;
-    }
-
-    inline static Attribute dumpAttribute(File& file,
-                                          const std::string& path,
-                                          const std::string& key,
-                                          const T& data,
-                                          const DumpOptions& options) {
-        using value_type = typename T::value_type;
-        Attribute attribute = initAttribute<value_type>(file, path, key, shape(data), options);
-        std::vector<value_type> v(data.begin(), data.end());
-        attribute.write_raw(v.data());
-        if (options.flush()) {
-            file.flush();
-        }
-        return attribute;
-    }
-
-    inline static T loadAttribute(const File& file,
-                                  const std::string& path,
-                                  const std::string& key) {
-        using value_type = typename T::value_type;
-        DataSet dataset = file.getDataSet(path);
-        Attribute attribute = dataset.getAttribute(key);
-        DataSpace dataspace = attribute.getSpace();
-        std::vector<int> dims = shape(file, path, dataspace.getDimensions());
-        T data(dims[0], dims[1]);
-        attribute.read(reinterpret_cast<value_type*>(data.data));
-        return data;
-    }
-};
-
-}  // namespace detail
-}  // namespace H5Easy
-
-#endif  // H5_USE_OPENCV
diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_scalar.hpp b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_scalar.hpp
index 056d8f2dc5aaa57f9754f94efed25155d7b2341a..0dd42799e798d6d9ddfef0c8816c4fc4d2d600a3 100644
--- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_scalar.hpp
+++ b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_scalar.hpp
@@ -10,6 +10,7 @@
 
 #include "../H5Easy.hpp"
 #include "H5Easy_misc.hpp"
+#include "default_io_impl.hpp"
 
 namespace H5Easy {
 
@@ -20,49 +21,7 @@ Base template for partial specialization: the fallback if specialized templates
 Used e.g. for scalars.
 */
 template <typename T, typename = void>
-struct io_impl {
-    inline static DataSet dump(File& file,
-                               const std::string& path,
-                               const T& data,
-                               const DumpOptions& options) {
-        DataSet dataset = initScalarDataset(file, path, data, options);
-        dataset.write(data);
-        if (options.flush()) {
-            file.flush();
-        }
-        return dataset;
-    }
-
-    inline static T load(const File& file, const std::string& path) {
-        DataSet dataset = file.getDataSet(path);
-        T data;
-        dataset.read(data);
-        return data;
-    }
-
-    inline static Attribute dumpAttribute(File& file,
-                                          const std::string& path,
-                                          const std::string& key,
-                                          const T& data,
-                                          const DumpOptions& options) {
-        Attribute attribute = initScalarAttribute(file, path, key, data, options);
-        attribute.write(data);
-        if (options.flush()) {
-            file.flush();
-        }
-        return attribute;
-    }
-
-    inline static T loadAttribute(const File& file,
-                                  const std::string& path,
-                                  const std::string& key) {
-        DataSet dataset = file.getDataSet(path);
-        Attribute attribute = dataset.getAttribute(key);
-        T data;
-        attribute.read(data);
-        return data;
-    }
-
+struct io_impl: public default_io_impl<T> {
     inline static DataSet dump_extend(File& file,
                                       const std::string& path,
                                       const T& data,
@@ -93,7 +52,6 @@ struct io_impl {
             return dataset;
         }
 
-        std::vector<size_t> shape = idx;
         const size_t unlim = DataSpace::UNLIMITED;
         std::vector<size_t> unlim_shape(idx.size(), unlim);
         std::vector<hsize_t> chunks(idx.size(), 10);
@@ -103,8 +61,9 @@ struct io_impl {
                 throw error(file, path, "H5Easy::dump: Incorrect dimension ChunkSize");
             }
         }
-        for (size_t& i: shape) {
-            i++;
+        std::vector<size_t> shape(idx.size());
+        for (size_t i = 0; i < idx.size(); ++i) {
+            shape[i] = idx[i] + 1;
         }
         DataSpace dataspace = DataSpace(shape, unlim_shape);
         DataSetCreateProps props;
@@ -121,10 +80,7 @@ struct io_impl {
                               const std::string& path,
                               const std::vector<size_t>& idx) {
         std::vector<size_t> ones(idx.size(), 1);
-        DataSet dataset = file.getDataSet(path);
-        T data;
-        dataset.select(idx, ones).read(data);
-        return data;
+        return file.getDataSet(path).select(idx, ones).read<T>();
     }
 };
 
diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp b/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp
deleted file mode 100644
index 6b0238c4db86c2aa8b2a492825d7b4bc54a3b5d2..0000000000000000000000000000000000000000
--- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_xtensor.hpp
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- *  Copyright (c), 2017, Adrien Devresse <adrien.devresse@epfl.ch>
- *
- *  Distributed under the Boost Software License, Version 1.0.
- *    (See accompanying file LICENSE_1_0.txt or copy at
- *          http://www.boost.org/LICENSE_1_0.txt)
- *
- */
-#pragma once
-
-#include "../H5Easy.hpp"
-#include "H5Easy_misc.hpp"
-#include "H5Easy_scalar.hpp"
-
-#ifdef H5_USE_XTENSOR
-
-namespace H5Easy {
-
-namespace detail {
-
-template <typename T>
-struct io_impl<T, typename std::enable_if<xt::is_xexpression<T>::value>::type> {
-    inline static std::vector<size_t> shape(const T& data) {
-        return std::vector<size_t>(data.shape().cbegin(), data.shape().cend());
-    }
-
-    inline static DataSet dump(File& file,
-                               const std::string& path,
-                               const T& data,
-                               const DumpOptions& options) {
-        using value_type = typename std::decay_t<T>::value_type;
-        DataSet dataset = initDataset<value_type>(file, path, shape(data), options);
-        dataset.write_raw(data.data());
-        if (options.flush()) {
-            file.flush();
-        }
-        return dataset;
-    }
-
-    inline static T load(const File& file, const std::string& path) {
-        static_assert(
-            xt::has_data_interface<T>::value,
-            "Cannot load to xt::xfunction or xt::xgenerator, use e.g. xt::xtensor or xt::xarray");
-        DataSet dataset = file.getDataSet(path);
-        std::vector<size_t> dims = dataset.getDimensions();
-        T data = T::from_shape(dims);
-        dataset.read(data.data());
-        return data;
-    }
-
-    inline static Attribute dumpAttribute(File& file,
-                                          const std::string& path,
-                                          const std::string& key,
-                                          const T& data,
-                                          const DumpOptions& options) {
-        using value_type = typename std::decay_t<T>::value_type;
-        Attribute attribute = initAttribute<value_type>(file, path, key, shape(data), options);
-        attribute.write_raw(data.data());
-        if (options.flush()) {
-            file.flush();
-        }
-        return attribute;
-    }
-
-    inline static T loadAttribute(const File& file,
-                                  const std::string& path,
-                                  const std::string& key) {
-        static_assert(
-            xt::has_data_interface<T>::value,
-            "Cannot load to xt::xfunction or xt::xgenerator, use e.g. xt::xtensor or xt::xarray");
-        DataSet dataset = file.getDataSet(path);
-        Attribute attribute = dataset.getAttribute(key);
-        DataSpace dataspace = attribute.getSpace();
-        std::vector<size_t> dims = dataspace.getDimensions();
-        T data = T::from_shape(dims);
-        attribute.read(data.data());
-        return data;
-    }
-};
-
-}  // namespace detail
-}  // namespace H5Easy
-
-#endif  // H5_USE_XTENSOR
diff --git a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_vector.hpp b/packages/HighFive/include/highfive/h5easy_bits/default_io_impl.hpp
similarity index 68%
rename from packages/HighFive/include/highfive/h5easy_bits/H5Easy_vector.hpp
rename to packages/HighFive/include/highfive/h5easy_bits/default_io_impl.hpp
index 4c60f5cfc632d2f77ecd55c219114ad17404241d..ccb6e71504b2590a413314d50738605957c6a899 100644
--- a/packages/HighFive/include/highfive/h5easy_bits/H5Easy_vector.hpp
+++ b/packages/HighFive/include/highfive/h5easy_bits/default_io_impl.hpp
@@ -13,28 +13,22 @@
 #include "H5Easy_scalar.hpp"
 
 namespace H5Easy {
-
 namespace detail {
 
-template <class T>
-struct is_vector: std::false_type {};
-template <class T>
-struct is_vector<std::vector<T>>: std::true_type {};
-
 using HighFive::details::inspector;
 
 template <typename T>
-struct io_impl<T, typename std::enable_if<is_vector<T>::value>::type> {
+struct default_io_impl {
+    inline static std::vector<size_t> shape(const T& data) {
+        return inspector<T>::getDimensions(data);
+    }
+
     inline static DataSet dump(File& file,
                                const std::string& path,
                                const T& data,
                                const DumpOptions& options) {
         using value_type = typename inspector<T>::base_type;
-        auto dims = inspector<T>::getDimensions(data);
-        DataSet dataset = initDataset<value_type>(file,
-                                                  path,
-                                                  std::vector<size_t>(dims.begin(), dims.end()),
-                                                  options);
+        DataSet dataset = initDataset<value_type>(file, path, shape(data), options);
         dataset.write(data);
         if (options.flush()) {
             file.flush();
@@ -43,10 +37,7 @@ struct io_impl<T, typename std::enable_if<is_vector<T>::value>::type> {
     }
 
     inline static T load(const File& file, const std::string& path) {
-        DataSet dataset = file.getDataSet(path);
-        T data;
-        dataset.read(data);
-        return data;
+        return file.getDataSet(path).read<T>();
     }
 
     inline static Attribute dumpAttribute(File& file,
@@ -55,9 +46,7 @@ struct io_impl<T, typename std::enable_if<is_vector<T>::value>::type> {
                                           const T& data,
                                           const DumpOptions& options) {
         using value_type = typename inspector<T>::base_type;
-        auto dims = inspector<T>::getDimensions(data);
-        std::vector<size_t> shape(dims.begin(), dims.end());
-        Attribute attribute = initAttribute<value_type>(file, path, key, shape, options);
+        Attribute attribute = initAttribute<value_type>(file, path, key, shape(data), options);
         attribute.write(data);
         if (options.flush()) {
             file.flush();
@@ -70,9 +59,7 @@ struct io_impl<T, typename std::enable_if<is_vector<T>::value>::type> {
                                   const std::string& key) {
         DataSet dataset = file.getDataSet(path);
         Attribute attribute = dataset.getAttribute(key);
-        T data;
-        attribute.read(data);
-        return data;
+        return attribute.read<T>();
     }
 };
 
diff --git a/packages/HighFive/include/highfive/half_float.hpp b/packages/HighFive/include/highfive/half_float.hpp
index 998e693ffe37c15b8ebba08f83f13c521f46423b..dc2464c225398fb23ff241021dec25a7144f2392 100644
--- a/packages/HighFive/include/highfive/half_float.hpp
+++ b/packages/HighFive/include/highfive/half_float.hpp
@@ -1,5 +1,4 @@
 #pragma once
-#ifdef H5_USE_HALF_FLOAT
 
 #include <half.hpp>
 
@@ -16,6 +15,5 @@ inline AtomicType<float16_t>::AtomicType() {
     // Floating point exponent bias
     detail::h5t_set_ebias(_hid, 15);
 }
-}  // namespace HighFive
 
-#endif
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/highfive.hpp b/packages/HighFive/include/highfive/highfive.hpp
index f5e20cae9131ae7e6440f6fa7f16bc8b3430b336..543fe80bcfda4519fe33c8ac31bbf9ff65029fc3 100644
--- a/packages/HighFive/include/highfive/highfive.hpp
+++ b/packages/HighFive/include/highfive/highfive.hpp
@@ -5,7 +5,6 @@
 #include <highfive/H5DataSpace.hpp>
 #include <highfive/H5DataType.hpp>
 #include <highfive/H5File.hpp>
-#include <highfive/H5FileDriver.hpp>
 #include <highfive/H5Group.hpp>
 #include <highfive/H5PropertyList.hpp>
 #include <highfive/H5Reference.hpp>
diff --git a/packages/HighFive/include/highfive/span.hpp b/packages/HighFive/include/highfive/span.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ab53319ee657f59cdec2605f07eba9d23979bc44
--- /dev/null
+++ b/packages/HighFive/include/highfive/span.hpp
@@ -0,0 +1,102 @@
+/*
+ *  Copyright (c) 2024 Blue Brain Project
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+
+#pragma once
+
+#include "bits/H5Inspector_decl.hpp"
+#include "H5Exception.hpp"
+
+#include <span>
+
+namespace HighFive {
+namespace details {
+
+template <class T, std::size_t Extent>
+struct inspector<std::span<T, Extent>> {
+    using type = std::span<T, Extent>;
+    using value_type = unqualified_t<T>;
+    using base_type = typename inspector<value_type>::base_type;
+    using hdf5_type = typename inspector<value_type>::hdf5_type;
+
+    static constexpr size_t ndim = 1;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
+    static constexpr bool is_trivially_copyable = std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_nestable;
+    static constexpr bool is_trivially_nestable = false;
+
+
+    static size_t getRank(const type& val) {
+        if (!val.empty()) {
+            return ndim + inspector<value_type>::getRank(val[0]);
+        } else {
+            return min_ndim;
+        }
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
+        auto rank = getRank(val);
+        std::vector<size_t> sizes(rank, 1ul);
+        sizes[0] = val.size();
+        if (!val.empty()) {
+            auto s = inspector<value_type>::getDimensions(val[0]);
+            assert(s.size() + ndim == sizes.size());
+            for (size_t i = 0; i < s.size(); ++i) {
+                sizes[i + ndim] = s[i];
+            }
+        }
+        return sizes;
+    }
+
+    static void prepare(type& val, const std::vector<size_t>& expected_dims) {
+        auto actual_dims = getDimensions(val);
+        if (actual_dims.size() != expected_dims.size()) {
+            throw DataSpaceException("Mismatching rank.");
+        }
+
+        for (size_t i = 0; i < actual_dims.size(); ++i) {
+            if (actual_dims[i] != expected_dims[i]) {
+                throw DataSpaceException("Mismatching dimensions.");
+            }
+        }
+    }
+
+    static hdf5_type* data(type& val) {
+        return val.empty() ? nullptr : inspector<value_type>::data(val[0]);
+    }
+
+    static const hdf5_type* data(const type& val) {
+        return val.empty() ? nullptr : inspector<value_type>::data(val[0]);
+    }
+
+    template <class It>
+    static void serialize(const type& val, const std::vector<size_t>& dims, It m) {
+        if (!val.empty()) {
+            auto subdims = std::vector<size_t>(dims.begin() + ndim, dims.end());
+            size_t subsize = compute_total_size(subdims);
+            for (const auto& e: val) {
+                inspector<value_type>::serialize(e, subdims, m);
+                m += subsize;
+            }
+        }
+    }
+
+    template <class It>
+    static void unserialize(const It& vec_align, const std::vector<size_t>& dims, type& val) {
+        std::vector<size_t> subdims(dims.begin() + ndim, dims.end());
+        size_t subsize = compute_total_size(subdims);
+        for (size_t i = 0; i < dims[0]; ++i) {
+            inspector<value_type>::unserialize(vec_align + i * subsize, subdims, val[i]);
+        }
+    }
+};
+
+}  // namespace details
+}  // namespace HighFive
diff --git a/packages/HighFive/include/highfive/xtensor.hpp b/packages/HighFive/include/highfive/xtensor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..729a1e34951f0ca5e54c8e01dfff1d963ba6a902
--- /dev/null
+++ b/packages/HighFive/include/highfive/xtensor.hpp
@@ -0,0 +1,212 @@
+#pragma once
+
+#include "bits/H5Inspector_decl.hpp"
+#include "H5Exception.hpp"
+
+#include <xtensor/xtensor.hpp>
+#include <xtensor/xarray.hpp>
+#include <xtensor/xadapt.hpp>
+
+namespace HighFive {
+namespace details {
+
+template <class XTensor>
+struct xtensor_get_rank;
+
+template <typename T, size_t N, xt::layout_type L>
+struct xtensor_get_rank<xt::xtensor<T, N, L>> {
+    static constexpr size_t value = N;
+};
+
+template <class EC, size_t N, xt::layout_type L, class Tag>
+struct xtensor_get_rank<xt::xtensor_adaptor<EC, N, L, Tag>> {
+    static constexpr size_t value = N;
+};
+
+template <class Derived, class XTensorType, xt::layout_type L>
+struct xtensor_inspector_base {
+    using type = XTensorType;
+    using value_type = typename type::value_type;
+    using base_type = typename inspector<value_type>::base_type;
+    using hdf5_type = base_type;
+
+    static_assert(std::is_same<value_type, base_type>::value,
+                  "HighFive's XTensor support only works for scalar elements.");
+
+    static constexpr bool IsConstExprRowMajor = L == xt::layout_type::row_major;
+    static constexpr bool is_trivially_copyable = IsConstExprRowMajor &&
+                                                  std::is_trivially_copyable<value_type>::value &&
+                                                  inspector<value_type>::is_trivially_copyable;
+
+    static constexpr bool is_trivially_nestable = false;
+
+    static size_t getRank(const type& val) {
+        // Non-scalar elements are not supported.
+        return val.shape().size();
+    }
+
+    static const value_type& getAnyElement(const type& val) {
+        return val.unchecked(0);
+    }
+
+    static value_type& getAnyElement(type& val) {
+        return val.unchecked(0);
+    }
+
+    static std::vector<size_t> getDimensions(const type& val) {
+        auto shape = val.shape();
+        return {shape.begin(), shape.end()};
+    }
+
+    static void prepare(type& val, const std::vector<size_t>& dims) {
+        val.resize(Derived::shapeFromDims(dims));
+    }
+
+    static hdf5_type* data(type& val) {
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<XTensor>::data`.");
+        }
+
+        if (val.size() == 0) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static const hdf5_type* data(const type& val) {
+        if (!is_trivially_copyable) {
+            throw DataSetException("Invalid used of `inspector<XTensor>::data`.");
+        }
+
+        if (val.size() == 0) {
+            return nullptr;
+        }
+
+        return inspector<value_type>::data(getAnyElement(val));
+    }
+
+    static void serialize(const type& val, const std::vector<size_t>& dims, hdf5_type* m) {
+        // since we only support scalar types we know all dims belong to us.
+        size_t size = compute_total_size(dims);
+        xt::adapt(m, size, xt::no_ownership(), dims) = val;
+    }
+
+    static void unserialize(const hdf5_type* vec_align,
+                            const std::vector<size_t>& dims,
+                            type& val) {
+        // since we only support scalar types we know all dims belong to us.
+        size_t size = compute_total_size(dims);
+        val = xt::adapt(vec_align, size, xt::no_ownership(), dims);
+    }
+};
+
+template <class XTensorType, xt::layout_type L>
+struct xtensor_inspector
+    : public xtensor_inspector_base<xtensor_inspector<XTensorType, L>, XTensorType, L> {
+  private:
+    using super = xtensor_inspector_base<xtensor_inspector<XTensorType, L>, XTensorType, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+
+    static constexpr size_t ndim = xtensor_get_rank<XTensorType>::value;
+    static constexpr size_t min_ndim = ndim + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = ndim + inspector<value_type>::max_ndim;
+
+    static std::array<size_t, ndim> shapeFromDims(const std::vector<size_t>& dims) {
+        std::array<size_t, ndim> shape;
+        std::copy(dims.cbegin(), dims.cend(), shape.begin());
+        return shape;
+    }
+};
+
+template <class XArrayType, xt::layout_type L>
+struct xarray_inspector
+    : public xtensor_inspector_base<xarray_inspector<XArrayType, L>, XArrayType, L> {
+  private:
+    using super = xtensor_inspector_base<xarray_inspector<XArrayType, L>, XArrayType, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+
+    static constexpr size_t min_ndim = 0 + inspector<value_type>::min_ndim;
+    static constexpr size_t max_ndim = 1024 + inspector<value_type>::max_ndim;
+
+    static const std::vector<size_t>& shapeFromDims(const std::vector<size_t>& dims) {
+        return dims;
+    }
+};
+
+template <typename T, size_t N, xt::layout_type L>
+struct inspector<xt::xtensor<T, N, L>>: public xtensor_inspector<xt::xtensor<T, N, L>, L> {
+  private:
+    using super = xtensor_inspector<xt::xtensor<T, N, L>, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <typename T, xt::layout_type L>
+struct inspector<xt::xarray<T, L>>: public xarray_inspector<xt::xarray<T, L>, L> {
+  private:
+    using super = xarray_inspector<xt::xarray<T, L>, L>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <typename CT, class... S>
+struct inspector<xt::xview<CT, S...>>
+    : public xarray_inspector<xt::xview<CT, S...>, xt::layout_type::any> {
+  private:
+    using super = xarray_inspector<xt::xview<CT, S...>, xt::layout_type::any>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+
+template <class EC, xt::layout_type L, class SC, class Tag>
+struct inspector<xt::xarray_adaptor<EC, L, SC, Tag>>
+    : public xarray_inspector<xt::xarray_adaptor<EC, L, SC, Tag>, xt::layout_type::any> {
+  private:
+    using super = xarray_inspector<xt::xarray_adaptor<EC, L, SC, Tag>, xt::layout_type::any>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+template <class EC, size_t N, xt::layout_type L, class Tag>
+struct inspector<xt::xtensor_adaptor<EC, N, L, Tag>>
+    : public xtensor_inspector<xt::xtensor_adaptor<EC, N, L, Tag>, xt::layout_type::any> {
+  private:
+    using super = xtensor_inspector<xt::xtensor_adaptor<EC, N, L, Tag>, xt::layout_type::any>;
+
+  public:
+    using type = typename super::type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+    using hdf5_type = typename super::hdf5_type;
+};
+
+}  // namespace details
+}  // namespace HighFive
diff --git a/packages/HighFive/src/examples/CMakeLists.txt b/packages/HighFive/src/examples/CMakeLists.txt
index 8b5f8b0affb8f68478582b66ac4c8ba01c0bb6d3..fa6f5f739ace8c585765025d2f14a4179a6ff83b 100644
--- a/packages/HighFive/src/examples/CMakeLists.txt
+++ b/packages/HighFive/src/examples/CMakeLists.txt
@@ -1,51 +1,112 @@
-include(HighFiveWarnings)
+set(core_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/compound_types.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/broadcasting_arrays.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_attribute_string_integer.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_dataset_double.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_datatype.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_extensible_dataset.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_large_attribute.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_page_allocated_files.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/readme_snippet.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_dataset_string.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_raw_ptr.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_single_scalar.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_strings.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_vector_dataset.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_vector_dataset_references.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/renaming_objects.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/select_by_id_dataset_cpp11.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/select_partial_dataset_cpp11.cpp
+)
 
-function(compile_example example_source)
+set(span_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/read_write_std_span.cpp
+)
+
+set(easy_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/easy_attribute.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/easy_dumpoptions.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/easy_load_dump.cpp
+)
+
+set(boost_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/boost_multi_array_2D.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/boost_multiarray_complex.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/boost_ublas_double.cpp
+)
+
+set(eigen_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/eigen_matrix.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/eigen_vector.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/eigen_map.cpp
+)
+
+set(hl_hdf5_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/hl_hdf5_inmemory_files.cpp
+)
 
-    get_filename_component(example_filename ${example_source} NAME)
-    string(REPLACE ".cpp" "_bin" example_name ${example_filename})
-
-    if(${example_filename} MATCHES ".*eigen.*")
-        if(NOT HIGHFIVE_USE_EIGEN)
-            return()
-        endif()
-    endif()
-
-    if(${example_filename} MATCHES ".*boost.*")
-        if(NOT HIGHFIVE_USE_BOOST)
-            return()
-        endif()
-    endif()
-
-    if(${example_filename} MATCHES ".*parallel_hdf5.*")
-        if(NOT HIGHFIVE_PARALLEL_HDF5)
-            return()
-        endif()
-    endif()
-
-    if(${example_filename} MATCHES ".*half_float.*")
-        if(NOT HIGHFIVE_USE_HALF_FLOAT)
-            return()
-        endif()
-    endif()
-
-    if(${example_name} MATCHES ".*hl_hdf5.*")
-        find_package(HDF5 QUIET COMPONENTS HL NAMES HDF5_HL)
-        if(${HDF5_HL_FOUND})
-            message("HDF5 HL: ${HDF5_HL_LIBRARIES}")
-            add_executable(${example_name} ${example_source})
-            target_link_libraries(${example_name} HighFive HighFiveWarnings ${HDF5_HL_LIBRARIES})
-        endif()
-        return()
-    endif()
-
-    add_executable(${example_name} ${example_source})
-    target_link_libraries(${example_name} HighFive HighFiveWarnings)
+set(parallel_hdf5_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/parallel_hdf5_collective_io.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/parallel_hdf5_independent_io.cpp
+)
 
+set(half_float_examples
+  ${CMAKE_CURRENT_SOURCE_DIR}/create_dataset_half_float.cpp
+)
+
+function(compile_example example_source)
+  get_filename_component(example_filename ${example_source} NAME)
+  string(REPLACE ".cpp" "_bin" example_name ${example_filename})
+
+  add_executable(${example_name} ${example_source})
+  target_link_libraries(${example_name} PUBLIC HighFive HighFiveWarnings HighFiveFlags)
+  if(${ARGC} EQUAL 2)
+    target_link_libraries(${example_name} PUBLIC ${ARGV1})
+  endif()
 endfunction()
 
-file(GLOB list_example "*.cpp")
 
-foreach(example_src ${list_example})
-    compile_example(${example_src})
+foreach(example_source ${core_examples})
+  compile_example(${example_source})
 endforeach()
+
+foreach(example_source ${easy_examples})
+  compile_example(${example_source} HighFiveOptionalDependencies)
+endforeach()
+
+if(HIGHFIVE_TEST_SPAN)
+  foreach(example_source ${span_examples})
+    compile_example(${example_source})
+  endforeach()
+endif()
+
+if(HIGHFIVE_TEST_BOOST)
+  foreach(example_source ${boost_examples})
+    compile_example(${example_source} HighFiveBoostDependency)
+  endforeach()
+endif()
+
+if(HIGHFIVE_TEST_EIGEN)
+  foreach(example_source ${eigen_examples})
+    compile_example(${example_source} HighFiveEigenDependency)
+  endforeach()
+endif()
+
+if(HDF5_IS_PARALLEL)
+  foreach(example_source ${parallel_hdf5_examples})
+    compile_example(${example_source})
+  endforeach()
+endif()
+
+add_library(HighFiveHlHdf5Dependency INTERFACE)
+find_package(HDF5 QUIET COMPONENTS HL NAMES HDF5_HL)
+if(${HDF5_HL_FOUND})
+    message("HDF5 HL: ${HDF5_HL_LIBRARIES}")
+    target_link_libraries(HighFiveHlHdf5Dependency ${HDF5_HL_LIBRARIES})
+
+    foreach(example_source ${hl_hdf5_examples})
+      compile_examples(${example_source} HighFiveHlHdf5Dependency)
+    endforeach()
+endif()
+
+# TODO Half-float examples
diff --git a/packages/HighFive/src/examples/boost_multi_array_2D.cpp b/packages/HighFive/src/examples/boost_multi_array_2D.cpp
index 4bec1ec12991391a58549e8fb044294523f4a74b..508c3a880f7d8deb1b571b87fc397580263c73d3 100644
--- a/packages/HighFive/src/examples/boost_multi_array_2D.cpp
+++ b/packages/HighFive/src/examples/boost_multi_array_2D.cpp
@@ -8,11 +8,9 @@
  */
 #include <iostream>
 
-#undef H5_USE_BOOST
-#define H5_USE_BOOST
-
 #include <boost/multi_array.hpp>
 #include <highfive/highfive.hpp>
+#include <highfive/boost.hpp>
 
 using namespace HighFive;
 
diff --git a/packages/HighFive/src/examples/boost_multiarray_complex.cpp b/packages/HighFive/src/examples/boost_multiarray_complex.cpp
index 37481db625b5b21d7d13c0ae7da4223c9d7eb327..34f18f55191fadcc81a8d226fc3805a0bac63e43 100644
--- a/packages/HighFive/src/examples/boost_multiarray_complex.cpp
+++ b/packages/HighFive/src/examples/boost_multiarray_complex.cpp
@@ -9,12 +9,9 @@
 #include <algorithm>
 #include <complex>
 
-#undef H5_USE_BOOST
-#define H5_USE_BOOST
-
 #include <highfive/highfive.hpp>
 
-#include <boost/multi_array.hpp>
+#include <highfive/boost.hpp>
 
 typedef std::complex<double> complex_t;
 
diff --git a/packages/HighFive/src/examples/boost_ublas_double.cpp b/packages/HighFive/src/examples/boost_ublas_double.cpp
index b025475b9584b2f27c6dbfcd51e3ee402898031a..3889df6807eb58a18132fd3bdc429dbb2786221b 100644
--- a/packages/HighFive/src/examples/boost_ublas_double.cpp
+++ b/packages/HighFive/src/examples/boost_ublas_double.cpp
@@ -8,11 +8,10 @@
  */
 #include <iostream>
 
-#undef H5_USE_BOOST
-#define H5_USE_BOOST
-
 #include <highfive/highfive.hpp>
 
+#include <highfive/boost.hpp>
+
 // In some versions of Boost (starting with 1.64), you have to include the serialization header
 // before ublas
 #include <boost/serialization/vector.hpp>
diff --git a/packages/HighFive/src/examples/broadcasting_arrays.cpp b/packages/HighFive/src/examples/broadcasting_arrays.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3684e17b043200fa4190ded702956237ffe9bf26
--- /dev/null
+++ b/packages/HighFive/src/examples/broadcasting_arrays.cpp
@@ -0,0 +1,50 @@
+#include <highfive/highfive.hpp>
+
+// This example explains how to read a dataset with some shape into an array of
+// some other shape. Naturally, this only makes sense if the number of elements
+// doesn't change.
+//
+// Note that due to how HDF5 works, writing from one shape into some other
+// shape is expected to work automatically.
+//
+// Same is true for reading. However, HighFive also allocates memory, the array
+// into which the data is read is forced to have the same shape as the
+// memspace. When performing selections it can often happen that one selects a
+// one-dimensional slice from a higher dimensional array.  In this case we want
+// to be able to read into a one dimensional array, e.g. `std::vector<double>`.
+//
+// Broadcasting is a common technique for hiding benign differences in
+// dimensionality. In HighFive we suggest to either "squeeze" or "reshape" the
+// memspace, rather than broadcasting. This example demonstrates the required
+// syntax.
+//
+// Note: These techniques can also be used for general hyperslabs which the
+// user knows are in fact hypercubes, i.e. regular.
+//
+// Note: HighFive v2 has support for broadcasting; but because it's quirky,
+// less powerful than the demonstrated technique, relied on a compile-time
+// constant rank and is quite complex to maintain, the functionality was
+// removed from v3.
+
+using namespace HighFive;
+
+int main(void) {
+    File file("broadcasting_arrays.h5", File::Truncate);
+
+    std::vector<size_t> dims{3, 1};
+    std::vector<double> values{1.0, 2.0, 3.0};
+
+    auto dset = file.createDataSet("dset", DataSpace(dims), create_datatype<double>());
+
+    // Note that because `values` is one-dimensional, we can't write it
+    // to a dataset of dimensions `[3, 1]` directly. Instead we use:
+    dset.squeezeMemSpace({1}).write(values);
+
+    // When reading, (re-)allocation might occur. The shape to be allocated is
+    // the dimensions of the memspace. Therefore, one might want to either remove
+    // an axis:
+    dset.squeezeMemSpace({1}).read(values);
+
+    // or reshape the memspace:
+    dset.reshapeMemSpace({3}).read(values);
+}
diff --git a/packages/HighFive/src/examples/create_dataset_half_float.cpp b/packages/HighFive/src/examples/create_dataset_half_float.cpp
index 837c58704c5d4e4d7a030bd3d784286004ca1030..01577669982c67fbf4e3ebee42203de0dcb2bebb 100644
--- a/packages/HighFive/src/examples/create_dataset_half_float.cpp
+++ b/packages/HighFive/src/examples/create_dataset_half_float.cpp
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include <highfive/highfive.hpp>
+#include <highfive/half_float.hpp>
 
 const std::string FILE_NAME("create_dataset_half_float_example.h5");
 const std::string DATASET_NAME("dset");
diff --git a/packages/HighFive/src/examples/eigen_map.cpp b/packages/HighFive/src/examples/eigen_map.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7cdd15cd460e6cfaf5935bcdf589c14007ba0eb8
--- /dev/null
+++ b/packages/HighFive/src/examples/eigen_map.cpp
@@ -0,0 +1,48 @@
+#include <highfive/highfive.hpp>
+#include <highfive/eigen.hpp>
+
+// Example showing reading and writing of `Eigen::Map`. Using
+// `Map<Matrix, ...>` as an example, but `Map<Array, ...>` works
+// analogously.
+//
+// Both `Eigen::Matrix` and `Eigen::Vector` have their own examples.
+
+int main() {
+    HighFive::File file("eigen_map.h5", HighFive::File::Truncate);
+
+    // Somehow allocate some memory:
+    double* p1 = (double*) malloc(4 * 3 * sizeof(double));
+
+    Eigen::Map<Eigen::MatrixXd> A(p1, 4, 3);
+
+    // clang-format off
+    A <<  1,  2,  3,
+          4,  5,  6,
+          7,  8,  9,
+         10, 11, 12;
+    // clang-format on
+    std::cout << "A = \n" << A << "\n\n";
+
+    // Write it to the file:
+    file.createDataSet("mat", A);
+
+    // ... and read it back as fixed-size and row-major:
+    using Matrix43d = Eigen::Matrix<double, 4, 3, Eigen::RowMajor>;
+
+    // Again, memory was obtain somehow, and we create an `Eigen::Map`
+    // from it:
+    double* p2 = (double*) malloc(4 * 3 * sizeof(double));
+    Eigen::Map<Matrix43d> B(p2, 4, 3);
+
+    // Since, we've pre-allocated the memory, we use the overload of `read`
+    // accepts `B` and an argument. Note, this will throw if `B` needs to be
+    // resized, because a map shouldn't resize the underlying memory:
+    file.getDataSet("mat").read(B);
+
+    std::cout << "B = \n" << B << "\n";
+
+    free(p1);
+    free(p2);
+
+    return 0;
+}
diff --git a/packages/HighFive/src/examples/eigen_matrix.cpp b/packages/HighFive/src/examples/eigen_matrix.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7a40445d7da4579bda0c5787d3f1966c133d4e65
--- /dev/null
+++ b/packages/HighFive/src/examples/eigen_matrix.cpp
@@ -0,0 +1,33 @@
+#include <highfive/highfive.hpp>
+#include <highfive/eigen.hpp>
+
+// Example showing reading and writing of `Eigen::Matrix`. Using
+// `Eigen::Matrix` as an example, but `Eigen::Array` works analogously.
+//
+// Both `Eigen::Vector` and `Eigen::Map` have their own examples.
+
+int main() {
+    HighFive::File file("eigen_matrix.h5", HighFive::File::Truncate);
+
+    // Create a matrix.
+    Eigen::MatrixXd A(4, 3);
+    // clang-format off
+    A <<  1,  2,  3,
+          4,  5,  6,
+          7,  8,  9,
+         10, 11, 12;
+    // clang-format on
+    //
+    std::cout << "A = \n" << A << "\n\n";
+
+    // Write it to the file:
+    file.createDataSet("mat", A);
+
+    // ... and read it back as fixed-size and row-major:
+    using Matrix43d = Eigen::Matrix<double, 4, 3, Eigen::RowMajor>;
+    auto B = file.getDataSet("mat").read<Matrix43d>();
+
+    std::cout << "B = \n" << B << "\n";
+
+    return 0;
+}
diff --git a/packages/HighFive/src/examples/eigen_vector.cpp b/packages/HighFive/src/examples/eigen_vector.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aa4772ce78c7b218c0c6b244f6b435e92b1f7a8c
--- /dev/null
+++ b/packages/HighFive/src/examples/eigen_vector.cpp
@@ -0,0 +1,33 @@
+#include <highfive/highfive.hpp>
+#include <highfive/eigen.hpp>
+
+// Example showing reading and writing of `Eigen::Matrix`. Using
+// `Eigen::Matrix` as an example, but `Eigen::Array` works analogously.
+//
+// Both `Eigen::Vector` and `Eigen::Map` have their own examples.
+
+int main() {
+    HighFive::File file("eigen_vector.h5", HighFive::File::Truncate);
+
+    // Create a matrix.
+    Eigen::VectorXd v(3);
+    v << 1, 2, 3;
+    std::cout << "v = \n" << v << "\n\n";
+
+    // Write it to the file:
+    file.createDataSet("col_vec", v);
+
+    // The twist is that Eigen typedefs:
+    //   using VectorXd = Matrix<double, Dynamic, 1>;
+    //
+    // Therefore, for HighFive it's indistinguishable from a Nx1 matrix. Since,
+    // Eigen distinguishes row and column vectors, the HighFive chooses to
+    // respect the distinction and deduces the shape of vector as Nx1.
+
+    // ... and read it back as fixed-size:
+    auto w = file.getDataSet("col_vec").read<Eigen::Vector3d>();
+
+    std::cout << "w = \n" << w << "\n";
+
+    return 0;
+}
diff --git a/packages/HighFive/src/examples/read_write_raw_ptr.cpp b/packages/HighFive/src/examples/read_write_raw_ptr.cpp
index b6cd9eda59e74e842a988f777ca5c95ffb26b702..4c8b563cb96916bd55d3fc32556b1028a74baa1d 100644
--- a/packages/HighFive/src/examples/read_write_raw_ptr.cpp
+++ b/packages/HighFive/src/examples/read_write_raw_ptr.cpp
@@ -66,7 +66,7 @@ int main(void) {
         auto nd_array = std::vector<double>(n_elements);
 
         // Finally, read into the memory by passing a raw pointer to the library.
-        dataset.read<double>(nd_array.data());
+        dataset.read_raw<double>(nd_array.data());
     }
 
     return 0;
diff --git a/packages/HighFive/src/examples/read_write_std_span.cpp b/packages/HighFive/src/examples/read_write_std_span.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..72465c46da5ee05f4849a91126eb3537ab9033f6
--- /dev/null
+++ b/packages/HighFive/src/examples/read_write_std_span.cpp
@@ -0,0 +1,56 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+
+// This example demonstrates using `std::span`. An `std::span` is a pointer
+// with a size.
+
+#include <string>
+#include <vector>
+
+#include <highfive/highfive.hpp>
+
+#include <highfive/span.hpp>
+
+int main(void) {
+    using namespace HighFive;
+
+    std::string file_name = "read_write_span.h5";
+    std::string dataset_name = "array";
+
+    File file(file_name, File::Truncate);
+
+    // Let's write to file.
+    {
+        // Assume we have one-dimensional data in some unsupported format (we
+        // use `std::vector` for simplicity). Further, assume that the data is
+        // stored contiguously. Then one can create an `std::span`.
+        std::vector<double> values{1.0, 2.0, 3.0};
+        auto view = std::span<double>(values.data(), values.size());
+
+        // Given the span, HighFive can deduce the shape of the dataset. Hence,
+        // spans are fully supported when writing. For example:
+        auto dataset = file.createDataSet(dataset_name, view);
+    }
+
+    // Let's read from file.
+    {
+        auto dataset = file.getDataSet(dataset_name);
+
+        // Since spans are views, HighFive can't (or wont) allocate memory.
+        // Instead one must preallocate memory and then create a span for that
+        // memory:
+        auto values = std::vector<double>(dataset.getElementCount());
+        auto view = std::span<double>(values.data(), values.size());
+
+        // ... now we can read into the preallocated memory:
+        dataset.read(view);
+    }
+
+    return 0;
+}
diff --git a/packages/HighFive/src/examples/select_slices.cpp b/packages/HighFive/src/examples/select_slices.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea428fa3fefa57399c542b5f62fa8860f4010071
--- /dev/null
+++ b/packages/HighFive/src/examples/select_slices.cpp
@@ -0,0 +1,131 @@
+#include <functional>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <highfive/highfive.hpp>
+
+void print_mask(const std::vector<std::vector<double>>& values,
+                const std::vector<std::vector<double>>& selected);
+
+void print_values(const std::vector<std::vector<double>>& values);
+
+void pretty_print(const std::vector<std::vector<double>>& values,
+                  const std::vector<std::vector<double>>& selected);
+
+int main(void) {
+    using namespace HighFive;
+    using container_type = std::vector<std::vector<double>>;
+
+    const std::string file_name("select_slices.h5");
+    const std::string dataset_name("dset");
+
+    // Create a new file using the default property lists.
+    File file(file_name, File::Truncate);
+
+    // we have some example values in a 2D vector 2x5
+    // clang-format off
+    container_type values = {
+      {1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7},
+      {2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7},
+      {3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7},
+      {4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7},
+      {5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7},
+      {6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7},
+      {7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7},
+      {8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7},
+      {9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7}
+    };
+    // clang-format on
+
+    auto dset = file.createDataSet(dataset_name, values);
+
+    // Let's start with the selection `values[1:4, 2:4]`.
+    {
+        auto xslice = std::array<size_t, 2>{2, 4};
+        auto yslice = std::array<size_t, 2>{1, 4};
+
+        auto selected = dset.select(ProductSet(yslice, xslice)).read<container_type>();
+        std::cout << " -- values[1:4, 2:4] ------------ \n";
+        pretty_print(values, selected);
+    }
+
+    // Now we'd like the selection `values[[1,2,8], 2:4]`.
+    {
+        auto xslice = std::array<size_t, 2>{2, 4};
+        auto yslice = std::vector<size_t>{1, 2, 8};
+
+        auto selected = dset.select(ProductSet(yslice, xslice)).read<container_type>();
+        std::cout << "\n -- values[[1,2,8], 2:4] -------- \n";
+        pretty_print(values, selected);
+    }
+
+    // ... or the union of multiple slices.
+    {
+        auto xslice = std::array<size_t, 2>{2, 4};
+        auto yslice = std::vector<std::array<size_t, 2>>{{0, 2}, {5, 9}};
+
+        auto selected = dset.select(ProductSet(yslice, xslice)).read<container_type>();
+        std::cout << "\n -- values[[0:2, 5:10], 2:4] -------- \n";
+        pretty_print(values, selected);
+    }
+
+    // Similar for the union of multiple slices in both x- and y-direction.
+    {
+        auto xslice = std::vector<std::array<size_t, 2>>{{0, 1}, {2, 4}, {6, 7}};
+        auto yslice = std::vector<std::array<size_t, 2>>{{0, 2}, {5, 9}};
+
+        auto selected = dset.select(ProductSet(yslice, xslice)).read<container_type>();
+        std::cout << "\n -- values[[0:2, 5:10], [0:1, 2:4, 6:7]] -------- \n";
+        pretty_print(values, selected);
+    }
+
+    // If only a single row/column is need use the following. However,
+    // selecting elements one-by-one in a loop can be a serious performance
+    // issue.
+    {
+        auto xslice = std::vector<std::array<size_t, 2>>{{0, 1}, {2, 4}, {6, 7}};
+        auto row_id = 3;
+
+        auto selected = dset.select(ProductSet(row_id, xslice)).read<container_type>();
+        std::cout << "\n -- values[3, [0:1, 2:4, 6:7]] -------- \n";
+        pretty_print(values, selected);
+    }
+
+    return 0;
+}
+
+void print_mask(const std::vector<std::vector<double>>& values,
+                const std::vector<std::vector<double>>& selected) {
+    std::vector<double> flat_selection;
+    for (const auto& row: selected) {
+        for (const auto& x: row) {
+            flat_selection.push_back(x);
+        }
+    }
+
+    for (const auto& row: values) {
+        for (const auto& x: row) {
+            auto found = std::find(flat_selection.begin(), flat_selection.end(), x) !=
+                         flat_selection.end();
+            std::cout << (found ? "x" : ".") << "  ";
+        }
+        std::cout << "\n";
+    }
+}
+
+void print_values(const std::vector<std::vector<double>>& values) {
+    for (const auto& row: values) {
+        for (const auto& x: row) {
+            std::cout << x << "  ";
+        }
+        std::cout << "\n";
+    }
+}
+
+void pretty_print(const std::vector<std::vector<double>>& values,
+                  const std::vector<std::vector<double>>& selected) {
+    print_mask(values, selected);
+    std::cout << "\n";
+    print_values(selected);
+}
diff --git a/packages/HighFive/tests/cmake_integration/README.md b/packages/HighFive/tests/cmake_integration/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..c69df2ced07b04daddb277df30f00a95dcda69e4
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/README.md
@@ -0,0 +1,22 @@
+# Examples of CMake Integration.
+This folder container examples of projects using CMake to integrate HighFive in
+the project. The following examples have been provided:
+
+*  `application` contains an application/executable
+  that uses HighFive and the optional Boost dependency.
+
+* `dependent_library` contains a library that uses HighFive in its API. It
+  consists of a shared and static library; and includes, as an optional
+  component, a Boost dependency.
+
+* `test_dependent_library` is an application to test that (or demonstrate how)
+  `dependent_library` can be consumed easily.
+
+## Vendoring and Integration Strategy
+Note that all examples have been written to pick different vendoring and
+integration strategies. This is for testing purposes only. Any real project
+would pick a single integration strategy and at most two vendoring strategies.
+
+## Testing
+Run `bash test_cmake_integration.sh` to check if the CMake integration example
+are working as expected.
diff --git a/packages/HighFive/tests/cmake_integration/application/CMakeLists.txt b/packages/HighFive/tests/cmake_integration/application/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..8f2a71423cc6625ac1a9119762d4cbe4c3442055
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/application/CMakeLists.txt
@@ -0,0 +1,87 @@
+# This is an example of an application/executable using HighFive. It
+# demonstrates the different vendoring strategies and targets provided by
+# HighFive.
+
+cmake_minimum_required(VERSION 3.14)
+project(Hi5Application VERSION 0.1)
+
+if(NOT DEFINED CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 14)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
+set(INTEGRATION_STRATEGY "short" CACHE STRING "Use 'Include' for HighFive::Include, 'full' for HighFive::HighFive, 'short' for HighFive.")
+set(VENDOR_STRATEGY "submodule" CACHE STRING "Use 'submodule' for Git submodules, 'fetch_content' for FetchContent, 'external' for `find_package`.")
+option(USE_STATIC_HDF5 "Link against static HDF5" OFF)
+option(USE_BOOST "Simulates an application using Boost" OFF)
+
+# Controlling HDF5 features is done by directly setting the HDF5 flags. The
+# interesting ones are probably:
+#  * HDF5_USE_STATIC_LIBRARIES
+#  * HDF5_PREFER_PARALLEL
+if(USE_STATIC_HDF5)
+  set(HDF5_USE_STATIC_LIBRARIES On)
+else()
+  set(HDF5_USE_STATIC_LIBRARIES Off)
+endif()
+
+if(${INTEGRATION_STRATEGY} STREQUAL "bailout")
+  set(HIGHFIVE_FIND_HDF5 Off)
+endif()
+
+if(${VENDOR_STRATEGY} STREQUAL "submodule")
+  # When vendoring via a Git submodule, this is the correct
+  # line to include HighFive.
+  add_subdirectory("deps/HighFive" EXCLUDE_FROM_ALL)
+elseif(${VENDOR_STRATEGY} STREQUAL "fetch_content")
+  include(FetchContent)
+  FetchContent_Declare(HighFive
+    GIT_REPOSITORY $ENV{HIGHFIVE_GIT_REPOSITORY}
+    GIT_TAG $ENV{HIGHFIVE_GIT_TAG}
+  )
+  FetchContent_MakeAvailable(HighFive)
+elseif(${VENDOR_STRATEGY} STREQUAL "external")
+  # When HighFive is installed like regular software and then "found", do the
+  # following:
+  find_package(HighFive REQUIRED)
+endif()
+
+add_executable(Hi5Application "hi5_application.cpp")
+
+if(   ${INTEGRATION_STRATEGY} STREQUAL "Include"
+   OR ${INTEGRATION_STRATEGY} STREQUAL "bailout")
+  # Only add `-I${HIGHFIVE_DIR}/include`.
+  target_link_libraries(Hi5Application PUBLIC HighFive::Include)
+
+  # Now link to HDF5 in whatever fashion you desire.
+  find_package(HDF5 REQUIRED)
+  target_link_libraries(Hi5Application PUBLIC HDF5::HDF5)
+
+  # You might need to take care of MPI.
+  find_package(MPI REQUIRED)
+  target_link_libraries(Hi5Application PUBLIC MPI::MPI_C MPI::MPI_CXX)
+elseif(${INTEGRATION_STRATEGY} STREQUAL "short")
+  # Highest chance of being backwards compatible with v2.
+  target_link_libraries(Hi5Application PUBLIC HighFive)
+elseif(${INTEGRATION_STRATEGY} STREQUAL "full")
+  target_link_libraries(Hi5Application PUBLIC HighFive::HighFive)
+endif()
+
+if(USE_BOOST)
+  find_package(Boost REQUIRED)
+  target_link_libraries(Hi5Application PUBLIC Boost::headers)
+  target_compile_definitions(Hi5Application PUBLIC HI5_APPLICATION_HAS_BOOST=1)
+endif()
+
+if(USE_STATIC_HDF5)
+  find_package(ZLIB REQUIRED)
+  target_link_libraries(${target} PUBLIC ZLIB::ZLIB)
+endif()
+
+# Install
+# -------
+install(TARGETS Hi5Application RUNTIME DESTINATION bin)
+
+enable_testing()
+add_test(NAME test_hi5_application COMMAND ${CMAKE_CURRENT_BINARY_DIR}/Hi5Application)
diff --git a/packages/HighFive/tests/cmake_integration/application/deps/.gitignore b/packages/HighFive/tests/cmake_integration/application/deps/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d6b7ef32c8478a48c3994dcadc86837f4371184d
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/application/deps/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/packages/HighFive/tests/cmake_integration/application/hi5_application.cpp b/packages/HighFive/tests/cmake_integration/application/hi5_application.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dd27260aa4014adae56462de4eab84c12bdc30d6
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/application/hi5_application.cpp
@@ -0,0 +1,36 @@
+#include <highfive/highfive.hpp>
+#if HI5_APPLICATION_HAS_BOOST == 1
+#include <highfive/boost.hpp>
+#endif
+
+int main() {
+    {
+        auto file = HighFive::File("foo.h5", HighFive::File::Truncate);
+
+        auto dset = file.createDataSet("foo", std::vector<double>{1.0, 2.0, 3.0});
+        auto x = dset.read<std::vector<double>>();
+
+        for (size_t i = 0; i < x.size(); i++) {
+            if (x[i] != double(i + 1)) {
+                throw std::runtime_error("HighFiveDemo is broken.");
+            }
+        }
+
+        std::cout << "Hi5Application: success \n";
+    }
+
+#if HI5_APPLICATION_HAS_BOOST == 1
+    {
+        using matrix_t = boost::numeric::ublas::matrix<double>;
+
+        auto file = HighFive::File("bar.h5", HighFive::File::Truncate);
+        matrix_t x(3, 5);
+        auto dset = file.createDataSet("foo", x);
+        auto y = dset.read<matrix_t>();
+
+        std::cout << "Hi5BoostApplication: success \n";
+    }
+#endif
+
+    return 0;
+}
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/CMakeLists.txt b/packages/HighFive/tests/cmake_integration/dependent_library/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ad76d99b200f5bf281181e6940bc7ca350f44334
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/CMakeLists.txt
@@ -0,0 +1,134 @@
+# Do NOT document the available components and targets. Guessing CMake targets
+# and components is an essential part of the game. This library for example has
+# one component called `boost` and three targets. Use
+#
+#   # Without Boost:
+#   find_package(Hi5Dependent REQUIRED)
+#   target_link_libraries(foo PUBLIC Hi5Dependent::Read Hi5Dependent::Write)
+#
+#   # With Boost:
+#   find_package(Hi5Dependent REQUIRED COMPONENTS boost)
+#   target_link_libraries(foo PUBLIC Hi5Dependent::Read Hi5Dependent::Write)
+#   target_link_libraries(foo PUBLIC Hi5Dependent::Boost)
+
+cmake_minimum_required(VERSION 3.14)
+project(Hi5Dependent VERSION 0.1)
+
+if(NOT DEFINED CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 14)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
+set(INTEGRATION_STRATEGY "short" CACHE STRING "Use 'Include' for HighFive::Include, 'full' for HighFive::HighFive, 'short' for HighFive.")
+option(USE_STATIC_HDF5 "Link against static HDF5" OFF)
+option(USE_BOOST "Build '${PROJECT_NAME}' with optional Boost dependency." OFF)
+
+if(USE_STATIC_HDF5)
+  set(HDF5_USE_STATIC_LIBRARIES On)
+else()
+  set(HDF5_USE_STATIC_LIBRARIES Off)
+endif()
+
+if(${INTEGRATION_STRATEGY} STREQUAL "bailout")
+  set(HIGHFIVE_FIND_HDF5 Off)
+endif()
+
+# Since any project depending on 'Hi5Dependent' also needs HighFive, it doesn't
+# make sense to vendor HighFive. Therefore, use
+find_package(HighFive REQUIRED)
+
+# For demonstration purposes it consists of a shared and static library
+add_library(${PROJECT_NAME}Write SHARED "src/hi5_dependent/write_vector.cpp")
+add_library(${PROJECT_NAME}::Write ALIAS ${PROJECT_NAME}Write)
+set_target_properties(${PROJECT_NAME}Write PROPERTIES EXPORT_NAME Write)
+
+add_library(${PROJECT_NAME}Read STATIC "src/hi5_dependent/read_vector.cpp")
+add_library(${PROJECT_NAME}::Read ALIAS ${PROJECT_NAME}Read)
+set_target_properties(${PROJECT_NAME}Read PROPERTIES EXPORT_NAME Read)
+
+set(Hi5DependentCoreTargets ${PROJECT_NAME}Write ${PROJECT_NAME}Read)
+set(Hi5DependentAllTargets ${Hi5DependentCoreTargets})
+
+# ... and two more for demonstrating an optional dependency (on Boost).
+if(USE_BOOST)
+  add_library(${PROJECT_NAME}Boost SHARED "src/hi5_dependent/boost.cpp")
+  add_library(${PROJECT_NAME}::Boost ALIAS ${PROJECT_NAME}Boost)
+  set_target_properties(${PROJECT_NAME}Boost PROPERTIES EXPORT_NAME Boost)
+
+  find_package(Boost REQUIRED)
+  target_link_libraries(${PROJECT_NAME}Boost PUBLIC Boost::headers)
+  target_compile_definitions(${PROJECT_NAME}Boost PUBLIC HI5_DEPENDENT_HAS_BOOST=1)
+
+  list(APPEND Hi5DependentAllTargets ${PROJECT_NAME}Boost)
+endif()
+
+foreach(target IN LISTS Hi5DependentAllTargets)
+  target_include_directories(${target}
+      PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+      PUBLIC $<INSTALL_INTERFACE:include>
+  )
+
+  # Remember to pick one. Writing out all variation serves testing and
+  # demonstration purposes only. Minimizing lines of code is probably a good
+  # strategy.
+  if(   ${INTEGRATION_STRATEGY} STREQUAL "Include"
+     OR ${INTEGRATION_STRATEGY} STREQUAL "bailout")
+    target_link_libraries(${target} PUBLIC HighFive::Include)
+
+    find_package(HDF5 REQUIRED)
+    target_link_libraries(${target} PUBLIC HDF5::HDF5)
+
+    find_package(MPI REQUIRED)
+    target_link_libraries(${target} PUBLIC MPI::MPI_C MPI::MPI_CXX)
+  elseif(${INTEGRATION_STRATEGY} STREQUAL "short")
+    target_link_libraries(${target} PUBLIC HighFive)
+  elseif(${INTEGRATION_STRATEGY} STREQUAL "full")
+    target_link_libraries(${target} PUBLIC HighFive::HighFive)
+  endif()
+
+  if(USE_STATIC_HDF5)
+    find_package(ZLIB REQUIRED)
+    target_link_libraries(${target} PUBLIC ZLIB::ZLIB)
+  endif()
+endforeach()
+
+# Install
+# -------
+include(CMakePackageConfigHelpers)
+write_basic_package_version_file(
+  ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake
+  VERSION ${PACKAGE_VERSION}
+  COMPATIBILITY AnyNewerVersion
+)
+
+install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
+  DESTINATION "include")
+
+install(TARGETS ${PROJECT_NAME}Read ${PROJECT_NAME}Write EXPORT ${PROJECT_NAME}Targets)
+install(EXPORT ${PROJECT_NAME}Targets
+  FILE ${PROJECT_NAME}Targets.cmake
+  NAMESPACE ${PROJECT_NAME}::
+  DESTINATION cmake
+)
+
+configure_file(
+  ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in
+  ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake
+  @ONLY
+)
+
+install(FILES
+  ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake
+  ${CMAKE_CURRENT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake
+  DESTINATION cmake
+)
+
+if(USE_BOOST)
+  install(TARGETS ${PROJECT_NAME}Boost EXPORT ${PROJECT_NAME}BoostTargets)
+  install(EXPORT ${PROJECT_NAME}BoostTargets
+    FILE ${PROJECT_NAME}BoostTargets.cmake
+    NAMESPACE ${PROJECT_NAME}::
+    DESTINATION cmake
+  )
+endif()
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/cmake/Hi5DependentConfig.cmake.in b/packages/HighFive/tests/cmake_integration/dependent_library/cmake/Hi5DependentConfig.cmake.in
new file mode 100644
index 0000000000000000000000000000000000000000..b5df933b9d443605276230db24e6def65160324c
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/cmake/Hi5DependentConfig.cmake.in
@@ -0,0 +1,24 @@
+include(CMakeFindDependencyMacro)
+
+find_dependency(HighFive)
+
+set(Hi5Dependent_INTEGRATION_STRATEGY @INTEGRATION_STRATEGY@)
+if(   ${Hi5Dependent_INTEGRATION_STRATEGY} STREQUAL "Include"
+   OR ${Hi5Dependent_INTEGRATION_STRATEGY} STREQUAL "bailout")
+  # Remember to 'find' any dependencies you introduce, including HDF5 if you
+  # use additional COMPONENTS; or MPI if you unconditionally use it.
+  find_dependency(MPI)
+endif()
+
+include("${CMAKE_CURRENT_LIST_DIR}/Hi5DependentTargets.cmake")
+
+if(boost IN_LIST Hi5Dependent_FIND_COMPONENTS)
+  set(Hi5Dependent_USE_BOOST @USE_BOOST@)
+
+  if(NOT Hi5Dependent_USE_BOOST)
+    message(FATAL_ERROR "Library was built without the component: boost")
+  endif()
+
+  find_dependency(Boost)
+  include("${CMAKE_CURRENT_LIST_DIR}/Hi5DependentBoostTargets.cmake")
+endif()
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/include/hi5_dependent/read.hpp b/packages/HighFive/tests/cmake_integration/dependent_library/include/hi5_dependent/read.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7550efc8a9d54503e2fd374e4ae3b9f9b8d4de9a
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/include/hi5_dependent/read.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <highfive/highfive.hpp>
+#include <vector>
+
+#if HI5_DEPENDENT_HAS_BOOST == 1
+#include <highfive/boost.hpp>
+#endif
+
+namespace hi5_dependent {
+std::vector<double> read_vector(const HighFive::DataSet& dset);
+
+#if HI5_DEPENDENT_HAS_BOOST == 1
+boost::numeric::ublas::matrix<double> read_boost(const HighFive::DataSet& dset);
+HighFive::DataSet write_boost(HighFive::File& file, const boost::numeric::ublas::matrix<double>& x);
+#endif
+}  // namespace hi5_dependent
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/include/hi5_dependent/write.hpp b/packages/HighFive/tests/cmake_integration/dependent_library/include/hi5_dependent/write.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..469b378bf4d978708a860b8869374d7c7fda01cc
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/include/hi5_dependent/write.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <highfive/highfive.hpp>
+#include <vector>
+
+namespace hi5_dependent {
+HighFive::DataSet write_vector(HighFive::File& file, const std::vector<double>& x);
+}
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/boost.cpp b/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/boost.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc5f6909a1cd75686be54761e353a32410e3ee7d
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/boost.cpp
@@ -0,0 +1,17 @@
+#include <hi5_dependent/read.hpp>
+#include <hi5_dependent/write.hpp>
+
+#include <boost/numeric/ublas/matrix.hpp>
+
+namespace hi5_dependent {
+
+boost::numeric::ublas::matrix<double> read_boost(const HighFive::DataSet& dset) {
+    return dset.read<boost::numeric::ublas::matrix<double>>();
+}
+
+HighFive::DataSet write_boost(HighFive::File& file,
+                              const boost::numeric::ublas::matrix<double>& x) {
+    return file.createDataSet("foo", x);
+}
+
+}  // namespace hi5_dependent
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/read_vector.cpp b/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/read_vector.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e7e6d973fc0b78d14a49f990492d8a661da6d6dd
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/read_vector.cpp
@@ -0,0 +1,7 @@
+#include <hi5_dependent/read.hpp>
+
+namespace hi5_dependent {
+std::vector<double> read_vector(const HighFive::DataSet& dset) {
+    return dset.read<std::vector<double>>();
+}
+}  // namespace hi5_dependent
diff --git a/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/write_vector.cpp b/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/write_vector.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..595cd96784b30f154ea79e0cccac7485f32985d9
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/dependent_library/src/hi5_dependent/write_vector.cpp
@@ -0,0 +1,9 @@
+#include <hi5_dependent/write.hpp>
+
+namespace hi5_dependent {
+
+HighFive::DataSet write_vector(HighFive::File& file, const std::vector<double>& x) {
+    return file.createDataSet("foo", x);
+}
+
+}  // namespace hi5_dependent
diff --git a/packages/HighFive/tests/cmake_integration/test_cmake_integration.sh b/packages/HighFive/tests/cmake_integration/test_cmake_integration.sh
new file mode 100644
index 0000000000000000000000000000000000000000..cf80fbfdda5d518da026abaeb77a7fd2b2b07f12
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/test_cmake_integration.sh
@@ -0,0 +1,104 @@
+#!/usr/bin/env bash
+set -xeuo pipefail
+cd "$( dirname "${BASH_SOURCE[0]}")"  # cd here
+
+# All output should be within this directory.
+TMP_DIR="${PWD}/tmp"
+
+# Root of the cmake integration tests.
+TEST_DIR="${PWD}"
+
+# Path of the sources, build and install directory of HighFive.
+HIGHFIVE_DIR="${TEST_DIR}/../.."
+HIGHFIVE_BUILD_DIR="${TMP_DIR}/build-highfive"
+HIGHFIVE_INSTALL_DIR="${HIGHFIVE_BUILD_DIR}/install"
+
+export HIGHFIVE_GIT_REPOSITORY="file://$(realpath "$HIGHFIVE_DIR")"
+export HIGHFIVE_GIT_TAG=$(git rev-parse HEAD)
+
+test_dependent_library() {
+    local project="dependent_library"
+    local project_dir="${TEST_DIR}/${project}"
+
+    for use_boost in On Off
+    do
+      local build_dir="${TMP_DIR}/build"
+      local install_dir="${TMP_DIR}/build/install"
+
+      rm -rf ${build_dir} || true
+
+      cmake "$@" \
+            -DUSE_BOOST=${use_boost} \
+            -DCMAKE_PREFIX_PATH="${HIGHFIVE_INSTALL_DIR}" \
+            -DCMAKE_INSTALL_PREFIX="${install_dir}" \
+            -B "${build_dir}" "${project_dir}"
+
+      cmake --build "${build_dir}" --verbose --target install
+
+      local test_project="test_dependent_library"
+      local test_build_dir="${TMP_DIR}/test_build"
+      local test_install_dir="${TMP_DIR}/test_build/install"
+
+      rm -rf ${test_build_dir} || true
+
+      cmake -DUSE_BOOST=${use_boost} \
+            -DCMAKE_PREFIX_PATH="${HIGHFIVE_INSTALL_DIR};${install_dir}" \
+            -DCMAKE_INSTALL_PREFIX="${test_install_dir}" \
+            -B "${test_build_dir}" "${test_project}"
+
+      cmake --build "${test_build_dir}" --verbose
+      ctest --test-dir "${test_build_dir}" --verbose
+
+    done
+}
+
+test_application() {
+    local project="application"
+    local project_dir="${TEST_DIR}/${project}"
+    local dep_dir="${TEST_DIR}/${project}/deps/HighFive"
+
+    rm "${dep_dir}" || true
+    ln -sf "${HIGHFIVE_DIR}" "${dep_dir}"
+
+    echo ${HIGHFIVE_DIR}
+    echo ${dep_dir}
+
+    for vendor in submodule fetch_content external
+    do
+      for use_boost in On Off
+      do
+        local build_dir="${TMP_DIR}/build"
+        local install_dir="${TMP_DIR}/build/install"
+
+        rm -rf ${build_dir} || true
+
+        cmake "$@" \
+              -DUSE_BOOST=${use_boost} \
+              -DVENDOR_STRATEGY=${vendor} \
+              -DCMAKE_PREFIX_PATH="${HIGHFIVE_INSTALL_DIR}" \
+              -DCMAKE_INSTALL_PREFIX="${install_dir}" \
+              -B "${build_dir}" "${project_dir}"
+
+        cmake --build "${build_dir}" --verbose --target install
+        ctest --test-dir "${build_dir}"
+        "${install_dir}"/bin/Hi5Application
+      done
+    done
+}
+
+cmake -DHIGHFIVE_EXAMPLES=OFF \
+      -DHIGHFIVE_UNIT_TESTS=OFF \
+      -DCMAKE_INSTALL_PREFIX="${HIGHFIVE_INSTALL_DIR}" \
+      -B "${HIGHFIVE_BUILD_DIR}" \
+      "${HIGHFIVE_DIR}"
+
+cmake --build "${HIGHFIVE_BUILD_DIR}" --target install
+
+for integration in Include full short bailout
+do
+  test_dependent_library \
+      -DINTEGRATION_STRATEGY=${integration}
+
+  test_application \
+      -DINTEGRATION_STRATEGY=${integration}
+done
diff --git a/packages/HighFive/tests/cmake_integration/test_dependent_library/CMakeList.txt b/packages/HighFive/tests/cmake_integration/test_dependent_library/CMakeList.txt
new file mode 100644
index 0000000000000000000000000000000000000000..08bc4a4adfce66472faa808e8ae8e9b13204ea55
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/test_dependent_library/CMakeList.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.14)
+project(TestHi5Dependent VERSION 0.1)
+
+if(NOT DEFINED CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 14)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
+add_executable(test_hi5_dependent test_dependent_library.cpp)
+
+if(NOT USE_BOOST)
+  find_package(Hi5Dependent REQUIRED)
+else()
+  find_package(Hi5Dependent REQUIRED COMPONENTS boost)
+  target_link_libraries(test_hi5_dependent PUBLIC Hi5Dependent::Boost)
+endif()
+
+target_link_libraries(test_hi5_dependent PUBLIC Hi5Dependent::Read Hi5Dependent::Write)
+
+enable_testing()
+add_test(NAME test_hi5_dependent COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_hi5_dependent)
diff --git a/packages/HighFive/tests/cmake_integration/test_dependent_library/CMakeLists.txt b/packages/HighFive/tests/cmake_integration/test_dependent_library/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c9023f98ac85600f0f6f8cc0b952373b72bf72e5
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/test_dependent_library/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.14)
+project(TestHi5Dependent VERSION 0.1)
+
+if(NOT DEFINED CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 14)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
+
+add_executable(test_hi5_dependent test_dependent_library.cpp)
+
+if(NOT USE_BOOST)
+  find_package(Hi5Dependent REQUIRED)
+  target_link_libraries(test_hi5_dependent PUBLIC Hi5Dependent::Read Hi5Dependent::Write)
+else()
+  find_package(Hi5Dependent REQUIRED COMPONENTS boost)
+  target_link_libraries(test_hi5_dependent PUBLIC Hi5Dependent::Read Hi5Dependent::Write)
+  target_link_libraries(test_hi5_dependent PUBLIC Hi5Dependent::Boost)
+endif()
+
+enable_testing()
+add_test(NAME run_test_hi5_dependent COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_hi5_dependent)
diff --git a/packages/HighFive/tests/cmake_integration/test_dependent_library/test_dependent_library.cpp b/packages/HighFive/tests/cmake_integration/test_dependent_library/test_dependent_library.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..40aef20a6b46bbf2eb7d21cdddbc49a50f3cbff3
--- /dev/null
+++ b/packages/HighFive/tests/cmake_integration/test_dependent_library/test_dependent_library.cpp
@@ -0,0 +1,34 @@
+#include <hi5_dependent/read.hpp>
+#include <hi5_dependent/write.hpp>
+
+
+int main() {
+    {
+        auto file = HighFive::File("foo.h5", HighFive::File::Truncate);
+
+        auto dset = hi5_dependent::write_vector(file, {1.0, 2.0, 3.0});
+        auto x = hi5_dependent::read_vector(dset);
+
+        for (size_t i = 0; i < x.size(); i++) {
+            if (x[i] != double(i + 1)) {
+                throw std::runtime_error("HighFiveDemo is broken.");
+            }
+        }
+
+        std::cout << "Hi5Dependent: success \n";
+    }
+
+#if HI5_DEPENDENT_HAS_BOOST == 1
+    {
+        auto file = HighFive::File("bar.h5", HighFive::File::Truncate);
+
+        boost::numeric::ublas::matrix<double> x(3, 5);
+        auto dset = hi5_dependent::write_boost(file, x);
+        auto y = hi5_dependent::read_boost(dset);
+
+        std::cout << "Hi5BoostDependent: success \n";
+    }
+#endif
+
+    return 0;
+}
diff --git a/packages/HighFive/tests/test_dependent_library/CMakeLists.txt b/packages/HighFive/tests/test_dependent_library/CMakeLists.txt
deleted file mode 100644
index 570dba224e65112c7af578f1337647b98daedc55..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_dependent_library/CMakeLists.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-# This is a sample library to test integration via add_subdirectory and CMakeConfig
-cmake_minimum_required(VERSION 3.1)
-
-project(test_project VERSION 0.1)
-
-if(NOT DEFINED CMAKE_CXX_STANDARD)
-  set(CMAKE_CXX11_STANDARD_COMPILE_OPTION "-std=c++11")  # For come compilers under cmake 3.1
-  set(CMAKE_CXX_STANDARD 11)
-  set(CMAKE_CXX_STANDARD_REQUIRED ON)
-  set(CMAKE_CXX_EXTENSIONS OFF)
-endif()
-
-option(USE_BUNDLED_HIGHFIVE "Use highfive from deps folder. Otherwise must be installed" ON)
-
-if(USE_BUNDLED_HIGHFIVE)
-    add_subdirectory("deps/HighFive" EXCLUDE_FROM_ALL)
-else()
-    find_package(HighFive REQUIRED QUIET)
-endif()
-
-add_library(simpleton SHARED "src/simpleton.cpp" "src/otherton.cpp")
-target_include_directories(simpleton
-    PUBLIC
-    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
-    $<INSTALL_INTERFACE:include>)
-target_link_libraries(simpleton PUBLIC HighFive)
-set_property(TARGET simpleton PROPERTY POSITION_INDEPENDENT_CODE ON)
-
-add_library(otherton STATIC "src/simpleton.cpp" "src/otherton.cpp")
-target_include_directories(otherton
-    PUBLIC
-    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
-    $<INSTALL_INTERFACE:include>)
-target_link_libraries(otherton PUBLIC HighFive)
-set_property(TARGET otherton PROPERTY POSITION_INDEPENDENT_CODE OFF)
-
-install(
-    TARGETS simpleton otherton
-    EXPORT simpletonTarget
-    DESTINATION lib
-    ARCHIVE DESTINATION lib)
-install(EXPORT simpletonTarget DESTINATION lib)
diff --git a/packages/HighFive/tests/test_dependent_library/deps/.gitignore b/packages/HighFive/tests/test_dependent_library/deps/.gitignore
deleted file mode 100644
index 5e7d2734cfc60289debf74293817c0a8f572ff32..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_dependent_library/deps/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
diff --git a/packages/HighFive/tests/test_dependent_library/include/simpleton.hpp b/packages/HighFive/tests/test_dependent_library/include/simpleton.hpp
deleted file mode 100644
index b98a09fda04045dbcb72444feff720cbb9db84c4..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_dependent_library/include/simpleton.hpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#ifndef H5_TEST_SIMPLETON_HPP
-#define H5_TEST_SIMPLETON_HPP
-
-// Include all headers here to catch any missing `inline` statements, since
-// they will be included by two different compilation units.
-#include <highfive/highfive.hpp>
-
-// Boost should always be found in this setup
-#include <boost/numeric/ublas/matrix.hpp>
-
-void function(const HighFive::Object& obj);
-void other_function(const boost::numeric::ublas::matrix<double>& m);
-
-#endif
diff --git a/packages/HighFive/tests/test_dependent_library/src/otherton.cpp b/packages/HighFive/tests/test_dependent_library/src/otherton.cpp
deleted file mode 100644
index 3e10a3630ddbec8d5883a67541d0ec4ea89fc15d..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_dependent_library/src/otherton.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include "simpleton.hpp"
-
-void other_function(const boost::numeric::ublas::matrix<double>& m) {
-    m(0, 0) * 0.0;
-}
diff --git a/packages/HighFive/tests/test_dependent_library/src/simpleton.cpp b/packages/HighFive/tests/test_dependent_library/src/simpleton.cpp
deleted file mode 100644
index 12cef5bfc49022af397669d06c40048c223d17b8..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_dependent_library/src/simpleton.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#include <stdexcept>
-
-#include "simpleton.hpp"
-
-void function(const HighFive::Object& obj) {
-    if (!obj.isValid()) {
-        throw std::exception();
-    }
-}
diff --git a/packages/HighFive/tests/test_project/CMakeLists.txt b/packages/HighFive/tests/test_project/CMakeLists.txt
deleted file mode 100644
index 1a8ef098ae0f68cd6317eb0f526913d1cb5c2b0b..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_project/CMakeLists.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-# This is a sample project to test integration via add_subdirectory and CMakeConfig
-cmake_minimum_required(VERSION 3.1)
-
-project(test_project VERSION 0.1)
-
-if(NOT DEFINED CMAKE_CXX_STANDARD)
-  set(CMAKE_CXX11_STANDARD_COMPILE_OPTION "-std=c++11")  # For come compilers under cmake 3.1
-  set(CMAKE_CXX_STANDARD 11)
-  set(CMAKE_CXX_STANDARD_REQUIRED ON)
-  set(CMAKE_CXX_EXTENSIONS OFF)
-endif()
-
-option(USE_BUNDLED_HIGHFIVE "Use highfive from deps folder. Otherwise must be installed" ON)
-
-if(USE_BUNDLED_HIGHFIVE)
-    add_subdirectory("deps/HighFive" EXCLUDE_FROM_ALL)
-else()
-    find_package(HighFive REQUIRED)
-endif()
-
-add_executable(read_write_bin "read_write_vector_dataset.cpp")
-target_link_libraries(read_write_bin HighFive)
-
-enable_testing()
-add_test(NAME test_project COMMAND ${CMAKE_CURRENT_BINARY_DIR}/read_write_bin)
diff --git a/packages/HighFive/tests/test_project/deps/.gitignore b/packages/HighFive/tests/test_project/deps/.gitignore
deleted file mode 100644
index 5e7d2734cfc60289debf74293817c0a8f572ff32..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_project/deps/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# Ignore everything in this directory
-*
-# Except this file
-!.gitignore
diff --git a/packages/HighFive/tests/test_project/read_write_vector_dataset.cpp b/packages/HighFive/tests/test_project/read_write_vector_dataset.cpp
deleted file mode 120000
index 84b5175b888472b478660178c7ec96b5edac3cf9..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_project/read_write_vector_dataset.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../../src/examples/read_write_vector_dataset.cpp
\ No newline at end of file
diff --git a/packages/HighFive/tests/test_project_integration.sh b/packages/HighFive/tests/test_project_integration.sh
deleted file mode 100644
index 95521995269edcc75362c34cfd755ccad37e6888..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/test_project_integration.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/sh
-set -xeo pipefail
-cd "$( dirname "${BASH_SOURCE[0]}")"  # cd here
-
-BUILD_DIR="${PWD}/build-highfive"
-ROOT="${PWD}/.."
-TEST_DIR="${PWD}"
-INSTALL_DIR="${BUILD_DIR}/install"
-
-test_install() {
-    local project="${1}"
-    local project_dir="${TEST_DIR}/${project}"
-    local dep_dir="${TEST_DIR}/${project}/deps/HighFive"
-    shift
-
-    pushd "${project_dir}"
-
-    local build_dir="build"
-
-    ln -sf ../../.. "${dep_dir}"
-
-    cmake "$@" -B "${build_dir}" .
-    cmake --build "${build_dir}" --verbose
-    ctest --test-dir "${build_dir}"
-
-    rm -f "${dep_dir}"
-    rm -rf "${build_dir}"
-
-    popd
-}
-
-cmake "${ROOT}" \
-    -DHIGHFIVE_EXAMPLES=OFF \
-    -DHIGHFIVE_UNIT_TESTS=OFF \
-    -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
-    -B "${BUILD_DIR}"
-cmake --build "${BUILD_DIR}" --target install
-
-for project in test_project test_dependent_library; do
-    # Case 1. Base case: include subdirectory
-    test_install "${project}"
-
-    # Case 2. We use an install dir and all deps configuration
-    # Install highfive (no tests required)
-    test_install "${project}" \
-        -DUSE_BUNDLED_HIGHFIVE=NO \
-        -DHIGHFIVE_USE_INSTALL_DEPS=YES \
-        -DCMAKE_PREFIX_PATH="${INSTALL_DIR}"
-
-    # Case 3. We redetect-dependencies
-    test_install "${project}" \
-        -DUSE_BUNDLED_HIGHFIVE=NO \
-        -DHIGHFIVE_USE_INSTALL_DEPS=NO \
-        -DCMAKE_PREFIX_PATH="${INSTALL_DIR}"
-done
-
-rm -rf "${BUILD_DIR}"
diff --git a/packages/HighFive/tests/unit/CMakeLists.txt b/packages/HighFive/tests/unit/CMakeLists.txt
index 2f01bdd81473cbc46e8ca990127e85b1c6d7eb34..10cff1f6fe24a5c8c3e4927bc7ec083fd85fdcc5 100644
--- a/packages/HighFive/tests/unit/CMakeLists.txt
+++ b/packages/HighFive/tests/unit/CMakeLists.txt
@@ -1,25 +1,26 @@
 include(CTest)
 include(Catch)
-include(HighFiveWarnings)
 
 if(MSVC)
   add_definitions(/bigobj)
 endif()
 
 ## Base tests
-foreach(test_name tests_high_five_base tests_high_five_multi_dims tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type)
+foreach(test_name tests_high_five_base tests_high_five_easy test_all_types test_high_five_selection tests_high_five_data_type test_boost test_empty_arrays test_legacy test_opencv test_string test_stl test_xtensor)
   add_executable(${test_name} "${test_name}.cpp")
-  target_link_libraries(${test_name} HighFive HighFiveWarnings Catch2::Catch2WithMain)
+  target_link_libraries(${test_name} HighFive HighFiveWarnings HighFiveFlags Catch2::Catch2WithMain)
+  target_link_libraries(${test_name} HighFiveOptionalDependencies)
+
   catch_discover_tests(${test_name})
 endforeach()
 
-if(HIGHFIVE_PARALLEL_HDF5)
-  include(TestHelpers)
+if(HDF5_IS_PARALLEL)
   set(tests_parallel_src "tests_high_five_parallel.cpp")
 
   ## parallel MPI tests
   add_executable(tests_parallel_bin ${tests_parallel_src})
-  target_link_libraries(tests_parallel_bin HighFive HighFiveWarnings Catch2::Catch2)
+  target_link_libraries(tests_parallel_bin HighFive HighFiveWarnings HighFiveFlags Catch2::Catch2)
+  target_link_libraries(tests_parallel_bin HighFiveOptionalDependencies)
 
   # We need to patch in a call to `mpirun` or equivalent when using
   # parallel tests. Somehow, this is not foreseen in Catch2, modify the
@@ -29,7 +30,7 @@ if(HIGHFIVE_PARALLEL_HDF5)
   file(READ "${original_catch_script}" original_catch_script_contents)
   string(REGEX REPLACE
          "(add_command\\(add_test.*TEST_EXECUTOR})"
-         "\\1 ${TEST_MPI_EXEC_PREFIX_DEFAULT} -n 2"
+         "\\1 ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} 2"
          modified_catch_script_contents
          "${original_catch_script_contents}")
   if(original_catch_script_contents STREQUAL modified_catch_script_contents)
@@ -41,16 +42,43 @@ if(HIGHFIVE_PARALLEL_HDF5)
   set(_CATCH_DISCOVER_TESTS_SCRIPT "${original_catch_script}")
 endif()
 
-option(HIGHFIVE_TEST_SINGLE_INCLUDES "Enable testing single includes" FALSE)
+# Test that each public header is self-sufficient. This is done by
+# creating a file for each header, that only includes the header. The
+# test succeeds if it compiles.
+file(GLOB public_headers LIST_DIRECTORIES false RELATIVE ${PROJECT_SOURCE_DIR}/include CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/include/highfive/*.hpp)
+foreach(PUBLIC_HEADER ${public_headers})
+    if(PUBLIC_HEADER STREQUAL "highfive/span.hpp" AND NOT HIGHFIVE_TEST_SPAN)
+      continue()
+    endif()
 
-if(HIGHFIVE_TEST_SINGLE_INCLUDES)
-    file(GLOB public_headers LIST_DIRECTORIES false RELATIVE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include/highfive/*.hpp)
-    foreach(PUBLIC_HEADER ${public_headers})
-        get_filename_component(CLASS_NAME ${PUBLIC_HEADER} NAME_WE)
-        configure_file(tests_import_public_headers.cpp "tests_${CLASS_NAME}.cpp" @ONLY)
-        add_executable("tests_include_${CLASS_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/tests_${CLASS_NAME}.cpp")
-        target_link_libraries("tests_include_${CLASS_NAME}" HighFive HighFiveWarnings)
-    endforeach()
-endif()
+    if(PUBLIC_HEADER STREQUAL "highfive/boost.hpp" AND NOT HIGHFIVE_TEST_BOOST)
+      continue()
+    endif()
+
+    if(PUBLIC_HEADER STREQUAL "highfive/half_float.hpp" AND NOT HIGHFIVE_TEST_HALF_FLOAT)
+      continue()
+    endif()
+
+    if(PUBLIC_HEADER STREQUAL "highfive/eigen.hpp" AND NOT HIGHFIVE_TEST_EIGEN)
+      continue()
+    endif()
 
-add_subdirectory(deprecated)
+    if(PUBLIC_HEADER STREQUAL "highfive/opencv.hpp" AND NOT HIGHFIVE_TEST_OPENCV)
+      continue()
+    endif()
+
+    if(PUBLIC_HEADER STREQUAL "highfive/xtensor.hpp" AND NOT HIGHFIVE_TEST_XTENSOR)
+      continue()
+    endif()
+
+    get_filename_component(CLASS_NAME ${PUBLIC_HEADER} NAME_WE)
+    configure_file(tests_import_public_headers.cpp "tests_${CLASS_NAME}.cpp" @ONLY)
+    add_executable("tests_include_${CLASS_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/tests_${CLASS_NAME}.cpp")
+    target_link_libraries(
+        "tests_include_${CLASS_NAME}" PUBLIC
+        HighFive
+        HighFiveWarnings
+        HighFiveFlags
+        HighFiveOptionalDependencies
+    )
+endforeach()
diff --git a/packages/HighFive/tests/unit/compary_arrays.hpp b/packages/HighFive/tests/unit/compary_arrays.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8a867a55d8607280928a0264af6e1345655c041a
--- /dev/null
+++ b/packages/HighFive/tests/unit/compary_arrays.hpp
@@ -0,0 +1,80 @@
+/*
+ *  Copyright (c), 2023, 2024 Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+
+#include <string>
+#include <sstream>
+#include <type_traits>
+
+#include <catch2/catch_template_test_macros.hpp>
+
+#include "data_generator.hpp"
+
+namespace HighFive {
+namespace testing {
+
+template <class T, class = void>
+struct DiffMessageTrait;
+
+template <class T>
+struct DiffMessageTrait<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
+    static std::string diff(T a, T b) {
+        std::stringstream sstream;
+        sstream << std::scientific << " delta: " << a - b;
+        return sstream.str();
+    }
+};
+
+template <class T>
+struct DiffMessageTrait<T, typename std::enable_if<!std::is_floating_point<T>::value>::type> {
+    static std::string diff(T /* a */, T /* b */) {
+        return "";
+    }
+};
+
+template <class T>
+std::string diff_message(T a, T b) {
+    return DiffMessageTrait<T>::diff(a, b);
+}
+
+template <class Actual, class Expected, class Comp>
+void compare_arrays(const Actual& actual,
+                    const Expected& expected,
+                    const std::vector<size_t>& dims,
+                    Comp comp) {
+    using actual_trait = testing::ContainerTraits<Actual>;
+    using expected_trait = testing::ContainerTraits<Expected>;
+    using base_type = typename actual_trait::base_type;
+
+    auto n = testing::flat_size(dims);
+
+    for (size_t i = 0; i < n; ++i) {
+        auto indices = testing::unravel(i, dims);
+        base_type actual_value = actual_trait::get(actual, indices);
+        base_type expected_value = expected_trait::get(expected, indices);
+        auto c = comp(actual_value, expected_value);
+        if (!c) {
+            std::stringstream sstream;
+            sstream << std::scientific << "i = " << i << ": " << actual_value
+                    << " != " << expected_value << diff_message(actual_value, expected_value);
+            INFO(sstream.str());
+        }
+        REQUIRE(c);
+    }
+}
+
+template <class Actual, class Expected>
+void compare_arrays(const Actual& actual,
+                    const Expected& expected,
+                    const std::vector<size_t>& dims) {
+    using base_type = typename testing::ContainerTraits<Actual>::base_type;
+    compare_arrays(expected, actual, dims, [](base_type a, base_type b) { return a == b; });
+}
+
+}  // namespace testing
+}  // namespace HighFive
diff --git a/packages/HighFive/tests/unit/data_generator.hpp b/packages/HighFive/tests/unit/data_generator.hpp
index 9a6712d53208a762a493ab257e8a54feea27cf9a..e4e7dfa85a28f50bdaa53fdd59c5bc9afa5cc4c9 100644
--- a/packages/HighFive/tests/unit/data_generator.hpp
+++ b/packages/HighFive/tests/unit/data_generator.hpp
@@ -7,16 +7,30 @@
 #include <vector>
 #include <array>
 
-#ifdef H5_USE_BOOST
-#include <boost/multi_array.hpp>
+#include <highfive/bits/H5Inspector_misc.hpp>
+
+#ifdef HIGHFIVE_TEST_BOOST
+#include <highfive/boost.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_EIGEN
+#include <highfive/eigen.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_SPAN
+#include <highfive/span.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_XTENSOR
+#include <highfive/xtensor.hpp>
 #endif
 
-#include <highfive/bits/H5Inspector_misc.hpp>
 
 namespace HighFive {
 namespace testing {
 
-std::vector<size_t> lstrip(const std::vector<size_t>& indices, size_t n) {
+template <class Dims>
+std::vector<size_t> lstrip(const Dims& indices, size_t n) {
     std::vector<size_t> subindices(indices.size() - n);
     for (size_t i = 0; i < subindices.size(); ++i) {
         subindices[i] = indices[i + n];
@@ -25,7 +39,8 @@ std::vector<size_t> lstrip(const std::vector<size_t>& indices, size_t n) {
     return subindices;
 }
 
-size_t ravel(std::vector<size_t>& indices, const std::vector<size_t> dims) {
+template <class Dims>
+size_t ravel(std::vector<size_t>& indices, const Dims& dims) {
     size_t rank = dims.size();
     size_t linear_index = 0;
     size_t ld = 1;
@@ -38,7 +53,8 @@ size_t ravel(std::vector<size_t>& indices, const std::vector<size_t> dims) {
     return linear_index;
 }
 
-std::vector<size_t> unravel(size_t flat_index, const std::vector<size_t> dims) {
+template <class Dims>
+std::vector<size_t> unravel(size_t flat_index, const Dims& dims) {
     size_t rank = dims.size();
     size_t ld = 1;
     std::vector<size_t> indices(rank);
@@ -51,7 +67,8 @@ std::vector<size_t> unravel(size_t flat_index, const std::vector<size_t> dims) {
     return indices;
 }
 
-static size_t flat_size(const std::vector<size_t>& dims) {
+template <class Dims>
+static size_t flat_size(const Dims& dims) {
     size_t n = 1;
     for (auto d: dims) {
         n *= d;
@@ -69,6 +86,9 @@ struct ScalarContainerTraits {
     using container_type = T;
     using base_type = T;
 
+    static constexpr bool is_view = false;
+    static constexpr size_t rank = 0;
+
     static void set(container_type& array, std::vector<size_t> /* indices */, base_type value) {
         array = value;
     }
@@ -85,6 +105,8 @@ struct ScalarContainerTraits {
         return container_type{};
     }
 
+    static void deallocate(container_type& /* array */, const std::vector<size_t>& /* dims */) {}
+
     static void sanitize_dims(std::vector<size_t>& /* dims */, size_t /* axis */) {}
 };
 
@@ -106,6 +128,9 @@ struct ContainerTraits<std::vector<bool>> {
     using value_type = bool;
     using base_type = bool;
 
+    static constexpr bool is_view = false;
+    static constexpr size_t rank = 1;
+
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
                     const base_type& value) {
@@ -125,6 +150,8 @@ struct ContainerTraits<std::vector<bool>> {
         return array;
     }
 
+    static void deallocate(container_type& /* array */, const std::vector<size_t>& /* dims */) {}
+
     static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
         ContainerTraits<value_type>::sanitize_dims(dims, axis + 1);
     }
@@ -136,6 +163,9 @@ struct STLLikeContainerTraits {
     using value_type = ValueType;
     using base_type = typename ContainerTraits<value_type>::base_type;
 
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 1 + ContainerTraits<value_type>::rank;
+
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
                     const base_type& value) {
@@ -151,15 +181,22 @@ struct STLLikeContainerTraits {
     }
 
     static container_type allocate(const std::vector<size_t>& dims) {
-        container_type array(dims[0]);
+        container_type array;
+        array.reserve(dims[0]);
         for (size_t i = 0; i < dims[0]; ++i) {
             auto value = ContainerTraits<value_type>::allocate(lstrip(dims, 1));
-            ContainerTraits<value_type>::assign(array[i], value);
+            array.push_back(value);
         }
 
         return array;
     }
 
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        for (size_t i = 0; i < dims[0]; ++i) {
+            ContainerTraits<value_type>::deallocate(array[i], lstrip(dims, 1));
+        }
+    }
+
     static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
         ContainerTraits<value_type>::sanitize_dims(dims, axis + 1);
     }
@@ -199,14 +236,96 @@ struct ContainerTraits<std::array<T, N>>: public STLLikeContainerTraits<std::arr
     }
 };
 
+
+#ifdef HIGHFIVE_TEST_SPAN
+template <class T, std::size_t Extent>
+struct ContainerTraits<std::span<T, Extent>>: public STLLikeContainerTraits<std::span<T, Extent>> {
+  private:
+    using super = STLLikeContainerTraits<std::span<T, Extent>>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+
+    static constexpr bool is_view = true;
+
+    static container_type allocate(const std::vector<size_t>& dims) {
+        size_t n_elements = dims[0];
+        value_type* ptr = new value_type[n_elements];
+
+        container_type array = container_type(ptr, n_elements);
+
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto element = ContainerTraits<value_type>::allocate(lstrip(dims, 1));
+            ContainerTraits<value_type>::assign(array[i], element);
+        }
+
+        return array;
+    }
+
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        size_t n_elements = dims[0];
+        for (size_t i = 0; i < n_elements; ++i) {
+            ContainerTraits<value_type>::deallocate(array[i], lstrip(dims, 1));
+        }
+
+        delete[] array.data();
+    }
+
+    static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
+        if (Extent != std::dynamic_extent) {
+            dims[axis] = Extent;
+            ContainerTraits<value_type>::sanitize_dims(dims, axis + 1);
+        }
+    }
+};
+#endif
+
+
+template <class T, size_t n>
+struct ContainerTraits<T[n]> {
+    using container_type = T[n];
+    using value_type = T;
+    using base_type = typename ContainerTraits<value_type>::base_type;
+
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 1 + ContainerTraits<value_type>::rank;
+
+    static void set(container_type& array,
+                    const std::vector<size_t>& indices,
+                    const base_type& value) {
+        return ContainerTraits<value_type>::set(array[indices[0]], lstrip(indices, 1), value);
+    }
+
+    static base_type get(const container_type& array, const std::vector<size_t>& indices) {
+        return ContainerTraits<value_type>::get(array[indices[0]], lstrip(indices, 1));
+    }
+
+    static void assign(container_type& dst, const container_type& src) {
+        for (size_t i = 0; i < n; ++i) {
+            dst[i] = src[i];
+        }
+    }
+
+    static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
+        dims[axis] = n;
+        ContainerTraits<value_type>::sanitize_dims(dims, axis + 1);
+    }
+};
+
+
 // -- Boost  -------------------------------------------------------------------
-#ifdef H5_USE_BOOST
+#ifdef HIGHFIVE_TEST_BOOST
 template <class T, size_t n>
 struct ContainerTraits<boost::multi_array<T, n>> {
     using container_type = typename boost::multi_array<T, n>;
     using value_type = T;
     using base_type = typename ContainerTraits<value_type>::base_type;
 
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = n + ContainerTraits<value_type>::rank;
+
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
                     const base_type& value) {
@@ -238,6 +357,14 @@ struct ContainerTraits<boost::multi_array<T, n>> {
         return array;
     }
 
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + n);
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i = 0; i < n_elements; ++i) {
+            ContainerTraits<value_type>::deallocate(array(unravel(i, local_dims)), lstrip(dims, n));
+        }
+    }
+
     static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
         ContainerTraits<value_type>::sanitize_dims(dims, axis + n);
     }
@@ -249,6 +376,9 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> {
     using value_type = T;
     using base_type = typename ContainerTraits<value_type>::base_type;
 
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 2 + ContainerTraits<value_type>::rank;
+
     static void set(container_type& array,
                     const std::vector<size_t>& indices,
                     const base_type& value) {
@@ -282,6 +412,15 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> {
         return array;
     }
 
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + 2);
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto indices = unravel(i, local_dims);
+            ContainerTraits<value_type>::deallocate(array(indices[0], indices[1]), lstrip(dims, 2));
+        }
+    }
+
     static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
         ContainerTraits<value_type>::sanitize_dims(dims, axis + 2);
     }
@@ -289,6 +428,228 @@ struct ContainerTraits<boost::numeric::ublas::matrix<T>> {
 
 #endif
 
+// -- Eigen  -------------------------------------------------------------------
+#if HIGHFIVE_TEST_EIGEN
+
+template <typename EigenType>
+struct EigenContainerTraits {
+    using container_type = EigenType;
+    using value_type = typename EigenType::Scalar;
+    using base_type = typename ContainerTraits<value_type>::base_type;
+
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+    static constexpr size_t rank = 2 + ContainerTraits<value_type>::rank;
+
+    static void set(container_type& array,
+                    const std::vector<size_t>& indices,
+                    const base_type& value) {
+        auto i = static_cast<Eigen::Index>(indices[0]);
+        auto j = static_cast<Eigen::Index>(indices[1]);
+        return ContainerTraits<value_type>::set(array(i, j), lstrip(indices, 2), value);
+    }
+
+    static base_type get(const container_type& array, const std::vector<size_t>& indices) {
+        auto i = static_cast<Eigen::Index>(indices[0]);
+        auto j = static_cast<Eigen::Index>(indices[1]);
+        return ContainerTraits<value_type>::get(array(i, j), lstrip(indices, 2));
+    }
+
+    static void assign(container_type& dst, const container_type& src) {
+        dst = src;
+    }
+
+    static container_type allocate(const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + 2);
+        auto n_rows = static_cast<Eigen::Index>(local_dims[0]);
+        auto n_cols = static_cast<Eigen::Index>(local_dims[1]);
+        container_type array = container_type::Zero(n_rows, n_cols);
+
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto element = ContainerTraits<value_type>::allocate(lstrip(dims, 2));
+            set(array, unravel(i, local_dims), element);
+        }
+
+        return array;
+    }
+
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + 2);
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i_flat = 0; i_flat < n_elements; ++i_flat) {
+            auto indices = unravel(i_flat, local_dims);
+            auto i = static_cast<Eigen::Index>(indices[0]);
+            auto j = static_cast<Eigen::Index>(indices[1]);
+            ContainerTraits<value_type>::deallocate(array(i, j), lstrip(dims, 2));
+        }
+    }
+
+    static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
+        if (EigenType::RowsAtCompileTime != Eigen::Dynamic) {
+            dims[axis + 0] = static_cast<size_t>(EigenType::RowsAtCompileTime);
+        }
+
+        if (EigenType::ColsAtCompileTime != Eigen::Dynamic) {
+            dims[axis + 1] = static_cast<size_t>(EigenType::ColsAtCompileTime);
+        }
+        ContainerTraits<value_type>::sanitize_dims(dims, axis + 2);
+    }
+};
+
+template <class T, int N_ROWS, int N_COLS, int Options>
+struct ContainerTraits<Eigen::Matrix<T, N_ROWS, N_COLS, Options>>
+    : public EigenContainerTraits<Eigen::Matrix<T, N_ROWS, N_COLS, Options>> {
+  private:
+    using super = EigenContainerTraits<Eigen::Matrix<T, N_ROWS, N_COLS, Options>>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+};
+
+template <class T, int N_ROWS, int N_COLS, int Options>
+struct ContainerTraits<Eigen::Array<T, N_ROWS, N_COLS, Options>>
+    : public EigenContainerTraits<Eigen::Array<T, N_ROWS, N_COLS, Options>> {
+  private:
+    using super = EigenContainerTraits<Eigen::Array<T, N_ROWS, N_COLS, Options>>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+};
+
+template <class PlainObjectType, int MapOptions>
+struct ContainerTraits<Eigen::Map<PlainObjectType, MapOptions>>
+    : public EigenContainerTraits<Eigen::Map<PlainObjectType, MapOptions>> {
+  private:
+    using super = EigenContainerTraits<Eigen::Map<PlainObjectType, MapOptions>>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+
+    static constexpr bool is_view = true;
+
+    static container_type allocate(const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + 2);
+        auto n_rows = static_cast<Eigen::Index>(local_dims[0]);
+        auto n_cols = static_cast<Eigen::Index>(local_dims[1]);
+
+        size_t n_elements = flat_size(local_dims);
+        value_type* ptr = new value_type[n_elements];
+
+        container_type array = container_type(ptr, n_rows, n_cols);
+
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto element = ContainerTraits<value_type>::allocate(lstrip(dims, 2));
+            ContainerTraits::set(array, unravel(i, local_dims), element);
+        }
+
+        return array;
+    }
+
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + 2);
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i_flat = 0; i_flat < n_elements; ++i_flat) {
+            auto indices = unravel(i_flat, local_dims);
+            auto i = static_cast<Eigen::Index>(indices[0]);
+            auto j = static_cast<Eigen::Index>(indices[1]);
+            ContainerTraits<value_type>::deallocate(array(i, j), lstrip(dims, 2));
+        }
+
+        delete[] array.data();
+    }
+};
+
+
+#endif
+
+// -- XTensor  -----------------------------------------------------------------
+
+#if HIGHFIVE_TEST_XTENSOR
+template <typename XTensorType, size_t Rank>
+struct XTensorContainerTraits {
+    using container_type = XTensorType;
+    using value_type = typename container_type::value_type;
+    using base_type = typename ContainerTraits<value_type>::base_type;
+
+    static constexpr size_t rank = Rank;
+    static constexpr bool is_view = ContainerTraits<value_type>::is_view;
+
+    static void set(container_type& array,
+                    const std::vector<size_t>& indices,
+                    const base_type& value) {
+        std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank);
+        return ContainerTraits<value_type>::set(array[local_indices], lstrip(indices, rank), value);
+    }
+
+    static base_type get(const container_type& array, const std::vector<size_t>& indices) {
+        std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank);
+        return ContainerTraits<value_type>::get(array[local_indices], lstrip(indices, rank));
+    }
+
+    static void assign(container_type& dst, const container_type& src) {
+        dst = src;
+    }
+
+    static container_type allocate(const std::vector<size_t>& dims) {
+        const auto& local_dims = details::inspector<XTensorType>::shapeFromDims(dims);
+        auto array = container_type(local_dims);
+
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i = 0; i < n_elements; ++i) {
+            auto element = ContainerTraits<value_type>::allocate(lstrip(dims, rank));
+            set(array, unravel(i, local_dims), element);
+        }
+
+        return array;
+    }
+
+    static void deallocate(container_type& array, const std::vector<size_t>& dims) {
+        auto local_dims = std::vector<size_t>(dims.begin(), dims.begin() + rank);
+        size_t n_elements = flat_size(local_dims);
+        for (size_t i_flat = 0; i_flat < n_elements; ++i_flat) {
+            auto indices = unravel(i_flat, local_dims);
+            std::vector<size_t> local_indices(indices.begin(), indices.begin() + rank);
+            ContainerTraits<value_type>::deallocate(array[local_indices], lstrip(dims, rank));
+        }
+    }
+
+    static void sanitize_dims(std::vector<size_t>& dims, size_t axis) {
+        ContainerTraits<value_type>::sanitize_dims(dims, axis + rank);
+    }
+};
+
+template <class T, size_t rank, xt::layout_type layout>
+struct ContainerTraits<xt::xtensor<T, rank, layout>>
+    : public XTensorContainerTraits<xt::xtensor<T, rank, layout>, rank> {
+  private:
+    using super = XTensorContainerTraits<xt::xtensor<T, rank, layout>, rank>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+};
+
+template <class T, xt::layout_type layout>
+struct ContainerTraits<xt::xarray<T, layout>>
+    : public XTensorContainerTraits<xt::xarray<T, layout>, 2> {
+  private:
+    using super = XTensorContainerTraits<xt::xarray<T, layout>, 2>;
+
+  public:
+    using container_type = typename super::container_type;
+    using value_type = typename super::value_type;
+    using base_type = typename super::base_type;
+};
+
+#endif
+
 template <class T, class C>
 T copy(const C& src, const std::vector<size_t>& dims) {
     auto dst = ContainerTraits<T>::allocate(dims);
@@ -394,14 +755,46 @@ struct MultiDimVector<T, 0> {
     using type = T;
 };
 
+template <class C, class F>
+void initialize_impl(C& array,
+                     const std::vector<size_t>& dims,
+                     std::vector<size_t>& indices,
+                     size_t axis,
+                     F f) {
+    using traits = ContainerTraits<C>;
+    if (axis == indices.size()) {
+        auto value = f(indices);
+        traits::set(array, indices, value);
+    } else {
+        for (size_t i = 0; i < dims[axis]; ++i) {
+            indices[axis] = i;
+            initialize_impl(array, dims, indices, axis + 1, f);
+        }
+    }
+}
+
+template <class C, class F>
+void initialize(C& array, const std::vector<size_t>& dims, F f) {
+    std::vector<size_t> indices(dims.size());
+    initialize_impl(array, dims, indices, 0, f);
+}
+
+template <class C>
+void initialize(C& array, const std::vector<size_t>& dims) {
+    using traits = ContainerTraits<C>;
+    initialize(array, dims, DefaultValues<typename traits::base_type>());
+}
+
+
 template <class Container>
 class DataGenerator {
   public:
-    constexpr static size_t rank = details::inspector<Container>::recursive_ndim;
     using traits = ContainerTraits<Container>;
     using base_type = typename traits::base_type;
     using container_type = Container;
 
+    constexpr static size_t rank = traits::rank;
+
   public:
     static container_type allocate(const std::vector<size_t>& dims) {
         return traits::allocate(dims);
@@ -431,30 +824,6 @@ class DataGenerator {
     static void sanitize_dims(std::vector<size_t>& dims) {
         ContainerTraits<Container>::sanitize_dims(dims, /* axis = */ 0);
     }
-
-  private:
-    template <class C, class F>
-    static void initialize(C& array, const std::vector<size_t>& dims, F f) {
-        std::vector<size_t> indices(dims.size());
-        initialize(array, dims, indices, 0, f);
-    }
-
-    template <class C, class F>
-    static void initialize(C& array,
-                           const std::vector<size_t>& dims,
-                           std::vector<size_t>& indices,
-                           size_t axis,
-                           F f) {
-        if (axis == indices.size()) {
-            auto value = f(indices);
-            traits::set(array, indices, value);
-        } else {
-            for (size_t i = 0; i < dims[axis]; ++i) {
-                indices[axis] = i;
-                initialize(array, dims, indices, axis + 1, f);
-            }
-        }
-    }
 };
 
 }  // namespace testing
diff --git a/packages/HighFive/tests/unit/deprecated/CMakeLists.txt b/packages/HighFive/tests/unit/deprecated/CMakeLists.txt
deleted file mode 100644
index 5e515374bfe5c67779613a6d8e165153ccebb735..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/unit/deprecated/CMakeLists.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-foreach(test_name test_fixed_len_string_array)
-  add_executable(${test_name} "${test_name}.cpp")
-
-  target_link_libraries(${test_name} HighFive HighFiveWarnings Catch2::Catch2WithMain)
-  catch_discover_tests(${test_name})
-
-  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-    target_compile_options(${test_name} PRIVATE -Wno-deprecated-declarations)
-  endif()
-endforeach()
diff --git a/packages/HighFive/tests/unit/deprecated/test_fixed_len_string_array.cpp b/packages/HighFive/tests/unit/deprecated/test_fixed_len_string_array.cpp
deleted file mode 100644
index 1d0c33aaa243134c3e99c3c2d769cc3b3902bb4c..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/unit/deprecated/test_fixed_len_string_array.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-
-#include <highfive/highfive.hpp>
-#include "../tests_high_five.hpp"
-
-namespace HighFive {
-
-TEST_CASE("HighFiveFixedLenStringArray") {
-    const std::string file_name("fixed_len_string_array.h5");
-
-    // Create a new file using the default property lists.
-    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-
-    {  // Dedicated FixedLenStringArray (now deprecated).
-        FixedLenStringArray<10> arr{"0000000", "1111111"};
-
-        // More API: test inserting something
-        arr.push_back("2222");
-        auto ds = file.createDataSet("ds7", arr);  // Short syntax ok
-
-        // Recover truncating
-        FixedLenStringArray<4> array_back;
-        ds.read(array_back);
-        CHECK(array_back.size() == 3);
-        CHECK(array_back[0] == std::string("000"));
-        CHECK(array_back[1] == std::string("111"));
-        CHECK(array_back[2] == std::string("222"));
-        CHECK(array_back.getString(1) == "111");
-        CHECK(array_back.front() == std::string("000"));
-        CHECK(array_back.back() == std::string("222"));
-        CHECK(array_back.data() == std::string("000"));
-        array_back.data()[0] = 'x';
-        CHECK(array_back.data() == std::string("x00"));
-
-        for (auto& raw_elem: array_back) {
-            raw_elem[1] = 'y';
-        }
-        CHECK(array_back.getString(1) == "1y1");
-        for (auto iter = array_back.cbegin(); iter != array_back.cend(); ++iter) {
-            CHECK((*iter)[1] == 'y');
-        }
-    }
-}
-
-template <size_t N>
-static void check_fixed_len_string_array_contents(const FixedLenStringArray<N>& array,
-                                                  const std::vector<std::string>& expected) {
-    REQUIRE(array.size() == expected.size());
-
-    for (size_t i = 0; i < array.size(); ++i) {
-        CHECK(array[i] == expected[i]);
-    }
-}
-
-
-TEST_CASE("HighFiveFixedLenStringArrayStructure") {
-    using fixed_array_t = FixedLenStringArray<10>;
-    // increment the characters of a string written in a std::array
-    auto increment_string = [](const fixed_array_t::value_type arr) {
-        fixed_array_t::value_type output(arr);
-        for (auto& c: output) {
-            if (c == 0) {
-                break;
-            }
-            ++c;
-        }
-        return output;
-    };
-
-    SECTION("create from std::vector (onpoint)") {
-        auto expected = std::vector<std::string>{"000", "111"};
-        auto actual = FixedLenStringArray<4>(expected);
-        check_fixed_len_string_array_contents(actual, expected);
-    }
-
-    SECTION("create from std::vector (oversized)") {
-        auto expected = std::vector<std::string>{"000", "111"};
-        auto actual = FixedLenStringArray<8>(expected);
-        check_fixed_len_string_array_contents(actual, expected);
-    }
-
-    SECTION("create from pointers (onpoint)") {
-        auto expected = std::vector<std::string>{"000", "111"};
-        auto actual = FixedLenStringArray<4>(expected.data(), expected.data() + expected.size());
-        check_fixed_len_string_array_contents(actual, expected);
-    }
-
-    SECTION("create from pointers (oversized)") {
-        auto expected = std::vector<std::string>{"000", "111"};
-        auto actual = FixedLenStringArray<8>(expected.data(), expected.data() + expected.size());
-        check_fixed_len_string_array_contents(actual, expected);
-    }
-
-
-    SECTION("create from std::initializer_list (onpoint)") {
-        auto expected = std::vector<std::string>{"000", "111"};
-        auto actual = FixedLenStringArray<4>{"000", "111"};
-        check_fixed_len_string_array_contents(actual, expected);
-    }
-
-    SECTION("create from std::initializer_list (oversized)") {
-        auto expected = std::vector<std::string>{"000", "111"};
-        auto actual = FixedLenStringArray<8>{"000", "111"};
-        check_fixed_len_string_array_contents(actual, expected);
-    }
-
-    // manipulate FixedLenStringArray with std::copy
-    SECTION("compatible with std::copy") {
-        const fixed_array_t arr1{"0000000", "1111111"};
-        fixed_array_t arr2{"0000000", "1111111"};
-        std::copy(arr1.begin(), arr1.end(), std::back_inserter(arr2));
-        CHECK(arr2.size() == 4);
-    }
-
-    SECTION("compatible with std::transform") {
-        fixed_array_t arr;
-        {
-            const fixed_array_t arr1{"0000000", "1111111"};
-            std::transform(arr1.begin(), arr1.end(), std::back_inserter(arr), increment_string);
-        }
-        CHECK(arr.size() == 2);
-        CHECK(arr[0] == std::string("1111111"));
-        CHECK(arr[1] == std::string("2222222"));
-    }
-
-    SECTION("compatible with std::transform (reverse iterator)") {
-        fixed_array_t arr;
-        {
-            const fixed_array_t arr1{"0000000", "1111111"};
-            std::copy(arr1.rbegin(), arr1.rend(), std::back_inserter(arr));
-        }
-        CHECK(arr.size() == 2);
-        CHECK(arr[0] == std::string("1111111"));
-        CHECK(arr[1] == std::string("0000000"));
-    }
-
-    SECTION("compatible with std::remove_copy_if") {
-        fixed_array_t arr2;
-        {
-            const fixed_array_t arr1{"0000000", "1111111"};
-            std::remove_copy_if(arr1.begin(),
-                                arr1.end(),
-                                std::back_inserter(arr2),
-                                [](const fixed_array_t::value_type& s) {
-                                    return std::strncmp(s.data(), "1111111", 7) == 0;
-                                });
-        }
-        CHECK(arr2.size() == 1);
-        CHECK(arr2[0] == std::string("0000000"));
-    }
-}
-
-TEST_CASE("HighFiveFixedLenStringArrayAttribute") {
-    const std::string file_name("fixed_array_attr.h5");
-    // Create a new file using the default property lists.
-    {
-        File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-        FixedLenStringArray<10> arr{"Hello", "world"};
-        file.createAttribute("str", arr);
-    }
-    // Re-read it
-    {
-        File file(file_name);
-        FixedLenStringArray<8> arr;  // notice the output strings can be smaller
-        file.getAttribute("str").read(arr);
-        CHECK(arr.size() == 2);
-        CHECK(arr[0] == std::string("Hello"));
-        CHECK(arr[1] == std::string("world"));
-    }
-}
-
-}  // namespace HighFive
diff --git a/packages/HighFive/tests/unit/supported_types.hpp b/packages/HighFive/tests/unit/supported_types.hpp
index f708303b1dd55f7aced2e14f9b139fcd9b38fbc6..5f1bf6453da895e041e7167efa4c71ea09ccf827 100644
--- a/packages/HighFive/tests/unit/supported_types.hpp
+++ b/packages/HighFive/tests/unit/supported_types.hpp
@@ -1,4 +1,3 @@
-
 #pragma once
 
 #include <type_traits>
@@ -6,10 +5,15 @@
 #include <array>
 #include <tuple>
 
-#ifdef H5_USE_BOOST
+#ifdef HIGHFIVE_TEST_BOOST
 #include <boost/multi_array.hpp>
 #endif
 
+#ifdef HIGHFIVE_TEST_EIGEN
+#include <Eigen/Core>
+#include <Eigen/Dense>
+#endif
+
 namespace HighFive {
 namespace testing {
 
@@ -30,7 +34,15 @@ struct STDArray {
     using type = std::array<typename C::template type<T>, n>;
 };
 
-#ifdef H5_USE_BOOST
+#ifdef HIGHFIVE_TEST_SPAN
+template <class C = type_identity>
+struct STDSpan {
+    template <class T>
+    using type = std::span<typename C::template type<T>>;
+};
+#endif
+
+#ifdef HIGHFIVE_TEST_BOOST
 template <size_t n, class C = type_identity>
 struct BoostMultiArray {
     template <class T>
@@ -44,6 +56,46 @@ struct BoostUblasMatrix {
 };
 #endif
 
+#ifdef HIGHFIVE_TEST_EIGEN
+template <int n, int m, int Option, class C = type_identity>
+struct EigenMatrix {
+    template <class T>
+    using type = Eigen::Matrix<typename C::template type<T>, n, m, Option>;
+};
+
+template <int n, int m, int Option, class C = type_identity>
+struct EigenArray {
+    template <class T>
+    using type = Eigen::Array<typename C::template type<T>, n, m, Option>;
+};
+
+template <int n, int m, int Option, class C = type_identity>
+struct EigenMapArray {
+    template <class T>
+    using type = Eigen::Map<Eigen::Array<typename C::template type<T>, n, m, Option>>;
+};
+
+template <int n, int m, int Option, class C = type_identity>
+struct EigenMapMatrix {
+    template <class T>
+    using type = Eigen::Map<Eigen::Matrix<typename C::template type<T>, n, m, Option>>;
+};
+#endif
+
+#ifdef HIGHFIVE_TEST_XTENSOR
+template <size_t rank, xt::layout_type layout, class C = type_identity>
+struct XTensor {
+    template <class T>
+    using type = xt::xtensor<typename C::template type<T>, rank, layout>;
+};
+
+template <xt::layout_type layout, class C = type_identity>
+struct XArray {
+    template <class T>
+    using type = xt::xarray<typename C::template type<T>, layout>;
+};
+#endif
+
 template <class C, class Tuple>
 struct ContainerProduct;
 
@@ -66,7 +118,7 @@ struct ConcatenateTuples<std::tuple<Args1...>> {
 };
 
 // clang-format off
-using numeric_scalar_types = std::tuple<
+using all_numeric_scalar_types = std::tuple<
     int,
     unsigned int,
     long,
@@ -79,11 +131,20 @@ using numeric_scalar_types = std::tuple<
     unsigned long long
 >;
 
-using scalar_types = typename ConcatenateTuples<numeric_scalar_types, std::tuple<bool, std::string>>::type;
-using scalar_types_boost = typename ConcatenateTuples<numeric_scalar_types, std::tuple<bool>>::type;
+
+// To reduce the explosion of combinations, we don't always need
+// to test against every numeric scalar type. These three should
+// suffice.
+using some_numeric_scalar_types = std::tuple<char, int, double>;
+
+using all_scalar_types = typename ConcatenateTuples<all_numeric_scalar_types, std::tuple<bool, std::string>>::type;
+using some_scalar_types = typename ConcatenateTuples<some_numeric_scalar_types, std::tuple<bool, std::string>>::type;
+
+using scalar_types_boost = some_numeric_scalar_types;
+using scalar_types_eigen = some_numeric_scalar_types;
 
 using supported_array_types = typename ConcatenateTuples<
-#ifdef H5_USE_BOOST
+#ifdef HIGHFIVE_TEST_BOOST
   typename ContainerProduct<BoostMultiArray<3>, scalar_types_boost>::type,
   typename ContainerProduct<STDVector<BoostMultiArray<3>>, scalar_types_boost>::type,
   typename ContainerProduct<STDArray<5, BoostMultiArray<3>>, scalar_types_boost>::type,
@@ -92,14 +153,51 @@ using supported_array_types = typename ConcatenateTuples<
   typename ContainerProduct<STDVector<BoostUblasMatrix<>>, scalar_types_boost>::type,
   typename ContainerProduct<STDArray<5, BoostUblasMatrix<>>, scalar_types_boost>::type,
 #endif
-  typename ContainerProduct<STDVector<>, scalar_types>::type,
-  typename ContainerProduct<STDVector<STDVector<>>, scalar_types>::type,
-  typename ContainerProduct<STDVector<STDVector<STDVector<>>>, scalar_types>::type,
-  typename ContainerProduct<STDVector<STDVector<STDVector<STDVector<>>>>, scalar_types>::type,
-  typename ContainerProduct<STDArray<3>, scalar_types>::type,
-  typename ContainerProduct<STDArray<7, STDArray<5>>, scalar_types>::type,
-  typename ContainerProduct<STDVector<STDArray<5>>, scalar_types>::type,
-  typename ContainerProduct<STDArray<7, STDVector<>>, scalar_types>::type
+#ifdef HIGHFIVE_TEST_EIGEN
+  typename ContainerProduct<EigenMatrix<3, 5, Eigen::ColMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenMatrix<3, 5, Eigen::RowMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenMatrix<Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenMatrix<Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenArray<3, 5, Eigen::ColMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenArray<3, 5, Eigen::RowMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenArray<Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>, scalar_types_eigen>::type,
+  typename ContainerProduct<EigenArray<Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>, scalar_types_eigen>::type,
+  std::tuple<Eigen::Vector2d, Eigen::VectorXd>,
+  typename ContainerProduct<EigenMapMatrix<3, 5, Eigen::ColMajor>, scalar_types_eigen>::type,
+
+  typename ContainerProduct<STDVector<EigenMatrix<3, 5, Eigen::ColMajor>>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDVector<EigenArray<Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>>, scalar_types_eigen>::type,
+  std::tuple<std::vector<Eigen::Vector3d>, std::vector<Eigen::VectorXd>>,
+
+  typename ContainerProduct<STDArray<7, EigenMatrix<3, 5, Eigen::RowMajor>>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDArray<7, EigenArray<Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>, scalar_types_eigen>::type,
+  std::tuple<std::array<Eigen::VectorXd, 7>>,
+#endif
+#ifdef HIGHFIVE_TEST_SPAN
+  typename ContainerProduct<STDSpan<>, all_scalar_types>::type,
+  typename ContainerProduct<STDVector<STDSpan<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDArray<7, STDSpan<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDSpan<STDVector<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDSpan<STDArray<3>>, some_scalar_types>::type,
+#endif
+#ifdef HIGHFIVE_TEST_XTENSOR
+  typename ContainerProduct<XTensor<3, xt::layout_type::row_major>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDVector<XTensor<3, xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDArray<5, XTensor<3, xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<XTensor<3, xt::layout_type::column_major>, scalar_types_eigen>::type,
+  typename ContainerProduct<XArray<xt::layout_type::row_major>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDVector<XArray<xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<STDArray<5, XArray<xt::layout_type::row_major>>, scalar_types_eigen>::type,
+  typename ContainerProduct<XArray<xt::layout_type::column_major>, scalar_types_eigen>::type,
+#endif
+  typename ContainerProduct<STDVector<>, all_scalar_types>::type,
+  typename ContainerProduct<STDVector<STDVector<>>, some_scalar_types>::type,
+  typename ContainerProduct<STDVector<STDVector<STDVector<>>>, some_scalar_types>::type,
+  typename ContainerProduct<STDVector<STDVector<STDVector<STDVector<>>>>, some_scalar_types>::type,
+  typename ContainerProduct<STDArray<3>, some_scalar_types>::type,
+  typename ContainerProduct<STDArray<7, STDArray<5>>, some_scalar_types>::type,
+  typename ContainerProduct<STDVector<STDArray<5>>, some_scalar_types>::type,
+  typename ContainerProduct<STDArray<7, STDVector<>>, some_scalar_types>::type
 >::type;
 
 // clang-format on
diff --git a/packages/HighFive/tests/unit/test_all_types.cpp b/packages/HighFive/tests/unit/test_all_types.cpp
index e772fd1d7d932a1d6905c0b9bec62794f4b693cf..0a18e0257a8113195436d056dcdc7c0fa583dc5b 100644
--- a/packages/HighFive/tests/unit/test_all_types.cpp
+++ b/packages/HighFive/tests/unit/test_all_types.cpp
@@ -7,16 +7,18 @@
  *
  */
 #include <string>
-#include <sstream>
 
 #include <catch2/catch_template_test_macros.hpp>
 
 #include <highfive/highfive.hpp>
+
+
 #include <type_traits>
 #include "tests_high_five.hpp"
 #include "data_generator.hpp"
 #include "create_traits.hpp"
 #include "supported_types.hpp"
+#include "compary_arrays.hpp"
 
 using namespace HighFive;
 
@@ -50,170 +52,6 @@ TEMPLATE_TEST_CASE("Scalar in DataSet", "[Types]", bool, std::string) {
     }
 }
 
-TEMPLATE_PRODUCT_TEST_CASE("Scalar in std::vector", "[Types]", std::vector, (bool, std::string)) {
-    const std::string file_name("rw_dataset_" + typeNameHelper<TestType>() + ".h5");
-    const std::string dataset_name("dset");
-    TestType t1(5);
-
-    {
-        // Create a new file using the default property lists.
-        File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-
-        // Create the dataset
-        DataSet dataset = file.createDataSet(
-            dataset_name, {5}, create_datatype<typename details::inspector<TestType>::base_type>());
-
-        // Write into the initial part of the dataset
-        dataset.write(t1);
-    }
-
-    // read it back
-    {
-        File file(file_name, File::ReadOnly);
-
-        TestType value;
-        DataSet dataset = file.getDataSet("/" + dataset_name);
-        dataset.read(value);
-        CHECK(t1 == value);
-        CHECK(value.size() == 5);
-    }
-}
-
-TEMPLATE_PRODUCT_TEST_CASE("Scalar in std::vector<std::vector>",
-                           "[Types]",
-                           std::vector,
-                           (bool, std::string)) {
-    const std::string file_name("rw_dataset_vector_" + typeNameHelper<TestType>() + ".h5");
-    const std::string dataset_name("dset");
-    std::vector<TestType> t1(5);
-    for (auto&& e: t1) {
-        e.resize(6);
-    }
-
-    {
-        // Create a new file using the default property lists.
-        File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-
-        // Create the dataset
-        DataSet dataset = file.createDataSet(
-            dataset_name,
-            {5, 6},
-            create_datatype<typename details::inspector<std::vector<TestType>>::base_type>());
-
-        // Write into the initial part of the dataset
-        dataset.write(t1);
-    }
-
-    // read it back
-    {
-        File file(file_name, File::ReadOnly);
-
-        std::vector<TestType> value;
-        DataSet dataset = file.getDataSet("/" + dataset_name);
-        dataset.read(value);
-        CHECK(t1 == value);
-        CHECK(value.size() == 5);
-    }
-}
-
-TEMPLATE_TEST_CASE("Scalar in std::array", "[Types]", bool, std::string) {
-    const std::string file_name("rw_dataset_array_" + typeNameHelper<TestType>() + ".h5");
-    const std::string dataset_name("dset");
-    std::array<TestType, 5> t1{};
-
-    {
-        // Create a new file using the default property lists.
-        File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-
-        // Create the dataset
-        DataSet dataset = file.createDataSet(
-            dataset_name,
-            {5},
-            create_datatype<typename details::inspector<std::array<TestType, 5>>::base_type>());
-
-        // Write into the initial part of the dataset
-        dataset.write(t1);
-    }
-
-    // read it back
-    {
-        File file(file_name, File::ReadOnly);
-
-        std::array<TestType, 5> value;
-        DataSet dataset = file.getDataSet("/" + dataset_name);
-        dataset.read(value);
-        CHECK(t1 == value);
-        CHECK(value.size() == 5);
-    }
-}
-
-TEMPLATE_TEST_CASE("Scalar in std::vector<std::array>", "[Types]", bool, std::string) {
-    const std::string file_name("rw_dataset_vector_array_" + typeNameHelper<TestType>() + ".h5");
-    const std::string dataset_name("dset");
-    std::vector<std::array<TestType, 6>> t1(5);
-
-    {
-        // Create a new file using the default property lists.
-        File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-
-        // Create the dataset
-        DataSet dataset = file.createDataSet(
-            dataset_name,
-            {5, 6},
-            create_datatype<
-                typename details::inspector<std::vector<std::array<TestType, 5>>>::base_type>());
-
-        // Write into the initial part of the dataset
-        dataset.write(t1);
-    }
-
-    // read it back
-    {
-        File file(file_name, File::ReadOnly);
-
-        std::vector<std::array<TestType, 6>> value;
-        DataSet dataset = file.getDataSet("/" + dataset_name);
-        dataset.read(value);
-        CHECK(t1 == value);
-        CHECK(value.size() == 5);
-    }
-}
-
-TEMPLATE_TEST_CASE("Scalar in std::array<std::vector>", "[Types]", bool, std::string) {
-    const std::string file_name("rw_dataset_array_vector" + typeNameHelper<TestType>() + ".h5");
-    const std::string dataset_name("dset");
-    std::array<std::vector<TestType>, 6> t1;
-    for (auto& tt: t1) {
-        tt = std::vector<TestType>(5);
-    }
-
-    {
-        // Create a new file using the default property lists.
-        File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-
-        // Create the dataset
-        DataSet dataset = file.createDataSet(
-            dataset_name,
-            {6, 5},
-            create_datatype<
-                typename details::inspector<std::vector<std::array<TestType, 5>>>::base_type>());
-
-        // Write into the initial part of the dataset
-        dataset.write(t1);
-    }
-
-    // read it back
-    {
-        File file(file_name, File::ReadOnly);
-
-        std::array<std::vector<TestType>, 6> value;
-        DataSet dataset = file.getDataSet("/" + dataset_name);
-        dataset.read(value);
-        CHECK(t1 == value);
-        CHECK(value.size() == 6);
-    }
-}
-
 #if HIGHFIVE_CXX_STD >= 17
 TEMPLATE_PRODUCT_TEST_CASE("Scalar in std::vector<std::byte>", "[Types]", std::vector, std::byte) {
     const std::string file_name("rw_dataset_vector_" + typeNameHelper<TestType>() + ".h5");
@@ -244,68 +82,16 @@ TEMPLATE_PRODUCT_TEST_CASE("Scalar in std::vector<std::byte>", "[Types]", std::v
 }
 #endif
 
-template <class T, class = void>
-struct DiffMessageTrait;
-
-template <class T>
-struct DiffMessageTrait<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
-    static std::string diff(T a, T b) {
-        std::stringstream sstream;
-        sstream << std::scientific << " delta: " << a - b;
-        return sstream.str();
-    }
-};
-
-template <class T>
-struct DiffMessageTrait<T, typename std::enable_if<!std::is_floating_point<T>::value>::type> {
-    static std::string diff(T /* a */, T /* b */) {
-        return "";
-    }
-};
 
-template <class T>
-std::string diff_message(T a, T b) {
-    return DiffMessageTrait<T>::diff(a, b);
-}
-
-template <class Actual, class Expected, class Comp>
-void compare_arrays(const Actual& actual,
-                    const Expected& expected,
-                    const std::vector<size_t>& dims,
-                    Comp comp) {
-    using actual_trait = testing::ContainerTraits<Actual>;
-    using expected_trait = testing::ContainerTraits<Expected>;
-    using base_type = typename actual_trait::base_type;
-
-    auto n = testing::flat_size(dims);
-
-    for (size_t i = 0; i < n; ++i) {
-        auto indices = testing::unravel(i, dims);
-        base_type actual_value = actual_trait::get(actual, indices);
-        base_type expected_value = expected_trait::get(expected, indices);
-        auto c = comp(actual_value, expected_value);
-        if (!c) {
-            std::stringstream sstream;
-            sstream << std::scientific << "i = " << i << ": " << actual_value
-                    << " != " << expected_value << diff_message(actual_value, expected_value);
-            INFO(sstream.str());
-        }
-        REQUIRE(c);
-    }
-}
-
-template <class Actual, class Expected>
-void compare_arrays(const Actual& actual,
-                    const Expected& expected,
-                    const std::vector<size_t>& dims) {
-    using base_type = typename testing::ContainerTraits<Actual>::base_type;
-    compare_arrays(expected, actual, dims, [](base_type a, base_type b) { return a == b; });
+template <class Container, class Expected, class Obj>
+auto check_read_auto(const Expected& expected, const std::vector<size_t>& dims, const Obj& obj) ->
+    typename std::enable_if<!testing::ContainerTraits<Container>::is_view>::type {
+    testing::compare_arrays(obj.template read<Container>(), expected, dims);
 }
 
 template <class Container, class Expected, class Obj>
-void check_read_auto(const Expected& expected, const std::vector<size_t>& dims, const Obj& obj) {
-    compare_arrays(obj.template read<Container>(), expected, dims);
-}
+auto check_read_auto(const Expected&, const std::vector<size_t>&, const Obj&) ->
+    typename std::enable_if<testing::ContainerTraits<Container>::is_view>::type {}
 
 template <class Container, class Expected, class Obj>
 void check_read_preallocated(const Expected& expected,
@@ -314,7 +100,9 @@ void check_read_preallocated(const Expected& expected,
     auto actual = testing::DataGenerator<Container>::allocate(dims);
     obj.read(actual);
 
-    compare_arrays(actual, expected, dims);
+    testing::compare_arrays(actual, expected, dims);
+
+    testing::ContainerTraits<Container>::deallocate(actual, dims);
 }
 
 template <class Container>
@@ -349,6 +137,8 @@ void check_read_regular(const std::string& file_name, const std::vector<size_t>&
     SECTION("attr.read(values)") {
         check_read_preallocated<Container>(expected, dims, attr);
     }
+
+    testing::ContainerTraits<reference_type>::deallocate(expected, dims);
 }
 
 template <class Container>
@@ -377,11 +167,16 @@ void check_writing(const std::vector<size_t>& dims, Write write) {
     auto actual = testing::DataGenerator<reference_type>::allocate(dims);
     obj.read(actual);
 
-    compare_arrays(actual, expected, dims);
+    testing::compare_arrays(actual, expected, dims);
+
+    testing::ContainerTraits<reference_type>::deallocate(actual, dims);
+    testing::ContainerTraits<Container>::deallocate(values, dims);
+    testing::ContainerTraits<reference_type>::deallocate(expected, dims);
 }
 
 template <class CreateTraits, class Container>
-void check_write_auto(File& file, const std::string& name, const std::vector<size_t>& dims) {
+auto check_write_auto(File& file, const std::string& name, const std::vector<size_t>& dims) ->
+    typename std::enable_if<!testing::ContainerTraits<Container>::is_view>::type {
     auto write_auto = [&](const Container& values) {
         return CreateTraits::create(file, "auto_" + name, values);
     };
@@ -389,6 +184,10 @@ void check_write_auto(File& file, const std::string& name, const std::vector<siz
     check_writing<Container>(dims, write_auto);
 }
 
+template <class CreateTraits, class Container>
+auto check_write_auto(File&, const std::string&, const std::vector<size_t>&) ->
+    typename std::enable_if<testing::ContainerTraits<Container>::is_view>::type {}
+
 template <class CreateTraits, class Container>
 void check_write_deduce_type(File& file, const std::string& name, const std::vector<size_t>& dims) {
     auto write_two_phase_auto = [&](const Container& values) {
diff --git a/packages/HighFive/tests/unit/test_boost.cpp b/packages/HighFive/tests/unit/test_boost.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..72bca9e7748c1d8c4e994fb7680e18b632aa0efc
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_boost.cpp
@@ -0,0 +1,27 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+#if HIGHFIVE_TEST_BOOST
+#include <string>
+
+#include <catch2/catch_template_test_macros.hpp>
+
+#include <highfive/highfive.hpp>
+#include <highfive/boost.hpp>
+
+using namespace HighFive;
+
+TEST_CASE("Test boost::multi_array with fortran_storage_order") {
+    const std::string file_name("h5_multi_array_fortran.h5");
+    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
+
+    boost::multi_array<int, 2> ma(boost::extents[2][2], boost::fortran_storage_order());
+    auto dset = file.createDataSet<int>("main_dset", DataSpace::From(ma));
+    CHECK_THROWS_AS(dset.write(ma), DataTypeException);
+}
+#endif
diff --git a/packages/HighFive/tests/unit/test_empty_arrays.cpp b/packages/HighFive/tests/unit/test_empty_arrays.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ea447ffafde802cd57737eb3a19c2ff7f2a1aac8
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_empty_arrays.cpp
@@ -0,0 +1,248 @@
+/*
+ *  Copyright (c), 2017-2024, Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+#include <string>
+#include <vector>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+#include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+#ifdef HIGHFIVE_TEST_BOOST
+#include <highfive/boost.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_EIGEN
+#include <highfive/eigen.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_SPAN
+#include <highfive/span.hpp>
+#endif
+
+using namespace HighFive;
+using Catch::Matchers::Equals;
+
+
+template <int n_dim>
+struct CreateEmptyVector;
+
+template <>
+struct CreateEmptyVector<1> {
+    using container_type = std::vector<int>;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        return container_type(dims[0], 2);
+    }
+};
+
+template <int n_dim>
+struct CreateEmptyVector {
+    using container_type = std::vector<typename CreateEmptyVector<n_dim - 1>::container_type>;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
+        return container_type(dims[0], CreateEmptyVector<n_dim - 1>::create(subdims));
+    }
+};
+
+#ifdef HIGHFIVE_TEST_BOOST
+template <int n_dim>
+struct CreateEmptyBoostMultiArray {
+    using container_type = boost::multi_array<int, static_cast<long unsigned>(n_dim)>;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        auto container = container_type(dims);
+
+        auto raw_data = std::vector<int>(compute_total_size(dims));
+        container.assign(raw_data.begin(), raw_data.end());
+
+        return container;
+    }
+};
+#endif
+
+
+#ifdef HIGHFIVE_TEST_EIGEN
+struct CreateEmptyEigenVector {
+    using container_type = Eigen::VectorXi;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        return container_type::Constant(int(dims[0]), 2);
+    }
+};
+
+struct CreateEmptyEigenMatrix {
+    using container_type = Eigen::MatrixXi;
+
+    static container_type create(const std::vector<size_t>& dims) {
+        return container_type::Constant(int(dims[0]), int(dims[1]), 2);
+    }
+};
+#endif
+
+template <class Container>
+void check_empty_dimensions(const Container& container, const std::vector<size_t>& expected_dims) {
+    auto deduced_dims = details::inspector<Container>::getDimensions(container);
+
+    REQUIRE(expected_dims.size() == deduced_dims.size());
+
+    // The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1`
+    // or what the original dims said. The `1` allows broadcasting, the "same as original" enables
+    // statically sized objects, which conceptually have dims, even if there's no object.
+    bool allow_one = false;
+    for (size_t i = 0; i < expected_dims.size(); ++i) {
+        REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul))));
+
+        if (expected_dims[i] == 0) {
+            allow_one = true;
+        }
+    }
+}
+
+template <class CreateContainer>
+void check_empty_dimensions(const std::vector<size_t>& dims) {
+    auto input_data = CreateContainer::create(dims);
+    check_empty_dimensions(input_data, dims);
+}
+
+template <class ReadWriteInterface, class CreateContainer>
+void check_empty_read_write_cycle(const std::vector<size_t>& dims) {
+    using container_type = typename CreateContainer::container_type;
+
+    const std::string file_name("h5_empty_attr.h5");
+    const std::string dataset_name("dset");
+    File file(file_name, File::Truncate);
+
+    auto input_data = CreateContainer::create(dims);
+    ReadWriteInterface::create(file, dataset_name, input_data);
+
+    SECTION("read; one-dimensional vector (empty)") {
+        auto output_data = CreateEmptyVector<1>::create({0ul});
+
+        ReadWriteInterface::get(file, dataset_name).reshapeMemSpace({0ul}).read(output_data);
+        check_empty_dimensions(output_data, {0ul});
+    }
+
+    SECTION("read; pre-allocated (empty)") {
+        auto output_data = CreateContainer::create(dims);
+        ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data);
+
+        check_empty_dimensions(output_data, dims);
+    }
+
+    SECTION("read; pre-allocated (oversized)") {
+        auto oversize_dims = std::vector<size_t>(dims.size(), 2ul);
+        auto output_data = CreateContainer::create(oversize_dims);
+        ReadWriteInterface::get(file, dataset_name).reshapeMemSpace(dims).read(output_data);
+
+        check_empty_dimensions(output_data, dims);
+    }
+
+    SECTION("read; auto-allocated") {
+        auto output_data = ReadWriteInterface::get(file, dataset_name)
+                               .reshapeMemSpace(dims)
+                               .template read<container_type>();
+        check_empty_dimensions(output_data, dims);
+    }
+}
+
+template <class CreateContainer>
+void check_empty_dataset(const std::vector<size_t>& dims) {
+    check_empty_read_write_cycle<testing::DataSetCreateTraits, CreateContainer>(dims);
+}
+
+template <class CreateContainer>
+void check_empty_attribute(const std::vector<size_t>& dims) {
+    check_empty_read_write_cycle<testing::AttributeCreateTraits, CreateContainer>(dims);
+}
+
+template <class CreateContainer>
+void check_empty_everything(const std::vector<size_t>& dims) {
+    SECTION("Empty dimensions") {
+        check_empty_dimensions<CreateContainer>(dims);
+    }
+
+    SECTION("Empty datasets") {
+        check_empty_dataset<CreateContainer>(dims);
+    }
+
+    SECTION("Empty attribute") {
+        check_empty_attribute<CreateContainer>(dims);
+    }
+}
+
+#ifdef HIGHFIVE_TEST_EIGEN
+template <int ndim>
+void check_empty_eigen(const std::vector<size_t>&) {}
+
+template <>
+void check_empty_eigen<1>(const std::vector<size_t>& dims) {
+    SECTION("Eigen::Vector") {
+        check_empty_everything<CreateEmptyEigenVector>({dims[0], 1ul});
+    }
+}
+
+template <>
+void check_empty_eigen<2>(const std::vector<size_t>& dims) {
+    SECTION("Eigen::Matrix") {
+        check_empty_everything<CreateEmptyEigenMatrix>(dims);
+    }
+}
+#endif
+
+template <int ndim>
+void check_empty(const std::vector<size_t>& dims) {
+    REQUIRE(dims.size() == ndim);
+
+    SECTION("std::vector") {
+        check_empty_everything<CreateEmptyVector<ndim>>(dims);
+    }
+
+#ifdef HIGHFIVE_TEST_BOOST
+    SECTION("boost::multi_array") {
+        check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims);
+    }
+#endif
+
+#ifdef HIGHFIVE_TEST_EIGEN
+    check_empty_eigen<ndim>(dims);
+#endif
+}
+
+TEST_CASE("Empty arrays") {
+    SECTION("one-dimensional") {
+        check_empty<1>({0ul});
+    }
+
+    SECTION("two-dimensional") {
+        std::vector<std::vector<size_t>> testcases{{0ul, 1ul}, {1ul, 0ul}};
+
+        for (const auto& dims: testcases) {
+            SECTION(details::format_vector(dims)) {
+                check_empty<2>(dims);
+            }
+        }
+    }
+
+    SECTION("three-dimensional") {
+        std::vector<std::vector<size_t>> testcases{{0ul, 1ul, 1ul},
+                                                   {1ul, 1ul, 0ul},
+                                                   {1ul, 0ul, 1ul}};
+
+        for (const auto& dims: testcases) {
+            SECTION(details::format_vector(dims)) {
+                check_empty<3>(dims);
+            }
+        }
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_legacy.cpp b/packages/HighFive/tests/unit/test_legacy.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6981314733edfeb15d0476030b6b91188990d0e1
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_legacy.cpp
@@ -0,0 +1,74 @@
+// This file collects tests the require legacy behaviour of v2 (and older) to
+// pass. Tests in this file could be bugs too.
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+
+using namespace HighFive;
+
+TEST_CASE("HighFiveReadWriteConsts") {
+    // This test seems really strange. Essentially, it malloc's a 3**3 doubles.
+    // Then reinterpret_cast's the pointer to the first double (a `double *`)
+    // as a `double***`. And then uses `inspector` based code to write from the
+    // `double***`.
+
+    const std::string file_name("3d_dataset_from_flat.h5");
+    const std::string dataset_name("dset");
+    const std::array<std::size_t, 3> DIMS{3, 3, 3};
+    using datatype = int;
+
+    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
+    DataSpace dataspace = DataSpace(DIMS);
+
+    DataSet dataset = file.createDataSet<datatype>(dataset_name, dataspace);
+    std::vector<datatype> const t1(DIMS[0] * DIMS[1] * DIMS[2], 1);
+    auto raw_3d_vec_const = reinterpret_cast<datatype const* const* const*>(t1.data());
+    dataset.write_raw(raw_3d_vec_const);
+
+    std::vector<std::vector<std::vector<datatype>>> result;
+    dataset.read(result);
+    for (const auto& vec2d: result) {
+        for (const auto& vec1d: vec2d) {
+            REQUIRE(vec1d == (std::vector<datatype>{1, 1, 1}));
+        }
+    }
+}
+
+TEST_CASE("Array of char pointers") {
+    // Currently, serializing an `std::vector<char*>` as
+    // fixed or variable length strings doesn't work.
+    //
+    // This isn't a test of correctness. Rather it asserts the fact that
+    // something doesn't work in HighFive. Knowing it doesn't work is useful
+    // for developers, but could change in the future.
+
+    const std::string file_name = "vector_char_pointer.h5";
+
+    File file(file_name, File::Truncate);
+
+    size_t n_strings = 3;
+    size_t n_chars = 4;
+    char storage[3][4] = {"foo", "bar", "000"};
+    auto strings = std::vector<char*>(n_strings);
+
+    for (size_t i = 0; i < n_strings; ++i) {
+        strings[i] = static_cast<char*>(storage[i]);
+    }
+
+    auto filespace = DataSpace({n_strings});
+
+    SECTION("fixed length") {
+        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+        auto dset = file.createDataSet("dset", filespace, datatype);
+        REQUIRE_THROWS(dset.write(strings));
+    }
+
+    SECTION("variable length") {
+        auto datatype = VariableLengthStringType();
+        auto dset = file.createDataSet("dset", filespace, datatype);
+        REQUIRE_THROWS(dset.write(strings));
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_opencv.cpp b/packages/HighFive/tests/unit/test_opencv.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b27c06daaa22f21dd704b34a3f57384b711ff943
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_opencv.cpp
@@ -0,0 +1,59 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+
+#if HIGHFIVE_TEST_OPENCV
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+#include <highfive/experimental/opencv.hpp>
+#include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+using namespace HighFive;
+using Catch::Matchers::Equals;
+
+TEST_CASE("OpenCV") {
+    auto file = File("rw_opencv.h5", File::Truncate);
+
+    auto a = cv::Mat_<double>(3, 5);
+    auto dset = file.createDataSet("a", a);
+    auto b = dset.read<cv::Mat_<double>>();
+    REQUIRE(a(0, 0) == b(0, 0));
+
+    auto va = std::vector<cv::Mat_<double>>(7, cv::Mat_<double>(3, 5));
+    auto vdset = file.createDataSet("va", va);
+    auto vb = vdset.read<std::vector<cv::Mat_<double>>>();
+    REQUIRE(vb.size() == va.size());
+    REQUIRE(vb[0](0, 0) == va[0](0, 0));
+}
+
+TEST_CASE("OpenCV subarrays") {
+    auto file = File("rw_opencv_subarray.h5", File::Truncate);
+
+    auto a = cv::Mat_<double>(3, 13);
+
+    SECTION("write") {
+        auto sa = cv::Mat_<double>(a.colRange(1, 4));
+        REQUIRE_THROWS(file.createDataSet("a", sa));
+    }
+
+    SECTION("read") {
+        auto b = cv::Mat_<double>(3, 17);
+        auto sb = cv::Mat_<double>(a.colRange(0, 13));
+        auto dset = file.createDataSet("a", a);
+
+        // Creates a new `Mat_` in `sb`.
+        dset.read(sb);
+    }
+}
+
+#endif
diff --git a/packages/HighFive/tests/unit/test_stl.cpp b/packages/HighFive/tests/unit/test_stl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..42a76c4b9a71e7ff2cb01f03d22f4ccd02d67a0b
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_stl.cpp
@@ -0,0 +1,58 @@
+/*
+ *  Copyright (c), 2017-2024, Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+
+#include <catch2/catch_template_test_macros.hpp>
+
+#include <highfive/highfive.hpp>
+
+
+#include "compary_arrays.hpp"
+#include "create_traits.hpp"
+#include "data_generator.hpp"
+
+using namespace HighFive;
+
+TEST_CASE("std::array undersized", "[stl]") {
+    auto file = File("rw_std_array_undersized.h5", File::Truncate);
+    auto x = std::array<double, 3>{1.0, 2.0, 3.0};
+    auto dset = file.createDataSet("x", x);
+
+    REQUIRE_THROWS(dset.read<std::array<double, 2>>());
+
+    auto xx = std::array<double, 2>();
+    REQUIRE_THROWS(dset.read(xx));
+}
+
+TEST_CASE("T[n][m]") {
+    using reference_container = std::vector<std::vector<double>>;
+    auto file = File("rw_carray.h5", File::Truncate);
+
+    constexpr size_t n = 3;
+    constexpr size_t m = 5;
+
+    double x[n][m];
+
+    SECTION("write") {
+        testing::initialize(x, {n, m});
+
+        auto dset = file.createDataSet("x", x);
+        auto actual = dset.read<reference_container>();
+
+        testing::compare_arrays(x, actual, {n, m});
+    }
+
+    SECTION("read") {
+        auto expected = testing::DataGenerator<reference_container>::create({n, m});
+
+        auto dset = file.createDataSet("x", expected);
+        dset.read(x);
+
+        testing::compare_arrays(expected, x, {n, m});
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_string.cpp b/packages/HighFive/tests/unit/test_string.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed6a4a5bfac67cb90d85438669ae449dde171711
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_string.cpp
@@ -0,0 +1,351 @@
+/*
+ *  Copyright (c), 2017-2024, Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/catch_template_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_vector.hpp>
+
+#include <highfive/highfive.hpp>
+#include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+
+using namespace HighFive;
+using Catch::Matchers::Equals;
+
+TEST_CASE("StringType") {
+    SECTION("enshrine-defaults") {
+        auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded);
+        auto variable_length = VariableLengthStringType();
+
+        REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii);
+        REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii);
+    }
+
+    SECTION("fixed-length") {
+        auto fixed_length =
+            FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8);
+        auto string_type = fixed_length.asStringType();
+
+        REQUIRE(string_type.getId() == fixed_length.getId());
+        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
+        REQUIRE(string_type.getPadding() == StringPadding::SpacePadded);
+        REQUIRE(string_type.getSize() == 32);
+        REQUIRE(!string_type.isVariableStr());
+        REQUIRE(string_type.isFixedLenStr());
+    }
+
+    SECTION("variable-length") {
+        auto variable_length = VariableLengthStringType(CharacterSet::Utf8);
+        auto string_type = variable_length.asStringType();
+
+        REQUIRE(string_type.getId() == variable_length.getId());
+        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
+        REQUIRE(string_type.isVariableStr());
+        REQUIRE(!string_type.isFixedLenStr());
+    }
+
+    SECTION("atomic") {
+        auto atomic = AtomicType<double>();
+        REQUIRE_THROWS(atomic.asStringType());
+    }
+}
+
+template <class CreateTraits>
+void check_single_string(File file, size_t string_length) {
+    auto value = std::string(string_length, 'o');
+    auto dataspace = DataSpace::From(value);
+
+    auto n_chars = value.size() + 1;
+    auto n_chars_overlength = n_chars + 10;
+    auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+    auto overlength_nullterm = FixedLengthStringType(n_chars_overlength,
+                                                     StringPadding::NullTerminated);
+    auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded);
+    auto overlength_spacepad = FixedLengthStringType(n_chars_overlength,
+                                                     StringPadding::SpacePadded);
+    auto variable_length = VariableLengthStringType();
+
+    SECTION("automatic") {
+        auto obj = CreateTraits::create(file, "auto", value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+
+    SECTION("fixed length") {
+        auto obj = CreateTraits::create(file, "fixed", dataspace, fixed_length);
+        obj.write(value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+
+    SECTION("overlength null-terminated") {
+        auto obj =
+            CreateTraits::create(file, "overlength_nullterm", dataspace, overlength_nullterm);
+        obj.write(value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+
+    SECTION("overlength null-padded") {
+        auto obj = CreateTraits::create(file, "overlength_nullpad", dataspace, overlength_nullpad);
+        obj.write(value);
+        auto expected = std::string(n_chars_overlength, '\0');
+        expected.replace(0, value.size(), value.data());
+        REQUIRE(obj.template read<std::string>() == expected);
+    }
+
+    SECTION("overlength space-padded") {
+        auto obj =
+            CreateTraits::create(file, "overlength_spacepad", dataspace, overlength_spacepad);
+        obj.write(value);
+        auto expected = std::string(n_chars_overlength, ' ');
+        expected.replace(0, value.size(), value.data());
+        REQUIRE(obj.template read<std::string>() == expected);
+    }
+
+    SECTION("variable length") {
+        auto obj = CreateTraits::create(file, "variable", dataspace, variable_length);
+        obj.write(value);
+        REQUIRE(obj.template read<std::string>() == value);
+    }
+}
+
+template <class CreateTraits>
+void check_multiple_string(File file, size_t string_length) {
+    using value_t = std::vector<std::string>;
+    auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')};
+
+    auto dataspace = DataSpace::From(value);
+
+    auto string_overlength = string_length + 10;
+    auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded);
+    auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded);
+
+    auto overlength_nullterm = FixedLengthStringType(string_overlength,
+                                                     StringPadding::NullTerminated);
+    auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded);
+    auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded);
+    auto variable_length = VariableLengthStringType();
+
+    auto check = [](const value_t actual, const value_t& expected) {
+        REQUIRE(actual.size() == expected.size());
+        for (size_t i = 0; i < actual.size(); ++i) {
+            REQUIRE(actual[i] == expected[i]);
+        }
+    };
+
+    SECTION("automatic") {
+        auto obj = CreateTraits::create(file, "auto", value);
+        check(obj.template read<value_t>(), value);
+    }
+
+    SECTION("variable length") {
+        auto obj = CreateTraits::create(file, "variable", dataspace, variable_length);
+        obj.write(value);
+        check(obj.template read<value_t>(), value);
+    }
+
+    auto make_padded_reference = [&](char pad, size_t n) {
+        auto expected = std::vector<std::string>(value.size(), std::string(n, pad));
+        for (size_t i = 0; i < value.size(); ++i) {
+            expected[i].replace(0, value[i].size(), value[i].data());
+        }
+
+        return expected;
+    };
+
+    auto check_fixed_length = [&](const std::string& label, size_t length) {
+        SECTION(label + " null-terminated") {
+            auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated);
+            auto obj = CreateTraits::create(file, label + "_nullterm", dataspace, datatype);
+            obj.write(value);
+            check(obj.template read<value_t>(), value);
+        }
+
+        SECTION(label + " null-padded") {
+            auto datatype = FixedLengthStringType(length, StringPadding::NullPadded);
+            auto obj = CreateTraits::create(file, label + "_nullpad", dataspace, datatype);
+            obj.write(value);
+            auto expected = make_padded_reference('\0', length);
+            check(obj.template read<value_t>(), expected);
+        }
+
+        SECTION(label + " space-padded") {
+            auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded);
+            auto obj = CreateTraits::create(file, label + "_spacepad", dataspace, datatype);
+            obj.write(value);
+            auto expected = make_padded_reference(' ', length);
+            check(obj.template read<value_t>(), expected);
+        }
+    };
+
+    check_fixed_length("onpoint", string_length);
+    check_fixed_length("overlength", string_length + 5);
+
+
+    SECTION("underlength null-terminated") {
+        auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated);
+        auto obj = CreateTraits::create(file, "underlength_nullterm", dataspace, datatype);
+        REQUIRE_THROWS(obj.write(value));
+    }
+
+    SECTION("underlength nullpad") {
+        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded);
+        auto obj = CreateTraits::create(file, "underlength_nullpad", dataspace, datatype);
+        REQUIRE_THROWS(obj.write(value));
+    }
+
+    SECTION("underlength spacepad") {
+        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated);
+        auto obj = CreateTraits::create(file, "underlength_spacepad", dataspace, datatype);
+        REQUIRE_THROWS(obj.write(value));
+    }
+}
+
+TEST_CASE("HighFiveSTDString (dataset, single, short)") {
+    File file("std_string_dataset_single_short.h5", File::Truncate);
+    check_single_string<testing::DataSetCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, single, short)") {
+    File file("std_string_attribute_single_short.h5", File::Truncate);
+    check_single_string<testing::AttributeCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (dataset, single, long)") {
+    File file("std_string_dataset_single_long.h5", File::Truncate);
+    check_single_string<testing::DataSetCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, single, long)") {
+    File file("std_string_attribute_single_long.h5", File::Truncate);
+    check_single_string<testing::AttributeCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveSTDString (dataset, multiple, short)") {
+    File file("std_string_dataset_multiple_short.h5", File::Truncate);
+    check_multiple_string<testing::DataSetCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, multiple, short)") {
+    File file("std_string_attribute_multiple_short.h5", File::Truncate);
+    check_multiple_string<testing::AttributeCreateTraits>(file, 3);
+}
+
+TEST_CASE("HighFiveSTDString (dataset, multiple, long)") {
+    File file("std_string_dataset_multiple_long.h5", File::Truncate);
+    check_multiple_string<testing::DataSetCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveSTDString (attribute, multiple, long)") {
+    File file("std_string_attribute_multiple_long.h5", File::Truncate);
+    check_multiple_string<testing::AttributeCreateTraits>(file, 256);
+}
+
+TEST_CASE("HighFiveFixedString") {
+    const std::string file_name("array_atomic_types.h5");
+    const std::string group_1("group1");
+
+    // Create a new file using the default property lists.
+    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
+    char raw_strings[][10] = {"abcd", "1234"};
+
+    /// This will not compile - only char arrays - hits static_assert with a nice
+    /// error
+    // file.createDataSet<int[10]>(ds_name, DataSpace(2)));
+
+    {  // But char should be fine
+        auto ds = file.createDataSet<char[10]>("ds1", DataSpace(2));
+        CHECK(ds.getDataType().getClass() == DataTypeClass::String);
+        ds.write(raw_strings);
+    }
+
+    {  // char[] is, by default, int8
+        auto ds2 = file.createDataSet("ds2", raw_strings);
+        CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer);
+    }
+
+    {  // String Truncate happens low-level if well setup
+        auto ds3 = file.createDataSet<char[6]>("ds3", DataSpace::FromCharArrayStrings(raw_strings));
+        ds3.write(raw_strings);
+    }
+
+    {  // Write as raw elements from pointer (with const)
+        const char(*strings_fixed)[10] = raw_strings;
+        // With a pointer we dont know how many strings -> manual DataSpace
+        file.createDataSet<char[10]>("ds4", DataSpace(2)).write(strings_fixed);
+    }
+
+
+    {  // Cant convert flex-length to fixed-length
+        const char* buffer[] = {"abcd", "1234"};
+        SilenceHDF5 silencer;
+        CHECK_THROWS_AS(file.createDataSet<char[10]>("ds5", DataSpace(2)).write(buffer),
+                        HighFive::DataSetException);
+    }
+
+    {  // scalar char strings
+        const char buffer[] = "abcd";
+        file.createDataSet<char[10]>("ds6", DataSpace(1)).write(buffer);
+    }
+
+    {
+        // Direct way of writing `std::string` as a fixed length
+        // HDF5 string.
+
+        std::string value = "foo";
+        auto n_chars = value.size() + 1;
+
+        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+        auto dataspace = DataSpace(1);
+
+        auto ds = file.createDataSet("ds8", dataspace, datatype);
+        ds.write_raw(value.data(), datatype);
+
+        {
+            // Due to missing non-const overload of `data()` until C++17 we'll
+            // read into something else instead (don't forget the '\0').
+            auto expected = std::vector<char>(n_chars, '!');
+            ds.read_raw(expected.data(), datatype);
+
+            CHECK(expected.size() == value.size() + 1);
+            for (size_t i = 0; i < value.size(); ++i) {
+                REQUIRE(expected[i] == value[i]);
+            }
+        }
+
+#if HIGHFIVE_CXX_STD >= 17
+        {
+            auto expected = std::string(value.size(), '-');
+            ds.read_raw(expected.data(), datatype);
+
+            REQUIRE(expected == value);
+        }
+#endif
+    }
+
+    {
+        size_t n_chars = 4;
+        size_t n_strings = 2;
+
+        std::vector<char> value(n_chars * n_strings, '!');
+
+        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
+        auto dataspace = DataSpace(n_strings);
+
+        auto ds = file.createDataSet("ds9", dataspace, datatype);
+        ds.write_raw(value.data(), datatype);
+
+        auto expected = std::vector<char>(value.size(), '-');
+        ds.read_raw(expected.data(), datatype);
+
+        CHECK(expected.size() == value.size());
+        for (size_t i = 0; i < value.size(); ++i) {
+            REQUIRE(expected[i] == value[i]);
+        }
+    }
+}
diff --git a/packages/HighFive/tests/unit/test_xtensor.cpp b/packages/HighFive/tests/unit/test_xtensor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ac0b4d7433f622c91b6b7a62e3e802330fccb18c
--- /dev/null
+++ b/packages/HighFive/tests/unit/test_xtensor.cpp
@@ -0,0 +1,142 @@
+/*
+ *  Copyright (c), 2024, Blue Brain Project - EPFL
+ *
+ *  Distributed under the Boost Software License, Version 1.0.
+ *    (See accompanying file LICENSE_1_0.txt or copy at
+ *          http://www.boost.org/LICENSE_1_0.txt)
+ *
+ */
+#if HIGHFIVE_TEST_XTENSOR
+#include <string>
+#include <sstream>
+
+#include <catch2/catch_template_test_macros.hpp>
+
+#include <highfive/highfive.hpp>
+#include <xtensor/xtensor.hpp>
+#include <xtensor/xview.hpp>
+#include <xtensor/xio.hpp>
+#include <highfive/xtensor.hpp>
+
+#include "data_generator.hpp"
+
+using namespace HighFive;
+
+template <size_t N>
+std::array<size_t, N> asStaticShape(const std::vector<size_t>& dims) {
+    assert(dims.size() == N);
+
+    std::array<size_t, N> shape;
+    std::copy(dims.cbegin(), dims.cend(), shape.begin());
+
+    return shape;
+}
+
+TEST_CASE("xt::xarray reshape", "[xtensor]") {
+    const std::string file_name("rw_dataset_xarray.h5");
+
+    File file(file_name, File::Truncate);
+
+    std::vector<size_t> shape{3, 2, 4};
+    std::vector<size_t> compatible_shape{1, 3, 2, 4};
+    std::vector<size_t> incompatible_shape{5, 2, 4};
+
+    xt::xarray<double> a = testing::DataGenerator<xt::xtensor<double, 3>>::create(shape);
+    xt::xarray<double> b(compatible_shape);
+    xt::xarray<double> c(incompatible_shape);
+
+    auto dset = file.createDataSet("baz", a);
+
+    SECTION("xarray_adaptor") {
+        // Changes the shape.
+        auto b_adapt = xt::adapt(b.data(), b.size(), xt::no_ownership(), b.shape());
+        dset.read(b_adapt);
+        REQUIRE(b_adapt.shape() == shape);
+
+        // But can't change the number of elements.
+        auto c_adapt = xt::adapt(c.data(), c.size(), xt::no_ownership(), c.shape());
+        REQUIRE_THROWS(dset.read(c_adapt));
+    }
+
+    SECTION("xtensor_adaptor") {
+        auto b_shape = asStaticShape<4>(compatible_shape);
+        auto c_shape = asStaticShape<3>(incompatible_shape);
+
+        // Doesn't change the shape:
+        auto b_adapt = xt::adapt(b.data(), b.size(), xt::no_ownership(), b_shape);
+        REQUIRE_THROWS(dset.read(b_adapt));
+
+        // and can't change the number of elements:
+        auto c_adapt = xt::adapt(c.data(), c.size(), xt::no_ownership(), c_shape);
+        REQUIRE_THROWS(dset.read(c_adapt));
+    }
+}
+
+TEST_CASE("xt::xview example", "[xtensor]") {
+    File file("rw_dataset_xview.h5", File::Truncate);
+
+    std::vector<size_t> shape{13, 5, 7};
+    xt::xarray<double> a = testing::DataGenerator<xt::xtensor<double, 3>>::create(shape);
+    auto c = xt::view(a, xt::range(3, 31, 4), xt::all(), xt::drop(0, 3, 4, 5));
+
+    auto dset = file.createDataSet("c", c);
+    auto d = dset.read<xt::xarray<double>>();
+    auto e = dset.read<xt::xarray<double, xt::layout_type::column_major>>();
+
+    REQUIRE(d == c);
+    REQUIRE(e == c);
+}
+
+template <class XTensor>
+void check_xtensor_scalar(File& file) {
+    XTensor a;
+    a = 42.0;
+    REQUIRE(a.shape() == std::vector<size_t>{});
+
+    SECTION("read") {
+        auto dset = file.createDataSet("a", a);
+        REQUIRE(dset.template read<double>() == a(0));
+    }
+
+    SECTION("write") {
+        double b = -42.0;
+        auto dset = file.createDataSet("b", b);
+        REQUIRE(dset.template read<xt::xarray<double>>()(0) == b);
+    }
+}
+
+TEST_CASE("xt::xarray scalar", "[xtensor]") {
+    File file("rw_dataset_xarray_scalar.h5", File::Truncate);
+    check_xtensor_scalar<xt::xarray<double>>(file);
+}
+
+TEST_CASE("xt::xtensor scalar", "[xtensor]") {
+    File file("rw_dataset_xtensor_scalar.h5", File::Truncate);
+    check_xtensor_scalar<xt::xarray<double>>(file);
+}
+
+template <class XTensor>
+void check_xtensor_empty(File& file, const XTensor& a, const std::vector<size_t>& expected_dims) {
+    auto dset = file.createDataSet("a", a);
+    auto b = dset.template read<XTensor>();
+    REQUIRE(b.size() == 0);
+    REQUIRE(b == a);
+
+    auto c = std::vector<XTensor>{};
+    auto c_shape = details::inspector<decltype(c)>::getDimensions(c);
+    REQUIRE(c_shape == expected_dims);
+}
+
+TEST_CASE("xt::xtensor empty", "[xtensor]") {
+    File file("rw_dataset_xtensor_empty.h5", File::Truncate);
+    xt::xtensor<double, 3> a({0, 1, 1});
+    check_xtensor_empty(file, a, {0, 1, 1, 1});
+}
+
+TEST_CASE("xt::xarray empty", "[xtensor]") {
+    File file("rw_dataset_xarray_empty.h5", File::Truncate);
+    xt::xarray<double> a(std::vector<size_t>{1, 0, 1});
+    check_xtensor_empty(file, a, {0});
+}
+
+#endif
diff --git a/packages/HighFive/tests/unit/tests_high_five.hpp b/packages/HighFive/tests/unit/tests_high_five.hpp
index 9d259c8d130bad483bae71c282cd9f887cf065d8..8a4f07bbf9da6eb3c5284860da713a13b1dc3bf5 100644
--- a/packages/HighFive/tests/unit/tests_high_five.hpp
+++ b/packages/HighFive/tests/unit/tests_high_five.hpp
@@ -21,6 +21,7 @@
 // The list of identifiers is taken from `Boost::Predef`.
 #if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__TOS_WIN__) || \
     defined(__WINDOWS__)
+#define NOMINMAX
 #include <Windows.h>
 #endif
 
@@ -42,7 +43,9 @@ using base_test_types = std::tuple<int,
                                    dcomplex,
                                    fcomplex>;
 
-#ifdef H5_USE_HALF_FLOAT
+#ifdef HIGHFIVE_TEST_HALF_FLOAT
+#include <highfive/half_float.hpp>
+
 using float16_t = half_float::half;
 using numerical_test_types =
     decltype(std::tuple_cat(std::declval<base_test_types>(), std::tuple<float16_t>()));
@@ -149,7 +152,7 @@ struct ContentGenerate<std::string> {
         ContentGenerate<char> gen;
         std::string random_string;
         std::mt19937_64 rgen;
-        rgen.seed(88);
+        rgen.seed(42);
         std::uniform_int_distribution<unsigned> int_dist(0, 1000);
         const size_t size_string = int_dist(rgen);
 
diff --git a/packages/HighFive/tests/unit/tests_high_five_base.cpp b/packages/HighFive/tests/unit/tests_high_five_base.cpp
index fefdcdd5586fa3748a99a729b2103e3232ba5739..02ce7a04de746187e096a274b0a6a78f930af450 100644
--- a/packages/HighFive/tests/unit/tests_high_five_base.cpp
+++ b/packages/HighFive/tests/unit/tests_high_five_base.cpp
@@ -6,6 +6,7 @@
  *          http://www.boost.org/LICENSE_1_0.txt)
  *
  */
+#include <H5Ipublic.h>
 #include <algorithm>
 #include <cstdio>
 #include <cstdlib>
@@ -20,13 +21,25 @@
 #include <type_traits>
 #include <vector>
 
-
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/catch_template_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_vector.hpp>
 
 #include <highfive/highfive.hpp>
 #include "tests_high_five.hpp"
+#include "create_traits.hpp"
+
+#ifdef HIGHFIVE_TEST_BOOST
+#include <highfive/boost.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_EIGEN
+#include <highfive/eigen.hpp>
+#endif
+
+#ifdef HIGHFIVE_TEST_SPAN
+#include <highfive/span.hpp>
+#endif
 
 using namespace HighFive;
 using Catch::Matchers::Equals;
@@ -313,27 +326,154 @@ TEST_CASE("Test allocation time") {
     CHECK(alloc_size == data.size() * sizeof(decltype(data)::value_type));
 }
 
-/*
- * Test to ensure legacy support: DataSet used to have a default constructor.
- * However, it is not useful to have a DataSet object that does not actually
- * refer to a dataset in a file. Hence, the the default constructor was
- * deprecated.
- * This test is to ensure that the constructor is not accidentally removed and
- * thereby break users' code.
- */
-TEST_CASE("Test default constructors") {
-    const std::string file_name("h5_default_ctors.h5");
-    const std::string dataset_name("dset");
-    File file(file_name, File::Truncate);
-    auto ds = file.createDataSet(dataset_name, std::vector<int>{1, 2, 3, 4, 5});
+template <class T>
+void check_invalid_hid_Object(T& obj) {
+    auto silence = SilenceHDF5();
+
+    CHECK(!obj.isValid());
+    CHECK(obj.getId() == H5I_INVALID_HID);
+
+    CHECK_THROWS(obj.getInfo());
+    CHECK_THROWS(obj.getType());
+}
+
+template <class T, class U>
+void check_invalid_hid_NodeTraits(T& obj, const U& linkable) {
+    auto silence = SilenceHDF5();
+
+    auto data_space = DataSpace{2, 3};
+    auto data_type = HighFive::create_datatype<double>();
+    auto data = std::vector<double>{1.0, 2.0, 3.0};
+    auto gcpl = GroupCreateProps();
+
+    CHECK_THROWS(obj.createDataSet("foo", data_space, data_type));
+    CHECK_THROWS(obj.template createDataSet<double>("foo", data_space));
+    CHECK_THROWS(obj.createDataSet("foo", data));
+
+    CHECK_THROWS(obj.getDataSet("foo"));
+    CHECK_THROWS(obj.createGroup("foo"));
+    CHECK_THROWS(obj.createGroup("foo", gcpl));
+    CHECK_THROWS(obj.getGroup("foo"));
+    CHECK_THROWS(obj.getDataType("foo"));
+    CHECK_THROWS(obj.getNumberObjects());
+    CHECK_THROWS(obj.getObjectName(0));
+    CHECK_THROWS(obj.rename("foo", "bar"));
+    CHECK_THROWS(obj.listObjectNames());
+    CHECK_THROWS(obj.exist("foo"));
+    CHECK_THROWS(obj.unlink("foo"));
+    CHECK_THROWS(obj.getLinkType("foo"));
+    CHECK_THROWS(obj.getObjectType("foo"));
+    CHECK_THROWS(obj.createSoftLink("foo", linkable));
+    CHECK_THROWS(obj.createSoftLink("foo", "bar"));
+    CHECK_THROWS(obj.createExternalLink("foo", "bar", "baz"));
+    CHECK_THROWS(obj.createHardLink("foo", linkable));
+}
+
+template <class T>
+void check_invalid_hid_DataSet(T& obj) {
+    auto silence = SilenceHDF5();
+
+    CHECK_THROWS(obj.getStorageSize());
+    CHECK_THROWS(obj.getOffset());
+    CHECK_THROWS(obj.getMemSpace());
+    CHECK_THROWS(obj.resize({1, 2, 3}));
+    CHECK_THROWS(obj.getDimensions());
+    CHECK_THROWS(obj.getElementCount());
+    CHECK_THROWS(obj.getCreatePropertyList());
+    CHECK_THROWS(obj.getAccessPropertyList());
+}
+
+template <class T>
+void check_invalid_hid_SliceTraits(T& obj) {
+    auto silence = SilenceHDF5();
+
+    auto slab = HighFive::HyperSlab(RegularHyperSlab({0}));
+    auto space = DataSpace{3};
+    auto set = ElementSet{0, 1, 3};
+    auto data = std::vector<double>{1.0, 2.0, 3.0};
+    auto type = create_datatype<double>();
+    auto cols = std::vector<size_t>{0, 2, 3};
+
+    CHECK_THROWS(obj.select(slab));
+    CHECK_THROWS(obj.select(slab, space));
+    CHECK_THROWS(obj.select({0}, {3}));
+    CHECK_THROWS(obj.select(cols));
+    CHECK_THROWS(obj.select(set));
+
+    CHECK_THROWS(obj.template read<double>());
+    CHECK_THROWS(obj.read(data));
+    CHECK_THROWS(obj.read_raw(data.data(), type));
+    CHECK_THROWS(obj.template read_raw<double>(data.data()));
+
+    CHECK_THROWS(obj.write(data));
+    CHECK_THROWS(obj.write_raw(data.data(), type));
+    CHECK_THROWS(obj.template write_raw<double>(data.data()));
+}
 
-    DataSet d2;  // expect deprecation warning, as it constructs unsafe object
-    // d2.getFile();  // runtime error
-    CHECK(!d2.isValid());
-    d2 = ds;  // copy
-    CHECK(d2.isValid());
+template <class T>
+void check_invalid_hid_PathTraits(T& obj) {
+    auto silence = SilenceHDF5();
+
+    CHECK_THROWS(obj.getPath());
+    CHECK_THROWS(obj.getFile());
+}
+
+template <class T>
+void check_invalid_hid_AnnotateTraits(T& obj) {
+    auto silence = SilenceHDF5();
+
+    auto space = DataSpace{3};
+    auto data = std::vector<double>{1.0, 2.0, 3.0};
+    auto type = create_datatype<double>();
+
+    CHECK_THROWS(obj.createAttribute("foo", space, type));
+    CHECK_THROWS(obj.template createAttribute<double>("foo", space));
+    CHECK_THROWS(obj.createAttribute("foo", data));
+
+    CHECK_THROWS(obj.deleteAttribute("foo"));
+    CHECK_THROWS(obj.getAttribute("foo"));
+    CHECK_THROWS(obj.getNumberAttributes());
+    CHECK_THROWS(obj.listAttributeNames());
+    CHECK_THROWS(obj.hasAttribute("foo"));
+}
+
+template <class T>
+void check_invalid_hid_Group(T& obj) {
+    auto silence = SilenceHDF5();
+
+    CHECK_THROWS(obj.getEstimatedLinkInfo());
+    CHECK_THROWS(obj.getCreatePropertyList());
+}
+
+TEST_CASE("Test default DataSet constructor") {
+    DataSet ds;
+    check_invalid_hid_Object(ds);
+    check_invalid_hid_DataSet(ds);
+    check_invalid_hid_SliceTraits(ds);
+    check_invalid_hid_AnnotateTraits(ds);
+    check_invalid_hid_PathTraits(ds);
+
+    File file("h5_default_dset_ctor.h5", File::Truncate);
+    ds = file.createDataSet("dset", std::vector<int>{1, 2, 3, 4, 5});
+    CHECK(ds.isValid());
+}
+
+TEST_CASE("Test default Group constructor") {
+    File file("h5_default_group_ctor.h5", File::Truncate);
+    Group linkable = file.createGroup("bar");
+
+    Group grp;
+    check_invalid_hid_Object(grp);
+    check_invalid_hid_NodeTraits(grp, linkable);
+    check_invalid_hid_AnnotateTraits(grp);
+    check_invalid_hid_PathTraits(grp);
+
+    grp = file.createGroup("grp");
+
+    CHECK(grp.isValid());
 }
 
+
 TEST_CASE("Test groups and datasets") {
     const std::string file_name("h5_group_test.h5");
     const std::string dataset_name("dset");
@@ -679,45 +819,6 @@ TEST_CASE("Test simple listings") {
     }
 }
 
-TEST_CASE("StringType") {
-    SECTION("enshrine-defaults") {
-        auto fixed_length = FixedLengthStringType(32, StringPadding::SpacePadded);
-        auto variable_length = VariableLengthStringType();
-
-        REQUIRE(fixed_length.getCharacterSet() == CharacterSet::Ascii);
-        REQUIRE(variable_length.getCharacterSet() == CharacterSet::Ascii);
-    }
-
-    SECTION("fixed-length") {
-        auto fixed_length =
-            FixedLengthStringType(32, StringPadding::SpacePadded, CharacterSet::Utf8);
-        auto string_type = fixed_length.asStringType();
-
-        REQUIRE(string_type.getId() == fixed_length.getId());
-        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
-        REQUIRE(string_type.getPadding() == StringPadding::SpacePadded);
-        REQUIRE(string_type.getSize() == 32);
-        REQUIRE(!string_type.isVariableStr());
-        REQUIRE(string_type.isFixedLenStr());
-    }
-
-    SECTION("variable-length") {
-        auto variable_length = VariableLengthStringType(CharacterSet::Utf8);
-        auto string_type = variable_length.asStringType();
-
-        REQUIRE(string_type.getId() == variable_length.getId());
-        REQUIRE(string_type.getCharacterSet() == CharacterSet::Utf8);
-        REQUIRE(string_type.isVariableStr());
-        REQUIRE(!string_type.isFixedLenStr());
-    }
-
-    SECTION("atomic") {
-        auto atomic = AtomicType<double>();
-        REQUIRE_THROWS(atomic.asStringType());
-    }
-}
-
-
 TEST_CASE("DataTypeEqualTakeBack") {
     const std::string file_name("h5tutr_dset.h5");
     const std::string dataset_name("dset");
@@ -778,6 +879,12 @@ TEST_CASE("DataSpace::getElementCount") {
         CHECK(detail::h5s_get_simple_extent_type(space.getId()) == H5S_NULL);
     }
 
+    SECTION("null initializer_list") {
+        auto space = DataSpace{DataSpace::dataspace_null};
+        CHECK(space.getElementCount() == 0);
+        CHECK(detail::h5s_get_simple_extent_type(space.getId()) == H5S_NULL);
+    }
+
     SECTION("null named ctor") {
         auto space = DataSpace::Null();
         CHECK(space.getElementCount() == 0);
@@ -790,6 +897,12 @@ TEST_CASE("DataSpace::getElementCount") {
         CHECK(detail::h5s_get_simple_extent_type(space.getId()) == H5S_SCALAR);
     }
 
+    SECTION("scalar initializer_list") {
+        auto space = DataSpace{DataSpace::dataspace_scalar};
+        CHECK(space.getElementCount() == 1);
+        CHECK(detail::h5s_get_simple_extent_type(space.getId()) == H5S_SCALAR);
+    }
+
     SECTION("scalar named ctor") {
         auto space = DataSpace::Scalar();
         CHECK(space.getElementCount() == 1);
@@ -907,8 +1020,9 @@ TEST_CASE("HighFiveReadWriteShortcut") {
     const std::string dataset_name("dset");
     std::vector<unsigned> vec;
     vec.resize(x_size);
-    for (unsigned i = 0; i < x_size; i++)
+    for (unsigned i = 0; i < x_size; i++) {
         vec[i] = i * 2;
+    }
     std::string at_contents("Contents of string");
     int my_int = 3;
     std::vector<std::vector<int>> my_nested = {{1, 2}, {3, 4}};
@@ -945,7 +1059,7 @@ TEST_CASE("HighFiveReadWriteShortcut") {
     }
 
     // Plain c arrays. 1D
-    {
+    SECTION("int-c-array") {
         int int_c_array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
         DataSet ds_int2 = file.createDataSet("/TmpCArrayInt", int_c_array);
 
@@ -957,7 +1071,7 @@ TEST_CASE("HighFiveReadWriteShortcut") {
     }
 
     // Plain c arrays. 2D
-    {
+    SECTION("char-c-array") {
         char char_c_2darray[][3] = {"aa", "bb", "cc", "12"};
         DataSet ds_char2 = file.createDataSet("/TmpCArray2dchar", char_c_2darray);
 
@@ -1110,6 +1224,207 @@ TEST_CASE("datasetOffset") {
 }
 
 
+// Ensure that "all" combinations of `ProductSet` are being instantiated.
+class CompileProductSet {
+    using Point = size_t;
+    using Points = std::vector<size_t>;
+    using Slice = std::array<size_t, 2>;
+    using Slices = std::vector<Slice>;
+
+    void all() {
+        one_arg();
+        two_args();
+        three_args();
+    }
+
+    template <class... Preamble>
+    void zero_args() {
+        ProductSet(Preamble()...);
+    }
+
+    template <class... Preamble>
+    void one_arg() {
+        zero_args<Preamble..., Point>();
+        zero_args<Preamble..., Points>();
+        zero_args<Preamble..., Slice>();
+        zero_args<Preamble..., Slices>();
+    }
+
+    template <class... Preamble>
+    void two_args() {
+        one_arg<Preamble..., Point>();
+        one_arg<Preamble..., Points>();
+        one_arg<Preamble..., Slice>();
+        one_arg<Preamble..., Slices>();
+    }
+
+    template <class... Preamble>
+    void three_args() {
+        two_args<Preamble..., Point>();
+        two_args<Preamble..., Points>();
+        two_args<Preamble..., Slice>();
+        two_args<Preamble..., Slices>();
+    }
+};
+
+template <class S, class Y, class X>
+void check_product_set_shape(const S& subarray, const Y& yslices, const X& xslices) {
+    std::vector<size_t> subshape{0, 0};
+
+    for (auto yslice: yslices) {
+        subshape[0] += yslice[1] - yslice[0];
+    }
+
+    for (auto xslice: xslices) {
+        subshape[1] += xslice[1] - xslice[0];
+    }
+
+    REQUIRE(subarray.size() == subshape[0]);
+    for (const auto& v: subarray) {
+        REQUIRE(v.size() == subshape[1]);
+    }
+}
+
+template <class S, class Y, class X>
+void check_product_set_values(const S& array,
+                              const S& subarray,
+                              const Y& yslices,
+                              const X& xslices) {
+    size_t il = 0;
+    for (const auto& yslice: yslices) {
+        for (size_t ig = yslice[0]; ig < yslice[1]; ++ig) {
+            size_t jl = 0;
+
+            for (const auto& xslice: xslices) {
+                for (size_t jg = xslice[0]; jg < xslice[1]; ++jg) {
+                    REQUIRE(subarray[il][jl] == array[ig][jg]);
+                    ++jl;
+                }
+            }
+            ++il;
+        }
+    }
+}
+
+template <class S, class Y, class X>
+void check(const S& array, const S& subarray, const Y& yslices, const X& xslices) {
+    check_product_set_shape(subarray, yslices, xslices);
+    check_product_set_values(array, subarray, yslices, xslices);
+}
+
+
+TEST_CASE("productSet") {
+    using Slice = std::array<size_t, 2>;
+    using Slices = std::vector<Slice>;
+    using Point = size_t;
+    using Points = std::vector<size_t>;
+
+    const std::string file_name("h5_test_product_set.h5");
+
+    auto generate = [](size_t n, size_t m, auto f) {
+        auto x = std::vector<std::vector<double>>(n);
+        for (size_t i = 0; i < n; ++i) {
+            x[i] = std::vector<double>(m);
+        }
+
+        for (size_t i = 0; i < n; ++i) {
+            for (size_t j = 0; j < m; ++j) {
+                x[i][j] = f(i, j);
+            }
+        }
+
+        return x;
+    };
+
+    auto array = generate(6, 12, [](size_t i, size_t j) { return double(i) + double(j) * 0.01; });
+
+    auto file = File(file_name, File::Truncate);
+    auto dset = file.createDataSet("dset", array);
+
+    SECTION("rR") {
+        std::vector<std::vector<double>> subarray;
+
+        auto yslice = Slice{1, 3};
+        auto yslices = Slices{yslice};
+        auto xslices = Slices{{0, 1}, {3, 5}};
+
+        dset.select(ProductSet(yslice, xslices)).read(subarray);
+
+        check(array, subarray, yslices, xslices);
+    }
+
+    SECTION("Rr") {
+        std::vector<std::vector<double>> subarray;
+
+        auto yslices = Slices{{0, 1}, {3, 5}};
+        auto xslice = Slice{1, 3};
+        auto xslices = Slices{xslice};
+
+        dset.select(ProductSet(yslices, xslice)).read(subarray);
+
+        check(array, subarray, yslices, xslices);
+    }
+
+    SECTION("RP") {
+        std::vector<std::vector<double>> subarray;
+
+        auto yslices = Slices{{0, 1}, {3, 5}};
+        auto xpoints = Points{2, 4, 5};
+        auto xslices = Slices{{2, 3}, {4, 6}};
+
+        dset.select(ProductSet(yslices, xpoints)).read(subarray);
+
+        check(array, subarray, yslices, xslices);
+    }
+
+    SECTION("pR") {
+        std::vector<std::vector<double>> subarray;
+
+        auto ypoint = Point{2};
+        auto yslices = Slices{{2, 3}};
+        auto xslices = Slices{{0, 1}, {3, 5}};
+
+        dset.select(ProductSet(ypoint, xslices)).read(subarray);
+
+        check(array, subarray, yslices, xslices);
+    }
+
+    SECTION("pp") {
+        std::vector<std::vector<double>> subarray;
+
+        auto xpoint = Point{3};
+        auto ypoint = Point{2};
+        auto yslices = Slices{{2, 3}};
+        auto xslices = Slices{{3, 4}};
+
+        dset.select(ProductSet(ypoint, xpoint)).read(subarray);
+        check(array, subarray, yslices, xslices);
+    }
+
+    SECTION("PP") {
+        std::vector<std::vector<double>> subarray;
+
+        auto xpoints = Points{0, 3, 4};
+        auto ypoints = Points{2, 3};
+        auto yslices = Slices{{2, 4}};
+        auto xslices = Slices{{0, 1}, {3, 5}};
+
+        dset.select(ProductSet(ypoints, xpoints)).read(subarray);
+        check(array, subarray, yslices, xslices);
+    }
+
+    SECTION("RR") {
+        std::vector<std::vector<double>> subarray;
+
+        auto yslices = Slices{{2, 4}};
+        auto xslices = Slices{{0, 1}, {3, 5}};
+
+        dset.select(ProductSet(yslices, xslices)).read(subarray);
+        check(array, subarray, yslices, xslices);
+    }
+}
+
+
 template <typename T>
 void attribute_scalar_rw() {
     std::ostringstream filename;
@@ -1313,413 +1628,161 @@ TEMPLATE_LIST_TEST_CASE("ReadWriteSzip", "[template]", dataset_test_types) {
     }
 }
 
-TEST_CASE("CheckDimensions") {
-    // List of dims which can all be one-dimensional.
-    std::vector<std::vector<size_t>> test_cases{
-        {1ul, 3ul}, {3ul, 1ul}, {1ul, 1ul, 3ul}, {3ul, 1ul, 1ul}, {1ul, 3ul, 1ul}};
-
-    for (const auto& dims: test_cases) {
-        auto actual = details::checkDimensions(dims, 1ul);
-
-        INFO("dims = " + details::format_vector(dims) + ", n_dims = 1");
-        CHECK(actual);
-
-        INFO("dims = " + details::format_vector(dims) + ", n_dims = 1");
-        CHECK(!details::checkDimensions(dims, dims.size() + 1));
-    }
-
-    CHECK(details::checkDimensions(std::vector<size_t>{1ul}, 0ul));
-    CHECK(details::checkDimensions(std::vector<size_t>{1ul}, 1ul));
-
-    CHECK(!details::checkDimensions(std::vector<size_t>{0ul}, 0ul));
-    CHECK(!details::checkDimensions(std::vector<size_t>{2ul}, 0ul));
+template <class CreateTraits>
+void check_broadcast_scalar_memspace(File& file,
+                                     const std::string& name,
+                                     const std::vector<size_t>& dims) {
+    auto datatype = create_datatype<double>();
+    auto obj = CreateTraits::create(file, name, DataSpace(dims), datatype);
 
-    CHECK(!details::checkDimensions(std::vector<size_t>{1ul, 2ul, 3ul}, 2ul));
-    CHECK(details::checkDimensions(std::vector<size_t>{3ul, 2ul, 1ul}, 2ul));
+    double expected = 3.0;
+    obj.write(expected);
 
-    CHECK(details::checkDimensions(std::vector<size_t>{1ul, 1ul, 1ul, 1ul}, 1ul));
-
-    CHECK(details::checkDimensions(std::vector<size_t>{}, 0ul));
-    CHECK(!details::checkDimensions(std::vector<size_t>{}, 1ul));
-    CHECK(!details::checkDimensions(std::vector<size_t>{}, 2ul));
+    auto actual = obj.template read<double>();
+    CHECK(actual == expected);
 }
 
+TEST_CASE("Broadcast scalar memspace, dset") {
+    File file("h5_broadcast_scalar_memspace_dset.h5", File::Truncate);
 
-TEST_CASE("SqueezeDimensions") {
-    SECTION("possible") {
-        // List of testcases: the first number is n_dims then the input dimensions
-        // and finally the squeezed dimensions.
-        std::vector<std::tuple<size_t, std::vector<size_t>, std::vector<size_t>>> test_cases{
-            {1ul, {3ul, 1ul}, {3ul}},
-
-            {1ul, {1ul, 1ul, 1ul}, {1ul}},
-
-            {1ul, {1ul, 3ul, 1ul}, {3ul}},
-
-            {1ul, {3ul, 1ul, 1ul}, {3ul}},
-            {2ul, {3ul, 1ul, 1ul}, {3ul, 1ul}},
-            {3ul, {3ul, 1ul, 1ul}, {3ul, 1ul, 1ul}},
-
-            {3ul, {2ul, 1ul, 3ul}, {2ul, 1ul, 3ul}}};
-
-        for (const auto& tc: test_cases) {
-            auto n_dim_requested = std::get<0>(tc);
-            auto dims = std::get<1>(tc);
-            auto expected = std::get<2>(tc);
-            auto actual = details::squeezeDimensions(dims, n_dim_requested);
-
-            CHECK(actual == expected);
-        }
+    SECTION("[1]") {
+        check_broadcast_scalar_memspace<testing::DataSetCreateTraits>(file, "dset", {1});
     }
 
-    SECTION("impossible") {
-        // List of testcases: the first number is n_dims then the input dimensions
-        // and finally the squeezed dimensions.
-        std::vector<std::tuple<size_t, std::vector<size_t>>> test_cases{{1ul, {1ul, 2ul, 3ul}},
-                                                                        {2ul, {1ul, 2ul, 3ul, 1ul}},
-
-                                                                        {1ul, {2ul, 1ul, 3ul}},
-                                                                        {2ul, {2ul, 1ul, 3ul}}};
-
-        for (const auto& tc: test_cases) {
-            auto n_dim_requested = std::get<0>(tc);
-            auto dims = std::get<1>(tc);
-
-            CHECK_THROWS(details::squeezeDimensions(dims, n_dim_requested));
-        }
+    SECTION("[1, 1, 1]") {
+        check_broadcast_scalar_memspace<testing::DataSetCreateTraits>(file, "dset", {1, 1, 1});
     }
 }
 
-void check_broadcast_1d(HighFive::File& file,
-                        const std::vector<size_t> dims,
-                        const std::string& dataset_name) {
-    // This checks that:
-    //   - we can write 1D array into 2D dataset.
-    //   - we can read 2D dataset into a 1D array.
-    std::vector<double> input_data{5.0, 6.0, 7.0};
-
-
-    DataSpace dataspace(dims);
-    DataSet dataset = file.createDataSet(dataset_name, dataspace, AtomicType<double>());
-
-    dataset.write(input_data);
+TEST_CASE("Broadcast scalar memspace, attr") {
+    File file("h5_broadcast_scalar_memspace_attr.h5", File::Truncate);
 
-    {
-        std::vector<double> read_back;
-        dataset.read(read_back);
-
-        CHECK(read_back == input_data);
+    SECTION("[1]") {
+        check_broadcast_scalar_memspace<testing::AttributeCreateTraits>(file, "attr", {1});
     }
 
-    {
-        auto read_back = dataset.read<std::vector<double>>();
-        CHECK(read_back == input_data);
+    SECTION("[1, 1, 1]") {
+        check_broadcast_scalar_memspace<testing::AttributeCreateTraits>(file, "attr", {1, 1, 1});
     }
 }
 
-// Broadcasting is supported
-TEST_CASE("ReadInBroadcastDims") {
-    const std::string file_name("h5_broadcast_dset.h5");
-    const std::string dataset_name("dset");
+template <class CreateTraits>
+void check_broadcast_scalar_filespace(File& file, const std::string& name) {
+    auto datatype = create_datatype<double>();
+    auto obj = CreateTraits::create(file, name, DataSpace::Scalar(), datatype);
 
-    // Create a new file using the default property lists.
-    File file(file_name, File::Truncate);
+    auto value = std::vector<double>{3.0};
 
-    SECTION("one-dimensional (1, 3)") {
-        check_broadcast_1d(file, {1, 3}, dataset_name + "_a");
-    }
-
-    SECTION("one-dimensional (3, 1)") {
-        check_broadcast_1d(file, {3, 1}, dataset_name + "_b");
-    }
-
-    SECTION("two-dimensional (2, 3, 1)") {
-        std::vector<size_t> dims{2, 3, 1};
-        std::vector<std::vector<double>> input_data_2d{{2.0, 3.0, 4.0}, {10.0, 11.0, 12.0}};
+    REQUIRE_THROWS(obj.write(value));
+    REQUIRE_THROWS(obj.template read<std::vector<double>>());
+    REQUIRE_THROWS(obj.read(value));
+}
 
-        DataSpace dataspace(dims);
-        DataSet dataset = file.createDataSet(dataset_name + "_c", dataspace, AtomicType<double>());
+TEST_CASE("Broadcast scalar filespace, dset") {
+    File file("h5_broadcast_scalar_filespace_dset.h5", File::Truncate);
+    check_broadcast_scalar_filespace<testing::DataSetCreateTraits>(file, "dset");
+}
 
-        dataset.write(input_data_2d);
+TEST_CASE("Broadcast scalar filespace, attr") {
+    File file("h5_broadcast_scalar_filespace_attr.h5", File::Truncate);
+    check_broadcast_scalar_filespace<testing::AttributeCreateTraits>(file, "attr");
+}
 
-        auto check = [](const std::vector<std::vector<double>>& lhs,
-                        const std::vector<std::vector<double>>& rhs) {
-            CHECK(lhs.size() == rhs.size());
-            for (size_t i = 0; i < rhs.size(); ++i) {
-                CHECK(lhs[i].size() == rhs[i].size());
+TEST_CASE("squeeze") {
+    CHECK(detail::squeeze({}, {}) == std::vector<size_t>{});
+    CHECK(detail::squeeze({3, 1, 1}, {}) == std::vector<size_t>{3, 1, 1});
+    CHECK(detail::squeeze({3, 1, 1}, {2, 1}) == std::vector<size_t>{3});
+    CHECK(detail::squeeze({1, 3, 1, 2}, {2, 0}) == std::vector<size_t>{3, 2});
 
-                for (size_t j = 0; j < rhs[i].size(); ++j) {
-                    CHECK(lhs[i][j] == rhs[i][j]);
-                }
-            }
-        };
+    CHECK_THROWS(detail::squeeze({3, 1, 1}, {3}));
+    CHECK_THROWS(detail::squeeze({3, 1, 1}, {0}));
+    CHECK_THROWS(detail::squeeze({}, {0}));
+}
 
-        {
-            std::vector<std::vector<double>> read_back;
-            dataset.read(read_back);
+template <class CreateTraits>
+void check_modify_memspace(File& file, const std::string& name) {
+    auto expected_values = std::vector<double>{1.0, 2.0, 3.0};
+    auto values = std::vector<std::vector<double>>{expected_values};
 
-            check(read_back, input_data_2d);
-        }
+    auto obj = CreateTraits::create(file, name, values);
+    SECTION("squeeze") {
+        auto actual_values = obj.squeezeMemSpace({0}).template read<std::vector<double>>();
 
-        {
-            auto read_back = dataset.read<std::vector<std::vector<double>>>();
-            check(read_back, input_data_2d);
+        REQUIRE(actual_values.size() == expected_values.size());
+        for (size_t i = 0; i < actual_values.size(); ++i) {
+            REQUIRE(actual_values[i] == expected_values[i]);
         }
     }
 
-    SECTION("one-dimensional fixed length string") {
-        std::vector<size_t> dims{1, 1, 2};
-        char input_data[2] = "a";
+    SECTION("reshape") {
+        auto actual_values = obj.reshapeMemSpace({3}).template read<std::vector<double>>();
 
-        DataSpace dataspace(dims);
-        DataSet dataset = file.createDataSet(dataset_name + "_d", dataspace, AtomicType<char>());
-        dataset.write(input_data);
-
-        {
-            char read_back[2];
-            dataset.read(read_back);
-
-            CHECK(read_back[0] == 'a');
-            CHECK(read_back[1] == '\0');
+        REQUIRE(actual_values.size() == expected_values.size());
+        for (size_t i = 0; i < actual_values.size(); ++i) {
+            REQUIRE(actual_values[i] == expected_values[i]);
         }
     }
 }
 
-
-template <int n_dim>
-struct CreateEmptyVector;
-
-template <>
-struct CreateEmptyVector<1> {
-    using container_type = std::vector<int>;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        return container_type(dims[0], 2);
-    }
-};
-
-template <int n_dim>
-struct CreateEmptyVector {
-    using container_type = std::vector<typename CreateEmptyVector<n_dim - 1>::container_type>;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        auto subdims = std::vector<size_t>(dims.begin() + 1, dims.end());
-        return container_type(dims[0], CreateEmptyVector<n_dim - 1>::create(subdims));
-    }
-};
-
-#ifdef H5_USE_BOOST
-template <int n_dim>
-struct CreateEmptyBoostMultiArray {
-    using container_type = boost::multi_array<int, static_cast<long unsigned>(n_dim)>;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        auto container = container_type(dims);
-
-        auto raw_data = std::vector<int>(compute_total_size(dims));
-        container.assign(raw_data.begin(), raw_data.end());
-
-        return container;
-    }
-};
-#endif
-
-
-#ifdef H5_USE_EIGEN
-struct CreateEmptyEigenVector {
-    using container_type = Eigen::VectorXi;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        return container_type::Constant(int(dims[0]), 2);
-    }
-};
-
-struct CreateEmptyEigenMatrix {
-    using container_type = Eigen::MatrixXi;
-
-    static container_type create(const std::vector<size_t>& dims) {
-        return container_type::Constant(int(dims[0]), int(dims[1]), 2);
-    }
-};
-#endif
-
-template <class Container>
-void check_empty_dimensions(const Container& container, const std::vector<size_t>& expected_dims) {
-    auto deduced_dims = details::inspector<Container>::getDimensions(container);
-
-    REQUIRE(expected_dims.size() == deduced_dims.size());
-
-    // The dims after hitting the first `0` are finicky. We allow those to be deduced as either `1`
-    // or what the original dims said. The `1` allows broadcasting, the "same as original" enables
-    // statically sized objects, which conceptually have dims, even if there's no object.
-    bool allow_one = false;
-    for (size_t i = 0; i < expected_dims.size(); ++i) {
-        REQUIRE(((expected_dims[i] == deduced_dims[i]) || (allow_one && (deduced_dims[i] == 1ul))));
-
-        if (expected_dims[i] == 0) {
-            allow_one = true;
-        }
-    }
+TEST_CASE("Modify MemSpace, dset") {
+    File file("h5_modify_memspace_dset.h5", File::Truncate);
+    check_modify_memspace<testing::DataSetCreateTraits>(file, "dset");
 }
 
-template <class CreateContainer>
-void check_empty_dimensions(const std::vector<size_t>& dims) {
-    auto input_data = CreateContainer::create(dims);
-    check_empty_dimensions(input_data, dims);
+TEST_CASE("Modify MemSpace, attr") {
+    File file("h5_modify_memspace_attr.h5", File::Truncate);
+    check_modify_memspace<testing::AttributeCreateTraits>(file, "attr");
 }
 
-struct ReadWriteAttribute {
-    template <class Container>
-    static void create(HighFive::File& file, const std::string& name, const Container& container) {
-        file.createAttribute(name, container);
-    }
-
-    static HighFive::Attribute get(HighFive::File& file, const std::string& name) {
-        return file.getAttribute(name);
-    }
-};
-
-struct ReadWriteDataSet {
-    template <class Container>
-    static void create(HighFive::File& file, const std::string& name, const Container& container) {
-        file.createDataSet(name, container);
-    }
-
-    static HighFive::DataSet get(HighFive::File& file, const std::string& name) {
-        return file.getDataSet(name);
-    }
-};
-
-template <class ReadWriteInterface, class CreateContainer>
-void check_empty_read_write_cycle(const std::vector<size_t>& dims) {
-    using container_type = typename CreateContainer::container_type;
-
-    const std::string file_name("h5_empty_attr.h5");
-    const std::string dataset_name("dset");
-    File file(file_name, File::Truncate);
-
-    auto input_data = CreateContainer::create(dims);
-    ReadWriteInterface::create(file, dataset_name, input_data);
-
-    SECTION("read; one-dimensional vector (empty)") {
-        auto output_data = CreateEmptyVector<1>::create({0ul});
-
-        ReadWriteInterface::get(file, dataset_name).read(output_data);
-        check_empty_dimensions(output_data, {0ul});
-    }
-
-    SECTION("read; pre-allocated (empty)") {
-        auto output_data = CreateContainer::create(dims);
-        ReadWriteInterface::get(file, dataset_name).read(output_data);
-
-        check_empty_dimensions(output_data, dims);
-    }
-
-    SECTION("read; pre-allocated (oversized)") {
-        auto oversize_dims = std::vector<size_t>(dims.size(), 2ul);
-        auto output_data = CreateContainer::create(oversize_dims);
-        ReadWriteInterface::get(file, dataset_name).read(output_data);
+template <class CreateTraits>
+void check_modify_scalar_filespace(File& file, const std::string& name) {
+    auto expected_value = 3.0;
 
-        check_empty_dimensions(output_data, dims);
-    }
+    auto obj = CreateTraits::create(file, name, expected_value);
+    SECTION("reshape") {
+        auto actual_values = obj.reshapeMemSpace({1}).template read<std::vector<double>>();
 
-    SECTION("read; auto-allocated") {
-        auto output_data =
-            ReadWriteInterface::get(file, dataset_name).template read<container_type>();
-        check_empty_dimensions(output_data, dims);
+        REQUIRE(actual_values.size() == 1);
+        REQUIRE(actual_values[0] == expected_value);
     }
 }
 
-template <class CreateContainer>
-void check_empty_dataset(const std::vector<size_t>& dims) {
-    check_empty_read_write_cycle<ReadWriteDataSet, CreateContainer>(dims);
+TEST_CASE("Modify Scalar FileSpace, dset") {
+    File file("h5_modify_scalar_filespace_dset.h5", File::Truncate);
+    check_modify_scalar_filespace<testing::DataSetCreateTraits>(file, "dset");
 }
 
-template <class CreateContainer>
-void check_empty_attribute(const std::vector<size_t>& dims) {
-    check_empty_read_write_cycle<ReadWriteAttribute, CreateContainer>(dims);
+TEST_CASE("Modify Scalar FileSpace, attr") {
+    File file("h5_modify_scalar_filespace_attr.h5", File::Truncate);
+    check_modify_scalar_filespace<testing::AttributeCreateTraits>(file, "attr");
 }
 
-template <class CreateContainer>
-void check_empty_everything(const std::vector<size_t>& dims) {
-    SECTION("Empty dimensions") {
-        check_empty_dimensions<CreateContainer>(dims);
-    }
+template <class CreateTraits>
+void check_modify_scalar_memspace(File& file, const std::string& name) {
+    auto expected_value = std::vector<double>{3.0};
 
-    SECTION("Empty datasets") {
-        check_empty_dataset<CreateContainer>(dims);
+    auto obj = CreateTraits::create(file, name, expected_value);
+    SECTION("squeeze") {
+        auto actual_value = obj.squeezeMemSpace({0}).template read<double>();
+        REQUIRE(actual_value == expected_value[0]);
     }
 
-    SECTION("Empty attribute") {
-        check_empty_attribute<CreateContainer>(dims);
+    SECTION("reshape") {
+        auto actual_value = obj.reshapeMemSpace({}).template read<double>();
+        REQUIRE(actual_value == expected_value[0]);
     }
 }
 
-#ifdef H5_USE_EIGEN
-template <int ndim>
-void check_empty_eigen(const std::vector<size_t>&) {}
-
-template <>
-void check_empty_eigen<1>(const std::vector<size_t>& dims) {
-    SECTION("Eigen::Vector") {
-        check_empty_everything<CreateEmptyEigenVector>({dims[0], 1ul});
-    }
+TEST_CASE("Modify Scalar MemSpace, dset") {
+    File file("h5_modify_scalar_memspace_dset.h5", File::Truncate);
+    check_modify_scalar_memspace<testing::DataSetCreateTraits>(file, "dset");
 }
 
-template <>
-void check_empty_eigen<2>(const std::vector<size_t>& dims) {
-    SECTION("Eigen::Matrix") {
-        check_empty_everything<CreateEmptyEigenMatrix>(dims);
-    }
+TEST_CASE("Modify Scalar MemSpace, attr") {
+    File file("h5_modify_scalar_memspace_attr.h5", File::Truncate);
+    check_modify_scalar_memspace<testing::AttributeCreateTraits>(file, "attr");
 }
-#endif
 
-template <int ndim>
-void check_empty(const std::vector<size_t>& dims) {
-    REQUIRE(dims.size() == ndim);
-
-    SECTION("std::vector") {
-        check_empty_everything<CreateEmptyVector<ndim>>(dims);
-    }
-
-#ifdef H5_USE_BOOST
-    SECTION("boost::multi_array") {
-        check_empty_everything<CreateEmptyBoostMultiArray<ndim>>(dims);
-    }
-#endif
-
-#ifdef H5_USE_EIGEN
-    check_empty_eigen<ndim>(dims);
-#endif
-}
-
-TEST_CASE("Empty arrays") {
-    SECTION("one-dimensional") {
-        check_empty<1>({0ul});
-    }
-
-    SECTION("two-dimensional") {
-        std::vector<std::vector<size_t>> testcases{{0ul, 1ul}, {1ul, 0ul}};
-
-        for (const auto& dims: testcases) {
-            SECTION(details::format_vector(dims)) {
-                check_empty<2>(dims);
-            }
-        }
-    }
-
-    SECTION("three-dimensional") {
-        std::vector<std::vector<size_t>> testcases{{0ul, 1ul, 1ul},
-                                                   {1ul, 1ul, 0ul},
-                                                   {1ul, 0ul, 1ul}};
-
-        for (const auto& dims: testcases) {
-            SECTION(details::format_vector(dims)) {
-                check_empty<3>(dims);
-            }
-        }
-    }
-}
 
 TEST_CASE("HighFiveRecursiveGroups") {
     const std::string file_name("h5_ds_exist.h5");
@@ -2113,7 +2176,7 @@ TEST_CASE("DirectWriteBool") {
     SECTION("WriteReadCycleAttribute") {
         auto attr = file.createAttribute("attr", dataspace, datatype);
         attr.write_raw(expected);
-        attr.read(actual);
+        attr.read_raw(actual);
 
         for (size_t i = 0; i < n; ++i) {
             REQUIRE(expected[i] == actual[i]);
@@ -2123,7 +2186,7 @@ TEST_CASE("DirectWriteBool") {
     SECTION("WriteReadCycleDataSet") {
         auto dset = file.createAttribute("dset", dataspace, datatype);
         dset.write_raw(expected);
-        dset.read(actual);
+        dset.read_raw(actual);
 
         for (size_t i = 0; i < n; ++i) {
             REQUIRE(expected[i] == actual[i]);
@@ -2135,333 +2198,6 @@ TEST_CASE("DirectWriteBool") {
 }
 
 
-class ForwardToAttribute {
-  public:
-    ForwardToAttribute(const HighFive::File& file)
-        : _file(file) {}
-
-    template <class T>
-    HighFive::Attribute create(const std::string& name, const T& value) {
-        return _file.createAttribute(name, value);
-    }
-
-    HighFive::Attribute create(const std::string& name,
-                               const HighFive::DataSpace filespace,
-                               const HighFive::DataType& datatype) {
-        return _file.createAttribute(name, filespace, datatype);
-    }
-
-    HighFive::Attribute get(const std::string& name) {
-        return _file.getAttribute(name);
-    }
-
-  private:
-    HighFive::File _file;
-};
-
-class ForwardToDataSet {
-  public:
-    ForwardToDataSet(const HighFive::File& file)
-        : _file(file) {}
-
-    template <class T>
-    HighFive::DataSet create(const std::string& name, const T& value) {
-        return _file.createDataSet(name, value);
-    }
-
-    HighFive::DataSet create(const std::string& name,
-                             const HighFive::DataSpace filespace,
-                             const HighFive::DataType& datatype) {
-        return _file.createDataSet(name, filespace, datatype);
-    }
-
-    HighFive::DataSet get(const std::string& name) {
-        return _file.getDataSet(name);
-    }
-
-  private:
-    HighFive::File _file;
-};
-
-template <class Proxy>
-void check_single_string(Proxy proxy, size_t string_length) {
-    auto value = std::string(string_length, 'o');
-    auto dataspace = DataSpace::From(value);
-
-    auto n_chars = value.size() + 1;
-    auto n_chars_overlength = n_chars + 10;
-    auto fixed_length = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
-    auto overlength_nullterm = FixedLengthStringType(n_chars_overlength,
-                                                     StringPadding::NullTerminated);
-    auto overlength_nullpad = FixedLengthStringType(n_chars_overlength, StringPadding::NullPadded);
-    auto overlength_spacepad = FixedLengthStringType(n_chars_overlength,
-                                                     StringPadding::SpacePadded);
-    auto variable_length = VariableLengthStringType();
-
-    SECTION("automatic") {
-        proxy.create("auto", value);
-        REQUIRE(proxy.get("auto").template read<std::string>() == value);
-    }
-
-    SECTION("fixed length") {
-        proxy.create("fixed", dataspace, fixed_length).write(value);
-        REQUIRE(proxy.get("fixed").template read<std::string>() == value);
-    }
-
-    SECTION("overlength null-terminated") {
-        proxy.create("overlength_nullterm", dataspace, overlength_nullterm).write(value);
-        REQUIRE(proxy.get("overlength_nullterm").template read<std::string>() == value);
-    }
-
-    SECTION("overlength null-padded") {
-        proxy.create("overlength_nullpad", dataspace, overlength_nullpad).write(value);
-        auto expected = std::string(n_chars_overlength, '\0');
-        expected.replace(0, value.size(), value.data());
-        REQUIRE(proxy.get("overlength_nullpad").template read<std::string>() == expected);
-    }
-
-    SECTION("overlength space-padded") {
-        proxy.create("overlength_spacepad", dataspace, overlength_spacepad).write(value);
-        auto expected = std::string(n_chars_overlength, ' ');
-        expected.replace(0, value.size(), value.data());
-        REQUIRE(proxy.get("overlength_spacepad").template read<std::string>() == expected);
-    }
-
-    SECTION("variable length") {
-        proxy.create("variable", dataspace, variable_length).write(value);
-        REQUIRE(proxy.get("variable").template read<std::string>() == value);
-    }
-}
-
-template <class Proxy>
-void check_multiple_string(Proxy proxy, size_t string_length) {
-    using value_t = std::vector<std::string>;
-    auto value = value_t{std::string(string_length, 'o'), std::string(string_length, 'x')};
-
-    auto dataspace = DataSpace::From(value);
-
-    auto string_overlength = string_length + 10;
-    auto onpoint_nullpad = FixedLengthStringType(string_length, StringPadding::NullPadded);
-    auto onpoint_spacepad = FixedLengthStringType(string_length, StringPadding::SpacePadded);
-
-    auto overlength_nullterm = FixedLengthStringType(string_overlength,
-                                                     StringPadding::NullTerminated);
-    auto overlength_nullpad = FixedLengthStringType(string_overlength, StringPadding::NullPadded);
-    auto overlength_spacepad = FixedLengthStringType(string_overlength, StringPadding::SpacePadded);
-    auto variable_length = VariableLengthStringType();
-
-    auto check = [](const value_t actual, const value_t& expected) {
-        REQUIRE(actual.size() == expected.size());
-        for (size_t i = 0; i < actual.size(); ++i) {
-            REQUIRE(actual[i] == expected[i]);
-        }
-    };
-
-    SECTION("automatic") {
-        proxy.create("auto", value);
-        check(proxy.get("auto").template read<value_t>(), value);
-    }
-
-    SECTION("variable length") {
-        proxy.create("variable", dataspace, variable_length).write(value);
-        check(proxy.get("variable").template read<value_t>(), value);
-    }
-
-    auto make_padded_reference = [&](char pad, size_t n) {
-        auto expected = std::vector<std::string>(value.size(), std::string(n, pad));
-        for (size_t i = 0; i < value.size(); ++i) {
-            expected[i].replace(0, value[i].size(), value[i].data());
-        }
-
-        return expected;
-    };
-
-    auto check_fixed_length = [&](const std::string& label, size_t length) {
-        SECTION(label + " null-terminated") {
-            auto datatype = FixedLengthStringType(length + 1, StringPadding::NullTerminated);
-            proxy.create(label + "_nullterm", dataspace, datatype).write(value);
-            check(proxy.get(label + "_nullterm").template read<value_t>(), value);
-        }
-
-        SECTION(label + " null-padded") {
-            auto datatype = FixedLengthStringType(length, StringPadding::NullPadded);
-            proxy.create(label + "_nullpad", dataspace, datatype).write(value);
-            auto expected = make_padded_reference('\0', length);
-            check(proxy.get(label + "_nullpad").template read<value_t>(), expected);
-        }
-
-        SECTION(label + " space-padded") {
-            auto datatype = FixedLengthStringType(length, StringPadding::SpacePadded);
-            proxy.create(label + "_spacepad", dataspace, datatype).write(value);
-            auto expected = make_padded_reference(' ', length);
-            check(proxy.get(label + "_spacepad").template read<value_t>(), expected);
-        }
-    };
-
-    check_fixed_length("onpoint", string_length);
-    check_fixed_length("overlength", string_length + 5);
-
-
-    SECTION("underlength null-terminated") {
-        auto datatype = FixedLengthStringType(string_length, StringPadding::NullTerminated);
-        REQUIRE_THROWS(proxy.create("underlength_nullterm", dataspace, datatype).write(value));
-    }
-
-    SECTION("underlength nullpad") {
-        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullPadded);
-        REQUIRE_THROWS(proxy.create("underlength_nullpad", dataspace, datatype).write(value));
-    }
-
-    SECTION("underlength spacepad") {
-        auto datatype = FixedLengthStringType(string_length - 1, StringPadding::NullTerminated);
-        REQUIRE_THROWS(proxy.create("underlength_spacepad", dataspace, datatype).write(value));
-    }
-}
-
-TEST_CASE("HighFiveSTDString (dataset, single, short)") {
-    File file("std_string_dataset_single_short.h5", File::Truncate);
-    check_single_string(ForwardToDataSet(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, single, short)") {
-    File file("std_string_attribute_single_short.h5", File::Truncate);
-    check_single_string(ForwardToAttribute(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (dataset, single, long)") {
-    File file("std_string_dataset_single_long.h5", File::Truncate);
-    check_single_string(ForwardToDataSet(file), 256);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, single, long)") {
-    File file("std_string_attribute_single_long.h5", File::Truncate);
-    check_single_string(ForwardToAttribute(file), 256);
-}
-
-TEST_CASE("HighFiveSTDString (dataset, multiple, short)") {
-    File file("std_string_dataset_multiple_short.h5", File::Truncate);
-    check_multiple_string(ForwardToDataSet(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, multiple, short)") {
-    File file("std_string_attribute_multiple_short.h5", File::Truncate);
-    check_multiple_string(ForwardToAttribute(file), 3);
-}
-
-TEST_CASE("HighFiveSTDString (dataset, multiple, long)") {
-    File file("std_string_dataset_multiple_long.h5", File::Truncate);
-    check_multiple_string(ForwardToDataSet(file), 256);
-}
-
-TEST_CASE("HighFiveSTDString (attribute, multiple, long)") {
-    File file("std_string_attribute_multiple_long.h5", File::Truncate);
-    check_multiple_string(ForwardToAttribute(file), 256);
-}
-
-TEST_CASE("HighFiveFixedString") {
-    const std::string file_name("array_atomic_types.h5");
-    const std::string group_1("group1");
-
-    // Create a new file using the default property lists.
-    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-    char raw_strings[][10] = {"abcd", "1234"};
-
-    /// This will not compile - only char arrays - hits static_assert with a nice
-    /// error
-    // file.createDataSet<int[10]>(ds_name, DataSpace(2)));
-
-    {  // But char should be fine
-        auto ds = file.createDataSet<char[10]>("ds1", DataSpace(2));
-        CHECK(ds.getDataType().getClass() == DataTypeClass::String);
-        ds.write(raw_strings);
-    }
-
-    {  // char[] is, by default, int8
-        auto ds2 = file.createDataSet("ds2", raw_strings);
-        CHECK(ds2.getDataType().getClass() == DataTypeClass::Integer);
-    }
-
-    {  // String Truncate happens low-level if well setup
-        auto ds3 = file.createDataSet<char[6]>("ds3", DataSpace::FromCharArrayStrings(raw_strings));
-        ds3.write(raw_strings);
-    }
-
-    {  // Write as raw elements from pointer (with const)
-        const char(*strings_fixed)[10] = raw_strings;
-        // With a pointer we dont know how many strings -> manual DataSpace
-        file.createDataSet<char[10]>("ds4", DataSpace(2)).write(strings_fixed);
-    }
-
-
-    {  // Cant convert flex-length to fixed-length
-        const char* buffer[] = {"abcd", "1234"};
-        SilenceHDF5 silencer;
-        CHECK_THROWS_AS(file.createDataSet<char[10]>("ds5", DataSpace(2)).write(buffer),
-                        HighFive::DataSetException);
-    }
-
-    {  // scalar char strings
-        const char buffer[] = "abcd";
-        file.createDataSet<char[10]>("ds6", DataSpace(1)).write(buffer);
-    }
-
-    {
-        // Direct way of writing `std::string` as a fixed length
-        // HDF5 string.
-
-        std::string value = "foo";
-        auto n_chars = value.size() + 1;
-
-        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
-        auto dataspace = DataSpace(1);
-
-        auto ds = file.createDataSet("ds8", dataspace, datatype);
-        ds.write_raw(value.data(), datatype);
-
-        {
-            // Due to missing non-const overload of `data()` until C++17 we'll
-            // read into something else instead (don't forget the '\0').
-            auto expected = std::vector<char>(n_chars, '!');
-            ds.read(expected.data(), datatype);
-
-            CHECK(expected.size() == value.size() + 1);
-            for (size_t i = 0; i < value.size(); ++i) {
-                REQUIRE(expected[i] == value[i]);
-            }
-        }
-
-#if HIGHFIVE_CXX_STD >= 17
-        {
-            auto expected = std::string(value.size(), '-');
-            ds.read(expected.data(), datatype);
-
-            REQUIRE(expected == value);
-        }
-#endif
-    }
-
-    {
-        size_t n_chars = 4;
-        size_t n_strings = 2;
-
-        std::vector<char> value(n_chars * n_strings, '!');
-
-        auto datatype = FixedLengthStringType(n_chars, StringPadding::NullTerminated);
-        auto dataspace = DataSpace(n_strings);
-
-        auto ds = file.createDataSet("ds9", dataspace, datatype);
-        ds.write_raw(value.data(), datatype);
-
-        auto expected = std::vector<char>(value.size(), '-');
-        ds.read(expected.data(), datatype);
-
-        CHECK(expected.size() == value.size());
-        for (size_t i = 0; i < value.size(); ++i) {
-            REQUIRE(expected[i] == value[i]);
-        }
-    }
-}
-
 TEST_CASE("HighFiveReference") {
     const std::string file_name("h5_ref_test.h5");
     const std::string dataset1_name("dset1");
@@ -2520,29 +2256,6 @@ TEST_CASE("HighFiveReference") {
     }
 }
 
-TEST_CASE("HighFiveReadWriteConsts") {
-    const std::string file_name("3d_dataset_from_flat.h5");
-    const std::string dataset_name("dset");
-    const std::array<std::size_t, 3> DIMS{3, 3, 3};
-    using datatype = int;
-
-    File file(file_name, File::ReadWrite | File::Create | File::Truncate);
-    DataSpace dataspace = DataSpace(DIMS);
-
-    DataSet dataset = file.createDataSet<datatype>(dataset_name, dataspace);
-    std::vector<datatype> const t1(DIMS[0] * DIMS[1] * DIMS[2], 1);
-    auto raw_3d_vec_const = reinterpret_cast<datatype const* const* const*>(t1.data());
-    dataset.write(raw_3d_vec_const);
-
-    std::vector<std::vector<std::vector<datatype>>> result;
-    dataset.read(result);
-    for (const auto& vec2d: result) {
-        for (const auto& vec1d: vec2d) {
-            REQUIRE(vec1d == (std::vector<datatype>{1, 1, 1}));
-        }
-    }
-}
-
 TEST_CASE("HighFiveDataTypeClass") {
     auto Float = DataTypeClass::Float;
     auto String = DataTypeClass::String;
@@ -2561,7 +2274,7 @@ TEST_CASE("HighFiveDataTypeClass") {
     CHECK(((Float | String) & String) == String);
 }
 
-#ifdef H5_USE_EIGEN
+#ifdef HIGHFIVE_TEST_EIGEN
 
 template <typename T>
 void test_eigen_vec(File& file, const std::string& test_flavor, const T& vec_input, T& vec_output) {
@@ -2612,7 +2325,7 @@ TEST_CASE("HighFiveEigen") {
         vec_in << 1, 2, 3, 4, 5, 6, 7, 8, 9;
         Eigen::Matrix<double, 3, 3> vec_out;
 
-        CHECK_THROWS(test_eigen_vec(file, ds_name_flavor, vec_in, vec_out));
+        test_eigen_vec(file, ds_name_flavor, vec_in, vec_out);
     }
 
     // Eigen MatrixXd
@@ -2621,7 +2334,7 @@ TEST_CASE("HighFiveEigen") {
         Eigen::MatrixXd vec_in = 100. * Eigen::MatrixXd::Random(20, 5);
         Eigen::MatrixXd vec_out(20, 5);
 
-        CHECK_THROWS(test_eigen_vec(file, ds_name_flavor, vec_in, vec_out));
+        test_eigen_vec(file, ds_name_flavor, vec_in, vec_out);
     }
 
     // std::vector<of EigenMatrixXd>
@@ -2635,10 +2348,10 @@ TEST_CASE("HighFiveEigen") {
         vec_in.push_back(m2);
         std::vector<Eigen::MatrixXd> vec_out(2, Eigen::MatrixXd::Zero(20, 5));
 
-        CHECK_THROWS(test_eigen_vec(file, ds_name_flavor, vec_in, vec_out));
+        test_eigen_vec(file, ds_name_flavor, vec_in, vec_out);
     }
 
-#ifdef H5_USE_BOOST
+#ifdef HIGHFIVE_TEST_BOOST
     // boost::multi_array<of EigenVector3f>
     {
         ds_name_flavor = "BMultiEigenVector3f";
@@ -2677,7 +2390,7 @@ TEST_CASE("HighFiveEigen") {
             }
         }
 
-        CHECK_THROWS(test_eigen_vec(file, ds_name_flavor, vec_in, vec_out));
+        test_eigen_vec(file, ds_name_flavor, vec_in, vec_out);
     }
 
 #endif
diff --git a/packages/HighFive/tests/unit/tests_high_five_easy.cpp b/packages/HighFive/tests/unit/tests_high_five_easy.cpp
index e003c323401a9460332883abd618fef10a9a169d..8bcce26d095100a8754738db0afe63dff8cebf5a 100644
--- a/packages/HighFive/tests/unit/tests_high_five_easy.cpp
+++ b/packages/HighFive/tests/unit/tests_high_five_easy.cpp
@@ -20,13 +20,22 @@
 
 #include <stdio.h>
 
-#include <highfive/H5Easy.hpp>
 
-#ifdef H5_USE_XTENSOR
+#ifdef HIGHFIVE_TEST_XTENSOR
 #include <xtensor/xrandom.hpp>
 #include <xtensor/xview.hpp>
 #endif
 
+#ifdef HIGHFIVE_TEST_EIGEN
+#include <Eigen/Dense>
+#endif
+
+#ifdef HIGHFIVE_TEST_OPENCV
+#define H5_USE_OPENCV
+#endif
+
+#include <highfive/H5Easy.hpp>
+
 #include <catch2/catch_test_macros.hpp>
 
 TEST_CASE("H5Easy_Compression") {
@@ -179,7 +188,7 @@ TEST_CASE("H5Easy_Attribute_scalar") {
     CHECK(c == c_r);
 }
 
-#ifdef H5_USE_XTENSOR
+#ifdef HIGHFIVE_TEST_XTENSOR
 TEST_CASE("H5Easy_extend1d") {
     H5Easy::File file("h5easy_extend1d.h5", H5Easy::File::Overwrite);
 
@@ -234,6 +243,48 @@ TEST_CASE("H5Easy_xtensor") {
     CHECK(xt::all(xt::equal(B, B_r)));
 }
 
+TEST_CASE("H5Easy_xtensor_column_major") {
+    H5Easy::File file("h5easy_xtensor_colum_major.h5", H5Easy::File::Overwrite);
+
+    using column_major_t = xt::xtensor<double, 2, xt::layout_type::column_major>;
+
+    xt::xtensor<double, 2> A = 100. * xt::random::randn<double>({20, 5});
+
+    SECTION("Write column major") {
+        column_major_t B = A;
+        H5Easy::dump(file, "/path/to/A", B);
+        auto A_r = H5Easy::load<xt::xtensor<double, 2>>(file, "/path/to/A");
+        CHECK(xt::allclose(A, A_r));
+    }
+
+    SECTION("Read column major") {
+        H5Easy::dump(file, "/path/to/A", A);
+        auto A_r = H5Easy::load<column_major_t>(file, "/path/to/A");
+        CHECK(xt::allclose(A, A_r));
+    }
+}
+
+TEST_CASE("H5Easy_xarray_column_major") {
+    H5Easy::File file("h5easy_xarray_colum_major.h5", H5Easy::File::Overwrite);
+
+    using column_major_t = xt::xarray<double, xt::layout_type::column_major>;
+
+    xt::xarray<double> A = 100. * xt::random::randn<double>({20, 5});
+
+    SECTION("Write column major") {
+        column_major_t B = A;
+        H5Easy::dump(file, "/path/to/A", B);
+        auto A_r = H5Easy::load<xt::xtensor<double, 2>>(file, "/path/to/A");
+        CHECK(xt::allclose(A, A_r));
+    }
+
+    SECTION("Read column major") {
+        H5Easy::dump(file, "/path/to/A", A);
+        auto A_r = H5Easy::load<column_major_t>(file, "/path/to/A");
+        CHECK(xt::allclose(A, A_r));
+    }
+}
+
 TEST_CASE("H5Easy_xarray") {
     H5Easy::File file("h5easy_xarray.h5", H5Easy::File::Overwrite);
 
@@ -304,7 +355,7 @@ TEST_CASE("H5Easy_Attribute_xtensor") {
 }
 #endif
 
-#ifdef H5_USE_EIGEN
+#ifdef HIGHFIVE_TEST_EIGEN
 TEST_CASE("H5Easy_Eigen_MatrixX") {
     H5Easy::File file("h5easy_eigen_MatrixX.h5", H5Easy::File::Overwrite);
 
@@ -439,7 +490,7 @@ TEST_CASE("H5Easy_Attribute_Eigen_MatrixX") {
 }
 #endif
 
-#ifdef H5_USE_OPENCV
+#ifdef HIGHFIVE_TEST_OPENCV
 TEST_CASE("H5Easy_OpenCV_Mat_") {
     H5Easy::File file("h5easy_opencv_Mat_.h5", H5Easy::File::Overwrite);
 
diff --git a/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp b/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp
deleted file mode 100644
index 08fbea9cec350a8b4de42393a1a7d5ef830b5d70..0000000000000000000000000000000000000000
--- a/packages/HighFive/tests/unit/tests_high_five_multi_dims.cpp
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- *  Copyright (c), 2017-2019, Blue Brain Project - EPFL
- *
- *  Distributed under the Boost Software License, Version 1.0.
- *    (See accompanying file LICENSE_1_0.txt or copy at
- *          http://www.boost.org/LICENSE_1_0.txt)
- *
- */
-
-#include <string>
-#include <iostream>
-
-#include <highfive/highfive.hpp>
-
-
-#ifdef H5_USE_BOOST
-#include <boost/multi_array.hpp>
-#endif
-
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/catch_template_test_macros.hpp>
-
-#include "tests_high_five.hpp"
-
-using namespace HighFive;
-
-/// \brief Test for 2D old-style arrays (T array[x][y])
-template <typename T>
-void readWrite2DArrayTest() {
-    std::ostringstream filename;
-    filename << "h5_rw_2d_array_" << typeNameHelper<T>() << "_test.h5";
-    const std::string DATASET_NAME("dset");
-    const size_t x_size = 100;
-    const size_t y_size = 10;
-
-    // Create a new file using the default property lists.
-    File file(filename.str(), File::ReadWrite | File::Create | File::Truncate);
-
-    // Create the data space for the dataset.
-    std::vector<size_t> dims{x_size, y_size};
-
-    DataSpace dataspace(dims);
-
-    // Create a dataset with arbitrary type
-    DataSet dataset = file.createDataSet<T>(DATASET_NAME, dataspace);
-
-    T array[x_size][y_size];
-
-    ContentGenerate<T> generator;
-    generate2D(array, x_size, y_size, generator);
-
-    dataset.write(array);
-
-    T result[x_size][y_size];
-    dataset.read(result);
-
-    for (size_t i = 0; i < x_size; ++i) {
-        for (size_t j = 0; j < y_size; ++j) {
-            CHECK(result[i][j] == array[i][j]);
-        }
-    }
-}
-
-TEMPLATE_LIST_TEST_CASE("ReadWrite2DArray", "[template]", numerical_test_types) {
-    readWrite2DArrayTest<TestType>();
-}
-
-template <typename T>
-void readWriteArrayTest() {
-    const size_t x_size = 200;
-    typename std::array<T, x_size> vec;
-    ContentGenerate<T> generator;
-    std::generate(vec.begin(), vec.end(), generator);
-
-    typename std::array<T, x_size> result;
-    auto dataset = readWriteDataset<T>(vec, result, 1, "std-array");
-
-    CHECK(result == vec);
-
-    typename std::array<T, 1> tooSmall;
-    CHECK_THROWS_AS(dataset.read(tooSmall), DataSpaceException);
-}
-
-TEMPLATE_LIST_TEST_CASE("readWriteArray", "[template]", numerical_test_types) {
-    readWriteArrayTest<TestType>();
-}
-
-template <typename T, typename VectorSubT>
-void readWriteVectorNDTest(std::vector<VectorSubT>& ndvec, const std::vector<size_t>& dims) {
-    fillVec(ndvec, dims, ContentGenerate<T>());
-
-    std::vector<VectorSubT> result;
-    readWriteDataset<T>(ndvec, result, dims.size(), "vector");
-
-    CHECK(checkLength(result, dims));
-    CHECK(ndvec == result);
-}
-
-TEMPLATE_LIST_TEST_CASE("readWritSimpleVector", "[template]", numerical_test_types) {
-    std::vector<TestType> vec;
-    readWriteVectorNDTest<TestType>(vec, {50});
-}
-
-TEMPLATE_LIST_TEST_CASE("readWrite2DVector", "[template]", numerical_test_types) {
-    std::vector<std::vector<TestType>> _2dvec;
-    readWriteVectorNDTest<TestType>(_2dvec, {10, 8});
-}
-
-TEMPLATE_LIST_TEST_CASE("readWrite3DVector", "[template]", numerical_test_types) {
-    std::vector<std::vector<std::vector<TestType>>> _3dvec;
-    readWriteVectorNDTest<TestType>(_3dvec, {10, 5, 4});
-}
-
-TEMPLATE_LIST_TEST_CASE("readWrite4DVector", "[template]", numerical_test_types) {
-    std::vector<std::vector<std::vector<std::vector<TestType>>>> _4dvec;
-    readWriteVectorNDTest<TestType>(_4dvec, {5, 4, 3, 2});
-}
-
-TEMPLATE_LIST_TEST_CASE("vector of array", "[template]", numerical_test_types) {
-    std::vector<std::array<TestType, 4>> vec{{1, 2, 3, 4}, {1, 2, 3, 4}};
-    std::vector<std::array<TestType, 4>> result;
-    readWriteDataset<TestType>(vec, result, 2, "vector");
-
-    CHECK(vec.size() == result.size());
-    CHECK(vec[0].size() == result[0].size());
-    CHECK(vec == result);
-}
-
-
-#ifdef H5_USE_BOOST
-
-template <typename T>
-void MultiArray3DTest() {
-    typedef typename boost::multi_array<T, 3> MultiArray;
-
-    std::ostringstream filename;
-    filename << "h5_rw_multiarray_" << typeNameHelper<T>() << "_test.h5";
-
-    const int size_x = 10, size_y = 10, size_z = 10;
-    const std::string DATASET_NAME("dset");
-    MultiArray array(boost::extents[size_x][size_y][size_z]);
-
-    ContentGenerate<T> generator;
-    std::generate(array.data(), array.data() + array.num_elements(), generator);
-
-    // Create a new file using the default property lists.
-    File file(filename.str(), File::ReadWrite | File::Create | File::Truncate);
-
-    DataSet dataset = file.createDataSet<T>(DATASET_NAME, DataSpace::From(array));
-
-    dataset.write(array);
-
-    // read it back
-    MultiArray result;
-
-    dataset.read(result);
-
-    for (long i = 0; i < size_x; ++i) {
-        for (long j = 0; j < size_y; ++j) {
-            for (long k = 0; k < size_z; ++k) {
-                CHECK(array[i][j][k] == result[i][j][k]);
-            }
-        }
-    }
-}
-
-TEMPLATE_LIST_TEST_CASE("MultiArray3D", "[template]", numerical_test_types) {
-    MultiArray3DTest<TestType>();
-}
-
-template <typename T>
-void ublas_matrix_Test() {
-    using Matrix = boost::numeric::ublas::matrix<T>;
-
-    std::ostringstream filename;
-    filename << "h5_rw_multiarray_" << typeNameHelper<T>() << "_test.h5";
-
-    const size_t size_x = 10, size_y = 10;
-    const std::string DATASET_NAME("dset");
-
-    Matrix mat(size_x, size_y);
-
-    ContentGenerate<T> generator;
-    for (std::size_t i = 0; i < mat.size1(); ++i) {
-        for (std::size_t j = 0; j < mat.size2(); ++j) {
-            mat(i, j) = generator();
-        }
-    }
-
-    // Create a new file using the default property lists.
-    File file(filename.str(), File::ReadWrite | File::Create | File::Truncate);
-
-    DataSet dataset = file.createDataSet<T>(DATASET_NAME, DataSpace::From(mat));
-
-    dataset.write(mat);
-
-    // read it back
-    Matrix result;
-
-    dataset.read(result);
-
-    for (size_t i = 0; i < size_x; ++i) {
-        for (size_t j = 0; j < size_y; ++j) {
-            CHECK(mat(i, j) == result(i, j));
-        }
-    }
-}
-
-TEMPLATE_LIST_TEST_CASE("ublas_matrix", "[template]", numerical_test_types) {
-    ublas_matrix_Test<TestType>();
-}
-
-#endif
diff --git a/src/analysis/PyramidGaussQuadrature.cpp b/src/analysis/PyramidGaussQuadrature.cpp
index 679f3ded5b85764bca43ed062f379d8171cd5c65..e85a74ac1af9ec6893d15f5489cd568891e0ca92 100644
--- a/src/analysis/PyramidGaussQuadrature.cpp
+++ b/src/analysis/PyramidGaussQuadrature.cpp
@@ -24,6 +24,11 @@ PyramidGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
 
       const double w = (4. / 3) * unit_weight;
 
+      // gcc's bound checking are messed up due to the use of
+      // std::array and the following dynamic/general switch
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Warray-bounds"
+
       switch (id) {
       case 1: {
         Assert(value_list.size() == 1);
@@ -96,6 +101,8 @@ PyramidGaussQuadrature::_buildPointAndWeightLists(const size_t degree)
       }
         // LCOV_EXCL_STOP
       }
+
+#pragma GCC diagnostic pop
     }
   };
 
diff --git a/src/dev/ParallelChecker.hpp b/src/dev/ParallelChecker.hpp
index ab9f092fbca41fdc52a978172fba6b83a7ac1568..3c5bcc7146e95c45ae06b5356c6471c5cffeaf5b 100644
--- a/src/dev/ParallelChecker.hpp
+++ b/src/dev/ParallelChecker.hpp
@@ -1,10 +1,7 @@
 #ifndef PARALLEL_CHECKER_HPP
 #define PARALLEL_CHECKER_HPP
 
-#include <utils/pugs_config.hpp>
-#ifdef PUGS_HAS_HDF5
-#include <highfive/highfive.hpp>
-#endif   // PUGS_HAS_HDF5
+#include <utils/HighFivePugsUtils.hpp>
 
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemArrayVariant.hpp>
@@ -16,6 +13,7 @@
 #include <utils/Filesystem.hpp>
 #include <utils/Messenger.hpp>
 #include <utils/SourceLocation.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
 
 #include <fstream>
 
@@ -86,34 +84,6 @@ class ParallelChecker
 
 #ifdef PUGS_HAS_HDF5
  private:
-  template <typename T>
-  struct TinyVectorDataType;
-
-  template <size_t Dimension, typename DataT>
-  struct TinyVectorDataType<TinyVector<Dimension, DataT>> : public HighFive::DataType
-  {
-    TinyVectorDataType()
-    {
-      hsize_t dim[]     = {Dimension};
-      auto h5_data_type = HighFive::create_datatype<DataT>();
-      _hid              = H5Tarray_create(h5_data_type.getId(), 1, dim);
-    }
-  };
-
-  template <typename T>
-  struct TinyMatrixDataType;
-
-  template <size_t M, size_t N, typename DataT>
-  struct TinyMatrixDataType<TinyMatrix<M, N, DataT>> : public HighFive::DataType
-  {
-    TinyMatrixDataType()
-    {
-      hsize_t dim[]     = {M, N};
-      auto h5_data_type = HighFive::create_datatype<DataT>();
-      _hid              = H5Tarray_create(h5_data_type.getId(), 2, dim);
-    }
-  };
-
   HighFive::File
   _createOrOpenFileRW() const
   {
@@ -140,20 +110,8 @@ class ParallelChecker
   _writeArray(HighFive::Group& group, const std::string& name, const Array<DataType>& array) const
   {
     using data_type = std::remove_const_t<DataType>;
-    if constexpr (is_tiny_vector_v<data_type>) {
-      auto dataset = group.createDataSet(name, HighFive::DataSpace{std::vector<size_t>{array.size()}},
-                                         TinyVectorDataType<data_type>{});
-
-      dataset.template write_raw<typename data_type::data_type>(&(array[0][0]), TinyVectorDataType<data_type>{});
-    } else if constexpr (is_tiny_matrix_v<data_type>) {
-      auto dataset = group.createDataSet(name, HighFive::DataSpace{std::vector<size_t>{array.size()}},
-                                         TinyMatrixDataType<data_type>{});
-
-      dataset.template write_raw<typename data_type::data_type>(&(array[0](0, 0)), TinyMatrixDataType<data_type>{});
-    } else {
-      auto dataset = group.createDataSet<data_type>(name, HighFive::DataSpace{std::vector<size_t>{array.size()}});
-      dataset.template write_raw<data_type>(&(array[0]));
-    }
+    auto dataset    = group.createDataSet<data_type>(name, HighFive::DataSpace{std::vector<size_t>{array.size()}});
+    dataset.template write_raw<data_type>(&(array[0]));
   }
 
   template <typename DataType>
@@ -161,26 +119,10 @@ class ParallelChecker
   _writeTable(HighFive::Group& group, const std::string& name, const Table<DataType>& table) const
   {
     using data_type = std::remove_const_t<DataType>;
-    if constexpr (is_tiny_vector_v<data_type>) {
-      auto dataset =
-        group.createDataSet(name,
-                            HighFive::DataSpace{std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}},
-                            TinyVectorDataType<data_type>{});
-
-      dataset.template write_raw<typename data_type::data_type>(&(table(0, 0)[0]), TinyVectorDataType<data_type>{});
-    } else if constexpr (is_tiny_matrix_v<data_type>) {
-      auto dataset =
-        group.createDataSet(name,
-                            HighFive::DataSpace{std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}},
-                            TinyMatrixDataType<data_type>{});
-
-      dataset.template write_raw<typename data_type::data_type>(&(table(0, 0)(0, 0)), TinyMatrixDataType<data_type>{});
-    } else {
-      auto dataset =
-        group.createDataSet<data_type>(name, HighFive::DataSpace{
-                                               std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}});
-      dataset.template write_raw<data_type>(&(table(0, 0)));
-    }
+    auto dataset =
+      group.createDataSet<data_type>(name, HighFive::DataSpace{
+                                             std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}});
+    dataset.template write_raw<data_type>(&(table(0, 0)));
   }
 
   template <typename DataType>
@@ -192,13 +134,7 @@ class ParallelChecker
     auto dataset = group.getDataSet(name);
     Array<data_type> array(dataset.getElementCount());
 
-    if constexpr (is_tiny_vector_v<data_type>) {
-      dataset.read<data_type>(&(array[0]), TinyVectorDataType<data_type>{});
-    } else if constexpr (is_tiny_matrix_v<data_type>) {
-      dataset.read<data_type>(&(array[0]), TinyMatrixDataType<data_type>{});
-    } else {
-      dataset.read<data_type>(&(array[0]));
-    }
+    dataset.read_raw(&(array[0]));
     return array;
   }
 
@@ -211,13 +147,7 @@ class ParallelChecker
     auto dataset = group.getDataSet(name);
     Table<data_type> table(dataset.getDimensions()[0], dataset.getDimensions()[1]);
 
-    if constexpr (is_tiny_vector_v<data_type>) {
-      dataset.read<data_type>(&(table(0, 0)), TinyVectorDataType<data_type>{});
-    } else if constexpr (is_tiny_matrix_v<data_type>) {
-      dataset.read<data_type>(&(table(0, 0)), TinyMatrixDataType<data_type>{});
-    } else {
-      dataset.read<data_type>(&(table(0, 0)));
-    }
+    dataset.read_raw(&(table(0, 0)));
     return table;
   }
 
@@ -1420,6 +1350,22 @@ class ParallelChecker
     return *m_instance;
   }
 
+  size_t
+  tag() const
+  {
+    return m_tag;
+  }
+
+  void
+  setTag(size_t tag)
+  {
+    if (ResumingManager::getInstance().isResuming()) {
+      m_tag = tag;
+    } else {
+      throw UnexpectedError("Cannot modify parallel checker tag if not resuming");
+    }
+  }
+
   Mode
   mode() const
   {
@@ -1429,7 +1375,7 @@ class ParallelChecker
   void
   setMode(const Mode& mode)
   {
-    if (m_tag != 0) {
+    if ((m_tag != 0) and not ResumingManager::getInstance().isResuming()) {
       throw UnexpectedError("Cannot modify parallel checker mode if it was already used");
     }
 
diff --git a/src/language/CMakeLists.txt b/src/language/CMakeLists.txt
index 2403c045d9e5d26a30f763cc1814ef9f550a4d35..e5e09ed768bd1e8796ad9d4cdc214de65f4cb5b2 100644
--- a/src/language/CMakeLists.txt
+++ b/src/language/CMakeLists.txt
@@ -10,8 +10,13 @@ add_library(
   PugsLanguage
   PugsParser.cpp)
 
+target_link_libraries(
+  PugsLanguage
+  ${HIGHFIVE_TARGET}
+)
+
 # Additional dependencies
-add_dependencies(PugsLanguage
+add_dependencies(
   PugsLanguage
   PugsLanguageAlgorithms
   PugsLanguageAST
diff --git a/src/language/PugsParser.cpp b/src/language/PugsParser.cpp
index 77bcd91f571ce75b9dcc3d8c672ad2a91f15d664..fb7f1926cae6c93cb9d0bbfed7435271133d6a6d 100644
--- a/src/language/PugsParser.cpp
+++ b/src/language/PugsParser.cpp
@@ -1,8 +1,8 @@
 #include <language/PugsParser.hpp>
 
 #include <language/PEGGrammar.hpp>
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNode.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -14,17 +14,20 @@
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolInitializationChecker.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
-#include <language/utils/ASTDotPrinter.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
 #include <language/utils/ASTExecutionInfo.hpp>
-#include <language/utils/ASTPrinter.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/Exit.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <utils/ConsoleManager.hpp>
 #include <utils/ExecutionStatManager.hpp>
+#include <utils/Messenger.hpp>
 #include <utils/PugsAssert.hpp>
 #include <utils/PugsUtils.hpp>
 #include <utils/SignalManager.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/checkpointing/ResumingUtils.hpp>
 
 #include <pegtl/contrib/analyze.hpp>
 #include <pegtl/contrib/parse_tree.hpp>
@@ -64,9 +67,11 @@ parser(const std::string& filename)
               << rang::style::reset << " ...\n";
   }
 
-  auto parse_and_execute = [](auto& input) {
+  auto parse_and_execute = [](auto& input, const std::string& file_content) {
     OperatorRepository::create();
-    ASTBacktrace::create(input);
+    CheckpointResumeRepository::create();
+
+    ASTExecutionStack::create(input, file_content);
     std::unique_ptr<ASTNode> root_node = ASTBuilder::build(*input);
 
     ASTModulesImporter module_importer{*root_node};
@@ -99,6 +104,8 @@ parser(const std::string& filename)
 
     ASTExecutionInfo execution_info{*root_node, module_importer.moduleRepository()};
 
+    ASTCheckpointsInfo checkpoint_info{*root_node};
+
     ExecutionPolicy exec_all;
     try {
       root_node->execute(exec_all);
@@ -107,15 +114,29 @@ parser(const std::string& filename)
       ExecutionStatManager::getInstance().setExitCode(e.code());
       clear_symbol_tables(root_node);
     }
+
     root_node->m_symbol_table->clearValues();
 
+    CheckpointResumeRepository::destroy();
     OperatorRepository::destroy();
   };
 
-  auto input = std::make_shared<TAO_PEGTL_NAMESPACE::file_input<>>(filename);
+  std::string file_content;
+  if (parallel::rank() == 0) {
+    if (ResumingManager::getInstance().isResuming()) {
+      file_content = resumingDatafile(filename);
+    } else {
+      std::ifstream file{filename};
+      file_content = std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
+    }
+  }
+  parallel::broadcast(file_content, 0);
+
+  auto input = std::make_shared<TAO_PEGTL_NAMESPACE::string_input<>>(file_content, filename);
+
   if (not SignalManager::pauseOnError()) {
     try {
-      parse_and_execute(input);
+      parse_and_execute(input, file_content);
     }
     catch (const ParseError& e) {
       const auto p = e.positions().front();
@@ -143,12 +164,12 @@ parser(const std::string& filename)
       std::exit(1);
     }
     catch (const IExitError& e) {
-      // Each failing process must write
-      std::cerr.clear();
-      std::cerr << ASTBacktrace::getInstance().errorMessageAt(e.what()) << '\n';
+      std::cerr << ASTExecutionStack::getInstance().errorMessageAt(e.what()) << '\n';
+
+      finalize();
       std::exit(1);
     }
   } else {
-    parse_and_execute(input);
+    parse_and_execute(input, file_content);
   }
 }
diff --git a/src/language/algorithms/CMakeLists.txt b/src/language/algorithms/CMakeLists.txt
index 63c2374987289e7e5c7352dbc773d77ef4e675fb..9612143a50b926c5bd96a8e46f8b87d746812ae5 100644
--- a/src/language/algorithms/CMakeLists.txt
+++ b/src/language/algorithms/CMakeLists.txt
@@ -3,8 +3,3 @@
 add_library(PugsLanguageAlgorithms
   INTERFACE # THIS SHOULD BE REMOVED IF FILES ARE FINALY PROVIDED
   )
-
-
-add_dependencies(PugsLanguageAlgorithms
-  PugsUtils
-  PugsMesh)
diff --git a/src/language/ast/ASTBacktrace.cpp b/src/language/ast/ASTBacktrace.cpp
deleted file mode 100644
index 18442ec439194924fe83a5080b5bee2016eab4da..0000000000000000000000000000000000000000
--- a/src/language/ast/ASTBacktrace.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-#include <language/ast/ASTBacktrace.hpp>
-
-#include <language/ast/ASTNode.hpp>
-
-ASTBacktrace* ASTBacktrace::m_instance = nullptr;
-
-ASTBacktrace::ASTBacktrace(const std::shared_ptr<TAO_PEGTL_NAMESPACE::file_input<>>& file_input)
-  : m_file_input(file_input)
-{}
-
-std::string
-ASTBacktrace::errorMessageAt(const std::string& message) const
-{
-  auto& stack = ASTBacktrace::getInstance().m_stack;
-  std::ostringstream error_msg;
-
-  auto p = stack[stack.size() - 1]->begin();
-  error_msg << rang::style::bold << p.source << ':' << p.line << ':' << p.column << ": " << rang::style::reset
-            << message << rang::fg::reset << '\n';
-
-  if (m_file_input.use_count() > 0) {
-    error_msg << m_file_input->line_at(p) << '\n'
-              << std::string(p.column - 1, ' ') << rang::fgB::yellow << '^' << rang::fg::reset << '\n';
-  }
-
-  return error_msg.str();
-}
-
-SourceLocation
-ASTBacktrace::sourceLocation() const
-{
-  auto& stack = ASTBacktrace::getInstance().m_stack;
-
-  auto p = stack[stack.size() - 1]->begin();
-  return SourceLocation(p.source, p.line, p.column);
-}
-
-void
-ASTBacktrace::create()
-{
-  if (m_instance == nullptr) {
-    m_instance = new ASTBacktrace();
-  } else {
-    throw UnexpectedError("ASTBackTrace was already created!");
-  }
-}
-
-void
-ASTBacktrace::create(const std::shared_ptr<TAO_PEGTL_NAMESPACE::file_input<>>& file_input)
-{
-  if (m_instance == nullptr) {
-    m_instance = new ASTBacktrace(file_input);
-  } else {
-    throw UnexpectedError("ASTBackTrace was already created!");
-  }
-}
-
-void
-ASTBacktrace::destroy()
-{
-  delete m_instance;
-  m_instance = nullptr;
-}
diff --git a/src/language/ast/ASTBacktrace.hpp b/src/language/ast/ASTBacktrace.hpp
deleted file mode 100644
index 6ea9a53645ea4c4fda5c35585a80e44392254b5f..0000000000000000000000000000000000000000
--- a/src/language/ast/ASTBacktrace.hpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef AST_BACTRACE_HPP
-#define AST_BACTRACE_HPP
-
-#include <utils/PugsAssert.hpp>
-#include <utils/SourceLocation.hpp>
-
-#include <pegtl/file_input.hpp>
-#include <string>
-#include <vector>
-
-class ASTNode;
-class ASTBacktrace
-{
- private:
-  std::vector<const ASTNode*> m_stack;
-
-  std::shared_ptr<TAO_PEGTL_NAMESPACE::file_input<>> m_file_input;
-
-  static ASTBacktrace* m_instance;
-
-  ASTBacktrace() = default;
-  ASTBacktrace(const std::shared_ptr<TAO_PEGTL_NAMESPACE::file_input<>>& file_input);
-  ~ASTBacktrace() = default;
-
- public:
-  std::string errorMessageAt(const std::string& error_msg) const;
-
-  SourceLocation sourceLocation() const;
-
-  void
-  push(const ASTNode* node)
-  {
-    m_stack.push_back(node);
-  }
-
-  void
-  pop()
-  {
-    m_stack.pop_back();
-  }
-
-  static void create();   // for unit tests only
-  static void create(const std::shared_ptr<TAO_PEGTL_NAMESPACE::file_input<>>& file_input);
-  static void destroy();
-
-  static ASTBacktrace&
-  getInstance()
-  {
-    Assert(m_instance != nullptr);
-    return *m_instance;
-  }
-
-  ASTBacktrace(const ASTBacktrace&) = delete;
-  ASTBacktrace(ASTBacktrace&&)      = delete;
-};
-
-#endif   // AST_BACTRACE_HPP
diff --git a/src/language/ast/ASTBuilder.cpp b/src/language/ast/ASTBuilder.cpp
index d7d8dbc1fe9936a734a395695570d982a9b10779..b15b79a04fc301a06a166a36e447a98df64599bb 100644
--- a/src/language/ast/ASTBuilder.cpp
+++ b/src/language/ast/ASTBuilder.cpp
@@ -312,5 +312,4 @@ ASTBuilder::build(InputT& input)
   return root_node;
 }
 
-template std::unique_ptr<ASTNode> ASTBuilder::build(TAO_PEGTL_NAMESPACE::file_input<>& input);
 template std::unique_ptr<ASTNode> ASTBuilder::build(TAO_PEGTL_NAMESPACE::string_input<>& input);
diff --git a/src/language/ast/ASTExecutionStack.cpp b/src/language/ast/ASTExecutionStack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a02759b393536735521e4ef0f406805c490c3c4c
--- /dev/null
+++ b/src/language/ast/ASTExecutionStack.cpp
@@ -0,0 +1,82 @@
+#include <language/ast/ASTExecutionStack.hpp>
+
+#include <language/ast/ASTNode.hpp>
+
+ASTExecutionStack* ASTExecutionStack::m_instance = nullptr;
+
+ASTExecutionStack::ASTExecutionStack(const std::shared_ptr<TAO_PEGTL_NAMESPACE::string_input<>>& file_input,
+                                     const std::string& file_content)
+  : m_file_input(file_input), m_file_content{file_content}
+{}
+
+std::string
+ASTExecutionStack::errorMessageAt(const std::string& message) const
+{
+  auto& stack = ASTExecutionStack::getInstance().m_stack;
+  std::ostringstream error_msg;
+
+  if (stack.size() > 0) {
+    auto p = stack[stack.size() - 1]->begin();
+    error_msg << rang::style::bold << p.source << ':' << p.line << ':' << p.column << ": " << rang::style::reset
+              << message << rang::fg::reset << '\n';
+
+    if (m_file_input.use_count() > 0) {
+      error_msg << m_file_input->line_at(p) << '\n'
+                << std::string(p.column - 1, ' ') << rang::fgB::yellow << '^' << rang::fg::reset << '\n';
+    }
+  } else {
+    error_msg << message;
+  }
+
+  return error_msg.str();
+}
+
+const ASTNode&
+ASTExecutionStack::currentNode() const
+{
+  auto& stack = ASTExecutionStack::getInstance().m_stack;
+  Assert(stack.size() > 0);
+
+  return *stack[stack.size() - 1];
+}
+
+SourceLocation
+ASTExecutionStack::sourceLocation() const
+{
+  auto& stack = ASTExecutionStack::getInstance().m_stack;
+  Assert(stack.size() > 0);
+
+  auto p = stack[stack.size() - 1]->begin();
+  return SourceLocation(p.source, p.line, p.column);
+}
+
+void
+ASTExecutionStack::create()
+{
+  if (m_instance == nullptr) {
+    m_instance = new ASTExecutionStack();
+  } else {
+    throw UnexpectedError("ASTExecutionStack was already created!");
+  }
+}
+
+void
+ASTExecutionStack::create(const std::shared_ptr<TAO_PEGTL_NAMESPACE::string_input<>>& file_input,
+                          const std::string& file_content)
+{
+  if (m_instance == nullptr) {
+    m_instance = new ASTExecutionStack(file_input, file_content);
+  } else {
+    throw UnexpectedError("ASTExecutionStack was already created!");
+  }
+}
+
+void
+ASTExecutionStack::destroy()
+{
+  if (m_instance == nullptr) {
+    throw UnexpectedError("ASTExecutionStack was not created!");
+  }
+  delete m_instance;
+  m_instance = nullptr;
+}
diff --git a/src/language/ast/ASTExecutionStack.hpp b/src/language/ast/ASTExecutionStack.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3fe6fbece629477e75be352d64f1be7120ae1745
--- /dev/null
+++ b/src/language/ast/ASTExecutionStack.hpp
@@ -0,0 +1,68 @@
+#ifndef AST_EXECUTION_STACK_HPP
+#define AST_EXECUTION_STACK_HPP
+
+#include <utils/PugsAssert.hpp>
+#include <utils/SourceLocation.hpp>
+
+#include <pegtl/string_input.hpp>
+#include <string>
+#include <vector>
+
+class ASTNode;
+class ASTExecutionStack
+{
+ private:
+  std::vector<const ASTNode*> m_stack;
+
+  std::shared_ptr<TAO_PEGTL_NAMESPACE::string_input<>> m_file_input;
+  std::string m_file_content;
+
+  static ASTExecutionStack* m_instance;
+
+  ASTExecutionStack() = default;
+  ASTExecutionStack(const std::shared_ptr<TAO_PEGTL_NAMESPACE::string_input<>>& file_input,
+                    const std::string& file_content);
+  ~ASTExecutionStack() = default;
+
+ public:
+  std::string errorMessageAt(const std::string& error_msg) const;
+
+  const std::string&
+  fileContent() const
+  {
+    return m_file_content;
+  }
+
+  SourceLocation sourceLocation() const;
+
+  const ASTNode& currentNode() const;
+
+  void
+  push(const ASTNode* node)
+  {
+    m_stack.push_back(node);
+  }
+
+  void
+  pop()
+  {
+    m_stack.pop_back();
+  }
+
+  static void create();   // for unit tests only
+  static void create(const std::shared_ptr<TAO_PEGTL_NAMESPACE::string_input<>>& file_input,
+                     const std::string& file_content);
+  static void destroy();
+
+  static ASTExecutionStack&
+  getInstance()
+  {
+    Assert(m_instance != nullptr);
+    return *m_instance;
+  }
+
+  ASTExecutionStack(const ASTExecutionStack&) = delete;
+  ASTExecutionStack(ASTExecutionStack&&)      = delete;
+};
+
+#endif   // AST_EXECUTION_STACK_HPP
diff --git a/src/language/ast/ASTModulesImporter.cpp b/src/language/ast/ASTModulesImporter.cpp
index 4154a7d8ca2bc874ba7df224c14a23838a21f493..4cae0a921c139b3ae68a3decb0ce4f83876c4701 100644
--- a/src/language/ast/ASTModulesImporter.cpp
+++ b/src/language/ast/ASTModulesImporter.cpp
@@ -1,6 +1,7 @@
 #include <language/ast/ASTModulesImporter.hpp>
 
 #include <language/PEGGrammar.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <utils/ConsoleManager.hpp>
 
@@ -27,6 +28,9 @@ ASTModulesImporter::_importModule(ASTNode& import_node)
 
   m_module_repository.populateSymbolTable(module_name_node, m_symbol_table);
   m_module_repository.registerOperators(module_name);
+  if (CheckpointResumeRepository::isCreated()) {
+    m_module_repository.registerCheckpointResume(module_name);
+  }
 }
 
 void
@@ -45,7 +49,7 @@ ASTModulesImporter::ASTModulesImporter(ASTNode& root_node) : m_symbol_table{*roo
 {
   Assert(root_node.is_root());
   OperatorRepository::instance().reset();
-  m_module_repository.populateMandatorySymbolTable(root_node, m_symbol_table);
+  m_module_repository.populateMandatoryData(root_node, m_symbol_table);
 
   this->_importAllModules(root_node);
 
diff --git a/src/language/ast/ASTNode.hpp b/src/language/ast/ASTNode.hpp
index 19b43a9272229d97bef9c96a8210506e23513a40..612b4d0c46b0790bf5406e085469a4afca53d4df 100644
--- a/src/language/ast/ASTNode.hpp
+++ b/src/language/ast/ASTNode.hpp
@@ -1,7 +1,7 @@
 #ifndef AST_NODE_HPP
 #define AST_NODE_HPP
 
-#include <language/ast/ASTBacktrace.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/node_processor/ExecutionPolicy.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
 #include <language/utils/ASTNodeDataType.hpp>
@@ -110,19 +110,19 @@ class ASTNode : public TAO_PEGTL_NAMESPACE::parse_tree::basic_node<ASTNode>
     if (exec_policy.exec()) {
       if (m_stack_details) {
         if (m_node_processor->type() == INodeProcessor::Type::builtin_function_processor) {
-          ASTBacktrace::getInstance().push(this);
+          ASTExecutionStack::getInstance().push(this);
 
           m_stack_details = false;
           auto result     = m_node_processor->execute(exec_policy);
           m_stack_details = true;
 
-          ASTBacktrace::getInstance().pop();
+          ASTExecutionStack::getInstance().pop();
 
           return result;
         } else {
-          ASTBacktrace::getInstance().push(this);
+          ASTExecutionStack::getInstance().push(this);
           auto result = m_node_processor->execute(exec_policy);
-          ASTBacktrace::getInstance().pop();
+          ASTExecutionStack::getInstance().pop();
 
           return result;
         }
diff --git a/src/language/ast/CMakeLists.txt b/src/language/ast/CMakeLists.txt
index 63e208c2b153b7f05318ee15066f5da0f7e2cb8c..1485606587abee39eee2a643df38f79820bace39 100644
--- a/src/language/ast/CMakeLists.txt
+++ b/src/language/ast/CMakeLists.txt
@@ -1,8 +1,8 @@
 # ------------------- Source files --------------------
 
 add_library(PugsLanguageAST
-  ASTBacktrace.cpp
   ASTBuilder.cpp
+  ASTExecutionStack.cpp
   ASTModulesImporter.cpp
   ASTNode.cpp
   ASTNodeAffectationExpressionBuilder.cpp
@@ -23,7 +23,12 @@ add_library(PugsLanguageAST
   ASTNodeUnaryOperatorExpressionBuilder.cpp
   ASTSymbolInitializationChecker.cpp
   ASTSymbolTableBuilder.cpp
-  )
+)
+
+target_link_libraries(
+  PugsLanguageAST
+  ${HIGHFIVE_TARGET}
+)
 
 
 add_dependencies(PugsLanguageAST
diff --git a/src/language/modules/CMakeLists.txt b/src/language/modules/CMakeLists.txt
index 5f87dcf34f5b8652f976a2f88d851eb381d8385b..1ecbe1db54c85a4282ed1a55a043115f09bc7525 100644
--- a/src/language/modules/CMakeLists.txt
+++ b/src/language/modules/CMakeLists.txt
@@ -23,6 +23,7 @@ target_link_libraries(
 )
 
 add_dependencies(
+  PugsCheckpointing
   PugsLanguageModules
   PugsLanguageAlgorithms
   PugsUtils
diff --git a/src/language/modules/CoreModule.cpp b/src/language/modules/CoreModule.cpp
index e9be93f3c1cc96af47d7665317d5a364cd54f17e..d7b331a1dc606a83cf77dfe405b184b9ff143308 100644
--- a/src/language/modules/CoreModule.cpp
+++ b/src/language/modules/CoreModule.cpp
@@ -19,6 +19,7 @@
 #include <language/utils/BinaryOperatorRegisterForString.hpp>
 #include <language/utils/BinaryOperatorRegisterForZ.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/Exit.hpp>
 #include <language/utils/IncDecOperatorRegisterForN.hpp>
 #include <language/utils/IncDecOperatorRegisterForZ.hpp>
@@ -33,6 +34,13 @@
 #include <utils/Messenger.hpp>
 #include <utils/PugsUtils.hpp>
 #include <utils/RandomEngine.hpp>
+#include <utils/Stop.hpp>
+
+#include <utils/checkpointing/Checkpoint.hpp>
+#include <utils/checkpointing/ReadOStream.hpp>
+#include <utils/checkpointing/Resume.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/checkpointing/WriteOStream.hpp>
 
 #include <random>
 
@@ -54,7 +62,7 @@ CoreModule::CoreModule() : BuiltinModule(true)
 
                                                      []() -> std::string {
                                                        const ModuleRepository& repository =
-                                                         ASTExecutionInfo::current().moduleRepository();
+                                                         ASTExecutionInfo::getInstance().moduleRepository();
 
                                                        return repository.getAvailableModules();
                                                      }
@@ -65,7 +73,7 @@ CoreModule::CoreModule() : BuiltinModule(true)
 
                                                [](const std::string& module_name) -> std::string {
                                                  const ModuleRepository& repository =
-                                                   ASTExecutionInfo::current().moduleRepository();
+                                                   ASTExecutionInfo::getInstance().moduleRepository();
 
                                                  return repository.getModuleInfo(module_name);
                                                }
@@ -99,7 +107,7 @@ CoreModule::CoreModule() : BuiltinModule(true)
   this->_addBuiltinFunction("exit", std::function(
 
                                       [](const int64_t& exit_code) -> void {
-                                        const auto& location = ASTBacktrace::getInstance().sourceLocation();
+                                        const auto& location = ASTExecutionStack::getInstance().sourceLocation();
                                         std::cout << "\n** " << rang::fgB::yellow << "exit" << rang::fg::reset
                                                   << " explicitly called with code " << rang::fgB::cyan << exit_code
                                                   << rang::fg::reset << "\n   from " << rang::style::underline
@@ -111,6 +119,35 @@ CoreModule::CoreModule() : BuiltinModule(true)
 
                                       ));
 
+  this->_addBuiltinFunction("checkpoint", std::function(
+
+                                            []() -> void {
+                                              if (ResumingManager::getInstance().isResuming()) {
+                                                resume();
+                                                ResumingManager::getInstance().setIsResuming(false);
+                                              } else {
+                                                checkpoint();
+                                              }
+                                            }
+
+                                            ));
+
+  this->_addBuiltinFunction("checkpoint_and_exit", std::function(
+
+                                                     []() -> void {
+                                                       if (ResumingManager::getInstance().isResuming()) {
+                                                         resume();
+                                                         ResumingManager::getInstance().setIsResuming(false);
+                                                       } else {
+                                                         checkpoint();
+                                                         throw language::Exit(0);
+                                                       }
+                                                     }
+
+                                                     ));
+
+  this->_addBuiltinFunction("stop", std::function([]() -> bool { return stop(); }));
+
   this->_addNameValue("cout", ast_node_data_type_from<std::shared_ptr<const OStream>>,
                       EmbeddedData{std::make_shared<DataHandler<const OStream>>(std::make_shared<OStream>(std::cout))});
 
@@ -162,3 +199,23 @@ CoreModule::registerOperators() const
   UnaryOperatorRegisterForRnxn<2>{};
   UnaryOperatorRegisterForRnxn<3>{};
 }
+
+void
+CoreModule::registerCheckpointResume() const
+{
+#ifdef PUGS_HAS_HDF5
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const OStream>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeOStream(symbol_name, embedded_data, file, checkpoint_group,
+                                                       symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readOStream(symbol_name, symbol_table_group);
+                         }));
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/src/language/modules/CoreModule.hpp b/src/language/modules/CoreModule.hpp
index 88c673d65a7d9635aba8698c678dda7bd2081fb1..26059267d445c742894522ff5d0c2101bb15a1c6 100644
--- a/src/language/modules/CoreModule.hpp
+++ b/src/language/modules/CoreModule.hpp
@@ -13,6 +13,7 @@ class CoreModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   CoreModule();
   ~CoreModule() = default;
diff --git a/src/language/modules/DevUtilsModule.cpp b/src/language/modules/DevUtilsModule.cpp
index 7685443b081fb2c8f71e65c8eed8aae16273100b..361b4357eda0ebbfe4350cc6c64bbe72cfaa0829 100644
--- a/src/language/modules/DevUtilsModule.cpp
+++ b/src/language/modules/DevUtilsModule.cpp
@@ -1,6 +1,8 @@
 #include <language/modules/DevUtilsModule.hpp>
 
 #include <dev/ParallelChecker.hpp>
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/ASTDotPrinter.hpp>
 #include <language/utils/ASTExecutionInfo.hpp>
 #include <language/utils/ASTPrinter.hpp>
@@ -9,37 +11,12 @@
 
 #include <fstream>
 
-class DiscreteFunctionVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const DiscreteFunctionVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh");
-
-class ItemValueVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemValueVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_value");
-
-class ItemArrayVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemArrayVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_array");
-
-class SubItemValuePerItemVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemValuePerItemVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_value");
-
-class SubItemArrayPerItemVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemArrayPerItemVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_array");
-
 DevUtilsModule::DevUtilsModule()
 {
   this->_addBuiltinFunction("getAST", std::function(
 
                                         []() -> std::string {
-                                          const auto& root_node = ASTExecutionInfo::current().rootNode();
+                                          const auto& root_node = ASTExecutionInfo::getInstance().rootNode();
 
                                           std::ostringstream os;
                                           os << ASTPrinter{root_node};
@@ -52,7 +29,7 @@ DevUtilsModule::DevUtilsModule()
   this->_addBuiltinFunction("saveASTDot", std::function(
 
                                             [](const std::string& dot_filename) -> void {
-                                              const auto& root_node = ASTExecutionInfo::current().rootNode();
+                                              const auto& root_node = ASTExecutionInfo::getInstance().rootNode();
 
                                               std::ofstream fout(dot_filename);
 
@@ -95,7 +72,8 @@ DevUtilsModule::DevUtilsModule()
 
                               [](const std::shared_ptr<const DiscreteFunctionVariant>& discrete_function,
                                  const std::string& name) {
-                                parallel_check(*discrete_function, name, ASTBacktrace::getInstance().sourceLocation());
+                                parallel_check(*discrete_function, name,
+                                               ASTExecutionStack::getInstance().sourceLocation());
                               }
 
                               ));
@@ -104,7 +82,7 @@ DevUtilsModule::DevUtilsModule()
                             std::function(
 
                               [](const std::shared_ptr<const ItemValueVariant>& item_value, const std::string& name) {
-                                parallel_check(*item_value, name, ASTBacktrace::getInstance().sourceLocation());
+                                parallel_check(*item_value, name, ASTExecutionStack::getInstance().sourceLocation());
                               }
 
                               ));
@@ -113,7 +91,7 @@ DevUtilsModule::DevUtilsModule()
                             std::function(
 
                               [](const std::shared_ptr<const ItemArrayVariant>& item_array, const std::string& name) {
-                                parallel_check(*item_array, name, ASTBacktrace::getInstance().sourceLocation());
+                                parallel_check(*item_array, name, ASTExecutionStack::getInstance().sourceLocation());
                               }
 
                               ));
@@ -124,7 +102,7 @@ DevUtilsModule::DevUtilsModule()
                               [](const std::shared_ptr<const SubItemValuePerItemVariant>& subitem_value_per_item,
                                  const std::string& name) {
                                 parallel_check(*subitem_value_per_item, name,
-                                               ASTBacktrace::getInstance().sourceLocation());
+                                               ASTExecutionStack::getInstance().sourceLocation());
                               }
 
                               ));
@@ -135,7 +113,7 @@ DevUtilsModule::DevUtilsModule()
                               [](const std::shared_ptr<const SubItemArrayPerItemVariant>& subitem_array_per_item,
                                  const std::string& name) {
                                 parallel_check(*subitem_array_per_item, name,
-                                               ASTBacktrace::getInstance().sourceLocation());
+                                               ASTExecutionStack::getInstance().sourceLocation());
                               }
 
                               ));
@@ -144,3 +122,7 @@ DevUtilsModule::DevUtilsModule()
 void
 DevUtilsModule::registerOperators() const
 {}
+
+void
+DevUtilsModule::registerCheckpointResume() const
+{}
diff --git a/src/language/modules/DevUtilsModule.hpp b/src/language/modules/DevUtilsModule.hpp
index 96392a8dca7b4ddf1c158d19e12276fd6842b835..1f9eb7ec4d39aa92452998117637dedf98cb38a4 100644
--- a/src/language/modules/DevUtilsModule.hpp
+++ b/src/language/modules/DevUtilsModule.hpp
@@ -13,6 +13,7 @@ class DevUtilsModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   DevUtilsModule();
   ~DevUtilsModule() = default;
diff --git a/src/language/modules/IModule.hpp b/src/language/modules/IModule.hpp
index 04aefbe91b57ab1925f55bae62ad233c1ea135cf..98fa56c117196931cd86ea49efb9c91236fe1967 100644
--- a/src/language/modules/IModule.hpp
+++ b/src/language/modules/IModule.hpp
@@ -19,8 +19,8 @@ class IModule
   using NameTypeMap            = std::unordered_map<std::string, std::shared_ptr<TypeDescriptor>>;
   using NameValueMap           = std::unordered_map<std::string, std::shared_ptr<ValueDescriptor>>;
 
-  IModule()          = default;
-  IModule(IModule&&) = default;
+  IModule()                     = default;
+  IModule(IModule&&)            = default;
   IModule& operator=(IModule&&) = default;
 
   virtual bool isMandatory() const = 0;
@@ -31,7 +31,8 @@ class IModule
 
   virtual const NameValueMap& getNameValueMap() const = 0;
 
-  virtual void registerOperators() const = 0;
+  virtual void registerOperators() const        = 0;
+  virtual void registerCheckpointResume() const = 0;
 
   virtual std::string_view name() const = 0;
 
diff --git a/src/language/modules/LinearSolverModule.cpp b/src/language/modules/LinearSolverModule.cpp
index 123a930073dfb0735dd30df1893613b5cb30a292..aef6470d2455909f5ad2d307aa4e5cb859b8bcbb 100644
--- a/src/language/modules/LinearSolverModule.cpp
+++ b/src/language/modules/LinearSolverModule.cpp
@@ -94,3 +94,7 @@ LinearSolverModule::LinearSolverModule()
 void
 LinearSolverModule::registerOperators() const
 {}
+
+void
+LinearSolverModule::registerCheckpointResume() const
+{}
diff --git a/src/language/modules/LinearSolverModule.hpp b/src/language/modules/LinearSolverModule.hpp
index 7e30c6b2f7c12cf4ede853578e6c064c7dce6292..78bbf46817fbdf4be72702be12312e1fb443504e 100644
--- a/src/language/modules/LinearSolverModule.hpp
+++ b/src/language/modules/LinearSolverModule.hpp
@@ -13,6 +13,7 @@ class LinearSolverModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   LinearSolverModule();
   ~LinearSolverModule() = default;
diff --git a/src/language/modules/MathModule.cpp b/src/language/modules/MathModule.cpp
index 9d0a44a7cf11448c0a0124e378e089697cc1406d..19a35a1529b1b65310c3c51d69fe4c5d02dc4d8a 100644
--- a/src/language/modules/MathModule.cpp
+++ b/src/language/modules/MathModule.cpp
@@ -132,3 +132,7 @@ MathModule::MathModule()
 void
 MathModule::registerOperators() const
 {}
+
+void
+MathModule::registerCheckpointResume() const
+{}
diff --git a/src/language/modules/MathModule.hpp b/src/language/modules/MathModule.hpp
index c80a74d2da37807750f25a286b9ccd9d12928850..9902f4c7ab8cf2654df062e541344b850fa53d15 100644
--- a/src/language/modules/MathModule.hpp
+++ b/src/language/modules/MathModule.hpp
@@ -13,6 +13,7 @@ class MathModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   MathModule();
 
diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index 548f42ddac09caded1533a96c572e9722bf6a63b..c76260ce58c75c130e4638054fc394458fedbf63 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -4,6 +4,7 @@
 #include <language/node_processor/ExecutionPolicy.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/FunctionTable.hpp>
 #include <language/utils/ItemArrayVariantFunctionInterpoler.hpp>
 #include <language/utils/ItemValueVariantFunctionInterpoler.hpp>
@@ -35,7 +36,24 @@
 #include <mesh/SubItemValuePerItemVariant.hpp>
 #include <utils/Exceptions.hpp>
 
-#include <Kokkos_Core.hpp>
+#include <utils/checkpointing/ReadIBoundaryDescriptor.hpp>
+#include <utils/checkpointing/ReadIInterfaceDescriptor.hpp>
+#include <utils/checkpointing/ReadIZoneDescriptor.hpp>
+#include <utils/checkpointing/ReadItemArrayVariant.hpp>
+#include <utils/checkpointing/ReadItemType.hpp>
+#include <utils/checkpointing/ReadItemValueVariant.hpp>
+#include <utils/checkpointing/ReadMesh.hpp>
+#include <utils/checkpointing/ReadSubItemArrayPerItemVariant.hpp>
+#include <utils/checkpointing/ReadSubItemValuePerItemVariant.hpp>
+#include <utils/checkpointing/WriteIBoundaryDescriptor.hpp>
+#include <utils/checkpointing/WriteIInterfaceDescriptor.hpp>
+#include <utils/checkpointing/WriteIZoneDescriptor.hpp>
+#include <utils/checkpointing/WriteItemArrayVariant.hpp>
+#include <utils/checkpointing/WriteItemType.hpp>
+#include <utils/checkpointing/WriteItemValueVariant.hpp>
+#include <utils/checkpointing/WriteMesh.hpp>
+#include <utils/checkpointing/WriteSubItemArrayPerItemVariant.hpp>
+#include <utils/checkpointing/WriteSubItemValuePerItemVariant.hpp>
 
 MeshModule::MeshModule()
 {
@@ -300,3 +318,127 @@ MeshModule::registerOperators() const
       BinaryOperatorProcessorBuilder<language::shift_left_op, std::shared_ptr<const OStream>,
                                      std::shared_ptr<const OStream>, std::shared_ptr<const MeshVariant>>>());
 }
+
+void
+MeshModule::registerCheckpointResume() const
+{
+#ifdef PUGS_HAS_HDF5
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const MeshVariant>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeMesh(symbol_name, embedded_data, file, checkpoint_group,
+                                                    symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readMesh(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIBoundaryDescriptor(symbol_name, embedded_data, file, checkpoint_group,
+                                                                   symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIBoundaryDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IInterfaceDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIInterfaceDescriptor(symbol_name, embedded_data, file, checkpoint_group,
+                                                                    symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIInterfaceDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIZoneDescriptor(symbol_name, embedded_data, file, checkpoint_group,
+                                                               symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIZoneDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const ItemType>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeItemType(symbol_name, embedded_data, file, checkpoint_group,
+                                                        symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readItemType(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const ItemArrayVariant>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeItemArrayVariant(symbol_name, embedded_data, file, checkpoint_group,
+                                                                symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readItemArrayVariant(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const ItemValueVariant>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeItemValueVariant(symbol_name, embedded_data, file, checkpoint_group,
+                                                                symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readItemValueVariant(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const SubItemArrayPerItemVariant>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeSubItemArrayPerItemVariant(symbol_name, embedded_data, file,
+                                                                          checkpoint_group, symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readSubItemArrayPerItemVariant(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const SubItemValuePerItemVariant>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeSubItemValuePerItemVariant(symbol_name, embedded_data, file,
+                                                                          checkpoint_group, symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readSubItemValuePerItemVariant(symbol_name, symbol_table_group);
+                         }));
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/src/language/modules/MeshModule.hpp b/src/language/modules/MeshModule.hpp
index 10bfc9e1cae386702386508d6861e97ba7857857..f9c4ca3de4a520354109d5e64e2364a4b7784c91 100644
--- a/src/language/modules/MeshModule.hpp
+++ b/src/language/modules/MeshModule.hpp
@@ -2,52 +2,7 @@
 #define MESH_MODULE_HPP
 
 #include <language/modules/BuiltinModule.hpp>
-#include <language/utils/ASTNodeDataTypeTraits.hpp>
-#include <utils/PugsMacros.hpp>
-
-class MeshVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const MeshVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("mesh");
-
-class IBoundaryDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary");
-
-class IInterfaceDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IInterfaceDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("interface");
-
-class IZoneDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("zone");
-
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemType>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_type");
-
-class ItemValueVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemValueVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_value");
-
-class ItemArrayVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemArrayVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_array");
-
-class SubItemValuePerItemVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemValuePerItemVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_value");
-
-class SubItemArrayPerItemVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemArrayPerItemVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_array");
+#include <language/modules/MeshModuleTypes.hpp>
 
 class MeshModule : public BuiltinModule
 {
@@ -59,6 +14,7 @@ class MeshModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   MeshModule();
 
diff --git a/src/language/modules/MeshModuleTypes.hpp b/src/language/modules/MeshModuleTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cfd26a817e872cda2e421adaf25262d982ce0489
--- /dev/null
+++ b/src/language/modules/MeshModuleTypes.hpp
@@ -0,0 +1,50 @@
+#ifndef MESH_MODULE_TYPES_HPP
+#define MESH_MODULE_TYPES_HPP
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+
+class MeshVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const MeshVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("mesh");
+
+class IBoundaryDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary");
+
+class IInterfaceDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IInterfaceDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("interface");
+
+class IZoneDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IZoneDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("zone");
+
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemType>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_type");
+
+class ItemValueVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemValueVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_value");
+
+class ItemArrayVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const ItemArrayVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("item_array");
+
+class SubItemValuePerItemVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemValuePerItemVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_value");
+
+class SubItemArrayPerItemVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const SubItemArrayPerItemVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("sub_item_array");
+
+#endif   // MESH_MODULE_TYPES_HPP
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 5bdad8bceff609288e318cce8b3f37304afc69b5..59689fb6fee512e45cd76c7c9d3cd5c11d543ee8 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -11,6 +11,7 @@
 #include <language/modules/WriterModule.hpp>
 #include <language/utils/BasicAffectationRegistrerFor.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/ParseError.hpp>
 #include <language/utils/SymbolTable.hpp>
 #include <language/utils/TypeDescriptor.hpp>
@@ -108,6 +109,7 @@ ModuleRepository::_populateSymbolTable(const ASTNode& module_node,
 
     i_symbol->attributes().setDataType(value_descriptor->type());
     i_symbol->attributes().setIsInitialized();
+    i_symbol->attributes().setIsModuleVariable();
     i_symbol->attributes().value() = value_descriptor->value();
   }
 }
@@ -149,7 +151,7 @@ ModuleRepository::populateSymbolTable(const ASTNode& module_name_node, SymbolTab
 }
 
 void
-ModuleRepository::populateMandatorySymbolTable(const ASTNode& root_node, SymbolTable& symbol_table)
+ModuleRepository::populateMandatoryData(const ASTNode& root_node, SymbolTable& symbol_table)
 {
   for (auto&& [module_name, i_module] : m_module_set) {
     if (i_module->isMandatory()) {
@@ -163,6 +165,10 @@ ModuleRepository::populateMandatorySymbolTable(const ASTNode& root_node, SymbolT
 
       this->_populateSymbolTable(root_node, module_name, i_module->getNameValueMap(), symbol_table);
 
+      if (CheckpointResumeRepository::isCreated()) {
+        i_module->registerCheckpointResume();
+      }
+
       for (const auto& [symbol_name, embedded] : i_module->getNameTypeMap()) {
         BasicAffectationRegisterFor<EmbeddedData>(ASTNodeDataType::build<ASTNodeDataType::type_id_t>(symbol_name));
       }
@@ -202,6 +208,17 @@ ModuleRepository::registerOperators(const std::string& module_name)
   }
 }
 
+void
+ModuleRepository::registerCheckpointResume(const std::string& module_name)
+{
+  auto i_module = m_module_set.find(module_name);
+  if (i_module != m_module_set.end()) {
+    i_module->second->registerCheckpointResume();
+  } else {
+    throw NormalError(std::string{"could not find module "} + module_name);
+  }
+}
+
 std::string
 ModuleRepository::getModuleInfo(const std::string& module_name) const
 {
diff --git a/src/language/modules/ModuleRepository.hpp b/src/language/modules/ModuleRepository.hpp
index c7289c8d1c4b9c7f795925bfdbf65e3f526082e7..407d7b55bd9b7c8454159a315b9c2f7a256eecb6 100644
--- a/src/language/modules/ModuleRepository.hpp
+++ b/src/language/modules/ModuleRepository.hpp
@@ -33,14 +33,15 @@ class ModuleRepository
 
  public:
   void populateSymbolTable(const ASTNode& module_name_node, SymbolTable& symbol_table);
-  void populateMandatorySymbolTable(const ASTNode& root_node, SymbolTable& symbol_table);
+  void populateMandatoryData(const ASTNode& root_node, SymbolTable& symbol_table);
   void registerOperators(const std::string& module_name);
+  void registerCheckpointResume(const std::string& module_name);
 
   std::string getAvailableModules() const;
   std::string getModuleInfo(const std::string& module_name) const;
 
   const ModuleRepository& operator=(const ModuleRepository&) = delete;
-  const ModuleRepository& operator=(ModuleRepository&&) = delete;
+  const ModuleRepository& operator=(ModuleRepository&&)      = delete;
 
   ModuleRepository(const ModuleRepository&) = delete;
   ModuleRepository(ModuleRepository&&)      = delete;
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index 5ad47feff49b54cb5f90d2d3d9ddd68641204fbe..f1a7d8d73606a6cd71e4aa6b4f7253f8773a804e 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -5,9 +5,12 @@
 #include <analysis/GaussQuadratureDescriptor.hpp>
 #include <language/modules/BinaryOperatorRegisterForVh.hpp>
 #include <language/modules/MathFunctionRegisterForVh.hpp>
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/modules/SocketModuleTypes.hpp>
 #include <language/modules/UnaryOperatorRegisterForVh.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/IBoundaryDescriptor.hpp>
@@ -39,12 +42,25 @@
 #include <scheme/IBoundaryConditionDescriptor.hpp>
 #include <scheme/IDiscreteFunctionDescriptor.hpp>
 #include <scheme/InflowBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowListBoundaryConditionDescriptor.hpp>
 #include <scheme/NeumannBoundaryConditionDescriptor.hpp>
 #include <scheme/OutflowBoundaryConditionDescriptor.hpp>
 #include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
 #include <scheme/VariableBCDescriptor.hpp>
+#include <scheme/WallBoundaryConditionDescriptor.hpp>
 #include <utils/Socket.hpp>
 
+#include <utils/checkpointing/ReadDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp>
+#include <utils/checkpointing/ReadIDiscreteFunctionDescriptor.hpp>
+#include <utils/checkpointing/ReadIQuadratureDescriptor.hpp>
+#include <utils/checkpointing/ReadVariableBCDescriptor.hpp>
+#include <utils/checkpointing/WriteDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp>
+#include <utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp>
+#include <utils/checkpointing/WriteIQuadratureDescriptor.hpp>
+#include <utils/checkpointing/WriteVariableBCDescriptor.hpp>
+
 #include <memory>
 
 SchemeModule::SchemeModule()
@@ -334,6 +350,15 @@ SchemeModule::SchemeModule()
 
                                           ));
 
+  this->_addBuiltinFunction("wall", std::function(
+
+                                      [](std::shared_ptr<const IBoundaryDescriptor> boundary)
+                                        -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                        return std::make_shared<WallBoundaryConditionDescriptor>(boundary);
+                                      }
+
+                                      ));
+
   this->_addBuiltinFunction("pressure", std::function(
 
                                           [](std::shared_ptr<const IBoundaryDescriptor> boundary,
@@ -393,6 +418,18 @@ SchemeModule::SchemeModule()
 
                                         ));
 
+  this->_addBuiltinFunction("inflow_list",
+                            std::function(
+
+                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
+                                 const std::vector<FunctionSymbolId>& function_id_list)
+                                -> std::shared_ptr<const IBoundaryConditionDescriptor> {
+                                return std::make_shared<InflowListBoundaryConditionDescriptor>(boundary,
+                                                                                               function_id_list);
+                              }
+
+                              ));
+
   this->_addBuiltinFunction("outflow", std::function(
 
                                          [](std::shared_ptr<const IBoundaryDescriptor> boundary)
@@ -700,3 +737,75 @@ SchemeModule::registerOperators() const
   BinaryOperatorRegisterForVh{};
   UnaryOperatorRegisterForVh{};
 }
+
+void
+SchemeModule::registerCheckpointResume() const
+{
+#ifdef PUGS_HAS_HDF5
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const DiscreteFunctionVariant>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeDiscreteFunctionVariant(symbol_name, embedded_data, file,
+                                                                       checkpoint_group, symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readDiscreteFunctionVariant(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIBoundaryConditionDescriptor(symbol_name, embedded_data, file,
+                                                                            checkpoint_group, symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIBoundaryConditionDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIDiscreteFunctionDescriptor(symbol_name, embedded_data, file,
+                                                                           checkpoint_group, symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIDiscreteFunctionDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIQuadratureDescriptor(symbol_name, embedded_data, file, checkpoint_group,
+                                                                     symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIQuadratureDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const VariableBCDescriptor>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeVariableBCDescriptor(symbol_name, embedded_data, file, checkpoint_group,
+                                                                    symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readVariableBCDescriptor(symbol_name, symbol_table_group);
+                         }));
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index 68cffcf18dc9438cfa5607bdf70739bf223a175d..c61b3084406dbd8ae9889c79b4172195daaa6fe8 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -2,33 +2,7 @@
 #define SCHEME_MODULE_HPP
 
 #include <language/modules/BuiltinModule.hpp>
-#include <language/utils/ASTNodeDataTypeTraits.hpp>
-#include <utils/PugsMacros.hpp>
-
-class IBoundaryConditionDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary_condition");
-
-class VariableBCDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const VariableBCDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("variable_boundary_condition");
-
-class DiscreteFunctionVariant;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const DiscreteFunctionVariant>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh");
-
-class IDiscreteFunctionDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh_type");
-
-class IQuadratureDescriptor;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("quadrature");
+#include <language/modules/SchemeModuleTypes.hpp>
 
 class SchemeModule : public BuiltinModule
 {
@@ -42,6 +16,7 @@ class SchemeModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   SchemeModule();
 
diff --git a/src/language/modules/SchemeModuleTypes.hpp b/src/language/modules/SchemeModuleTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..027dbd4419efed294ced2998573b546f86ae9f93
--- /dev/null
+++ b/src/language/modules/SchemeModuleTypes.hpp
@@ -0,0 +1,31 @@
+#ifndef SCHEME_MODULE_TYPES_HPP
+#define SCHEME_MODULE_TYPES_HPP
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+
+class IBoundaryConditionDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("boundary_condition");
+
+class VariableBCDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const VariableBCDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("variable_boundary_condition");
+
+class DiscreteFunctionVariant;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const DiscreteFunctionVariant>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh");
+
+class IDiscreteFunctionDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("Vh_type");
+
+class IQuadratureDescriptor;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IQuadratureDescriptor>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("quadrature");
+
+#endif   // SCHEME_MODULE_TYPES_HPP
diff --git a/src/language/modules/SocketModule.cpp b/src/language/modules/SocketModule.cpp
index 426a561372289256182d0a5d981eaeaf00de2e08..79321d088f960e0bb7450c82820cf6568e79d174 100644
--- a/src/language/modules/SocketModule.cpp
+++ b/src/language/modules/SocketModule.cpp
@@ -2,6 +2,7 @@
 
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/OStream.hpp>
 #include <language/utils/OperatorRepository.hpp>
 #include <utils/Socket.hpp>
@@ -257,3 +258,16 @@ SocketModule::registerOperators() const
     std::make_shared<BinaryOperatorProcessorBuilder<language::shift_left_op, std::shared_ptr<const OStream>,
                                                     std::shared_ptr<const OStream>, std::shared_ptr<const Socket>>>());
 }
+
+void
+SocketModule::registerCheckpointResume() const
+{
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const Socket>>,
+                         std::function(
+                           [](const std::string&, const EmbeddedData&, HighFive::File&, HighFive::Group&,
+                              HighFive::Group&) { throw NotImplementedError("checkpoint/resume with sockets"); }),
+                         std::function([](const std::string&, const HighFive::Group&) -> EmbeddedData {
+                           throw NotImplementedError("checkpoint/resume with sockets");
+                         }));
+}
diff --git a/src/language/modules/SocketModule.hpp b/src/language/modules/SocketModule.hpp
index 0b32b64fafc72829e6598cfa78a95ce14f61ed9b..dc14c4b273d3e21c62c6cb42078ad4d2d4212480 100644
--- a/src/language/modules/SocketModule.hpp
+++ b/src/language/modules/SocketModule.hpp
@@ -2,13 +2,7 @@
 #define SOCKET_MODULE_HPP
 
 #include <language/modules/BuiltinModule.hpp>
-#include <language/utils/ASTNodeDataTypeTraits.hpp>
-
-class Socket;
-
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const Socket>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("socket");
+#include <language/modules/SocketModuleTypes.hpp>
 
 class SocketModule : public BuiltinModule
 {
@@ -20,6 +14,7 @@ class SocketModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   SocketModule();
   ~SocketModule() = default;
diff --git a/src/language/modules/SocketModuleTypes.hpp b/src/language/modules/SocketModuleTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b12370feb7af814e2ee725dd84aca40568a72c72
--- /dev/null
+++ b/src/language/modules/SocketModuleTypes.hpp
@@ -0,0 +1,11 @@
+#ifndef SOCKET_MODULE_TYPES_HPP
+#define SOCKET_MODULE_TYPES_HPP
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+
+class Socket;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const Socket>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("socket");
+
+#endif   // SOCKET_MODULE_TYPES_HPP
diff --git a/src/language/modules/WriterModule.cpp b/src/language/modules/WriterModule.cpp
index 4db29e203492c098a4807665ee50eb68b3be8287..6d84506501de277f1970c13a7c24cd587ed79f20 100644
--- a/src/language/modules/WriterModule.cpp
+++ b/src/language/modules/WriterModule.cpp
@@ -1,6 +1,9 @@
 #include <language/modules/WriterModule.hpp>
 
+#include <language/modules/MeshModule.hpp>
+#include <language/modules/SchemeModule.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/TypeDescriptor.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/GmshReader.hpp>
@@ -17,6 +20,11 @@
 #include <output/VTKWriter.hpp>
 #include <scheme/DiscreteFunctionVariant.hpp>
 
+#include <utils/checkpointing/ReadINamedDiscreteData.hpp>
+#include <utils/checkpointing/ReadIWriter.hpp>
+#include <utils/checkpointing/WriteINamedDiscreteData.hpp>
+#include <utils/checkpointing/WriteIWriter.hpp>
+
 WriterModule::WriterModule()
 {
   this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const INamedDiscreteData>>);
@@ -200,3 +208,37 @@ WriterModule::WriterModule()
 void
 WriterModule::registerOperators() const
 {}
+
+void
+WriterModule::registerCheckpointResume() const
+{
+#ifdef PUGS_HAS_HDF5
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const INamedDiscreteData>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeINamedDiscreteData(symbol_name, embedded_data, file, checkpoint_group,
+                                                                  symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readINamedDiscreteData(symbol_name, symbol_table_group);
+                         }));
+
+  CheckpointResumeRepository::instance()
+    .addCheckpointResume(ast_node_data_type_from<std::shared_ptr<const IWriter>>,
+                         std::function([](const std::string& symbol_name, const EmbeddedData& embedded_data,
+                                          HighFive::File& file, HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group) {
+                           checkpointing::writeIWriter(symbol_name, embedded_data, file, checkpoint_group,
+                                                       symbol_table_group);
+                         }),
+                         std::function([](const std::string& symbol_name,
+                                          const HighFive::Group& symbol_table_group) -> EmbeddedData {
+                           return checkpointing::readIWriter(symbol_name, symbol_table_group);
+                         }));
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/src/language/modules/WriterModule.hpp b/src/language/modules/WriterModule.hpp
index 97bef5a78ac6f90cff1af303bed901c4c46c665a..7abdba663361ce627db6a406108f3accd4689000 100644
--- a/src/language/modules/WriterModule.hpp
+++ b/src/language/modules/WriterModule.hpp
@@ -2,22 +2,7 @@
 #define WRITER_MODULE_HPP
 
 #include <language/modules/BuiltinModule.hpp>
-#include <language/utils/ASTNodeDataTypeTraits.hpp>
-#include <utils/PugsMacros.hpp>
-
-class OutputNamedItemValueSet;
-class INamedDiscreteData;
-
-#include <string>
-
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const INamedDiscreteData>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("output");
-
-class IWriter;
-template <>
-inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IWriter>> =
-  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("writer");
+#include <language/modules/WriterModuleTypes.hpp>
 
 class WriterModule : public BuiltinModule
 {
@@ -29,6 +14,7 @@ class WriterModule : public BuiltinModule
   }
 
   void registerOperators() const final;
+  void registerCheckpointResume() const final;
 
   WriterModule();
 
diff --git a/src/language/modules/WriterModuleTypes.hpp b/src/language/modules/WriterModuleTypes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..428dbbaa2bc73025b2b8d6ea440a58ef8eb884f9
--- /dev/null
+++ b/src/language/modules/WriterModuleTypes.hpp
@@ -0,0 +1,18 @@
+#ifndef WRITER_MODULE_TYPES_HPP
+#define WRITER_MODULE_TYPES_HPP
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+
+class OutputNamedItemValueSet;
+class INamedDiscreteData;
+
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const INamedDiscreteData>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("output");
+
+class IWriter;
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const IWriter>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("writer");
+
+#endif   // WRITER_MODULE_TYPES_HPP
diff --git a/src/language/node_processor/ASTNodeListProcessor.hpp b/src/language/node_processor/ASTNodeListProcessor.hpp
index 142d33f7d7ce085b57c3a3746f9f559c87e4408e..4e34dcbe90a64d5cbc6106bee97172deec97abe8 100644
--- a/src/language/node_processor/ASTNodeListProcessor.hpp
+++ b/src/language/node_processor/ASTNodeListProcessor.hpp
@@ -4,7 +4,11 @@
 #include <language/PEGGrammar.hpp>
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
 #include <language/utils/SymbolTable.hpp>
+#include <utils/checkpointing/Checkpoint.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/pugs_config.hpp>
 
 class ASTNodeListProcessor final : public INodeProcessor
 {
@@ -21,8 +25,23 @@ class ASTNodeListProcessor final : public INodeProcessor
   DataVariant
   execute(ExecutionPolicy& exec_policy)
   {
-    for (auto& child : m_node.children) {
-      child->execute(exec_policy);
+    ResumingManager& resuming_manager = ResumingManager::getInstance();
+    if (resuming_manager.isResuming()) {
+#ifdef PUGS_HAS_HDF5
+      const size_t checkpoint_id = resuming_manager.checkpointId();
+
+      const ASTCheckpointsInfo& ast_checkpoint_info = ASTCheckpointsInfo::getInstance();
+      const ASTCheckpoint& ast_checkpoint           = ast_checkpoint_info.getASTCheckpoint(checkpoint_id);
+
+      for (size_t i_child = ast_checkpoint.getASTLocation()[resuming_manager.currentASTLevel()++];
+           i_child < m_node.children.size(); ++i_child) {
+        m_node.children[i_child]->execute(exec_policy);
+      }
+#endif   // PUGS_HAS_HDF5
+    } else {
+      for (auto&& child : m_node.children) {
+        child->execute(exec_policy);
+      }
     }
 
     if (not(m_node.is_root() or m_node.is_type<language::for_statement_block>()))
diff --git a/src/language/node_processor/BinaryExpressionProcessor.hpp b/src/language/node_processor/BinaryExpressionProcessor.hpp
index d91f39d905c1f055ed7d95d6a15ff7f258fc2385..5688f86db6c5cfb34bf3b5f2eb10818b1b56f4a9 100644
--- a/src/language/node_processor/BinaryExpressionProcessor.hpp
+++ b/src/language/node_processor/BinaryExpressionProcessor.hpp
@@ -8,7 +8,7 @@
 
 #include <type_traits>
 
-template <typename DataType>
+template <typename DataT>
 class DataHandler;
 
 template <typename Op>
diff --git a/src/language/node_processor/DoWhileProcessor.hpp b/src/language/node_processor/DoWhileProcessor.hpp
index 593fd4bf178ea0734ce4e512fd913c4cc760ee98..352229edf50c659adcad43b6887c01f76fcd8c0c 100644
--- a/src/language/node_processor/DoWhileProcessor.hpp
+++ b/src/language/node_processor/DoWhileProcessor.hpp
@@ -3,7 +3,11 @@
 
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
 #include <language/utils/SymbolTable.hpp>
+#include <utils/checkpointing/Checkpoint.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/pugs_config.hpp>
 
 class DoWhileProcessor final : public INodeProcessor
 {
@@ -22,7 +26,19 @@ class DoWhileProcessor final : public INodeProcessor
   {
     bool continuation_test = true;
     ExecutionPolicy exec_until_jump;
+    ResumingManager& resuming_manager = ResumingManager::getInstance();
     do {
+      if (resuming_manager.isResuming()) {
+#ifdef PUGS_HAS_HDF5
+        const size_t checkpoint_id = resuming_manager.checkpointId();
+
+        const ASTCheckpointsInfo& ast_checkpoint_info = ASTCheckpointsInfo::getInstance();
+        const ASTCheckpoint& ast_checkpoint           = ast_checkpoint_info.getASTCheckpoint(checkpoint_id);
+
+        const size_t i_child = ast_checkpoint.getASTLocation()[resuming_manager.currentASTLevel()++];
+        Assert(i_child == 0);
+#endif   // PUGS_HAS_HDF5
+      };
       m_node.children[0]->execute(exec_until_jump);
       if (not exec_until_jump.exec()) {
         if (exec_until_jump.jumpType() == ExecutionPolicy::JumpType::break_jump) {
diff --git a/src/language/node_processor/ForProcessor.hpp b/src/language/node_processor/ForProcessor.hpp
index 674c7d05ea4e6f89c4941eddc15540556961ae87..caf202bdbe72fbd5e4588f72c32f686b3b60436f 100644
--- a/src/language/node_processor/ForProcessor.hpp
+++ b/src/language/node_processor/ForProcessor.hpp
@@ -3,7 +3,11 @@
 
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
 #include <language/utils/SymbolTable.hpp>
+#include <utils/checkpointing/Checkpoint.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/pugs_config.hpp>
 
 class ForProcessor final : public INodeProcessor
 {
@@ -21,8 +25,12 @@ class ForProcessor final : public INodeProcessor
   execute(ExecutionPolicy& exec_policy)
   {
     ExecutionPolicy exec_until_jump;
-    m_node.children[0]->execute(exec_policy);
-    while ([&]() {
+    ResumingManager& resuming_manager = ResumingManager::getInstance();
+
+    if (not resuming_manager.isResuming()) {
+      m_node.children[0]->execute(exec_policy);
+    }
+    while (resuming_manager.isResuming() or [&]() {
       return static_cast<bool>(std::visit(
         [](auto&& value) -> bool {
           using T = std::decay_t<decltype(value)>;
@@ -34,6 +42,17 @@ class ForProcessor final : public INodeProcessor
         },
         m_node.children[1]->execute(exec_policy)));
     }()) {
+      if (resuming_manager.isResuming()) {
+#ifdef PUGS_HAS_HDF5
+        const size_t checkpoint_id = resuming_manager.checkpointId();
+
+        const ASTCheckpointsInfo& ast_checkpoint_info = ASTCheckpointsInfo::getInstance();
+        const ASTCheckpoint& ast_checkpoint           = ast_checkpoint_info.getASTCheckpoint(checkpoint_id);
+
+        const size_t i_child = ast_checkpoint.getASTLocation()[resuming_manager.currentASTLevel()++];
+        Assert(i_child == 3);
+#endif   // PUGS_HAS_HDF5
+      }
       m_node.children[3]->execute(exec_until_jump);
       if (not exec_until_jump.exec()) {
         if (exec_until_jump.jumpType() == ExecutionPolicy::JumpType::break_jump) {
diff --git a/src/language/node_processor/IfProcessor.hpp b/src/language/node_processor/IfProcessor.hpp
index 0d7497eaf832d257d12dec349de4fe0c6b033ab9..bb41dc1651d3e4404a17077d24b0023d96064cad 100644
--- a/src/language/node_processor/IfProcessor.hpp
+++ b/src/language/node_processor/IfProcessor.hpp
@@ -3,7 +3,11 @@
 
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
 #include <language/utils/SymbolTable.hpp>
+#include <utils/checkpointing/Checkpoint.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/pugs_config.hpp>
 
 class IfProcessor final : public INodeProcessor
 {
@@ -20,29 +24,42 @@ class IfProcessor final : public INodeProcessor
   DataVariant
   execute(ExecutionPolicy& exec_policy)
   {
-    const bool is_true = static_cast<bool>(std::visit(   // LCOV_EXCL_LINE (false negative)
-      [](const auto& value) -> bool {
-        using T = std::decay_t<decltype(value)>;
-        if constexpr (std::is_arithmetic_v<T>) {
-          return value;
-        } else {
-          return false;   // LCOV_EXCL_LINE (unreachable: only there for compilation purpose)
-        }
-      },
-      m_node.children[0]->execute(exec_policy)));
-    if (is_true) {
-      Assert(m_node.children[1] != nullptr);
-      m_node.children[1]->execute(exec_policy);
-      if (m_node.children[1]->m_symbol_table != m_node.m_symbol_table)
-        m_node.children[1]->m_symbol_table->clearValues();
+    ResumingManager& resuming_manager = ResumingManager::getInstance();
+    if (resuming_manager.isResuming()) {
+#ifdef PUGS_HAS_HDF5
+      const size_t checkpoint_id = resuming_manager.checkpointId();
+
+      const ASTCheckpointsInfo& ast_checkpoint_info = ASTCheckpointsInfo::getInstance();
+      const ASTCheckpoint& ast_checkpoint           = ast_checkpoint_info.getASTCheckpoint(checkpoint_id);
 
+      const size_t i_child = ast_checkpoint.getASTLocation()[resuming_manager.currentASTLevel()++];
+      m_node.children[i_child]->execute(exec_policy);
+#endif   // PUGS_HAS_HDF5
     } else {
-      if (m_node.children.size() == 3) {
-        // else statement
-        Assert(m_node.children[2] != nullptr);
-        m_node.children[2]->execute(exec_policy);
-        if (m_node.children[2]->m_symbol_table != m_node.m_symbol_table)
-          m_node.children[2]->m_symbol_table->clearValues();
+      const bool is_true = static_cast<bool>(std::visit(   // LCOV_EXCL_LINE (false negative)
+        [](const auto& value) -> bool {
+          using T = std::decay_t<decltype(value)>;
+          if constexpr (std::is_arithmetic_v<T>) {
+            return value;
+          } else {
+            return false;   // LCOV_EXCL_LINE (unreachable: only there for compilation purpose)
+          }
+        },
+        m_node.children[0]->execute(exec_policy)));
+      if (is_true) {
+        Assert(m_node.children[1] != nullptr);
+        m_node.children[1]->execute(exec_policy);
+        if (m_node.children[1]->m_symbol_table != m_node.m_symbol_table)
+          m_node.children[1]->m_symbol_table->clearValues();
+
+      } else {
+        if (m_node.children.size() == 3) {
+          // else statement
+          Assert(m_node.children[2] != nullptr);
+          m_node.children[2]->execute(exec_policy);
+          if (m_node.children[2]->m_symbol_table != m_node.m_symbol_table)
+            m_node.children[2]->m_symbol_table->clearValues();
+        }
       }
     }
 
diff --git a/src/language/node_processor/WhileProcessor.hpp b/src/language/node_processor/WhileProcessor.hpp
index 72c8f4186e81cfcc587e6cf55d6f503ee3f0f154..d36360573cad3f97bb661eff3aad78a3523d1256 100644
--- a/src/language/node_processor/WhileProcessor.hpp
+++ b/src/language/node_processor/WhileProcessor.hpp
@@ -3,7 +3,11 @@
 
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/INodeProcessor.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
 #include <language/utils/SymbolTable.hpp>
+#include <utils/checkpointing/Checkpoint.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+#include <utils/pugs_config.hpp>
 
 class WhileProcessor final : public INodeProcessor
 {
@@ -21,7 +25,8 @@ class WhileProcessor final : public INodeProcessor
   execute(ExecutionPolicy& exec_policy)
   {
     ExecutionPolicy exec_until_jump;
-    while ([&]() {
+    ResumingManager& resuming_manager = ResumingManager::getInstance();
+    while (resuming_manager.isResuming() or [&]() {
       return static_cast<bool>(std::visit(
         [](const auto& value) -> bool {
           using T = std::decay_t<decltype(value)>;
@@ -33,7 +38,19 @@ class WhileProcessor final : public INodeProcessor
         },
         m_node.children[0]->execute(exec_policy)));
     }()) {
+      if (resuming_manager.isResuming()) {
+#ifdef PUGS_HAS_HDF5
+        const size_t checkpoint_id = resuming_manager.checkpointId();
+
+        const ASTCheckpointsInfo& ast_checkpoint_info = ASTCheckpointsInfo::getInstance();
+        const ASTCheckpoint& ast_checkpoint           = ast_checkpoint_info.getASTCheckpoint(checkpoint_id);
+
+        const size_t i_child = ast_checkpoint.getASTLocation()[resuming_manager.currentASTLevel()++];
+        Assert(i_child == 1);
+#endif   // PUGS_HAS_HDF5
+      }
       m_node.children[1]->execute(exec_until_jump);
+
       if (not exec_until_jump.exec()) {
         if (exec_until_jump.jumpType() == ExecutionPolicy::JumpType::break_jump) {
           break;
diff --git a/src/language/utils/ASTCheckpoint.hpp b/src/language/utils/ASTCheckpoint.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2114083da18d97d9d0deb81b0da6d14c429899ae
--- /dev/null
+++ b/src/language/utils/ASTCheckpoint.hpp
@@ -0,0 +1,36 @@
+#ifndef AST_CHECKPOINT_HPP
+#define AST_CHECKPOINT_HPP
+
+#include <vector>
+
+class ASTNode;
+class ASTCheckpoint
+{
+ private:
+  const ASTNode* const m_p_node;
+  const std::vector<size_t> m_ast_location;
+
+ public:
+  const ASTNode&
+  node() const
+  {
+    return *m_p_node;
+  }
+
+  const std::vector<size_t>&
+  getASTLocation() const
+  {
+    return m_ast_location;
+  }
+
+  ASTCheckpoint(const std::vector<size_t>& ast_location, const ASTNode* const p_node)
+    : m_p_node{p_node}, m_ast_location{ast_location}
+  {}
+
+  ASTCheckpoint(const ASTCheckpoint&) = default;
+  ASTCheckpoint(ASTCheckpoint&&)      = default;
+
+  ~ASTCheckpoint() = default;
+};
+
+#endif   // AST_CHECKPOINT_HPP
diff --git a/src/language/utils/ASTCheckpointsInfo.cpp b/src/language/utils/ASTCheckpointsInfo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fa1898e88e447883a0bcd0cd6eaec3be653fba5f
--- /dev/null
+++ b/src/language/utils/ASTCheckpointsInfo.cpp
@@ -0,0 +1,53 @@
+#include <language/utils/ASTCheckpointsInfo.hpp>
+
+#include <language/PEGGrammar.hpp>
+#include <language/ast/ASTNode.hpp>
+
+const ASTCheckpointsInfo* ASTCheckpointsInfo::m_checkpoints_info_instance = nullptr;
+
+void
+ASTCheckpointsInfo::_findASTCheckpoint(std::vector<size_t>& location, const ASTNode& node)
+{
+  if (node.is_type<language::function_evaluation>()) {
+    const ASTNode& node_name = *node.children[0];
+    if (node_name.is_type<language::name>() and (node_name.m_data_type == ASTNodeDataType::builtin_function_t) and
+        ((node_name.string() == "checkpoint") or (node_name.string() == "checkpoint_and_exit"))) {
+      m_ast_checkpoint_list.push_back(ASTCheckpoint{location, &node});
+      return;
+    }
+  }
+
+  if (node.children.size() > 0) {
+    location.push_back(0);
+    for (size_t i_node = 0; i_node < node.children.size(); ++i_node) {
+      location[location.size() - 1] = i_node;
+      this->_findASTCheckpoint(location, *node.children[i_node]);
+    }
+    location.pop_back();
+  }
+}
+
+ASTCheckpointsInfo::ASTCheckpointsInfo(const ASTNode& root_node)
+{
+  Assert(m_checkpoints_info_instance == nullptr, "Can only define one ASTCheckpointsInfo");
+  m_checkpoints_info_instance = this;
+
+  Assert(root_node.is_root());
+
+  std::vector<size_t> location;
+  this->_findASTCheckpoint(location, root_node);
+
+  Assert(location.size() == 0);
+}
+
+const ASTCheckpointsInfo&
+ASTCheckpointsInfo::getInstance()
+{
+  Assert(m_checkpoints_info_instance != nullptr, "ASTCheckpointsInfo is not defined!");
+  return *m_checkpoints_info_instance;
+}
+
+ASTCheckpointsInfo::~ASTCheckpointsInfo()
+{
+  m_checkpoints_info_instance = nullptr;
+}
diff --git a/src/language/utils/ASTCheckpointsInfo.hpp b/src/language/utils/ASTCheckpointsInfo.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3a521dcb63488de0564e4a5571597afa0abafb41
--- /dev/null
+++ b/src/language/utils/ASTCheckpointsInfo.hpp
@@ -0,0 +1,58 @@
+#ifndef AST_CHECKPOINTS_INFO_HPP
+#define AST_CHECKPOINTS_INFO_HPP
+
+#include <language/utils/ASTCheckpoint.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/PugsAssert.hpp>
+
+#include <string>
+#include <vector>
+
+class ASTNode;
+
+class ASTCheckpointsInfo
+{
+ private:
+  static const ASTCheckpointsInfo* m_checkpoints_info_instance;
+
+  std::vector<ASTCheckpoint> m_ast_checkpoint_list;
+
+  void _findASTCheckpoint(std::vector<size_t>& location, const ASTNode& node);
+
+  // The only place where the ASTCheckpointsInfo can be built
+  friend void parser(const std::string& filename);
+
+  // to allow special manipulations in tests
+  friend class ASTCheckpointsInfoTester;
+
+  ASTCheckpointsInfo(const ASTNode& root_node);
+
+ public:
+  size_t
+  getCheckpointId(const ASTNode& node) const
+  {
+    for (size_t i = 0; i < m_ast_checkpoint_list.size(); ++i) {
+      if (&m_ast_checkpoint_list[i].node() == &node) {
+        return i;
+      }
+    }
+    // LCOV_EXCL_START
+    throw UnexpectedError("Could not find node");
+    // LCOV_EXCL_STOP
+  }
+
+  const ASTCheckpoint&
+  getASTCheckpoint(size_t checkpoint_id) const
+  {
+    Assert(checkpoint_id < m_ast_checkpoint_list.size());
+    return m_ast_checkpoint_list[checkpoint_id];
+  }
+
+  static const ASTCheckpointsInfo& getInstance();
+
+  ASTCheckpointsInfo() = delete;
+
+  ~ASTCheckpointsInfo();
+};
+
+#endif   // AST_CHECKPOINTS_INFO_HPP
diff --git a/src/language/utils/ASTExecutionInfo.cpp b/src/language/utils/ASTExecutionInfo.cpp
index 6015b6a8f03625d436916224566725eaa7a18e2d..8a3131a6ef392e6f07c7b381096b69074b12ff82 100644
--- a/src/language/utils/ASTExecutionInfo.cpp
+++ b/src/language/utils/ASTExecutionInfo.cpp
@@ -2,24 +2,24 @@
 
 #include <language/ast/ASTNode.hpp>
 
-const ASTExecutionInfo* ASTExecutionInfo::m_current_execution_info = nullptr;
+const ASTExecutionInfo* ASTExecutionInfo::m_execution_info_instance = nullptr;
 
 ASTExecutionInfo::ASTExecutionInfo(const ASTNode& root_node, const ModuleRepository& module_repository)
   : m_root_node{root_node}, m_module_repository{module_repository}
 {
-  Assert(m_current_execution_info == nullptr, "Can only define one ASTExecutionInfo");
+  Assert(m_execution_info_instance == nullptr, "Can only define one ASTExecutionInfo");
 
-  m_current_execution_info = this;
+  m_execution_info_instance = this;
 }
 
 const ASTExecutionInfo&
-ASTExecutionInfo::current()
+ASTExecutionInfo::getInstance()
 {
-  Assert(m_current_execution_info != nullptr, "ASTExecutionInfo is not defined!");
-  return *m_current_execution_info;
+  Assert(m_execution_info_instance != nullptr, "ASTExecutionInfo is not defined!");
+  return *m_execution_info_instance;
 }
 
 ASTExecutionInfo::~ASTExecutionInfo()
 {
-  m_current_execution_info = nullptr;
+  m_execution_info_instance = nullptr;
 }
diff --git a/src/language/utils/ASTExecutionInfo.hpp b/src/language/utils/ASTExecutionInfo.hpp
index da4b817428c2ea00e8c089e14de2049c4faae4af..1f389761ab1f14f6b2343fb195641d346a432cfe 100644
--- a/src/language/utils/ASTExecutionInfo.hpp
+++ b/src/language/utils/ASTExecutionInfo.hpp
@@ -8,7 +8,7 @@ class ASTNode;
 class ASTExecutionInfo
 {
  private:
-  static const ASTExecutionInfo* m_current_execution_info;
+  static const ASTExecutionInfo* m_execution_info_instance;
 
   const ASTNode& m_root_node;
 
@@ -34,7 +34,7 @@ class ASTExecutionInfo
     return m_module_repository;
   }
 
-  static const ASTExecutionInfo& current();
+  static const ASTExecutionInfo& getInstance();
 
   ASTExecutionInfo() = delete;
 
diff --git a/src/language/utils/ASTNodeDataTypeTraits.hpp b/src/language/utils/ASTNodeDataTypeTraits.hpp
index 2ba019646dac53291dc73169fadc47e737f68bad..7d4ad7852d6f90f9b1efb0384e4fbd20fd242d82 100644
--- a/src/language/utils/ASTNodeDataTypeTraits.hpp
+++ b/src/language/utils/ASTNodeDataTypeTraits.hpp
@@ -9,7 +9,15 @@
 #include <vector>
 
 template <typename T>
-inline ASTNodeDataType ast_node_data_type_from = ASTNodeDataType{};
+inline ASTNodeDataType
+_ast_node_data_type_undefined()
+{
+  constexpr bool type_is_undefined = not std::is_same_v<T, T>;
+  static_assert(type_is_undefined, "Module header defining this data type must be included");
+  return {};
+}
+template <typename T>
+inline ASTNodeDataType ast_node_data_type_from = _ast_node_data_type_undefined<T>();
 
 template <>
 inline ASTNodeDataType ast_node_data_type_from<void> = ASTNodeDataType::build<ASTNodeDataType::void_t>();
diff --git a/src/language/utils/CMakeLists.txt b/src/language/utils/CMakeLists.txt
index 33a41962e8f954ef214e0c6a50eefde1d8e29f7f..08ea07e3326921adb268edd70122c7c1b5251434 100644
--- a/src/language/utils/CMakeLists.txt
+++ b/src/language/utils/CMakeLists.txt
@@ -8,6 +8,7 @@ add_library(PugsLanguageUtils
   AffectationRegisterForRnxn.cpp
   AffectationRegisterForString.cpp
   AffectationRegisterForZ.cpp
+  ASTCheckpointsInfo.cpp
   ASTDotPrinter.cpp
   ASTExecutionInfo.cpp
   ASTNodeDataType.cpp
@@ -21,6 +22,7 @@ add_library(PugsLanguageUtils
   BinaryOperatorRegisterForString.cpp
   BinaryOperatorRegisterForZ.cpp
   BuiltinFunctionEmbedderUtils.cpp
+  CheckpointResumeRepository.cpp
   DataVariant.cpp
   EmbeddedData.cpp
   EmbeddedDiscreteFunctionMathFunctions.cpp
diff --git a/src/language/utils/CheckpointResumeRepository.cpp b/src/language/utils/CheckpointResumeRepository.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..59860cf3ba27adec4dbcc6fffde7b95f6e282939
--- /dev/null
+++ b/src/language/utils/CheckpointResumeRepository.cpp
@@ -0,0 +1,64 @@
+#include <language/utils/CheckpointResumeRepository.hpp>
+
+CheckpointResumeRepository* CheckpointResumeRepository::m_instance = nullptr;
+
+#ifdef PUGS_HAS_HDF5
+
+void
+CheckpointResumeRepository::checkpoint(const ASTNodeDataType& data_type,
+                                       const std::string& symbol_name,
+                                       const EmbeddedData& embedded_data,
+                                       HighFive::File& file,
+                                       HighFive::Group& checkpoint_group,
+                                       HighFive::Group& symbol_table_group) const
+{
+  std::string data_type_name = dataTypeName(data_type);
+  if (auto i_dt_function = m_data_type_checkpointing.find(data_type_name);
+      i_dt_function != m_data_type_checkpointing.end()) {
+    const CheckpointFunction& function = i_dt_function->second;
+    function(symbol_name, embedded_data, file, checkpoint_group, symbol_table_group);
+  } else {
+    // LCOV_EXCL_START
+    std::ostringstream error_msg;
+    error_msg << "cannot find checkpointing function for type '" << rang::fgB::yellow << data_type_name
+              << rang::fg::reset << "'";
+    throw UnexpectedError(error_msg.str());
+    // LCOV_EXCL_STOP
+  }
+}
+
+EmbeddedData
+CheckpointResumeRepository::resume(const ASTNodeDataType& data_type,
+                                   const std::string& symbol_name,
+                                   const HighFive::Group& symbol_table_group) const
+{
+  std::string data_type_name = dataTypeName(data_type);
+  if (auto i_dt_function = m_data_type_resuming.find(data_type_name); i_dt_function != m_data_type_resuming.end()) {
+    const ResumeFunction& function = i_dt_function->second;
+    return function(symbol_name, symbol_table_group);
+  } else {
+    // LCOV_EXCL_START
+    std::ostringstream error_msg;
+    error_msg << "cannot find resuming function for type '" << rang::fgB::yellow << data_type_name << rang::fg::reset
+              << "'";
+    throw UnexpectedError(error_msg.str());
+    // LCOV_EXCL_STOP
+  }
+}
+
+#endif   // PUGS_HAS_HDF5
+
+void
+CheckpointResumeRepository::create()
+{
+  Assert(m_instance == nullptr, "CheckpointResumeRepository was already created");
+  m_instance = new CheckpointResumeRepository;
+}
+
+void
+CheckpointResumeRepository::destroy()
+{
+  Assert(m_instance != nullptr, "CheckpointResumeRepository was not created");
+  delete m_instance;
+  m_instance = nullptr;
+}
diff --git a/src/language/utils/CheckpointResumeRepository.hpp b/src/language/utils/CheckpointResumeRepository.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..386c586b7b34c3c7618526614106373aab069396
--- /dev/null
+++ b/src/language/utils/CheckpointResumeRepository.hpp
@@ -0,0 +1,95 @@
+#ifndef CHECKPOINT_RESUME_REPOSITORY_HPP
+#define CHECKPOINT_RESUME_REPOSITORY_HPP
+
+#include <language/utils/SymbolTable.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/PugsUtils.hpp>
+#include <utils/pugs_build_info.hpp>
+
+#include <string>
+#include <unordered_map>
+
+class CheckpointResumeRepository
+{
+ public:
+  using CheckpointFunction = std::function<void(const std::string& symbol_name,
+                                                const EmbeddedData& embedded_data,
+                                                HighFive::File& file,
+                                                HighFive::Group& checkpoint_group,
+                                                HighFive::Group& symbol_table_group)>;
+
+  using ResumeFunction =
+    std::function<EmbeddedData(const std::string& symbol_name, const HighFive::Group& symbol_table_group)>;
+
+ private:
+  std::unordered_map<std::string, CheckpointFunction> m_data_type_checkpointing;
+  std::unordered_map<std::string, ResumeFunction> m_data_type_resuming;
+
+ public:
+  void
+  addCheckpointResume(const ASTNodeDataType& ast_node_data_type,
+                      CheckpointFunction&& checkpoint_function,
+                      ResumeFunction&& resume_function)
+  {
+    const std::string& data_type_name = dataTypeName(ast_node_data_type);
+    {
+      const auto [i, inserted] =
+        m_data_type_checkpointing.insert({data_type_name, std::forward<CheckpointFunction>(checkpoint_function)});
+      if (not(inserted)) {
+        // LCOV_EXCL_START
+        std::ostringstream error_msg;
+        error_msg << "checkpointing for type '" << rang::fgB::yellow << data_type_name << rang::fg::reset
+                  << "' has already be defined";
+        throw UnexpectedError(error_msg.str());
+        // LCOV_EXCL_STOP
+      }
+    }
+    {
+      const auto [i, inserted] =
+        m_data_type_resuming.insert({data_type_name, std::forward<ResumeFunction>(resume_function)});
+      Assert(inserted);
+    }
+  }
+
+#ifdef PUGS_HAS_HDF5
+  void checkpoint(const ASTNodeDataType& data_type,
+                  const std::string& symbol_name,
+                  const EmbeddedData& embedded_data,
+                  HighFive::File& file,
+                  HighFive::Group& checkpoint_group,
+                  HighFive::Group& symbol_table_group) const;
+
+  EmbeddedData resume(const ASTNodeDataType& data_type,
+                      const std::string& symbol_name,
+                      const HighFive::Group& symbol_table_group) const;
+#endif   // PUGS_HAS_HDF5
+
+  static void create();
+
+  static bool
+  isCreated()
+  {
+    return m_instance != nullptr;
+  }
+
+  PUGS_INLINE
+  static CheckpointResumeRepository&
+  instance()
+  {
+    Assert(m_instance != nullptr);
+    return *m_instance;
+  }
+
+  static void destroy();
+
+ private:
+  static CheckpointResumeRepository* m_instance;
+
+  CheckpointResumeRepository() = default;
+
+  ~CheckpointResumeRepository() = default;
+};
+
+#endif   // CHECKPOINT_RESUME_REPOSITORY_HPP
diff --git a/src/language/utils/IntegrateCellValue.hpp b/src/language/utils/IntegrateCellValue.hpp
index d76e9af6716ee2245691b91cefd27d355db1d8e4..24c708b0462970155effb2fedfb41c52edafabdc 100644
--- a/src/language/utils/IntegrateCellValue.hpp
+++ b/src/language/utils/IntegrateCellValue.hpp
@@ -40,7 +40,9 @@ class IntegrateCellValue<OutputType(InputType)>
         if constexpr ((is_polygonal_mesh_v<MeshType>)and(MeshType::Dimension == InputType::Dimension)) {
           return integrate(function_symbol_id, quadrature_descriptor, *p_mesh);
         } else {
+          // LCOV_EXCL_START
           throw NormalError("invalid mesh type");
+          // LCOV_EXCL_STOP
         }
       },
       mesh_v->variant());
@@ -70,7 +72,9 @@ class IntegrateCellValue<OutputType(InputType)>
         if constexpr ((is_polygonal_mesh_v<MeshType>)and(MeshType::Dimension == InputType::Dimension)) {
           return integrate(function_symbol_id, quadrature_descriptor, *p_mesh, list_of_cells);
         } else {
+          // LCOV_EXCL_START
           throw NormalError("invalid mesh type");
+          // LCOV_EXCL_STOP
         }
       },
       mesh_v->variant());
@@ -99,7 +103,9 @@ class IntegrateCellValue<OutputType(InputType)>
         if constexpr ((is_polygonal_mesh_v<MeshType>)and(MeshType::Dimension == InputType::Dimension)) {
           return integrate(function_symbol_id, quadrature_descriptor, *p_mesh, list_of_cells);
         } else {
+          // LCOV_EXCL_START
           throw NormalError("invalid mesh type");
+          // LCOV_EXCL_STOP
         }
       },
       mesh_v->variant());
diff --git a/src/language/utils/OFStream.cpp b/src/language/utils/OFStream.cpp
index ca7c77fad8a69fd79ae2b512635acbb7f03277fd..59ebeb7e2544bf41fde37388573fd3df58d5e17c 100644
--- a/src/language/utils/OFStream.cpp
+++ b/src/language/utils/OFStream.cpp
@@ -3,11 +3,16 @@
 #include <utils/Filesystem.hpp>
 #include <utils/Messenger.hpp>
 
-OFStream::OFStream(const std::string& filename)
+OFStream::OFStream(const std::string& filename, bool append)
+  : OStream(OStream::Type::std_ofstream), m_filename{filename}
 {
   if (parallel::rank() == 0) {
     createDirectoryIfNeeded(filename);
-    m_fstream.open(filename);
+    if (append) {
+      m_fstream.open(filename, std::ios_base::app);
+    } else {
+      m_fstream.open(filename);
+    }
     if (m_fstream.is_open()) {
       m_ostream = &m_fstream;
     } else {
diff --git a/src/language/utils/OFStream.hpp b/src/language/utils/OFStream.hpp
index 8421906fca8746192b0d24730f7a731fe2640894..ebd9f5b4c2c09aa52c777ad1d758ec8b11351e20 100644
--- a/src/language/utils/OFStream.hpp
+++ b/src/language/utils/OFStream.hpp
@@ -8,10 +8,17 @@
 class OFStream final : public OStream
 {
  private:
+  std::string m_filename;
   std::ofstream m_fstream;
 
  public:
-  OFStream(const std::string& filename);
+  const std::string&
+  filename() const
+  {
+    return m_filename;
+  }
+
+  OFStream(const std::string& filename, bool append = false);
 
   OFStream()  = delete;
   ~OFStream() = default;
diff --git a/src/language/utils/OStream.hpp b/src/language/utils/OStream.hpp
index bef02277ef8e07be09cc5731b33c6f5c3363e37d..9d79db82df64471f162f5ed27e5ae779c444b7d9 100644
--- a/src/language/utils/OStream.hpp
+++ b/src/language/utils/OStream.hpp
@@ -8,10 +8,24 @@
 
 class OStream
 {
+ public:
+  enum class Type
+  {
+    std_ostream,
+    std_ofstream
+  };
+
  protected:
   mutable std::ostream* m_ostream = nullptr;
+  Type m_type;
 
  public:
+  const Type&
+  type() const
+  {
+    return m_type;
+  }
+
   template <typename DataT>
   friend std::shared_ptr<const OStream>
   operator<<(const std::shared_ptr<const OStream>& os, const DataT& t)
@@ -34,8 +48,8 @@ class OStream
     return os;
   }
 
-  OStream(std::ostream& os) : m_ostream(&os) {}
-  OStream() = default;
+  OStream(std::ostream& os, Type type = Type::std_ostream) : m_ostream(&os), m_type{type} {}
+  OStream(Type type) : m_type{type} {}
 
   virtual ~OStream() = default;
 };
diff --git a/src/language/utils/SymbolTable.hpp b/src/language/utils/SymbolTable.hpp
index 9f7bc8129587b8acd04be2297d604871018cf0a2..658c3a2be2ab232408066a17f68ebbf00aded70d 100644
--- a/src/language/utils/SymbolTable.hpp
+++ b/src/language/utils/SymbolTable.hpp
@@ -24,6 +24,7 @@ class SymbolTable
     int32_t m_context_id;
 
     bool m_is_initialized{false};
+    bool m_is_module_variable{false};   // variable that is created by a module (ex: cout,...)
 
     ASTNodeDataType m_data_type;
     DataVariant m_value;
@@ -35,7 +36,7 @@ class SymbolTable
       return m_context_id != -1;
     }
 
-    const int32_t&
+    int32_t
     contextId() const
     {
       return m_context_id;
@@ -53,7 +54,7 @@ class SymbolTable
       return m_value;
     }
 
-    const bool&
+    bool
     isInitialized() const
     {
       return m_is_initialized;
@@ -65,6 +66,18 @@ class SymbolTable
       m_is_initialized = true;
     }
 
+    bool
+    isModuleVariable() const
+    {
+      return m_is_module_variable;
+    }
+
+    void
+    setIsModuleVariable()
+    {
+      m_is_module_variable = true;
+    }
+
     const ASTNodeDataType&
     dataType() const
     {
@@ -98,7 +111,7 @@ class SymbolTable
       return os;
     }
 
-    Attributes& operator=(Attributes&&) = default;
+    Attributes& operator=(Attributes&&)      = default;
     Attributes& operator=(const Attributes&) = default;
 
     Attributes(const TAO_PEGTL_NAMESPACE::position& position, int32_t context_id)
@@ -138,7 +151,7 @@ class SymbolTable
 
     Symbol(const std::string& name, const Attributes& attributes) : m_name(name), m_attributes(attributes) {}
 
-    Symbol& operator=(Symbol&&) = default;
+    Symbol& operator=(Symbol&&)      = default;
     Symbol& operator=(const Symbol&) = default;
 
     Symbol(const Symbol&) = default;
@@ -179,7 +192,7 @@ class SymbolTable
     Context() : m_id{next_context_id++} {}
 
     Context& operator=(const Context&) = default;   // clazy:exclude=function-args-by-value
-    Context& operator=(Context&&) = default;
+    Context& operator=(Context&&)      = default;
 
     Context(const Context&) = default;
     Context(Context&&)      = default;
@@ -198,6 +211,24 @@ class SymbolTable
   std::shared_ptr<EmbedderTable<TypeDescriptor>> m_type_embedder_table;
 
  public:
+  const std::vector<Symbol>&
+  symbolList() const
+  {
+    return m_symbol_list;
+  }
+
+  bool
+  hasParentTable() const
+  {
+    return m_parent_table.use_count() > 0;
+  }
+
+  const std::shared_ptr<SymbolTable>
+  parentTable() const
+  {
+    return m_parent_table;
+  }
+
   bool
   hasContext() const
   {
@@ -283,7 +314,7 @@ class SymbolTable
   }
 
   std::vector<Symbol>
-  getBuiltinFunctionSymbolList(const std::string& symbol, const TAO_PEGTL_NAMESPACE::position& use_position)
+  getBuiltinFunctionSymbolList(const std::string& symbol, const TAO_PEGTL_NAMESPACE::position& use_position) const
   {
     std::vector<Symbol> builtin_function_symbol_list;
 
@@ -307,7 +338,7 @@ class SymbolTable
   }
 
   bool
-  has(const std::string& symbol, const TAO_PEGTL_NAMESPACE::position& use_position)
+  has(const std::string& symbol, const TAO_PEGTL_NAMESPACE::position& use_position) const
   {
     for (auto i_stored_symbol : m_symbol_list) {
       // Symbol must be defined before the call
diff --git a/src/main.cpp b/src/main.cpp
index 7d0112c80715b3cd0b69ea91b874527134b6a2ed..a373bd2cd867b21b1d931449f12b64239ef03c22 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -9,11 +9,13 @@
 #include <utils/GlobalVariableManager.hpp>
 #include <utils/PugsUtils.hpp>
 #include <utils/RandomEngine.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
 
 int
 main(int argc, char* argv[])
 {
   ExecutionStatManager::create();
+  ResumingManager::create();
   ParallelChecker::create();
 
   std::string filename = initialize(argc, argv);
@@ -42,6 +44,7 @@ main(int argc, char* argv[])
   finalize();
 
   ParallelChecker::destroy();
+  ResumingManager::destroy();
 
   int return_code = ExecutionStatManager::getInstance().exitCode();
   ExecutionStatManager::destroy();
diff --git a/src/mesh/DualConnectivityManager.hpp b/src/mesh/DualConnectivityManager.hpp
index 6389c5d459ac677295c09fcfce730cec2bd2f525..d82eabb72c7d42f215ffdf44cec579749f21e88c 100644
--- a/src/mesh/DualConnectivityManager.hpp
+++ b/src/mesh/DualConnectivityManager.hpp
@@ -44,7 +44,7 @@ class DualConnectivityManager
     }
 
     DualConnectivityInfo& operator=(const DualConnectivityInfo&) = default;
-    DualConnectivityInfo& operator=(DualConnectivityInfo&&) = delete;
+    DualConnectivityInfo& operator=(DualConnectivityInfo&&)      = delete;
 
     DualConnectivityInfo()                            = default;
     DualConnectivityInfo(const DualConnectivityInfo&) = default;
@@ -86,6 +86,12 @@ class DualConnectivityManager
   ~DualConnectivityManager() = default;
 
  public:
+  const auto&
+  primalToDualConnectivityInfoMap() const
+  {
+    return m_primal_to_dual_connectivity_info_map;
+  }
+
   static void create();
   static void destroy();
 
diff --git a/src/mesh/DualMeshManager.hpp b/src/mesh/DualMeshManager.hpp
index 46ad9c9f8d62cbff40a7d33efcc72c02e79f1ac7..201b0e663835cad99fca62551319024763488dc9 100644
--- a/src/mesh/DualMeshManager.hpp
+++ b/src/mesh/DualMeshManager.hpp
@@ -34,6 +34,12 @@ class DualMeshManager
   ~DualMeshManager() = default;
 
  public:
+  const auto&
+  primalToDualMeshMap() const
+  {
+    return m_mesh_to_dual_mesh_map;
+  }
+
   static void create();
   static void destroy();
 
diff --git a/src/mesh/NamedBoundaryDescriptor.hpp b/src/mesh/NamedBoundaryDescriptor.hpp
index 7ee732c59df7814da24b684df9bed60f4676742c..34ef2370fbd66ae539b8ab882058c43ef7db8311 100644
--- a/src/mesh/NamedBoundaryDescriptor.hpp
+++ b/src/mesh/NamedBoundaryDescriptor.hpp
@@ -19,6 +19,12 @@ class NamedBoundaryDescriptor final : public IBoundaryDescriptor
   }
 
  public:
+  [[nodiscard]] const std::string&
+  name() const
+  {
+    return m_name;
+  }
+
   [[nodiscard]] bool
   operator==(const RefId& ref_id) const final
   {
diff --git a/src/mesh/NamedInterfaceDescriptor.hpp b/src/mesh/NamedInterfaceDescriptor.hpp
index 0b6a0e996c4753ee2f33e69f482820520886ad19..a7b94ae81cc5bae9953fa7870f89ce4cbd430172 100644
--- a/src/mesh/NamedInterfaceDescriptor.hpp
+++ b/src/mesh/NamedInterfaceDescriptor.hpp
@@ -25,6 +25,12 @@ class NamedInterfaceDescriptor final : public IInterfaceDescriptor
     return m_name == ref_id.tagName();
   }
 
+  [[nodiscard]] const std::string&
+  name() const
+  {
+    return m_name;
+  }
+
   [[nodiscard]] Type
   type() const final
   {
diff --git a/src/mesh/NamedZoneDescriptor.hpp b/src/mesh/NamedZoneDescriptor.hpp
index 0fbe101421f7f5048019f945622ad4263e8d3e39..f69e2b21c08cf192fa21b2ccf720641f75811fa5 100644
--- a/src/mesh/NamedZoneDescriptor.hpp
+++ b/src/mesh/NamedZoneDescriptor.hpp
@@ -25,7 +25,13 @@ class NamedZoneDescriptor final : public IZoneDescriptor
     return m_name == ref_id.tagName();
   }
 
-  Type
+  [[nodiscard]] const std::string&
+  name() const
+  {
+    return m_name;
+  }
+
+  [[nodiscard]] Type
   type() const final
   {
     return Type::named;
diff --git a/src/mesh/NumberedBoundaryDescriptor.hpp b/src/mesh/NumberedBoundaryDescriptor.hpp
index 2abab93956f9fd2dc98a7d3967484444cba4a0fb..9367d67468779cb5f629cc28002b5f5b44b860c9 100644
--- a/src/mesh/NumberedBoundaryDescriptor.hpp
+++ b/src/mesh/NumberedBoundaryDescriptor.hpp
@@ -24,6 +24,12 @@ class NumberedBoundaryDescriptor final : public IBoundaryDescriptor
   }
 
  public:
+  [[nodiscard]] unsigned int
+  number() const
+  {
+    return m_number;
+  }
+
   [[nodiscard]] Type
   type() const final
   {
diff --git a/src/mesh/NumberedInterfaceDescriptor.hpp b/src/mesh/NumberedInterfaceDescriptor.hpp
index d72197221f5811a22836871e89f601479d480123..95ce579744ae4874463bfd034d66b1d4d4203320 100644
--- a/src/mesh/NumberedInterfaceDescriptor.hpp
+++ b/src/mesh/NumberedInterfaceDescriptor.hpp
@@ -24,6 +24,12 @@ class NumberedInterfaceDescriptor final : public IInterfaceDescriptor
   }
 
  public:
+  [[nodiscard]] unsigned int
+  number() const
+  {
+    return m_number;
+  }
+
   [[nodiscard]] Type
   type() const final
   {
diff --git a/src/mesh/NumberedZoneDescriptor.hpp b/src/mesh/NumberedZoneDescriptor.hpp
index 7e72521d2c69346c4fc46edc82dd0bbedd99e20a..3d4ef7d2b28885a6db96871199f369bb41f85488 100644
--- a/src/mesh/NumberedZoneDescriptor.hpp
+++ b/src/mesh/NumberedZoneDescriptor.hpp
@@ -24,7 +24,13 @@ class NumberedZoneDescriptor final : public IZoneDescriptor
   }
 
  public:
-  Type
+  [[nodiscard]] unsigned int
+  number() const
+  {
+    return m_number;
+  }
+
+  [[nodiscard]] Type
   type() const final
   {
     return Type::numbered;
diff --git a/src/output/GnuplotWriter.cpp b/src/output/GnuplotWriter.cpp
index 5bc70494c2d6dab1f6d0d243acc60ad229c07b1c..a63f1f7a654de6b526584b843379ab6ed25c6aa1 100644
--- a/src/output/GnuplotWriter.cpp
+++ b/src/output/GnuplotWriter.cpp
@@ -214,6 +214,8 @@ GnuplotWriter::_write(const MeshType& mesh,
       item_data_variant);
   }
 
+  createDirectoryIfNeeded(m_base_filename);
+
   for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
     if (i_rank == parallel::rank()) {
       std::ofstream fout(_getFilename(), std::ios_base::app);
diff --git a/src/output/GnuplotWriter.hpp b/src/output/GnuplotWriter.hpp
index 0c14204d930bc947832a484c617a9aa5965b04e6..f1a1f030315c2991c814c8af2212a6d34bc609ab 100644
--- a/src/output/GnuplotWriter.hpp
+++ b/src/output/GnuplotWriter.hpp
@@ -54,6 +54,12 @@ class GnuplotWriter final : public GnuplotWriterBase
   void _writeMesh(const MeshVariant& mesh_v) const final;
 
  public:
+  Type
+  type() const final
+  {
+    return Type::gnuplot;
+  }
+
   GnuplotWriter(const std::string& base_filename) : GnuplotWriterBase(base_filename) {}
 
   GnuplotWriter(const std::string& base_filename, const double time_period)
diff --git a/src/output/GnuplotWriter1D.cpp b/src/output/GnuplotWriter1D.cpp
index a40b068853bfe7b985ebd678fd73467539c70091..1f6bd91f6d54e93054f77ab95b6b54a08e4447ef 100644
--- a/src/output/GnuplotWriter1D.cpp
+++ b/src/output/GnuplotWriter1D.cpp
@@ -220,6 +220,8 @@ GnuplotWriter1D::_write(const MeshType& mesh,
 
   std::ofstream fout;
 
+  createDirectoryIfNeeded(m_base_filename);
+
   if (parallel::rank() == 0) {
     fout.open(_getFilename());
     if (not fout) {
diff --git a/src/output/GnuplotWriter1D.hpp b/src/output/GnuplotWriter1D.hpp
index 32e4501873b175fb9af10123935d4666cd331404..d105fdf05e52ef81bf37e76ecfa61eac8ed38edf 100644
--- a/src/output/GnuplotWriter1D.hpp
+++ b/src/output/GnuplotWriter1D.hpp
@@ -42,6 +42,12 @@ class GnuplotWriter1D final : public GnuplotWriterBase
   void _writeMesh(const MeshVariant& mesh_v) const final;
 
  public:
+  Type
+  type() const final
+  {
+    return Type::gnuplot_1d;
+  }
+
   GnuplotWriter1D(const std::string& base_filename) : GnuplotWriterBase(base_filename) {}
 
   GnuplotWriter1D(const std::string& base_filename, const double time_period)
diff --git a/src/output/GnuplotWriterRaw.hpp b/src/output/GnuplotWriterRaw.hpp
index 481bb2a4bfc548c89ea684ec7a841df67bede57b..d72b77162f65b2500d89313f3a0005ce9aaa8182 100644
--- a/src/output/GnuplotWriterRaw.hpp
+++ b/src/output/GnuplotWriterRaw.hpp
@@ -42,6 +42,12 @@ class GnuplotWriterRaw final : public GnuplotWriterBase
   void _writeMesh(const MeshVariant& mesh_v) const final;
 
  public:
+  Type
+  type() const final
+  {
+    return Type::gnuplot_raw;
+  }
+
   GnuplotWriterRaw(const std::string& base_filename) : GnuplotWriterBase(base_filename) {}
 
   GnuplotWriterRaw(const std::string& base_filename, const double time_period)
diff --git a/src/output/IWriter.hpp b/src/output/IWriter.hpp
index 3d821fdacaaf12b3ec28560cf6197fde29e9494c..85060e37e8abbc46c06fe70933c3a032721dd01c 100644
--- a/src/output/IWriter.hpp
+++ b/src/output/IWriter.hpp
@@ -11,6 +11,16 @@ class MeshVariant;
 class IWriter
 {
  public:
+  enum class Type
+  {
+    gnuplot,
+    gnuplot_1d,
+    gnuplot_raw,
+    vtk
+  };
+
+  virtual Type type() const = 0;
+
   virtual void writeMesh(const std::shared_ptr<const MeshVariant>& mesh_v) const = 0;
   virtual void writeMesh(const MeshVariant& mesh_v) const                        = 0;
 
diff --git a/src/output/VTKWriter.hpp b/src/output/VTKWriter.hpp
index 6c40f1005952d1649dcbd989f639907826a312fd..94c1fb8442bdfaa8bd9ee162f04c084c9714c09e 100644
--- a/src/output/VTKWriter.hpp
+++ b/src/output/VTKWriter.hpp
@@ -109,6 +109,12 @@ class VTKWriter final : public WriterBase
   void _writeMesh(const MeshVariant& mesh_v) const final;
 
  public:
+  Type
+  type() const final
+  {
+    return Type::vtk;
+  }
+
   VTKWriter(const std::string& base_filename) : WriterBase(base_filename) {}
 
   VTKWriter(const std::string& base_filename, const double time_period) : WriterBase(base_filename, time_period) {}
diff --git a/src/output/WriterBase.cpp b/src/output/WriterBase.cpp
index bc7f90be252c42465ec3107f6f362f9658997909..5baa126018248dcddd6c3178e72aba2043162354 100644
--- a/src/output/WriterBase.cpp
+++ b/src/output/WriterBase.cpp
@@ -304,7 +304,9 @@ WriterBase::WriterBase(const std::string& base_filename, const double& time_peri
   : m_base_filename{base_filename}, m_period_manager(time_period), m_signature(std::nullopt)
 {}
 
-WriterBase::WriterBase(const std::string& base_filename) : m_base_filename{base_filename}, m_signature(std::nullopt) {}
+WriterBase::WriterBase(const std::string& base_filename)
+  : m_base_filename{base_filename}, m_period_manager{std::nullopt}, m_signature(std::nullopt)
+{}
 
 void
 WriterBase::write(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const
diff --git a/src/output/WriterBase.hpp b/src/output/WriterBase.hpp
index c07c20c711e827ae566635ca82c9456bbda853ac..6de7ab558638d61106ccfc882eb85ddfe0a22eec 100644
--- a/src/output/WriterBase.hpp
+++ b/src/output/WriterBase.hpp
@@ -4,6 +4,8 @@
 #include <output/IWriter.hpp>
 #include <utils/PugsAssert.hpp>
 
+#include <utils/HighFivePugsUtils.hpp>
+
 #include <limits>
 #include <optional>
 #include <string>
@@ -11,6 +13,18 @@
 class MeshVariant;
 class OutputNamedItemDataSet;
 class NamedDiscreteFunction;
+class EmbeddedData;
+
+namespace checkpointing
+{
+void writeIWriter(const std::string& symbol_name,
+                  const EmbeddedData& embedded_data,
+                  HighFive::File& file,
+                  HighFive::Group& checkpoint_group,
+                  HighFive::Group& symbol_table_group);
+
+EmbeddedData readIWriter(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+}   // namespace checkpointing
 
 class WriterBase : public IWriter
 {
@@ -42,6 +56,12 @@ class WriterBase : public IWriter
       m_next_time = next_time;
     }
 
+    const std::vector<double>&
+    getSavedTimes() const
+    {
+      return m_saved_times;
+    }
+
     size_t
     nbSavedTimes() const
     {
@@ -79,9 +99,23 @@ class WriterBase : public IWriter
     PeriodManager(PeriodManager&&)      = default;
     PeriodManager(double time_period) : m_time_period{time_period}, m_next_time{std::numeric_limits<double>::lowest()}
     {}
+    PeriodManager(double time_period, double next_time, const std::vector<double>& saved_times)
+      : m_time_period{time_period}, m_next_time{next_time}, m_saved_times{saved_times}
+    {}
   };
 
  protected:
+  // useful for checkpoint
+  friend void checkpointing::writeIWriter(const std::string& symbol_name,
+                                          const EmbeddedData& embedded_data,
+                                          HighFive::File& file,
+                                          HighFive::Group& checkpoint_group,
+                                          HighFive::Group& symbol_table_group);
+
+  // useful for resume
+  friend EmbeddedData checkpointing::readIWriter(const std::string& symbol_name,
+                                                 const HighFive::Group& symbol_table_group);
+
   const std::string m_base_filename;
 
   std::optional<PeriodManager> m_period_manager;
@@ -117,6 +151,27 @@ class WriterBase : public IWriter
   virtual void _writeMesh(const MeshVariant& mesh_v) const = 0;
 
  public:
+  PUGS_INLINE
+  const std::string&
+  baseFilename() const
+  {
+    return m_base_filename;
+  }
+
+  PUGS_INLINE
+  const std::optional<PeriodManager>&
+  periodManager() const
+  {
+    return m_period_manager;
+  }
+
+  PUGS_INLINE
+  const std::optional<std::string>&
+  signature() const
+  {
+    return m_signature;
+  }
+
   void write(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list) const final;
 
   void writeIfNeeded(const std::vector<std::shared_ptr<const INamedDiscreteData>>& named_discrete_data_list,
diff --git a/src/scheme/DirichletBoundaryConditionDescriptor.hpp b/src/scheme/DirichletBoundaryConditionDescriptor.hpp
index 3b80103cb0314a1b2db51fed18c9d1dd88d5a486..2ddfa649cd151dec643984becae7ae5718ef000a 100644
--- a/src/scheme/DirichletBoundaryConditionDescriptor.hpp
+++ b/src/scheme/DirichletBoundaryConditionDescriptor.hpp
@@ -17,12 +17,12 @@ class DirichletBoundaryConditionDescriptor : public BoundaryConditionDescriptorB
     return os;
   }
 
-  const std::string_view m_name;
+  const std::string m_name;
 
   const FunctionSymbolId m_rhs_symbol_id;
 
  public:
-  std::string_view
+  const std::string&
   name() const
   {
     return m_name;
@@ -40,7 +40,7 @@ class DirichletBoundaryConditionDescriptor : public BoundaryConditionDescriptorB
     return Type::dirichlet;
   }
 
-  DirichletBoundaryConditionDescriptor(const std::string_view name,
+  DirichletBoundaryConditionDescriptor(const std::string& name,
                                        const std::shared_ptr<const IBoundaryDescriptor>& boundary_descriptor,
                                        const FunctionSymbolId& rhs_symbol_id)
     : BoundaryConditionDescriptorBase{boundary_descriptor}, m_name{name}, m_rhs_symbol_id{rhs_symbol_id}
diff --git a/src/scheme/ExternalBoundaryConditionDescriptor.hpp b/src/scheme/ExternalBoundaryConditionDescriptor.hpp
index eb915db373d47f2ae5bb8bbc6d0de2fa004b3261..d015547f88862f7d16825f3888e1814a7702a7cd 100644
--- a/src/scheme/ExternalBoundaryConditionDescriptor.hpp
+++ b/src/scheme/ExternalBoundaryConditionDescriptor.hpp
@@ -17,12 +17,12 @@ class ExternalBoundaryConditionDescriptor : public BoundaryConditionDescriptorBa
     return os;
   }
 
-  const std::string_view m_name;
+  const std::string m_name;
 
   const std::shared_ptr<const Socket> m_socket;
 
  public:
-  std::string_view
+  const std::string&
   name() const
   {
     return m_name;
@@ -40,7 +40,7 @@ class ExternalBoundaryConditionDescriptor : public BoundaryConditionDescriptorBa
     return Type::external;
   }
 
-  ExternalBoundaryConditionDescriptor(const std::string_view name,
+  ExternalBoundaryConditionDescriptor(const std::string& name,
                                       const std::shared_ptr<const IBoundaryDescriptor>& boundary_descriptor,
                                       const std::shared_ptr<const Socket>& socket)
     : BoundaryConditionDescriptorBase{boundary_descriptor}, m_name{name}, m_socket{socket}
diff --git a/src/scheme/FixedBoundaryConditionDescriptor.hpp b/src/scheme/FixedBoundaryConditionDescriptor.hpp
index 010d427c3336c5ffa5c83ffdd3f90e587aa99ec7..b8b5446248f040505d8a8f3653142076ae4d3d97 100644
--- a/src/scheme/FixedBoundaryConditionDescriptor.hpp
+++ b/src/scheme/FixedBoundaryConditionDescriptor.hpp
@@ -17,7 +17,7 @@ class FixedBoundaryConditionDescriptor : public BoundaryConditionDescriptorBase
     return os;
   }
 
-  const std::string_view m_name;
+  std::shared_ptr<const IBoundaryDescriptor> m_boundary_descriptor;
 
  public:
   Type
diff --git a/src/scheme/FourierBoundaryConditionDescriptor.hpp b/src/scheme/FourierBoundaryConditionDescriptor.hpp
index 7c539f0538b0418350e722b3bdd601d21b9b0550..c04995634691554ce3174b7a6b23f0cf086ebed8 100644
--- a/src/scheme/FourierBoundaryConditionDescriptor.hpp
+++ b/src/scheme/FourierBoundaryConditionDescriptor.hpp
@@ -17,13 +17,13 @@ class FourierBoundaryConditionDescriptor : public BoundaryConditionDescriptorBas
     return os;
   }
 
-  const std::string_view m_name;
+  const std::string m_name;
 
   const FunctionSymbolId m_mass_symbol_id;
   const FunctionSymbolId m_rhs_symbol_id;
 
  public:
-  std::string_view
+  const std::string&
   name() const
   {
     return m_name;
@@ -47,7 +47,7 @@ class FourierBoundaryConditionDescriptor : public BoundaryConditionDescriptorBas
     return Type::fourier;
   }
 
-  FourierBoundaryConditionDescriptor(const std::string_view name,
+  FourierBoundaryConditionDescriptor(const std::string& name,
                                      const std::shared_ptr<const IBoundaryDescriptor>& boundary_descriptor,
                                      const FunctionSymbolId& mass_symbol_id,
                                      const FunctionSymbolId& rhs_symbol_id)
diff --git a/src/scheme/IBoundaryConditionDescriptor.hpp b/src/scheme/IBoundaryConditionDescriptor.hpp
index 58e8b6076ff2c363049de6f107e6a949966d6c72..fc9ff381b97758e79642647a36680364093c193b 100644
--- a/src/scheme/IBoundaryConditionDescriptor.hpp
+++ b/src/scheme/IBoundaryConditionDescriptor.hpp
@@ -18,9 +18,11 @@ class IBoundaryConditionDescriptor
     fixed,
     free,
     inflow,
+    inflow_list,
     neumann,
     outflow,
-    symmetry
+    symmetry,
+    wall
   };
 
  protected:
diff --git a/src/scheme/InflowListBoundaryConditionDescriptor.hpp b/src/scheme/InflowListBoundaryConditionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ad1427cf72532b770a378c5e86ecbc1cc39ac063
--- /dev/null
+++ b/src/scheme/InflowListBoundaryConditionDescriptor.hpp
@@ -0,0 +1,48 @@
+#ifndef INFLOW_LIST_BOUNDARY_CONDITION_DESCRIPTOR_HPP
+#define INFLOW_LIST_BOUNDARY_CONDITION_DESCRIPTOR_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IBoundaryDescriptor.hpp>
+#include <scheme/BoundaryConditionDescriptorBase.hpp>
+
+#include <memory>
+
+class InflowListBoundaryConditionDescriptor : public BoundaryConditionDescriptorBase
+{
+ private:
+  std::ostream&
+  _write(std::ostream& os) const final
+  {
+    os << "inflow_list(" << ',' << *m_boundary_descriptor << ")";
+    return os;
+  }
+
+  const std::vector<FunctionSymbolId> m_function_symbol_id_list;
+
+ public:
+  const std::vector<FunctionSymbolId>&
+  functionSymbolIdList() const
+  {
+    return m_function_symbol_id_list;
+  }
+
+  Type
+  type() const final
+  {
+    return Type::inflow_list;
+  }
+
+  InflowListBoundaryConditionDescriptor(std::shared_ptr<const IBoundaryDescriptor> boundary_descriptor,
+                                        const std::vector<FunctionSymbolId>& function_symbol_id_list)
+    : BoundaryConditionDescriptorBase{boundary_descriptor}, m_function_symbol_id_list{function_symbol_id_list}
+  {
+    ;
+  }
+
+  InflowListBoundaryConditionDescriptor(const InflowListBoundaryConditionDescriptor&) = delete;
+  InflowListBoundaryConditionDescriptor(InflowListBoundaryConditionDescriptor&&)      = delete;
+
+  ~InflowListBoundaryConditionDescriptor() = default;
+};
+
+#endif   // INFLOW_LIST_BOUNDARY_CONDITION_DESCRIPTOR_HPP
diff --git a/src/scheme/NeumannBoundaryConditionDescriptor.hpp b/src/scheme/NeumannBoundaryConditionDescriptor.hpp
index ae591a38f987d7b906e26c3fc8015f0035e9eefc..2d63df7451dba2a7a0cb222665757d1b6f692b09 100644
--- a/src/scheme/NeumannBoundaryConditionDescriptor.hpp
+++ b/src/scheme/NeumannBoundaryConditionDescriptor.hpp
@@ -17,12 +17,12 @@ class NeumannBoundaryConditionDescriptor : public BoundaryConditionDescriptorBas
     return os;
   }
 
-  const std::string_view m_name;
+  const std::string m_name;
 
   const FunctionSymbolId m_rhs_symbol_id;
 
  public:
-  std::string_view
+  const std::string&
   name() const
   {
     return m_name;
@@ -40,7 +40,7 @@ class NeumannBoundaryConditionDescriptor : public BoundaryConditionDescriptorBas
     return Type::neumann;
   }
 
-  NeumannBoundaryConditionDescriptor(const std::string_view name,
+  NeumannBoundaryConditionDescriptor(const std::string& name,
                                      const std::shared_ptr<const IBoundaryDescriptor>& boundary_descriptor,
                                      const FunctionSymbolId& rhs_symbol_id)
     : BoundaryConditionDescriptorBase{boundary_descriptor}, m_name{name}, m_rhs_symbol_id{rhs_symbol_id}
diff --git a/src/scheme/WallBoundaryConditionDescriptor.hpp b/src/scheme/WallBoundaryConditionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf16fba7d1186f0d3c200884ebc319c4b461e582
--- /dev/null
+++ b/src/scheme/WallBoundaryConditionDescriptor.hpp
@@ -0,0 +1,40 @@
+#ifndef WALL_BOUNDARY_CONDITION_DESCRIPTOR_HPP
+#define WALL_BOUNDARY_CONDITION_DESCRIPTOR_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IBoundaryDescriptor.hpp>
+#include <scheme/BoundaryConditionDescriptorBase.hpp>
+
+#include <memory>
+
+class WallBoundaryConditionDescriptor : public BoundaryConditionDescriptorBase
+{
+ private:
+  std::ostream&
+  _write(std::ostream& os) const final
+  {
+    os << "wall(" << *m_boundary_descriptor << ")";
+    return os;
+  }
+
+ public:
+  Type
+  type() const final
+  {
+    return Type::wall;
+  }
+
+  WallBoundaryConditionDescriptor(const std::shared_ptr<const IBoundaryDescriptor>& boundary_descriptor)
+
+    : BoundaryConditionDescriptorBase{boundary_descriptor}
+  {
+    ;
+  }
+
+  WallBoundaryConditionDescriptor(const WallBoundaryConditionDescriptor&) = delete;
+  WallBoundaryConditionDescriptor(WallBoundaryConditionDescriptor&&)      = delete;
+
+  ~WallBoundaryConditionDescriptor() = default;
+};
+
+#endif   // WALL_BOUNDARY_CONDITION_DESCRIPTOR_HPP
diff --git a/src/utils/BuildInfo.cpp b/src/utils/BuildInfo.cpp
index c6ac6cde607ba6e32e47b9586b6bc88ec3c9d2f5..81d7567750eaef8513b580daf11eac6d3f916642 100644
--- a/src/utils/BuildInfo.cpp
+++ b/src/utils/BuildInfo.cpp
@@ -21,6 +21,10 @@
 #include <highfive/highfive.hpp>
 #endif   // PUGS_HAS_HDF5
 
+#ifdef PUGS_HAS_SLURM
+#include <slurm/slurm.h>
+#endif   // PUGS_HAS_SLURM
+
 std::string
 BuildInfo::type()
 {
@@ -51,7 +55,7 @@ BuildInfo::mpiLibrary()
     MPI_Get_library_version(mpi_version, &length);
     return std::string(mpi_version);
   }();
-#else
+#else    // PUGS_HAS_MPI
   return "none";
 #endif   // PUGS_HAS_MPI
 }
@@ -62,7 +66,7 @@ BuildInfo::petscLibrary()
 #ifdef PUGS_HAS_PETSC
   return stringify(PETSC_VERSION_MAJOR) + "." + stringify(PETSC_VERSION_MINOR) + "." +
          stringify(PETSC_VERSION_SUBMINOR);
-#else
+#else    // PUGS_HAS_PETSC
   return "none";
 #endif   // PUGS_HAS_PETSC
 }
@@ -73,7 +77,7 @@ BuildInfo::slepcLibrary()
 #ifdef PUGS_HAS_SLEPC
   return stringify(SLEPC_VERSION_MAJOR) + "." + stringify(SLEPC_VERSION_MINOR) + "." +
          stringify(SLEPC_VERSION_SUBMINOR);
-#else
+#else    // PUGS_HAS_SLEPC
   return "none";
 #endif   // PUGS_HAS_SLEPC
 }
@@ -89,7 +93,21 @@ BuildInfo::hdf5Library()
   return stringify(H5_VERSION) + " [sequential]";
 #endif   // H5_HAVE_PARALLEL
 
-#else
+#else    // PUGS_HAS_HDF5
   return "none";
 #endif   // PUGS_HAS_HDF5
 }
+
+std::string
+BuildInfo::slurmLibrary()
+{
+#ifdef PUGS_HAS_SLURM
+
+  return stringify(SLURM_VERSION_MAJOR(SLURM_VERSION_NUMBER)) + "." +
+         stringify(SLURM_VERSION_MINOR(SLURM_VERSION_NUMBER)) + "." +
+         stringify(SLURM_VERSION_MICRO(SLURM_VERSION_NUMBER));
+
+#else    // PUGS_HAS_SLURM
+  return "none";
+#endif   // PUGS_HAS_SLURM
+}
diff --git a/src/utils/BuildInfo.hpp b/src/utils/BuildInfo.hpp
index 89a73e5411716c27a69de65fa9976fb062f77dca..df4a32d93a284db7fc8d3abbea9ac2e43ea5d36e 100644
--- a/src/utils/BuildInfo.hpp
+++ b/src/utils/BuildInfo.hpp
@@ -12,6 +12,7 @@ struct BuildInfo
   static std::string petscLibrary();
   static std::string slepcLibrary();
   static std::string hdf5Library();
+  static std::string slurmLibrary();
 };
 
 #endif   // BUILD_INFO_HPP
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index c05afb4659617f413d533c06b98c82c4d823c5c2..13b0db80862c2b28e80dc18ff22951b08157db28 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -1,5 +1,7 @@
 # ------------------- Source files --------------------
 
+add_subdirectory(checkpointing)
+
 add_library(
   PugsUtils
   BuildInfo.cpp
@@ -20,7 +22,8 @@ add_library(
   RevisionInfo.cpp
   SignalManager.cpp
   SLEPcWrapper.cpp
-  Socket.cpp)
+  Socket.cpp
+  Stop.cpp)
 
 if(${MPI_FOUND})
   target_include_directories(PugsUtils PRIVATE ${PARMETIS_INCLUDE_DIR})
diff --git a/src/utils/CommunicatorManager.hpp b/src/utils/CommunicatorManager.hpp
index df0ea8dba61b7ab51f9dd5819a9a20c9eca684ec..a03620a6001e09b61a716b4c558df9884ed8c652 100644
--- a/src/utils/CommunicatorManager.hpp
+++ b/src/utils/CommunicatorManager.hpp
@@ -8,6 +8,8 @@ class CommunicatorManager
  private:
   static std::optional<int> s_split_color;
 
+  friend void resetCommunicationManagerForTests();
+
  public:
   static bool hasSplitColor();
   static int splitColor();
diff --git a/src/utils/ExecutionStatManager.cpp b/src/utils/ExecutionStatManager.cpp
index 6918d4d7acfc89bb12420b1540a240e1bcd026dc..6422d08c026ddf2df2169f503a585df936108ad9 100644
--- a/src/utils/ExecutionStatManager.cpp
+++ b/src/utils/ExecutionStatManager.cpp
@@ -44,7 +44,7 @@ ExecutionStatManager::_prettyPrintTime(double time_in_seconds) const
 }
 
 void
-ExecutionStatManager::_printMaxResidentMemory() const
+ExecutionStatManager::_printMaxResidentMemory(std::ostream& os) const
 {
   class Memory
   {
@@ -85,63 +85,100 @@ ExecutionStatManager::_printMaxResidentMemory() const
   };
 
   Memory memory;
-  std::cout << "Memory: " << rang::style::bold << Memory{parallel::allReduceSum(memory.value())}.prettyPrint()
-            << rang::style::reset;
-  if (parallel::size() > 1) {
-    std::cout << " (over " << parallel::size() << " processes)";
-    std::cout << " Avg: " << rang::style::bold
-              << Memory{parallel::allReduceSum(memory.value()) / parallel::size()}.prettyPrint() << rang::style::reset;
-    std::cout << " Min: " << rang::style::bold << Memory{parallel::allReduceMin(memory.value())}.prettyPrint()
-              << rang::style::reset;
-    std::cout << " Max: " << rang::style::bold << Memory{parallel::allReduceMax(memory.value())}.prettyPrint()
-              << rang::style::reset;
-  }
-  std::cout << '\n';
+  os << "Memory: " << rang::style::bold << Memory{parallel::allReduceSum(memory.value())}.prettyPrint()
+     << rang::style::reset;
+  os << " (over " << parallel::size() << " processes)";
+  os << " Avg: " << rang::style::bold << Memory{parallel::allReduceSum(memory.value()) / parallel::size()}.prettyPrint()
+     << rang::style::reset;
+  os << " Min: " << rang::style::bold << Memory{parallel::allReduceMin(memory.value())}.prettyPrint()
+     << rang::style::reset;
+  os << " Max: " << rang::style::bold << Memory{parallel::allReduceMax(memory.value())}.prettyPrint()
+     << rang::style::reset;
+  os << '\n';
 }
 
 void
-ExecutionStatManager::_printElapseTime() const
+ExecutionStatManager::_printElapseTime(std::ostream& os) const
 {
   const double elapse_time = m_instance->m_elapse_time.seconds();
-  std::cout << "Execution: " << rang::style::bold << m_instance->m_elapse_time.seconds() << 's' << rang::style::reset;
+  os << "Execution: " << rang::style::bold << elapse_time << 's' << rang::style::reset;
   if (elapse_time > 60) {
-    std::cout << " [" << rang::style::bold << this->_prettyPrintTime(elapse_time) << rang::style::reset << ']';
+    os << " [" << rang::style::bold << this->_prettyPrintTime(elapse_time) << rang::style::reset << ']';
+  }
+  if (m_run_number > 1) {
+    const double cumulative_elapse_time = elapse_time + m_previous_cumulative_elapse_time;
+    os << " (Run number " << m_run_number << ").\n - Cumulative execution time: " << rang::style::bold
+       << cumulative_elapse_time << 's' << rang::style::reset;
+    if (cumulative_elapse_time > 60) {
+      os << " [" << rang::style::bold << this->_prettyPrintTime(cumulative_elapse_time) << rang::style::reset << ']';
+    }
   }
-  std::cout << '\n';
+  os << '\n';
 }
 
 void
-ExecutionStatManager::_printTotalCPUTime() const
+ExecutionStatManager::_printTotalCPUTime(std::ostream& os) const
 {
   rusage u;
   getrusage(RUSAGE_SELF, &u);
 
   const double total_cpu_time =
-    u.ru_utime.tv_sec + u.ru_stime.tv_sec + (u.ru_utime.tv_usec + u.ru_stime.tv_usec) * 1E-6;
+    parallel::allReduceSum(u.ru_utime.tv_sec + u.ru_stime.tv_sec + (u.ru_utime.tv_usec + u.ru_stime.tv_usec) * 1E-6);
 
-  std::cout << "Total CPU: " << rang::style::bold << parallel::allReduceSum(total_cpu_time) << 's'
-            << rang::style::reset;
-  std::cout << " (" << parallel::allReduceSum(Kokkos::DefaultHostExecutionSpace::concurrency()) << " threads over "
-            << parallel::size() << " processes)";
+  os << "Total CPU: " << rang::style::bold << total_cpu_time << 's' << rang::style::reset;
+  os << " (" << parallel::allReduceSum(Kokkos::DefaultHostExecutionSpace::concurrency()) << " threads over "
+     << parallel::size() << " processes)";
   if (total_cpu_time > 60) {
-    std::cout << " [" << _prettyPrintTime(total_cpu_time) << ']';
+    os << " [" << _prettyPrintTime(total_cpu_time) << ']';
+  }
+
+  if (m_run_number > 1) {
+    const double cumulative_total_cpu_time = total_cpu_time + m_previous_cumulative_total_cpu_time;
+    os << "\n - Cumulative total CPU: " << rang::style::bold << cumulative_total_cpu_time << 's' << rang::style::reset;
+    if (cumulative_total_cpu_time > 60) {
+      os << " [" << rang::style::bold << this->_prettyPrintTime(cumulative_total_cpu_time) << rang::style::reset << ']';
+    }
   }
-  std::cout << '\n';
+
+  os << '\n';
 }
 
 void
-ExecutionStatManager::printInfo()
+ExecutionStatManager::printInfo(std::ostream& os)
 {
   if (ExecutionStatManager::getInstance().doPrint()) {
-    std::cout << "----------------- " << rang::fg::green << "pugs exec stats" << rang::fg::reset
-              << " ---------------------\n";
+    os << "----------------- " << rang::fg::green << "pugs exec stats" << rang::fg::reset << " ---------------------\n";
 
-    ExecutionStatManager::getInstance()._printElapseTime();
-    ExecutionStatManager::getInstance()._printTotalCPUTime();
-    ExecutionStatManager::getInstance()._printMaxResidentMemory();
+    ExecutionStatManager::getInstance()._printElapseTime(os);
+    ExecutionStatManager::getInstance()._printTotalCPUTime(os);
+    ExecutionStatManager::getInstance()._printMaxResidentMemory(os);
   }
 }
 
+double
+ExecutionStatManager::getElapseTime() const
+{
+  return m_elapse_time.seconds();
+}
+
+double
+ExecutionStatManager::getCumulativeElapseTime() const
+{
+  return m_previous_cumulative_elapse_time + m_elapse_time.seconds();
+}
+
+double
+ExecutionStatManager::getCumulativeTotalCPUTime() const
+{
+  rusage u;
+  getrusage(RUSAGE_SELF, &u);
+
+  const double total_cpu_time =
+    u.ru_utime.tv_sec + u.ru_stime.tv_sec + (u.ru_utime.tv_usec + u.ru_stime.tv_usec) * 1E-6;
+
+  return m_previous_cumulative_total_cpu_time + parallel::allReduceSum(total_cpu_time);
+}
+
 void
 ExecutionStatManager::create()
 {
diff --git a/src/utils/ExecutionStatManager.hpp b/src/utils/ExecutionStatManager.hpp
index 3738492a1741c7ee7a4e4a45edf4139b579a022e..a99f6e08c239d0b832feb32748d17c0bb61f023c 100644
--- a/src/utils/ExecutionStatManager.hpp
+++ b/src/utils/ExecutionStatManager.hpp
@@ -4,20 +4,29 @@
 #include <utils/PugsAssert.hpp>
 #include <utils/Timer.hpp>
 
+#include <iostream>
+
 class ExecutionStatManager
 {
  private:
+  // For unit tests only
+  friend class ExecutionStatManagerTester;
+
   static ExecutionStatManager* m_instance;
 
   Timer m_elapse_time;
   bool m_do_print = true;
   int m_exit_code = 0;
 
+  size_t m_run_number                         = 1;
+  double m_previous_cumulative_elapse_time    = 0;
+  double m_previous_cumulative_total_cpu_time = 0;
+
   std::string _prettyPrintTime(double seconds) const;
 
-  void _printMaxResidentMemory() const;
-  void _printElapseTime() const;
-  void _printTotalCPUTime() const;
+  void _printMaxResidentMemory(std::ostream& os) const;
+  void _printElapseTime(std::ostream& os) const;
+  void _printTotalCPUTime(std::ostream& os) const;
 
   explicit ExecutionStatManager()                   = default;
   ExecutionStatManager(ExecutionStatManager&&)      = delete;
@@ -25,6 +34,26 @@ class ExecutionStatManager
   ~ExecutionStatManager()                           = default;
 
  public:
+  double getElapseTime() const;
+
+  double getCumulativeElapseTime() const;
+
+  double getCumulativeTotalCPUTime() const;
+
+  PUGS_INLINE
+  void
+  setPreviousCumulativeElapseTime(double cumulative_elapse_time)
+  {
+    m_previous_cumulative_elapse_time = cumulative_elapse_time;
+  }
+
+  PUGS_INLINE
+  void
+  setPreviousCumulativeTotalCPUTime(double cumulative_cpu_time)
+  {
+    m_previous_cumulative_total_cpu_time = cumulative_cpu_time;
+  }
+
   PUGS_INLINE
   bool
   doPrint() const
@@ -39,6 +68,20 @@ class ExecutionStatManager
     m_do_print = do_print;
   }
 
+  PUGS_INLINE
+  size_t
+  runNumber() const
+  {
+    return m_run_number;
+  }
+
+  PUGS_INLINE
+  void
+  setRunNumber(size_t run_number)
+  {
+    m_run_number = run_number;
+  }
+
   PUGS_INLINE
   int
   exitCode() const
@@ -61,7 +104,7 @@ class ExecutionStatManager
     return *m_instance;
   }
 
-  static void printInfo();
+  static void printInfo(std::ostream& os = std::cout);
   static void create();
   static void destroy();
 };
diff --git a/src/utils/Filesystem.hpp b/src/utils/Filesystem.hpp
index 63af52b2e432f1baf55d2cbfd895485a408f7e10..0f74342138ef95bf734b56658bcac34a9f782602 100644
--- a/src/utils/Filesystem.hpp
+++ b/src/utils/Filesystem.hpp
@@ -14,9 +14,11 @@ createDirectoryIfNeeded(const std::string& filename)
     try {
       std::filesystem::create_directories(path);
     }
+    // LCOV_EXCL_START
     catch (std::filesystem::filesystem_error& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 }
 
diff --git a/src/utils/GlobalVariableManager.hpp b/src/utils/GlobalVariableManager.hpp
index f720252fdd7c04b1ddd8e234d890bf5b1956c899..0768a2ab2beb28a9060cadd44ee705f3aba598e2 100644
--- a/src/utils/GlobalVariableManager.hpp
+++ b/src/utils/GlobalVariableManager.hpp
@@ -18,6 +18,13 @@ class GlobalVariableManager
   ~GlobalVariableManager()                            = default;
 
  public:
+  PUGS_INLINE
+  size_t
+  getConnectivityId() const
+  {
+    return m_connectivity_id;
+  }
+
   PUGS_INLINE
   size_t
   getAndIncrementConnectivityId()
@@ -25,6 +32,20 @@ class GlobalVariableManager
     return m_connectivity_id++;
   }
 
+  PUGS_INLINE
+  void
+  setConnectivityId(size_t connectivity_id)
+  {
+    m_connectivity_id = connectivity_id;
+  }
+
+  PUGS_INLINE
+  size_t
+  getMeshId() const
+  {
+    return m_mesh_id;
+  }
+
   PUGS_INLINE
   size_t
   getAndIncrementMeshId()
@@ -32,6 +53,13 @@ class GlobalVariableManager
     return m_mesh_id++;
   }
 
+  PUGS_INLINE
+  void
+  setMeshId(size_t mesh_id)
+  {
+    m_mesh_id = mesh_id;
+  }
+
   PUGS_INLINE
   static GlobalVariableManager&
   instance()
diff --git a/src/utils/HighFivePugsUtils.hpp b/src/utils/HighFivePugsUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e8aa4ca37cc991cddc6a146d8740d8040d7fa74f
--- /dev/null
+++ b/src/utils/HighFivePugsUtils.hpp
@@ -0,0 +1,52 @@
+#ifndef HIGH_FIVE_PUGS_UTILS_HPP
+#define HIGH_FIVE_PUGS_UTILS_HPP
+
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+#include <highfive/highfive.hpp>
+
+template <size_t Dimension, typename T>
+class TinyVector;
+template <size_t M, size_t N, typename T>
+class TinyMatrix;
+
+namespace HighFive
+{
+template <size_t Dimension, typename T>
+class AtomicType<TinyVector<Dimension, T>> : public HighFive::DataType
+{
+ public:
+  inline AtomicType()
+  {
+    hsize_t dim[]     = {Dimension};
+    auto h5_data_type = HighFive::create_datatype<T>();
+    _hid              = H5Tarray_create(h5_data_type.getId(), 1, dim);
+  }
+};
+
+template <size_t M, size_t N, typename T>
+class AtomicType<TinyMatrix<M, N, T>> : public HighFive::DataType
+{
+ public:
+  inline AtomicType()
+  {
+    hsize_t dim[]     = {M, N};
+    auto h5_data_type = HighFive::create_datatype<T>();
+    _hid              = H5Tarray_create(h5_data_type.getId(), 2, dim);
+  }
+};
+
+}   // namespace HighFive
+
+#else
+
+namespace HighFive
+{
+class File;
+class Group;
+}   // namespace HighFive
+
+#endif   // PUGS_HAS_HDF5
+
+#endif   // HIGH_FIVE_PUGS_UTILS_HPP
diff --git a/src/utils/Messenger.cpp b/src/utils/Messenger.cpp
index c66102669709be2f4d5aa19677b212611391ea16..db8ed17a69f180c0e0be39eaa8852ab7e53ed8e2 100644
--- a/src/utils/Messenger.cpp
+++ b/src/utils/Messenger.cpp
@@ -34,12 +34,14 @@ Messenger::Messenger([[maybe_unused]] int& argc, [[maybe_unused]] char* argv[],
   MPI_Init(&argc, &argv);
 
   if (CommunicatorManager::hasSplitColor()) {
+    // LCOV_EXCL_START
     int key = 0;
 
     auto res = MPI_Comm_split(MPI_COMM_WORLD, CommunicatorManager::splitColor(), key, &m_pugs_comm_world);
     if (res) {
       MPI_Abort(MPI_COMM_WORLD, res);
     }
+    // LCOV_EXCL_STOP
   }
 
   m_rank = [&]() {
diff --git a/src/utils/Messenger.hpp b/src/utils/Messenger.hpp
index a04c664d7dcbe36817d2748a6f27e0b8d8dc3481..061fdc7861a4bcce7a824f000e00f0b37bace935 100644
--- a/src/utils/Messenger.hpp
+++ b/src/utils/Messenger.hpp
@@ -770,10 +770,12 @@ class Messenger
       Array s = convert_to_array(data);
       broadcast(s, root_rank);
       if (m_rank != root_rank) {
+        // LCOV_EXCL_START
         data.resize(s.size());
         for (size_t i = 0; i < s.size(); ++i) {
           data[i] = s[i];
         }
+        // LCOV_EXCL_STOP
       }
     } else {
       static_assert(is_false_v<DataType>, "unexpected type of data");
diff --git a/src/utils/PETScWrapper.cpp b/src/utils/PETScWrapper.cpp
index fb7fc932a2b5c3013d3831fb22bbbaa997d09bb0..36285338af948cd72c289aa682df853b46ef57e9 100644
--- a/src/utils/PETScWrapper.cpp
+++ b/src/utils/PETScWrapper.cpp
@@ -16,6 +16,7 @@ initialize([[maybe_unused]] int& argc, [[maybe_unused]] char* argv[])
   PETSC_COMM_WORLD = parallel::Messenger::getInstance().comm();
   PetscOptionsSetValue(NULL, "-no_signal_handler", "true");
   PetscOptionsSetValue(NULL, "-fp_trap", "false");
+  PetscOptionsSetValue(NULL, "-options_left", "false");
   PetscInitialize(&argc, &argv, 0, 0);
 #endif   // PUGS_HAS_PETSC
 }
diff --git a/src/utils/PugsUtils.cpp b/src/utils/PugsUtils.cpp
index f89f543f92bc3a175e4f055b3286e4a45d985396..1ac38b32696417d887e91205715b9fc80d9f81af 100644
--- a/src/utils/PugsUtils.cpp
+++ b/src/utils/PugsUtils.cpp
@@ -13,6 +13,7 @@
 #include <utils/RevisionInfo.hpp>
 #include <utils/SLEPcWrapper.hpp>
 #include <utils/SignalManager.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
 #include <utils/pugs_build_info.hpp>
 
 #include <rang.hpp>
@@ -64,6 +65,7 @@ pugsBuildInfo()
   os << "PETSc:    " << rang::style::bold << BuildInfo::petscLibrary() << rang::style::reset << '\n';
   os << "SLEPc:    " << rang::style::bold << BuildInfo::slepcLibrary() << rang::style::reset << '\n';
   os << "HDF5:     " << rang::style::bold << BuildInfo::hdf5Library() << rang::style::reset << '\n';
+  os << "SLURM:    " << rang::style::bold << BuildInfo::slurmLibrary() << rang::style::reset << '\n';
   os << "-------------------------------------------------------";
 
   return os.str();
@@ -133,6 +135,9 @@ initialize(int& argc, char* argv[])
     app.add_flag("--reproducible-sums,!--no-reproducible-sums", reproducible_sums,
                  "Special treatment of array sums to ensure reproducibility [default: true]");
 
+    bool is_resuming = false;
+    app.add_flag("--resume", is_resuming, "Resume at checkpoint");
+
     app.add_flag("--parallel-output", parallel_output, "All MPI processes output to console [default: false]");
 
     std::map<std::string, ParallelChecker::Mode> pc_mode_map{{"auto", ParallelChecker::Mode::automatic},
@@ -165,6 +170,13 @@ initialize(int& argc, char* argv[])
       CommunicatorManager::setSplitColor(mpi_split_color);
     }
 
+    ResumingManager::getInstance().setIsResuming(is_resuming);
+    if (is_resuming) {
+      ResumingManager::getInstance().setFilename(filename);
+    } else {
+      ResumingManager::getInstance().setFilename("checkpoint.h5");
+    }
+
     ExecutionStatManager::getInstance().setPrint(print_exec_stat);
     BacktraceManager::setShow(show_backtrace);
     ConsoleManager::setShowPreamble(show_preamble);
@@ -223,7 +235,6 @@ initialize(int& argc, char* argv[])
 // LCOV_EXCL_STOP
 
 // LCOV_EXCL_START
-
 // This function cannot be unit-tested: run once when pugs stops
 
 void
diff --git a/src/utils/ReproducibleSumMPI.hpp b/src/utils/ReproducibleSumMPI.hpp
index fcb9bdc1e39db434581937287b24d5b0c8bbaae5..bb466e9db94bc4808ce0ac29130f5ae2df4573c8 100644
--- a/src/utils/ReproducibleSumMPI.hpp
+++ b/src/utils/ReproducibleSumMPI.hpp
@@ -7,6 +7,9 @@
 
 #ifdef PUGS_HAS_MPI
 
+// LCOV_EXCL_START
+// This piece of code is not catchable by coverage...
+
 template <typename RSumType>
 struct ReproducibleMPIReducer
 {
@@ -22,6 +25,8 @@ struct ReproducibleMPIReducer
   }
 };
 
+// LCOV_EXCL_STOP
+
 template <typename ArrayT, typename BoolArrayT>
 auto
 allReduceReproducibleSum(const ReproducibleScalarSum<ArrayT, BoolArrayT>& s)
diff --git a/src/utils/SignalManager.cpp b/src/utils/SignalManager.cpp
index 62505d7b4d171ff3ff9bf207f9e357aada060fda..4b2046a98796fc72cef406f77e7498930089f533 100644
--- a/src/utils/SignalManager.cpp
+++ b/src/utils/SignalManager.cpp
@@ -1,6 +1,6 @@
 #include <utils/SignalManager.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/utils/ParseError.hpp>
 #include <utils/BacktraceManager.hpp>
 #include <utils/ConsoleManager.hpp>
@@ -93,7 +93,7 @@ SignalManager::handler(int signal)
       } else {
         std::ostringstream error_msg;
         error_msg << "received " << signalName(signal);
-        std::cerr << ASTBacktrace::getInstance().errorMessageAt(error_msg.str()) << '\n';
+        std::cerr << ASTExecutionStack::getInstance().errorMessageAt(error_msg.str()) << '\n';
       }
     }
     catch (const IBacktraceError& backtrace_error) {
@@ -101,7 +101,7 @@ SignalManager::handler(int signal)
       std::cerr << rang::fgB::cyan << source_location.file_name() << ':' << source_location.line() << ':'
                 << source_location.column() << ':' << rang::fg::reset << rang::fgB::yellow
                 << " threw the following exception" << rang::fg::reset << "\n\n";
-      std::cerr << ASTBacktrace::getInstance().errorMessageAt(backtrace_error.what()) << '\n';
+      std::cerr << ASTExecutionStack::getInstance().errorMessageAt(backtrace_error.what()) << '\n';
     }
     catch (const ParseError& parse_error) {
       auto p = parse_error.positions().front();
@@ -110,7 +110,7 @@ SignalManager::handler(int signal)
                 << rang::style::reset << '\n';
     }
     catch (const IExitError& exit_error) {
-      std::cerr << ASTBacktrace::getInstance().errorMessageAt(exit_error.what()) << '\n';
+      std::cerr << ASTExecutionStack::getInstance().errorMessageAt(exit_error.what()) << '\n';
     }
     catch (const AssertError& assert_error) {
       std::cerr << assert_error << '\n';
diff --git a/src/utils/Stop.cpp b/src/utils/Stop.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..593094cc311a8d1d72ed687d9a90d3af65f1c881
--- /dev/null
+++ b/src/utils/Stop.cpp
@@ -0,0 +1,52 @@
+#include <utils/Stop.hpp>
+
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/pugs_config.hpp>
+
+#include <filesystem>
+#include <iostream>
+
+#ifdef PUGS_HAS_SLURM
+#include <slurm/slurm.h>
+#endif   // PUGS_HAS_SLURM
+
+bool
+stop()
+{
+  bool stop = false;
+
+  if (parallel::rank() == 0) {
+    std::filesystem::path stop_file("stop");
+    if (std::filesystem::exists(stop_file)) {
+      const double elapse_time = ExecutionStatManager::getInstance().getElapseTime();
+
+      const double stop_file_age =
+        std::chrono::duration_cast<std::chrono::seconds>(std::filesystem::file_time_type::clock::now() -
+                                                         std::filesystem::last_write_time(stop_file))
+          .count();
+
+      stop = elapse_time > stop_file_age;
+    }
+
+#ifdef PUGS_HAS_SLURM
+    // LCOV_EXCL_START
+    char* env = getenv("SLURM_JOB_ID");
+    if (env != nullptr) {
+      slurm_init(nullptr);
+      int slurm_job_id = std::atoi(env);
+
+      if (slurm_get_rem_time(slurm_job_id) < 150) {
+        stop = true;
+      }
+
+      slurm_fini();
+    }
+    // LCOV_EXCL_STOP
+#endif   // PUGS_HAS_SLURM
+  }
+
+  parallel::broadcast(stop, 0);
+
+  return stop;
+}
diff --git a/src/utils/Stop.hpp b/src/utils/Stop.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5ba32eb647ba0080f6170b23554a10d3b9535725
--- /dev/null
+++ b/src/utils/Stop.hpp
@@ -0,0 +1,6 @@
+#ifndef STOP_HPP
+#define STOP_HPP
+
+bool stop();
+
+#endif   // STOP_HPP
diff --git a/src/utils/Timer.hpp b/src/utils/Timer.hpp
index 786d7b789fb02993e2279f5e557c22423663a4e9..76e135d8c9711596f238b4e50c2c73664df31ca4 100644
--- a/src/utils/Timer.hpp
+++ b/src/utils/Timer.hpp
@@ -96,7 +96,7 @@ class Timer
   }
 
   Timer& operator=(const Timer&) = default;
-  Timer& operator=(Timer&&) = default;
+  Timer& operator=(Timer&&)      = default;
 
   Timer(const Timer&) = default;
   Timer(Timer&&)      = default;
@@ -107,6 +107,10 @@ class Timer
       m_status{Status::running}
   {}
 
+  Timer(const std::chrono::time_point<std::chrono::high_resolution_clock>& start)
+    : m_start{start}, m_elapsed_sum{std::chrono::duration<double>::zero()}, m_status{Status::running}
+  {}
+
   ~Timer() = default;
 };
 
diff --git a/src/utils/checkpointing/CMakeLists.txt b/src/utils/checkpointing/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b051975e8538df40a2b9b8a8d7f0486874cda938
--- /dev/null
+++ b/src/utils/checkpointing/CMakeLists.txt
@@ -0,0 +1,69 @@
+# ------------------- Source files --------------------
+
+set(checkpointing_SOURCES)
+
+list(APPEND checkpointing_SOURCES
+  Checkpoint.cpp
+  PrintCheckpointInfo.cpp
+  PrintScriptFrom.cpp
+  Resume.cpp
+  ResumingManager.cpp
+  ResumingUtils.cpp
+  SetResumeFrom.cpp
+)
+
+if(PUGS_HAS_HDF5)
+  list(APPEND checkpointing_SOURCES
+    ReadDiscreteFunctionVariant.cpp
+    ReadIBoundaryConditionDescriptor.cpp
+    ReadIBoundaryDescriptor.cpp
+    ReadIDiscreteFunctionDescriptor.cpp
+    ReadIInterfaceDescriptor.cpp
+    ReadINamedDiscreteData.cpp
+    ReadIQuadratureDescriptor.cpp
+    ReadItemArrayVariant.cpp
+    ReadItemType.cpp
+    ReadItemValueVariant.cpp
+    ReadIWriter.cpp
+    ReadIZoneDescriptor.cpp
+    ReadMesh.cpp
+    ReadOStream.cpp
+    ReadSubItemArrayPerItemVariant.cpp
+    ReadSubItemValuePerItemVariant.cpp
+    ReadVariableBCDescriptor.cpp
+    ResumingData.cpp
+    WriteConnectivity.cpp
+    WriteDiscreteFunctionVariant.cpp
+    WriteIBoundaryConditionDescriptor.cpp
+    WriteIBoundaryDescriptor.cpp
+    WriteIDiscreteFunctionDescriptor.cpp
+    WriteIInterfaceDescriptor.cpp
+    WriteItemArrayVariant.cpp
+    WriteItemType.cpp
+    WriteItemValueVariant.cpp
+    WriteIQuadratureDescriptor.cpp
+    WriteINamedDiscreteData.cpp
+    WriteIWriter.cpp
+    WriteIZoneDescriptor.cpp
+    WriteMesh.cpp
+    WriteOStream.cpp
+    WriteSubItemArrayPerItemVariant.cpp
+    WriteSubItemValuePerItemVariant.cpp
+    WriteVariableBCDescriptor.cpp
+  )
+endif(PUGS_HAS_HDF5)
+
+add_library(
+  PugsCheckpointing
+  ${checkpointing_SOURCES}
+)
+
+# Additional dependencies
+add_dependencies(PugsCheckpointing
+  PugsLanguageAST
+  PugsUtils)
+
+target_link_libraries(
+  PugsCheckpointing
+  ${HIGHFIVE_TARGET}
+)
diff --git a/src/utils/checkpointing/Checkpoint.cpp b/src/utils/checkpointing/Checkpoint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5976cb2fb5b3c5024bc56a1b77d384abd698e97c
--- /dev/null
+++ b/src/utils/checkpointing/Checkpoint.cpp
@@ -0,0 +1,252 @@
+#include <utils/checkpointing/Checkpoint.hpp>
+
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <algebra/LinearSolverOptions.hpp>
+#include <dev/ParallelChecker.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/SymbolTable.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/RandomEngine.hpp>
+#include <utils/checkpointing/DualMeshTypeHFType.hpp>
+#include <utils/checkpointing/LinearSolverOptionsHFType.hpp>
+#include <utils/checkpointing/ParallelCheckerHFType.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+
+#include <iostream>
+#include <map>
+
+void
+checkpoint()
+{
+  try {
+    HighFive::SilenceHDF5 m_silence_hdf5{true};
+    auto create_props = HighFive::FileCreateProps{};
+    create_props.add(HighFive::FileSpaceStrategy(H5F_FSPACE_STRATEGY_FSM_AGGR, true, 0));
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{parallel::Messenger::getInstance().comm(), MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+
+    uint64_t& checkpoint_number = ResumingManager::getInstance().checkpointNumber();
+
+    const auto file_openmode = (checkpoint_number == 0) ? HighFive::File::Truncate : HighFive::File::ReadWrite;
+
+    HighFive::File file(ResumingManager::getInstance().filename(), file_openmode, create_props, fapl);
+
+    std::string checkpoint_name = "checkpoint_" + std::to_string(checkpoint_number);
+
+    std::cout << " * " << rang::fgB::green << "Checkpointing " << rang::fg::reset << "at execution line "
+              << rang::fgB::yellow << ASTExecutionStack::getInstance().currentNode().begin().line << rang::fg::reset
+              << " [using " << rang::fgB::cyan << checkpoint_name << rang::fg::reset << "]\n";
+
+    if (checkpoint_number > 0) {
+      uint64_t resuming_checkpoint_number =
+        file.getGroup("resuming_checkpoint").getAttribute("checkpoint_number").read<uint64_t>();
+      uint64_t last_checkpoint_number =
+        file.getGroup("last_checkpoint").getAttribute("checkpoint_number").read<uint64_t>();
+      for (uint64_t i = resuming_checkpoint_number + 1; i <= last_checkpoint_number; ++i) {
+        std::string old_checkpoint_name = "checkpoint_" + std::to_string(i);
+        std::cout << "   - removing deprecated " << rang::fgB::yellow << old_checkpoint_name << rang::fg::reset << '\n';
+        if (file.exist(old_checkpoint_name)) {
+          file.unlink(old_checkpoint_name);
+        }
+      }
+    }
+
+    HighFive::Group checkpoint = file.createGroup(checkpoint_name);
+
+    uint64_t checkpoint_id =
+      ASTCheckpointsInfo::getInstance().getCheckpointId((ASTExecutionStack::getInstance().currentNode()));
+
+    std::string time = [] {
+      std::ostringstream os;
+      auto t = std::time(nullptr);
+      os << std::put_time(std::localtime(&t), "%c");
+      return os.str();
+    }();
+
+    checkpoint.createAttribute("creation_date", time);
+    checkpoint.createAttribute("name", checkpoint_name);
+    checkpoint.createAttribute("id", checkpoint_id);
+    checkpoint.createAttribute("data.pgs", ASTExecutionStack::getInstance().fileContent());
+
+    checkpoint.createAttribute("checkpoint_number", checkpoint_number);
+    ++checkpoint_number;
+
+    {
+      HighFive::Group global_variables_group = checkpoint.createGroup("singleton/global_variables");
+      global_variables_group.createAttribute("connectivity_id", GlobalVariableManager::instance().getConnectivityId());
+      global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+    }
+    {
+      HighFive::Group random_seed_group = checkpoint.createGroup("singleton/random_seed");
+      random_seed_group.createAttribute("current_seed", RandomEngine::instance().getCurrentSeed());
+    }
+    {
+      HighFive::Group execution_info_group = checkpoint.createGroup("singleton/execution_info");
+      execution_info_group.createAttribute("run_number", ExecutionStatManager::getInstance().runNumber());
+      execution_info_group.createAttribute("cumulative_elapse_time",
+                                           ExecutionStatManager::getInstance().getCumulativeElapseTime());
+      execution_info_group.createAttribute("cumulative_total_cpu_time",
+                                           ExecutionStatManager::getInstance().getCumulativeTotalCPUTime());
+    }
+    {
+      HighFive::Group parallel_checker_group = checkpoint.createGroup("singleton/parallel_checker");
+      parallel_checker_group.createAttribute("tag", ParallelChecker::instance().tag());
+      parallel_checker_group.createAttribute("mode", ParallelChecker::instance().mode());
+    }
+    {
+      HighFive::Group linear_solver_options_default_group =
+        checkpoint.createGroup("singleton/linear_solver_options_default");
+
+      const LinearSolverOptions& default_options = LinearSolverOptions::default_options;
+
+      linear_solver_options_default_group.createAttribute("epsilon", default_options.epsilon());
+      linear_solver_options_default_group.createAttribute("maximum_iteration", default_options.maximumIteration());
+      linear_solver_options_default_group.createAttribute("verbose", default_options.verbose());
+
+      linear_solver_options_default_group.createAttribute("library", default_options.library());
+      linear_solver_options_default_group.createAttribute("method", default_options.method());
+      linear_solver_options_default_group.createAttribute("precond", default_options.precond());
+    }
+    {
+      const auto& primal_to_dual_connectivity_info_map =
+        DualConnectivityManager::instance().primalToDualConnectivityInfoMap();
+      for (auto&& primal_to_dual_connectivity_info : primal_to_dual_connectivity_info_map) {
+        const DualMeshType type_of_dual     = primal_to_dual_connectivity_info.first.first;
+        const size_t primal_connectivity_id = primal_to_dual_connectivity_info.first.second->id();
+        const size_t dual_connectivity_id   = primal_to_dual_connectivity_info.second.dualConnectivity()->id();
+
+        HighFive::Group connectivity_group =
+          checkpoint.createGroup("connectivity/" + std::to_string(dual_connectivity_id));
+        connectivity_group.createAttribute("type", std::string{"dual_connectivity"});
+        connectivity_group.createAttribute("type_of_dual", type_of_dual);
+        connectivity_group.createAttribute("id", dual_connectivity_id);
+        connectivity_group.createAttribute("primal_connectivity_id", primal_connectivity_id);
+      }
+    }
+    {
+      const auto& primal_to_dual_mesh_info_map = DualMeshManager::instance().primalToDualMeshMap();
+      for (auto&& primal_to_dual_mesh_info : primal_to_dual_mesh_info_map) {
+        const DualMeshType type_of_dual = primal_to_dual_mesh_info.first.first;
+        const size_t primal_mesh_id     = primal_to_dual_mesh_info.first.second;
+        const size_t dual_mesh_id       = primal_to_dual_mesh_info.second->id();
+
+        HighFive::Group connectivity_group = checkpoint.createGroup("mesh/" + std::to_string(dual_mesh_id));
+        connectivity_group.createAttribute("type", std::string{"dual_mesh"});
+        connectivity_group.createAttribute("type_of_dual", type_of_dual);
+        connectivity_group.createAttribute("id", dual_mesh_id);
+        connectivity_group.createAttribute("primal_mesh_id", primal_mesh_id);
+      }
+    }
+
+    std::shared_ptr<const SymbolTable> p_symbol_table = ASTExecutionStack::getInstance().currentNode().m_symbol_table;
+    auto symbol_table_group                           = checkpoint;
+    size_t symbol_table_id                            = 0;
+    while (p_symbol_table.use_count() > 0) {
+      symbol_table_group = symbol_table_group.createGroup("symbol table");
+
+      const SymbolTable& symbol_table = *p_symbol_table;
+
+      const auto& symbol_list = symbol_table.symbolList();
+
+      for (auto& symbol : symbol_list) {
+        switch (symbol.attributes().dataType()) {
+        case ASTNodeDataType::builtin_function_t:
+        case ASTNodeDataType::type_name_id_t: {
+          break;
+        }
+        case ASTNodeDataType::function_t: {
+          HighFive::Group function_group = checkpoint.createGroup("functions/" + symbol.name());
+          function_group.createAttribute("id", std::get<size_t>(symbol.attributes().value()));
+          function_group.createAttribute("symbol_table_id", symbol_table_id);
+          break;
+        }
+        default: {
+          if ((symbol_table.has(symbol.name(), ASTExecutionStack::getInstance().currentNode().begin())) and
+              (not symbol.attributes().isModuleVariable())) {
+            std::visit(
+              [&](auto&& data) {
+                using DataT = std::decay_t<decltype(data)>;
+                if constexpr (std::is_same_v<DataT, std::monostate>) {
+                } else if constexpr ((std::is_arithmetic_v<DataT>) or (std::is_same_v<DataT, std::string>) or
+                                     (is_tiny_vector_v<DataT>) or (is_tiny_matrix_v<DataT>)) {
+                  symbol_table_group.createAttribute(symbol.name(), data);
+                } else if constexpr (std::is_same_v<DataT, EmbeddedData>) {
+                  CheckpointResumeRepository::instance().checkpoint(symbol.attributes().dataType(), symbol.name(), data,
+                                                                    file, checkpoint, symbol_table_group);
+                } else if constexpr (is_std_vector_v<DataT>) {
+                  using value_type = typename DataT::value_type;
+                  if constexpr ((std::is_arithmetic_v<value_type>) or (std::is_same_v<value_type, std::string>) or
+                                (is_tiny_vector_v<value_type>) or (is_tiny_matrix_v<value_type>)) {
+                    symbol_table_group.createAttribute(symbol.name(), data);
+                  } else if constexpr (std::is_same_v<value_type, EmbeddedData>) {
+                    symbol_table_group.createGroup("embedded/" + symbol.name())
+                      .createAttribute("type", dataTypeName(symbol.attributes().dataType()));
+                    for (size_t i = 0; i < data.size(); ++i) {
+                      CheckpointResumeRepository::instance().checkpoint(symbol.attributes().dataType().contentType(),
+                                                                        symbol.name() + "/" + std::to_string(i),
+                                                                        data[i], file, checkpoint, symbol_table_group);
+                    }
+                  } else {
+                    // LCOV_EXCL_START
+                    throw UnexpectedError("unexpected data type");
+                    // LCOV_EXCL_STOP
+                  }
+                } else {
+                  // LCOV_EXCL_START
+                  throw UnexpectedError("unexpected data type");
+                  // LCOV_EXCL_STOP
+                }
+              },
+              symbol.attributes().value());
+          }
+        }
+        }
+      }
+
+      p_symbol_table = symbol_table.parentTable();
+      ++symbol_table_id;
+    }
+
+    if (file.exist("last_checkpoint")) {
+      file.unlink("last_checkpoint");
+    }
+    file.createHardLink("last_checkpoint", checkpoint);
+
+    if (file.exist("resuming_checkpoint")) {
+      file.unlink("resuming_checkpoint");
+    }
+    file.createHardLink("resuming_checkpoint", checkpoint);
+  }
+  // LCOV_EXCL_START
+  catch (HighFive::Exception& e) {
+    throw NormalError(e.what());
+  }
+  // LCOV_EXCL_STOP
+}
+
+#else   // PUGS_HAS_HDF5
+
+#include <utils/Exceptions.hpp>
+
+void
+checkpoint()
+{
+  throw NormalError("checkpoint/resume mechanism requires HDF5");
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/src/utils/checkpointing/Checkpoint.hpp b/src/utils/checkpointing/Checkpoint.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d5c4e910021128339d1e52d1ac9f471b64094fd3
--- /dev/null
+++ b/src/utils/checkpointing/Checkpoint.hpp
@@ -0,0 +1,6 @@
+#ifndef CHECKPOINT_HPP
+#define CHECKPOINT_HPP
+
+void checkpoint();
+
+#endif   // CHECKPOINT_HPP
diff --git a/src/utils/checkpointing/DiscreteFunctionTypeHFType.hpp b/src/utils/checkpointing/DiscreteFunctionTypeHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c9940f248c85a761761e661fa2c068872131b04b
--- /dev/null
+++ b/src/utils/checkpointing/DiscreteFunctionTypeHFType.hpp
@@ -0,0 +1,14 @@
+#ifndef DISCRETE_FUNCTION_TYPE_HF_TYPE_HPP
+#define DISCRETE_FUNCTION_TYPE_HF_TYPE_HPP
+
+#include <scheme/DiscreteFunctionType.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+HighFive::EnumType<DiscreteFunctionType> PUGS_INLINE
+create_enum_discrete_function_type()
+{
+  return {{"P0", DiscreteFunctionType::P0}, {"P0Vector", DiscreteFunctionType::P0Vector}};
+}
+HIGHFIVE_REGISTER_TYPE(DiscreteFunctionType, create_enum_discrete_function_type)
+
+#endif   // DISCRETE_FUNCTION_TYPE_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/DualMeshTypeHFType.hpp b/src/utils/checkpointing/DualMeshTypeHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ef742b231763d53e66c9a57bfb9765c16fa6eb54
--- /dev/null
+++ b/src/utils/checkpointing/DualMeshTypeHFType.hpp
@@ -0,0 +1,14 @@
+#ifndef DUAL_MESH_TYPE_HF_TYPE_HPP
+#define DUAL_MESH_TYPE_HF_TYPE_HPP
+
+#include <mesh/DualConnectivityManager.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+HighFive::EnumType<DualMeshType> PUGS_INLINE
+create_enum_DualMeshType_type()
+{
+  return {{"dual1d", DualMeshType::Dual1D}, {"diamond", DualMeshType::Diamond}, {"median", DualMeshType::Median}};
+}
+HIGHFIVE_REGISTER_TYPE(DualMeshType, create_enum_DualMeshType_type)
+
+#endif   // DUAL_MESH_TYPE_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp b/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..210b521164075f275829865397ea1771a460c207
--- /dev/null
+++ b/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp
@@ -0,0 +1,26 @@
+#ifndef I_BOUNDARY_CONDITION_DESCRIPTOR_HF_TYPE_HPP
+#define I_BOUNDARY_CONDITION_DESCRIPTOR_HF_TYPE_HPP
+
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<IBoundaryConditionDescriptor::Type> PUGS_INLINE
+create_enum_i_boundary_condition_descriptor_type()
+{
+  return {{"axis", IBoundaryConditionDescriptor::Type::axis},
+          {"dirichlet", IBoundaryConditionDescriptor::Type::dirichlet},
+          {"external", IBoundaryConditionDescriptor::Type::external},
+          {"fixed", IBoundaryConditionDescriptor::Type::fixed},
+          {"fourier", IBoundaryConditionDescriptor::Type::fourier},
+          {"free", IBoundaryConditionDescriptor::Type::free},
+          {"inflow", IBoundaryConditionDescriptor::Type::inflow},
+          {"inflow_list", IBoundaryConditionDescriptor::Type::inflow_list},
+          {"neumann", IBoundaryConditionDescriptor::Type::neumann},
+          {"outflow", IBoundaryConditionDescriptor::Type::outflow},
+          {"symmetry", IBoundaryConditionDescriptor::Type::symmetry},
+          {"wall", IBoundaryConditionDescriptor::Type::wall}};
+}
+HIGHFIVE_REGISTER_TYPE(IBoundaryConditionDescriptor::Type, create_enum_i_boundary_condition_descriptor_type)
+
+#endif   // I_BOUNDARY_CONDITION_DESCRIPTOR_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/IBoundaryDescriptorHFType.hpp b/src/utils/checkpointing/IBoundaryDescriptorHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..495ace8acdd5fc10180827f3c63420157981556c
--- /dev/null
+++ b/src/utils/checkpointing/IBoundaryDescriptorHFType.hpp
@@ -0,0 +1,15 @@
+#ifndef I_BOUNDARY_DESCRIPTOR_HF_TYPE_HPP
+#define I_BOUNDARY_DESCRIPTOR_HF_TYPE_HPP
+
+#include <mesh/IBoundaryDescriptor.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<IBoundaryDescriptor::Type> PUGS_INLINE
+create_enum_i_boundary_descriptor_type()
+{
+  return {{"named", IBoundaryDescriptor::Type::named}, {"numbered", IBoundaryDescriptor::Type::numbered}};
+}
+HIGHFIVE_REGISTER_TYPE(IBoundaryDescriptor::Type, create_enum_i_boundary_descriptor_type)
+
+#endif   // I_BOUNDARY_DESCRIPTOR_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/IInterfaceDescriptorHFType.hpp b/src/utils/checkpointing/IInterfaceDescriptorHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9f170b0e769b03f3c92679b5b0928af1e5a6267a
--- /dev/null
+++ b/src/utils/checkpointing/IInterfaceDescriptorHFType.hpp
@@ -0,0 +1,15 @@
+#ifndef I_INTERFACE_DESCRIPTOR_HF_TYPE_HPP
+#define I_INTERFACE_DESCRIPTOR_HF_TYPE_HPP
+
+#include <mesh/IInterfaceDescriptor.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<IInterfaceDescriptor::Type> PUGS_INLINE
+create_enum_i_interface_descriptor_type()
+{
+  return {{"named", IInterfaceDescriptor::Type::named}, {"numbered", IInterfaceDescriptor::Type::numbered}};
+}
+HIGHFIVE_REGISTER_TYPE(IInterfaceDescriptor::Type, create_enum_i_interface_descriptor_type)
+
+#endif   // I_INTERFACE_DESCRIPTOR_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/INamedDiscreteDataHFType.hpp b/src/utils/checkpointing/INamedDiscreteDataHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0f35ccceca6541f13ec81d2c269ba596618d4195
--- /dev/null
+++ b/src/utils/checkpointing/INamedDiscreteDataHFType.hpp
@@ -0,0 +1,17 @@
+#ifndef I_NAMED_DISCRETE_DATA_HF_HPP
+#define I_NAMED_DISCRETE_DATA_HF_HPP
+
+#include <output/INamedDiscreteData.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<INamedDiscreteData::Type> PUGS_INLINE
+create_enum_i_named_discrete_data_type()
+{
+  return {{"discrete_function", INamedDiscreteData::Type::discrete_function},
+          {"item_array", INamedDiscreteData::Type::item_array},
+          {"item_value", INamedDiscreteData::Type::item_value}};
+}
+HIGHFIVE_REGISTER_TYPE(INamedDiscreteData::Type, create_enum_i_named_discrete_data_type)
+
+#endif   // I_NAMED_DISCRETE_DATA_HF_HPP
diff --git a/src/utils/checkpointing/IWriterHFType.hpp b/src/utils/checkpointing/IWriterHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..046998283e0c4a8567e1624741ddd920108413e7
--- /dev/null
+++ b/src/utils/checkpointing/IWriterHFType.hpp
@@ -0,0 +1,18 @@
+#ifndef I_WRITER_HF_TYPE_HPP
+#define I_WRITER_HF_TYPE_HPP
+
+#include <output/IWriter.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<IWriter::Type> PUGS_INLINE
+create_enum_i_writer_type()
+{
+  return {{"gnuplot", IWriter::Type::gnuplot},
+          {"gnuplot_1d", IWriter::Type::gnuplot_1d},
+          {"gnuplot_raw", IWriter::Type::gnuplot_raw},
+          {"vtk", IWriter::Type::vtk}};
+}
+HIGHFIVE_REGISTER_TYPE(IWriter::Type, create_enum_i_writer_type)
+
+#endif   // I_WRITER_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/IZoneDescriptorHFType.hpp b/src/utils/checkpointing/IZoneDescriptorHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ffed7f4d02620b3aae3117cee945537a4164d952
--- /dev/null
+++ b/src/utils/checkpointing/IZoneDescriptorHFType.hpp
@@ -0,0 +1,15 @@
+#ifndef I_ZONE_DESCRIPTOR_HF_TYPE_HPP
+#define I_ZONE_DESCRIPTOR_HF_TYPE_HPP
+
+#include <mesh/IZoneDescriptor.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<IZoneDescriptor::Type> PUGS_INLINE
+create_enum_i_zone_descriptor_type()
+{
+  return {{"named", IZoneDescriptor::Type::named}, {"numbered", IZoneDescriptor::Type::numbered}};
+}
+HIGHFIVE_REGISTER_TYPE(IZoneDescriptor::Type, create_enum_i_zone_descriptor_type)
+
+#endif   // I_ZONE_DESCRIPTOR_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/ItemTypeHFType.hpp b/src/utils/checkpointing/ItemTypeHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2b7db0ee4061ee6a9695023ecce893710ba99f82
--- /dev/null
+++ b/src/utils/checkpointing/ItemTypeHFType.hpp
@@ -0,0 +1,15 @@
+#ifndef ITEM_TYPE_HF_TYPE_HPP
+#define ITEM_TYPE_HF_TYPE_HPP
+
+#include <mesh/ItemType.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<ItemType> PUGS_INLINE
+create_enum_item_type()
+{
+  return {{"node", ItemType::node}, {"edge", ItemType::edge}, {"face", ItemType::face}, {"cell", ItemType::cell}};
+}
+HIGHFIVE_REGISTER_TYPE(ItemType, create_enum_item_type)
+
+#endif   // ITEM_TYPE_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/LinearSolverOptionsHFType.hpp b/src/utils/checkpointing/LinearSolverOptionsHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..94f88fc60088c297479af36975d533d1c94bdd71
--- /dev/null
+++ b/src/utils/checkpointing/LinearSolverOptionsHFType.hpp
@@ -0,0 +1,38 @@
+#ifndef LINEAR_SOLVER_OPTIONS_HF_TYPE_HPP
+#define LINEAR_SOLVER_OPTIONS_HF_TYPE_HPP
+
+#include <algebra/LinearSolverOptions.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<LSLibrary> PUGS_INLINE
+create_enum_LSOptions_library_type()
+{
+  return {{"builtin", LSLibrary::builtin}, {"petsc", LSLibrary::petsc}};
+}
+HIGHFIVE_REGISTER_TYPE(LSLibrary, create_enum_LSOptions_library_type)
+
+HighFive::EnumType<LSMethod> PUGS_INLINE
+create_enum_LSOptions_method_type()
+{
+  return {{"cg", LSMethod::cg},
+          {"bicgstab", LSMethod::bicgstab},
+          {"bicgstab2", LSMethod::bicgstab2},
+          {"gmres", LSMethod::gmres},
+          {"lu", LSMethod::lu},
+          {"cholesky", LSMethod::cholesky}};
+}
+HIGHFIVE_REGISTER_TYPE(LSMethod, create_enum_LSOptions_method_type)
+
+HighFive::EnumType<LSPrecond> PUGS_INLINE
+create_enum_LSOptions_precond_type()
+{
+  return {{"none", LSPrecond::none},
+          {"diagonal", LSPrecond::diagonal},
+          {"incomplete_cholesky", LSPrecond::incomplete_cholesky},
+          {"incomplete_LU", LSPrecond::incomplete_LU},
+          {"amg", LSPrecond::amg}};
+}
+HIGHFIVE_REGISTER_TYPE(LSPrecond, create_enum_LSOptions_precond_type)
+
+#endif   // LINEAR_SOLVER_OPTIONS_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/OStreamTypeHFType.hpp b/src/utils/checkpointing/OStreamTypeHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..71ced52e7ed58d28f0e8fccd687a8534447b7ce5
--- /dev/null
+++ b/src/utils/checkpointing/OStreamTypeHFType.hpp
@@ -0,0 +1,15 @@
+#ifndef OSTREAM_TYPE_HF_TYPE_HPP
+#define OSTREAM_TYPE_HF_TYPE_HPP
+
+#include <language/utils/OStream.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<OStream::Type> PUGS_INLINE
+create_enum_ostream_type()
+{
+  return {{"std_ostream", OStream::Type::std_ostream}, {"std_ofstream", OStream::Type::std_ofstream}};
+}
+HIGHFIVE_REGISTER_TYPE(OStream::Type, create_enum_ostream_type)
+
+#endif   // OSTREAM_TYPE_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/ParallelCheckerHFType.hpp b/src/utils/checkpointing/ParallelCheckerHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..288326d60ccba261fc586d920b1b5f2c67895133
--- /dev/null
+++ b/src/utils/checkpointing/ParallelCheckerHFType.hpp
@@ -0,0 +1,16 @@
+#ifndef PARALLEL_CHECKER_HF_TYPE_HPP
+#define PARALLEL_CHECKER_HF_TYPE_HPP
+
+#include <dev/ParallelChecker.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+HighFive::EnumType<ParallelChecker::Mode> PUGS_INLINE
+create_enum_ParallelChecker_mode()
+{
+  return {{"automatic", ParallelChecker::Mode::automatic},
+          {"read", ParallelChecker::Mode::read},
+          {"write", ParallelChecker::Mode::write}};
+}
+HIGHFIVE_REGISTER_TYPE(ParallelChecker::Mode, create_enum_ParallelChecker_mode)
+
+#endif   // PARALLEL_CHECKER_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/PrintCheckpointInfo.cpp b/src/utils/checkpointing/PrintCheckpointInfo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc7cdcfdd1730921d950318b727cb14f4825c0e0
--- /dev/null
+++ b/src/utils/checkpointing/PrintCheckpointInfo.cpp
@@ -0,0 +1,193 @@
+#include <utils/checkpointing/PrintCheckpointInfo.hpp>
+
+#include <utils/Exceptions.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/pugs_config.hpp>
+
+#include <rang.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <utils/HighFivePugsUtils.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+
+#include <iomanip>
+#include <regex>
+
+#endif   // PUGS_HAS_HDF5
+
+#ifdef PUGS_HAS_HDF5
+
+template <typename T>
+void
+printAttributeValue(const HighFive::Attribute& attribute, std::ostream& os)
+{
+  std::string delim = "";
+
+  if constexpr (std::is_same_v<T, std::string>) {
+    delim = "\"";
+  }
+
+  HighFive::DataSpace data_space = attribute.getSpace();
+  if (data_space.getNumberDimensions() == 0) {
+    os << std::boolalpha << delim << attribute.read<T>() << delim;
+  } else if (data_space.getNumberDimensions() == 1) {
+    std::vector value = attribute.read<std::vector<T>>();
+    if (value.size() > 0) {
+      os << '(' << std::boolalpha << delim << value[0] << delim;
+      for (size_t i = 1; i < value.size(); ++i) {
+        os << ", " << std::boolalpha << delim << value[i] << delim;
+      }
+      os << ')';
+    }
+  }
+}
+
+void
+printCheckpointInfo(const std::string& filename, std::ostream& os)
+{
+  if (parallel::rank() == 0) {
+    try {
+      HighFive::SilenceHDF5 m_silence_hdf5{true};
+      HighFive::File file(filename, HighFive::File::ReadOnly);
+
+      std::map<size_t, std::string> checkpoint_name_list;
+
+      for (auto name : file.listObjectNames()) {
+        std::smatch number_string;
+        const std::regex checkpoint_regex("checkpoint_([0-9]+)");
+        if (std::regex_match(name, number_string, checkpoint_regex)) {
+          std::stringstream ss;
+          ss << number_string[1].str();
+
+          size_t id = 0;
+          ss >> id;
+
+          checkpoint_name_list[id] = name;
+        }
+      }
+
+      for (auto&& [id, checkpoint_name] : checkpoint_name_list) {
+        HighFive::Group checkpoint      = file.getGroup(checkpoint_name);
+        const std::string creation_date = checkpoint.getAttribute("creation_date").read<std::string>();
+
+        os << rang::fgB::yellow << " * " << rang::fg::reset << rang::fgB::magenta << checkpoint_name << rang::fg::reset
+           << " [" << rang::fg::green << creation_date << rang::fg::reset << "]\n";
+
+        HighFive::Group saved_symbol_table = checkpoint.getGroup("symbol table");
+
+        bool finished = true;
+        do {
+          finished = true;
+
+          for (auto symbol_name : saved_symbol_table.listAttributeNames()) {
+            HighFive::Attribute attribute = saved_symbol_table.getAttribute(symbol_name);
+            HighFive::DataType data_type  = attribute.getDataType();
+
+            os << "   ";
+            os << std::setw(25) << std::setfill('.') << std::left << symbol_name + ' ' << std::setfill(' ');
+            os << ' ';
+
+            switch (data_type.getClass()) {
+            case HighFive::DataTypeClass::Float: {
+              printAttributeValue<double>(attribute, os);
+              break;
+            }
+            case HighFive::DataTypeClass::Integer: {
+              if (data_type == HighFive::AtomicType<uint64_t>()) {
+                printAttributeValue<uint64_t>(attribute, os);
+              } else if (data_type == HighFive::AtomicType<int64_t>()) {
+                printAttributeValue<int64_t>(attribute, os);
+              }
+              break;
+            }
+            case HighFive::DataTypeClass::Array: {
+              HighFive::DataSpace data_space = attribute.getSpace();
+
+              if (data_type == HighFive::AtomicType<TinyVector<1>>()) {
+                printAttributeValue<TinyVector<1>>(attribute, os);
+              } else if (data_type == HighFive::AtomicType<TinyVector<2>>()) {
+                printAttributeValue<TinyVector<2>>(attribute, os);
+              } else if (data_type == HighFive::AtomicType<TinyVector<3>>()) {
+                printAttributeValue<TinyVector<3>>(attribute, os);
+              } else if (data_type == HighFive::AtomicType<TinyMatrix<1>>()) {
+                printAttributeValue<TinyMatrix<1>>(attribute, os);
+              } else if (data_type == HighFive::AtomicType<TinyMatrix<2>>()) {
+                printAttributeValue<TinyMatrix<2>>(attribute, os);
+              } else if (data_type == HighFive::AtomicType<TinyMatrix<3>>()) {
+                printAttributeValue<TinyMatrix<3>>(attribute, os);
+              }
+              break;
+            }
+            case HighFive::DataTypeClass::Enum: {
+              if (data_type == HighFive::create_datatype<bool>()) {
+                printAttributeValue<bool>(attribute, os);
+              } else {
+                os << "????";   // LCOV_EXCL_LINE
+              }
+              break;
+            }
+            case HighFive::DataTypeClass::String: {
+              printAttributeValue<std::string>(attribute, os);
+              break;
+            }
+              // LCOV_EXCL_START
+            default: {
+              std::ostringstream error_msg;
+              error_msg << "invalid data type class '" << rang::fgB::yellow << data_type.string() << rang::fg::reset
+                        << "' for symbol " << rang::fgB::cyan << symbol_name << rang::fg::reset;
+              throw UnexpectedError(error_msg.str());
+            }
+              // LCOV_EXCL_STOP
+            }
+
+            os << rang::style::reset << '\n';
+          }
+
+          if (saved_symbol_table.exist("embedded")) {
+            HighFive::Group embedded_data_list = saved_symbol_table.getGroup("embedded");
+            for (auto name : embedded_data_list.listObjectNames()) {
+              os << "   ";
+              os << std::setw(25) << std::setfill('.') << std::left << name + ' ' << std::setfill(' ');
+              os << ' ';
+              os << embedded_data_list.getGroup(name).getAttribute("type").read<std::string>() << '\n';
+            }
+          }
+
+          const bool saved_symbol_table_has_parent = saved_symbol_table.exist("symbol table");
+
+          if (saved_symbol_table_has_parent) {
+            saved_symbol_table = saved_symbol_table.getGroup("symbol table");
+
+            finished = false;
+          }
+
+        } while (not finished);
+      }
+      os << "-------------------------------------------------------\n";
+      for (auto path : std::array{"resuming_checkpoint", "last_checkpoint"}) {
+        os << rang::fgB::yellow << " * " << rang::fg::reset << rang::style::bold << path << rang::style::reset << " -> "
+           << rang::fgB::green << file.getGroup(path).getAttribute("name").read<std::string>() << rang::style::reset
+           << '\n';
+      }
+    }
+    // LCOV_EXCL_START
+    catch (HighFive::Exception& e) {
+      std::cerr << rang::fgB::red << "error: " << rang::fg::reset << rang::style::bold << e.what() << rang::style::reset
+                << '\n';
+    }
+    // LCOV_EXCL_STOP
+  }
+}
+
+#else   // PUGS_HAS_HDF5
+
+void
+printCheckpointInfo(const std::string&, std::ostream&)
+{
+  std::cerr << rang::fgB::red << "error: " << rang::fg::reset << "checkpoint info requires HDF5\n";
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/src/utils/checkpointing/PrintCheckpointInfo.hpp b/src/utils/checkpointing/PrintCheckpointInfo.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fcfb800622fcc2b03ab2770c3abc6c3a23bf8fc8
--- /dev/null
+++ b/src/utils/checkpointing/PrintCheckpointInfo.hpp
@@ -0,0 +1,9 @@
+#ifndef PRINT_CHECKPOINT_INFO_HPP
+#define PRINT_CHECKPOINT_INFO_HPP
+
+#include <iostream>
+#include <string>
+
+void printCheckpointInfo(const std::string& filename, std::ostream& os = std::cout);
+
+#endif   // PRINT_CHECKPOINT_INFO_HPP
diff --git a/src/utils/checkpointing/PrintScriptFrom.cpp b/src/utils/checkpointing/PrintScriptFrom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41c80c1b767945db0ade360d84bce4fbee8d62bf
--- /dev/null
+++ b/src/utils/checkpointing/PrintScriptFrom.cpp
@@ -0,0 +1,44 @@
+#include <utils/checkpointing/PrintScriptFrom.hpp>
+
+#include <rang.hpp>
+#include <utils/pugs_config.hpp>
+
+#include <iostream>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <utils/Exceptions.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+void
+printScriptFrom(const std::string& filename, const uint64_t& checkpoint_number, std::ostream& os)
+{
+  try {
+    HighFive::SilenceHDF5 m_silence_hdf5{true};
+    HighFive::File file(filename, HighFive::File::ReadWrite);
+    const std::string checkpoint_name = "checkpoint_" + std::to_string(checkpoint_number);
+
+    if (not file.exist(checkpoint_name)) {
+      std::ostringstream error_msg;
+      error_msg << "cannot find checkpoint " << rang::fgB::magenta << checkpoint_number << rang::fg::reset << " in "
+                << rang::fgB::yellow << filename << rang::fg::reset;
+      throw NormalError(error_msg.str());
+    }
+
+    HighFive::Group checkpoint = file.getGroup(checkpoint_name);
+    os << checkpoint.getAttribute("data.pgs").read<std::string>();
+  }
+  catch (HighFive::Exception& e) {
+    throw NormalError(e.what());
+  }
+}
+
+#else   // PUGS_HAS_HDF5
+
+void
+printScriptFrom(const std::string&, const uint64_t&, std::ostream&)
+{
+  std::cerr << rang::fgB::red << "error: " << rang::fg::reset << "printing  checkpoint's script requires HDF5\n";
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/src/utils/checkpointing/PrintScriptFrom.hpp b/src/utils/checkpointing/PrintScriptFrom.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..416a0c76dc5126e19699d22bba5926050f9bbb33
--- /dev/null
+++ b/src/utils/checkpointing/PrintScriptFrom.hpp
@@ -0,0 +1,10 @@
+#ifndef PRINT_SCRIPT_FROM_HPP
+#define PRINT_SCRIPT_FROM_HPP
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+void printScriptFrom(const std::string& filename, const uint64_t& checkpoint_number, std::ostream& os = std::cout);
+
+#endif   // PRINT_SCRIPT_FROM_HPP
diff --git a/src/utils/checkpointing/QuadratureTypeHFType.hpp b/src/utils/checkpointing/QuadratureTypeHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bba68e822a74237a9e540a5bb5fd3c65714c3976
--- /dev/null
+++ b/src/utils/checkpointing/QuadratureTypeHFType.hpp
@@ -0,0 +1,17 @@
+#ifndef QUADRATURE_TYPE_HF_TYPE_HPP
+#define QUADRATURE_TYPE_HF_TYPE_HPP
+
+#include <analysis/QuadratureType.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<QuadratureType> PUGS_INLINE
+create_enum_quadrature_descriptor_type()
+{
+  return {{"gauss", QuadratureType::Gauss},
+          {"gauss-legendre", QuadratureType::GaussLegendre},
+          {"gauss-lobatto", QuadratureType::GaussLobatto}};
+}
+HIGHFIVE_REGISTER_TYPE(QuadratureType, create_enum_quadrature_descriptor_type)
+
+#endif   // QUADRATURE_TYPE_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/ReadArray.hpp b/src/utils/checkpointing/ReadArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5aa934e71bf4b7e9e8c49127c4851aaae47b929
--- /dev/null
+++ b/src/utils/checkpointing/ReadArray.hpp
@@ -0,0 +1,52 @@
+#ifndef READ_ARRAY_HPP
+#define READ_ARRAY_HPP
+
+#include <mesh/CellType.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+
+namespace checkpointing
+{
+
+template <typename DataType>
+PUGS_INLINE Array<DataType>
+readArray(const HighFive::Group& group, const std::string& name)
+{
+  auto get_address = [](auto& x) { return (x.size() > 0) ? &(x[0]) : nullptr; };
+
+  using data_type = std::remove_const_t<DataType>;
+
+  auto dataset = group.getDataSet(name);
+
+  std::vector<size_t> size_per_rank = dataset.getAttribute("size_per_rank").read<std::vector<size_t>>();
+
+  if (size_per_rank.size() != parallel::size()) {
+    throw NormalError("cannot change number of processes");   // LCOV_EXCL_LINE
+  }
+
+  std::vector<size_t> offset = {0, 0ul};
+  for (size_t i = 0; i < parallel::rank(); ++i) {
+    offset[0] += size_per_rank[i];   // LCOV_EXCL_LINE
+  }
+  std::vector<size_t> count = {size_per_rank[parallel::rank()]};
+
+  Array<DataType> array(size_per_rank[parallel::rank()]);
+  if constexpr (std::is_same_v<CellType, data_type>) {
+    using base_type = std::underlying_type_t<CellType>;
+    dataset.select(offset, count).read_raw(reinterpret_cast<base_type*>(get_address(array)));
+  } else if constexpr ((std::is_same_v<CellId, data_type>) or (std::is_same_v<FaceId, data_type>) or
+                       (std::is_same_v<EdgeId, data_type>) or (std::is_same_v<NodeId, data_type>)) {
+    using base_type = typename data_type::base_type;
+    dataset.select(offset, count).read_raw(reinterpret_cast<base_type*>(get_address(array)));
+  } else {
+    dataset.select(offset, count).read_raw(get_address(array));
+  }
+
+  return array;
+}
+
+}   // namespace checkpointing
+
+#endif   // READ_ARRAY_HPP
diff --git a/src/utils/checkpointing/ReadDiscreteFunctionVariant.cpp b/src/utils/checkpointing/ReadDiscreteFunctionVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..15efc050adbe7979bfdcc75f63ea141bb400af76
--- /dev/null
+++ b/src/utils/checkpointing/ReadDiscreteFunctionVariant.cpp
@@ -0,0 +1,102 @@
+#include <utils/checkpointing/ReadDiscreteFunctionVariant.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/DiscreteFunctionTypeHFType.hpp>
+#include <utils/checkpointing/ReadItemArray.hpp>
+#include <utils/checkpointing/ReadItemValue.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+std::shared_ptr<DiscreteFunctionVariant>
+readDiscreteFunctionVariant(const HighFive::Group& discrete_function_group)
+{
+  DiscreteFunctionType type = discrete_function_group.getAttribute("Vh_type").read<DiscreteFunctionType>();
+  size_t mesh_id            = discrete_function_group.getAttribute("mesh_id").read<size_t>();
+
+  std::shared_ptr<const MeshVariant> mesh_v = ResumingData::instance().meshVariant(mesh_id);
+
+  const std::string data_type = discrete_function_group.getAttribute("data_type").read<std::string>();
+
+  std::shared_ptr<DiscreteFunctionVariant> p_discrete_function;
+  switch (type) {
+  case DiscreteFunctionType::P0: {
+    if (data_type == dataTypeName(ast_node_data_type_from<double>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const double>(mesh_v,
+                                         readItemValue<double, ItemType::cell>(discrete_function_group, "values",
+                                                                               mesh_v->connectivity())));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<1>>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const TinyVector<1>>(mesh_v,
+                                                readItemValue<TinyVector<1>, ItemType::cell>(discrete_function_group,
+                                                                                             "values",
+                                                                                             mesh_v->connectivity())));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<2>>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const TinyVector<2>>(mesh_v,
+                                                readItemValue<TinyVector<2>, ItemType::cell>(discrete_function_group,
+                                                                                             "values",
+                                                                                             mesh_v->connectivity())));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<3>>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const TinyVector<3>>(mesh_v,
+                                                readItemValue<TinyVector<3>, ItemType::cell>(discrete_function_group,
+                                                                                             "values",
+                                                                                             mesh_v->connectivity())));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<1>>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const TinyMatrix<1>>(mesh_v,
+                                                readItemValue<TinyMatrix<1>, ItemType::cell>(discrete_function_group,
+                                                                                             "values",
+                                                                                             mesh_v->connectivity())));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<2>>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const TinyMatrix<2>>(mesh_v,
+                                                readItemValue<TinyMatrix<2>, ItemType::cell>(discrete_function_group,
+                                                                                             "values",
+                                                                                             mesh_v->connectivity())));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<3>>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0<const TinyMatrix<3>>(mesh_v,
+                                                readItemValue<TinyMatrix<3>, ItemType::cell>(discrete_function_group,
+                                                                                             "values",
+                                                                                             mesh_v->connectivity())));
+    } else {
+      // LCOV_EXCL_START
+      throw UnexpectedError("unexpected discrete function data type: " + data_type);
+      // LCOV_EXCL_STOP
+    }
+    break;
+  }
+  case DiscreteFunctionType::P0Vector: {
+    if (data_type == dataTypeName(ast_node_data_type_from<double>)) {
+      p_discrete_function = std::make_shared<DiscreteFunctionVariant>(
+        DiscreteFunctionP0Vector<const double>(mesh_v,
+                                               readItemArray<double, ItemType::cell>(discrete_function_group, "values",
+                                                                                     mesh_v->connectivity())));
+    } else {
+      // LCOV_EXCL_START
+      throw UnexpectedError("unexpected discrete function vector data type: " + data_type);
+      // LCOV_EXCL_STOP
+    }
+    break;
+  }
+  }
+  return p_discrete_function;
+}
+
+EmbeddedData
+readDiscreteFunctionVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group discrete_function_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<DiscreteFunctionVariant> p_discrete_function = readDiscreteFunctionVariant(discrete_function_group);
+
+  return {std::make_shared<DataHandler<const DiscreteFunctionVariant>>(p_discrete_function)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadDiscreteFunctionVariant.hpp b/src/utils/checkpointing/ReadDiscreteFunctionVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..70b87e415654c644cdc68256052ad8d186a7bd3b
--- /dev/null
+++ b/src/utils/checkpointing/ReadDiscreteFunctionVariant.hpp
@@ -0,0 +1,18 @@
+#ifndef READ_DISCRETE_FUNCTION_VARIANT_HPP
+#define READ_DISCRETE_FUNCTION_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class DiscreteFunctionVariant;
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+std::shared_ptr<DiscreteFunctionVariant> readDiscreteFunctionVariant(const HighFive::Group& discrete_function_group);
+
+EmbeddedData readDiscreteFunctionVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_DISCRETE_FUNCTION_VARIANT_HPP
diff --git a/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp b/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..55b43c9f04bf1b3b585df208bd7a3ea86a3d2850
--- /dev/null
+++ b/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp
@@ -0,0 +1,134 @@
+#include <utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/AxisBoundaryConditionDescriptor.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/ExternalBoundaryConditionDescriptor.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/FreeBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowListBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/OutflowBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <scheme/WallBoundaryConditionDescriptor.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp>
+#include <utils/checkpointing/ReadIBoundaryDescriptor.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+std::shared_ptr<const IBoundaryConditionDescriptor>
+readIBoundaryConditionDescriptor(const HighFive::Group& iboundaryconditiondecriptor_group)
+{
+  const IBoundaryConditionDescriptor::Type iboundary_condition_descriptor_type =
+    iboundaryconditiondecriptor_group.getAttribute("iboundary_condition_descriptor_type")
+      .read<IBoundaryConditionDescriptor::Type>();
+
+  HighFive::Group boundary_group = iboundaryconditiondecriptor_group.getGroup("boundary");
+  auto i_boundary_descriptor     = readIBoundaryDescriptor(boundary_group);
+
+  std::shared_ptr<const IBoundaryConditionDescriptor> bc_descriptor;
+
+  switch (iboundary_condition_descriptor_type) {
+  case IBoundaryConditionDescriptor::Type::axis: {
+    bc_descriptor = std::make_shared<const AxisBoundaryConditionDescriptor>(i_boundary_descriptor);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::dirichlet: {
+    const std::string name = iboundaryconditiondecriptor_group.getAttribute("name").read<std::string>();
+    const size_t rhs_id    = iboundaryconditiondecriptor_group.getAttribute("rhs_function_id").read<size_t>();
+
+    bc_descriptor =
+      std::make_shared<const DirichletBoundaryConditionDescriptor>(name, i_boundary_descriptor,
+                                                                   *ResumingData::instance().functionSymbolId(rhs_id));
+    break;
+  }
+    // LCOV_EXCL_START
+  case IBoundaryConditionDescriptor::Type::external: {
+    throw NotImplementedError("checkpoint/resume with sockets");
+
+    break;
+  }
+    // LCOV_EXCL_STOP
+  case IBoundaryConditionDescriptor::Type::fourier: {
+    const std::string name = iboundaryconditiondecriptor_group.getAttribute("name").read<std::string>();
+    const size_t rhs_id    = iboundaryconditiondecriptor_group.getAttribute("rhs_function_id").read<size_t>();
+    const size_t mass_id   = iboundaryconditiondecriptor_group.getAttribute("mass_function_id").read<size_t>();
+
+    bc_descriptor =
+      std::make_shared<const FourierBoundaryConditionDescriptor>(name, i_boundary_descriptor,
+                                                                 *ResumingData::instance().functionSymbolId(mass_id),
+                                                                 *ResumingData::instance().functionSymbolId(rhs_id));
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::fixed: {
+    bc_descriptor = std::make_shared<const FixedBoundaryConditionDescriptor>(i_boundary_descriptor);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::free: {
+    bc_descriptor = std::make_shared<const FreeBoundaryConditionDescriptor>(i_boundary_descriptor);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::inflow: {
+    const size_t function_id = iboundaryconditiondecriptor_group.getAttribute("function_id").read<size_t>();
+
+    bc_descriptor =
+      std::make_shared<const InflowBoundaryConditionDescriptor>(i_boundary_descriptor,
+                                                                *ResumingData::instance().functionSymbolId(
+                                                                  function_id));
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::inflow_list: {
+    const std::vector function_id_list =
+      iboundaryconditiondecriptor_group.getAttribute("function_id_list").read<std::vector<size_t>>();
+
+    std::vector<FunctionSymbolId> function_symbol_id_list;
+    for (auto function_id : function_id_list) {
+      function_symbol_id_list.push_back(*ResumingData::instance().functionSymbolId(function_id));
+    }
+
+    bc_descriptor =
+      std::make_shared<const InflowListBoundaryConditionDescriptor>(i_boundary_descriptor, function_symbol_id_list);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::neumann: {
+    const std::string name = iboundaryconditiondecriptor_group.getAttribute("name").read<std::string>();
+    const size_t rhs_id    = iboundaryconditiondecriptor_group.getAttribute("rhs_function_id").read<size_t>();
+
+    bc_descriptor =
+      std::make_shared<const NeumannBoundaryConditionDescriptor>(name, i_boundary_descriptor,
+                                                                 *ResumingData::instance().functionSymbolId(rhs_id));
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::outflow: {
+    bc_descriptor = std::make_shared<const OutflowBoundaryConditionDescriptor>(i_boundary_descriptor);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::symmetry: {
+    bc_descriptor = std::make_shared<const SymmetryBoundaryConditionDescriptor>(i_boundary_descriptor);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::wall: {
+    bc_descriptor = std::make_shared<const WallBoundaryConditionDescriptor>(i_boundary_descriptor);
+    break;
+  }
+  }
+
+  return bc_descriptor;
+}
+
+EmbeddedData
+readIBoundaryConditionDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  std::shared_ptr<const IBoundaryConditionDescriptor> bc_descriptor =
+    readIBoundaryConditionDescriptor(symbol_table_group.getGroup("embedded/" + symbol_name));
+  return {std::make_shared<DataHandler<const IBoundaryConditionDescriptor>>(bc_descriptor)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp b/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..380772d98d846d42c3c99e4695755585cc3685f4
--- /dev/null
+++ b/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp
@@ -0,0 +1,20 @@
+#ifndef READ_IBOUNDARY_CONDITION_DESCRIPTOR_HPP
+#define READ_IBOUNDARY_CONDITION_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class IBoundaryConditionDescriptor;
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+std::shared_ptr<const IBoundaryConditionDescriptor> readIBoundaryConditionDescriptor(
+  const HighFive::Group& iboundaryconditiondecriptor_group);
+
+EmbeddedData readIBoundaryConditionDescriptor(const std::string& symbol_name,
+                                              const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IBOUNDARY_CONDITION_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/ReadIBoundaryDescriptor.cpp b/src/utils/checkpointing/ReadIBoundaryDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..819c22180880ab0f7556c04144bb56db53525c1e
--- /dev/null
+++ b/src/utils/checkpointing/ReadIBoundaryDescriptor.cpp
@@ -0,0 +1,46 @@
+#include <utils/checkpointing/ReadIBoundaryDescriptor.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/IBoundaryDescriptor.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <utils/checkpointing/IBoundaryDescriptorHFType.hpp>
+
+namespace checkpointing
+{
+
+std::shared_ptr<const IBoundaryDescriptor>
+readIBoundaryDescriptor(const HighFive::Group& iboundarydescriptor_group)
+{
+  const IBoundaryDescriptor::Type iboundary_descriptor_type =
+    iboundarydescriptor_group.getAttribute("iboundary_descriptor_type").read<IBoundaryDescriptor::Type>();
+
+  std::shared_ptr<const IBoundaryDescriptor> i_boundary_descriptor;
+
+  switch (iboundary_descriptor_type) {
+  case IBoundaryDescriptor::Type::named: {
+    const std::string name = iboundarydescriptor_group.getAttribute("name").read<std::string>();
+    i_boundary_descriptor  = std::make_shared<const NamedBoundaryDescriptor>(name);
+    break;
+  }
+  case IBoundaryDescriptor::Type::numbered: {
+    const unsigned int number = iboundarydescriptor_group.getAttribute("number").read<unsigned int>();
+    i_boundary_descriptor     = std::make_shared<const NumberedBoundaryDescriptor>(number);
+    break;
+  }
+  }
+
+  return i_boundary_descriptor;
+}
+
+EmbeddedData
+readIBoundaryDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group iboundarydescriptor_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  return {std::make_shared<DataHandler<const IBoundaryDescriptor>>(readIBoundaryDescriptor(iboundarydescriptor_group))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIBoundaryDescriptor.hpp b/src/utils/checkpointing/ReadIBoundaryDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4384dd80bba0fff82d5a43ef2c0946d874a07668
--- /dev/null
+++ b/src/utils/checkpointing/ReadIBoundaryDescriptor.hpp
@@ -0,0 +1,18 @@
+#ifndef READ_IBOUNDARY_DESCRIPTOR_HPP
+#define READ_IBOUNDARY_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class IBoundaryDescriptor;
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+std::shared_ptr<const IBoundaryDescriptor> readIBoundaryDescriptor(const HighFive::Group& iboundarydescriptor_group);
+
+EmbeddedData readIBoundaryDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IBOUNDARY_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/ReadIDiscreteFunctionDescriptor.cpp b/src/utils/checkpointing/ReadIDiscreteFunctionDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4c016ed735c0b99d164c1c665b76a68ab6d17da2
--- /dev/null
+++ b/src/utils/checkpointing/ReadIDiscreteFunctionDescriptor.cpp
@@ -0,0 +1,35 @@
+#include <utils/checkpointing/ReadIDiscreteFunctionDescriptor.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/DiscreteFunctionDescriptorP0.hpp>
+#include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
+#include <utils/checkpointing/DiscreteFunctionTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readIDiscreteFunctionDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group idiscrete_function_descriptor_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  const DiscreteFunctionType discrete_function_type =
+    idiscrete_function_descriptor_group.getAttribute("discrete_function_type").read<DiscreteFunctionType>();
+
+  std::shared_ptr<const IDiscreteFunctionDescriptor> idiscrete_function_descriptor;
+
+  switch (discrete_function_type) {
+  case DiscreteFunctionType::P0: {
+    idiscrete_function_descriptor = std::make_shared<const DiscreteFunctionDescriptorP0>();
+    break;
+  }
+  case DiscreteFunctionType::P0Vector: {
+    idiscrete_function_descriptor = std::make_shared<const DiscreteFunctionDescriptorP0Vector>();
+    break;
+  }
+  }
+
+  return {std::make_shared<DataHandler<const IDiscreteFunctionDescriptor>>(idiscrete_function_descriptor)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIDiscreteFunctionDescriptor.hpp b/src/utils/checkpointing/ReadIDiscreteFunctionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f804b23405cf4b4cef4002c2759f829f5f9ac019
--- /dev/null
+++ b/src/utils/checkpointing/ReadIDiscreteFunctionDescriptor.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_IDISCRETE_FUNCTION_DESCRIPTOR_HPP
+#define READ_IDISCRETE_FUNCTION_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readIDiscreteFunctionDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IDISCRETE_FUNCTION_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/ReadIInterfaceDescriptor.cpp b/src/utils/checkpointing/ReadIInterfaceDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..79a7469f40c27ce6f2c103358508bd2129145081
--- /dev/null
+++ b/src/utils/checkpointing/ReadIInterfaceDescriptor.cpp
@@ -0,0 +1,36 @@
+#include <utils/checkpointing/ReadIInterfaceDescriptor.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
+#include <utils/checkpointing/IInterfaceDescriptorHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readIInterfaceDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group iinterfacedescriptor_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  const IInterfaceDescriptor::Type iinterface_descriptor_type =
+    iinterfacedescriptor_group.getAttribute("iinterface_descriptor_type").read<IInterfaceDescriptor::Type>();
+
+  std::shared_ptr<const IInterfaceDescriptor> iinterface_descriptor;
+
+  switch (iinterface_descriptor_type) {
+  case IInterfaceDescriptor::Type::named: {
+    const std::string name = iinterfacedescriptor_group.getAttribute("name").read<std::string>();
+    iinterface_descriptor  = std::make_shared<const NamedInterfaceDescriptor>(name);
+    break;
+  }
+  case IInterfaceDescriptor::Type::numbered: {
+    const unsigned int number = iinterfacedescriptor_group.getAttribute("number").read<unsigned int>();
+    iinterface_descriptor     = std::make_shared<const NumberedInterfaceDescriptor>(number);
+    break;
+  }
+  }
+  return {std::make_shared<DataHandler<const IInterfaceDescriptor>>(iinterface_descriptor)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIInterfaceDescriptor.hpp b/src/utils/checkpointing/ReadIInterfaceDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fe2f462fe8ae5760ed51ec4c7e919cafeb04b799
--- /dev/null
+++ b/src/utils/checkpointing/ReadIInterfaceDescriptor.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_IINTERFACE_DESCRIPTOR_HPP
+#define READ_IINTERFACE_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readIInterfaceDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IINTERFACE_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/ReadINamedDiscreteData.cpp b/src/utils/checkpointing/ReadINamedDiscreteData.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..58879d6b019375ea8d6eed29dc61b975fc87cc8e
--- /dev/null
+++ b/src/utils/checkpointing/ReadINamedDiscreteData.cpp
@@ -0,0 +1,50 @@
+#include <utils/checkpointing/ReadINamedDiscreteData.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <output/NamedDiscreteFunction.hpp>
+#include <output/NamedItemArrayVariant.hpp>
+#include <output/NamedItemValueVariant.hpp>
+#include <utils/checkpointing/INamedDiscreteDataHFType.hpp>
+#include <utils/checkpointing/ReadDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/ReadItemArrayVariant.hpp>
+#include <utils/checkpointing/ReadItemValueVariant.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readINamedDiscreteData(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group inamed_discrete_data_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  const INamedDiscreteData::Type& type =
+    inamed_discrete_data_group.getAttribute("named_discrete_data_type").read<INamedDiscreteData::Type>();
+
+  const std::string name = inamed_discrete_data_group.getAttribute("named_discrete_data_name").read<std::string>();
+
+  std::shared_ptr<const INamedDiscreteData> inamed_discrete_data;
+
+  switch (type) {
+  case INamedDiscreteData::Type::discrete_function: {
+    HighFive::Group discrete_function_group = inamed_discrete_data_group.getGroup("discrete_function_variant");
+    inamed_discrete_data =
+      std::make_shared<const NamedDiscreteFunction>(readDiscreteFunctionVariant(discrete_function_group), name);
+    break;
+  }
+  case INamedDiscreteData::Type::item_array: {
+    HighFive::Group item_array_group = inamed_discrete_data_group.getGroup("item_array_variant");
+    inamed_discrete_data = std::make_shared<const NamedItemArrayVariant>(readItemArrayVariant(item_array_group), name);
+    break;
+  }
+  case INamedDiscreteData::Type::item_value: {
+    HighFive::Group item_value_group = inamed_discrete_data_group.getGroup("item_value_variant");
+    inamed_discrete_data = std::make_shared<const NamedItemValueVariant>(readItemValueVariant(item_value_group), name);
+    break;
+  }
+  }
+
+  return {std::make_shared<DataHandler<const INamedDiscreteData>>(inamed_discrete_data)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadINamedDiscreteData.hpp b/src/utils/checkpointing/ReadINamedDiscreteData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3c90ccf9e330075362be28221cbc05c00c98257f
--- /dev/null
+++ b/src/utils/checkpointing/ReadINamedDiscreteData.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_INAMED_DISCRETE_DATA_HPP
+#define READ_INAMED_DISCRETE_DATA_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readINamedDiscreteData(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_INAMED_DISCRETE_DATA_HPP
diff --git a/src/utils/checkpointing/ReadIQuadratureDescriptor.cpp b/src/utils/checkpointing/ReadIQuadratureDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..902f0f64c0383477e9cd2f271f10e6de2ded1d9b
--- /dev/null
+++ b/src/utils/checkpointing/ReadIQuadratureDescriptor.cpp
@@ -0,0 +1,41 @@
+#include <utils/checkpointing/ReadIQuadratureDescriptor.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/checkpointing/QuadratureTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readIQuadratureDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group iquadraturedescriptor_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  const QuadratureType quadrature_type =
+    iquadraturedescriptor_group.getAttribute("quadrature_type").read<QuadratureType>();
+  const size_t degree = iquadraturedescriptor_group.getAttribute("quadrature_degree").read<size_t>();
+
+  std::shared_ptr<const IQuadratureDescriptor> iquadrature_descrptor;
+
+  switch (quadrature_type) {
+  case QuadratureType::Gauss: {
+    iquadrature_descrptor = std::make_shared<const GaussQuadratureDescriptor>(degree);
+    break;
+  }
+  case QuadratureType::GaussLegendre: {
+    iquadrature_descrptor = std::make_shared<const GaussLegendreQuadratureDescriptor>(degree);
+    break;
+  }
+  case QuadratureType::GaussLobatto: {
+    iquadrature_descrptor = std::make_shared<const GaussLobattoQuadratureDescriptor>(degree);
+    break;
+  }
+  }
+
+  return {std::make_shared<DataHandler<const IQuadratureDescriptor>>(iquadrature_descrptor)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIQuadratureDescriptor.hpp b/src/utils/checkpointing/ReadIQuadratureDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ac1b9670f1dc749578d55c369456b1c9ec31f685
--- /dev/null
+++ b/src/utils/checkpointing/ReadIQuadratureDescriptor.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_IQUADRATURE_DESCRIPTOR_HPP
+#define READ_IQUADRATURE_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readIQuadratureDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IQUADRATURE_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/ReadIWriter.cpp b/src/utils/checkpointing/ReadIWriter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d7037808859bd8916530e98ce1705214ef1c5972
--- /dev/null
+++ b/src/utils/checkpointing/ReadIWriter.cpp
@@ -0,0 +1,62 @@
+#include <utils/checkpointing/ReadIWriter.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <output/GnuplotWriter.hpp>
+#include <output/GnuplotWriter1D.hpp>
+#include <output/GnuplotWriterRaw.hpp>
+#include <output/VTKWriter.hpp>
+#include <utils/checkpointing/IWriterHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readIWriter(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group iwriter_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<WriterBase> p_writer;
+
+  IWriter::Type type = iwriter_group.getAttribute("iwriter_type").read<IWriter::Type>();
+
+  std::string base_filename = iwriter_group.getAttribute("base_filename").read<std::string>();
+
+  switch (type) {
+  case IWriter::Type::gnuplot: {
+    p_writer = std::make_shared<GnuplotWriter>(base_filename);
+    break;
+  }
+  case IWriter::Type::gnuplot_1d: {
+    p_writer = std::make_shared<GnuplotWriter1D>(base_filename);
+    break;
+  }
+  case IWriter::Type::gnuplot_raw: {
+    p_writer = std::make_shared<GnuplotWriterRaw>(base_filename);
+    break;
+  }
+  case IWriter::Type::vtk: {
+    p_writer = std::make_shared<VTKWriter>(base_filename);
+    break;
+  }
+  }
+
+  if (iwriter_group.exist("period_manager")) {
+    HighFive::Group period_manager_group = iwriter_group.getGroup("period_manager");
+
+    const double time_period = period_manager_group.getAttribute("time_period").read<double>();
+    const double next_time   = period_manager_group.getAttribute("next_time").read<double>();
+    const std::vector<double> saved_times =
+      period_manager_group.getAttribute("saved_times").read<std::vector<double>>();
+
+    p_writer->m_period_manager.emplace(WriterBase::PeriodManager(time_period, next_time, saved_times));
+  }
+
+  if (iwriter_group.hasAttribute("signature")) {
+    p_writer->m_signature = iwriter_group.getAttribute("signature").read<std::string>();
+  }
+
+  return {std::make_shared<DataHandler<const IWriter>>(p_writer)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIWriter.hpp b/src/utils/checkpointing/ReadIWriter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..88c0eaaff2990200d951fbf929e888d86a8a33d6
--- /dev/null
+++ b/src/utils/checkpointing/ReadIWriter.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_IWRITER_HPP
+#define READ_IWRITER_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readIWriter(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IWRITER_HPP
diff --git a/src/utils/checkpointing/ReadIZoneDescriptor.cpp b/src/utils/checkpointing/ReadIZoneDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3ae82c9aea4cc1713f97dccf6fbcfed407d58cec
--- /dev/null
+++ b/src/utils/checkpointing/ReadIZoneDescriptor.cpp
@@ -0,0 +1,37 @@
+#include <utils/checkpointing/ReadIZoneDescriptor.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedZoneDescriptor.hpp>
+#include <mesh/NumberedZoneDescriptor.hpp>
+#include <utils/checkpointing/IZoneDescriptorHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readIZoneDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group izonedescriptor_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  const IZoneDescriptor::Type izone_descriptor_type =
+    izonedescriptor_group.getAttribute("izone_descriptor_type").read<IZoneDescriptor::Type>();
+
+  std::shared_ptr<const IZoneDescriptor> izone_descriptor;
+
+  switch (izone_descriptor_type) {
+  case IZoneDescriptor::Type::named: {
+    const std::string name = izonedescriptor_group.getAttribute("name").read<std::string>();
+    izone_descriptor       = std::make_shared<const NamedZoneDescriptor>(name);
+    break;
+  }
+  case IZoneDescriptor::Type::numbered: {
+    const unsigned int number = izonedescriptor_group.getAttribute("number").read<unsigned int>();
+    izone_descriptor          = std::make_shared<const NumberedZoneDescriptor>(number);
+    break;
+  }
+  }
+
+  return {std::make_shared<DataHandler<const IZoneDescriptor>>(izone_descriptor)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadIZoneDescriptor.hpp b/src/utils/checkpointing/ReadIZoneDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..cf8f59e8d3a1674d27a35869a7c20ca2b6961fe8
--- /dev/null
+++ b/src/utils/checkpointing/ReadIZoneDescriptor.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_IZONE_DESCRIPTOR_HPP
+#define READ_IZONE_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readIZoneDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_IZONE_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/ReadItemArray.hpp b/src/utils/checkpointing/ReadItemArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7e23a480bf135f86f7bab1b4c01457daa07d9bf3
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemArray.hpp
@@ -0,0 +1,20 @@
+#ifndef READ_ITEM_ARRAY_HPP
+#define READ_ITEM_ARRAY_HPP
+
+#include <mesh/ItemArray.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/checkpointing/ReadTable.hpp>
+
+namespace checkpointing
+{
+
+template <typename T, ItemType item_type>
+ItemArray<T, item_type>
+readItemArray(const HighFive::Group& group, const std::string& name, const IConnectivity& connectivity)
+{
+  return {connectivity, readTable<T>(group, name)};
+}
+
+}   // namespace checkpointing
+
+#endif   // READ_ITEM_ARRAY_HPP
diff --git a/src/utils/checkpointing/ReadItemArrayVariant.cpp b/src/utils/checkpointing/ReadItemArrayVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d14087366ac79b7e47acb14b4b8310cf46e4786d
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemArrayVariant.cpp
@@ -0,0 +1,100 @@
+#include <utils/checkpointing/ReadItemArrayVariant.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemArrayVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/ReadItemArray.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+template <ItemType item_type>
+std::shared_ptr<ItemArrayVariant>
+readItemArrayVariant(const HighFive::Group& item_array_variant_group)
+{
+  const std::string data_type  = item_array_variant_group.getAttribute("data_type").read<std::string>();
+  const size_t connectivity_id = item_array_variant_group.getAttribute("connectivity_id").read<size_t>();
+
+  const IConnectivity& connectivity = *ResumingData::instance().iConnectivity(connectivity_id);
+
+  std::shared_ptr<ItemArrayVariant> p_item_array;
+
+  if (data_type == dataTypeName(ast_node_data_type_from<bool>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<bool, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<long int>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<long int, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<unsigned long int>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<unsigned long int, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<double>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<double, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<1>>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<TinyVector<1>, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<2>>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<TinyVector<2>, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<3>>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<TinyVector<3>, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<1>>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<TinyMatrix<1>, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<2>>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<TinyMatrix<2>, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<3>>)) {
+    p_item_array = std::make_shared<ItemArrayVariant>(
+      readItemArray<TinyMatrix<3>, item_type>(item_array_variant_group, "arrays", connectivity));
+  } else {
+    // LCOV_EXCL_START
+    throw UnexpectedError("unexpected discrete function data type: " + data_type);
+    // LCOV_EXCL_STOP
+  }
+  return p_item_array;
+}
+
+std::shared_ptr<ItemArrayVariant>
+readItemArrayVariant(const HighFive::Group& item_array_variant_group)
+{
+  const ItemType item_type = item_array_variant_group.getAttribute("item_type").read<ItemType>();
+
+  std::shared_ptr<ItemArrayVariant> p_item_array;
+
+  switch (item_type) {
+  case ItemType::cell: {
+    p_item_array = readItemArrayVariant<ItemType::cell>(item_array_variant_group);
+    break;
+  }
+  case ItemType::face: {
+    p_item_array = readItemArrayVariant<ItemType::face>(item_array_variant_group);
+    break;
+  }
+  case ItemType::edge: {
+    p_item_array = readItemArrayVariant<ItemType::edge>(item_array_variant_group);
+    break;
+  }
+  case ItemType::node: {
+    p_item_array = readItemArrayVariant<ItemType::node>(item_array_variant_group);
+    break;
+  }
+  }
+
+  return p_item_array;
+}
+
+EmbeddedData
+readItemArrayVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group item_array_variant_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  return {std::make_shared<DataHandler<const ItemArrayVariant>>(readItemArrayVariant(item_array_variant_group))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadItemArrayVariant.hpp b/src/utils/checkpointing/ReadItemArrayVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c3f0890ad3e8c835a4726d92238656b9ef934602
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemArrayVariant.hpp
@@ -0,0 +1,18 @@
+#ifndef READ_ITEM_ARRAY_VARIANT_HPP
+#define READ_ITEM_ARRAY_VARIANT_HPP
+
+#include <mesh/ItemArrayVariant.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+std::shared_ptr<ItemArrayVariant> readItemArrayVariant(const HighFive::Group& item_array_variant_group);
+
+EmbeddedData readItemArrayVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_ITEM_ARRAY_VARIANT_HPP
diff --git a/src/utils/checkpointing/ReadItemType.cpp b/src/utils/checkpointing/ReadItemType.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3974f872c46fee05538f9d2c7aedc97b9660b8f3
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemType.cpp
@@ -0,0 +1,19 @@
+#include <utils/checkpointing/ReadItemType.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readItemType(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group item_type_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  const ItemType item_type              = item_type_group.getAttribute("item_type").read<ItemType>();
+
+  return {std::make_shared<DataHandler<const ItemType>>(std::make_shared<const ItemType>(item_type))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadItemType.hpp b/src/utils/checkpointing/ReadItemType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..94abfd33fd254441d90c70776db9c2c998f60122
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemType.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_ITEM_TYPE_HPP
+#define READ_ITEM_TYPE_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readItemType(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_ITEM_TYPE_HPP
diff --git a/src/utils/checkpointing/ReadItemValue.hpp b/src/utils/checkpointing/ReadItemValue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a973a56fca094104b10922bbf9d3fef3abd95e42
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemValue.hpp
@@ -0,0 +1,20 @@
+#ifndef READ_ITEM_VALUE_HPP
+#define READ_ITEM_VALUE_HPP
+
+#include <mesh/ItemValue.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/checkpointing/ReadArray.hpp>
+
+namespace checkpointing
+{
+
+template <typename T, ItemType item_type>
+ItemValue<T, item_type>
+readItemValue(const HighFive::Group& group, const std::string& name, const IConnectivity& connectivity)
+{
+  return {connectivity, readArray<T>(group, name)};
+}
+
+}   // namespace checkpointing
+
+#endif   // READ_ITEM_VALUE_HPP
diff --git a/src/utils/checkpointing/ReadItemValueVariant.cpp b/src/utils/checkpointing/ReadItemValueVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8775dc1778da3d05596e5e634eda3debb7511434
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemValueVariant.cpp
@@ -0,0 +1,100 @@
+#include <utils/checkpointing/ReadItemValueVariant.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/ReadItemValue.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+template <ItemType item_type>
+std::shared_ptr<ItemValueVariant>
+readItemValueVariant(const HighFive::Group& item_value_variant_group)
+{
+  const std::string data_type  = item_value_variant_group.getAttribute("data_type").read<std::string>();
+  const size_t connectivity_id = item_value_variant_group.getAttribute("connectivity_id").read<size_t>();
+
+  const IConnectivity& connectivity = *ResumingData::instance().iConnectivity(connectivity_id);
+
+  std::shared_ptr<ItemValueVariant> p_item_value;
+
+  if (data_type == dataTypeName(ast_node_data_type_from<bool>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<bool, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<long int>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<long int, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<unsigned long int>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<unsigned long int, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<double>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<double, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<1>>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<TinyVector<1>, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<2>>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<TinyVector<2>, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<3>>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<TinyVector<3>, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<1>>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<TinyMatrix<1>, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<2>>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<TinyMatrix<2>, item_type>(item_value_variant_group, "values", connectivity));
+  } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<3>>)) {
+    p_item_value = std::make_shared<ItemValueVariant>(
+      readItemValue<TinyMatrix<3>, item_type>(item_value_variant_group, "values", connectivity));
+  } else {
+    // LCOV_EXCL_START
+    throw UnexpectedError("unexpected discrete function data type: " + data_type);
+    // LCOV_EXCL_STOP
+  }
+  return p_item_value;
+}
+
+std::shared_ptr<ItemValueVariant>
+readItemValueVariant(const HighFive::Group& item_value_variant_group)
+{
+  const ItemType item_type = item_value_variant_group.getAttribute("item_type").read<ItemType>();
+
+  std::shared_ptr<ItemValueVariant> p_item_value;
+
+  switch (item_type) {
+  case ItemType::cell: {
+    p_item_value = readItemValueVariant<ItemType::cell>(item_value_variant_group);
+    break;
+  }
+  case ItemType::face: {
+    p_item_value = readItemValueVariant<ItemType::face>(item_value_variant_group);
+    break;
+  }
+  case ItemType::edge: {
+    p_item_value = readItemValueVariant<ItemType::edge>(item_value_variant_group);
+    break;
+  }
+  case ItemType::node: {
+    p_item_value = readItemValueVariant<ItemType::node>(item_value_variant_group);
+    break;
+  }
+  }
+
+  return p_item_value;
+}
+
+EmbeddedData
+readItemValueVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group item_value_variant_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  return {std::make_shared<DataHandler<const ItemValueVariant>>(readItemValueVariant(item_value_variant_group))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadItemValueVariant.hpp b/src/utils/checkpointing/ReadItemValueVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0c5c7450ec11e981caf813307a2efddc7d3b04f2
--- /dev/null
+++ b/src/utils/checkpointing/ReadItemValueVariant.hpp
@@ -0,0 +1,18 @@
+#ifndef READ_ITEM_VALUE_VARIANT_HPP
+#define READ_ITEM_VALUE_VARIANT_HPP
+
+#include <mesh/ItemValueVariant.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+std::shared_ptr<ItemValueVariant> readItemValueVariant(const HighFive::Group& item_value_variant_group);
+
+EmbeddedData readItemValueVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_ITEM_VALUE_VARIANT_HPP
diff --git a/src/utils/checkpointing/ReadMesh.cpp b/src/utils/checkpointing/ReadMesh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fd93cd54dfcefb886e916cb2f378f82f5bd2ce91
--- /dev/null
+++ b/src/utils/checkpointing/ReadMesh.cpp
@@ -0,0 +1,22 @@
+#include <utils/checkpointing/ReadMesh.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readMesh(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group mesh_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  const size_t mesh_id = mesh_group.getAttribute("id").read<uint64_t>();
+
+  return {std::make_shared<DataHandler<const MeshVariant>>(ResumingData::instance().meshVariant(mesh_id))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadMesh.hpp b/src/utils/checkpointing/ReadMesh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..50d43511e3a634a93a065e5e110b4561ea3d1c73
--- /dev/null
+++ b/src/utils/checkpointing/ReadMesh.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_MESH_HPP
+#define READ_MESH_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readMesh(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_MESH_HPP
diff --git a/src/utils/checkpointing/ReadOStream.cpp b/src/utils/checkpointing/ReadOStream.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d8f0aa5304a74703e4dbeefcc95c11501d260948
--- /dev/null
+++ b/src/utils/checkpointing/ReadOStream.cpp
@@ -0,0 +1,38 @@
+#include <utils/checkpointing/ReadOStream.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <language/utils/OFStream.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/checkpointing/OStreamTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readOStream(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group ostream_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  const OStream::Type ostream_type = ostream_group.getAttribute("ostream_type").read<OStream::Type>();
+
+  std::shared_ptr<const OStream> p_ostream;
+
+  switch (ostream_type) {
+  case OStream::Type::std_ofstream: {
+    std::string filename = ostream_group.getAttribute("filename").read<std::string>();
+
+    p_ostream = std::make_shared<OFStream>(filename, true);
+    break;
+  }
+    // LCOV_EXCL_START
+  case OStream::Type::std_ostream: {
+    throw NotImplementedError("std::ostream resume");
+  }
+    // LCOV_EXCL_STOP
+  }
+
+  return {std::make_shared<DataHandler<const OStream>>(p_ostream)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadOStream.hpp b/src/utils/checkpointing/ReadOStream.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..871426f21effc18c20ae8edce8db56a1e60fcd71
--- /dev/null
+++ b/src/utils/checkpointing/ReadOStream.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_OSTREAM_HPP
+#define READ_OSTREAM_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readOStream(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_OSTREAM_HPP
diff --git a/src/utils/checkpointing/ReadSubItemArrayPerItemVariant.cpp b/src/utils/checkpointing/ReadSubItemArrayPerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..274a7901376fbd09266151542cacc1175b9e01bb
--- /dev/null
+++ b/src/utils/checkpointing/ReadSubItemArrayPerItemVariant.cpp
@@ -0,0 +1,162 @@
+#include <utils/checkpointing/ReadSubItemArrayPerItemVariant.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/SubItemArrayPerItemVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/ReadTable.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+template <typename T, ItemType item_type, ItemType sub_item_type>
+SubItemArrayPerItem<T, ItemOfItemType<sub_item_type, item_type>>
+readSubItemArrayPerItem(const HighFive::Group& group, const std::string& name, const IConnectivity& connectivity)
+{
+  return {connectivity, readTable<T>(group, name)};
+}
+
+template <ItemType item_type, ItemType sub_item_type>
+std::shared_ptr<SubItemArrayPerItemVariant>
+readSubItemArrayPerItemVariant(const HighFive::Group& sub_item_array_per_item_variant_group)
+{
+  if constexpr (item_type != sub_item_type) {
+    const std::string data_type  = sub_item_array_per_item_variant_group.getAttribute("data_type").read<std::string>();
+    const size_t connectivity_id = sub_item_array_per_item_variant_group.getAttribute("connectivity_id").read<size_t>();
+
+    const IConnectivity& connectivity = *ResumingData::instance().iConnectivity(connectivity_id);
+
+    std::shared_ptr<SubItemArrayPerItemVariant> p_sub_item_array_per_item_variant;
+
+    if (data_type == dataTypeName(ast_node_data_type_from<bool>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<bool, item_type, sub_item_type>(sub_item_array_per_item_variant_group, "arrays",
+                                                                connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<long int>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<long int, item_type, sub_item_type>(sub_item_array_per_item_variant_group, "arrays",
+                                                                    connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<unsigned long int>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<unsigned long int, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                             "arrays", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<double>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<double, item_type, sub_item_type>(sub_item_array_per_item_variant_group, "arrays",
+                                                                  connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<1>>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<TinyVector<1>, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                         "arrays", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<2>>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<TinyVector<2>, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                         "arrays", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<3>>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<TinyVector<3>, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                         "arrays", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<1>>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<TinyMatrix<1>, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                         "arrays", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<2>>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<TinyMatrix<2>, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                         "arrays", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<3>>)) {
+      p_sub_item_array_per_item_variant = std::make_shared<SubItemArrayPerItemVariant>(
+        readSubItemArrayPerItem<TinyMatrix<3>, item_type, sub_item_type>(sub_item_array_per_item_variant_group,
+                                                                         "arrays", connectivity));
+    } else {
+      // LCOV_EXCL_START
+      throw UnexpectedError("unexpected discrete function data type: " + data_type);
+      // LCOV_EXCL_STOP
+    }
+    return p_sub_item_array_per_item_variant;
+  } else {
+    // LCOV_EXCL_START
+    throw UnexpectedError("item_type and sub_item_type must be different");
+    // LCOV_EXCL_STOP
+  }
+}
+
+template <ItemType item_type>
+std::shared_ptr<SubItemArrayPerItemVariant>
+readSubItemArrayPerItemVariant(const HighFive::Group& sub_item_array_per_item_variant_group)
+{
+  const ItemType sub_item_type = sub_item_array_per_item_variant_group.getAttribute("sub_item_type").read<ItemType>();
+
+  std::shared_ptr<SubItemArrayPerItemVariant> p_sub_item_array_per_item_variant;
+
+  switch (sub_item_type) {
+  case ItemType::cell: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<item_type, ItemType::cell>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  case ItemType::face: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<item_type, ItemType::face>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  case ItemType::edge: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<item_type, ItemType::edge>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  case ItemType::node: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<item_type, ItemType::node>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  }
+
+  return p_sub_item_array_per_item_variant;
+}
+
+std::shared_ptr<SubItemArrayPerItemVariant>
+readSubItemArrayPerItemVariant(const HighFive::Group& sub_item_array_per_item_variant_group)
+{
+  const ItemType item_type = sub_item_array_per_item_variant_group.getAttribute("item_type").read<ItemType>();
+
+  std::shared_ptr<SubItemArrayPerItemVariant> p_sub_item_array_per_item_variant;
+
+  switch (item_type) {
+  case ItemType::cell: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<ItemType::cell>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  case ItemType::face: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<ItemType::face>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  case ItemType::edge: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<ItemType::edge>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  case ItemType::node: {
+    p_sub_item_array_per_item_variant =
+      readSubItemArrayPerItemVariant<ItemType::node>(sub_item_array_per_item_variant_group);
+    break;
+  }
+  }
+
+  return p_sub_item_array_per_item_variant;
+}
+
+EmbeddedData
+readSubItemArrayPerItemVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group sub_item_array_per_item_variant_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  return {std::make_shared<DataHandler<const SubItemArrayPerItemVariant>>(
+    readSubItemArrayPerItemVariant(sub_item_array_per_item_variant_group))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadSubItemArrayPerItemVariant.hpp b/src/utils/checkpointing/ReadSubItemArrayPerItemVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7146d7c422ffb84c9c729afe7824cf97634c2895
--- /dev/null
+++ b/src/utils/checkpointing/ReadSubItemArrayPerItemVariant.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_SUB_ITEM_ARRAY_PER_ITEM_VARIANT_HPP
+#define READ_SUB_ITEM_ARRAY_PER_ITEM_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readSubItemArrayPerItemVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_SUB_ITEM_ARRAY_PER_ITEM_VARIANT_HPP
diff --git a/src/utils/checkpointing/ReadSubItemValuePerItemVariant.cpp b/src/utils/checkpointing/ReadSubItemValuePerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..509b2e29ebeedb4d6a88b732462ab30f4a18310c
--- /dev/null
+++ b/src/utils/checkpointing/ReadSubItemValuePerItemVariant.cpp
@@ -0,0 +1,162 @@
+#include <utils/checkpointing/ReadSubItemValuePerItemVariant.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/ReadArray.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+
+namespace checkpointing
+{
+
+template <typename T, ItemType item_type, ItemType sub_item_type>
+SubItemValuePerItem<T, ItemOfItemType<sub_item_type, item_type>>
+readSubItemValuePerItem(const HighFive::Group& group, const std::string& name, const IConnectivity& connectivity)
+{
+  return {connectivity, readArray<T>(group, name)};
+}
+
+template <ItemType item_type, ItemType sub_item_type>
+std::shared_ptr<SubItemValuePerItemVariant>
+readSubItemValuePerItemVariant(const HighFive::Group& sub_item_value_per_item_variant_group)
+{
+  if constexpr (item_type != sub_item_type) {
+    const std::string data_type  = sub_item_value_per_item_variant_group.getAttribute("data_type").read<std::string>();
+    const size_t connectivity_id = sub_item_value_per_item_variant_group.getAttribute("connectivity_id").read<size_t>();
+
+    const IConnectivity& connectivity = *ResumingData::instance().iConnectivity(connectivity_id);
+
+    std::shared_ptr<SubItemValuePerItemVariant> p_sub_item_value_per_item_variant;
+
+    if (data_type == dataTypeName(ast_node_data_type_from<bool>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<bool, item_type, sub_item_type>(sub_item_value_per_item_variant_group, "values",
+                                                                connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<long int>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<long int, item_type, sub_item_type>(sub_item_value_per_item_variant_group, "values",
+                                                                    connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<unsigned long int>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<unsigned long int, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                             "values", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<double>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<double, item_type, sub_item_type>(sub_item_value_per_item_variant_group, "values",
+                                                                  connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<1>>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<TinyVector<1>, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                         "values", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<2>>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<TinyVector<2>, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                         "values", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyVector<3>>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<TinyVector<3>, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                         "values", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<1>>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<TinyMatrix<1>, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                         "values", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<2>>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<TinyMatrix<2>, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                         "values", connectivity));
+    } else if (data_type == dataTypeName(ast_node_data_type_from<TinyMatrix<3>>)) {
+      p_sub_item_value_per_item_variant = std::make_shared<SubItemValuePerItemVariant>(
+        readSubItemValuePerItem<TinyMatrix<3>, item_type, sub_item_type>(sub_item_value_per_item_variant_group,
+                                                                         "values", connectivity));
+    } else {
+      // LCOV_EXCL_START
+      throw UnexpectedError("unexpected discrete function data type: " + data_type);
+      // LCOV_EXCL_STOP
+    }
+    return p_sub_item_value_per_item_variant;
+  } else {
+    // LCOV_EXCL_START
+    throw UnexpectedError("item_type and sub_item_type must be different");
+    // LCOV_EXCL_STOP
+  }
+}
+
+template <ItemType item_type>
+std::shared_ptr<SubItemValuePerItemVariant>
+readSubItemValuePerItemVariant(const HighFive::Group& sub_item_value_per_item_variant_group)
+{
+  const ItemType sub_item_type = sub_item_value_per_item_variant_group.getAttribute("sub_item_type").read<ItemType>();
+
+  std::shared_ptr<SubItemValuePerItemVariant> p_sub_item_value_per_item_variant;
+
+  switch (sub_item_type) {
+  case ItemType::cell: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<item_type, ItemType::cell>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  case ItemType::face: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<item_type, ItemType::face>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  case ItemType::edge: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<item_type, ItemType::edge>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  case ItemType::node: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<item_type, ItemType::node>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  }
+
+  return p_sub_item_value_per_item_variant;
+}
+
+std::shared_ptr<SubItemValuePerItemVariant>
+readSubItemValuePerItemVariant(const HighFive::Group& sub_item_value_per_item_variant_group)
+{
+  const ItemType item_type = sub_item_value_per_item_variant_group.getAttribute("item_type").read<ItemType>();
+
+  std::shared_ptr<SubItemValuePerItemVariant> p_sub_item_value_per_item_variant;
+
+  switch (item_type) {
+  case ItemType::cell: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<ItemType::cell>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  case ItemType::face: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<ItemType::face>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  case ItemType::edge: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<ItemType::edge>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  case ItemType::node: {
+    p_sub_item_value_per_item_variant =
+      readSubItemValuePerItemVariant<ItemType::node>(sub_item_value_per_item_variant_group);
+    break;
+  }
+  }
+
+  return p_sub_item_value_per_item_variant;
+}
+
+EmbeddedData
+readSubItemValuePerItemVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group sub_item_value_per_item_variant_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+  return {std::make_shared<DataHandler<const SubItemValuePerItemVariant>>(
+    readSubItemValuePerItemVariant(sub_item_value_per_item_variant_group))};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadSubItemValuePerItemVariant.hpp b/src/utils/checkpointing/ReadSubItemValuePerItemVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..d1ce926b9670fe0ab9cc95b61f8d30b394212dac
--- /dev/null
+++ b/src/utils/checkpointing/ReadSubItemValuePerItemVariant.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
+#define READ_SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readSubItemValuePerItemVariant(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
diff --git a/src/utils/checkpointing/ReadTable.hpp b/src/utils/checkpointing/ReadTable.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..074dc5e2578287e75c881f9b0262f2796ac4ab8f
--- /dev/null
+++ b/src/utils/checkpointing/ReadTable.hpp
@@ -0,0 +1,50 @@
+#ifndef READ_TABLE_HPP
+#define READ_TABLE_HPP
+
+#include <mesh/CellType.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/Table.hpp>
+
+namespace checkpointing
+{
+
+template <typename DataType>
+PUGS_INLINE Table<DataType>
+readTable(const HighFive::Group& group, const std::string& name)
+{
+  auto get_address = [](auto& t) { return (t.numberOfRows() * t.numberOfColumns() > 0) ? &(t(0, 0)) : nullptr; };
+
+  using data_type = std::remove_const_t<DataType>;
+
+  auto dataset = group.getDataSet(name);
+
+  const size_t number_of_columns = dataset.getAttribute("number_of_columns").read<size_t>();
+  const std::vector<size_t> number_of_rows_per_rank =
+    dataset.getAttribute("number_of_rows_per_rank").read<std::vector<size_t>>();
+
+  std::vector<size_t> offset = {0, 0ul};
+  for (size_t i = 0; i < parallel::rank(); ++i) {
+    offset[0] += number_of_rows_per_rank[i] * number_of_columns;   // LCOV_EXCL_LINE
+  }
+  std::vector<size_t> count = {number_of_rows_per_rank[parallel::rank()] * number_of_columns};
+
+  Table<DataType> table(number_of_rows_per_rank[parallel::rank()], number_of_columns);
+  if constexpr (std::is_same_v<CellType, data_type>) {
+    using base_type = std::underlying_type_t<CellType>;
+    dataset.select(offset, count).read_raw(reinterpret_cast<base_type*>(get_address(table)));
+  } else if constexpr ((std::is_same_v<CellId, data_type>) or (std::is_same_v<FaceId, data_type>) or
+                       (std::is_same_v<EdgeId, data_type>) or (std::is_same_v<NodeId, data_type>)) {
+    using base_type = typename data_type::base_type;
+    dataset.select(offset, count).read_raw<base_type>(reinterpret_cast<base_type*>(get_address(table)));
+  } else {
+    dataset.select(offset, count).read_raw<data_type>(get_address(table));
+  }
+
+  return table;
+}
+
+}   // namespace checkpointing
+
+#endif   // READ_TABLE_HPP
diff --git a/src/utils/checkpointing/ReadVariableBCDescriptor.cpp b/src/utils/checkpointing/ReadVariableBCDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e58deca44af756e545673717a0c46e7c084dd91
--- /dev/null
+++ b/src/utils/checkpointing/ReadVariableBCDescriptor.cpp
@@ -0,0 +1,35 @@
+#include <utils/checkpointing/ReadVariableBCDescriptor.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/VariableBCDescriptor.hpp>
+#include <utils/checkpointing/ReadDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp>
+
+namespace checkpointing
+{
+
+EmbeddedData
+readVariableBCDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group)
+{
+  const HighFive::Group variable_bc_descriptor_group = symbol_table_group.getGroup("embedded/" + symbol_name);
+
+  HighFive::Group discrete_function_group = variable_bc_descriptor_group.getGroup("discrete_function");
+
+  std::shared_ptr<const DiscreteFunctionVariant> discrete_function =
+    readDiscreteFunctionVariant(discrete_function_group);
+
+  HighFive::Group bc_descriptor_list_group = variable_bc_descriptor_group.getGroup("bc_descriptor_list");
+  std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>> bc_descriptor_list;
+  for (size_t i = 0; i < bc_descriptor_list_group.getNumberObjects(); ++i) {
+    HighFive::Group bc_descriptor_group = bc_descriptor_list_group.getGroup(std::to_string(i));
+    bc_descriptor_list.push_back(readIBoundaryConditionDescriptor(bc_descriptor_group));
+  }
+
+  std::shared_ptr<const VariableBCDescriptor> p_variable_bc_descriptor =
+    std::make_shared<const VariableBCDescriptor>(discrete_function, bc_descriptor_list);
+
+  return {std::make_shared<DataHandler<const VariableBCDescriptor>>(p_variable_bc_descriptor)};
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ReadVariableBCDescriptor.hpp b/src/utils/checkpointing/ReadVariableBCDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f80ba5f1395517aff60ba663b79893d0232a8b01
--- /dev/null
+++ b/src/utils/checkpointing/ReadVariableBCDescriptor.hpp
@@ -0,0 +1,15 @@
+#ifndef READ_VARIABLE_BC_DESCRIPTOR_HPP
+#define READ_VARIABLE_BC_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+EmbeddedData readVariableBCDescriptor(const std::string& symbol_name, const HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // READ_VARIABLE_BC_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/RefItemListHFType.hpp b/src/utils/checkpointing/RefItemListHFType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..f9912a61856ed0e52ed6c153f8d9135c63bcaf75
--- /dev/null
+++ b/src/utils/checkpointing/RefItemListHFType.hpp
@@ -0,0 +1,18 @@
+#ifndef REF_ITEM_LIST_HF_TYPE_HPP
+#define REF_ITEM_LIST_HF_TYPE_HPP
+
+#include <mesh/RefItemList.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsMacros.hpp>
+
+HighFive::EnumType<RefItemListBase::Type> PUGS_INLINE
+create_enum_ref_item_list_type()
+{
+  return {{"boundary", RefItemListBase::Type::boundary},
+          {"interface", RefItemListBase::Type::interface},
+          {"set", RefItemListBase::Type::set},
+          {"undefined", RefItemListBase::Type::undefined}};
+}
+HIGHFIVE_REGISTER_TYPE(RefItemListBase::Type, create_enum_ref_item_list_type)
+
+#endif   // REF_ITEM_LIST_HF_TYPE_HPP
diff --git a/src/utils/checkpointing/Resume.cpp b/src/utils/checkpointing/Resume.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9d58e1d9dec4050f218d3efad71e5194ccaa5111
--- /dev/null
+++ b/src/utils/checkpointing/Resume.cpp
@@ -0,0 +1,310 @@
+#include <utils/checkpointing/Resume.hpp>
+
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <algebra/LinearSolverOptions.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <language/utils/SymbolTable.hpp>
+#include <mesh/Connectivity.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/RandomEngine.hpp>
+#include <utils/checkpointing/LinearSolverOptionsHFType.hpp>
+#include <utils/checkpointing/ParallelCheckerHFType.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+
+#include <iostream>
+
+void
+resume()
+{
+  try {
+    HighFive::SilenceHDF5 m_silence_hdf5{true};
+    checkpointing::ResumingData::create();
+    HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    HighFive::Group saved_symbol_table = checkpoint.getGroup("symbol table");
+
+    const ASTNode* p_node = &ASTExecutionStack::getInstance().currentNode();
+    auto p_symbol_table   = p_node->m_symbol_table;
+
+    ResumingManager& resuming_manager = ResumingManager::getInstance();
+
+    resuming_manager.checkpointNumber() = checkpoint.getAttribute("checkpoint_number").read<uint64_t>() + 1;
+
+    std::cout << " * " << rang::fgB::green << "Resuming " << rang::fg::reset << "execution at line "
+              << rang::fgB::yellow << p_node->begin().line << rang::fg::reset << " [using " << rang::fgB::cyan
+              << checkpoint.getAttribute("name").read<std::string>() << rang::fg::reset << "]\n";
+
+    {
+      HighFive::Group random_seed_group = checkpoint.getGroup("singleton/random_seed");
+      RandomEngine::instance().setRandomSeed(random_seed_group.getAttribute("current_seed").read<uint64_t>());
+    }
+    {
+      HighFive::Group global_variables_group = checkpoint.getGroup("singleton/execution_info");
+      const size_t run_number                = global_variables_group.getAttribute("run_number").read<size_t>();
+      const double cumulative_elapse_time =
+        global_variables_group.getAttribute("cumulative_elapse_time").read<double>();
+      const double cumulative_total_cpu_time =
+        global_variables_group.getAttribute("cumulative_total_cpu_time").read<double>();
+
+      ExecutionStatManager::getInstance().setRunNumber(run_number + 1);
+      ExecutionStatManager::getInstance().setPreviousCumulativeElapseTime(cumulative_elapse_time);
+      ExecutionStatManager::getInstance().setPreviousCumulativeTotalCPUTime(cumulative_total_cpu_time);
+    }
+    {
+      HighFive::Group random_seed_group = checkpoint.getGroup("singleton/parallel_checker");
+      // Ordering is important! Must set mode before changing the tag (changing mode is not allowed if tag!=0)
+      ParallelChecker::instance().setMode(random_seed_group.getAttribute("mode").read<ParallelChecker::Mode>());
+      ParallelChecker::instance().setTag(random_seed_group.getAttribute("tag").read<size_t>());
+    }
+    {
+      HighFive::Group linear_solver_options_default_group =
+        checkpoint.getGroup("singleton/linear_solver_options_default");
+
+      LinearSolverOptions& default_options = LinearSolverOptions::default_options;
+
+      default_options.epsilon() = linear_solver_options_default_group.getAttribute("epsilon").read<double>();
+      default_options.maximumIteration() =
+        linear_solver_options_default_group.getAttribute("maximum_iteration").read<size_t>();
+      default_options.verbose() = linear_solver_options_default_group.getAttribute("verbose").read<bool>();
+
+      default_options.library() = linear_solver_options_default_group.getAttribute("library").read<LSLibrary>();
+      default_options.method()  = linear_solver_options_default_group.getAttribute("method").read<LSMethod>();
+      default_options.precond() = linear_solver_options_default_group.getAttribute("precond").read<LSPrecond>();
+    }
+
+    checkpointing::ResumingData::instance().readData(checkpoint, p_symbol_table);
+
+    bool finished = true;
+    do {
+      finished = true;
+
+      for (auto symbol_name : saved_symbol_table.listAttributeNames()) {
+        auto [p_symbol, found] = p_symbol_table->find(symbol_name, p_node->begin());
+        auto& attribute        = p_symbol->attributes();
+        switch (attribute.dataType()) {
+        case ASTNodeDataType::bool_t: {
+          attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<bool>();
+          break;
+        }
+        case ASTNodeDataType::unsigned_int_t: {
+          attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<uint64_t>();
+          break;
+        }
+        case ASTNodeDataType::int_t: {
+          attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<int64_t>();
+          break;
+        }
+        case ASTNodeDataType::double_t: {
+          attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<double_t>();
+          break;
+        }
+        case ASTNodeDataType::string_t: {
+          attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::string>();
+          break;
+        }
+        case ASTNodeDataType::vector_t: {
+          switch (attribute.dataType().dimension()) {
+          case 1: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<TinyVector<1>>();
+            break;
+          }
+          case 2: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<TinyVector<2>>();
+            break;
+          }
+          case 3: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<TinyVector<3>>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError(dataTypeName(attribute.dataType()) + " unexpected vector dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case ASTNodeDataType::matrix_t: {
+          // LCOV_EXCL_START
+          if (attribute.dataType().numberOfRows() != attribute.dataType().numberOfColumns()) {
+            throw UnexpectedError(dataTypeName(attribute.dataType()) + " unexpected matrix dimension");
+          }
+          // LCOV_EXCL_STOP
+          switch (attribute.dataType().numberOfRows()) {
+          case 1: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<TinyMatrix<1>>();
+            break;
+          }
+          case 2: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<TinyMatrix<2>>();
+            break;
+          }
+          case 3: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<TinyMatrix<3>>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError(dataTypeName(attribute.dataType()) + " unexpected matrix dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case ASTNodeDataType::tuple_t: {
+          switch (attribute.dataType().contentType()) {
+          case ASTNodeDataType::bool_t: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<bool>>();
+            break;
+          }
+          case ASTNodeDataType::unsigned_int_t: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<uint64_t>>();
+            break;
+          }
+          case ASTNodeDataType::int_t: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<int64_t>>();
+            break;
+          }
+          case ASTNodeDataType::double_t: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<double_t>>();
+            break;
+          }
+          case ASTNodeDataType::string_t: {
+            attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<std::string>>();
+            break;
+          }
+          case ASTNodeDataType::vector_t: {
+            switch (attribute.dataType().contentType().dimension()) {
+            case 1: {
+              attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<TinyVector<1>>>();
+              break;
+            }
+            case 2: {
+              attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<TinyVector<2>>>();
+              break;
+            }
+            case 3: {
+              attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<TinyVector<3>>>();
+              break;
+            }
+              // LCOV_EXCL_START
+            default: {
+              throw UnexpectedError(dataTypeName(attribute.dataType()) + " unexpected vector dimension");
+            }
+              // LCOV_EXCL_STOP
+            }
+            break;
+          }
+          case ASTNodeDataType::matrix_t: {
+            // LCOV_EXCL_START
+            if (attribute.dataType().contentType().numberOfRows() !=
+                attribute.dataType().contentType().numberOfColumns()) {
+              throw UnexpectedError(dataTypeName(attribute.dataType().contentType()) + " unexpected matrix dimension");
+            }
+            // LCOV_EXCL_STOP
+            switch (attribute.dataType().contentType().numberOfRows()) {
+            case 1: {
+              attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<TinyMatrix<1>>>();
+              break;
+            }
+            case 2: {
+              attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<TinyMatrix<2>>>();
+              break;
+            }
+            case 3: {
+              attribute.value() = saved_symbol_table.getAttribute(symbol_name).read<std::vector<TinyMatrix<3>>>();
+              break;
+            }
+              // LCOV_EXCL_START
+            default: {
+              throw UnexpectedError(dataTypeName(attribute.dataType().contentType()) + " unexpected matrix dimension");
+            }
+              // LCOV_EXCL_STOP
+            }
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw NotImplementedError(symbol_name + " of type " + dataTypeName(attribute.dataType().contentType()));
+          }
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          throw NotImplementedError(symbol_name + " of type " + dataTypeName(attribute.dataType()));
+        }
+          // LCOV_EXCL_STOP
+        }
+      }
+
+      if (saved_symbol_table.exist("embedded")) {
+        HighFive::Group embedded = saved_symbol_table.getGroup("embedded");
+
+        for (auto symbol_name : embedded.listObjectNames()) {
+          auto [p_symbol, found] = p_symbol_table->find(symbol_name, p_node->begin());
+          if (p_symbol->attributes().dataType() == ASTNodeDataType::tuple_t) {
+            HighFive::Group embedded_tuple_group = embedded.getGroup(symbol_name);
+            const size_t number_of_components    = embedded_tuple_group.getNumberObjects();
+            std::vector<EmbeddedData> embedded_tuple(number_of_components);
+
+            for (size_t i_component = 0; i_component < number_of_components; ++i_component) {
+              embedded_tuple[i_component] =
+                CheckpointResumeRepository::instance().resume(p_symbol->attributes().dataType().contentType(),
+                                                              p_symbol->name() + "/" + std::to_string(i_component),
+                                                              saved_symbol_table);
+            }
+            p_symbol->attributes().value() = embedded_tuple;
+          } else {
+            p_symbol->attributes().value() =
+              CheckpointResumeRepository::instance().resume(p_symbol->attributes().dataType(), p_symbol->name(),
+                                                            saved_symbol_table);
+          }
+        }
+      }
+
+      const bool symbol_table_has_parent       = p_symbol_table->hasParentTable();
+      const bool saved_symbol_table_has_parent = saved_symbol_table.exist("symbol table");
+
+      Assert(not(symbol_table_has_parent xor saved_symbol_table_has_parent));
+
+      if (symbol_table_has_parent and saved_symbol_table_has_parent) {
+        p_symbol_table     = p_symbol_table->parentTable();
+        saved_symbol_table = saved_symbol_table.getGroup("symbol table");
+
+        finished = false;
+      }
+
+    } while (not finished);
+
+    checkpointing::ResumingData::destroy();
+  }
+  // LCOV_EXCL_START
+  catch (HighFive::Exception& e) {
+    throw NormalError(e.what());
+  }
+  // LCOV_EXCL_STOP
+}
+
+#else   // PUGS_HAS_HDF5
+
+#include <utils/Exceptions.hpp>
+
+void
+resume()
+{
+  throw NormalError("checkpoint/resume mechanism requires HDF5");
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/src/utils/checkpointing/Resume.hpp b/src/utils/checkpointing/Resume.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..9512539bc5b552f0d012c075b0f9ff7a1b69862c
--- /dev/null
+++ b/src/utils/checkpointing/Resume.hpp
@@ -0,0 +1,6 @@
+#ifndef RESUME_HPP
+#define RESUME_HPP
+
+void resume();
+
+#endif   // RESUME_HPP
diff --git a/src/utils/checkpointing/ResumingData.cpp b/src/utils/checkpointing/ResumingData.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2301f199496df36343dbe301feb5df11d3fcd9f2
--- /dev/null
+++ b/src/utils/checkpointing/ResumingData.cpp
@@ -0,0 +1,480 @@
+#include <utils/checkpointing/ResumingData.hpp>
+
+#include <language/utils/SymbolTable.hpp>
+#include <mesh/ConnectivityDescriptor.hpp>
+#include <mesh/DualConnectivityManager.hpp>
+#include <mesh/DualMeshManager.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/checkpointing/DualMeshTypeHFType.hpp>
+#include <utils/checkpointing/ReadArray.hpp>
+#include <utils/checkpointing/RefItemListHFType.hpp>
+
+namespace checkpointing
+{
+
+ResumingData* ResumingData::m_instance = nullptr;
+
+void
+ResumingData::_getConnectivityList(const HighFive::Group& checkpoint)
+{
+  if (checkpoint.exist("connectivity")) {
+    HighFive::Group connectivity_group = checkpoint.getGroup("connectivity");
+
+    // it is important to use a map here. "id"s must be treated in
+    // increasing order.
+    std::map<size_t, std::string> id_name_map;
+    for (auto connectivity_id_name : connectivity_group.listObjectNames()) {
+      HighFive::Group connectivity_info = connectivity_group.getGroup(connectivity_id_name);
+      id_name_map[connectivity_info.getAttribute("id").read<uint64_t>()] = connectivity_id_name;
+    }
+
+    for (auto [id, name] : id_name_map) {
+      HighFive::Group connectivity_data = connectivity_group.getGroup(name);
+      const std::string type            = connectivity_data.getAttribute("type").read<std::string>();
+
+      while (id > GlobalVariableManager::instance().getConnectivityId()) {
+        GlobalVariableManager::instance().getAndIncrementConnectivityId();
+      }
+
+      // LCOV_EXCL_START
+      if (m_id_to_iconnectivity_map.contains(id)) {
+        throw UnexpectedError("connectivity of id " + std::to_string(id) + " already defined!");
+      }
+      // LCOV_EXCL_STOP
+
+      if (type == "unstructured") {
+        const uint64_t dimension = connectivity_data.getAttribute("dimension").read<uint64_t>();
+
+        ConnectivityDescriptor descriptor;
+        descriptor.setCellTypeVector(readArray<CellType>(connectivity_data, "cell_type"));
+        descriptor.setCellNumberVector(readArray<int>(connectivity_data, "cell_numbers"));
+        descriptor.setNodeNumberVector(readArray<int>(connectivity_data, "node_numbers"));
+
+        descriptor.setCellOwnerVector(readArray<int>(connectivity_data, "cell_owner"));
+        descriptor.setNodeOwnerVector(readArray<int>(connectivity_data, "node_owner"));
+
+        using index_type = typename ConnectivityMatrix::IndexType;
+
+        descriptor.setCellToNodeMatrix(
+          ConnectivityMatrix{readArray<index_type>(connectivity_data, "cell_to_node_matrix_rowsMap"),
+                             readArray<index_type>(connectivity_data, "cell_to_node_matrix_values")});
+
+        if (dimension > 1) {
+          descriptor.setFaceNumberVector(readArray<int>(connectivity_data, "face_numbers"));
+          descriptor.setFaceOwnerVector(readArray<int>(connectivity_data, "face_owner"));
+
+          descriptor.setCellToFaceMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "cell_to_face_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "cell_to_face_matrix_values")});
+
+          descriptor.setFaceToNodeMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "face_to_node_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "face_to_node_matrix_values")});
+
+          descriptor.setNodeToFaceMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "node_to_face_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "node_to_face_matrix_values")});
+
+          descriptor.setCellFaceIsReversed(readArray<bool>(connectivity_data, "cell_face_is_reversed"));
+        }
+
+        if (dimension > 2) {
+          descriptor.setEdgeNumberVector(readArray<int>(connectivity_data, "edge_numbers"));
+          descriptor.setEdgeOwnerVector(readArray<int>(connectivity_data, "edge_owner"));
+
+          descriptor.setCellToEdgeMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "cell_to_edge_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "cell_to_edge_matrix_values")});
+
+          descriptor.setFaceToEdgeMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "face_to_edge_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "face_to_edge_matrix_values")});
+
+          descriptor.setEdgeToNodeMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "edge_to_node_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "edge_to_node_matrix_values")});
+
+          descriptor.setNodeToEdgeMatrix(
+            ConnectivityMatrix{readArray<index_type>(connectivity_data, "node_to_edge_matrix_rowsMap"),
+                               readArray<index_type>(connectivity_data, "node_to_edge_matrix_values")});
+
+          descriptor.setFaceEdgeIsReversed(readArray<bool>(connectivity_data, "face_edge_is_reversed"));
+        }
+
+        if (connectivity_data.exist("item_ref_list")) {
+          HighFive::Group item_group = connectivity_data.getGroup("item_ref_list");
+          for (auto item_type_name : item_group.listObjectNames()) {
+            HighFive::Group item_ref_name_list = item_group.getGroup(item_type_name);
+
+            for (auto item_ref_list_id : item_ref_name_list.listObjectNames()) {
+              HighFive::Group item_ref_list_data = item_ref_name_list.getGroup(item_ref_list_id);
+
+              RefId ref_id(item_ref_list_data.getAttribute("tag_number").read<uint64_t>(),
+                           item_ref_list_data.getAttribute("tag_name").read<std::string>());
+
+              RefItemListBase::Type ref_item_list_type =
+                item_ref_list_data.getAttribute("type").read<RefItemListBase::Type>();
+
+              if (item_type_name == "cell") {
+                descriptor.addRefItemList(RefItemList<ItemType::cell>{ref_id,
+                                                                      readArray<CellId>(item_ref_list_data, "list"),
+                                                                      ref_item_list_type});
+              } else if (item_type_name == "face") {
+                descriptor.addRefItemList(RefItemList<ItemType::face>{ref_id,
+                                                                      readArray<FaceId>(item_ref_list_data, "list"),
+                                                                      ref_item_list_type});
+              } else if (item_type_name == "edge") {
+                descriptor.addRefItemList(RefItemList<ItemType::edge>{ref_id,
+                                                                      readArray<EdgeId>(item_ref_list_data, "list"),
+                                                                      ref_item_list_type});
+              } else if (item_type_name == "node") {
+                descriptor.addRefItemList(RefItemList<ItemType::node>{ref_id,
+                                                                      readArray<NodeId>(item_ref_list_data, "list"),
+                                                                      ref_item_list_type});
+              } else {
+                // LCOV_EXCL_START
+                throw UnexpectedError("invalid item type: " + item_type_name);
+                // LCOV_EXCL_STOP
+              }
+            }
+          }
+        }
+
+        switch (dimension) {
+        case 1: {
+          m_id_to_iconnectivity_map.insert({id, Connectivity<1>::build(descriptor)});
+          break;
+        }
+        case 2: {
+          m_id_to_iconnectivity_map.insert({id, Connectivity<2>::build(descriptor)});
+          break;
+        }
+        case 3: {
+          m_id_to_iconnectivity_map.insert({id, Connectivity<3>::build(descriptor)});
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          throw UnexpectedError("invalid dimension " + std::to_string(dimension));
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else if (type == "dual_connectivity") {
+        const DualMeshType type_of_dual     = connectivity_data.getAttribute("type_of_dual").read<DualMeshType>();
+        const size_t primal_connectivity_id = connectivity_data.getAttribute("primal_connectivity_id").read<size_t>();
+
+        std::shared_ptr<const IConnectivity> iprimal_connectivity =
+          m_id_to_iconnectivity_map.at(primal_connectivity_id);
+
+        switch (iprimal_connectivity->dimension()) {
+        case 1: {
+          if (type_of_dual != DualMeshType::Dual1D) {
+            // LCOV_EXCL_START
+            throw UnexpectedError("unexpected dual mesh type");
+            // LCOV_EXCL_STOP
+          }
+
+          const Connectivity<1>& primal_connectivity = dynamic_cast<const Connectivity<1>&>(*iprimal_connectivity);
+          m_id_to_iconnectivity_map.insert(
+            {id, DualConnectivityManager::instance().getDual1DConnectivity(primal_connectivity)});
+          break;
+        }
+        case 2: {
+          const Connectivity<2>& primal_connectivity = dynamic_cast<const Connectivity<2>&>(*iprimal_connectivity);
+
+          if (type_of_dual == DualMeshType::Diamond) {
+            m_id_to_iconnectivity_map.insert(
+              {id, DualConnectivityManager::instance().getDiamondDualConnectivity(primal_connectivity)});
+          } else if (type_of_dual == DualMeshType::Median) {
+            m_id_to_iconnectivity_map.insert(
+              {id, DualConnectivityManager::instance().getMedianDualConnectivity(primal_connectivity)});
+          } else {
+            // LCOV_EXCL_START
+            throw UnexpectedError("unexpected dual mesh type");
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+        case 3: {
+          const Connectivity<3>& primal_connectivity = dynamic_cast<const Connectivity<3>&>(*iprimal_connectivity);
+
+          if (type_of_dual == DualMeshType::Diamond) {
+            m_id_to_iconnectivity_map.insert(
+              {id, DualConnectivityManager::instance().getDiamondDualConnectivity(primal_connectivity)});
+          } else if (type_of_dual == DualMeshType::Median) {
+            m_id_to_iconnectivity_map.insert(
+              {id, DualConnectivityManager::instance().getMedianDualConnectivity(primal_connectivity)});
+          } else {
+            // LCOV_EXCL_START
+            throw UnexpectedError("unexpected dual mesh type");
+            // LCOV_EXCL_STOP
+          }
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          throw UnexpectedError("unexpected dimension");
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else {
+        // LCOV_EXCL_START
+        throw UnexpectedError("invalid connectivity type: " + type);
+        // LCOV_EXCL_STOP
+      }
+    }
+  }
+
+  const size_t next_connectivity_id =
+    checkpoint.getGroup("singleton/global_variables").getAttribute("connectivity_id").read<size_t>();
+
+  while (next_connectivity_id > GlobalVariableManager::instance().getConnectivityId()) {
+    GlobalVariableManager::instance().getAndIncrementConnectivityId();
+  }
+}
+
+template <size_t Dimension>
+std::shared_ptr<const MeshVariant>
+ResumingData::_readPolygonalMesh(const HighFive::Group& mesh_data)
+{
+  const uint64_t connectivity_id = mesh_data.getAttribute("connectivity").read<uint64_t>();
+  auto i_id_to_iconnectivity     = m_id_to_iconnectivity_map.find(connectivity_id);
+  if (i_id_to_iconnectivity == m_id_to_iconnectivity_map.end()) {
+    // LCOV_EXCL_START
+    throw UnexpectedError("cannot find connectivity " + std::to_string(connectivity_id));
+    // LCOV_EXCL_STOP
+  }
+  std::shared_ptr<const IConnectivity> i_connectivity = i_id_to_iconnectivity->second;
+  if (i_connectivity->dimension() != Dimension) {
+    // LCOV_EXCL_START
+    throw UnexpectedError("invalid connectivity dimension " + std::to_string(i_connectivity->dimension()));
+    // LCOV_EXCL_STOP
+  }
+
+  std::shared_ptr<const Connectivity<Dimension>> connectivity =
+    std::dynamic_pointer_cast<const Connectivity<Dimension>>(i_connectivity);
+
+  Array<const TinyVector<Dimension>> xr_array = readArray<TinyVector<Dimension>>(mesh_data, "xr");
+  NodeValue<const TinyVector<Dimension>> xr{*connectivity, xr_array};
+
+  std::shared_ptr mesh = std::make_shared<const Mesh<Dimension>>(connectivity, xr);
+  return std::make_shared<const MeshVariant>(mesh);
+}
+
+void
+ResumingData::_getMeshVariantList(const HighFive::Group& checkpoint)
+{
+  if (checkpoint.exist("mesh")) {
+    HighFive::Group mesh_group = checkpoint.getGroup("mesh");
+
+    // it is important to use a map here. "id"s must be treated in
+    // increasing order.
+    std::map<size_t, std::string> id_name_map;
+    for (auto mesh_id_name : mesh_group.listObjectNames()) {
+      HighFive::Group mesh_info                                  = mesh_group.getGroup(mesh_id_name);
+      id_name_map[mesh_info.getAttribute("id").read<uint64_t>()] = mesh_id_name;
+    }
+
+    for (auto&& id_name : id_name_map) {
+      // workaround due to clang not handling correctly captures
+      const auto& id   = id_name.first;
+      const auto& name = id_name.second;
+
+      HighFive::Group mesh_data = mesh_group.getGroup(name);
+      const std::string type    = mesh_data.getAttribute("type").read<std::string>();
+
+      while (id > GlobalVariableManager::instance().getMeshId()) {
+        GlobalVariableManager::instance().getAndIncrementMeshId();
+      }
+
+      if (type == "polygonal") {
+        const uint64_t dimension = mesh_data.getAttribute("dimension").read<uint64_t>();
+
+        switch (dimension) {
+        case 1: {
+          m_id_to_mesh_variant_map.insert({id, this->_readPolygonalMesh<1>(mesh_data)});
+          break;
+        }
+        case 2: {
+          m_id_to_mesh_variant_map.insert({id, this->_readPolygonalMesh<2>(mesh_data)});
+          break;
+        }
+        case 3: {
+          m_id_to_mesh_variant_map.insert({id, this->_readPolygonalMesh<3>(mesh_data)});
+          break;
+        }
+          // LCOV_EXCL_START
+        default: {
+          throw UnexpectedError("invalid mesh dimension " + std::to_string(dimension));
+        }
+          // LCOV_EXCL_STOP
+        }
+      } else if (type == "dual_mesh") {
+        const DualMeshType type_of_dual = mesh_data.getAttribute("type_of_dual").read<DualMeshType>();
+        const size_t primal_mesh_id     = mesh_data.getAttribute("primal_mesh_id").read<size_t>();
+
+        std::shared_ptr<const MeshVariant> primal_mesh_variant = m_id_to_mesh_variant_map.at(primal_mesh_id);
+
+        std::visit(
+          [&](auto&& mesh) {
+            using MeshType = std::decay_t<decltype(*mesh)>;
+            if constexpr (std::is_same_v<MeshType, Mesh<1>>) {
+              if (type_of_dual == DualMeshType::Dual1D) {
+                m_id_to_mesh_variant_map.insert({id, DualMeshManager::instance().getDual1DMesh(primal_mesh_variant)});
+              } else {
+                // LCOV_EXCL_START
+                throw UnexpectedError("unexpected dual mesh type");
+                // LCOV_EXCL_STOP
+              }
+            } else if constexpr (std::is_same_v<MeshType, Mesh<2>> or std::is_same_v<MeshType, Mesh<3>>) {
+              if (type_of_dual == DualMeshType::Diamond) {
+                m_id_to_mesh_variant_map.insert(
+                  {id, DualMeshManager::instance().getDiamondDualMesh(primal_mesh_variant)});
+              } else if (type_of_dual == DualMeshType::Median) {
+                m_id_to_mesh_variant_map.insert(
+                  {id, DualMeshManager::instance().getMedianDualMesh(primal_mesh_variant)});
+              } else {
+                // LCOV_EXCL_START
+                throw UnexpectedError("unexpected dual mesh type");
+                // LCOV_EXCL_STOP
+              }
+            }
+          },
+          primal_mesh_variant->variant());
+
+      } else {
+        // LCOV_EXCL_START
+        throw UnexpectedError("invalid mesh type");
+        // LCOV_EXCL_STOP
+      }
+    }
+  }
+
+  const size_t next_mesh_id = checkpoint.getGroup("singleton/global_variables").getAttribute("mesh_id").read<size_t>();
+
+  while (next_mesh_id > GlobalVariableManager::instance().getMeshId()) {
+    GlobalVariableManager::instance().getAndIncrementMeshId();
+  }
+}
+
+void
+ResumingData::_getFunctionIds(const HighFive::Group& checkpoint, std::shared_ptr<SymbolTable> p_symbol_table)
+{
+  if (checkpoint.exist("functions")) {
+    size_t symbol_table_id               = 0;
+    const HighFive::Group function_group = checkpoint.getGroup("functions");
+    while (p_symbol_table.use_count() > 0) {
+      for (auto symbol : p_symbol_table->symbolList()) {
+        if (symbol.attributes().dataType() == ASTNodeDataType::function_t) {
+          if (not function_group.exist(symbol.name())) {
+            // LCOV_EXCL_START
+            std::ostringstream error_msg;
+            error_msg << "cannot find function " << rang::fgB::yellow << symbol.name() << rang::fg::reset << " in "
+                      << rang::fgB::cyan << checkpoint.getFile().getName() << rang::fg::reset;
+            throw UnexpectedError(error_msg.str());
+            // LCOV_EXCL_STOP
+          } else {   // LCOV_EXCL_LINE
+            const HighFive::Group function  = function_group.getGroup(symbol.name());
+            const size_t stored_function_id = function.getAttribute("id").read<size_t>();
+            const size_t function_id        = std::get<size_t>(symbol.attributes().value());
+            if (symbol_table_id != function.getAttribute("symbol_table_id").read<size_t>()) {
+              // LCOV_EXCL_START
+              std::ostringstream error_msg;
+              error_msg << "symbol table of function " << rang::fgB::yellow << symbol.name() << rang::fg::reset
+                        << " does not match the one stored in " << rang::fgB::cyan << checkpoint.getFile().getName()
+                        << rang::fg::reset;
+              throw UnexpectedError(error_msg.str());
+              // LCOV_EXCL_STOP
+            } else if (function_id != stored_function_id) {
+              // LCOV_EXCL_START
+              std::ostringstream error_msg;
+              error_msg << "id (" << function_id << ") of function " << rang::fgB::yellow << symbol.name()
+                        << rang::fg::reset << " does not match the one stored in " << rang::fgB::cyan
+                        << checkpoint.getFile().getName() << rang::fg::reset << "(" << stored_function_id << ")";
+              throw UnexpectedError(error_msg.str());
+              // LCOV_EXCL_STOP
+            } else {   // LCOV_EXCL_LINE
+              if (m_id_to_function_symbol_id_map.contains(function_id)) {
+                // LCOV_EXCL_START
+                std::ostringstream error_msg;
+                error_msg << "id (" << function_id << ") of function " << rang::fgB::yellow << symbol.name()
+                          << rang::fg::reset << " is duplicated";
+                throw UnexpectedError(error_msg.str());
+                // LCOV_EXCL_STOP
+              }   // LCOV_EXCL_LINE
+              m_id_to_function_symbol_id_map[function_id] =
+                std::make_shared<FunctionSymbolId>(function_id, p_symbol_table);
+            }
+          }
+        }
+      }
+      p_symbol_table = p_symbol_table->parentTable();
+      ++symbol_table_id;
+    }
+  }
+}
+
+void
+ResumingData::readData(const HighFive::Group& checkpoint, std::shared_ptr<SymbolTable> p_symbol_table)
+{
+  this->_getConnectivityList(checkpoint);
+  this->_getMeshVariantList(checkpoint);
+  this->_getFunctionIds(checkpoint, p_symbol_table);
+}
+
+const std::shared_ptr<const IConnectivity>&
+ResumingData::iConnectivity(const size_t connectivity_id) const
+{
+  auto i_id_to_connectivity = m_id_to_iconnectivity_map.find(connectivity_id);
+  if (i_id_to_connectivity == m_id_to_iconnectivity_map.end()) {
+    // LCOV_EXCL_START
+    throw UnexpectedError("cannot find connectivity of id " + std::to_string(connectivity_id));
+    // LCOV_EXCL_STOP
+  } else {
+    return i_id_to_connectivity->second;
+  }
+}
+
+const std::shared_ptr<const MeshVariant>&
+ResumingData::meshVariant(const size_t mesh_id) const
+{
+  auto i_id_to_mesh = m_id_to_mesh_variant_map.find(mesh_id);
+  if (i_id_to_mesh == m_id_to_mesh_variant_map.end()) {
+    // LCOV_EXCL_START
+    throw UnexpectedError("cannot find mesh of id " + std::to_string(mesh_id));
+    // LCOV_EXCL_STOP
+  } else {
+    return i_id_to_mesh->second;
+  }
+}
+
+const std::shared_ptr<const FunctionSymbolId>&
+ResumingData::functionSymbolId(const size_t function_symbol_id) const
+{
+  auto i_id_to_function_symbol_id = m_id_to_function_symbol_id_map.find(function_symbol_id);
+  if (i_id_to_function_symbol_id == m_id_to_function_symbol_id_map.end()) {
+    // LCOV_EXCL_START
+    throw UnexpectedError("cannot find function_symbol_id of id " + std::to_string(function_symbol_id));
+    // LCOV_EXCL_STOP
+  } else {
+    return i_id_to_function_symbol_id->second;
+  }
+}
+
+void
+ResumingData::create()
+{
+  Assert(m_instance == nullptr, "instance already created");
+  m_instance = new ResumingData;
+}
+
+void
+ResumingData::destroy()
+{
+  Assert(m_instance != nullptr, "instance not created");
+  delete m_instance;
+  m_instance = nullptr;
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/ResumingData.hpp b/src/utils/checkpointing/ResumingData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..68e8d3cc91a6ddbf581411a117d17894e9e516bd
--- /dev/null
+++ b/src/utils/checkpointing/ResumingData.hpp
@@ -0,0 +1,59 @@
+#ifndef RESUMING_DATA_HPP
+#define RESUMING_DATA_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/PugsAssert.hpp>
+
+#include <map>
+
+class IConnectivity;
+class MeshVariant;
+class SymbolTable;
+class FunctionSymbolId;
+
+namespace checkpointing
+{
+
+class ResumingData
+{
+ private:
+  std::map<size_t, std::shared_ptr<const IConnectivity>> m_id_to_iconnectivity_map;
+  std::map<size_t, std::shared_ptr<const MeshVariant>> m_id_to_mesh_variant_map;
+  std::map<size_t, std::shared_ptr<const FunctionSymbolId>> m_id_to_function_symbol_id_map;
+
+  ResumingData()  = default;
+  ~ResumingData() = default;
+
+  static ResumingData* m_instance;
+
+  template <size_t Dimension>
+  std::shared_ptr<const MeshVariant> _readPolygonalMesh(const HighFive::Group& mesh_group);
+
+  void _getConnectivityList(const HighFive::Group& checkpoint);
+  void _getMeshVariantList(const HighFive::Group& checkpoint);
+  void _getFunctionIds(const HighFive::Group& checkpoint, std::shared_ptr<SymbolTable> p_symbol_table);
+
+ public:
+  void readData(const HighFive::Group& checkpoint, std::shared_ptr<SymbolTable> p_symbol_table);
+
+  const std::shared_ptr<const IConnectivity>& iConnectivity(const size_t connectivity_id) const;
+  const std::shared_ptr<const MeshVariant>& meshVariant(const size_t mesh_id) const;
+  const std::shared_ptr<const FunctionSymbolId>& functionSymbolId(const size_t function_symbol_id) const;
+
+  static void create();
+  static void destroy();
+
+  static ResumingData&
+  instance()
+  {
+    Assert(m_instance != nullptr, "instance not created");
+    return *m_instance;
+  }
+
+  ResumingData(const ResumingData&) = delete;
+  ResumingData(ResumingData&&)      = delete;
+};
+
+}   // namespace checkpointing
+
+#endif   // RESUMING_DATA_HPP
diff --git a/src/utils/checkpointing/ResumingManager.cpp b/src/utils/checkpointing/ResumingManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0dfe83fd93bb126684193c15426dbac553762f4d
--- /dev/null
+++ b/src/utils/checkpointing/ResumingManager.cpp
@@ -0,0 +1,44 @@
+#include <utils/checkpointing/ResumingManager.hpp>
+
+#include <utils/HighFivePugsUtils.hpp>
+
+ResumingManager* ResumingManager::m_instance = nullptr;
+
+ResumingManager&
+ResumingManager::getInstance()
+{
+  Assert(m_instance != nullptr, "instance was not created");
+  return *m_instance;
+}
+
+void
+ResumingManager::create()
+{
+  Assert(m_instance == nullptr, "Resuming manager was already created");
+  m_instance = new ResumingManager;
+}
+
+void
+ResumingManager::destroy()
+{
+  Assert(m_instance != nullptr, "Resuming manager was not created");
+  delete m_instance;
+  m_instance = nullptr;
+}
+
+size_t
+ResumingManager::checkpointId()
+{
+#ifdef PUGS_HAS_HDF5
+  if (not m_checkpoint_id) {
+    HighFive::File file(m_filename, HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    m_checkpoint_id = std::make_unique<size_t>(checkpoint.getAttribute("id").read<uint64_t>());
+  }
+#else    // PUGS_HAS_HDF5
+  m_checkpoint_id = std::make_unique<uint64_t>(0);
+#endif   //  PUGS_HAS_HDF5
+  return *m_checkpoint_id;
+}
diff --git a/src/utils/checkpointing/ResumingManager.hpp b/src/utils/checkpointing/ResumingManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a39c68d020453f6ba7ab875a4c6f183f724294c2
--- /dev/null
+++ b/src/utils/checkpointing/ResumingManager.hpp
@@ -0,0 +1,72 @@
+#ifndef RESUMING_MANAGER_HPP
+#define RESUMING_MANAGER_HPP
+
+#include <utils/PugsAssert.hpp>
+
+#include <memory>
+
+class ResumingManager
+{
+ private:
+  bool m_is_resuming = false;
+  std::string m_filename;
+  // numbering of the checkpoints (during execution)
+  uint64_t m_checkpoint_number = 0;
+  // id of the checkpoint defined in the script file
+  std::unique_ptr<uint64_t> m_checkpoint_id;
+  size_t m_current_ast_level = 0;
+
+  ResumingManager() = default;
+
+  ResumingManager(ResumingManager&&)      = delete;
+  ResumingManager(const ResumingManager&) = delete;
+
+  ~ResumingManager() = default;
+
+  static ResumingManager* m_instance;
+
+ public:
+  static void create();
+  static void destroy();
+  static ResumingManager& getInstance();
+
+  uint64_t&
+  checkpointNumber()
+  {
+    return m_checkpoint_number;
+  }
+
+  uint64_t checkpointId();
+
+  uint64_t&
+  currentASTLevel()
+  {
+    return m_current_ast_level;
+  }
+
+  void
+  setIsResuming(const bool is_resuming)
+  {
+    m_is_resuming = is_resuming;
+  }
+
+  bool
+  isResuming() const
+  {
+    return m_is_resuming;
+  }
+
+  void
+  setFilename(const std::string& filename)
+  {
+    m_filename = filename;
+  }
+
+  const std::string&
+  filename() const
+  {
+    return m_filename;
+  }
+};
+
+#endif   // RESUMING_MANAGER_HPP
diff --git a/src/utils/checkpointing/ResumingUtils.cpp b/src/utils/checkpointing/ResumingUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28a782b49f4bc4c38132e1eab0ae9cd6eeb56f02
--- /dev/null
+++ b/src/utils/checkpointing/ResumingUtils.cpp
@@ -0,0 +1,26 @@
+#include <utils/checkpointing/ResumingUtils.hpp>
+
+#include <utils/Exceptions.hpp>
+
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <utils/HighFivePugsUtils.hpp>
+
+std::string
+resumingDatafile(const std::string& filename)
+{
+  HighFive::File file(filename, HighFive::File::ReadOnly);
+  return file.getGroup("/resuming_checkpoint").getAttribute("data.pgs").read<std::string>();
+}
+
+#else   // PUGS_HAS_HDF5
+
+std::string
+resumingDatafile(const std::string&)
+{
+  throw NormalError("Resuming requires HDF5");
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/src/utils/checkpointing/ResumingUtils.hpp b/src/utils/checkpointing/ResumingUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..46cd27ed7056097d20af24f3c352971af85bc946
--- /dev/null
+++ b/src/utils/checkpointing/ResumingUtils.hpp
@@ -0,0 +1,8 @@
+#ifndef RESUMING_UTILS_HPP
+#define RESUMING_UTILS_HPP
+
+#include <string>
+
+std::string resumingDatafile(const std::string& filename);
+
+#endif   // RESUMING_UTILS_HPP
diff --git a/src/utils/checkpointing/SetResumeFrom.cpp b/src/utils/checkpointing/SetResumeFrom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7f040e7caaaf7c4eb8a27017f608127507b59cf0
--- /dev/null
+++ b/src/utils/checkpointing/SetResumeFrom.cpp
@@ -0,0 +1,57 @@
+#include <utils/checkpointing/SetResumeFrom.hpp>
+
+#include <rang.hpp>
+#include <utils/pugs_config.hpp>
+
+#include <iostream>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <utils/Exceptions.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+
+void
+setResumeFrom(const std::string& filename, const uint64_t& checkpoint_number, std::ostream& os)
+{
+  try {
+    HighFive::SilenceHDF5 m_silence_hdf5{true};
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{parallel::Messenger::getInstance().comm(), MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+
+    HighFive::File file(filename, HighFive::File::ReadWrite, fapl);
+    const std::string checkpoint_name = "checkpoint_" + std::to_string(checkpoint_number);
+
+    if (not file.exist(checkpoint_name)) {
+      std::ostringstream error_msg;
+      error_msg << "cannot find checkpoint " << rang::fgB::magenta << checkpoint_number << rang::fg::reset << " in "
+                << rang::fgB::yellow << filename << rang::fg::reset;
+      throw NormalError(error_msg.str());
+    }
+
+    HighFive::Group checkpoint = file.getGroup(checkpoint_name);
+    if (file.exist("resuming_checkpoint")) {
+      file.unlink("resuming_checkpoint");
+    }
+    file.createHardLink("resuming_checkpoint", checkpoint);
+    os << "Resuming checkpoint " << rang::style::bold << "successfully" << rang::style::reset << " set to "
+       << rang::fgB::yellow << checkpoint_number << rang::fg::reset << '\n';
+  }
+  // LCOV_EXCL_START
+  catch (HighFive::Exception& e) {
+    throw NormalError(e.what());
+  }
+  // LCOV_EXCL_STOP
+}
+
+#else   // PUGS_HAS_HDF5
+
+void
+setResumeFrom(const std::string&, const uint64_t&, std::ostream&)
+{
+  std::cerr << rang::fgB::red << "error: " << rang::fg::reset << "setting resuming checkpoint requires HDF5\n";
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/src/utils/checkpointing/SetResumeFrom.hpp b/src/utils/checkpointing/SetResumeFrom.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..842c638933a7ee3083b44aebc234d8797542185a
--- /dev/null
+++ b/src/utils/checkpointing/SetResumeFrom.hpp
@@ -0,0 +1,10 @@
+#ifndef SET_RESUME_FROM_HPP
+#define SET_RESUME_FROM_HPP
+
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+void setResumeFrom(const std::string& filename, const uint64_t& checkpoint_number, std::ostream& os = std::cout);
+
+#endif   // SET_RESUME_FROM_HPP
diff --git a/src/utils/checkpointing/WriteArray.hpp b/src/utils/checkpointing/WriteArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5cff412b80e111716a680d0db0ff43d64b53d39e
--- /dev/null
+++ b/src/utils/checkpointing/WriteArray.hpp
@@ -0,0 +1,67 @@
+#ifndef WRITE_ARRAY_HPP
+#define WRITE_ARRAY_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+#include <mesh/CellType.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Array.hpp>
+#include <utils/Messenger.hpp>
+
+namespace checkpointing
+{
+
+template <typename DataType>
+PUGS_INLINE void
+write(HighFive::Group& group, const std::string& name, const Array<DataType>& array)
+{
+  auto get_address = [](auto& x) { return (x.size() > 0) ? &(x[0]) : nullptr; };
+
+  Array<size_t> size_per_rank = parallel::allGather(array.size());
+  size_t global_size          = sum(size_per_rank);
+
+  size_t current_offset = 0;
+  for (size_t i = 0; i < parallel::rank(); ++i) {
+    current_offset += size_per_rank[i];   // LCOV_EXCL_LINE
+  }
+  std::vector<size_t> offset{current_offset, 0ul};
+  std::vector<size_t> count{array.size()};
+
+  using data_type = std::remove_const_t<DataType>;
+  HighFive::DataSetCreateProps properties;
+  // properties.add(HighFive::Chunking(std::vector<hsize_t>{std::min(4ul * 1024ul * 1024ul, global_size)}));
+  // properties.add(HighFive::Shuffle());
+  // properties.add(HighFive::Deflate(3));
+
+  auto xfer_props = HighFive::DataTransferProps{};
+  xfer_props.add(HighFive::UseCollectiveIO{});
+
+  HighFive::DataSet dataset;
+  if constexpr (std::is_same_v<CellType, data_type>) {
+    using base_type = std::underlying_type_t<CellType>;
+    dataset = group.createDataSet<base_type>(name, HighFive::DataSpace{std::vector<size_t>{global_size}}, properties);
+    dataset.select(offset, count)
+      .template write_raw<base_type>(reinterpret_cast<const base_type*>(get_address(array)), xfer_props);
+  } else if constexpr ((std::is_same_v<CellId, data_type>) or (std::is_same_v<FaceId, data_type>) or
+                       (std::is_same_v<EdgeId, data_type>) or (std::is_same_v<NodeId, data_type>)) {
+    using base_type = typename data_type::base_type;
+
+    dataset = group.createDataSet<base_type>(name, HighFive::DataSpace{std::vector<size_t>{global_size}}, properties);
+    dataset.select(offset, count)
+      .template write_raw<base_type>(reinterpret_cast<const base_type*>(get_address(array)), xfer_props);
+  } else {
+    dataset = group.createDataSet<data_type>(name, HighFive::DataSpace{std::vector<size_t>{global_size}}, properties);
+    dataset.select(offset, count).template write_raw<data_type>(get_address(array), xfer_props);
+  }
+
+  std::vector<size_t> size_vector;
+  for (size_t i = 0; i < size_per_rank.size(); ++i) {
+    size_vector.push_back(size_per_rank[i]);
+  }
+
+  dataset.createAttribute("size_per_rank", size_vector);
+}
+
+}   // namespace checkpointing
+
+#endif   // WRITE_ARRAY_HPP
diff --git a/src/utils/checkpointing/WriteConnectivity.cpp b/src/utils/checkpointing/WriteConnectivity.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d8ee2984182ef790358a99cb06b5ecd97269b9ab
--- /dev/null
+++ b/src/utils/checkpointing/WriteConnectivity.cpp
@@ -0,0 +1,161 @@
+#include <utils/checkpointing/WriteConnectivity.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <utils/checkpointing/RefItemListHFType.hpp>
+#include <utils/checkpointing/WriteArray.hpp>
+#include <utils/checkpointing/WriteItemValue.hpp>
+
+#include <iomanip>
+
+namespace checkpointing
+{
+
+template <ItemType item_type, size_t Dimension>
+void
+writeRefItemList(const Connectivity<Dimension>& connectivity, HighFive::Group& connectivity_group)
+{
+  for (size_t i_item_list = 0; i_item_list < connectivity.template numberOfRefItemList<item_type>(); ++i_item_list) {
+    auto ref_item_list = connectivity.template refItemList<item_type>(i_item_list);
+
+    std::ostringstream ref_item_list_group_name;
+    ref_item_list_group_name << "item_ref_list/" << itemName(item_type) << '/' << std::setfill('0')
+                             << std::setw(
+                                  std::to_string(connectivity.template numberOfRefItemList<item_type>()).length())
+                             << i_item_list;
+    HighFive::Group ref_item_list_group = connectivity_group.createGroup(ref_item_list_group_name.str());
+    ref_item_list_group.createAttribute("tag_name", ref_item_list.refId().tagName());
+    ref_item_list_group.createAttribute("tag_number", ref_item_list.refId().tagNumber());
+    ref_item_list_group.createAttribute("type", ref_item_list.type());
+
+    write(ref_item_list_group, "list", ref_item_list.list());
+  }
+}
+
+template <size_t Dimension>
+void
+writeConnectivity(const Connectivity<Dimension>& connectivity, HighFive::File& file, HighFive::Group& checkpoint_group)
+{
+  std::string connectivity_group_name = "connectivity/" + std::to_string(connectivity.id());
+  if (not checkpoint_group.exist(connectivity_group_name)) {
+    bool linked = false;
+    for (auto group_name : file.listObjectNames()) {
+      const std::string stored_connectivity_group_name = group_name + "/" + connectivity_group_name;
+      if (file.exist(stored_connectivity_group_name)) {
+        HighFive::Group stored_connectivity_group = file.getGroup(stored_connectivity_group_name);
+
+        const std::string type_name = stored_connectivity_group.getAttribute("type").read<std::string>();
+        if (type_name != "dual_connectivity") {
+          checkpoint_group.createHardLink(connectivity_group_name, file.getGroup(stored_connectivity_group_name));
+          linked = true;
+          break;
+        }
+      }
+    }
+
+    if (not linked) {
+      HighFive::Group connectivity_group = checkpoint_group.createGroup(connectivity_group_name);
+
+      connectivity_group.createAttribute("dimension", connectivity.dimension());
+      connectivity_group.createAttribute("id", connectivity.id());
+      connectivity_group.createAttribute("type", std::string{"unstructured"});
+
+      write(connectivity_group, "cell_to_node_matrix_values",
+            connectivity.getMatrix(ItemType::cell, ItemType::node).values());
+      write(connectivity_group, "cell_to_node_matrix_rowsMap",
+            connectivity.getMatrix(ItemType::cell, ItemType::node).rowsMap());
+
+      if constexpr (Dimension > 1) {
+        write(connectivity_group, "cell_to_face_matrix_values",
+              connectivity.getMatrix(ItemType::cell, ItemType::face).values());
+        write(connectivity_group, "cell_to_face_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::cell, ItemType::face).rowsMap());
+
+        write(connectivity_group, "face_to_node_matrix_values",
+              connectivity.getMatrix(ItemType::face, ItemType::node).values());
+        write(connectivity_group, "face_to_node_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::face, ItemType::node).rowsMap());
+
+        write(connectivity_group, "node_to_face_matrix_values",
+              connectivity.getMatrix(ItemType::node, ItemType::face).values());
+        write(connectivity_group, "node_to_face_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::node, ItemType::face).rowsMap());
+
+        write(connectivity_group, "cell_face_is_reversed", connectivity.cellFaceIsReversed().arrayView());
+      }
+
+      if constexpr (Dimension > 2) {
+        write(connectivity_group, "cell_to_edge_matrix_values",
+              connectivity.getMatrix(ItemType::cell, ItemType::edge).values());
+        write(connectivity_group, "cell_to_edge_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::cell, ItemType::edge).rowsMap());
+
+        write(connectivity_group, "face_to_edge_matrix_values",
+              connectivity.getMatrix(ItemType::face, ItemType::edge).values());
+        write(connectivity_group, "face_to_edge_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::face, ItemType::edge).rowsMap());
+
+        write(connectivity_group, "edge_to_node_matrix_values",
+              connectivity.getMatrix(ItemType::edge, ItemType::node).values());
+        write(connectivity_group, "edge_to_node_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::edge, ItemType::node).rowsMap());
+
+        write(connectivity_group, "node_to_edge_matrix_values",
+              connectivity.getMatrix(ItemType::node, ItemType::edge).values());
+        write(connectivity_group, "node_to_edge_matrix_rowsMap",
+              connectivity.getMatrix(ItemType::node, ItemType::edge).rowsMap());
+
+        write(connectivity_group, "face_edge_is_reversed", connectivity.faceEdgeIsReversed().arrayView());
+      }
+
+      write(connectivity_group, "cell_type", connectivity.cellType());
+
+      write(connectivity_group, "cell_numbers", connectivity.cellNumber());
+      write(connectivity_group, "node_numbers", connectivity.nodeNumber());
+
+      write(connectivity_group, "cell_owner", connectivity.cellOwner());
+      write(connectivity_group, "node_owner", connectivity.nodeOwner());
+
+      if constexpr (Dimension > 1) {
+        write(connectivity_group, "face_numbers", connectivity.faceNumber());
+
+        write(connectivity_group, "face_owner", connectivity.faceOwner());
+      }
+      if constexpr (Dimension > 2) {
+        write(connectivity_group, "edge_numbers", connectivity.edgeNumber());
+
+        write(connectivity_group, "edge_owner", connectivity.edgeOwner());
+      }
+
+      writeRefItemList<ItemType::cell>(connectivity, connectivity_group);
+      writeRefItemList<ItemType::face>(connectivity, connectivity_group);
+      writeRefItemList<ItemType::edge>(connectivity, connectivity_group);
+      writeRefItemList<ItemType::node>(connectivity, connectivity_group);
+    }
+  }
+}
+
+void
+writeConnectivity(const IConnectivity& connectivity, HighFive::File& file, HighFive::Group& checkpoint_group)
+{
+  switch (connectivity.dimension()) {
+  case 1: {
+    writeConnectivity(dynamic_cast<const Connectivity<1>&>(connectivity), file, checkpoint_group);
+    break;
+  }
+  case 2: {
+    writeConnectivity(dynamic_cast<const Connectivity<2>&>(connectivity), file, checkpoint_group);
+    break;
+  }
+  case 3: {
+    writeConnectivity(dynamic_cast<const Connectivity<3>&>(connectivity), file, checkpoint_group);
+    break;
+  }
+    // LCOV_EXCL_START
+  default: {
+    throw UnexpectedError("invalid connectivity dimension");
+  }
+    // LCOV_EXCL_STOP
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteConnectivity.hpp b/src/utils/checkpointing/WriteConnectivity.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8e3fb4334b7af81691929da12d5f3e28b8cc01c6
--- /dev/null
+++ b/src/utils/checkpointing/WriteConnectivity.hpp
@@ -0,0 +1,15 @@
+#ifndef WRITE_CONNECTIVITY_HPP
+#define WRITE_CONNECTIVITY_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class IConnectivity;
+
+namespace checkpointing
+{
+
+void writeConnectivity(const IConnectivity& connectivity, HighFive::File& file, HighFive::Group& checkpoint_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_CONNECTIVITY_HPP
diff --git a/src/utils/checkpointing/WriteDiscreteFunctionVariant.cpp b/src/utils/checkpointing/WriteDiscreteFunctionVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ddedb95cbad205b8b2f415104730f105d74bc630
--- /dev/null
+++ b/src/utils/checkpointing/WriteDiscreteFunctionVariant.cpp
@@ -0,0 +1,60 @@
+#include <utils/checkpointing/WriteDiscreteFunctionVariant.hpp>
+
+#include <language/modules/SchemeModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/DiscreteFunctionTypeHFType.hpp>
+#include <utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp>
+#include <utils/checkpointing/WriteItemArray.hpp>
+#include <utils/checkpointing/WriteItemValue.hpp>
+#include <utils/checkpointing/WriteMesh.hpp>
+
+namespace checkpointing
+{
+
+void
+writeDiscreteFunctionVariant(HighFive::Group& variable_group,
+                             std::shared_ptr<const DiscreteFunctionVariant> discrete_function_v,
+                             HighFive::File& file,
+                             HighFive::Group& checkpoint_group)
+{
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(discrete_function_v)>));
+
+  std::visit(
+    [&](auto&& discrete_function) {
+      auto mesh_v  = discrete_function.meshVariant();
+      using DFType = std::decay_t<decltype(discrete_function)>;
+      variable_group.createAttribute("Vh_type", discrete_function.descriptor().type());
+
+      variable_group.createAttribute("mesh_id", mesh_v->id());
+      writeMesh(mesh_v, file, checkpoint_group);
+      if constexpr (is_discrete_function_P0_v<DFType>) {
+        using data_type = std::decay_t<typename DFType::data_type>;
+        variable_group.createAttribute("data_type", dataTypeName(ast_node_data_type_from<data_type>));
+        write(variable_group, "values", discrete_function.cellValues());
+      } else if constexpr (is_discrete_function_P0_vector_v<DFType>) {
+        using data_type = std::decay_t<typename DFType::data_type>;
+        variable_group.createAttribute("data_type", dataTypeName(ast_node_data_type_from<data_type>));
+        write(variable_group, "values", discrete_function.cellArrays());
+      }
+    },
+    discrete_function_v->discreteFunction());
+}
+
+void
+writeDiscreteFunctionVariant(const std::string& symbol_name,
+                             const EmbeddedData& embedded_data,
+                             HighFive::File& file,
+                             HighFive::Group& checkpoint_group,
+                             HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const DiscreteFunctionVariant> discrete_function_p =
+    dynamic_cast<const DataHandler<const DiscreteFunctionVariant>&>(embedded_data.get()).data_ptr();
+
+  writeDiscreteFunctionVariant(variable_group, discrete_function_p, file, checkpoint_group);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteDiscreteFunctionVariant.hpp b/src/utils/checkpointing/WriteDiscreteFunctionVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..981d6ea5a2adc0756dcf8003b382a67a543f636e
--- /dev/null
+++ b/src/utils/checkpointing/WriteDiscreteFunctionVariant.hpp
@@ -0,0 +1,24 @@
+#ifndef WRITE_DISCRETE_FUNCTION_VARIANT_HPP
+#define WRITE_DISCRETE_FUNCTION_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class DiscreteFunctionVariant;
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeDiscreteFunctionVariant(HighFive::Group& variable_group,
+                                  std::shared_ptr<const DiscreteFunctionVariant> discrete_function_v,
+                                  HighFive::File& file,
+                                  HighFive::Group& checkpoint_group);
+
+void writeDiscreteFunctionVariant(const std::string& symbol_name,
+                                  const EmbeddedData& embedded_data,
+                                  HighFive::File& file,
+                                  HighFive::Group& checkpoint_group,
+                                  HighFive::Group& symbol_table_group);
+}   // namespace checkpointing
+
+#endif   // WRITE_DISCRETE_FUNCTION_VARIANT_HPP
diff --git a/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp b/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..66a8670583ae63442313da42c847bbeee758a2d5
--- /dev/null
+++ b/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp
@@ -0,0 +1,141 @@
+#include <utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp>
+
+#include <language/modules/SchemeModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <scheme/AxisBoundaryConditionDescriptor.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/FreeBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowListBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/OutflowBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <scheme/WallBoundaryConditionDescriptor.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp>
+#include <utils/checkpointing/WriteIBoundaryDescriptor.hpp>
+
+namespace checkpointing
+{
+
+void
+writeIBoundaryConditionDescriptor(HighFive::Group& variable_group,
+                                  std::shared_ptr<const IBoundaryConditionDescriptor> iboundary_condition_descriptor_p)
+{
+  const IBoundaryConditionDescriptor& iboundary_condition_descriptor = *iboundary_condition_descriptor_p;
+
+  variable_group.createAttribute("type",
+                                 dataTypeName(ast_node_data_type_from<decltype(iboundary_condition_descriptor_p)>));
+  variable_group.createAttribute("iboundary_condition_descriptor_type", iboundary_condition_descriptor.type());
+
+  HighFive::Group boundary_group = variable_group.createGroup("boundary");
+
+  switch (iboundary_condition_descriptor.type()) {
+  case IBoundaryConditionDescriptor::Type::axis: {
+    const AxisBoundaryConditionDescriptor& axis_bc_descriptor =
+      dynamic_cast<const AxisBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, axis_bc_descriptor.boundaryDescriptor());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::dirichlet: {
+    const DirichletBoundaryConditionDescriptor& dirichlet_bc_descriptor =
+      dynamic_cast<const DirichletBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, dirichlet_bc_descriptor.boundaryDescriptor());
+    variable_group.createAttribute("name", dirichlet_bc_descriptor.name());
+    variable_group.createAttribute("rhs_function_id", dirichlet_bc_descriptor.rhsSymbolId().id());
+    break;
+  }
+    // LCOV_EXCL_START
+  case IBoundaryConditionDescriptor::Type::external: {
+    throw NotImplementedError("checkpoint/resume with sockets");
+
+    break;
+  }
+    // LCOV_EXCL_STOP
+  case IBoundaryConditionDescriptor::Type::fourier: {
+    const FourierBoundaryConditionDescriptor& fourier_bc_descriptor =
+      dynamic_cast<const FourierBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, fourier_bc_descriptor.boundaryDescriptor());
+    variable_group.createAttribute("name", fourier_bc_descriptor.name());
+    variable_group.createAttribute("rhs_function_id", fourier_bc_descriptor.rhsSymbolId().id());
+    variable_group.createAttribute("mass_function_id", fourier_bc_descriptor.massSymbolId().id());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::fixed: {
+    const FixedBoundaryConditionDescriptor& fixed_bc_descriptor =
+      dynamic_cast<const FixedBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, fixed_bc_descriptor.boundaryDescriptor());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::free: {
+    const FreeBoundaryConditionDescriptor& free_bc_descriptor =
+      dynamic_cast<const FreeBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, free_bc_descriptor.boundaryDescriptor());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::inflow: {
+    const InflowBoundaryConditionDescriptor& inflow_bc_descriptor =
+      dynamic_cast<const InflowBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, inflow_bc_descriptor.boundaryDescriptor());
+    variable_group.createAttribute("function_id", inflow_bc_descriptor.functionSymbolId().id());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::inflow_list: {
+    const InflowListBoundaryConditionDescriptor& inflow_list_bc_descriptor =
+      dynamic_cast<const InflowListBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, inflow_list_bc_descriptor.boundaryDescriptor());
+    std::vector<size_t> function_id_list;
+    for (auto&& function_symbol_id : inflow_list_bc_descriptor.functionSymbolIdList()) {
+      function_id_list.push_back(function_symbol_id.id());
+    }
+    variable_group.createAttribute("function_id_list", function_id_list);
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::neumann: {
+    const NeumannBoundaryConditionDescriptor& neumann_bc_descriptor =
+      dynamic_cast<const NeumannBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, neumann_bc_descriptor.boundaryDescriptor());
+    variable_group.createAttribute("name", neumann_bc_descriptor.name());
+    variable_group.createAttribute("rhs_function_id", neumann_bc_descriptor.rhsSymbolId().id());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::outflow: {
+    const OutflowBoundaryConditionDescriptor& outflow_bc_descriptor =
+      dynamic_cast<const OutflowBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, outflow_bc_descriptor.boundaryDescriptor());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::symmetry: {
+    const SymmetryBoundaryConditionDescriptor& symmetric_bc_descriptor =
+      dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, symmetric_bc_descriptor.boundaryDescriptor());
+    break;
+  }
+  case IBoundaryConditionDescriptor::Type::wall: {
+    const WallBoundaryConditionDescriptor& wall_bc_descriptor =
+      dynamic_cast<const WallBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    writeIBoundaryDescriptor(boundary_group, wall_bc_descriptor.boundaryDescriptor());
+    break;
+  }
+  }
+}
+
+void
+writeIBoundaryConditionDescriptor(const std::string& symbol_name,
+                                  const EmbeddedData& embedded_data,
+                                  HighFive::File&,
+                                  HighFive::Group&,
+                                  HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IBoundaryConditionDescriptor> iboundary_condition_descriptor_p =
+    dynamic_cast<const DataHandler<const IBoundaryConditionDescriptor>&>(embedded_data.get()).data_ptr();
+
+  writeIBoundaryConditionDescriptor(variable_group, iboundary_condition_descriptor_p);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp b/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1cfc07416f4f7d59509d91997bc02c41857e4cd2
--- /dev/null
+++ b/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp
@@ -0,0 +1,23 @@
+#ifndef WRITE_IBOUNDARY_CONDITION_DESCRIPTOR_HPP
+#define WRITE_IBOUNDARY_CONDITION_DESCRIPTOR_HPP
+
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/IBoundaryConditionDescriptor.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+namespace checkpointing
+{
+
+void writeIBoundaryConditionDescriptor(
+  HighFive::Group& variable_group,
+  std::shared_ptr<const IBoundaryConditionDescriptor> iboundary_condition_descriptor_p);
+
+void writeIBoundaryConditionDescriptor(const std::string& symbol_name,
+                                       const EmbeddedData& embedded_data,
+                                       HighFive::File&,
+                                       HighFive::Group&,
+                                       HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_IBOUNDARY_CONDITION_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/WriteIBoundaryDescriptor.cpp b/src/utils/checkpointing/WriteIBoundaryDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..fee719592bcb72c7a9bb48a801d0afe001b1b116
--- /dev/null
+++ b/src/utils/checkpointing/WriteIBoundaryDescriptor.cpp
@@ -0,0 +1,53 @@
+#include <utils/checkpointing/WriteIBoundaryDescriptor.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <utils/checkpointing/IBoundaryDescriptorHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeIBoundaryDescriptor(HighFive::Group& variable_group, const IBoundaryDescriptor& iboundary_descriptor)
+{
+  variable_group.createAttribute("iboundary_descriptor_type", iboundary_descriptor.type());
+
+  switch (iboundary_descriptor.type()) {
+  case IBoundaryDescriptor::Type::named: {
+    const NamedBoundaryDescriptor& named_boundary_descriptor =
+      dynamic_cast<const NamedBoundaryDescriptor&>(iboundary_descriptor);
+    variable_group.createAttribute("name", named_boundary_descriptor.name());
+    break;
+  }
+  case IBoundaryDescriptor::Type::numbered: {
+    const NumberedBoundaryDescriptor& numbered_boundary_descriptor =
+      dynamic_cast<const NumberedBoundaryDescriptor&>(iboundary_descriptor);
+    variable_group.createAttribute("number", numbered_boundary_descriptor.number());
+    break;
+  }
+  }
+}
+
+void
+writeIBoundaryDescriptor(const std::string& symbol_name,
+                         const EmbeddedData& embedded_data,
+                         HighFive::File&,
+                         HighFive::Group&,
+                         HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IBoundaryDescriptor> iboundary_descriptor_p =
+    dynamic_cast<const DataHandler<const IBoundaryDescriptor>&>(embedded_data.get()).data_ptr();
+
+  const IBoundaryDescriptor& iboundary_descriptor = *iboundary_descriptor_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(iboundary_descriptor_p)>));
+
+  writeIBoundaryDescriptor(variable_group, iboundary_descriptor);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIBoundaryDescriptor.hpp b/src/utils/checkpointing/WriteIBoundaryDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4dce7298d9d846aa14ff1f3cd57b5e176a1d71a1
--- /dev/null
+++ b/src/utils/checkpointing/WriteIBoundaryDescriptor.hpp
@@ -0,0 +1,21 @@
+#ifndef WRITE_IBOUNDARY_DESCRIPTOR_HPP
+#define WRITE_IBOUNDARY_DESCRIPTOR_HPP
+
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/IBoundaryDescriptor.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+namespace checkpointing
+{
+
+void writeIBoundaryDescriptor(HighFive::Group& variable_group, const IBoundaryDescriptor& iboundary_descriptor);
+
+void writeIBoundaryDescriptor(const std::string& symbol_name,
+                              const EmbeddedData& embedded_data,
+                              HighFive::File&,
+                              HighFive::Group&,
+                              HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_IBOUNDARY_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/WriteIDiscreteFunctionDescriptor.cpp b/src/utils/checkpointing/WriteIDiscreteFunctionDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6633854b28befc0efbfc0adaf8f41e3f318de06f
--- /dev/null
+++ b/src/utils/checkpointing/WriteIDiscreteFunctionDescriptor.cpp
@@ -0,0 +1,32 @@
+#include <utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp>
+
+#include <language/modules/SchemeModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <utils/checkpointing/DiscreteFunctionTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeIDiscreteFunctionDescriptor(const std::string& symbol_name,
+                                 const EmbeddedData& embedded_data,
+                                 HighFive::File&,
+                                 HighFive::Group&,
+                                 HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IDiscreteFunctionDescriptor> idiscrete_function_desriptor_p =
+    dynamic_cast<const DataHandler<const IDiscreteFunctionDescriptor>&>(embedded_data.get()).data_ptr();
+
+  const IDiscreteFunctionDescriptor& idiscrete_function_descriptor = *idiscrete_function_desriptor_p;
+
+  variable_group.createAttribute("type",
+                                 dataTypeName(ast_node_data_type_from<decltype(idiscrete_function_desriptor_p)>));
+  variable_group.createAttribute("discrete_function_type", idiscrete_function_descriptor.type());
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp b/src/utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..89e1b33016abd8da16596cd1e86f0aee9a45194a
--- /dev/null
+++ b/src/utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp
@@ -0,0 +1,19 @@
+#ifndef WRITE_IDISCRETE_FUNCTION_DESCRIPTOR_HPP
+#define WRITE_IDISCRETE_FUNCTION_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeIDiscreteFunctionDescriptor(const std::string& symbol_name,
+                                      const EmbeddedData& embedded_data,
+                                      HighFive::File&,
+                                      HighFive::Group&,
+                                      HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_IDISCRETE_FUNCTION_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/WriteIInterfaceDescriptor.cpp b/src/utils/checkpointing/WriteIInterfaceDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e3b34dd5790562d0085a34c83acb1e6a15976cc8
--- /dev/null
+++ b/src/utils/checkpointing/WriteIInterfaceDescriptor.cpp
@@ -0,0 +1,47 @@
+#include <utils/checkpointing/WriteIInterfaceDescriptor.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
+#include <utils/checkpointing/IInterfaceDescriptorHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeIInterfaceDescriptor(const std::string& symbol_name,
+                          const EmbeddedData& embedded_data,
+                          HighFive::File&,
+                          HighFive::Group&,
+                          HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IInterfaceDescriptor> iinterface_descriptor_p =
+    dynamic_cast<const DataHandler<const IInterfaceDescriptor>&>(embedded_data.get()).data_ptr();
+
+  const IInterfaceDescriptor& iinterface_descriptor = *iinterface_descriptor_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(iinterface_descriptor_p)>));
+  variable_group.createAttribute("iinterface_descriptor_type", iinterface_descriptor.type());
+
+  switch (iinterface_descriptor.type()) {
+  case IInterfaceDescriptor::Type::named: {
+    const NamedInterfaceDescriptor& named_interface_descriptor =
+      dynamic_cast<const NamedInterfaceDescriptor&>(iinterface_descriptor);
+    variable_group.createAttribute("name", named_interface_descriptor.name());
+    break;
+  }
+  case IInterfaceDescriptor::Type::numbered: {
+    const NumberedInterfaceDescriptor& numbered_boundary_descriptor =
+      dynamic_cast<const NumberedInterfaceDescriptor&>(iinterface_descriptor);
+    variable_group.createAttribute("number", numbered_boundary_descriptor.number());
+    break;
+  }
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIInterfaceDescriptor.hpp b/src/utils/checkpointing/WriteIInterfaceDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..98a4e37abfe6535956347685d8212aabb4d0923e
--- /dev/null
+++ b/src/utils/checkpointing/WriteIInterfaceDescriptor.hpp
@@ -0,0 +1,19 @@
+#ifndef WRITE_IINTERFACE_DESCRIPTOR_HPP
+#define WRITE_IINTERFACE_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeIInterfaceDescriptor(const std::string& symbol_name,
+                               const EmbeddedData& embedded_data,
+                               HighFive::File&,
+                               HighFive::Group&,
+                               HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_IINTERFACE_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/WriteINamedDiscreteData.cpp b/src/utils/checkpointing/WriteINamedDiscreteData.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a1ed691091d18c568f5560119b1f8fb35ed93de5
--- /dev/null
+++ b/src/utils/checkpointing/WriteINamedDiscreteData.cpp
@@ -0,0 +1,66 @@
+#include <utils/checkpointing/WriteINamedDiscreteData.hpp>
+
+#include <language/modules/WriterModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <output/INamedDiscreteData.hpp>
+#include <output/NamedDiscreteFunction.hpp>
+#include <output/NamedItemArrayVariant.hpp>
+#include <output/NamedItemValueVariant.hpp>
+#include <utils/checkpointing/INamedDiscreteDataHFType.hpp>
+#include <utils/checkpointing/WriteDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/WriteItemArrayVariant.hpp>
+#include <utils/checkpointing/WriteItemValueVariant.hpp>
+
+namespace checkpointing
+{
+
+void
+writeINamedDiscreteData(const std::string& symbol_name,
+                        const EmbeddedData& embedded_data,
+                        HighFive::File& file,
+                        HighFive::Group& checkpoint_group,
+                        HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const INamedDiscreteData> inamed_discrete_data_p =
+    dynamic_cast<const DataHandler<const INamedDiscreteData>&>(embedded_data.get()).data_ptr();
+
+  const INamedDiscreteData& inamed_discrete_data = *inamed_discrete_data_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(inamed_discrete_data_p)>));
+  variable_group.createAttribute("named_discrete_data_type", inamed_discrete_data.type());
+  variable_group.createAttribute("named_discrete_data_name", inamed_discrete_data.name());
+
+  switch (inamed_discrete_data.type()) {
+  case INamedDiscreteData::Type::discrete_function: {
+    const NamedDiscreteFunction& named_discrete_function =
+      dynamic_cast<const NamedDiscreteFunction&>(inamed_discrete_data);
+
+    HighFive::Group discrete_function_group = variable_group.createGroup("discrete_function_variant");
+    writeDiscreteFunctionVariant(discrete_function_group, named_discrete_function.discreteFunctionVariant(), file,
+                                 checkpoint_group);
+    break;
+  }
+  case INamedDiscreteData::Type::item_array: {
+    const NamedItemArrayVariant& named_item_array_v = dynamic_cast<const NamedItemArrayVariant&>(inamed_discrete_data);
+
+    HighFive::Group item_array_group = variable_group.createGroup("item_array_variant");
+
+    writeItemArrayVariant(item_array_group, named_item_array_v.itemArrayVariant(), file, checkpoint_group);
+    break;
+  }
+  case INamedDiscreteData::Type::item_value: {
+    const NamedItemValueVariant& named_item_value_v = dynamic_cast<const NamedItemValueVariant&>(inamed_discrete_data);
+
+    HighFive::Group item_value_group = variable_group.createGroup("item_value_variant");
+
+    writeItemValueVariant(item_value_group, named_item_value_v.itemValueVariant(), file, checkpoint_group);
+    break;
+  }
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteINamedDiscreteData.hpp b/src/utils/checkpointing/WriteINamedDiscreteData.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..076e1318dc0596e1ed57af9abb57e3946cc3e5c8
--- /dev/null
+++ b/src/utils/checkpointing/WriteINamedDiscreteData.hpp
@@ -0,0 +1,18 @@
+#ifndef WRITE_INAMED_DISCRETE_DATA_HPP
+#define WRITE_INAMED_DISCRETE_DATA_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeINamedDiscreteData(const std::string& symbol_name,
+                             const EmbeddedData& embedded_data,
+                             HighFive::File& file,
+                             HighFive::Group& checkpoint_group,
+                             HighFive::Group& symbol_table_group);
+}   // namespace checkpointing
+
+#endif   // WRITE_INAMED_DISCRETE_DATA_HPP
diff --git a/src/utils/checkpointing/WriteIQuadratureDescriptor.cpp b/src/utils/checkpointing/WriteIQuadratureDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed0b2cb2dc31677689a8e9c7c07fa8e4d62c768c
--- /dev/null
+++ b/src/utils/checkpointing/WriteIQuadratureDescriptor.cpp
@@ -0,0 +1,30 @@
+#include <utils/checkpointing/WriteIQuadratureDescriptor.hpp>
+
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
+#include <language/utils/ASTNodeDataType.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <utils/checkpointing/QuadratureTypeHFType.hpp>
+
+namespace checkpointing
+{
+void
+writeIQuadratureDescriptor(const std::string& symbol_name,
+                           const EmbeddedData& embedded_data,
+                           HighFive::File&,
+                           HighFive::Group&,
+                           HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IQuadratureDescriptor> iquadrature_descriptor_p =
+    dynamic_cast<const DataHandler<const IQuadratureDescriptor>&>(embedded_data.get()).data_ptr();
+
+  const IQuadratureDescriptor& iquadrature_descriptor = *iquadrature_descriptor_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(iquadrature_descriptor_p)>));
+  variable_group.createAttribute("quadrature_type", iquadrature_descriptor.type());
+  variable_group.createAttribute("quadrature_degree", iquadrature_descriptor.degree());
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIQuadratureDescriptor.hpp b/src/utils/checkpointing/WriteIQuadratureDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..52439403bc169013f5e24bcee246a76a916f0bde
--- /dev/null
+++ b/src/utils/checkpointing/WriteIQuadratureDescriptor.hpp
@@ -0,0 +1,18 @@
+#ifndef WRITE_IQUADRATURE_DESCRIPTOR_HPP
+#define WRITE_IQUADRATURE_DESCRIPTOR_HPP
+
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+namespace checkpointing
+{
+
+void writeIQuadratureDescriptor(const std::string& symbol_name,
+                                const EmbeddedData& embedded_data,
+                                HighFive::File&,
+                                HighFive::Group&,
+                                HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_IQUADRATURE_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/WriteIWriter.cpp b/src/utils/checkpointing/WriteIWriter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b04b6b6cd3c2cff62762285c1fbd64e466be1182
--- /dev/null
+++ b/src/utils/checkpointing/WriteIWriter.cpp
@@ -0,0 +1,54 @@
+#include <utils/checkpointing/WriteIWriter.hpp>
+
+#include <language/modules/WriterModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <output/WriterBase.hpp>
+#include <utils/checkpointing/IWriterHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeIWriter(const std::string& symbol_name,
+             const EmbeddedData& embedded_data,
+             HighFive::File&,
+             HighFive::Group&,
+             HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IWriter> iwriter_p =
+    dynamic_cast<const DataHandler<const IWriter>&>(embedded_data.get()).data_ptr();
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(iwriter_p)>));
+
+  variable_group.createAttribute("iwriter_type", iwriter_p->type());
+
+  switch (iwriter_p->type()) {
+  case IWriter::Type::gnuplot:
+  case IWriter::Type::gnuplot_1d:
+  case IWriter::Type::gnuplot_raw:
+  case IWriter::Type::vtk: {
+    const WriterBase& writer = dynamic_cast<const WriterBase&>(*iwriter_p);
+
+    variable_group.createAttribute("base_filename", writer.m_base_filename);
+    if (writer.m_signature.has_value()) {
+      variable_group.createAttribute("signature", writer.m_signature.value());
+    }
+    if (writer.m_period_manager.has_value()) {
+      const WriterBase::PeriodManager period_manager = writer.m_period_manager.value();
+      HighFive::Group period_manager_group           = variable_group.createGroup("period_manager");
+
+      period_manager_group.createAttribute("time_period", period_manager.timePeriod());
+      period_manager_group.createAttribute("next_time", period_manager.nextTime());
+      period_manager_group.createAttribute("saved_times", period_manager.getSavedTimes());
+    }
+
+    break;
+  }
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIWriter.hpp b/src/utils/checkpointing/WriteIWriter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..309ed5cb18b9dcbd8f4bc23a5b0ce0d4fcc9e02c
--- /dev/null
+++ b/src/utils/checkpointing/WriteIWriter.hpp
@@ -0,0 +1,19 @@
+#ifndef WRITE_IWRITER_HPP
+#define WRITE_IWRITER_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeIWriter(const std::string& symbol_name,
+                  const EmbeddedData& embedded_data,
+                  HighFive::File&,
+                  HighFive::Group&,
+                  HighFive::Group& symbol_table_group);
+
+}
+
+#endif   // WRITE_IWRITER_HPP
diff --git a/src/utils/checkpointing/WriteIZoneDescriptor.cpp b/src/utils/checkpointing/WriteIZoneDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..32851b6a282a799c4e50105f03e49373324050f2
--- /dev/null
+++ b/src/utils/checkpointing/WriteIZoneDescriptor.cpp
@@ -0,0 +1,46 @@
+#include <utils/checkpointing/WriteIZoneDescriptor.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedZoneDescriptor.hpp>
+#include <mesh/NumberedZoneDescriptor.hpp>
+#include <utils/checkpointing/IZoneDescriptorHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeIZoneDescriptor(const std::string& symbol_name,
+                     const EmbeddedData& embedded_data,
+                     HighFive::File&,
+                     HighFive::Group&,
+                     HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const IZoneDescriptor> izone_descriptor_p =
+    dynamic_cast<const DataHandler<const IZoneDescriptor>&>(embedded_data.get()).data_ptr();
+
+  const IZoneDescriptor& izone_descriptor = *izone_descriptor_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(izone_descriptor_p)>));
+  variable_group.createAttribute("izone_descriptor_type", izone_descriptor.type());
+
+  switch (izone_descriptor.type()) {
+  case IZoneDescriptor::Type::named: {
+    const NamedZoneDescriptor& named_zone_descriptor = dynamic_cast<const NamedZoneDescriptor&>(izone_descriptor);
+    variable_group.createAttribute("name", named_zone_descriptor.name());
+    break;
+  }
+  case IZoneDescriptor::Type::numbered: {
+    const NumberedZoneDescriptor& numbered_boundary_descriptor =
+      dynamic_cast<const NumberedZoneDescriptor&>(izone_descriptor);
+    variable_group.createAttribute("number", numbered_boundary_descriptor.number());
+    break;
+  }
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteIZoneDescriptor.hpp b/src/utils/checkpointing/WriteIZoneDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c4de384c8aaf72848172286b4ce7f609ae67be8e
--- /dev/null
+++ b/src/utils/checkpointing/WriteIZoneDescriptor.hpp
@@ -0,0 +1,18 @@
+#ifndef WRITE_IZONE_DESCRIPTOR_HPP
+#define WRITE_IZONE_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeIZoneDescriptor(const std::string& symbol_name,
+                          const EmbeddedData& embedded_data,
+                          HighFive::File&,
+                          HighFive::Group&,
+                          HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_IZONE_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/WriteItemArray.hpp b/src/utils/checkpointing/WriteItemArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..1bc52b16b78f5f9c0df801e971e1ddb1ef355607
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemArray.hpp
@@ -0,0 +1,22 @@
+#ifndef WRITE_ITEM_ARRAY_HPP
+#define WRITE_ITEM_ARRAY_HPP
+
+#include <mesh/ItemArray.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/checkpointing/WriteTable.hpp>
+
+namespace checkpointing
+{
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+void
+write(HighFive::Group& group,
+      const std::string& name,
+      const ItemArray<DataType, item_type, ConnectivityPtr>& item_array)
+{
+  write(group, name, item_array.tableView());
+}
+
+}   // namespace checkpointing
+
+#endif   // WRITE_ITEM_ARRAY_HPP
diff --git a/src/utils/checkpointing/WriteItemArrayVariant.cpp b/src/utils/checkpointing/WriteItemArrayVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..df950cf1eb3b8f904ab66bae217af5ec264c8a26
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemArrayVariant.cpp
@@ -0,0 +1,56 @@
+#include <utils/checkpointing/WriteItemArrayVariant.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/ItemArrayVariant.hpp>
+#include <scheme/VariableBCDescriptor.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/WriteConnectivity.hpp>
+#include <utils/checkpointing/WriteItemArray.hpp>
+
+namespace checkpointing
+{
+
+void
+writeItemArrayVariant(HighFive::Group& variable_group,
+                      std::shared_ptr<const ItemArrayVariant> item_array_variant_v,
+                      HighFive::File& file,
+                      HighFive::Group& checkpoint_group)
+{
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(item_array_variant_v)>));
+
+  std::visit(
+    [&](auto&& item_array) {
+      using ItemArrayT = std::decay_t<decltype(item_array)>;
+
+      variable_group.createAttribute("item_type", ItemArrayT::item_t);
+      using data_type = std::decay_t<typename ItemArrayT::data_type>;
+      variable_group.createAttribute("data_type", dataTypeName(ast_node_data_type_from<data_type>));
+
+      const IConnectivity& connectivity = *item_array.connectivity_ptr();
+      variable_group.createAttribute("connectivity_id", connectivity.id());
+      writeConnectivity(connectivity, file, checkpoint_group);
+
+      write(variable_group, "arrays", item_array);
+    },
+    item_array_variant_v->itemArray());
+}
+
+void
+writeItemArrayVariant(const std::string& symbol_name,
+                      const EmbeddedData& embedded_data,
+                      HighFive::File& file,
+                      HighFive::Group& checkpoint_group,
+                      HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const ItemArrayVariant> item_array_variant_p =
+    dynamic_cast<const DataHandler<const ItemArrayVariant>&>(embedded_data.get()).data_ptr();
+
+  writeItemArrayVariant(variable_group, item_array_variant_p, file, checkpoint_group);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteItemArrayVariant.hpp b/src/utils/checkpointing/WriteItemArrayVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ddd9c2e70d74cff0f11b160c999cdc82016ab131
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemArrayVariant.hpp
@@ -0,0 +1,24 @@
+#ifndef WRITE_ITEM_ARRAY_VARIANT_HPP
+#define WRITE_ITEM_ARRAY_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+class ItemArrayVariant;
+
+namespace checkpointing
+{
+
+void writeItemArrayVariant(HighFive::Group& variable_group,
+                           std::shared_ptr<const ItemArrayVariant> item_array_variant_v,
+                           HighFive::File& file,
+                           HighFive::Group& checkpoint_group);
+
+void writeItemArrayVariant(const std::string& symbol_name,
+                           const EmbeddedData& embedded_data,
+                           HighFive::File& file,
+                           HighFive::Group& checkpoint_group,
+                           HighFive::Group& symbol_table_group);
+}   // namespace checkpointing
+
+#endif   // WRITE_ITEM_ARRAY_VARIANT_HPP
diff --git a/src/utils/checkpointing/WriteItemType.cpp b/src/utils/checkpointing/WriteItemType.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8eff5445bd17912cd58b85da202e81c008f76704
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemType.cpp
@@ -0,0 +1,30 @@
+#include <utils/checkpointing/WriteItemType.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <mesh/ItemType.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeItemType(const std::string& symbol_name,
+              const EmbeddedData& embedded_data,
+              HighFive::File&,
+              HighFive::Group&,
+              HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const ItemType> item_type_p =
+    dynamic_cast<const DataHandler<const ItemType>&>(embedded_data.get()).data_ptr();
+
+  const ItemType& item_type = *item_type_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(item_type_p)>));
+  variable_group.createAttribute("item_type", item_type);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteItemType.hpp b/src/utils/checkpointing/WriteItemType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b06abb59fd7316d4af97f27063e597af4bc3ddcc
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemType.hpp
@@ -0,0 +1,18 @@
+#ifndef WRITE_ITEM_TYPE_HPP
+#define WRITE_ITEM_TYPE_HPP
+
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+namespace checkpointing
+{
+
+void writeItemType(const std::string& symbol_name,
+                   const EmbeddedData& embedded_data,
+                   HighFive::File&,
+                   HighFive::Group&,
+                   HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_ITEM_TYPE_HPP
diff --git a/src/utils/checkpointing/WriteItemValue.hpp b/src/utils/checkpointing/WriteItemValue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc6739b84fc25c50e3f8c7aeb67e585759d8a824
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemValue.hpp
@@ -0,0 +1,22 @@
+#ifndef WRITE_ITEM_VALUE_HPP
+#define WRITE_ITEM_VALUE_HPP
+
+#include <mesh/ItemValue.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/checkpointing/WriteArray.hpp>
+
+namespace checkpointing
+{
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+void
+write(HighFive::Group& group,
+      const std::string& name,
+      const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  write(group, name, item_value.arrayView());
+}
+
+}   // namespace checkpointing
+
+#endif   // WRITE_ITEM_VALUE_HPP
diff --git a/src/utils/checkpointing/WriteItemValueVariant.cpp b/src/utils/checkpointing/WriteItemValueVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..47246049d84dce9b586c35908805fe344c27efd3
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemValueVariant.cpp
@@ -0,0 +1,56 @@
+#include <utils/checkpointing/WriteItemValueVariant.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <scheme/VariableBCDescriptor.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/WriteConnectivity.hpp>
+#include <utils/checkpointing/WriteItemValue.hpp>
+
+namespace checkpointing
+{
+
+void
+writeItemValueVariant(HighFive::Group& variable_group,
+                      std::shared_ptr<const ItemValueVariant> item_value_variant_v,
+                      HighFive::File& file,
+                      HighFive::Group& checkpoint_group)
+{
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(item_value_variant_v)>));
+
+  std::visit(
+    [&](auto&& item_value) {
+      using ItemValueT = std::decay_t<decltype(item_value)>;
+
+      variable_group.createAttribute("item_type", ItemValueT::item_t);
+      using data_type = std::decay_t<typename ItemValueT::data_type>;
+      variable_group.createAttribute("data_type", dataTypeName(ast_node_data_type_from<data_type>));
+
+      const IConnectivity& connectivity = *item_value.connectivity_ptr();
+      variable_group.createAttribute("connectivity_id", connectivity.id());
+      writeConnectivity(connectivity, file, checkpoint_group);
+
+      write(variable_group, "values", item_value);
+    },
+    item_value_variant_v->itemValue());
+}
+
+void
+writeItemValueVariant(const std::string& symbol_name,
+                      const EmbeddedData& embedded_data,
+                      HighFive::File& file,
+                      HighFive::Group& checkpoint_group,
+                      HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const ItemValueVariant> item_value_variant_p =
+    dynamic_cast<const DataHandler<const ItemValueVariant>&>(embedded_data.get()).data_ptr();
+
+  writeItemValueVariant(variable_group, item_value_variant_p, file, checkpoint_group);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteItemValueVariant.hpp b/src/utils/checkpointing/WriteItemValueVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..55157df238f069f0e571bff064e08a10a28076bc
--- /dev/null
+++ b/src/utils/checkpointing/WriteItemValueVariant.hpp
@@ -0,0 +1,25 @@
+#ifndef WRITE_ITEM_VALUE_VARIANT_HPP
+#define WRITE_ITEM_VALUE_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+class ItemValueVariant;
+
+namespace checkpointing
+{
+
+void writeItemValueVariant(HighFive::Group& variable_group,
+                           std::shared_ptr<const ItemValueVariant> item_value_variant_v,
+                           HighFive::File& file,
+                           HighFive::Group& checkpoint_group);
+
+void writeItemValueVariant(const std::string& symbol_name,
+                           const EmbeddedData& embedded_data,
+                           HighFive::File& file,
+                           HighFive::Group& checkpoint_group,
+                           HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_ITEM_VALUE_VARIANT_HPP
diff --git a/src/utils/checkpointing/WriteMesh.cpp b/src/utils/checkpointing/WriteMesh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..09212cebe092e884deb7cff6d7bfb48968fb5c11
--- /dev/null
+++ b/src/utils/checkpointing/WriteMesh.cpp
@@ -0,0 +1,75 @@
+#include <utils/checkpointing/WriteMesh.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/checkpointing/WriteConnectivity.hpp>
+#include <utils/checkpointing/WriteItemValue.hpp>
+
+namespace checkpointing
+{
+
+void
+writeMesh(std::shared_ptr<const MeshVariant> mesh_v, HighFive::File& file, HighFive::Group& checkpoint_group)
+{
+  std::string mesh_group_name = "mesh/" + std::to_string(mesh_v->id());
+  if (not checkpoint_group.exist(mesh_group_name)) {
+    bool linked = false;
+    for (auto group_name : file.listObjectNames()) {
+      if (file.exist(group_name + "/" + mesh_group_name)) {
+        checkpoint_group.createHardLink(mesh_group_name, file.getGroup(group_name + "/" + mesh_group_name));
+        linked = true;
+        break;
+      }
+    }
+
+    if (not linked) {
+      HighFive::Group mesh_group = checkpoint_group.createGroup(mesh_group_name);
+      mesh_group.createAttribute("connectivity", mesh_v->connectivity().id());
+      std::visit(
+        [&](auto&& mesh) {
+          using MeshType = mesh_type_t<decltype(mesh)>;
+          if constexpr (is_polygonal_mesh_v<MeshType>) {
+            mesh_group.createAttribute("id", mesh->id());
+            mesh_group.createAttribute("type", std::string{"polygonal"});
+            mesh_group.createAttribute("dimension", mesh->dimension());
+            write(mesh_group, "xr", mesh->xr());
+          } else {
+            throw UnexpectedError("unexpected mesh type");
+          }
+        },
+        mesh_v->variant());
+    }
+  }
+
+  std::visit(
+    [&](auto&& mesh) {
+      using MeshType = mesh_type_t<decltype(mesh)>;
+      if constexpr (is_polygonal_mesh_v<MeshType>) {
+        writeConnectivity(mesh->connectivity(), file, checkpoint_group);
+      }
+    },
+    mesh_v->variant());
+}
+
+void
+writeMesh(const std::string& symbol_name,
+          const EmbeddedData& embedded_data,
+          HighFive::File& file,
+          HighFive::Group& checkpoint_group,
+          HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const MeshVariant> mesh_v =
+    dynamic_cast<const DataHandler<const MeshVariant>&>(embedded_data.get()).data_ptr();
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(mesh_v)>));
+  variable_group.createAttribute("id", mesh_v->id());
+
+  writeMesh(mesh_v, file, checkpoint_group);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteMesh.hpp b/src/utils/checkpointing/WriteMesh.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..07ce40ad29e97545a31d99732a3074b227855f63
--- /dev/null
+++ b/src/utils/checkpointing/WriteMesh.hpp
@@ -0,0 +1,22 @@
+#ifndef WRITE_MESH_HPP
+#define WRITE_MESH_HPP
+
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+class MeshVariant;
+
+namespace checkpointing
+{
+
+void writeMesh(std::shared_ptr<const MeshVariant> mesh_v, HighFive::File& file, HighFive::Group& checkpoint_group);
+
+void writeMesh(const std::string& symbol_name,
+               const EmbeddedData& embedded_data,
+               HighFive::File& file,
+               HighFive::Group& checkpoint_group,
+               HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_MESH_HPP
diff --git a/src/utils/checkpointing/WriteOStream.cpp b/src/utils/checkpointing/WriteOStream.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..51201f5987abca1689123d52833efe5520691f4a
--- /dev/null
+++ b/src/utils/checkpointing/WriteOStream.cpp
@@ -0,0 +1,42 @@
+#include <utils/checkpointing/WriteOStream.hpp>
+
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/OFStream.hpp>
+#include <language/utils/OStream.hpp>
+#include <utils/Exceptions.hpp>
+#include <utils/checkpointing/OStreamTypeHFType.hpp>
+
+namespace checkpointing
+{
+
+void
+writeOStream(const std::string& symbol_name,
+             const EmbeddedData& embedded_data,
+             HighFive::File&,
+             HighFive::Group&,
+             HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const OStream> ostream_p =
+    dynamic_cast<const DataHandler<const OStream>&>(embedded_data.get()).data_ptr();
+
+  const OStream& ostream = *ostream_p;
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(ostream_p)>));
+  variable_group.createAttribute("ostream_type", ostream.type());
+
+  switch (ostream.type()) {
+  case OStream::Type::std_ofstream: {
+    const OFStream& ofstream = dynamic_cast<const OFStream&>(ostream);
+    variable_group.createAttribute("filename", ofstream.filename());
+    break;
+  }
+  case OStream::Type::std_ostream: {
+    throw NotImplementedError("std::ostream checkpoint");
+  }
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteOStream.hpp b/src/utils/checkpointing/WriteOStream.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..14616a50ce10cdf21b4de2f313807ac162ef5950
--- /dev/null
+++ b/src/utils/checkpointing/WriteOStream.hpp
@@ -0,0 +1,17 @@
+#ifndef WRITE_OSTREAM_HPP
+#define WRITE_OSTREAM_HPP
+
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+
+namespace checkpointing
+{
+
+void writeOStream(const std::string& symbol_name,
+                  const EmbeddedData& embedded_data,
+                  HighFive::File&,
+                  HighFive::Group&,
+                  HighFive::Group& symbol_table_group);
+}   // namespace checkpointing
+
+#endif   // WRITE_OSTREAM_HPP
diff --git a/src/utils/checkpointing/WriteSubItemArrayPerItemVariant.cpp b/src/utils/checkpointing/WriteSubItemArrayPerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d71838fb6dd57550cadbb5452d69a8b7c9009f0b
--- /dev/null
+++ b/src/utils/checkpointing/WriteSubItemArrayPerItemVariant.cpp
@@ -0,0 +1,57 @@
+#include <utils/checkpointing/WriteSubItemArrayPerItemVariant.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/SubItemArrayPerItemVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/WriteConnectivity.hpp>
+#include <utils/checkpointing/WriteTable.hpp>
+
+namespace checkpointing
+{
+
+void
+writeSubItemArrayPerItemVariant(HighFive::Group& variable_group,
+                                std::shared_ptr<const SubItemArrayPerItemVariant> sub_item_array_per_item_variant_v,
+                                HighFive::File& file,
+                                HighFive::Group& checkpoint_group)
+{
+  variable_group.createAttribute("type",
+                                 dataTypeName(ast_node_data_type_from<decltype(sub_item_array_per_item_variant_v)>));
+
+  std::visit(
+    [&](auto&& sub_item_array_per_item) {
+      using SubItemArrayPerItemT = std::decay_t<decltype(sub_item_array_per_item)>;
+
+      variable_group.createAttribute("item_type", SubItemArrayPerItemT::item_type);
+      variable_group.createAttribute("sub_item_type", SubItemArrayPerItemT::sub_item_type);
+      using data_type = std::decay_t<typename SubItemArrayPerItemT::data_type>;
+      variable_group.createAttribute("data_type", dataTypeName(ast_node_data_type_from<data_type>));
+
+      const IConnectivity& connectivity = *sub_item_array_per_item.connectivity_ptr();
+      variable_group.createAttribute("connectivity_id", connectivity.id());
+      writeConnectivity(connectivity, file, checkpoint_group);
+
+      write(variable_group, "arrays", sub_item_array_per_item.tableView());
+    },
+    sub_item_array_per_item_variant_v->subItemArrayPerItem());
+}
+
+void
+writeSubItemArrayPerItemVariant(const std::string& symbol_name,
+                                const EmbeddedData& embedded_data,
+                                HighFive::File& file,
+                                HighFive::Group& checkpoint_group,
+                                HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const SubItemArrayPerItemVariant> sub_item_array_per_item_variant_p =
+    dynamic_cast<const DataHandler<const SubItemArrayPerItemVariant>&>(embedded_data.get()).data_ptr();
+
+  writeSubItemArrayPerItemVariant(variable_group, sub_item_array_per_item_variant_p, file, checkpoint_group);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteSubItemArrayPerItemVariant.hpp b/src/utils/checkpointing/WriteSubItemArrayPerItemVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8e03a4d925c9e29bb13e791792e0b5d48200bb7d
--- /dev/null
+++ b/src/utils/checkpointing/WriteSubItemArrayPerItemVariant.hpp
@@ -0,0 +1,18 @@
+#ifndef WRITE_SUB_ITEM_ARRAY_PER_ITEM_VARIANT_HPP
+#define WRITE_SUB_ITEM_ARRAY_PER_ITEM_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeSubItemArrayPerItemVariant(const std::string& symbol_name,
+                                     const EmbeddedData& embedded_data,
+                                     HighFive::File& file,
+                                     HighFive::Group& checkpoint_group,
+                                     HighFive::Group& symbol_table_group);
+}   // namespace checkpointing
+
+#endif   // WRITE_SUB_ITEM_ARRAY_PER_ITEM_VARIANT_HPP
diff --git a/src/utils/checkpointing/WriteSubItemValuePerItemVariant.cpp b/src/utils/checkpointing/WriteSubItemValuePerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dfc7d49df769c8d9eec4aa5f31a04ad0b09fcc0f
--- /dev/null
+++ b/src/utils/checkpointing/WriteSubItemValuePerItemVariant.cpp
@@ -0,0 +1,57 @@
+#include <utils/checkpointing/WriteSubItemValuePerItemVariant.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/WriteArray.hpp>
+#include <utils/checkpointing/WriteConnectivity.hpp>
+
+namespace checkpointing
+{
+
+void
+writeSubItemValuePerItemVariant(HighFive::Group& variable_group,
+                                std::shared_ptr<const SubItemValuePerItemVariant> sub_item_value_per_item_variant_v,
+                                HighFive::File& file,
+                                HighFive::Group& checkpoint_group)
+{
+  variable_group.createAttribute("type",
+                                 dataTypeName(ast_node_data_type_from<decltype(sub_item_value_per_item_variant_v)>));
+
+  std::visit(
+    [&](auto&& sub_item_value_per_item) {
+      using SubItemValuePerItemT = std::decay_t<decltype(sub_item_value_per_item)>;
+
+      variable_group.createAttribute("item_type", SubItemValuePerItemT::item_type);
+      variable_group.createAttribute("sub_item_type", SubItemValuePerItemT::sub_item_type);
+      using data_type = std::decay_t<typename SubItemValuePerItemT::data_type>;
+      variable_group.createAttribute("data_type", dataTypeName(ast_node_data_type_from<data_type>));
+
+      const IConnectivity& connectivity = *sub_item_value_per_item.connectivity_ptr();
+      variable_group.createAttribute("connectivity_id", connectivity.id());
+      writeConnectivity(connectivity, file, checkpoint_group);
+
+      write(variable_group, "values", sub_item_value_per_item.arrayView());
+    },
+    sub_item_value_per_item_variant_v->subItemValuePerItem());
+}
+
+void
+writeSubItemValuePerItemVariant(const std::string& symbol_name,
+                                const EmbeddedData& embedded_data,
+                                HighFive::File& file,
+                                HighFive::Group& checkpoint_group,
+                                HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const SubItemValuePerItemVariant> sub_item_value_per_item_variant_p =
+    dynamic_cast<const DataHandler<const SubItemValuePerItemVariant>&>(embedded_data.get()).data_ptr();
+
+  writeSubItemValuePerItemVariant(variable_group, sub_item_value_per_item_variant_p, file, checkpoint_group);
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteSubItemValuePerItemVariant.hpp b/src/utils/checkpointing/WriteSubItemValuePerItemVariant.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e1842cad5852b7d2ba0be9a9d5593641893af7c1
--- /dev/null
+++ b/src/utils/checkpointing/WriteSubItemValuePerItemVariant.hpp
@@ -0,0 +1,19 @@
+#ifndef WRITE_SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
+#define WRITE_SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeSubItemValuePerItemVariant(const std::string& symbol_name,
+                                     const EmbeddedData& embedded_data,
+                                     HighFive::File& file,
+                                     HighFive::Group& checkpoint_group,
+                                     HighFive::Group& symbol_table_group);
+
+}   // namespace checkpointing
+
+#endif   // WRITE_SUB_ITEM_VALUE_PER_ITEM_VARIANT_HPP
diff --git a/src/utils/checkpointing/WriteTable.hpp b/src/utils/checkpointing/WriteTable.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..b52455950081a784bf440afa8cab84c0842bb5a7
--- /dev/null
+++ b/src/utils/checkpointing/WriteTable.hpp
@@ -0,0 +1,76 @@
+#ifndef WRITE_TABLE_HPP
+#define WRITE_TABLE_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+#include <mesh/CellType.hpp>
+#include <mesh/ItemId.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/Table.hpp>
+
+namespace checkpointing
+{
+
+template <typename DataType>
+PUGS_INLINE void
+write(HighFive::Group& group, const std::string& name, const Table<DataType>& table)
+{
+  const size_t number_of_columns = parallel::allReduceMax(table.numberOfColumns());
+  // LCOV_EXCL_START
+  if ((table.numberOfColumns() != number_of_columns) and (table.numberOfRows() > 0)) {
+    throw UnexpectedError("table must have same number of columns in parallel");
+  }
+  // LCOV_EXCL_STOP
+
+  auto get_address = [](auto& t) { return (t.numberOfRows() * t.numberOfColumns() > 0) ? &(t(0, 0)) : nullptr; };
+
+  Array<size_t> number_of_rows_per_rank = parallel::allGather(table.numberOfRows());
+  size_t global_size                    = sum(number_of_rows_per_rank) * number_of_columns;
+
+  size_t current_offset = 0;
+  for (size_t i = 0; i < parallel::rank(); ++i) {
+    current_offset += number_of_rows_per_rank[i] * table.numberOfColumns();   // LCOV_EXCL_LINE
+  }
+  std::vector<size_t> offset{current_offset, 0ul};
+  std::vector<size_t> count{table.numberOfRows() * table.numberOfColumns()};
+
+  using data_type = std::remove_const_t<DataType>;
+  HighFive::DataSetCreateProps properties;
+  // properties.add(HighFive::Chunking(std::vector<hsize_t>{std::min(4ul * 1024ul * 1024ul, global_size)}));
+  // properties.add(HighFive::Shuffle());
+  // properties.add(HighFive::Deflate(3));
+
+  auto xfer_props = HighFive::DataTransferProps{};
+  xfer_props.add(HighFive::UseCollectiveIO{});
+
+  HighFive::DataSet dataset;
+
+  if constexpr (std::is_same_v<CellType, data_type>) {
+    using base_type = std::underlying_type_t<CellType>;
+    dataset = group.createDataSet<base_type>(name, HighFive::DataSpace{std::vector<size_t>{global_size}}, properties);
+    dataset.select(offset, count)
+      .template write_raw<base_type>(reinterpret_cast<const base_type*>(get_address(table)), xfer_props);
+  } else if constexpr ((std::is_same_v<CellId, data_type>) or (std::is_same_v<FaceId, data_type>) or
+                       (std::is_same_v<EdgeId, data_type>) or (std::is_same_v<NodeId, data_type>)) {
+    using base_type = typename data_type::base_type;
+
+    dataset = group.createDataSet<base_type>(name, HighFive::DataSpace{std::vector<size_t>{global_size}}, properties);
+    dataset.select(offset, count)
+      .template write_raw<base_type>(reinterpret_cast<const base_type*>(get_address(table)), xfer_props);
+  } else {
+    dataset = group.createDataSet<data_type>(name, HighFive::DataSpace{std::vector<size_t>{global_size}}, properties);
+    dataset.select(offset, count).template write_raw<data_type>(get_address(table), xfer_props);
+  }
+
+  std::vector<size_t> number_of_rows_per_rank_vector;
+  for (size_t i = 0; i < number_of_rows_per_rank.size(); ++i) {
+    number_of_rows_per_rank_vector.push_back(number_of_rows_per_rank[i]);
+  }
+
+  dataset.createAttribute("number_of_rows_per_rank", number_of_rows_per_rank_vector);
+  dataset.createAttribute("number_of_columns", number_of_columns);
+}
+
+}   // namespace checkpointing
+
+#endif   // WRITE_TABLE_HPP
diff --git a/src/utils/checkpointing/WriteVariableBCDescriptor.cpp b/src/utils/checkpointing/WriteVariableBCDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7d4432e80052546bf35ecaca6313bac5d504d580
--- /dev/null
+++ b/src/utils/checkpointing/WriteVariableBCDescriptor.cpp
@@ -0,0 +1,43 @@
+#include <utils/checkpointing/WriteVariableBCDescriptor.hpp>
+
+#include <language/modules/SchemeModuleTypes.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/VariableBCDescriptor.hpp>
+#include <utils/checkpointing/DiscreteFunctionTypeHFType.hpp>
+#include <utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp>
+#include <utils/checkpointing/WriteDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp>
+
+namespace checkpointing
+{
+
+void
+writeVariableBCDescriptor(const std::string& symbol_name,
+                          const EmbeddedData& embedded_data,
+                          HighFive::File& file,
+                          HighFive::Group& checkpoint_group,
+                          HighFive::Group& symbol_table_group)
+{
+  HighFive::Group variable_group = symbol_table_group.createGroup("embedded/" + symbol_name);
+
+  std::shared_ptr<const VariableBCDescriptor> variable_bc_descriptor_p =
+    dynamic_cast<const DataHandler<const VariableBCDescriptor>&>(embedded_data.get()).data_ptr();
+
+  variable_group.createAttribute("type", dataTypeName(ast_node_data_type_from<decltype(variable_bc_descriptor_p)>));
+
+  HighFive::Group discrete_function_group = variable_group.createGroup("discrete_function");
+  writeDiscreteFunctionVariant(discrete_function_group, variable_bc_descriptor_p->discreteFunctionVariant(), file,
+                               checkpoint_group);
+
+  const auto bc_descriptor_list            = variable_bc_descriptor_p->bcDescriptorList();
+  HighFive::Group bc_descriptor_list_group = variable_group.createGroup("bc_descriptor_list");
+  for (size_t i_bc_descriptor = 0; i_bc_descriptor < bc_descriptor_list.size(); ++i_bc_descriptor) {
+    HighFive::Group bc_descriptor_group = bc_descriptor_list_group.createGroup(std::to_string(i_bc_descriptor));
+
+    writeIBoundaryConditionDescriptor(bc_descriptor_group, bc_descriptor_list[i_bc_descriptor]);
+  }
+}
+
+}   // namespace checkpointing
diff --git a/src/utils/checkpointing/WriteVariableBCDescriptor.hpp b/src/utils/checkpointing/WriteVariableBCDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..ab82a6f7498cb1053a0fb92faad784a63e233c34
--- /dev/null
+++ b/src/utils/checkpointing/WriteVariableBCDescriptor.hpp
@@ -0,0 +1,19 @@
+#ifndef WRITE_VARIABLE_BC_DESCRIPTOR_HPP
+#define WRITE_VARIABLE_BC_DESCRIPTOR_HPP
+
+#include <utils/HighFivePugsUtils.hpp>
+
+class EmbeddedData;
+
+namespace checkpointing
+{
+
+void writeVariableBCDescriptor(const std::string& symbol_name,
+                               const EmbeddedData& embedded_data,
+                               HighFive::File& file,
+                               HighFive::Group& checkpoint_group,
+                               HighFive::Group& symbol_table_group);
+
+}
+
+#endif   // WRITE_VARIABLE_BC_DESCRIPTOR_HPP
diff --git a/src/utils/checkpointing/pugs_checkpoint_main.cpp b/src/utils/checkpointing/pugs_checkpoint_main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5373923d06fe35cfb8dc7766349f7ac4b3bb3864
--- /dev/null
+++ b/src/utils/checkpointing/pugs_checkpoint_main.cpp
@@ -0,0 +1,63 @@
+#include <CLI/CLI.hpp>
+#include <rang.hpp>
+#include <utils/ConsoleManager.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/checkpointing/PrintCheckpointInfo.hpp>
+#include <utils/checkpointing/PrintScriptFrom.hpp>
+#include <utils/checkpointing/SetResumeFrom.hpp>
+
+int
+main(int argc, char* argv[])
+{
+  std::string filename;
+
+  CLI::App app{"pugs_checkpoint help"};
+  app.description("A collection of simple tools to manage checkpoint/resume files for pugs.");
+  app.add_option("filename", filename, "pugs checkpoint file (HDF5)")->check(CLI::ExistingFile)->required();
+
+  bool enable_color = true;
+  app.add_flag("--color,!--no-color", enable_color, "Colorize console output [default: true]");
+
+  bool print_info = false;
+  auto info_flag  = app.add_flag("--info", print_info, "Print checkpoints info");
+
+  uint64_t checkpoint_number = 0;
+  auto resume_from =
+    app.add_option("--resume-from", checkpoint_number, "Use the given checkpoint number for next resume")
+      ->excludes(info_flag);
+
+  auto get_script_resume_from =
+    app.add_option("--get-script-from", checkpoint_number, "Print script file used for a given checkpoint number")
+      ->excludes(info_flag)
+      ->excludes(resume_from);
+
+  std::atexit([]() { std::cout << rang::style::reset; });
+  try {
+    app.parse(argc, argv);
+  }
+  catch (const CLI::ParseError& e) {
+    std::exit(app.exit(e, std::cout, std::cerr));
+  }
+
+  parallel::Messenger::create(argc, argv);
+  ConsoleManager::init(enable_color);
+
+  try {
+    if (*info_flag) {
+      printCheckpointInfo(filename);
+    } else if (*resume_from) {
+      setResumeFrom(filename, checkpoint_number);
+    } else if (*get_script_resume_from) {
+      printScriptFrom(filename, checkpoint_number);
+    }
+  }
+  catch (const std::runtime_error& e) {
+    std::cerr << e.what() << '\n';
+    parallel::Messenger::destroy();
+    std::exit(1);
+  }
+
+  parallel::Messenger::destroy();
+
+  return 0;
+}
diff --git a/src/utils/pugs_config.hpp.in b/src/utils/pugs_config.hpp.in
index 4dc5babd4c157b9b41161dfbd3bb1156c1c6b631..7402a526d5f34505dbda8524226c971e4701d4b6 100644
--- a/src/utils/pugs_config.hpp.in
+++ b/src/utils/pugs_config.hpp.in
@@ -6,6 +6,7 @@
 #cmakedefine PUGS_HAS_PETSC
 #cmakedefine PUGS_HAS_SLEPC
 #cmakedefine PUGS_HAS_HDF5
+#cmakedefine PUGS_HAS_SLURM
 
 #cmakedefine SYSTEM_IS_LINUX
 #cmakedefine SYSTEM_IS_DARWIN
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index e7665f8944202ff0c139ca1ccce1403026891f50..871bfbe45bded361f68b64b0a315f7daa9e508a7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -4,6 +4,12 @@ include_directories(${PUGS_SOURCE_DIR}/src)
 include_directories(${PUGS_BINARY_DIR}/src)
 include_directories(${PUGS_SOURCE_DIR}/tests)
 
+set(checkpointing_sequential_TESTS
+  # this one should enventually integrate parallel tests
+  test_checkpointing_Checkpoint_sequential.cpp
+  test_checkpointing_Resume_sequential.cpp
+)
+
 add_executable (unit_tests
   test_main.cpp
   test_AffectationProcessor.cpp
@@ -12,7 +18,10 @@ add_executable (unit_tests
   test_Array.cpp
   test_ArraySubscriptProcessor.cpp
   test_ASTBuilder.cpp
+  test_ASTCheckpoint.cpp
+  test_ASTCheckpointsInfo.cpp
   test_ASTDotPrinter.cpp
+  test_ASTExecutionStack.cpp
   test_ASTModulesImporter.cpp
   test_ASTNode.cpp
   test_ASTNodeAffectationExpressionBuilder.cpp
@@ -55,6 +64,7 @@ add_executable (unit_tests
   test_CastArray.cpp
   test_CellIntegrator.cpp
   test_CellType.cpp
+  test_CommunicatorManager.cpp
   test_ConsoleManager.cpp
   test_CG.cpp
   test_ConcatExpressionProcessor.cpp
@@ -86,6 +96,7 @@ add_executable (unit_tests
   test_EscapedString.cpp
   test_Exceptions.cpp
   test_ExecutionPolicy.cpp
+  test_Exit.cpp
   test_FaceIntegrator.cpp
   test_FakeProcessor.cpp
   test_ForProcessor.cpp
@@ -130,14 +141,17 @@ add_executable (unit_tests
   test_QuadratureType.cpp
   test_RefId.cpp
   test_RefItemList.cpp
+  test_ReproducibleSumManager.cpp
   test_RevisionInfo.cpp
   test_SmallArray.cpp
   test_SmallMatrix.cpp
   test_SmallVector.cpp
   test_Socket.cpp
   test_SocketModule.cpp
+  test_SourceLocation.cpp
   test_SquareGaussQuadrature.cpp
   test_SquareTransformation.cpp
+  test_Stop.cpp
   test_SymbolTable.cpp
   test_Table.cpp
   test_TetrahedronGaussQuadrature.cpp
@@ -154,7 +168,46 @@ add_executable (unit_tests
   test_UnaryOperatorMangler.cpp
   test_Vector.cpp
   test_WhileProcessor.cpp
+  ${checkpointing_sequential_TESTS}
+  )
+
+  set(checkpointing_TESTS
+    test_checkpointing_Checkpoint.cpp
+    test_checkpointing_PrintCheckpointInfo.cpp
+    test_checkpointing_PrintScriptFrom.cpp
+    test_checkpointing_Resume.cpp
+    test_checkpointing_ResumingManager.cpp
+    test_checkpointing_ResumingUtils.cpp
+    test_checkpointing_SetResumeFrom.cpp
+  )
+
+if(PUGS_HAS_HDF5)
+  list(APPEND checkpointing_TESTS
+    test_checkpointing_Array.cpp
+    test_checkpointing_Connectivity.cpp
+    test_checkpointing_DiscreteFunctionVariant.cpp
+    test_checkpointing_HFTypes.cpp
+    test_checkpointing_IBoundaryDescriptor.cpp
+    test_checkpointing_IBoundaryConditionDescriptor.cpp
+    test_checkpointing_IQuadratureDescriptor.cpp
+    test_checkpointing_IDiscreteFunctionDescriptor.cpp
+    test_checkpointing_IInterfaceDescriptor.cpp
+    test_checkpointing_INamedDiscreteData.cpp
+    test_checkpointing_ItemArray.cpp
+    test_checkpointing_ItemArrayVariant.cpp
+    test_checkpointing_ItemType.cpp
+    test_checkpointing_ItemValue.cpp
+    test_checkpointing_ItemValueVariant.cpp
+    test_checkpointing_IWriter.cpp
+    test_checkpointing_IZoneDescriptor.cpp
+    test_checkpointing_Mesh.cpp
+    test_checkpointing_OStream.cpp
+    test_checkpointing_SubItemArrayPerItemVariant.cpp
+    test_checkpointing_SubItemValuePerItemVariant.cpp
+    test_checkpointing_Table.cpp
+    test_checkpointing_VariableBCDescriptor.cpp
   )
+endif(PUGS_HAS_HDF5)
 
 add_executable (mpi_unit_tests
   mpi_test_main.cpp
@@ -175,6 +228,7 @@ add_executable (mpi_unit_tests
   test_EmbeddedDiscreteFunctionOperators1D.cpp
   test_EmbeddedDiscreteFunctionOperators2D.cpp
   test_EmbeddedDiscreteFunctionOperators3D.cpp
+  test_ExecutionStatManager.cpp
   test_InterpolateItemArray.cpp
   test_InterpolateItemValue.cpp
   test_ItemArray.cpp
@@ -210,6 +264,7 @@ add_executable (mpi_unit_tests
   test_SubItemArrayPerItem.cpp
   test_SubItemArrayPerItemUtils.cpp
   test_Synchronizer.cpp
+  ${checkpointing_TESTS}
   )
 
 add_library(test_Pugs_MeshDataBase
@@ -235,6 +290,7 @@ target_link_libraries (unit_tests
   PugsScheme
   PugsOutput
   PugsUtils
+  PugsCheckpointing
   PugsDev
   Kokkos::kokkos
   ${PARMETIS_LIBRARIES}
@@ -243,6 +299,7 @@ target_link_libraries (unit_tests
   Catch2
   ${PUGS_STD_LINK_FLAGS}
   ${HIGHFIVE_TARGET}
+  ${SLURM_LIBRARY}
   stdc++fs
   )
 
@@ -262,8 +319,9 @@ target_link_libraries (mpi_unit_tests
   PugsLanguageUtils
   PugsScheme
   PugsOutput
-  PugsDev
   PugsUtils
+  PugsCheckpointing
+  PugsDev
   PugsAlgebra
   PugsMesh
   Kokkos::kokkos
@@ -273,15 +331,6 @@ target_link_libraries (mpi_unit_tests
   Catch2
   ${PUGS_STD_LINK_FLAGS}
   ${HIGHFIVE_TARGET}
+  ${SLURM_LIBRARY}
   stdc++fs
   )
-
-enable_testing()
-
-if(${PUGS_HAS_MPI})
-  set(MPIEXEC_OPTION_FLAGS --oversubscribe)
-  if (NOT "$ENV{GITLAB_CI}" STREQUAL "")
-    set(MPIEXEC_OPTION_FLAGS ${MPIEXEC_OPTION_FLAGS} --allow-run-as-root)
-  endif()
-  set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} 3 ${MPIEXEC_OPTION_FLAGS} --path ${PUGS_BINARY_DIR})
-endif()
diff --git a/tests/ParallelCheckerTester.cpp b/tests/ParallelCheckerTester.cpp
index 80e92e4494afda30bdf5c8b6653b29f3a200970e..467705e7f12db8ed2645f7d2fa2615754b13470b 100644
--- a/tests/ParallelCheckerTester.cpp
+++ b/tests/ParallelCheckerTester.cpp
@@ -41,3 +41,9 @@ ParallelCheckerTester::setTag(size_t tag) const
 {
   ParallelChecker::instance().m_tag = tag;
 }
+
+void
+ParallelCheckerTester::setTagWithCheck(size_t tag) const
+{
+  ParallelChecker::instance().setTag(tag);
+}
diff --git a/tests/ParallelCheckerTester.hpp b/tests/ParallelCheckerTester.hpp
index 9aefed506d34e7b899a9fa3fae1128180c03958e..90b476558e79924eca267710bcad2d20260805dd 100644
--- a/tests/ParallelCheckerTester.hpp
+++ b/tests/ParallelCheckerTester.hpp
@@ -15,6 +15,7 @@ class ParallelCheckerTester
   void setFilename(const std::string& filename) const;
   void setMode(ParallelChecker::Mode mode) const;
   void setTag(size_t tag) const;
+  void setTagWithCheck(size_t tag) const;
 
   ParallelCheckerTester()  = default;
   ~ParallelCheckerTester() = default;
diff --git a/tests/checkpointing_Connectivity_utilities.hpp b/tests/checkpointing_Connectivity_utilities.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c71f4ed629f9e2e21a5d593d777609c05bce4e5a
--- /dev/null
+++ b/tests/checkpointing_Connectivity_utilities.hpp
@@ -0,0 +1,233 @@
+#ifndef CHECKPOINTING_CONNECTIVITY_UTILITIES_HPP
+#define CHECKPOINTING_CONNECTIVITY_UTILITIES_HPP
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityDescriptor.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/PugsMacros.hpp>
+
+namespace test_only
+{
+
+PUGS_INLINE
+std::shared_ptr<const IConnectivity>
+duplicateConnectivity(const IConnectivity& i_connectivity)
+{
+  auto setRefItemLists = [](const auto& connectivity, ConnectivityDescriptor& descriptor) {
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::cell>(); ++i) {
+      descriptor.addRefItemList(connectivity.template refItemList<ItemType::cell>(i));
+    }
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::face>(); ++i) {
+      descriptor.addRefItemList(connectivity.template refItemList<ItemType::face>(i));
+    }
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::edge>(); ++i) {
+      descriptor.addRefItemList(connectivity.template refItemList<ItemType::edge>(i));
+    }
+    for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::node>(); ++i) {
+      descriptor.addRefItemList(connectivity.template refItemList<ItemType::node>(i));
+    }
+  };
+
+  ConnectivityDescriptor descriptor;
+  switch (i_connectivity.dimension()) {
+  case 1: {
+    using ConnectivityType = Connectivity<1>;
+
+    const ConnectivityType& connectivity = dynamic_cast<const ConnectivityType&>(i_connectivity);
+    descriptor.setCellNumberVector(connectivity.cellNumber().arrayView());
+    descriptor.setNodeNumberVector(connectivity.nodeNumber().arrayView());
+
+    descriptor.setCellTypeVector(connectivity.cellType().arrayView());
+
+    descriptor.setCellOwnerVector(connectivity.cellOwner().arrayView());
+    descriptor.setNodeOwnerVector(connectivity.nodeOwner().arrayView());
+
+    descriptor.setCellToNodeMatrix(connectivity.getMatrix(ItemType::cell, ItemType::node));
+
+    setRefItemLists(connectivity, descriptor);
+
+    return ConnectivityType::build(descriptor);
+  }
+  case 2: {
+    using ConnectivityType = Connectivity<2>;
+
+    const ConnectivityType& connectivity = dynamic_cast<const ConnectivityType&>(i_connectivity);
+    descriptor.setCellNumberVector(connectivity.cellNumber().arrayView());
+    descriptor.setFaceNumberVector(connectivity.faceNumber().arrayView());
+    descriptor.setNodeNumberVector(connectivity.nodeNumber().arrayView());
+
+    descriptor.setCellTypeVector(connectivity.cellType().arrayView());
+
+    descriptor.setCellFaceIsReversed(connectivity.cellFaceIsReversed().arrayView());
+
+    descriptor.setCellOwnerVector(connectivity.cellOwner().arrayView());
+    descriptor.setFaceOwnerVector(connectivity.faceOwner().arrayView());
+    descriptor.setNodeOwnerVector(connectivity.nodeOwner().arrayView());
+
+    descriptor.setCellToFaceMatrix(connectivity.getMatrix(ItemType::cell, ItemType::face));
+    descriptor.setCellToNodeMatrix(connectivity.getMatrix(ItemType::cell, ItemType::node));
+    descriptor.setFaceToNodeMatrix(connectivity.getMatrix(ItemType::face, ItemType::node));
+
+    setRefItemLists(connectivity, descriptor);
+
+    return ConnectivityType::build(descriptor);
+  }
+  case 3: {
+    using ConnectivityType = Connectivity<3>;
+
+    const ConnectivityType& connectivity = dynamic_cast<const ConnectivityType&>(i_connectivity);
+    descriptor.setCellNumberVector(connectivity.cellNumber().arrayView());
+    descriptor.setFaceNumberVector(connectivity.faceNumber().arrayView());
+    descriptor.setEdgeNumberVector(connectivity.edgeNumber().arrayView());
+    descriptor.setNodeNumberVector(connectivity.nodeNumber().arrayView());
+
+    descriptor.setCellTypeVector(connectivity.cellType().arrayView());
+
+    descriptor.setCellFaceIsReversed(connectivity.cellFaceIsReversed().arrayView());
+    descriptor.setFaceEdgeIsReversed(connectivity.faceEdgeIsReversed().arrayView());
+
+    descriptor.setCellOwnerVector(connectivity.cellOwner().arrayView());
+    descriptor.setFaceOwnerVector(connectivity.faceOwner().arrayView());
+    descriptor.setEdgeOwnerVector(connectivity.edgeOwner().arrayView());
+    descriptor.setNodeOwnerVector(connectivity.nodeOwner().arrayView());
+
+    descriptor.setCellToFaceMatrix(connectivity.getMatrix(ItemType::cell, ItemType::face));
+    descriptor.setCellToEdgeMatrix(connectivity.getMatrix(ItemType::cell, ItemType::edge));
+    descriptor.setCellToNodeMatrix(connectivity.getMatrix(ItemType::cell, ItemType::node));
+    descriptor.setFaceToEdgeMatrix(connectivity.getMatrix(ItemType::face, ItemType::edge));
+    descriptor.setFaceToNodeMatrix(connectivity.getMatrix(ItemType::face, ItemType::node));
+    descriptor.setEdgeToNodeMatrix(connectivity.getMatrix(ItemType::edge, ItemType::node));
+
+    setRefItemLists(connectivity, descriptor);
+
+    return ConnectivityType::build(descriptor);
+  }
+  default: {
+    throw UnexpectedError("invalid connectivity");
+  }
+  }
+}
+
+template <size_t Dimension>
+PUGS_INLINE bool
+isSameConnectivity(const Connectivity<Dimension>& connectivity, const Connectivity<Dimension>& read_connectivity)
+{
+  using ConnectivityType = std::decay_t<decltype(connectivity)>;
+
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  auto same_ref_item_list = [&same_value](const auto& a, const auto& b) -> bool {
+    REQUIRE(a.type() == b.type());
+    REQUIRE(a.refId() == b.refId());
+
+    return same_value(a.list(), b.list());
+  };
+
+  bool same = true;
+  same &= same_value(connectivity.cellNumber().arrayView(), read_connectivity.cellNumber().arrayView());
+  same &= same_value(connectivity.nodeNumber().arrayView(), read_connectivity.nodeNumber().arrayView());
+
+  same &= same_value(connectivity.cellType().arrayView(), read_connectivity.cellType().arrayView());
+
+  same &= same_value(connectivity.cellOwner().arrayView(), read_connectivity.cellOwner().arrayView());
+  same &= same_value(connectivity.nodeOwner().arrayView(), read_connectivity.nodeOwner().arrayView());
+
+  same &= same_value(connectivity.cellToNodeMatrix().values(), read_connectivity.cellToNodeMatrix().values());
+
+  if constexpr (ConnectivityType::Dimension >= 2) {
+    same &= same_value(connectivity.faceNumber().arrayView(), read_connectivity.faceNumber().arrayView());
+    same &= same_value(connectivity.faceOwner().arrayView(), read_connectivity.faceOwner().arrayView());
+    same &=
+      same_value(connectivity.cellFaceIsReversed().arrayView(), read_connectivity.cellFaceIsReversed().arrayView());
+    same &= same_value(connectivity.cellToFaceMatrix().values(), read_connectivity.cellToFaceMatrix().values());
+    same &= same_value(connectivity.faceToNodeMatrix().values(), read_connectivity.faceToNodeMatrix().values());
+  }
+
+  if constexpr (ConnectivityType::Dimension == 3) {
+    same &= same_value(connectivity.edgeNumber().arrayView(), read_connectivity.edgeNumber().arrayView());
+    same &= same_value(connectivity.edgeOwner().arrayView(), read_connectivity.edgeOwner().arrayView());
+    same &=
+      same_value(connectivity.faceEdgeIsReversed().arrayView(), read_connectivity.faceEdgeIsReversed().arrayView());
+    same &= same_value(connectivity.cellToEdgeMatrix().values(), read_connectivity.cellToEdgeMatrix().values());
+    same &= same_value(connectivity.faceToEdgeMatrix().values(), read_connectivity.faceToEdgeMatrix().values());
+    same &= same_value(connectivity.edgeToNodeMatrix().values(), read_connectivity.edgeToNodeMatrix().values());
+  }
+
+  REQUIRE(connectivity.template numberOfRefItemList<ItemType::cell>() ==
+          read_connectivity.template numberOfRefItemList<ItemType::cell>());
+  for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::cell>(); ++i) {
+    same &= same_ref_item_list(connectivity.template refItemList<ItemType::cell>(i),
+                               read_connectivity.template refItemList<ItemType::cell>(i));
+  }
+  REQUIRE(connectivity.template numberOfRefItemList<ItemType::face>() ==
+          read_connectivity.template numberOfRefItemList<ItemType::face>());
+  for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::face>(); ++i) {
+    same &= same_ref_item_list(connectivity.template refItemList<ItemType::face>(i),
+                               read_connectivity.template refItemList<ItemType::face>(i));
+  }
+  REQUIRE(connectivity.template numberOfRefItemList<ItemType::edge>() ==
+          read_connectivity.template numberOfRefItemList<ItemType::edge>());
+  for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::edge>(); ++i) {
+    same &= same_ref_item_list(connectivity.template refItemList<ItemType::edge>(i),
+                               read_connectivity.template refItemList<ItemType::edge>(i));
+  }
+  REQUIRE(connectivity.template numberOfRefItemList<ItemType::node>() ==
+          read_connectivity.template numberOfRefItemList<ItemType::node>());
+  for (size_t i = 0; i < connectivity.template numberOfRefItemList<ItemType::node>(); ++i) {
+    same &= same_ref_item_list(connectivity.template refItemList<ItemType::node>(i),
+                               read_connectivity.template refItemList<ItemType::node>(i));
+  }
+
+  return same;
+}
+
+PUGS_INLINE bool
+isSameConnectivity(const IConnectivity& i_connectivity, const IConnectivity& read_i_connectivity)
+{
+  if (i_connectivity.dimension() != read_i_connectivity.dimension()) {
+    return false;
+  } else {
+    switch (i_connectivity.dimension()) {
+    case 1: {
+      constexpr size_t Dimension = 1;
+
+      const Connectivity<Dimension>& connectivity = dynamic_cast<const Connectivity<Dimension>&>(i_connectivity);
+      const Connectivity<Dimension>& read_connectivity =
+        dynamic_cast<const Connectivity<Dimension>&>(read_i_connectivity);
+
+      return isSameConnectivity(connectivity, read_connectivity);
+    }
+    case 2: {
+      constexpr size_t Dimension = 2;
+
+      const Connectivity<Dimension>& connectivity = dynamic_cast<const Connectivity<Dimension>&>(i_connectivity);
+      const Connectivity<Dimension>& read_connectivity =
+        dynamic_cast<const Connectivity<Dimension>&>(read_i_connectivity);
+
+      return isSameConnectivity(connectivity, read_connectivity);
+    }
+    case 3: {
+      constexpr size_t Dimension = 3;
+
+      const Connectivity<Dimension>& connectivity = dynamic_cast<const Connectivity<Dimension>&>(i_connectivity);
+      const Connectivity<Dimension>& read_connectivity =
+        dynamic_cast<const Connectivity<Dimension>&>(read_i_connectivity);
+
+      return isSameConnectivity(connectivity, read_connectivity);
+    }
+    default: {
+      return false;
+    }
+    }
+  }
+}
+
+}   // namespace test_only
+
+#endif   // CHECKPOINTING_CONNECTIVITY_UTILITIES_HPP
diff --git a/tests/checkpointing_Mesh_utilities.hpp b/tests/checkpointing_Mesh_utilities.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..aabfacbbd6ca259de9468fda12ea706c8784005b
--- /dev/null
+++ b/tests/checkpointing_Mesh_utilities.hpp
@@ -0,0 +1,68 @@
+#ifndef CHECKPOINTING_MESH_UTILITIES_HPP
+#define CHECKPOINTING_MESH_UTILITIES_HPP
+
+#include <checkpointing_Connectivity_utilities.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshTraits.hpp>
+#include <mesh/MeshVariant.hpp>
+
+namespace test_only
+{
+
+PUGS_INLINE
+std::shared_ptr<const MeshVariant>
+duplicateMesh(std::shared_ptr<const MeshVariant> mesh_v)
+{
+  std::shared_ptr<const MeshVariant> new_mesh_v;
+
+  std::visit(
+    [&](auto&& mesh) {
+      using MeshType = mesh_type_t<decltype(mesh)>;
+      if constexpr (is_polygonal_mesh_v<MeshType>) {
+        using Rd = TinyVector<MeshType::Dimension>;
+        auto new_connectivity =
+          std::dynamic_pointer_cast<const typename MeshType::Connectivity>(duplicateConnectivity(mesh->connectivity()));
+
+        NodeValue<const Rd> new_xr{*new_connectivity, mesh->xr().arrayView()};
+        new_mesh_v = std::make_shared<MeshVariant>(std::make_shared<const MeshType>(new_connectivity, new_xr));
+      } else {
+        throw NotImplementedError("unexpected mesh type");
+      }
+    },
+    mesh_v->variant());
+
+  return new_mesh_v;
+}
+
+PUGS_INLINE bool
+isSameMesh(std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const MeshVariant> read_mesh_v)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  bool same = true;
+
+  std::visit(
+    [&](auto&& mesh, auto&& read_mesh) {
+      using MeshType     = mesh_type_t<decltype(mesh)>;
+      using ReadMeshType = mesh_type_t<decltype(read_mesh)>;
+      if constexpr (std::is_same_v<MeshType, ReadMeshType>) {
+        same &= test_only::isSameConnectivity(mesh->connectivity(), read_mesh->connectivity());
+        same &= same_value(mesh->xr().arrayView(), read_mesh->xr().arrayView());
+      } else {
+        throw UnexpectedError("mesh of different kind");
+      }
+    },
+    mesh_v->variant(), read_mesh_v->variant());
+
+  return same;
+}
+
+}   // namespace test_only
+
+#endif   // CHECKPOINTING_MESH_UTILITIES_HPP
diff --git a/tests/mpi_test_main.cpp b/tests/mpi_test_main.cpp
index ef3fdfcc00569ea3042de6724171fbd24e2ed680..cd74479774dbf255460e5afe1b056c73f3e80821 100644
--- a/tests/mpi_test_main.cpp
+++ b/tests/mpi_test_main.cpp
@@ -13,6 +13,7 @@
 #include <utils/PETScWrapper.hpp>
 #include <utils/RandomEngine.hpp>
 #include <utils/Stringify.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
 #include <utils/pugs_config.hpp>
 
 #include <MeshDataBaseForTests.hpp>
@@ -27,7 +28,16 @@ main(int argc, char* argv[])
   parallel::Messenger::create(argc, argv);
 
   const int nb_max_threads = std::max(std::thread::hardware_concurrency(), 1u);
-  const int nb_threads     = std::max(nb_max_threads / parallel::Messenger::getInstance().size(), 1ul);
+  int nb_threads           = std::min(std::max(nb_max_threads / parallel::Messenger::getInstance().size(), 1ul), 8ul);
+
+  Catch::Session session;
+
+  auto cli = session.cli() | Catch::Clara::Opt(nb_threads, "number of threads")["--threads"](
+                               "number of threads (default: max logical threads)");
+
+  session.cli(cli);
+
+  int result = session.applyCommandLine(argc, argv);
 
   {
     Kokkos::InitializationSettings args;
@@ -46,12 +56,13 @@ main(int argc, char* argv[])
 
   std::filesystem::path gcov_prefix = [&]() -> std::filesystem::path {
     std::string template_temp_dir = std::filesystem::temp_directory_path() / "pugs_gcov_XXXXXX";
-    return std::filesystem::path{mkdtemp(&template_temp_dir[0])};
+    if constexpr (std::string_view{PUGS_BUILD_TYPE} == "Coverage") {
+      return std::filesystem::path{mkdtemp(&template_temp_dir[0])};
+    } else {
+      return "";
+    }
   }();
 
-  Catch::Session session;
-  int result = session.applyCommandLine(argc, argv);
-
   // disable file locking to avoid mess in tests
   setenv("HDF5_USE_FILE_LOCKING", "FALSE", 1);
 
@@ -93,6 +104,8 @@ main(int argc, char* argv[])
       // Disable outputs from tested classes to the standard output
       std::cout.setstate(std::ios::badbit);
 
+      ResumingManager::create();
+
       SynchronizerManager::create();
       RandomEngine::create();
       QuadratureManager::create();
@@ -118,6 +131,8 @@ main(int argc, char* argv[])
       QuadratureManager::destroy();
       RandomEngine::destroy();
       SynchronizerManager::destroy();
+
+      ResumingManager::destroy();
     }
   }
 
diff --git a/tests/test_ASTBuilder.cpp b/tests/test_ASTBuilder.cpp
index e1ee2761d87ce23be90f6c4b56503ed46a16eaa6..1a443ba4e9f892f68979164c9f254054458b6692 100644
--- a/tests/test_ASTBuilder.cpp
+++ b/tests/test_ASTBuilder.cpp
@@ -3,6 +3,7 @@
 
 #include <language/ast/ASTBuilder.hpp>
 #include <language/utils/ASTPrinter.hpp>
+#include <language/utils/SymbolTable.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -20,6 +21,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::none}}; \
                                                                                                \
     REQUIRE(ast_output.str() == expected_output);                                              \
+    ast->m_symbol_table->clearValues();                                                        \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTCheckpoint.cpp b/tests/test_ASTCheckpoint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..85f0a5c90b98ef9f04c5596ba12cd94ede03c6a7
--- /dev/null
+++ b/tests/test_ASTCheckpoint.cpp
@@ -0,0 +1,28 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/ast/ASTNode.hpp>
+#include <language/utils/ASTCheckpoint.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ASTCheckpoint", "[language]")
+{
+  ASTNode ast_node;
+
+  std::vector<size_t> location = {1, 2, 4};
+  ASTCheckpoint cp{location, &ast_node};
+
+  REQUIRE(&cp.node() == &ast_node);
+  REQUIRE(cp.getASTLocation() == location);
+
+  ASTCheckpoint cp_copy = cp;
+
+  REQUIRE(&cp_copy.node() == &ast_node);
+  REQUIRE(cp_copy.getASTLocation() == location);
+
+  ASTCheckpoint cp_move = std::move(cp);
+
+  REQUIRE(&cp_move.node() == &ast_node);
+  REQUIRE(cp_move.getASTLocation() == location);
+}
diff --git a/tests/test_ASTCheckpointsInfo.cpp b/tests/test_ASTCheckpointsInfo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..61eb5c00f311f73994b3ba2f60d28f857fd149c7
--- /dev/null
+++ b/tests/test_ASTCheckpointsInfo.cpp
@@ -0,0 +1,83 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_predicate.hpp>
+
+#include <utils/pugs_config.hpp>
+
+#include <dev/ParallelChecker.hpp>
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/modules/MathModule.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <utils/ExecutionStatManager.hpp>
+
+class ASTCheckpointsInfoTester
+{
+ private:
+  ASTCheckpointsInfo m_ast_checkpoint_info;
+
+ public:
+  ASTCheckpointsInfoTester(const ASTNode& root_node) : m_ast_checkpoint_info(root_node) {}
+  ~ASTCheckpointsInfoTester() = default;
+};
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ASTCheckpointsInfo", "[utils/checkpointing]")
+{
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(ASTCheckpointsInfo::getInstance(), "ASTCheckpointsInfo is not defined!");
+#endif   // NDEBUG
+
+  std::string data = R"(
+for(let i:N, i=0; i<7; ++i) {
+  checkpoint();
+
+  if (i == 2) {
+    checkpoint();
+  }
+
+  if (i == 5) {
+    checkpoint_and_exit();
+  }
+}
+)";
+
+  ExecutionStatManager::create();
+
+  TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+  auto ast = ASTBuilder::build(input);
+
+  ASTModulesImporter{*ast};
+  ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+  ASTSymbolTableBuilder{*ast};
+  ASTNodeDataTypeBuilder{*ast};
+
+  ASTNodeDeclarationToAffectationConverter{*ast};
+  ASTNodeTypeCleaner<language::var_declaration>{*ast};
+  ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+
+  ASTNodeExpressionBuilder{*ast};
+  ExecutionPolicy exec_policy;
+  ASTExecutionStack::create();
+  ASTCheckpointsInfoTester ast_cp_info_tester{*ast};
+  ASTExecutionStack::destroy();
+  ExecutionStatManager::destroy();
+  ast->m_symbol_table->clearValues();
+
+  REQUIRE(ASTCheckpointsInfo::getInstance().getASTCheckpoint(0).getASTLocation() == std::vector<size_t>{0, 3, 0});
+  REQUIRE(ASTCheckpointsInfo::getInstance().getASTCheckpoint(1).getASTLocation() == std::vector<size_t>{0, 3, 1, 1});
+  REQUIRE(ASTCheckpointsInfo::getInstance().getASTCheckpoint(2).getASTLocation() == std::vector<size_t>{0, 3, 2, 1});
+
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(ASTCheckpointsInfoTester(*ast), "Can only define one ASTCheckpointsInfo");
+#endif   // NDEBUG
+}
diff --git a/tests/test_ASTDotPrinter.cpp b/tests/test_ASTDotPrinter.cpp
index 7081821e6635432d71ad7d18cd5b2fa5883e613e..ca5f817a8622090ffc7a732aa30400a778972ada 100644
--- a/tests/test_ASTDotPrinter.cpp
+++ b/tests/test_ASTDotPrinter.cpp
@@ -3,6 +3,7 @@
 
 #include <language/ast/ASTBuilder.hpp>
 #include <language/utils/ASTDotPrinter.hpp>
+#include <language/utils/SymbolTable.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -20,6 +21,7 @@
     ast_output << '\n' << ASTDotPrinter{*ast};                                                \
                                                                                               \
     REQUIRE(ast_output.str() == expected_output);                                             \
+    ast->m_symbol_table->clearValues();                                                       \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTExecutionStack.cpp b/tests/test_ASTExecutionStack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d78c8572c9e6f44372f03f1c500c9c922b1e994b
--- /dev/null
+++ b/tests/test_ASTExecutionStack.cpp
@@ -0,0 +1,82 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+
+#include <pegtl/string_input.hpp>
+
+#include <sstream>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ASTExecutionStack", "[language]")
+{
+  REQUIRE_NOTHROW(ASTExecutionStack::create());
+  REQUIRE_THROWS_WITH(ASTExecutionStack::create(), "unexpected error: ASTExecutionStack was already created!");
+
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(ASTExecutionStack::getInstance().sourceLocation(), "stack.size() > 0");
+#endif   // NDEBUG
+
+  REQUIRE_NOTHROW(ASTExecutionStack::destroy());
+
+  std::string data = R"(
+import math;
+
+for (let i:N, i=0; i<5; ++i) {
+  cout << 2 * i << "\n";
+  i += 1;
+}
+)";
+
+  auto input = std::make_shared<TAO_PEGTL_NAMESPACE::string_input<>>(data, "test.pgs");
+
+  ASTExecutionStack::create(input, data);
+
+  REQUIRE_THROWS_WITH(ASTExecutionStack::create(input, data),
+                      "unexpected error: ASTExecutionStack was already created!");
+
+  auto ast = ASTBuilder::build(*input);
+
+  ASTModulesImporter{*ast};
+  ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+  ASTSymbolTableBuilder{*ast};
+  ASTNodeDataTypeBuilder{*ast};
+
+  ASTNodeDeclarationToAffectationConverter{*ast};
+  ASTNodeTypeCleaner<language::var_declaration>{*ast};
+
+  ASTNodeExpressionBuilder{*ast};
+
+  REQUIRE(ASTExecutionStack::getInstance().errorMessageAt("error_msg") == "error_msg");
+
+  ASTExecutionStack::getInstance().push(ast->children[0].get());
+  ASTExecutionStack::getInstance().push(ast->children[0]->children[3]->children[1].get());
+
+  REQUIRE(ASTExecutionStack::getInstance().errorMessageAt("error_msg") == R"(test.pgs:6:5: error_msg
+  i += 1;
+    ^
+)");
+
+  auto source_location = ASTExecutionStack::getInstance().sourceLocation();
+  REQUIRE(source_location.filename() == "test.pgs");
+  REQUIRE(source_location.line() == 6);
+  REQUIRE(source_location.column() == 5);
+  REQUIRE(source_location.function() == "");
+
+  ASTExecutionStack::getInstance().pop();
+  ASTExecutionStack::getInstance().pop();
+
+  ast->m_symbol_table->clearValues();
+
+  REQUIRE_NOTHROW(ASTExecutionStack::destroy());
+  REQUIRE_THROWS_WITH(ASTExecutionStack::destroy(), "unexpected error: ASTExecutionStack was not created!");
+}
diff --git a/tests/test_ASTModulesImporter.cpp b/tests/test_ASTModulesImporter.cpp
index f70fa9b2bc118c4498072de10414d346b17717b4..fdfa69ce4cf8d97a7d4872fb47588b90c0c8f08e 100644
--- a/tests/test_ASTModulesImporter.cpp
+++ b/tests/test_ASTModulesImporter.cpp
@@ -1,8 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
@@ -19,7 +19,7 @@ test_ASTExecutionInfo(const ASTNode& root_node, const ModuleRepository& module_r
   REQUIRE(&root_node == &execution_info.rootNode());
   REQUIRE(&module_repository == &execution_info.moduleRepository());
 
-  REQUIRE(&ASTExecutionInfo::current() == &execution_info);
+  REQUIRE(&ASTExecutionInfo::getInstance() == &execution_info);
 }
 
 #define CHECK_AST(data, expected_output)                                                            \
@@ -38,14 +38,15 @@ test_ASTExecutionInfo(const ASTNode& root_node, const ModuleRepository& module_r
     test_ASTExecutionInfo(*ast, module_repository);                                                 \
                                                                                                     \
     ExecutionPolicy exec_policy;                                                                    \
-    ASTBacktrace::create();                                                                         \
+    ASTExecutionStack::create();                                                                    \
     ast->execute(exec_policy);                                                                      \
-    ASTBacktrace::destroy();                                                                        \
+    ASTExecutionStack::destroy();                                                                   \
                                                                                                     \
     std::stringstream ast_output;                                                                   \
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -103,6 +104,7 @@ import unknown_module;
       auto ast = ASTBuilder::build(input);
 
       REQUIRE_THROWS_AS(ASTModulesImporter{*ast}, ParseError);
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("symbol already defined (same builtin function)")
@@ -117,6 +119,7 @@ import math;
       ast->m_symbol_table->add("sin:R", ast->begin());
 
       REQUIRE_THROWS_AS(ASTModulesImporter{*ast}, ParseError);
+      ast->m_symbol_table->clearValues();
     }
   }
 }
diff --git a/tests/test_ASTNode.cpp b/tests/test_ASTNode.cpp
index cda39283b3848f893a8bae7955d9ef57efc70839..1a2e5911898ac7d859ecd732916ea6bbf532d5fb 100644
--- a/tests/test_ASTNode.cpp
+++ b/tests/test_ASTNode.cpp
@@ -2,7 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/PEGGrammar.hpp>
-#include <language/ast/ASTBacktrace.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTNode.hpp>
 #include <language/node_processor/FakeProcessor.hpp>
 #include <utils/Demangle.hpp>
@@ -18,16 +18,16 @@ TEST_CASE("ASTNode", "[language]")
     ExecutionPolicy exec_policy;
 
 #ifndef NDEBUG
-    ASTBacktrace::create();
+    ASTExecutionStack::create();
     REQUIRE_THROWS(ast_node.execute(exec_policy));
-    ASTBacktrace::destroy();
+    ASTExecutionStack::destroy();
 #endif   // NDEBUG
 
     ast_node.m_node_processor = std::make_unique<FakeProcessor>();
 
-    ASTBacktrace::create();
+    ASTExecutionStack::create();
     REQUIRE_NOTHROW(ast_node.execute(exec_policy));
-    ASTBacktrace::destroy();
+    ASTExecutionStack::destroy();
   }
 
   SECTION("name")
diff --git a/tests/test_ASTNodeAffectationExpressionBuilder.cpp b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
index 39fcf659b208ac75ebbf198bddfe01b4c6ed06a1..7524a099c02f502a4b9b17eb908f88a37b4a26db 100644
--- a/tests/test_ASTNodeAffectationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeAffectationExpressionBuilder.cpp
@@ -45,6 +45,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}};             \
                                                                                                                 \
     REQUIRE(ast_output.str() == expected_output);                                                               \
+    ast->m_symbol_table->clearValues();                                                                         \
   }
 
 template <>
@@ -100,6 +101,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
                                                                                                                 \
     REQUIRE(ast_output.str() == expected_output);                                                               \
                                                                                                                 \
+    ast->m_symbol_table->clearValues();                                                                         \
     OperatorRepository::instance().reset();                                                                     \
   }
 
@@ -121,6 +123,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
     ASTNodeTypeCleaner<language::var_declaration>{*ast};                                      \
                                                                                               \
     REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, expected_error);                      \
+    ast->m_symbol_table->clearValues();                                                       \
   }
 
 #define CHECK_AST_WITH_BUILTIN_THROWS_WITH(data, expected_error)                                                \
@@ -168,6 +171,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
                                                                                                                 \
     REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, expected_error);                                        \
                                                                                                                 \
+    ast->m_symbol_table->clearValues();                                                                         \
     OperatorRepository::instance().reset();                                                                     \
   }
 
@@ -3268,6 +3272,7 @@ let (x,y,z):R*R*R, (x,y) = (2,3);
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast},
                           std::string{"invalid number of definition identifiers, expecting 3 found 2"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("incorrect identifier/expression number of symbols")
@@ -3285,6 +3290,7 @@ let x:R, (x,y) = (2,3);
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast},
                           std::string{"unexpected variable list, expecting one identifier"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("incorrect definition variable identifier")
@@ -3301,6 +3307,7 @@ let x:R, y = 3;
 
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"invalid identifier, expecting 'x'"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("invalid definition variable identifier order")
@@ -3316,6 +3323,7 @@ let (x,y):R, (y,x) = (3,2);
 
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"invalid identifier, expecting 'x'"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("undefined affectations =")
@@ -7508,6 +7516,7 @@ let a:void;
 
           ASTSymbolTableBuilder{*ast};
           REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message);
+          ast->m_symbol_table->clearValues();
         }
       }
 
@@ -7527,6 +7536,7 @@ let a:void, a = 3;
 
           ASTSymbolTableBuilder{*ast};
           REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message);
+          ast->m_symbol_table->clearValues();
         }
       }
     }
diff --git a/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp b/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
index 8443d55bea5ad952d9960cedd82168f6d8a81716..0bd3f48fa73cbd3045e98cf3efec718f40ddc58a 100644
--- a/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
+++ b/tests/test_ASTNodeBinaryOperatorExpressionBuilder.cpp
@@ -45,6 +45,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 #define REQUIRE_AST_THROWS_WITH(data, expected_output)                             \
@@ -56,6 +57,7 @@
                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                   \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, expected_output);            \
+    ast->m_symbol_table->clearValues();                                            \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -1874,6 +1876,7 @@ x!=[4,3,2];
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}};             \
                                                                                                                 \
     REQUIRE(ast_output.str() == expected_output);                                                               \
+    ast->m_symbol_table->clearValues();                                                                         \
   }
 
     SECTION("shift left (builtin)")
diff --git a/tests/test_ASTNodeBuiltinFunctionExpressionBuilder.cpp b/tests/test_ASTNodeBuiltinFunctionExpressionBuilder.cpp
index 09b2a05f2c844f6cd7d14f333177ead2288d3e63..83450e7f10cd66e4b430d69ef3a3807f5a84ce2f 100644
--- a/tests/test_ASTNodeBuiltinFunctionExpressionBuilder.cpp
+++ b/tests/test_ASTNodeBuiltinFunctionExpressionBuilder.cpp
@@ -42,6 +42,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 #define CHECK_AST_THROWS_WITH(data, expected_error)                                             \
@@ -61,6 +62,7 @@
     ASTNodeTypeCleaner<language::var_declaration>{*ast};                                        \
     ASTNodeTypeCleaner<language::fct_declaration>{*ast};                                        \
     REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, expected_error);                        \
+    ast->m_symbol_table->clearValues();                                                         \
   }
 
 #define CHECK_AST_THROWS_AT_BUILD_WITH(data, expected_error)                                    \
@@ -76,6 +78,7 @@
                                                                                                 \
     ASTSymbolTableBuilder{*ast};                                                                \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, expected_error);                          \
+    ast->m_symbol_table->clearValues();                                                         \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTNodeDataTypeBuilder.cpp b/tests/test_ASTNodeDataTypeBuilder.cpp
index 2a5777b32272c8c10ba39543c30b103d4f629439..d82f6c514b4c4e84d54847d8e5bcf4506d76f7d3 100644
--- a/tests/test_ASTNodeDataTypeBuilder.cpp
+++ b/tests/test_ASTNodeDataTypeBuilder.cpp
@@ -32,6 +32,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 template <>
@@ -65,6 +66,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}};           \
                                                                                                               \
     REQUIRE(ast_output.str() == expected_output);                                                             \
+    ast->m_symbol_table->clearValues();                                                                       \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -93,6 +95,7 @@ import a_module_name;
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::data_type}};
 
     REQUIRE(ast_output.str() == result);
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("integer")
@@ -321,6 +324,7 @@ let x:R, x = 1; let y:R, y = x[2];
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid subscript expression: R cannot be indexed");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid R^d subscript index list")
@@ -334,6 +338,7 @@ let x:R^2, x = 0; let y:R, y = x[2,2];
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid index type: R^2 requires a single integer");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid R^dxd subscript index list 1")
@@ -347,6 +352,7 @@ let x:R^2x2, x = 0; let y:R, y = x[2][1];
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid index type: R^2x2 requires two integers");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid R^dxd subscript index list 2")
@@ -374,6 +380,7 @@ let (x,b,n,s,t) : R*B*N*string;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "number of product spaces (4) R*B*N*string differs from "
                                                           "number of variables (5) (x,b,n,s,t) ");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("too few variables")
@@ -388,6 +395,7 @@ let (x,b,n) : R*B*N*string;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "number of product spaces (4) R*B*N*string differs from "
                                                           "number of variables (3) (x,b,n) ");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("unexpected variable list")
@@ -401,6 +409,7 @@ let  (x,y) : R;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "unexpected variable list for single space");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid R-list -> R^d")
@@ -415,6 +424,7 @@ let square : R -> R^3, x -> (x, 2);
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "number of image spaces (1) R^3 differs from number of expressions (2) (x, 2)");
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -713,6 +723,7 @@ let t : builtin_t;
           ASTSymbolTableBuilder{*ast};
 
           REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
+          ast->m_symbol_table->clearValues();
         }
 
         SECTION("type_id 2")
@@ -728,6 +739,7 @@ let t : a;
 
           REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                               "invalid type identifier, 'a' was previously defined as a 'R'");
+          ast->m_symbol_table->clearValues();
         }
 
         SECTION("type_id tuples")
@@ -741,6 +753,7 @@ let t : (builtin_t);
           ASTSymbolTableBuilder{*ast};
 
           REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
+          ast->m_symbol_table->clearValues();
         }
 
         SECTION("type_id tuples 2")
@@ -756,6 +769,7 @@ let t : (a);
 
           REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                               "invalid type identifier, 'a' was previously defined as a 'R'");
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -1067,6 +1081,7 @@ let f : R*Z -> B, x -> 3;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "number of product spaces (2) R*Z  differs from number of variables (1) x");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("wrong parameter number 2")
@@ -1080,6 +1095,7 @@ let f : R -> B, (x,y) -> 3;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "number of product spaces (1) R differs from number of variables (2) (x,y) ");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("wrong image size")
@@ -1093,6 +1109,7 @@ let f : R*Z -> B, (x,z) -> (3, x);
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "number of image spaces (1) B differs from number of expressions (2) (3, x)");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("wrong image size 2")
@@ -1110,6 +1127,7 @@ let f : R -> R*R, x -> x*x*x;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "number of image spaces (2) R*R differs from number of expressions (1) x*x*x");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("wrong image size 3")
@@ -1127,6 +1145,7 @@ let f : R -> R^2x2, x -> (x, 2*x, 2);
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "number of image spaces (1) R^2x2 differs from number of expressions (3) (x, 2*x, 2)");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("undefined type identifier")
@@ -1139,6 +1158,7 @@ let x:X;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("undefined type identifier")
@@ -1153,6 +1173,7 @@ let x:X;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "invalid type identifier, 'X' was previously defined as a 'R'");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("undefined image type identifier")
@@ -1165,6 +1186,7 @@ let f: R -> X, x -> x;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "undefined type identifier");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid image type identifier")
@@ -1179,6 +1201,7 @@ let f: R -> X, x -> x;
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast},
                             "invalid type identifier, 'X' was previously defined as a 'R'");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid tuple type domain")
@@ -1191,6 +1214,7 @@ let f: (R) -> R, x -> x;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "cannot use tuple (R) as a domain for user functions");
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("invalid tuple type in compound domain")
@@ -1203,6 +1227,7 @@ let f: N*(R^2) -> R, (x,t) -> x;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "cannot use tuple (R^2) as a domain for user functions");
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -1460,6 +1485,7 @@ not_a_function(2,3);
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid function call\n"
                                                           "note: 'not_a_function' (type: R) is not a function!");
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -1920,6 +1946,7 @@ if ("string");
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
+      ast->m_symbol_table->clearValues();
     }
   }
 
@@ -2006,6 +2033,7 @@ while ("string");
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
+      ast->m_symbol_table->clearValues();
     }
   }
 
@@ -2092,6 +2120,7 @@ do 1; while ("string");
       auto ast = ASTBuilder::build(input);
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, "invalid implicit conversion: string -> B");
+      ast->m_symbol_table->clearValues();
     }
   }
 
diff --git a/tests/test_ASTNodeDataTypeChecker.cpp b/tests/test_ASTNodeDataTypeChecker.cpp
index 5c0fc6d8fd38054e4343470ef8f6196d7e34ac60..69aa66383e47be1a4dc61443d68f91679a0ce96a 100644
--- a/tests/test_ASTNodeDataTypeChecker.cpp
+++ b/tests/test_ASTNodeDataTypeChecker.cpp
@@ -34,6 +34,7 @@ for(let i:Z, i=0; i<10; ++i) {
     ASTNodeDataTypeBuilder{*ast};
 
     REQUIRE_NOTHROW(ASTNodeDataTypeChecker{*ast});
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("everything uninitialized node type")
@@ -57,5 +58,6 @@ for(let i:Z, i=0; i<10; ++i) {
     ast->children[0]->m_data_type = ASTNodeDataType::build<ASTNodeDataType::undefined_t>();
 
     REQUIRE_THROWS_AS(ASTNodeDataTypeChecker{*ast}, ParseError);
+    ast->m_symbol_table->clearValues();
   }
 }
diff --git a/tests/test_ASTNodeDataTypeFlattener.cpp b/tests/test_ASTNodeDataTypeFlattener.cpp
index aaa3739b2266f45e9c62b8a606000749468c33d8..65249b0780b7ae897d0ffba0627fc09ac20b26c7 100644
--- a/tests/test_ASTNodeDataTypeFlattener.cpp
+++ b/tests/test_ASTNodeDataTypeFlattener.cpp
@@ -46,6 +46,7 @@ b;
       REQUIRE(flattened_datatype_list.size() == 1);
       REQUIRE(flattened_datatype_list[0].m_data_type == ASTNodeDataType::bool_t);
       REQUIRE(&flattened_datatype_list[0].m_parent_node == root_node->children[1].get());
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("N")
@@ -72,6 +73,7 @@ n;
       REQUIRE(flattened_datatype_list.size() == 1);
       REQUIRE(flattened_datatype_list[0].m_data_type == ASTNodeDataType::unsigned_int_t);
       REQUIRE(&flattened_datatype_list[0].m_parent_node == root_node->children[1].get());
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -107,6 +109,7 @@ f(2);
       REQUIRE(flattened_datatype_list.size() == 1);
       REQUIRE(flattened_datatype_list[0].m_data_type == ASTNodeDataType::unsigned_int_t);
       REQUIRE(&flattened_datatype_list[0].m_parent_node == root_node->children[0].get());
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("function evaluation -> N*R*B*string*Z*builtin_t")
@@ -158,6 +161,7 @@ f(2, b);
       REQUIRE(flattened_datatype_list[3].m_data_type == ASTNodeDataType::string_t);
       REQUIRE(flattened_datatype_list[4].m_data_type == ASTNodeDataType::int_t);
       REQUIRE(flattened_datatype_list[5].m_data_type == ASTNodeDataType::type_id_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("function evaluation -> R*R^3")
@@ -191,6 +195,7 @@ f(2);
       REQUIRE(flattened_datatype_list[0].m_data_type == ASTNodeDataType::double_t);
       REQUIRE(flattened_datatype_list[1].m_data_type == ASTNodeDataType::vector_t);
       REQUIRE(flattened_datatype_list[1].m_data_type.dimension() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin_function -> R*R")
@@ -225,6 +230,7 @@ sum_vector(2,3);
       REQUIRE(flattened_datatype_list[0].m_data_type == ASTNodeDataType::double_t);
       REQUIRE(flattened_datatype_list[1].m_data_type == ASTNodeDataType::vector_t);
       REQUIRE(flattened_datatype_list[1].m_data_type.dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
   }
 }
diff --git a/tests/test_ASTNodeDeclarationToAffectationConverter.cpp b/tests/test_ASTNodeDeclarationToAffectationConverter.cpp
index f38b7aaf7c57d60d7fb7a6a44b1241b3d71aa6c0..ee9d2ebd56c2fbf22156d200e3004f311af7a6bd 100644
--- a/tests/test_ASTNodeDeclarationToAffectationConverter.cpp
+++ b/tests/test_ASTNodeDeclarationToAffectationConverter.cpp
@@ -31,6 +31,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::none}}; \
                                                                                                \
     REQUIRE(ast_output.str() == expected_output);                                              \
+    ast->m_symbol_table->clearValues();                                                        \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTNodeEmptyBlockCleaner.cpp b/tests/test_ASTNodeEmptyBlockCleaner.cpp
index baf6978c6a6d04d8a4e68a75e5e61fad635f8b55..db8178420525e3ac5882f910bf4ed402a32be3e1 100644
--- a/tests/test_ASTNodeEmptyBlockCleaner.cpp
+++ b/tests/test_ASTNodeEmptyBlockCleaner.cpp
@@ -39,6 +39,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTNodeExpressionBuilder.cpp b/tests/test_ASTNodeExpressionBuilder.cpp
index 127b2e01624a07ba9579494692573106ced0bc9a..79fae8ff86bb274ca5759a3affdb00f8670b3290 100644
--- a/tests/test_ASTNodeExpressionBuilder.cpp
+++ b/tests/test_ASTNodeExpressionBuilder.cpp
@@ -36,6 +36,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 #define CHECK_AST_THROWS_WITH(data, error_message)                    \
@@ -49,6 +50,7 @@
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -916,5 +918,6 @@ continue;
     // for this test
     ast->children[0]->set_type<language::ignored>();
     REQUIRE_THROWS_AS(ASTNodeExpressionBuilder{*ast}, ParseError);
+    ast->m_symbol_table->clearValues();
   }
 }
diff --git a/tests/test_ASTNodeFunctionEvaluationExpressionBuilder.cpp b/tests/test_ASTNodeFunctionEvaluationExpressionBuilder.cpp
index 289eb44be78bc57ed86a03ea308c649b2d17cc87..7ea1be3ace8354f3ed84af47f5dc9b5db8c9e851 100644
--- a/tests/test_ASTNodeFunctionEvaluationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeFunctionEvaluationExpressionBuilder.cpp
@@ -35,6 +35,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTNodeFunctionExpressionBuilder.cpp b/tests/test_ASTNodeFunctionExpressionBuilder.cpp
index f26ee8ad39eac1c79150d3283ef71d45eba4ca71..ef9ad42fe3ddf16f862a58f8c95acab4374a4c86 100644
--- a/tests/test_ASTNodeFunctionExpressionBuilder.cpp
+++ b/tests/test_ASTNodeFunctionExpressionBuilder.cpp
@@ -57,6 +57,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}};             \
                                                                                                                 \
     REQUIRE(ast_output.str() == expected_output);                                                               \
+    ast->m_symbol_table->clearValues();                                                                         \
   }
 
 #define CHECK_AST_THROWS(data)                                                     \
@@ -75,6 +76,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
     ASTNodeTypeCleaner<language::var_declaration>{*ast};                           \
     ASTNodeTypeCleaner<language::fct_declaration>{*ast};                           \
     REQUIRE_THROWS_AS(ASTNodeExpressionBuilder{*ast}, ParseError);                 \
+    ast->m_symbol_table->clearValues();                                            \
   }
 
 #define CHECK_TYPE_BUILDER_THROWS_WITH(data, error)                                \
@@ -90,6 +92,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
                                                                                    \
     ASTSymbolTableBuilder{*ast};                                                   \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error);                      \
+    ast->m_symbol_table->clearValues();                                            \
   }
 
 #define CHECK_EXPRESSION_BUILDER_THROWS_WITH(data, error)                          \
@@ -109,6 +112,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
     ASTNodeTypeCleaner<language::var_declaration>{*ast};                           \
     ASTNodeTypeCleaner<language::fct_declaration>{*ast};                           \
     REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, error);                    \
+    ast->m_symbol_table->clearValues();                                            \
   }
 
 #ifdef __clang__
@@ -4018,6 +4022,7 @@ let g:R -> R, void -> 2;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error);
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("void as a parameter")
@@ -4037,6 +4042,7 @@ let g:void -> R, x -> 2;
         ASTSymbolTableBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error);
+        ast->m_symbol_table->clearValues();
       }
 
       SECTION("void in compound domain")
@@ -4048,13 +4054,6 @@ let h:R*void -> R, (x,void) -> 2.5;
         std::string error = "parse error, expecting type specifier";
 
         TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
-        // auto ast = ASTBuilder::build(input);
-
-        // ASTModulesImporter{*ast};
-        // ASTNodeTypeCleaner<language::import_instruction>{*ast};
-
-        // ASTSymbolTableBuilder{*ast};
-        // ASTNodeDataTypeBuilder{*ast};
 
         REQUIRE_THROWS_WITH(ASTBuilder::build(input), error);
       }
diff --git a/tests/test_ASTNodeIncDecExpressionBuilder.cpp b/tests/test_ASTNodeIncDecExpressionBuilder.cpp
index 41297a7f05884dfbdde0ecd384a416b39f9ff417..049fe462feba79f297e640fe4d7e5ec35c3e44cf 100644
--- a/tests/test_ASTNodeIncDecExpressionBuilder.cpp
+++ b/tests/test_ASTNodeIncDecExpressionBuilder.cpp
@@ -39,6 +39,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 #define DISALLOWED_CHAINED_AST(data, expected_error)                                          \
@@ -60,6 +61,7 @@
     ASTNodeTypeCleaner<language::var_declaration>{*ast};                                      \
                                                                                               \
     REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, expected_error);                      \
+    ast->m_symbol_table->clearValues();                                                       \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTNodeJumpPlacementChecker.cpp b/tests/test_ASTNodeJumpPlacementChecker.cpp
index f69a09ac43565f90a0e42c71f8e9f38cd18c401f..b25a6669f08b7f47004e06f8d7b6fdb4e866fd36 100644
--- a/tests/test_ASTNodeJumpPlacementChecker.cpp
+++ b/tests/test_ASTNodeJumpPlacementChecker.cpp
@@ -29,6 +29,7 @@ for(;;) {
       ASTNodeDataTypeBuilder{*ast};
 
       REQUIRE_NOTHROW(ASTNodeJumpPlacementChecker{*ast});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("in while loop")
@@ -45,6 +46,7 @@ while(true) {
       ASTNodeDataTypeBuilder{*ast};
 
       REQUIRE_NOTHROW(ASTNodeJumpPlacementChecker{*ast});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("in do while loop")
@@ -61,6 +63,7 @@ do {
       ASTNodeDataTypeBuilder{*ast};
 
       REQUIRE_NOTHROW(ASTNodeJumpPlacementChecker{*ast});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("misplaced")
@@ -79,6 +82,7 @@ do {
       ast->children[0]->m_data_type = ASTNodeDataType::build<ASTNodeDataType::undefined_t>();
 
       REQUIRE_THROWS_AS(ASTNodeJumpPlacementChecker{*ast}, ParseError);
+      ast->m_symbol_table->clearValues();
     }
   }
 
@@ -98,6 +102,7 @@ for(;;) {
       ASTNodeDataTypeBuilder{*ast};
 
       REQUIRE_NOTHROW(ASTNodeJumpPlacementChecker{*ast});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("in while loop")
@@ -114,6 +119,7 @@ while(true) {
       ASTNodeDataTypeBuilder{*ast};
 
       REQUIRE_NOTHROW(ASTNodeJumpPlacementChecker{*ast});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("in do while loop")
@@ -130,6 +136,7 @@ do {
       ASTNodeDataTypeBuilder{*ast};
 
       REQUIRE_NOTHROW(ASTNodeJumpPlacementChecker{*ast});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("misplaced")
@@ -148,6 +155,7 @@ do {
       ast->children[0]->m_data_type = ASTNodeDataType::build<ASTNodeDataType::undefined_t>();
 
       REQUIRE_THROWS_AS(ASTNodeJumpPlacementChecker{*ast}, ParseError);
+      ast->m_symbol_table->clearValues();
     }
   }
 }
diff --git a/tests/test_ASTNodeListAffectationExpressionBuilder.cpp b/tests/test_ASTNodeListAffectationExpressionBuilder.cpp
index 3f71228607f80f2ac04ad69fc408258ced89b413..d929860eb33e3c361e0ae363c8e2e9c2b28887cd 100644
--- a/tests/test_ASTNodeListAffectationExpressionBuilder.cpp
+++ b/tests/test_ASTNodeListAffectationExpressionBuilder.cpp
@@ -59,6 +59,7 @@ const auto builtin_data_type = ast_node_data_type_from<std::shared_ptr<const dou
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}};             \
                                                                                                                 \
     REQUIRE(ast_output.str() == expected_output);                                                               \
+    ast->m_symbol_table->clearValues();                                                                         \
   }
 
 #define CHECK_AST_THROWS_WITH(data, error)                                                                      \
diff --git a/tests/test_ASTNodeListProcessor.cpp b/tests/test_ASTNodeListProcessor.cpp
index d858b313aa695d39912953712858bf1bbb5e257e..583035059dee34e14121f64e6b449921b13d0d44 100644
--- a/tests/test_ASTNodeListProcessor.cpp
+++ b/tests/test_ASTNodeListProcessor.cpp
@@ -1,8 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -36,13 +36,14 @@ true;
 
   ASTNodeExpressionBuilder{*ast};
   ExecutionPolicy exec_policy;
-  ASTBacktrace::create();
+  ASTExecutionStack::create();
   ast->execute(exec_policy);
-  ASTBacktrace::destroy();
+  ASTExecutionStack::destroy();
 
   REQUIRE(ast->children[0]->is_type<language::integer>());
   REQUIRE(ast->children[1]->is_type<language::true_kw>());
   REQUIRE(ast->children[2]->is_type<language::real>());
 
   REQUIRE(ast->m_node_processor->typeIdName() == demangle<ASTNodeListProcessor>());
+  ast->m_symbol_table->clearValues();
 }
diff --git a/tests/test_ASTNodeTypeCleaner.cpp b/tests/test_ASTNodeTypeCleaner.cpp
index d78d4ce17fba3046d75e3ea1108d3874a3636dec..9bc9d50fddb02e7ef5cae9d5770af7062ac7ae80 100644
--- a/tests/test_ASTNodeTypeCleaner.cpp
+++ b/tests/test_ASTNodeTypeCleaner.cpp
@@ -28,6 +28,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::none}}; \
                                                                                                \
     REQUIRE(ast_output.str() == expected_output);                                              \
+    ast->m_symbol_table->clearValues();                                                        \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp b/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp
index d320aee0257de0e2cabf852ee9426885c755e7e4..9a12c70898dc730ea3ea2198cb21ef75a340f31f 100644
--- a/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp
+++ b/tests/test_ASTNodeUnaryOperatorExpressionBuilder.cpp
@@ -38,6 +38,7 @@
     ast_output << '\n' << ASTPrinter{*ast, ASTPrinter::Format::raw, {ASTPrinter::Info::exec_type}}; \
                                                                                                     \
     REQUIRE(ast_output.str() == expected_output);                                                   \
+    ast->m_symbol_table->clearValues();                                                             \
   }
 
 #define CHECK_AST_THROWS_WITH(data, error_message)                    \
@@ -51,6 +52,7 @@
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTPrinter.cpp b/tests/test_ASTPrinter.cpp
index 87072e631f8920d9885f4d996b9611e23daf4e10..375c942979a13fadef7bc8395fb8aba51d6ba930 100644
--- a/tests/test_ASTPrinter.cpp
+++ b/tests/test_ASTPrinter.cpp
@@ -4,6 +4,7 @@
 
 #include <language/ast/ASTBuilder.hpp>
 #include <language/utils/ASTPrinter.hpp>
+#include <language/utils/SymbolTable.hpp>
 
 #include <pegtl/string_input.hpp>
 
@@ -21,6 +22,7 @@
     ast_output << '\n' << ASTPrinter{*ast, format};                                           \
                                                                                               \
     REQUIRE(ast_output.str() == expected_output);                                             \
+    ast->m_symbol_table->clearValues();                                                       \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_ASTSymbolInitializationChecker.cpp b/tests/test_ASTSymbolInitializationChecker.cpp
index 52b49d7c90ef3183a1cb6bd984493320f7484495..57396e8dab8be32e179078e2ac0cdee02869ce16 100644
--- a/tests/test_ASTSymbolInitializationChecker.cpp
+++ b/tests/test_ASTSymbolInitializationChecker.cpp
@@ -41,6 +41,7 @@ let p:N;
     auto [symbol_p, found_p] = ast->m_symbol_table->find("p", position);
     REQUIRE(found_p);
     REQUIRE(not symbol_p->attributes().isInitialized());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("Declaration plus affectation")
@@ -73,6 +74,7 @@ m = n;
     auto [symbol_z, found_z] = ast->m_symbol_table->find("z", position);
     REQUIRE(found_z);
     REQUIRE(not symbol_z->attributes().isInitialized());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("Declarative function initialization")
@@ -93,6 +95,7 @@ let f: R->R, x->x+1;
     auto [symbol_m, found_m] = ast->m_symbol_table->find("f", position);
     REQUIRE(found_m);
     REQUIRE(symbol_m->attributes().isInitialized());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("Lists")
@@ -119,6 +122,7 @@ let (x,y):R*R, (x,y) = (2.3, 4.1);
       auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
       REQUIRE(found_y);
       REQUIRE(symbol_y->attributes().isInitialized());
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Declarative initialization")
@@ -139,6 +143,7 @@ let x:R^2, x = (2.3, 4.1);
       auto [symbol_x, found_x] = ast->m_symbol_table->find("x", position);
       REQUIRE(found_x);
       REQUIRE(symbol_x->attributes().isInitialized());
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Not initialized")
@@ -164,6 +169,7 @@ y = 3;
       auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
       REQUIRE(found_y);
       REQUIRE(symbol_y->attributes().isInitialized());
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Affectation")
@@ -189,6 +195,7 @@ let (x,y):R*R;
       auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
       REQUIRE(found_y);
       REQUIRE(symbol_y->attributes().isInitialized());
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Affectation")
@@ -214,6 +221,7 @@ let (x,y):R^3*R;
       auto [symbol_y, found_y] = ast->m_symbol_table->find("y", position);
       REQUIRE(found_y);
       REQUIRE(symbol_y->attributes().isInitialized());
+      ast->m_symbol_table->clearValues();
     }
   }
 
@@ -231,6 +239,7 @@ let m:N, m = n;
 
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"uninitialized symbol 'n'"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("used uninitialized in list affectation")
@@ -247,6 +256,7 @@ let (l, x) : N*R;
 
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"uninitialized symbol 'l'"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("used uninitialized in function")
@@ -261,6 +271,7 @@ let f : R->R, x->x+y;
 
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"uninitialized symbol 'y'"});
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("expecting a list of identifiers")
@@ -274,6 +285,7 @@ let (x,y,z):R*R*R, x = 3;
 
       ASTSymbolTableBuilder{*ast};
       REQUIRE_THROWS_WITH(ASTSymbolInitializationChecker{*ast}, std::string{"expecting a list of identifiers"});
+      ast->m_symbol_table->clearValues();
     }
   }
 }
diff --git a/tests/test_ASTSymbolTableBuilder.cpp b/tests/test_ASTSymbolTableBuilder.cpp
index 5f5eae42fe968a730ae82b5ae347a94fa3a9a4fb..2e29ac2f87662133a5e69a2a046f7b3b7dce3891 100644
--- a/tests/test_ASTSymbolTableBuilder.cpp
+++ b/tests/test_ASTSymbolTableBuilder.cpp
@@ -28,6 +28,7 @@ let n:N, n = 2;
     auto ast = ASTBuilder::build(input);
 
     ASTSymbolTableBuilder{*ast};
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("Populate symbol table")
@@ -66,6 +67,7 @@ let f: R*Z*B->R, (x,n,z) -> x+n;
                     << "------------------------\n";
 
     REQUIRE(st_output.str() == expected_output.str());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("errors")
@@ -80,6 +82,7 @@ let n:N, n = a;
       auto ast = ASTBuilder::build(input);
 
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "undefined symbol 'a'");
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Re-declared symbol")
@@ -93,6 +96,7 @@ let n:N, n = 1;
       auto ast = ASTBuilder::build(input);
 
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'n' was already defined at line 2");
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Re-declared symbol (nested scope)")
@@ -108,6 +112,7 @@ let n:N, n = 0;
       auto ast = ASTBuilder::build(input);
 
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'n' was already defined at line 2");
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Re-declared symbol (function)")
@@ -121,6 +126,7 @@ let f : R -> R, x -> 1;
       auto ast = ASTBuilder::build(input);
 
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'f' was already defined at line 2");
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Re-declared symbol (builtin function)")
@@ -135,6 +141,7 @@ let cos:N;
       ASTModulesImporter{*ast};
 
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'cos' already denotes a builtin function!");
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Re-declared symbol (builtin function) 2")
@@ -148,8 +155,8 @@ let cos: R -> R, x->2*x;
       auto ast = ASTBuilder::build(input);
       ASTModulesImporter{*ast};
 
-      // REQUIRE_THROWS_AS(ASTSymbolTableBuilder{*ast}, ParseError);
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'cos' already denotes a builtin function!");
+      ast->m_symbol_table->clearValues();
     }
 
     SECTION("Re-declared parameter (function)")
@@ -163,6 +170,7 @@ let f : R*R*N -> R,
       auto ast = ASTBuilder::build(input);
 
       REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'x' was already defined at line 3");
+      ast->m_symbol_table->clearValues();
     }
   }
 }
diff --git a/tests/test_AffectationProcessor.cpp b/tests/test_AffectationProcessor.cpp
index 599a9457f4cb940f08409d47ebb7ef2c792e7478..393be0d6725ac5f8a0c3fbefe1245f7e5538763b 100644
--- a/tests/test_AffectationProcessor.cpp
+++ b/tests/test_AffectationProcessor.cpp
@@ -16,7 +16,7 @@
 #include <utils/Stringify.hpp>
 
 #include <FixturesForBuiltinT.hpp>
-#include <language/ast/ASTBacktrace.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/utils/BasicAffectationRegistrerFor.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -38,9 +38,9 @@
                                                                               \
     ASTNodeExpressionBuilder{*ast};                                           \
     ExecutionPolicy exec_policy;                                              \
-    ASTBacktrace::create();                                                   \
+    ASTExecutionStack::create();                                              \
     ast->execute(exec_policy);                                                \
-    ASTBacktrace::destroy();                                                  \
+    ASTExecutionStack::destroy();                                             \
                                                                               \
     auto symbol_table = ast->m_symbol_table;                                  \
                                                                               \
@@ -52,6 +52,7 @@
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_BUILTIN_AFFECTATION_RESULT(data, variable_name, expected_value)                                   \
@@ -100,9 +101,9 @@
                                                                                                                 \
     ASTNodeExpressionBuilder{*ast};                                                                             \
     ExecutionPolicy exec_policy;                                                                                \
-    ASTBacktrace::create();                                                                                     \
+    ASTExecutionStack::create();                                                                                \
     ast->execute(exec_policy);                                                                                  \
-    ASTBacktrace::destroy();                                                                                    \
+    ASTExecutionStack::destroy();                                                                               \
                                                                                                                 \
     using namespace TAO_PEGTL_NAMESPACE;                                                                        \
     position use_position{10000, 1000, 10, "fixture"};                                                          \
@@ -113,6 +114,7 @@
                                                                                                                 \
     double value = *dynamic_cast<const DataHandler<const double>&>(embedded_value.get()).data_ptr();            \
     REQUIRE(value == expected);                                                                                 \
+    ast->m_symbol_table->clearValues();                                                                         \
   }
 
 #define CHECK_AFFECTATION_THROWS_WITH(data, error_message)              \
@@ -129,6 +131,7 @@
     ASTNodeTypeCleaner<language::var_declaration>{*ast};                \
                                                                         \
     REQUIRE_THROWS_WITH(ASTNodeExpressionBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                                 \
   }
 
 #define CHECK_BUILD_THROWS_WITH(data, error_message)              \
@@ -153,9 +156,10 @@
     ASTNodeExpressionBuilder{*ast};                                \
     ExecutionPolicy exec_policy;                                   \
                                                                    \
-    ASTBacktrace::create();                                        \
+    ASTExecutionStack::create();                                   \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), error_message); \
-    ASTBacktrace::destroy();                                       \
+    ASTExecutionStack::destroy();                                  \
+    ast->m_symbol_table->clearValues();                            \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_AffectationToStringProcessor.cpp b/tests/test_AffectationToStringProcessor.cpp
index 68f97dfedd38da47c31a3aef9e64364e31e29a97..fd6260123d3a7e5926b984ee8bfe388876e36c76 100644
--- a/tests/test_AffectationToStringProcessor.cpp
+++ b/tests/test_AffectationToStringProcessor.cpp
@@ -1,8 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -33,9 +33,9 @@
                                                                               \
     ASTNodeExpressionBuilder{*ast};                                           \
     ExecutionPolicy exec_policy;                                              \
-    ASTBacktrace::create();                                                   \
+    ASTExecutionStack::create();                                              \
     ast->execute(exec_policy);                                                \
-    ASTBacktrace::destroy();                                                  \
+    ASTExecutionStack::destroy();                                             \
                                                                               \
     auto symbol_table = ast->m_symbol_table;                                  \
                                                                               \
@@ -48,6 +48,7 @@
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_AffectationToTupleProcessor.cpp b/tests/test_AffectationToTupleProcessor.cpp
index b8e7010c9bcd748b168f048163619b7c61622edc..056df82924f86ae194fdcec3709763235d9680ec 100644
--- a/tests/test_AffectationToTupleProcessor.cpp
+++ b/tests/test_AffectationToTupleProcessor.cpp
@@ -1,8 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -33,9 +33,9 @@
                                                                               \
     ASTNodeExpressionBuilder{*ast};                                           \
     ExecutionPolicy exec_policy;                                              \
-    ASTBacktrace::create();                                                   \
+    ASTExecutionStack::create();                                              \
     ast->execute(exec_policy);                                                \
-    ASTBacktrace::destroy();                                                  \
+    ASTExecutionStack::destroy();                                             \
                                                                               \
     auto symbol_table = ast->m_symbol_table;                                  \
                                                                               \
@@ -47,6 +47,7 @@
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_AFFECTATION_EXEC_THROWS_WITH(data, error_message)    \
@@ -65,9 +66,10 @@
     ASTNodeExpressionBuilder{*ast};                                \
     ExecutionPolicy exec_policy;                                   \
                                                                    \
-    ASTBacktrace::create();                                        \
+    ASTExecutionStack::create();                                   \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), error_message); \
-    ASTBacktrace::destroy();                                       \
+    ASTExecutionStack::destroy();                                  \
+    ast->m_symbol_table->clearValues();                            \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_Array.cpp b/tests/test_Array.cpp
index 28c018cced7ac2c4a2295b1824882a7dc5b492f8..fbac561b8dfd79b15a5cabf4f12f4b9ee2bb1004 100644
--- a/tests/test_Array.cpp
+++ b/tests/test_Array.cpp
@@ -448,6 +448,10 @@ TEST_CASE("Array", "[utils]")
       REQUIRE(sum_before_shuffle == sum_after_shuffle);
 
       REQUIRE(sum_before_shuffle == Catch::Approx(direct_sum(array)));
+
+      ReproducibleSumManager::setReproducibleSums(false);
+      REQUIRE(sum(array) == Catch::Approx(direct_sum(array)));
+      ReproducibleSumManager::setReproducibleSums(true);
     }
 
     SECTION("reproducible float sum")
@@ -1250,6 +1254,19 @@ TEST_CASE("Array", "[utils]")
         }
       }
     }
+
+    SECTION("non reproducible double sum")
+    {
+      Array<double> array(10'000);
+
+      for (size_t i = 0; i < array.size(); ++i) {
+        array[i] = ((i < (array.size() / 10)) * 1E25 + 1E10) * ((i + 1) % 1'000) * std::sin(3 * i + 1);
+      }
+
+      ReproducibleSumManager::setReproducibleSums(false);
+      REQUIRE(sum(array) == Catch::Approx(direct_sum(array)));
+      ReproducibleSumManager::setReproducibleSums(true);
+    }
   }
 
   SECTION("checking for subArrayView")
@@ -1295,6 +1312,10 @@ TEST_CASE("Array", "[utils]")
         REQUIRE(array[i] == 2 * int_i + 1);
       }
     }
+
+    std::ostringstream os;
+    os << view;
+    REQUIRE(os.str() == "0:3 1:1 2:-1 3:-3 4:-5 5:-7");
   }
 
 #ifndef NDEBUG
diff --git a/tests/test_ArraySubscriptProcessor.cpp b/tests/test_ArraySubscriptProcessor.cpp
index 48998c7d39999b5c45590a80054377d5057c4355..9ece9e4c7c0359c8cf5c038e001c256bf85ba72f 100644
--- a/tests/test_ArraySubscriptProcessor.cpp
+++ b/tests/test_ArraySubscriptProcessor.cpp
@@ -1,8 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_all.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -34,9 +34,9 @@
                                                                               \
     ASTNodeExpressionBuilder{*ast};                                           \
     ExecutionPolicy exec_policy;                                              \
-    ASTBacktrace::create();                                                   \
+    ASTExecutionStack::create();                                              \
     ast->execute(exec_policy);                                                \
-    ASTBacktrace::destroy();                                                  \
+    ASTExecutionStack::destroy();                                             \
                                                                               \
     auto symbol_table = ast->m_symbol_table;                                  \
                                                                               \
@@ -48,6 +48,7 @@
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_EVALUATION_THROWS_WITH(data, error_message)        \
@@ -68,9 +69,10 @@
                                                                  \
       ASTNodeExpressionBuilder{*ast};                            \
       ExecutionPolicy exec_policy;                               \
-      ASTBacktrace::create();                                    \
+      ASTExecutionStack::create();                               \
       ast->execute(exec_policy);                                 \
-      ASTBacktrace::destroy();                                   \
+      ASTExecutionStack::destroy();                              \
+      ast->m_symbol_table->clearValues();                        \
     };                                                           \
                                                                  \
     REQUIRE_THROWS_WITH(eval(), error_message);                  \
diff --git a/tests/test_BinaryExpressionProcessor_arithmetic.cpp b/tests/test_BinaryExpressionProcessor_arithmetic.cpp
index c1ce3de0e03f5d9b0b14e43ba71cc06fd4c70359..70e4efdef48fd32f07c286705a90e12205aa5eb1 100644
--- a/tests/test_BinaryExpressionProcessor_arithmetic.cpp
+++ b/tests/test_BinaryExpressionProcessor_arithmetic.cpp
@@ -76,9 +76,9 @@
                                                                                                                 \
     ASTNodeExpressionBuilder{*ast};                                                                             \
     ExecutionPolicy exec_policy;                                                                                \
-    ASTBacktrace::create();                                                                                     \
+    ASTExecutionStack::create();                                                                                \
     ast->execute(exec_policy);                                                                                  \
-    ASTBacktrace::destroy();                                                                                    \
+    ASTExecutionStack::destroy();                                                                               \
                                                                                                                 \
     using namespace TAO_PEGTL_NAMESPACE;                                                                        \
     position use_position{10000, 1000, 10, "fixture"};                                                          \
@@ -89,6 +89,7 @@
                                                                                                                 \
     double value = *dynamic_cast<const DataHandler<const double>&>(embedded_value.get()).data_ptr();            \
     REQUIRE(value == expected);                                                                                 \
+    ast->m_symbol_table->clearValues();                                                                         \
   }
 
 #define CHECK_BUILTIN_BINARY_EXPRESSION_ERROR(data, error_msg)                                                    \
@@ -154,9 +155,10 @@
                                                                                                                   \
     ASTNodeExpressionBuilder{*ast};                                                                               \
     ExecutionPolicy exec_policy;                                                                                  \
-    ASTBacktrace::create();                                                                                       \
+    ASTExecutionStack::create();                                                                                  \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), error_msg);                                                    \
-    ASTBacktrace::destroy();                                                                                      \
+    ASTExecutionStack::destroy();                                                                                 \
+    ast->m_symbol_table->clearValues();                                                                           \
   }
 
 TEST_CASE("BinaryExpressionProcessor arithmetic", "[language]")
diff --git a/tests/test_BinaryExpressionProcessor_shift.cpp b/tests/test_BinaryExpressionProcessor_shift.cpp
index 190b9f2b26d39d831550b70fef528a06f2c4503e..1ccf1231a8720295727a3801de781f2c08223130 100644
--- a/tests/test_BinaryExpressionProcessor_shift.cpp
+++ b/tests/test_BinaryExpressionProcessor_shift.cpp
@@ -53,6 +53,7 @@ fout << createSocketServer(0) << "\n";)";
       TAO_PEGTL_NAMESPACE::string_input input{data.str(), "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
+      CheckpointResumeRepository::create();
       ASTModulesImporter{*ast};
       ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
@@ -64,9 +65,11 @@ fout << createSocketServer(0) << "\n";)";
 
       ASTNodeExpressionBuilder{*ast};
       ExecutionPolicy exec_policy;
-      ASTBacktrace::create();
+      ASTExecutionStack::create();
       ast->execute(exec_policy);
-      ASTBacktrace::destroy();
+      ASTExecutionStack::destroy();
+      CheckpointResumeRepository::destroy();
+      ast->m_symbol_table->clearValues();
     }
 
     REQUIRE(std::filesystem::exists(filename));
diff --git a/tests/test_BinaryExpressionProcessor_utils.hpp b/tests/test_BinaryExpressionProcessor_utils.hpp
index 4b5deff94b6cab09ccdb72e6113dd5c2cc83d2e5..459115866b4a54d75044aa88f44a4172ec195e15 100644
--- a/tests/test_BinaryExpressionProcessor_utils.hpp
+++ b/tests/test_BinaryExpressionProcessor_utils.hpp
@@ -1,14 +1,15 @@
 #ifndef TEST_BINARY_EXPRESSION_PROCESSOR_UTILS_HPP
 #define TEST_BINARY_EXPRESSION_PROCESSOR_UTILS_HPP
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
 #include <utils/Demangle.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -23,6 +24,7 @@
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
+    CheckpointResumeRepository::create();                                     \
     ASTSymbolTableBuilder{*ast};                                              \
     ASTNodeDataTypeBuilder{*ast};                                             \
                                                                               \
@@ -31,9 +33,9 @@
                                                                               \
     ASTNodeExpressionBuilder{*ast};                                           \
     ExecutionPolicy exec_policy;                                              \
-    ASTBacktrace::create();                                                   \
+    ASTExecutionStack::create();                                              \
     ast->execute(exec_policy);                                                \
-    ASTBacktrace::destroy();                                                  \
+    ASTExecutionStack::destroy();                                             \
                                                                               \
     auto symbol_table = ast->m_symbol_table;                                  \
                                                                               \
@@ -43,6 +45,7 @@
                                                                               \
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
+    CheckpointResumeRepository::destroy();                                    \
                                                                               \
     REQUIRE(value == expected_value);                                         \
   }
diff --git a/tests/test_BuildInfo.cpp b/tests/test_BuildInfo.cpp
index 847db9f3cdc8517bcca91b50329fb6ff70fbe3f7..9414902f4d63b498e68bffd4d84b3cae32a165bd 100644
--- a/tests/test_BuildInfo.cpp
+++ b/tests/test_BuildInfo.cpp
@@ -16,6 +16,10 @@
 #include <petsc.h>
 #endif   // PUGS_HAS_PETSC
 
+#ifdef PUGS_HAS_SLURM
+#include <slurm/slurm.h>
+#endif   // PUGS_HAS_SLURM
+
 // clazy:excludeall=non-pod-global-static
 
 TEST_CASE("BuildInfo", "[utils]")
@@ -64,4 +68,17 @@ TEST_CASE("BuildInfo", "[utils]")
     REQUIRE(BuildInfo::petscLibrary() == "none");
 #endif   // PUGS_HAS_PETSC
   }
+
+  SECTION("slurm")
+  {
+#ifdef PUGS_HAS_SLURM
+    const std::string slurm_library = stringify(SLURM_VERSION_MAJOR(SLURM_VERSION_NUMBER)) + "." +
+                                      stringify(SLURM_VERSION_MINOR(SLURM_VERSION_NUMBER)) + "." +
+                                      stringify(SLURM_VERSION_MICRO(SLURM_VERSION_NUMBER));
+
+    REQUIRE(BuildInfo::slurmLibrary() == slurm_library);
+#else
+    REQUIRE(BuildInfo::slurmLibrary() == "none");
+#endif   // PUGS_HAS_SLURM
+  }
 }
diff --git a/tests/test_BuiltinFunctionEmbedder.cpp b/tests/test_BuiltinFunctionEmbedder.cpp
index 62c5129cd079cb65e6663ecb60fdbb451535fa2b..95726767904a9263f82ee199f3a0e7973bde2fc5 100644
--- a/tests/test_BuiltinFunctionEmbedder.cpp
+++ b/tests/test_BuiltinFunctionEmbedder.cpp
@@ -13,6 +13,11 @@ template <>
 inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const uint64_t>> =
   ASTNodeDataType::build<ASTNodeDataType::type_id_t>("shared_const_uint64_t");
 
+template <>
+inline ASTNodeDataType ast_node_data_type_from<int> = ASTNodeDataType{};
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const int>> = ASTNodeDataType{};
+
 TEST_CASE("BuiltinFunctionEmbedder", "[language]")
 {
   rang::setControlMode(rang::control::Off);
diff --git a/tests/test_BuiltinFunctionEmbedderUtils.cpp b/tests/test_BuiltinFunctionEmbedderUtils.cpp
index 3dee7e0092ab8e6a725fcf671fdedb45a8dc6a74..8b414382e333316da16cf7be605c182a1ede1716 100644
--- a/tests/test_BuiltinFunctionEmbedderUtils.cpp
+++ b/tests/test_BuiltinFunctionEmbedderUtils.cpp
@@ -69,6 +69,7 @@ foo(3);
       REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*string -> R")
@@ -92,6 +93,7 @@ foo(3,"bar");
       REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*string -> R (using builtin function result)")
@@ -115,6 +117,7 @@ foo(foo(3),"bar");
       REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function N -> N (choosing appropriate candidate)")
@@ -139,6 +142,7 @@ foo(2);
       REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::int_t);
       REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::int_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R -> R (using function result)")
@@ -158,6 +162,7 @@ foo(f(3));
       REQUIRE(function_embedder->getReturnDataType() == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*string -> R (using  function result)")
@@ -182,6 +187,7 @@ foo(f(3));
       REQUIRE(function_embedder->getParameterDataTypes().size() == 2);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::string_t);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -210,6 +216,7 @@ foo(3,x);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^1 -> R^2")
@@ -236,6 +243,7 @@ foo(3,1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^1 -> R^2")
@@ -263,6 +271,7 @@ foo(3.1,f(1));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R^1 -> R^2 (from f: R -> R*R^1)")
@@ -290,6 +299,7 @@ foo(f(2));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -318,6 +328,7 @@ foo(3,x);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^2 -> R^3")
@@ -344,6 +355,7 @@ foo(3,0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^2 -> R^2 (R^2 from list)")
@@ -370,6 +382,7 @@ foo(3.1,[1,2.3]);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -400,6 +413,7 @@ foo(3,x);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^3 -> R^3")
@@ -426,6 +440,7 @@ foo(3,0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^3 -> R^2 (R^3 from list)")
@@ -452,6 +467,7 @@ foo(3.1,[1,2.3,4]);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].dimension() == 3);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -480,6 +496,7 @@ foo(3,x);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^1x1 -> R^2x2")
@@ -506,6 +523,7 @@ foo(3,1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^1x1 -> R^2x2")
@@ -533,6 +551,7 @@ foo(3.1,f(1));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R^1x1 -> R^2x2 (from f: R -> R*R^1x1)")
@@ -560,6 +579,7 @@ foo(f(2));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -588,6 +608,7 @@ foo(3,x);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^2x2 -> R^3x3")
@@ -616,6 +637,7 @@ foo(3,0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^2x2 -> R^2x2 (R^2x2 from list)")
@@ -642,6 +664,7 @@ foo(3.1,[[1,2.3],[0,3]]);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 2);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -673,6 +696,7 @@ foo(3,x);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^3x3 -> R^3x3")
@@ -699,6 +723,7 @@ foo(3,0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function R*R^3x3 -> R^2x2 (R^3x3 from list)")
@@ -726,6 +751,7 @@ foo(3.1,[[1, 2.3, 4], [0.3, 2.5, 4.6], [2.7, 8.1, -9]]);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::double_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1] == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[1].numberOfRows() == 3);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -752,6 +778,7 @@ foo(v);
       REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::double_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R) -> N (from Z)")
@@ -774,6 +801,7 @@ foo(-4);
       REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::double_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1) -> N (from (N...))")
@@ -798,6 +826,7 @@ foo(v);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1) -> N (from 0)")
@@ -821,6 +850,7 @@ foo(0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R) -> N (from list)")
@@ -843,6 +873,7 @@ foo((1,2,3,5));
       REQUIRE(function_embedder->getParameterDataTypes().size() == 1);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::double_t);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1) -> N (from castable list)")
@@ -866,6 +897,7 @@ foo((1,2,3,5));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1) -> N (from list)")
@@ -890,6 +922,7 @@ foo((x,2*x));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1x1) -> N (from castable list)")
@@ -913,6 +946,7 @@ foo((1,2,3,5));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1x1) -> N (from 0)")
@@ -936,6 +970,7 @@ foo(0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^1x1) -> N (from list)")
@@ -960,6 +995,7 @@ foo((x,2*x));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 1);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^2) -> N (from list)")
@@ -984,6 +1020,7 @@ foo((x,2*x));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^2) -> N (from 0)")
@@ -1007,6 +1044,7 @@ foo(0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^2x2) -> N (from 0)")
@@ -1030,6 +1068,7 @@ foo(0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^2x2) -> N (from list)")
@@ -1054,6 +1093,7 @@ foo((x,2*x));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^3) -> N (from list)")
@@ -1078,6 +1118,7 @@ foo((x,2*x));
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^3) -> N (from 0)")
@@ -1101,6 +1142,7 @@ foo(0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().dimension() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^3x3) -> N (from 0)")
@@ -1124,6 +1166,7 @@ foo(0);
       REQUIRE(function_embedder->getParameterDataTypes()[0] == ASTNodeDataType::tuple_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 3);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("builtin function (R^3x3) -> N (from list)")
@@ -1149,6 +1192,7 @@ foo((x,2*x));
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType() == ASTNodeDataType::matrix_t);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfRows() == 3);
       REQUIRE(function_embedder->getParameterDataTypes()[0].contentType().numberOfColumns() == 3);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -1191,6 +1235,7 @@ foo((x,2*x), 1, "bar", [2,3]);
       REQUIRE(function_embedder->getParameterDataTypes()[2] == ASTNodeDataType::string_t);
       REQUIRE(function_embedder->getParameterDataTypes()[3] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[3].dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("tuple not first")
@@ -1230,6 +1275,7 @@ foo(1, "bar", (x,2*x), [2,3]);
       REQUIRE(function_embedder->getParameterDataTypes()[2].contentType().numberOfColumns() == 3);
       REQUIRE(function_embedder->getParameterDataTypes()[3] == ASTNodeDataType::vector_t);
       REQUIRE(function_embedder->getParameterDataTypes()[3].dimension() == 2);
+      root_node->m_symbol_table->clearValues();
     }
   }
 
@@ -1264,6 +1310,7 @@ foo(x);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^2: invalid conversion")
@@ -1295,6 +1342,7 @@ foo(x);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^3: invalid conversion")
@@ -1326,6 +1374,7 @@ foo(x);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^2: invalid argument list size")
@@ -1356,6 +1405,7 @@ foo(1,2,3,4);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^3: invalid argument list size")
@@ -1386,6 +1436,7 @@ foo(1,2,3,4);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^1x1: invalid conversion")
@@ -1417,6 +1468,7 @@ foo(x);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^2x2: invalid conversion")
@@ -1448,6 +1500,7 @@ foo(x);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^3x3: invalid conversion")
@@ -1479,6 +1532,7 @@ foo(x);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^2x2: invalid argument list size")
@@ -1509,6 +1563,7 @@ foo(1,2,3);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("R^3x3: invalid argument list size")
@@ -1539,6 +1594,7 @@ foo(1,2,3,4);
                                                                              return x;
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("(N) invalid cast")
@@ -1562,6 +1618,7 @@ foo(1.34);
                                                                          return t.size();
                                                                        }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
 
     SECTION("ambiguous function call")
@@ -1599,6 +1656,7 @@ foo(1);
                                                                              return x[0];
                                                                            }))}),
                           error_msg);
+      root_node->m_symbol_table->clearValues();
     }
   }
 }
diff --git a/tests/test_BuiltinFunctionProcessor.cpp b/tests/test_BuiltinFunctionProcessor.cpp
index 5556eca1945be30e55b72982094fc5a8d4625b50..ca5a79339a99ac55a9d0bbd36340d570eab3b7e5 100644
--- a/tests/test_BuiltinFunctionProcessor.cpp
+++ b/tests/test_BuiltinFunctionProcessor.cpp
@@ -1,8 +1,8 @@
 #include <catch2/catch_test_macros.hpp>
 #include <catch2/matchers/catch_matchers_predicate.hpp>
 
-#include <language/ast/ASTBacktrace.hpp>
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -10,6 +10,7 @@
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
 #include <language/modules/MathModule.hpp>
+#include <language/node_processor/BuiltinFunctionProcessor.hpp>
 
 #include <test_BuiltinFunctionRegister.hpp>
 
@@ -32,9 +33,9 @@
                                                                                                                       \
     ASTNodeExpressionBuilder{*ast};                                                                                   \
     ExecutionPolicy exec_policy;                                                                                      \
-    ASTBacktrace::create();                                                                                           \
+    ASTExecutionStack::create();                                                                                      \
     ast->execute(exec_policy);                                                                                        \
-    ASTBacktrace::destroy();                                                                                          \
+    ASTExecutionStack::destroy();                                                                                     \
                                                                                                                       \
     auto symbol_table = ast->m_symbol_table;                                                                          \
                                                                                                                       \
@@ -49,6 +50,7 @@
     auto value      = std::get<decltype(expected_value)>(attributes.value());                                         \
                                                                                                                       \
     REQUIRE(value == expected_value);                                                                                 \
+    ast->m_symbol_table->clearValues();                                                                               \
   }
 
 #define CHECK_AST_THROWS_WITH(data, expected_error)                                             \
@@ -71,9 +73,10 @@
     ASTNodeExpressionBuilder{*ast};                                                             \
     ExecutionPolicy exec_policy;                                                                \
     using namespace Catch::Matchers;                                                            \
-    ASTBacktrace::create();                                                                     \
+    ASTExecutionStack::create();                                                                \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), expected_error);                             \
-    ASTBacktrace::destroy();                                                                    \
+    ASTExecutionStack::destroy();                                                               \
+    ast->m_symbol_table->clearValues();                                                         \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -672,4 +675,12 @@ let x:R, x = tuple_ZtoR(3);
       CHECK_BUILTIN_FUNCTION_EVALUATION_RESULT(data, "x", 0.5 * 3);
     }
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE(BuiltinFunctionExpressionProcessor{nullptr}.type() ==
+            INodeProcessor::Type::builtin_function_expression_processor);
+    REQUIRE(BuiltinFunctionProcessor{node}.type() == INodeProcessor::Type::builtin_function_processor);
+  }
 }
diff --git a/tests/test_CommunicatorManager.cpp b/tests/test_CommunicatorManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..56b76b2610a49dc84800bb777af29293e751eec4
--- /dev/null
+++ b/tests/test_CommunicatorManager.cpp
@@ -0,0 +1,29 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/CommunicatorManager.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+inline void
+resetCommunicationManagerForTests()
+{
+  CommunicatorManager::s_split_color.reset();
+}
+
+TEST_CASE("CommunicatorManager", "[utils]")
+{
+  resetCommunicationManagerForTests();
+
+  REQUIRE(not CommunicatorManager::hasSplitColor());
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(CommunicatorManager::splitColor(), "split color has not been defined");
+#endif   // NDEBUG
+
+  REQUIRE_NOTHROW(CommunicatorManager::setSplitColor(2));
+  REQUIRE(CommunicatorManager::splitColor() == 2);
+
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(CommunicatorManager::setSplitColor(2), "split color has already been defined");
+#endif   // NDEBUG
+}
diff --git a/tests/test_ConcatExpressionProcessor.cpp b/tests/test_ConcatExpressionProcessor.cpp
index 315f89d518e0262cdc6b44d3d99f13673d803168..ed278039759ade26d58b54d8a1ef65be24f575f3 100644
--- a/tests/test_ConcatExpressionProcessor.cpp
+++ b/tests/test_ConcatExpressionProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -9,6 +10,7 @@
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/node_processor/ConcatExpressionProcessor.hpp>
 #include <language/utils/ASTPrinter.hpp>
 #include <utils/Demangle.hpp>
 #include <utils/Stringify.hpp>
@@ -22,6 +24,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -44,7 +48,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -199,4 +206,11 @@ TEST_CASE("ConcatExpressionProcessor", "[language]")
     CHECK_CONCAT_EXPRESSION_RESULT(R"(let x:R^3x3, x = [[1,2,3],[4,5,6],[7,8,9]]; let s:string, s = "_foo"; s = x+s;)",
                                    "s", os.str());
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE(ConcatExpressionProcessor<std::string, std::string>{node}.type() ==
+            INodeProcessor::Type::concat_expression_processor);
+  }
 }
diff --git a/tests/test_DiscreteFunctionIntegrator.cpp b/tests/test_DiscreteFunctionIntegrator.cpp
index 1ab77c2e34478946bdffb81f7e522c7a1fd6d544..1baf0b95d8fed70d31550ae60a264d2a8c26860c 100644
--- a/tests/test_DiscreteFunctionIntegrator.cpp
+++ b/tests/test_DiscreteFunctionIntegrator.cpp
@@ -275,6 +275,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -509,6 +510,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -743,6 +745,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_DiscreteFunctionIntegratorByZone.cpp b/tests/test_DiscreteFunctionIntegratorByZone.cpp
index 608a523b40b8e169bfe8e27c833214f8281c593a..c360395788cf69d630f33b86d0d0fa08ea2a078b 100644
--- a/tests/test_DiscreteFunctionIntegratorByZone.cpp
+++ b/tests/test_DiscreteFunctionIntegratorByZone.cpp
@@ -101,7 +101,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_1d->connectivity()};
@@ -128,7 +128,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_1d->connectivity()};
@@ -182,7 +182,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<double(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_1d->connectivity()};
@@ -211,7 +211,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_1d->connectivity()};
@@ -240,7 +240,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_1d->connectivity()};
@@ -269,7 +269,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_1d->connectivity()};
@@ -298,7 +298,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_1d->connectivity()};
@@ -327,7 +327,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_1d->connectivity()};
@@ -356,7 +356,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_1d,
+        IntegrateCellValue<DataType(TinyVector<1>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_1d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_1d->connectivity()};
@@ -373,6 +373,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
     }
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("2D")
@@ -386,7 +387,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
     zone_list.push_back(std::make_shared<NamedZoneDescriptor>("LEFT"));
 
     auto mesh_cell_zone = getMeshCellZone(*mesh_2d, *zone_list[0]);
-    auto zone_cell_list = mesh_cell_zone.cellList();
+    auto zone_cell_list = copy(mesh_cell_zone.cellList());
 
     std::string_view data = R"(
 import math;
@@ -429,7 +430,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_2d->connectivity()};
@@ -456,7 +457,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_2d->connectivity()};
@@ -483,7 +484,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_2d->connectivity()};
@@ -510,7 +511,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<double(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_2d->connectivity()};
@@ -568,7 +569,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_2d->connectivity()};
@@ -597,7 +598,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_2d->connectivity()};
@@ -626,7 +627,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_2d->connectivity()};
@@ -655,7 +656,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_2d->connectivity()};
@@ -684,7 +685,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_2d,
+        IntegrateCellValue<DataType(TinyVector<2>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_2d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_2d->connectivity()};
@@ -701,6 +702,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
     }
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("3D")
@@ -757,7 +759,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_3d->connectivity()};
@@ -784,7 +786,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_3d->connectivity()};
@@ -811,7 +813,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_3d->connectivity()};
@@ -838,7 +840,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<double> array =
-        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<double(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                              zone_cell_list);
 
       CellValue<double> cell_value{mesh_3d->connectivity()};
@@ -896,7 +898,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_3d->connectivity()};
@@ -925,7 +927,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_3d->connectivity()};
@@ -954,7 +956,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_3d->connectivity()};
@@ -983,7 +985,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_3d->connectivity()};
@@ -1012,7 +1014,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
       FunctionSymbolId function_symbol_id(std::get<uint64_t>(i_symbol->attributes().value()), symbol_table);
 
       Array<DataType> array =
-        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, *mesh_3d,
+        IntegrateCellValue<DataType(TinyVector<3>)>::integrate(function_symbol_id, *quadrature_descriptor, mesh_3d_v,
                                                                zone_cell_list);
 
       CellValue<DataType> cell_value{mesh_3d->connectivity()};
@@ -1029,6 +1031,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
     }
+    ast->m_symbol_table->clearValues();
   }
 
   ASTNode::setStackDetails(stack_details);
diff --git a/tests/test_DiscreteFunctionInterpoler.cpp b/tests/test_DiscreteFunctionInterpoler.cpp
index bb27d85aa962fef7061cad81750a4e808dfe92a7..7d7454fa79be6caa0f6abbf6d54c93ab6449fe4d 100644
--- a/tests/test_DiscreteFunctionInterpoler.cpp
+++ b/tests/test_DiscreteFunctionInterpoler.cpp
@@ -329,6 +329,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -620,6 +621,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -911,6 +913,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_DiscreteFunctionInterpolerByZone.cpp b/tests/test_DiscreteFunctionInterpolerByZone.cpp
index 9a228fa7c26099be940ac45c1bbb361277cc9fd7..578dd0087afd5fbae340f89ced95156a2da24f29 100644
--- a/tests/test_DiscreteFunctionInterpolerByZone.cpp
+++ b/tests/test_DiscreteFunctionInterpolerByZone.cpp
@@ -377,6 +377,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
       REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
     }
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("2D")
@@ -712,6 +713,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
     }
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("3D")
@@ -1047,6 +1049,7 @@ let R3x3_non_linear_3d: R^3 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
       REQUIRE(same_cell_value(cell_value, discrete_function.get<DiscreteFunctionP0<const DataType>>()));
     }
+    ast->m_symbol_table->clearValues();
   }
 
   ASTNode::setStackDetails(stack_details);
diff --git a/tests/test_DiscreteFunctionVectorIntegrator.cpp b/tests/test_DiscreteFunctionVectorIntegrator.cpp
index c176efb6535ae8b50438c31358b63b8f88d647c2..ac6e426862dce3274bb3a65842d7728631536f1b 100644
--- a/tests/test_DiscreteFunctionVectorIntegrator.cpp
+++ b/tests/test_DiscreteFunctionVectorIntegrator.cpp
@@ -138,6 +138,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
         }
 
         REQUIRE(i == function_id_list.size());
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -224,6 +225,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
         }
 
         REQUIRE(i == function_id_list.size());
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -308,6 +310,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
         }
 
         REQUIRE(i == function_id_list.size());
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -386,6 +389,7 @@ Invalid value type: R^2)";
 
           REQUIRE_THROWS_WITH(integrator.integrate(), error_msg);
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp b/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp
index 180bc3b2af49cc49aa0f2e36a23af5c1e8e5c62c..ba29056d3462e76180dc1a2dc177bdb1c552dc6b 100644
--- a/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp
+++ b/tests/test_DiscreteFunctionVectorIntegratorByZone.cpp
@@ -181,6 +181,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
     }
 
     REQUIRE(i == function_id_list.size());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("2D")
@@ -306,6 +307,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
     }
 
     REQUIRE(i == function_id_list.size());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("3D")
@@ -431,6 +433,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
     }
 
     REQUIRE(i == function_id_list.size());
+    ast->m_symbol_table->clearValues();
   }
 
   ASTNode::setStackDetails(stack_details);
diff --git a/tests/test_DiscreteFunctionVectorInterpoler.cpp b/tests/test_DiscreteFunctionVectorInterpoler.cpp
index 466e88bb5e2af9f69caedd7a487aa80e0b960560..e7369e653c3fcea833efd9f747c8e4fa169798e3 100644
--- a/tests/test_DiscreteFunctionVectorInterpoler.cpp
+++ b/tests/test_DiscreteFunctionVectorInterpoler.cpp
@@ -150,6 +150,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
         }
 
         REQUIRE(i == function_id_list.size());
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -251,6 +252,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
         }
 
         REQUIRE(i == function_id_list.size());
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -352,6 +354,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
         }
 
         REQUIRE(i == function_id_list.size());
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -436,6 +439,7 @@ Invalid interpolation value type: R^2)";
           DiscreteFunctionVectorInterpoler interpoler{mesh_3d_v, std::make_shared<DiscreteFunctionDescriptorP0>(), {}};
           REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp b/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
index e5e99b8542a0aad471434bc805dc00d437f74978..7388e66e462c7e91ffbfa9ebfe6c3c2aa00dd73b 100644
--- a/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
+++ b/tests/test_DiscreteFunctionVectorInterpolerByZone.cpp
@@ -175,6 +175,7 @@ let R_scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
     }
 
     REQUIRE(i == function_id_list.size());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("2D")
@@ -297,6 +298,7 @@ let R_scalar_non_linear_2d: R^2 -> R, x -> 2 * exp(x[0] + x[1]) + 3;
     }
 
     REQUIRE(i == function_id_list.size());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("3D")
@@ -419,6 +421,7 @@ let R_scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0] + x[1]) + 3 * x[2];
     }
 
     REQUIRE(i == function_id_list.size());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("errors")
@@ -501,6 +504,7 @@ Invalid interpolation value type: R^2)";
           DiscreteFunctionVectorInterpoler interpoler{mesh_3d_v, std::make_shared<DiscreteFunctionDescriptorP0>(), {}};
           REQUIRE_THROWS_WITH(interpoler.interpolate(), error_msg);
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_DoWhileProcessor.cpp b/tests/test_DoWhileProcessor.cpp
index ca8138191bf37e963d56492adb682b3cc66fe161..fffd4a7f4fcf746cf5de504f1114564c66191945 100644
--- a/tests/test_DoWhileProcessor.cpp
+++ b/tests/test_DoWhileProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -9,6 +10,7 @@
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/node_processor/DoWhileProcessor.hpp>
 #include <language/utils/ASTPrinter.hpp>
 #include <utils/Demangle.hpp>
 
@@ -21,6 +23,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -43,7 +47,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_DO_WHILE_PROCESSOR_THROWS_WITH(data, error_message)     \
@@ -51,12 +58,17 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTExecutionStack::create();                                      \
+                                                                      \
     ASTModulesImporter{*ast};                                         \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
                                                                       \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
+    ASTExecutionStack::destroy();                                     \
+                                                                      \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -136,4 +148,10 @@ do {
       CHECK_DO_WHILE_PROCESSOR_THROWS_WITH(data, "invalid implicit conversion: Z -> B");
     }
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE(DoWhileProcessor{node}.type() == INodeProcessor::Type::do_while_processor);
+  }
 }
diff --git a/tests/test_Exceptions.cpp b/tests/test_Exceptions.cpp
index 739046f44aa8349364ce16f074dc05d0a0a886eb..99b03b55bac75cd6c2fb515d02aec6c3e22cc9ef 100644
--- a/tests/test_Exceptions.cpp
+++ b/tests/test_Exceptions.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <utils/Exceptions.hpp>
+#include <utils/SourceLocation.hpp>
 
 // clazy:excludeall=non-pod-global-static
 
@@ -14,8 +15,10 @@ TEST_CASE("Exceptions", "[utils]")
 
     UnexpectedError unexpected_error{"an unexpected error"};
     REQUIRE(std::string{unexpected_error.what()} == "unexpected error: an unexpected error");
+    REQUIRE(std::string{unexpected_error.sourceLocation().file_name()} == SourceLocation{}.filename());
 
     NotImplementedError not_implemented_error{"not implemented error"};
     REQUIRE(std::string{not_implemented_error.what()} == "not implemented yet: not implemented error");
+    REQUIRE(std::string{not_implemented_error.sourceLocation().file_name()} == SourceLocation{}.filename());
   }
 }
diff --git a/tests/test_ExecutionStatManager.cpp b/tests/test_ExecutionStatManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..434b1eaf1cf9460049cc923de3263b52d5a88509
--- /dev/null
+++ b/tests/test_ExecutionStatManager.cpp
@@ -0,0 +1,89 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/Stringify.hpp>
+#include <utils/pugs_config.hpp>
+
+#include <sstream>
+
+class ExecutionStatManagerTester
+{
+ public:
+  double
+  getPreviousCumulativeElapseTime() const
+  {
+    return ExecutionStatManager::getInstance().m_previous_cumulative_elapse_time;
+  }
+
+  double
+  getPreviousCumulativeTotalCpuTime() const
+  {
+    return ExecutionStatManager::getInstance().m_previous_cumulative_total_cpu_time;
+  }
+
+  void
+  setElapseTime(const Timer& timer) const
+  {
+    ExecutionStatManager::getInstance().m_elapse_time = timer;
+  }
+
+  Timer&
+  getElapseTime() const
+  {
+    return ExecutionStatManager::getInstance().m_elapse_time;
+  }
+
+  ExecutionStatManagerTester()  = default;
+  ~ExecutionStatManagerTester() = default;
+};
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ExecutionStatManager", "[utils]")
+{
+  REQUIRE_NOTHROW(ExecutionStatManager::create());
+  REQUIRE_THROWS_WITH(ExecutionStatManager::create(), "unexpected error: ExecutionStatManager already created");
+
+  ExecutionStatManagerTester esm_tester;
+
+  REQUIRE(ExecutionStatManager::getInstance().runNumber() == 1);
+  REQUIRE(ExecutionStatManager::getInstance().doPrint() == true);
+  REQUIRE(ExecutionStatManager::getInstance().exitCode() == 0);
+
+  REQUIRE(esm_tester.getPreviousCumulativeElapseTime() == 0);
+  REQUIRE(esm_tester.getPreviousCumulativeTotalCpuTime() == 0);
+
+  REQUIRE_NOTHROW(ExecutionStatManager::getInstance().setRunNumber(2));
+  REQUIRE(ExecutionStatManager::getInstance().runNumber() == 2);
+
+  REQUIRE_NOTHROW(ExecutionStatManager::getInstance().setPreviousCumulativeElapseTime(100));
+  REQUIRE_NOTHROW(ExecutionStatManager::getInstance().setPreviousCumulativeTotalCPUTime(200));
+
+  REQUIRE(esm_tester.getPreviousCumulativeElapseTime() == 100);
+  REQUIRE(esm_tester.getPreviousCumulativeTotalCpuTime() == 200);
+
+  using namespace std::chrono_literals;
+  Timer t{std::chrono::high_resolution_clock::now() - (2 * 21h + 267s)};
+  t.pause();
+  esm_tester.setElapseTime(t);
+
+  REQUIRE(ExecutionStatManager::getInstance().getElapseTime() == t.seconds());
+  REQUIRE(ExecutionStatManager::getInstance().getCumulativeElapseTime() == 100 + t.seconds());
+  REQUIRE(ExecutionStatManager::getInstance().getCumulativeTotalCPUTime() >= 200);
+
+  std::ostringstream os;
+  // One just call the function not test is performed. It is quite
+  // boring to check the result and not that useful.
+  ExecutionStatManager::getInstance().printInfo(os);
+
+  ExecutionStatManager::getInstance().setPrint(false);
+  REQUIRE(ExecutionStatManager::getInstance().doPrint() == false);
+
+  ExecutionStatManager::getInstance().setExitCode(1);
+  REQUIRE(ExecutionStatManager::getInstance().exitCode() == 1);
+
+  REQUIRE_NOTHROW(ExecutionStatManager::destroy());
+  // One allows multiple destruction to handle unexpected code exit
+  REQUIRE_NOTHROW(ExecutionStatManager::destroy());
+}
diff --git a/tests/test_Exit.cpp b/tests/test_Exit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c405b12e234670aa28d9e631d1daa6af2c0b2005
--- /dev/null
+++ b/tests/test_Exit.cpp
@@ -0,0 +1,19 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <language/utils/Exit.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("Exit", "[language/utils]")
+{
+  language::Exit exit0 = language::Exit{0};
+
+  REQUIRE(exit0.code() == 0);
+
+  language::Exit exit1{1};
+  REQUIRE(exit1.code() == 1);
+
+  language::Exit exit_move = std::move(exit1);
+  REQUIRE(exit_move.code() == 1);
+}
diff --git a/tests/test_ForProcessor.cpp b/tests/test_ForProcessor.cpp
index b55de095c9335178bd0dcc92543218a1bde9c14e..addc9c0b51ca6c7aa8cdba977ed5db13417b3f84 100644
--- a/tests/test_ForProcessor.cpp
+++ b/tests/test_ForProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -9,6 +10,7 @@
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/node_processor/ForProcessor.hpp>
 #include <language/utils/ASTPrinter.hpp>
 #include <utils/Demangle.hpp>
 
@@ -21,6 +23,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -43,12 +47,18 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_FOR_PROCESSOR_THROWS_WITH(data, error_message)          \
   {                                                                   \
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
+                                                                      \
+    ASTExecutionStack::create();                                      \
+                                                                      \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
     ASTModulesImporter{*ast};                                         \
@@ -56,7 +66,10 @@
                                                                       \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
+    ASTExecutionStack::destroy();                                     \
+                                                                      \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -110,4 +123,10 @@ for(let l:N, l=0; l; ++l) {
       CHECK_FOR_PROCESSOR_THROWS_WITH(data, "invalid implicit conversion: N -> B");
     }
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE(ForProcessor{node}.type() == INodeProcessor::Type::for_processor);
+  }
 }
diff --git a/tests/test_FunctionProcessor.cpp b/tests/test_FunctionProcessor.cpp
index 2a11caff89e418956cea81a8f4f64276c93c5756..aa8c75e9968b230617aa45afaaf976ccaacae628 100644
--- a/tests/test_FunctionProcessor.cpp
+++ b/tests/test_FunctionProcessor.cpp
@@ -2,12 +2,14 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/node_processor/FunctionProcessor.hpp>
 #include <utils/Demangle.hpp>
 #include <utils/Stringify.hpp>
 
@@ -20,6 +22,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -43,7 +47,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_FUNCTION_EVALUATION_THROWS(data, error_msg)      \
@@ -51,6 +58,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"}; \
     auto ast = ASTBuilder::build(input);                       \
                                                                \
+    ASTExecutionStack::create();                               \
+                                                               \
     ASTModulesImporter{*ast};                                  \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};    \
                                                                \
@@ -64,6 +73,9 @@
     ASTNodeExpressionBuilder{*ast};                            \
     ExecutionPolicy exec_policy;                               \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), error_msg); \
+                                                               \
+    ASTExecutionStack::destroy();                              \
+    ast->m_symbol_table->clearValues();                        \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -1943,4 +1955,12 @@ f(1, 2);
       }
     }
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE((FunctionProcessor{node, SymbolTable::Context{}}.type() == INodeProcessor::Type::function_processor));
+    REQUIRE((FunctionExpressionProcessor<double, double>{node}.type() ==
+             INodeProcessor::Type::function_expression_processor));
+  }
 }
diff --git a/tests/test_IfProcessor.cpp b/tests/test_IfProcessor.cpp
index 8c0ee437d76585d3a0381d136ed70de136ed0cba..0d71675b40372730070e560fe9443812249e8315 100644
--- a/tests/test_IfProcessor.cpp
+++ b/tests/test_IfProcessor.cpp
@@ -2,12 +2,14 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/node_processor/IfProcessor.hpp>
 #include <utils/Demangle.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -19,6 +21,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -41,7 +45,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_IF_PROCESSOR_THROWS_WITH(data, error_message)           \
@@ -49,12 +56,17 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTExecutionStack::create();                                      \
+                                                                      \
     ASTModulesImporter{*ast};                                         \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
                                                                       \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
+    ASTExecutionStack::destroy();                                     \
+                                                                      \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -146,4 +158,10 @@ if (1.2) {
       CHECK_IF_PROCESSOR_THROWS_WITH(data, "invalid implicit conversion: R -> B");
     }
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE(IfProcessor{node}.type() == INodeProcessor::Type::if_processor);
+  }
 }
diff --git a/tests/test_IncDecExpressionProcessor.cpp b/tests/test_IncDecExpressionProcessor.cpp
index 960845cf229abb9f436e6016e6202d5b0d8dcb99..5e951aaacb9d45f35e1a354bddde77b321fca1c9 100644
--- a/tests/test_IncDecExpressionProcessor.cpp
+++ b/tests/test_IncDecExpressionProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -19,6 +20,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -41,7 +44,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_INCDEC_EXPRESSION_THROWS_WITH(data, error_message)      \
@@ -49,12 +55,17 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTExecutionStack::create();                                      \
+                                                                      \
     ASTModulesImporter{*ast};                                         \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};           \
                                                                       \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+                                                                      \
+    ASTExecutionStack::destroy();                                     \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 #define CHECK_INCDEC_THROWS_WITH(data, error_message)              \
@@ -62,6 +73,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};     \
     auto ast = ASTBuilder::build(input);                           \
                                                                    \
+    ASTExecutionStack::create();                                   \
+                                                                   \
     ASTSymbolTableBuilder{*ast};                                   \
     ASTNodeDataTypeBuilder{*ast};                                  \
                                                                    \
@@ -72,6 +85,9 @@
     ExecutionPolicy exec_policy;                                   \
                                                                    \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), error_message); \
+                                                                   \
+    ASTExecutionStack::destroy();                                  \
+    ast->m_symbol_table->clearValues();                            \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_IntegrateCellArray.cpp b/tests/test_IntegrateCellArray.cpp
index 738fad05fa74ff45820a5f4071dff9a91abbc42b..27a2e330065791a7d378590ed398d7ef1b5d0dbb 100644
--- a/tests/test_IntegrateCellArray.cpp
+++ b/tests/test_IntegrateCellArray.cpp
@@ -122,11 +122,16 @@ let g: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               PUGS_LAMBDA(const CellId cell_id) { cell_integral_array[cell_id][1] = cell_g_integral[cell_id]; });
           }
 
+          ASTNode::setStackDetails(false);
+
           CellArray<double> integrate_array =
             IntegrateCellArray<double(TinyVector<Dimension>)>::integrate(function_symbol_id_list, quadrature_descriptor,
                                                                          *mesh_1d);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral_array, integrate_array));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -209,11 +214,16 @@ let g: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
               PUGS_LAMBDA(const CellId cell_id) { cell_integral_array[cell_id][1] = cell_g_integral[cell_id]; });
           }
 
+          ASTNode::setStackDetails(false);
+
           CellArray<double> integrate_array =
             IntegrateCellArray<double(TinyVector<Dimension>)>::integrate(function_symbol_id_list, quadrature_descriptor,
                                                                          *mesh_2d);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral_array, integrate_array));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -307,11 +317,16 @@ let g: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
               PUGS_LAMBDA(const CellId cell_id) { cell_integral_array[cell_id][1] = cell_g_integral[cell_id]; });
           }
 
+          ASTNode::setStackDetails(false);
+
           CellArray<double> integrate_array =
             IntegrateCellArray<double(TinyVector<Dimension>)>::integrate(function_symbol_id_list, quadrature_descriptor,
                                                                          *mesh_3d);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral_array, integrate_array));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -418,11 +433,16 @@ let g: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               PUGS_LAMBDA(const CellId cell_id) { cell_integral_array[cell_id][1] = cell_g_integral[cell_id]; });
           }
 
+          ASTNode::setStackDetails(false);
+
           Table<const double> integrate_value =
             IntegrateCellArray<double(TinyVector<Dimension>)>::integrate(function_symbol_id_list, quadrature_descriptor,
                                                                          *mesh_1d, cell_list);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral_array, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -514,11 +534,16 @@ let g: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
               PUGS_LAMBDA(const CellId cell_id) { cell_integral_array[cell_id][1] = cell_g_integral[cell_id]; });
           }
 
+          ASTNode::setStackDetails(false);
+
           Table<const double> integrate_value =
             IntegrateCellArray<double(TinyVector<Dimension>)>::integrate(function_symbol_id_list, quadrature_descriptor,
                                                                          *mesh_2d, cell_list);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral_array, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -621,11 +646,16 @@ let g: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
               PUGS_LAMBDA(const CellId cell_id) { cell_integral_array[cell_id][1] = cell_g_integral[cell_id]; });
           }
 
+          ASTNode::setStackDetails(false);
+
           Table<const double> integrate_value =
             IntegrateCellArray<double(TinyVector<Dimension>)>::integrate(function_symbol_id_list, quadrature_descriptor,
                                                                          *mesh_3d, cell_list);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral_array, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
diff --git a/tests/test_IntegrateCellValue.cpp b/tests/test_IntegrateCellValue.cpp
index ab2c86f82c7b2b10db0a54555d70a06558460468..e2563d473ec91925a65ab8bed4ae139ec93d004a 100644
--- a/tests/test_IntegrateCellValue.cpp
+++ b/tests/test_IntegrateCellValue.cpp
@@ -91,11 +91,16 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
           };
           CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
+          ASTNode::setStackDetails(false);
+
           CellValue<R2x2> integrate_value =
             IntegrateCellValue<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -150,11 +155,16 @@ let R3_2d: R^2 -> R^3, x -> [2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3];
           };
           CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
+          ASTNode::setStackDetails(false);
+
           CellValue<R3> integrate_value =
             IntegrateCellValue<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                      *mesh_2d);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -217,11 +227,16 @@ let scalar_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
           auto f = [](const TinyVector<Dimension>& x) -> double { return 2 * exp(x[0]) * sin(x[1]) * x[2] + 3; };
           CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
+          ASTNode::setStackDetails(false);
+
           CellValue<double> integrate_value =
             IntegrateCellValue<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -296,11 +311,17 @@ let scalar_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
           auto f = [](const TinyVector<Dimension>& x) -> double { return 2 * std::exp(x[0]) + 3; };
 
           Array<const double> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+          ASTNode::setStackDetails(false);
+
           Array<const double> integrate_value =
             IntegrateCellValue<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, cell_list);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -365,11 +386,17 @@ let R3_2d: R^2 -> R^3, x -> [2*exp(x[0])*sin(x[1])+3, x[0]-2*x[1], 3];
           };
 
           Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+          ASTNode::setStackDetails(false);
+
           Array<const R3> integrate_value =
             IntegrateCellValue<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                      *mesh_2d, cell_list);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -446,11 +473,16 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
           Array<const R2x2> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
 
+          ASTNode::setStackDetails(false);
+
           Array<R2x2> integrate_value =
             IntegrateCellValue<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, cell_list);
 
+          ASTNode::setStackDetails(true);
+
           REQUIRE(same_item_integral(cell_integral, integrate_value));
+          ast->m_symbol_table->clearValues();
         }
       }
     }
diff --git a/tests/test_IntegrateOnCells.cpp b/tests/test_IntegrateOnCells.cpp
index 93d1c6806dda456aa17b7f138c6d1f23d8e33315..7a285a612c43464147d0159be8b00e1ccea336ff 100644
--- a/tests/test_IntegrateOnCells.cpp
+++ b/tests/test_IntegrateOnCells.cpp
@@ -95,9 +95,14 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<double> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -115,9 +120,14 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<R3> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -137,11 +147,17 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -197,9 +213,14 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<double> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -219,9 +240,14 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<R3> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -241,11 +267,17 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -311,9 +343,14 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<double> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -333,9 +370,14 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<R3> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -356,11 +398,17 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -439,10 +487,15 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -458,10 +511,15 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               auto f = [](const TinyVector<Dimension>& x) -> R3 { return R3{2 * exp(x[0]) + 3, x[0] - 2, 3}; };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -480,12 +538,18 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -551,10 +615,15 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -572,10 +641,15 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -594,12 +668,18 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -675,10 +755,15 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -696,10 +781,15 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -719,12 +809,18 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -798,9 +894,14 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<double> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -818,9 +919,14 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<R3> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -840,11 +946,17 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -900,9 +1012,14 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<double> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -922,9 +1039,14 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<R3> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -944,11 +1066,17 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1014,9 +1142,14 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<double> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1036,9 +1169,14 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<R3> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1059,11 +1197,17 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1142,10 +1286,15 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1161,10 +1310,15 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               auto f = [](const TinyVector<Dimension>& x) -> R3 { return R3{2 * exp(x[0]) + 3, x[0] - 2, 3}; };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1183,12 +1337,18 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1254,10 +1414,15 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1275,10 +1440,15 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1297,12 +1467,18 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1378,10 +1554,15 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1399,10 +1580,15 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1422,12 +1608,18 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1501,9 +1693,14 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<double> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1521,9 +1718,14 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<R3> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1543,11 +1745,17 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_1d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_1d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1603,9 +1811,14 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<double> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1625,9 +1838,14 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<R3> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1647,11 +1865,17 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_2d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_2d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_2d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1717,9 +1941,14 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<double> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<double(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1739,9 +1968,14 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<R3> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R3(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1762,11 +1996,17 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               CellIntegrator::integrateTo(f, quadrature_descriptor, *mesh_3d, cell_integral);
 
               Array<R2x2> integrate_value(mesh_3d->numberOfCells());
+
+              ASTNode::setStackDetails(false);
+
               IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrateTo(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d, integrate_value);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1845,10 +2085,15 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1864,9 +2109,13 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
               auto f = [](const TinyVector<Dimension>& x) -> R3 { return R3{2 * exp(x[0]) + 3, x[0] - 2, 3}; };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_1d, cell_list);
+              ASTNode::setStackDetails(true);
 
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
@@ -1886,12 +2135,18 @@ let R2x2_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(x[0] - 2 *
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_1d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_1d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1957,10 +2212,15 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -1978,10 +2238,15 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
               };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -2000,12 +2265,18 @@ let R2x2_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2*x[1])], [3
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_2d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_2d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -2081,10 +2352,15 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
               Array<const double> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const double> integrate_value =
                 IntegrateOnCells<double(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                            *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -2102,10 +2378,15 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
               };
 
               Array<const R3> cell_integral = CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R3> integrate_value =
                 IntegrateOnCells<R3(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                        *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
 
@@ -2125,12 +2406,18 @@ let R2x2_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos(x[2]), sin
 
               Array<const R2x2> cell_integral =
                 CellIntegrator::integrate(f, quadrature_descriptor, *mesh_3d, cell_list);
+
+              ASTNode::setStackDetails(false);
+
               Array<const R2x2> integrate_value =
                 IntegrateOnCells<R2x2(TinyVector<Dimension>)>::integrate(function_symbol_id, quadrature_descriptor,
                                                                          *mesh_3d, cell_list);
 
+              ASTNode::setStackDetails(true);
+
               REQUIRE(same_item_integral(cell_integral, integrate_value));
             }
+            ast->m_symbol_table->clearValues();
           }
         }
       }
diff --git a/tests/test_InterpolateItemArray.cpp b/tests/test_InterpolateItemArray.cpp
index cab00ae0a3540baba764495e13ce89be9511923c..4e2dfbb553c56c98c646509bab3c6910e90db6c0 100644
--- a/tests/test_InterpolateItemArray.cpp
+++ b/tests/test_InterpolateItemArray.cpp
@@ -117,6 +117,7 @@ let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
               InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -177,6 +178,7 @@ let f_1d: R^1 -> (R), x -> (2*x[0] + 2, 2 * exp(x[0]) + 3);
               InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -239,6 +241,7 @@ let f_1d: R^1 -> (R^1), x -> (2*x[0] + 2, [2 * exp(x[0]) + 3]);
               InterpolateItemArray<R1(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -316,6 +319,7 @@ let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
               InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -376,6 +380,7 @@ let f_2d: R^2 -> (R), x -> (2*x[0] + 3*x[1] + 2, 2*exp(x[0])*sin(x[1])+3);
               InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -453,6 +458,7 @@ let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
               InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -513,6 +519,7 @@ let f_3d: R^3 -> (R), x -> (2 * x[0] + 3 * x[1] + 2 * x[2] - 1, 2 * exp(x[0]) *
               InterpolateItemArray<double(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -613,6 +620,7 @@ let scalar_non_linear_1d: R^1 -> R, x -> 2 * exp(x[0]) + 3;
                                                                                cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -682,6 +690,7 @@ let f_1d: R^1 -> (R), x -> (2*x[0] + 2, 2 * exp(x[0]) + 3);
                                                                                cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -765,6 +774,7 @@ let scalar_non_linear_2d: R^2 -> R, x -> 2*exp(x[0])*sin(x[1])+3;
                                                                                cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -831,6 +841,7 @@ let f_2d: R^2 -> (R), x -> (2*x[0] + 3*x[1] + 2, 2*exp(x[0])*sin(x[1])+3);
                                                                                cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -902,6 +913,7 @@ let f_2d: R^2 -> (R^2x2), x -> ([[x[0],0],[2-x[1], x[0]*x[1]]], [[2*x[0], x[1]],
               InterpolateItemArray<R2x2(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -985,6 +997,7 @@ let scalar_non_linear_3d: R^3 -> R, x -> 2 * exp(x[0]) * sin(x[1]) * x[2] + 3;
                                                                                cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1051,6 +1064,7 @@ let f_3d: R^3 -> (R), x -> (2 * x[0] + 3 * x[1] + 2 * x[2] - 1, 2 * exp(x[0]) *
                                                                                cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
@@ -1119,6 +1133,7 @@ let f_3d: R^3 -> (R^3), x -> (2*x, [2*x[0]-x[1], 3*x[2]-x[0], x[1]+x[2]], 0);
               InterpolateItemArray<R3(TinyVector<Dimension>)>::interpolate(function_symbol_id_list, xj, cell_id_list);
 
             REQUIRE(same_cell_array(cell_array, interpolate_array));
+            ast->m_symbol_table->clearValues();
           }
         }
       }
diff --git a/tests/test_InterpolateItemValue.cpp b/tests/test_InterpolateItemValue.cpp
index 370b8b4015122ce3227c3ee99da177b1b7e20978..63f59db780c144d95cf526b6e9ec0dfe83fb6f43 100644
--- a/tests/test_InterpolateItemValue.cpp
+++ b/tests/test_InterpolateItemValue.cpp
@@ -210,6 +210,7 @@ let R2x2_non_linear_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
             REQUIRE(same_item_value(cell_value, interpolate_value));
           }
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -376,6 +377,7 @@ let R2x2_non_linear_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2
 
             REQUIRE(same_item_value(cell_value, interpolate_value));
           }
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -543,6 +545,7 @@ let R2x2_non_linear_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos
 
             REQUIRE(same_item_value(cell_value, interpolate_value));
           }
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -739,6 +742,7 @@ let R2x2_non_linear_1d: R^1 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
             REQUIRE(same_item_value(cell_value, interpolate_value));
           }
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -914,6 +918,7 @@ let R2x2_non_linear_2d: R^2 -> R^2x2, x -> [[2*exp(x[0])*sin(x[1])+3, sin(x[0]-2
 
             REQUIRE(same_item_value(cell_value, interpolate_value));
           }
+          ast->m_symbol_table->clearValues();
         }
       }
     }
@@ -1090,6 +1095,7 @@ let R2x2_non_linear_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3 * cos
 
             REQUIRE(same_item_value(cell_value, interpolate_value));
           }
+          ast->m_symbol_table->clearValues();
         }
       }
     }
diff --git a/tests/test_ItemArrayVariantFunctionInterpoler.cpp b/tests/test_ItemArrayVariantFunctionInterpoler.cpp
index 95f9d9b5c45c0ff66d0c908e5831877f99ef2e29..18b6e87671fa929b53cae05f5d87eeb3c9c9a5de 100644
--- a/tests/test_ItemArrayVariantFunctionInterpoler.cpp
+++ b/tests/test_ItemArrayVariantFunctionInterpoler.cpp
@@ -417,6 +417,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           REQUIRE_THROWS_WITH(interpoler.interpolate(), "error: functions must have the same type");
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -551,6 +552,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_item_array(face_array, item_array_variant->get<FaceArray<DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -675,6 +677,7 @@ let R2x2_non_linear_3d: R^3 -> R^2x2,
 
           REQUIRE(same_item_array(edge_array, item_array_variant->get<EdgeArray<DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_ItemValueVariantFunctionInterpoler.cpp b/tests/test_ItemValueVariantFunctionInterpoler.cpp
index 4bc251aba0b6b75d7c65d87474e72010f0cecf1b..d93f046fe877f3c47286d02a8047b5c4f666ae91 100644
--- a/tests/test_ItemValueVariantFunctionInterpoler.cpp
+++ b/tests/test_ItemValueVariantFunctionInterpoler.cpp
@@ -317,6 +317,7 @@ let R3x3_non_linear_1d: R^1 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[0]) + 3, sin(
 
           REQUIRE(same_item_value(cell_value, item_value_variant->get<CellValue<DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -437,6 +438,7 @@ let R3x3_non_linear_2d: R^2 -> R^3x3, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_item_value(face_value, item_value_variant->get<FaceValue<DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
@@ -547,6 +549,7 @@ let R2x2_non_linear_3d: R^3 -> R^2x2, x -> [[2 * exp(x[0]) * sin(x[1]) + 3, sin(
 
           REQUIRE(same_item_value(edge_value, item_value_variant->get<EdgeValue<DataType>>()));
         }
+        ast->m_symbol_table->clearValues();
       }
     }
   }
diff --git a/tests/test_ListAffectationProcessor.cpp b/tests/test_ListAffectationProcessor.cpp
index 73733b9833200f492c5e022ff1b727e57d104640..0c89251e3b2142e59a9d23c7c40052f7a9e30238 100644
--- a/tests/test_ListAffectationProcessor.cpp
+++ b/tests/test_ListAffectationProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -19,6 +20,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -41,7 +44,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_AFFECTATION_THROW_WITH(data, error_message)          \
@@ -49,6 +55,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};     \
     auto ast = ASTBuilder::build(input);                           \
                                                                    \
+    ASTExecutionStack::create();                                   \
+                                                                   \
     ASTModulesImporter{*ast};                                      \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};        \
                                                                    \
@@ -61,6 +69,9 @@
     ASTNodeExpressionBuilder{*ast};                                \
     ExecutionPolicy exec_policy;                                   \
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), error_message); \
+                                                                   \
+    ASTExecutionStack::destroy();                                  \
+    ast->m_symbol_table->clearValues();                            \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_Messenger.cpp b/tests/test_Messenger.cpp
index 4565614fbcd4e4dc387527b77ddb1be23b092e23..3789f2e4ffaba718b4a936d61a2e5bb9b867e77f 100644
--- a/tests/test_Messenger.cpp
+++ b/tests/test_Messenger.cpp
@@ -123,6 +123,8 @@ TEST_CASE("Messenger", "[mpi]")
     int size = 1;
     IF_MPI(MPI_Comm_size(MPI_COMM_WORLD, &size));
     REQUIRE(size == static_cast<int>(parallel::size()));
+
+    REQUIRE(parallel::size() <= parallel::Messenger::getInstance().globalSize());
   }
 
   SECTION("reduction")
diff --git a/tests/test_NameProcessor.cpp b/tests/test_NameProcessor.cpp
index 1433eff93265614a36dfd0dcb16d4d9c7db13257..941eb207529fb607b42d6f260156576afa47f0a0 100644
--- a/tests/test_NameProcessor.cpp
+++ b/tests/test_NameProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -30,6 +31,8 @@ n = 2;
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
     auto ast = ASTBuilder::build(input);
 
+    ASTExecutionStack::create();
+
     ASTModulesImporter{*ast};
     ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
@@ -49,6 +52,8 @@ n = 2;
     auto symbol_n = symbol_table->find("n", use_position).first->attributes();
     auto value_n  = std::get<long unsigned int>(symbol_n.value());
 
+    ASTExecutionStack::destroy();
+
     REQUIRE(value_n == 2);
 
     auto symbol_m = symbol_table->find("m", use_position).first->attributes();
@@ -60,6 +65,7 @@ n = 2;
     REQUIRE(ast->children[1]->children[0]->m_node_processor->typeIdName() == demangle<NameProcessor>());
     REQUIRE(ast->children[1]->children[1]->m_node_processor->typeIdName() == demangle<NameProcessor>());
     REQUIRE(ast->children[2]->children[0]->m_node_processor->typeIdName() == demangle<NameProcessor>());
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("error")
@@ -75,6 +81,8 @@ let m:N, m = n;
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
     auto ast = ASTBuilder::build(input);
 
+    ASTExecutionStack::create();
+
     ASTModulesImporter{*ast};
     ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
@@ -87,5 +95,8 @@ let m:N, m = n;
     ASTNodeExpressionBuilder{*ast};
     ExecutionPolicy exec_policy;
     REQUIRE_THROWS_WITH(ast->execute(exec_policy), "trying to use uninitialized symbol 'n'");
+
+    ASTExecutionStack::destroy();
+    ast->m_symbol_table->clearValues();
   }
 }
diff --git a/tests/test_OFStream.cpp b/tests/test_OFStream.cpp
index 2ea0615c87aac0d519b62d47b33392a022d9a10d..ecbab1edcd4e8e3e8d566803290e8aa25e61fd06 100644
--- a/tests/test_OFStream.cpp
+++ b/tests/test_OFStream.cpp
@@ -42,10 +42,40 @@ TEST_CASE("OFStream", "[language]")
 
       std::string content = file_content;
       REQUIRE(content == "foo3 bar\n");
+    }
+
+    // Ensures that the file is closed after this line
+    std::make_shared<OFStream>(filename, true) << "new content\n";
 
-      std::filesystem::remove(filename);
+    if (parallel::rank() == 0) {
+      REQUIRE(std::filesystem::is_regular_file(filename));
+
+      std::ifstream is(filename);
+
+      char file_content[21];
+      int count = 0;
+      for (size_t i = 0; i < 21; ++i) {
+        char c = is.get();
+
+        file_content[i] = c;
+        if (c == '\n') {
+          count++;
+          if (count == 2) {
+            file_content[i + 1] = '\0';
+            REQUIRE(i == 20);
+
+            c = is.get();
+            REQUIRE(is.eof());
+            break;
+          }
+        }
+      }
+
+      std::string content = file_content;
+      REQUIRE(content == "foo3 bar\nnew content\n");
     }
 
+    std::filesystem::remove(filename);
     REQUIRE(not std::filesystem::exists(filename));
   }
 
diff --git a/tests/test_OStream.cpp b/tests/test_OStream.cpp
index 417db374ee366531623af397e2d62e78d1c83475..bc532e0d4963b0f33719ef942a2ed4aace8cc13c 100644
--- a/tests/test_OStream.cpp
+++ b/tests/test_OStream.cpp
@@ -11,7 +11,7 @@ TEST_CASE("OStream", "[language]")
 {
   SECTION("null ostream")
   {
-    std::shared_ptr os = std::make_shared<OStream>();
+    std::shared_ptr os = std::make_shared<OStream>(OStream::Type::std_ostream);
 
     REQUIRE_NOTHROW(os << "foo" << 3 << " bar");
   }
@@ -20,7 +20,7 @@ TEST_CASE("OStream", "[language]")
   {
     std::stringstream sstr;
 
-    std::shared_ptr os = std::make_shared<OStream>(sstr);
+    std::shared_ptr os = std::make_shared<OStream>(sstr, OStream::Type::std_ofstream);
     os << "foo" << 3 << " bar";
 
     REQUIRE(sstr.str() == "foo3 bar");
diff --git a/tests/test_ParallelChecker_read.cpp b/tests/test_ParallelChecker_read.cpp
index 0bb1d5566e3754aa6969ce956fba4d1398059fcd..94a43dcdae0244c1c7eb0fdeed46882210db7ddf 100644
--- a/tests/test_ParallelChecker_read.cpp
+++ b/tests/test_ParallelChecker_read.cpp
@@ -9,14 +9,14 @@
 #include <mesh/SubItemArrayPerItemUtils.hpp>
 #include <mesh/SubItemValuePerItemUtils.hpp>
 
+#include <ParallelCheckerTester.hpp>
+
 #include <filesystem>
 
 // clazy:excludeall=non-pod-global-static
 
 #ifdef PUGS_HAS_HDF5
 
-#include <ParallelCheckerTester.hpp>
-
 template <typename T>
 struct test_TinyVectorDataType;
 
@@ -56,6 +56,26 @@ TEST_CASE("ParallelChecker_read", "[dev]")
   REQUIRE_NOTHROW(ParallelChecker::create());
   REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::read));
 
+  {
+    ParallelCheckerTester pc_tester;
+    pc_tester.setTag(12);
+    REQUIRE(pc_tester.getTag() == 12);
+
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setFilename("custom.h5"),
+                        "unexpected error: Cannot modify parallel checker file if it was already used");
+    REQUIRE(ParallelChecker::instance().filename() == "parallel_checker.h5");
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setMode(ParallelChecker::Mode::read),
+                        "unexpected error: Cannot modify parallel checker mode if it was already used");
+
+    pc_tester.setTag(0);
+    REQUIRE(pc_tester.getTag() == 0);
+
+    const bool is_resuming = ResumingManager::getInstance().isResuming();
+    ResumingManager::getInstance().setIsResuming(false);
+    REQUIRE_THROWS_WITH(pc_tester.setTagWithCheck(0),
+                        "unexpected error: Cannot modify parallel checker tag if not resuming");
+    ResumingManager::getInstance().setIsResuming(is_resuming);
+  }
   std::string tmp_dirname;
 
   {
@@ -1186,8 +1206,8 @@ TEST_CASE("ParallelChecker_read", "[dev]")
           values[node_id][i_cell] = std::sin(node_number[node_id] + 2 * i_cell);
         }
       }
-
       REQUIRE_NOTHROW(parallel_check(values, "sin", source_location));
+
       pc_tester.setTag(tag);
       UNSCOPED_INFO("can have different name in ref");
       REQUIRE_NOTHROW(parallel_check(values, "not_sin", source_location));
@@ -2199,6 +2219,35 @@ TEST_CASE("ParallelChecker_read", "[dev]")
   REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::read));
   REQUIRE_NOTHROW(not ParallelChecker::instance().isWriting());
 
+  REQUIRE(ParallelChecker::instance().filename() == "parallel_checker.h5");
+  REQUIRE_NOTHROW(ParallelChecker::instance().setFilename("custom.h5"));
+  REQUIRE(ParallelChecker::instance().filename() == "custom.h5");
+  REQUIRE_NOTHROW(ParallelChecker::instance().setFilename("parallel_checker.h5"));
+  REQUIRE(ParallelChecker::instance().filename() == "parallel_checker.h5");
+
+  {
+    ParallelCheckerTester pc_tester;
+    pc_tester.setTag(12);
+    REQUIRE(pc_tester.getTag() == 12);
+
+    REQUIRE(not ResumingManager::getInstance().isResuming());
+    ResumingManager::getInstance().setIsResuming(true);
+    pc_tester.setTagWithCheck(3);
+    REQUIRE(ParallelChecker::instance().tag() == 3);
+
+    ResumingManager::getInstance().setIsResuming(false);
+    REQUIRE_THROWS_WITH(pc_tester.setTagWithCheck(0),
+                        "unexpected error: Cannot modify parallel checker tag if not resuming");
+
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setFilename("custom.h5"),
+                        "unexpected error: Cannot modify parallel checker file if it was already used");
+    REQUIRE(ParallelChecker::instance().filename() == "parallel_checker.h5");
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setMode(ParallelChecker::Mode::read),
+                        "unexpected error: Cannot modify parallel checker mode if it was already used");
+
+    pc_tester.setTag(0);
+    REQUIRE(pc_tester.getTag() == 0);
+  }
   auto mesh = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
 
   const Connectivity<1>& connectivity = mesh->connectivity();
@@ -2227,6 +2276,12 @@ TEST_CASE("ParallelChecker_read", "[dev]")
   REQUIRE_THROWS_WITH(parallel_check(SubItemArrayPerItemVariant{napc}, "test"),
                       "error: parallel checker cannot be used without HDF5 support");
 
+  DiscreteFunctionP0<double> df_R{mesh};
+  REQUIRE_THROWS_WITH(parallel_check(df_R, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  DiscreteFunctionP0Vector<double> df_Rn{mesh, 2};
+  REQUIRE_THROWS_WITH(parallel_check(df_Rn, "test"), "error: parallel checker cannot be used without HDF5 support");
+
   REQUIRE_NOTHROW(ParallelChecker::destroy());
 }
 
diff --git a/tests/test_PugsFunctionAdapter.cpp b/tests/test_PugsFunctionAdapter.cpp
index 68d02133b26e07382dac7f6588f4554093bd2151..a89017e6b62ab2dff33c01c44bf36227c520d935 100644
--- a/tests/test_PugsFunctionAdapter.cpp
+++ b/tests/test_PugsFunctionAdapter.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
@@ -90,6 +91,8 @@ let R33toR33zero: R^3x3 -> R^3x3, x -> 0;
 
     auto ast = ASTBuilder::build(input);
 
+    ASTExecutionStack::create();
+
     ASTModulesImporter{*ast};
     ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
@@ -361,6 +364,9 @@ let R33toR33zero: R^3x3 -> R^3x3, x -> 0;
 
       REQUIRE(result == TinyMatrix<3>{0, 0, 0, 0, 0, 0, 0, 0, 0});
     }
+
+    ASTExecutionStack::destroy();
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("Errors calls")
@@ -378,6 +384,8 @@ let R33toR22: R^3x3 -> R^2x2, x -> [[x[0,0], x[0,1]+x[0,2]], [x[2,0]*x[1,1], x[2
 
     auto ast = ASTBuilder::build(input);
 
+    ASTExecutionStack::create();
+
     ASTModulesImporter{*ast};
     ASTNodeTypeCleaner<language::import_instruction>{*ast};
 
@@ -492,5 +500,8 @@ let R33toR22: R^3x3 -> R^2x2, x -> [[x[0,0], x[0,1]+x[0,2]], [x[2,0]*x[1,1], x[2
                           "note: expecting R -> R\n"
                           "note: provided function R33toR22: R^3x3 -> R^2x2");
     }
+
+    ASTExecutionStack::destroy();
+    ast->m_symbol_table->clearValues();
   }
 }
diff --git a/tests/test_PugsUtils.cpp b/tests/test_PugsUtils.cpp
index 9dfd686da65c5027f43bdfcf38ba3b54f90607ba..fb86486527e3f1d91ce5c2f1e7d2653a1dea3bde 100644
--- a/tests/test_PugsUtils.cpp
+++ b/tests/test_PugsUtils.cpp
@@ -52,6 +52,7 @@ TEST_CASE("PugsUtils", "[utils]")
       os << "PETSc:    " << rang::style::bold << BuildInfo::petscLibrary() << rang::style::reset << '\n';
       os << "SLEPc:    " << rang::style::bold << BuildInfo::slepcLibrary() << rang::style::reset << '\n';
       os << "HDF5:     " << rang::style::bold << BuildInfo::hdf5Library() << rang::style::reset << '\n';
+      os << "SLURM:    " << rang::style::bold << BuildInfo::slurmLibrary() << rang::style::reset << '\n';
       os << "-------------------------------------------------------";
 
       return os.str();
diff --git a/tests/test_ReproducibleSumManager.cpp b/tests/test_ReproducibleSumManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a406a7cd020a0980b9e928e241f487348058d852
--- /dev/null
+++ b/tests/test_ReproducibleSumManager.cpp
@@ -0,0 +1,22 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/ReproducibleSumManager.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ReproducibleSumManager", "[utils]")
+{
+  SECTION("control settings")
+  {
+    const bool init_value = ReproducibleSumManager::reproducibleSums();
+
+    ReproducibleSumManager::setReproducibleSums(not init_value);
+
+    REQUIRE(init_value == not ReproducibleSumManager::reproducibleSums());
+
+    ReproducibleSumManager::setReproducibleSums(init_value);
+
+    REQUIRE(init_value == ReproducibleSumManager::reproducibleSums());
+  }
+}
diff --git a/tests/test_SourceLocation.cpp b/tests/test_SourceLocation.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f27a14b95381a67db840664796b7bd1067a6939d
--- /dev/null
+++ b/tests/test_SourceLocation.cpp
@@ -0,0 +1,30 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/SourceLocation.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SourceLocation", "[utils]")
+{
+  SECTION("provided")
+  {
+    SourceLocation source_location("filename", 3, 2, "function_name");
+
+    REQUIRE(source_location.filename() == "filename");
+    REQUIRE(source_location.line() == 3);
+    REQUIRE(source_location.column() == 2);
+    REQUIRE(source_location.function() == "function_name");
+  }
+
+  SECTION("from std::source_location")
+  {
+    auto std_source_location = std::experimental::source_location::current();
+    SourceLocation source_location(std_source_location);
+
+    REQUIRE(source_location.filename() == std_source_location.file_name());
+    REQUIRE(source_location.line() == std_source_location.line());
+    REQUIRE(source_location.column() == std_source_location.column());
+    REQUIRE(source_location.function() == std_source_location.function_name());
+  }
+}
diff --git a/tests/test_Stop.cpp b/tests/test_Stop.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4e5d62049c6353119358ffe3b073c127a62b5dbc
--- /dev/null
+++ b/tests/test_Stop.cpp
@@ -0,0 +1,36 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/Stop.hpp>
+
+#include <chrono>
+#include <filesystem>
+#include <fstream>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("Stop", "[utils]")
+{
+  ExecutionStatManager::create();
+  auto stop_file = std::filesystem::current_path() / "stop";
+  std::filesystem::remove(stop_file);
+
+  REQUIRE(not std::filesystem::exists(stop_file));
+  REQUIRE(not stop());
+
+  std::ofstream{stop_file.c_str()}.put('a');
+  REQUIRE(std::filesystem::exists(stop_file));
+
+  std::filesystem::file_time_type ftime = std::filesystem::last_write_time(stop_file);
+
+  using namespace std::chrono_literals;
+  std::filesystem::last_write_time(stop_file, ftime - 1h);
+
+  REQUIRE(not stop());
+
+  std::filesystem::last_write_time(stop_file, ftime);
+  REQUIRE(stop());
+
+  ExecutionStatManager::destroy();
+}
diff --git a/tests/test_Table.cpp b/tests/test_Table.cpp
index 5ce90fe9ce460041d26ebcfbc88ff891da0ffdde..844c7ab34854de4b890e498226be96ed754b6c8f 100644
--- a/tests/test_Table.cpp
+++ b/tests/test_Table.cpp
@@ -310,6 +310,12 @@ TEST_CASE("Table", "[utils]")
         }
       }
     }
+
+    std::ostringstream os;
+    os << sub_table_view;
+    REQUIRE(os.str() == R"(0| 0:5 1:8 2:11
+1| 0:2 1:-1 2:-4
+)");
   }
 
   SECTION("UnsafeRowView")
diff --git a/tests/test_Timer.cpp b/tests/test_Timer.cpp
index c615f9f3ee72e700c6cbf217800ea331b735f569..f0083a477b084861965f4480c7d9750f0c22d128 100644
--- a/tests/test_Timer.cpp
+++ b/tests/test_Timer.cpp
@@ -59,6 +59,11 @@ TEST_CASE("Timer", "[utils]")
     t2.reset();
     REQUIRE(t2.status() == Timer::Status::paused);
     REQUIRE(t2.seconds() == 0);
+
+    using namespace std::chrono_literals;
+    Timer t3{std::chrono::high_resolution_clock::now() - 24h};
+    REQUIRE(t3.status() == Timer::Status::running);
+    REQUIRE(t3.seconds() > 24 * 3600);
   }
 
   SECTION("stop/start")
diff --git a/tests/test_TupleToVectorProcessor.cpp b/tests/test_TupleToVectorProcessor.cpp
index 0eab5884c6a590a8adf8ee656bd62cf3b528deb5..71475fbd5099d485f44260361c32db5543f4b90a 100644
--- a/tests/test_TupleToVectorProcessor.cpp
+++ b/tests/test_TupleToVectorProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -19,6 +20,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -42,7 +45,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_EVALUATION_THROWS_WITH(data, error_message)        \
@@ -51,6 +57,8 @@
       TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"}; \
       auto ast = ASTBuilder::build(input);                       \
                                                                  \
+      ASTExecutionStack::create();                               \
+                                                                 \
       ASTModulesImporter{*ast};                                  \
       ASTNodeTypeCleaner<language::import_instruction>{*ast};    \
                                                                  \
@@ -66,7 +74,10 @@
       ast->execute(exec_policy);                                 \
     };                                                           \
                                                                  \
+    ASTExecutionStack::destroy();                                \
+                                                                 \
     REQUIRE_THROWS_WITH(eval(), error_message);                  \
+    ast->m_symbol_table->clearValues();                          \
   }
 
 // clazy:excludeall=non-pod-global-static
diff --git a/tests/test_UnaryExpressionProcessor.cpp b/tests/test_UnaryExpressionProcessor.cpp
index 235ba29d9f370b53abbf82a5d027797bd834e672..e4c25a818b3318121da0892881cddb47fd764e62 100644
--- a/tests/test_UnaryExpressionProcessor.cpp
+++ b/tests/test_UnaryExpressionProcessor.cpp
@@ -4,6 +4,7 @@
 #include <FixturesForBuiltinT.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
 #include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
@@ -29,6 +30,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -51,7 +54,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_UNARY_EXPRESSION_THROWS_WITH(data, error_message)       \
@@ -59,9 +65,14 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTExecutionStack::create();                                      \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+                                                                      \
+    ASTExecutionStack::destroy();                                     \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -82,6 +93,8 @@ TEST_CASE("UnaryExpressionProcessor", "[language]")
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
     auto ast = ASTBuilder::build(input);
 
+    ASTExecutionStack::create();
+
     ASTModulesImporter{*ast};
 
     BasicAffectationRegisterFor<EmbeddedData>{ASTNodeDataType::build<ASTNodeDataType::type_id_t>("builtin_t")};
@@ -132,7 +145,11 @@ TEST_CASE("UnaryExpressionProcessor", "[language]")
     auto embedded_value = std::get<EmbeddedData>(attributes.value());
 
     double value = *dynamic_cast<const DataHandler<const double>&>(embedded_value.get()).data_ptr();
+
+    ASTExecutionStack::destroy();
+
     REQUIRE(value == double{-3.2});
+    ast->m_symbol_table->clearValues();
   }
 
   SECTION("unary not")
diff --git a/tests/test_WhileProcessor.cpp b/tests/test_WhileProcessor.cpp
index 69fa63d729c669dd4aba67f29887699147f428db..8b4e3452744cd7daa2661be9904f8506dac2fc05 100644
--- a/tests/test_WhileProcessor.cpp
+++ b/tests/test_WhileProcessor.cpp
@@ -2,6 +2,7 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
 #include <language/ast/ASTModulesImporter.hpp>
 #include <language/ast/ASTNodeAffectationExpressionBuilder.hpp>
 #include <language/ast/ASTNodeDataTypeBuilder.hpp>
@@ -9,6 +10,7 @@
 #include <language/ast/ASTNodeExpressionBuilder.hpp>
 #include <language/ast/ASTNodeTypeCleaner.hpp>
 #include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/node_processor/WhileProcessor.hpp>
 #include <utils/Demangle.hpp>
 
 #include <pegtl/string_input.hpp>
@@ -20,6 +22,8 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};                \
     auto ast = ASTBuilder::build(input);                                      \
                                                                               \
+    ASTExecutionStack::create();                                              \
+                                                                              \
     ASTModulesImporter{*ast};                                                 \
     ASTNodeTypeCleaner<language::import_instruction>{*ast};                   \
                                                                               \
@@ -42,7 +46,10 @@
     auto attributes = symbol->attributes();                                   \
     auto value      = std::get<decltype(expected_value)>(attributes.value()); \
                                                                               \
+    ASTExecutionStack::destroy();                                             \
+                                                                              \
     REQUIRE(value == expected_value);                                         \
+    ast->m_symbol_table->clearValues();                                       \
   }
 
 #define CHECK_WHILE_PROCESSOR_THROWS_WITH(data, error_message)        \
@@ -50,9 +57,14 @@
     TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};        \
     auto ast = ASTBuilder::build(input);                              \
                                                                       \
+    ASTExecutionStack::create();                                      \
+                                                                      \
     ASTSymbolTableBuilder{*ast};                                      \
                                                                       \
+    ASTExecutionStack::destroy();                                     \
+                                                                      \
     REQUIRE_THROWS_WITH(ASTNodeDataTypeBuilder{*ast}, error_message); \
+    ast->m_symbol_table->clearValues();                               \
   }
 
 // clazy:excludeall=non-pod-global-static
@@ -131,4 +143,10 @@ while(1) {
       CHECK_WHILE_PROCESSOR_THROWS_WITH(data, "invalid implicit conversion: Z -> B");
     }
   }
+
+  SECTION("expression type")
+  {
+    ASTNode node;
+    REQUIRE(WhileProcessor{node}.type() == INodeProcessor::Type::while_processor);
+  }
 }
diff --git a/tests/test_checkpointing_Array.cpp b/tests/test_checkpointing_Array.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28881ea1391c878b9daa0ebbe2318a0116d61cfd
--- /dev/null
+++ b/tests/test_checkpointing_Array.cpp
@@ -0,0 +1,132 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/checkpointing/ReadArray.hpp>
+#include <utils/checkpointing/WriteArray.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Array", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("Array")
+    {
+      HighFive::Group checkpoint_group = file.createGroup("checkpoint_group");
+      HighFive::Group useless_group;
+
+      Array<CellType> cell_type_array{17 + 3 * parallel::rank()};
+      cell_type_array.fill(CellType::Line);
+      for (size_t i = 0; i < 10; ++i) {
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Triangle;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Quadrangle;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Polygon;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Tetrahedron;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Pyramid;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Prism;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Diamond;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Hexahedron;
+        cell_type_array[std::rand() / (RAND_MAX / cell_type_array.size())] = CellType::Line;
+      }
+      checkpointing::write(checkpoint_group, "cell_type_array", cell_type_array);
+
+      Array<CellId> cell_id_array{27 + 2 * parallel::rank()};
+      cell_id_array.fill(0);
+      for (size_t i = 0; i < 10; ++i) {
+        cell_id_array[std::rand() / (RAND_MAX / cell_id_array.size())] =
+          std::rand() / (RAND_MAX / cell_id_array.size());
+      }
+      checkpointing::write(checkpoint_group, "cell_id_array", cell_id_array);
+
+      Array<FaceId> face_id_array{29 + 2 * parallel::rank()};
+      face_id_array.fill(0);
+      for (size_t i = 0; i < 10; ++i) {
+        face_id_array[std::rand() / (RAND_MAX / face_id_array.size())] =
+          std::rand() / (RAND_MAX / face_id_array.size());
+      }
+      checkpointing::write(checkpoint_group, "face_id_array", face_id_array);
+
+      Array<EdgeId> edge_id_array{13 + 2 * parallel::rank()};
+      edge_id_array.fill(0);
+      for (size_t i = 0; i < 10; ++i) {
+        edge_id_array[std::rand() / (RAND_MAX / edge_id_array.size())] =
+          std::rand() / (RAND_MAX / edge_id_array.size());
+      }
+      checkpointing::write(checkpoint_group, "edge_id_array", edge_id_array);
+
+      Array<NodeId> node_id_array{22 + 2 * parallel::rank()};
+      node_id_array.fill(0);
+      for (size_t i = 0; i < 10; ++i) {
+        node_id_array[std::rand() / (RAND_MAX / node_id_array.size())] =
+          std::rand() / (RAND_MAX / node_id_array.size());
+      }
+      checkpointing::write(checkpoint_group, "node_id_array", node_id_array);
+
+      Array<double> double_array{16 + 3 * parallel::rank()};
+      double_array.fill(0);
+      for (size_t i = 0; i < 10; ++i) {
+        double_array[std::rand() / (RAND_MAX / double_array.size())] =
+          (1. * std::rand()) / (1. * RAND_MAX / double_array.size());
+      }
+      checkpointing::write(checkpoint_group, "double_array", double_array);
+
+      file.flush();
+
+      auto is_same = [](const auto& a, const auto& b) {
+        bool same = true;
+        for (size_t i = 0; i < a.size(); ++i) {
+          if (a[i] != b[i]) {
+            same = false;
+          }
+        }
+        return parallel::allReduceAnd(same);
+      };
+
+      Array read_cell_type_array = checkpointing::readArray<CellType>(checkpoint_group, "cell_type_array");
+      REQUIRE(is_same(cell_type_array, read_cell_type_array));
+
+      Array read_cell_id_array = checkpointing::readArray<CellId>(checkpoint_group, "cell_id_array");
+      REQUIRE(is_same(cell_id_array, read_cell_id_array));
+
+      Array read_face_id_array = checkpointing::readArray<FaceId>(checkpoint_group, "face_id_array");
+      REQUIRE(is_same(face_id_array, read_face_id_array));
+
+      Array read_edge_id_array = checkpointing::readArray<EdgeId>(checkpoint_group, "edge_id_array");
+      REQUIRE(is_same(edge_id_array, read_edge_id_array));
+
+      Array read_node_id_array = checkpointing::readArray<NodeId>(checkpoint_group, "node_id_array");
+      REQUIRE(is_same(node_id_array, read_node_id_array));
+
+      Array read_double_array = checkpointing::readArray<double>(checkpoint_group, "double_array");
+      REQUIRE(is_same(double_array, read_double_array));
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_Checkpoint.cpp b/tests/test_checkpointing_Checkpoint.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..317683b26dfca317b4b6f0059b6a8e55c8d5e4f0
--- /dev/null
+++ b/tests/test_checkpointing_Checkpoint.cpp
@@ -0,0 +1,196 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_predicate.hpp>
+
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <dev/ParallelChecker.hpp>
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/modules/MathModule.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <utils/ExecutionStatManager.hpp>
+
+class ASTCheckpointsInfoTester
+{
+ private:
+  ASTCheckpointsInfo m_ast_checkpoint_info;
+
+ public:
+  ASTCheckpointsInfoTester(const ASTNode& root_node) : m_ast_checkpoint_info(root_node) {}
+  ~ASTCheckpointsInfoTester() = default;
+};
+
+#define RUN_AST(data)                                          \
+  {                                                            \
+    ExecutionStatManager::create();                            \
+    ParallelChecker::create();                                 \
+    CheckpointResumeRepository::create();                      \
+                                                               \
+    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"}; \
+    auto ast = ASTBuilder::build(input);                       \
+                                                               \
+    ASTModulesImporter{*ast};                                  \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};    \
+                                                               \
+    ASTSymbolTableBuilder{*ast};                               \
+    ASTNodeDataTypeBuilder{*ast};                              \
+                                                               \
+    ASTNodeDeclarationToAffectationConverter{*ast};            \
+    ASTNodeTypeCleaner<language::var_declaration>{*ast};       \
+    ASTNodeTypeCleaner<language::fct_declaration>{*ast};       \
+                                                               \
+    ASTNodeExpressionBuilder{*ast};                            \
+    ExecutionPolicy exec_policy;                               \
+    ASTExecutionStack::create();                               \
+    ASTCheckpointsInfoTester ast_cp_info_tester{*ast};         \
+    ast->execute(exec_policy);                                 \
+    ASTExecutionStack::destroy();                              \
+    ast->m_symbol_table->clearValues();                        \
+                                                               \
+    CheckpointResumeRepository::destroy();                     \
+    ParallelChecker::destroy();                                \
+    ExecutionStatManager::destroy();                           \
+    ast->m_symbol_table->clearValues();                        \
+  }
+
+#else   // PUGS_HAS_HDF5
+
+#include <utils/checkpointing/Checkpoint.hpp>
+
+#endif   // PUGS_HAS_HDF5
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Checkpoint", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  ResumingManager::destroy();
+  ResumingManager::create();
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    ResumingManager::getInstance().setFilename(filename);
+  }
+
+  std::string data = R"(
+import math;
+import mesh;
+import scheme;
+
+let f:R*R^3 -> R^3, (a, v) -> a * v;
+
+let alpha:R, alpha = 3.2;
+let u:R^3, u = [1,2,3];
+
+let m:mesh, m = cartesianMesh(0, [1,1], (10,10));
+
+let n:(N), n = (1,2,3,4);
+
+for(let i:N, i=0; i<3; ++i) {
+  checkpoint();
+
+  let g:R -> R^3, x -> [x, 1.2*x, 3];
+  u = f(alpha,u);
+
+  checkpoint();
+}
+)";
+
+  const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+  const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+  RUN_AST(data);
+
+  GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+  GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+  {   // Check checkpoint file
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::File file(filename, HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 5);
+    REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_5");
+
+    HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+    REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 2);
+
+    HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+    REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+    REQUIRE(symbol_table1.getAttribute("n").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u").read<TinyVector<3>>() - TinyVector<3>{32.768, 65.536, 98.304}) ==
+            Catch::Approx(0).margin(1E-12));
+
+    HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+    HighFive::Group m = embedded1.getGroup("m");
+    REQUIRE(m.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+
+    HighFive::Group singleton        = checkpoint.getGroup("singleton");
+    HighFive::Group global_variables = singleton.getGroup("global_variables");
+    REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() ==
+            initial_connectivity_id + 1 + (parallel::size() > 1));
+    REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() == initial_mesh_id + 1 + (parallel::size() > 1));
+    HighFive::Group execution_info = singleton.getGroup("execution_info");
+    REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+    HighFive::Group connectivity = checkpoint.getGroup("connectivity");
+    HighFive::Group connectivity0 =
+      connectivity.getGroup(std::to_string(initial_connectivity_id + (parallel::size() > 1)));
+    REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+    REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group mesh  = checkpoint.getGroup("mesh");
+    HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id + (parallel::size() > 1)));
+    REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+    REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+    REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group functions = checkpoint.getGroup("functions");
+    HighFive::Group f         = functions.getGroup("f");
+    REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+    REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+    HighFive::Group g = functions.getGroup("g");
+    REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+#else    // PUGS_HAS_HDF5
+  REQUIRE_THROWS_WITH(checkpoint(), "error: checkpoint/resume mechanism requires HDF5");
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_Checkpoint_sequential.cpp b/tests/test_checkpointing_Checkpoint_sequential.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..160404f8835a7fcf6a0c94700ae79d023391b2ca
--- /dev/null
+++ b/tests/test_checkpointing_Checkpoint_sequential.cpp
@@ -0,0 +1,234 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_predicate.hpp>
+
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <dev/ParallelChecker.hpp>
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/modules/MathModule.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <mesh/DualMeshType.hpp>
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/checkpointing/DualMeshTypeHFType.hpp>
+
+class ASTCheckpointsInfoTester
+{
+ private:
+  ASTCheckpointsInfo m_ast_checkpoint_info;
+
+ public:
+  ASTCheckpointsInfoTester(const ASTNode& root_node) : m_ast_checkpoint_info(root_node) {}
+  ~ASTCheckpointsInfoTester() = default;
+};
+
+#define RUN_AST(data)                                          \
+  {                                                            \
+    ExecutionStatManager::create();                            \
+    ParallelChecker::create();                                 \
+    CheckpointResumeRepository::create();                      \
+                                                               \
+    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"}; \
+    auto ast = ASTBuilder::build(input);                       \
+                                                               \
+    ASTModulesImporter{*ast};                                  \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};    \
+                                                               \
+    ASTSymbolTableBuilder{*ast};                               \
+    ASTNodeDataTypeBuilder{*ast};                              \
+                                                               \
+    ASTNodeDeclarationToAffectationConverter{*ast};            \
+    ASTNodeTypeCleaner<language::var_declaration>{*ast};       \
+    ASTNodeTypeCleaner<language::fct_declaration>{*ast};       \
+                                                               \
+    ASTNodeExpressionBuilder{*ast};                            \
+    ExecutionPolicy exec_policy;                               \
+    ASTExecutionStack::create();                               \
+    ASTCheckpointsInfoTester ast_cp_info_tester{*ast};         \
+    ast->execute(exec_policy);                                 \
+    ASTExecutionStack::destroy();                              \
+    ast->m_symbol_table->clearValues();                        \
+                                                               \
+    CheckpointResumeRepository::destroy();                     \
+    ParallelChecker::destroy();                                \
+    ExecutionStatManager::destroy();                           \
+    ast->m_symbol_table->clearValues();                        \
+  }
+
+#else   // PUGS_HAS_HDF5
+
+#include <utils/checkpointing/Checkpoint.hpp>
+
+#endif   // PUGS_HAS_HDF5
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Checkpoint_sequential", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  ResumingManager::destroy();
+  ResumingManager::create();
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    ResumingManager::getInstance().setFilename(filename);
+  }
+
+  std::string data = R"(
+import math;
+import mesh;
+import scheme;
+
+let f:R*R^3 -> R^3, (a, v) -> a * v;
+
+let alpha:R, alpha = 3.2;
+let u:R^3, u = [1,2,3];
+
+let m:mesh, m = cartesianMesh(0, [1,1], (10,10));
+
+let n:(N), n = (1,2,3,4);
+
+let duals:(mesh), duals = (diamondDual(m), medianDual(m));
+
+for(let i:N, i=0; i<3; ++i) {
+  checkpoint();
+
+  let g:R -> R^3, x -> [x, 1.2*x, 3];
+  u = f(alpha,u);
+
+  checkpoint();
+}
+)";
+
+  const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+  const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+  RUN_AST(data);
+
+  GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+  GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+  {   // Check checkpoint file
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::File file(filename, HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 5);
+    REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_5");
+
+    HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+    REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 2);
+
+    HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+    REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+    REQUIRE(symbol_table1.getAttribute("n").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u").read<TinyVector<3>>() - TinyVector<3>{32.768, 65.536, 98.304}) ==
+            Catch::Approx(0).margin(1E-12));
+
+    HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+    HighFive::Group duals = embedded1.getGroup("duals");
+    REQUIRE(duals.getAttribute("type").read<std::string>() == "(mesh)");
+    HighFive::Group duals_0 = duals.getGroup("0");
+    REQUIRE(duals_0.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_0.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    HighFive::Group duals_1 = duals.getGroup("1");
+    REQUIRE(duals_1.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+
+    HighFive::Group m = embedded1.getGroup("m");
+    REQUIRE(m.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+
+    HighFive::Group singleton        = checkpoint.getGroup("singleton");
+    HighFive::Group global_variables = singleton.getGroup("global_variables");
+    REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() == initial_mesh_id + 3);
+    HighFive::Group execution_info = singleton.getGroup("execution_info");
+    REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+    HighFive::Group connectivity  = checkpoint.getGroup("connectivity");
+    HighFive::Group connectivity0 = connectivity.getGroup(std::to_string(initial_connectivity_id));
+    REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity1 = connectivity.getGroup(std::to_string(initial_connectivity_id + 1));
+    REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 1);
+    REQUIRE(connectivity1.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group connectivity2 = connectivity.getGroup(std::to_string(initial_connectivity_id + 2));
+    REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 2);
+    REQUIRE(connectivity2.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group mesh  = checkpoint.getGroup("mesh");
+    HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id));
+    REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + 1));
+    REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    REQUIRE(mesh1.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh1.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + 2));
+    REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+    REQUIRE(mesh2.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh2.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group functions = checkpoint.getGroup("functions");
+    HighFive::Group f         = functions.getGroup("f");
+    REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+    REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+    HighFive::Group g = functions.getGroup("g");
+    REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+  // Revert to default value
+  ResumingManager::getInstance().setFilename("checkpoint.h5");
+
+#else    // PUGS_HAS_HDF5
+  REQUIRE_THROWS_WITH(checkpoint(), "error: checkpoint/resume mechanism requires HDF5");
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_Connectivity.cpp b/tests/test_checkpointing_Connectivity.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dcb6bb14665bebec43b08eaa710e67ab3fc5f76d
--- /dev/null
+++ b/tests/test_checkpointing_Connectivity.cpp
@@ -0,0 +1,151 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteConnectivity.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Connectivity", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    std::map<size_t, size_t> connectivity_id_map;
+
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+    SECTION("Connectivity")
+    {
+      HighFive::Group checkpoint_group_0 = file.createGroup("checkpoint_0");
+      HighFive::Group checkpoint_group_1 = file.createGroup("checkpoint_1");
+
+      {   // Write
+        auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+        // creates artificially holes in numbering
+        test_only::duplicateConnectivity(mesh_1d->connectivity());
+
+        auto new_connectivity_1d = test_only::duplicateConnectivity(mesh_1d->connectivity());
+        checkpointing::writeConnectivity(*new_connectivity_1d, file, checkpoint_group_0);
+        checkpointing::writeConnectivity(*new_connectivity_1d, file, checkpoint_group_1);
+        connectivity_id_map[mesh_1d->id()] = new_connectivity_1d->id();
+
+        auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+        // creates artificially holes in numbering
+        test_only::duplicateConnectivity(mesh_2d->connectivity());
+        test_only::duplicateConnectivity(mesh_2d->connectivity());
+
+        auto new_connectivity_2d = test_only::duplicateConnectivity(mesh_2d->connectivity());
+
+        checkpointing::writeConnectivity(*new_connectivity_2d, file, checkpoint_group_0);
+        checkpointing::writeConnectivity(*new_connectivity_2d, file, checkpoint_group_1);
+        connectivity_id_map[mesh_2d->id()] = new_connectivity_2d->id();
+
+        HighFive::Group global_variables_group_0 = checkpoint_group_0.createGroup("singleton/global_variables");
+        global_variables_group_0.createAttribute("connectivity_id",
+                                                 GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group_0.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+
+        auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+        // creates artificially holes in numbering
+        test_only::duplicateConnectivity(mesh_3d->connectivity());
+
+        auto new_connectivity_3d = test_only::duplicateConnectivity(mesh_3d->connectivity());
+        checkpointing::writeConnectivity(*new_connectivity_3d, file, checkpoint_group_1);
+        connectivity_id_map[mesh_3d->id()] = new_connectivity_3d->id();
+
+        // creates artificially holes in numbering
+        test_only::duplicateConnectivity(mesh_3d->connectivity());
+        test_only::duplicateConnectivity(mesh_3d->connectivity());
+        test_only::duplicateConnectivity(mesh_3d->connectivity());
+
+        HighFive::Group global_variables_group_1 = checkpoint_group_1.createGroup("singleton/global_variables");
+        global_variables_group_1.createAttribute("connectivity_id",
+                                                 GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group_1.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group_1, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      {   // Read
+
+        auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+        const Connectivity<1>& connectivity_1d = mesh_1d->connectivity();
+
+        std::shared_ptr<const IConnectivity> p_new_connectivity_1d =
+          checkpointing::ResumingData::instance().iConnectivity(connectivity_id_map.at(connectivity_1d.id()));
+
+        const Connectivity<1>& read_connectivity_1d = dynamic_cast<const Connectivity<1>&>(*p_new_connectivity_1d);
+
+        REQUIRE(test_only::isSameConnectivity(connectivity_1d, read_connectivity_1d));
+
+        auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+        const Connectivity<2>& connectivity_2d = mesh_2d->connectivity();
+
+        std::shared_ptr<const IConnectivity> p_new_connectivity_2d =
+          checkpointing::ResumingData::instance().iConnectivity(connectivity_id_map.at(connectivity_2d.id()));
+
+        const Connectivity<2>& read_connectivity_2d = dynamic_cast<const Connectivity<2>&>(*p_new_connectivity_2d);
+
+        REQUIRE(test_only::isSameConnectivity(connectivity_2d, read_connectivity_2d));
+
+        auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+        const Connectivity<3>& connectivity_3d = mesh_3d->connectivity();
+
+        std::shared_ptr<const IConnectivity> p_new_connectivity_3d =
+          checkpointing::ResumingData::instance().iConnectivity(connectivity_id_map.at(connectivity_3d.id()));
+
+        const Connectivity<3>& read_connectivity_3d = dynamic_cast<const Connectivity<3>&>(*p_new_connectivity_3d);
+
+        REQUIRE(test_only::isSameConnectivity(connectivity_3d, read_connectivity_3d));
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_DiscreteFunctionVariant.cpp b/tests/test_checkpointing_DiscreteFunctionVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6dbcbafad5c7c0fcfa449313da4e568d1d0f4b7f
--- /dev/null
+++ b/tests/test_checkpointing_DiscreteFunctionVariant.cpp
@@ -0,0 +1,330 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadDiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteDiscreteFunctionVariant.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Mesh_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+template <typename DataType>
+PUGS_INLINE void
+DiscreteFunctionVariant_check_is_same(const DiscreteFunctionP0<DataType>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const DiscreteFunctionVariant>&>(e_read_data.get()));
+
+  std::shared_ptr<const DiscreteFunctionVariant> p_new_data_v =
+    dynamic_cast<const DataHandler<const DiscreteFunctionVariant>&>(e_read_data.get()).data_ptr();
+
+  using DiscreteFunctionT = DiscreteFunctionP0<const DataType>;
+
+  DiscreteFunctionT read_data = p_new_data_v->get<DiscreteFunctionT>();
+
+  REQUIRE(test_only::isSameMesh(read_data.meshVariant(), reference.meshVariant()));
+
+  REQUIRE(same_value(reference.cellValues().arrayView(), read_data.cellValues().arrayView()));
+}
+
+template <typename DataType>
+PUGS_INLINE void
+DiscreteFunctionVariant_check_is_same(const DiscreteFunctionP0Vector<DataType>& reference,
+                                      const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.numberOfRows(); ++i) {
+      for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+        same &= (a(i, j) == b(i, j));
+      }
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const DiscreteFunctionVariant>&>(e_read_data.get()));
+
+  std::shared_ptr<const DiscreteFunctionVariant> p_new_data_v =
+    dynamic_cast<const DataHandler<const DiscreteFunctionVariant>&>(e_read_data.get()).data_ptr();
+
+  using DiscreteFunctionT = DiscreteFunctionP0Vector<const DataType>;
+
+  DiscreteFunctionT read_data = p_new_data_v->get<DiscreteFunctionT>();
+
+  REQUIRE(test_only::isSameMesh(read_data.meshVariant(), reference.meshVariant()));
+
+  REQUIRE(same_value(reference.cellArrays().tableView(), read_data.cellArrays().tableView()));
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_DiscreteFunctionVariant", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+    const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+
+    SECTION("Mesh")
+    {
+      using R1   = TinyVector<1>;
+      using R2   = TinyVector<2>;
+      using R3   = TinyVector<3>;
+      using R1x1 = TinyMatrix<1>;
+      using R2x2 = TinyMatrix<2>;
+      using R3x3 = TinyMatrix<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      DiscreteFunctionP0<double> df_R_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R_1d[cell_id] = std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells());
+      }
+
+      DiscreteFunctionP0<R1> df_R1_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R1_1d[cell_id] = R1{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R2> df_R2_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R2_1d[cell_id] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+      }
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      DiscreteFunctionP0<R3> df_R3_2d{mesh_2d};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        df_R3_2d[cell_id] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R2x2> df_R2x2_2d{mesh_2d};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        df_R2x2_2d[cell_id] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0Vector<double> dfv_R_2d{mesh_2d, 3};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < dfv_R_2d.size(); ++i) {
+          dfv_R_2d[cell_id][i] = std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells());
+        }
+      }
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      DiscreteFunctionP0<R3> df_R3_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R3_3d[cell_id] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R1x1> df_R1x1_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R1x1_3d[cell_id] = R1x1{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R3x3> df_R3x3_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R3x3_3d[cell_id] = R3x3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      {   // Write
+        using DataHandlerT = DataHandler<const DiscreteFunctionVariant>;
+
+        auto new_mesh_1d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_1d));
+        auto new_mesh_1d                = new_mesh_1d_v->get<const Mesh<1>>();
+        const auto& new_connectivity_1d = new_mesh_1d->connectivity();
+
+        DiscreteFunctionP0<const double> df_R_1d_new{new_mesh_1d_v,
+                                                     CellValue<const double>{new_connectivity_1d,
+                                                                             df_R_1d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R1> df_R1_1d_new{new_mesh_1d_v,
+                                                  CellValue<const R1>{new_connectivity_1d,
+                                                                      df_R1_1d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R2> df_R2_1d_new{new_mesh_1d_v,
+                                                  CellValue<const R2>{new_connectivity_1d,
+                                                                      df_R2_1d.cellValues().arrayView()}};
+
+        checkpointing::writeDiscreteFunctionVariant("df_R_1d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R_1d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeDiscreteFunctionVariant("df_R1_1d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R1_1d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeDiscreteFunctionVariant("df_R2_1d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R2_1d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        auto new_mesh_2d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_2d));
+        auto new_mesh_2d                = new_mesh_2d_v->get<const Mesh<2>>();
+        const auto& new_connectivity_2d = new_mesh_2d->connectivity();
+
+        DiscreteFunctionP0<const R3> df_R3_2d_new{new_mesh_2d_v,
+                                                  CellValue<const R3>{new_connectivity_2d,
+                                                                      df_R3_2d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R2x2> df_R2x2_2d_new{new_mesh_2d_v,
+                                                      CellValue<const R2x2>{new_connectivity_2d,
+                                                                            df_R2x2_2d.cellValues().arrayView()}};
+
+        DiscreteFunctionP0Vector<const double> dfv_R_2d_new{new_mesh_2d_v,
+                                                            CellArray<const double>{new_connectivity_2d,
+                                                                                    dfv_R_2d.cellArrays().tableView()}};
+
+        checkpointing::writeDiscreteFunctionVariant("df_R3_2d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R3_2d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeDiscreteFunctionVariant("df_R2x2_2d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R2x2_2d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeDiscreteFunctionVariant("dfv_R_2d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(dfv_R_2d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        auto new_mesh_3d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_3d));
+        auto new_mesh_3d                = new_mesh_3d_v->get<const Mesh<3>>();
+        const auto& new_connectivity_3d = new_mesh_3d->connectivity();
+
+        DiscreteFunctionP0<const R3> df_R3_3d_new{new_mesh_3d, CellValue<const R3>{new_connectivity_3d,
+                                                                                   df_R3_3d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R1x1> df_R1x1_3d_new{new_mesh_3d,
+                                                      CellValue<const R1x1>{new_connectivity_3d,
+                                                                            df_R1x1_3d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R3x3> df_R3x3_3d_new{new_mesh_3d,
+                                                      CellValue<const R3x3>{new_connectivity_3d,
+                                                                            df_R3x3_3d.cellValues().arrayView()}};
+
+        checkpointing::writeDiscreteFunctionVariant("df_R3_3d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R3_3d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeDiscreteFunctionVariant("df_R1x1_3d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R1x1_3d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeDiscreteFunctionVariant("df_R3x3_3d",
+                                                    EmbeddedData{std::make_shared<DataHandlerT>(
+                                                      std::make_shared<const DiscreteFunctionVariant>(df_R3x3_3d_new))},
+                                                    file, checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+      {   // Read
+        auto e_df_R_1d = checkpointing::readDiscreteFunctionVariant("df_R_1d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R_1d, e_df_R_1d);
+
+        auto e_df_R1_1d = checkpointing::readDiscreteFunctionVariant("df_R1_1d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R1_1d, e_df_R1_1d);
+
+        auto e_df_R2_1d = checkpointing::readDiscreteFunctionVariant("df_R2_1d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R2_1d, e_df_R2_1d);
+
+        auto e_df_R3_2d = checkpointing::readDiscreteFunctionVariant("df_R3_2d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R3_2d, e_df_R3_2d);
+
+        auto e_df_R2x2_2d = checkpointing::readDiscreteFunctionVariant("df_R2x2_2d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R2x2_2d, e_df_R2x2_2d);
+
+        auto e_dfv_R_2d = checkpointing::readDiscreteFunctionVariant("dfv_R_2d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(dfv_R_2d, e_dfv_R_2d);
+
+        auto e_df_R3_3d = checkpointing::readDiscreteFunctionVariant("df_R3_3d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R3_3d, e_df_R3_3d);
+
+        auto e_df_R1x1_3d = checkpointing::readDiscreteFunctionVariant("df_R1x1_3d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R1x1_3d, e_df_R1x1_3d);
+
+        auto e_df_R3x3_3d = checkpointing::readDiscreteFunctionVariant("df_R3x3_3d", symbol_table_group);
+        test_only::DiscreteFunctionVariant_check_is_same(df_R3x3_3d, e_df_R3x3_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_HFTypes.cpp b/tests/test_checkpointing_HFTypes.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..41fc6948c382e3d8f4a3f0bf100cd94405c50875
--- /dev/null
+++ b/tests/test_checkpointing_HFTypes.cpp
@@ -0,0 +1,244 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/checkpointing/DiscreteFunctionTypeHFType.hpp>
+#include <utils/checkpointing/DualMeshTypeHFType.hpp>
+#include <utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp>
+#include <utils/checkpointing/IBoundaryDescriptorHFType.hpp>
+#include <utils/checkpointing/IInterfaceDescriptorHFType.hpp>
+#include <utils/checkpointing/INamedDiscreteDataHFType.hpp>
+#include <utils/checkpointing/IWriterHFType.hpp>
+#include <utils/checkpointing/IZoneDescriptorHFType.hpp>
+#include <utils/checkpointing/ItemTypeHFType.hpp>
+#include <utils/checkpointing/LinearSolverOptionsHFType.hpp>
+#include <utils/checkpointing/OStreamTypeHFType.hpp>
+#include <utils/checkpointing/ParallelCheckerHFType.hpp>
+#include <utils/checkpointing/QuadratureTypeHFType.hpp>
+#include <utils/checkpointing/RefItemListHFType.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("HFTypes", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("DiscreteFunctionTypeHFType")
+    {
+      file.createAttribute("P0", DiscreteFunctionType::P0);
+      file.createAttribute("P0Vector", DiscreteFunctionType::P0Vector);
+
+      REQUIRE(file.getAttribute("P0").read<DiscreteFunctionType>() == DiscreteFunctionType::P0);
+      REQUIRE(file.getAttribute("P0Vector").read<DiscreteFunctionType>() == DiscreteFunctionType::P0Vector);
+    }
+
+    SECTION("DualMeshTypeHFType")
+    {
+      file.createAttribute("Dual1D", DualMeshType::Dual1D);
+      file.createAttribute("Diamond", DualMeshType::Diamond);
+      file.createAttribute("Median", DualMeshType::Median);
+
+      REQUIRE(file.getAttribute("Dual1D").read<DualMeshType>() == DualMeshType::Dual1D);
+      REQUIRE(file.getAttribute("Diamond").read<DualMeshType>() == DualMeshType::Diamond);
+      REQUIRE(file.getAttribute("Median").read<DualMeshType>() == DualMeshType::Median);
+    }
+
+    SECTION("IBoundaryConditionDescriptorHFType")
+    {
+      file.createAttribute("axis", IBoundaryConditionDescriptor::Type::axis);
+      file.createAttribute("dirichlet", IBoundaryConditionDescriptor::Type::dirichlet);
+      file.createAttribute("external", IBoundaryConditionDescriptor::Type::external);
+      file.createAttribute("fixed", IBoundaryConditionDescriptor::Type::fixed);
+      file.createAttribute("fourier", IBoundaryConditionDescriptor::Type::fourier);
+      file.createAttribute("free", IBoundaryConditionDescriptor::Type::free);
+      file.createAttribute("inflow", IBoundaryConditionDescriptor::Type::inflow);
+      file.createAttribute("neumann", IBoundaryConditionDescriptor::Type::neumann);
+      file.createAttribute("outflow", IBoundaryConditionDescriptor::Type::outflow);
+      file.createAttribute("symmetry", IBoundaryConditionDescriptor::Type::symmetry);
+
+      REQUIRE(file.getAttribute("axis").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::axis);
+      REQUIRE(file.getAttribute("dirichlet").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::dirichlet);
+      REQUIRE(file.getAttribute("external").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::external);
+      REQUIRE(file.getAttribute("fixed").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::fixed);
+      REQUIRE(file.getAttribute("fourier").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::fourier);
+      REQUIRE(file.getAttribute("free").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::free);
+      REQUIRE(file.getAttribute("inflow").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::inflow);
+      REQUIRE(file.getAttribute("neumann").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::neumann);
+      REQUIRE(file.getAttribute("outflow").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::outflow);
+      REQUIRE(file.getAttribute("symmetry").read<IBoundaryConditionDescriptor::Type>() ==
+              IBoundaryConditionDescriptor::Type::symmetry);
+    }
+
+    SECTION("IBoundaryDescriptorHFType")
+    {
+      file.createAttribute("named", IBoundaryDescriptor::Type::named);
+      file.createAttribute("numbered", IBoundaryDescriptor::Type::numbered);
+
+      REQUIRE(file.getAttribute("named").read<IBoundaryDescriptor::Type>() == IBoundaryDescriptor::Type::named);
+      REQUIRE(file.getAttribute("numbered").read<IBoundaryDescriptor::Type>() == IBoundaryDescriptor::Type::numbered);
+    }
+
+    SECTION("IInterfaceDescriptorHFType")
+    {
+      file.createAttribute("named", IInterfaceDescriptor::Type::named);
+      file.createAttribute("numbered", IInterfaceDescriptor::Type::numbered);
+
+      REQUIRE(file.getAttribute("named").read<IInterfaceDescriptor::Type>() == IInterfaceDescriptor::Type::named);
+      REQUIRE(file.getAttribute("numbered").read<IInterfaceDescriptor::Type>() == IInterfaceDescriptor::Type::numbered);
+    }
+
+    SECTION("INamedDiscreteDataHFType")
+    {
+      file.createAttribute("discrete_function", INamedDiscreteData::Type::discrete_function);
+      file.createAttribute("item_array", INamedDiscreteData::Type::item_array);
+      file.createAttribute("item_value", INamedDiscreteData::Type::item_value);
+
+      REQUIRE(file.getAttribute("discrete_function").read<INamedDiscreteData::Type>() ==
+              INamedDiscreteData::Type::discrete_function);
+      REQUIRE(file.getAttribute("item_array").read<INamedDiscreteData::Type>() == INamedDiscreteData::Type::item_array);
+      REQUIRE(file.getAttribute("item_value").read<INamedDiscreteData::Type>() == INamedDiscreteData::Type::item_value);
+    }
+
+    SECTION("IWriterHFType")
+    {
+      file.createAttribute("gnuplot", IWriter::Type::gnuplot);
+      file.createAttribute("gnuplot_1d", IWriter::Type::gnuplot_1d);
+      file.createAttribute("vtk", IWriter::Type::vtk);
+
+      REQUIRE(file.getAttribute("gnuplot").read<IWriter::Type>() == IWriter::Type::gnuplot);
+      REQUIRE(file.getAttribute("gnuplot_1d").read<IWriter::Type>() == IWriter::Type::gnuplot_1d);
+      REQUIRE(file.getAttribute("vtk").read<IWriter::Type>() == IWriter::Type::vtk);
+    }
+
+    SECTION("IZoneDescriptorHFType")
+    {
+      file.createAttribute("named", IZoneDescriptor::Type::named);
+      file.createAttribute("numbered", IZoneDescriptor::Type::numbered);
+
+      REQUIRE(file.getAttribute("named").read<IZoneDescriptor::Type>() == IZoneDescriptor::Type::named);
+      REQUIRE(file.getAttribute("numbered").read<IZoneDescriptor::Type>() == IZoneDescriptor::Type::numbered);
+    }
+
+    SECTION("ItemTypeHFType")
+    {
+      file.createAttribute("node", ItemType::node);
+      file.createAttribute("edge", ItemType::edge);
+      file.createAttribute("face", ItemType::face);
+      file.createAttribute("cell", ItemType::cell);
+
+      REQUIRE(file.getAttribute("node").read<ItemType>() == ItemType::node);
+      REQUIRE(file.getAttribute("edge").read<ItemType>() == ItemType::edge);
+      REQUIRE(file.getAttribute("face").read<ItemType>() == ItemType::face);
+      REQUIRE(file.getAttribute("cell").read<ItemType>() == ItemType::cell);
+    }
+
+    SECTION("LinearSolverOptionsHFType")
+    {
+      file.createAttribute("builtin", LSLibrary::builtin);
+      file.createAttribute("petsc", LSLibrary::petsc);
+
+      file.createAttribute("cg", LSMethod::cg);
+      file.createAttribute("bicgstab", LSMethod::bicgstab);
+      file.createAttribute("bicgstab2", LSMethod::bicgstab2);
+      file.createAttribute("gmres", LSMethod::gmres);
+      file.createAttribute("lu", LSMethod::lu);
+      file.createAttribute("cholesky", LSMethod::cholesky);
+
+      file.createAttribute("none", LSPrecond::none);
+      file.createAttribute("diagonal", LSPrecond::diagonal);
+      file.createAttribute("incomplete_cholesky", LSPrecond::incomplete_cholesky);
+      file.createAttribute("incomplete_LU", LSPrecond::incomplete_LU);
+      file.createAttribute("amg", LSPrecond::amg);
+
+      REQUIRE(file.getAttribute("builtin").read<LSLibrary>() == LSLibrary::builtin);
+      REQUIRE(file.getAttribute("petsc").read<LSLibrary>() == LSLibrary::petsc);
+
+      REQUIRE(file.getAttribute("cg").read<LSMethod>() == LSMethod::cg);
+      REQUIRE(file.getAttribute("bicgstab").read<LSMethod>() == LSMethod::bicgstab);
+      REQUIRE(file.getAttribute("bicgstab2").read<LSMethod>() == LSMethod::bicgstab2);
+      REQUIRE(file.getAttribute("gmres").read<LSMethod>() == LSMethod::gmres);
+      REQUIRE(file.getAttribute("lu").read<LSMethod>() == LSMethod::lu);
+      REQUIRE(file.getAttribute("cholesky").read<LSMethod>() == LSMethod::cholesky);
+
+      REQUIRE(file.getAttribute("none").read<LSPrecond>() == LSPrecond::none);
+      REQUIRE(file.getAttribute("diagonal").read<LSPrecond>() == LSPrecond::diagonal);
+      REQUIRE(file.getAttribute("incomplete_cholesky").read<LSPrecond>() == LSPrecond::incomplete_cholesky);
+      REQUIRE(file.getAttribute("incomplete_LU").read<LSPrecond>() == LSPrecond::incomplete_LU);
+      REQUIRE(file.getAttribute("amg").read<LSPrecond>() == LSPrecond::amg);
+    }
+
+    SECTION("OStreamTypeHFType")
+    {
+      file.createAttribute("std_ostream", OStream::Type::std_ostream);
+      file.createAttribute("std_ofstream", OStream::Type::std_ofstream);
+
+      REQUIRE(file.getAttribute("std_ostream").read<OStream::Type>() == OStream::Type::std_ostream);
+      REQUIRE(file.getAttribute("std_ofstream").read<OStream::Type>() == OStream::Type::std_ofstream);
+    }
+
+    SECTION("ParallelCheckerHFType")
+    {
+      file.createAttribute("automatic", ParallelChecker::Mode::automatic);
+      file.createAttribute("read", ParallelChecker::Mode::read);
+      file.createAttribute("write", ParallelChecker::Mode::write);
+
+      REQUIRE(file.getAttribute("automatic").read<ParallelChecker::Mode>() == ParallelChecker::Mode::automatic);
+      REQUIRE(file.getAttribute("read").read<ParallelChecker::Mode>() == ParallelChecker::Mode::read);
+      REQUIRE(file.getAttribute("write").read<ParallelChecker::Mode>() == ParallelChecker::Mode::write);
+    }
+
+    SECTION("QuadratureTypeHFType")
+    {
+      file.createAttribute("gauss", QuadratureType::Gauss);
+      file.createAttribute("gauss-legendre", QuadratureType::GaussLegendre);
+      file.createAttribute("gauss-lobatto", QuadratureType::GaussLobatto);
+
+      REQUIRE(file.getAttribute("gauss").read<QuadratureType>() == QuadratureType::Gauss);
+      REQUIRE(file.getAttribute("gauss-legendre").read<QuadratureType>() == QuadratureType::GaussLegendre);
+      REQUIRE(file.getAttribute("gauss-lobatto").read<QuadratureType>() == QuadratureType::GaussLobatto);
+    }
+
+    SECTION("RefItemListHFType")
+    {
+      file.createAttribute("boundary", RefItemListBase::Type::boundary);
+      file.createAttribute("interface", RefItemListBase::Type::interface);
+      file.createAttribute("set", RefItemListBase::Type::set);
+      file.createAttribute("undefined", RefItemListBase::Type::undefined);
+
+      REQUIRE(file.getAttribute("boundary").read<RefItemListBase::Type>() == RefItemListBase::Type::boundary);
+      REQUIRE(file.getAttribute("interface").read<RefItemListBase::Type>() == RefItemListBase::Type::interface);
+      REQUIRE(file.getAttribute("set").read<RefItemListBase::Type>() == RefItemListBase::Type::set);
+      REQUIRE(file.getAttribute("undefined").read<RefItemListBase::Type>() == RefItemListBase::Type::undefined);
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp b/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..34505db62eff529b480c912ce5ca36714c7bc17b
--- /dev/null
+++ b/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp
@@ -0,0 +1,354 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <scheme/AxisBoundaryConditionDescriptor.hpp>
+#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/ExternalBoundaryConditionDescriptor.hpp>
+#include <scheme/FixedBoundaryConditionDescriptor.hpp>
+#include <scheme/FourierBoundaryConditionDescriptor.hpp>
+#include <scheme/FreeBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowBoundaryConditionDescriptor.hpp>
+#include <scheme/InflowListBoundaryConditionDescriptor.hpp>
+#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
+#include <scheme/OutflowBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <scheme/WallBoundaryConditionDescriptor.hpp>
+#include <utils/checkpointing/ReadIBoundaryConditionDescriptor.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteIBoundaryConditionDescriptor.hpp>
+
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionEvaluationExpressionBuilder.hpp>
+#include <language/ast/ASTNodeFunctionExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+#include <language/utils/BasicAffectationRegistrerFor.hpp>
+#include <language/utils/TypeDescriptor.hpp>
+#include <utils/GlobalVariableManager.hpp>
+
+// #include <utils/Demangle.hpp>
+
+#include <pegtl/string_input.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IBoundaryConditionDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IBoundaryConditionDescriptor")
+    {
+      std::shared_ptr<SymbolTable> symbol_table;
+
+      std::string data =
+        R"(
+let f: R -> R, x -> 2*x;
+let g: R -> R, x -> 2*x+1;
+let h: R -> R, x -> -x;
+let i: R -> R, x -> x+3;
+)";
+
+      TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"};
+      auto ast = ASTBuilder::build(input);
+
+      ASTModulesImporter{*ast};
+      BasicAffectationRegisterFor<EmbeddedData>{ASTNodeDataType::build<ASTNodeDataType::type_id_t>("builtin_t")};
+
+      ASTNodeTypeCleaner<language::import_instruction>{*ast};
+
+      ASTSymbolTableBuilder{*ast};
+      ASTNodeDataTypeBuilder{*ast};
+
+      ASTNodeTypeCleaner<language::var_declaration>{*ast};
+      ASTNodeTypeCleaner<language::fct_declaration>{*ast};
+      ASTNodeExpressionBuilder{*ast};
+
+      symbol_table = ast->m_symbol_table;
+
+      HighFive::Group checkpoint_group = file.createGroup("checkpoint");
+
+      HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+      global_variables_group.createAttribute("connectivity_id", GlobalVariableManager::instance().getConnectivityId());
+      global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+
+      HighFive::Group function_group = checkpoint_group.createGroup("functions");
+
+      {
+        HighFive::Group f = function_group.createGroup("f");
+        f.createAttribute("id", size_t{0});
+        f.createAttribute("symbol_table_id", size_t{0});
+
+        HighFive::Group g = function_group.createGroup("g");
+        g.createAttribute("id", size_t{1});
+        g.createAttribute("symbol_table_id", size_t{0});
+
+        HighFive::Group h = function_group.createGroup("h");
+        h.createAttribute("id", size_t{2});
+        h.createAttribute("symbol_table_id", size_t{0});
+
+        HighFive::Group i = function_group.createGroup("i");
+        i.createAttribute("id", size_t{3});
+        i.createAttribute("symbol_table_id", size_t{0});
+      }
+
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto p_boundary_1 = std::make_shared<NamedBoundaryDescriptor>("bd1");
+      auto p_boundary_2 = std::make_shared<NumberedBoundaryDescriptor>(2);
+
+      HighFive::Group useless_group;
+
+      auto p_axis_bc_descriptor = std::make_shared<const AxisBoundaryConditionDescriptor>(p_boundary_1);
+      checkpointing::writeIBoundaryConditionDescriptor("axis_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_axis_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      auto p_sym_bc_descriptor = std::make_shared<const SymmetryBoundaryConditionDescriptor>(p_boundary_1);
+      checkpointing::writeIBoundaryConditionDescriptor("sym_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_sym_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      auto p_wall_bc_descriptor = std::make_shared<const WallBoundaryConditionDescriptor>(p_boundary_1);
+      checkpointing::writeIBoundaryConditionDescriptor("wall_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_wall_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      auto p_outflow_bc_descriptor = std::make_shared<const OutflowBoundaryConditionDescriptor>(p_boundary_2);
+      checkpointing::writeIBoundaryConditionDescriptor("outflow_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_outflow_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      auto p_free_bc_descriptor = std::make_shared<const FreeBoundaryConditionDescriptor>(p_boundary_2);
+      checkpointing::writeIBoundaryConditionDescriptor("free_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_free_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      auto p_fixed_bc_descriptor = std::make_shared<const FixedBoundaryConditionDescriptor>(p_boundary_2);
+      checkpointing::writeIBoundaryConditionDescriptor("fixed_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_fixed_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      const FunctionSymbolId dirichlet_function_id{3, symbol_table};
+      auto p_dirichlet_bc_descriptor =
+        std::make_shared<const DirichletBoundaryConditionDescriptor>("dirichlet_name", p_boundary_1,
+                                                                     dirichlet_function_id);
+      checkpointing::writeIBoundaryConditionDescriptor("dirichlet_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_dirichlet_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      const FunctionSymbolId neumann_function_id{1, symbol_table};
+      auto p_neumann_bc_descriptor =
+        std::make_shared<const NeumannBoundaryConditionDescriptor>("neumann_name", p_boundary_1, neumann_function_id);
+      checkpointing::writeIBoundaryConditionDescriptor("neumann_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_neumann_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      const FunctionSymbolId fourier_mass_function_id{0, symbol_table};
+      const FunctionSymbolId fourier_rhs_function_id{2, symbol_table};
+      auto p_fourier_bc_descriptor =
+        std::make_shared<const FourierBoundaryConditionDescriptor>("fourier_name", p_boundary_2,
+                                                                   fourier_mass_function_id, fourier_rhs_function_id);
+      checkpointing::writeIBoundaryConditionDescriptor("fourier_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_fourier_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      const FunctionSymbolId inflow_function_id{3, symbol_table};
+      auto p_inflow_bc_descriptor =
+        std::make_shared<const InflowBoundaryConditionDescriptor>(p_boundary_2, inflow_function_id);
+      checkpointing::writeIBoundaryConditionDescriptor("inflow_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_inflow_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      const std::vector<FunctionSymbolId> inflow_list_function_id_list{FunctionSymbolId{2, symbol_table},
+                                                                       FunctionSymbolId{3, symbol_table}};
+      auto p_inflow_list_bc_descriptor =
+        std::make_shared<const InflowListBoundaryConditionDescriptor>(p_boundary_1, inflow_list_function_id_list);
+      checkpointing::writeIBoundaryConditionDescriptor("inflow_list_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_inflow_list_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+
+      checkpointing::ResumingData::instance().readData(checkpoint_group, symbol_table);
+
+      EmbeddedData read_axis_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("axis_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_sym_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("sym_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_wall_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("wall_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_outflow_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("outflow_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_free_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("free_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_fixed_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("fixed_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_dirichlet_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("dirichlet_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_neumann_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("neumann_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_fourier_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("fourier_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_inflow_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("inflow_bc_descriptor", symbol_table_group);
+
+      EmbeddedData read_inflow_list_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("inflow_list_bc_descriptor", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IBoundaryConditionDescriptor& {
+        return *dynamic_cast<const DataHandler<const IBoundaryConditionDescriptor>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_axis_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_sym_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_wall_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_outflow_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_free_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_fixed_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_dirichlet_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_neumann_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_fourier_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_inflow_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_inflow_list_bc_descriptor));
+
+      REQUIRE(get_value(read_axis_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::axis);
+      REQUIRE(get_value(read_sym_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::symmetry);
+      REQUIRE(get_value(read_wall_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::wall);
+      REQUIRE(get_value(read_outflow_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::outflow);
+      REQUIRE(get_value(read_free_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::free);
+      REQUIRE(get_value(read_fixed_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::fixed);
+      REQUIRE(get_value(read_dirichlet_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::dirichlet);
+      REQUIRE(get_value(read_neumann_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::neumann);
+      REQUIRE(get_value(read_fourier_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::fourier);
+      REQUIRE(get_value(read_inflow_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::inflow);
+      REQUIRE(get_value(read_inflow_list_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::inflow_list);
+
+      REQUIRE_NOTHROW(dynamic_cast<const AxisBoundaryConditionDescriptor&>(get_value(read_axis_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(get_value(read_sym_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const WallBoundaryConditionDescriptor&>(get_value(read_wall_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const OutflowBoundaryConditionDescriptor&>(get_value(read_outflow_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const FreeBoundaryConditionDescriptor&>(get_value(read_free_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const FixedBoundaryConditionDescriptor&>(get_value(read_fixed_bc_descriptor)));
+      REQUIRE_NOTHROW(
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(get_value(read_dirichlet_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const NeumannBoundaryConditionDescriptor&>(get_value(read_neumann_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const FourierBoundaryConditionDescriptor&>(get_value(read_fourier_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const InflowBoundaryConditionDescriptor&>(get_value(read_inflow_bc_descriptor)));
+      REQUIRE_NOTHROW(
+        dynamic_cast<const InflowListBoundaryConditionDescriptor&>(get_value(read_inflow_list_bc_descriptor)));
+
+      auto& read_axis_bc = dynamic_cast<const AxisBoundaryConditionDescriptor&>(get_value(read_axis_bc_descriptor));
+      auto& read_sym_bc  = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(get_value(read_sym_bc_descriptor));
+      auto& read_wall_bc = dynamic_cast<const WallBoundaryConditionDescriptor&>(get_value(read_wall_bc_descriptor));
+      auto& read_outflow_bc =
+        dynamic_cast<const OutflowBoundaryConditionDescriptor&>(get_value(read_outflow_bc_descriptor));
+      auto& read_free_bc  = dynamic_cast<const FreeBoundaryConditionDescriptor&>(get_value(read_free_bc_descriptor));
+      auto& read_fixed_bc = dynamic_cast<const FixedBoundaryConditionDescriptor&>(get_value(read_fixed_bc_descriptor));
+      auto& read_dirichlet_bc =
+        dynamic_cast<const DirichletBoundaryConditionDescriptor&>(get_value(read_dirichlet_bc_descriptor));
+      auto& read_neumann_bc =
+        dynamic_cast<const NeumannBoundaryConditionDescriptor&>(get_value(read_neumann_bc_descriptor));
+      auto& read_fourier_bc =
+        dynamic_cast<const FourierBoundaryConditionDescriptor&>(get_value(read_fourier_bc_descriptor));
+      auto& read_inflow_bc =
+        dynamic_cast<const InflowBoundaryConditionDescriptor&>(get_value(read_inflow_bc_descriptor));
+      auto& read_inflow_list_bc =
+        dynamic_cast<const InflowListBoundaryConditionDescriptor&>(get_value(read_inflow_list_bc_descriptor));
+
+      REQUIRE(read_axis_bc.boundaryDescriptor().type() == p_axis_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_sym_bc.boundaryDescriptor().type() == p_sym_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_wall_bc.boundaryDescriptor().type() == p_wall_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_outflow_bc.boundaryDescriptor().type() == p_outflow_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_free_bc.boundaryDescriptor().type() == p_free_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_fixed_bc.boundaryDescriptor().type() == p_fixed_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_dirichlet_bc.boundaryDescriptor().type() == p_dirichlet_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_dirichlet_bc.name() == p_dirichlet_bc_descriptor->name());
+      REQUIRE(read_dirichlet_bc.rhsSymbolId().id() == p_dirichlet_bc_descriptor->rhsSymbolId().id());
+      REQUIRE(read_neumann_bc.boundaryDescriptor().type() == p_neumann_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_neumann_bc.name() == p_neumann_bc_descriptor->name());
+      REQUIRE(read_neumann_bc.rhsSymbolId().id() == p_neumann_bc_descriptor->rhsSymbolId().id());
+      REQUIRE(read_fourier_bc.boundaryDescriptor().type() == p_fourier_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_fourier_bc.name() == p_fourier_bc_descriptor->name());
+      REQUIRE(read_fourier_bc.massSymbolId().id() == p_fourier_bc_descriptor->massSymbolId().id());
+      REQUIRE(read_fourier_bc.rhsSymbolId().id() == p_fourier_bc_descriptor->rhsSymbolId().id());
+      REQUIRE(read_inflow_bc.boundaryDescriptor().type() == p_inflow_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_inflow_bc.functionSymbolId().id() == p_inflow_bc_descriptor->functionSymbolId().id());
+      REQUIRE(read_inflow_list_bc.boundaryDescriptor().type() ==
+              p_inflow_list_bc_descriptor->boundaryDescriptor().type());
+      for (size_t i = 0; i < read_inflow_list_bc.functionSymbolIdList().size(); ++i) {
+        REQUIRE(read_inflow_list_bc.functionSymbolIdList()[i].id() ==
+                p_inflow_list_bc_descriptor->functionSymbolIdList()[i].id());
+      }
+
+      checkpointing::ResumingData::destroy();
+      ast->m_symbol_table->clearValues();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IBoundaryDescriptor.cpp b/tests/test_checkpointing_IBoundaryDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..28c1aee1d37142b68abb973a16f0c69813131390
--- /dev/null
+++ b/tests/test_checkpointing_IBoundaryDescriptor.cpp
@@ -0,0 +1,89 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <utils/checkpointing/ReadIBoundaryDescriptor.hpp>
+#include <utils/checkpointing/WriteIBoundaryDescriptor.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IBoundaryDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IBoundaryDescriptor")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_named_boundary_descriptor = std::make_shared<const NamedBoundaryDescriptor>("boundary_name1");
+      checkpointing::writeIBoundaryDescriptor("named_boundary_descriptor",
+                                              EmbeddedData{std::make_shared<DataHandler<const IBoundaryDescriptor>>(
+                                                p_named_boundary_descriptor)},
+                                              file, useless_group, symbol_table_group);
+
+      auto p_numbered_boundary_descriptor = std::make_shared<const NumberedBoundaryDescriptor>(3);
+      checkpointing::writeIBoundaryDescriptor("numbered_boundary_descriptor",
+                                              EmbeddedData{std::make_shared<DataHandler<const IBoundaryDescriptor>>(
+                                                p_numbered_boundary_descriptor)},
+                                              file, useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_named_boundary_descriptor =
+        checkpointing::readIBoundaryDescriptor("named_boundary_descriptor", symbol_table_group);
+
+      EmbeddedData read_numbered_boundary_descriptor =
+        checkpointing::readIBoundaryDescriptor("numbered_boundary_descriptor", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IBoundaryDescriptor& {
+        return *dynamic_cast<const DataHandler<const IBoundaryDescriptor>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_named_boundary_descriptor));
+      REQUIRE_NOTHROW(get_value(read_numbered_boundary_descriptor));
+
+      REQUIRE(get_value(read_named_boundary_descriptor).type() == IBoundaryDescriptor::Type::named);
+      REQUIRE(get_value(read_numbered_boundary_descriptor).type() == IBoundaryDescriptor::Type::numbered);
+
+      REQUIRE_NOTHROW(dynamic_cast<const NamedBoundaryDescriptor&>(get_value(read_named_boundary_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const NumberedBoundaryDescriptor&>(get_value(read_numbered_boundary_descriptor)));
+
+      auto& read_named = dynamic_cast<const NamedBoundaryDescriptor&>(get_value(read_named_boundary_descriptor));
+      auto& read_numbered =
+        dynamic_cast<const NumberedBoundaryDescriptor&>(get_value(read_numbered_boundary_descriptor));
+
+      REQUIRE(read_named.name() == p_named_boundary_descriptor->name());
+      REQUIRE(read_numbered.number() == p_numbered_boundary_descriptor->number());
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IDiscreteFunctionDescriptor.cpp b/tests/test_checkpointing_IDiscreteFunctionDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..da98859ed0ec057fd8ec2f764137274067b33d10
--- /dev/null
+++ b/tests/test_checkpointing_IDiscreteFunctionDescriptor.cpp
@@ -0,0 +1,84 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <scheme/DiscreteFunctionDescriptorP0.hpp>
+#include <scheme/DiscreteFunctionDescriptorP0Vector.hpp>
+#include <scheme/IDiscreteFunctionDescriptor.hpp>
+#include <utils/checkpointing/ReadIDiscreteFunctionDescriptor.hpp>
+#include <utils/checkpointing/WriteIDiscreteFunctionDescriptor.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IDiscreteFunctionDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IDiscreteFunctionDescriptor")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_discrete_function_p0 = std::make_shared<DiscreteFunctionDescriptorP0>();
+      checkpointing::writeIDiscreteFunctionDescriptor("P0",
+                                                      EmbeddedData{std::make_shared<
+                                                        DataHandler<const IDiscreteFunctionDescriptor>>(
+                                                        p_discrete_function_p0)},
+                                                      file, useless_group, symbol_table_group);
+
+      auto p_discrete_function_p0_vector = std::make_shared<DiscreteFunctionDescriptorP0Vector>();
+      checkpointing::writeIDiscreteFunctionDescriptor("P0Vector",
+                                                      EmbeddedData{std::make_shared<
+                                                        DataHandler<const IDiscreteFunctionDescriptor>>(
+                                                        p_discrete_function_p0_vector)},
+                                                      file, useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_df_descriptor_p0 = checkpointing::readIDiscreteFunctionDescriptor("P0", symbol_table_group);
+
+      EmbeddedData read_df_descriptor_p0_vector =
+        checkpointing::readIDiscreteFunctionDescriptor("P0Vector", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IDiscreteFunctionDescriptor& {
+        return *dynamic_cast<const DataHandler<const IDiscreteFunctionDescriptor>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_df_descriptor_p0));
+      REQUIRE_NOTHROW(get_value(read_df_descriptor_p0_vector));
+
+      REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionDescriptorP0&>(get_value(read_df_descriptor_p0)));
+      REQUIRE_NOTHROW(dynamic_cast<const DiscreteFunctionDescriptorP0Vector&>(get_value(read_df_descriptor_p0_vector)));
+
+      REQUIRE(get_value(read_df_descriptor_p0).type() == DiscreteFunctionType::P0);
+      REQUIRE(get_value(read_df_descriptor_p0_vector).type() == DiscreteFunctionType::P0Vector);
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IInterfaceDescriptor.cpp b/tests/test_checkpointing_IInterfaceDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1dcea8c12a8d8e461117b52d67918679989ccf8c
--- /dev/null
+++ b/tests/test_checkpointing_IInterfaceDescriptor.cpp
@@ -0,0 +1,89 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedInterfaceDescriptor.hpp>
+#include <mesh/NumberedInterfaceDescriptor.hpp>
+#include <utils/checkpointing/ReadIInterfaceDescriptor.hpp>
+#include <utils/checkpointing/WriteIInterfaceDescriptor.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IInterfaceDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IInterfaceDescriptor")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_named_interface_descriptor = std::make_shared<const NamedInterfaceDescriptor>("interface_name1");
+      checkpointing::writeIInterfaceDescriptor("named_interface_descriptor",
+                                               EmbeddedData{std::make_shared<DataHandler<const IInterfaceDescriptor>>(
+                                                 p_named_interface_descriptor)},
+                                               file, useless_group, symbol_table_group);
+
+      auto p_numbered_interface_descriptor = std::make_shared<const NumberedInterfaceDescriptor>(3);
+      checkpointing::writeIInterfaceDescriptor("numbered_interface_descriptor",
+                                               EmbeddedData{std::make_shared<DataHandler<const IInterfaceDescriptor>>(
+                                                 p_numbered_interface_descriptor)},
+                                               file, useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_named_interface_descriptor =
+        checkpointing::readIInterfaceDescriptor("named_interface_descriptor", symbol_table_group);
+
+      EmbeddedData read_numbered_interface_descriptor =
+        checkpointing::readIInterfaceDescriptor("numbered_interface_descriptor", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IInterfaceDescriptor& {
+        return *dynamic_cast<const DataHandler<const IInterfaceDescriptor>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_named_interface_descriptor));
+      REQUIRE_NOTHROW(get_value(read_numbered_interface_descriptor));
+
+      REQUIRE(get_value(read_named_interface_descriptor).type() == IInterfaceDescriptor::Type::named);
+      REQUIRE(get_value(read_numbered_interface_descriptor).type() == IInterfaceDescriptor::Type::numbered);
+
+      REQUIRE_NOTHROW(dynamic_cast<const NamedInterfaceDescriptor&>(get_value(read_named_interface_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const NumberedInterfaceDescriptor&>(get_value(read_numbered_interface_descriptor)));
+
+      auto& read_named = dynamic_cast<const NamedInterfaceDescriptor&>(get_value(read_named_interface_descriptor));
+      auto& read_numbered =
+        dynamic_cast<const NumberedInterfaceDescriptor&>(get_value(read_numbered_interface_descriptor));
+
+      REQUIRE(read_named.name() == p_named_interface_descriptor->name());
+      REQUIRE(read_numbered.number() == p_numbered_interface_descriptor->number());
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_INamedDiscreteData.cpp b/tests/test_checkpointing_INamedDiscreteData.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..be8bd252c944adb60e7ed9cf6641a086eafb4446
--- /dev/null
+++ b/tests/test_checkpointing_INamedDiscreteData.cpp
@@ -0,0 +1,553 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/ItemArrayVariant.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/Mesh.hpp>
+#include <output/INamedDiscreteData.hpp>
+#include <output/NamedDiscreteFunction.hpp>
+#include <output/NamedItemArrayVariant.hpp>
+#include <output/NamedItemValueVariant.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadINamedDiscreteData.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteINamedDiscreteData.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+#include <checkpointing_Mesh_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+PUGS_INLINE void
+INamedDiscreteData_check_is_same_type(const INamedDiscreteData::Type& type, const EmbeddedData& e_read_data)
+{
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()));
+
+  std::shared_ptr<const INamedDiscreteData> p_new_data_v =
+    dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()).data_ptr();
+
+  REQUIRE(p_new_data_v->type() == type);
+}
+
+PUGS_INLINE void
+INamedDiscreteData_check_is_same_name(const std::string& name, const EmbeddedData& e_read_data)
+{
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()));
+
+  std::shared_ptr<const INamedDiscreteData> p_new_data_v =
+    dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()).data_ptr();
+
+  REQUIRE(p_new_data_v->name() == name);
+}
+
+template <typename DataType>
+PUGS_INLINE void
+INamedDiscreteData_check_is_same_data(const DiscreteFunctionP0<DataType>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()));
+
+  const INamedDiscreteData& i_named_discrete_data =
+    *dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()).data_ptr();
+
+  REQUIRE_NOTHROW(dynamic_cast<const NamedDiscreteFunction&>(i_named_discrete_data));
+
+  const NamedDiscreteFunction& named_discrete_function =
+    dynamic_cast<const NamedDiscreteFunction&>(i_named_discrete_data);
+
+  using DiscreteFunctionT = DiscreteFunctionP0<const DataType>;
+
+  DiscreteFunctionT read_data = named_discrete_function.discreteFunctionVariant()->get<DiscreteFunctionT>();
+
+  REQUIRE(test_only::isSameMesh(read_data.meshVariant(), reference.meshVariant()));
+
+  REQUIRE(same_value(reference.cellValues().arrayView(), read_data.cellValues().arrayView()));
+}
+
+template <typename DataType>
+PUGS_INLINE void
+INamedDiscreteData_check_is_same_data(const DiscreteFunctionP0Vector<DataType>& reference,
+                                      const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.numberOfRows(); ++i) {
+      for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+        same &= (a(i, j) == b(i, j));
+      }
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()));
+
+  const INamedDiscreteData& i_named_discrete_data =
+    *dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()).data_ptr();
+
+  REQUIRE_NOTHROW(dynamic_cast<const NamedDiscreteFunction&>(i_named_discrete_data));
+
+  const NamedDiscreteFunction& named_discrete_function =
+    dynamic_cast<const NamedDiscreteFunction&>(i_named_discrete_data);
+
+  using DiscreteFunctionT = DiscreteFunctionP0Vector<const DataType>;
+
+  DiscreteFunctionT read_data = named_discrete_function.discreteFunctionVariant()->get<DiscreteFunctionT>();
+
+  REQUIRE(test_only::isSameMesh(read_data.meshVariant(), reference.meshVariant()));
+
+  REQUIRE(same_value(reference.cellArrays().tableView(), read_data.cellArrays().tableView()));
+}
+
+template <ItemType item_type, typename DataType>
+PUGS_INLINE void
+INamedDiscreteData_check_is_same_data(const ItemValue<DataType, item_type>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()));
+
+  const INamedDiscreteData& i_named_discrete_data =
+    *dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()).data_ptr();
+
+  REQUIRE_NOTHROW(dynamic_cast<const NamedItemValueVariant&>(i_named_discrete_data));
+
+  const NamedItemValueVariant& named_item_value_variant =
+    dynamic_cast<const NamedItemValueVariant&>(i_named_discrete_data);
+
+  using ItemValueT = ItemValue<const DataType, item_type>;
+
+  ItemValueT read_data = named_item_value_variant.itemValueVariant()->get<ItemValueT>();
+
+  REQUIRE(test_only::isSameConnectivity(*read_data.connectivity_ptr(), *reference.connectivity_ptr()));
+
+  REQUIRE(same_value(reference.arrayView(), read_data.arrayView()));
+}
+
+template <ItemType item_type, typename DataType>
+PUGS_INLINE void
+INamedDiscreteData_check_is_same_data(const ItemArray<DataType, item_type>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.numberOfRows(); ++i) {
+      for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+        same &= (a(i, j) == b(i, j));
+      }
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()));
+
+  const INamedDiscreteData& i_named_discrete_data =
+    *dynamic_cast<const DataHandler<const INamedDiscreteData>&>(e_read_data.get()).data_ptr();
+
+  REQUIRE_NOTHROW(dynamic_cast<const NamedItemArrayVariant&>(i_named_discrete_data));
+
+  const NamedItemArrayVariant& named_item_array_variant =
+    dynamic_cast<const NamedItemArrayVariant&>(i_named_discrete_data);
+
+  using ItemArrayT = ItemArray<const DataType, item_type>;
+
+  ItemArrayT read_data = named_item_array_variant.itemArrayVariant()->get<ItemArrayT>();
+
+  REQUIRE(test_only::isSameConnectivity(*read_data.connectivity_ptr(), *reference.connectivity_ptr()));
+
+  REQUIRE(reference.sizeOfArrays() == read_data.sizeOfArrays());
+
+  REQUIRE(same_value(reference.tableView(), read_data.tableView()));
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_INamedDiscreteData", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+    const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+
+    SECTION("Mesh")
+    {
+      using R1   = TinyVector<1>;
+      using R2   = TinyVector<2>;
+      using R3   = TinyVector<3>;
+      using R1x1 = TinyMatrix<1>;
+      using R2x2 = TinyMatrix<2>;
+      using R3x3 = TinyMatrix<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      DiscreteFunctionP0<double> df_R_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R_1d[cell_id] = std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells());
+      }
+
+      DiscreteFunctionP0<R1> df_R1_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R1_1d[cell_id] = R1{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R2> df_R2_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R2_1d[cell_id] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+      }
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      DiscreteFunctionP0<R3> df_R3_2d{mesh_2d};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        df_R3_2d[cell_id] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R2x2> df_R2x2_2d{mesh_2d};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        df_R2x2_2d[cell_id] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells())};
+      }
+
+      NodeValue<R2x2> iv_R2x2_2d{mesh_2d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_2d->numberOfNodes(); ++node_id) {
+        iv_R2x2_2d[node_id] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes())};
+      }
+
+      FaceArray<R2x2> ia_R2x2_2d{mesh_2d->connectivity(), 3};
+      for (FaceId face_id = 0; face_id < mesh_2d->numberOfFaces(); ++face_id) {
+        for (size_t i = 0; i < ia_R2x2_2d.sizeOfArrays(); ++i) {
+          ia_R2x2_2d[face_id][i] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                        std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                        std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                        std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces())};
+        }
+      }
+
+      DiscreteFunctionP0Vector<double> dfv_R_2d{mesh_2d, 3};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < dfv_R_2d.size(); ++i) {
+          dfv_R_2d[cell_id][i] = std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells());
+        }
+      }
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      DiscreteFunctionP0<R3> df_R3_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R3_3d[cell_id] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R1x1> df_R1x1_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R1x1_3d[cell_id] = R1x1{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      DiscreteFunctionP0<R3x3> df_R3x3_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R3x3_3d[cell_id] = R3x3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                                   std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      EdgeValue<R3x3> iv_R3x3_3d{mesh_3d->connectivity()};
+      for (EdgeId edge_id = 0; edge_id < mesh_3d->numberOfEdges(); ++edge_id) {
+        iv_R3x3_3d[edge_id] = R3x3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges()),
+                                   std::rand() / (1. * RAND_MAX / mesh_2d->numberOfEdges())};
+      }
+
+      {   // Write
+        using DataHandlerT = DataHandler<const INamedDiscreteData>;
+
+        auto new_mesh_1d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_1d));
+        auto new_mesh_1d                = new_mesh_1d_v->get<const Mesh<1>>();
+        const auto& new_connectivity_1d = new_mesh_1d->connectivity();
+
+        DiscreteFunctionP0<const double> df_R_1d_new{new_mesh_1d_v,
+                                                     CellValue<const double>{new_connectivity_1d,
+                                                                             df_R_1d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R1> df_R1_1d_new{new_mesh_1d_v,
+                                                  CellValue<const R1>{new_connectivity_1d,
+                                                                      df_R1_1d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R2> df_R2_1d_new{new_mesh_1d_v,
+                                                  CellValue<const R2>{new_connectivity_1d,
+                                                                      df_R2_1d.cellValues().arrayView()}};
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R_1d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R_1d_new),
+                                                  "df_R_1d");
+
+        checkpointing::writeINamedDiscreteData("df_R_1d", EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R_1d)},
+                                               file, checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R1_1d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R1_1d_new),
+                                                  "df_R1_1d");
+
+        checkpointing::writeINamedDiscreteData("df_R1_1d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R1_1d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R2_1d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R2_1d_new),
+                                                  "df_R2_1d");
+
+        checkpointing::writeINamedDiscreteData("df_R2_1d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R2_1d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        auto new_mesh_2d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_2d));
+        auto new_mesh_2d                = new_mesh_2d_v->get<const Mesh<2>>();
+        const auto& new_connectivity_2d = new_mesh_2d->connectivity();
+
+        DiscreteFunctionP0<const R3> df_R3_2d_new{new_mesh_2d_v,
+                                                  CellValue<const R3>{new_connectivity_2d,
+                                                                      df_R3_2d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R2x2> df_R2x2_2d_new{new_mesh_2d_v,
+                                                      CellValue<const R2x2>{new_connectivity_2d,
+                                                                            df_R2x2_2d.cellValues().arrayView()}};
+
+        NodeValue<const R2x2> iv_R2x2_2d_new{new_connectivity_2d, iv_R2x2_2d.arrayView()};
+
+        FaceArray<const R2x2> ia_R2x2_2d_new{new_connectivity_2d, ia_R2x2_2d.tableView()};
+
+        DiscreteFunctionP0Vector<const double> dfv_R_2d_new{new_mesh_2d_v,
+                                                            CellArray<const double>{new_connectivity_2d,
+                                                                                    dfv_R_2d.cellArrays().tableView()}};
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R3_2d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R3_2d_new),
+                                                  "df_R3_2d");
+
+        checkpointing::writeINamedDiscreteData("df_R3_2d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R3_2d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R2x2_2d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R2x2_2d_new),
+                                                  "df_R2x2_2d");
+
+        checkpointing::writeINamedDiscreteData("df_R2x2_2d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R2x2_2d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_iv_R2x2_2d =
+          std::make_shared<NamedItemValueVariant>(std::make_shared<const ItemValueVariant>(iv_R2x2_2d_new),
+                                                  "iv_R2x2_2d");
+
+        checkpointing::writeINamedDiscreteData("iv_R2x2_2d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_iv_R2x2_2d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_ia_R2x2_2d =
+          std::make_shared<NamedItemArrayVariant>(std::make_shared<const ItemArrayVariant>(ia_R2x2_2d_new),
+                                                  "ia_R2x2_2d");
+
+        checkpointing::writeINamedDiscreteData("ia_R2x2_2d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_ia_R2x2_2d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_dfv_R_2d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(dfv_R_2d_new),
+                                                  "dfv_R_2d");
+
+        checkpointing::writeINamedDiscreteData("dfv_R_2d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_dfv_R_2d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        auto new_mesh_3d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_3d));
+        auto new_mesh_3d                = new_mesh_3d_v->get<const Mesh<3>>();
+        const auto& new_connectivity_3d = new_mesh_3d->connectivity();
+
+        DiscreteFunctionP0<const R3> df_R3_3d_new{new_mesh_3d, CellValue<const R3>{new_connectivity_3d,
+                                                                                   df_R3_3d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R1x1> df_R1x1_3d_new{new_mesh_3d,
+                                                      CellValue<const R1x1>{new_connectivity_3d,
+                                                                            df_R1x1_3d.cellValues().arrayView()}};
+        DiscreteFunctionP0<const R3x3> df_R3x3_3d_new{new_mesh_3d,
+                                                      CellValue<const R3x3>{new_connectivity_3d,
+                                                                            df_R3x3_3d.cellValues().arrayView()}};
+
+        EdgeValue<const R3x3> iv_R3x3_3d_new{new_connectivity_3d, iv_R3x3_3d.arrayView()};
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R3_3d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R3_3d_new),
+                                                  "df_R3_3d");
+
+        checkpointing::writeINamedDiscreteData("df_R3_3d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R3_3d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R1x1_3d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R1x1_3d_new),
+                                                  "df_R1x1_3d");
+
+        checkpointing::writeINamedDiscreteData("df_R1x1_3d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R1x1_3d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_df_R3x3_3d =
+          std::make_shared<NamedDiscreteFunction>(std::make_shared<const DiscreteFunctionVariant>(df_R3x3_3d_new),
+                                                  "df_R3x3_3d");
+
+        checkpointing::writeINamedDiscreteData("df_R3x3_3d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_df_R3x3_3d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        std::shared_ptr<const INamedDiscreteData> i_named_iv_R3x3_3d =
+          std::make_shared<NamedItemValueVariant>(std::make_shared<const ItemValueVariant>(iv_R3x3_3d_new),
+                                                  "iv_R3x3_3d");
+        checkpointing::writeINamedDiscreteData("iv_R3x3_3d",
+                                               EmbeddedData{std::make_shared<DataHandlerT>(i_named_iv_R3x3_3d)}, file,
+                                               checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+      {   // Read
+        auto e_df_R_1d = checkpointing::readINamedDiscreteData("df_R_1d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R_1d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R_1d", e_df_R_1d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R_1d, e_df_R_1d);
+
+        auto e_df_R1_1d = checkpointing::readINamedDiscreteData("df_R1_1d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R1_1d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R1_1d", e_df_R1_1d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R1_1d, e_df_R1_1d);
+
+        auto e_df_R2_1d = checkpointing::readINamedDiscreteData("df_R2_1d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R2_1d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R2_1d", e_df_R2_1d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R2_1d, e_df_R2_1d);
+
+        auto e_df_R3_2d = checkpointing::readINamedDiscreteData("df_R3_2d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R3_2d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R3_2d", e_df_R3_2d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R3_2d, e_df_R3_2d);
+
+        auto e_df_R2x2_2d = checkpointing::readINamedDiscreteData("df_R2x2_2d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R2x2_2d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R2x2_2d", e_df_R2x2_2d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R2x2_2d, e_df_R2x2_2d);
+
+        auto e_iv_R2x2_2d = checkpointing::readINamedDiscreteData("iv_R2x2_2d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::item_value, e_iv_R2x2_2d);
+        test_only::INamedDiscreteData_check_is_same_name("iv_R2x2_2d", e_iv_R2x2_2d);
+        test_only::INamedDiscreteData_check_is_same_data(iv_R2x2_2d, e_iv_R2x2_2d);
+
+        auto e_ia_R2x2_2d = checkpointing::readINamedDiscreteData("ia_R2x2_2d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::item_array, e_ia_R2x2_2d);
+        test_only::INamedDiscreteData_check_is_same_name("ia_R2x2_2d", e_ia_R2x2_2d);
+        test_only::INamedDiscreteData_check_is_same_data(ia_R2x2_2d, e_ia_R2x2_2d);
+
+        auto e_dfv_R_2d = checkpointing::readINamedDiscreteData("dfv_R_2d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_dfv_R_2d);
+        test_only::INamedDiscreteData_check_is_same_name("dfv_R_2d", e_dfv_R_2d);
+        test_only::INamedDiscreteData_check_is_same_data(dfv_R_2d, e_dfv_R_2d);
+
+        auto e_df_R3_3d = checkpointing::readINamedDiscreteData("df_R3_3d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R3_3d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R3_3d", e_df_R3_3d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R3_3d, e_df_R3_3d);
+
+        auto e_iv_R3x3_3d = checkpointing::readINamedDiscreteData("iv_R3x3_3d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::item_value, e_iv_R3x3_3d);
+        test_only::INamedDiscreteData_check_is_same_name("iv_R3x3_3d", e_iv_R3x3_3d);
+        test_only::INamedDiscreteData_check_is_same_data(iv_R3x3_3d, e_iv_R3x3_3d);
+
+        auto e_df_R1x1_3d = checkpointing::readINamedDiscreteData("df_R1x1_3d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R1x1_3d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R1x1_3d", e_df_R1x1_3d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R1x1_3d, e_df_R1x1_3d);
+
+        auto e_df_R3x3_3d = checkpointing::readINamedDiscreteData("df_R3x3_3d", symbol_table_group);
+        test_only::INamedDiscreteData_check_is_same_type(INamedDiscreteData::Type::discrete_function, e_df_R3x3_3d);
+        test_only::INamedDiscreteData_check_is_same_name("df_R3x3_3d", e_df_R3x3_3d);
+        test_only::INamedDiscreteData_check_is_same_data(df_R3x3_3d, e_df_R3x3_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IQuadratureDescriptor.cpp b/tests/test_checkpointing_IQuadratureDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..01e040496e846eb691be50f0b4d71bcbbeb312ff
--- /dev/null
+++ b/tests/test_checkpointing_IQuadratureDescriptor.cpp
@@ -0,0 +1,101 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <analysis/GaussLegendreQuadratureDescriptor.hpp>
+#include <analysis/GaussLobattoQuadratureDescriptor.hpp>
+#include <analysis/GaussQuadratureDescriptor.hpp>
+#include <analysis/IQuadratureDescriptor.hpp>
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/checkpointing/ReadIQuadratureDescriptor.hpp>
+#include <utils/checkpointing/WriteIQuadratureDescriptor.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IQuadratureDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IQuadratureDescriptor")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_gauss_quadrature_descriptor = std::make_shared<GaussQuadratureDescriptor>(3);
+      checkpointing::writeIQuadratureDescriptor("gauss",
+                                                EmbeddedData{std::make_shared<DataHandler<const IQuadratureDescriptor>>(
+                                                  p_gauss_quadrature_descriptor)},
+                                                file, useless_group, symbol_table_group);
+
+      auto p_gauss_legendre_quadrature_descriptor = std::make_shared<GaussLegendreQuadratureDescriptor>(7);
+      checkpointing::writeIQuadratureDescriptor("gauss_legendre",
+                                                EmbeddedData{std::make_shared<DataHandler<const IQuadratureDescriptor>>(
+                                                  p_gauss_legendre_quadrature_descriptor)},
+                                                file, useless_group, symbol_table_group);
+
+      auto p_gauss_lobatto_quadrature_descriptor = std::make_shared<GaussLobattoQuadratureDescriptor>(2);
+      checkpointing::writeIQuadratureDescriptor("gauss_lobatto",
+                                                EmbeddedData{std::make_shared<DataHandler<const IQuadratureDescriptor>>(
+                                                  p_gauss_lobatto_quadrature_descriptor)},
+                                                file, useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_gauss_quadrature_descriptor =
+        checkpointing::readIQuadratureDescriptor("gauss", symbol_table_group);
+
+      EmbeddedData read_gauss_legendre_quadrature_descriptor =
+        checkpointing::readIQuadratureDescriptor("gauss_legendre", symbol_table_group);
+
+      EmbeddedData read_gauss_lobatto_quadrature_descriptor =
+        checkpointing::readIQuadratureDescriptor("gauss_lobatto", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IQuadratureDescriptor& {
+        return *dynamic_cast<const DataHandler<const IQuadratureDescriptor>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_gauss_quadrature_descriptor));
+      REQUIRE_NOTHROW(get_value(read_gauss_legendre_quadrature_descriptor));
+      REQUIRE_NOTHROW(get_value(read_gauss_lobatto_quadrature_descriptor));
+
+      REQUIRE_NOTHROW(dynamic_cast<const GaussQuadratureDescriptor&>(get_value(read_gauss_quadrature_descriptor)));
+      REQUIRE_NOTHROW(
+        dynamic_cast<const GaussLegendreQuadratureDescriptor&>(get_value(read_gauss_legendre_quadrature_descriptor)));
+      REQUIRE_NOTHROW(
+        dynamic_cast<const GaussLobattoQuadratureDescriptor&>(get_value(read_gauss_lobatto_quadrature_descriptor)));
+
+      REQUIRE(get_value(read_gauss_quadrature_descriptor).type() == QuadratureType::Gauss);
+      REQUIRE(get_value(read_gauss_quadrature_descriptor).degree() == 3);
+      REQUIRE(get_value(read_gauss_legendre_quadrature_descriptor).type() == QuadratureType::GaussLegendre);
+      REQUIRE(get_value(read_gauss_legendre_quadrature_descriptor).degree() == 7);
+      REQUIRE(get_value(read_gauss_lobatto_quadrature_descriptor).type() == QuadratureType::GaussLobatto);
+      REQUIRE(get_value(read_gauss_lobatto_quadrature_descriptor).degree() == 2);
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IWriter.cpp b/tests/test_checkpointing_IWriter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f14156544e9896c3a59d0b8691fee8f78f475253
--- /dev/null
+++ b/tests/test_checkpointing_IWriter.cpp
@@ -0,0 +1,143 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <output/GnuplotWriter.hpp>
+#include <output/GnuplotWriter1D.hpp>
+#include <output/GnuplotWriterRaw.hpp>
+#include <output/NamedDiscreteFunction.hpp>
+#include <output/VTKWriter.hpp>
+#include <scheme/DiscreteFunctionP0.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <utils/checkpointing/ReadIWriter.hpp>
+#include <utils/checkpointing/WriteIWriter.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IWriter", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IWriter")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_gnuplot_writer = std::make_shared<const GnuplotWriter>("gnuplot_basename");
+      checkpointing::writeIWriter("gnuplot",
+                                  EmbeddedData{std::make_shared<DataHandler<const IWriter>>(p_gnuplot_writer)}, file,
+                                  useless_group, symbol_table_group);
+
+      auto p_gnuplot_writer_1d = std::make_shared<const GnuplotWriter1D>("gnuplot_1d_basename", 1.21);
+      p_gnuplot_writer_1d->periodManager().value().setSaveTime(2);
+      checkpointing::writeIWriter("gnuplot_1d",
+                                  EmbeddedData{std::make_shared<DataHandler<const IWriter>>(p_gnuplot_writer_1d)}, file,
+                                  useless_group, symbol_table_group);
+
+      auto p_gnuplot_writer_raw = std::make_shared<const GnuplotWriterRaw>("gnuplot_raw_basename", 1.18);
+      p_gnuplot_writer_raw->periodManager().value().setSaveTime(2.3);
+      checkpointing::writeIWriter("gnuplot_raw",
+                                  EmbeddedData{std::make_shared<DataHandler<const IWriter>>(p_gnuplot_writer_raw)},
+                                  file, useless_group, symbol_table_group);
+
+      const std::string vtk_filename = path / "vtk_example";
+
+      auto mesh_v = MeshDataBaseForTests::get().cartesian1DMesh();
+
+      DiscreteFunctionP0<double> fh{mesh_v};
+      fh.fill(0);
+      std::shared_ptr<const DiscreteFunctionVariant> discrete_function = std::make_shared<DiscreteFunctionVariant>(fh);
+
+      std::shared_ptr<const INamedDiscreteData> value =
+        std::make_shared<const NamedDiscreteFunction>(discrete_function, "fh");
+
+      auto p_vtk_writer = std::make_shared<const VTKWriter>(vtk_filename, 0.02);
+
+      p_vtk_writer->writeIfNeeded({value}, 1);
+
+      checkpointing::writeIWriter("vtk", EmbeddedData{std::make_shared<DataHandler<const IWriter>>(p_vtk_writer)}, file,
+                                  useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_gnuplot_writer     = checkpointing::readIWriter("gnuplot", symbol_table_group);
+      EmbeddedData read_gnuplot_writer_1d  = checkpointing::readIWriter("gnuplot_1d", symbol_table_group);
+      EmbeddedData read_gnuplot_writer_raw = checkpointing::readIWriter("gnuplot_raw", symbol_table_group);
+      EmbeddedData read_vtk_writer         = checkpointing::readIWriter("vtk", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IWriter& {
+        return *dynamic_cast<const DataHandler<const IWriter>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_gnuplot_writer));
+      REQUIRE_NOTHROW(dynamic_cast<const GnuplotWriter&>(get_value(read_gnuplot_writer)));
+      const GnuplotWriter& read_gp_writer = dynamic_cast<const GnuplotWriter&>(get_value(read_gnuplot_writer));
+      REQUIRE(read_gp_writer.type() == IWriter::Type::gnuplot);
+      REQUIRE(read_gp_writer.baseFilename() == "gnuplot_basename");
+      REQUIRE(not read_gp_writer.periodManager().has_value());
+      REQUIRE(not read_gp_writer.signature().has_value());
+
+      REQUIRE_NOTHROW(get_value(read_gnuplot_writer_1d));
+      REQUIRE_NOTHROW(dynamic_cast<const GnuplotWriter1D&>(get_value(read_gnuplot_writer_1d)));
+      const GnuplotWriter1D& read_gp_writer_1d =
+        dynamic_cast<const GnuplotWriter1D&>(get_value(read_gnuplot_writer_1d));
+      REQUIRE(read_gp_writer_1d.type() == IWriter::Type::gnuplot_1d);
+      REQUIRE(read_gp_writer_1d.baseFilename() == "gnuplot_1d_basename");
+      REQUIRE(read_gp_writer_1d.periodManager().has_value());
+      REQUIRE(read_gp_writer_1d.periodManager().value().timePeriod() == 1.21);
+      REQUIRE(read_gp_writer_1d.periodManager().value().nextTime() == Catch::Approx(3.21));
+      REQUIRE(not read_gp_writer_1d.signature().has_value());
+
+      REQUIRE_NOTHROW(get_value(read_gnuplot_writer_raw));
+      REQUIRE_NOTHROW(dynamic_cast<const GnuplotWriterRaw&>(get_value(read_gnuplot_writer_raw)));
+      const GnuplotWriterRaw& read_gp_writer_raw =
+        dynamic_cast<const GnuplotWriterRaw&>(get_value(read_gnuplot_writer_raw));
+      REQUIRE(read_gp_writer_raw.type() == IWriter::Type::gnuplot_raw);
+      REQUIRE(read_gp_writer_raw.baseFilename() == "gnuplot_raw_basename");
+      REQUIRE(read_gp_writer_raw.periodManager().has_value());
+      REQUIRE(read_gp_writer_raw.periodManager().value().timePeriod() == 1.18);
+      REQUIRE(read_gp_writer_raw.periodManager().value().nextTime() == Catch::Approx(3.48));
+      REQUIRE(not read_gp_writer_raw.signature().has_value());
+
+      REQUIRE_NOTHROW(get_value(read_vtk_writer));
+      REQUIRE_NOTHROW(dynamic_cast<const VTKWriter&>(get_value(read_vtk_writer)));
+      const VTKWriter& read_vtk = dynamic_cast<const VTKWriter&>(get_value(read_vtk_writer));
+      REQUIRE(read_vtk.type() == IWriter::Type::vtk);
+      REQUIRE(read_vtk.baseFilename() == vtk_filename);
+      REQUIRE(read_vtk.periodManager().has_value());
+      REQUIRE(read_vtk.periodManager().value().timePeriod() == 0.02);
+      REQUIRE(read_vtk.periodManager().value().nextTime() == Catch::Approx(1.02));
+      REQUIRE(read_vtk.signature().has_value());
+      REQUIRE(read_vtk.signature().value() == p_vtk_writer->signature().value());
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_IZoneDescriptor.cpp b/tests/test_checkpointing_IZoneDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4b9e551063f09f108716a114331045cfe497e9f1
--- /dev/null
+++ b/tests/test_checkpointing_IZoneDescriptor.cpp
@@ -0,0 +1,88 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/NamedZoneDescriptor.hpp>
+#include <mesh/NumberedZoneDescriptor.hpp>
+#include <utils/checkpointing/ReadIZoneDescriptor.hpp>
+#include <utils/checkpointing/WriteIZoneDescriptor.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_IZoneDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("IZoneDescriptor")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_named_zone_descriptor = std::make_shared<const NamedZoneDescriptor>("zone_name1");
+      checkpointing::writeIZoneDescriptor("named_zone_descriptor",
+                                          EmbeddedData{std::make_shared<DataHandler<const IZoneDescriptor>>(
+                                            p_named_zone_descriptor)},
+                                          file, useless_group, symbol_table_group);
+
+      auto p_numbered_zone_descriptor = std::make_shared<const NumberedZoneDescriptor>(3);
+      checkpointing::writeIZoneDescriptor("numbered_zone_descriptor",
+                                          EmbeddedData{std::make_shared<DataHandler<const IZoneDescriptor>>(
+                                            p_numbered_zone_descriptor)},
+                                          file, useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_named_zone_descriptor =
+        checkpointing::readIZoneDescriptor("named_zone_descriptor", symbol_table_group);
+
+      EmbeddedData read_numbered_zone_descriptor =
+        checkpointing::readIZoneDescriptor("numbered_zone_descriptor", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const IZoneDescriptor& {
+        return *dynamic_cast<const DataHandler<const IZoneDescriptor>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_named_zone_descriptor));
+      REQUIRE_NOTHROW(get_value(read_numbered_zone_descriptor));
+
+      REQUIRE(get_value(read_named_zone_descriptor).type() == IZoneDescriptor::Type::named);
+      REQUIRE(get_value(read_numbered_zone_descriptor).type() == IZoneDescriptor::Type::numbered);
+
+      REQUIRE_NOTHROW(dynamic_cast<const NamedZoneDescriptor&>(get_value(read_named_zone_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const NumberedZoneDescriptor&>(get_value(read_numbered_zone_descriptor)));
+
+      auto& read_named    = dynamic_cast<const NamedZoneDescriptor&>(get_value(read_named_zone_descriptor));
+      auto& read_numbered = dynamic_cast<const NumberedZoneDescriptor&>(get_value(read_numbered_zone_descriptor));
+
+      REQUIRE(read_named.name() == p_named_zone_descriptor->name());
+      REQUIRE(read_numbered.number() == p_numbered_zone_descriptor->number());
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_ItemArray.cpp b/tests/test_checkpointing_ItemArray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f2a844e0333cab64adde0d6c48a197ac3d4032cb
--- /dev/null
+++ b/tests/test_checkpointing_ItemArray.cpp
@@ -0,0 +1,82 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/checkpointing/ReadItemArray.hpp>
+#include <utils/checkpointing/WriteItemArray.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_ItemArray", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("ItemArray")
+    {
+      HighFive::Group checkpoint_group = file.createGroup("checkpoint_group");
+      HighFive::Group useless_group;
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      CellArray<double> item_array{mesh_2d->connectivity(), 3};
+      item_array.fill(0);
+
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < 3; ++i) {
+          item_array[cell_id][i] = (1. * std::rand()) / (1. / 3 * RAND_MAX);
+        }
+      }
+
+      checkpointing::write(checkpoint_group, "item_array", item_array);
+
+      file.flush();
+
+      auto is_same = [](const auto& a, const auto& b) {
+        bool same = true;
+        for (size_t i = 0; i < a.numberOfRows(); ++i) {
+          for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+            if (a[i][j] != b[i][j]) {
+              same = false;
+            }
+          }
+        }
+        return parallel::allReduceAnd(same);
+      };
+
+      CellArray<double> read_item_array =
+        checkpointing::readItemArray<double, ItemType::cell>(checkpoint_group, "item_array", mesh_2d->connectivity());
+      REQUIRE(is_same(item_array.tableView(), read_item_array.tableView()));
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_ItemArrayVariant.cpp b/tests/test_checkpointing_ItemArrayVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..847d6d0e06a41437aa1f6b854457648673ce9712
--- /dev/null
+++ b/tests/test_checkpointing_ItemArrayVariant.cpp
@@ -0,0 +1,346 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/ItemArrayVariant.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadItemArrayVariant.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteItemArrayVariant.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+template <typename DataType, ItemType item_type>
+PUGS_INLINE void
+check_is_same(const ItemArray<DataType, item_type>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_table = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    if ((a.numberOfRows() == b.numberOfRows()) and (a.numberOfColumns() == b.numberOfColumns())) {
+      for (size_t i = 0; i < a.numberOfRows(); ++i) {
+        for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+          same &= (a(i, j) == b(i, j));
+        }
+      }
+    }
+
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const ItemArrayVariant>&>(e_read_data.get()));
+
+  std::shared_ptr<const ItemArrayVariant> p_new_data_v =
+    dynamic_cast<const DataHandler<const ItemArrayVariant>&>(e_read_data.get()).data_ptr();
+
+  using ItemTypeT = ItemArray<const DataType, item_type>;
+
+  ItemTypeT read_data = p_new_data_v->get<ItemTypeT>();
+
+  switch (reference.connectivity_ptr()->dimension()) {
+  case 1: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<1>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<1>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 2: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<2>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<2>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 3: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<3>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<3>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid connectivity dimension");
+  }
+  }
+
+  REQUIRE(same_table(reference.tableView(), read_data.tableView()));
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_ItemArrayVariant", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+    SECTION("Connectivity")
+    {
+      using R1   = TinyVector<1>;
+      using R2   = TinyVector<2>;
+      using R3   = TinyVector<3>;
+      using R1x1 = TinyMatrix<1>;
+      using R2x2 = TinyMatrix<2>;
+      using R3x3 = TinyMatrix<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      CellArray<bool> cell_B_1d{mesh_1d->connectivity(), 3};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < cell_B_1d.sizeOfArrays(); ++i) {
+          cell_B_1d[cell_id][i] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells())) % 2;
+        }
+      }
+
+      CellArray<uint64_t> cell_N_1d{mesh_1d->connectivity(), 2};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < cell_N_1d.sizeOfArrays(); ++i) {
+          cell_N_1d[cell_id][i] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells()));
+        }
+      }
+
+      NodeArray<int64_t> node_Z_1d{mesh_1d->connectivity(), 1};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        for (size_t i = 0; i < node_Z_1d.sizeOfArrays(); ++i) {
+          node_Z_1d[node_id][i] = 100 * (std::rand() - RAND_MAX / 2.) / (RAND_MAX / mesh_1d->numberOfNodes());
+        }
+      }
+
+      NodeArray<double> node_R_1d{mesh_1d->connectivity(), 4};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        for (size_t i = 0; i < node_R_1d.sizeOfArrays(); ++i) {
+          node_R_1d[node_id][i] = std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes());
+        }
+      }
+
+      CellArray<R1> cell_R1_1d{mesh_1d->connectivity(), 3};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        for (size_t i = 0; i < cell_R1_1d.sizeOfArrays(); ++i) {
+          cell_R1_1d[cell_id][i] = R1{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+        }
+      }
+
+      NodeArray<R2> node_R2_1d{mesh_1d->connectivity(), 2};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        for (size_t i = 0; i < node_R2_1d.sizeOfArrays(); ++i) {
+          node_R2_1d[node_id][i] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes()),
+                                      std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes())};
+        }
+      }
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      FaceArray<R3> face_R3_2d{mesh_2d->connectivity(), 3};
+      for (FaceId face_id = 0; face_id < mesh_2d->numberOfFaces(); ++face_id) {
+        for (size_t i = 0; i < face_R3_2d.sizeOfArrays(); ++i) {
+          face_R3_2d[face_id][i] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                      std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                      std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces())};
+        }
+      }
+
+      NodeArray<R2x2> node_R2x2_2d{mesh_2d->connectivity(), 4};
+      for (NodeId node_id = 0; node_id < mesh_2d->numberOfNodes(); ++node_id) {
+        for (size_t i = 0; i < node_R2x2_2d.sizeOfArrays(); ++i) {
+          node_R2x2_2d[node_id][i] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes())};
+        }
+      }
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      EdgeArray<R3> edge_R3_3d{mesh_3d->connectivity(), 2};
+      for (EdgeId edge_id = 0; edge_id < mesh_3d->numberOfEdges(); ++edge_id) {
+        for (size_t i = 0; i < edge_R3_3d.sizeOfArrays(); ++i) {
+          edge_R3_3d[edge_id][i] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                      std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                      std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges())};
+        }
+      }
+
+      FaceArray<R1x1> face_R1x1_3d{mesh_3d->connectivity(), 1};
+      for (FaceId face_id = 0; face_id < mesh_3d->numberOfFaces(); ++face_id) {
+        for (size_t i = 0; i < face_R1x1_3d.sizeOfArrays(); ++i) {
+          face_R1x1_3d[face_id][i] = R1x1{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfFaces())};
+        }
+      }
+
+      NodeArray<R3x3> node_R3x3_3d{mesh_3d->connectivity(), 2};
+      for (NodeId node_id = 0; node_id < mesh_3d->numberOfNodes(); ++node_id) {
+        for (size_t i = 0; i < node_R3x3_3d.sizeOfArrays(); ++i) {
+          node_R3x3_3d[node_id][i] = R3x3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                          std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes())};
+        }
+      }
+
+      {   // Write
+        using DataHandlerT = DataHandler<const ItemArrayVariant>;
+
+        auto new_connectivity_1d = test_only::duplicateConnectivity(mesh_1d->connectivity());
+
+        CellArray<const bool> cell_B_1d_new{*new_connectivity_1d, cell_B_1d.tableView()};
+        NodeArray<const int64_t> node_Z_1d_new{*new_connectivity_1d, node_Z_1d.tableView()};
+        CellArray<const uint64_t> cell_N_1d_new{*new_connectivity_1d, cell_N_1d.tableView()};
+        NodeArray<const double> node_R_1d_new{*new_connectivity_1d, node_R_1d.tableView()};
+        CellArray<const R1> cell_R1_1d_new{*new_connectivity_1d, cell_R1_1d.tableView()};
+        NodeArray<const R2> node_R2_1d_new{*new_connectivity_1d, node_R2_1d.tableView()};
+
+        checkpointing::writeItemArrayVariant("cell_B_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(cell_B_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("cell_N_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(cell_N_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("node_Z_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(node_Z_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("node_R_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(node_R_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("cell_R1_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(cell_R1_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("node_R2_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(node_R2_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_2d = test_only::duplicateConnectivity(mesh_2d->connectivity());
+
+        FaceArray<const R3> face_R3_2d_new{*new_connectivity_2d, face_R3_2d.tableView()};
+        NodeArray<const R2x2> node_R2x2_2d_new{*new_connectivity_2d, node_R2x2_2d.tableView()};
+
+        checkpointing::writeItemArrayVariant("face_R3_2d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(face_R3_2d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("node_R2x2_2d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(node_R2x2_2d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_3d = test_only::duplicateConnectivity(mesh_3d->connectivity());
+
+        EdgeArray<const R3> edge_R3_3d_new{*new_connectivity_3d, edge_R3_3d.tableView()};
+        FaceArray<const R1x1> face_R1x1_3d_new{*new_connectivity_3d, face_R1x1_3d.tableView()};
+        NodeArray<const R3x3> node_R3x3_3d_new{*new_connectivity_3d, node_R3x3_3d.tableView()};
+
+        checkpointing::writeItemArrayVariant("edge_R3_3d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(edge_R3_3d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("face_R1x1_3d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(face_R1x1_3d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemArrayVariant("node_R3x3_3d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemArrayVariant>(node_R3x3_3d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      {   // Read
+        auto e_cell_B_1d = checkpointing::readItemArrayVariant("cell_B_1d", symbol_table_group);
+        test_only::check_is_same(cell_B_1d, e_cell_B_1d);
+
+        auto e_cell_N_1d = checkpointing::readItemArrayVariant("cell_N_1d", symbol_table_group);
+        test_only::check_is_same(cell_N_1d, e_cell_N_1d);
+
+        auto e_node_Z_1d = checkpointing::readItemArrayVariant("node_Z_1d", symbol_table_group);
+        test_only::check_is_same(node_Z_1d, e_node_Z_1d);
+
+        auto e_node_R_1d = checkpointing::readItemArrayVariant("node_R_1d", symbol_table_group);
+        test_only::check_is_same(node_R_1d, e_node_R_1d);
+
+        auto e_cell_R1_1d = checkpointing::readItemArrayVariant("cell_R1_1d", symbol_table_group);
+        test_only::check_is_same(cell_R1_1d, e_cell_R1_1d);
+
+        auto e_node_R2_1d = checkpointing::readItemArrayVariant("node_R2_1d", symbol_table_group);
+        test_only::check_is_same(node_R2_1d, e_node_R2_1d);
+
+        auto e_face_R3_2d = checkpointing::readItemArrayVariant("face_R3_2d", symbol_table_group);
+        test_only::check_is_same(face_R3_2d, e_face_R3_2d);
+
+        auto e_node_R2x2_2d = checkpointing::readItemArrayVariant("node_R2x2_2d", symbol_table_group);
+        test_only::check_is_same(node_R2x2_2d, e_node_R2x2_2d);
+
+        auto e_edge_R3_3d = checkpointing::readItemArrayVariant("edge_R3_3d", symbol_table_group);
+        test_only::check_is_same(edge_R3_3d, e_edge_R3_3d);
+
+        auto e_face_R1x1_3d = checkpointing::readItemArrayVariant("face_R1x1_3d", symbol_table_group);
+        test_only::check_is_same(face_R1x1_3d, e_face_R1x1_3d);
+
+        auto e_node_R3x3_3d = checkpointing::readItemArrayVariant("node_R3x3_3d", symbol_table_group);
+        test_only::check_is_same(node_R3x3_3d, e_node_R3x3_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_ItemType.cpp b/tests/test_checkpointing_ItemType.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..12b80ca4d6d0fadbc9988d98510ef4c851f2dfba
--- /dev/null
+++ b/tests/test_checkpointing_ItemType.cpp
@@ -0,0 +1,92 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/ItemType.hpp>
+#include <utils/checkpointing/ReadItemType.hpp>
+#include <utils/checkpointing/WriteItemType.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_ItemType", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("ItemType")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_cell_type = std::make_shared<const ItemType>(ItemType::cell);
+      checkpointing::writeItemType("cell_type",
+                                   EmbeddedData{std::make_shared<DataHandler<const ItemType>>(p_cell_type)}, file,
+                                   useless_group, symbol_table_group);
+
+      auto p_face_type = std::make_shared<const ItemType>(ItemType::face);
+      checkpointing::writeItemType("face_type",
+                                   EmbeddedData{std::make_shared<DataHandler<const ItemType>>(p_face_type)}, file,
+                                   useless_group, symbol_table_group);
+
+      auto p_edge_type = std::make_shared<const ItemType>(ItemType::edge);
+      checkpointing::writeItemType("edge_type",
+                                   EmbeddedData{std::make_shared<DataHandler<const ItemType>>(p_edge_type)}, file,
+                                   useless_group, symbol_table_group);
+
+      auto p_node_type = std::make_shared<const ItemType>(ItemType::node);
+      checkpointing::writeItemType("node_type",
+                                   EmbeddedData{std::make_shared<DataHandler<const ItemType>>(p_node_type)}, file,
+                                   useless_group, symbol_table_group);
+
+      file.flush();
+
+      EmbeddedData read_cell_type = checkpointing::readItemType("cell_type", symbol_table_group);
+
+      EmbeddedData read_face_type = checkpointing::readItemType("face_type", symbol_table_group);
+
+      EmbeddedData read_edge_type = checkpointing::readItemType("edge_type", symbol_table_group);
+
+      EmbeddedData read_node_type = checkpointing::readItemType("node_type", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const ItemType& {
+        return *dynamic_cast<const DataHandler<const ItemType>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_cell_type));
+      REQUIRE_NOTHROW(get_value(read_face_type));
+      REQUIRE_NOTHROW(get_value(read_edge_type));
+      REQUIRE_NOTHROW(get_value(read_node_type));
+
+      REQUIRE(get_value(read_cell_type) == ItemType::cell);
+      REQUIRE(get_value(read_face_type) == ItemType::face);
+      REQUIRE(get_value(read_edge_type) == ItemType::edge);
+      REQUIRE(get_value(read_node_type) == ItemType::node);
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_ItemValue.cpp b/tests/test_checkpointing_ItemValue.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..55c2cb3ea409ff4d01d2c84bfe2e4e18a6a47688
--- /dev/null
+++ b/tests/test_checkpointing_ItemValue.cpp
@@ -0,0 +1,72 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/checkpointing/ReadItemValue.hpp>
+#include <utils/checkpointing/WriteItemValue.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_ItemValue", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("ItemValue")
+    {
+      HighFive::Group checkpoint_group = file.createGroup("checkpoint_group");
+      HighFive::Group useless_group;
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      auto xr = mesh_2d->xr();
+      checkpointing::write(checkpoint_group, "xr", xr);
+
+      file.flush();
+
+      auto is_same = [](const auto& a, const auto& b) {
+        bool same = true;
+        for (size_t i = 0; i < a.size(); ++i) {
+          if (a[i] != b[i]) {
+            same = false;
+          }
+        }
+        return parallel::allReduceAnd(same);
+      };
+
+      NodeValue<TinyVector<2>> read_xr =
+        checkpointing::readItemValue<TinyVector<2>, ItemType::node>(checkpoint_group, "xr", mesh_2d->connectivity());
+      REQUIRE(is_same(xr.arrayView(), read_xr.arrayView()));
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_ItemValueVariant.cpp b/tests/test_checkpointing_ItemValueVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..08ed52a93cb1e4778ff0f1cf337ee1f186911fa2
--- /dev/null
+++ b/tests/test_checkpointing_ItemValueVariant.cpp
@@ -0,0 +1,319 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/ItemValueVariant.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadItemValueVariant.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteItemValueVariant.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+template <typename DataType, ItemType item_type>
+PUGS_INLINE void
+check_is_same(const ItemValue<DataType, item_type>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const ItemValueVariant>&>(e_read_data.get()));
+
+  std::shared_ptr<const ItemValueVariant> p_new_data_v =
+    dynamic_cast<const DataHandler<const ItemValueVariant>&>(e_read_data.get()).data_ptr();
+
+  using ItemTypeT = ItemValue<const DataType, item_type>;
+
+  ItemTypeT read_data = p_new_data_v->get<ItemTypeT>();
+
+  switch (reference.connectivity_ptr()->dimension()) {
+  case 1: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<1>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<1>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 2: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<2>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<2>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 3: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<3>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<3>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid connectivity dimension");
+  }
+  }
+
+  REQUIRE(same_value(reference.arrayView(), read_data.arrayView()));
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_ItemValueVariant", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+    SECTION("Connectivity")
+    {
+      using R1   = TinyVector<1>;
+      using R2   = TinyVector<2>;
+      using R3   = TinyVector<3>;
+      using R1x1 = TinyMatrix<1>;
+      using R2x2 = TinyMatrix<2>;
+      using R3x3 = TinyMatrix<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      CellValue<bool> cell_B_1d{mesh_1d->connectivity()};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        cell_B_1d[cell_id] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells())) % 2;
+      }
+
+      CellValue<uint64_t> cell_N_1d{mesh_1d->connectivity()};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        cell_N_1d[cell_id] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells()));
+      }
+
+      NodeValue<int64_t> node_Z_1d{mesh_1d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        node_Z_1d[node_id] = 100 * (std::rand() - RAND_MAX / 2.) / (RAND_MAX / mesh_1d->numberOfNodes());
+      }
+
+      NodeValue<double> node_R_1d{mesh_1d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        node_R_1d[node_id] = std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes());
+      }
+
+      CellValue<R1> cell_R1_1d{mesh_1d->connectivity()};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        cell_R1_1d[cell_id] = R1{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+      }
+
+      NodeValue<R2> node_R2_1d{mesh_1d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        node_R2_1d[node_id] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes()),
+                                 std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes())};
+      }
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      FaceValue<R3> face_R3_2d{mesh_2d->connectivity()};
+      for (FaceId face_id = 0; face_id < mesh_2d->numberOfFaces(); ++face_id) {
+        face_R3_2d[face_id] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                 std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                 std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces())};
+      }
+
+      NodeValue<R2x2> node_R2x2_2d{mesh_2d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_2d->numberOfNodes(); ++node_id) {
+        node_R2x2_2d[node_id] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes())};
+      }
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      EdgeValue<R3> edge_R3_3d{mesh_3d->connectivity()};
+      for (EdgeId edge_id = 0; edge_id < mesh_3d->numberOfEdges(); ++edge_id) {
+        edge_R3_3d[edge_id] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                 std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                 std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges())};
+      }
+
+      FaceValue<R1x1> face_R1x1_3d{mesh_3d->connectivity()};
+      for (FaceId face_id = 0; face_id < mesh_3d->numberOfFaces(); ++face_id) {
+        face_R1x1_3d[face_id] = R1x1{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfFaces())};
+      }
+
+      NodeValue<R3x3> node_R3x3_3d{mesh_3d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_3d->numberOfNodes(); ++node_id) {
+        node_R3x3_3d[node_id] = R3x3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                     std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes())};
+      }
+
+      {   // Write
+        using DataHandlerT = DataHandler<const ItemValueVariant>;
+
+        auto new_connectivity_1d = test_only::duplicateConnectivity(mesh_1d->connectivity());
+
+        CellValue<const bool> cell_B_1d_new{*new_connectivity_1d, cell_B_1d.arrayView()};
+        NodeValue<const int64_t> node_Z_1d_new{*new_connectivity_1d, node_Z_1d.arrayView()};
+        CellValue<const uint64_t> cell_N_1d_new{*new_connectivity_1d, cell_N_1d.arrayView()};
+        NodeValue<const double> node_R_1d_new{*new_connectivity_1d, node_R_1d.arrayView()};
+        CellValue<const R1> cell_R1_1d_new{*new_connectivity_1d, cell_R1_1d.arrayView()};
+        NodeValue<const R2> node_R2_1d_new{*new_connectivity_1d, node_R2_1d.arrayView()};
+
+        checkpointing::writeItemValueVariant("cell_B_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(cell_B_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("cell_N_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(cell_N_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("node_Z_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(node_Z_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("node_R_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(node_R_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("cell_R1_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(cell_R1_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("node_R2_1d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(node_R2_1d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_2d = test_only::duplicateConnectivity(mesh_2d->connectivity());
+
+        FaceValue<const R3> face_R3_2d_new{*new_connectivity_2d, face_R3_2d.arrayView()};
+        NodeValue<const R2x2> node_R2x2_2d_new{*new_connectivity_2d, node_R2x2_2d.arrayView()};
+
+        checkpointing::writeItemValueVariant("face_R3_2d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(face_R3_2d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("node_R2x2_2d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(node_R2x2_2d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_3d = test_only::duplicateConnectivity(mesh_3d->connectivity());
+
+        EdgeValue<const R3> edge_R3_3d_new{*new_connectivity_3d, edge_R3_3d.arrayView()};
+        FaceValue<const R1x1> face_R1x1_3d_new{*new_connectivity_3d, face_R1x1_3d.arrayView()};
+        NodeValue<const R3x3> node_R3x3_3d_new{*new_connectivity_3d, node_R3x3_3d.arrayView()};
+
+        checkpointing::writeItemValueVariant("edge_R3_3d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(edge_R3_3d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("face_R1x1_3d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(face_R1x1_3d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeItemValueVariant("node_R3x3_3d",
+                                             EmbeddedData{std::make_shared<DataHandlerT>(
+                                               std::make_shared<const ItemValueVariant>(node_R3x3_3d_new))},
+                                             file, checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      {   // Read
+        auto e_cell_B_1d = checkpointing::readItemValueVariant("cell_B_1d", symbol_table_group);
+        test_only::check_is_same(cell_B_1d, e_cell_B_1d);
+
+        auto e_cell_N_1d = checkpointing::readItemValueVariant("cell_N_1d", symbol_table_group);
+        test_only::check_is_same(cell_N_1d, e_cell_N_1d);
+
+        auto e_node_Z_1d = checkpointing::readItemValueVariant("node_Z_1d", symbol_table_group);
+        test_only::check_is_same(node_Z_1d, e_node_Z_1d);
+
+        auto e_node_R_1d = checkpointing::readItemValueVariant("node_R_1d", symbol_table_group);
+        test_only::check_is_same(node_R_1d, e_node_R_1d);
+
+        auto e_cell_R1_1d = checkpointing::readItemValueVariant("cell_R1_1d", symbol_table_group);
+        test_only::check_is_same(cell_R1_1d, e_cell_R1_1d);
+
+        auto e_node_R2_1d = checkpointing::readItemValueVariant("node_R2_1d", symbol_table_group);
+        test_only::check_is_same(node_R2_1d, e_node_R2_1d);
+
+        auto e_face_R3_2d = checkpointing::readItemValueVariant("face_R3_2d", symbol_table_group);
+        test_only::check_is_same(face_R3_2d, e_face_R3_2d);
+
+        auto e_node_R2x2_2d = checkpointing::readItemValueVariant("node_R2x2_2d", symbol_table_group);
+        test_only::check_is_same(node_R2x2_2d, e_node_R2x2_2d);
+
+        auto e_edge_R3_3d = checkpointing::readItemValueVariant("edge_R3_3d", symbol_table_group);
+        test_only::check_is_same(edge_R3_3d, e_edge_R3_3d);
+
+        auto e_face_R1x1_3d = checkpointing::readItemValueVariant("face_R1x1_3d", symbol_table_group);
+        test_only::check_is_same(face_R1x1_3d, e_face_R1x1_3d);
+
+        auto e_node_R3x3_3d = checkpointing::readItemValueVariant("node_R3x3_3d", symbol_table_group);
+        test_only::check_is_same(node_R3x3_3d, e_node_R3x3_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_Mesh.cpp b/tests/test_checkpointing_Mesh.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8d9a27ea40585c1b7253881fd93d7a3ba3404b5e
--- /dev/null
+++ b/tests/test_checkpointing_Mesh.cpp
@@ -0,0 +1,159 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadMesh.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteMesh.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Mesh_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Mesh", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+    const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+
+    SECTION("Mesh")
+    {
+      HighFive::Group checkpoint_group_0 = file.createGroup("checkpoint_0");
+      HighFive::Group symbol_table_0     = checkpoint_group_0.createGroup("symbol_table");
+
+      HighFive::Group checkpoint_group_1 = file.createGroup("checkpoint_1");
+      HighFive::Group symbol_table_1     = checkpoint_group_1.createGroup("symbol_table");
+
+      {   // Write
+        auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh();
+
+        // creates artificially holes in numbering
+        test_only::duplicateMesh(mesh_1d);
+
+        auto new_mesh_1d = test_only::duplicateMesh(mesh_1d);
+
+        using DataHandlerT = DataHandler<const MeshVariant>;
+
+        checkpointing::writeMesh("mesh_1d", EmbeddedData{std::make_shared<DataHandlerT>(new_mesh_1d)}, file,
+                                 checkpoint_group_0, symbol_table_0);
+        checkpointing::writeMesh("mesh_1d", EmbeddedData{std::make_shared<DataHandlerT>(new_mesh_1d)}, file,
+                                 checkpoint_group_1, symbol_table_1);
+
+        auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh();
+
+        // creates artificially holes in numbering
+        test_only::duplicateMesh(mesh_2d);
+        auto new_mesh_2d = test_only::duplicateMesh(mesh_2d);
+
+        checkpointing::writeMesh("mesh_2d", EmbeddedData{std::make_shared<DataHandlerT>(new_mesh_2d)}, file,
+                                 checkpoint_group_0, symbol_table_0);
+        checkpointing::writeMesh("mesh_2d", EmbeddedData{std::make_shared<DataHandlerT>(new_mesh_2d)}, file,
+                                 checkpoint_group_1, symbol_table_1);
+
+        HighFive::Group global_variables_group_0 = checkpoint_group_0.createGroup("singleton/global_variables");
+        global_variables_group_0.createAttribute("connectivity_id",
+                                                 GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group_0.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+
+        auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh();
+
+        // creates artificially holes in numbering
+        test_only::duplicateMesh(mesh_3d);
+
+        auto new_mesh_3d = test_only::duplicateMesh(mesh_3d);
+        checkpointing::writeMesh("mesh_3d", EmbeddedData{std::make_shared<DataHandlerT>(new_mesh_3d)}, file,
+                                 checkpoint_group_1, symbol_table_1);
+
+        // creates artificially holes in numbering
+        test_only::duplicateMesh(mesh_3d);
+        test_only::duplicateMesh(mesh_3d);
+        test_only::duplicateMesh(mesh_3d);
+
+        HighFive::Group global_variables_group_1 = checkpoint_group_1.createGroup("singleton/global_variables");
+        global_variables_group_1.createAttribute("connectivity_id",
+                                                 GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group_1.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group_1, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+      {   // Read
+
+        auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh();
+
+        EmbeddedData e_mesh_1d = checkpointing::readMesh("mesh_1d", symbol_table_1);
+
+        REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const MeshVariant>&>(e_mesh_1d.get()));
+
+        std::shared_ptr<const MeshVariant> read_mesh_1d =
+          dynamic_cast<const DataHandler<const MeshVariant>&>(e_mesh_1d.get()).data_ptr();
+
+        REQUIRE(test_only::isSameMesh(mesh_1d, read_mesh_1d));
+
+        auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh();
+
+        EmbeddedData e_mesh_2d = checkpointing::readMesh("mesh_2d", symbol_table_1);
+
+        REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const MeshVariant>&>(e_mesh_2d.get()));
+
+        std::shared_ptr<const MeshVariant> read_mesh_2d =
+          dynamic_cast<const DataHandler<const MeshVariant>&>(e_mesh_2d.get()).data_ptr();
+
+        REQUIRE(test_only::isSameMesh(mesh_2d, read_mesh_2d));
+
+        auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh();
+
+        EmbeddedData e_mesh_3d = checkpointing::readMesh("mesh_3d", symbol_table_1);
+
+        REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const MeshVariant>&>(e_mesh_3d.get()));
+
+        std::shared_ptr<const MeshVariant> read_mesh_3d =
+          dynamic_cast<const DataHandler<const MeshVariant>&>(e_mesh_3d.get()).data_ptr();
+
+        REQUIRE(test_only::isSameMesh(mesh_3d, read_mesh_3d));
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_OStream.cpp b/tests/test_checkpointing_OStream.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2dedc1e5e68e3038c70c890ccf4e9ce1803280ef
--- /dev/null
+++ b/tests/test_checkpointing_OStream.cpp
@@ -0,0 +1,74 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <language/utils/OFStream.hpp>
+#include <utils/checkpointing/ReadOStream.hpp>
+#include <utils/checkpointing/WriteOStream.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_OStream", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("OStream")
+    {
+      HighFive::Group symbol_table_group = file.createGroup("symbol_table");
+      HighFive::Group useless_group;
+
+      auto p_ofstream = std::make_shared<OFStream>("output_name");
+      checkpointing::writeOStream("ofstream", EmbeddedData{std::make_shared<DataHandler<const OStream>>(p_ofstream)},
+                                  file, useless_group, symbol_table_group);
+
+      auto p_ostream = std::make_shared<OStream>(std::cout);
+      REQUIRE_THROWS_WITH(checkpointing::writeOStream("ostream",
+                                                      EmbeddedData{
+                                                        std::make_shared<DataHandler<const OStream>>(p_ostream)},
+                                                      file, useless_group, symbol_table_group),
+                          "not implemented yet: std::ostream checkpoint");
+
+      file.flush();
+
+      EmbeddedData read_ofstream = checkpointing::readOStream("ofstream", symbol_table_group);
+
+      auto get_value = [](const EmbeddedData& embedded_data) -> const OStream& {
+        return *dynamic_cast<const DataHandler<const OStream>&>(embedded_data.get()).data_ptr();
+      };
+
+      REQUIRE_NOTHROW(get_value(read_ofstream));
+
+      REQUIRE_NOTHROW(dynamic_cast<const OFStream&>(get_value(read_ofstream)));
+
+      REQUIRE(get_value(read_ofstream).type() == OStream::Type::std_ofstream);
+      REQUIRE(dynamic_cast<const OFStream&>(get_value(read_ofstream)).filename() == "output_name");
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_PrintCheckpointInfo.cpp b/tests/test_checkpointing_PrintCheckpointInfo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8f3d28ecfddf97ab8162c0ad60e878ea67f5ff3d
--- /dev/null
+++ b/tests/test_checkpointing_PrintCheckpointInfo.cpp
@@ -0,0 +1,162 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/checkpointing/PrintCheckpointInfo.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("PrintCheckpointInfo", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    const std::string data_file0 = R"(Un tiens vaut mieux que deux tu l'auras,
+Un tiens vaut mieux que deux tu l'auras,...)";
+    const std::string data_file1 = R"(All work and no play makes Jack a dull boy,
+All work and no play makes Jack a dull boy,...)";
+    const std::string data_file2 = R"(solo trabajo y nada de juego hacen de Jack un chico aburrido,
+solo trabajo y nada de juego hacen de Jack un chico aburrido,...)";
+
+    std::string info = R"( * checkpoint_0 [Today]
+   X1 ...................... [2.7]
+   X2 ...................... [-2.3,4.1]
+   a ....................... 1.3
+   b ....................... true
+   n ....................... 12
+   z ....................... -3
+   mesh0 ................... mesh
+ * checkpoint_1 [Tomorrow]
+   X3 ...................... [1.2,1.4,-1]
+   m ....................... 8
+   z ....................... -6
+   tv ...................... ([1,2], [-1.2,3])
+ * checkpoint_2 [Yesterday]
+   M1 ...................... [[2.7]]
+   M2 ...................... [[-2.3,4.1],[-1.8,1.5]]
+   M3 ...................... [[-2.3,4.1,3.2],[-1.8,1.5,1.7],[-1.2,0.7,1.2]]
+   s ....................... "foo"
+   ts ...................... ("foo", "bar")
+-------------------------------------------------------
+ * resuming_checkpoint -> checkpoint_1
+ * last_checkpoint -> checkpoint_2
+)";
+
+    {
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+      {
+        HighFive::Group cp = file.createGroup("/checkpoint_0");
+        cp.createAttribute("data.pgs", data_file0);
+        cp.createAttribute("id", 0ul);
+        cp.createAttribute("creation_date", std::string{"Today"});
+        cp.createAttribute("checkpoint_number", 0ul);
+        cp.createAttribute("name", std::string{"checkpoint_0"});
+        HighFive::Group symbol_table = cp.createGroup("symbol table");
+        symbol_table.createAttribute("a", 1.3);
+        symbol_table.createAttribute("n", uint64_t{12});
+        symbol_table.createAttribute("z", int64_t{-3});
+        symbol_table.createAttribute("X1", TinyVector<1>{2.7});
+        symbol_table.createAttribute("X2", TinyVector<2>{-2.3, 4.1});
+        symbol_table.createAttribute("b", true);
+        HighFive::Group embedded = symbol_table.createGroup("embedded");
+        HighFive::Group mesh0    = embedded.createGroup("mesh0");
+        mesh0.createAttribute("type", std::string{"mesh"});
+      }
+
+      {
+        HighFive::Group cp = file.createGroup("/checkpoint_1");
+        cp.createAttribute("data.pgs", data_file1);
+        cp.createAttribute("id", 1ul);
+        cp.createAttribute("creation_date", std::string{"Tomorrow"});
+        cp.createAttribute("checkpoint_number", 1ul);
+        cp.createAttribute("name", std::string{"checkpoint_1"});
+        HighFive::Group symbol_table = cp.createGroup("symbol table");
+        symbol_table.createAttribute("m", uint64_t{8});
+        symbol_table.createAttribute("z", int64_t{-6});
+        symbol_table.createAttribute("X3", TinyVector<3>{1.2, 1.4, -1});
+
+        HighFive::Group sub_symbol_table = symbol_table.createGroup("symbol table");
+
+        sub_symbol_table.createAttribute("tv", std::vector{TinyVector<2>{1, 2}, TinyVector<2>{-1.2, 3}});
+
+        file.createHardLink("resuming_checkpoint", cp);
+      }
+
+      {
+        HighFive::Group cp = file.createGroup("/checkpoint_2");
+        cp.createAttribute("data.pgs", data_file2);
+        cp.createAttribute("id", 2ul);
+        cp.createAttribute("creation_date", std::string{"Yesterday"});
+        cp.createAttribute("checkpoint_number", 2ul);
+        cp.createAttribute("name", std::string{"checkpoint_2"});
+        HighFive::Group symbol_table = cp.createGroup("symbol table");
+        symbol_table.createAttribute("s", std::string{"foo"});
+        symbol_table.createAttribute("M1", TinyMatrix<1>{2.7});
+        symbol_table.createAttribute("M2", TinyMatrix<2>{-2.3, 4.1,   //
+                                                         -1.8, 1.5});
+        symbol_table.createAttribute("M3", TinyMatrix<3>{-2.3, 4.1, 3.2,   //
+                                                         -1.8, 1.5, 1.7,   //
+                                                         -1.2, 0.7, 1.2});
+
+        symbol_table.createAttribute("ts", std::vector<std::string>{"foo", "bar"});
+
+        file.createHardLink("last_checkpoint", cp);
+      }
+    }
+
+    {
+      std::ostringstream os;
+      printCheckpointInfo(filename, os);
+
+      if (parallel::rank() == 0) {
+        REQUIRE(os.str() == info);
+      } else {
+        REQUIRE(os.str() == "");
+      }
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+#else   // PUGS_HAS_HDF5
+
+  if (parallel::rank() == 0) {
+    std::cerr.setstate(std::ios::badbit);
+  }
+
+  std::ostringstream os;
+  REQUIRE_NOTHROW(printCheckpointInfo("foo.h5", os));
+
+  if (parallel::rank() == 0) {
+    std::cerr.clear();
+  }
+
+  REQUIRE(os.str() == "");
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_PrintScriptFrom.cpp b/tests/test_checkpointing_PrintScriptFrom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0896124064e6aa2542f9d4f0996b961485845be2
--- /dev/null
+++ b/tests/test_checkpointing_PrintScriptFrom.cpp
@@ -0,0 +1,109 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/checkpointing/PrintScriptFrom.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_PrintScriptFrom", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    const std::string data_file0 = R"(Un tiens vaut mieux que deux tu l'auras,
+Un tiens vaut mieux que deux tu l'auras,...)";
+    const std::string data_file1 = R"(All work and no play makes Jack a dull boy,
+All work and no play makes Jack a dull boy,...)";
+    const std::string data_file2 = R"(solo trabajo y nada de juego hacen de Jack un chico aburrido,
+solo trabajo y nada de juego hacen de Jack un chico aburrido,...)";
+
+    {
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+      file.createGroup("/checkpoint_0").createAttribute("data.pgs", data_file0);
+      file.createGroup("/checkpoint_1").createAttribute("data.pgs", data_file1);
+      file.createGroup("/checkpoint_2").createAttribute("data.pgs", data_file2);
+    }
+
+    {
+      std::ostringstream os;
+      printScriptFrom(filename, 0, os);
+      REQUIRE(os.str() == data_file0);
+    }
+
+    {
+      std::ostringstream os;
+      printScriptFrom(filename, 1, os);
+      REQUIRE(os.str() == data_file1);
+    }
+
+    {
+      std::ostringstream os;
+      printScriptFrom(filename, 2, os);
+      REQUIRE(os.str() == data_file2);
+    }
+
+    {
+      std::ostringstream error_msg;
+      error_msg << "error: cannot find checkpoint " << 12 << " in " << filename;
+      REQUIRE_THROWS_WITH(printScriptFrom(filename, 12), error_msg.str());
+    }
+
+    {
+      const std::string malformed_filename = path / "malformed.h5";
+
+      {
+        HighFive::FileAccessProps fapl;
+        fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+        fapl.add(HighFive::MPIOCollectiveMetadata{});
+        HighFive::File file = HighFive::File(malformed_filename, HighFive::File::Truncate, fapl);
+
+        file.createGroup("/checkpoint_0");
+      }
+      REQUIRE_THROWS_WITH(printScriptFrom(malformed_filename, 0),
+                          "error: Unable to open the attribute \"data.pgs\": (Attribute) Object not found");
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+#else   // PUGS_HAS_HDF5
+
+  if (parallel::rank() == 0) {
+    std::cerr.setstate(std::ios::badbit);
+  }
+
+  std::ostringstream os;
+  REQUIRE_NOTHROW(printScriptFrom("foo.h5", 0, os));
+
+  if (parallel::rank() == 0) {
+    std::cerr.clear();
+  }
+
+  REQUIRE(os.str() == "");
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_Resume.cpp b/tests/test_checkpointing_Resume.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..82ea9202b75958e7d77a33c7af47d9a4516860bc
--- /dev/null
+++ b/tests/test_checkpointing_Resume.cpp
@@ -0,0 +1,1020 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_predicate.hpp>
+
+#include <utils/checkpointing/Resume.hpp>
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <MeshDataBaseForTests.hpp>
+#include <dev/ParallelChecker.hpp>
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/modules/MathModule.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/checkpointing/SetResumeFrom.hpp>
+
+class ASTCheckpointsInfoTester
+{
+ private:
+  ASTCheckpointsInfo m_ast_checkpoint_info;
+
+ public:
+  ASTCheckpointsInfoTester(const ASTNode& root_node) : m_ast_checkpoint_info(root_node) {}
+  ~ASTCheckpointsInfoTester() = default;
+};
+
+#define RUN_AST(data)                                          \
+  {                                                            \
+    ExecutionStatManager::create();                            \
+    ParallelChecker::create();                                 \
+    CheckpointResumeRepository::create();                      \
+                                                               \
+    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"}; \
+    auto ast = ASTBuilder::build(input);                       \
+                                                               \
+    ASTModulesImporter{*ast};                                  \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};    \
+                                                               \
+    ASTSymbolTableBuilder{*ast};                               \
+    ASTNodeDataTypeBuilder{*ast};                              \
+                                                               \
+    ASTNodeDeclarationToAffectationConverter{*ast};            \
+    ASTNodeTypeCleaner<language::var_declaration>{*ast};       \
+    ASTNodeTypeCleaner<language::fct_declaration>{*ast};       \
+                                                               \
+    ASTNodeExpressionBuilder{*ast};                            \
+    ExecutionPolicy exec_policy;                               \
+    ASTExecutionStack::create();                               \
+    ASTCheckpointsInfoTester ast_cp_info_tester{*ast};         \
+    ast->execute(exec_policy);                                 \
+    ASTExecutionStack::destroy();                              \
+    ast->m_symbol_table->clearValues();                        \
+                                                               \
+    CheckpointResumeRepository::destroy();                     \
+    ParallelChecker::destroy();                                \
+    ExecutionStatManager::destroy();                           \
+    ast->m_symbol_table->clearValues();                        \
+  }
+
+#endif   // PUGS_HAS_HDF5
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Resume", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  SECTION("general")
+  {
+    auto frobeniusNorm = [](const auto& A) {
+      using A_T = std::decay_t<decltype(A)>;
+      static_assert(is_tiny_matrix_v<A_T>);
+      return std::sqrt(trace(transpose(A) * A));
+    };
+
+    using R1 = TinyVector<1>;
+    using R2 = TinyVector<2>;
+    using R3 = TinyVector<3>;
+
+    using R11 = TinyMatrix<1>;
+    using R22 = TinyMatrix<2>;
+    using R33 = TinyMatrix<3>;
+
+    MeshDataBaseForTests::destroy();
+    GlobalVariableManager::instance().setMeshId(0);
+    GlobalVariableManager::instance().setConnectivityId(0);
+
+    ResumingManager::destroy();
+    ResumingManager::create();
+
+    std::string tmp_dirname;
+    {
+      {
+        if (parallel::rank() == 0) {
+          tmp_dirname = [&]() -> std::string {
+            std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+            return std::string{mkdtemp(&temp_filename[0])};
+          }();
+        }
+        parallel::broadcast(tmp_dirname, 0);
+      }
+      std::filesystem::path path = tmp_dirname;
+      const std::string filename = path / "checkpoint.h5";
+
+      ResumingManager::getInstance().setFilename(filename);
+    }
+
+    const std::string filename = ResumingManager::getInstance().filename();
+
+    std::string data = R"(
+import math;
+import mesh;
+import scheme;
+
+let f:R*R^3 -> R^3, (a, v) -> a * v;
+
+let alpha:R, alpha = 3.2;
+let u1:R^1, u1 = [0.3];
+let u2:R^2, u2 = [0.3, 1.2];
+let u3:R^3, u3 = [1, 2, 3];
+
+let A1:R^1x1, A1 = [[0.7]];
+let A2:R^2x2, A2 = [[1.4, 2.1], [0.6, 3]];
+let A3:R^3x3, A3 = [[1.1, 2.2, 3.3], [0.1, 0.2, 0.3], [1.6, 1.2, 1.4]];
+
+let m2d:mesh, m2d = cartesianMesh(0, [1,1], (10,10));
+
+let b_tuple:(B), b_tuple = (true, false, true);
+let n_tuple:(N), n_tuple = (1, 2, 3, 4);
+let z_tuple:(Z), z_tuple = (1, -2, 3, -4);
+let r_tuple:(R), r_tuple = (1.2, -2.4, 3.1, -4.3);
+let s_tuple:(string), s_tuple = ("foo", "bar");
+
+let r1_tuple:(R^1), r1_tuple = ([1], [2]);
+let r2_tuple:(R^2), r2_tuple = ([1.2, 3], [2.3, 4], [3.2, 1.4]);
+let r3_tuple:(R^3), r3_tuple = ([1.2, 0.2, 3], [2.3, -1, 4], [3.2, 2.1, 1.4]);
+
+let r11_tuple:(R^1x1), r11_tuple = ([[1.3]], [[2.4]]);
+let r22_tuple:(R^2x2), r22_tuple = ([[1.2, 3], [2.3, 4]], [[3.2, 1.4], [1.3, 5.2]]);
+let r33_tuple:(R^3x3), r33_tuple = ([[1.2, 0.2, 3], [2.3, -1, 4], [3.2, 2.1, 1.4]]);
+
+let m1d:mesh, m1d = cartesianMesh([0], [1], 10);
+
+let m3d:mesh, m3d = cartesianMesh(0, [1,1,1], (6,6,6));
+
+let b:B, b = false;
+let z:Z, z = -2;
+let s:string, s = "foobar";
+
+for(let i:N, i=0; i<3; ++i) {
+  checkpoint();
+
+  s = "foobar_"+i;
+  z += 1;
+  b = not b;
+
+  let g:R -> R^3, x -> [x, 1.2*x, 3];
+  u3 = f(alpha,u3);
+
+  checkpoint();
+}
+)";
+
+    const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+    RUN_AST(data);
+
+    GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+    GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 5);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_5");
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 2);
+
+      HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+      REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u1").read<R1>() - R1{0.3}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u2").read<R2>() - R2{0.3, 1.2}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u3").read<R3>() - R3{32.768, 65.536, 98.304}) ==
+              Catch::Approx(0).margin(1E-12));
+      REQUIRE(symbol_table1.getAttribute("b").read<bool>() == true);
+      REQUIRE(symbol_table1.getAttribute("z").read<int64_t>() == 1);
+      REQUIRE(symbol_table1.getAttribute("s").read<std::string>() == "foobar_2");
+      REQUIRE(symbol_table1.getAttribute("A1").read<R11>()(0, 0) == Catch::Approx(0.7).margin(1E-12));
+      REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A2").read<R22>() - R22{1.4, 2.1, 0.6, 3}) ==
+              Catch::Approx(0).margin(1E-12));
+      REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A3").read<R33>() -
+                            R33{1.1, 2.2, 3.3, 0.1, 0.2, 0.3, 1.6, 1.2, 1.4}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(symbol_table1.getAttribute("b_tuple").read<std::vector<bool>>() == std::vector<bool>{true, false, true});
+      REQUIRE(symbol_table1.getAttribute("n_tuple").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+      REQUIRE(symbol_table1.getAttribute("z_tuple").read<std::vector<int64_t>>() == std::vector<int64_t>{1, -2, 3, -4});
+      REQUIRE(symbol_table1.getAttribute("r_tuple").read<std::vector<double>>() ==
+              std::vector<double>{1.2, -2.4, 3.1, -4.3});
+      REQUIRE(symbol_table1.getAttribute("s_tuple").read<std::vector<std::string>>() ==
+              std::vector<std::string>{"foo", "bar"});
+
+      REQUIRE(symbol_table1.getAttribute("r1_tuple").read<std::vector<R1>>() == std::vector{R1{1}, R1{2}});
+      REQUIRE(symbol_table1.getAttribute("r2_tuple").read<std::vector<R2>>() ==
+              std::vector{R2{1.2, 3}, R2{2.3, 4}, R2{3.2, 1.4}});
+      REQUIRE(symbol_table1.getAttribute("r3_tuple").read<std::vector<R3>>() ==
+              std::vector{R3{1.2, 0.2, 3}, R3{2.3, -1, 4}, R3{3.2, 2.1, 1.4}});
+
+      REQUIRE(symbol_table1.getAttribute("r11_tuple").read<std::vector<R11>>() == std::vector{R11{1.3}, R11{2.4}});
+      REQUIRE(symbol_table1.getAttribute("r22_tuple").read<std::vector<R22>>() ==
+              std::vector{R22{1.2, 3, 2.3, 4}, R22{3.2, 1.4, 1.3, 5.2}});
+      REQUIRE(symbol_table1.getAttribute("r33_tuple").read<std::vector<R33>>() ==
+              std::vector{R33{1.2, 0.2, 3, 2.3, -1, 4, 3.2, 2.1, 1.4}});
+
+      HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+      HighFive::Group m1d = embedded1.getGroup("m1d");
+      REQUIRE(m1d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m1d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (1 + 2 * (parallel::size() > 1)));
+
+      HighFive::Group m2d = embedded1.getGroup("m2d");
+      REQUIRE(m2d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m2d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+
+      HighFive::Group m3d = embedded1.getGroup("m3d");
+      REQUIRE(m3d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (2 + 3 * (parallel::size() > 1)));
+
+      HighFive::Group singleton        = checkpoint.getGroup("singleton");
+      HighFive::Group global_variables = singleton.getGroup("global_variables");
+      REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() ==
+              initial_connectivity_id + 3 * (1 + (parallel::size() > 1)));
+      REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() ==
+              initial_mesh_id + 3 * (1 + (parallel::size() > 1)));
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group connectivity = checkpoint.getGroup("connectivity");
+      HighFive::Group connectivity0 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (parallel::size() > 1)));
+      REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+      REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+      REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group connectivity1 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (1 + 2 * (parallel::size() > 1))));
+      REQUIRE(connectivity1.getAttribute("dimension").read<uint64_t>() == 1);
+      REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() ==
+              initial_connectivity_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group connectivity2 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (2 + 3 * (parallel::size() > 1))));
+      REQUIRE(connectivity2.getAttribute("dimension").read<uint64_t>() == 3);
+      REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() ==
+              initial_connectivity_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group mesh  = checkpoint.getGroup("mesh");
+      HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id + (parallel::size() > 1)));
+      REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+      REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+      REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+      REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + (1 + 2 * (parallel::size() > 1))));
+      REQUIRE(mesh1.getAttribute("connectivity").read<uint64_t>() ==
+              initial_connectivity_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(mesh1.getAttribute("dimension").read<uint64_t>() == 1);
+      REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(mesh1.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + (2 + 3 * (parallel::size() > 1))));
+      REQUIRE(mesh2.getAttribute("connectivity").read<uint64_t>() ==
+              initial_connectivity_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(mesh2.getAttribute("dimension").read<uint64_t>() == 3);
+      REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(mesh2.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group functions = checkpoint.getGroup("functions");
+      HighFive::Group f         = functions.getGroup("f");
+      REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+      HighFive::Group g = functions.getGroup("g");
+      REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+    }
+
+    parallel::barrier();
+
+    setResumeFrom(filename, 3);
+
+    parallel::barrier();
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 3);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_3");
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+      REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u1").read<R1>() - R1{0.3}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u2").read<R2>() - R2{0.3, 1.2}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u3").read<R3>() - R3{10.24, 20.48, 30.72}) ==
+              Catch::Approx(0).margin(1E-12));
+      REQUIRE(symbol_table1.getAttribute("b").read<bool>() == false);
+      REQUIRE(symbol_table1.getAttribute("z").read<int64_t>() == 0);
+      REQUIRE(symbol_table1.getAttribute("s").read<std::string>() == "foobar_1");
+      REQUIRE(symbol_table1.getAttribute("A1").read<R11>()(0, 0) == Catch::Approx(0.7).margin(1E-12));
+      REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A2").read<R22>() - R22{1.4, 2.1, 0.6, 3}) ==
+              Catch::Approx(0).margin(1E-12));
+      REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A3").read<R33>() -
+                            R33{1.1, 2.2, 3.3, 0.1, 0.2, 0.3, 1.6, 1.2, 1.4}) == Catch::Approx(0).margin(1E-12));
+
+      REQUIRE(symbol_table1.getAttribute("b_tuple").read<std::vector<bool>>() == std::vector<bool>{true, false, true});
+      REQUIRE(symbol_table1.getAttribute("n_tuple").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+      REQUIRE(symbol_table1.getAttribute("z_tuple").read<std::vector<int64_t>>() == std::vector<int64_t>{1, -2, 3, -4});
+      REQUIRE(symbol_table1.getAttribute("r_tuple").read<std::vector<double>>() ==
+              std::vector<double>{1.2, -2.4, 3.1, -4.3});
+      REQUIRE(symbol_table1.getAttribute("s_tuple").read<std::vector<std::string>>() ==
+              std::vector<std::string>{"foo", "bar"});
+
+      REQUIRE(symbol_table1.getAttribute("r1_tuple").read<std::vector<R1>>() == std::vector{R1{1}, R1{2}});
+      REQUIRE(symbol_table1.getAttribute("r2_tuple").read<std::vector<R2>>() ==
+              std::vector{R2{1.2, 3}, R2{2.3, 4}, R2{3.2, 1.4}});
+      REQUIRE(symbol_table1.getAttribute("r3_tuple").read<std::vector<R3>>() ==
+              std::vector{R3{1.2, 0.2, 3}, R3{2.3, -1, 4}, R3{3.2, 2.1, 1.4}});
+
+      REQUIRE(symbol_table1.getAttribute("r11_tuple").read<std::vector<R11>>() == std::vector{R11{1.3}, R11{2.4}});
+      REQUIRE(symbol_table1.getAttribute("r22_tuple").read<std::vector<R22>>() ==
+              std::vector{R22{1.2, 3, 2.3, 4}, R22{3.2, 1.4, 1.3, 5.2}});
+      REQUIRE(symbol_table1.getAttribute("r33_tuple").read<std::vector<R33>>() ==
+              std::vector{R33{1.2, 0.2, 3, 2.3, -1, 4, 3.2, 2.1, 1.4}});
+
+      HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+      HighFive::Group m1d = embedded1.getGroup("m1d");
+      REQUIRE(m1d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m1d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (1 + 2 * (parallel::size() > 1)));
+
+      HighFive::Group m2d = embedded1.getGroup("m2d");
+      REQUIRE(m2d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m2d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+
+      HighFive::Group m3d = embedded1.getGroup("m3d");
+      REQUIRE(m3d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (2 + 3 * (parallel::size() > 1)));
+
+      HighFive::Group singleton        = checkpoint.getGroup("singleton");
+      HighFive::Group global_variables = singleton.getGroup("global_variables");
+      REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() ==
+              initial_connectivity_id + 3 * (1 + (parallel::size() > 1)));
+      REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() ==
+              initial_mesh_id + 3 * (1 + (parallel::size() > 1)));
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group connectivity = checkpoint.getGroup("connectivity");
+      HighFive::Group connectivity0 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (parallel::size() > 1)));
+      REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+      REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+      REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group connectivity1 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (1 + 2 * (parallel::size() > 1))));
+      REQUIRE(connectivity1.getAttribute("dimension").read<uint64_t>() == 1);
+      REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() ==
+              initial_connectivity_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group connectivity2 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (2 + 3 * (parallel::size() > 1))));
+      REQUIRE(connectivity2.getAttribute("dimension").read<uint64_t>() == 3);
+      REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() ==
+              initial_connectivity_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group mesh  = checkpoint.getGroup("mesh");
+      HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id + (parallel::size() > 1)));
+      REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+      REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+      REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+      REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + (1 + 2 * (parallel::size() > 1))));
+      REQUIRE(mesh1.getAttribute("connectivity").read<uint64_t>() ==
+              initial_connectivity_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(mesh1.getAttribute("dimension").read<uint64_t>() == 1);
+      REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(mesh1.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + (2 + 3 * (parallel::size() > 1))));
+      REQUIRE(mesh2.getAttribute("connectivity").read<uint64_t>() ==
+              initial_connectivity_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(mesh2.getAttribute("dimension").read<uint64_t>() == 3);
+      REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(mesh2.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group functions = checkpoint.getGroup("functions");
+      HighFive::Group f         = functions.getGroup("f");
+      REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+      HighFive::Group g = functions.getGroup("g");
+      REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+    }
+
+    ResumingManager::destroy();
+    ResumingManager::create();
+    ResumingManager::getInstance().setFilename(filename);
+    ResumingManager::getInstance().setIsResuming(true);
+    GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+    GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 5);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_5");
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 2);
+
+      HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+      REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u1").read<R1>() - R1{0.3}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u2").read<R2>() - R2{0.3, 1.2}) == Catch::Approx(0).margin(1E-12));
+      REQUIRE(l2Norm(symbol_table1.getAttribute("u3").read<R3>() - R3{32.768, 65.536, 98.304}) ==
+              Catch::Approx(0).margin(1E-12));
+      REQUIRE(symbol_table1.getAttribute("b").read<bool>() == true);
+      REQUIRE(symbol_table1.getAttribute("z").read<int64_t>() == 1);
+      REQUIRE(symbol_table1.getAttribute("s").read<std::string>() == "foobar_2");
+      REQUIRE(symbol_table1.getAttribute("A1").read<R11>()(0, 0) == Catch::Approx(0.7).margin(1E-12));
+      REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A2").read<R22>() - R22{1.4, 2.1, 0.6, 3}) ==
+              Catch::Approx(0).margin(1E-12));
+      REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A3").read<R33>() -
+                            R33{1.1, 2.2, 3.3, 0.1, 0.2, 0.3, 1.6, 1.2, 1.4}) == Catch::Approx(0).margin(1E-12));
+
+      REQUIRE(symbol_table1.getAttribute("b_tuple").read<std::vector<bool>>() == std::vector<bool>{true, false, true});
+      REQUIRE(symbol_table1.getAttribute("n_tuple").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+      REQUIRE(symbol_table1.getAttribute("z_tuple").read<std::vector<int64_t>>() == std::vector<int64_t>{1, -2, 3, -4});
+      REQUIRE(symbol_table1.getAttribute("r_tuple").read<std::vector<double>>() ==
+              std::vector<double>{1.2, -2.4, 3.1, -4.3});
+      REQUIRE(symbol_table1.getAttribute("s_tuple").read<std::vector<std::string>>() ==
+              std::vector<std::string>{"foo", "bar"});
+
+      REQUIRE(symbol_table1.getAttribute("r1_tuple").read<std::vector<R1>>() == std::vector{R1{1}, R1{2}});
+      REQUIRE(symbol_table1.getAttribute("r2_tuple").read<std::vector<R2>>() ==
+              std::vector{R2{1.2, 3}, R2{2.3, 4}, R2{3.2, 1.4}});
+      REQUIRE(symbol_table1.getAttribute("r3_tuple").read<std::vector<R3>>() ==
+              std::vector{R3{1.2, 0.2, 3}, R3{2.3, -1, 4}, R3{3.2, 2.1, 1.4}});
+
+      REQUIRE(symbol_table1.getAttribute("r11_tuple").read<std::vector<R11>>() == std::vector{R11{1.3}, R11{2.4}});
+      REQUIRE(symbol_table1.getAttribute("r22_tuple").read<std::vector<R22>>() ==
+              std::vector{R22{1.2, 3, 2.3, 4}, R22{3.2, 1.4, 1.3, 5.2}});
+      REQUIRE(symbol_table1.getAttribute("r33_tuple").read<std::vector<R33>>() ==
+              std::vector{R33{1.2, 0.2, 3, 2.3, -1, 4, 3.2, 2.1, 1.4}});
+
+      HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+      HighFive::Group m1d = embedded1.getGroup("m1d");
+      REQUIRE(m1d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m1d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (1 + 2 * (parallel::size() > 1)));
+
+      HighFive::Group m2d = embedded1.getGroup("m2d");
+      REQUIRE(m2d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m2d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+
+      HighFive::Group m3d = embedded1.getGroup("m3d");
+      REQUIRE(m3d.getAttribute("type").read<std::string>() == "mesh");
+      REQUIRE(m3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + (2 + 3 * (parallel::size() > 1)));
+
+      HighFive::Group singleton        = checkpoint.getGroup("singleton");
+      HighFive::Group global_variables = singleton.getGroup("global_variables");
+      REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() ==
+              initial_connectivity_id + 3 * (1 + (parallel::size() > 1)));
+      REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() ==
+              initial_mesh_id + 3 * (1 + (parallel::size() > 1)));
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 2);
+
+      HighFive::Group connectivity = checkpoint.getGroup("connectivity");
+      HighFive::Group connectivity0 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (parallel::size() > 1)));
+      REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+      REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+      REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group connectivity1 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (1 + 2 * (parallel::size() > 1))));
+      REQUIRE(connectivity1.getAttribute("dimension").read<uint64_t>() == 1);
+      REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() ==
+              initial_connectivity_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group connectivity2 =
+        connectivity.getGroup(std::to_string(initial_connectivity_id + (2 + 3 * (parallel::size() > 1))));
+      REQUIRE(connectivity2.getAttribute("dimension").read<uint64_t>() == 3);
+      REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() ==
+              initial_connectivity_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "unstructured");
+
+      HighFive::Group mesh  = checkpoint.getGroup("mesh");
+      HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id + (parallel::size() > 1)));
+      REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + (parallel::size() > 1));
+      REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+      REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id + (parallel::size() > 1));
+      REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + (1 + 2 * (parallel::size() > 1))));
+      REQUIRE(mesh1.getAttribute("connectivity").read<uint64_t>() ==
+              initial_connectivity_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(mesh1.getAttribute("dimension").read<uint64_t>() == 1);
+      REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + (1 + 2 * (parallel::size() > 1)));
+      REQUIRE(mesh1.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + (2 + 3 * (parallel::size() > 1))));
+      REQUIRE(mesh2.getAttribute("connectivity").read<uint64_t>() ==
+              initial_connectivity_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(mesh2.getAttribute("dimension").read<uint64_t>() == 3);
+      REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + (2 + 3 * (parallel::size() > 1)));
+      REQUIRE(mesh2.getAttribute("type").read<std::string>() == "polygonal");
+
+      HighFive::Group functions = checkpoint.getGroup("functions");
+      HighFive::Group f         = functions.getGroup("f");
+      REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+      HighFive::Group g = functions.getGroup("g");
+      REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+    }
+
+    parallel::barrier();
+    if (parallel::rank() == 0) {
+      std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+    }
+
+    // Revert to default value
+    ResumingManager::getInstance().setFilename("checkpoint.h5");
+    MeshDataBaseForTests::create();
+  }
+
+  SECTION("simple if")
+  {
+    ResumingManager::destroy();
+    ResumingManager::create();
+
+    std::string tmp_dirname;
+    {
+      {
+        if (parallel::rank() == 0) {
+          tmp_dirname = [&]() -> std::string {
+            std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+            return std::string{mkdtemp(&temp_filename[0])};
+          }();
+        }
+        parallel::broadcast(tmp_dirname, 0);
+      }
+      std::filesystem::path path = tmp_dirname;
+      const std::string filename = path / "checkpoint.h5";
+
+      ResumingManager::getInstance().setFilename(filename);
+    }
+
+    const std::string filename = ResumingManager::getInstance().filename();
+
+    std::string data = R"(
+let i:N, i = 3;
+
+if (true) {
+  checkpoint();
+}
+i = 7;
+checkpoint();
+)";
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+
+    setResumeFrom(filename, 0);
+
+    parallel::barrier();
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_0");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 3);
+    }
+
+    ResumingManager::destroy();
+    ResumingManager::create();
+    ResumingManager::getInstance().setFilename(filename);
+    ResumingManager::getInstance().setIsResuming(true);
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 2);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+    if (parallel::rank() == 0) {
+      std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+    }
+
+    // Revert to default value
+    ResumingManager::getInstance().setFilename("checkpoint.h5");
+  }
+
+  SECTION("simple else")
+  {
+    ResumingManager::destroy();
+    ResumingManager::create();
+
+    std::string tmp_dirname;
+    {
+      {
+        if (parallel::rank() == 0) {
+          tmp_dirname = [&]() -> std::string {
+            std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+            return std::string{mkdtemp(&temp_filename[0])};
+          }();
+        }
+        parallel::broadcast(tmp_dirname, 0);
+      }
+      std::filesystem::path path = tmp_dirname;
+      const std::string filename = path / "checkpoint.h5";
+
+      ResumingManager::getInstance().setFilename(filename);
+    }
+
+    const std::string filename = ResumingManager::getInstance().filename();
+
+    std::string data = R"(
+let i:N, i = 3;
+
+if (false) {
+} else {
+  checkpoint();
+}
+i = 7;
+checkpoint();
+)";
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+
+    setResumeFrom(filename, 0);
+
+    parallel::barrier();
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_0");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 3);
+    }
+
+    ResumingManager::destroy();
+    ResumingManager::create();
+    ResumingManager::getInstance().setFilename(filename);
+    ResumingManager::getInstance().setIsResuming(true);
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 2);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+    if (parallel::rank() == 0) {
+      std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+    }
+
+    // Revert to default value
+    ResumingManager::getInstance().setFilename("checkpoint.h5");
+  }
+
+  SECTION("simple do while")
+  {
+    ResumingManager::destroy();
+    ResumingManager::create();
+
+    std::string tmp_dirname;
+    {
+      {
+        if (parallel::rank() == 0) {
+          tmp_dirname = [&]() -> std::string {
+            std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+            return std::string{mkdtemp(&temp_filename[0])};
+          }();
+        }
+        parallel::broadcast(tmp_dirname, 0);
+      }
+      std::filesystem::path path = tmp_dirname;
+      const std::string filename = path / "checkpoint.h5";
+
+      ResumingManager::getInstance().setFilename(filename);
+    }
+
+    const std::string filename = ResumingManager::getInstance().filename();
+
+    std::string data = R"(
+let i:N, i = 3;
+
+do {
+  checkpoint();
+} while(false);
+
+i = 7;
+checkpoint();
+)";
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+
+    setResumeFrom(filename, 0);
+
+    parallel::barrier();
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_0");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+      REQUIRE(symbol_table1.getAttribute("i").read<uint64_t>() == 3);
+    }
+
+    ResumingManager::destroy();
+    ResumingManager::create();
+    ResumingManager::getInstance().setFilename(filename);
+    ResumingManager::getInstance().setIsResuming(true);
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 2);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+    if (parallel::rank() == 0) {
+      std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+    }
+
+    // Revert to default value
+    ResumingManager::getInstance().setFilename("checkpoint.h5");
+  }
+
+  SECTION("simple while")
+  {
+    ResumingManager::destroy();
+    ResumingManager::create();
+
+    std::string tmp_dirname;
+    {
+      {
+        if (parallel::rank() == 0) {
+          tmp_dirname = [&]() -> std::string {
+            std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+            return std::string{mkdtemp(&temp_filename[0])};
+          }();
+        }
+        parallel::broadcast(tmp_dirname, 0);
+      }
+      std::filesystem::path path = tmp_dirname;
+      const std::string filename = path / "checkpoint.h5";
+
+      ResumingManager::getInstance().setFilename(filename);
+    }
+
+    const std::string filename = ResumingManager::getInstance().filename();
+
+    std::string data = R"(
+let i:N, i = 3;
+
+while (true) {
+  checkpoint();
+  break;
+};
+
+i = 7;
+checkpoint();
+)";
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+
+    setResumeFrom(filename, 0);
+
+    parallel::barrier();
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 0);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_0");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+      HighFive::Group symbol_table2 = symbol_table1.getGroup("symbol table");
+      REQUIRE(symbol_table2.getAttribute("i").read<uint64_t>() == 3);
+    }
+
+    ResumingManager::destroy();
+    ResumingManager::create();
+    ResumingManager::getInstance().setFilename(filename);
+    ResumingManager::getInstance().setIsResuming(true);
+
+    RUN_AST(data);
+
+    {   // Check checkpoint file
+
+      HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+      HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+      REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 1);
+      REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_1");
+
+      HighFive::Group singleton      = checkpoint.getGroup("singleton");
+      HighFive::Group execution_info = singleton.getGroup("execution_info");
+      REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 2);
+
+      HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+      REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 7);
+    }
+
+    parallel::barrier();
+    if (parallel::rank() == 0) {
+      std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+    }
+
+    // Revert to default value
+    ResumingManager::getInstance().setFilename("checkpoint.h5");
+  }
+#else    // PUGS_HAS_HDF5
+  REQUIRE_THROWS_WITH(resume(), "error: checkpoint/resume mechanism requires HDF5");
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_Resume_sequential.cpp b/tests/test_checkpointing_Resume_sequential.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e8a3aa1038a3089c5188bfb2dc19f4dc6a4abdbc
--- /dev/null
+++ b/tests/test_checkpointing_Resume_sequential.cpp
@@ -0,0 +1,729 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_predicate.hpp>
+
+#include <utils/checkpointing/Resume.hpp>
+#include <utils/pugs_config.hpp>
+
+#ifdef PUGS_HAS_HDF5
+
+#include <MeshDataBaseForTests.hpp>
+#include <dev/ParallelChecker.hpp>
+#include <language/ast/ASTBuilder.hpp>
+#include <language/ast/ASTExecutionStack.hpp>
+#include <language/ast/ASTModulesImporter.hpp>
+#include <language/ast/ASTNodeDataTypeBuilder.hpp>
+#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
+#include <language/ast/ASTNodeExpressionBuilder.hpp>
+#include <language/ast/ASTNodeTypeCleaner.hpp>
+#include <language/ast/ASTSymbolTableBuilder.hpp>
+#include <language/modules/MathModule.hpp>
+#include <language/utils/ASTCheckpointsInfo.hpp>
+#include <language/utils/CheckpointResumeRepository.hpp>
+#include <mesh/DualMeshType.hpp>
+#include <utils/ExecutionStatManager.hpp>
+#include <utils/checkpointing/DualMeshTypeHFType.hpp>
+#include <utils/checkpointing/SetResumeFrom.hpp>
+
+class ASTCheckpointsInfoTester
+{
+ private:
+  ASTCheckpointsInfo m_ast_checkpoint_info;
+
+ public:
+  ASTCheckpointsInfoTester(const ASTNode& root_node) : m_ast_checkpoint_info(root_node) {}
+  ~ASTCheckpointsInfoTester() = default;
+};
+
+#define RUN_AST(data)                                          \
+  {                                                            \
+    ExecutionStatManager::create();                            \
+    ParallelChecker::create();                                 \
+    CheckpointResumeRepository::create();                      \
+                                                               \
+    TAO_PEGTL_NAMESPACE::string_input input{data, "test.pgs"}; \
+    auto ast = ASTBuilder::build(input);                       \
+                                                               \
+    ASTModulesImporter{*ast};                                  \
+    ASTNodeTypeCleaner<language::import_instruction>{*ast};    \
+                                                               \
+    ASTSymbolTableBuilder{*ast};                               \
+    ASTNodeDataTypeBuilder{*ast};                              \
+                                                               \
+    ASTNodeDeclarationToAffectationConverter{*ast};            \
+    ASTNodeTypeCleaner<language::var_declaration>{*ast};       \
+    ASTNodeTypeCleaner<language::fct_declaration>{*ast};       \
+                                                               \
+    ASTNodeExpressionBuilder{*ast};                            \
+    ExecutionPolicy exec_policy;                               \
+    ASTExecutionStack::create();                               \
+    ASTCheckpointsInfoTester ast_cp_info_tester{*ast};         \
+    ast->execute(exec_policy);                                 \
+    ASTExecutionStack::destroy();                              \
+    ast->m_symbol_table->clearValues();                        \
+                                                               \
+    CheckpointResumeRepository::destroy();                     \
+    ParallelChecker::destroy();                                \
+    ExecutionStatManager::destroy();                           \
+    ast->m_symbol_table->clearValues();                        \
+  }
+
+#endif   // PUGS_HAS_HDF5
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Resume_sequential", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  auto frobeniusNorm = [](const auto& A) {
+    using A_T = std::decay_t<decltype(A)>;
+    static_assert(is_tiny_matrix_v<A_T>);
+    return std::sqrt(trace(transpose(A) * A));
+  };
+
+  using R1 = TinyVector<1>;
+  using R2 = TinyVector<2>;
+  using R3 = TinyVector<3>;
+
+  using R11 = TinyMatrix<1>;
+  using R22 = TinyMatrix<2>;
+  using R33 = TinyMatrix<3>;
+
+  MeshDataBaseForTests::destroy();
+  GlobalVariableManager::instance().setMeshId(0);
+  GlobalVariableManager::instance().setConnectivityId(0);
+
+  ResumingManager::destroy();
+  ResumingManager::create();
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    ResumingManager::getInstance().setFilename(filename);
+  }
+
+  const std::string filename = ResumingManager::getInstance().filename();
+
+  std::string data = R"(
+import math;
+import mesh;
+import scheme;
+
+let f:R*R^3 -> R^3, (a, v) -> a * v;
+
+let alpha:R, alpha = 3.2;
+let u1:R^1, u1 = [0.3];
+let u2:R^2, u2 = [0.3, 1.2];
+let u3:R^3, u3 = [1, 2, 3];
+
+let A1:R^1x1, A1 = [[0.7]];
+let A2:R^2x2, A2 = [[1.4, 2.1], [0.6, 3]];
+let A3:R^3x3, A3 = [[1.1, 2.2, 3.3], [0.1, 0.2, 0.3], [1.6, 1.2, 1.4]];
+
+let m2d:mesh, m2d = cartesianMesh(0, [1,1], (10,10));
+
+let b_tuple:(B), b_tuple = (true, false, true);
+let n_tuple:(N), n_tuple = (1, 2, 3, 4);
+let z_tuple:(Z), z_tuple = (1, -2, 3, -4);
+let r_tuple:(R), r_tuple = (1.2, -2.4, 3.1, -4.3);
+let s_tuple:(string), s_tuple = ("foo", "bar");
+
+let r1_tuple:(R^1), r1_tuple = ([1], [2]);
+let r2_tuple:(R^2), r2_tuple = ([1.2, 3], [2.3, 4], [3.2, 1.4]);
+let r3_tuple:(R^3), r3_tuple = ([1.2, 0.2, 3], [2.3, -1, 4], [3.2, 2.1, 1.4]);
+
+let r11_tuple:(R^1x1), r11_tuple = ([[1.3]], [[2.4]]);
+let r22_tuple:(R^2x2), r22_tuple = ([[1.2, 3], [2.3, 4]], [[3.2, 1.4], [1.3, 5.2]]);
+let r33_tuple:(R^3x3), r33_tuple = ([[1.2, 0.2, 3], [2.3, -1, 4], [3.2, 2.1, 1.4]]);
+
+let duals_2d:(mesh), duals_2d = (diamondDual(m2d), medianDual(m2d));
+
+let m1d:mesh, m1d = cartesianMesh([0], [1], 10);
+let dual_1d:mesh, dual_1d = diamondDual(m1d);
+
+let m3d:mesh, m3d = cartesianMesh(0, [1,1,1], (6,6,6));
+let dual_3d:mesh, dual_3d = diamondDual(m3d);
+
+let b:B, b = false;
+let z:Z, z = -2;
+let s:string, s = "foobar";
+
+for(let i:N, i=0; i<3; ++i) {
+  checkpoint();
+
+  s = "foobar_"+i;
+  z += 1;
+  b = not b;
+
+  let g:R -> R^3, x -> [x, 1.2*x, 3];
+  u3 = f(alpha,u3);
+
+  checkpoint();
+}
+)";
+
+  const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+  const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+  RUN_AST(data);
+
+  GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+  GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+  {   // Check checkpoint file
+
+    HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 5);
+    REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_5");
+
+    HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+    REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 2);
+
+    HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+    REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u1").read<R1>() - R1{0.3}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u2").read<R2>() - R2{0.3, 1.2}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u3").read<R3>() - R3{32.768, 65.536, 98.304}) ==
+            Catch::Approx(0).margin(1E-12));
+    REQUIRE(symbol_table1.getAttribute("b").read<bool>() == true);
+    REQUIRE(symbol_table1.getAttribute("z").read<int64_t>() == 1);
+    REQUIRE(symbol_table1.getAttribute("s").read<std::string>() == "foobar_2");
+    REQUIRE(symbol_table1.getAttribute("A1").read<R11>()(0, 0) == Catch::Approx(0.7).margin(1E-12));
+    REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A2").read<R22>() - R22{1.4, 2.1, 0.6, 3}) ==
+            Catch::Approx(0).margin(1E-12));
+    REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A3").read<R33>() -
+                          R33{1.1, 2.2, 3.3, 0.1, 0.2, 0.3, 1.6, 1.2, 1.4}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(symbol_table1.getAttribute("b_tuple").read<std::vector<bool>>() == std::vector<bool>{true, false, true});
+    REQUIRE(symbol_table1.getAttribute("n_tuple").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+    REQUIRE(symbol_table1.getAttribute("z_tuple").read<std::vector<int64_t>>() == std::vector<int64_t>{1, -2, 3, -4});
+    REQUIRE(symbol_table1.getAttribute("r_tuple").read<std::vector<double>>() ==
+            std::vector<double>{1.2, -2.4, 3.1, -4.3});
+    REQUIRE(symbol_table1.getAttribute("s_tuple").read<std::vector<std::string>>() ==
+            std::vector<std::string>{"foo", "bar"});
+
+    REQUIRE(symbol_table1.getAttribute("r1_tuple").read<std::vector<R1>>() == std::vector{R1{1}, R1{2}});
+    REQUIRE(symbol_table1.getAttribute("r2_tuple").read<std::vector<R2>>() ==
+            std::vector{R2{1.2, 3}, R2{2.3, 4}, R2{3.2, 1.4}});
+    REQUIRE(symbol_table1.getAttribute("r3_tuple").read<std::vector<R3>>() ==
+            std::vector{R3{1.2, 0.2, 3}, R3{2.3, -1, 4}, R3{3.2, 2.1, 1.4}});
+
+    REQUIRE(symbol_table1.getAttribute("r11_tuple").read<std::vector<R11>>() == std::vector{R11{1.3}, R11{2.4}});
+    REQUIRE(symbol_table1.getAttribute("r22_tuple").read<std::vector<R22>>() ==
+            std::vector{R22{1.2, 3, 2.3, 4}, R22{3.2, 1.4, 1.3, 5.2}});
+    REQUIRE(symbol_table1.getAttribute("r33_tuple").read<std::vector<R33>>() ==
+            std::vector{R33{1.2, 0.2, 3, 2.3, -1, 4, 3.2, 2.1, 1.4}});
+
+    HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+    HighFive::Group m1d = embedded1.getGroup("m1d");
+    REQUIRE(m1d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m1d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 3);
+
+    HighFive::Group dual_1d = embedded1.getGroup("dual_1d");
+    REQUIRE(dual_1d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(dual_1d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 4);
+
+    HighFive::Group m2d = embedded1.getGroup("m2d");
+    REQUIRE(m2d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m2d.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+
+    HighFive::Group duals_2d = embedded1.getGroup("duals_2d");
+    REQUIRE(duals_2d.getAttribute("type").read<std::string>() == "(mesh)");
+    HighFive::Group duals_2d_0 = duals_2d.getGroup("0");
+    REQUIRE(duals_2d_0.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_2d_0.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    HighFive::Group duals_2d_1 = duals_2d.getGroup("1");
+    REQUIRE(duals_2d_1.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_2d_1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+
+    HighFive::Group m3d = embedded1.getGroup("m3d");
+    REQUIRE(m3d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 5);
+
+    HighFive::Group dual_3d = embedded1.getGroup("dual_3d");
+    REQUIRE(dual_3d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(dual_3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 6);
+
+    HighFive::Group singleton        = checkpoint.getGroup("singleton");
+    HighFive::Group global_variables = singleton.getGroup("global_variables");
+    REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() == initial_connectivity_id + 7);
+    REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() == initial_mesh_id + 7);
+    HighFive::Group execution_info = singleton.getGroup("execution_info");
+    REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+    HighFive::Group connectivity  = checkpoint.getGroup("connectivity");
+    HighFive::Group connectivity0 = connectivity.getGroup(std::to_string(initial_connectivity_id));
+    REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity1 = connectivity.getGroup(std::to_string(initial_connectivity_id + 1));
+    REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 1);
+    REQUIRE(connectivity1.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group connectivity2 = connectivity.getGroup(std::to_string(initial_connectivity_id + 2));
+    REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 2);
+    REQUIRE(connectivity2.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group connectivity3 = connectivity.getGroup(std::to_string(initial_connectivity_id + 3));
+    REQUIRE(connectivity3.getAttribute("dimension").read<uint64_t>() == 1);
+    REQUIRE(connectivity3.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(connectivity3.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity4 = connectivity.getGroup(std::to_string(initial_connectivity_id + 4));
+    REQUIRE(connectivity4.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 4);
+    REQUIRE(connectivity4.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(connectivity4.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity4.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Dual1D);
+
+    HighFive::Group connectivity5 = connectivity.getGroup(std::to_string(initial_connectivity_id + 5));
+    REQUIRE(connectivity5.getAttribute("dimension").read<uint64_t>() == 3);
+    REQUIRE(connectivity5.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(connectivity5.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity6 = connectivity.getGroup(std::to_string(initial_connectivity_id + 6));
+    REQUIRE(connectivity6.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 6);
+    REQUIRE(connectivity6.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(connectivity6.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity6.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh  = checkpoint.getGroup("mesh");
+    HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id));
+    REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + 1));
+    REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    REQUIRE(mesh1.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh1.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + 2));
+    REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+    REQUIRE(mesh2.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh2.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group mesh3 = mesh.getGroup(std::to_string(initial_mesh_id + 3));
+    REQUIRE(mesh3.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(mesh3.getAttribute("dimension").read<uint64_t>() == 1);
+    REQUIRE(mesh3.getAttribute("id").read<uint64_t>() == initial_mesh_id + 3);
+    REQUIRE(mesh3.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh4 = mesh.getGroup(std::to_string(initial_mesh_id + 4));
+    REQUIRE(mesh4.getAttribute("id").read<uint64_t>() == initial_mesh_id + 4);
+    REQUIRE(mesh4.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id + 3);
+    REQUIRE(mesh4.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh4.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Dual1D);
+
+    HighFive::Group mesh5 = mesh.getGroup(std::to_string(initial_mesh_id + 5));
+    REQUIRE(mesh5.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(mesh5.getAttribute("dimension").read<uint64_t>() == 3);
+    REQUIRE(mesh5.getAttribute("id").read<uint64_t>() == initial_mesh_id + 5);
+    REQUIRE(mesh5.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh6 = mesh.getGroup(std::to_string(initial_mesh_id + 6));
+    REQUIRE(mesh6.getAttribute("id").read<uint64_t>() == initial_mesh_id + 6);
+    REQUIRE(mesh6.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id + 5);
+    REQUIRE(mesh6.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh6.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group functions = checkpoint.getGroup("functions");
+    HighFive::Group f         = functions.getGroup("f");
+    REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+    REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+    HighFive::Group g = functions.getGroup("g");
+    REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+  }
+
+  parallel::barrier();
+
+  setResumeFrom(filename, 3);
+
+  parallel::barrier();
+
+  {   // Check checkpoint file
+
+    HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 3);
+    REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_3");
+
+    HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+    REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 1);
+
+    HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+    REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u1").read<R1>() - R1{0.3}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u2").read<R2>() - R2{0.3, 1.2}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u3").read<R3>() - R3{10.24, 20.48, 30.72}) ==
+            Catch::Approx(0).margin(1E-12));
+    REQUIRE(symbol_table1.getAttribute("b").read<bool>() == false);
+    REQUIRE(symbol_table1.getAttribute("z").read<int64_t>() == 0);
+    REQUIRE(symbol_table1.getAttribute("s").read<std::string>() == "foobar_1");
+    REQUIRE(symbol_table1.getAttribute("A1").read<R11>()(0, 0) == Catch::Approx(0.7).margin(1E-12));
+    REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A2").read<R22>() - R22{1.4, 2.1, 0.6, 3}) ==
+            Catch::Approx(0).margin(1E-12));
+    REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A3").read<R33>() -
+                          R33{1.1, 2.2, 3.3, 0.1, 0.2, 0.3, 1.6, 1.2, 1.4}) == Catch::Approx(0).margin(1E-12));
+
+    REQUIRE(symbol_table1.getAttribute("b_tuple").read<std::vector<bool>>() == std::vector<bool>{true, false, true});
+    REQUIRE(symbol_table1.getAttribute("n_tuple").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+    REQUIRE(symbol_table1.getAttribute("z_tuple").read<std::vector<int64_t>>() == std::vector<int64_t>{1, -2, 3, -4});
+    REQUIRE(symbol_table1.getAttribute("r_tuple").read<std::vector<double>>() ==
+            std::vector<double>{1.2, -2.4, 3.1, -4.3});
+    REQUIRE(symbol_table1.getAttribute("s_tuple").read<std::vector<std::string>>() ==
+            std::vector<std::string>{"foo", "bar"});
+
+    REQUIRE(symbol_table1.getAttribute("r1_tuple").read<std::vector<R1>>() == std::vector{R1{1}, R1{2}});
+    REQUIRE(symbol_table1.getAttribute("r2_tuple").read<std::vector<R2>>() ==
+            std::vector{R2{1.2, 3}, R2{2.3, 4}, R2{3.2, 1.4}});
+    REQUIRE(symbol_table1.getAttribute("r3_tuple").read<std::vector<R3>>() ==
+            std::vector{R3{1.2, 0.2, 3}, R3{2.3, -1, 4}, R3{3.2, 2.1, 1.4}});
+
+    REQUIRE(symbol_table1.getAttribute("r11_tuple").read<std::vector<R11>>() == std::vector{R11{1.3}, R11{2.4}});
+    REQUIRE(symbol_table1.getAttribute("r22_tuple").read<std::vector<R22>>() ==
+            std::vector{R22{1.2, 3, 2.3, 4}, R22{3.2, 1.4, 1.3, 5.2}});
+    REQUIRE(symbol_table1.getAttribute("r33_tuple").read<std::vector<R33>>() ==
+            std::vector{R33{1.2, 0.2, 3, 2.3, -1, 4, 3.2, 2.1, 1.4}});
+
+    HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+    HighFive::Group m2d = embedded1.getGroup("m2d");
+    REQUIRE(m2d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m2d.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+
+    HighFive::Group duals_2d = embedded1.getGroup("duals_2d");
+    REQUIRE(duals_2d.getAttribute("type").read<std::string>() == "(mesh)");
+    HighFive::Group duals_2d_0 = duals_2d.getGroup("0");
+    REQUIRE(duals_2d_0.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_2d_0.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    HighFive::Group duals_2d_1 = duals_2d.getGroup("1");
+    REQUIRE(duals_2d_1.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_2d_1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+
+    HighFive::Group m3d = embedded1.getGroup("m3d");
+    REQUIRE(m3d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 5);
+
+    HighFive::Group dual_3d = embedded1.getGroup("dual_3d");
+    REQUIRE(dual_3d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(dual_3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 6);
+
+    HighFive::Group singleton        = checkpoint.getGroup("singleton");
+    HighFive::Group global_variables = singleton.getGroup("global_variables");
+    REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() == initial_connectivity_id + 7);
+    REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() == initial_mesh_id + 7);
+    HighFive::Group execution_info = singleton.getGroup("execution_info");
+    REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 1);
+
+    HighFive::Group connectivity  = checkpoint.getGroup("connectivity");
+    HighFive::Group connectivity0 = connectivity.getGroup(std::to_string(initial_connectivity_id));
+    REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity1 = connectivity.getGroup(std::to_string(initial_connectivity_id + 1));
+    REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 1);
+    REQUIRE(connectivity1.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group connectivity2 = connectivity.getGroup(std::to_string(initial_connectivity_id + 2));
+    REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 2);
+    REQUIRE(connectivity2.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group connectivity3 = connectivity.getGroup(std::to_string(initial_connectivity_id + 3));
+    REQUIRE(connectivity3.getAttribute("dimension").read<uint64_t>() == 1);
+    REQUIRE(connectivity3.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(connectivity3.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity4 = connectivity.getGroup(std::to_string(initial_connectivity_id + 4));
+    REQUIRE(connectivity4.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 4);
+    REQUIRE(connectivity4.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(connectivity4.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity4.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Dual1D);
+
+    HighFive::Group connectivity5 = connectivity.getGroup(std::to_string(initial_connectivity_id + 5));
+    REQUIRE(connectivity5.getAttribute("dimension").read<uint64_t>() == 3);
+    REQUIRE(connectivity5.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(connectivity5.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity6 = connectivity.getGroup(std::to_string(initial_connectivity_id + 6));
+    REQUIRE(connectivity6.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 6);
+    REQUIRE(connectivity6.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(connectivity6.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity6.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh  = checkpoint.getGroup("mesh");
+    HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id));
+    REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + 1));
+    REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    REQUIRE(mesh1.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh1.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + 2));
+    REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+    REQUIRE(mesh2.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh2.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group mesh3 = mesh.getGroup(std::to_string(initial_mesh_id + 3));
+    REQUIRE(mesh3.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(mesh3.getAttribute("dimension").read<uint64_t>() == 1);
+    REQUIRE(mesh3.getAttribute("id").read<uint64_t>() == initial_mesh_id + 3);
+    REQUIRE(mesh3.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh4 = mesh.getGroup(std::to_string(initial_mesh_id + 4));
+    REQUIRE(mesh4.getAttribute("id").read<uint64_t>() == initial_mesh_id + 4);
+    REQUIRE(mesh4.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id + 3);
+    REQUIRE(mesh4.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh4.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Dual1D);
+
+    HighFive::Group mesh5 = mesh.getGroup(std::to_string(initial_mesh_id + 5));
+    REQUIRE(mesh5.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(mesh5.getAttribute("dimension").read<uint64_t>() == 3);
+    REQUIRE(mesh5.getAttribute("id").read<uint64_t>() == initial_mesh_id + 5);
+    REQUIRE(mesh5.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh6 = mesh.getGroup(std::to_string(initial_mesh_id + 6));
+    REQUIRE(mesh6.getAttribute("id").read<uint64_t>() == initial_mesh_id + 6);
+    REQUIRE(mesh6.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id + 5);
+    REQUIRE(mesh6.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh6.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group functions = checkpoint.getGroup("functions");
+    HighFive::Group f         = functions.getGroup("f");
+    REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+    REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+    HighFive::Group g = functions.getGroup("g");
+    REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+  }
+
+  ResumingManager::destroy();
+  ResumingManager::create();
+  ResumingManager::getInstance().setFilename(filename);
+  ResumingManager::getInstance().setIsResuming(true);
+  GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+  GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+  RUN_AST(data);
+
+  {   // Check checkpoint file
+
+    HighFive::File file(ResumingManager::getInstance().filename(), HighFive::File::ReadOnly);
+
+    HighFive::Group checkpoint = file.getGroup("/resuming_checkpoint");
+
+    REQUIRE(checkpoint.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(checkpoint.getAttribute("checkpoint_number").read<uint64_t>() == 5);
+    REQUIRE(checkpoint.getAttribute("name").read<std::string>() == "checkpoint_5");
+
+    HighFive::Group symbol_table0 = checkpoint.getGroup("symbol table");
+    REQUIRE(symbol_table0.getAttribute("i").read<uint64_t>() == 2);
+
+    HighFive::Group symbol_table1 = symbol_table0.getGroup("symbol table");
+    REQUIRE(symbol_table1.getAttribute("alpha").read<double>() == 3.2);
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u1").read<R1>() - R1{0.3}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u2").read<R2>() - R2{0.3, 1.2}) == Catch::Approx(0).margin(1E-12));
+    REQUIRE(l2Norm(symbol_table1.getAttribute("u3").read<R3>() - R3{32.768, 65.536, 98.304}) ==
+            Catch::Approx(0).margin(1E-12));
+    REQUIRE(symbol_table1.getAttribute("b").read<bool>() == true);
+    REQUIRE(symbol_table1.getAttribute("z").read<int64_t>() == 1);
+    REQUIRE(symbol_table1.getAttribute("s").read<std::string>() == "foobar_2");
+    REQUIRE(symbol_table1.getAttribute("A1").read<R11>()(0, 0) == Catch::Approx(0.7).margin(1E-12));
+    REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A2").read<R22>() - R22{1.4, 2.1, 0.6, 3}) ==
+            Catch::Approx(0).margin(1E-12));
+    REQUIRE(frobeniusNorm(symbol_table1.getAttribute("A3").read<R33>() -
+                          R33{1.1, 2.2, 3.3, 0.1, 0.2, 0.3, 1.6, 1.2, 1.4}) == Catch::Approx(0).margin(1E-12));
+
+    REQUIRE(symbol_table1.getAttribute("b_tuple").read<std::vector<bool>>() == std::vector<bool>{true, false, true});
+    REQUIRE(symbol_table1.getAttribute("n_tuple").read<std::vector<uint64_t>>() == std::vector<uint64_t>{1, 2, 3, 4});
+    REQUIRE(symbol_table1.getAttribute("z_tuple").read<std::vector<int64_t>>() == std::vector<int64_t>{1, -2, 3, -4});
+    REQUIRE(symbol_table1.getAttribute("r_tuple").read<std::vector<double>>() ==
+            std::vector<double>{1.2, -2.4, 3.1, -4.3});
+    REQUIRE(symbol_table1.getAttribute("s_tuple").read<std::vector<std::string>>() ==
+            std::vector<std::string>{"foo", "bar"});
+
+    REQUIRE(symbol_table1.getAttribute("r1_tuple").read<std::vector<R1>>() == std::vector{R1{1}, R1{2}});
+    REQUIRE(symbol_table1.getAttribute("r2_tuple").read<std::vector<R2>>() ==
+            std::vector{R2{1.2, 3}, R2{2.3, 4}, R2{3.2, 1.4}});
+    REQUIRE(symbol_table1.getAttribute("r3_tuple").read<std::vector<R3>>() ==
+            std::vector{R3{1.2, 0.2, 3}, R3{2.3, -1, 4}, R3{3.2, 2.1, 1.4}});
+
+    REQUIRE(symbol_table1.getAttribute("r11_tuple").read<std::vector<R11>>() == std::vector{R11{1.3}, R11{2.4}});
+    REQUIRE(symbol_table1.getAttribute("r22_tuple").read<std::vector<R22>>() ==
+            std::vector{R22{1.2, 3, 2.3, 4}, R22{3.2, 1.4, 1.3, 5.2}});
+    REQUIRE(symbol_table1.getAttribute("r33_tuple").read<std::vector<R33>>() ==
+            std::vector{R33{1.2, 0.2, 3, 2.3, -1, 4, 3.2, 2.1, 1.4}});
+
+    HighFive::Group embedded1 = symbol_table1.getGroup("embedded");
+
+    HighFive::Group m2d = embedded1.getGroup("m2d");
+    REQUIRE(m2d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m2d.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+
+    HighFive::Group duals_2d = embedded1.getGroup("duals_2d");
+    REQUIRE(duals_2d.getAttribute("type").read<std::string>() == "(mesh)");
+    HighFive::Group duals_2d_0 = duals_2d.getGroup("0");
+    REQUIRE(duals_2d_0.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_2d_0.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    HighFive::Group duals_2d_1 = duals_2d.getGroup("1");
+    REQUIRE(duals_2d_1.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(duals_2d_1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+
+    HighFive::Group m3d = embedded1.getGroup("m3d");
+    REQUIRE(m3d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(m3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 5);
+
+    HighFive::Group dual_3d = embedded1.getGroup("dual_3d");
+    REQUIRE(dual_3d.getAttribute("type").read<std::string>() == "mesh");
+    REQUIRE(dual_3d.getAttribute("id").read<uint64_t>() == initial_mesh_id + 6);
+
+    HighFive::Group singleton        = checkpoint.getGroup("singleton");
+    HighFive::Group global_variables = singleton.getGroup("global_variables");
+    REQUIRE(global_variables.getAttribute("connectivity_id").read<uint64_t>() == initial_connectivity_id + 7);
+    REQUIRE(global_variables.getAttribute("mesh_id").read<uint64_t>() == initial_mesh_id + 7);
+    HighFive::Group execution_info = singleton.getGroup("execution_info");
+    REQUIRE(execution_info.getAttribute("run_number").read<uint64_t>() == 2);
+
+    HighFive::Group connectivity  = checkpoint.getGroup("connectivity");
+    HighFive::Group connectivity0 = connectivity.getGroup(std::to_string(initial_connectivity_id));
+    REQUIRE(connectivity0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(connectivity0.getAttribute("id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity0.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity1 = connectivity.getGroup(std::to_string(initial_connectivity_id + 1));
+    REQUIRE(connectivity1.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 1);
+    REQUIRE(connectivity1.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity1.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group connectivity2 = connectivity.getGroup(std::to_string(initial_connectivity_id + 2));
+    REQUIRE(connectivity2.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 2);
+    REQUIRE(connectivity2.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(connectivity2.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group connectivity3 = connectivity.getGroup(std::to_string(initial_connectivity_id + 3));
+    REQUIRE(connectivity3.getAttribute("dimension").read<uint64_t>() == 1);
+    REQUIRE(connectivity3.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(connectivity3.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity4 = connectivity.getGroup(std::to_string(initial_connectivity_id + 4));
+    REQUIRE(connectivity4.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 4);
+    REQUIRE(connectivity4.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(connectivity4.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity4.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Dual1D);
+
+    HighFive::Group connectivity5 = connectivity.getGroup(std::to_string(initial_connectivity_id + 5));
+    REQUIRE(connectivity5.getAttribute("dimension").read<uint64_t>() == 3);
+    REQUIRE(connectivity5.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(connectivity5.getAttribute("type").read<std::string>() == "unstructured");
+
+    HighFive::Group connectivity6 = connectivity.getGroup(std::to_string(initial_connectivity_id + 6));
+    REQUIRE(connectivity6.getAttribute("id").read<uint64_t>() == initial_connectivity_id + 6);
+    REQUIRE(connectivity6.getAttribute("primal_connectivity_id").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(connectivity6.getAttribute("type").read<std::string>() == "dual_connectivity");
+    REQUIRE(connectivity6.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh  = checkpoint.getGroup("mesh");
+    HighFive::Group mesh0 = mesh.getGroup(std::to_string(initial_mesh_id));
+    REQUIRE(mesh0.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id);
+    REQUIRE(mesh0.getAttribute("dimension").read<uint64_t>() == 2);
+    REQUIRE(mesh0.getAttribute("id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh0.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh1 = mesh.getGroup(std::to_string(initial_mesh_id + 1));
+    REQUIRE(mesh1.getAttribute("id").read<uint64_t>() == initial_mesh_id + 1);
+    REQUIRE(mesh1.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh1.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh1.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group mesh2 = mesh.getGroup(std::to_string(initial_mesh_id + 2));
+    REQUIRE(mesh2.getAttribute("id").read<uint64_t>() == initial_mesh_id + 2);
+    REQUIRE(mesh2.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id);
+    REQUIRE(mesh2.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh2.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Median);
+
+    HighFive::Group mesh3 = mesh.getGroup(std::to_string(initial_mesh_id + 3));
+    REQUIRE(mesh3.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + 3);
+    REQUIRE(mesh3.getAttribute("dimension").read<uint64_t>() == 1);
+    REQUIRE(mesh3.getAttribute("id").read<uint64_t>() == initial_mesh_id + 3);
+    REQUIRE(mesh3.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh4 = mesh.getGroup(std::to_string(initial_mesh_id + 4));
+    REQUIRE(mesh4.getAttribute("id").read<uint64_t>() == initial_mesh_id + 4);
+    REQUIRE(mesh4.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id + 3);
+    REQUIRE(mesh4.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh4.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Dual1D);
+
+    HighFive::Group mesh5 = mesh.getGroup(std::to_string(initial_mesh_id + 5));
+    REQUIRE(mesh5.getAttribute("connectivity").read<uint64_t>() == initial_connectivity_id + 5);
+    REQUIRE(mesh5.getAttribute("dimension").read<uint64_t>() == 3);
+    REQUIRE(mesh5.getAttribute("id").read<uint64_t>() == initial_mesh_id + 5);
+    REQUIRE(mesh5.getAttribute("type").read<std::string>() == "polygonal");
+
+    HighFive::Group mesh6 = mesh.getGroup(std::to_string(initial_mesh_id + 6));
+    REQUIRE(mesh6.getAttribute("id").read<uint64_t>() == initial_mesh_id + 6);
+    REQUIRE(mesh6.getAttribute("primal_mesh_id").read<uint64_t>() == initial_mesh_id + 5);
+    REQUIRE(mesh6.getAttribute("type").read<std::string>() == "dual_mesh");
+    REQUIRE(mesh6.getAttribute("type_of_dual").read<DualMeshType>() == DualMeshType::Diamond);
+
+    HighFive::Group functions = checkpoint.getGroup("functions");
+    HighFive::Group f         = functions.getGroup("f");
+    REQUIRE(f.getAttribute("id").read<uint64_t>() == 0);
+    REQUIRE(f.getAttribute("symbol_table_id").read<uint64_t>() == 1);
+    HighFive::Group g = functions.getGroup("g");
+    REQUIRE(g.getAttribute("id").read<uint64_t>() == 1);
+    REQUIRE(g.getAttribute("symbol_table_id").read<uint64_t>() == 0);
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+  // Revert to default value
+  ResumingManager::getInstance().setFilename("checkpoint.h5");
+  MeshDataBaseForTests::create();
+
+#else    // PUGS_HAS_HDF5
+  REQUIRE_THROWS_WITH(resume(), "error: checkpoint/resume mechanism requires HDF5");
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_ResumingManager.cpp b/tests/test_checkpointing_ResumingManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1d5da95081d179894d66fe04306af877e0af676b
--- /dev/null
+++ b/tests/test_checkpointing_ResumingManager.cpp
@@ -0,0 +1,95 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_ResumingManager", "[utils/checkpointing]")
+{
+  // Need to destroy the instance created in the main for tests
+  REQUIRE_NOTHROW(ResumingManager::destroy());
+
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(ResumingManager::destroy(), "Resuming manager was not created");
+  REQUIRE_THROWS_WITH(ResumingManager::getInstance(), "instance was not created");
+#endif   // NDEBUG
+
+  REQUIRE_NOTHROW(ResumingManager::create());
+
+#ifndef NDEBUG
+  REQUIRE_THROWS_WITH(ResumingManager::create(), "Resuming manager was already created");
+#endif   // NDEBUG
+
+#ifdef PUGS_HAS_HDF5
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    {
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+      {
+        HighFive::Group cp = file.createGroup("/resuming_checkpoint");
+        cp.createAttribute("id", 13ul);
+      }
+    }
+
+    parallel::barrier();
+    REQUIRE_NOTHROW(ResumingManager::getInstance().setFilename(filename));
+    REQUIRE(ResumingManager::getInstance().filename() == filename);
+
+    REQUIRE(ResumingManager::getInstance().checkpointId() == 13);
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+#else   // PUGS_HAS_HDF5
+
+  ResumingManager::getInstance().setFilename("useless");
+  REQUIRE(ResumingManager::getInstance().filename() == "useless");
+  REQUIRE(ResumingManager::getInstance().checkpointId() == 0);
+
+#endif   // PUGS_HAS_HDF5
+
+  ResumingManager::getInstance().currentASTLevel()  = 3;
+  ResumingManager::getInstance().checkpointNumber() = 7;
+  ResumingManager::getInstance().setIsResuming(false);
+
+  REQUIRE(ResumingManager::getInstance().currentASTLevel() == 3);
+  REQUIRE(ResumingManager::getInstance().checkpointNumber() == 7);
+  REQUIRE(not ResumingManager::getInstance().isResuming());
+
+  ResumingManager::getInstance().currentASTLevel()  = 1;
+  ResumingManager::getInstance().checkpointNumber() = 5;
+  ResumingManager::getInstance().setIsResuming(true);
+
+  REQUIRE(ResumingManager::getInstance().currentASTLevel() == 1);
+  REQUIRE(ResumingManager::getInstance().checkpointNumber() == 5);
+  REQUIRE(ResumingManager::getInstance().isResuming());
+
+  REQUIRE_NOTHROW(ResumingManager::destroy());
+
+  // Recreate ResumingManager for remaining tests
+  REQUIRE_NOTHROW(ResumingManager::create());
+}
diff --git a/tests/test_checkpointing_ResumingUtils.cpp b/tests/test_checkpointing_ResumingUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..23c8e7e99d36e009a2f8f6b04d53da4c28183331
--- /dev/null
+++ b/tests/test_checkpointing_ResumingUtils.cpp
@@ -0,0 +1,77 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/checkpointing/ResumingUtils.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_ResumingUtils", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    const std::string data_file0 = R"(Un tiens vaut mieux que deux tu l'auras,
+Un tiens vaut mieux que deux tu l'auras,...)";
+    const std::string data_file1 = R"(All work and no play makes Jack a dull boy,
+All work and no play makes Jack a dull boy,...)";
+    const std::string data_file2 = R"(solo trabajo y nada de juego hacen de Jack un chico aburrido,
+solo trabajo y nada de juego hacen de Jack un chico aburrido,...)";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    file.createGroup("/checkpoint_0").createAttribute("data.pgs", data_file0);
+    file.createGroup("/checkpoint_1").createAttribute("data.pgs", data_file1);
+    file.createGroup("/checkpoint_2").createAttribute("data.pgs", data_file2);
+    file.createHardLink("last_checkpoint", file.getGroup("/checkpoint_2"));
+
+    file.createHardLink("resuming_checkpoint", file.getGroup("/checkpoint_0"));
+    file.flush();
+    parallel::barrier();
+    REQUIRE(resumingDatafile(filename) == data_file0);
+    parallel::barrier();
+
+    file.unlink("resuming_checkpoint");
+    file.createHardLink("resuming_checkpoint", file.getGroup("/checkpoint_1"));
+    file.flush();
+    parallel::barrier();
+    REQUIRE(resumingDatafile(filename) == data_file1);
+    parallel::barrier();
+
+    file.unlink("resuming_checkpoint");
+    file.createHardLink("resuming_checkpoint", file.getGroup("/checkpoint_2"));
+    file.flush();
+    parallel::barrier();
+    REQUIRE(resumingDatafile(filename) == data_file2);
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+#else   // PUGS_HAS_HDF5
+
+  REQUIRE_THROWS_WITH(resumingDatafile("foo.h5"), "error: Resuming requires HDF5");
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_SetResumeFrom.cpp b/tests/test_checkpointing_SetResumeFrom.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bead609386049914cc3a55edcab77651f67812e4
--- /dev/null
+++ b/tests/test_checkpointing_SetResumeFrom.cpp
@@ -0,0 +1,112 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/HighFivePugsUtils.hpp>
+#include <utils/Messenger.hpp>
+#include <utils/checkpointing/SetResumeFrom.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_SetResumeFrom", "[utils/checkpointing]")
+{
+#ifdef PUGS_HAS_HDF5
+
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    const std::string data_file0 = R"(Un tiens vaut mieux que deux tu l'auras,
+Un tiens vaut mieux que deux tu l'auras,...)";
+    const std::string data_file1 = R"(All work and no play makes Jack a dull boy,
+All work and no play makes Jack a dull boy,...)";
+    const std::string data_file2 = R"(solo trabajo y nada de juego hacen de Jack un chico aburrido,
+solo trabajo y nada de juego hacen de Jack un chico aburrido,...)";
+
+    {
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+      file.createGroup("/checkpoint_0").createAttribute("data.pgs", data_file0);
+      file.createGroup("/checkpoint_1").createAttribute("data.pgs", data_file1);
+      file.createGroup("/checkpoint_2").createAttribute("data.pgs", data_file2);
+    }
+
+    {
+      std::ostringstream os;
+      setResumeFrom(filename, 0, os);
+      REQUIRE(os.str() == "Resuming checkpoint successfully set to 0\n");
+
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::ReadOnly, fapl);
+      REQUIRE(file.getGroup("/resuming_checkpoint").getAttribute("data.pgs").read<std::string>() == data_file0);
+    }
+
+    {
+      std::ostringstream os;
+      setResumeFrom(filename, 1, os);
+      REQUIRE(os.str() == "Resuming checkpoint successfully set to 1\n");
+
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::ReadOnly, fapl);
+      REQUIRE(file.getGroup("/resuming_checkpoint").getAttribute("data.pgs").read<std::string>() == data_file1);
+    }
+
+    {
+      std::ostringstream os;
+      setResumeFrom(filename, 2, os);
+      REQUIRE(os.str() == "Resuming checkpoint successfully set to 2\n");
+
+      HighFive::FileAccessProps fapl;
+      fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+      fapl.add(HighFive::MPIOCollectiveMetadata{});
+      HighFive::File file = HighFive::File(filename, HighFive::File::ReadOnly, fapl);
+      REQUIRE(file.getGroup("/resuming_checkpoint").getAttribute("data.pgs").read<std::string>() == data_file2);
+    }
+
+    {
+      std::ostringstream error_msg;
+      error_msg << "error: cannot find checkpoint " << 12 << " in " << filename;
+      REQUIRE_THROWS_WITH(setResumeFrom(filename, 12), error_msg.str());
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+
+#else   // PUGS_HAS_HDF5
+
+  if (parallel::rank() == 0) {
+    std::cerr.setstate(std::ios::badbit);
+  }
+
+  std::ostringstream os;
+  REQUIRE_NOTHROW(setResumeFrom("foo.h5", 0, os));
+
+  if (parallel::rank() == 0) {
+    std::cerr.clear();
+  }
+
+  REQUIRE(os.str() == "");
+
+#endif   // PUGS_HAS_HDF5
+}
diff --git a/tests/test_checkpointing_SubItemArrayPerItemVariant.cpp b/tests/test_checkpointing_SubItemArrayPerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bf13d74e01e0b24b18caaf88859fba598c153f10
--- /dev/null
+++ b/tests/test_checkpointing_SubItemArrayPerItemVariant.cpp
@@ -0,0 +1,396 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemArrayPerItemVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadSubItemArrayPerItemVariant.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteSubItemArrayPerItemVariant.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+template <typename DataType, typename ItemOfItemTypeT>
+PUGS_INLINE void
+check_is_same(const SubItemArrayPerItem<DataType, ItemOfItemTypeT>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_table = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    if ((a.numberOfRows() == b.numberOfRows()) and (a.numberOfColumns() == b.numberOfColumns())) {
+      for (size_t i = 0; i < a.numberOfRows(); ++i) {
+        for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+          same &= (a(i, j) == b(i, j));
+        }
+      }
+    }
+
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const SubItemArrayPerItemVariant>&>(e_read_data.get()));
+
+  std::shared_ptr<const SubItemArrayPerItemVariant> p_new_data_v =
+    dynamic_cast<const DataHandler<const SubItemArrayPerItemVariant>&>(e_read_data.get()).data_ptr();
+
+  using SubItemArrayPerItemT = SubItemArrayPerItem<const DataType, ItemOfItemTypeT>;
+
+  SubItemArrayPerItemT read_data = p_new_data_v->get<SubItemArrayPerItemT>();
+
+  switch (reference.connectivity_ptr()->dimension()) {
+  case 1: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<1>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<1>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 2: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<2>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<2>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 3: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<3>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<3>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid connectivity dimension");
+  }
+  }
+
+  REQUIRE(same_table(reference.tableView(), read_data.tableView()));
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_SubItemArrayPerItemVariant", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+    SECTION("Connectivity")
+    {
+      using R1   = TinyVector<1>;
+      using R2   = TinyVector<2>;
+      using R3   = TinyVector<3>;
+      using R1x1 = TinyMatrix<1>;
+      using R2x2 = TinyMatrix<2>;
+      using R3x3 = TinyMatrix<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      NodeArrayPerCell<bool> node_B_per_cell_1d{mesh_1d->connectivity(), 2};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        auto node_list = mesh_1d->connectivity().cellToNodeMatrix()[cell_id];
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          for (size_t i = 0; i < node_B_per_cell_1d.sizeOfArrays(); ++i) {
+            node_B_per_cell_1d[cell_id][i_node][i] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells())) % 2;
+          }
+        }
+      }
+
+      NodeArrayPerCell<uint64_t> node_N_per_cell_1d{mesh_1d->connectivity(), 3};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        auto node_list = mesh_1d->connectivity().cellToNodeMatrix()[cell_id];
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          for (size_t i = 0; i < node_N_per_cell_1d.sizeOfArrays(); ++i) {
+            node_N_per_cell_1d[cell_id][i_node][i] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells()));
+          }
+        }
+      }
+
+      CellArrayPerNode<int64_t> cell_Z_per_node_1d{mesh_1d->connectivity(), 2};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        auto cell_list = mesh_1d->connectivity().nodeToCellMatrix()[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          for (size_t i = 0; i < cell_Z_per_node_1d.sizeOfArrays(); ++i) {
+            cell_Z_per_node_1d[node_id][i_cell][i] =
+              100 * (std::rand() - RAND_MAX / 2.) / (RAND_MAX / mesh_1d->numberOfNodes());
+          }
+        }
+      }
+
+      CellArrayPerNode<double> cell_R_per_node_1d{mesh_1d->connectivity(), 3};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        auto cell_list = mesh_1d->connectivity().nodeToCellMatrix()[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          for (size_t i = 0; i < cell_R_per_node_1d.sizeOfArrays(); ++i) {
+            cell_R_per_node_1d[node_id][i_cell][i] = std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes());
+          }
+        }
+      }
+
+      NodeArrayPerCell<R1> node_R1_per_cell_1d{mesh_1d->connectivity(), 4};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        auto node_list = mesh_1d->connectivity().cellToNodeMatrix()[cell_id];
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          for (size_t i = 0; i < node_R1_per_cell_1d.sizeOfArrays(); ++i) {
+            node_R1_per_cell_1d[cell_id][i_node][i] = R1{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+          }
+        }
+      }
+
+      CellArrayPerNode<R2> cell_R2_per_node_1d{mesh_1d->connectivity(), 2};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        auto cell_list = mesh_1d->connectivity().nodeToCellMatrix()[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          for (size_t i = 0; i < cell_R2_per_node_1d.sizeOfArrays(); ++i) {
+            cell_R2_per_node_1d[node_id][i_cell][i] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes()),
+                                                         std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes())};
+          }
+        }
+      }
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      CellArrayPerFace<R3> cell_R3_per_face_2d{mesh_2d->connectivity(), 3};
+      for (FaceId face_id = 0; face_id < mesh_2d->numberOfFaces(); ++face_id) {
+        auto cell_list = mesh_2d->connectivity().faceToCellMatrix()[face_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          for (size_t i = 0; i < cell_R3_per_face_2d.sizeOfArrays(); ++i) {
+            cell_R3_per_face_2d[face_id][i_cell][i] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                                         std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                                         std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces())};
+          }
+        }
+      }
+
+      FaceArrayPerNode<R2x2> face_R2x2_per_node_2d{mesh_2d->connectivity(), 3};
+      for (NodeId node_id = 0; node_id < mesh_2d->numberOfNodes(); ++node_id) {
+        auto face_list = mesh_2d->connectivity().nodeToFaceMatrix()[node_id];
+        for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+          for (size_t i = 0; i < face_R2x2_per_node_2d.sizeOfArrays(); ++i) {
+            face_R2x2_per_node_2d[node_id][i_face][i] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes())};
+          }
+        }
+      }
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      FaceArrayPerEdge<R3> face_R3_per_edge_3d{mesh_3d->connectivity(), 2};
+      for (EdgeId edge_id = 0; edge_id < mesh_3d->numberOfEdges(); ++edge_id) {
+        auto face_list = mesh_3d->connectivity().edgeToFaceMatrix()[edge_id];
+        for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+          for (size_t i = 0; i < face_R3_per_edge_3d.sizeOfArrays(); ++i) {
+            face_R3_per_edge_3d[edge_id][i_face][i] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                                         std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                                         std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges())};
+          }
+        }
+      }
+
+      EdgeArrayPerFace<R1x1> edge_R1x1_per_face_3d{mesh_3d->connectivity(), 3};
+      for (FaceId face_id = 0; face_id < mesh_3d->numberOfFaces(); ++face_id) {
+        auto edge_list = mesh_3d->connectivity().faceToEdgeMatrix()[face_id];
+        for (size_t i_edge = 0; i_edge < edge_list.size(); ++i_edge) {
+          for (size_t i = 0; i < edge_R1x1_per_face_3d.sizeOfArrays(); ++i) {
+            edge_R1x1_per_face_3d[face_id][i_edge][i] = R1x1{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfFaces())};
+          }
+        }
+      }
+
+      EdgeArrayPerNode<R3x3> edge_R3x3_per_node_3d{mesh_3d->connectivity(), 4};
+      for (NodeId node_id = 0; node_id < mesh_3d->numberOfNodes(); ++node_id) {
+        auto edge_list = mesh_3d->connectivity().nodeToEdgeMatrix()[node_id];
+        for (size_t i_edge = 0; i_edge < edge_list.size(); ++i_edge) {
+          for (size_t i = 0; i < edge_R3x3_per_node_3d.sizeOfArrays(); ++i) {
+            edge_R3x3_per_node_3d[node_id][i_edge][i] = R3x3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                             std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes())};
+          }
+        }
+      }
+
+      {   // Write
+        using DataHandlerT = DataHandler<const SubItemArrayPerItemVariant>;
+
+        auto new_connectivity_1d = test_only::duplicateConnectivity(mesh_1d->connectivity());
+
+        NodeArrayPerCell<const bool> node_B_per_cell_1d_new{*new_connectivity_1d, node_B_per_cell_1d.tableView()};
+        CellArrayPerNode<const int64_t> cell_Z_per_node_1d_new{*new_connectivity_1d, cell_Z_per_node_1d.tableView()};
+        NodeArrayPerCell<const uint64_t> node_N_per_cell_1d_new{*new_connectivity_1d, node_N_per_cell_1d.tableView()};
+        CellArrayPerNode<const double> cell_R_per_node_1d_new{*new_connectivity_1d, cell_R_per_node_1d.tableView()};
+        NodeArrayPerCell<const R1> node_R1_per_cell_1d_new{*new_connectivity_1d, node_R1_per_cell_1d.tableView()};
+        CellArrayPerNode<const R2> cell_R2_per_node_1d_new{*new_connectivity_1d, cell_R2_per_node_1d.tableView()};
+
+        checkpointing::writeSubItemArrayPerItemVariant("node_B_per_cell_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           node_B_per_cell_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("node_N_per_cell_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           node_N_per_cell_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("cell_Z_per_node_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           cell_Z_per_node_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("cell_R_per_node_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           cell_R_per_node_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("node_R1_per_cell_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           node_R1_per_cell_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("cell_R2_per_node_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           cell_R2_per_node_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_2d = test_only::duplicateConnectivity(mesh_2d->connectivity());
+
+        CellArrayPerFace<const R3> cell_R3_per_face_2d_new{*new_connectivity_2d, cell_R3_per_face_2d.tableView()};
+        FaceArrayPerNode<const R2x2> face_R2x2_per_node_2d_new{*new_connectivity_2d, face_R2x2_per_node_2d.tableView()};
+
+        checkpointing::writeSubItemArrayPerItemVariant("cell_R3_per_face_2d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           cell_R3_per_face_2d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("face_R2x2_per_node_2d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           face_R2x2_per_node_2d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_3d = test_only::duplicateConnectivity(mesh_3d->connectivity());
+
+        FaceArrayPerEdge<const R3> face_R3_per_edge_3d_new{*new_connectivity_3d, face_R3_per_edge_3d.tableView()};
+        EdgeArrayPerFace<const R1x1> edge_R1x1_per_face_3d_new{*new_connectivity_3d, edge_R1x1_per_face_3d.tableView()};
+        EdgeArrayPerNode<const R3x3> edge_R3x3_per_node_3d_new{*new_connectivity_3d, edge_R3x3_per_node_3d.tableView()};
+
+        checkpointing::writeSubItemArrayPerItemVariant("face_R3_per_edge_3d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           face_R3_per_edge_3d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("edge_R1x1_per_face_3d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           edge_R1x1_per_face_3d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemArrayPerItemVariant("edge_R3x3_per_node_3d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemArrayPerItemVariant>(
+                                                           edge_R3x3_per_node_3d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      {   // Read
+        auto e_cell_B_per_node_1d =
+          checkpointing::readSubItemArrayPerItemVariant("node_B_per_cell_1d", symbol_table_group);
+        test_only::check_is_same(node_B_per_cell_1d, e_cell_B_per_node_1d);
+
+        auto e_cell_N_per_node_1d =
+          checkpointing::readSubItemArrayPerItemVariant("node_N_per_cell_1d", symbol_table_group);
+        test_only::check_is_same(node_N_per_cell_1d, e_cell_N_per_node_1d);
+
+        auto e_node_Z_1d = checkpointing::readSubItemArrayPerItemVariant("cell_Z_per_node_1d", symbol_table_group);
+        test_only::check_is_same(cell_Z_per_node_1d, e_node_Z_1d);
+
+        auto e_node_R_1d = checkpointing::readSubItemArrayPerItemVariant("cell_R_per_node_1d", symbol_table_group);
+        test_only::check_is_same(cell_R_per_node_1d, e_node_R_1d);
+
+        auto e_cell_R1_1d = checkpointing::readSubItemArrayPerItemVariant("node_R1_per_cell_1d", symbol_table_group);
+        test_only::check_is_same(node_R1_per_cell_1d, e_cell_R1_1d);
+
+        auto e_node_R2_1d = checkpointing::readSubItemArrayPerItemVariant("cell_R2_per_node_1d", symbol_table_group);
+        test_only::check_is_same(cell_R2_per_node_1d, e_node_R2_1d);
+
+        auto e_face_R3_2d = checkpointing::readSubItemArrayPerItemVariant("cell_R3_per_face_2d", symbol_table_group);
+        test_only::check_is_same(cell_R3_per_face_2d, e_face_R3_2d);
+
+        auto e_node_R2x2_2d =
+          checkpointing::readSubItemArrayPerItemVariant("face_R2x2_per_node_2d", symbol_table_group);
+        test_only::check_is_same(face_R2x2_per_node_2d, e_node_R2x2_2d);
+
+        auto e_edge_R3_3d = checkpointing::readSubItemArrayPerItemVariant("face_R3_per_edge_3d", symbol_table_group);
+        test_only::check_is_same(face_R3_per_edge_3d, e_edge_R3_3d);
+
+        auto e_face_R1x1_3d =
+          checkpointing::readSubItemArrayPerItemVariant("edge_R1x1_per_face_3d", symbol_table_group);
+        test_only::check_is_same(edge_R1x1_per_face_3d, e_face_R1x1_3d);
+
+        auto e_node_R3x3_3d =
+          checkpointing::readSubItemArrayPerItemVariant("edge_R3x3_per_node_3d", symbol_table_group);
+        test_only::check_is_same(edge_R3x3_per_node_3d, e_node_R3x3_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_SubItemValuePerItemVariant.cpp b/tests/test_checkpointing_SubItemValuePerItemVariant.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cc1553757677c8e32a25fe1393a807d7f1024f9b
--- /dev/null
+++ b/tests/test_checkpointing_SubItemValuePerItemVariant.cpp
@@ -0,0 +1,369 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemValuePerItemVariant.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadSubItemValuePerItemVariant.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteSubItemValuePerItemVariant.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+template <typename DataType, typename ItemOfItemTypeT>
+PUGS_INLINE void
+check_is_same(const SubItemValuePerItem<DataType, ItemOfItemTypeT>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const SubItemValuePerItemVariant>&>(e_read_data.get()));
+
+  std::shared_ptr<const SubItemValuePerItemVariant> p_new_data_v =
+    dynamic_cast<const DataHandler<const SubItemValuePerItemVariant>&>(e_read_data.get()).data_ptr();
+
+  using SubItemValuePerItemT = SubItemValuePerItem<const DataType, ItemOfItemTypeT>;
+
+  SubItemValuePerItemT read_data = p_new_data_v->get<SubItemValuePerItemT>();
+
+  switch (reference.connectivity_ptr()->dimension()) {
+  case 1: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<1>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<1>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 2: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<2>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<2>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  case 3: {
+    REQUIRE(test_only::isSameConnectivity(dynamic_cast<const Connectivity<3>&>(*reference.connectivity_ptr()),
+                                          dynamic_cast<const Connectivity<3>&>(*read_data.connectivity_ptr())));
+    break;
+  }
+  default: {
+    throw UnexpectedError("invalid connectivity dimension");
+  }
+  }
+
+  REQUIRE(same_value(reference.arrayView(), read_data.arrayView()));
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_SubItemValuePerItemVariant", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+
+    SECTION("Connectivity")
+    {
+      using R1   = TinyVector<1>;
+      using R2   = TinyVector<2>;
+      using R3   = TinyVector<3>;
+      using R1x1 = TinyMatrix<1>;
+      using R2x2 = TinyMatrix<2>;
+      using R3x3 = TinyMatrix<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      NodeValuePerCell<bool> node_B_per_cell_1d{mesh_1d->connectivity()};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        auto node_list = mesh_1d->connectivity().cellToNodeMatrix()[cell_id];
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          node_B_per_cell_1d[cell_id][i_node] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells())) % 2;
+        }
+      }
+
+      NodeValuePerCell<uint64_t> node_N_per_cell_1d{mesh_1d->connectivity()};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        auto node_list = mesh_1d->connectivity().cellToNodeMatrix()[cell_id];
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          node_N_per_cell_1d[cell_id][i_node] = (std::rand() / (RAND_MAX / mesh_1d->numberOfCells()));
+        }
+      }
+
+      CellValuePerNode<int64_t> cell_Z_per_node_1d{mesh_1d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        auto cell_list = mesh_1d->connectivity().nodeToCellMatrix()[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          cell_Z_per_node_1d[node_id][i_cell] =
+            100 * (std::rand() - RAND_MAX / 2.) / (RAND_MAX / mesh_1d->numberOfNodes());
+        }
+      }
+
+      CellValuePerNode<double> cell_R_per_node_1d{mesh_1d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        auto cell_list = mesh_1d->connectivity().nodeToCellMatrix()[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          cell_R_per_node_1d[node_id][i_cell] = std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes());
+        }
+      }
+
+      NodeValuePerCell<R1> node_R1_per_cell_1d{mesh_1d->connectivity()};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        auto node_list = mesh_1d->connectivity().cellToNodeMatrix()[cell_id];
+        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
+          node_R1_per_cell_1d[cell_id][i_node] = R1{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+        }
+      }
+
+      CellValuePerNode<R2> cell_R2_per_node_1d{mesh_1d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_1d->numberOfNodes(); ++node_id) {
+        auto cell_list = mesh_1d->connectivity().nodeToCellMatrix()[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          cell_R2_per_node_1d[node_id][i_cell] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes()),
+                                                    std::rand() / (1. * RAND_MAX / mesh_1d->numberOfNodes())};
+        }
+      }
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      CellValuePerFace<R3> cell_R3_per_face_2d{mesh_2d->connectivity()};
+      for (FaceId face_id = 0; face_id < mesh_2d->numberOfFaces(); ++face_id) {
+        auto cell_list = mesh_2d->connectivity().faceToCellMatrix()[face_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          cell_R3_per_face_2d[face_id][i_cell] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                                    std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces()),
+                                                    std::rand() / (1. * RAND_MAX / mesh_2d->numberOfFaces())};
+        }
+      }
+
+      FaceValuePerNode<R2x2> face_R2x2_per_node_2d{mesh_2d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_2d->numberOfNodes(); ++node_id) {
+        auto face_list = mesh_2d->connectivity().nodeToFaceMatrix()[node_id];
+        for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+          face_R2x2_per_node_2d[node_id][i_face] = R2x2{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_2d->numberOfNodes())};
+        }
+      }
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      FaceValuePerEdge<R3> face_R3_per_edge_3d{mesh_3d->connectivity()};
+      for (EdgeId edge_id = 0; edge_id < mesh_3d->numberOfEdges(); ++edge_id) {
+        auto face_list = mesh_3d->connectivity().edgeToFaceMatrix()[edge_id];
+        for (size_t i_face = 0; i_face < face_list.size(); ++i_face) {
+          face_R3_per_edge_3d[edge_id][i_face] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                                    std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges()),
+                                                    std::rand() / (1. * RAND_MAX / mesh_3d->numberOfEdges())};
+        }
+      }
+
+      EdgeValuePerFace<R1x1> edge_R1x1_per_face_3d{mesh_3d->connectivity()};
+      for (FaceId face_id = 0; face_id < mesh_3d->numberOfFaces(); ++face_id) {
+        auto edge_list = mesh_3d->connectivity().faceToEdgeMatrix()[face_id];
+        for (size_t i_edge = 0; i_edge < edge_list.size(); ++i_edge) {
+          edge_R1x1_per_face_3d[face_id][i_edge] = R1x1{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfFaces())};
+        }
+      }
+
+      EdgeValuePerNode<R3x3> edge_R3x3_per_node_3d{mesh_3d->connectivity()};
+      for (NodeId node_id = 0; node_id < mesh_3d->numberOfNodes(); ++node_id) {
+        auto edge_list = mesh_3d->connectivity().nodeToEdgeMatrix()[node_id];
+        for (size_t i_edge = 0; i_edge < edge_list.size(); ++i_edge) {
+          edge_R3x3_per_node_3d[node_id][i_edge] = R3x3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes()),
+                                                        std::rand() / (1. * RAND_MAX / mesh_3d->numberOfNodes())};
+        }
+      }
+
+      {   // Write
+        using DataHandlerT = DataHandler<const SubItemValuePerItemVariant>;
+
+        auto new_connectivity_1d = test_only::duplicateConnectivity(mesh_1d->connectivity());
+
+        NodeValuePerCell<const bool> node_B_per_cell_1d_new{*new_connectivity_1d, node_B_per_cell_1d.arrayView()};
+        CellValuePerNode<const int64_t> cell_Z_per_node_1d_new{*new_connectivity_1d, cell_Z_per_node_1d.arrayView()};
+        NodeValuePerCell<const uint64_t> node_N_per_cell_1d_new{*new_connectivity_1d, node_N_per_cell_1d.arrayView()};
+        CellValuePerNode<const double> cell_R_per_node_1d_new{*new_connectivity_1d, cell_R_per_node_1d.arrayView()};
+        NodeValuePerCell<const R1> node_R1_per_cell_1d_new{*new_connectivity_1d, node_R1_per_cell_1d.arrayView()};
+        CellValuePerNode<const R2> cell_R2_per_node_1d_new{*new_connectivity_1d, cell_R2_per_node_1d.arrayView()};
+
+        checkpointing::writeSubItemValuePerItemVariant("node_B_per_cell_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           node_B_per_cell_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("node_N_per_cell_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           node_N_per_cell_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("cell_Z_per_node_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           cell_Z_per_node_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("cell_R_per_node_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           cell_R_per_node_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("node_R1_per_cell_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           node_R1_per_cell_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("cell_R2_per_node_1d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           cell_R2_per_node_1d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_2d = test_only::duplicateConnectivity(mesh_2d->connectivity());
+
+        CellValuePerFace<const R3> cell_R3_per_face_2d_new{*new_connectivity_2d, cell_R3_per_face_2d.arrayView()};
+        FaceValuePerNode<const R2x2> face_R2x2_per_node_2d_new{*new_connectivity_2d, face_R2x2_per_node_2d.arrayView()};
+
+        checkpointing::writeSubItemValuePerItemVariant("cell_R3_per_face_2d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           cell_R3_per_face_2d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("face_R2x2_per_node_2d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           face_R2x2_per_node_2d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        auto new_connectivity_3d = test_only::duplicateConnectivity(mesh_3d->connectivity());
+
+        FaceValuePerEdge<const R3> face_R3_per_edge_3d_new{*new_connectivity_3d, face_R3_per_edge_3d.arrayView()};
+        EdgeValuePerFace<const R1x1> edge_R1x1_per_face_3d_new{*new_connectivity_3d, edge_R1x1_per_face_3d.arrayView()};
+        EdgeValuePerNode<const R3x3> edge_R3x3_per_node_3d_new{*new_connectivity_3d, edge_R3x3_per_node_3d.arrayView()};
+
+        checkpointing::writeSubItemValuePerItemVariant("face_R3_per_edge_3d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           face_R3_per_edge_3d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("edge_R1x1_per_face_3d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           edge_R1x1_per_face_3d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        checkpointing::writeSubItemValuePerItemVariant("edge_R3x3_per_node_3d",
+                                                       EmbeddedData{std::make_shared<DataHandlerT>(
+                                                         std::make_shared<const SubItemValuePerItemVariant>(
+                                                           edge_R3x3_per_node_3d_new))},
+                                                       file, checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      {   // Read
+        auto e_cell_B_per_node_1d =
+          checkpointing::readSubItemValuePerItemVariant("node_B_per_cell_1d", symbol_table_group);
+        test_only::check_is_same(node_B_per_cell_1d, e_cell_B_per_node_1d);
+
+        auto e_cell_N_per_node_1d =
+          checkpointing::readSubItemValuePerItemVariant("node_N_per_cell_1d", symbol_table_group);
+        test_only::check_is_same(node_N_per_cell_1d, e_cell_N_per_node_1d);
+
+        auto e_node_Z_1d = checkpointing::readSubItemValuePerItemVariant("cell_Z_per_node_1d", symbol_table_group);
+        test_only::check_is_same(cell_Z_per_node_1d, e_node_Z_1d);
+
+        auto e_node_R_1d = checkpointing::readSubItemValuePerItemVariant("cell_R_per_node_1d", symbol_table_group);
+        test_only::check_is_same(cell_R_per_node_1d, e_node_R_1d);
+
+        auto e_cell_R1_1d = checkpointing::readSubItemValuePerItemVariant("node_R1_per_cell_1d", symbol_table_group);
+        test_only::check_is_same(node_R1_per_cell_1d, e_cell_R1_1d);
+
+        auto e_node_R2_1d = checkpointing::readSubItemValuePerItemVariant("cell_R2_per_node_1d", symbol_table_group);
+        test_only::check_is_same(cell_R2_per_node_1d, e_node_R2_1d);
+
+        auto e_face_R3_2d = checkpointing::readSubItemValuePerItemVariant("cell_R3_per_face_2d", symbol_table_group);
+        test_only::check_is_same(cell_R3_per_face_2d, e_face_R3_2d);
+
+        auto e_node_R2x2_2d =
+          checkpointing::readSubItemValuePerItemVariant("face_R2x2_per_node_2d", symbol_table_group);
+        test_only::check_is_same(face_R2x2_per_node_2d, e_node_R2x2_2d);
+
+        auto e_edge_R3_3d = checkpointing::readSubItemValuePerItemVariant("face_R3_per_edge_3d", symbol_table_group);
+        test_only::check_is_same(face_R3_per_edge_3d, e_edge_R3_3d);
+
+        auto e_face_R1x1_3d =
+          checkpointing::readSubItemValuePerItemVariant("edge_R1x1_per_face_3d", symbol_table_group);
+        test_only::check_is_same(edge_R1x1_per_face_3d, e_face_R1x1_3d);
+
+        auto e_node_R3x3_3d =
+          checkpointing::readSubItemValuePerItemVariant("edge_R3x3_per_node_3d", symbol_table_group);
+        test_only::check_is_same(edge_R3x3_per_node_3d, e_node_R3x3_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_Table.cpp b/tests/test_checkpointing_Table.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dd4223555dec37cbc2efc610af6f87154e94ac7c
--- /dev/null
+++ b/tests/test_checkpointing_Table.cpp
@@ -0,0 +1,146 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <utils/checkpointing/ReadTable.hpp>
+#include <utils/checkpointing/WriteTable.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("checkpointing_Table", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    SECTION("Table")
+    {
+      HighFive::Group checkpoint_group = file.createGroup("checkpoint_group");
+      HighFive::Group useless_group;
+
+      Table<CellType> cell_type_table{19 + 3 * parallel::rank(), 3};
+      cell_type_table.fill(CellType::Line);
+      for (size_t i = 0; i < 10; ++i) {
+        for (size_t j = 0; j < cell_type_table.numberOfColumns(); ++j) {
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Line;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Triangle;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Quadrangle;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Polygon;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Tetrahedron;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Diamond;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Hexahedron;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Prism;
+          cell_type_table[std::rand() / (RAND_MAX / cell_type_table.numberOfRows())][j] = CellType::Pyramid;
+        }
+      }
+      checkpointing::write(checkpoint_group, "cell_type_table", cell_type_table);
+
+      Table<CellId> cell_id_table{27 + 2 * parallel::rank(), 3};
+      cell_id_table.fill(0);
+      for (size_t i = 0; i < 20; ++i) {
+        for (size_t j = 0; j < cell_id_table.numberOfColumns(); ++j) {
+          cell_id_table[std::rand() / (RAND_MAX / cell_id_table.numberOfRows())][j] =
+            std::rand() / (RAND_MAX / cell_id_table.numberOfRows());
+        }
+      }
+      checkpointing::write(checkpoint_group, "cell_id_table", cell_id_table);
+
+      Table<FaceId> face_id_table{29 + 2 * parallel::rank(), 2};
+      face_id_table.fill(0);
+      for (size_t i = 0; i < 20; ++i) {
+        for (size_t j = 0; j < face_id_table.numberOfColumns(); ++j) {
+          face_id_table[std::rand() / (RAND_MAX / face_id_table.numberOfRows())][j] =
+            std::rand() / (RAND_MAX / face_id_table.numberOfRows());
+        }
+      }
+      checkpointing::write(checkpoint_group, "face_id_table", face_id_table);
+
+      Table<EdgeId> edge_id_table{13 + 2 * parallel::rank(), 4};
+      edge_id_table.fill(0);
+      for (size_t i = 0; i < 20; ++i) {
+        for (size_t j = 0; j < edge_id_table.numberOfColumns(); ++j) {
+          edge_id_table[std::rand() / (RAND_MAX / edge_id_table.numberOfRows())][j] =
+            std::rand() / (RAND_MAX / edge_id_table.numberOfRows());
+        }
+      }
+      checkpointing::write(checkpoint_group, "edge_id_table", edge_id_table);
+
+      Table<NodeId> node_id_table{22 + 2 * parallel::rank(), 3};
+      node_id_table.fill(0);
+      for (size_t i = 0; i < 20; ++i) {
+        for (size_t j = 0; j < node_id_table.numberOfColumns(); ++j) {
+          node_id_table[std::rand() / (RAND_MAX / node_id_table.numberOfRows())][j] =
+            std::rand() / (RAND_MAX / node_id_table.numberOfRows());
+        }
+      }
+      checkpointing::write(checkpoint_group, "node_id_table", node_id_table);
+
+      Table<double> double_table{16 + 3 * parallel::rank(), 5};
+      double_table.fill(0);
+      for (size_t i = 0; i < 20; ++i) {
+        for (size_t j = 0; j < double_table.numberOfColumns(); ++j) {
+          double_table[std::rand() / (RAND_MAX / double_table.numberOfRows())][j] =
+            (1. * std::rand()) / (1. * RAND_MAX / double_table.numberOfRows());
+        }
+      }
+      checkpointing::write(checkpoint_group, "double_table", double_table);
+
+      file.flush();
+
+      auto is_same = [](const auto& a, const auto& b) {
+        bool same = true;
+        for (size_t i = 0; i < a.numberOfRows(); ++i) {
+          for (size_t j = 0; j < a.numberOfColumns(); ++j) {
+            if (a(i, j) != b(i, j)) {
+              same = false;
+            }
+          }
+        }
+        return parallel::allReduceAnd(same);
+      };
+
+      Table read_cell_type_table = checkpointing::readTable<CellType>(checkpoint_group, "cell_type_table");
+      REQUIRE(is_same(cell_type_table, read_cell_type_table));
+
+      Table read_cell_id_table = checkpointing::readTable<CellId>(checkpoint_group, "cell_id_table");
+      REQUIRE(is_same(cell_id_table, read_cell_id_table));
+
+      Table read_face_id_table = checkpointing::readTable<FaceId>(checkpoint_group, "face_id_table");
+      REQUIRE(is_same(face_id_table, read_face_id_table));
+
+      Table read_edge_id_table = checkpointing::readTable<EdgeId>(checkpoint_group, "edge_id_table");
+      REQUIRE(is_same(edge_id_table, read_edge_id_table));
+
+      Table read_node_id_table = checkpointing::readTable<NodeId>(checkpoint_group, "node_id_table");
+      REQUIRE(is_same(node_id_table, read_node_id_table));
+
+      Table read_double_table = checkpointing::readTable<double>(checkpoint_group, "double_table");
+      REQUIRE(is_same(double_table, read_double_table));
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_checkpointing_VariableBCDescriptor.cpp b/tests/test_checkpointing_VariableBCDescriptor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e2d08dda03924c20ea290036e2fc740b32ac1024
--- /dev/null
+++ b/tests/test_checkpointing_VariableBCDescriptor.cpp
@@ -0,0 +1,259 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/Messenger.hpp>
+
+#include <language/utils/DataHandler.hpp>
+#include <language/utils/EmbeddedData.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/NumberedBoundaryDescriptor.hpp>
+#include <scheme/DiscreteFunctionVariant.hpp>
+#include <scheme/OutflowBoundaryConditionDescriptor.hpp>
+#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>
+#include <scheme/VariableBCDescriptor.hpp>
+#include <utils/GlobalVariableManager.hpp>
+#include <utils/checkpointing/ReadVariableBCDescriptor.hpp>
+#include <utils/checkpointing/ResumingData.hpp>
+#include <utils/checkpointing/WriteVariableBCDescriptor.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <checkpointing_Connectivity_utilities.hpp>
+#include <checkpointing_Mesh_utilities.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+namespace test_only
+{
+
+template <typename DataType>
+PUGS_INLINE void
+VariableBCDescriptor_check_is_same_data(const DiscreteFunctionP0<DataType>& reference, const EmbeddedData& e_read_data)
+{
+  auto same_value = [](const auto& a, const auto& b) -> bool {
+    bool same = true;
+    for (size_t i = 0; i < a.size(); ++i) {
+      same &= (a[i] == b[i]);
+    }
+    return parallel::allReduceAnd(same);
+  };
+
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const VariableBCDescriptor>&>(e_read_data.get()));
+
+  const VariableBCDescriptor& var_bc_desc_discrete_data =
+    *dynamic_cast<const DataHandler<const VariableBCDescriptor>&>(e_read_data.get()).data_ptr();
+
+  using DiscreteFunctionT = DiscreteFunctionP0<const DataType>;
+
+  DiscreteFunctionT read_data = var_bc_desc_discrete_data.discreteFunctionVariant()->get<DiscreteFunctionT>();
+
+  REQUIRE(test_only::isSameMesh(read_data.meshVariant(), reference.meshVariant()));
+
+  REQUIRE(same_value(reference.cellValues().arrayView(), read_data.cellValues().arrayView()));
+}
+
+PUGS_INLINE void
+VariableBCDescriptor_check_is_bc_list(
+  const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>& reference_bc_list,
+  const EmbeddedData& e_read_data)
+{
+  REQUIRE_NOTHROW(dynamic_cast<const DataHandler<const VariableBCDescriptor>&>(e_read_data.get()));
+
+  const VariableBCDescriptor& var_bc_desc_discrete_data =
+    *dynamic_cast<const DataHandler<const VariableBCDescriptor>&>(e_read_data.get()).data_ptr();
+
+  auto read_bc_list = var_bc_desc_discrete_data.bcDescriptorList();
+
+  REQUIRE(read_bc_list.size() == reference_bc_list.size());
+  for (size_t i = 0; i < read_bc_list.size(); ++i) {
+    const auto reference_bc = reference_bc_list[i];
+    const auto read_bc      = read_bc_list[i];
+    REQUIRE(read_bc->type() == reference_bc->type());
+    REQUIRE(read_bc->boundaryDescriptor().type() == reference_bc->boundaryDescriptor().type());
+
+    switch (read_bc->boundaryDescriptor().type()) {
+    case IBoundaryDescriptor::Type::named: {
+      const NamedBoundaryDescriptor& ref_named_bc =
+        dynamic_cast<const NamedBoundaryDescriptor&>(reference_bc->boundaryDescriptor());
+      const NamedBoundaryDescriptor& read_named_bc =
+        dynamic_cast<const NamedBoundaryDescriptor&>(read_bc->boundaryDescriptor());
+      REQUIRE(ref_named_bc.name() == read_named_bc.name());
+      break;
+    }
+    case IBoundaryDescriptor::Type::numbered: {
+      const NumberedBoundaryDescriptor& ref_numbered_bc =
+        dynamic_cast<const NumberedBoundaryDescriptor&>(reference_bc->boundaryDescriptor());
+      const NumberedBoundaryDescriptor& read_numbered_bc =
+        dynamic_cast<const NumberedBoundaryDescriptor&>(read_bc->boundaryDescriptor());
+      REQUIRE(ref_numbered_bc.number() == read_numbered_bc.number());
+      break;
+    }
+    }
+  }
+}
+
+}   // namespace test_only
+
+TEST_CASE("checkpointing_VariableBCDescriptor", "[utils/checkpointing]")
+{
+  std::string tmp_dirname;
+  {
+    {
+      if (parallel::rank() == 0) {
+        tmp_dirname = [&]() -> std::string {
+          std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_checkpointing_XXXXXX";
+          return std::string{mkdtemp(&temp_filename[0])};
+        }();
+      }
+      parallel::broadcast(tmp_dirname, 0);
+    }
+    std::filesystem::path path = tmp_dirname;
+    const std::string filename = path / "checkpoint.h5";
+
+    HighFive::FileAccessProps fapl;
+    fapl.add(HighFive::MPIOFileAccess{MPI_COMM_WORLD, MPI_INFO_NULL});
+    fapl.add(HighFive::MPIOCollectiveMetadata{});
+    HighFive::File file = HighFive::File(filename, HighFive::File::Truncate, fapl);
+
+    const size_t initial_connectivity_id = GlobalVariableManager::instance().getConnectivityId();
+    const size_t initial_mesh_id         = GlobalVariableManager::instance().getMeshId();
+
+    SECTION("Mesh")
+    {
+      using R2 = TinyVector<2>;
+      using R3 = TinyVector<3>;
+
+      HighFive::Group checkpoint_group   = file.createGroup("checkpoint");
+      HighFive::Group symbol_table_group = checkpoint_group.createGroup("symbol_table");
+
+      auto mesh_1d = MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+      DiscreteFunctionP0<R2> df_R2_1d{mesh_1d};
+      for (CellId cell_id = 0; cell_id < mesh_1d->numberOfCells(); ++cell_id) {
+        df_R2_1d[cell_id] = R2{std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_1d->numberOfCells())};
+      }
+
+      std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>> bc_1d =
+        {std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("XMIN")),
+         std::make_shared<OutflowBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("XMAX"))};
+
+      auto mesh_2d = MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+      DiscreteFunctionP0<R3> df_R3_2d{mesh_2d};
+      for (CellId cell_id = 0; cell_id < mesh_2d->numberOfCells(); ++cell_id) {
+        df_R3_2d[cell_id] = R3{std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_2d->numberOfCells())};
+      }
+
+      std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>> bc_2d =
+        {std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("XMIN")),
+         std::make_shared<OutflowBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("XMAX")),
+         std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("YMIN")),
+         std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("YMAX"))};
+
+      auto mesh_3d = MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+      DiscreteFunctionP0<R3> df_R3_3d{mesh_3d};
+      for (CellId cell_id = 0; cell_id < mesh_3d->numberOfCells(); ++cell_id) {
+        df_R3_3d[cell_id] = R3{std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells()),
+                               std::rand() / (1. * RAND_MAX / mesh_3d->numberOfCells())};
+      }
+
+      std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>> bc_3d =
+        {std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("XMIN")),
+         std::make_shared<OutflowBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("XMAX")),
+         std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("YMIN")),
+         std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("YMAX")),
+         std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("ZMIN")),
+         std::make_shared<SymmetryBoundaryConditionDescriptor>(std::make_shared<NamedBoundaryDescriptor>("ZMAX"))};
+
+      {   // Write
+        using DataHandlerT = DataHandler<const VariableBCDescriptor>;
+
+        auto new_mesh_1d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_1d));
+        auto new_mesh_1d                = new_mesh_1d_v->get<const Mesh<1>>();
+        const auto& new_connectivity_1d = new_mesh_1d->connectivity();
+
+        DiscreteFunctionP0<const R2> df_R2_1d_new{new_mesh_1d_v,
+                                                  CellValue<const R2>{new_connectivity_1d,
+                                                                      df_R2_1d.cellValues().arrayView()}};
+        std::shared_ptr<const VariableBCDescriptor> var_bc_desc_1d =
+          std::make_shared<VariableBCDescriptor>(std::make_shared<const DiscreteFunctionVariant>(df_R2_1d_new), bc_1d);
+
+        checkpointing::writeVariableBCDescriptor("var_bc_desc_1d",
+                                                 EmbeddedData{std::make_shared<DataHandlerT>(var_bc_desc_1d)}, file,
+                                                 checkpoint_group, symbol_table_group);
+
+        auto new_mesh_2d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_2d));
+        auto new_mesh_2d                = new_mesh_2d_v->get<const Mesh<2>>();
+        const auto& new_connectivity_2d = new_mesh_2d->connectivity();
+
+        DiscreteFunctionP0<const R3> df_R3_2d_new{new_mesh_2d_v,
+                                                  CellValue<const R3>{new_connectivity_2d,
+                                                                      df_R3_2d.cellValues().arrayView()}};
+
+        std::shared_ptr<const VariableBCDescriptor> var_bc_desc_2d =
+          std::make_shared<VariableBCDescriptor>(std::make_shared<const DiscreteFunctionVariant>(df_R3_2d_new), bc_2d);
+
+        checkpointing::writeVariableBCDescriptor("var_bc_desc_2d",
+                                                 EmbeddedData{std::make_shared<DataHandlerT>(var_bc_desc_2d)}, file,
+                                                 checkpoint_group, symbol_table_group);
+
+        auto new_mesh_3d_v              = test_only::duplicateMesh(std::make_shared<MeshVariant>(mesh_3d));
+        auto new_mesh_3d                = new_mesh_3d_v->get<const Mesh<3>>();
+        const auto& new_connectivity_3d = new_mesh_3d->connectivity();
+
+        DiscreteFunctionP0<const R3> df_R3_3d_new{new_mesh_3d, CellValue<const R3>{new_connectivity_3d,
+                                                                                   df_R3_3d.cellValues().arrayView()}};
+
+        std::shared_ptr<const VariableBCDescriptor> var_bc_desc_3d =
+          std::make_shared<VariableBCDescriptor>(std::make_shared<const DiscreteFunctionVariant>(df_R3_3d_new), bc_3d);
+
+        checkpointing::writeVariableBCDescriptor("var_bc_desc_3d",
+                                                 EmbeddedData{std::make_shared<DataHandlerT>(var_bc_desc_3d)}, file,
+                                                 checkpoint_group, symbol_table_group);
+
+        HighFive::Group global_variables_group = checkpoint_group.createGroup("singleton/global_variables");
+        global_variables_group.createAttribute("connectivity_id",
+                                               GlobalVariableManager::instance().getConnectivityId());
+        global_variables_group.createAttribute("mesh_id", GlobalVariableManager::instance().getMeshId());
+      }
+
+      // reset to reuse after resuming
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+
+      file.flush();
+
+      checkpointing::ResumingData::create();
+      checkpointing::ResumingData::instance().readData(checkpoint_group, nullptr);
+
+      GlobalVariableManager::instance().setConnectivityId(initial_connectivity_id);
+      GlobalVariableManager::instance().setMeshId(initial_mesh_id);
+      {   // Read
+        auto e_var_bc_desc_1d = checkpointing::readVariableBCDescriptor("var_bc_desc_1d", symbol_table_group);
+        test_only::VariableBCDescriptor_check_is_bc_list(bc_1d, e_var_bc_desc_1d);
+        test_only::VariableBCDescriptor_check_is_same_data(df_R2_1d, e_var_bc_desc_1d);
+
+        auto e_var_bc_desc_2d = checkpointing::readVariableBCDescriptor("var_bc_desc_2d", symbol_table_group);
+        test_only::VariableBCDescriptor_check_is_bc_list(bc_2d, e_var_bc_desc_2d);
+        test_only::VariableBCDescriptor_check_is_same_data(df_R3_2d, e_var_bc_desc_2d);
+
+        auto e_var_bc_desc_3d = checkpointing::readVariableBCDescriptor("var_bc_desc_3d", symbol_table_group);
+        test_only::VariableBCDescriptor_check_is_bc_list(bc_3d, e_var_bc_desc_3d);
+        test_only::VariableBCDescriptor_check_is_same_data(df_R3_3d, e_var_bc_desc_3d);
+      }
+      checkpointing::ResumingData::destroy();
+    }
+  }
+
+  parallel::barrier();
+  if (parallel::rank() == 0) {
+    std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  }
+}
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index d8641d318f243eb624915882a92fc15d57a71346..c4fca371ef152cba6e319141f3fac6424cb7d4ef 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -13,6 +13,7 @@
 #include <utils/PETScWrapper.hpp>
 #include <utils/RandomEngine.hpp>
 #include <utils/SLEPcWrapper.hpp>
+#include <utils/checkpointing/ResumingManager.hpp>
 
 #include <MeshDataBaseForTests.hpp>
 
@@ -22,7 +23,16 @@ int
 main(int argc, char* argv[])
 {
   parallel::Messenger::create(argc, argv);
-  const int nb_threads = std::max(std::thread::hardware_concurrency() / 2, 1u);
+  int nb_threads = std::min(std::max(std::thread::hardware_concurrency() / 2, 1u), 8u);
+
+  Catch::Session session;
+
+  auto cli = session.cli() | Catch::Clara::Opt(nb_threads, "number of threads")["--threads"](
+                               "number of threads (default: max logical threads)");
+
+  session.cli(cli);
+
+  int result = session.applyCommandLine(argc, argv);
 
   {
     Kokkos::InitializationSettings args;
@@ -39,9 +49,6 @@ main(int argc, char* argv[])
   PETScWrapper::initialize(argc, argv);
   SLEPcWrapper::initialize(argc, argv);
 
-  Catch::Session session;
-  int result = session.applyCommandLine(argc, argv);
-
   if (result == 0) {
     const auto& config = session.config();
     if (config.listReporters() or config.listTags() or config.listTests()) {
@@ -52,6 +59,8 @@ main(int argc, char* argv[])
       // Disable outputs from tested classes to the standard output
       std::cout.setstate(std::ios::badbit);
 
+      ResumingManager::create();
+
       SynchronizerManager::create();
       RandomEngine::create();
       QuadratureManager::create();
@@ -77,6 +86,8 @@ main(int argc, char* argv[])
       QuadratureManager::destroy();
       RandomEngine::destroy();
       SynchronizerManager::destroy();
+
+      ResumingManager::destroy();
     }
   }
 
diff --git a/tools/docker-pugs.sh b/tools/docker-pugs.sh
index 4a5b0d6611b2e62b6bf84adcfdd0805add61e7b0..35d98b545cb932743f82e711251c3325a3608161 100755
--- a/tools/docker-pugs.sh
+++ b/tools/docker-pugs.sh
@@ -35,7 +35,7 @@ mkdir -p ${DOCKERFILE_DIR}
 DOCKERFILE="${DOCKERFILE_DIR}/Dockerfile"
 
 cat > ${DOCKERFILE} <<EOF
-FROM ubuntu:focal
+FROM ubuntu:jammy
 
 ARG DEBIAN_FRONTEND=noninteractive
 ENV USER="${USER}" USER_ID="${USER_ID}" USER_GID="${USER_GID}" HOSTNAME="${DOCKER_HOSTNAME}"
@@ -46,7 +46,7 @@ RUN useradd --uid "${USER_ID}" --gid "${USER_GID}" --create-home --shell /bin/ba
 
 RUN apt-get update && apt-get -y upgrade && apt-get -y remove g++ gcc && apt-get -y install apt-utils gnupg gnupg2 gnupg1 wget cmake git make lcov bc pkg-config sudo doxygen
 
-RUN apt-get -y install clang-10 clang-format-10
+RUN apt-get -y install clang-11 clang-format-11
 
 RUN apt-get -y install libparmetis-dev petsc-dev slepc-dev
 RUN apt-get clean
@@ -54,9 +54,9 @@ RUN apt-get clean
 RUN rm /usr/bin/cc
 RUN echo "${USER} ALL=(ALL:ALL) NOPASSWD:ALL" > "/etc/sudoers.d/${USER}"
 
-RUN ln -s /usr/bin/clang-format-10 /usr/bin/clang-format
+RUN ln -s /usr/bin/clang-format-11 /usr/bin/clang-format
 
-ENV CC="clang-10" CXX="clang++-10"
+ENV CC="clang-11" CXX="clang++-11"
 
 EOF