diff --git a/CMakeLists.txt b/CMakeLists.txt
index eaf41d2..984d80d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,330 +1,352 @@
 project(specmicp)
 cmake_minimum_required(VERSION 2.8.8)
 
 
 #######################  External Package #####################################
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/")
 find_package(Eigen3 REQUIRED)  # This module comes from the Eigen3 Package
 find_package(CxxTest)          # Necessary if you want the tests
 
 # catch for unit testing
 include(ExternalProject)
 find_package(Git REQUIRED)
 
 ExternalProject_Add(
     catch
     PREFIX ${CMAKE_BINARY_DIR}/catch
     GIT_REPOSITORY https://github.com/philsquared/Catch.git
     TIMEOUT 10
     UPDATE_COMMAND ${GIT_EXECUTABLE} pull
     CONFIGURE_COMMAND ""
     BUILD_COMMAND ""
     INSTALL_COMMAND ""
     LOG_DOWNLOAD ON
    )
 
 
 ########################  Compilation flags #################################
 if(UNIX)
    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -std=c++11")
    SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -pedantic")
    SET(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG")
    message(STATUS "c++ flags ${CMAKE_CXX_FLAGS}")
 else()
     message(WARNING "not tested !")
 endif()
 
 
 ################ Directories #####################
 
 set(PROJECT_TEST_DIR ${PROJECT_SOURCE_DIR}/tests)
 ExternalProject_Get_Property(catch source_dir)
 set(CATCH_INCLUDE_DIR ${source_dir}/include CACHE INTERNAL "Path to include folder for Catch")
 
 set(PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
 # include
 include_directories(${EIGEN3_INCLUDE_DIR})
 include_directories(${PROJECT_SOURCE_DIR})
 include_directories(${CATCH_INCLUDE_DIR})
 include_directories(${PROJECT_TEST_DIR})
 
 ################ Modules ##########################
 
 # -------- MiCPSolver ----------------
 
 set(MICPSOLVER_DIR ${PROJECT_SOURCE_DIR}/micpsolver)
 
 add_custom_target(specmisolver_inc SOURCES
     ${MICPSOLVER_DIR}/micpsolver_base.hpp
     ${MICPSOLVER_DIR}/micpsolver_base.inl
     ${MICPSOLVER_DIR}/micpsolver.hpp
     ${MICPSOLVER_DIR}/micpsolver.inl
     ${MICPSOLVER_DIR}/micpsolver_min.hpp
     ${MICPSOLVER_DIR}/micpsolver_min.inl
     ${MICPSOLVER_DIR}/micpsolverold.hpp
     ${MICPSOLVER_DIR}/micpsolverold.inl
     ${MICPSOLVER_DIR}/micpsolver_structs.hpp
     ${MICPSOLVER_DIR}/micpprog.hpp
     ${MICPSOLVER_DIR}/ncp_function.hpp
     ${MICPSOLVER_DIR}/estimate_cond_number.hpp
 )
 
 # ------- SpecMiCP ---------------
 
 set(SPECMICP_DIR ${PROJECT_SOURCE_DIR}/specmicp)
 
 add_custom_target(specmic_inc SOURCES
     #${SPECMICP_DIR}/.hpp
     ${SPECMICP_DIR}/simulation_box.hpp
 )
 
 set(SPECMICP_LIBFILE
     ${SPECMICP_DIR}/equilibrium_data.cpp
     ${SPECMICP_DIR}/reduced_system.cpp
     ${SPECMICP_DIR}/reduced_system_solver.cpp
     ${SPECMICP_DIR}/reaction_path.cpp
     ${SPECMICP_DIR}/speciation_solver.cpp
     ${SPECMICP_DIR}/simple_composition.cpp
 )
 
 add_library(specmicp ${SPECMICP_LIBFILE})
 
 # -------- Database ----------------
 
 set(DATABASE_DIR ${PROJECT_SOURCE_DIR}/database)
 
 set(DATABASE_LIBFILE
     ${DATABASE_DIR}/data_container.cpp
     ${DATABASE_DIR}/database.cpp
     ${DATABASE_DIR}/reader.cpp
     ${DATABASE_DIR}/selector.cpp
     ${DATABASE_DIR}/mineral_selector.cpp
     ${DATABASE_DIR}/canonicalizer.cpp
     ${DATABASE_DIR}/switch_basis.cpp
 )
 
 add_custom_target(data_incl SOURCES
     ${DATABASE_DIR}/common_def.hpp
     ${DATABASE_DIR}/module.hpp
 )
 
 add_library(specmicp_database ${DATABASE_LIBFILE})
 
 # --------- ODE int ------------------
 
 set(ODEINT_DIR ${PROJECT_SOURCE_DIR}/odeint)
 
 
 add_custom_target(odeint_incl SOURCES
     ${ODEINT_DIR}/odeint_types.hpp
     ${ODEINT_DIR}/butcher_tableau.hpp
     ${ODEINT_DIR}/runge_kutta_step.hpp
     ${ODEINT_DIR}/runge_kutta_step.inl
 )
 
+# --------- DFPMSolver -----------------
+
+set(DFPMSOLVER_DIR ${PROJECT_SOURCE_DIR}/dfpmsolver)
+
+add_custom_target(dfpmsolver_incl SOURCES
+    ${DFPMSOLVER_DIR}/driver.hpp
+    ${DFPMSOLVER_DIR}/driver.inl
+    ${DFPMSOLVER_DIR}/driver_structs.hpp
+    ${DFPMSOLVER_DIR}/dfpm_program.hpp
+    #
+    ${DFPMSOLVER_DIR}/sparse_solver.hpp
+    ${DFPMSOLVER_DIR}/sparse_solver_structs.hpp
+    # Parabolic solver
+    ${DFPMSOLVER_DIR}/parabolic_driver.hpp
+    ${DFPMSOLVER_DIR}/parabolic_driver.inl
+    ${DFPMSOLVER_DIR}/parabolic_program.hpp
+    ${DFPMSOLVER_DIR}/parabolic_structs.hpp
+)
+
 # --------- ReactMiCP -----------------
 
 set(REACTMICP_DIR ${PROJECT_SOURCE_DIR}/reactmicp)
 
 add_custom_target(reactmicp_incl SOURCES
     ${REACTMICP_DIR}/common.hpp
     #
     ${REACTMICP_DIR}/micpsolvers/parabolicprogram.hpp
     ${REACTMICP_DIR}/micpsolvers/micpsolver_structs.hpp
     ${REACTMICP_DIR}/micpsolvers/micpsolver.hpp
     ${REACTMICP_DIR}/micpsolvers/micpsolver.inl
     #
     ${REACTMICP_DIR}/meshes/mesh1d.hpp
 
 
     ${REACTMICP_DIR}/systems/secondary_variables/secondary.hpp
     ${REACTMICP_DIR}/systems/secondary_variables/secondary.inl
 
     ${REACTMICP_DIR}/systems/saturated_diffusion/options.hpp
     ${REACTMICP_DIR}/systems/saturated_diffusion/variables.hpp
+    ${REACTMICP_DIR}/systems/saturated_diffusion/transport_parameters.hpp
 )
 
 set(REACTMICP_LIBFILES
     ${REACTMICP_DIR}/systems/diffusion/diffusion.cpp
     ${REACTMICP_DIR}/systems/diffusion/diffusion_numbering.cpp
     ${REACTMICP_DIR}/systems/diffusion/diffusion_secondary.cpp
 
     ${REACTMICP_DIR}/systems/saturated_diffusion/reactive_transport_solver.cpp
     ${REACTMICP_DIR}/systems/saturated_diffusion/equation_numbering.cpp
     ${REACTMICP_DIR}/systems/saturated_diffusion/transport_program.cpp
     ${REACTMICP_DIR}/systems/saturated_diffusion/speciation_program.cpp
 )
 
 add_library(reactmicp ${REACTMICP_LIBFILES})
 
 # ------- Utils ------------------
 
 set(UTILS_DIR ${PROJECT_SOURCE_DIR}/utils)
 
 add_custom_target(utils_inc SOURCES
     ${UTILS_DIR}/log.hpp
 )
 
 # ------- Physics ----------------
 
 set(PHYSICS_DIR ${PROJECT_SOURCE_DIR}/physics)
 
 add_custom_target(physics_inc SOURCES
     ${PHYSICS_DIR}/constants.hpp
     ${PHYSICS_DIR}/laws.hpp
     ${PHYSICS_DIR}/units.hpp
 )
 
 # ------- Data -------------------
 
 set(DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
 
 set(DATABASE_LIST
         ${DATA_DIR}/specmicp_database.js
         ${DATA_DIR}/cemdata_specmicp.js
 )
 
 add_custom_target(data SOURCES ${DATABASE_LIST})
 
 file(INSTALL ${DATABASE_LIST}
      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/)
 
 # ------ Documentation -----------
 
 set(
     DOCS_LIST
     ${CMAKE_CURRENT_SOURCE_DIR}/README.md
     ${CMAKE_CURRENT_SOURCE_DIR}/INSTALL
     ${CMAKE_CURRENT_SOURCE_DIR}/TODO
     ${CMAKE_CURRENT_SOURCE_DIR}/COPYING
 )
 
 add_custom_target(docs SOURCES ${DOCS_LIST})
 
 # add a target to generate API documentation with Doxygen
 find_package(Doxygen)
 if(DOXYGEN_FOUND)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/doc/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile @ONLY)
 add_custom_target(doc
 ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/doc/Doxyfile
 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
 COMMENT "Generating API documentation with Doxygen" VERBATIM
 )
 add_custom_target(citations SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/doc/citations.bib)
 file(INSTALL ${CMAKE_CURRENT_SOURCE_DIR}/doc/citations.bib
      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/doc/)
 endif(DOXYGEN_FOUND)
 
 # --------- Python interface ----------
 
 
 add_subdirectory( cython )
 
 
 
 # --------- Test ----------------------
 
 enable_testing(true)
 
 # CXX Test
 # ========
 
 if(CXXTEST_FOUND)
     set(CXXTEST_USE_PYTHON TRUE)
     include_directories(${CXXTEST_INCLUDE_DIR})
     enable_testing()
 
     # MiCP Solver
     # ------------
     CXXTEST_ADD_TEST(test_cond_number
                      test_cond_number.cpp
                      ${PROJECT_TEST_DIR}/micpsolver/test_cond_number.hpp)
 
     CXXTEST_ADD_TEST(test_ncp_funtion
                      test_ncp_function.cpp
                      ${PROJECT_TEST_DIR}/micpsolver/test_ncp_function.hpp)
 
     CXXTEST_ADD_TEST(test_micpsolver
                      test_micpsolver.cpp
                      ${PROJECT_TEST_DIR}/micpsolver/test_micpsolver.hpp)
 
     # Database
     # --------
 
     CXXTEST_ADD_TEST(test_data_reader
                     test_data_reader.cpp
                     ${PROJECT_TEST_DIR}/database/test_data_reader.hpp)
     target_link_libraries(test_data_reader specmicp_database jsoncpp)
 
 
     CXXTEST_ADD_TEST(test_data_selector
                     test_data_selector.cpp
                     ${PROJECT_TEST_DIR}/database/test_data_selector.hpp)
     target_link_libraries(test_data_selector specmicp_database jsoncpp)
 
     # Odeint
     # ------
 
     CXXTEST_ADD_TEST(test_butchertableau
                      test_butchertableau.cpp
                      ${PROJECT_TEST_DIR}/odeint/test_butchertableau.hpp)
 
 
     CXXTEST_ADD_TEST(test_embeddedrungekutta
                      test_embeddedrungekutta.cpp
                      ${PROJECT_TEST_DIR}/odeint/test_embeddedrungekutta.hpp)
 
 endif()
 
 # Test 'Perso'
 # ============
 
 add_executable(thermocarbo ${PROJECT_TEST_DIR}/specmicp/thermocarbo.cpp)
 target_link_libraries(thermocarbo specmicp specmicp_database jsoncpp)
 
 add_executable(carboalu ${PROJECT_TEST_DIR}/specmicp/carboalu.cpp)
 target_link_libraries(carboalu specmicp specmicp_database jsoncpp)
 
 add_executable(alkaliactivated ${PROJECT_TEST_DIR}/specmicp/alkaliactivated.cpp)
 target_link_libraries(alkaliactivated specmicp specmicp_database jsoncpp)
 
 add_executable(rate_nicoleau ${PROJECT_TEST_DIR}/specmicp/rate_nicoleau.cpp)
 target_link_libraries(rate_nicoleau specmicp specmicp_database jsoncpp)
 
 add_executable(kinetics ${PROJECT_TEST_DIR}/specmicp/kinetics.cpp)
 target_link_libraries(kinetics specmicp specmicp_database jsoncpp)
 
 
 set(DATA_TEST ${PROJECT_TEST_DIR}/specmicp/data_test/data_nicoleau_c3sm.csv
               ${PROJECT_TEST_DIR}/specmicp/data_test/data_nicoleau_c3st1.csv
               ${PROJECT_TEST_DIR}/specmicp/data_test/data_nicoleau_c3st2.csv
 )
 
 add_custom_target(data_nicoleau SOURCES ${DATA_TEST})
 
 file(INSTALL ${DATA_TEST}
      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data_test/)
 
 # Catch test
 #===========
 
 add_custom_target(catch_header SOURCES
     ${PROJECT_TEST_DIR}/test_database.hpp
 )
 
 add_executable(test_reactmicp_diffusion
     ${PROJECT_TEST_DIR}/reactmip/systems/diffusion/test_reactmicp_diffusion.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/diffusion/diffusion.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/diffusion/numbering.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/diffusion/secondary.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/diffusion/solving.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/diffusion/utils.cpp
 )
 target_link_libraries(test_reactmicp_diffusion reactmicp specmicp specmicp_database jsoncpp)
 
 add_executable(test_reactmicp_saturated_diffusion
     ${PROJECT_TEST_DIR}/reactmip/systems/saturated_diffusion/test_reactmicp_saturated_diffusion.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/saturated_diffusion/variables.cpp
+    ${PROJECT_TEST_DIR}/reactmip/systems/saturated_diffusion/solver.cpp
+    ${PROJECT_TEST_DIR}/reactmip/systems/saturated_diffusion/transport_program.cpp
     ${PROJECT_TEST_DIR}/reactmip/systems/saturated_diffusion/utils.cpp
 )
 target_link_libraries(test_reactmicp_saturated_diffusion reactmicp specmicp specmicp_database jsoncpp)
diff --git a/src/dfpmsolver/dfpm_program.hpp b/src/dfpmsolver/dfpm_program.hpp
new file mode 100644
index 0000000..09d9436
--- /dev/null
+++ b/src/dfpmsolver/dfpm_program.hpp
@@ -0,0 +1,27 @@
+#ifndef SPECMICP_DFPMSOLVER_DFPMPROGRAM_HPP
+#define SPECMICP_DFPMSOLVER_DFPMPROGRAM_HPP
+
+namespace specmicp {
+namespace dfpmsolver {
+
+//! \brief Base class for a program
+template <class Derived>
+class DFPMProgram
+{
+public:
+    //! \brief Return a pointer to the true object
+    Derived* derived() {static_cast<Derived*>(this);}
+
+    //! \brief Return the number of equations
+    int get_neq() {return derived()->get_neq();}
+
+    //! \brief Return the number of degrees of freedom per node
+    int get_ndf() {return derived()->get_ndf();}
+
+
+};
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+#endif // SPECMICP_DFPMSOLVER_DFPMPROGRAM_HPP
diff --git a/src/dfpmsolver/driver.hpp b/src/dfpmsolver/driver.hpp
new file mode 100644
index 0000000..52d0a30
--- /dev/null
+++ b/src/dfpmsolver/driver.hpp
@@ -0,0 +1,76 @@
+#ifndef SPECMICP_DFPMSOLVER_DRIVER_HPP
+#define SPECMICP_DFPMSOLVER_DRIVER_HPP
+
+namespace specmicp {
+namespace dfpmsolver {
+
+//! \brief Base class for a driver
+//!
+//! Options should be a subclass of DriverOptions
+//! Performance should be a subclass of DriverPerformance
+//!
+template <class Program, class Options, class Performance>
+class Driver
+{
+public:
+    Driver(Program& the_program):
+        m_program(the_program),
+        m_options()
+    {}
+
+    Driver(Program& the_program,
+            Options some_options):
+        m_program(the_program),
+        m_options(some_options)
+    {}
+
+    // basic program management
+    // ------------------------
+
+    //! Return the program
+    Program& program() {return m_program;}
+
+    //! Return the number of equations
+    int get_neq() {return m_program.get_neq();}
+
+    // common process
+    // --------------
+    //! \brief rescale the update if needed
+    //!
+    //! Return the step length
+    double is_step_too_long(Eigen::VectorXd& update);
+
+
+    // options
+    // -------
+
+    //! \brief Return a read/write reference to the options
+    Options& get_options() {return m_options;}
+    //! \brief Return a read-only reference to the options
+    const Options& get_options() const {return m_options;}
+
+    // performance
+    // -----------
+
+    //! \brief Return a const reference to the performance
+    const Performance& get_perfs() const {return m_performance;}
+
+protected:
+    //! \brief Read/write access to the performance
+    Performance& get_perfs() {return m_performance;}
+
+private:
+    Program& m_program;        //!< The program
+    Performance m_performance; //!< The performance
+    Options m_options;         //!< The options
+};
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+// implementation
+// ==============
+
+#include "driver.inl"
+
+#endif // SPECMICP_DFPMSOLVER_DRIVER_HPP
diff --git a/src/dfpmsolver/driver.inl b/src/dfpmsolver/driver.inl
new file mode 100644
index 0000000..778178e
--- /dev/null
+++ b/src/dfpmsolver/driver.inl
@@ -0,0 +1,22 @@
+#include "driver.hpp" // syntaxic coloration only
+
+namespace specmicp {
+namespace dfpmsolver {
+
+
+template <class Program, class Options, class Performance>
+double Driver<Program, Options, Performance>::is_step_too_long(Eigen::VectorXd& update)
+{
+    double steplength = update.norm();
+    if (steplength > get_options().maximum_step_length)
+    {
+        get_perfs().maximum_step_taken = true;
+        update  = get_options().maximum_step_length / steplength * update;
+        steplength = get_options().maximum_step_length;
+    }
+    return steplength;
+}
+
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
diff --git a/src/dfpmsolver/driver_structs.hpp b/src/dfpmsolver/driver_structs.hpp
new file mode 100644
index 0000000..6fb407e
--- /dev/null
+++ b/src/dfpmsolver/driver_structs.hpp
@@ -0,0 +1,49 @@
+#ifndef SPECMICP_DFPMSOLVER_DRIVERSTRUCTS_HPP
+#define SPECMICP_DFPMSOLVER_DRIVERSTRUCTS_HPP
+
+#include "sparse_solver_structs.hpp"
+
+namespace specmicp {
+namespace dfpmsolver {
+
+
+//! \brief Options of a driver
+//!
+struct DriverOptions
+{
+    double residuals_tolerance;    //!< Tolerance for the residual
+    double step_tolerance;        //!< Tolerance for the minimum step length
+    double threshold_stationary_point; //!< if ||R||>threshold, the point is classified as stationary
+    int maximum_iterations;       //!< Maximum iterations allowed
+    double maximum_step_length;   //!< Maximum step length allowed
+    int max_iterations_at_max_length;   //!< Maximum number of iterations at maximum step length
+    double coeff_accept_newton_step; //!< Accept Newton step if enough progress is made
+    SparseSolver sparse_solver;   //!< The sparse solver to use
+
+    DriverOptions():
+        residuals_tolerance(1e-6),
+        step_tolerance(1e-8),
+        threshold_stationary_point(1e-4),
+        maximum_iterations(200),
+        maximum_step_length(1e3),
+        max_iterations_at_max_length(50),
+        coeff_accept_newton_step(0.9),
+        sparse_solver(SparseSolver::SparseQR)
+    {}
+};
+
+//! \brief Performance of a driver
+struct DriverPerformance
+{
+    int nb_call_residuals;
+    int nb_call_jacobian;
+    int nb_iterations;
+    int nb_consecutive_max_step_taken;
+    int nb_max_step_taken;
+    bool maximum_step_taken;
+};
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+#endif // SPECMICP_DFPMSOLVER_DRIVERSTRUCTS_HPP
diff --git a/src/dfpmsolver/parabolic_driver.hpp b/src/dfpmsolver/parabolic_driver.hpp
new file mode 100644
index 0000000..9ad8335
--- /dev/null
+++ b/src/dfpmsolver/parabolic_driver.hpp
@@ -0,0 +1,100 @@
+#ifndef SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP
+#define SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP
+
+#include <Eigen/Dense>
+#include <Eigen/Sparse>
+
+#include "driver.hpp"
+#include "parabolic_structs.hpp"
+
+namespace specmicp {
+namespace dfpmsolver {
+
+//! \brief The parabolic driver
+//!
+//!  Parabolic driver for finite element programs
+template <class Program>
+class ParabolicDriver: public Driver<Program, ParabolicDriverOptions, ParabolicDriverPerformance>
+{
+public:
+    using base=Driver<Program, ParabolicDriverOptions, ParabolicDriverPerformance>;
+    using base::program;
+    using base::get_neq;
+    using base::get_options;
+    using base::get_perfs;
+
+
+    ParabolicDriver(Program& the_program):
+        Driver<Program, ParabolicDriverOptions, ParabolicDriverPerformance>(the_program)
+    {}
+
+    //! \brief Solve a timestep of length dt
+    ParabolicDriverReturnCode  solve_timestep(double dt, Eigen::VectorXd& displacement);
+    //! \brief Restart the current timestep
+    ParabolicDriverReturnCode  restart_timestep(Eigen::VectorXd& displacement);
+    //! \brief Check if the solution has been found
+    ParabolicDriverReturnCode check_convergence(double norm_update);
+
+    //! \brief Compute the residuals
+    void compute_residuals(const Eigen::VectorXd& displacement,
+                           const Eigen::VectorXd& velocity,
+                           Eigen::VectorXd& residual)
+    {
+        program().compute_residuals(displacement, velocity, residual);
+        get_perfs().nb_call_residuals += 1;
+    }
+    //! \brief Compute the jacobian
+    void compute_jacobian(Eigen::VectorXd& displacement,
+                          Eigen::VectorXd& velocity,
+                          Eigen::SparseMatrix<double>& jacobian
+                          )
+    {
+        program().compute_jacobian(displacement, velocity, jacobian, get_options().alpha*m_current_dt);
+        get_perfs().nb_call_jacobian += 1;
+    }
+    //! \brief Return the norm of the current residuals
+    double norm_residuals() {return m_residuals.norm();}
+
+    Eigen::VectorXd& get_velocity() {return m_velocity;}
+
+private:
+    void update_variable(
+            const Eigen::VectorXd& update,
+            double lambda,
+            Eigen::VectorXd& displacement
+            );
+    //! \brief Set the predictor
+    void set_predictor(Eigen::VectorXd& displacement);
+    //! \brief solve the linear system
+    int solve_linear_system(Eigen::VectorXd& update);
+    //! \brief perform the linesearch
+    int linesearch(Eigen::VectorXd& update,
+                   Eigen::VectorXd& displacements);
+
+    double compute_residuals_linesearch(Eigen::VectorXd& update,
+                                        double lambda,
+                                        Eigen::VectorXd& displacement
+                                        );
+
+    //! \brief Initialize the computation
+    void initialize_timestep(double dt, Eigen::VectorXd& displacement);
+
+    double m_norm_0;
+    double m_current_dt;
+    Eigen::VectorXd m_gradient;
+    Eigen::VectorXd m_velocity;
+    Eigen::VectorXd m_residuals;
+    Eigen::VectorXd m_predictor;
+    Eigen::SparseMatrix<double> m_jacobian;
+
+};
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+
+// implementation
+// ==============
+#include "parabolic_driver.inl"
+
+#endif // SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP
diff --git a/src/dfpmsolver/parabolic_driver.inl b/src/dfpmsolver/parabolic_driver.inl
new file mode 100644
index 0000000..db4c060
--- /dev/null
+++ b/src/dfpmsolver/parabolic_driver.inl
@@ -0,0 +1,245 @@
+#include "parabolic_driver.hpp" // for syntaxic coloration only
+#include "utils/log.hpp"
+#include "sparse_solver.hpp"
+
+namespace specmicp {
+namespace dfpmsolver {
+
+template <class Program>
+ParabolicDriverReturnCode  ParabolicDriver<Program>::solve_timestep(double dt, Eigen::VectorXd& displacement)
+{
+    initialize_timestep(dt, displacement);
+    compute_residuals(displacement, m_velocity, m_residuals);
+    m_norm_0 = norm_residuals();
+    return restart_timestep(displacement);
+}
+
+template <class Program>
+void  ParabolicDriver<Program>::initialize_timestep(double dt, Eigen::VectorXd& displacement)
+{
+    m_residuals = Eigen::VectorXd::Zero(get_neq());
+    m_current_dt = dt;
+    set_predictor(displacement);
+    m_velocity.setZero();
+    program().apply_bc(dt, displacement, m_velocity);
+}
+
+template <class Program>
+void  ParabolicDriver<Program>::set_predictor(Eigen::VectorXd& displacement)
+{
+    if (get_options().alpha < 1)
+        m_predictor = displacement + (1-get_options().alpha)*m_current_dt*m_velocity;
+    else
+        m_predictor = displacement;
+}
+
+template <class Program>
+ParabolicDriverReturnCode  ParabolicDriver<Program>::restart_timestep(Eigen::VectorXd& displacement)
+{
+    ParabolicDriverReturnCode return_code = ParabolicDriverReturnCode::NotConvergedYet;
+    while (return_code == ParabolicDriverReturnCode::NotConvergedYet)
+    {
+        Eigen::VectorXd update(get_neq());
+        compute_residuals(displacement, m_velocity, m_residuals);
+        return_code = check_convergence(-1.0);
+        if (return_code != ParabolicDriverReturnCode::NotConvergedYet) break;
+        compute_jacobian(displacement, m_velocity, m_jacobian);
+        m_gradient = m_jacobian.transpose()*m_residuals;
+        int retcode = solve_linear_system(update);
+        if (retcode <1)
+        {
+            ERROR << "Error when solving linear system : " << retcode << std::endl;
+            return_code = ParabolicDriverReturnCode::ErrorLinearSystem;
+        }
+        linesearch(update, displacement);
+    }
+    return return_code;
+
+}
+
+template <class Program>
+ParabolicDriverReturnCode  ParabolicDriver<Program>::check_convergence(double norm_update)
+{
+    ParabolicDriverReturnCode termcode = ParabolicDriverReturnCode::NotConvergedYet;
+    const int nb_iterations = get_perfs().nb_iterations;
+    const double norm_residuals = m_residuals.norm();
+    WARNING << "Residuals : " << nb_iterations << " - " << norm_residuals/m_norm_0;
+    if (norm_residuals/m_norm_0 < get_options().residuals_tolerance)
+    {
+        termcode = ParabolicDriverReturnCode::ResidualMinimized;
+    }
+    else if (nb_iterations > 0 and norm_update >0 and norm_update < get_options().step_tolerance)
+    {
+        if (norm_residuals > get_options().threshold_stationary_point)
+        {
+            ERROR << "Stationary point detected !";
+            termcode = ParabolicDriverReturnCode::StationaryPoint;
+        }
+        WARNING << "Error is minimized - may indicate a stationnary point";
+        termcode = ParabolicDriverReturnCode::ErrorMinimized;
+
+    }
+    else if (nb_iterations >  get_options().maximum_iterations)
+    {
+        ERROR << "Maximum number of iteration reached (" << get_options().maximum_iterations << ")";
+        termcode = ParabolicDriverReturnCode::MaxIterations;
+    }
+    else if (get_perfs().maximum_step_taken)
+    {
+        get_perfs().nb_consecutive_max_step_taken += 1;
+        get_perfs().nb_max_step_taken += 1;
+        if (get_perfs().nb_consecutive_max_step_taken == get_options().max_iterations_at_max_length) {
+            ERROR << "Divergence detected - Maximum step length taken two many times";
+            termcode = ParabolicDriverReturnCode::MaxStepTakenTooManyTimes;
+        }
+    }
+    else
+    {
+        get_perfs().nb_consecutive_max_step_taken = 0;
+    }
+    return termcode;
+}
+
+template <class Program>
+int ParabolicDriver<Program>::solve_linear_system(Eigen::VectorXd& update)
+{
+    return solve_sparse_linear_system(m_residuals, m_jacobian, update, get_options().sparse_solver);
+}
+
+template <class Program>
+double ParabolicDriver<Program>::compute_residuals_linesearch(
+        Eigen::VectorXd& update,
+        double lambda,
+        Eigen::VectorXd& displacement
+        )
+{
+    Eigen::VectorXd velocity(m_velocity);
+    Eigen::VectorXd residual = Eigen::VectorXd::Zero(get_neq());
+    program().update_solution(update, lambda, get_options().alpha*m_current_dt, m_predictor, displacement, velocity);
+    compute_residuals(displacement, velocity, residual);
+    return 0.5*residual.squaredNorm();
+}
+
+template <class Program>
+void ParabolicDriver<Program>::update_variable(
+        const Eigen::VectorXd& update,
+        double lambda,
+        Eigen::VectorXd& displacement
+        )
+{
+    program().update_solution(update, lambda, get_options().alpha*m_current_dt,
+                              m_predictor, displacement, m_velocity);
+}
+
+template <class Program>
+int ParabolicDriver<Program>::linesearch(
+        Eigen::VectorXd& update,
+        Eigen::VectorXd& displacements)
+{
+    // References
+    // ----------
+    //    - Algo A6.3.1 : Dennis and Schnabel (1983)
+    //    - Nocedal & Wrigth (2006)
+    DEBUG << "Linesearch";
+    Eigen::VectorXd xp(displacements);
+    double fcp;
+    get_perfs().maximum_step_taken = false;
+    int retcode = 2;
+    const double alpha = 1e-4;
+    double newtlen = base::is_step_too_long(update);
+    double init_slope = m_gradient.dot(update);
+    double rellength = update.cwiseAbs().maxCoeff();
+    const double minlambda = get_options().step_tolerance / rellength;
+    double lambda = 1.0;
+    double lambda_prev = lambda;
+
+    double merit_value = 0.5*m_residuals.squaredNorm();
+    // new residual
+    fcp = compute_residuals_linesearch(update, lambda, xp);
+    // Skip linesearch if enough progress is done
+    // ------------------------------------------
+    if (fcp < get_options().coeff_accept_newton_step *merit_value)
+    {
+        WARNING << "Skip linesearch ";
+        update_variable(update, lambda, displacements);
+        return 0;
+    }
+    // The linesearch
+    // --------------
+    double fc = merit_value;
+    double fcp_prev;
+    int cnt = 0;
+    do
+    {
+        WARNING << "cnt : " <<cnt << " - lambda : " << lambda << " # " << fc << " # " << fcp << " # " << init_slope;
+        if (fcp <= fc + alpha*lambda*init_slope) //pg760 Fachinei2003
+        {
+            retcode = 0;
+            if (lambda ==1 and (newtlen > 0.99 * get_options().maximum_step_length)) {
+                get_perfs().maximum_step_taken = true;
+            }
+            break;
+        }
+        else if (lambda < minlambda)
+        {
+            retcode = 1;
+            break;
+        }
+        else
+        {
+            //WARNING << "denom : " << fcp - fc -init_slope;
+            // Select a new step length
+            // - - - - - - - - - - - -
+            double lambdatmp;
+            if (cnt == 0) { // only a quadratic at the first
+                lambdatmp = - init_slope / (2*(fcp - fc -init_slope));
+            }
+            else
+            {
+                const double factor = 1 /(lambda - lambda_prev);
+                const double x1 = fcp - fc - lambda*init_slope;
+                const double x2 = fcp_prev - fc - lambda_prev*init_slope;
+
+                const double a = factor * ( x1/(lambda*lambda) - x2/(lambda_prev*lambda_prev));
+                const double b = factor * ( -x1*lambda_prev/(lambda*lambda) + x2*lambda/(lambda_prev*lambda_prev));
+
+                if (a == 0)
+                { // cubic interpolation is in fact a quadratic interpolation
+                    lambdatmp = - init_slope/(2*b);
+                }
+                else
+                {
+                    const double disc = b*b-3*a*init_slope;
+                    lambdatmp = (-b+std::sqrt(disc))/(3*a);
+                }
+                if (lambdatmp > 0.5*lambda ) lambdatmp = 0.5*lambda;
+            }
+            //WARNING << "lambdatmp : " << lambdatmp;
+            lambda_prev = lambda;
+            fcp_prev = fcp;
+            if (lambdatmp < 0.1*lambda) {
+                lambda = 0.1 * lambda;
+            } else {
+                lambda = lambdatmp;
+            }
+        }
+        if (not std::isfinite(lambda))
+        {
+            ERROR << "Lambda is non finite - we stop";
+            throw std::runtime_error("Lambda is nonfinite : "+std::to_string(lambda));
+        }
+        fcp = compute_residuals_linesearch(update, lambda, xp);
+        //xp.velocity = x.velocity;
+        ++cnt;
+    } while(retcode == 2 and cnt < 100);
+    //WARNING << "Lambda : " << lambda << " - iterations : " << cnt;
+    if (cnt == 100)
+    {
+        ERROR << "Too much linesearch iterations ! We stop";
+    }
+    update_variable(update, lambda, displacements);
+    return retcode;
+}
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
diff --git a/src/dfpmsolver/parabolic_program.hpp b/src/dfpmsolver/parabolic_program.hpp
new file mode 100644
index 0000000..5a4d783
--- /dev/null
+++ b/src/dfpmsolver/parabolic_program.hpp
@@ -0,0 +1,63 @@
+#ifndef SPECMICP_DFPMSOLVER_PARABOLICPROGRAM_HPP
+#define SPECMICP_DFPMSOLVER_PARABOLICPROGRAM_HPP
+
+#include <Eigen/Dense>
+#include <Eigen/Sparse>
+#include "dfpm_program.hpp"
+
+namespace specmicp {
+namespace dfpmsolver {
+
+//! Concept class for a program
+template <class Derived>
+class ParabolicProgram: DFPMProgram<ParabolicProgram<Derived>>
+{
+public:
+    Derived* derived() {static_cast<Derived*>(this);}
+
+    //! \brief Return the number of equations
+    int get_neq() {return derived()->get_neq();}
+    //! \brief Return the number of degrees of freedom per node
+    int get_ndf() {return derived()->get_ndf();}
+
+    //! \brief Compute the residuals
+    void compute_residuals(const Eigen::VectorXd& displacement,
+                           const Eigen::VectorXd& velocity,
+                           Eigen::VectorXd& residual
+                           )
+    {
+        derived()->compute_residuals(displacement, velocity, residual);
+    }
+    //! \brief Compute the jacobian
+    void compute_jacobian(Eigen::VectorXd& displacement,
+                          Eigen::VectorXd& velocity,
+                          Eigen::SparseMatrix<double>& jacobian,
+                          double alphadt
+                          )
+    {
+        derived()->compute_jacobian(displacement, velocity, jacobian);
+    }
+    //! \brief Update the solutions
+    void update_solution(const Eigen::VectorXd& update,
+                         double lambda,
+                         double alpha_dt,
+                         Eigen::VectorXd& predictor,
+                         Eigen::VectorXd& displacement,
+                         Eigen::VectorXd& velocity)
+    {
+        derived()->update_solution(update, lambda, alpha_dt, predictor, displacement, velocity);
+    }
+    //! \brief Apply boundary conditions to the velocity vector
+    //!
+    //! by default do nothing.
+    void apply_bc(double dt,
+                  const Eigen::VectorXd& displacement,
+                  Eigen::VectorXd velocity)
+    {}
+
+};
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+#endif // SPECMICP_DFPMSOLVER_PARABOLICPROGRAM_HPP
diff --git a/src/dfpmsolver/parabolic_structs.hpp b/src/dfpmsolver/parabolic_structs.hpp
new file mode 100644
index 0000000..19f1085
--- /dev/null
+++ b/src/dfpmsolver/parabolic_structs.hpp
@@ -0,0 +1,42 @@
+#ifndef SPECMICP_DFPMSOLVER_PARABOLCSTRUCTS_HPP
+#define SPECMICP_DFPMSOLVER_PARABOLICSTRUCTS_HPP
+
+#include "driver_structs.hpp"
+
+namespace specmicp {
+namespace dfpmsolver {
+
+
+//! \brief Options of a parabolic driver
+//!
+struct ParabolicDriverOptions: public DriverOptions
+{
+    double alpha;                 //!< Implicit/Cranck-Nicholson parameter
+
+    ParabolicDriverOptions():
+        DriverOptions(),
+        alpha(1.0)
+    {}
+};
+
+//! \brief Performance of the parabolic driver
+struct ParabolicDriverPerformance: public DriverPerformance
+{
+};
+
+//! \brief Return codes
+enum class ParabolicDriverReturnCode
+{
+    MaxIterations = -4,            //!< Maximum number of iterations reached
+    MaxStepTakenTooManyTimes = -3, //!< Maximum step taken too many times (divergence)
+    ErrorLinearSystem = -2,        //!< Error when solving the linear system
+    StationaryPoint = -1,          //!< Stationnary points are detected
+    NotConvergedYet = 0,           //!< Problem is not converged
+    ResidualMinimized = 1,         //!< The residual is minimized (Success)
+    ErrorMinimized = 2             //!< Error is minimized (may be good)
+};
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+#endif // SPECMICP_DFPMSOLVER_PARABOLICSTRUCTS_HPP
diff --git a/src/dfpmsolver/sparse_solver.hpp b/src/dfpmsolver/sparse_solver.hpp
new file mode 100644
index 0000000..e6d8a1d
--- /dev/null
+++ b/src/dfpmsolver/sparse_solver.hpp
@@ -0,0 +1,97 @@
+#ifndef SPECMICP_DFPMSOLVER_SPARSESOLVER_HPP
+#define SPECMICP_DFPMSOLVER_SPARSESOLVER_HPP
+
+#include <Eigen/Dense>
+#include <Eigen/Sparse>
+
+#include "sparse_solver_structs.hpp"
+
+namespace specmicp {
+namespace dfpmsolver {
+
+// header
+// ======
+
+//! \brief Solve a sparse linear system
+template <class Vector, class Matrix>
+int solve_sparse_linear_system(Vector& residual, Matrix& jacobian, Vector& update, SparseSolver solver);
+
+
+//! \brief Solve a sparse linear system using the QR decomposition
+template <class Vector, class Matrix>
+int solve_linear_system_sparseqr(Vector& residual, Matrix& jacobian, Vector& update);
+
+
+//! \brief Solve a sparse linear system using the LU decomposition
+template <class Vector, class Matrix>
+int solve_linear_system_sparselu(Vector& residual, Matrix& jacobian, Vector& update);
+
+
+//! \brief Solve a sparse linear system using the BiCGSTAB algorithm
+template <class Vector, class Matrix>
+int solve_linear_system_bicgstab(Vector& residual, Matrix& jacobian, Vector& update);
+
+// Implementation
+// ==============
+
+template <class Vector, class Matrix>
+int solve_sparse_linear_system(Vector& residual, Matrix& jacobian, Vector& update, SparseSolver solver)
+{
+    if (solver == SparseSolver::SparseQR) {
+        return solve_linear_system_sparseqr(residual, jacobian, update);
+    } else if (solver == SparseSolver::SparseLU) {
+        return solve_linear_system_sparselu(residual, jacobian, update);
+    }
+    else {
+        return solve_linear_system_bicgstab(residual, jacobian, update);
+    }
+}
+
+template <class Vector, class Matrix>
+int solve_linear_system_sparseqr(Vector& residual, Matrix& jacobian, Vector& update)
+{
+    Eigen::SparseQR<Matrix, Eigen::COLAMDOrdering<int>> solver(jacobian);
+    if (solver.info() != Eigen::Success)
+    {
+        return -2;
+    }
+    update = solver.solve(-residual);
+    if (solver.info() != Eigen::Success)
+    {
+        return -1;
+    }
+    return 1;
+}
+
+template <class Vector, class Matrix>
+int solve_linear_system_sparselu(Vector& residual, Matrix& jacobian, Vector& update)
+{
+    Eigen::SparseLU<Matrix> solver(jacobian);
+    if (solver.info() != Eigen::Success)
+    {
+        return -2;
+    }
+    update = solver.solve(-residual);
+    if (solver.info() != Eigen::Success)
+    {
+        return -1;
+    }
+    return 1;
+}
+
+template <class Vector, class Matrix>
+int solve_linear_system_bicgstab(Vector& residual, Matrix& jacobian, Vector& update)
+{
+    Eigen::BiCGSTAB<Matrix, Eigen::IncompleteLUT<typename Matrix::Scalar>> solver(jacobian);
+    update = solver.solve(-residual);
+    if (solver.info() != Eigen::Success)
+    {
+        return -1;
+    }
+    return 1;
+}
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+#endif //SPECMICP_DFPMSOLVER_SPARSESOLVER_HPP
diff --git a/src/dfpmsolver/sparse_solver_structs.hpp b/src/dfpmsolver/sparse_solver_structs.hpp
new file mode 100644
index 0000000..50cec47
--- /dev/null
+++ b/src/dfpmsolver/sparse_solver_structs.hpp
@@ -0,0 +1,20 @@
+#ifndef SPECMICP_DFPMSOLVER_SPARSESOLVERSTRUCT_HPP
+#define SPECMICP_DFPMSOLVER_SPARSESOLVERSTRUCT_HPP
+
+
+namespace specmicp {
+namespace dfpmsolver {
+
+//! \brief Available sparse solver
+enum class SparseSolver
+{
+    SparseLU, //!< Eigen Sparse LU solver
+    SparseQR, //!< Eigen Sparse QR solver
+    BiCGSTAB  //!< Eigne BiCGSTAB solver
+};
+
+
+} // end namespace dfpmsolver
+} // end namespace specmicp
+
+#endif //SPECMICP_DFPMSOLVER_SPARSESOLVERSTRUCT_HPP
diff --git a/src/reactmicp/systems/saturated_diffusion/transport_parameters.hpp b/src/reactmicp/systems/saturated_diffusion/transport_parameters.hpp
new file mode 100644
index 0000000..0a61019
--- /dev/null
+++ b/src/reactmicp/systems/saturated_diffusion/transport_parameters.hpp
@@ -0,0 +1,38 @@
+#ifndef SPECMICP_REACTMICP_SATURATEDDIFFUSION_TRANSPORTPARAMETERS_HPP
+#define SPECMICP_REACTMICP_SATURATEDDIFFUSION_TRANSPORTPARAMETERS_HPP
+
+namespace specmicp {
+namespace reactmicp {
+namespace systems {
+namespace siasaturated {
+
+
+//! \brief Parameters for the diffusion system
+class SaturatedDiffusionTransportParameters
+{
+public:
+
+    SaturatedDiffusionTransportParameters(double diffusion_coefficient, double porosity):
+        m_diff(diffusion_coefficient), m_poro(porosity)
+    {}
+
+    //! \brief Density of water (kg/m^3)
+    double density_water() {return 1e3;}
+
+    //! \brief Diffusion coefficient (element by element) (m^2/s)
+    double diffusion_coefficient(int element) {return m_diff;}
+
+    //! \brief Porosity (at a node (function of composition))
+    double porosity(int node) {return m_poro;}
+
+private:
+    double m_diff;
+    double m_poro;
+};
+
+} // end namespace siasaturated
+} // end namespace systems
+} // end namespace reactmicp
+} // end namespace specmicp
+
+#endif // SPECMICP_REACTMICP_SATURATEDDIFFUSION_TRANSPORTPARAMETERS_HPP
diff --git a/src/reactmicp/systems/saturated_diffusion/transport_program.cpp b/src/reactmicp/systems/saturated_diffusion/transport_program.cpp
index 4810681..2f6689d 100644
--- a/src/reactmicp/systems/saturated_diffusion/transport_program.cpp
+++ b/src/reactmicp/systems/saturated_diffusion/transport_program.cpp
@@ -1,7 +1,205 @@
 #include "transport_program.hpp"
+#include <iostream>
+
+#define EPS_J 1e-8
 
 namespace specmicp {
 namespace reactmicp {
+namespace systems {
+namespace siasaturated {
+
+void SaturatedDiffusionProgram::number_equations(
+        std::vector<int> list_fixed_nodes
+        )
+{
+    m_ideq = Eigen::VectorXd::Zero(get_ndf()*m_mesh->nnodes());
+    for (auto it=list_fixed_nodes.begin(); it<list_fixed_nodes.end(); ++it)
+    {
+        for (int component: m_data->range_aqueous_component())
+        {
+            m_ideq(get_dof_component(*it, component)) = no_equation;
+        }
+    }
+
+    int neq = 0;
+    for (int dof=0; dof<m_ideq.rows(); ++dof)
+    {
+        if (m_ideq(dof) != no_equation)
+        {
+            m_ideq(dof) = neq;
+            ++neq;
+        }
+    }
+    m_neq = neq;
+}
+
+void SaturatedDiffusionProgram::compute_residuals(
+        const Eigen::VectorXd& displacement,
+        const Eigen::VectorXd& velocity,
+        Eigen::VectorXd& residual
+        )
+{
+    residual = Eigen::VectorXd::Zero(get_neq());
+    for (ind_t element: m_mesh->range_elements())
+    {
+        element_residual_transport(element, displacement, velocity, residual);
+    }
+}
+
+
+void SaturatedDiffusionProgram::element_residual_transport(
+        ind_t element,
+        const Eigen::VectorXd& displacement,
+        const Eigen::VectorXd& velocity,
+        Eigen::VectorXd& residual)
+{
+    Eigen::VectorXd element_residual(2);
+    for (ind_t component: m_data->range_aqueous_component())
+    {
+        element_residual_transport_component(element, component, displacement, velocity, element_residual);
+
+        for (int en: m_mesh->range_nen())
+        {
+            const ind_t node = m_mesh->get_node(element, en);
+            const ind_t id = m_ideq(get_dof_component(node, component));
+            if (id != no_equation) {residual(id) += element_residual(en);}
+        }
+    }
+}
+
+void SaturatedDiffusionProgram::element_residual_transport_component(
+        ind_t element,
+        int component,
+        const Eigen::VectorXd& displacement,
+        const Eigen::VectorXd& velocity,
+        Vector& element_residual)
+{
+
+    Eigen::Matrix2d mass, jacob;
+    Eigen::Vector2d evelocity, edisplacement;
+    double mass_coeff = -(m_param->density_water() *
+                          m_param->porosity(0) *
+                          m_mesh->crosssection()*m_mesh->get_dx(element)/2);
+    mass << 1, 0, 0, 1;
+    mass *= mass_coeff;
+    double flux_coeff = -( m_mesh->crosssection() / m_mesh->get_dx(element)
+                           * m_param->porosity(0)
+                           * m_param->diffusion_coefficient(element)
+                           * m_param->density_water()
+                           );
+    jacob << 1, -1, -1, 1;
+    jacob *= flux_coeff;
+    evelocity << velocity(get_dof_component(m_mesh->get_node(element, 0), component)),
+                velocity(get_dof_component(m_mesh->get_node(element, 1), component));
+
+    edisplacement <<  displacement(get_dof_component(m_mesh->get_node(element, 0), component)),
+                      displacement(get_dof_component(m_mesh->get_node(element, 1), component));
+
+    element_residual = mass*evelocity+jacob*edisplacement;
+
+    for (int en: m_mesh->range_nen())
+    {
+        element_residual(en) += (m_mesh->crosssection()*m_mesh->get_dx(element)/2
+                                 *external_flux(m_mesh->get_node(element, en), component));
+    }
+}
+
+
+void SaturatedDiffusionProgram::compute_jacobian(
+        Eigen::VectorXd& displacement,
+        Eigen::VectorXd& velocity,
+        Eigen::SparseMatrix<double>& jacobian,
+        double alphadt
+        )
+{
+
+    list_triplet_t jacob;
+    const ind_t ncomp = m_data->nb_component-1;
+    const ind_t estimation = m_mesh->nnodes()*(ncomp*m_mesh->nen);
+    jacob.reserve(estimation);
+    for (ind_t element: m_mesh->range_elements())
+    {
+        element_jacobian_transport(element, displacement, velocity, jacob, alphadt);
+    }
+    jacobian = SparseMatrix(get_neq(), get_neq());
+    jacobian.setFromTriplets(jacob.begin(), jacob.end());
+}
+
+// Transport
+// =========
+
+void SaturatedDiffusionProgram::element_jacobian_transport(
+        ind_t element,
+        Eigen::VectorXd& displacement,
+        Eigen::VectorXd& velocity,
+        list_triplet_t& jacobian,
+        double alphadt)
+{
+    for (int component: m_data->range_aqueous_component())
+    {
+        Eigen::VectorXd element_residual_orig(Eigen::VectorXd::Zero(2));
+        element_residual_transport_component(element, component, displacement, velocity, element_residual_orig);
+
+        for (int en: m_mesh->range_nen())
+        {
+            Eigen::VectorXd element_residual(Eigen::VectorXd::Zero(2));
+
+            const ind_t node = m_mesh->get_node(element, en);
+            const int dof = get_dof_component(node, component);
+            const int idc = m_ideq(dof);
+
+            if (idc == no_equation) continue;
+
+            const double tmp_v = velocity(dof);
+            const double tmp_d = displacement(dof);
+
+            double h = EPS_J*std::abs(tmp_v);
+            if (h == 0) h = EPS_J;
+            velocity(dof) = tmp_v + h;
+            h = velocity(dof) - tmp_v;
+
+            displacement(dof) = tmp_d + alphadt*h;
+
+            element_residual_transport_component(element, component, displacement, velocity, element_residual);
+            velocity(dof) = tmp_v;
+            displacement(dof) = tmp_d;
+
+            for (int enr: m_mesh->range_nen())
+            {
+                const ind_t noder = m_mesh->get_node(element, enr);
+                const ind_t idr = m_ideq(get_dof_component(noder, component));
+
+                if (idr == no_equation) continue;
+                jacobian.push_back(triplet_t(idr, idc, (element_residual(enr) - element_residual_orig(enr))/h));
+            }
+        }
+
+    }
+}
+
+void SaturatedDiffusionProgram::update_solution(
+        const Eigen::VectorXd& update,
+        double lambda,
+        double alpha_dt,
+        Eigen::VectorXd& predictor,
+        Eigen::VectorXd& displacement,
+        Eigen::VectorXd& velocity
+        )
+{
+    for (int node: m_mesh->range_nodes())
+    {
+        for (int component: m_data->range_aqueous_component())
+        {
+            const int dof = get_dof_component(node, component);
+            const int id = m_ideq(dof);
+            if (id == no_equation) continue;
+            velocity(dof) += update(id);
+        }
+    }
+    displacement = predictor + alpha_dt*velocity;
+}
 
+} // end namespace siasaturated
+} // end namespace systems
 } // end namespace reactmicp
 } // end namespace specmicp
diff --git a/src/reactmicp/systems/saturated_diffusion/transport_program.hpp b/src/reactmicp/systems/saturated_diffusion/transport_program.hpp
index 8e2e74c..cd2744c 100644
--- a/src/reactmicp/systems/saturated_diffusion/transport_program.hpp
+++ b/src/reactmicp/systems/saturated_diffusion/transport_program.hpp
@@ -1,10 +1,137 @@
 #ifndef SPECMICP_REACTMICP_SATURATED_DIFFUSION_TRANSPORTPROGRAM_HPP
 #define SPECMICP_REACTMICP_SATURATED_DIFFUSION_TRANSPORTPROGRAM_HPP
 
+#include <memory>
+
+#include "database/data_container.hpp"
+#include "dfpmsolver/parabolic_program.hpp"
+#include "transport_parameters.hpp"
+#include "reactmicp/meshes/mesh1d.hpp"
+
 namespace specmicp {
 namespace reactmicp {
+namespace systems {
+namespace siasaturated {
+
+class SaturatedDiffusionProgram: dfpmsolver::ParabolicProgram<SaturatedDiffusionProgram>
+{
+public:
+    SaturatedDiffusionProgram(
+            std::shared_ptr<mesh::Mesh1D> the_mesh,
+            std::shared_ptr<database::DataContainer> the_data,
+            std::shared_ptr<SaturatedDiffusionTransportParameters> the_param
+            ):
+        m_mesh(the_mesh),
+        m_data(the_data),
+        m_param(the_param),
+        m_external_flux(Eigen::VectorXd::Zero(the_mesh->nnodes()*(the_data->nb_component-1)))
+    {}
+
+    const int no_equation = -1;
+    //! \brief Return the number of equations
+    int get_neq() const {return m_neq;}
+    //! \brief Return the number of degrees of freedom per node
+    int get_ndf() const {return m_data->nb_component-1;}
+
+    // number the equations
+    void number_equations(std::vector<int> list_fixed_nodes);
+
+    //! \brief Compute the residuals
+    void compute_residuals(const Eigen::VectorXd& displacement,
+                           const Eigen::VectorXd& velocity,
+                           Eigen::VectorXd& residual
+                           );
+
+    //! \brief Compute the jacobian
+    void compute_jacobian(Eigen::VectorXd& displacement,
+                          Eigen::VectorXd& velocity,
+                          Eigen::SparseMatrix<double> &jacobian,
+                          double alphadt);
+
+    //! \brief Update the solutions
+    void update_solution(const Eigen::VectorXd& update,
+                         double lambda,
+                         double alpha_dt,
+                         Eigen::VectorXd& predictor,
+                         Eigen::VectorXd& displacement,
+                         Eigen::VectorXd& velocity);
+
+    //! \brief Apply boundary conditions to the velocity vector
+    //!
+    //! by default do nothing.
+    void apply_bc(double dt,
+                  const Eigen::VectorXd& displacement,
+                  Eigen::VectorXd velocity)
+    {}
+
+    //! Return the degree of freedom number of 'component' at 'node'
+    int get_dof_component(int node, int component) const {
+        assert(component >=1 and component < m_data->nb_component);
+        assert(node >= 0 and node < m_mesh->nnodes());
+        return (component - 1)+get_ndf()*node;
+    }
+    //! Return the degree of freedom number of 'component' at 'node'
+    int get_dof_mineral(int node, int mineral) const {
+        assert(mineral >= 0 and mineral < m_data->nb_mineral);
+        assert(node >= 0 and node < m_mesh->nnodes());
+        return mineral+m_data->nb_mineral*node;
+    }
+    //! Return the total mobile concentration
+    double& get_total_mobile_concentration(int node, int component, Eigen::VectorXd& concentrations) const
+    {
+        return concentrations.coeffRef(get_dof_component(node, component));
+    }
+    //! Return the total mobile concentration
+    double get_total_mobile_concentration(int node, int component, const Eigen::VectorXd& concentrations) const
+    {
+        return concentrations.coeff(get_dof_component(node, component));
+    }
+    //! Return the external flux
+    Eigen::VectorXd& external_flux() {return m_external_flux;}
+    //! Return the external flux for 'component' at 'node'
+    double& external_flux(int node, int component) {return m_external_flux(get_dof_component(node, component));}
+
+private:
+
+    void element_residual_transport(
+            ind_t element,
+            const Eigen::VectorXd& displacement,
+            const Eigen::VectorXd& velocity,
+            Eigen::VectorXd& residual);
+
+    void element_residual_transport_component(
+            ind_t element,
+            int component,
+            const Eigen::VectorXd& displacement,
+            const Eigen::VectorXd& velocity,
+            Vector& element_residual);
+
+    double nodal_mineral_transient_term_transport(
+            ind_t node,
+            ind_t component);
+
+
+    void element_jacobian_transport(
+            ind_t element,
+            Eigen::VectorXd& displacement,
+            Eigen::VectorXd& velocity,
+            list_triplet_t& jacobian,
+            double alphadt);
+
+    int m_neq;
+    Eigen::VectorXd m_ideq;
+
+    std::shared_ptr<mesh::Mesh1D> m_mesh;
+    std::shared_ptr<database::DataContainer> m_data;
+    std::shared_ptr<SaturatedDiffusionTransportParameters> m_param;
+
+    Eigen::VectorXd m_external_flux;
+
+};
 
+} // end namespace siasaturated
+} // end namespace systems
 } // end namespace reactmicp
 } // end namespace specmicp
 
 #endif // SPECMICP_REACTMICP_SATURATED_DIFFUSION_TRANSPORTPROGRAM_HPP
diff --git a/tests/reactmip/systems/saturated_diffusion/solver.cpp b/tests/reactmip/systems/saturated_diffusion/solver.cpp
new file mode 100644
index 0000000..5a33c39
--- /dev/null
+++ b/tests/reactmip/systems/saturated_diffusion/solver.cpp
@@ -0,0 +1,51 @@
+#include "catch.hpp"
+
+#include "utils.hpp"
+#include "dfpmsolver/parabolic_driver.hpp"
+#include "reactmicp/systems/saturated_diffusion/transport_program.hpp"
+
+using namespace specmicp;
+using namespace specmicp::reactmicp;
+using namespace specmicp::reactmicp::systems;
+using namespace specmicp::reactmicp::systems::siasaturated;
+
+
+TEST_CASE("dfpm solver", "[dfpm, solver]") {
+    SECTION("transport program") {
+        //specmicp::stdlog::ReportLevel() = specmicp::logger::Warning;
+        //specmicp::logger::ErrFile::stream() = &std::cerr;
+
+        std::shared_ptr<database::DataContainer> database = get_test_carbo_database();
+        int nb_nodes = 5;
+
+        EquilibriumState initial_state = sample_carbo_composition(database);
+
+        std::shared_ptr<mesh::Mesh1D> themesh = std::make_shared<mesh::Mesh1D>(nb_nodes-1, 1e-3, 0.001);
+        auto parameters = std::make_shared<SaturatedDiffusionTransportParameters>(1e-8, 0.2);
+
+        SaturatedDiffusionProgram the_program(themesh, database, parameters);
+
+        Eigen::VectorXd concentrations(themesh->nnodes()*the_program.get_ndf());
+        concentrations.setOnes();
+        concentrations.block(0, 0, the_program.get_ndf(), 1).setZero();
+        the_program.number_equations({0,});
+
+        dfpmsolver::ParabolicDriver<SaturatedDiffusionProgram> solver(the_program);
+        solver.get_velocity() = Eigen::VectorXd::Zero(concentrations.rows());
+        double dt = 100;
+        dfpmsolver::ParabolicDriverReturnCode retcode = solver.solve_timestep(dt, concentrations);
+        REQUIRE(retcode == dfpmsolver::ParabolicDriverReturnCode::ResidualMinimized);
+
+        for (int node=1; node<themesh->nnodes(); ++node)
+        {
+            for (int component: database->range_aqueous_component())
+            {
+                the_program.external_flux(node, component) =
+                        parameters->porosity(0)*(1.0 - concentrations(the_program.get_dof_component(node, component)))
+                        / (parameters->density_water()*dt*themesh->cell_volume(node));
+            }
+        }
+        retcode = solver.restart_timestep(concentrations);
+        REQUIRE(retcode == dfpmsolver::ParabolicDriverReturnCode::ResidualMinimized);
+    }
+}
diff --git a/tests/reactmip/systems/saturated_diffusion/transport_program.cpp b/tests/reactmip/systems/saturated_diffusion/transport_program.cpp
new file mode 100644
index 0000000..86e540b
--- /dev/null
+++ b/tests/reactmip/systems/saturated_diffusion/transport_program.cpp
@@ -0,0 +1,26 @@
+#include "catch.hpp"
+
+#include "utils.hpp"
+#include "reactmicp/systems/saturated_diffusion/transport_program.hpp"
+
+using namespace specmicp;
+using namespace specmicp::reactmicp;
+using namespace specmicp::reactmicp::systems;
+using namespace specmicp::reactmicp::systems::siasaturated;
+
+TEST_CASE("Transport program", "[Transport, Diffusion, Finite Element, Solver]") {
+
+    std::shared_ptr<database::DataContainer> database = get_test_carbo_database();
+    int nb_nodes = 5;
+
+    EquilibriumState initial_state = sample_carbo_composition(database);
+
+    std::shared_ptr<mesh::Mesh1D> themesh = std::make_shared<mesh::Mesh1D>(nb_nodes-1, 1e-3, 0.001);
+    auto parameters = std::make_shared<SaturatedDiffusionTransportParameters>(1e-6, 0.2);
+
+    SECTION("Initialization") {
+        SaturatedDiffusionProgram(themesh, database, parameters);
+    }
+
+
+}