From eaca766aa0a7328080225750390695791129e6a3 Mon Sep 17 00:00:00 2001
From: Paul Dechamps <pauldechamps@uliege.be>
Date: Wed, 15 Nov 2023 12:55:22 +0100
Subject: [PATCH] First commit.

Contains 2D and 3D developements as well as the api.
---
 .gitignore                                    |  59 ++
 .gitlab-ci.yml                                |  75 ++
 .gitmodules                                   |   3 +
 CMakeLists.txt                                | 132 +++
 CODEOWNERS                                    |  15 +
 LICENSE                                       | 201 +++++
 README.md                                     |  93 +-
 amfe                                          |   1 +
 blast/CMakeLists.txt                          |  29 +
 blast/__init__.py                             |  21 +
 blast/_src/CMakeLists.txt                     |  35 +
 blast/_src/blastw.h                           |  17 +
 blast/_src/blastw.i                           |  57 ++
 blast/api/__init__.py                         |   1 +
 blast/api/core.py                             | 117 +++
 blast/coupler.py                              | 102 +++
 blast/interfaces/DDataStructure.py            |  88 ++
 blast/interfaces/DSolversInterface.py         | 261 ++++++
 blast/interfaces/dart/DartInterface.py        | 337 ++++++++
 blast/interfaces/dart/__init__.py             |   1 +
 .../interfaces/interpolators/DGroupBuilder.py | 169 ++++
 .../interpolators/DMatchingInterpolator.py    |  27 +
 .../interpolators/DRbfInterpolator.py         |  56 ++
 blast/interfaces/su2/SU2Interface.py          | 209 +++++
 blast/models/dart/n0012.geo                   | 282 +++++++
 blast/models/dart/rae2822.geo                 | 200 +++++
 blast/models/references/blXfoil_n0012.dat     | 227 +++++
 blast/models/references/cpRef_rae2822.dat     | 103 +++
 blast/models/references/cpXfoilInv_n0012.dat  | 201 +++++
 blast/models/references/cpXfoil_n0012.dat     | 201 +++++
 blast/src/CMakeLists.txt                      |  27 +
 blast/src/DBoundaryLayer.cpp                  | 196 +++++
 blast/src/DBoundaryLayer.h                    |  69 ++
 blast/src/DClosures.cpp                       | 257 ++++++
 blast/src/DClosures.h                         |  30 +
 blast/src/DDiscretization.cpp                 |  77 ++
 blast/src/DDiscretization.h                   |  54 ++
 blast/src/DDriver.cpp                         | 795 ++++++++++++++++++
 blast/src/DDriver.h                           |  76 ++
 blast/src/DEdge.cpp                           |  59 ++
 blast/src/DEdge.h                             |  44 +
 blast/src/DFluxes.cpp                         | 243 ++++++
 blast/src/DFluxes.h                           |  31 +
 blast/src/DSolver.cpp                         | 247 ++++++
 blast/src/DSolver.h                           |  51 ++
 blast/src/blast.h                             |  53 ++
 blast/tests/dart/blidart.py                   | 196 +++++
 blast/utils.py                                | 192 +++++
 run.py                                        |  36 +
 49 files changed, 5966 insertions(+), 87 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 create mode 100644 .gitmodules
 create mode 100644 CMakeLists.txt
 create mode 100644 CODEOWNERS
 create mode 100644 LICENSE
 create mode 160000 amfe
 create mode 100644 blast/CMakeLists.txt
 create mode 100644 blast/__init__.py
 create mode 100644 blast/_src/CMakeLists.txt
 create mode 100644 blast/_src/blastw.h
 create mode 100644 blast/_src/blastw.i
 create mode 100644 blast/api/__init__.py
 create mode 100644 blast/api/core.py
 create mode 100644 blast/coupler.py
 create mode 100644 blast/interfaces/DDataStructure.py
 create mode 100644 blast/interfaces/DSolversInterface.py
 create mode 100644 blast/interfaces/dart/DartInterface.py
 create mode 100644 blast/interfaces/dart/__init__.py
 create mode 100755 blast/interfaces/interpolators/DGroupBuilder.py
 create mode 100644 blast/interfaces/interpolators/DMatchingInterpolator.py
 create mode 100644 blast/interfaces/interpolators/DRbfInterpolator.py
 create mode 100644 blast/interfaces/su2/SU2Interface.py
 create mode 100644 blast/models/dart/n0012.geo
 create mode 100644 blast/models/dart/rae2822.geo
 create mode 100644 blast/models/references/blXfoil_n0012.dat
 create mode 100644 blast/models/references/cpRef_rae2822.dat
 create mode 100644 blast/models/references/cpXfoilInv_n0012.dat
 create mode 100644 blast/models/references/cpXfoil_n0012.dat
 create mode 100644 blast/src/CMakeLists.txt
 create mode 100644 blast/src/DBoundaryLayer.cpp
 create mode 100644 blast/src/DBoundaryLayer.h
 create mode 100644 blast/src/DClosures.cpp
 create mode 100644 blast/src/DClosures.h
 create mode 100644 blast/src/DDiscretization.cpp
 create mode 100644 blast/src/DDiscretization.h
 create mode 100644 blast/src/DDriver.cpp
 create mode 100644 blast/src/DDriver.h
 create mode 100644 blast/src/DEdge.cpp
 create mode 100644 blast/src/DEdge.h
 create mode 100644 blast/src/DFluxes.cpp
 create mode 100644 blast/src/DFluxes.h
 create mode 100644 blast/src/DSolver.cpp
 create mode 100644 blast/src/DSolver.h
 create mode 100644 blast/src/blast.h
 create mode 100644 blast/tests/dart/blidart.py
 create mode 100644 blast/utils.py
 create mode 100755 run.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b1589cf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,59 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+
+# Misc
+core
+*.pyc
+*.*~
+
+# OS
+.DS_Store
+*.swp
+*.bak
+
+# Workspace
+workspace
+workspace.tar.gz
+*.tar.gz
+*.tgz
+
+# build dir
+build
+
+# gitlab-ci / clang-format
+patches
+
+# Gmsh
+*.db
+*.pos
+*.msh # do not commit the mesh...
+!*_lfs.msh # ... except in the lfs
+
+# sge output
+*.o*
+*.po*
+
+# matlab
+*.asv
+
+# paraview
+*.ogv
+*.pvsm
+
+# IDE
+.project
+.pydevproject
+.settings
+.vscode
+.vscode/*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..d4c2bbc
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,75 @@
+default:
+    image: rboman/waves-py3:2020.3
+    before_script:
+        - source /opt/intel/mkl/bin/mklvars.sh intel64
+        - source /opt/intel/tbb/bin/tbbvars.sh intel64
+        - echo $(nproc)
+        - printenv | sort
+
+.global_tag: &global_tag_def
+    tags:
+        - mn2l
+#        - warson   # you can choose a set of runners here
+
+variables:
+    GIT_SUBMODULE_STRATEGY: recursive
+    GIT_STRATEGY: clone # workaround full clone for each pipeline (https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26993)
+    GIT_LFS_SKIP_SMUDGE: 1 # do not pull LFS
+
+stages:
+    - build
+    - test
+
+format:
+    <<: *global_tag_def
+    stage: build
+    script:
+        - clang-format --version # we use clang-format-10 exclusively
+        - ./scripts/format_code.py
+        - mkdir -p patches
+        - if git diff --patch --exit-code > patches/clang-format.patch; then echo "Clang format changed nothing"; else echo "Clang format found changes to make!"; false; fi
+    artifacts:
+        paths:
+            - patches/
+        expire_in: 1 day
+        when: on_failure
+    allow_failure: true
+
+build:
+    <<: *global_tag_def
+    stage: build
+    script:
+        - git submodule init
+        - git submodule update
+        - rm -rf build workspace
+        - mkdir build
+        - cd build
+        - cmake -Wno-dev ..
+        - make -j 8
+    artifacts:
+        paths:
+            - build/
+        expire_in: 1 day
+
+doxygen:
+    <<: *global_tag_def
+    stage: test
+    script:
+        - cd build
+        - make dox
+    artifacts:
+        paths:
+            - build/doxygen/
+        expire_in: 1 week
+    dependencies:
+        - build
+
+ctest:
+    <<: *global_tag_def
+    stage: test
+    script:
+        - cd build
+        - ctest --output-on-failure -j 8
+    #timeout: 10 hours  # will be available in 12.3
+    dependencies:
+        - build
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..4d29aa8
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "amfe"]
+	path = amfe
+	url = git@gitlab.uliege.be:am-dept/amfe.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..a21aa56
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,132 @@
+# Copyright 2023 University of Liège
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ----------------------------------------------------------------------------
+PROJECT(BLASTER)
+# ----------------------------------------------------------------------------
+CMAKE_MINIMUM_REQUIRED(VERSION 3.14)
+
+# -- I/O
+# Lib/Exe dir
+SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin CACHE PATH
+                        "Single output directory for building all libraries.")
+SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin CACHE PATH
+                        "Single output directory for building all executables.")
+MARK_AS_ADVANCED(LIBRARY_OUTPUT_PATH EXECUTABLE_OUTPUT_PATH)
+
+# Build type
+IF(NOT CMAKE_BUILD_TYPE)
+    SET( CMAKE_BUILD_TYPE "Release" CACHE STRING 
+         "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel."
+         FORCE)
+ENDIF(NOT CMAKE_BUILD_TYPE)
+
+# Additional modules and macros
+LIST(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMake")
+
+# -- C/C++
+# Set specific languages flags
+SET(CMAKE_CXX_STANDARD 11) # newer way to set C++11 (requires cmake>=3.1)
+SET(CMAKE_CXX_STANDARD_REQUIRED ON)
+IF((CMAKE_CXX_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID MATCHES "Intel"))
+    IF(NOT APPLE)
+        SET(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-as-needed")
+    ENDIF()
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") # add verbosity
+ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
+    ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_DEPRECATE)
+    ADD_DEFINITIONS(-D_USE_MATH_DEFINES) # otherwise M_PI is undefined
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")  # parallel build with MSVC
+    #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")  # add verbosity
+ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-register")
+    #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything") # add verbosity
+ENDIF()
+
+# -- DEPENDENCIES
+# Python
+# use Python3_ROOT_DIR if wrong python found (e.g. anaconda)
+FIND_PACKAGE(Python3 COMPONENTS Interpreter Development)
+SET(PYTHON_EXECUTABLE ${Python3_EXECUTABLE})
+SET(PYTHON_LIBRARIES ${Python3_LIBRARIES})
+SET(PYTHON_INCLUDE_PATH ${Python3_INCLUDE_DIRS}) 
+SET(PYTHONLIBS_VERSION_STRING ${Python3_VERSION})     
+
+# SWIG
+FIND_PACKAGE(SWIG REQUIRED)
+IF(CMAKE_GENERATOR MATCHES "Visual Studio") # not MSVC because of nmake & jom
+    SET(CMAKE_SWIG_OUTDIR "${EXECUTABLE_OUTPUT_PATH}/$(Configuration)/")
+ELSE()
+    SET(CMAKE_SWIG_OUTDIR "${EXECUTABLE_OUTPUT_PATH}")
+ENDIF()
+
+# Doxygen (https://vicrucann.github.io/tutorials/quick-cmake-doxygen/)
+FIND_PACKAGE(Doxygen) # check if Doxygen is installed
+IF(DOXYGEN_FOUND)
+    # set input and output files
+    SET(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/Doxyfile.in)
+    SET(DOXYGEN_OUT ${PROJECT_BINARY_DIR}/Doxyfile)
+    # request to configure the file
+    CONFIGURE_FILE(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
+    # note the option ALL which allows to build the docs together with the application
+    # "make dox" rebuilds the doc
+    ADD_CUSTOM_TARGET( dox #ALL
+        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
+        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+        COMMENT "Generating API documentation with Doxygen"
+        VERBATIM )
+ELSE()
+    MESSAGE("Doxygen needs to be installed to generate the doxygen documentation")
+ENDIF()
+
+# -- DEFINE (for SWIG to detect definitions)
+INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}) # to find "amfe_def.h"
+
+# -- CTest
+ENABLE_TESTING()
+
+# -- INSTALL
+IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+    EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} -m site --user-site OUTPUT_VARIABLE PY_SITE OUTPUT_STRIP_TRAILING_WHITESPACE)
+    STRING(REGEX REPLACE "\\\\" "/" PY_SITE ${PY_SITE})
+    SET(CMAKE_INSTALL_PREFIX "${PY_SITE}/blaster" CACHE STRING "Install location" FORCE)
+    SET(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT FALSE)
+ELSE()
+    IF(NOT(CMAKE_INSTALL_PREFIX MATCHES "blaster$"))
+        SET(CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/blaster" CACHE STRING "Install location" FORCE)
+    ENDIF()
+ENDIF()
+
+IF(UNIX)
+    IF(APPLE)
+        SET(CMAKE_INSTALL_RPATH "@loader_path")
+    ELSE()
+        SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}")
+    ENDIF()
+ENDIF()
+
+# -- Sub directories
+ADD_SUBDIRECTORY( amfe )
+ADD_SUBDIRECTORY( blast )
+
+# -- FINAL
+MESSAGE(STATUS "PROJECT: ${CMAKE_PROJECT_NAME}")
+MESSAGE(STATUS "* SYSTEM NAME=\"${CMAKE_SYSTEM_NAME}\"")
+MESSAGE(STATUS "* CXX COMPILER: ${CMAKE_CXX_COMPILER_ID}")
+MESSAGE(STATUS "* CXX STANDARD: ${CMAKE_CXX_STANDARD}")
+MESSAGE(STATUS "* INSTALL DIR: ${CMAKE_INSTALL_PREFIX}")
+MESSAGE(STATUS "* BUILD TYPE: ${CMAKE_BUILD_TYPE}")
+MESSAGE(STATUS "* VTK SUPPORT: ${USE_VTK}")
+MESSAGE(STATUS "* MKL SUPPORT: ${USE_MKL}")
+MESSAGE(STATUS "* MUMPS SUPPORT: ${USE_MUMPS}")
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..d278ed4
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,15 @@
+# This is a comment.
+# Each line is a file pattern followed by one or more owners.
+
+# These owners will be the default owners for everything in
+# the repo. Unless a later match takes precedence, they
+# will be requested for review when someone opens a pull request.
+*       @R.Boman
+*       @acrovato
+*       @pdechamps
+
+# You can also use email addresses if you prefer. They'll be
+# used to look up users just like we do for commit author
+# emails.
+#*.go docs@example.com
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
index 193146f..4b37f4a 100644
--- a/README.md
+++ b/README.md
@@ -1,92 +1,11 @@
-# blaster
+## BLASTER
 
+BLASTER (Boundary-Layer Adjoint Solver for Transonic External and high Reynolds number flow) is an open-source boundary layer solver written in C++ and python. It is designed to work in a viscous-inviscid interaction (VII) scheme. Blaster is developed at the University of Liège by Paul Dechamps with the active collaboration of Adrien Crovato and the help of Amaury Bilocq and Romain Boman, and under the supervision of Vincent Terrapon and Grigorios Dimitriadis, since 2022.
 
+# Working with
 
-## Getting started
+Blaster is curently interfaced with different inviscid flow solvers in order to perform the viscous-inviscid interaction algorithm
 
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
+- dartflo, a full potential transonic solver [DARTFlo] (git@gitlab.uliege.be:am-dept/dartflo.git) developed by A. Crovato [Steady Transonic Aerodynamic and Aeroelastic Modeling for Preliminary Aircraft Design](http://hdl.handle.net/2268/251906), PhD thesis, University of Liège, 2020.
 
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
-
-## Add your files
-
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
-
-```
-cd existing_repo
-git remote add origin https://gitlab.uliege.be/am-dept/blaster.git
-git branch -M main
-git push -uf origin main
-```
-
-## Integrate with your tools
-
-- [ ] [Set up project integrations](https://gitlab.uliege.be/am-dept/blaster/-/settings/integrations)
-
-## Collaborate with your team
-
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
-
-## Test and Deploy
-
-Use the built-in continuous integration in GitLab.
-
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
-
-***
-
-# Editing this README
-
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
-
-## Suggestions for a good README
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
-
-## Name
-Choose a self-explaining name for your project.
-
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
-
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
-
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
-
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
-
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
-
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
-
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
-
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
-
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
-
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
-
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
-
-## License
-For open source projects, say how it is licensed.
-
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+- SU2 (The Euler solver), developed at Standford University [SU2] (https://github.com/su2code/SU2.git). It is recommended to use our own fork of the project as different tools to have a correct interface have been implemented there [SU2 FORK] (https://github.com/Paul-Dech/SU2.git).
diff --git a/amfe b/amfe
new file mode 160000
index 0000000..4bcec9c
--- /dev/null
+++ b/amfe
@@ -0,0 +1 @@
+Subproject commit 4bcec9c604d927933d2d74fdc837f85cb642032e
diff --git a/blast/CMakeLists.txt b/blast/CMakeLists.txt
new file mode 100644
index 0000000..530e1b8
--- /dev/null
+++ b/blast/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Copyright 2020 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Add source dir
+ADD_SUBDIRECTORY( src )
+ADD_SUBDIRECTORY( _src )
+
+# Add test dir
+MACRO_AddTest(${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+# Add to install
+INSTALL(FILES ${CMAKE_CURRENT_LIST_DIR}/__init__.py
+              ${CMAKE_CURRENT_LIST_DIR}/coupler.py
+              ${CMAKE_CURRENT_LIST_DIR}/utils.py
+        DESTINATION blast)
+INSTALL(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/api
+                  ${CMAKE_CURRENT_LIST_DIR}/interfaces
+        DESTINATION blast)
diff --git a/blast/__init__.py b/blast/__init__.py
new file mode 100644
index 0000000..c53785c
--- /dev/null
+++ b/blast/__init__.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# blast MODULE initialization file
+
+# Copyright 2020 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import fwk
+import tbox
+from blastw import *
diff --git a/blast/_src/CMakeLists.txt b/blast/_src/CMakeLists.txt
new file mode 100644
index 0000000..0fdd32e
--- /dev/null
+++ b/blast/_src/CMakeLists.txt
@@ -0,0 +1,35 @@
+# Copyright 2020 University of Liège
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# CMake input file of the SWIG wrapper around "blastw.so"
+
+INCLUDE(${SWIG_USE_FILE})
+
+FILE(GLOB SRCS *.h *.cpp *.inl *.swg)
+FILE(GLOB ISRCS *.i)
+
+SET_SOURCE_FILES_PROPERTIES(${ISRCS} PROPERTIES CPLUSPLUS ON)
+SET(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} "-interface" "_blastw") # avoids "import _module_d" with MSVC/Debug
+SWIG_ADD_LIBRARY(blastw LANGUAGE python SOURCES ${ISRCS} ${SRCS})
+SET_PROPERTY(TARGET blastw PROPERTY SWIG_USE_TARGET_INCLUDE_DIRECTORIES ON)
+MACRO_DebugPostfix(blastw)
+
+TARGET_INCLUDE_DIRECTORIES(blastw PRIVATE ${PROJECT_SOURCE_DIR}/blast/_src
+                                        ${PROJECT_SOURCE_DIR}/amfe/tbox/_src
+                                        ${PROJECT_SOURCE_DIR}/amfe/fwk/_src
+                                        ${PYTHON_INCLUDE_PATH})
+TARGET_LINK_LIBRARIES(blastw PRIVATE blast tbox fwk ${PYTHON_LIBRARIES})
+
+INSTALL(FILES ${EXECUTABLE_OUTPUT_PATH}/\${BUILD_TYPE}/blastw.py DESTINATION ${CMAKE_INSTALL_PREFIX})
+INSTALL(TARGETS blastw DESTINATION ${CMAKE_INSTALL_PREFIX})
diff --git a/blast/_src/blastw.h b/blast/_src/blastw.h
new file mode 100644
index 0000000..ec45463
--- /dev/null
+++ b/blast/_src/blastw.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 University of Liège
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "DDriver.h"
\ No newline at end of file
diff --git a/blast/_src/blastw.i b/blast/_src/blastw.i
new file mode 100644
index 0000000..ab103f1
--- /dev/null
+++ b/blast/_src/blastw.i
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2020 University of Liège
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// SWIG input file of the 'blast' module
+
+%feature("autodoc","1");
+
+%module(docstring=
+"'blastw' module: Viscous inviscid interaction for blast 2021-2022
+(c) ULiege - A&M",
+directors="0",
+threads="1"
+) blastw
+%{
+
+#include "blast.h"
+
+#include "fwkw.h"
+#include "tboxw.h"
+#include "blastw.h"
+
+%}
+
+
+%include "fwkw.swg"
+
+// ----------- MODULES UTILISES ------------
+%import "tboxw.i"
+
+// ----------- blast CLASSES ----------------
+%include "blast.h"
+
+%shared_ptr(blast::Driver);
+
+%immutable blast::Driver::Re; // read-only variable (no setter)
+%immutable blast::Driver::Cdt;
+%immutable blast::Driver::Cdt_sec;
+%immutable blast::Driver::Cdf;
+%immutable blast::Driver::Cdf_sec;
+%immutable blast::Driver::Cdp;
+%immutable blast::Driver::Cdp_sec;
+%immutable blast::Driver::CFL0;
+%immutable blast::Driver::verbose;
+%include "DDriver.h"
\ No newline at end of file
diff --git a/blast/api/__init__.py b/blast/api/__init__.py
new file mode 100644
index 0000000..40a96af
--- /dev/null
+++ b/blast/api/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/blast/api/core.py b/blast/api/core.py
new file mode 100644
index 0000000..4d6b826
--- /dev/null
+++ b/blast/api/core.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2022 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+## Initialize blast computation
+# Paul Dechamps
+
+def initBlast(cfg, icfg, iSolverName='DART'):
+    """
+    Inputs
+    ------
+    - cfg: Dict with options
+        - Re (float): Flow Reynolds number.
+        - Minf (float): Freestream Mach number (only used for time step computation).
+        - CFL0 (float): Initial CFL number.
+        - Verb (int): Verbosity level of the viscous solver.
+        - xtrF ([float, float]): Forced transition location [upper, lower].
+
+        - couplIter (int): Maximum number of coupling iterations.
+        - couplTol (float): Tolerance to end computation (drag count between 2 iterations).
+        - iterPrint (int): Number of iterations between which the coupler outputs its state.
+        - resetInv (bool): Resets the inviscid solver at each iteration (True), reuses previous
+                            state as initial condition (False).
+        Note
+        All inputs have default values except Re.
+    - icg: Dict with options. Inviscid solver configuration.
+    - iSolverName (string): Name of the inviscid solver.
+
+    Outputs
+    -------
+    dict with keys
+        - coupler: Coupler python object to run the viscous-inviscid algorithm.
+        - solversAPI: Interface between the solver objects to ensure proper communication.
+        - vSolver: blast::Driver object to run the viscous computation.
+        - iSolverObjects: Dict containing.
+            - All inviscid solver dependencies (depend on iSolverName).
+
+    @todo: - Correctly handle 3D computation parameters.
+    """
+    # Imports
+    from blast.coupler import Coupler
+    import blast
+
+    if iSolverName == 'DART':
+        from blast.interfaces.dart.DartInterface import DartInterface as interface
+        from dart.api.core import initDart
+        iSolverObjects = initDart(icfg, scenario=icfg['scenario'], task=icfg['task'], viscous = 1)
+
+    # Check viscous solver parameters.
+    if 'Re' in cfg and cfg['Re'] > 0:
+        _Re = cfg['Re']
+    else:
+        raise RuntimeError('Missing or invalid Reynolds number')
+    if 'Minf' in cfg and cfg['Minf'] > 0:
+        _Minf = cfg['Minf']
+    else:
+        _Minf = 0.1
+    if 'CFL0' in cfg and cfg['CFL0'] > 0:
+        _CFL0 = cfg['CFL0']
+    else:
+        _CFL0 = 1
+    if 'Verb' in cfg and 0<= cfg['Verb'] <= 3:
+        _verbose = cfg['Verb']
+    else:
+        _verbose = 1
+    _xtrF = [-1, -1]
+    if 'xtrF' in cfg:
+        for i, xtr in enumerate(cfg['xtrF']):
+            if not(0 <= xtr <= 1) and xtr != -1:
+                raise RuntimeError("Incorrect forced transition location.")
+            _xtrF[i] = xtr
+    _span = 0
+    _nSections = 1 
+
+    # Check coupler parameters.
+    if 'couplIter' in cfg and cfg['couplIter'] > 0:
+        __couplIter = cfg['couplIter']
+    else:
+        __couplIter = 150
+    if 'couplTol' in cfg:
+        __couplTol = cfg['couplTol']
+    else:
+        __couplTol = 1e-4
+    if 'iterPrint' in cfg:
+        __iterPrint = cfg['iterPrint']
+    else:
+        __iterPrint = 1
+    if 'resetInv' in cfg:
+        __resetInv = cfg['resetInv']
+    else:
+        __resetInv = False
+
+    # Viscous solver object.
+    vSolver = blast.Driver(_Re, _Minf, _CFL0, _nSections, _xtrF[0], _xtrF[1], _span, verbose =_verbose)
+    # Solvers interface.
+    solversAPI = interface(iSolverObjects['sol'], vSolver, [iSolverObjects['blwb'], iSolverObjects['blww']])
+    # Coupler
+    coupler = Coupler(solversAPI, vSolver, _maxCouplIter = __couplIter, _couplTol = __couplTol, _iterPrint = __iterPrint, _resetInv = __resetInv)
+
+    return {'coupler': coupler,
+            'solversAPI': solversAPI,
+            'vSolver': vSolver,
+            'iSolverObjects': iSolverObjects}
diff --git a/blast/coupler.py b/blast/coupler.py
new file mode 100644
index 0000000..397f843
--- /dev/null
+++ b/blast/coupler.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# VII Coupler
+# Paul Dechamps
+
+import fwk
+from fwk.coloring import ccolors
+import math
+import numpy as np
+
+class Coupler:
+    def __init__(self, iSolverAPI, vSolver, _maxCouplIter=150, _couplTol=1e-4, _iterPrint=1, _resetInv=False, sfx=''):
+        self.iSolverAPI = iSolverAPI
+        self.vSolver = vSolver
+        self.maxCouplIter = _maxCouplIter
+        self.couplTol = _couplTol
+        self.resetInv = _resetInv
+        self.iterPrint = _iterPrint if self.iSolverAPI.getVerbose() == 0 and self.vSolver.verbose == 0 else 1
+        self.tms = fwk.Timers()
+        self.filesfx = sfx
+
+    def run(self):
+        # Aerodynamic coefficients.
+        aeroCoeffs = np.zeros((0, 4))
+
+        # Convergence parameters.
+        couplIter = 0
+        cdPrev = 0.0
+
+        while couplIter < self.maxCouplIter:
+            # Run inviscid solver.
+            self.tms['inviscid'].start()
+            if self.resetInv:
+                self.iSolverAPI.reset()
+            self.iSolverAPI.run()
+            self.tms['inviscid'].stop()
+
+            # Write inviscid Cp distribution.
+            if couplIter == 0:
+                self.iSolverAPI.writeCp(sfx='_inviscid'+self.filesfx)
+
+            # Impose inviscid boundary in the viscous solver.
+            self.tms['processing'].start()
+            self.iSolverAPI.imposeInvBC(couplIter)
+            self.tms['processing'].stop()
+
+            # Run viscous solver.
+            self.tms['viscous'].start()
+            exitCode = self.vSolver.run(couplIter)
+            self.tms['viscous'].stop()
+
+            # Impose blowing boundary condition in the inviscid solver.
+            self.tms['processing'].start()
+            self.iSolverAPI.imposeBlwVel()
+            self.tms['processing'].stop()
+
+            aeroCoeffs = np.vstack((aeroCoeffs, [self.iSolverAPI.getCl(), self.vSolver.Cdt, self.vSolver.Cdf + self.iSolverAPI.getCd(), self.vSolver.Cdf]))
+
+            # Check convergence status.
+            #cd = self.vSolver.Cdf + self.iSolverAPI.getCd()
+            cd = self.vSolver.Cdt if self.vSolver.Cdt != 0 else self.vSolver.Cdf + self.iSolverAPI.getCd()
+            error = abs((cd - cdPrev) / cd) if cd != 0 else 1
+
+            if error <= self.couplTol:
+                print(ccolors.ANSI_GREEN, '{:>4.0f}| {:>7.5f} {:>7.5f} {:>7.5f} | {:>6.4f} {:>6.4f} | {:>6.3f}\n'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getxoctr(0, 0), self.vSolver.getxoctr(0, 1), np.log10(error)), ccolors.ANSI_RESET)
+                self.iSolverAPI.writeCp(sfx='_viscous'+self.filesfx)
+                return aeroCoeffs
+            cdPrev = cd
+
+            if couplIter == 0:
+                print('- AoA: {:<2.2f} Mach: {:<1.1f} Re: {:<2.1f}e6'.format(self.iSolverAPI.getAoA()*180/math.pi, self.iSolverAPI.getMinf(), self.vSolver.getRe()/1e6))
+                print('{:>5s}| {:>7s} {:>7s} {:>7s} | {:>6s} {:>6s} | {:>6s}'.format('It', 'Cl', 'Cd', 'Cdwake', 'xtrT', 'xtrB', 'Error'))
+                print('  ----------------------------------------------------------')
+            if couplIter % self.iterPrint == 0:
+                print(' {:>4.0f}| {:>7.4f} {:>7.4f} {:>7.4f} | {:>6.4f} {:>6.4f} | {:>6.3f}'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getxoctr(0, 0), self.vSolver.getxoctr(0, 1), np.log10(error)))
+                if self.iSolverAPI.getVerbose() != 0 or self.vSolver.verbose != 0:
+                    print('')
+            couplIter += 1
+            self.tms['processing'].stop()
+        else:
+            print(ccolors.ANSI_RED, 'Maximum number of {:<.0f} coupling iterations reached'.format(self.maxCouplIter), ccolors.ANSI_RESET)
+            print('')
+            print('{:>5s}| {:>7s} {:>7s} {:>7s} | {:>6s} {:>8s} | {:>6s}'.format('It', 'Cl', 'Cd', 'Cdwake', 'xtrT', 'xtrB', 'Error'))
+            print('  ----------------------------------------------------------')
+            print(ccolors.ANSI_RED, '{:>4.0f}| {:>7.5f} {:>7.5f} {:>7.5f} | {:>6.4f} {:>7.4f} | {:>6.3f}\n'.format(couplIter, self.iSolverAPI.getCl(), self.iSolverAPI.getCd()+self.vSolver.Cdf, self.vSolver.Cdt, self.vSolver.getxoctr(0, 0), self.vSolver.getxoctr(0, 1), np.log10(error)), ccolors.ANSI_RESET)
+            self.iSolverAPI.writeCp(sfx='_viscous'+self.filesfx)
+            return aeroCoeffs
diff --git a/blast/interfaces/DDataStructure.py b/blast/interfaces/DDataStructure.py
new file mode 100644
index 0000000..8417d4c
--- /dev/null
+++ b/blast/interfaces/DDataStructure.py
@@ -0,0 +1,88 @@
+import numpy as np
+class Group:
+    def __init__(self, _name):
+        self.name = _name
+        self.nUpperPrev = 0
+        self.stagPoint = None
+
+    def initStructures(self, nPoints):
+        self.nPoints = nPoints
+        self.nElems = nPoints - 1
+        self.nodesCoord = np.zeros((self.nPoints,4))
+        self.elemsCoord = np.zeros((self.nPoints,4))
+        
+        self.V = np.zeros((self.nPoints,3))
+        self.M = np.zeros(self.nPoints)
+        self.Rho = np.zeros(self.nPoints)
+        self.blowingVel = np.zeros(self.nElems)
+
+        self.deltaStarExt = np.zeros(self.nPoints)
+        self.xxExt = np.zeros(self.nPoints)
+
+    def updateVars(self, nodes, _V, _M, _Rho):
+        self.nodesCoord = nodes
+        self.nPoints = len(self.nodesCoord[:,0])
+        self.nElems = self.nPoints - 1
+        self.V = _V
+        self.M = _M
+        self.Rho = _Rho
+        self.computeStagPoint()
+
+    def getXUpper(self):
+        return np.flip(self.nodesCoord[:self.stagPoint+1,0])
+    def getXLower(self):
+        return self.nodesCoord[self.stagPoint:, 0]
+    def getYUpper(self):
+        return np.flip(self.nodesCoord[:self.stagPoint+1,1])
+    def getYLower(self):
+        return self.nodesCoord[self.stagPoint:, 1]
+    def getZUpper(self):
+        return np.flip(self.nodesCoord[:self.stagPoint+1,2])
+    def getZLower(self):
+        return self.nodesCoord[self.stagPoint:, 2]
+    
+    def getVelocityXUpper(self):
+        return np.flip(self.V[:self.stagPoint+1,0])
+    def getVelocityXLower(self):
+        return self.V[self.stagPoint:, 0]
+    def getVelocityYUpper(self):
+        return np.flip(self.V[:self.stagPoint+1,1])
+    def getVelocityYLower(self):
+        return self.V[self.stagPoint:, 1]
+    def getVelocityZUpper(self):
+        return np.flip(self.V[:self.stagPoint+1,2])
+    def getVelocityZLower(self):
+        return self.V[self.stagPoint:, 2]
+
+    def getMachUpper(self):
+        return np.flip(self.M[:self.stagPoint+1])
+    def getMachLower(self):
+        return self.M[self.stagPoint:]
+
+    def getRhoUpper(self):
+        return np.flip(self.Rho[:self.stagPoint+1])
+    def getRhoLower(self):
+        return self.Rho[self.stagPoint:]
+
+    def setBlowingVelocity(self, blwVel):
+        self.blowingVel = blwVel
+    def setConnectList(self, _connectListNodes, _connectListElems):
+        if self.name != 'iWing' and self.name != 'iWake':
+            raise RuntimeError('Cannot set connectivity lists for viscous structures', self.name)
+        self.connectListNodes = _connectListNodes
+        self.connectListElems = _connectListElems
+    def connectBlowingVel(self):
+        if self.name != 'iWing' and self.name != 'iWake':
+            raise RuntimeWarning('Can not connect blowing velocities for viscous structure. Maybe it was not set.', self.name)
+        self.blowingVel = self.blowingVel[self.connectListElems.argsort()]
+
+    def isMeshChanged(self):
+        return self.nPoints - self.nUpperPrev
+    def updatePrev(self):
+        self.nUpperPrev = self.nPoints
+    def computeStagPoint(self):
+        if self.name == 'iWake' or self.name == 'vWake':
+            self.stagPoint = None
+        else:
+            if self.stagPoint is None:
+                self.stagPoint = np.argmin(np.sqrt(self.V[:,0]**2 + self.V[:,1]**2))
\ No newline at end of file
diff --git a/blast/interfaces/DSolversInterface.py b/blast/interfaces/DSolversInterface.py
new file mode 100644
index 0000000..cdfaa70
--- /dev/null
+++ b/blast/interfaces/DSolversInterface.py
@@ -0,0 +1,261 @@
+import numpy as np
+from scipy.interpolate import interp1d
+from blast.interfaces.DDataStructure import Group
+
+class SolversInterface:
+    def __init__(self, _vSolver, _cfg):
+        self.vSolver = _vSolver
+        self.cfg = _cfg
+
+        if 'nSections' not in self.cfg:
+            self.cfg['nSections'] = len(self.cfg['sections'])
+        
+        if 'sweep' not in self.cfg:
+            self.cfg['sweep'] = False
+
+        # Initialize data structures.
+        self.iBnd = [Group('iWing' if iReg == 0 else 'iWake') for iReg in range(2)]
+        self.vBnd = [[Group('vAirfoil' if iReg == 0 else 'vWake') for iReg in range(2)] for _ in range(self.cfg['nSections'])]
+        
+        if self.cfg['interpolator'] == 'Matching':
+            from blast.interfaces.interpolators.DMatchingInterpolator import MatchingInterpolator as interp
+        elif self.cfg['interpolator'] == 'Rbf':
+            from blast.interfaces.interpolators.DRbfInterpolator import RbfInterpolator as interp
+            print('RBF interpolator initialized.')
+            print('Loading viscous mesh... ')
+            if self.cfg['nDim'] == 2:
+                self.getVAirfoil()
+            elif self.cfg['nDim'] == 3:
+                self.getVWing()
+            print('done.')
+        else:
+            raise RuntimeError('Incorrect interpolator specified in config.')
+        self.interpolator = interp(self.cfg)
+
+        print('Loading inviscid mesh... ')
+        self.setMesh()
+        print('done.\n')
+
+        #self.interpolator.ilwIdx = self.ilwIdx
+        #self.interpolator.vlwIdx = self.vlwIdx
+
+        self.deltaStarExt = [[np.zeros(0) for iReg in range(3)]\
+                             for iSec in range(self.cfg['nSections'])]
+        self.xxExt        = [[np.zeros(0) for iReg in range(3)]\
+                             for iSec in range(self.cfg['nSections'])]
+        #self.vtOld = [[np.zeros(0) for iReg in range(3)] for iSec in range(self.cfg['nSections'])]
+    
+    def setViscousSolver(self, couplIter):
+        """Interpolate solution to viscous mesh and sets the inviscid boundary in the viscous solver.
+        """
+        self.interpolator.inviscidToViscous(self.iBnd, self.vBnd, couplIter)
+        for iSec in range(self.cfg['nSections']):
+            """print('')
+            print('// Section ', iSec)
+            for i in range(len(self.vBnd[iSec][0].nodesCoord[:,0])):
+                print('Point({0:>5.0f}) = ({1:>8.6f}, {2:>8.6f}, {3:>8.6f});'.format(i+(iSec+7)*10000, self.vBnd[iSec][0].nodesCoord[i,0], self.vBnd[iSec][0].nodesCoord[i,2], self.vBnd[iSec][0].nodesCoord[i,1]))"""
+            """from matplotlib import pyplot as plt
+            plt.plot(self.vBnd[iSec][0].getXUpper(), self.vBnd[iSec][0].getYUpper(), 'o-')
+            plt.plot(self.vBnd[iSec][0].getXLower(), self.vBnd[iSec][0].getYLower(), 'x-')
+            plt.plot(self.vBnd[iSec][1].nodesCoord[:,0], self.vBnd[iSec][1].nodesCoord[:,1], 'x-')
+            plt.show()
+            plt.plot(self.vBnd[0][0].getXUpper(), self.vBnd[0][0].getVelocityXUpper())
+            plt.plot(self.vBnd[0][0].getXLower(), self.vBnd[0][0].getVelocityXLower())
+            #plt.plot(self.vBnd[0][1].nodesCoord[:,0], self.vBnd[0][1].V[:,0])
+            plt.title('vx')
+            plt.show()
+            plt.plot(self.vBnd[0][0].getXUpper(), self.vBnd[0][0].getVelocityYUpper())
+            plt.plot(self.vBnd[0][0].getXLower(), self.vBnd[0][0].getVelocityYLower())
+            plt.plot(self.vBnd[0][1].nodesCoord[:,0], self.vBnd[0][1].V[:,1])
+            plt.title('vy')
+            plt.show()
+            plt.plot(self.vBnd[0][0].getXUpper(), self.vBnd[0][0].getVelocityZUpper())
+            plt.plot(self.vBnd[0][0].getXLower(), self.vBnd[0][0].getVelocityZLower())
+            plt.plot(self.vBnd[0][1].nodesCoord[:,0], self.vBnd[0][1].V[:,2])
+            plt.title('vz')
+            plt.show()
+            plt.plot(self.vBnd[0][0].getXUpper(), self.vBnd[0][0].getMachUpper())
+            plt.plot(self.vBnd[0][0].getXLower(), self.vBnd[0][0].getMachLower())
+            plt.plot(self.vBnd[0][1].nodesCoord[:,0], self.vBnd[0][1].M)
+            plt.title('Me')
+            plt.show()
+            plt.plot(self.vBnd[0][0].getXUpper(), self.vBnd[0][0].getRhoUpper())
+            plt.plot(self.vBnd[0][0].getXLower(), self.vBnd[0][0].getRhoLower())
+            plt.plot(self.vBnd[0][1].nodesCoord[:,0], self.vBnd[0][1].Rho)
+            plt.title('Rho')
+            plt.show()
+            quit()"""
+
+            ## Mesh
+            if (self.vBnd[iSec][0].isMeshChanged()):
+                for reg in self.vBnd[iSec]:
+                    if reg.name == 'vWake':
+                        iReg = 2
+                        self.vSolver.setGlobMesh(iSec, iReg,
+                                                 reg.nodesCoord[:,0],
+                                                 reg.nodesCoord[:,1],
+                                                 reg.nodesCoord[:,2])
+                        # External variables
+                        self.xxExt[iSec][2] = np.zeros(reg.nPoints)
+                        self.deltaStarExt[iSec][2] = np.zeros(reg.nPoints)
+                        #self.vtOld[iSec][2] = np.zeros(reg.nPoints)
+                    elif reg.name == 'vAirfoil':
+                        self.vSolver.setGlobMesh(iSec, 0,
+                                                 reg.getXUpper(),
+                                                 reg.getYUpper(),
+                                                 reg.getZUpper())
+                        self.vSolver.setGlobMesh(iSec, 1,
+                                                 abs(reg.getXLower()),
+                                                 reg.getYLower(),
+                                                 reg.getZLower())
+                        # External variables
+                        self.xxExt[iSec][0]        = np.zeros(reg.stagPoint+1)
+                        self.deltaStarExt[iSec][0] = np.zeros(reg.stagPoint+1)
+                        #self.vtOld[iSec][0]        = np.zeros(reg.stagPoint+1)
+                        self.xxExt[iSec][1]        = np.zeros(reg.nPoints
+                                                              - reg.stagPoint)
+                        self.deltaStarExt[iSec][1] = np.zeros(reg.nPoints
+                                                              - reg.stagPoint)
+                        #self.vtOld[iSec][1]        = np.zeros(reg.nPoints
+                        #                                     - reg.stagPoint)
+                    else:
+                        raise RuntimeError('Tried to initialized viscous\
+                                           struture but name was', reg.name)
+            ## Inviscid variables
+            for reg in self.vBnd[iSec]:
+                reg.updatePrev()
+                if reg.name == 'vWake':
+                    iReg = 2
+                    self.vSolver.setVelocities(iSec, iReg,
+                                               reg.V[:,0],
+                                               reg.V[:,1],
+                                               reg.V[:,2])
+                    self.vSolver.setMe(iSec, iReg, reg.M)
+                    self.vSolver.setRhoe(iSec, iReg, reg.Rho)
+                elif reg.name == 'vAirfoil':
+                    iReg = 0 # Upper side
+                    self.vSolver.setVelocities(iSec, iReg,
+                                               reg.getVelocityXUpper(),
+                                               reg.getVelocityYUpper(),
+                                               reg.getVelocityZUpper())
+                    self.vSolver.setMe(iSec, iReg, reg.getMachUpper())
+                    self.vSolver.setRhoe(iSec, iReg, reg.getRhoUpper())
+                    iReg = 1 # Lower side
+                    self.vSolver.setVelocities(iSec, iReg,
+                                               reg.getVelocityXLower(),
+                                               reg.getVelocityYLower(),
+                                               reg.getVelocityZLower())
+                    self.vSolver.setMe(iSec, iReg, reg.getMachLower())
+                    self.vSolver.setRhoe(iSec, iReg, reg.getRhoLower())
+                else:
+                    raise RuntimeError('Tried to initialize a viscous\
+                                       struture but name was', reg.name)
+            ## External variables
+            for iRegv in range(3):
+                self.vSolver.setxxExt(iSec, iRegv, self.xxExt[iSec][iRegv])
+                self.vSolver.setDeltaStarExt(iSec, iRegv,
+                                             self.deltaStarExt[iSec][iRegv])
+                #self.vSolver.setVtOld(iSec, iRegv, self.vtOld[iSec][iRegv])
+    def getBlowingBoundary(self):
+        for iSec in range(len(self.vBnd)):
+            for reg in self.vBnd[iSec]:
+                if reg.name == 'vWake':
+                    reg.blowingVel = np.asarray(self.vSolver.extractBlowingVel(iSec, 2))
+                elif reg.name == 'vAirfoil':
+                    reg.blowingVel = np.concatenate((np.flip(np.asarray(self.vSolver.extractBlowingVel(iSec, 0))),
+                                                     np.asarray(self.vSolver.extractBlowingVel(iSec, 1))))
+            for iRegv in range(3):
+                self.xxExt[iSec][iRegv] = self.vSolver.extractxx(iSec, iRegv)
+                self.deltaStarExt[iSec][iRegv] = self.vSolver.extractDeltaStar(iSec, iRegv)
+                #self.vtOld[iSec][iRegv] = self.vSolver.extractUe(iSec, iRegv)
+                
+        self.interpolator.viscousToInviscid(self.iBnd, self.vBnd)
+
+    def getVWing(self):
+        """Obtain the nodes' location and row and cg of all elements.
+        If the mesh is the viscous one, TE nodes are duplicated (by creating a new row).
+        They exist for the interpolation and for the solution computation.
+        If not, the nodes are pseudo-duplicated (without changing the row). They therefore
+        exist only for the interpolation but not for the solution.
+        """
+
+        data = [np.zeros((0,4)) for _ in range(2)]
+
+        leNodes = np.zeros((0, 3))
+        lwRows = []
+        ctlw = 0
+
+        for e in self.cfg['vMsh'].elems:
+            if e.ptag.name == 'wing' or e.ptag.name == 'wing_'\
+                or e.ptag.name == 'leadingEdge':
+                iRegion = 0
+            elif e.ptag.name == 'wake' or e.ptag.name == 'wake_':
+                iRegion = 1
+            else:
+                continue
+            
+            # Get nodes
+            for nw in e.nodes:
+                if nw.row not in data[iRegion][:,3]:
+                    data[iRegion] = np.vstack((data[iRegion], [nw.pos[0],\
+                                                                nw.pos[2],\
+                                                                nw.pos[1],\
+                                                                nw.row]))
+                    # If wing_ exists, the lower side is identifiable.
+                    if e.ptag.name == 'wing_':
+                        lwRows.append(nw.row)
+        
+        if len(lwRows) == 0:
+            raise RuntimeError('Could not identify lower side.')
+
+        self.vlwIdx = []
+        for idx in range(len(data[0][:,3])):
+            if data[0][idx, 3] in lwRows:
+                self.vlwIdx.append(idx)
+
+        maxRowNb = max(data[0][:,3])
+        # Initialize viscous strutures
+        self.cfg['EffSections'] = np.empty(0)
+
+        for iReg, reg in enumerate(data):
+            for iy, y in enumerate(self.cfg['Sections']):
+                # Find nearest value in the mesh
+                try:
+                    idx = (np.abs(reg[:,2]-y).argmin())
+                except:
+                    print('Region', reg[:,2], 'cannot be found through\
+                          config input', y)
+                    raise RuntimeError("Could not identify section.\
+                                       Possibly a missing tag")
+                section = reg[idx,2]
+                if iReg == 0:
+                    self.cfg['EffSections'] = np.append(self.cfg['EffSections'], section)
+                nodesSec = reg[abs(reg[:,2]-section)<1e-3, :]
+
+                # Separate points on upper and lower side
+                if iReg == 0:
+                    lower_points = []
+                    upper_points = []
+                    for point in nodesSec:
+                        if point[3] in lwRows:
+                            lower_points.append(point)
+                        else:
+                            upper_points.append(point)
+
+                    # Sort points in selig format
+                    lower_points = sorted(lower_points, key=lambda p: p[0])
+                    upper_points = sorted(upper_points, key=lambda p: p[0], reverse=True)
+                    nodesSec = np.row_stack((upper_points, lower_points))
+                    nodesSec = np.row_stack((nodesSec, [nodesSec[0,0], nodesSec[0,1], nodesSec[0,2], nodesSec[0,3]+maxRowNb]))
+                else:
+                    nodesSec = nodesSec[nodesSec[:,0].argsort()]
+
+                # Compute cg pos on the section (!= cg of 2D elems on the surface)
+                cgSec = np.zeros((len(nodesSec[:,0])-1, 4))
+                for i in range(len(cgSec)):
+                    cgSec[i,:3] = (nodesSec[i,:3] + nodesSec[i+1,:3])/2
+                    cgSec[i, 3] = i
+
+                self.vBnd[iy][iReg].initStructures(nodesSec.shape[0])
+                self.vBnd[iy][iReg].nodesCoord = nodesSec
+                self.vBnd[iy][iReg].elemsCoord = cgSec
diff --git a/blast/interfaces/dart/DartInterface.py b/blast/interfaces/dart/DartInterface.py
new file mode 100644
index 0000000..321fec3
--- /dev/null
+++ b/blast/interfaces/dart/DartInterface.py
@@ -0,0 +1,337 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# Dart interface
+# Paul Dechamps
+
+import numpy as np
+
+from fwk.coloring import ccolors
+from blast.interfaces.DSolversInterface import SolversInterface
+
+class DartInterface(SolversInterface):
+    def __init__(self, dartCfg, vSolver, _cfg):
+        try:
+            from dartflo.dart.api.core import initDart
+            argOut = initDart(dartCfg, viscous=True)
+            self.solver = argOut.get('sol') # Solver
+            self.wrt = argOut.get('wrt') # Writer
+            self.blw = [argOut.get('blwb'), argOut.get('blww')]
+            print('Dart successfully loaded.')
+        except:
+            print(ccolors.ANSI_RED, 'Could not load DART. Make sure it is installed.', ccolors.ANSI_RESET)
+            raise RuntimeError('Import error')
+        SolversInterface.__init__(self, vSolver, _cfg)
+    
+    def run(self):
+        return self.solver.run()
+    
+    def writeCp(self, sfx=''):
+        """ Write Cp distribution around the airfoil on file.
+        """
+        # Extract Cp on elements
+        vElems = self.blw[0].tag.elems
+        vData = self.solver.Cp
+        if isinstance(vData[0], float):
+            size = 1
+        elif vData[0].size() == 3:
+            size = 3
+        else:
+            raise RuntimeError('unrecognized format for vData!')
+        # store data (not efficient, but OK since only meant for small 2D cases)
+        data = np.zeros((vElems.size()+1,3+size))
+        i = 0
+        while i < vElems.size()+1:
+            data[i,0] = vElems[i%vElems.size()].nodes[0].pos[0]
+            data[i,1] = vElems[i%vElems.size()].nodes[0].pos[1]
+            data[i,2] = vElems[i%vElems.size()].nodes[0].pos[2]
+            if size == 1:
+                data[i,3] = vData[vElems[i%vElems.size()].nodes[0].row]
+            else:
+                for j in range(size):
+                    data[i,3+j] = vData[vElems[i%vElems.size()].nodes[0].row][j]
+            i += 1
+        print('writing data file ' + 'Cp' +sfx + '.dat')
+        np.savetxt('Cp'+sfx+'.dat', data, fmt='%1.6e', delimiter=', ', header='x, y, z, Cp', comments='')
+    
+    def save(self, sfx='inviscid'):
+        self.solver.save(self.wrt, sfx)
+
+    def getAoA(self):
+        return self.solver.pbl.alpha
+    
+    def getMinf(self):
+        return self.solver.pbl.M_inf
+    
+    def getCl(self):
+        return self.solver.Cl
+
+    def getCd(self):
+        return self.solver.Cd
+
+    def getCm(self):
+        return self.solver.Cm
+    
+    def getVerbose(self):
+        return self.solver.verbose
+    
+    def reset(self):
+        self.solver.reset()
+    
+    def imposeInvBC(self, couplIter):
+        """ Extract the inviscid paramaters at the edge of the boundary layer
+        and use it as a boundary condition in the viscous solver.
+
+        Params
+        ------
+        - couplIter (int): Coupling iteration.
+        """
+        # Retreive parameters.
+        for n in range(2):
+            for iRow, row in enumerate(self.iBnd[n].nodesCoord[:,3]):
+                row=int(row)
+                for iDim in range(3):
+                    self.iBnd[n].V[iRow,iDim] = self.solver.U[row][iDim]
+                self.iBnd[n].M[iRow] = self.solver.M[row]
+                self.iBnd[n].Rho[iRow] = self.solver.rho[row]
+            if self.cfg['nDim']==3:
+                self.iBnd[n].V[:,[1,2]] = self.iBnd[n].V[:,[2,1]]
+        self.setViscousSolver(couplIter)
+
+    def imposeBlwVel(self):
+        """ Extract the solution of the viscous calculation (blowing velocity)
+        and use it as a boundary condition in the inviscid solver.
+        """
+        self.getBlowingBoundary()
+        # Impose blowing velocities.
+        for n in range(len(self.iBnd)):
+            if self.cfg['nDim'] == 2:
+                self.iBnd[n].connectBlowingVel()
+            for i, blw in enumerate(self.iBnd[n].blowingVel):
+                self.blw[n].setU(i, blw)
+
+    def setMesh(self):
+
+        if self.cfg['nDim'] == 2:
+            self.getAirfoil()
+        elif self.cfg['nDim'] == 3:
+            self.getWing()
+        else:
+            raise RuntimeError('dartInterface::Could not resolve how to set\
+            the mesh. Either ''nDim'' is incorrect or ''msh'' was not set for\
+            3D computations')
+
+    def getAirfoil(self):
+        """Create data structure for information transfer.
+        """
+        self.iBnd[0].initStructures(self.blw[0].nodes.size())
+        # Node number
+        N1 = np.zeros(self.blw[0].nodes.size(), dtype=int)
+        # Index in boundary.nodes
+        connectListNodes = np.zeros(self.blw[0].nodes.size(), dtype=int)
+        # Index in boundary.elems
+        connectListElems = np.zeros((self.blw[0].nodes.size()-1), dtype=int)
+        data = np.zeros((self.blw[0].nodes.size(), 5))
+        i = 0
+        for n in self.blw[0].nodes:
+            data[i,0] = n.no
+            data[i,1] = n.pos[0]
+            data[i,2] = n.pos[1]
+            data[i,3] = n.pos[2]
+            data[i,4] = n.row
+            i += 1
+        # Table containing the element and its nodes
+        eData = np.zeros(((self.blw[0].nodes.size()-1),3), dtype=int)
+        elemData = np.zeros(((self.blw[0].nodes.size()-1),4))
+        for i in range(0, (self.blw[0].nodes.size()-1)):
+            eData[i,0] = self.blw[0].tag.elems[i].no
+            eData[i,1] = self.blw[0].tag.elems[i].nodes[0].no
+            eData[i,2] = self.blw[0].tag.elems[i].nodes[1].no
+            elemData[i,0] = self.blw[0].tag.elems[i].nodes[0].pos[0] \
+            + self.blw[0].tag.elems[i].nodes[1].pos[0]
+            elemData[i,1] = self.blw[0].tag.elems[i].nodes[0].pos[1] \
+            + self.blw[0].tag.elems[i].nodes[1].pos[1]
+            elemData[i,2] = self.blw[0].tag.elems[i].nodes[0].pos[2] \
+            + self.blw[0].tag.elems[i].nodes[1].pos[2]
+            elemData[i,3] = self.blw[0].tag.elems[i].no
+        elemData[i, :3] /= 2
+
+        # Find the stagnation point
+        idxStag = np.argmin(data[:,1])
+        globStag = int(data[idxStag,0]) # Position of the stagnation point
+
+        # Find TE nodes.
+        # Assuming suction side element is above pressure side element in the
+        # y direction.
+        # TE nodes
+        idxTE = np.where(data[:,1] == np.max(data[:,1]))[0]
+        # TE element 1
+        idxTEe0 = np.where(np.logical_or(eData[:,1] == data[idxTE[0],0], \
+        eData[:,2] == data[idxTE[0],0]))[0]
+        # TE element 2
+        idxTEe1 = np.where(np.logical_or(eData[:,1] == data[idxTE[1],0], \
+        eData[:,2] == data[idxTE[1],0]))[0]
+        # y coordinates of TE element 1 CG
+        ye0 = 0.5 * (data[np.where(data[:,0] == eData[idxTEe0[0],1])[0],2] \
+        + data[np.where(data[:,0] == eData[idxTEe0[0],2])[0],2])
+        # y coordinates of TE element 2 CG
+        ye1 = 0.5 * (data[np.where(data[:,0] == eData[idxTEe1[0],1])[0],2] \
+        + data[np.where(data[:,0] == eData[idxTEe1[0],2])[0],2])
+        if ye0 - ye1 > 0: # element 1 containing TE node 1 is above element 2
+            upperGlobTE = int(data[idxTE[0],0])
+            lowerGlobTE = int(data[idxTE[1],0])
+        else:
+             upperGlobTE = int(data[idxTE[1],0])
+             lowerGlobTE = int(data[idxTE[0],0])
+        # Connectivity
+        connectListElems[0] = np.where(eData[:,2] == upperGlobTE)[0]
+        # number of the stag node elems.nodes -> globStag
+        N1[0] = eData[connectListElems[0],1]
+        #connectListNodes[0] = np.where(data[:,0] == N1[0])[0]
+        connectListNodes[0] = np.where(data[:,0] == upperGlobTE)[0]
+        i = 1
+        upperStag = 0
+        lowerTE = 0
+        # Sort the suction part
+        while upperStag == 0:
+            N1[i] = eData[connectListElems[i-1],1] # Second node of the element
+            # Index of the first node of the next element in elems.nodes
+            connectListElems[i] = np.where(eData[:,2] == N1[i])[0]
+            # Index of the node in boundary.nodes
+            connectListNodes[i] = np.where(data[:,0] == N1[i])[0]
+            if eData[connectListElems[i],1] == globStag:
+                upperStag = 1
+            i += 1
+        self.idxRt = i+1
+        # Sort the pressure side
+        connectListElems[i] = np.where(eData[:,2] == globStag)[0]
+        connectListNodes[i] = np.where(data[:,0] == globStag)[0]
+        N1[i] = eData[connectListElems[i],2]
+        while lowerTE == 0:
+            # First node of the element
+            N1[i+1]  = eData[connectListElems[i],1]
+            # Index of the second node of the next element in elems.nodes
+            connectListElems[i+1] = np.where(eData[:,2] == N1[i+1])[0]
+            # Index of the node in boundary.nodes
+            connectListNodes[i+1] = np.where(data[:,0] == N1[i+1])[0]
+            if eData[connectListElems[i+1],1] == lowerGlobTE:
+                lowerTE = 1
+            i += 1
+        connectListNodes[i+1] = np.where(data[:,0] == lowerGlobTE)[0]
+        data[:,0] = data[connectListNodes,0]
+        data[:,1] = data[connectListNodes,1]
+        data[:,2] = data[connectListNodes,2]
+        data[:,3] = data[connectListNodes,3]
+        data[:,4] = data[connectListNodes,4]
+        self.iBnd[0].nodesCoord = np.column_stack((data[:,1], data[:,2],\
+                                                   data[:,3], data[:,4]))
+        self.iBnd[0].setConnectList(connectListNodes, connectListElems)
+
+        # Wake
+        self.iBnd[1].initStructures(self.blw[1].nodes.size())
+        # Node number
+        N1 = np.zeros(self.blw[1].nodes.size(), dtype=int)
+        # Index in boundary.nodes
+        connectListNodes = np.zeros(self.blw[1].nodes.size(), dtype=int)
+        # Index in boundary.elems
+        connectListElems = np.zeros((self.blw[1].nodes.size()-1), dtype=int)
+        dataW = np.zeros((self.blw[1].nodes.size(), 5))
+        i = 0
+        for n in self.blw[1].nodes:
+            dataW[i,0] = n.no
+            dataW[i,1] = n.pos[0]
+            dataW[i,2] = n.pos[1]
+            dataW[i,3] = n.pos[2]
+            dataW[i,4] = n.row
+            i += 1
+        # Table containing the element and its nodes
+        eData = np.zeros(((self.blw[1].nodes.size()-1),3), dtype=int)
+        for i in range(0, (self.blw[1].nodes.size()-1)):
+            eData[i,0] = self.blw[1].tag.elems[i].no
+            eData[i,1] = self.blw[1].tag.elems[i].nodes[0].no
+            eData[i,2] = self.blw[1].tag.elems[i].nodes[1].no
+        connectListNodes = dataW[:,1].argsort()
+        # connectListElems[0] = np.where(eData[:,1] == min(data[:,1]))[0]
+        connectListElems[0] = 0
+        for i in range(1, len(eData[:,0])):
+            connectListElems[i] = \
+            np.where(eData[:,1] == eData[connectListElems[i-1],2])[0]
+        dataW[:,0] = dataW[connectListNodes,0]
+        dataW[:,1] = dataW[connectListNodes,1]
+        dataW[:,2] = dataW[connectListNodes,2]
+        dataW[:,3] = dataW[connectListNodes,3]
+        dataW[:,4] = dataW[connectListNodes,4]
+        self.iBnd[1].nodesCoord = np.column_stack((dataW[:,1], dataW[:,2], \
+                                                   dataW[:,3], dataW[:,4]))
+        self.iBnd[1].setConnectList(connectListNodes, connectListElems)
+
+    def getWing(self):
+        """Obtain the nodes' location and row and cg of all elements.
+        """
+
+        data = [np.zeros((0,4)) for _ in range(len(self.cfg['iMsh']))]
+        cg = [np.zeros((0,4)) for _ in range(len(self.cfg['iMsh']))]
+
+        """lwRows = []
+        for e in self.cfg['iMsh2'].elems:
+            if e.ptag.name == 'wing_':
+                for n in e.nodes:
+                    if n.row not in lwRows:
+                        lwRows.append(n.row)
+
+        if len(lwRows) == 0:
+            raise RuntimeError('Could not identify the lower side in the inviscid mesh.')
+        """
+
+        print('ddd')
+
+        for iRegion in range(len(self.cfg['iMsh'])):
+            for e in self.cfg['iMsh'][iRegion].tag.elems:
+                # Compute cg position
+                cg_pt = np.zeros(3)
+                for nw in e.nodes:
+                    cg_pt += [nw.pos[0], nw.pos[2], nw.pos[1]]
+                    if nw.row not in data[iRegion][:,3]:
+                        data[iRegion] = np.vstack((data[iRegion], [nw.pos[0],\
+                                                                   nw.pos[2],\
+                                                                   nw.pos[1],\
+                                                                   nw.row]))
+                cg_pt/=e.nodes.size()
+                cg_pt = np.hstack((cg_pt, e.no))
+                cg[iRegion] = np.vstack((cg[iRegion], cg_pt))
+
+        print('cccccc')
+        self.ilwIdx = []
+        """for idx in range(len(data[0][:,3])):
+            if data[0][idx, 3] in lwRows:
+                self.ilwIdx.append(idx)"""
+
+        if 'Sym' in self.cfg:
+            for s in self.cfg['Sym']:
+                for iReg in range(len(data)):
+                    dummy = data[iReg].copy()
+                    dummy[:,2] -= s
+                    dummy[:,2] *= -1
+                    dummy[:,2] += s
+                    data[iReg] = np.row_stack((data[iReg], dummy))
+
+        for n in range(len(data)):
+            self.iBnd[n].initStructures(data[n].shape[0])
+            self.iBnd[n].nodesCoord = data[n]
+            self.iBnd[n].elemsCoord = cg[n]
+            self.iBnd[n].setConnectList(data[n][:,3], cg[n][:,3])
diff --git a/blast/interfaces/dart/__init__.py b/blast/interfaces/dart/__init__.py
new file mode 100644
index 0000000..7c68785
--- /dev/null
+++ b/blast/interfaces/dart/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
\ No newline at end of file
diff --git a/blast/interfaces/interpolators/DGroupBuilder.py b/blast/interfaces/interpolators/DGroupBuilder.py
new file mode 100755
index 0000000..d4fcbca
--- /dev/null
+++ b/blast/interfaces/interpolators/DGroupBuilder.py
@@ -0,0 +1,169 @@
+import numpy as np
+
+def mergeVectors(dataIn, mapMesh, nFactor):
+    """Merges 3 groups upper/lower sides and wake.
+    """
+    for k in range(len(dataIn)):
+        if len(dataIn[k]) == 2:  # Airfoil case.
+            xx_up, dv_up, vtExt_up, _, alpha_up = getBLcoordinates(dataIn[k][0])
+            xx_lw, dv_lw, vtExt_lw, _, alpha_lw = getBLcoordinates(dataIn[k][1])
+        else:  # Wake case
+            xx_wk, dv_wk, vtExt_wk, _, alpha_wk = getBLcoordinates(dataIn[k])
+
+    if mapMesh:
+        # Save initial mesh.
+        cMesh = np.concatenate((dataIn[0][0][:, 0], dataIn[0][1][1:, 0], dataIn[1][:, 0]))
+        cMeshbNodes = [0, len(dataIn[0][0][:, 0]), len(dataIn[0][0][:, 0]) + len(dataIn[0][1][1:, 0])]
+        cxx = np.concatenate((xx_up, xx_lw[1:], xx_wk))
+        cvtExt = np.concatenate((vtExt_up, vtExt_lw[1:], vtExt_wk))
+        cxxExt = np.concatenate((dataIn[0][0][:, 8], dataIn[0][1][1:, 8], dataIn[1][:, 8]))
+
+        # Create fine mesh.
+        fMeshUp = createFineMesh(dataIn[0][0][:, 0], nFactor)
+        fMeshLw = createFineMesh(dataIn[0][1][:, 0], nFactor)
+        fMeshWk = createFineMesh(dataIn[1][:, 0], nFactor)
+        fMesh = np.concatenate((fMeshUp, fMeshLw[1:], fMeshWk))
+        fMeshbNodes = [0, len(fMeshUp), len(fMeshUp) + len(fMeshLw[1:])]
+
+        fxx_up = interpolateToFineMesh(xx_up, fMeshUp, nFactor)
+        fxx_lw = interpolateToFineMesh(xx_lw, fMeshLw, nFactor)
+        fxx_wk = interpolateToFineMesh(xx_wk, fMeshWk, nFactor)
+        fxx = np.concatenate((fxx_up, fxx_lw[1:], fxx_wk))
+
+        fvtExt_up = interpolateToFineMesh(vtExt_up, fMeshUp, nFactor)
+        fvtExt_lw = interpolateToFineMesh(vtExt_lw, fMeshLw, nFactor)
+        fvtExt_wk = interpolateToFineMesh(vtExt_wk, fMeshWk, nFactor)
+        fvtExt = np.concatenate((fvtExt_up, fvtExt_lw[1:], fvtExt_wk))
+
+        fMe_up = interpolateToFineMesh(dataIn[0][0][:, 5], fMeshUp, nFactor)
+        fMe_lw = interpolateToFineMesh(dataIn[0][1][:, 5], fMeshLw, nFactor)
+        fMe_wk = interpolateToFineMesh(dataIn[1][:, 5], fMeshWk, nFactor)
+        fMe = np.concatenate((fMe_up, fMe_lw[1:], fMe_wk))
+
+        frho_up = interpolateToFineMesh(dataIn[0][0][:, 6], fMeshUp, nFactor)
+        frho_lw = interpolateToFineMesh(dataIn[0][1][:, 6], fMeshLw, nFactor)
+        frho_wk = interpolateToFineMesh(dataIn[1][:, 6], fMeshWk, nFactor)
+        frho = np.concatenate((frho_up, frho_lw[1:], frho_wk))
+
+        fdStarExt_up = interpolateToFineMesh(dataIn[0][0][:, 7], fMeshUp, nFactor)
+        fdStarExt_lw = interpolateToFineMesh(dataIn[0][1][:, 7], fMeshLw, nFactor)
+        fdStarExt_wk = interpolateToFineMesh(dataIn[1][:, 7], fMeshWk, nFactor)
+
+        fxxExt_up = interpolateToFineMesh(dataIn[0][0][:, 8], fMeshUp, nFactor)
+        fxxExt_lw = interpolateToFineMesh(dataIn[0][1][:, 8], fMeshLw, nFactor)
+        fxxExt_wk = interpolateToFineMesh(dataIn[1][:, 8], fMeshWk, nFactor)
+
+        fdStarext = np.concatenate((fdStarExt_up, fdStarExt_lw[1:], fdStarExt_wk))
+        fxxext = np.concatenate((fxxExt_up, fxxExt_lw[1:], fxxExt_wk))
+        fdv = np.concatenate((dv_up, dv_lw[1:], dv_wk))
+
+        # Create dictionnaries for the fine and coarse meshes.
+        fMeshDict = {'globCoord': fMesh, 'bNodes': fMeshbNodes, 'locCoord': fxx,
+                        'vtExt': fvtExt, 'Me': fMe, 'rho': frho, 'deltaStarExt': fdStarext, 'xxExt': fxxext, 'dv': fdv}
+
+        cMe = np.concatenate((dataIn[0][0][:, 5], dataIn[0][1][1:, 5], dataIn[1][:, 5]))
+        cRho = np.concatenate((dataIn[0][0][:, 6], dataIn[0][1][1:, 6], dataIn[1][:, 6]))
+        cdStarExt = np.concatenate((dataIn[0][0][:, 7], dataIn[0][1][1:, 7], dataIn[1][:, 7]))
+        cMeshDict = {'globCoord': cMesh, 'bNodes': cMeshbNodes, 'locCoord': cxx,
+                        'vtExt': cvtExt, 'Me': cMe, 'rho': cRho, 'deltaStarExt': cdStarExt, 'xxExt': cxxExt, 'dv': fdv}
+
+        dataUpper = np.zeros((len(fMeshUp), 0))
+        dataLower = np.zeros((len(fMeshLw), 0))
+        dataWake = np.zeros((len(fMeshWk), 0))
+        for iParam in range(len(dataIn[0][0][0, :])):
+            interpParamUp = interpolateToFineMesh(dataIn[0][0][:, iParam], fMeshUp, nFactor)
+            dataUpper = np.column_stack((dataUpper, interpParamUp))
+            interpParamLw = interpolateToFineMesh(dataIn[0][1][:, iParam], fMeshLw, nFactor)
+            dataLower = np.column_stack((dataLower, interpParamLw))
+            interpParamWk = interpolateToFineMesh(dataIn[1][:, iParam], fMeshWk, nFactor)
+            dataWake = np.column_stack((dataWake, interpParamWk))
+        # Remove stagnation point doublon.
+        dataLower = np.delete(dataLower, 0, axis=0)
+    else:
+        Mesh = np.concatenate((dataIn[0][0][:, 0], dataIn[0][1][:, 0], dataIn[1][:, 0]))
+        Meshy = np.concatenate((dataIn[0][0][:, 1], dataIn[0][1][:, 1], dataIn[1][:, 1]))
+        Meshz = np.concatenate((dataIn[0][0][:, 2], dataIn[0][1][:, 2], dataIn[1][:, 2]))
+        MeshbNodes = [0, len(dataIn[0][0][:, 0]), len(dataIn[0][0][:, 0]) + len(dataIn[0][1][:, 0]), len(dataIn[0][0][:, 0]) + len(dataIn[0][1][:, 0]) + len(dataIn[1][:, 0])]
+
+        # Concatenate all parameters (upper side, lower side, wake)
+        xx = np.concatenate((xx_up, xx_lw, xx_wk))
+        vtExt = np.concatenate((vtExt_up, vtExt_lw, vtExt_wk))
+        vx = np.concatenate((dataIn[0][0][:, 3], dataIn[0][1][:, 3], dataIn[1][:, 3]))
+        vy = np.concatenate((dataIn[0][0][:, 4], dataIn[0][1][:, 4], dataIn[1][:, 4]))
+        vz = np.concatenate((dataIn[0][0][:, 5], dataIn[0][1][:, 5], dataIn[1][:, 5]))
+        alpha = np.concatenate((alpha_up, alpha_lw, alpha_wk))
+        dv = np.concatenate((dv_up, dv_lw, dv_wk))
+        Me = np.concatenate((dataIn[0][0][:, 5], dataIn[0][1][:, 5], dataIn[1][:, 5]))
+        rho = np.concatenate((dataIn[0][0][:, 6], dataIn[0][1][:, 6], dataIn[1][:, 6]))
+        dStarext = np.concatenate((dataIn[0][0][:, 7], dataIn[0][1][:, 7], dataIn[1][:, 7]))
+        xxExt = np.concatenate((dataIn[0][0][:, 8], dataIn[0][1][:, 8], dataIn[1][:, 8]))
+
+        # Create dictionnaries for the fine and coarse meshes which are equivalent if meshMap is disabled. 
+        fMeshDict = {'globCoord': Mesh, 'yGlobCoord': Meshy, 'zGlobCoord': Meshz, 'bNodes': MeshbNodes, 'locCoord': xx,
+                        'vtExt': vtExt, 'vx': vx, 'vy': vy, 'vz': vz, 'Me': Me, 'rho': rho, 'deltaStarExt': dStarext, 'xxExt': xxExt, 'dv': dv}
+        cMeshDict = fMeshDict.copy()
+        dataUpper = dataIn[0][0]
+        dataLower = dataIn[0][1][1:, :]
+        dataWake = dataIn[1]
+
+    data = np.concatenate((dataUpper, dataLower, dataWake), axis=0)
+
+    return fMeshDict, cMeshDict, data
+
+def getBLcoordinates(data):
+    """Transform the reference coordinates into airfoil coordinates.
+    """
+    nN = len(data[:, 0])
+    nE = nN-1            # Nbr of element along the surface.
+    vt = np.zeros(nN)    # Outer flow velocity.
+    xx = np.zeros(nN)    # Position along the surface of the airfoil.
+    dx = np.zeros(nE)    # Distance along two nodes.
+    dv = np.zeros(nE)    # Speed difference according to element.
+    alpha = np.zeros(nE)    # Angle of the element for the rotation matrix.
+
+    for i in range(0, nE):
+        alpha[i] = np.arctan2((data[i+1, 1] - data[i, 1]), (data[i+1, 0]-data[i, 0]))
+        dx[i] = np.sqrt((data[i+1, 0] - data[i, 0]) **2 + (data[i+1, 1]-data[i, 1])**2)
+        xx[i+1] = dx[i] + xx[i]
+        # Tangential speed at node 1 element i
+        vt[i] = (data[i, 3] * np.cos(alpha[i]) + data[i, 4] * np.sin(alpha[i]))
+        # Tangential speed at node 2 element i
+        vt[i+1] = (data[i+1, 3] * np.cos(alpha[i]) + data[i+1, 4] * np.sin(alpha[i]))
+        # Velocity gradient according to element i.
+        dv[i] = (vt[i+1] - vt[i]) / dx[i]
+    return xx, dv, vt, nN, alpha
+
+def interpolateToFineMesh(cVector, fMesh, nFactor):
+    """ Interpolate variables of cVector on the fine mesh fMesh.
+
+    Params
+    ------
+    - cVector (numpy.ndarray): Distribution to be interpolated.
+    - fMesh (numpy.ndarray): Local tangential coordinate (xi) of the fine mesh.
+    - nFactor (int): Number of times each cell was split when creating the fine mesh.
+    """
+    fVector = np.zeros(len(fMesh)-1)
+    for cPoint in range(len(cVector) - 1):
+        dv = cVector[cPoint + 1] - cVector[cPoint]
+        for fPoint in range(nFactor):
+            fVector[nFactor * cPoint + fPoint] = cVector[cPoint] + \
+                fPoint * dv / nFactor
+    fVector = np.append(fVector, cVector[-1])
+    return fVector
+
+def createFineMesh(cMesh, nFactor):
+    """ Create fine mesh distribution in the local frame of reference.
+
+    Params
+    ------
+    - cMesh (numpy.ndarray): Initial mesh (used for the inviscid calculations.
+    - nFactor (int): Number of times each cell will be split to create the fine mesh.
+    """
+    fMesh = []
+    for iPoint in range(len(cMesh) - 1):
+        dx = cMesh[iPoint + 1] - cMesh[iPoint]
+        for iRefinedPoint in range(nFactor):
+            fMesh = np.append(
+                fMesh, cMesh[iPoint] + iRefinedPoint * dx / nFactor)
+    fMesh = np.append(fMesh, cMesh[-1])
+    return fMesh
diff --git a/blast/interfaces/interpolators/DMatchingInterpolator.py b/blast/interfaces/interpolators/DMatchingInterpolator.py
new file mode 100644
index 0000000..e286024
--- /dev/null
+++ b/blast/interfaces/interpolators/DMatchingInterpolator.py
@@ -0,0 +1,27 @@
+import numpy as np
+class MatchingInterpolator:
+    def __init__(self, cfg):
+        self.cfg = cfg
+
+    def inviscidToViscous(self, iDict, vDict, couplIter):
+        ## Airfoil
+        # Find stagnation point
+        if self.cfg['nDim'] == 2:
+            for iReg in range(len(iDict)):
+                vDict[0][iReg].updateVars(iDict[iReg].nodesCoord, iDict[iReg].V, iDict[iReg].M, iDict[iReg].Rho)
+        elif self.cfg['nDim'] == 3:
+            for iSec, ysec in enumerate(self.cfg['Sections']):
+                for iReg in range(2):
+                    print(iSec)
+                    print(iDict[iReg].nodesCoord[iDict[iReg].nodesCoord[:,1] == ysec])
+                    print(iDict[iReg].V[iDict[iReg].nodesCoord[:,1] == ysec])
+                    vDict[iSec][iReg].updateVars(iDict[iReg].nodesCoord[iDict[iReg].nodesCoord[:,1] == ysec], iDict[iReg].V[iDict[iReg].nodesCoord[:,1] == ysec], iDict[iReg].Rho[iDict[iReg].nodesCoord[:,1] == ysec])
+                    
+                    
+
+    def viscousToInviscid(self, iDict, vDict):
+        if self.cfg['nDim'] == 2:
+            for iReg in range(2):
+                iDict[iReg].blowingVel = vDict[0][iReg].blowingVel
+        else:
+            raise RuntimeError('Incorrect number of dimensions', self.nDim)
\ No newline at end of file
diff --git a/blast/interfaces/interpolators/DRbfInterpolator.py b/blast/interfaces/interpolators/DRbfInterpolator.py
new file mode 100644
index 0000000..cb075f0
--- /dev/null
+++ b/blast/interfaces/interpolators/DRbfInterpolator.py
@@ -0,0 +1,56 @@
+
+
+import numpy as np
+from scipy.interpolate import RBFInterpolator
+class RbfInterpolator:
+    def __init__(self, _cfg):
+        self.cfg = _cfg
+
+    def inviscidToViscous(self, iDict, vDict, couplIter):
+        ## Airfoil
+        # Find stagnation point
+        for iSec in range(len(vDict)):
+            for iReg, reg in enumerate(vDict[iSec]):
+                v = np.zeros((reg.nodesCoord.shape[0], 3))
+                M = np.zeros(reg.nodesCoord.shape[0])
+                rho = np.zeros(reg.nodesCoord.shape[0])
+                for iDim in range(3):
+                    v[:,iDim] = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self.cfg['nDim'] if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].V[:,iDim], reg.nodesCoord[:,:(self.cfg['nDim'] if reg.name == 'vAirfoil' else 1)])
+                M = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self.cfg['nDim'] if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].M, reg.nodesCoord[:,:(self.cfg['nDim'] if reg.name == 'vAirfoil' else 1)])
+                rho = self.__rbfToSection(iDict[iReg].nodesCoord[:,:(self.cfg['nDim'] if iDict[iReg].name == 'iWing' else 1)], iDict[iReg].Rho, reg.nodesCoord[:,:(self.cfg['nDim'] if reg.name == 'vAirfoil' else 1)])
+                vDict[iSec][iReg].updateVars(vDict[iSec][iReg].nodesCoord, v, M, rho)
+
+    def viscousToInviscid(self, iDict, vDict):
+        if self.cfg['nDim'] == 2:
+            for iReg, reg in enumerate(iDict):
+                iDict[iReg].blowingVel = self.__rbfToSection(vDict[0][iReg].elemsCoordTr[:,:(self.cfg['nDim']-1 if reg.name == 'vAirfoil' else 1)], vDict[0][iReg].blowingVel, reg.elemsCoordTr[:,:(self.cfg['nDim']-1 if reg.name == 'iWing' else 1)])
+        elif self.cfg['nDim'] == 3:
+            for iReg, reg in enumerate(iDict):
+                viscElemsCoord = np.zeros((0,3))
+                viscBlowing = np.zeros(0)
+                for iSec, sec in enumerate(vDict):
+                    viscElemsCoord = np.row_stack((viscElemsCoord, sec[iReg].elemsCoord[:,:3]))
+                    viscBlowing = np.concatenate((viscBlowing, sec[iReg].blowingVel))
+                if 'Sym' in self.cfg:
+                    for s in self.cfg['Sym']:
+                        dummy = viscElemsCoord.copy()
+                        dummy[:,2] = (dummy[:,2] - s)*(-1) + s
+                        viscElemsCoord = np.row_stack((viscElemsCoord, dummy))
+                        viscBlowing = np.concatenate((viscBlowing, viscBlowing))
+                reg.blowingVel = self.__rbfToSection(viscElemsCoord, viscBlowing, reg.elemsCoord[:,:3])
+        else:
+            raise RuntimeError('Incorrect number of dimensions', self.cfg['nDim'])
+
+    def __rbfToSection(self, x, var, xs):
+        if np.all(var == var[0]):
+            varOut = RBFInterpolator(x, var, neighbors=1, kernel='linear', smoothing=0.0, degree=0)(xs)
+        else:
+            varOut = RBFInterpolator(x, var, neighbors=self.cfg['neighbors'], kernel=self.cfg['rbftype'], smoothing=self.cfg['smoothing'], degree=self.cfg['degree'])(xs)
+        """from matplotlib import pyplot as plt
+        plt.plot(xs[:,0], RBFInterpolator(x, var, neighbors=self.cfg['neighbors'], kernel=self.cfg['rbftype'], smoothing=0.01, degree=self.cfg['degree'])(xs), 'o-')
+        plt.plot(xs[:,0], RBFInterpolator(x, var, neighbors=self.cfg['neighbors'], kernel=self.cfg['rbftype'], smoothing=0.001, degree=self.cfg['degree'])(xs), '--')
+        plt.plot(xs[:,0], RBFInterpolator(x, var, neighbors=self.cfg['neighbors'], kernel=self.cfg['rbftype'], smoothing=0.0001, degree=self.cfg['degree'])(xs), '*-')
+        plt.plot(xs[:,0], RBFInterpolator(x, var, neighbors=self.cfg['neighbors'], kernel=self.cfg['rbftype'], smoothing=1e-9, degree=self.cfg['degree'])(xs))
+        plt.show()
+        quit()"""
+        return varOut
\ No newline at end of file
diff --git a/blast/interfaces/su2/SU2Interface.py b/blast/interfaces/su2/SU2Interface.py
new file mode 100644
index 0000000..b0c06cb
--- /dev/null
+++ b/blast/interfaces/su2/SU2Interface.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2020 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# SU2 interface
+# Paul Dechamps
+import numpy as np
+
+from blast.interfaces.DSolversInterface import SolversInterface
+
+class SU2Interface(SolversInterface):
+    def __init__(self, fileName, vSolver, cfg):
+        self.have_mpi, self.comm, self.status, self.myid = self.__setup_mpi()
+        try:
+            import pysu2
+            self.solver = pysu2.CSinglezoneDriver(fileName, 1, self.comm)
+            print('SU2 successfully loaded.')
+        except:
+            print(ccolors.ANSI_RED, 'Could not load SU2. Make sure it is installed.', ccolors.ANSI_RESET)
+            raise RuntimeError('Import error')
+        SolversInterface.__init__(self, vSolver, cfg)
+
+    def __setup_mpi(self):
+        import sys
+        from mpi4py import MPI
+        if 'mpi4py.MPI' in sys.modules:
+            have_mpi = True
+            comm = MPI.COMM_WORLD
+            status = MPI.Status()
+            myid = comm.Get_rank()
+        else:
+            have_mpi = False
+            comm = None
+            status = None
+            myid = 0
+        return have_mpi, comm, status, myid
+    
+    def run(self):
+        self.comm.barrier()
+        # run solver
+        self.solver.Run()
+        self.solver.Postprocess()
+        # write outputs
+        self.solver.Monitor(0) 
+        self.solver.Output(0)
+        self.comm.barrier()
+        return 1
+
+    def writeCp(self, sfx=''):
+        cp = np.zeros((self.nPoints[0], 4))
+        for i in range(len(self.iBnd[0].nodesCoord[:,0])):
+            cp[i, :3] = self.solver.GetVertexCoord(int(self.bndMarkers[0]), int(self.iBnd[0].connectListNodes[i]))
+            cp[i, 3] = self.solver.GetVertexCp(int(self.bndMarkers[0]), int(self.iBnd[0].connectListNodes[i]))
+        np.savetxt('Cp'+sfx+'.dat', cp, fmt='%1.8e', delimiter=', ',
+                   header='{0:>14s}, {1:>14s}, {2:>14s}, {3:>14s}'.format('x','y', 'z', 'Cp'), comments='')
+    
+    def save(self, writer):
+        pass
+
+    def getAoA(self):
+        return 1
+    
+    def getMinf(self):
+        return 1
+    
+    def getCl(self):
+        return self.solver.Get_LiftCoeff()
+    
+    def getCd(self):
+        return self.solver.Get_DragCoeff()
+
+    def getCm(self):
+        return self.solver.Get_Mx()
+    
+    def getVerbose(self):
+        return 1
+    
+    def reset(self):
+        self.solver.Preprocess(0)
+    
+    def imposeInvBC(self, couplIter):
+        """ Extract the inviscid paramaters at the edge of the boundary layer
+        and use it as a boundary condition in the viscous solver.
+
+        Params
+        ------
+        - couplIter (int): Coupling iteration.
+        """
+        # Retreive parameters.
+        for n in range(len(self.iBnd)):
+            for iRow, row in enumerate(self.iBnd[n].connectListNodes):
+                row=int(row)
+                self.iBnd[n].V[iRow,:] = self.solver.GetVertexVelocity(self.bndMarkers[n], row)
+                self.iBnd[n].M[iRow] = self.solver.GetVertexMach(self.bndMarkers[n], row)
+                self.iBnd[n].Rho[iRow] = self.solver.GetVertexDensity(self.bndMarkers[n], row)
+                if self.iBnd[n].Rho[iRow] == 0:
+                    self.iBnd[n].Rho[iRow] = 1
+        self.setViscousSolver(couplIter)
+
+    def imposeBlwVel(self):
+        """ Extract the solution of the viscous calculation (blowing velocity)
+        and use it as a boundary condition in the inviscid solver.
+        """
+        if self.iBnd[0].connectListNodes[0] != self.iBnd[0].connectListNodes[-1]:
+            raise RuntimeError("List of connectivity is incorrect.")
+
+        self.getBlowingBoundary()
+        # Impose blowing velocities.
+        for n in range(len(self.bndMarkers)):
+            if n == 0:
+                self.iBnd[n].blowingVel[0] = .5 * (self.iBnd[n].blowingVel[0] + self.iBnd[n].blowingVel[-1])
+                self.iBnd[n].blowingVel = self.iBnd[n].blowingVel[self.iBnd[n].connectListElems[:-1].argsort()]
+            elif n == 1:
+                self.iBnd[1].blowingVel = np.insert(self.iBnd[1].blowingVel, 0, self.iBnd[0].blowingVel[0])
+                self.iBnd[n].blowingVel = self.iBnd[n].blowingVel[self.iBnd[n].connectListElems.argsort()]
+            for iVertex in range(len(self.iBnd[n].blowingVel)-1):
+                self.solver.SetBlowing(self.bndMarkers[n], iVertex, self.iBnd[n].blowingVel[iVertex])
+    
+    def setMesh(self):
+
+        if self.cfg['nDim'] == 2:
+            self.getAirfoil()
+        elif self.cfg['nDim'] == 3:
+            self.getWing()
+        else:
+            raise RuntimeError('su2Interface::Could not resolve how to set\
+            the mesh. Either ''nDim'' is incorrect or ''msh'' was not set for\
+            3D computations')
+
+    def getAirfoil(self):
+        """ Retreives the mesh used for SU2 Euler calculation.
+        Airfoil is already in seilig format (Upper TE -> Lower TE).
+        """
+        alltags = self.solver.GetAllBoundaryMarkers()
+        if 'wake' not in alltags:
+            #raise RuntimeError('Can not find tag wake.')
+            self.bndMarkers = [alltags['airfoil']]
+            del self.iBnd[1]
+            del self.vBnd[0][1]
+        else:
+            self.bndMarkers = [alltags['airfoil'], alltags['wake']]
+
+        self.nPoints = [self.solver.GetNumberVertices(tag) for tag in self.bndMarkers]
+        data = [np.zeros((nPts, 4)) for nPts in self.nPoints]
+        for n in range(len(self.nPoints)):
+            for i in range(self.nPoints[n]):
+                data[n][i,:3] = self.solver.GetVertexCoord(self.bndMarkers[n], i)
+                data[n][i,3] = i
+
+        # Find TE and reorder coordinates
+        teNode = data[0][:,0].argmax()
+        """if teNode != 0:
+            save = data[0][0,:].copy()
+            data[0][0,:] = data[0][teNode,:].copy()
+            data[0][teNode,:] = save
+            teNode = data[0][:,0].argmax()
+            print('Airfoil coordinates were reordered automatically.')"""
+
+        for i in range(len(data[0][0,:])):
+            data[0][:teNode+1,i] = np.flip(data[0][:teNode+1,i])
+            data[0][teNode+1:,i] = np.flip(data[0][teNode+1:,i])
+
+        # Duplicate TE point
+        data[0] = np.row_stack((data[0], data[0][0,:]))
+        # Find LE point
+        self.leNode = data[0][:,0].argmin()+1
+        # Recompute number of points
+        self.nPoints = [len(data[n]) for n in range(len(data))]
+        
+        dataElems = [np.zeros((nPts-1, 3)) for nPts in self.nPoints]
+        for n in range(len(data)):
+            for iPoint in range(len(data[n][:,0]) - 1):
+                dataElems[n][iPoint, :] = .5*(data[n][iPoint+1,:3] + data[n][iPoint, :3])
+        # Nodes coordinates in the transformed space
+        self.phantomNodesCoord = [d.copy() for d in data]
+        self.phantomNodesCoord[0][:self.leNode, 0] *= -1
+
+         # Compute elements cg coordinates.
+        self.elemCgs = [np.zeros((nPts-1, 4)) for nPts in self.nPoints]
+        for n in range(len(self.elemCgs)):
+            for i in range(len(self.elemCgs[n][:,0])):
+                self.elemCgs[n][i,:] = (data[n][i,:] + data[n][i+1,:]) / 2
+        
+        # Elements cg coordinates in the transformed space
+        self.phantomElemsCoord = [ecg.copy() for ecg in self.elemCgs]
+        self.phantomElemsCoord[0][:self.leNode, 0] *= -1
+
+        for n, bnd in enumerate(self.iBnd):
+            bnd.elemsCoord = dataElems[n]
+            bnd.nodesCoord = data[n]
+            bnd.setConnectList(data[n][:,3], data[n][:,3])
+            bnd.V = np.zeros((self.nPoints[n], 3))
+            bnd.M = np.zeros(self.nPoints[n])
+            bnd.Rho = np.zeros(self.nPoints[n])
+        self.solver.InitBlowing()
diff --git a/blast/models/dart/n0012.geo b/blast/models/dart/n0012.geo
new file mode 100644
index 0000000..e03f649
--- /dev/null
+++ b/blast/models/dart/n0012.geo
@@ -0,0 +1,282 @@
+/* Naca0012 */
+
+// Geometry
+DefineConstant[ xLgt = { 5.0, Name "Domain length (x-dir)" }  ];
+DefineConstant[ yLgt = { 5.0, Name "Domain length (y-dir)" }  ];
+
+// Mesh
+DefineConstant[ msF = { 1.0, Name "Farfield mesh size" }  ];
+DefineConstant[ msTe = { 0.01, Name "Airfoil TE mesh size" }  ];
+DefineConstant[ msLe = { 0.01, Name "Airfoil LE mesh size" }  ];
+
+// Rotation
+DefineConstant[ xRot = { 0.25, Name "Center of rotation" }  ];
+DefineConstant[ angle = { 0*Pi/180, Name "Angle of rotation" }  ];
+Geometry.AutoCoherence = 0; // Needed so that gmsh does not remove duplicate
+
+/**************
+    Geometry  
+ **************/
+
+// Airfoil
+Te = 1;
+Le = 101;
+N = 200;
+
+//Point(201) = {1.000000,0.000000,0,msTe};
+Point(200) = {0.999753,-0.000035,0, msTe};
+Point(199) = {0.999013,-0.000141,0};
+Point(198) = {0.997781,-0.000317,0};
+Point(197) = {0.996057,-0.000562,0};
+Point(196) = {0.993844,-0.000876,0};
+Point(195) = {0.991144,-0.001258,0};
+Point(194) = {0.987958,-0.001707,0};
+Point(193) = {0.984292,-0.002222,0};
+Point(192) = {0.980147,-0.002801,0};
+Point(191) = {0.975528,-0.003443,0};
+Point(190) = {0.970440,-0.004147,0};
+Point(189) = {0.964888,-0.004909,0};
+Point(188) = {0.958877,-0.005729,0};
+Point(187) = {0.952414,-0.006603,0};
+Point(186) = {0.945503,-0.007531,0};
+Point(185) = {0.938153,-0.008510,0};
+Point(184) = {0.930371,-0.009537,0};
+Point(183) = {0.922164,-0.010610,0};
+Point(182) = {0.913540,-0.011726,0};
+Point(181) = {0.904508,-0.012883,0};
+Point(180) = {0.895078,-0.014079,0};
+Point(179) = {0.885257,-0.015310,0};
+Point(178) = {0.875056,-0.016574,0};
+Point(177) = {0.864484,-0.017868,0};
+Point(176) = {0.853553,-0.019189,0};
+Point(175) = {0.842274,-0.020535,0};
+Point(174) = {0.830656,-0.021904,0};
+Point(173) = {0.818712,-0.023291,0};
+Point(172) = {0.806454,-0.024694,0};
+Point(171) = {0.793893,-0.026111,0};
+Point(170) = {0.781042,-0.027539,0};
+Point(169) = {0.767913,-0.028974,0};
+Point(168) = {0.754521,-0.030414,0};
+Point(167) = {0.740877,-0.031856,0};
+Point(166) = {0.726995,-0.033296,0};
+Point(165) = {0.712890,-0.034733,0};
+Point(164) = {0.698574,-0.036163,0};
+Point(163) = {0.684062,-0.037582,0};
+Point(162) = {0.669369,-0.038988,0};
+Point(161) = {0.654508,-0.040378,0};
+Point(160) = {0.639496,-0.041747,0};
+Point(159) = {0.624345,-0.043094,0};
+Point(158) = {0.609072,-0.044414,0};
+Point(157) = {0.593691,-0.045705,0};
+Point(156) = {0.578217,-0.046962,0};
+Point(155) = {0.562667,-0.048182,0};
+Point(154) = {0.547054,-0.049362,0};
+Point(153) = {0.531395,-0.050499,0};
+Point(152) = {0.515705,-0.051587,0};
+Point(151) = {0.500000,-0.052625,0};
+Point(150) = {0.484295,-0.053608,0};
+Point(149) = {0.468605,-0.054534,0};
+Point(148) = {0.452946,-0.055397,0};
+Point(147) = {0.437333,-0.056195,0};
+Point(146) = {0.421783,-0.056924,0};
+Point(145) = {0.406309,-0.057581,0};
+Point(144) = {0.390928,-0.058163,0};
+Point(143) = {0.375655,-0.058666,0};
+Point(142) = {0.360504,-0.059087,0};
+Point(141) = {0.345492,-0.059424,0};
+Point(140) = {0.330631,-0.059674,0};
+Point(139) = {0.315938,-0.059834,0};
+Point(138) = {0.301426,-0.059902,0};
+Point(137) = {0.287110,-0.059876,0};
+Point(136) = {0.273005,-0.059754,0};
+Point(135) = {0.259123,-0.059535,0};
+Point(134) = {0.245479,-0.059217,0};
+Point(133) = {0.232087,-0.058799,0};
+Point(132) = {0.218958,-0.058280,0};
+Point(131) = {0.206107,-0.057661,0};
+Point(130) = {0.193546,-0.056940,0};
+Point(129) = {0.181288,-0.056119,0};
+Point(128) = {0.169344,-0.055197,0};
+Point(127) = {0.157726,-0.054176,0};
+Point(126) = {0.146447,-0.053056,0};
+Point(125) = {0.135516,-0.051839,0};
+Point(124) = {0.124944,-0.050527,0};
+Point(123) = {0.114743,-0.049121,0};
+Point(122) = {0.104922,-0.047624,0};
+Point(121) = {0.095492,-0.046037,0};
+Point(120) = {0.086460,-0.044364,0};
+Point(119) = {0.077836,-0.042608,0};
+Point(118) = {0.069629,-0.040770,0};
+Point(117) = {0.061847,-0.038854,0};
+Point(116) = {0.054497,-0.036863,0};
+Point(115) = {0.047586,-0.034800,0};
+Point(114) = {0.041123,-0.032668,0};
+Point(113) = {0.035112,-0.030471,0};
+Point(112) = {0.029560,-0.028212,0};
+Point(111) = {0.024472,-0.025893,0};
+Point(110) = {0.019853,-0.023517,0};
+Point(109) = {0.015708,-0.021088,0};
+Point(108) = {0.012042,-0.018607,0};
+Point(107) = {0.008856,-0.016078,0};
+Point(106) = {0.006156,-0.013503,0};
+Point(105) = {0.003943,-0.010884,0};
+Point(104) = {0.002219,-0.008223,0};
+Point(103) = {0.000987,-0.005521,0};
+Point(102) = {0.000247,-0.002779,0};
+Point(101) = {0.000000,0.000000,0,msLe};
+Point(100) = {0.000247,0.002779,0};
+Point(99) = {0.000987,0.005521,0};
+Point(98) = {0.002219,0.008223,0};
+Point(97) = {0.003943,0.010884,0};
+Point(96) = {0.006156,0.013503,0};
+Point(95) = {0.008856,0.016078,0};
+Point(94) = {0.012042,0.018607,0};
+Point(93) = {0.015708,0.021088,0};
+Point(92) = {0.019853,0.023517,0};
+Point(91) = {0.024472,0.025893,0};
+Point(90) = {0.029560,0.028212,0};
+Point(89) = {0.035112,0.030471,0};
+Point(88) = {0.041123,0.032668,0};
+Point(87) = {0.047586,0.034800,0};
+Point(86) = {0.054497,0.036863,0};
+Point(85) = {0.061847,0.038854,0};
+Point(84) = {0.069629,0.040770,0};
+Point(83) = {0.077836,0.042608,0};
+Point(82) = {0.086460,0.044364,0};
+Point(81) = {0.095492,0.046037,0};
+Point(80) = {0.104922,0.047624,0};
+Point(79) = {0.114743,0.049121,0};
+Point(78) = {0.124944,0.050527,0};
+Point(77) = {0.135516,0.051839,0};
+Point(76) = {0.146447,0.053056,0};
+Point(75) = {0.157726,0.054176,0};
+Point(74) = {0.169344,0.055197,0};
+Point(73) = {0.181288,0.056119,0};
+Point(72) = {0.193546,0.056940,0};
+Point(71) = {0.206107,0.057661,0};
+Point(70) = {0.218958,0.058280,0};
+Point(69) = {0.232087,0.058799,0};
+Point(68) = {0.245479,0.059217,0};
+Point(67) = {0.259123,0.059535,0};
+Point(66) = {0.273005,0.059754,0};
+Point(65) = {0.287110,0.059876,0};
+Point(64) = {0.301426,0.059902,0};
+Point(63) = {0.315938,0.059834,0};
+Point(62) = {0.330631,0.059674,0};
+Point(61) = {0.345492,0.059424,0};
+Point(60) = {0.360504,0.059087,0};
+Point(59) = {0.375655,0.058666,0};
+Point(58) = {0.390928,0.058163,0};
+Point(57) = {0.406309,0.057581,0};
+Point(56) = {0.421783,0.056924,0};
+Point(55) = {0.437333,0.056195,0};
+Point(54) = {0.452946,0.055397,0};
+Point(53) = {0.468605,0.054534,0};
+Point(52) = {0.484295,0.053608,0};
+Point(51) = {0.500000,0.052625,0};
+Point(50) = {0.515705,0.051587,0};
+Point(49) = {0.531395,0.050499,0};
+Point(48) = {0.547054,0.049362,0};
+Point(47) = {0.562667,0.048182,0};
+Point(46) = {0.578217,0.046962,0};
+Point(45) = {0.593691,0.045705,0};
+Point(44) = {0.609072,0.044414,0};
+Point(43) = {0.624345,0.043094,0};
+Point(42) = {0.639496,0.041747,0};
+Point(41) = {0.654508,0.040378,0};
+Point(40) = {0.669369,0.038988,0};
+Point(39) = {0.684062,0.037582,0};
+Point(38) = {0.698574,0.036163,0};
+Point(37) = {0.712890,0.034733,0};
+Point(36) = {0.726995,0.033296,0};
+Point(35) = {0.740877,0.031856,0};
+Point(34) = {0.754521,0.030414,0};
+Point(33) = {0.767913,0.028974,0};
+Point(32) = {0.781042,0.027539,0};
+Point(31) = {0.793893,0.026111,0};
+Point(30) = {0.806454,0.024694,0};
+Point(29) = {0.818712,0.023291,0};
+Point(28) = {0.830656,0.021904,0};
+Point(27) = {0.842274,0.020535,0};
+Point(26) = {0.853553,0.019189,0};
+Point(25) = {0.864484,0.017868,0};
+Point(24) = {0.875056,0.016574,0};
+Point(23) = {0.885257,0.015310,0};
+Point(22) = {0.895078,0.014079,0};
+Point(21) = {0.904508,0.012883,0};
+Point(20) = {0.913540,0.011726,0};
+Point(19) = {0.922164,0.010610,0};
+Point(18) = {0.930371,0.009537,0};
+Point(17) = {0.938153,0.008510,0};
+Point(16) = {0.945503,0.007531,0};
+Point(15) = {0.952414,0.006603,0};
+Point(14) = {0.958877,0.005729,0};
+Point(13) = {0.964888,0.004909,0};
+Point(12) = {0.970440,0.004147,0};
+Point(11) = {0.975528,0.003443,0};
+Point(10) = {0.980147,0.002801,0};
+Point(9) = {0.984292,0.002222,0};
+Point(8) = {0.987958,0.001707,0};
+Point(7) = {0.991144,0.001258,0};
+Point(6) = {0.993844,0.000876,0};
+Point(5) = {0.996057,0.000562,0};
+Point(4) = {0.997781,0.000317,0};
+Point(3) = {0.999013,0.000141,0};
+Point(2) = {0.999753,0.000035,0};
+Point(1) = {1.000000,0.000000,0,msTe};
+
+Spline(1) = {101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1};
+Spline(2) = {1,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101};
+
+// Rotation
+If (angle != 0)
+    For i In {Te:N:1}
+        Rotate{{0, 0, 1}, {xRot, 0, 0}, -angle} {Point{i};}
+    EndFor
+EndIf
+
+// Farfield
+Point(10001) = {1+xLgt, 0, 0,msF};
+Point(10002) = {1+xLgt, yLgt, 0,msF};
+Point(10003) = {-xLgt, yLgt, 0,msF};
+Point(10004) = {-xLgt, 0, 0,msF};
+Point(10005) = {-xLgt,-yLgt, 0,msF};
+Point(10006) = {1+xLgt, -yLgt, 0,msF};
+
+Line(10001) = {10001, 10002};
+Line(10002) = {10002, 10003};
+Line(10003) = {10003, 10004};
+Line(10004) = {10004, 10005};
+Line(10005) = {10005, 10006};
+Line(10006) = {10006, 10001};
+
+// Front and wake
+Line(10007) = {Le, 10004};
+Line(10008) = {Te, 10001};
+
+// Internal field
+Line Loop(10001) = {10008, 10001, 10002, 10003, -10007, 1};
+Line Loop(10002) = {10007, 10004, 10005, 10006, -10008, 2};
+Plane Surface(10001) = {10001};
+Plane Surface(10002) = {10002};
+
+/*************************
+       Mesh Options
+ *************************/
+
+Mesh.Algorithm = 5; // Delaunay
+
+/*************************
+    Physical Groups
+ *************************/
+
+Physical Point("te") = {Te};
+Physical Line("upstream") = {10003, 10004};
+Physical Line("farfield") = {10002, 10005};
+Physical Line("downstream") = {10001};
+Physical Line("downstream") += {10006};
+Physical Line("airfoil") = {1};
+Physical Line("airfoil_") = {2};
+Physical Line("wake") = {10008};
+Physical Surface("field") = {10001};
+Physical Surface("field") += {10002};
diff --git a/blast/models/dart/rae2822.geo b/blast/models/dart/rae2822.geo
new file mode 100644
index 0000000..2cc1676
--- /dev/null
+++ b/blast/models/dart/rae2822.geo
@@ -0,0 +1,200 @@
+/* Rae 2822 */
+
+// Geometry
+DefineConstant[ xLgt = { 5.0, Name "Domain length (x-dir)" }  ];
+DefineConstant[ yLgt = { 5.0, Name "Domain length (y-dir)" }  ];
+
+// Mesh
+DefineConstant[ msF = { 1.0, Name "Farfield mesh size" }  ];
+DefineConstant[ msTe = { 0.01, Name "Airfoil TE mesh size" }  ];
+DefineConstant[ msLe = { 0.01, Name "Airfoil LE mesh size" }  ];
+
+/**************
+    Geometry  
+ **************/
+
+// Airfoil
+Te = 1;
+Le = 65;
+
+Point(1) =   {1.000000,  0.000000, 0.000000,msTe};
+Point(2) =   {0.999398,  0.000128, 0.000000};
+Point(3) =   {0.997592,  0.000510, 0.000000};
+Point(4) =   {0.994588,  0.001137, 0.000000};
+Point(5) =   {0.990393,  0.002001, 0.000000};
+Point(6) =   {0.985016,  0.003092, 0.000000};
+Point(7) =   {0.978470,  0.004401, 0.000000};
+Point(8) =   {0.970772,  0.005915, 0.000000};
+Point(9) =   {0.961940,  0.007622, 0.000000};
+Point(10) =  {0.951995,  0.009508, 0.000000};
+Point(11) =  {0.940961,  0.011562, 0.000000};
+Point(12) =  {0.928864,  0.013769, 0.000000};
+Point(13) =  {0.915735,  0.016113, 0.000000};
+Point(14) =  {0.901604,  0.018580, 0.000000};
+Point(15) =  {0.886505,  0.021153, 0.000000};
+Point(16) =  {0.870476,  0.023817, 0.000000};
+Point(17) =  {0.853553,  0.026554, 0.000000};
+Point(18) =  {0.835779,  0.029347, 0.000000};
+Point(19) =  {0.817197,  0.032176, 0.000000};
+Point(20) =  {0.797850,  0.035017, 0.000000};
+Point(21) =  {0.777785,  0.037847, 0.000000};
+Point(22) =  {0.757051,  0.040641, 0.000000};
+Point(23) =  {0.735698,  0.043377, 0.000000};
+Point(24) =  {0.713778,  0.046029, 0.000000};
+Point(25) =  {0.691342,  0.048575, 0.000000};
+Point(26) =  {0.668445,  0.050993, 0.000000};
+Point(27) =  {0.645142,  0.053258, 0.000000};
+Point(28) =  {0.621490,  0.055344, 0.000000};
+Point(29) =  {0.597545,  0.057218, 0.000000};
+Point(30) =  {0.573365,  0.058845, 0.000000};
+Point(31) =  {0.549009,  0.060194, 0.000000};
+Point(32) =  {0.524534,  0.061254, 0.000000};
+Point(33) =  {0.500000,  0.062029, 0.000000};
+Point(34) =  {0.475466,  0.062530, 0.000000};
+Point(35) =  {0.450991,  0.062774, 0.000000};
+Point(36) =  {0.426635,  0.062779, 0.000000};
+Point(37) =  {0.402455,  0.062562, 0.000000};
+Point(38) =  {0.378510,  0.062133, 0.000000};
+Point(39) =  {0.354858,  0.061497, 0.000000};
+Point(40) =  {0.331555,  0.060660, 0.000000};
+Point(41) =  {0.308658,  0.059629, 0.000000};
+Point(42) =  {0.286222,  0.058414, 0.000000};
+Point(43) =  {0.264302,  0.057026, 0.000000};
+Point(44) =  {0.242949,  0.055470, 0.000000};
+Point(45) =  {0.222215,  0.053753, 0.000000};
+Point(46) =  {0.202150,  0.051885, 0.000000};
+Point(47) =  {0.182803,  0.049874, 0.000000};
+Point(48) =  {0.164221,  0.047729, 0.000000};
+Point(49) =  {0.146447,  0.045457, 0.000000};
+Point(50) =  {0.129524,  0.043071, 0.000000};
+Point(51) =  {0.113495,  0.040585, 0.000000};
+Point(52) =  {0.098396,  0.038011, 0.000000};
+Point(53) =  {0.084265,  0.035360, 0.000000};
+Point(54) =  {0.071136,  0.032644, 0.000000};
+Point(55) =  {0.059039,  0.029874, 0.000000};
+Point(56) =  {0.048005,  0.027062, 0.000000};
+Point(57) =  {0.038060,  0.024219, 0.000000};
+Point(58) =  {0.029228,  0.021348, 0.000000};
+Point(59) =  {0.021530,  0.018441, 0.000000};
+Point(60) =  {0.014984,  0.015489, 0.000000};
+Point(61) =  {0.009607,  0.012480, 0.000000};
+Point(62) =  {0.005412,  0.009416, 0.000000};
+Point(63) =  {0.002408,  0.006306, 0.000000};
+Point(64) =  {0.000602,  0.003165, 0.000000};
+Point(65) =  {0.000000,  0.000000, 0.000000, msLe};
+Point(66) =  {0.000602, -0.003160, 0.000000};
+Point(67) =  {0.002408, -0.006308, 0.000000};
+Point(68) =  {0.005412, -0.009443, 0.000000};
+Point(69) =  {0.009607, -0.012559, 0.000000};
+Point(70) =  {0.014984, -0.015649, 0.000000};
+Point(71) =  {0.021530, -0.018707, 0.000000};
+Point(72) =  {0.029228, -0.021722, 0.000000};
+Point(73) =  {0.038060, -0.024685, 0.000000};
+Point(74) =  {0.048005, -0.027586, 0.000000};
+Point(75) =  {0.059039, -0.030416, 0.000000};
+Point(76) =  {0.071136, -0.033170, 0.000000};
+Point(77) =  {0.084265, -0.035843, 0.000000};
+Point(78) =  {0.098396, -0.038431, 0.000000};
+Point(79) =  {0.113495, -0.040929, 0.000000};
+Point(80) =  {0.129524, -0.043326, 0.000000};
+Point(81) =  {0.146447, -0.045610, 0.000000};
+Point(82) =  {0.164221, -0.047773, 0.000000};
+Point(83) =  {0.182803, -0.049805, 0.000000};
+Point(84) =  {0.202150, -0.051694, 0.000000};
+Point(85) =  {0.222215, -0.053427, 0.000000};
+Point(86) =  {0.242949, -0.054994, 0.000000};
+Point(87) =  {0.264302, -0.056376, 0.000000};
+Point(88) =  {0.286222, -0.057547, 0.000000};
+Point(89) =  {0.308658, -0.058459, 0.000000};
+Point(90) =  {0.331555, -0.059046, 0.000000};
+Point(91) =  {0.354858, -0.059236, 0.000000};
+Point(92) =  {0.378510, -0.058974, 0.000000};
+Point(93) =  {0.402455, -0.058224, 0.000000};
+Point(94) =  {0.426635, -0.056979, 0.000000};
+Point(95) =  {0.450991, -0.055257, 0.000000};
+Point(96) =  {0.475466, -0.053099, 0.000000};
+Point(97) =  {0.500000, -0.050563, 0.000000};
+Point(98) =  {0.524534, -0.047719, 0.000000};
+Point(99) =  {0.549009, -0.044642, 0.000000};
+Point(100) = {0.573365, -0.041397, 0.000000};
+Point(101) = {0.597545, -0.038043, 0.000000};
+Point(102) = {0.621490, -0.034631, 0.000000};
+Point(103) = {0.645142, -0.031207, 0.000000};
+Point(104) = {0.668445, -0.027814, 0.000000};
+Point(105) = {0.691342, -0.024495, 0.000000};
+Point(106) = {0.713778, -0.021289, 0.000000};
+Point(107) = {0.735698, -0.018232, 0.000000};
+Point(108) = {0.757051, -0.015357, 0.000000};
+Point(109) = {0.777785, -0.012690, 0.000000};
+Point(110) = {0.797850, -0.010244, 0.000000};
+Point(111) = {0.817197, -0.008027, 0.000000};
+Point(112) = {0.835779, -0.006048, 0.000000};
+Point(113) = {0.853553, -0.004314, 0.000000};
+Point(114) = {0.870476, -0.002829, 0.000000};
+Point(115) = {0.886505, -0.001592, 0.000000};
+Point(116) = {0.901604, -0.000600, 0.000000};
+Point(117) = {0.915735,  0.000157, 0.000000};
+Point(118) = {0.928864,  0.000694, 0.000000};
+Point(119) = {0.940961,  0.001033, 0.000000};
+Point(120) = {0.951995,  0.001197, 0.000000};
+Point(121) = {0.961940,  0.001212, 0.000000};
+Point(122) = {0.970772,  0.001112, 0.000000};
+Point(123) = {0.978470,  0.000935, 0.000000};
+Point(124) = {0.985016,  0.000719, 0.000000};
+Point(125) = {0.990393,  0.000497, 0.000000};
+Point(126) = {0.994588,  0.000296, 0.000000};
+Point(127) = {0.997592,  0.000137, 0.000000};
+Point(128) = {0.999398,  0.000035, 0.000000, msTe};
+//Point(129) = {1.000000,  0.000000, 0.000000, msTe};
+
+Spline(1) = {65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1};
+Spline(2) = {1,128,127,126,125,124,123,122,121,120,119,118,117,116,115,114,113,112,111,110,109,108,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65};
+
+// Center
+Point(0) = {1,0,0};
+
+// Farfield
+Point(10001) = {1+xLgt, 0, 0,msF};
+Point(10002) = {1+xLgt, yLgt, 0,msF};
+Point(10003) = {-xLgt, yLgt, 0,msF};
+Point(10004) = {-xLgt, 0, 0,msF};
+Point(10005) = {-xLgt,-yLgt, 0,msF};
+Point(10006) = {1+xLgt, -yLgt, 0,msF};
+
+Line(10001) = {10001, 10002};
+Line(10002) = {10002, 10003};
+Line(10003) = {10003, 10004};
+Line(10004) = {10004, 10005};
+Line(10005) = {10005, 10006};
+Line(10006) = {10006, 10001};
+
+// Front and wake
+Line(10007) = {Le, 10004};
+Line(10008) = {Te, 10001};
+
+// Internal field
+Line Loop(10001) = {10008, 10001, 10002, 10003, -10007, 1};
+Line Loop(10002) = {10007, 10004, 10005, 10006, -10008, 2};
+Plane Surface(10001) = {10001};
+Plane Surface(10002) = {10002};
+
+/*************************
+       Mesh Options
+ *************************/
+
+Mesh.Algorithm = 5; // Delaunay
+
+/*************************
+    Physical Groups
+ *************************/
+
+Physical Point("te") = {Te};
+Physical Line("upstream") = {10003, 10004};
+Physical Line("farfield") = {10002, 10005};
+Physical Line("downstream") = {10001};
+Physical Line("downstream") += {10006};
+Physical Line("airfoil") = {1};
+Physical Line("airfoil_") = {2};
+Physical Line("wake") = {10008};
+Physical Surface("field") = {10001};
+Physical Surface("field") += {10002};
diff --git a/blast/models/references/blXfoil_n0012.dat b/blast/models/references/blXfoil_n0012.dat
new file mode 100644
index 0000000..18a3758
--- /dev/null
+++ b/blast/models/references/blXfoil_n0012.dat
@@ -0,0 +1,227 @@
+      s        x        y  Ue/Vinf     Dstar     Theta        Cf       H   
+0.00000  1.00000  0.00126  0.88291  0.004080  0.002600  0.001120  1.5546
+0.00660  0.99347  0.00217  0.89361  0.003845  0.002487  0.001206  1.5312
+0.01530  0.98484  0.00337  0.90908  0.003539  0.002336  0.001333  1.4999
+0.02520  0.97503  0.00471  0.92373  0.003281  0.002202  0.001453  1.4746
+0.03619  0.96415  0.00618  0.93755  0.003062  0.002083  0.001566  1.4541
+0.04803  0.95241  0.00775  0.95021  0.002877  0.001979  0.001669  1.4379
+0.06049  0.94005  0.00937  0.96169  0.002722  0.001888  0.001762  1.4250
+0.07335  0.92730  0.01102  0.97205  0.002589  0.001808  0.001845  1.4149
+0.08644  0.91432  0.01268  0.98144  0.002474  0.001738  0.001921  1.4068
+0.09968  0.90118  0.01433  0.98999  0.002374  0.001674  0.001989  1.4003
+0.11299  0.88797  0.01596  0.99785  0.002284  0.001617  0.002052  1.3950
+0.12635  0.87471  0.01758  1.00513  0.002203  0.001564  0.002110  1.3907
+0.13974  0.86141  0.01918  1.01193  0.002129  0.001515  0.002164  1.3870
+0.15315  0.84810  0.02075  1.01833  0.002060  0.001469  0.002214  1.3840
+0.16656  0.83478  0.02230  1.02439  0.001996  0.001426  0.002263  1.3815
+0.17998  0.82145  0.02382  1.03017  0.001936  0.001385  0.002309  1.3794
+0.19340  0.80811  0.02533  1.03571  0.001879  0.001345  0.002353  1.3775
+0.20682  0.79477  0.02681  1.04105  0.001825  0.001308  0.002396  1.3760
+0.22025  0.78142  0.02827  1.04621  0.001773  0.001271  0.002438  1.3747
+0.23367  0.76808  0.02970  1.05123  0.001723  0.001236  0.002479  1.3737
+0.24710  0.75473  0.03111  1.05612  0.001674  0.001202  0.002519  1.3728
+0.26052  0.74137  0.03250  1.06091  0.001627  0.001169  0.002558  1.3720
+0.27394  0.72802  0.03386  1.06561  0.001582  0.001137  0.002597  1.3714
+0.28736  0.71467  0.03520  1.07024  0.001537  0.001105  0.002636  1.3709
+0.30077  0.70132  0.03651  1.07481  0.001494  0.001074  0.002675  1.3705
+0.31418  0.68797  0.03780  1.07932  0.001451  0.001043  0.002714  1.3703
+0.32759  0.67463  0.03906  1.08380  0.001410  0.001013  0.002753  1.3701
+0.34099  0.66128  0.04030  1.08825  0.001369  0.000984  0.002792  1.3700
+0.35439  0.64794  0.04151  1.09268  0.001328  0.000955  0.002831  1.3699
+0.36778  0.63460  0.04269  1.09710  0.001289  0.000926  0.002871  1.3700
+0.38116  0.62127  0.04385  1.10150  0.001250  0.000898  0.002912  1.3701
+0.39453  0.60795  0.04498  1.10591  0.001211  0.000870  0.002953  1.3702
+0.40790  0.59463  0.04607  1.11032  0.001173  0.000842  0.002995  1.3704
+0.42125  0.58131  0.04714  1.11474  0.001135  0.000815  0.003038  1.3707
+0.43460  0.56801  0.04817  1.11917  0.001098  0.000788  0.003081  1.3711
+0.44793  0.55471  0.04918  1.12362  0.001061  0.000761  0.003126  1.3714
+0.46125  0.54142  0.05015  1.12809  0.001024  0.000735  0.003172  1.3719
+0.47456  0.52815  0.05108  1.13259  0.000988  0.000708  0.003219  1.3724
+0.48786  0.51488  0.05198  1.13711  0.000952  0.000682  0.003267  1.3730
+0.50114  0.50163  0.05284  1.14166  0.000917  0.000656  0.003317  1.3736
+0.51441  0.48839  0.05366  1.14624  0.000881  0.000631  0.003368  1.3743
+0.52766  0.47516  0.05444  1.15085  0.000846  0.000605  0.003421  1.3751
+0.54089  0.46195  0.05518  1.15550  0.000812  0.000580  0.003476  1.3759
+0.55410  0.44875  0.05587  1.16019  0.000777  0.000555  0.003532  1.3768
+0.56730  0.43558  0.05652  1.16491  0.000743  0.000530  0.003591  1.3778
+0.58047  0.42242  0.05712  1.16967  0.000709  0.000505  0.003653  1.3789
+0.59362  0.40928  0.05767  1.17447  0.000675  0.000481  0.003716  1.3801
+0.60675  0.39616  0.05817  1.17931  0.000642  0.000456  0.003783  1.3814
+0.61985  0.38307  0.05862  1.18418  0.000608  0.000432  0.003853  1.3829
+0.63292  0.37000  0.05900  1.18910  0.000575  0.000408  0.003926  1.3845
+0.64597  0.35696  0.05933  1.19405  0.000542  0.000384  0.004004  1.3862
+0.65898  0.34395  0.05960  1.19904  0.000510  0.000360  0.004086  1.3881
+0.67196  0.33097  0.05981  1.20406  0.000477  0.000337  0.004173  1.3901
+0.68491  0.31802  0.05994  1.20912  0.000444  0.000313  0.004267  1.3923
+0.69782  0.30511  0.06001  1.21421  0.000412  0.000290  0.004370  1.3946
+0.71068  0.29225  0.06000  1.21931  0.000379  0.000267  0.004484  1.3967
+0.72351  0.27942  0.05992  1.22442  0.000347  0.000243  0.004615  1.3985
+0.73628  0.26665  0.05976  1.22951  0.000314  0.000220  0.004769  1.3995
+0.74901  0.25393  0.05951  1.23453  0.000280  0.000196  0.004959  1.3989
+0.76167  0.24127  0.05917  1.23934  0.000246  0.000173  0.005192  1.3970
+0.77428  0.22867  0.05874  1.24374  0.000212  0.000149  0.005437  1.3984
+0.78681  0.21615  0.05821  1.24755  0.000184  0.000125  0.005391  1.4362
+0.79926  0.20371  0.05758  1.25181  0.000175  0.000105  0.004066  1.6387
+0.81162  0.19137  0.05685  1.26457  0.000226  0.000092  0.001138  2.4214
+0.82388  0.17914  0.05600  1.27152  0.000243  0.000087  0.000483  2.7547
+0.83602  0.16704  0.05504  1.27594  0.000234  0.000084  0.000496  2.7636
+0.84802  0.15509  0.05396  1.28129  0.000224  0.000080  0.000527  2.7551
+0.85985  0.14333  0.05275  1.28693  0.000213  0.000076  0.000566  2.7424
+0.87146  0.13179  0.05141  1.29270  0.000202  0.000073  0.000611  2.7287
+0.88281  0.12053  0.04995  1.29852  0.000191  0.000069  0.000661  2.7149
+0.89385  0.10961  0.04837  1.30433  0.000180  0.000065  0.000716  2.7012
+0.90448  0.09912  0.04668  1.31009  0.000169  0.000062  0.000779  2.6877
+0.91462  0.08913  0.04489  1.31571  0.000158  0.000058  0.000849  2.6742
+0.92420  0.07974  0.04302  1.32110  0.000148  0.000055  0.000927  2.6606
+0.93311  0.07103  0.04110  1.32616  0.000138  0.000051  0.001015  2.6470
+0.94133  0.06305  0.03917  1.33078  0.000128  0.000048  0.001112  2.6331
+0.94881  0.05582  0.03724  1.33484  0.000119  0.000045  0.001220  2.6188
+0.95559  0.04932  0.03534  1.33822  0.000111  0.000042  0.001340  2.6042
+0.96169  0.04350  0.03348  1.34081  0.000103  0.000039  0.001472  2.5892
+0.96718  0.03832  0.03168  1.34250  0.000095  0.000036  0.001617  2.5736
+0.97212  0.03370  0.02992  1.34317  0.000088  0.000034  0.001777  2.5575
+0.97658  0.02958  0.02822  1.34266  0.000082  0.000032  0.001952  2.5409
+0.98062  0.02589  0.02657  1.34078  0.000076  0.000030  0.002145  2.5237
+0.98430  0.02258  0.02496  1.33729  0.000070  0.000028  0.002357  2.5059
+0.98767  0.01960  0.02338  1.33189  0.000065  0.000026  0.002590  2.4876
+0.99077  0.01692  0.02183  1.32420  0.000060  0.000024  0.002846  2.4687
+0.99363  0.01449  0.02031  1.31372  0.000055  0.000022  0.003125  2.4493
+0.99629  0.01230  0.01880  1.29986  0.000051  0.000021  0.003427  2.4294
+0.99878  0.01033  0.01730  1.28184  0.000047  0.000019  0.003752  2.4091
+1.00110  0.00854  0.01580  1.25873  0.000043  0.000018  0.004095  2.3884
+1.00329  0.00695  0.01431  1.22937  0.000039  0.000016  0.004447  2.3676
+1.00535  0.00552  0.01281  1.19244  0.000036  0.000015  0.004794  2.3469
+1.00731  0.00427  0.01131  1.14645  0.000033  0.000014  0.005112  2.3266
+1.00918  0.00318  0.00980  1.08992  0.000031  0.000013  0.005368  2.3072
+1.01096  0.00225  0.00827  1.02150  0.000028  0.000012  0.005526  2.2885
+1.01267  0.00148  0.00675  0.94041  0.000026  0.000012  0.005528  2.2724
+1.01431  0.00088  0.00522  0.84676  0.000025  0.000011  0.005356  2.2567
+1.01589  0.00044  0.00370  0.74209  0.000024  0.000011  0.004948  2.2474
+1.01743  0.00015  0.00219  0.62874  0.000023  0.000010  0.004391  2.2340
+1.01890  0.00002  0.00072  0.51132  0.000023  0.000010  0.003616  2.2345
+1.02035  0.00002 -0.00072  0.39270  0.000022  0.000010  0.002831  2.2244
+1.02183  0.00015 -0.00219  0.27038  0.000023  0.000010  0.001918  2.2328
+1.02336  0.00044 -0.00370  0.14527  0.000023  0.000010  0.001037  2.2249
+1.02494  0.00088 -0.00522  0.02220  0.000024  0.000011  0.000149  2.2295
+1.02658  0.00148 -0.00675 -0.09571  0.000024  0.000011  0.000639  2.2295
+1.02829  0.00225 -0.00827 -0.20585  0.000026  0.000012  0.001254  2.2468
+1.03007  0.00318 -0.00980 -0.30632  0.000027  0.000012  0.001792  2.2374
+1.03194  0.00427 -0.01131 -0.39687  0.000029  0.000013  0.002113  2.2603
+1.03390  0.00552 -0.01281 -0.47737  0.000031  0.000014  0.002385  2.2591
+1.03597  0.00695 -0.01431 -0.54877  0.000034  0.000015  0.002504  2.2754
+1.03815  0.00854 -0.01580 -0.61175  0.000036  0.000016  0.002584  2.2816
+1.04048  0.01033 -0.01730 -0.66745  0.000039  0.000017  0.002587  2.2936
+1.04296  0.01230 -0.01880 -0.71674  0.000042  0.000018  0.002558  2.3030
+1.04562  0.01449 -0.02031 -0.76053  0.000045  0.000020  0.002497  2.3134
+1.04848  0.01692 -0.02183 -0.79958  0.000049  0.000021  0.002418  2.3234
+1.05158  0.01960 -0.02338 -0.83455  0.000053  0.000022  0.002325  2.3334
+1.05495  0.02258 -0.02496 -0.86602  0.000056  0.000024  0.002223  2.3433
+1.05863  0.02589 -0.02657 -0.89444  0.000061  0.000026  0.002117  2.3531
+1.06268  0.02958 -0.02822 -0.92022  0.000065  0.000027  0.002009  2.3629
+1.06714  0.03370 -0.02992 -0.94366  0.000070  0.000029  0.001900  2.3725
+1.07208  0.03832 -0.03168 -0.96502  0.000075  0.000031  0.001792  2.3821
+1.07757  0.04350 -0.03348 -0.98448  0.000081  0.000034  0.001686  2.3917
+1.08367  0.04932 -0.03534 -1.00217  0.000087  0.000036  0.001582  2.4012
+1.09044  0.05582 -0.03724 -1.01818  0.000093  0.000038  0.001482  2.4107
+1.09793  0.06305 -0.03917 -1.03257  0.000100  0.000041  0.001387  2.4201
+1.10614  0.07103 -0.04110 -1.04537  0.000108  0.000044  0.001296  2.4295
+1.11506  0.07974 -0.04302 -1.05661  0.000115  0.000047  0.001212  2.4388
+1.12463  0.08913 -0.04489 -1.06635  0.000123  0.000050  0.001133  2.4479
+1.13477  0.09912 -0.04668 -1.07468  0.000131  0.000053  0.001061  2.4570
+1.14541  0.10961 -0.04837 -1.08171  0.000140  0.000056  0.000995  2.4660
+1.15644  0.12053 -0.04995 -1.08757  0.000148  0.000059  0.000935  2.4748
+1.16779  0.13179 -0.05141 -1.09238  0.000157  0.000062  0.000880  2.4836
+1.17941  0.14333 -0.05275 -1.09628  0.000165  0.000065  0.000830  2.4924
+1.19123  0.15509 -0.05396 -1.09939  0.000174  0.000069  0.000784  2.5012
+1.20323  0.16704 -0.05504 -1.10182  0.000182  0.000072  0.000742  2.5100
+1.21537  0.17914 -0.05600 -1.10364  0.000190  0.000075  0.000704  2.5189
+1.22763  0.19137 -0.05685 -1.10495  0.000199  0.000078  0.000668  2.5279
+1.23999  0.20371 -0.05758 -1.10580  0.000207  0.000081  0.000635  2.5370
+1.25245  0.21615 -0.05821 -1.10626  0.000216  0.000084  0.000604  2.5461
+1.26498  0.22867 -0.05874 -1.10637  0.000224  0.000087  0.000575  2.5555
+1.27758  0.24127 -0.05917 -1.10617  0.000233  0.000090  0.000548  2.5649
+1.29025  0.25393 -0.05951 -1.10571  0.000242  0.000093  0.000523  2.5745
+1.30297  0.26665 -0.05976 -1.10500  0.000250  0.000096  0.000499  2.5843
+1.31575  0.27942 -0.05992 -1.10409  0.000259  0.000099  0.000477  2.5942
+1.32857  0.29225 -0.06000 -1.10300  0.000268  0.000102  0.000455  2.6042
+1.34144  0.30511 -0.06001 -1.10175  0.000276  0.000105  0.000435  2.6143
+1.35434  0.31802 -0.05994 -1.10035  0.000285  0.000107  0.000417  2.6245
+1.36729  0.33097 -0.05981 -1.09885  0.000294  0.000110  0.000399  2.6346
+1.38027  0.34395 -0.05960 -1.09725  0.000302  0.000113  0.000382  2.6444
+1.39329  0.35696 -0.05933 -1.09561  0.000311  0.000116  0.000367  2.6535
+1.40633  0.37000 -0.05900 -1.09400  0.000319  0.000119  0.000354  2.6604
+1.41941  0.38307 -0.05862 -1.09264  0.000326  0.000121  0.000346  2.6604
+1.43251  0.39616 -0.05817 -1.09240  0.000329  0.000123  0.000354  2.6359
+1.44563  0.40928 -0.05767 -1.08553  0.000277  0.000132  0.001426  2.0764
+1.45878  0.42242 -0.05712 -1.07997  0.000236  0.000147  0.003157  1.5838
+1.47196  0.43558 -0.05652 -1.07882  0.000243  0.000167  0.003937  1.4295
+1.48515  0.44875 -0.05587 -1.07791  0.000269  0.000190  0.004033  1.3925
+1.49836  0.46195 -0.05518 -1.07654  0.000301  0.000214  0.003929  1.3871
+1.51160  0.47516 -0.05444 -1.07479  0.000335  0.000238  0.003800  1.3882
+1.52484  0.48839 -0.05366 -1.07282  0.000368  0.000261  0.003684  1.3896
+1.53811  0.50163 -0.05284 -1.07071  0.000400  0.000283  0.003585  1.3901
+1.55139  0.51488 -0.05198 -1.06850  0.000432  0.000306  0.003501  1.3898
+1.56469  0.52815 -0.05108 -1.06623  0.000463  0.000329  0.003429  1.3888
+1.57800  0.54142 -0.05015 -1.06390  0.000494  0.000351  0.003364  1.3875
+1.59132  0.55471 -0.04918 -1.06153  0.000525  0.000374  0.003306  1.3859
+1.60466  0.56801 -0.04817 -1.05913  0.000556  0.000396  0.003253  1.3843
+1.61800  0.58131 -0.04714 -1.05669  0.000587  0.000418  0.003203  1.3827
+1.63136  0.59463 -0.04607 -1.05422  0.000618  0.000441  0.003156  1.3811
+1.64472  0.60795 -0.04498 -1.05172  0.000649  0.000463  0.003112  1.3796
+1.65810  0.62127 -0.04385 -1.04918  0.000679  0.000486  0.003069  1.3782
+1.67148  0.63460 -0.04269 -1.04662  0.000711  0.000509  0.003028  1.3769
+1.68487  0.64794 -0.04151 -1.04401  0.000742  0.000532  0.002988  1.3757
+1.69826  0.66128 -0.04030 -1.04137  0.000773  0.000555  0.002950  1.3746
+1.71166  0.67463 -0.03906 -1.03869  0.000805  0.000578  0.002912  1.3736
+1.72507  0.68797 -0.03780 -1.03595  0.000836  0.000601  0.002875  1.3727
+1.73848  0.70132 -0.03651 -1.03316  0.000868  0.000624  0.002838  1.3720
+1.75190  0.71467 -0.03520 -1.03032  0.000901  0.000648  0.002803  1.3713
+1.76531  0.72802 -0.03386 -1.02740  0.000934  0.000672  0.002767  1.3707
+1.77873  0.74137 -0.03250 -1.02440  0.000967  0.000696  0.002732  1.3703
+1.79216  0.75473 -0.03111 -1.02131  0.001001  0.000721  0.002696  1.3699
+1.80558  0.76808 -0.02970 -1.01812  0.001035  0.000746  0.002661  1.3697
+1.81901  0.78142 -0.02827 -1.01481  0.001070  0.000771  0.002625  1.3696
+1.83243  0.79477 -0.02681 -1.01136  0.001106  0.000797  0.002588  1.3697
+1.84585  0.80811 -0.02533 -1.00776  0.001143  0.000824  0.002551  1.3699
+1.85928  0.82145 -0.02382 -1.00397  0.001182  0.000851  0.002513  1.3703
+1.87270  0.83478 -0.02230 -0.99997  0.001221  0.000879  0.002474  1.3710
+1.88611  0.84810 -0.02075 -0.99572  0.001263  0.000909  0.002433  1.3719
+1.89951  0.86141 -0.01918 -0.99117  0.001306  0.000939  0.002389  1.3731
+1.91290  0.87471 -0.01758 -0.98628  0.001352  0.000971  0.002344  1.3747
+1.92626  0.88797 -0.01596 -0.98096  0.001401  0.001005  0.002295  1.3768
+1.93958  0.90118 -0.01433 -0.97515  0.001454  0.001041  0.002242  1.3795
+1.95281  0.91432 -0.01268 -0.96871  0.001512  0.001080  0.002184  1.3829
+1.96590  0.92730 -0.01102 -0.96154  0.001577  0.001123  0.002120  1.3874
+1.97876  0.94005 -0.00937 -0.95345  0.001649  0.001170  0.002048  1.3932
+1.99122  0.95241 -0.00775 -0.94427  0.001733  0.001223  0.001966  1.4009
+2.00307  0.96415 -0.00618 -0.93381  0.001831  0.001283  0.001873  1.4110
+2.01405  0.97503 -0.00471 -0.92188  0.001948  0.001353  0.001767  1.4246
+2.02395  0.98484 -0.00337 -0.90849  0.002087  0.001432  0.001648  1.4423
+2.03266  0.99347 -0.00217 -0.89279  0.002266  0.001529  0.001506  1.4673
+2.03925  1.00000 -0.00126 -0.88291  0.002389  0.001595  0.001422  1.4829
+2.03925  1.00010  0.00000  0.88291  0.008964  0.004196  0.000000  2.1200
+2.04585  1.00670  0.00000  0.85376  0.008010  0.004779  0.000000  1.6621
+2.05330  1.01414  0.00002  0.87032  0.007119  0.004459  0.000000  1.5823
+2.06170  1.02254  0.00005  0.88511  0.006449  0.004201  0.000000  1.5207
+2.07117  1.03202  0.00009  0.89809  0.005935  0.003994  0.000000  1.4713
+2.08187  1.04271  0.00015  0.90943  0.005527  0.003825  0.000000  1.4299
+2.09393  1.05478  0.00022  0.91936  0.005195  0.003687  0.000000  1.3940
+2.10754  1.06839  0.00032  0.92808  0.004919  0.003572  0.000000  1.3619
+2.12290  1.08375  0.00044  0.93579  0.004684  0.003475  0.000000  1.3326
+2.14023  1.10108  0.00058  0.94265  0.004481  0.003392  0.000000  1.3053
+2.15979  1.12063  0.00077  0.94879  0.004301  0.003321  0.000000  1.2797
+2.18185  1.14269  0.00100  0.95433  0.004142  0.003259  0.000000  1.2553
+2.20674  1.16758  0.00127  0.95934  0.003999  0.003204  0.000000  1.2322
+2.23483  1.19567  0.00161  0.96388  0.003870  0.003156  0.000000  1.2103
+2.26652  1.22735  0.00202  0.96800  0.003753  0.003114  0.000000  1.1895
+2.30227  1.26311  0.00251  0.97174  0.003648  0.003076  0.000000  1.1699
+2.34262  1.30344  0.00311  0.97514  0.003552  0.003042  0.000000  1.1517
+2.38813  1.34896  0.00382  0.97821  0.003467  0.003013  0.000000  1.1348
+2.43949  1.40031  0.00467  0.98099  0.003391  0.002986  0.000000  1.1194
+2.49744  1.45825  0.00568  0.98348  0.003323  0.002963  0.000000  1.1054
+2.56282  1.52362  0.00688  0.98572  0.003263  0.002942  0.000000  1.0929
+2.63659  1.59737  0.00831  0.98772  0.003210  0.002924  0.000000  1.0818
+2.71982  1.68059  0.00999  0.98948  0.003165  0.002908  0.000000  1.0721
+2.81374  1.77448  0.01198  0.99104  0.003125  0.002894  0.000000  1.0636
+2.91970  1.88042  0.01431  0.99239  0.003091  0.002882  0.000000  1.0563
+3.03925  1.99994  0.01706  0.99368  0.003061  0.002871  0.000000  1.04
diff --git a/blast/models/references/cpRef_rae2822.dat b/blast/models/references/cpRef_rae2822.dat
new file mode 100644
index 0000000..f050931
--- /dev/null
+++ b/blast/models/references/cpRef_rae2822.dat
@@ -0,0 +1,103 @@
+0.9938 -0.1432
+0.9875 -0.1318                                                            
+0.9750 -0.1082                                                            
+0.9500 -0.0592                                                            
+0.9250 -0.0115                                                            
+0.9000  0.0365                                                            
+0.8750  0.0808                                                            
+0.8500  0.1296                                                            
+0.8250  0.1746                                                            
+0.8000  0.2186
+0.7750  0.2702                                                            
+0.7500  0.3029                                                            
+0.7000  0.3913                                                            
+0.6771  0.4263                                                            
+0.6500  0.4778                                                            
+0.6196  0.5361                                                            
+0.6000  0.5798                                                            
+0.5750  0.6769                                                            
+0.5500  0.9137                                                            
+0.5250  1.2201                                                            
+0.5000  1.2164                                                            
+0.4750  1.2038                                                            
+0.4500  1.1940                                                            
+0.4250  1.1819                                                            
+0.4000  1.1601                                                            
+0.3750  1.1490                                                            
+0.3500  1.1273                                                            
+0.3250  1.1168                                                            
+0.3000  1.1091                                                            
+0.2800  1.1010                                                            
+0.2500  1.0736                                                            
+0.2208  1.0456                                                            
+0.2000  1.0433                                                            
+0.1500  1.0096                                                            
+0.1000  0.9886                                                            
+0.0750  1.0432                                                            
+0.0625  1.0730                                                            
+0.0500  1.0923                                                            
+0.0375  1.0820                                                            
+0.0271  1.0435                                                            
+0.0187  1.0362                                                            
+0.0146  0.9204                                                            
+0.0125  0.8680                                                            
+0.0104  0.8041                                                            
+0.0087  0.7658                                                            
+0.0073  0.7250                                                            
+0.0060  0.6269                                                            
+0.0048  0.5309                                                            
+0.0036  0.4667                                                            
+0.0026  0.2746                                                            
+0.0016  0.0521                                                            
+0.0008 -0.1042                                                            
+0.0002  0.4219                                                            
+0.0000 -0.8328                                                            
+0.0002 -1.0396                                                            
+0.0008 -1.1053                                                            
+0.0016 -1.1338                                                            
+0.0026 -1.1223                                                            
+0.0036 -1.0823                                                            
+0.0048 -1.0273                                                            
+0.0060 -0.9736                                                            
+0.0073 -0.9148                                                            
+0.0087 -0.8703                                                            
+0.0104 -0.8148                                                            
+0.0125 -0.7537                                                            
+0.0146 -0.6980                                                            
+0.0186 -0.6159                                                            
+0.0271 -0.5444                                                            
+0.0375 -0.4040                                                            
+0.0500 -0.3088                                                            
+0.0625 -0.2454                                                            
+0.0750 -0.1926                                                            
+0.1000 -0.1042                                                            
+0.1500  0.0081                                                            
+0.2000  0.0940                                                            
+0.2500  0.1754                                                            
+0.3000  0.2506                                                            
+0.3250  0.2904                                                            
+0.3500  0.3214                                                            
+0.3750  0.3406                                                            
+0.4000  0.3403                                                            
+0.4250  0.3194                                                            
+0.4500  0.2881                                                            
+0.4750  0.2469                                                            
+0.5000  0.2079                                                            
+0.5250  0.1657                                                            
+0.5500  0.1169                                                            
+0.5750  0.0731                                                            
+0.6000  0.0301                                                            
+0.6196  0.0003                                                            
+0.6500 -0.0431                                                            
+0.6771 -0.0836                                                            
+0.7000 -0.1134                                                            
+0.7500 -0.1731                                                            
+0.7750 -0.1988                                                            
+0.8500 -0.2618                                                            
+0.8750 -0.2778                                                            
+0.9000 -0.2907                                                            
+0.9250 -0.2972                                                            
+0.9500 -0.2937                                                            
+0.9750 -0.2676                                                            
+0.9875 -0.2396                                                            
+0.9938 -0.2146
diff --git a/blast/models/references/cpXfoilInv_n0012.dat b/blast/models/references/cpXfoilInv_n0012.dat
new file mode 100644
index 0000000..eb2343c
--- /dev/null
+++ b/blast/models/references/cpXfoilInv_n0012.dat
@@ -0,0 +1,201 @@
+#      x          Cp  
+     1.00000    0.42192
+     0.99347    0.25411
+     0.98484    0.20417
+     0.97503    0.16255
+     0.96415    0.12998
+     0.95241    0.10224
+     0.94005    0.07811
+     0.92730    0.05675
+     0.91432    0.03758
+     0.90118    0.02016
+     0.88797    0.00415
+     0.87471   -0.01073
+     0.86141   -0.02467
+     0.84810   -0.03785
+     0.83478   -0.05038
+     0.82145   -0.06239
+     0.80811   -0.07394
+     0.79477   -0.08513
+     0.78142   -0.09600
+     0.76808   -0.10661
+     0.75473   -0.11700
+     0.74137   -0.12722
+     0.72802   -0.13730
+     0.71467   -0.14726
+     0.70132   -0.15713
+     0.68797   -0.16694
+     0.67463   -0.17671
+     0.66128   -0.18645
+     0.64794   -0.19619
+     0.63460   -0.20594
+     0.62127   -0.21571
+     0.60795   -0.22553
+     0.59463   -0.23539
+     0.58131   -0.24531
+     0.56801   -0.25531
+     0.55471   -0.26538
+     0.54142   -0.27554
+     0.52815   -0.28581
+     0.51488   -0.29617
+     0.50163   -0.30665
+     0.48839   -0.31724
+     0.47516   -0.32796
+     0.46195   -0.33880
+     0.44875   -0.34978
+     0.43558   -0.36090
+     0.42242   -0.37215
+     0.40928   -0.38355
+     0.39616   -0.39510
+     0.38307   -0.40680
+     0.37000   -0.41865
+     0.35696   -0.43066
+     0.34395   -0.44283
+     0.33097   -0.45516
+     0.31802   -0.46765
+     0.30511   -0.48031
+     0.29225   -0.49314
+     0.27942   -0.50614
+     0.26665   -0.51931
+     0.25393   -0.53266
+     0.24127   -0.54619
+     0.22867   -0.55991
+     0.21615   -0.57382
+     0.20371   -0.58792
+     0.19137   -0.60222
+     0.17914   -0.61673
+     0.16704   -0.63145
+     0.15509   -0.64637
+     0.14333   -0.66148
+     0.13179   -0.67676
+     0.12053   -0.69217
+     0.10961   -0.70762
+     0.09912   -0.72299
+     0.08913   -0.73812
+     0.07974   -0.75276
+     0.07103   -0.76664
+     0.06305   -0.77946
+     0.05582   -0.79090
+     0.04932   -0.80065
+     0.04350   -0.80839
+     0.03832   -0.81382
+     0.03370   -0.81659
+     0.02958   -0.81628
+     0.02589   -0.81240
+     0.02258   -0.80432
+     0.01960   -0.79129
+     0.01692   -0.77234
+     0.01449   -0.74630
+     0.01230   -0.71173
+     0.01033   -0.66694
+     0.00854   -0.60994
+     0.00695   -0.53854
+     0.00552   -0.45052
+     0.00427   -0.34398
+     0.00318   -0.21795
+     0.00225   -0.07301
+     0.00148    0.08774
+     0.00088    0.25799
+     0.00044    0.42849
+     0.00015    0.58893
+     0.00002    0.72842
+     0.00002    0.84111
+     0.00015    0.92755
+     0.00044    0.98434
+     0.00088    1.00891
+     0.00148    1.00319
+     0.00225    0.97198
+     0.00318    0.92162
+     0.00427    0.85852
+     0.00552    0.78815
+     0.00695    0.71473
+     0.00854    0.64122
+     0.01033    0.56952
+     0.01230    0.50078
+     0.01449    0.43557
+     0.01692    0.37413
+     0.01960    0.31648
+     0.02258    0.26250
+     0.02589    0.21200
+     0.02958    0.16479
+     0.03370    0.12069
+     0.03832    0.07953
+     0.04350    0.04122
+     0.04932    0.00571
+     0.05582   -0.02699
+     0.06305   -0.05683
+     0.07103   -0.08373
+     0.07974   -0.10766
+     0.08913   -0.12862
+     0.09912   -0.14672
+     0.10961   -0.16211
+     0.12053   -0.17504
+     0.13179   -0.18574
+     0.14333   -0.19448
+     0.15509   -0.20150
+     0.16704   -0.20700
+     0.17914   -0.21119
+     0.19137   -0.21422
+     0.20371   -0.21624
+     0.21615   -0.21739
+     0.22867   -0.21775
+     0.24127   -0.21743
+     0.25393   -0.21651
+     0.26665   -0.21505
+     0.27942   -0.21312
+     0.29225   -0.21077
+     0.30511   -0.20806
+     0.31802   -0.20501
+     0.33097   -0.20167
+     0.34395   -0.19808
+     0.35696   -0.19426
+     0.37000   -0.19024
+     0.38307   -0.18604
+     0.39616   -0.18168
+     0.40928   -0.17718
+     0.42242   -0.17257
+     0.43558   -0.16784
+     0.44875   -0.16302
+     0.46195   -0.15812
+     0.47516   -0.15315
+     0.48839   -0.14811
+     0.50163   -0.14301
+     0.51488   -0.13787
+     0.52815   -0.13267
+     0.54142   -0.12743
+     0.55471   -0.12214
+     0.56801   -0.11682
+     0.58131   -0.11145
+     0.59463   -0.10604
+     0.60795   -0.10058
+     0.62127   -0.09507
+     0.63460   -0.08951
+     0.64794   -0.08388
+     0.66128   -0.07819
+     0.67463   -0.07241
+     0.68797   -0.06655
+     0.70132   -0.06058
+     0.71467   -0.05449
+     0.72802   -0.04827
+     0.74137   -0.04189
+     0.75473   -0.03533
+     0.76808   -0.02856
+     0.78142   -0.02155
+     0.79477   -0.01426
+     0.80811   -0.00665
+     0.82145    0.00133
+     0.83478    0.00976
+     0.84810    0.01870
+     0.86141    0.02825
+     0.87471    0.03854
+     0.88797    0.04971
+     0.90118    0.06196
+     0.91432    0.07554
+     0.92730    0.09077
+     0.94005    0.10809
+     0.95241    0.12807
+     0.96415    0.15158
+     0.97503    0.17983
+     0.98484    0.21687
+     0.99347    0.26257
+     1.00000    0.42192
diff --git a/blast/models/references/cpXfoil_n0012.dat b/blast/models/references/cpXfoil_n0012.dat
new file mode 100644
index 0000000..dd7d700
--- /dev/null
+++ b/blast/models/references/cpXfoil_n0012.dat
@@ -0,0 +1,201 @@
+#      x          Cp  
+     1.00000    0.22096
+     0.99347    0.20186
+     0.98484    0.17388
+     0.97503    0.14693
+     0.96415    0.12115
+     0.95241    0.09719
+     0.94005    0.07520
+     0.92730    0.05514
+     0.91432    0.03679
+     0.90118    0.01992
+     0.88797    0.00430
+     0.87471   -0.01028
+     0.86141   -0.02400
+     0.84810   -0.03698
+     0.83478   -0.04936
+     0.82145   -0.06122
+     0.80811   -0.07265
+     0.79477   -0.08372
+     0.78142   -0.09448
+     0.76808   -0.10498
+     0.75473   -0.11527
+     0.74137   -0.12538
+     0.72802   -0.13535
+     0.71467   -0.14520
+     0.70132   -0.15497
+     0.68797   -0.16467
+     0.67463   -0.17433
+     0.66128   -0.18396
+     0.64794   -0.19358
+     0.63460   -0.20321
+     0.62127   -0.21286
+     0.60795   -0.22254
+     0.59463   -0.23227
+     0.58131   -0.24206
+     0.56801   -0.25191
+     0.55471   -0.26184
+     0.54142   -0.27186
+     0.52815   -0.28196
+     0.51488   -0.29216
+     0.50163   -0.30247
+     0.48839   -0.31289
+     0.47516   -0.32342
+     0.46195   -0.33407
+     0.44875   -0.34485
+     0.43558   -0.35575
+     0.42242   -0.36678
+     0.40928   -0.37795
+     0.39616   -0.38925
+     0.38307   -0.40068
+     0.37000   -0.41225
+     0.35696   -0.42396
+     0.34395   -0.43580
+     0.33097   -0.44777
+     0.31802   -0.45986
+     0.30511   -0.47207
+     0.29225   -0.48438
+     0.27942   -0.49675
+     0.26665   -0.50911
+     0.25393   -0.52134
+     0.24127   -0.53313
+     0.22867   -0.54394
+     0.21615   -0.55332
+     0.20371   -0.56384
+     0.19137   -0.59560
+     0.17914   -0.61300
+     0.16704   -0.62412
+     0.15509   -0.63763
+     0.14333   -0.65194
+     0.13179   -0.66662
+     0.12053   -0.68150
+     0.10961   -0.69643
+     0.09912   -0.71127
+     0.08913   -0.72582
+     0.07974   -0.73984
+     0.07103   -0.75304
+     0.06305   -0.76512
+     0.05582   -0.77577
+     0.04932   -0.78467
+     0.04350   -0.79151
+     0.03832   -0.79598
+     0.03370   -0.79775
+     0.02958   -0.79639
+     0.02589   -0.79142
+     0.02258   -0.78222
+     0.01960   -0.76803
+     0.01692   -0.74790
+     0.01449   -0.72067
+     0.01230   -0.68494
+     0.01033   -0.63904
+     0.00854   -0.58103
+     0.00695   -0.50877
+     0.00552   -0.42015
+     0.00427   -0.31337
+     0.00318   -0.18757
+     0.00225   -0.04345
+     0.00148    0.11577
+     0.00088    0.28381
+     0.00044    0.45133
+     0.00015    0.60839
+     0.00002    0.74408
+     0.00002    0.85306
+     0.00015    0.93565
+     0.00044    0.98867
+     0.00088    1.00970
+     0.00148    1.00086
+     0.00225    0.96697
+     0.00318    0.91453
+     0.00427    0.84972
+     0.00552    0.77817
+     0.00695    0.70381
+     0.00854    0.62973
+     0.01033    0.55762
+     0.01230    0.48868
+     0.01449    0.42339
+     0.01692    0.36199
+     0.01960    0.30445
+     0.02258    0.25064
+     0.02589    0.20038
+     0.02958    0.15343
+     0.03370    0.10962
+     0.03832    0.06879
+     0.04350    0.03082
+     0.04932   -0.00434
+     0.05582   -0.03669
+     0.06305   -0.06616
+     0.07103   -0.09271
+     0.07974   -0.11629
+     0.08913   -0.13692
+     0.09912   -0.15471
+     0.10961   -0.16981
+     0.12053   -0.18247
+     0.13179   -0.19292
+     0.14333   -0.20143
+     0.15509   -0.20823
+     0.16704   -0.21355
+     0.17914   -0.21755
+     0.19137   -0.22042
+     0.20371   -0.22230
+     0.21615   -0.22331
+     0.22867   -0.22355
+     0.24127   -0.22312
+     0.25393   -0.22209
+     0.26665   -0.22055
+     0.27942   -0.21855
+     0.29225   -0.21614
+     0.30511   -0.21339
+     0.31802   -0.21034
+     0.33097   -0.20704
+     0.34395   -0.20355
+     0.35696   -0.19996
+     0.37000   -0.19644
+     0.38307   -0.19349
+     0.39616   -0.19296
+     0.40928   -0.17806
+     0.42242   -0.16606
+     0.43558   -0.16359
+     0.44875   -0.16163
+     0.46195   -0.15868
+     0.47516   -0.15494
+     0.48839   -0.15072
+     0.50163   -0.14621
+     0.51488   -0.14150
+     0.52815   -0.13666
+     0.54142   -0.13171
+     0.55471   -0.12669
+     0.56801   -0.12160
+     0.58131   -0.11645
+     0.59463   -0.11125
+     0.60795   -0.10599
+     0.62127   -0.10068
+     0.63460   -0.09531
+     0.64794   -0.08988
+     0.66128   -0.08438
+     0.67463   -0.07881
+     0.68797   -0.07314
+     0.70132   -0.06738
+     0.71467   -0.06151
+     0.72802   -0.05551
+     0.74137   -0.04937
+     0.75473   -0.04306
+     0.76808   -0.03655
+     0.78142   -0.02983
+     0.79477   -0.02285
+     0.80811   -0.01557
+     0.82145   -0.00795
+     0.83478    0.00007
+     0.84810    0.00855
+     0.86141    0.01758
+     0.87471    0.02727
+     0.88797    0.03772
+     0.90118    0.04912
+     0.91432    0.06163
+     0.92730    0.07550
+     0.94005    0.09102
+     0.95241    0.10847
+     0.96415    0.12816
+     0.97503    0.15037
+     0.98484    0.17496
+     0.99347    0.20334
+     1.00000    0.22096
diff --git a/blast/src/CMakeLists.txt b/blast/src/CMakeLists.txt
new file mode 100644
index 0000000..394f4cb
--- /dev/null
+++ b/blast/src/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2020 University of Liège
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# CMake input file of blast.so
+
+FILE(GLOB SRCS *.h *.cpp *.inl *.hpp)
+
+ADD_LIBRARY(blast SHARED ${SRCS})
+MACRO_DebugPostfix(blast)
+TARGET_INCLUDE_DIRECTORIES(blast PUBLIC ${PROJECT_SOURCE_DIR}/blast/src)
+
+TARGET_LINK_LIBRARIES(blast tbox)
+
+INSTALL(TARGETS blast DESTINATION ${CMAKE_INSTALL_PREFIX})
+
+SOURCE_GROUP(base REGULAR_EXPRESSION ".*\\.(cpp|inl|hpp|h)")
diff --git a/blast/src/DBoundaryLayer.cpp b/blast/src/DBoundaryLayer.cpp
new file mode 100644
index 0000000..9886a79
--- /dev/null
+++ b/blast/src/DBoundaryLayer.cpp
@@ -0,0 +1,196 @@
+#include "DBoundaryLayer.h"
+#include "DClosures.h"
+#include "DDiscretization.h"
+#include "DEdge.h"
+#include <cmath>
+#include <iomanip>
+
+using namespace blast;
+
+/**
+ * @brief Construct a new BoundaryLayer::BoundaryLayer object.
+ *
+ * @param _xtrF Forced transition location (default=-1; free transition).
+ */
+BoundaryLayer::BoundaryLayer(double _xtrF)
+{
+    xtrF = _xtrF;
+    xtr = 1.0;
+    xoctr = 1.0;
+    closSolver = new Closures();
+    mesh = new Discretization();
+    blEdge = new Edge();
+    transMarker = 0;
+}
+
+BoundaryLayer::~BoundaryLayer()
+{
+    delete closSolver;
+    delete mesh;
+    delete blEdge;
+    // std::cout << "~blast::BoundaryLayer()\n";
+}
+
+/**
+ * @brief Set the mesh and resize all boundary layer quantities accordingly.
+ *
+ * @param x Position x in the global frame of reference.
+ * @param y Position y in the global frame of reference.
+ * @param z Position z in the global frame of reference.
+ */
+void BoundaryLayer::setMesh(std::vector<double> x,
+                            std::vector<double> y,
+                            std::vector<double> z)
+{
+    size_t nMarkers = x.size();
+    mesh->setGlob(x, y, z);
+
+    // Resize and reset all variables if needed.
+    if (mesh->getDiffnElm() != 0)
+    {
+        u.resize(nVar * nMarkers, 0.);
+        Ret.resize(nMarkers, 0.);
+        cd.resize(nMarkers, 0.);
+        cf.resize(nMarkers, 0.);
+        Hstar.resize(nMarkers, 0.);
+        Hstar2.resize(nMarkers, 0.);
+        Hk.resize(nMarkers, 0.);
+        ctEq.resize(nMarkers, 0.);
+        us.resize(nMarkers, 0.);
+        deltaStar.resize(nMarkers, 0.);
+        delta.resize(nMarkers, 0.);
+        regime.resize(nMarkers, 0);
+    }
+}
+
+/**
+ * @brief Set the boundary condition at the stagnation point.
+ *
+ * @param Re Freestream Reynolds number.
+ */
+void BoundaryLayer::setStagBC(double Re)
+{
+    double dv0 = (blEdge->getVt(1) - blEdge->getVt(0)) / (mesh->getLoc(1) - mesh->getLoc(0));
+    if (dv0 > 0)
+        u[0] = sqrt(0.075 / (Re * dv0)); // Theta
+    else
+        u[0] = 1e-6;         // Theta
+    u[1] = 2.23;             // H
+    u[2] = 0.;               // N
+    u[3] = blEdge->getVt(0); // ue
+    u[4] = 0.;               // Ctau
+
+    Ret[0] = u[3] * u[0] * Re;
+    if (Ret[0] < 1)
+    {
+        Ret[0] = 1.;
+        u[3] = Ret[0] / (u[0] * Re);
+    }
+    deltaStar[0] = u[0] * u[1];
+
+    std::vector<double> lamParam(8, 0.);
+    closSolver->laminarClosures(lamParam, u[0], u[1], Ret[0], blEdge->getMe(0), name);
+
+    Hstar[0] = lamParam[0];
+    Hstar2[0] = lamParam[1];
+    Hk[0] = lamParam[2];
+    cf[0] = lamParam[3];
+    cd[0] = lamParam[4];
+    delta[0] = lamParam[5];
+    ctEq[0] = lamParam[6];
+    us[0] = lamParam[7];
+}
+
+/**
+ * @brief Set the boundary condition at the first wake point.
+ *
+ * @param Re Freestream Reynolds number
+ * @param UpperCond Variables at the last point on the upper side.
+ * @param LowerCond Variables at the last point on the lower side.
+ */
+void BoundaryLayer::setWakeBC(double Re, std::vector<double> UpperCond, std::vector<double> LowerCond)
+{
+    if (name != "wake")
+        std::cout << "Warning: Imposing wake boundary condition on " << name << std::endl;
+    u[0] = UpperCond[0] + LowerCond[0];                                        // (Theta_up+Theta_low).
+    u[1] = (UpperCond[0] * UpperCond[1] + LowerCond[0] * LowerCond[1]) / u[0]; // ((Theta*H)_up+(Theta*H)_low)/(Theta_up+Theta_low).
+    u[2] = 9.;                                                                 // Amplification factor.
+    u[3] = blEdge->getVt(0);                                                   // Inviscid velocity.
+    u[4] = (UpperCond[0] * UpperCond[4] + LowerCond[0] * LowerCond[4]) / u[0]; // ((Ct*Theta)_up+(Theta)_low)/(Ct*Theta_up+Theta_low).
+
+    Ret[0] = u[3] * u[0] * Re; // Reynolds number based on the momentum thickness.
+
+    if (Ret[0] < 1)
+    {
+        Ret[0] = 1;
+        u[3] = Ret[0] / (u[0] * Re);
+    }
+    deltaStar[0] = u[0] * u[1];
+
+    // Laminar closures.
+    std::vector<double> lamParam(8, 0.);
+    closSolver->laminarClosures(lamParam, u[0], u[1], Ret[0], blEdge->getMe(0), name);
+    Hstar[0] = lamParam[0];
+    Hstar2[0] = lamParam[1];
+    Hk[0] = lamParam[2];
+    cf[0] = lamParam[3];
+    cd[0] = lamParam[4];
+    delta[0] = lamParam[5];
+    ctEq[0] = lamParam[6];
+    us[0] = lamParam[7];
+
+    for (size_t k = 0; k < mesh->getnMarkers(); ++k)
+        regime[k] = 1;
+}
+
+/**
+ * @brief Compute the blowing velocity in each station of the region.
+ *
+ */
+void BoundaryLayer::computeBlwVel()
+{
+    deltaStar[0] = u[0] * u[1];
+    blowingVelocity.resize(mesh->getnMarkers() - 1, 0.);
+    for (size_t iPoint = 1; iPoint < mesh->getnMarkers(); ++iPoint)
+    {
+        deltaStar[iPoint] = u[iPoint * nVar + 0] * u[iPoint * nVar + 1];
+        blowingVelocity[iPoint - 1] = (blEdge->getRhoe(iPoint) * blEdge->getVt(iPoint) * deltaStar[iPoint] - blEdge->getRhoe(iPoint - 1) * blEdge->getVt(iPoint - 1) * deltaStar[iPoint - 1]) / (blEdge->getRhoe(iPoint) * (mesh->getLoc(iPoint) - mesh->getLoc(iPoint - 1)));
+        if (std::isnan(blowingVelocity[iPoint - 1]))
+        {
+            if (blEdge->getRhoe(iPoint) == 0.)
+                std::cout << "Density is zero at point " << iPoint << std::endl;
+            if ((mesh->getLoc(iPoint) - mesh->getLoc(iPoint - 1)) == 0.)
+                std::cout << "Points " << iPoint -1 << " and " << iPoint << " are at the same position " << mesh->getLoc(iPoint) << ", resulting in an infinite gradient." << std::endl;
+            if (std::isnan(deltaStar[iPoint]))
+                std::cout << "Displacement thickness is nan at position " << iPoint << std::endl;
+            if (std::isnan(deltaStar[iPoint - 1]))
+                std::cout << "Displacement thickness is nan at position " << iPoint - 1 << std::endl;
+            throw std::runtime_error("NaN detected in blowing velocity computation (see reason above)");
+        }
+    }
+}
+
+/**
+ * @brief Print solution at a given station.
+ *
+ * @param iPoint Marker id.
+ * @note Function used for debugging.
+ */
+void BoundaryLayer::printSolution(size_t iPoint) const
+{
+    if (iPoint < 0 || iPoint > mesh->getnMarkers()-1)
+        throw std::runtime_error("Tried to access element outside of region size.");
+    std::cout << "Pt " << iPoint << "Reg " << regime[iPoint] << std::endl;
+    std::cout << "x " << mesh->getx(iPoint) << "xx " << mesh->getLoc(iPoint) << "xxExt " << mesh->getExt(iPoint) << std::endl;
+    std::cout << std::scientific << std::setprecision(15);
+    std::cout << std::setw(10) << "T " << u[iPoint * nVar + 0] << "\n"
+              << std::setw(10) << "H " << u[iPoint * nVar + 1] << "\n"
+              << std::setw(10) << "N " << u[iPoint * nVar + 2] << "\n"
+              << std::setw(10) << "ue " << u[iPoint * nVar + 3] << "\n"
+              << std::setw(10) << "Ct " << u[iPoint * nVar + 4] << "\n"
+              << std::setw(10) << "dStar (BL) " << deltaStar[iPoint] << "\n"
+              << std::setw(10) << "dStar (old) " << blEdge->getDeltaStar(iPoint) << "\n"
+              << std::setw(10) << "vt " << blEdge->getVt(iPoint) << "\n"
+              << std::setw(10) << "Me " << blEdge->getMe(iPoint) << "\n"
+              << std::setw(10) << "rhoe " << blEdge->getRhoe(iPoint) << std::endl;
+}
\ No newline at end of file
diff --git a/blast/src/DBoundaryLayer.h b/blast/src/DBoundaryLayer.h
new file mode 100644
index 0000000..d56795e
--- /dev/null
+++ b/blast/src/DBoundaryLayer.h
@@ -0,0 +1,69 @@
+#ifndef DBOUNDARYLAYER_H
+#define DBOUNDARYLAYER_H
+
+#include "blast.h"
+#include "DClosures.h"
+#include "DDiscretization.h"
+#include "DEdge.h"
+
+namespace blast
+{
+
+/**
+ * @brief Boundary layer region upper/lower side or wake.
+ * @author Paul Dechamps
+ */
+class BLAST_API BoundaryLayer
+{
+
+private:
+    unsigned int const nVar = 5; ///< Number of variables of the partial differential problem.
+    double nCrit = 9.0;          ///< Critical amplification factor.
+
+public:
+    Discretization *mesh; ///< 1D surface boundary layer mesh.
+    Edge *blEdge;         ///< Quantites at the inviscid boundary (edge of the boundary layer).
+    Closures *closSolver; ///< Closure relations class.
+    std::string name;     ///< Name of the region.
+
+    // Boundary layer variables.
+    std::vector<double> u;         ///< Solution vector (theta, H, N, ue, Ct).
+    std::vector<double> Ret;       ///< Reynolds number based on the momentum thickness (theta).
+    std::vector<double> cd;        ///< Local dissipation coefficient.
+    std::vector<double> cf;        ///< Local friction coefficient.
+    std::vector<double> Hstar;     ///< Kinetic energy shape parameter (thetaStar/theta).
+    std::vector<double> Hstar2;    ///< Density shape parameter (deltaStarStar/theta).
+    std::vector<double> Hk;        ///< Kinematic shape parameter (int(1-u/ue d_eta)).
+    std::vector<double> ctEq;      ///< Equilibrium shear stress coefficient (turbulent BL).
+    std::vector<double> us;        ///< Equivalent normalized wall slip velocity.
+    std::vector<double> delta;     ///< Boundary layer thickness.
+    std::vector<double> deltaStar; ///< Dispacement thickness (int(1-rho*u/rhoe*ue d_eta)).
+
+    // Transition related variables.
+    std::vector<int> regime;             ///< Laminar (0) or turbulent (1) regime.
+    std::vector<double> blowingVelocity; ///< Blowing velocity.
+    size_t transMarker;                  ///< Marker id of the transition location.
+    double xtrF;                         ///< Forced transition location in the global frame of reference.
+    double xtr;                          ///< Transition location in the global frame of reference.
+    double xoctr;                        ///< Transition location in % of the chord.
+
+    BoundaryLayer(double _xtrF = -1.0);
+    ~BoundaryLayer();
+
+    // Boundary conditions.
+    void setStagBC(double Re);
+    void setWakeBC(double Re, std::vector<double> upperConditions, std::vector<double> lowerConditions);
+    void setMesh(std::vector<double> x,
+                 std::vector<double> y,
+                 std::vector<double> z);
+
+    // Getters.
+    size_t getnVar() const { return nVar; };
+    double getnCrit() const { return nCrit; };
+
+    // Others
+    void printSolution(size_t iPoint) const;
+    void computeBlwVel();
+};
+} // namespace blast
+#endif // DBOUNDARYLAYER_H
diff --git a/blast/src/DClosures.cpp b/blast/src/DClosures.cpp
new file mode 100644
index 0000000..c411d1c
--- /dev/null
+++ b/blast/src/DClosures.cpp
@@ -0,0 +1,257 @@
+#include "DClosures.h"
+#include <cmath>
+#include <iostream>
+
+using namespace blast;
+
+/**
+ * @brief Laminar closure relations at a given station.
+ *
+ * @param closureVars Vector where the computed variables will be stored.
+ * @param theta Momentum thickness.
+ * @param H Shape factor of the boundary layer.
+ * @param Ret Reynolds number based on the momentum thickness.
+ * @param Me Local Mach number.
+ * @param name Name of the region.
+ */
+void Closures::laminarClosures(std::vector<double> &closureVars, double theta,
+                               double H, double Ret, const double Me,
+                               const std::string name)
+{
+    H = std::max(H, 1.00005);
+
+    // Kinematic shape factor H*.
+    double Hk = (H - 0.29 * Me * Me) / (1 + 0.113 * Me * Me);
+    if (name == "wake")
+        Hk = std::max(Hk, 1.00005);
+    else
+        Hk = std::max(Hk, 1.05000);
+
+    // Density shape parameter.
+    double Hstar2 = (0.064 / (Hk - 0.8) + 0.251) * Me * Me;
+
+    // Boundary layer thickness.
+    double delta = std::min((3.15 + H + (1.72 / (Hk - 1))) * theta, 12 * theta);
+
+    double Hstar = 0.;
+    double Hks = Hk - 4.35;
+    if (Hk <= 4.35)
+        Hstar = 0.0111 * Hks * Hks / (Hk + 1) - 0.0278 * Hks * Hks * Hks / (Hk + 1) + 1.528 - 0.0002 * (Hks * Hk) * (Hks * Hk);
+    else
+        Hstar = 1.528 + 0.015 * Hks * Hks / Hk;
+
+    // Whitfield's minor additional compressibility correction.
+    Hstar = (Hstar + 0.028 * Me * Me) / (1 + 0.014 * Me * Me);
+
+    // Friction coefficient.
+    double tmp = 0.;
+    double cf = 0.;
+    if (Hk < 5.5)
+    {
+        tmp = (5.5 - Hk) * (5.5 - Hk) * (5.5 - Hk) / (Hk + 1.0);
+        cf = (-0.07 + 0.0727 * tmp) / Ret;
+    }
+    else if (Hk >= 5.5)
+    {
+        tmp = 1.0 - 1.0 / (Hk - 4.5);
+        cf = (-0.07 + 0.015 * tmp * tmp) / Ret;
+    }
+
+    // Dissipation coefficient.
+    double Cd = 0.;
+    if (Hk < 4)
+        Cd = (0.00205 * std::pow(4.0 - Hk, 5.5) + 0.207) * (Hstar / (2 * Ret));
+    else
+    {
+        double HkCd = (Hk - 4) * (Hk - 4);
+        double denCd = 1 + 0.02 * HkCd;
+        Cd = (-0.0016 * HkCd / denCd + 0.207) * (Hstar / (2 * Ret));
+    }
+
+    // Wake relations.
+    if (name == "wake")
+    {
+        Cd = 2 * (1.10 * (1.0 - 1.0 / Hk) * (1.0 - 1.0 / Hk) / Hk) * (Hstar / (2 * Ret));
+        cf = 0.;
+    }
+
+    double us = 0.;
+    double ctEq = 0.;
+
+    closureVars = {Hstar, Hstar2, Hk, cf, Cd, delta, ctEq, us};
+}
+
+/**
+ * @brief Turbulent closure relations at a given station.
+ *
+ * @param closureVars Vector where the computed variables will be stored.
+ * @param theta Momentum thickness.
+ * @param H Shape factor of the boundary layer.
+ * @param Ct Shear stress coefficient.
+ * @param Ret Reynolds number based on the momentum thickness.
+ * @param Me Local Mach number.
+ * @param name Name of the region.
+ */
+void Closures::turbulentClosures(std::vector<double> &closureVars, double theta, double H, double Ct, double Ret, const double Me, const std::string name)
+{
+    H = std::max(H, 1.00005);
+    Ct = std::min(Ct, 0.3);
+    // Ct = std::max(std::min(Ct, 0.30), 0.0000001);
+
+    double Hk = (H - 0.29 * Me * Me) / (1 + 0.113 * Me * Me);
+    if (name == "wake")
+        Hk = std::max(Hk, 1.00005);
+    else
+        Hk = std::max(Hk, 1.05000);
+
+    double Hstar2 = ((0.064 / (Hk - 0.8)) + 0.251) * Me * Me;
+    double gamma = 1.4 - 1.;
+    double Fc = std::sqrt(1 + 0.5 * gamma * Me * Me);
+
+    double H0 = 0.;
+    if (Ret <= 400)
+        H0 = 4.0;
+    else
+        H0 = 3 + (400 / Ret);
+    if (Ret <= 200)
+        Ret = 200;
+
+    double Hstar = 0.;
+    if (Hk <= H0)
+        Hstar = ((0.5 - 4 / Ret) * ((H0 - Hk) / (H0 - 1)) * ((H0 - Hk) / (H0 - 1))) * (1.5 / (Hk + 0.5)) + 1.5 + 4 / Ret;
+    else
+        Hstar = (Hk - H0) * (Hk - H0) * (0.007 * std::log(Ret) / ((Hk - H0 + 4 / std::log(Ret)) * (Hk - H0 + 4 / std::log(Ret))) + 0.015 / Hk) + 1.5 + 4 / Ret;
+
+    // Whitfield's minor additional compressibility correction.
+    Hstar = (Hstar + 0.028 * Me * Me) / (1 + 0.014 * Me * Me);
+
+    double logRt = std::log(Ret / Fc);
+    logRt = std::max(logRt, 3.0);
+    double arg = std::max(-1.33 * Hk, -20.0);
+
+    // Equivalent normalized wall slip velocity.
+    double us = (Hstar / 2) * (1 - 4 * (Hk - 1) / (3 * H));
+
+    // Boundary layer thickness.
+    double delta = std::min((3.15 + H + (1.72 / (Hk - 1))) * theta, 12 * theta);
+
+    double Ctcon = 0.5 / (6.7 * 6.7 * 0.75);
+    double Hkc = 0.;
+    double Cdw = 0.;
+    double Cdd = 0.;
+    double Cdl = 0.;
+
+    double Hmin = 0.;
+    double Fl = 0.;
+    double Dfac = 0.;
+
+    double cf = 0.;
+    double Cd = 0.;
+
+    double n = 1.0;
+
+    double ctEq = 0.;
+
+    if (name == "wake")
+    {
+        if (us > 0.99995)
+            us = 0.99995;
+        n = 2.0;  // two wake halves
+        cf = 0.0; // no friction inside the wake
+        Hkc = Hk - 1;
+        Cdw = 0.0;                                                                                 // Wall contribution.
+        Cdd = (0.995 - us) * Ct * Ct;                                                              // Turbulent outer layer contribution.
+        Cdl = 0.15 * (0.995 - us) * (0.995 - us) / Ret;                                            // Laminar stress contribution to outer layer.
+        ctEq = std::sqrt(4 * Hstar * Ctcon * ((Hk - 1) * Hkc * Hkc) / ((1 - us) * (Hk * Hk) * H)); // Here it is ctEq^0.5.
+    }
+    else
+    {
+        if (us > 0.95)
+            us = 0.98;
+        n = 1.0;
+        cf = (1 / Fc) * (0.3 * std::exp(arg) * std::pow(logRt / 2.3026, (-1.74 - 0.31 * Hk)) + 0.00011 * (std::tanh(4 - (Hk / 0.875)) - 1));
+        Hkc = std::max(Hk - 1 - 18 / Ret, 0.01);
+        // Dissipation coefficient.
+        Hmin = 1 + 2.1 / std::log(Ret);
+        Fl = (Hk - 1) / (Hmin - 1);
+        Dfac = 0.5 + 0.5 * std::tanh(Fl);
+        Cdw = 0.5 * (cf * us) * Dfac;
+        Cdd = (0.995 - us) * Ct * Ct;
+        Cdl = 0.15 * (0.995 - us) * (0.995 - us) / Ret;
+        ctEq = std::sqrt(Hstar * Ctcon * ((Hk - 1) * Hkc * Hkc) / ((1 - us) * (Hk * Hk) * H)); // Here it is ctEq^0.5
+        //ctEq = std::sqrt(Hstar * 0.015/(1-us) * (Hk-1)*(Hk-1)*(Hk-1)/(Hk*Hk*H)); // Drela 1987
+    }
+    if (n * Hstar * Ctcon * ((Hk - 1) * Hkc * Hkc) / ((1 - us) * (Hk * Hk) * H) < 0)
+        std::cout << "Negative sqrt encountered " << std::endl;
+
+    // Dissipation coefficient.
+    Cd = n * (Cdw + Cdd + Cdl);
+    closureVars = {Hstar, Hstar2, Hk, cf, Cd, delta, ctEq, us};
+}
+
+/**
+ * @brief Turbulent closure relations at a given station.
+ *
+ * @param closureVars Computed equilibrium shear stress coefficient.
+ * @param theta Momentum thickness.
+ * @param H Shape factor of the boundary layer.
+ * @param Ct Shear stress coefficient.
+ * @param Ret Reynolds number based on the momentum thickness.
+ * @param Me Local Mach number.
+ * @param name Name of the region.
+ */
+void Closures::turbulentClosures(double &closureVars, double theta, double H, double Ct, double Ret, const double Me, const std::string name)
+{
+    H = std::max(H, 1.00005);
+    Ct = std::min(Ct, 0.3);
+
+    double Hk = (H - 0.29 * Me * Me) / (1 + 0.113 * Me * Me);
+    if (name == "wake")
+        Hk = std::max(Hk, 1.00005);
+    else
+        Hk = std::max(Hk, 1.05000);
+
+    double H0 = 0.;
+    if (Ret <= 400)
+        H0 = 4.0;
+    else
+        H0 = 3 + (400 / Ret);
+    if (Ret <= 200)
+        Ret = 200;
+
+    double Hstar = 0.;
+    if (Hk <= H0)
+        Hstar = ((0.5 - 4 / Ret) * ((H0 - Hk) / (H0 - 1)) * ((H0 - Hk) / (H0 - 1))) * (1.5 / (Hk + 0.5)) + 1.5 + 4 / Ret;
+    else
+        Hstar = (Hk - H0) * (Hk - H0) * (0.007 * std::log(Ret) / ((Hk - H0 + 4 / std::log(Ret)) * (Hk - H0 + 4 / std::log(Ret))) + 0.015 / Hk) + 1.5 + 4 / Ret;
+
+    // Whitfield's minor additional compressibility correction.
+    Hstar = (Hstar + 0.028 * Me * Me) / (1 + 0.014 * Me * Me);
+
+    // Equivalent normalized wall slip velocity.
+    double us = (Hstar / 2) * (1 - 4 * (Hk - 1) / (3 * H));
+
+    double Ctcon = 0.5 / (6.7 * 6.7 * 0.75);
+
+    double Hkc = 0.;
+    double ctEq = 0.;
+
+    if (name == "wake")
+    {
+        if (us > 0.99995)
+            us = 0.99995;
+        Hkc = Hk - 1;
+        ctEq = std::sqrt(4 * Hstar * Ctcon * ((Hk - 1) * Hkc * Hkc) / ((1 - us) * (Hk * Hk) * H)); // Here it is ctEq^0.5
+    }
+    else
+    {
+        if (us > 0.95)
+            us = 0.98;
+        Hkc = std::max(Hk - 1 - 18 / Ret, 0.01);
+        ctEq = std::sqrt(Hstar * Ctcon * ((Hk - 1) * Hkc * Hkc) / ((1 - us) * (Hk * Hk) * H)); // Here it is ctEq^0.5
+    }
+    if (Hstar * Ctcon * ((Hk - 1) * Hkc * Hkc) / ((1 - us) * (Hk * Hk) * H) < 0)
+        std::cout << "Negative sqrt encountered " << std::endl;
+
+    closureVars = ctEq;
+}
\ No newline at end of file
diff --git a/blast/src/DClosures.h b/blast/src/DClosures.h
new file mode 100644
index 0000000..a820065
--- /dev/null
+++ b/blast/src/DClosures.h
@@ -0,0 +1,30 @@
+#ifndef DCLOSURES_H
+#define DCLOSURES_H
+
+#include "blast.h"
+#include <vector>
+#include <string>
+
+namespace blast
+{
+
+/**
+ * @brief Boundary layer closure relations.
+ * @author Paul Dechamps
+ */
+class BLAST_API Closures
+{
+
+public:
+    static void laminarClosures(std::vector<double> &closureVars, double theta,
+                                double H, double Ret, const double Me,
+                                const std::string name);
+    static void turbulentClosures(std::vector<double> &closureVars, double theta,
+                                  double H, double Ct, double Ret, double Me,
+                                  const std::string name);
+    static void turbulentClosures(double &closureVars, double theta,
+                                  double H, double Ct, double Ret, const double Me,
+                                  const std::string name);
+};
+} // namespace blast
+#endif // DCLOSURES_H
diff --git a/blast/src/DDiscretization.cpp b/blast/src/DDiscretization.cpp
new file mode 100644
index 0000000..032e2d8
--- /dev/null
+++ b/blast/src/DDiscretization.cpp
@@ -0,0 +1,77 @@
+#include "DDiscretization.h"
+#include <cmath>
+#include <algorithm>
+
+using namespace blast;
+
+Discretization::Discretization()
+{
+    nMarkers = 0;
+    nElm = 0;
+    prevnMarkers = 0;
+}
+
+Discretization::~Discretization() {}
+
+void Discretization::setGlob(std::vector<double> &_x, std::vector<double> &_y, std::vector<double> &_z)
+{
+    if (_x.size() != _y.size() || _y.size() != _z.size() || _x.size() != _z.size())
+        throw std::runtime_error("blast::Discretization Wrong mesh sizes.\n");
+
+    nMarkers = _x.size();
+    x.resize(nMarkers, 0.);
+    y.resize(nMarkers, 0.);
+    z.resize(nMarkers, 0.);
+    nElm = nMarkers - 1;
+    x = _x;
+    y = _y;
+    z = _z;
+
+    computeBLcoord();
+    computeOCcoord();
+}
+
+/**
+ * @brief Compute nodes coordinates in the local frame of reference.
+ */
+void Discretization::computeBLcoord()
+{
+    loc.resize(nMarkers, 0.);
+    alpha.resize(nElm, 0.);
+    dx.resize(nElm, 0.);
+
+    for (size_t iPoint = 0; iPoint < nElm; ++iPoint)
+    {
+        alpha[iPoint] = std::atan2(y[iPoint + 1] - y[iPoint], x[iPoint + 1] - x[iPoint]);
+        // dx = sqrt(delta x ^2 + delta y ^2).
+        dx[iPoint] = std::sqrt((x[iPoint + 1] - x[iPoint]) * (x[iPoint + 1] - x[iPoint]) + (y[iPoint + 1] - y[iPoint]) * (y[iPoint + 1] - y[iPoint]));
+        loc[iPoint + 1] = loc[iPoint] + dx[iPoint];
+    }
+}
+
+/**
+ * @brief Compute x/chord coordinate.
+*/
+void Discretization::computeOCcoord()
+{
+    xoc.resize(nMarkers, 0.);
+
+    // Find min and max of array x
+    auto min_it = std::min_element(x.begin(), x.end());
+    double xMin = *min_it;
+    auto max_it = std::max_element(x.begin(), x.end());
+    double xMax = *max_it;
+
+    // Compute xoc
+    chord = xMax - xMin;
+    if (chord <= 0)
+        throw;
+    for (size_t iPoint = 0; iPoint < x.size(); ++iPoint)
+        xoc[iPoint] = (x[iPoint] - xMin) / chord;
+}
+
+void Discretization::setExt(std::vector<double> extM)
+{
+    ext.resize(extM.size(), 0.);
+    ext = extM;
+};
diff --git a/blast/src/DDiscretization.h b/blast/src/DDiscretization.h
new file mode 100644
index 0000000..1f4d534
--- /dev/null
+++ b/blast/src/DDiscretization.h
@@ -0,0 +1,54 @@
+#ifndef DDISCRETIZATION_H
+#define DDISCRETIZATION_H
+
+#include "blast.h"
+#include "DBoundaryLayer.h"
+
+namespace blast
+{
+
+/**
+ * @brief Boundary layer mesh.
+ * @author Paul Dechamps
+ */
+class BLAST_API Discretization
+{
+private:
+    std::vector<double> x;     ///< x coordinate in the global frame of reference.
+    std::vector<double> y;     ///< y coordinate in the global frame of reference.
+    std::vector<double> z;     ///< z coordinate in the global frame of reference.
+    std::vector<double> xoc;   ///< x/c coordinate in the global frame of reference.
+    std::vector<double> loc;   ///< xi coordinate in the local frame of reference.
+    std::vector<double> ext;   ///< xi coordinate in the local frame of reference, at the edge of the boundary layer.
+    std::vector<double> dx;    ///< Cell size.
+    std::vector<double> alpha; ///< Angle of the cell wrt the x axis of the global frame of reference.
+    size_t nMarkers;           ///< Number of points in the domain.
+    size_t nElm;               ///< Number of cells in the domain.
+    double chord;              ///< Chord of the section.
+
+public:
+    size_t prevnMarkers; ///< Number of points in the domain at the previous iteration.
+
+    Discretization();
+    ~Discretization();
+
+    // Getters.
+    size_t getnMarkers() const { return nMarkers; };
+    double getLoc(size_t iPoint) const { return loc[iPoint]; };
+    double getx(size_t iPoint) const { return x[iPoint]; };
+    double gety(size_t iPoint) const { return y[iPoint]; };
+    double getz(size_t iPoint) const { return z[iPoint]; };
+    double getxoc(size_t iPoint) const { return xoc[iPoint]; };
+    double getExt(size_t iPoint) const { return ext[iPoint]; };
+    double getAlpha(size_t iElm) const { return alpha[iElm]; };
+    size_t getDiffnElm() const { return nMarkers - prevnMarkers; };
+    double getChord() const { return chord; };
+
+    // Setters.
+    void setGlob(std::vector<double> &_x, std::vector<double> &_y, std::vector<double> &_z);
+    void setExt(std::vector<double> extM);
+    void computeBLcoord();
+    void computeOCcoord();
+};
+} // namespace blast
+#endif // WDISCRETIZATION_H
diff --git a/blast/src/DDriver.cpp b/blast/src/DDriver.cpp
new file mode 100644
index 0000000..ab58b07
--- /dev/null
+++ b/blast/src/DDriver.cpp
@@ -0,0 +1,795 @@
+#include "DDriver.h"
+#include "DBoundaryLayer.h"
+#include "DSolver.h"
+#include <iomanip>
+
+#define ANSI_COLOR_RED "\x1b[1;31m"
+#define ANSI_COLOR_GREEN "\x1b[1;32m"
+#define ANSI_COLOR_YELLOW "\x1b[1;33m"
+#define ANSI_COLOR_RESET "\x1b[0m"
+
+using namespace blast;
+
+/**
+ * @brief Driver of the viscous solver.
+ *
+ * @param _Re Freestream Reynolds number.
+ * @param _Minf Freestream Mach number.
+ * @param _CFL0 Initial CFL number for pseudo-time integration.
+ * @param nSections Number of sections in the computation (1 for 2D cases).
+ * @param _Span Span of the considered wing (only used for drag computation).
+ * @param _verbose Verbosity level of the solver 0<=verbose<=1.
+ */
+Driver::Driver(double _Re, double _Minf, double _CFL0, size_t nSections,
+               double xtrF_top, double xtrF_bot,  double _span, unsigned int _verbose)
+{
+    // Freestream parameters.
+    Re = _Re;
+    CFL0 = _CFL0;
+    verbose = _verbose;
+
+    std::vector<double> xtrF = {xtrF_top, xtrF_bot, 0.};
+
+    // Initialzing boundary layer
+    sections.resize(nSections);
+    convergenceStatus.resize(nSections);
+    for (size_t iSec = 0; iSec < nSections; ++iSec)
+    {
+        convergenceStatus[iSec].resize(3);
+        for (size_t i = 0; i < 3; ++i)
+        {
+            BoundaryLayer *region = new BoundaryLayer(xtrF[i]);
+            sections[iSec].push_back(region);
+        }
+        sections[iSec][0]->name = "upper";
+        sections[iSec][1]->name = "lower";
+        sections[iSec][2]->name = "wake";
+    }
+
+    Cdt_sec.resize(sections.size(), 0.);
+    Cdf_sec.resize(sections.size(), 0.);
+    Cdp_sec.resize(sections.size(), 0.);
+    Cdt = 0.;
+    Cdf = 0.;
+    Cdp = 0.;
+    span = _span;
+
+    // Pre/post processing flags.
+    stagPtMvmt.resize(sections.size(), false);
+
+    // Time solver initialization.
+    tSolver = new Solver(_CFL0, _Minf, Re);
+
+    // Numerical parameters.
+    std::cout << "--- Viscous solver setup ---\n"
+              << "Reynolds number: " << Re << " \n"
+              << "Freestream Mach number: " << _Minf << " \n"
+              << "Initial CFL number: " << CFL0 << " \n"
+              << std::endl;
+    std::cout << "Boundary layer initialized." << std::endl;
+}
+
+/**
+ * @brief Driver and subsequent dependencies destruction.
+ */
+Driver::~Driver()
+{
+    for (size_t iSec = 0; iSec < sections.size(); ++iSec)
+        for (size_t i = 0; i < sections[iSec].size(); ++i)
+            delete sections[iSec][i];
+    delete tSolver;
+    std::cout << "~blast::Driver()\n";
+} // end ~Driver
+
+/**
+ * @brief Runs viscous operations. Temporal solver parametrisation is first adapted,
+ * Solutions are initialized than time marched towards steady state successively for each points.
+ *
+ * @param couplIter Current coupling iteration.
+ * @return int Solver exit code.
+ */
+int Driver::run(unsigned int const couplIter)
+{
+    bool lockTrans = false; // Flagging activation of transition routines.
+    int pointExitCode;      // Output of pseudo time integration (0: converged; -1: unsuccessful, failed; >0: unsuccessful nb iter).
+
+    double maxMach = 0.;
+
+    for (size_t iSec = 0; iSec < sections.size(); ++iSec)
+    {
+        tms["0-CheckIn"].start();
+        if (verbose > 1)
+            std::cout << "Sec " << iSec << " ";
+
+        // Check for stagnation point movement.
+        if (couplIter != 0 && (sections[iSec][0]->mesh->getDiffnElm()))
+        {
+            stagPtMvmt[iSec] = true;
+            if (verbose > 2)
+                std::cout << "Stagnation point moved by " << sections[iSec][0]->mesh->getDiffnElm() << " elements." << std::endl;
+        }
+        else
+            stagPtMvmt[iSec] = false;
+
+        // Set boundary conditions.
+        sections[iSec][0]->xtr = 1.; // Upper side initial xtr.
+        sections[iSec][1]->xtr = 1.; // Lower side initial xtr.
+        sections[iSec][2]->xtr = 0.; // Wake initial xtr (fully turbulent).
+        sections[iSec][0]->setStagBC(Re);
+        sections[iSec][1]->setStagBC(Re);
+        tms["0-CheckIn"].stop();
+
+        // Loop over the regions (upper, lower and wake).
+        for (size_t iRegion = 0; iRegion < sections[iSec].size(); ++iRegion)
+        {
+            convergenceStatus[iSec][iRegion].resize(0);
+
+            // Maximum Mach number in the region.
+            if (sections[iSec][iRegion]->blEdge->getMaxMach() > maxMach)
+                maxMach = sections[iSec][iRegion]->blEdge->getMaxMach();
+
+            if (verbose > 1)
+                std::cout << sections[iSec][iRegion]->name << " ";
+
+            // Check if safe mode is required.
+            if (couplIter == 0 || sections[iSec][iRegion]->mesh->getDiffnElm() != 0 || stagPtMvmt[iSec] == true || solverExitCode != 0)
+            {
+                tSolver->setCFL0(1);
+                tSolver->setitFrozenJac(1);
+                tSolver->setinitSln(true);
+
+                if (sections[iSec][iRegion]->mesh->getDiffnElm())
+                {
+                    std::vector<double> xxExtCurr(sections[iSec][iRegion]->mesh->getnMarkers(), 0.);
+                    for (size_t iPoint = 0; iPoint < xxExtCurr.size(); ++iPoint)
+                        xxExtCurr[iPoint] = sections[iSec][iRegion]->mesh->getLoc(iPoint);
+                    sections[iSec][iRegion]->mesh->setExt(xxExtCurr);
+
+                    std::vector<double> DeltaStarZeros(sections[iSec][iRegion]->mesh->getnMarkers(), 0.0);
+                    sections[iSec][iRegion]->blEdge->setDeltaStar(DeltaStarZeros);
+
+                    std::vector<double> ueOld(sections[iSec][iRegion]->mesh->getnMarkers(), 0.0);
+                    for (size_t iPoint = 0; iPoint < ueOld.size(); ++iPoint)
+                        ueOld[iPoint] = sections[iSec][iRegion]->blEdge->getVt(iPoint);
+                    sections[iSec][iRegion]->blEdge->setVtOld(ueOld);
+                }
+
+                // Keyword annoncing iteration safety level.
+                if (verbose > 1)
+                    std::cout << "restart. ";
+            }
+            else
+            {
+                tSolver->setCFL0(CFL0);
+                tSolver->setitFrozenJac(5);
+                tSolver->setinitSln(false);
+
+                // Keyword annoncing iteration safety level.
+                if (verbose > 1)
+                    std::cout << "update. ";
+            }
+
+            // Save number of points in each regions.
+            sections[iSec][iRegion]->mesh->prevnMarkers = sections[iSec][iRegion]->mesh->getnMarkers();
+
+            // Reset regime.
+            lockTrans = false;
+            if (iRegion < 2) // Airfoil
+                for (size_t k = 0; k < sections[iSec][iRegion]->mesh->getnMarkers(); k++)
+                    sections[iSec][iRegion]->regime[k] = 0;
+            else if (iRegion == 2) // Wake
+            {
+                for (size_t k = 0; k < sections[iSec][iRegion]->mesh->getnMarkers(); k++)
+                    sections[iSec][iRegion]->regime[k] = 1;
+
+                // Impose wake boundary condition.
+                std::vector<double> upperConditions(sections[iSec][iRegion]->getnVar(), 0.);
+                std::vector<double> lowerConditions(sections[iSec][iRegion]->getnVar(), 0.);
+                for (size_t k = 0; k < upperConditions.size(); ++k)
+                {
+                    upperConditions[k] = sections[iSec][0]->u[(sections[iSec][0]->mesh->getnMarkers() - 1) * sections[iSec][0]->getnVar() + k];
+                    lowerConditions[k] = sections[iSec][1]->u[(sections[iSec][1]->mesh->getnMarkers() - 1) * sections[iSec][1]->getnVar() + k];
+                }
+                sections[iSec][iRegion]->setWakeBC(Re, upperConditions, lowerConditions);
+                lockTrans = true;
+            }
+            else
+                throw;
+
+            // Loop over points
+            for (size_t iPoint = 1; iPoint < sections[iSec][iRegion]->mesh->getnMarkers(); ++iPoint)
+            {
+                // Initialize solution at point
+                tSolver->initialCondition(iPoint, sections[iSec][iRegion]);
+                // Solve equations
+                tms["1-Solver"].start();
+                pointExitCode = tSolver->integration(iPoint, sections[iSec][iRegion]);
+                if (sections[iSec][iRegion]->xtrF != -1 && sections[iSec][iRegion]->mesh->getxoc(iPoint) >= sections[iSec][iRegion]->xtrF)
+                    sections[iSec][iRegion]->u[iPoint * sections[iSec][iRegion]->getnVar()+2] = 9.;
+                tms["1-Solver"].stop();
+
+                if (pointExitCode != 0)
+                    convergenceStatus[iSec][iRegion].push_back(iPoint);
+
+                // Transition
+                tms["2-Transition"].start();
+                if (!lockTrans)
+                {
+                    // Check if perturbation waves are growing or not.
+                    if (sections[iSec][iRegion]->xtrF != -1 && sections[iSec][iRegion]->mesh->getxoc(iPoint) <= sections[iSec][iRegion]->xtrF)
+                        checkWaves(iPoint, sections[iSec][iRegion]);
+
+                    // Amplification factor is compared to critical amplification factor.
+                    if (sections[iSec][iRegion]->u[iPoint * sections[iSec][iRegion]->getnVar() + 2] >= sections[iSec][iRegion]->getnCrit())
+                    {
+                        averageTransition(iPoint, sections[iSec][iRegion], 0);
+                        if (verbose > 1)
+                        {
+                            std::cout << std::fixed;
+                            std::cout << std::setprecision(2);
+                            std::cout << sections[iSec][iRegion]->xoctr
+                                      << " (" << sections[iSec][iRegion]->transMarker << ") ";
+                        }
+                        lockTrans = true;
+                    }
+                }
+                tms["2-Transition"].stop();
+            }
+            tms["3-Blowing"].start();
+            sections[iSec][iRegion]->computeBlwVel();
+            tms["3-Blowing"].stop();
+        }
+        if (verbose > 1)
+            std::cout << std::endl;
+    }
+
+    for (size_t i = 0; i < sections.size(); ++i)
+        computeSectionalDrag(sections[i], i);
+    computeTotalDrag();
+
+    // Output information.
+    solverExitCode = outputStatus(maxMach);
+
+    return solverExitCode; // exit code
+}
+
+/**
+ * @brief Check whether or not instabilities are growing in the laminar boundary layer.
+ *
+ * @param iPoint Marker id.
+ * @param bl BoundaryLayer region.
+ */
+void Driver::checkWaves(size_t iPoint, BoundaryLayer *bl)
+{
+
+    double logRet_crit = 2.492 * std::pow(1 / (bl->Hk[iPoint] - 1), 0.43) + 0.7 * (std::tanh(14 * (1 / (bl->Hk[iPoint] - 1)) - 9.24) + 1.0);
+
+    if (bl->Ret[iPoint] > 0) // Avoid log of negative number.
+    {
+        if (std::log10(bl->Ret[iPoint]) <= logRet_crit - 0.08)
+            bl->u[iPoint * bl->getnVar() + 2] = 0; // Set N to 0 at that point if waves do not grow.
+    }
+    else
+        bl->u[iPoint * bl->getnVar() + 2] = 0; // Set N to 0 at that point if waves do not grow.
+}
+
+/**
+ * @brief Computes turbulent solution @ the transition point and averages solutions.
+ *
+ * @param iPoint Marker id.
+ * @param bl BoundaryLayer region.
+ * @param forced Flag for forced transition.
+ */
+void Driver::averageTransition(size_t iPoint, BoundaryLayer *bl, int forced)
+{
+    // Averages solution on transition marker.
+    size_t nVar = bl->getnVar();
+
+    // Save laminar solution.
+    std::vector<double> lamSol(nVar, 0.0);
+    for (size_t k = 0; k < lamSol.size(); ++k)
+        lamSol[k] = bl->u[iPoint * nVar + k];
+
+    // Set up turbulent portion boundary condition.
+    bl->transMarker = iPoint; // Save transition marker.
+
+    // Loop over remaining points in the region.
+    for (size_t k = iPoint; k < bl->mesh->getnMarkers(); ++k)
+        bl->regime[k] = 1; // Set Turbulent regime.
+
+    // Compute transition location.
+    bl->xtr = (bl->getnCrit() - bl->u[(iPoint - 1) * nVar + 2]) * ((bl->mesh->getx(iPoint) - bl->mesh->getx(iPoint - 1)) / (bl->u[iPoint * nVar + 2] - bl->u[(iPoint - 1) * nVar + 2])) + bl->mesh->getx(iPoint - 1);
+    bl->xoctr = (bl->getnCrit() - bl->u[(iPoint - 1) * nVar + 2]) * ((bl->mesh->getxoc(iPoint) - bl->mesh->getxoc(iPoint - 1)) / (bl->u[iPoint * nVar + 2] - bl->u[(iPoint - 1) * nVar + 2])) + bl->mesh->getxoc(iPoint - 1);
+
+    // Percentage of the subsection corresponding to a turbulent flow.
+    double avTurb = (bl->mesh->getx(iPoint) - bl->xtr) / (bl->mesh->getx(iPoint) - bl->mesh->getx(iPoint - 1));
+    double avLam = 1. - avTurb;
+
+    // Impose boundary condition.
+    double Cteq_trans;
+    bl->closSolver->turbulentClosures(Cteq_trans, bl->u[(iPoint - 1) * nVar + 0], bl->u[(iPoint - 1) * nVar + 1], 0.03, bl->Ret[iPoint - 1], bl->blEdge->getMe(iPoint - 1), bl->name);
+    bl->ctEq[iPoint - 1] = Cteq_trans;
+    bl->u[(iPoint - 1) * nVar + 4] = 0.7 * bl->ctEq[iPoint - 1];
+
+    // Avoid starting with ill conditioned IC for turbulent computation. (Regime was switched above).
+    // These initial guess values do not influence the converged solution.
+    bl->u[iPoint * nVar + 4] = bl->u[(iPoint - 1) * nVar + 4]; // IC of transition point = turbulent BC (imposed @ previous point).
+    bl->u[iPoint * nVar + 1] = 1.515;                          // Because we expect a significant drop of the shape factor.
+
+    // Solve point in turbulent configuration.
+    int exitCode = tSolver->integration(iPoint, bl);
+
+    if (exitCode != 0 && verbose > 1)
+        std::cout << "Warning: Transition point turbulent computation terminated with exit code " << exitCode << std::endl;
+
+    // Average both solutions (Now solution @ iPoint is the turbulent solution).
+    bl->u[iPoint * nVar + 0] = avLam * lamSol[0] + avTurb * bl->u[(iPoint)*nVar + 0]; // Theta.
+    bl->u[iPoint * nVar + 1] = avLam * lamSol[1] + avTurb * bl->u[(iPoint)*nVar + 1]; // H.
+    bl->u[iPoint * nVar + 2] = 9.;                                                    // N.
+    bl->u[iPoint * nVar + 3] = avLam * lamSol[3] + avTurb * bl->u[(iPoint)*nVar + 3]; // ue.
+    bl->u[iPoint * nVar + 4] = avTurb * bl->u[(iPoint)*nVar + 4];                     // Ct.
+
+    // Recompute closures. (The turbulent BC @ iPoint - 1 does not influence laminar closure relations).
+    std::vector<double> lamParam(8, 0.);
+    bl->closSolver->laminarClosures(lamParam, bl->u[(iPoint - 1) * nVar + 0], bl->u[(iPoint - 1) * nVar + 1], bl->Ret[iPoint - 1], bl->blEdge->getMe(iPoint - 1), bl->name);
+    bl->Hstar[iPoint - 1] = lamParam[0];
+    bl->Hstar2[iPoint - 1] = lamParam[1];
+    bl->Hk[iPoint - 1] = lamParam[2];
+    bl->cf[iPoint - 1] = lamParam[3];
+    bl->cd[iPoint - 1] = lamParam[4];
+    bl->delta[iPoint - 1] = lamParam[5];
+    bl->ctEq[iPoint - 1] = lamParam[6];
+    bl->us[iPoint - 1] = lamParam[7];
+
+    std::vector<double> turbParam(8, 0.);
+    bl->closSolver->turbulentClosures(turbParam, bl->u[(iPoint)*nVar + 0], bl->u[(iPoint)*nVar + 1], bl->u[(iPoint)*nVar + 4], bl->Ret[iPoint], bl->blEdge->getMe(iPoint), bl->name);
+    bl->Hstar[iPoint] = turbParam[0];
+    bl->Hstar2[iPoint] = turbParam[1];
+    bl->Hk[iPoint] = turbParam[2];
+    bl->cf[iPoint] = turbParam[3];
+    bl->cd[iPoint] = turbParam[4];
+    bl->delta[iPoint] = turbParam[5];
+    bl->ctEq[iPoint] = turbParam[6];
+    bl->us[iPoint] = turbParam[7];
+}
+
+/**
+ * @brief Friction, pressure and total drag computation qt each station of the configuration.
+ *
+ * @tparam Cdt = theta * Ue^((H+5)/2).
+ * @tparam Cdf = integral(Cf dx)_upper + integral(Cf dx)_lower.
+ * @tparam Cdp = Cdtot - Cdf.
+ *
+ * @param bl BoundaryLayer region.
+ * @param i Considered section.
+ */
+void Driver::computeSectionalDrag(std::vector<BoundaryLayer *> const &bl, size_t i)
+{
+    size_t nVar = bl[0]->getnVar();
+    size_t lastWkPt = (bl[2]->mesh->getnMarkers() - 1) * nVar;
+
+    // Normalize friction and dissipation coefficients.
+    std::vector<double> frictioncoeff_upper(bl[0]->mesh->getnMarkers(), 0.0);
+    for (size_t k = 0; k < frictioncoeff_upper.size(); ++k)
+        frictioncoeff_upper[k] = bl[0]->blEdge->getVt(k) * bl[0]->blEdge->getVt(k) * bl[0]->cf[k];
+
+    std::vector<double> frictioncoeff_lower(bl[1]->mesh->getnMarkers(), 0.0);
+    for (size_t k = 0; k < frictioncoeff_lower.size(); ++k)
+        frictioncoeff_lower[k] = bl[1]->blEdge->getVt(k) * bl[1]->blEdge->getVt(k) * bl[1]->cf[k];
+
+    // Compute friction drag coefficient.
+    double Cdf_upper = 0.0;
+    for (size_t k = 1; k < bl[0]->mesh->getnMarkers(); ++k)
+        Cdf_upper += (frictioncoeff_upper[k] + frictioncoeff_upper[k - 1]) * (bl[0]->mesh->getx(k) - bl[0]->mesh->getx(k - 1)) / 2;
+    double Cdf_lower = 0.0;
+    for (size_t k = 1; k < bl[1]->mesh->getnMarkers(); ++k)
+        Cdf_lower += (frictioncoeff_lower[k] + frictioncoeff_lower[k - 1]) * (bl[1]->mesh->getx(k) - bl[1]->mesh->getx(k - 1)) / 2;
+
+    // Friction drag coefficient.
+    Cdf_sec[i] = Cdf_upper + Cdf_lower; // No friction in the wake
+    // Total drag coefficient.
+    Cdt_sec[i] = 2 * bl[2]->u[lastWkPt + 0] * pow(bl[2]->u[lastWkPt + 3], (bl[2]->u[lastWkPt + 1] + 5) / 2);
+    // Pressure drag coefficient.
+    Cdp_sec[i] = Cdt_sec[i] - Cdf_sec[i];
+}
+
+/**
+ * @brief Compute total drag coefficient on the airfoil/wing.
+ * Performs the sectional drag integration for 3D computations.
+ *
+ */
+void Driver::computeTotalDrag()
+{
+    Cdt = 0.;
+    Cdf = 0.;
+    Cdp = 0.;
+
+    if (sections.size() == 1 || span == 0.)
+    {
+        Cdt = Cdt_sec[0];
+        Cdf = Cdf_sec[0];
+        Cdp = Cdp_sec[0];
+    }
+    else
+    {
+        Cdt += (sections[0][0]->mesh->getz(0) - 0) * Cdt_sec[0];
+        Cdp += (sections[0][0]->mesh->getz(0) - 0) * Cdp_sec[0];
+        Cdf += (sections[0][0]->mesh->getz(0) - 0) * Cdf_sec[0];
+        double dz = 0.;
+        for (size_t k = 1; k < sections.size(); ++k)
+        {
+            dz = (sections[k][0]->mesh->getz(0) - sections[k - 1][0]->mesh->getz(0));
+            Cdt += dz * Cdt_sec[k];
+            Cdp += dz * Cdp_sec[k];
+            Cdf += dz * Cdf_sec[k];
+        }
+        Cdt /= span;
+        Cdp /= span;
+        Cdf /= span;
+    }
+}
+
+/**
+ * @brief Outputs solver status depending on the verbosity level (verbose).
+ *
+ * @param maxMach Maximum Mach number identified on the configuration.
+ * @return int Solver exit code (0 converged, !=0 not converged).
+ */
+int Driver::outputStatus(double maxMach) const
+{
+    int solverExitCode = 0;
+    for (size_t iSec = 0; iSec < sections.size(); ++iSec)
+        for (size_t iReg = 0; iReg < sections[iSec].size(); ++iReg)
+            if (convergenceStatus[iSec][iReg].size() != 0)
+            {
+                solverExitCode = -1;
+                break;
+            }
+
+    if (verbose > 2 && solverExitCode != 0)
+    {
+        // Output unconverged points.
+        std::cout << "Unconverged points" << std::endl;
+        std::cout << std::setw(10) << "Section"
+                  << std::setw(12) << "Region"
+                  << std::setw(16) << "Markers" << std::endl;
+        for (size_t iSec = 0; iSec < sections.size(); ++iSec)
+        {
+            unsigned int count = 0;
+            for (size_t iReg = 0; iReg < sections[iSec].size(); ++iReg)
+            {
+                std::cout << std::setw(10) << iSec;
+                std::cout << std::setw(12) << iReg << "          ";
+                if (convergenceStatus[iSec][iReg].size() != 0)
+                    for (size_t iPoint = 0; iPoint < convergenceStatus[iSec][iReg].size(); ++iPoint)
+                    {
+                        std::cout << convergenceStatus[iSec][iReg][iPoint] << " (" << sections[iSec][iReg]->mesh->getx(convergenceStatus[iSec][iReg][iPoint]) << "), ";
+                        ++count;
+                    }
+                std::cout << "\n";
+            }
+            std::cout << "total: " << count << "\n";
+        }
+        std::cout << "" << std::endl;
+    }
+
+    if (verbose > 0)
+    {
+        // Compute mean transition locations.
+        std::vector<double> meanTransitions(2, 0.);
+        for (size_t iSec = 0; iSec < sections.size(); ++iSec)
+            for (size_t iReg = 0; iReg < 2; ++iReg)
+                meanTransitions[iReg] += sections[iSec][iReg]->xoctr;
+        for (size_t iReg = 0; iReg < meanTransitions.size(); ++iReg)
+            meanTransitions[iReg] /= sections.size();
+
+        std::cout << std::fixed << std::setprecision(2)
+                  << "Viscous solver for Re " << Re / 1e6 << "e6, Mach max "
+                  << maxMach
+                  << std::endl;
+
+        std::cout << std::setw(10) << "xTr Upper"
+                  << std::setw(12) << "xTr Lower"
+                  << std::setw(12) << "Cd" << std::endl;
+        std::cout << std::fixed << std::setprecision(2);
+        std::cout << std::setw(10) << meanTransitions[0]
+                  << std::setw(12) << meanTransitions[1];
+        std::cout << std::fixed << std::setprecision(5);
+        std::cout << std::setw(12) << Cdt << "\n";
+
+        if (verbose > 0)
+        {
+            if (solverExitCode == 0)
+                std::cout << ANSI_COLOR_GREEN << "Viscous solver converged." << ANSI_COLOR_RESET << std::endl;
+            else
+                std::cout << ANSI_COLOR_YELLOW << "Viscous solver not converged." << ANSI_COLOR_RESET << std::endl;
+        }
+
+        if (verbose > 2)
+        {
+            std::cout << "Viscous solver CPU" << std::endl
+                      << tms;
+        }
+    }
+
+    if (verbose > 0)
+        std::cout << "" << std::endl;
+    return solverExitCode;
+}
+
+/**
+ * @brief Set nodes coordinates in the global frame of reference.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param _x Vector of x coordinates of the nodes in the region.
+ * @param _y Vector of y coordinates of the nodes in the region.
+ * @param _z Vector of z coordinates of the nodes in the region.
+ */
+void Driver::setGlobMesh(size_t iSec, size_t iRegion, std::vector<double> _x, std::vector<double> _y, std::vector<double> _z)
+{
+    sections[iSec][iRegion]->setMesh(_x, _y, _z);
+}
+
+/**
+ * @brief Set the velocity at the nodes in the global frame of reference.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param vx Vector of velocities projected on the x axis of the global frame of reference.
+ * @param vy Vector of velocities projected on the y axis of the global frame of reference.
+ * @param vz Vector of velocities projected on the z axis of the global frame of reference.
+ */
+void Driver::setVelocities(size_t iSec, size_t iRegion, std::vector<double> vx, std::vector<double> vy, std::vector<double> vz)
+{
+    if (vx.size() != vy.size() || vy.size() != vz.size() || vx.size() != vz.size())
+        throw std::runtime_error("blast::Driver Wrong velocity vector sizes.");
+    if (vx.size() != sections[iSec][iRegion]->mesh->getnMarkers())
+        throw std::runtime_error("blast::Driver Velocity vector is not coherent with the mesh.");
+
+    std::vector<double> vt(vx.size(), 0.0);
+
+    for (size_t iPoint = 0; iPoint < vx.size() - 1; ++iPoint)
+    {
+        vt[iPoint] = vx[iPoint] * std::cos(sections[iSec][iRegion]->mesh->getAlpha(iPoint)) + vy[iPoint] * std::sin(sections[iSec][iRegion]->mesh->getAlpha(iPoint));
+        vt[iPoint + 1] = vx[iPoint + 1] * std::cos(sections[iSec][iRegion]->mesh->getAlpha(iPoint)) + vy[iPoint + 1] * std::sin(sections[iSec][iRegion]->mesh->getAlpha(iPoint));
+    }
+
+    sections[iSec][iRegion]->blEdge->setVt(vt);
+}
+
+/**
+ * @brief Set the Mach number at the nodes.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param _Me Vector of Mach numbers at the nodes.
+ */
+void Driver::setMe(size_t iSec, size_t iRegion, std::vector<double> _Me)
+{
+    if (_Me.size() != sections[iSec][iRegion]->mesh->getnMarkers())
+        throw std::runtime_error("blast::Driver Mach number vector is not coherent with the mesh.");
+    sections[iSec][iRegion]->blEdge->setMe(_Me);
+}
+
+/**
+ * @brief Set the density at the nodes.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param _Rhoe Vector of density at the nodes.
+ */
+void Driver::setRhoe(size_t iSec, size_t iRegion, std::vector<double> _Rhoe)
+{
+    if (_Rhoe.size() != sections[iSec][iRegion]->mesh->getnMarkers())
+        throw std::runtime_error("blast::Driver Density vector is not coherent with the mesh.");
+    sections[iSec][iRegion]->blEdge->setRhoe(_Rhoe);
+}
+
+/**
+ * @brief Set the nodes coordinates in the local frame of reference at the edge of the boundary layer.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param _xxExt Vector of coordinates of the nodes.
+ */
+void Driver::setxxExt(size_t iSec, size_t iRegion, std::vector<double> _xxExt)
+{
+    if (_xxExt.size() != sections[iSec][iRegion]->mesh->getnMarkers())
+        throw std::runtime_error("blast::Driver External mesh vector is not coherent with the mesh.");
+    sections[iSec][iRegion]->mesh->setExt(_xxExt);
+}
+
+/**
+ * @brief Set the displacement thickness (previous iteration).
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param _DeltaStarExt Vector of displacement thicknesses at the nodes.
+ */
+void Driver::setDeltaStarExt(size_t iSec, size_t iRegion, std::vector<double> _DeltaStarExt)
+{
+    if (_DeltaStarExt.size() != sections[iSec][iRegion]->mesh->getnMarkers())
+    {
+        std::cout << "size DeltaStar: " << _DeltaStarExt.size() << ". nMarkers: " << sections[iSec][iRegion]->mesh->getnMarkers() << std::endl;
+        throw std::runtime_error("blast::Driver External delta star vector is not coherent with the mesh.");
+    }
+    sections[iSec][iRegion]->blEdge->setDeltaStar(_DeltaStarExt);
+}
+
+/**
+ * @brief Returns x coordinates of the nodes in the local frame of reference in the considered region.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @return std::vector<double>
+ */
+std::vector<double> Driver::extractxCoord(size_t iSec, size_t iRegion) const
+{
+    std::vector<double> xCoord(sections[iSec][iRegion]->mesh->getnMarkers(), 0.);
+    for (size_t iPoint = 0; iPoint < xCoord.size(); ++iPoint)
+        xCoord[iPoint] = sections[iSec][iRegion]->mesh->getx(iPoint);
+    return xCoord;
+}
+
+/**
+ * @brief Returns blowing velocities (defined on the cells' cg) in the considered region.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @return std::vector<double>
+ */
+std::vector<double> Driver::extractBlowingVel(size_t iSec, size_t iRegion) const
+{
+    return sections[iSec][iRegion]->blowingVelocity;
+}
+std::vector<double> Driver::extractxx(size_t iSec, size_t iRegion) const
+{
+    std::vector<double> xx(sections[iSec][iRegion]->mesh->getnMarkers(), 0.);
+    for (size_t iPoint = 0; iPoint < xx.size(); ++iPoint)
+        xx[iPoint] = sections[iSec][iRegion]->mesh->getLoc(iPoint);
+    return xx;
+}
+
+/**
+ * @brief Returns the displacement thickness in the considered region.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @return std::vector<double>
+ */
+std::vector<double> Driver::extractDeltaStar(size_t iSec, size_t iRegion) const
+{
+    return sections[iSec][iRegion]->deltaStar;
+}
+
+/**
+ * @brief Returns the velocity of the interaction law in the considered region.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @return std::vector<double>
+ */
+std::vector<double> Driver::extractUe(size_t iSec, size_t iRegion) const
+{
+    std::vector<double> ueCurr(sections[iSec][iRegion]->mesh->getnMarkers(), 0.0);
+    for (size_t i = 0; i < ueCurr.size(); ++i)
+        ueCurr[i] = sections[iSec][iRegion]->u[i * sections[iSec][iRegion]->getnVar() + 3];
+    return ueCurr;
+}
+
+/**
+ * @brief Set the velocity at the edge of the BL at the previous iteration in the considered region.
+ *
+ * @param iSec id of the considered section.
+ * @param iRegion id of the considered region.
+ * @param _ue Velocity vector.
+ * @return std::vector<double>
+ */
+void Driver::setUeOld(size_t iSec, size_t iRegion, std::vector<double> _ue)
+{
+    if (_ue.size() != sections[iSec][iRegion]->mesh->getnMarkers())
+        throw std::runtime_error("blast::Driver External velocity vector is not coherent with the mesh.");
+    sections[iSec][iRegion]->blEdge->setVtOld(_ue);
+}
+
+/**
+ * @brief Returns the integral boundary layer solution in a section (sorted from upper Te to lower Te than wake).
+ *
+ * @param iSec id of the considered section.
+ * @return std::vector<std::vector<double>>
+ */
+std::vector<std::vector<double>> Driver::getSolution(size_t iSec)
+{
+    size_t nMarkersUp = sections[iSec][0]->mesh->getnMarkers();
+    size_t nMarkersLw = sections[iSec][1]->mesh->getnMarkers(); // No stagnation point doublon.
+    size_t nVar = sections[iSec][0]->getnVar();
+
+    std::vector<std::vector<double>> Sln(20);
+    for (size_t iVector = 0; iVector < Sln.size(); ++iVector)
+        Sln[iVector].resize(nMarkersUp + nMarkersLw + sections[iSec][2]->mesh->getnMarkers(), 0.);
+    // Upper side.
+    size_t revPoint = nMarkersUp - 1;
+    for (size_t iPoint = 0; iPoint < nMarkersUp; ++iPoint)
+    {
+        Sln[0][iPoint] = sections[iSec][0]->u[revPoint * nVar + 0];
+        Sln[1][iPoint] = sections[iSec][0]->u[revPoint * nVar + 1];
+        Sln[2][iPoint] = sections[iSec][0]->u[revPoint * nVar + 2];
+        Sln[3][iPoint] = sections[iSec][0]->u[revPoint * nVar + 3];
+        Sln[4][iPoint] = sections[iSec][0]->u[revPoint * nVar + 4];
+        Sln[5][iPoint] = sections[iSec][0]->deltaStar[revPoint];
+
+        Sln[6][iPoint] = sections[iSec][0]->Ret[revPoint];
+        Sln[7][iPoint] = sections[iSec][0]->cd[revPoint];
+        Sln[8][iPoint] = sections[iSec][0]->cf[revPoint];
+        Sln[9][iPoint] = sections[iSec][0]->Hstar[revPoint];
+        Sln[10][iPoint] = sections[iSec][0]->Hstar2[revPoint];
+        Sln[11][iPoint] = sections[iSec][0]->Hk[revPoint];
+        Sln[12][iPoint] = sections[iSec][0]->ctEq[revPoint];
+        Sln[13][iPoint] = sections[iSec][0]->us[revPoint];
+        Sln[14][iPoint] = sections[iSec][0]->delta[revPoint];
+
+        Sln[15][iPoint] = sections[iSec][0]->mesh->getx(revPoint);
+        Sln[16][iPoint] = sections[iSec][0]->mesh->getxoc(revPoint);
+        Sln[17][iPoint] = sections[iSec][0]->mesh->gety(revPoint);
+        Sln[18][iPoint] = sections[iSec][0]->mesh->getz(revPoint);
+        Sln[19][iPoint] = sections[iSec][0]->blEdge->getVt(revPoint);
+        --revPoint;
+    }
+    // Lower side.
+    for (size_t iPoint = 0; iPoint < sections[iSec][1]->mesh->getnMarkers(); ++iPoint)
+    {
+        Sln[0][nMarkersUp + iPoint] = sections[iSec][1]->u[iPoint * nVar + 0];
+        Sln[1][nMarkersUp + iPoint] = sections[iSec][1]->u[iPoint * nVar + 1];
+        Sln[2][nMarkersUp + iPoint] = sections[iSec][1]->u[iPoint * nVar + 2];
+        Sln[3][nMarkersUp + iPoint] = sections[iSec][1]->u[iPoint * nVar + 3];
+        Sln[4][nMarkersUp + iPoint] = sections[iSec][1]->u[iPoint * nVar + 4];
+        Sln[5][nMarkersUp + iPoint] = sections[iSec][1]->deltaStar[iPoint];
+
+        Sln[6][nMarkersUp + iPoint] = sections[iSec][1]->Ret[iPoint];
+        Sln[7][nMarkersUp + iPoint] = sections[iSec][1]->cd[iPoint];
+        Sln[8][nMarkersUp + iPoint] = sections[iSec][1]->cf[iPoint];
+        Sln[9][nMarkersUp + iPoint] = sections[iSec][1]->Hstar[iPoint];
+        Sln[10][nMarkersUp + iPoint] = sections[iSec][1]->Hstar2[iPoint];
+        Sln[11][nMarkersUp + iPoint] = sections[iSec][1]->Hk[iPoint];
+        Sln[12][nMarkersUp + iPoint] = sections[iSec][1]->ctEq[iPoint];
+        Sln[13][nMarkersUp + iPoint] = sections[iSec][1]->us[iPoint];
+        Sln[14][nMarkersUp + iPoint] = sections[iSec][1]->delta[iPoint];
+
+        Sln[15][nMarkersUp + iPoint] = sections[iSec][1]->mesh->getx(iPoint);
+        Sln[16][nMarkersUp + iPoint] = sections[iSec][1]->mesh->getxoc(iPoint);
+        Sln[17][nMarkersUp + iPoint] = sections[iSec][1]->mesh->gety(iPoint);
+        Sln[18][nMarkersUp + iPoint] = sections[iSec][1]->mesh->getz(iPoint);
+        Sln[19][nMarkersUp + iPoint] = sections[iSec][1]->blEdge->getVt(iPoint);
+    }
+    // Wake.
+    for (size_t iPoint = 0; iPoint < sections[iSec][2]->mesh->getnMarkers(); ++iPoint)
+    {
+        Sln[0][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->u[iPoint * nVar + 0];
+        Sln[1][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->u[iPoint * nVar + 1];
+        Sln[2][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->u[iPoint * nVar + 2];
+        Sln[3][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->u[iPoint * nVar + 3];
+        Sln[4][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->u[iPoint * nVar + 4];
+        Sln[5][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->deltaStar[iPoint];
+
+        Sln[6][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->Ret[iPoint];
+        Sln[7][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->cd[iPoint];
+        Sln[8][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->cf[iPoint];
+        Sln[9][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->Hstar[iPoint];
+        Sln[10][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->Hstar2[iPoint];
+        Sln[11][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->Hk[iPoint];
+        Sln[12][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->ctEq[iPoint];
+        Sln[13][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->us[iPoint];
+        Sln[14][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->delta[iPoint];
+
+        Sln[15][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->mesh->getx(iPoint);
+        Sln[16][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->mesh->getxoc(iPoint);
+        Sln[17][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->mesh->gety(iPoint);
+        Sln[18][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->mesh->getz(iPoint);
+        Sln[19][nMarkersUp + nMarkersLw + iPoint] = sections[iSec][2]->blEdge->getVt(iPoint);
+    }
+
+    if (verbose > 2)
+        std::cout << "Solution structure sent." << std::endl;
+    return Sln;
+}
\ No newline at end of file
diff --git a/blast/src/DDriver.h b/blast/src/DDriver.h
new file mode 100644
index 0000000..a954586
--- /dev/null
+++ b/blast/src/DDriver.h
@@ -0,0 +1,76 @@
+#ifndef DDRIVER_H
+#define DDRIVER_H
+
+#include "blast.h"
+#include "wObject.h"
+#include "DBoundaryLayer.h"
+#include "wTimers.h"
+namespace blast
+{
+
+/**
+ * @brief Boundary layer solver driver. Performs the space marching and set up time marching for each point.
+ * @authors Paul Dechamps
+ */
+class BLAST_API Driver : public fwk::wSharedObject
+{
+public:
+    double Cdt;                                         ///< Total drag coefficient.
+    double Cdf;                                         ///< Total friction drag coefficient.
+    double Cdp;                                         ///< Total pressure drag coefficient.
+    std::vector<double> Cdt_sec;                        ///< Sectional total drag coefficient.
+    std::vector<double> Cdf_sec;                        ///< Sectional friction drag coefficient.
+    std::vector<double> Cdp_sec;                        ///< Sectional pressure drag coefficient.
+    std::vector<std::vector<BoundaryLayer *>> sections; ///< Boundary layer regions sorted by section.
+    unsigned int verbose;                               ///< Verbosity level of the solver 0<=verbose<=1.
+
+private:
+    double Re;                                                       ///< Freestream Reynolds number.
+    double CFL0;                                                     ///< Initial CFL number for pseudo time integration.
+    double span;                                                     ///< Wing Span (Used only for drag computation, not used if 2D case).
+    std::vector<bool> stagPtMvmt;                                    ///< Flag to account for stagnation point movements.
+    Solver *tSolver;                                                 ///< Pseudo-time solver.
+    int solverExitCode;                                              ///< Exit code of viscous calculations.
+    std::vector<std::vector<std::vector<size_t>>> convergenceStatus; ///< Vector containing points that did not converged.
+
+protected:
+    fwk::Timers tms; ///< Internal timers
+
+public:
+    Driver(double _Re, double _Minf, double _CFL0, size_t nSections,
+           double xtrF_top=-1., double xtrF_bot=-1.,  double _span = 0., unsigned int _verbose = 1);
+    ~Driver();
+    int run(unsigned int const couplIter);
+
+    // Getters.
+    double getxtr(size_t iSec, size_t iRegion) const { return sections[iSec][iRegion]->xtr; };
+    double getxoctr(size_t iSec, size_t iRegion) const { return sections[iSec][iRegion]->xoctr; };
+    double getRe() const { return Re; };
+    std::vector<std::vector<double>> getSolution(size_t iSec);
+
+    // Setters.
+    void setGlobMesh(size_t iSec, size_t iRegion, std::vector<double> _x, std::vector<double> _y, std::vector<double> _z);
+    void setVelocities(size_t iSec, size_t iRegion, std::vector<double> _vx, std::vector<double> _vy, std::vector<double> _vz);
+    void setMe(size_t iSec, size_t iRegion, std::vector<double> _Me);
+    void setRhoe(size_t iSec, size_t iRegion, std::vector<double> _rhoe);
+    void setxxExt(size_t iSec, size_t iRegion, std::vector<double> _xxExt);
+    void setDeltaStarExt(size_t iSec, size_t iRegion, std::vector<double> _deltaStarExt);
+    void setUeOld(size_t iSec, size_t iRegion, std::vector<double> _ue);
+
+    // Extractors.
+    std::vector<double> extractBlowingVel(size_t iSec, size_t iRegion) const;
+    std::vector<double> extractxx(size_t iSec, size_t iRegion) const;
+    std::vector<double> extractDeltaStar(size_t iSec, size_t iRegion) const;
+    std::vector<double> extractxCoord(size_t iSec, size_t iRegion) const;
+    std::vector<double> extractUe(size_t iSec, size_t iRegion) const;
+
+private:
+    void checkWaves(size_t iPoint, BoundaryLayer *bl);
+    void averageTransition(size_t iPoint, BoundaryLayer *bl, int forced);
+    void computeSectionalDrag(std::vector<BoundaryLayer *> const &bl, size_t i);
+    void computeTotalDrag();
+    void computeBlwVel();
+    int outputStatus(double maxMach) const;
+};
+} // namespace blast
+#endif // DDRIVER_H
diff --git a/blast/src/DEdge.cpp b/blast/src/DEdge.cpp
new file mode 100644
index 0000000..762d63c
--- /dev/null
+++ b/blast/src/DEdge.cpp
@@ -0,0 +1,59 @@
+#include "DEdge.h"
+
+using namespace blast;
+
+/**
+ * @brief Construct a new Edge::Edge object.
+ *
+ */
+Edge::Edge()
+{
+    Me.resize(0, 0.);
+    vt.resize(0, 0.);
+    rhoe.resize(0, 0.);
+    deltaStar.resize(0, 0.);
+    vtOld.resize(0, 0.);
+}
+
+/**
+ * @brief Return the maximum inviscid Mach number in the region.
+ *
+ * @return double
+ */
+double Edge::getMaxMach() const
+{
+    if (Me.size() <= 0)
+        throw std::runtime_error("blast::Edge Invalid Mach number vector.");
+    double Mmax = 0.0;
+    for (size_t i = 0; i < Me.size(); ++i)
+        if (Me[i] > Mmax)
+            Mmax = Me[i];
+    return Mmax;
+}
+
+// Setters.
+void Edge::setMe(std::vector<double> const _Me)
+{
+    Me.resize(_Me.size(), 0.);
+    Me = _Me;
+}
+void Edge::setVt(std::vector<double> const _vt)
+{
+    vt.resize(_vt.size(), 0.);
+    vt = _vt;
+}
+void Edge::setRhoe(std::vector<double> const _rhoe)
+{
+    rhoe.resize(_rhoe.size(), 0.);
+    rhoe = _rhoe;
+}
+void Edge::setDeltaStar(std::vector<double> const _deltaStar)
+{
+    deltaStar.resize(_deltaStar.size(), 0.);
+    deltaStar = _deltaStar;
+}
+void Edge::setVtOld(std::vector<double> const _vtOld)
+{
+    vtOld.resize(_vtOld.size(), 0.);
+    vtOld = _vtOld;
+}
diff --git a/blast/src/DEdge.h b/blast/src/DEdge.h
new file mode 100644
index 0000000..0833564
--- /dev/null
+++ b/blast/src/DEdge.h
@@ -0,0 +1,44 @@
+#ifndef DEDGE_H
+#define DEDGE_H
+
+#include "blast.h"
+#include <vector>
+#include <iostream>
+
+namespace blast
+{
+
+/**
+ * @brief Inviscid quantities at the edge of the boundary layer of a BoundaryLayer region.
+ * @author Paul Dechamps
+ */
+class BLAST_API Edge
+{
+private:
+    std::vector<double> Me;        ///< Mach number.
+    std::vector<double> vt;        ///< Velocity.
+    std::vector<double> rhoe;      ///< Density.
+    std::vector<double> deltaStar; ///< Displacement thickness.
+    std::vector<double> vtOld;     ///< Previous velocity.
+
+public:
+    Edge();
+    ~Edge(){};
+
+    // Getters.
+    double getMe(size_t iPoint) const { return Me[iPoint]; };
+    double getVt(size_t iPoint) const { return vt[iPoint]; };
+    double getRhoe(size_t iPoint) const { return rhoe[iPoint]; };
+    double getDeltaStar(size_t iPoint) const { return deltaStar[iPoint]; };
+    double getVtOld(size_t iPoint) const { return vtOld[iPoint]; };
+    double getMaxMach() const;
+
+    // Setters.
+    void setMe(std::vector<double> const _Me);
+    void setVt(std::vector<double> const _vt);
+    void setRhoe(std::vector<double> const _rhoe);
+    void setDeltaStar(std::vector<double> const _deltaStar);
+    void setVtOld(std::vector<double> const _vtOld);
+};
+} // namespace blast
+#endif // DEDGE_H
diff --git a/blast/src/DFluxes.cpp b/blast/src/DFluxes.cpp
new file mode 100644
index 0000000..c3beca0
--- /dev/null
+++ b/blast/src/DFluxes.cpp
@@ -0,0 +1,243 @@
+#include "DFluxes.h"
+#include "DBoundaryLayer.h"
+#include <Eigen/Dense>
+
+using namespace blast;
+using namespace Eigen;
+
+/**
+ * @brief Compute residual vector of the integral boundary layer equations.
+ *
+ * @param iPoint Marker id.
+ * @param bl Boundary layer region.
+ * @return VectorXd
+ */
+VectorXd Fluxes::computeResiduals(size_t iPoint, BoundaryLayer *bl)
+{
+    size_t nVar = bl->getnVar();
+    std::vector<double> u(nVar, 0.);
+    for (size_t k = 0; k < nVar; ++k)
+        u[k] = bl->u[iPoint * nVar + k];
+    return blLaws(iPoint, bl, u);
+}
+
+/**
+ * @brief Compute Jacobian of the integral boundary layer equations.
+ *
+ * @param iPoint Marker id.
+ * @param bl Boundary layer region.
+ * @param sysRes Residual of the IBL at point iPoint.
+ * @param eps Pertubation from current solution used to compute the Jacobian.
+ * @return MatrixXd
+ */
+MatrixXd Fluxes::computeJacobian(size_t iPoint, BoundaryLayer *bl, VectorXd const &sysRes, double eps)
+{
+    size_t nVar = bl->getnVar();
+    MatrixXd JacMatrix = MatrixXd::Zero(5, 5);
+    std::vector<double> uUp(nVar, 0.);
+    for (size_t k = 0; k < nVar; ++k)
+        uUp[k] = bl->u[iPoint * nVar + k];
+
+    double varSave = 0.;
+    for (size_t iVar = 0; iVar < nVar; ++iVar)
+    {
+        varSave = uUp[iVar];
+        uUp[iVar] += eps;
+        JacMatrix.col(iVar) = (blLaws(iPoint, bl, uUp) - sysRes) / eps;
+        uUp[iVar] = varSave;
+    }
+    return JacMatrix;
+}
+
+/**
+ * @brief Integral boundary layer equation.
+ *
+ * @param iPoint Marker id.
+ * @param bl Boundary layer region.
+ * @param u Solution vector at point iPoint.
+ * @return VectorXd
+ */
+VectorXd Fluxes::blLaws(size_t iPoint, BoundaryLayer *bl, std::vector<double> u)
+{
+    size_t nVar = bl->getnVar();
+
+    MatrixXd timeMatrix = MatrixXd::Zero(5, 5);
+    VectorXd spaceVector = VectorXd::Zero(5);
+
+    // Dissipation ratio.
+    double dissipRatio;
+    if (bl->name == "wake")
+        dissipRatio = 0.9;
+    else
+        dissipRatio = 1.;
+
+    bl->Ret[iPoint] = std::max(u[3] * u[0] * Re, 1.0); // Reynolds number based on theta.
+    bl->deltaStar[iPoint] = u[0] * u[1];               // Displacement thickness.
+
+    // Boundary layer closure relations.
+    if (bl->regime[iPoint] == 0)
+    {
+        // Laminar closure relations.
+        std::vector<double> lamParam(8, 0.);
+        bl->closSolver->laminarClosures(lamParam, u[0], u[1], bl->Ret[iPoint], bl->blEdge->getMe(iPoint), bl->name);
+        bl->Hstar[iPoint] = lamParam[0];
+        bl->Hstar2[iPoint] = lamParam[1];
+        bl->Hk[iPoint] = lamParam[2];
+        bl->cf[iPoint] = lamParam[3];
+        bl->cd[iPoint] = lamParam[4];
+        bl->delta[iPoint] = lamParam[5];
+        bl->ctEq[iPoint] = lamParam[6];
+        bl->us[iPoint] = lamParam[7];
+    }
+
+    else if (bl->regime[iPoint] == 1)
+    {
+        // Turbulent closure relations.
+        std::vector<double> turbParam(8, 0.);
+        bl->closSolver->turbulentClosures(turbParam, u[0], u[1], u[4], bl->Ret[iPoint], bl->blEdge->getMe(iPoint), bl->name);
+        bl->Hstar[iPoint] = turbParam[0];
+        bl->Hstar2[iPoint] = turbParam[1];
+        bl->Hk[iPoint] = turbParam[2];
+        bl->cf[iPoint] = turbParam[3];
+        bl->cd[iPoint] = turbParam[4];
+        bl->delta[iPoint] = turbParam[5];
+        bl->ctEq[iPoint] = turbParam[6];
+        bl->us[iPoint] = turbParam[7];
+    }
+    else
+    {
+        std::cout << "Wrong regime\n"
+                  << std::endl;
+        throw;
+    }
+
+    // Gradients computation.
+    double dx = (bl->mesh->getLoc(iPoint) - bl->mesh->getLoc(iPoint - 1));
+    double dxExt = (bl->mesh->getExt(iPoint) - bl->mesh->getExt(iPoint - 1));
+    double dt_dx = (u[0] - bl->u[(iPoint - 1) * nVar + 0]) / dx;
+    double dH_dx = (u[1] - bl->u[(iPoint - 1) * nVar + 1]) / dx;
+    double due_dx = (u[3] - bl->u[(iPoint - 1) * nVar + 3]) / dx;
+    double dct_dx = (u[4] - bl->u[(iPoint - 1) * nVar + 4]) / dx;
+    double dHstar_dx = (bl->Hstar[iPoint] - bl->Hstar[iPoint - 1]) / dx;
+
+    double dueExt_dx = (bl->blEdge->getVt(iPoint) - bl->blEdge->getVt(iPoint - 1)) / dxExt;
+    double ddeltaStarExt_dx = (bl->blEdge->getDeltaStar(iPoint) - bl->blEdge->getDeltaStar(iPoint - 1)) / dxExt;
+
+    double c = 4 / (M_PI * dx);
+    double cExt = 4 / (M_PI * dxExt);
+
+    // Amplification routine.
+    double dN_dx = 0.0;
+    double ax = 0.0;
+    if (bl->xtrF == -1.0 && bl->regime[iPoint] == 0)
+    {
+        // No forced transition and laminar regime.
+        ax = amplificationRoutine(bl->Hk[iPoint], bl->Ret[iPoint], u[0]);
+        dN_dx = (u[2] - bl->u[(iPoint - 1) * nVar + 2]) / dx;
+    }
+
+    double Mea = bl->blEdge->getMe(iPoint);
+    double Hstara = bl->Hstar[iPoint];
+    double Hstar2a = bl->Hstar2[iPoint];
+    double Hka = bl->Hk[iPoint];
+    double cfa = bl->cf[iPoint];
+    double cda = bl->cd[iPoint];
+    double deltaa = bl->delta[iPoint];
+    double ctEqa = bl->ctEq[iPoint];
+    double usa = bl->us[iPoint];
+
+    // Space part.
+    spaceVector(0) = dt_dx + (2 + u[1] - Mea * Mea) * (u[0] / u[3]) * due_dx - cfa / 2;
+    spaceVector(1) = u[0] * dHstar_dx + (2 * Hstar2a + Hstara * (1 - u[1])) * u[0] / u[3] * due_dx - 2 * cda + Hstara * cfa / 2;
+    spaceVector(2) = dN_dx - ax;
+    spaceVector(3) = due_dx - c * (u[1] * dt_dx + u[0] * dH_dx) - dueExt_dx + cExt * ddeltaStarExt_dx;
+
+    if (bl->regime[iPoint] == 1)
+        spaceVector(4) = (2 * deltaa / u[4]) * dct_dx - 5.6 * (ctEqa - u[4] * dissipRatio) - 2 * deltaa * (4 / (3 * u[1] * u[0]) * (cfa / 2 - (((Hka - 1) / (6.7 * Hka * dissipRatio)) * ((Hka - 1) / (6.7 * Hka * dissipRatio)))) - 1 / u[3] * due_dx);
+
+    // Time part.
+    timeMatrix(0, 0) = u[1] / u[3];
+    timeMatrix(0, 1) = u[0] / u[3];
+    timeMatrix(0, 3) = u[0] * u[1] / (u[3] * u[3]);
+
+    timeMatrix(1, 0) = (1 + u[1] * (1 - Hstara)) / u[3];
+    timeMatrix(1, 1) = (1 - Hstara) * u[0] / u[3];
+    timeMatrix(1, 3) = (2 - Hstara * u[1]) * u[0] / (u[3] * u[3]);
+
+    timeMatrix(2, 2) = 1.;
+
+    timeMatrix(3, 0) = -c * u[1];
+    timeMatrix(3, 1) = -c * u[0];
+    timeMatrix(3, 3) = 1.;
+
+    if (bl->regime[iPoint] == 1)
+    {
+        timeMatrix(4, 3) = 2 * deltaa / (usa * u[3] * u[3]);
+        timeMatrix(4, 4) = 2 * deltaa / (u[3] * u[4] * usa);
+    }
+    else
+        timeMatrix(4, 4) = 1.0;
+
+    return timeMatrix.partialPivLu().solve(spaceVector);
+}
+
+/**
+ * @brief Amplification routines of the e^N method for transition capturing.
+ *
+ * @param Hk Kinematic shape parameter.
+ * @param Ret Reynolds number based on the momentum thickness.
+ * @param theta Momentum thickness.
+ * @return double
+ */
+double Fluxes::amplificationRoutine(double Hk, double Ret, double theta) const
+{
+    double dgr = 0.08;
+    double Hk1 = 3.5;
+    double Hk2 = 4.;
+    double Hmi = (1 / (Hk - 1));
+    double logRet_crit = 2.492 * std::pow(1 / (Hk - 1.), 0.43) + 0.7 * (std::tanh(14 * Hmi - 9.24) + 1.0); // value at which the amplification starts to grow
+
+    double ax = 0.;
+    if (Ret <= 0.)
+        Ret = 1.;
+    if (std::log10(Ret) < logRet_crit - dgr)
+        ax = 0.;
+    else
+    {
+        double rNorm = (std::log10(Ret) - (logRet_crit - dgr)) / (2 * dgr);
+        double Rfac = 0.;
+        if (rNorm <= 0)
+            Rfac = 0.;
+        if (rNorm >= 1)
+            Rfac = 1.;
+        else
+            Rfac = 3 * rNorm * rNorm - 2 * rNorm * rNorm * rNorm;
+
+        // Normal envelope amplification rate
+        double m = -0.05 + 2.7 * Hmi - 5.5 * Hmi * Hmi + 3 * Hmi * Hmi * Hmi + 0.1 * std::exp(-20 * Hmi);
+        double arg = 3.87 * Hmi - 2.52;
+        double dndRet = 0.028 * (Hk - 1) - 0.0345 * std::exp(-(arg * arg));
+        ax = (m * dndRet / theta) * Rfac;
+
+        // Set correction for separated profiles
+        if (Hk > Hk1)
+        {
+            double Hnorm = (Hk - Hk1) / (Hk2 - Hk1);
+            double Hfac = 0.;
+            if (Hnorm >= 1)
+                Hfac = 1.;
+            else
+                Hfac = 3 * Hnorm * Hnorm - 2 * Hnorm * Hnorm * Hnorm;
+            double ax1 = ax;
+            double gro = 0.3 + 0.35 * std::exp(-0.15 * (Hk - 5));
+            double tnr = std::tanh(1.2 * (std::log10(Ret) - gro));
+            double ax2 = (0.086 * tnr - 0.25 / std::pow((Hk - 1), 1.5)) / theta;
+            if (ax2 < 0.)
+                ax2 = 0.;
+            ax = Hfac * ax2 + (1 - Hfac) * ax1;
+            if (ax < 0.)
+                ax = 0.;
+        }
+    }
+    return ax;
+}
\ No newline at end of file
diff --git a/blast/src/DFluxes.h b/blast/src/DFluxes.h
new file mode 100644
index 0000000..7d88b21
--- /dev/null
+++ b/blast/src/DFluxes.h
@@ -0,0 +1,31 @@
+#ifndef DFLUXES_H
+#define DFLUXES_H
+
+#include "blast.h"
+#include "DBoundaryLayer.h"
+#include <Eigen/Dense>
+
+namespace blast
+{
+
+/**
+ * @brief Residual and Jacobian computation of the unsteady integral boundary layer equations.
+ * @author Paul Dechamps
+ */
+class BLAST_API Fluxes
+{
+public:
+    double Re; ///< Freestream Reynolds number.
+
+public:
+    Fluxes(double _Re) : Re(_Re){};
+    ~Fluxes(){};
+    Eigen::VectorXd computeResiduals(size_t iPoint, BoundaryLayer *bl);
+    Eigen::MatrixXd computeJacobian(size_t iPoint, BoundaryLayer *bl, Eigen::VectorXd const &sysRes, double eps);
+
+private:
+    Eigen::VectorXd blLaws(size_t iPoint, BoundaryLayer *bl, std::vector<double> u);
+    double amplificationRoutine(double Hk, double Ret, double theta) const;
+};
+} // namespace blast
+#endif // DFLUXES_H
diff --git a/blast/src/DSolver.cpp b/blast/src/DSolver.cpp
new file mode 100644
index 0000000..fc3b94d
--- /dev/null
+++ b/blast/src/DSolver.cpp
@@ -0,0 +1,247 @@
+#include "DSolver.h"
+#include "DBoundaryLayer.h"
+#include "DFluxes.h"
+#include <Eigen/Dense>
+
+using namespace Eigen;
+using namespace tbox;
+using namespace blast;
+
+/**
+ * @brief Construct a new Time Solver:: Time Solver object.
+ *
+ * @param _CFL0 Initial CFL number.
+ * @param _Minf Freestream Mach number.
+ * @param Re Freestream Reynolds number.
+ * @param _maxIt Maximum number of iterations.
+ * @param _tol Convergence tolerance.
+ * @param _itFrozenJac Number of iterations between which the Jacobian is frozen.
+ */
+Solver::Solver(double _CFL0, double _Minf, double Re, unsigned int _maxIt, double _tol, unsigned int _itFrozenJac)
+{
+    CFL0 = _CFL0;
+    maxIt = _maxIt;
+    tol = _tol;
+    itFrozenJac0 = _itFrozenJac;
+    Minf = std::max(_Minf, 0.1);
+    initSln = true;
+    sysMatrix = new Fluxes(Re);
+}
+
+/**
+ * @brief Destroy the Time Solver:: Time Solver object.
+ *
+ */
+Solver::~Solver()
+{
+    delete sysMatrix;
+    std::cout << "~blast::Solver()\n";
+}
+
+/**
+ * @brief Impose initial condition at one point.
+ *
+ * @param iPoint Marker id.
+ * @param bl Boundary layer region.
+ */
+void Solver::initialCondition(size_t iPoint, BoundaryLayer *bl)
+{
+    size_t nVar = bl->getnVar();
+    if (initSln)
+    {
+        bl->u[iPoint * nVar + 0] = bl->u[(iPoint - 1) * nVar + 0];
+        bl->u[iPoint * nVar + 1] = bl->u[(iPoint - 1) * nVar + 1];
+        bl->u[iPoint * nVar + 2] = bl->u[(iPoint - 1) * nVar + 2];
+        bl->u[iPoint * nVar + 3] = bl->blEdge->getVt(iPoint);
+        bl->u[iPoint * nVar + 4] = bl->u[(iPoint - 1) * nVar + 4];
+
+        bl->Ret[iPoint] = bl->Ret[iPoint - 1];
+        bl->cd[iPoint] = bl->cd[iPoint - 1];
+        bl->cf[iPoint] = bl->cf[iPoint - 1];
+        bl->Hstar[iPoint] = bl->Hstar[iPoint - 1];
+        bl->Hstar2[iPoint] = bl->Hstar2[iPoint - 1];
+        bl->Hk[iPoint] = bl->Hk[iPoint - 1];
+        bl->ctEq[iPoint] = bl->ctEq[iPoint - 1];
+        bl->us[iPoint] = bl->us[iPoint - 1];
+        bl->delta[iPoint] = bl->delta[iPoint - 1];
+        bl->deltaStar[iPoint] = bl->deltaStar[iPoint - 1];
+    }
+    if (bl->regime[iPoint] == 1 && bl->u[iPoint * nVar + 4] <= 0)
+        bl->u[iPoint * nVar + 4] = 0.03;
+}
+
+/**
+ * @brief Pseudo-time integration.
+ *
+ * @param iPoint Marker id.
+ * @param bl Boundary layer region.
+ * @return int
+ */
+int Solver::integration(size_t iPoint, BoundaryLayer *bl)
+{
+    size_t nVar = bl->getnVar();
+
+    // Save initial condition.
+    std::vector<double> sln0(nVar, 0.);
+    for (size_t i = 0; i < nVar; ++i)
+        sln0[i] = bl->u[iPoint * nVar + i];
+
+    // Initialize time integration variables.
+    double dx = bl->mesh->getLoc(iPoint) - bl->mesh->getLoc(iPoint - 1);
+
+    // Initial time step.
+    double CFL = CFL0;
+    unsigned int itFrozenJac = itFrozenJac0;
+    double timeStep = setTimeStep(CFL, dx, bl->blEdge->getVt(iPoint));
+    MatrixXd IMatrix(5, 5);
+    IMatrix = MatrixXd::Identity(5, 5) / timeStep;
+
+    // Initial system residuals.
+    VectorXd sysRes = sysMatrix->computeResiduals(iPoint, bl);
+    double normSysRes0 = sysRes.norm();
+    double normSysRes = normSysRes0;
+
+    MatrixXd JacMatrix = MatrixXd::Zero(5, 5);
+    VectorXd slnIncr = VectorXd::Zero(5);
+
+    unsigned int innerIters = 0; // Inner (non-linear) iterations
+
+    while (innerIters < maxIt)
+    {
+        // Jacobian computation.
+        if (innerIters % itFrozenJac == 0)
+            JacMatrix = sysMatrix->computeJacobian(iPoint, bl, sysRes, 1e-8);
+
+        // Linear system.
+        slnIncr = (JacMatrix + IMatrix).partialPivLu().solve(-sysRes);
+
+        // Solution increment.
+        for (size_t k = 0; k < nVar; ++k)
+            bl->u[iPoint * nVar + k] += slnIncr(k);
+        bl->u[iPoint * nVar + 0] = std::max(bl->u[iPoint * nVar + 0], 1e-6);
+        bl->u[iPoint * nVar + 1] = std::max(bl->u[iPoint * nVar + 1], 1.0005);
+
+        sysRes = sysMatrix->computeResiduals(iPoint, bl);
+        normSysRes = (sysRes + slnIncr / timeStep).norm();
+
+        if (std::isnan(normSysRes))
+        {
+            resetSolution(iPoint, bl, sln0, true);
+            sysRes = sysMatrix->computeResiduals(iPoint, bl);
+            CFL = 1.0;
+            timeStep = setTimeStep(CFL, dx, bl->blEdge->getVt(iPoint));
+            IMatrix = MatrixXd::Identity(5, 5) / timeStep;
+            normSysRes = (sysRes + slnIncr / timeStep).norm();
+            itFrozenJac = 1;
+        }
+
+        if (normSysRes <= tol * normSysRes0)
+            return 0; // Successfull exit.
+
+        // CFL adaptation.
+        CFL = std::max(CFL0 * pow(normSysRes0 / normSysRes, 0.7), 0.1);
+        timeStep = setTimeStep(CFL, dx, bl->blEdge->getVt(iPoint));
+        IMatrix = MatrixXd::Identity(5, 5) / timeStep;
+
+        innerIters++;
+    }
+
+    if (std::isnan(normSysRes) || normSysRes / normSysRes0 > 1e-3)
+    {
+        resetSolution(iPoint, bl, sln0);
+        return -1;
+    }
+    return innerIters;
+}
+
+/**
+ * @brief Set time step.
+ *
+ * @param CFL CFL number.
+ * @param dx Cell size.
+ * @param invVel Inviscid velocity.
+ * @return double
+ */
+double Solver::setTimeStep(double CFL, double dx, double invVel) const
+{
+    // Speed of sound.
+    double gradU2 = (invVel * invVel);
+    double soundSpeed = computeSoundSpeed(gradU2);
+
+    // Velocity of the fastest travelling wave.
+    double advVel = soundSpeed + invVel;
+
+    // Time step computation.
+    return CFL * dx / advVel;
+}
+
+/**
+ * @brief Compute the speed of sound in a cell.
+ *
+ * @param gradU2 Inviscid velocity squared in the cell.
+ * @return double
+ */
+double Solver::computeSoundSpeed(double gradU2) const
+{
+    return sqrt(1 / (Minf * Minf) + 0.5 * (gamma - 1) * (1 - gradU2));
+}
+
+/**
+ * @brief Resets the solution of the point wrt its initial condition (sln0) or the previous point.
+ *
+ * @param iPoint Marker id.
+ * @param bl Boundary layer region.
+ * @param sln0 Initial solution at the point.
+ * @param usePrevPoint if 1, use the solution at previous point instead of sln0.
+ */
+void Solver::resetSolution(size_t iPoint, BoundaryLayer *bl, std::vector<double> sln0, bool usePrevPoint)
+{
+    size_t nVar = bl->getnVar();
+
+    // Reset solution.
+    if (usePrevPoint)
+        for (size_t k = 0; k < nVar; ++k)
+            bl->u[iPoint * nVar + k] = bl->u[(iPoint - 1) * nVar + k];
+    else
+        for (size_t k = 0; k < nVar; ++k)
+            bl->u[iPoint * nVar + k] = sln0[k];
+
+    // Reset closures.
+    bl->Ret[iPoint] = std::max(bl->u[iPoint * nVar + 3] * bl->u[iPoint * nVar + 0] * sysMatrix->Re, 1.0); // Reynolds number based on theta.
+    bl->deltaStar[iPoint] = bl->u[iPoint * nVar + 0] * bl->u[iPoint * nVar + 1];                          // Displacement thickness.
+
+    if (bl->regime[iPoint] == 0)
+    {
+        std::vector<double> lamParam(8, 0.);
+        bl->closSolver->laminarClosures(lamParam, bl->u[iPoint * nVar + 0], bl->u[iPoint * nVar + 1], bl->Ret[iPoint], bl->blEdge->getMe(iPoint), bl->name);
+        bl->Hstar[iPoint] = lamParam[0];
+        bl->Hstar2[iPoint] = lamParam[1];
+        bl->Hk[iPoint] = lamParam[2];
+        bl->cf[iPoint] = lamParam[3];
+        bl->cd[iPoint] = lamParam[4];
+        bl->delta[iPoint] = lamParam[5];
+        bl->ctEq[iPoint] = lamParam[6];
+        bl->us[iPoint] = lamParam[7];
+    }
+    else if (bl->regime[iPoint] == 1)
+    {
+        std::vector<double> turbParam(8, 0.);
+        bl->closSolver->turbulentClosures(turbParam, bl->u[iPoint * nVar + 0], bl->u[iPoint * nVar + 1], bl->u[iPoint * nVar + 4], bl->Ret[iPoint], bl->blEdge->getMe(iPoint), bl->name);
+        bl->Hstar[iPoint] = turbParam[0];
+        bl->Hstar2[iPoint] = turbParam[1];
+        bl->Hk[iPoint] = turbParam[2];
+        bl->cf[iPoint] = turbParam[3];
+        bl->cd[iPoint] = turbParam[4];
+        bl->delta[iPoint] = turbParam[5];
+        bl->ctEq[iPoint] = turbParam[6];
+        bl->us[iPoint] = turbParam[7];
+    }
+}
+
+void Solver::setCFL0(double _CFL0)
+{
+    if (_CFL0 > 0)
+        CFL0 = _CFL0;
+    else
+        throw std::runtime_error("Negative CFL numbers are not allowed.\n");
+}
\ No newline at end of file
diff --git a/blast/src/DSolver.h b/blast/src/DSolver.h
new file mode 100644
index 0000000..f6ce735
--- /dev/null
+++ b/blast/src/DSolver.h
@@ -0,0 +1,51 @@
+#ifndef DSOLVER_H
+#define DSOLVER_H
+
+#include "blast.h"
+#include "DFluxes.h"
+
+namespace blast
+{
+
+/**
+ * @brief Pseudo-time integration.
+ * @author Paul Dechamps
+ */
+class BLAST_API Solver
+{
+
+private:
+    double CFL0;               ///< Initial CFL number.
+    unsigned int maxIt;        ///< Maximum number of iterations.
+    double tol;                ///< Convergence tolerance.
+    unsigned int itFrozenJac0; ///< Number of iterations between which the Jacobian is frozen.
+    bool initSln;              ///< Flag to initialize the solution at the point.
+    double const gamma = 1.4;  ///< Air heat capacity ratio.
+    double Minf;               ///< Freestream Mach number.
+
+    Fluxes *sysMatrix;
+
+public:
+    Solver(double _CFL0, double _Minf, double Re, unsigned int _maxIt = 100, double _tol = 1e-8, unsigned int _itFrozenJac = 1);
+    ~Solver();
+    void initialCondition(size_t iPoint, BoundaryLayer *bl);
+    int integration(size_t iPoint, BoundaryLayer *bl);
+
+    // Getters.
+    double getCFL0() const { return CFL0; };
+    unsigned int getmaxIt() const { return maxIt; };
+    unsigned int getitFrozenJac() const { return itFrozenJac0; };
+
+    // Setters.
+    void setCFL0(double _CFL0);
+    void setmaxIt(unsigned int _maxIt) { maxIt = _maxIt; };
+    void setitFrozenJac(unsigned int _itFrozenJac) { itFrozenJac0 = _itFrozenJac; };
+    void setinitSln(bool _initSln) { initSln = _initSln; };
+
+private:
+    double setTimeStep(double CFL, double dx, double invVel) const;
+    double computeSoundSpeed(double gradU2) const;
+    void resetSolution(size_t iPoint, BoundaryLayer *bl, std::vector<double> Sln0, bool usePrevPoint = false);
+};
+} // namespace blast
+#endif // DSOLVER_H
diff --git a/blast/src/blast.h b/blast/src/blast.h
new file mode 100644
index 0000000..71cdab8
--- /dev/null
+++ b/blast/src/blast.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2020 University of Liège
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Global header of the "blast" module.
+
+#ifndef BLAST_H
+#define BLAST_H
+
+#if defined(WIN32)
+#ifdef blast_EXPORTS
+#define BLAST_API __declspec(dllexport)
+#else
+#define BLAST_API __declspec(dllimport)
+#endif
+#else
+#define BLAST_API
+#endif
+
+#include "tbox.h"
+
+/**
+ * @brief Namespace for blast module
+ */
+namespace blast
+{
+
+// Solvers
+class Driver;
+class Solver;
+
+// Data structures
+class BoundaryLayer;
+class Discretization;
+class Edge;
+
+// Other
+class Closures;
+} // namespace blast
+
+#endif // BLAST_H
diff --git a/blast/tests/dart/blidart.py b/blast/tests/dart/blidart.py
new file mode 100644
index 0000000..a1549e1
--- /dev/null
+++ b/blast/tests/dart/blidart.py
@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright 2022 University of Liège
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# @author Paul Dechamps
+# @date 2022
+# Test the blast implementation. The test case is a compressible attached transitional flow.
+# Tested functionalities;
+# - Time integration.
+# - Two time-marching techniques (agressive and safe).
+# - Transition routines.
+# - Forced transition.
+# - Compressible flow routines.
+# - Laminar and turbulent flow.
+#
+# Untested functionalities.
+# - Separation routines.
+# - Multiple failure case at one iteration.
+# - Response to unconverged inviscid solver.
+
+# Imports.
+
+import blast.utils as viscUtils
+import numpy as np
+
+from fwk.wutils import parseargs
+import fwk
+from fwk.testing import *
+from fwk.coloring import ccolors
+
+import math
+
+def cfgInviscid(nthrds, verb):
+    import os.path
+    # Parameters
+    return {
+    # Options
+    'scenario' : 'aerodynamic',
+    'task' : 'analysis',
+    'Threads' : nthrds, # number of threads
+    'Verb' : verb, # verbosity
+    # Model (geometry or mesh)
+    'File' : os.path.dirname(os.path.abspath(__file__)) + '/../../models/dart/n0012.geo', # Input file containing the model
+    'Pars' : {'xLgt' : 5, 'yLgt' : 5, 'msF' : 1, 'msTe' : 0.01, 'msLe' : 0.001}, # parameters for input file model
+    'Dim' : 2, # problem dimension
+    'Format' : 'gmsh', # save format (vtk or gmsh)
+    # Markers
+    'Fluid' : 'field', # name of physical group containing the fluid
+    'Farfield' : ['upstream', 'farfield', 'downstream'], # LIST of names of physical groups containing the farfield boundaries (upstream/downstream should be first/last element)
+    'Wing' : 'airfoil', # LIST of names of physical groups containing the lifting surface boundary
+    'Wake' : 'wake', # LIST of names of physical group containing the wake
+    'WakeTip' : 'wakeTip', # LIST of names of physical group containing the edge of the wake
+    'Te' : 'te', # LIST of names of physical group containing the trailing edge
+    'dbc' : True,
+    'Upstream' : 'upstream',
+    # Freestream
+    'M_inf' : 0.2, # freestream Mach number
+    'AoA' : 2., # freestream angle of attack
+    # Geometry
+    'S_ref' : 1., # reference surface length
+    'c_ref' : 1., # reference chord length
+    'x_ref' : .25, # reference point for moment computation (x)
+    'y_ref' : 0.0, # reference point for moment computation (y)
+    'z_ref' : 0.0, # reference point for moment computation (z)
+    # Numerical
+    'LSolver' : 'GMRES', # inner solver (Pardiso, MUMPS or GMRES)
+    'G_fill' : 2, # fill-in factor for GMRES preconditioner
+    'G_tol' : 1e-5, # tolerance for GMRES
+    'G_restart' : 50, # restart for GMRES
+    'Rel_tol' : 1e-6, # relative tolerance on solver residual
+    'Abs_tol' : 1e-8, # absolute tolerance on solver residual
+    'Max_it' : 20 # solver maximum number of iterations
+    }
+
+def cfgBlast(verb):
+    return {
+        'Re' : 1e7,       # Freestream Reynolds number
+        'Minf' : 0.2,     # Freestream Mach number (used for the computation of the time step only)
+        'CFL0' : 1,         # Inital CFL number of the calculation
+        'Verb': verb,       # Verbosity level of the solver
+        'couplIter': 25,    # Maximum number of iterations
+        'couplTol' : 1e-4,  # Tolerance of the VII methodology
+        'iterPrint': 5,     # int, number of iterations between outputs
+        'resetInv' : True,  # bool, flag to reset the inviscid calculation at every iteration.
+        'sections' : [0],
+        'xtrF' : [None, 0.4],# Forced transition location
+        'interpolator' : 'Matching',
+        'nDim' : 2
+    }
+
+def main():
+    # Timer.
+    tms = fwk.Timers()
+    tms['total'].start()
+
+    args = parseargs()
+    icfg = cfgInviscid(args.k, args.verb)
+    vcfg = cfgBlast(args.verb)
+
+    tms['pre'].start()
+    coupler, iSolverAPI, vSolver = viscUtils.initBlast(icfg, vcfg)
+    tms['pre'].stop()
+
+    print(ccolors.ANSI_BLUE + 'PySolving...' + ccolors.ANSI_RESET)
+    tms['solver'].start()
+    aeroCoeffs = coupler.run()
+    tms['solver'].stop()
+
+    # Display results.
+    print(ccolors.ANSI_BLUE + 'PyRes...' + ccolors.ANSI_RESET)
+    print('      Re        M    alpha       Cl       Cd      Cdp      Cdf       Cm')
+    print('{0:6.1f}e6 {1:8.2f} {2:8.1f} {3:8.4f} {4:8.4f} {5:8.4f} {6:8.4f} {7:8.4f}'.format(vcfg['Re']/1e6, iSolverAPI.getMinf(), iSolverAPI.getAoA()*180/math.pi, iSolverAPI.getCl(), vSolver.Cdt, vSolver.Cdp, vSolver.Cdf, iSolverAPI.getCm()))
+
+     # Write results to file.
+    vSolution = viscUtils.getSolution(vSolver)
+    vSolution['Cdt_int'] = vSolution['Cdf'] + iSolverAPI.getCd()
+    viscUtils.write(vSolution, vSolver.getRe())
+    # Save pressure coefficient
+    iSolverAPI.save(sfx='_viscous')
+    tms['total'].stop()
+
+    print(ccolors.ANSI_BLUE + 'PyTiming...' + ccolors.ANSI_RESET)
+    print('CPU statistics')
+    print(tms)
+    print('SOLVERS statistics')
+    print(coupler.tms)
+    
+    # Test solution
+    print(ccolors.ANSI_BLUE + 'PyTesting...' + ccolors.ANSI_RESET)
+    tests = CTests()
+    tests.add(CTest('Cl', iSolverAPI.getCl(), 0.230, 5e-2)) # XFOIL 0.2325
+    tests.add(CTest('Cd wake', vSolution['Cdt_w'], 0.0056, 1e-3, forceabs=True)) # XFOIL 0.00531
+    tests.add(CTest('Cd integral', vSolution['Cdt_int'], 0.0059, 1e-3, forceabs=True)) # XFOIL 0.00531
+    tests.add(CTest('Cdf', vSolution['Cdf'], 0.00465, 1e-3, forceabs=True)) # XFOIL 0.00084, Cdf = 0.00447
+    tests.add(CTest('xtr_top', vSolution['xtrT'], 0.20, 0.03, forceabs=True)) # XFOIL 0.1877
+    tests.add(CTest('xtr_bot', vSolution['xtrB'], 0.40, 0.01, forceabs=True)) # XFOIL 0.4998
+    tests.add(CTest('Iterations', len(aeroCoeffs), 18, 0, forceabs=True))
+    tests.run()
+
+    # Show results
+    if not args.nogui:
+        iCp = viscUtils.read('Cp_inviscid.dat')
+        vCp = viscUtils.read('Cp_viscous.dat')
+        xfoilCp = viscUtils.read('../../blast/models/references/cpXfoil_n0012.dat', delim=None, skip = 1)
+        xfoilCpInv = viscUtils.read('../../blast/models/references/cpXfoilInv_n0012.dat', delim=None, skip = 1)
+        plotcp = {'curves': [np.column_stack((vCp[:,0], vCp[:,3])),
+                            np.column_stack((xfoilCp[:,0], xfoilCp[:,1])),
+                            np.column_stack((iCp[:,0], iCp[:,3])),
+                            np.column_stack((xfoilCpInv[:,0], xfoilCpInv[:,1]))],
+                    'labels': ['Blast (VII)', 'XFOIL VII', 'DART (inviscid)', 'XFOIL (inviscid)'],
+                    'lw': [3, 3, 2, 2],
+                    'color': ['firebrick', 'darkblue', 'firebrick', 'darkblue'],
+                    'ls': ['-', '-', '--', '--'],
+                    'reverse': True,
+                    'xlim':[0, 1],
+                    'yreverse': True,
+                    'legend': True,
+                    'xlabel': '$x/c$',
+                    'ylabel': '$c_p$'
+                    }
+        viscUtils.plot(plotcp)
+
+        xfoilBl = viscUtils.read('../../blast/models/references/blXfoil_n0012.dat', delim=None, skip = 1)
+        plotcf = {'curves': [np.column_stack((vSolution['x'], vSolution['Cf'])),
+                            np.column_stack((xfoilBl[:,1], xfoilBl[:,6]))],
+                'labels': ['Blast', 'XFOIL'],
+                'lw': [3, 3],
+                'color': ['firebrick', 'darkblue'],
+                'ls': ['-', '-'],
+                'reverse': True,
+                'xlim':[0, 1],
+                'legend': True,
+                'xlabel': '$x/c$',
+                'ylabel': '$c_f$'
+                    }
+        viscUtils.plot(plotcf)
+
+    # eof
+    print('')
+
+if __name__ == "__main__":
+    main()
diff --git a/blast/utils.py b/blast/utils.py
new file mode 100644
index 0000000..b84f576
--- /dev/null
+++ b/blast/utils.py
@@ -0,0 +1,192 @@
+import blast
+from fwk.wutils import parseargs
+from fwk.coloring import ccolors
+import numpy as np
+
+def initBL(Re, Minf, CFL0, nSections, xtrF = [None, None], span=0, verb=None):
+    """ Initialize boundary layer solver.
+    
+    Params
+    ------
+    - Re: Flow Reynolds number.
+    - Minf: Freestream Mach number.
+    - CFL0: Initial CFL number for time integration.
+    - nSections: Number of sections in the domain.
+    - span: Wing span (not used for 2D computations.
+    - verb: Verbosity level of the solver.
+    
+    Return
+    ------
+    - solver: blast::Driver class.
+    """
+    if Re<=0.:
+        print(ccolors.ANSI_RED, "blast::vUtils Error : Negative Reynolds number.", Re, ccolors.ANSI_RESET)
+        raise RuntimeError("Invalid parameter")
+    if Minf<0.:
+        print(ccolors.ANSI_RED, "blast::vUtils Error : Negative Mach number.", Minf, ccolors.ANSI_RESET)
+        raise RuntimeError("Invalid parameter")
+    elif Minf>=1.:
+        print(ccolors.ANSI_YELLOW, "blast::vUtils Warning : (Super)sonic freestream Mach number.", Minf, ccolors.ANSI_RESET)
+    if nSections < 0:
+        print(ccolors.ANSI_RED, "blast::vUtils Fatal error : Negative number of sections.", nSections, ccolors.ANSI_RESET)
+        raise RuntimeError("Invalid parameter")
+    if verb is None:
+      args = parseargs()
+      verbose = args.verb
+    else:
+      if not(0<=verb<=3):
+        print(ccolors.ANSI_RED, "blast::vUtils Fatal error : verbose not in valid range.", verbose, ccolors.ANSI_RESET)
+        raise RuntimeError("Invalid parameter")
+      else:
+        verbose = verb
+    
+    for i in range(len(xtrF)):
+        if xtrF[i] is None:
+            xtrF[i] = -1
+        if xtrF[i] != -1 and not(0<= xtrF[i] <= 1):
+            raise RuntimeError('Incorrect forced transition location') 
+
+    solver = blast.Driver(Re, Minf, CFL0, nSections, xtrF_top=xtrF[0], xtrF_bot=xtrF[1], _span=span, _verbose=verbose)
+    return solver
+
+def initBlast(iconfig, vconfig, iSolver='DART'):
+    """Initialize blast coupling objects.
+    
+    Params
+    ------
+    - iconfig (dict): Dictionnary to initialize solver 'iSolver'.
+    - vconfig (dict): Dictionnary to initialize boundary layer solver.
+    - iSolver (string): Name of the inviscid solver to use.
+
+    Return
+    ------
+    - coupler: coupler object
+    - iSolverAPI: api interface of the inviscid solver selected with 'iSolver'.
+    - vSolver: blast::Driver class.
+    """
+    # Viscous solver
+    vSolver = initBL(vconfig['Re'], vconfig['Minf'], vconfig['CFL0'], 1, xtrF=vconfig['xtrF'])
+
+    # Inviscid solver
+    if iSolver == 'DART':
+        from blast.interfaces.dart.DartInterface import DartInterface as interface
+    else:
+        print(ccolors.ANSI_RED + 'Solver '+iSolver+' currently not implemented' + ccolors.ANSI_RESET)
+        raise RuntimeError
+    iSolverAPI = interface(iconfig, vSolver, vconfig)
+
+    # Coupler
+    import blast.coupler as blastCoupler
+    coupler = blastCoupler.Coupler(iSolverAPI, vSolver, _maxCouplIter=vconfig['couplIter'], _couplTol=vconfig['couplTol'], _iterPrint=vconfig['iterPrint'], _resetInv=vconfig['resetInv'])
+    return coupler, iSolverAPI, vSolver
+
+def mesh(file, pars):
+    """Initialize mesh and mesh writer
+
+    Parameters
+    ----------
+    file : str
+        file contaning mesh (w/o extention)
+    pars : dict
+        parameters for mesh
+    """
+    import tbox.gmsh as tmsh
+    # Load the mesh.
+    msh = tmsh.MeshLoader(file,__file__).execute(**pars)
+    return msh
+
+
+def getSolution(vSolver, iSec=0):
+    """Extract viscous solution.
+    Args
+    ----
+    - vSolver: blast::Driver class. Driver of the viscous calculations
+    - iSec (int): Marker of the section (default: 0)
+    """
+    if iSec<0:
+        raise RuntimeError("blast::viscU Invalid section number", iSec)
+    
+    solverOutput = vSolver.getSolution(iSec)
+
+    sln = { 'theta'    : solverOutput[0],
+            'H'        : solverOutput[1],
+            'N'        : solverOutput[2],
+            'ue'       : solverOutput[3],
+            'Ct'       : solverOutput[4],
+            'deltaStar': solverOutput[5],
+            'Ret'      : solverOutput[6],
+            'Cd'       : solverOutput[7],
+            'Cf'       : np.asarray(solverOutput[8])*np.asarray(solverOutput[3])**2,
+            'Hstar'    : solverOutput[9],
+            'Hstar2'   : solverOutput[10],
+            'Hk'       : solverOutput[11],
+            'Cteq'     : solverOutput[12],
+            'Us'       : solverOutput[13],
+            'delta'    : solverOutput[14],
+            'x'        : solverOutput[15],
+            'xoc'      : solverOutput[16],
+            'y'        : solverOutput[17],
+            'z'        : solverOutput[18],
+            'ueInv'    : solverOutput[19],
+            'xtrT'     : vSolver.getxtr(iSec, 0),
+            'xtrB'     : vSolver.getxtr(iSec, 1),
+            'Cdt_w'    : vSolver.Cdt_sec[iSec],
+            'Cdf'      : vSolver.Cdf_sec[iSec],
+            'Cdp'      : vSolver.Cdp_sec[iSec]
+            }
+    return sln
+
+def write(wData, Re, toW=['deltaStar', 'H', 'Hstar', 'Cf', 'Ct', 'ue', 'ueInv', 'delta'], sfx=''):
+    """Write the results in bl files
+    """
+    # Write
+    print('Writing file: bl_'+sfx+'.dat...', end = '')
+    f = open('bl'+sfx+'.dat', 'w+')
+
+    f.write('$Sectional aerodynamic coefficients\n')
+    f.write('             Re             Cdw             Cdp             Cdf         xtr_top         xtr_bot\n')
+    f.write('{0:15.6f} {1:15.6f} {2:15.6f} {3:15.6f} {4:15.6f} {5:15.6f}\n'.format(Re, wData['Cdt_w'], wData['Cdp'],
+                                                                                   wData['Cdf'], wData['xtrT'],
+                                                                                   wData['xtrB']))
+    f.write('$Boundary layer variables\n')
+    f.write('{0:>15s} {1:>15s} {2:>15s} {3:>15s}'.format('x','y', 'z', 'xoc'))
+
+
+    for s in toW:
+        f.write(' {0:>15s}'.format(s))
+    f.write('\n')
+
+    for i in range(len(wData['x'])):
+        f.write('{0:>15.6f} {1:>15.6f} {2:>15.6f} {3:>15.6f}'.format(wData['x'][i], wData['y'][i], wData['z'][i], (wData['x'][i] - min(wData['x'])) / (max(wData['x']) - min(wData['x']))))
+        for s in toW:
+            f.write(' {0:15.6f}'.format(wData[s][i]))
+        f.write('\n')
+
+    f.close()
+    print('done.')
+
+def read(filename, delim=',', skip=1):
+    """Read from file and store in data array
+    """
+    import io
+    import numpy as np
+    # read file
+    fl = io.open(filename, 'r')
+    label = fl.readline().split(',')
+    fl.close()
+    data = np.loadtxt(filename, delimiter=delim, skiprows=skip)
+    return data
+
+def plot(cfg):
+    from matplotlib import pyplot as plt
+    for i in range(len(cfg['curves'])):
+        plt.plot(cfg['curves'][i][:,0], cfg['curves'][i][:,1], cfg['ls'][i], color=cfg['color'][i], lw=cfg['lw'][i], label=cfg['labels'][i])
+    if 'yreverse' in cfg and cfg['yreverse'] is True: plt.gca().invert_yaxis()
+    if 'xreverse' in cfg and cfg['xreverse'] is True: plt.gca().invert_xaxis()
+    if 'title' in cfg: plt.title(cfg['title'])
+    if 'xlim' in cfg: plt.xlim(cfg['xlim'])
+    if 'ylim' in cfg: plt.xlim(cfg['ylim'])
+    if 'legend' in cfg and cfg['legend'] is True: plt.legend(frameon=False)
+    if 'xlabel' in cfg: plt.xlabel(cfg['xlabel'])
+    if 'ylabel' in cfg: plt.ylabel(cfg['ylabel'])
+    plt.show()
\ No newline at end of file
diff --git a/run.py b/run.py
new file mode 100755
index 0000000..ab76a3c
--- /dev/null
+++ b/run.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# -*- coding: utf8 -*-
+# test encoding: à-é-è-ô-ï-€
+
+# Copyright 2020 University of Liège
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Calls fwk.wutils.run to execute a script as if blaster was installed
+
+def main():
+    import os.path, sys
+    # adds fwk/tbox to the python path
+    thisdir = os.path.split(os.path.abspath(__file__))[0]
+    fwkdir = os.path.abspath(os.path.join(thisdir, 'amfe'))
+    if not os.path.isdir(fwkdir):
+        raise Exception('blaster/amfe not found!\n')
+    sys.path.append(fwkdir)
+    # adds "." to the pythonpath
+    sys.path.append(thisdir)
+
+    import fwk.wutils as wu
+    wu.run()
+
+if __name__ == "__main__":
+    main()
-- 
GitLab