From e8991f54ced96f3a576c411993c307ef1d79642b Mon Sep 17 00:00:00 2001
From: HEDIN Florent <florent.hedin@inria.fr>
Date: Fri, 8 Mar 2019 12:08:48 +0100
Subject: [PATCH] CI enabled, reproducibility added, ready for release v1.2

---
 .gitignore                                    |   3 +
 .gitlab-ci.yml                                |  35 ++
 CHANGELOG.md                                  |  42 +-
 CMakeLists.txt                                |  16 +-
 LICENSE.txt                                   |   2 +-
 README.md                                     | 164 +++---
 ...cies.sh => download_deps_and_run_cmake.sh} |   2 +-
 ci/.gitignore                                 |   3 +
 ci/README.md                                  |   3 +
 ci/input_generalized_parRep.lua               | 495 ++++++++++++++++++
 ci/lj7/Integrator.xml                         |   2 +
 ci/lj7/State.xml                              |  26 +
 ci/lj7/System.xml                             |  36 ++
 ci/lj7/omm_LJ7.py                             | 139 +++++
 ci/ref.sim/README.md                          |  23 +
 ci/ref.sim/lj7.db                             | Bin 0 -> 16384 bytes
 ci/ref.sim/seeds.bin                          | Bin 0 -> 16384 bytes
 ci/run.ci.sh                                  |  27 +
 include/dyna/GelmanRubin.hpp                  |  13 +-
 include/dyna/observable.hpp                   |   2 +-
 include/dyna/parRep.hpp                       |   2 +-
 include/dyna/parRep_FV.hpp                    |   2 +-
 include/dyna/parRep_FV_multiExits.hpp         |   2 +-
 include/dyna/parRep_abstract.hpp              |   2 +-
 include/dyna/runSim.hpp                       |  11 +-
 include/global.hpp                            |   2 +-
 include/logger.hpp                            |   2 +-
 include/md/md_interface.hpp                   |   2 +-
 include/md/omm_interface.hpp                  |   2 +-
 include/rand.hpp                              |  38 +-
 include/utils/lua_interface.hpp               |   2 +-
 include/utils/mpi_utils.hpp                   |   2 +-
 mol/1d7h_i/input_generalized_parRep.lua       |  15 +-
 mol/ala2vac/input_generalized_parRep.lua      |  10 +-
 .../input_generalized_parRep_multiExit.lua    |  10 +-
 mol/ala2vac/input_parRep.lua                  |   5 +-
 .../input_generalized_parRep.lua              |  22 +-
 mol/ala2vac_transient/input_parRep.lua        |  11 +-
 parRep.doxy                                   |   2 +-
 run/run.bash.sh                               |  31 +-
 run/run.slurm.sh                              |   8 +-
 src/dyna/GelmanRubin.cpp                      |  26 +-
 src/dyna/observable.cpp                       |   2 +-
 src/dyna/parRep.cpp                           |   2 +-
 src/dyna/parRep_FV.cpp                        |   3 +-
 src/dyna/parRep_FV_multiExits.cpp             |   2 +-
 src/dyna/runSim.cpp                           |  55 +-
 src/logger.cpp                                |   2 +-
 src/main.cpp                                  |  29 +-
 src/md/md_interface.cpp                       |   2 +-
 src/md/omm_interface.cpp                      |  30 +-
 src/rand.cpp                                  | 155 ++++--
 src/utils/lua_interface.cpp                   |  90 +++-
 src/utils/mpi_utils.cpp                       |   2 +-
 54 files changed, 1364 insertions(+), 252 deletions(-)
 create mode 100644 .gitlab-ci.yml
 rename build/{download_dependencies.sh => download_deps_and_run_cmake.sh} (94%)
 create mode 100644 ci/.gitignore
 create mode 100644 ci/README.md
 create mode 100755 ci/input_generalized_parRep.lua
 create mode 100644 ci/lj7/Integrator.xml
 create mode 100644 ci/lj7/State.xml
 create mode 100644 ci/lj7/System.xml
 create mode 100755 ci/lj7/omm_LJ7.py
 create mode 100644 ci/ref.sim/README.md
 create mode 100644 ci/ref.sim/lj7.db
 create mode 100644 ci/ref.sim/seeds.bin
 create mode 100755 ci/run.ci.sh

diff --git a/.gitignore b/.gitignore
index 2804c78..b51356f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@ build/LuaJIT-2.0.5*
 build/Makefile
 build/cmake_install.cmake
 build/openmm-7.3.0*
+build/CTestTestfile.cmake
+build/parRep
+build/Testing
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..8459704
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,35 @@
+stages: 
+- build
+- test
+
+variables:
+    MAKE_COMMAND: "make -j 4"
+
+cache: # Directories that need to be kept between the build job and the test job
+    paths:
+    - build/
+
+#----------------------------------------------------------------
+# Linux build/test procedure
+#----------------------------------------------------------------
+
+# Build job
+parrep_build_ubuntu:
+    stage: build
+    tags:
+    - parrep-ubuntu-1604-amd64
+    script:
+    - cd build
+    - bash download_deps_and_run_cmake.sh
+    - make
+
+# Test job
+parrep_test_ubuntu:
+    stage: test
+    tags:
+    - parrep-ubuntu-1604-amd64
+    script:
+    - echo "$PWD"
+    - cd build
+    - ctest -V
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfb604c..0a7d665 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,39 @@
+# v1.2.0
+
+Mar 08th, 2019
+
+The software was modified in order to allow reproducibility in some cases (the limiting factor is OpenMM which does not
+always provides deterministic output even when using the same seeds (!), see http://docs.openmm.org/latest/userguide/library.html#determinism ).
+
+The main executable now has 2 more command line options, '--inp-seeds [fname]' or '--out-seeds [fname]' for respectively loading seeds or writting seeds 
+from/to a unique binary file. See rand.hpp/rand.cpp, od the Doxygen doc for more details.
+
+These modifications now allow Continuous Integration (CI) on the infrastructure provided by INRIA : in mol/ci a small test case will be executed at each commit to the repository and compared to reference results.
+
+The two other minor modifications concern the Lua scripts:
+
+* "get_minimised_energy_crdvels" was added to the set of functions that the user can call from the Lua script : it simply combines in one call what 
+"get_minimised_energy" and "get_minimised_crdvels" already provided.
+
+* a extra optional parameter is available for the "simulation" parameters block when "simulation.algorithm" is "PARREP_FV" ; this parameter is
+"simulation.minAccumulatedObs" : it will enforce that at least minAccumulatedObs observations of an observable have already been accumulated
+before the convergence test is performed ; this may be useful if there is a risk of early pseudo-convergence for some of the observables when only a few samples have been accumulated.
+
+
+**Download sources:**
+
+https://gitlab.inria.fr/parallel-replica/gen.parRep/tags/v1.2.0
+
+or
+
+https://github.com/FHedin/gen.parRep/releases/tag/v1.2.0
+
 ----------------------------------------------
 
 # v1.1.0
 
+Nov 16th, 2018
+
 Finished the implementation of a "MD_interface" abstract class for future addition of different Molecular Dynamics engines.
 This required important changes to a significant part of the code.
 Optimizations to the MPI communications were also performed, therefore the minor version number
@@ -19,7 +51,9 @@ https://github.com/FHedin/gen.parRep/releases/tag/v1.1.0
 
 # v1.0.1
 
-simply added directory structure to readme file 
+Aug 16th, 2018
+
+Simply added directory structure to readme file.
 
 **Download sources:**
 
@@ -33,7 +67,9 @@ https://github.com/FHedin/gen.parRep/releases/tag/v1.0.1
 
 # v1.0.0
 
-Release of v1.0.0 : several bug fixes like memory leaks adressed, added input files for alanine
+Aug 13th, 2018
+
+Release of v1.0.0 : several bug fixes like memory leaks addressed, added input files for alanine
 dipeptide using transient propagation.
 
 **Download sources:**
@@ -52,7 +88,7 @@ July 6th, 2018
 
 First public release of the software.
 It is following the submission of the corresponding article on arXiv ; this is a release candidate 1.0.0-rc1,
-the 1.0.0 release should follow within the next weeks
+the 1.0.0 release should follow within the next weeks.
 
 **Download sources:**
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b27dc9a..91af3b1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,8 @@ if(NOT CMAKE_BUILD_TYPE)
       FORCE)
 endif()
 
+enable_testing()
+
 set(TGT "parRep" CACHE STRING "The executable name")
 
 project(${TGT} C CXX)
@@ -37,7 +39,7 @@ include
 include/dyna
 include/md
 include/utils
-# external headers (dependancies provided with this project)
+# external headers (dependencies provided with this project)
 external/sol2
 )
 
@@ -61,12 +63,10 @@ src/md/omm_interface.cpp
 # utilities files
 src/utils/mpi_utils.cpp
 src/utils/lua_interface.cpp
-# external dependancies provided with this project
+# external dependencies provided with this project
 external/luasqlite3/lsqlite3.c
 )
 
-add_executable(${TGT} ${SRCS})
-
 set(cWarnings "-Wall -Wextra")
 # more warnings for c++
 # set(cxxWarnings "-Wall -Wextra -Wformat=2 -Wshadow -Wconversion -Wuseless-cast")
@@ -90,4 +90,12 @@ set(CMAKE_CXX_FLAGS_RELEASE        "-O3    -march=native")
 set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -march=native")
 set(CMAKE_CXX_FLAGS_MINSIZEREL     "-Os -s -march=native")
 
+add_executable(${TGT} ${SRCS})
+
 target_link_libraries(${TGT} ${OpenMM_LIBRARY} ${LUA_LIBRARIES} ${MPI_LIBRARIES} ${SQLITE3_LIBRARIES})
+
+add_test(
+NAME test_ci_ar7_gen.parRep
+COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/ci/run.ci.sh
+WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ci
+)
diff --git a/LICENSE.txt b/LICENSE.txt
index 9e1f08e..3a0db8c 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2016-2018, Florent Hédin, Tony Lelièvre, and École des Ponts - ParisTech
+Copyright (c) 2016-2019, Florent Hédin, Tony Lelièvre, and École des Ponts - ParisTech,
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification,
diff --git a/README.md b/README.md
index 9e0f280..3e5253b 100644
--- a/README.md
+++ b/README.md
@@ -1,137 +1,162 @@
-----------------------------------------------
-# gen.parRep (v1.1)
-----------------------------------------------
+# gen.parRep (v1.2)
 
 C++ implementation of the Generalized Parallel Replica algorithm
 
 Link to this repository : 
-  * https://gitlab.inria.fr/parallel-replica/gen.parRep
+
+* https://gitlab.inria.fr/parallel-replica/gen.parRep
 
 Link to the project gitlab page (includes more resources):
-  * https://gitlab.inria.fr/parallel-replica
+
+* https://gitlab.inria.fr/parallel-replica
 
 See the following articles:
-  * A generalized parallel replica dynamics: Binder, Lelièvre & Simpson, 2015: https://doi.org/10.1016/j.jcp.2015.01.002
-  * A new implementation of the Generalized Parallel Replica Dynamics targeting metastable biochemical systems: Hédin & Lelièvre, 2018: https://arxiv.org/abs/1807.02431
 
+* A generalized parallel replica dynamics: Binder, Lelièvre & Simpson, 2015: https://doi.org/10.1016/j.jcp.2015.01.002
+
+* gen.parRep: a first implementation of the Generalized Parallel Replica dynamics for the long time simulation of metastable biochemical systems: Hédin & Lelièvre, 2018: https://arxiv.org/abs/1807.02431 and https://doi.org/10.1016/j.cpc.2019.01.005
 
 Molecular dynamics is performed by using external codes linked to this program such as:
-  * OpenMM (https://github.com/pandegroup/openmm and https://simtk.org/home/openmm)
 
-----------------------------------------------
+* OpenMM (https://github.com/pandegroup/openmm and https://simtk.org/home/openmm)
+
+If you find any error, bug, or limitation, please open an issue on https://gitlab.inria.fr/parallel-replica/gen.parRep/issues so that the community can benefit from its correction ; also feel free to clone/fork the repo and perform the modifications yourself if you have the programming abilities to do so !
+
+Continuous Integration is performed on a test case system (see ./ci), ensuring that no release breaks the ability to compile the software; furthermore reference results from a previous run are available in ./ci/ref.sim in an attempt to achieve reproducibility whenever possible.
+
+[![pipeline status](https://gitlab.inria.fr/parallel-replica/gen.parRep/badges/test_ci/pipeline.svg)](https://gitlab.inria.fr/parallel-replica/gen.parRep/commits/master)
+
 ## DOCUMENTATION
-----------------------------------------------
 
 Please consult the Gitlab wiki for an overview of this software use: 
 
 https://gitlab.inria.fr/parallel-replica/gen.parRep/wikis/home
 
-Doxygen can be used for generating a code reference documentation within the docs directory:
-  * doxygen parRep.doxy 
+Doxygen can be used for generating a code reference documentation within the docs directory (particularly useful if you plan to modify/extend the software):
+
+* doxygen parRep.doxy 
 
 By default an HTML reference is generated (ready to read), and a docs/latex directory is also created, although the pdf version requires to be manually compiled (pdfLaTeX, see the Makefile in ./docs/latex).
 
-The Lua input files, in ./mol, used for starting computations, are self documented, and together
-with the bash submission files in the ./run directory should be enough for starting to use the program
+The Lua input files, in ./mol, used for starting simulations, are self documented, and together
+with the bash submission files in the ./run directory, it should be enough for starting to use the program.
+
+## DEPENDENCIES
+
+### Dependencies required before running the CMakeLists.txt script
 
-----------------------------------------------
-## DEPENDANCIES
-----------------------------------------------
+You will need an MPI development framework installed, compatible with the MPI 3.0 or newer standard : tested implementations :
 
-### Dependancies required before running the CMakeLists.txt script
+* Open MPI 1.10.2 (from Ubuntu 16.04 repositories)
 
-You will need an MPI development framework installed, compatible with the MPI 3.0 or newer standard : tested implementations : 
-  * Open MPI 1.10.2 (from Ubuntu 16.04 repositories)
 CMake will try to locate it automatically.
 
-You will need the SQLite3 dynamic library on your system: 
-See :
-  * https://sqlite.org/index.html
+You will need the SQLite3 dynamic library on your system. See :
 
-### Dependancies downloaded automatically via the script "build/download_dependencies.sh"
+* https://sqlite.org/index.html
 
-The script "build/download_dependencies.sh" can download and compile if required the OpenMM and LuaJIT dependancies.
+### Dependencies downloaded automatically via the script "build/download_deps_and_run_cmake.sh"
 
-You can however use your own version of the OpenMM library, or any Lua inplementation (either compiled yourself or downloaded somewhere).
+The script "build/download_deps_and_run_cmake.sh" will download and compile if required the OpenMM and LuaJIT Dependencies.
+
+You can however use your own version of the OpenMM library, or any Lua implementation (either compiled yourself or downloaded somewhere).
 
 #### Ressources (OpenMM): 
+
 * see https://simtk.org/home/openmm
+
 * and/or https://github.com/pandegroup/openmm
+
 * tested with version 7.0 to 7.3.
 
-You may need Nvidia CUDA or AMD OpenCL toolkit for enabling GPU acceleration ; see OpenMM documentation.
+You may need NVIDIA CUDA or AMD OpenCL toolkit for enabling GPU acceleration ; see OpenMM documentation.
 You may also need to edit CMakeLists.txt for specifying path to the include and lib directories of OpenMM.
 CMake will try to locate it automatically.
 
 #### Ressources (Lua/LuaJIT): 
 
 A Lua implementation compatible with Lua API version >= 5.1 is required :
-  * A release of Lua 5.x is usually already installed by default for recent linux versions, try to execute 'lua' and/or 'locate liblua'
-  * You can Download and compile the official implementation : http://www.lua.org/download.html
-  * The LuaJIT implementation can provide an important speedup : http://luajit.org/download.html
+
+* A release of Lua 5.x is usually already installed by default for recent linux versions, try to execute 'lua' and/or 'locate liblua'
+
+* You can Download and compile the official implementation : http://www.lua.org/download.html
+
+* The LuaJIT implementation can provide an important speedup : http://luajit.org/download.html
+
 Please download and compile any of the two. See below for hints for setting DCMAKE_PREFIX_PATH in case of a manual install.
 CMake will try to locate it automatically.
 
-### Dependancies provided in directory "external"
+### Dependencies provided in directory "external"
 
-The excellent Sol2 (header-only Lua<->c++ inteface) and LuaSQLite3 (a Lua/SQLite3 binding) dependancies are provided
+The excellent Sol2 (header-only Lua<->c++ interface) and LuaSQLite3 (a Lua/SQLite3 binding) Dependencies are provided
 in the './external' directory and are automatically included/compiled if required, and thus should not require extra
-configuration.
-See : 
-  * https://github.com/ThePhD/sol2
-  * http://lua.sqlite.org/index.cgi/home
+configuration. See :
+
+* https://github.com/ThePhD/sol2
+
+* http://lua.sqlite.org/index.cgi/home
 
-----------------------------------------------
 ## COMPILE & INSTALL
-----------------------------------------------
 
 C and C++ compiler compatible with the C99 and C++14 standards are required.
 Tested compilers:
-  * gcc/g++ 5.4.0 and 6.2.0 (from Ubuntu 16.04 repositories)
+
+* gcc/g++ 5.4.0 and 6.2.0 (from Ubuntu 16.04 repositories)
 
 Be sure to have CMake installed (http://www.cmake.org/), available on most repositories.
 Tested version :
-  * CMake 3.5.1 (from Ubuntu 16.04 repositories)
 
-Create a build directory and move to that directory: 
-  * mkdir build && cd build
+* CMake 3.5.1 (from Ubuntu 16.04 repositories)
+
+Create a build directory and move to that directory:
+
+* mkdir build && cd build
 
 For building a debug or release or an intermediate release with debug information, do:
-  * cmake -DCMAKE_BUILD_TYPE=Debug ..
-  * cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
-  * cmake -DCMAKE_BUILD_TYPE=Release ..         (default, with cpu targeting for the current compilation machine)
 
-Debug builds are possibly slower, usually more memory consuming, but useful when debugging with gdb or valgrind.
+* cmake -DCMAKE_BUILD_TYPE=Debug ..
+
+* cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..
 
-If some dependancies were note detected (e.g. because they are not available within /usr/local) 
+* cmake -DCMAKE_BUILD_TYPE=Release ..         (default, with cpu targeting for the current compilation machine)
+
+Debug builds are possibly slower, usually more memory consuming, but useful when debugging with gdb or Valgrind.
+
+If some dependencies were note detected (e.g. because they are not available within /usr/local)
 it is required to add the path where the were installed to CMAKE_PREFIX_PATH, e.g.:
-  * cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$HOME/bin/something" ..
+
+* cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$HOME/bin/something" ..
 
 Then, once CMake built a Makefile without error, just execute the following in order to build the executable:
-  * make
 
-For a verbose make, use: 
-  * make VERBOSE=1
+* make
+
+For a verbose make, use:
+
+* make VERBOSE=1
 
 For a faster multi-core (on N cores) build, use:
-  * make -j N
 
-Please never edit the autogenerated Makefile, edit the CMakeLists.txt instead.
+* make -j N
+
+Please never edit the automatically generated Makefile, edit the CMakeLists.txt instead.
+
+For specifying another compiler on linux, for example clang, or a proprietary one like Intel icc (untested) :
+
+* CC=clang CXX=clang++ cmake ..
+
+* CC=icc CXX=icpc cmake ..
 
-For specifying another compiler on linux, for example clang, or a proprietary one like Intel icc (untested) : 
-  * CC=clang CXX=clang++ cmake ..
-  * CC=icc CXX=icpc cmake ..
- 
 In any case, you may need to edit the variables CMAKE_C_FLAGS_* and CMAKE_CXX_FLAGS_*
 for setting proper levels of optimization. By default 
 
-When using OpenMPI and forcing another C/C++ compilers version, the following might be required (possibly unsafe !): 
-  * OMPI_CC=gcc-5 OMPI_CXX=g++-5 CC=gcc-5 CXX=g++-5 ccmake ..
+When using OpenMPI and forcing another C/C++ compilers version, the following might be required (possibly unsafe !):
+
+* OMPI_CC=gcc-5 OMPI_CXX=g++-5 CC=gcc-5 CXX=g++-5 ccmake ..
 
-----------------------------------------------
 ## LICENSING (all files excepted subdirectory external and its content)
-----------------------------------------------
-Copyright (c) 2016-2018, Florent Hédin, Tony Lelièvre, and École des Ponts - ParisTech
+
+Copyright (c) 2016-2019, Florent Hédin, Tony Lelièvre, and École des Ponts - ParisTech
 All rights reserved.
 
 The 3-clause BSD license is applied to this software.
@@ -140,9 +165,7 @@ See LICENSE.txt
 
 See in ./external/* for licensing information concerning the embedded libraries provided in './external' 
 
-----------------------------------------------
 ## NOTES CONCERNING OpenMM
-----------------------------------------------
 
 The software should automatically detect the fastest OpenMM Platform available on your computer (i.e. CUDA, OpenCL, ...)
 If not it will run with the slow Reference platform : it is most probably because the OpenMM directory with the
@@ -151,18 +174,18 @@ If not it will run with the slow Reference platform : it is most probably becaus
 You may need to export the following OPENMM_PLUGIN_DIR environment variable to solve the problem : 
 
 For example for a custom installation in /home/$USER/bin/openmm
-  * export OPENMM_PLUGIN_DIR=$HOME/bin/openmm/lib/plugins
+
+* export OPENMM_PLUGIN_DIR=$HOME/bin/openmm/lib/plugins
 
 For setting the amount of cpu threads used by each MPI process use the following env. variable :
-  * export OPENMM_CPU_THREADS=1
 
-----------------------------------------------
+* export OPENMM_CPU_THREADS=1
+
 ## Directory structure
-----------------------------------------------
 
 **src** and **include** -> contains the C++ source and headers of the gen.parRep software.
 
-**build** -> directory where dependancies will be downloaded, and the program built.
+**build** -> directory where dependencies will be downloaded, and the program built.
 
 **external** -> contains source and headers of dependencies used by the gen.parRep software.
 
@@ -172,5 +195,6 @@ For setting the amount of cpu threads used by each MPI process use the following
 
 **mol** -> contains ready to use test systems, i.e. input files and OpenMM configurations of various molecular systems.
 
-**run** -> contains bash scripts demonstrating how to run the software using either mpirun or the SLURM scheduler; also contains three bash files for runing the software under the following debuggers/profilers: GDB, Valgrind and Scalasca.
+**run** -> contains bash scripts demonstrating how to run the software using either mpirun or the SLURM scheduler; also contains bash files for running the software under the following debuggers/profilers: GDB, Valgrind and Scalasca.
 
+**ci** -> contains a molecular system, an input script and reference data for performing continuous integration and non regression tests.
diff --git a/build/download_dependencies.sh b/build/download_deps_and_run_cmake.sh
similarity index 94%
rename from build/download_dependencies.sh
rename to build/download_deps_and_run_cmake.sh
index 976dad2..f27d3c5 100755
--- a/build/download_dependencies.sh
+++ b/build/download_deps_and_run_cmake.sh
@@ -21,7 +21,7 @@ then
   fi
   tar xf LuaJIT-2.0.5.tar.gz
   cd LuaJIT-2.0.5
-  make PREFIX=$PWD/build -j 8 install
+  make PREFIX=$PWD/build install
   cd ..
 fi
 export LUAJIT_INSTALL_DIR=$PWD/LuaJIT-2.0.5/build
diff --git a/ci/.gitignore b/ci/.gitignore
new file mode 100644
index 0000000..40eb0f2
--- /dev/null
+++ b/ci/.gitignore
@@ -0,0 +1,3 @@
+*.txt
+*.log
+*.db
diff --git a/ci/README.md b/ci/README.md
new file mode 100644
index 0000000..fbf5afd
--- /dev/null
+++ b/ci/README.md
@@ -0,0 +1,3 @@
+# WARNING
+
+This is a simple test case executed when Continuous Integration tasks are performed ; it does not really make sense to use parRep on such a small test system !
diff --git a/ci/input_generalized_parRep.lua b/ci/input_generalized_parRep.lua
new file mode 100755
index 0000000..fec690d
--- /dev/null
+++ b/ci/input_generalized_parRep.lua
@@ -0,0 +1,495 @@
+-- Input Lua script providing simulation parameters and functions for the parRep software
+-- All lines starting with '--' are comments and thus ignored
+
+-- Cluster of 7 Argon atoms at low temperature (15 K)
+-- see omm_LJ7.py 
+
+print("Lua version : ",_VERSION)
+
+--------------------------------------------------------------------------------------------------------------
+-- --------------- SIMULATION PARAMETERS ------------------------------
+--------------------------------------------------------------------------------------------------------------
+
+-- NOTE on reproducibility : Only the REF platform was tested, and only with the 3 xml files from the ci directory ;
+--      there is not guarantee that reproducibility might be achieved otherwise, just try yourself ! If you want to share a success or failure,
+--      please open an issue on https://gitlab.inria.fr/parallel-replica/gen.parRep/issues ; thanks !
+
+-- This is used for defining a maximum allowed running time for the simulation
+-- The starting time off the program is saved at initialisation
+-- in max_run_time_hours the user stores for example the maximum running time allowed by the queueing system when running on a cluster
+--  fractional representation is allowed, e.g. 1.25 would be 1 hour and 15 minutes
+max_run_time_hours = 0.5 -- default if not set in this script will be 24 hours
+-- in minutes_to_stop_before_max_run_time the user requires the program to stop a few minutes before max_run_time_hours,
+--  useful for large systems if the I/O may take some time; should be at least 1 minute, and no fractional value allowed
+minutes_to_stop_before_max_run_time = 1 -- default if not set in this script will be 5 minutes
+
+-- This uses OpenMM as MD engine
+MD_Engine = "OpenMM"
+
+-- OpenMM platform to use
+--  AUTO : let OpenMM find the fastest platform (default)
+--  REF  : on cpu, not optimised, not parallellised : slow !
+--  CPU  : on cpu, optimised, parallellised with threads
+--  OCL  : on cpu or gpu of any brand, or any accelerating device available
+--  CUDA : on nvidia gpu only, usually the fastest
+-- OMMplatform = "AUTO"
+OMMplatform = "REF"
+-- OMMplatform = "CPU"
+-- OMMplatform = "OCL"
+-- OMMplatform = "CUDA"
+
+-- We can define here in an array extra platform specific properties passed to OpenMM
+--  see http://docs.openmm.org/latest/userguide/library.html#platform-specific-properties
+--  for a list of OpenMM platform specific properties 
+-- Internally coerced to a std::map<std::string,std::string> so use a string for indexing (key, before the =)
+--  and also be sure to define the values (after the =) also as strings (wrapped within ""), even if it is an integer
+
+-- REF platform has no extra properties
+-- ...
+
+-- CPU platform properties : Threads = "1" is equivalent to exporting OPENMM_CPU_THREADS=1 in the environnment
+-- OMMplatform_properties = { Threads = "1"}
+
+-- OpenCL platform properties
+-- OMMplatform_properties = { 
+--   Precision = "mixed", -- or "single" or "double"
+--   UseCpuPme = "false", -- or true
+--   OpenCLPlatformIndex = "0",
+--   DeviceIndex = "0"
+-- }
+
+-- CUDA platform properties
+-- OMMplatform_properties = {
+--   Precision = "mixed", -- or "single" or "double"
+--   UseCpuPme = "false", -- or "true"
+--   DeviceIndex = "0",
+-- --   CudaCompiler = "/path/to/nvcc",
+--   UseBlockingSync = "false" -- or "true"
+-- }
+
+-- load the integrator parameters from a serialised OpenMM XML file ;
+-- no default, error if undefined
+--  adapt the path to the file in the following line
+integrator = { xml = "./lj7/Integrator.xml" }
+
+-- load the OpenMM System from a serialised XML file
+-- no default, error if undefined
+system = { xml = "./lj7/System.xml" }
+
+-- load the OpenMM State from a serialised XML file
+-- no default, error if undefined
+state = { xml = "./lj7/State.xml" }
+
+-- parameters for energy minimisation : tolerance and maximum number of steps (if 0 : no limit, continue until tolerance satsfied)
+-- defaults are : minimisation={Tolerance=10.0, MaxSteps=0}
+minimisation =
+{
+  Tolerance = 0.00001,
+  MaxSteps  = 0
+}
+
+-- before the parRep algorithm actually starts, perform some steps of equilibration (MPI rank 0 performs dynamics alone)
+-- default : 50e3
+equilibrationSteps = 0
+
+-- the total number of steps of the simulation ; together with the timestep read from the integrator file
+--  it gives to total simulation time
+-- no default, error if undefined
+numSteps = 1
+
+-- Define the type of ParRep simulation to perform, and provide its parameters
+--  only one of the two following tables should be defined
+
+-- FV ParRep (Lelièvre et al. algorithm): only "algorithm" and "GRobservables" are mandatory,
+--  the others have default (c.f. documentation)
+simulation =
+{
+  -- The parallel replica algorithm based on a Fleming-Viot process as defined by ...
+  algorithm = "PARREP_FV",
+  -- parameter for the ParRep Fleming-Viot stage
+  --  A Fleming-Viot particle process is used for sampling the QSD,
+  --  without any a priori defined decorrelation or dephasing time stage.
+  --  Convergence is checked with Gelman-Rubin statistics
+  -- a frequency (in steps) at which to verify if the system left the current state during FV procedure
+  checkFV = 500, -- 0.5 ps
+  -- a frequency (in steps) at which to evaluate convergence using the Gelman-Rubin statistics
+  checkGR = 5, -- 5 fs
+  -- check FV convergence if at least minAccumulatedObs have already been accumulated 
+  --  This may be useful if there is a risk of early pseudo-convergence for some of the observables when only a few samples have been accumulated
+  --  Therefore the minimum FV convergence time will be minAccumulatedObs*checkGR*dt (here 5000*5*dt = 25000 fs = 25 ps, but it is likely that the convergence time will be larger than that)
+  --  Default is 100
+  minAccumulatedObs = 5000,
+  -- parameter for parallel dynamics stage
+  --  checkDynamics is the frequency (in steps) at which to verify if the system left the current state
+  checkDynamics = 500, --  0.5 ps
+  -- Gelman-Rubin statistics checks convergence of several Observables: we define their name here, then there should be one function matching this name below in this file
+  GRobservables = {"getEpot","getEkin"},
+  -- Gelman-Rubin convergence : 0.01 means 1 %
+  GRtol = 0.01
+}
+
+--------------------------------------------------------------------------------------------------------------
+-- --------------- IMPLICIT VARIABLES AND FUNCTIONS ------------------------------
+--------------------------------------------------------------------------------------------------------------
+
+-- Together with the previous variables, the following implicit global variables and functions
+-- are defined from the c++ code, and are accessible from the code you define below
+--
+-- ------------------
+-- implicit variables (read only) :
+-- ------------------
+--
+-- natoms : number of atoms of the system, read from OMM XML files
+--
+-- mpi_rank_id   : the id of the MPI process working on this file
+-- mpi_num_ranks : the total number of MPI processes running
+--
+-- epot,ekin,etot : 3 variables containing the values of the potential, kinetic and total energies (kcal/mol),
+--  of the system for the current simulation time
+--
+-- timeStep : the MD timeStep, read from OMM XML files
+--
+-- referenceTime : the reference clock time, simulation will stop when referenceTime >= (numSteps*timestep),
+--                 where timestep is defined within the xml OMM integrator file 
+--
+-- temperature : T of the system in Kelvin, initial value read from OMM XML files ; use get_temperature() for instantaneous values
+--
+-- ------------------
+-- implicit functions : 
+-- ------------------
+--
+-- exit_from_lua() : call this if it is required to finish the simulation from the lua script : it will terminate MPI properly
+--  and flush I/O files ; but it won't perform a DB backup so do it manually before.   
+--
+-- get_coordinates(n) : function returning a tuple x,y,z containing the coordinates of atom n (in nm)
+--  NOTE n is indexed in Lua style starting from 1, internally it will be accessed as (n-1) i.e. C++ style
+--
+-- get_velocities(n) : function returning a tuple vx,vy,vz containing the velocities of atom n (in nm/picosecond)
+--  NOTE n is indexed in Lua style starting from 1, internally it will be accessed as (n-1) i.e. C++ style
+--
+-- get_all_coordinates() : function returning a table crds containing a safe read-only copy of the coordinates
+--  access by using : crds.x[i] crds.y[i] crds.z[i] for respectively getting x,y,z coordinates of atom i 
+--  NOTE lua indexing, starting from 1
+--
+-- get_all_velocities() : same as gel_all_coordinates but for velocities ; returns a table vels such that the access is : vels.x[i], etc.
+--
+-- get_all_crdvels() : returns a 2-tuple crds,vels containing coordinates and vels : internally it call the 2 above defined functions
+--
+-- get_pbc() : returns periodic boundary conditions as a table pbc with members pbc.a pbc.b pbc.c, each being a xyz vector (i.e. pbc.a.x pbc.a.y pbc.a.z) 
+--
+-- set_pbc(pbc) : set the openmm periodic boundary conditions to be used, the atgument is a table pbc a described above
+--
+-- NOTE possibly slow (especially if a copy to a OCL or CUDA device is required), use rarely for performance
+-- set_all_coordinates(crds)  : uses a crds tables (as described above) and use it for setting c++/openMM coordinates
+-- set_all_velocities(vels)   : the same with velocities
+-- set_all_crdvels(crds,vels) : does with one function only what the 2 above defined do
+--
+-- get_mass(n) : function returning the mass of atom n in a.m.u.
+--  NOTE n is indexed in Lua style starting from 1, internally it will be accessed as (n-1) i.e. C++ style
+--
+-- get_temperature() : get instantaneous value of the temperature in K
+--
+-- get_COM() : function returning the center of mass of the system as a tuple x,y,z
+--
+-- get_COM_idxs(idxs) : function returning the center of mass of a subset of the system as a tuple x,y,z
+--  NOTE this time idxs is indexed directly in C++ style
+--  for example get_COM_idxs({1,2,3}) to get COM of atoms 1, 2 and 3  (C++ : atoms 0, 1, 2)
+--
+-- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
+--  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
+--  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
+--
+-- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
+--  a copy of coordinates and velocities after minimisation.
+--  crds and vels are both tables with x,y,z members, each of size natoms,  : e.g. crds.x[i] returns the x coordinate of atom i, idem for vels.x[i]
+--  note that C++/OpenMM coordinates are not modified if modifying this table : this is a safe read-only copy
+--  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  NOTE lua indexing, starting from 1
+--
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
+-- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
+--  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
+--
+-- hr_timediff_ns(before,after) : returns the time difference in nanoseconds between two hr_timer() 'before' and 'after' : usage:
+--
+--      local bf = hr_timer()
+--      function_to_profile()
+--      local af = hr_timer()
+--      print('Exec. time of function function_to_profile() is (ns) : ',hr_timediff_ns(bf,af))
+--
+-- hr_timediff_us() and hr_timediff_ms() : same as above but exec time is returned respectively in microseconds and milliseconds
+--
+
+--------------------------------------------------------------------------------------------------------------
+-- --------------- USER DEFINED VARIABLES AND FUNCTIONS ------------------------------
+--------------------------------------------------------------------------------------------------------------
+
+-- Some of the following VARIABLES and FUNCTIONS are mandatory and called from C++ (if it is the case it is explicitly documented)
+-- If not they can be restrited to this file using the local keyword
+
+-- Define here local variables and functions used later within state_init() and check_state_left()
+
+--------------------------------------------------------------------------------------------------------------
+-- --------------- FUNCTIONS DEFINING A PARREP STATE ------------------------------
+--------------------------------------------------------------------------------------------------------------
+
+-- TWO functions, state_init() and check_state_left(), will be called from c++ code to know if the 
+--  dynamics left the current state. You are free to define the state in any way, using variables defined explicitly in this file
+--  or implicitly (c++ interface, see above).
+
+-- Define here local variables and functions used later within state_init() and check_state_left()
+
+local fromState = 'unknown'
+
+local LJ7_epsilon = 0.99607255958 -- kJ/mol
+
+-- after minimisation, potential energy divided by the epsilon should be one of the following (because the following are multiples of epsilon)
+local LJ7_ref_energies = {first  = -16.505000000000000,
+                          second = -15.935000000000000,
+                          third  = -15.593000000000000,
+                          fourth = -15.533000000000000
+                         }
+
+-- local dumpFile = "traj."..tostring(mpi_rank_id)..".xyz"
+
+-- local function dump_XYZ(crds) 
+--    local fi = io.open(dumpFile,"a")
+--    
+--    fi:write(tostring(natoms).."\n")
+--    fi:write("# dump after error\n")
+--    for i=1,natoms
+--    do
+--      local str = string.format("AR\t%.3f\t%.3f\t%.3f\n",10.0*crds.x[i],10.0*crds.y[i],10.0*crds.z[i])
+--      fi:write(str)
+--    end
+--    
+--    fi:close()
+--    
+-- end
+
+-- Will identify the current state by performing a minimsation and checking the potential energy to the known values from LJ7_ref_energies
+local function LJ7_stateID()
+
+  -- get potential energy, coordinates are untouched (internal copy was made before calling minimisation)
+  local ep,ek,et = get_minimised_energy(minimisation.Tolerance,minimisation.maxSteps)
+--   local ep,ek,et,crds,vels = get_minimised_energy_crdvels(minimisation.Tolerance,minimisation.maxSteps)
+  
+  -- scale energy in units of epsilon
+  ep = ep/LJ7_epsilon
+  
+--   print("Potential energy is : ",ep)
+  
+--   dump_XYZ(crds,vels)
+  
+  if( math.abs(math.abs(ep)-math.abs(LJ7_ref_energies.first)) < 1e-3 ) then
+    return "first"
+  elseif( math.abs(math.abs(ep)-math.abs(LJ7_ref_energies.second)) < 1e-3 ) then
+    return "second"
+  elseif( math.abs(math.abs(ep)-math.abs(LJ7_ref_energies.third)) < 1e-3 ) then
+    return "third"
+  elseif( math.abs(math.abs(ep)-math.abs(LJ7_ref_energies.fourth)) < 1e-3 ) then
+    return "fourth"
+  else
+    print("Warning : unable to identify the minimum which has after minimisation an epot of ",ep," units of epsilon !!!")
+--     return "unknown"
+--     dump_XYZ(crds)
+    exit_from_lua()
+  end
+  
+end
+
+-- this function is mandatory and called from C++, program will fail if not defined
+--  it should take no arguments
+--  it returns nothing
+-- Use it if you have global variables used in check_state_left() (or other functions) that you need to initialise
+-- It will be called only once after equilibration from C++, before starting the parRep or parRep_FV algorithm
+-- It can also be called at any time from this file if required
+function state_init()
+
+  print("Calling LJ7_stateID() on rank ",mpi_rank_id)
+  fromState = LJ7_stateID()
+  print("Initial state is: ",fromState)
+  
+end
+
+-- this function is mandatory and called from C++, program will fail if not defined
+--  it should take no arguments
+--  it should return a boolean : true in case the dynamics left the state, false otherwise
+-- You may create as many functions as you want and call them from check_state_left(),
+--  but the c++ code will in the end only call check_state_left()
+function check_state_left()
+
+--   -- value of the phi and psi dihedral angles of ala2 at a given time value
+--   local phi = calcDihe(phi_def,nil)
+--   local psi = calcDihe(psi_def,nil)
+-- 
+  local escaped = false
+
+  local toState = LJ7_stateID()
+  
+  if(fromState == toState) then
+    escaped = false
+  else
+    escaped = true
+    print("Transition from state ",fromState," to state ",toState," detected on MPI rank ",mpi_rank_id," !!! ")
+  end
+  
+  return escaped
+end
+
+-- this function is mandatory and called from C++, program will fail if not defined
+--  it should take no arguments
+--  it should return a boolean : true in case the system is currently outside of any of the known states, false otherwise
+-- If the configuration space is a partition the system enters a new state as soon as it exits another, and therefore this function can just return false without doing anything
+function check_transient_propagation_required()
+ -- state space is partitioned for the LJ7
+ return false
+end
+
+--------------------------------------------------------------------------------------------------------------
+-- --------------- DATABASE OF STATES STORAGE FUNCTIONS ------------------------------
+--------------------------------------------------------------------------------------------------------------
+
+-- parameters for the database used for storing information about parRep states
+-- The database can be stored in memory for performance, with a backup regularly performed to file 'name' : in that case define backupFrequency,
+--  together with an appropriate backup function below
+-- If writes to the database are directly performed to disk this is less important, and a large value can be assigned to backupFrequency,
+--  and the backup function below can be empty or either undefined (there is a default one doing nothing defined on c++ side)
+-- defaults are : database={name="run.db",backupFrequency=500.0}
+database = 
+{
+  name = "lj7.db",
+  -- frequency, in ps, at which to backup the database to a file
+  backupFrequency = 1e99 -- this disables intermediate backup of the db : if the db was indeed not opened in memory but instead on disk it is useless to perform backups
+}
+
+-- functions for storing data to a database are defined as members of the table SQLiteDB (which already exists, do not recreate it)
+--  the following functions can be defined as they are used from the c++ code : 
+--
+--    function SQLiteDB.open()
+--    function SQLiteDB.close()
+--    function SQLiteDB.insert_state()
+--    function SQLiteDB.backup_to_file()
+--
+-- if not defined within this file there are default empty functions defined, doing nothing
+--
+-- You are free to define more functions or variables as members of the table SQLiteDB
+--  but they will only be accessible from this file.
+--
+-- For example table creation statements can be stored in the table : 
+--    SQLiteDB.insert_statement_...
+--    ... more ...
+--
+-- The database object is itself stored in this table : SQLiteDB.db
+
+-- only rank 0 manages a database
+if(mpi_rank_id==0)
+then
+
+  SQLiteDB.insert_statement_states  = [[ INSERT INTO STATES (PARREP_DONE,REF_TIME,ESC_TIME,STATE_FROM,TAU)
+                                                     VALUES ($lprep,$reft,$esct,$state_from,$tau); ]]
+
+  SQLiteDB.insert_statement_crdvels = [[ INSERT INTO CRDVELS (ID,X,Y,Z,VX,VY,VZ)
+                                                      VALUES ($id,$x,$y,$z,$vx,$vy,$vz); ]]
+
+  function SQLiteDB.open()
+
+    print('Lua SQLite3 opening an on disk db named ',database.name,' on rank ',mpi_rank_id)
+    
+    -- open database and enable foreign keys
+    SQLiteDB.db = sqlite3.open(database.name)
+    SQLiteDB.db:exec("PRAGMA foreign_keys = ON;")
+
+    SQLiteDB.db:exec("BEGIN TRANSACTION;")
+    
+    -- create the states table : it contains the escape time for this state
+    SQLiteDB.db:exec[[ CREATE TABLE STATES(
+                        ID          INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+                        PARREP_DONE INTEGER NOT NULL,
+                        REF_TIME    REAL,
+                        ESC_TIME    REAL,
+                        STATE_FROM  TEXT,
+                        TAU         REAL); ]]
+
+    -- create the crdvels table : it contains coordinates and velocities of the system for a given 'states' record
+    SQLiteDB.db:exec[[ CREATE TABLE CRDVELS(
+                        ID INTEGER NOT NULL,
+                        X   REAL,
+                        Y   REAL,
+                        Z   REAL,
+                        VX  REAL,
+                        VY  REAL,
+                        VZ  REAL,
+                        FOREIGN KEY(ID) REFERENCES STATES(ID) ); ]]
+
+    SQLiteDB.db:exec("END TRANSACTION;")
+
+  end
+
+  function SQLiteDB.close()
+    SQLiteDB.db:close()
+  end
+
+  local stateID=0
+
+  -- the first three arguments are always provided from c++ code
+  -- extra arguments might be provided by the ... and should be retrieved from Lua's side using the ... special token, converted to a table (see args below)
+  function SQLiteDB.insert_state(parRepDone,tauTime,escapeTime)
+    
+    local ref_time=referenceTime
+    local from=fromState
+    
+    SQLiteDB.db:exec("BEGIN TRANSACTION;")
+
+    local stmt = SQLiteDB.db:prepare(SQLiteDB.insert_statement_states)
+    stmt:bind_names{lprep=parRepDone, reft=ref_time, esct=escapeTime, state_from=from, tau=tauTime}
+    stmt:step()
+    stmt:finalize()
+    
+    stateID = stateID+1
+    
+    for n=1,natoms
+    do
+      local x,y,z = get_coordinates(n)
+      local vx,vy,vz = get_velocities(n)
+      
+      stmt = SQLiteDB.db:prepare(SQLiteDB.insert_statement_crdvels)
+      stmt:bind_names{ id=stateID, x=x, y=y, z=z, vx=vx, vy=vy, vz=vz }
+      stmt:step()
+      stmt:finalize()
+      
+    end
+    
+    SQLiteDB.db:exec("END TRANSACTION;")
+
+  end
+
+end -- if(mpi_rank_id==0) ...
+
+--------------------------------------------------------------------------------------------------------------
+-- --------------- GELMAN RUBIN OBSERVABLES MONITORING FUNCTIONS ---------------------------------------------
+--------------------------------------------------------------------------------------------------------------
+
+-- Define a function for calculating the value of each Observable
+-- Those functions should:
+-- 1) take no arguments 
+-- 2) return a double precision (any Lua numeric value is returned as a double precision)
+--
+-- Those GR functions are called from C++ if they were listed in simulation.GRobservables
+--
+
+-- Definition of the "getEpot" and "getEkin" observables used for Gelman-Rubin statistics
+-- Just returns value of the potential and kinetic energy, scaled to units of epsilon
+function getEpot()
+  return epot
+end
+
+function getEkin()
+  return ekin
+end
+
+--------------------------------------------------------------------------------------------------------------
+
diff --git a/ci/lj7/Integrator.xml b/ci/lj7/Integrator.xml
new file mode 100644
index 0000000..b1cb0e6
--- /dev/null
+++ b/ci/lj7/Integrator.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" ?>
+<Integrator constraintTolerance="1e-08" friction="10" randomSeed="123456789" stepSize=".001" temperature="15" type="LangevinIntegrator" version="1"/>
diff --git a/ci/lj7/State.xml b/ci/lj7/State.xml
new file mode 100644
index 0000000..03ed2d3
--- /dev/null
+++ b/ci/lj7/State.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" ?>
+<State openmmVersion="7.3" time="999.9999999832651" type="State" version="1">
+	<PeriodicBoxVectors>
+		<A x="20" y="0" z="0"/>
+		<B x="0" y="20" z="0"/>
+		<C x="0" y="0" z="20"/>
+	</PeriodicBoxVectors>
+	<Positions>
+		<Position x="9.983385059299602" y="10.174980427137598" z="10.020986348782928"/>
+		<Position x="9.770224100810191" y="10.037570850276143" z="10.293565548378014"/>
+		<Position x="9.59030488821879" y="10.02885267592082" z="9.931518707870099"/>
+		<Position x="9.648648572516473" y="10.349048230249956" z="10.107352368306797"/>
+		<Position x="9.766183439034616" y="10.306491926935744" z="9.758201173961519"/>
+		<Position x="9.93467672489858" y="9.95859189806722" z="9.682105588219425"/>
+		<Position x="9.906861537781493" y="9.817471151536196" z="10.038531569331555"/>
+	</Positions>
+	<Velocities>
+		<Velocity x=".06812021378266309" y="-.014536461211633878" z="-.05354852211603145"/>
+		<Velocity x="-.0003575163347591115" y=".004253822229571824" z=".10209696385565792"/>
+		<Velocity x=".01955586418134203" y=".01921201910093373" z="-.003143989026455074"/>
+		<Velocity x=".06026768071265565" y=".04230138984162579" z=".07773255456200445"/>
+		<Velocity x="-.05115033391156487" y=".045557891198200196" z=".03101618878886825"/>
+		<Velocity x="-.028826659962177814" y=".059965265059247486" z="-.07743277297400653"/>
+		<Velocity x=".06604694795520061" y="-.02419439117318234" z=".015470123640781708"/>
+	</Velocities>
+</State>
diff --git a/ci/lj7/System.xml b/ci/lj7/System.xml
new file mode 100644
index 0000000..ecb7213
--- /dev/null
+++ b/ci/lj7/System.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" ?>
+<System openmmVersion="7.3" type="System" version="1">
+	<PeriodicBoxVectors>
+		<A x="20" y="0" z="0"/>
+		<B x="0" y="20" z="0"/>
+		<C x="0" y="0" z="20"/>
+	</PeriodicBoxVectors>
+	<Particles>
+		<Particle mass="39.948"/>
+		<Particle mass="39.948"/>
+		<Particle mass="39.948"/>
+		<Particle mass="39.948"/>
+		<Particle mass="39.948"/>
+		<Particle mass="39.948"/>
+		<Particle mass="39.948"/>
+	</Particles>
+	<Constraints/>
+	<Forces>
+		<Force alpha="0" cutoff="1" dispersionCorrection="0" ewaldTolerance=".0005" forceGroup="0" ljAlpha="0" ljnx="0" ljny="0" ljnz="0" method="0" nx="0" ny="0" nz="0" recipForceGroup="-1" rfDielectric="78.3" switchingDistance="-1" type="NonbondedForce" useSwitchingFunction="0" version="3">
+			<GlobalParameters/>
+			<ParticleOffsets/>
+			<ExceptionOffsets/>
+			<Particles>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+				<Particle eps=".99607255958" q="0" sig=".3405"/>
+			</Particles>
+			<Exceptions/>
+		</Force>
+		<Force forceGroup="0" frequency="100" type="CMMotionRemover" version="1"/>
+	</Forces>
+</System>
diff --git a/ci/lj7/omm_LJ7.py b/ci/lj7/omm_LJ7.py
new file mode 100755
index 0000000..70c82e0
--- /dev/null
+++ b/ci/lj7/omm_LJ7.py
@@ -0,0 +1,139 @@
+from simtk.openmm.app import *
+from simtk.openmm import *
+from simtk.unit import *
+
+import numpy as np
+from math import *
+from sys import stdout
+
+platform = Platform.getPlatformByName('Reference')
+
+# periodic conditions
+# periodic vectors
+a = Vec3(20.0,0.0,0.0)
+b = Vec3(0.0,20.0,0.0)
+c = Vec3(0.0,0.0,20.0)
+
+center = Vec3(10.,10.,10.)
+
+print("a = ",a)
+print("b = ",b)
+print("c = ",c)
+print("center = ",center)
+
+natoms = 7
+mass = 39.948*amu
+temperature = 15.0*kelvin
+
+integrator = LangevinIntegrator(temperature, 10/picosecond, 0.001*picosecond)
+integrator.setConstraintTolerance(1e-8)
+
+integrator.setRandomNumberSeed(123456789)
+
+nb = NonbondedForce()
+nb.setNonbondedMethod(NonbondedForce.NoCutoff)
+#nb.setUseSwitchingFunction(True)
+#nb.setSwitchingDistance(1.0*nanometer)
+#nb.setCutoffDistance(1.2*nanometer)
+nb.setUseDispersionCorrection(False)
+
+# see http://www.sklogwiki.org/SklogWiki/index.php/Argon for argon LJ parameters
+k_B = 0.0083144621*(kilojoule/(mole*kelvin))
+lj_epsilon = 119.8*kelvin * k_B # in kJ/mol
+lj_sigma = 0.3405*nanometer  # in nm
+
+# All seven particles will initially be placed on a 2D plane
+# Each particle is by construction at distance r_min of another atom
+# r_min = lj_sigma * 2^(1./6.) is the distance at which the LJ potential is minimum
+# See on the right for the numbering of particles
+#
+#     *   *               3   2
+#   *   *   *           4   0   1
+#     *   *               5   6
+#
+r_min = 2.**(1./6.) * lj_sigma._value
+xpos  = r_min*cos(pi/3.)
+ypos  = r_min*sin(pi/3.)
+
+iniPos = np.zeros((natoms,3))
+iniPos[0,:] = (center[0],center[1],center[2])
+iniPos[1,:] = (center[0]+r_min,center[1],center[2])
+iniPos[2,:] = (center[0]+xpos,center[1]+ypos,center[2])
+iniPos[3,:] = (center[0]-xpos,center[1]+ypos,center[2])
+iniPos[4,:] = (center[0]-r_min,center[1],center[2])
+iniPos[5,:] = (center[0]-xpos,center[1]-ypos,center[2])
+iniPos[6,:] = (center[0]+xpos,center[1]-ypos,center[2])
+
+system = System()
+for i in range(natoms):
+  system.addParticle(mass)
+  nb.addParticle(0.0,lj_sigma,lj_epsilon)
+  
+system.setDefaultPeriodicBoxVectors(a,b,c)
+system.addForce(nb)
+system.addForce(CMMotionRemover(100))
+
+# generate basic topology object to use with simulation class (and to be able to write pdb files)
+top = Topology()
+top.setPeriodicBoxVectors([a,b,c])
+chain = top.addChain("A")
+for i in range(natoms):
+  top.addResidue("AR",chain)
+for r in top.residues():
+  top.addAtom("AR",Element.getBySymbol("Ar"),r)
+
+
+simulation = Simulation(top,system,integrator,platform)#,platformProperties)
+
+simulation.context.setPositions(iniPos)
+
+state = simulation.context.getState(getPositions=True,getVelocities=True,enforcePeriodicBox=True)
+with open("start.pdb", 'w') as f:
+  PDBFile.writeFile(top,state.getPositions(),f)
+  
+print('Minimizing energy...')
+simulation.minimizeEnergy(1e-5)
+
+simulation.context.setVelocitiesToTemperature(temperature)
+
+state = simulation.context.getState(getPositions=True,getVelocities=True,enforcePeriodicBox=True)
+with open("minim.pdb", 'w') as f:
+  PDBFile.writeFile(top,state.getPositions(),f)
+  
+nsteps   = 1e6 
+# save each 1 ps
+saveFreq = 1000
+
+simulation.reporters.append(StateDataReporter(stdout, saveFreq, step=True, 
+    time=True, potentialEnergy=True, kineticEnergy=True, totalEnergy=True,
+    temperature=True, progress=True,
+    remainingTime=True, speed=True,
+    totalSteps=nsteps, separator='\t'))
+
+simulation.reporters.append(DCDReporter(file='sim.dcd', reportInterval=saveFreq, enforcePeriodicBox=True))
+
+print("Running dynamics...")
+simulation.step(nsteps)
+
+state = simulation.context.getState(getPositions=True,getVelocities=True,enforcePeriodicBox=True)
+with open("1ns.pdb", 'w') as f:
+  PDBFile.writeFile(top,state.getPositions(),f)
+  
+#serialize the state to xml for load in parRep later
+print('Serializing state : it contains coordinates, velocities, etc. for parRep...')
+f = open('State.xml','w')
+f.write(XmlSerializer.serialize(state))
+f.close()
+
+#serialize the system to xml for load in parRep later
+print('Serializing system : it contains all definition of forces for parRep...')
+f = open('System.xml','w')
+f.write(XmlSerializer.serialize(system))
+f.close()
+
+#serialize the integrator to xml for load in parRep later
+print('Serializing integrator for parRep...')
+f = open('Integrator.xml','w')
+f.write(XmlSerializer.serialize(integrator))
+f.close()
+
diff --git a/ci/ref.sim/README.md b/ci/ref.sim/README.md
new file mode 100644
index 0000000..9da4e99
--- /dev/null
+++ b/ci/ref.sim/README.md
@@ -0,0 +1,23 @@
+# Software used for producing this output : 
+
+sqlite3 : 3.22.0 2018-01-22 18:45:57 0c55d179733b46d8d0ba4d88e01a25e10677046ee3da1d5b1581e86726f2alt1
+
+gcc/g++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
+
+Luajit : LuaJIT 2.0.5, download and compiled from script download_dependcies in the build directory.
+
+OpenMM : openmm-7.3.0-py27_cuda92_rc_1.tar.bz2, bin version downloaded from script download_dependcies in the build directory.
+
+MPI : mpirun (Open MPI) 2.1.1, from repository
+
+# Linux (Ubuntu 18.04): 
+
+$ lsb_release -a
+LSB Version:    core-9.20170808ubuntu1-noarch:printing-9.20170808ubuntu1-noarch:security-9.20170808ubuntu1-noarch
+Distributor ID: Ubuntu
+Description:    Ubuntu 18.04.2 LTS
+Release:        18.04
+Codename:       bionic
+
+$ uname -a
+Linux pl267-pro 4.15.0-45-generic #48-Ubuntu SMP Tue Jan 29 16:28:13 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
diff --git a/ci/ref.sim/lj7.db b/ci/ref.sim/lj7.db
new file mode 100644
index 0000000000000000000000000000000000000000..c5b3f9a5179b01b4754227a6d6eaf3df39af635f
GIT binary patch
literal 16384
zcmeI&TSyd97y#fiyXt7vuH9Tkg9Edcixp%M3q8!)+@r1S&W^LAtQD?SyQXPrZdWTR
z(tNO}r-BO966zr}x<b(erC@IbQQ74oD>Qqt5V}HVS8Yjft)PeEKkUqKX8!;DGvAq)
z!>lLYSswJ`z~(Jgr9r$HB_fVP1Y?Adj%_h)8*AhH+}MREM;%(2);Wy@=%>CyhE{{j
z4$wdV1V8`;KmY_l00ck)1VG@g2^=Wb^QP2PuHF(XU0dnTR_uk;=@~u5WGmFBQmopR
zJ1HJLnK1=V&>VK`kW`vW6)d?`EEPDN^Ct}UvO(DBQ6h>ajaW5lL?J5^y-Fb~Gf4z1
z6Fnlwtx!j<gjdp{42RvqqI0N1rEKcK9+d%l!e=eAF3yyh$rXjAYilam;?h^^ui5I~
zw9Y@StN%%U+{KJdrB(j<EYoseHOHG$Qn>o*;W0;wjqG(ljWBu}6V3*kx*u(94rRsR
zvMEKOr^2=Z)$NejkS;2*nVocYWt*Z<*=Ki4^f%rK=H^o!E*iR(=;G9q{fm*|C->zj
zZWqQX^{NxUk*clWhe4>iCA2o@jJ=5cpn(7gfB*=900@8p2!H?xfB*=900{hZ0%nd!
z2|RD&$sQcK`E_JRWB4Hg<y&flL@8k%?(oR^e;65u!`EO30w4eaAOHd&00JNY0w4ea
zAOHd&@V^KoaQc*lk-tlX*8jYr0vWrFO-99-Y<OWf%ev4&00ck)1V8`;KmY_l00ck)
z1VG>q1+08L`y3r=M0nEO@HDT72+zF(agBS4nf8C!ub-jS51iUHfRK3P>O<3!W+a~O
z8&a;{M%K7U=FQaHjj;hD+-O*vp7@>!jn8^{u~j>B{?_&-B}hBh*O0fsip1uMOUKTX
zAZu(SbGoaiN)U<gX~XM9?Ftc=#k5syTd!U9^$j$xK;q@Eg|qXjStfpyWba4Tm`J8w
zt!}GpCg$VUx_fQ4L}<BN+*Ef^ywssJ&Ff?t#mQ~65|Oq~EBoAmkX0YaOrPRCCqE&=
z$Kn&$<I`B}EV=i>W$j|%+aa%v#Lk(^PCF}+*xEHG>A4qKb&<>lvGaaVCg#I)>t3dp
z5VLg5oNRm`o`@TKxAG{`uH-Mh^71wk9fObi?y;I&B=bwn+pg+<VlLl3lvx%}ggcM2
d<oizXR{o)#fgsXOsuim@GpUv}Q(M}PqVKcAQ_}zd

literal 0
HcmV?d00001

diff --git a/ci/ref.sim/seeds.bin b/ci/ref.sim/seeds.bin
new file mode 100644
index 0000000000000000000000000000000000000000..fc3504f94422bcdb14a040dda024abb0a387d279
GIT binary patch
literal 16384
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
OLCor@UfFK2rUus?Cy@^T

literal 0
HcmV?d00001

diff --git a/ci/run.ci.sh b/ci/run.ci.sh
new file mode 100755
index 0000000..8c2d99b
--- /dev/null
+++ b/ci/run.ci.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+rm -f *.db *.log *.txt
+
+export OPENMM_CPU_THREADS=1
+export OPENMM_PLUGIN_DIR=$PWD/../build/openmm-7.3.0/lib/plugins
+
+mpirun -np 8 $PWD/../build/parRep -i input_generalized_parRep.lua -log warn -o out.txt -e err.txt --inp-seeds ./ref.sim/seeds.bin
+
+MPI_RET_CODE=$?
+
+# compare the db file from the run and the reference, they should be the same
+sqldiff lj7.db ref.sim/lj7.db > diff.txt
+
+SQL_RET_CODE=$?
+
+if [ -s "diff.txt" ]
+then 
+  TEST_RET_CODE=-1
+else
+  TEST_RET_CODE=0
+fi
+
+cat out.*.txt
+
+# success only if there was no error code from any of the previous commands
+exit $(($MPI_RET_CODE|$SQL_RET_CODE|$TEST_RET_CODE))
diff --git a/include/dyna/GelmanRubin.hpp b/include/dyna/GelmanRubin.hpp
index fefc5cf..b33c672 100644
--- a/include/dyna/GelmanRubin.hpp
+++ b/include/dyna/GelmanRubin.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef GELMANRUBIN_HPP_INCLUDED
@@ -34,13 +34,11 @@ public:
 
   /**
   * @brief Constructor for a Gelman Rubin Analysis object; requires at least the number of chains (replica) as first argument.
-  *        The second argument allows to discard the first n observations when averaging, default is zero and thus disables 
-  *        discarding.  
   * 
   * @param _num_chains The number of G-R chains (same as the number of replicas usually).
-  * @param _discard Discards the first n observations, as usually there won't be convergence at the beginning.
+  * @param _no_convergence_if_less_than Optional argument : if set there won't be convergence check until at least _no_convergence_if_less_than observations have been accumulated for each observable : this may hel avoiding pseudo-convergence.
   */
-  GelmanRubinAnalysis(const uint32_t _num_chains, const uint32_t _discard=0);
+  GelmanRubinAnalysis(const uint32_t _num_chains, const uint32_t _no_convergence_if_less_than=0);
   
   
   /**
@@ -195,8 +193,9 @@ private:
   std::map<std::string,double> tolerance;             ///< Allows one tolerance treshold per observable type
   std::map<std::string,std::vector<double>> ratio;    ///< Allows one ratio vector per observable type
   
-  uint32_t num_chains;                      ///< The total number of chains
-  uint32_t discard_first;                   ///< A given number of records to ignore at the beginning
+  uint32_t num_chains = 0;                          ///< The total number of chains
+  uint32_t min_num_observations_before_check = 0;   ///< There won't be convergence check until at least this amount ob observations have been accumulated for each obseravble
+  
 };
 
 
diff --git a/include/dyna/observable.hpp b/include/dyna/observable.hpp
index 113b359..e01b70d 100644
--- a/include/dyna/observable.hpp
+++ b/include/dyna/observable.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef OBSERVABLE_HPP_INCLUDED
diff --git a/include/dyna/parRep.hpp b/include/dyna/parRep.hpp
index 9e6efdd..56ca276 100644
--- a/include/dyna/parRep.hpp
+++ b/include/dyna/parRep.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef PARREP_HPP_INCLUDED
diff --git a/include/dyna/parRep_FV.hpp b/include/dyna/parRep_FV.hpp
index 0a40dc9..3d610ec 100644
--- a/include/dyna/parRep_FV.hpp
+++ b/include/dyna/parRep_FV.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef PARREP_FV_HPP_INCLUDED
diff --git a/include/dyna/parRep_FV_multiExits.hpp b/include/dyna/parRep_FV_multiExits.hpp
index 6006d44..05a6d08 100644
--- a/include/dyna/parRep_FV_multiExits.hpp
+++ b/include/dyna/parRep_FV_multiExits.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef PARREP_FV_MULTI_HPP_INCLUDED
diff --git a/include/dyna/parRep_abstract.hpp b/include/dyna/parRep_abstract.hpp
index 7d698b5..85a1aad 100644
--- a/include/dyna/parRep_abstract.hpp
+++ b/include/dyna/parRep_abstract.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef PARREP_ABSTRACT_HPP_INCLUDED
diff --git a/include/dyna/runSim.hpp b/include/dyna/runSim.hpp
index c0b144e..bfee426 100644
--- a/include/dyna/runSim.hpp
+++ b/include/dyna/runSim.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef RUNSIM_HPP_INCLUDED
@@ -20,8 +20,15 @@
  *  + Parsing Input file
  *  + Setting up the desired type of parRep run_simulation
  *  + Running the simulation
+ * 
+ * @param inpf The path to the input Lua file, mandatory.
+ * @param seeds_inp_file The path to the unique binary file containing random seeds to be loaded, optional.
+ * @param seeds_out_file The path to the unique binary file containing random seeds to be loaded, optional.
  */
-void run_simulation(const std::string& inpf);
+void run_simulation(const std::string& inpf,
+                    const std::string& seeds_inp_file = NULLFILE,
+                    const std::string& seeds_out_file = NULLFILE
+                   );
 
 #endif // RUNSIM_HPP_INCLUDED
 
diff --git a/include/global.hpp b/include/global.hpp
index 78ece31..279d41c 100644
--- a/include/global.hpp
+++ b/include/global.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef GLOBAL_HPP_INCLUDED
diff --git a/include/logger.hpp b/include/logger.hpp
index 0af6c4a..7b2d6ea 100644
--- a/include/logger.hpp
+++ b/include/logger.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef LOGGER_H_INCLUDED
diff --git a/include/md/md_interface.hpp b/include/md/md_interface.hpp
index 7da5996..d8a0f93 100644
--- a/include/md/md_interface.hpp
+++ b/include/md/md_interface.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef MD_INTERFACE_H
diff --git a/include/md/omm_interface.hpp b/include/md/omm_interface.hpp
index c3acb1d..29ec520 100644
--- a/include/md/omm_interface.hpp
+++ b/include/md/omm_interface.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef OMM_INTERFACE_H
diff --git a/include/rand.hpp b/include/rand.hpp
index ad8b181..9f9ebac 100644
--- a/include/rand.hpp
+++ b/include/rand.hpp
@@ -4,31 +4,45 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef RAND_HPP_INCLUDED
 #define RAND_HPP_INCLUDED
 
-#include <cstdint>
-
+#include "global.hpp"
 #include <string>
 
 /**
- * @brief Call this function for initialising a Mersenne twister 19937 random numbers generator, using random seeds.
- * 
- * The generator is seeded by using integers taken from a std::random_device (usually by reading /dev/urandom where available, or using the hardware instruction RDRND on compatible CPUs)
- */
-void init_rand();
+* @brief Used to indicate if the random seeds used for initialising the random number generators should be read from or written to a file (for reproducibility).
+* See init_rand(...) which uses it.
+*/
+enum SEEDS_IO
+{
+  NONE = 0, ///< No I/O of the seeds
+  SAVE_TO_FILE   = 1, ///< seeds from all ranks will be saved to a unique binary file
+  LOAD_FROM_FILE = 2, ///< seeds for  all ranks will be read from  a unique binary file
+};
 
 /**
- * @brief Call this function for initialising a Mersenne twister 19937 random numbers generator, using as seeds a list of unsigned 64 bits integers (encoded as a comma separated string, at least 5 seeds required).
+ * @brief Call this function for initialising a Mersenne twister 19937 random numbers generator, using random seeds.
+ * 
+ * The generator is by default seeded using integers taken from a std::random_device (usually by reading /dev/urandom where available, or using the hardware instruction RDRND on compatible CPUs).
+ * Furthermore the seeds obtained from a std::random_device can be saved to a file for later re-use.
+ *
+ * If you load a seed file generated by this function during a previous run, you will get the same initialization and should be able to reproduce results (at least if runing on the same architecture and using the same compiler...).
+ * 
+ * The file should contain exactly NRANKS*512 unsigned 32 bits integers (uint32_t), where NRANKS is the number of MPI ranks running, i.e. each ranks requires 512 uint32_t for seeding
+ * its Mersenne twister 19937 engine.
+ * 
+ * If you want to visualize as text the binary seed file "seeds.bin", use the following unix command : "od -A n -t u4 seeds.bin"
  * 
- * @attention it is crucial to provide a different string on each MPI rank, otherwise the generators will be correlated/identical !!!!
  * 
- * @param seeds seeds is a std::string containing n seeds, comma separated (e.g. seed = "123,456,789"). n is >= 5
+ * @param io_type Set to either SEEDS_IO::NONE, SEEDS_IO::LOAD_FROM_FILE or SEEDS_IO::SAVE_TO_FILE. ; the first only generates seeds for internal use, the second will load seeds from file seeds_file_name, and the third will save the seeds internally generated to file seeds_file_name.
+ * @param seeds_file_name if io_type is not SEEDS_IO::NONE, this should contain a valide path/filename from which to load the seeds or where to write the seeds. Defaults to NULLFILE.
  */
-void init_rand(const std::string& seeds);
+void init_rand(SEEDS_IO io_type,
+               const std::string& seeds_file_name = NULLFILE);
 
 /**
  * @brief Call this function for obtaining a uniformly distributed random number in the range [0.0,1.0]
diff --git a/include/utils/lua_interface.hpp b/include/utils/lua_interface.hpp
index d35feed..0616bb2 100644
--- a/include/utils/lua_interface.hpp
+++ b/include/utils/lua_interface.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef LUA_INTERFACE_HPP_INCLUDED
diff --git a/include/utils/mpi_utils.hpp b/include/utils/mpi_utils.hpp
index 3f22630..c3f45e5 100644
--- a/include/utils/mpi_utils.hpp
+++ b/include/utils/mpi_utils.hpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #ifndef MPI_UTILS_H_INCLUDED
diff --git a/mol/1d7h_i/input_generalized_parRep.lua b/mol/1d7h_i/input_generalized_parRep.lua
index 1dcbeda..25b1e50 100755
--- a/mol/1d7h_i/input_generalized_parRep.lua
+++ b/mol/1d7h_i/input_generalized_parRep.lua
@@ -116,6 +116,11 @@ simulation =
   checkFV = 2000, -- 2 ps
   -- a frequency (in steps) at which to evaluate convergence using the Gelman-Rubin statistics
   checkGR = 100,  -- 100 fs
+  -- check FV convergence if at least minAccumulatedObs have already been accumulated 
+  --  This may be useful if there is a risk of early pseudo-convergence for some of the observables when only a few samples have been accumulated
+  --  Therefore the minimum FV convergence time will be minAccumulatedObs*checkGR*dt (here 100*100*dt = 20000 fs = 20 ps, but it is likely that the convergence time will be larger than that)
+  --  Default is 100
+  minAccumulatedObs = 100,
   -- parameter for parallel dynamics stage
   --  checkDynamics is the frequency (in steps) at which to verify if the system left the current state
   checkDynamics = 2000, -- 2 ps
@@ -155,7 +160,6 @@ simulation =
 -- implicit functions : 
 -- ------------------
 --
---
 -- exit_from_lua() : call this if it is required to finish the simulation from the lua script : it will terminate MPI properly
 --  and flush I/O files ; but it won't perform a DB backup so do it manually before.   
 --
@@ -190,13 +194,13 @@ simulation =
 -- get_COM() : function returning the center of mass of the system as a tuple x,y,z
 --
 -- get_COM_idxs(idxs) : function returning the center of mass of a subset of the system as a tuple x,y,z
---  NOTE this time idxs is indexed in C++ style
---  for example get_COM_idxs({0,1,2}) to get COM of atoms 1, 2 and 3  (C++ : atoms 0, 1, 2)
+--  NOTE this time idxs is indexed directly in C++ style
+--  for example get_COM_idxs({1,2,3}) to get COM of atoms 1, 2 and 3  (C++ : atoms 0, 1, 2)
 --
 -- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
 --  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
 --  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
---  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
 --
 -- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
 --  a copy of coordinates and velocities after minimisation.
@@ -205,6 +209,9 @@ simulation =
 --  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
 --  NOTE lua indexing, starting from 1
 --
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
 -- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
 --  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
 --
diff --git a/mol/ala2vac/input_generalized_parRep.lua b/mol/ala2vac/input_generalized_parRep.lua
index fab791f..6e181b1 100755
--- a/mol/ala2vac/input_generalized_parRep.lua
+++ b/mol/ala2vac/input_generalized_parRep.lua
@@ -108,6 +108,11 @@ simulation =
   checkFV = 250, -- 0.5 ps
   -- a frequency (in steps) at which to evaluate convergence using the Gelman-Rubin statistics
   checkGR = 10, -- 20 fs
+  -- check FV convergence if at least minAccumulatedObs have already been accumulated 
+  --  This may be useful if there is a risk of early pseudo-convergence for some of the observables when only a few samples have been accumulated
+  --  Therefore the minimum FV convergence time will be minAccumulatedObs*checkGR*dt (here 1000*10*dt = 20000 fs = 20 ps, but it is likely that the convergence time will be larger than that)
+  --  Default is 100
+  minAccumulatedObs = 1000,
   -- parameter for parallel dynamics stage
   --  checkDynamics is the frequency (in steps) at which to verify if the system left the current state
   checkDynamics = 2500, -- 5 ps
@@ -187,7 +192,7 @@ simulation =
 -- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
 --  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
 --  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
---  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
 --
 -- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
 --  a copy of coordinates and velocities after minimisation.
@@ -196,6 +201,9 @@ simulation =
 --  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
 --  NOTE lua indexing, starting from 1
 --
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
 -- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
 --  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
 --
diff --git a/mol/ala2vac/input_generalized_parRep_multiExit.lua b/mol/ala2vac/input_generalized_parRep_multiExit.lua
index b3ead1b..e2ef33c 100755
--- a/mol/ala2vac/input_generalized_parRep_multiExit.lua
+++ b/mol/ala2vac/input_generalized_parRep_multiExit.lua
@@ -115,6 +115,11 @@ simulation =
   checkFV = 250, -- 0.5 ps
   -- a frequency (in steps) at which to evaluate convergence using the Gelman-Rubin statistics
   checkGR = 10, -- 20 fs
+  -- check FV convergence if at least minAccumulatedObs have already been accumulated 
+  --  This may be useful if there is a risk of early pseudo-convergence for some of the observables when only a few samples have been accumulated
+  --  Therefore the minimum FV convergence time will be minAccumulatedObs*checkGR*dt (here 1000*10*dt = 20000 fs = 20 ps, but it is likely that the convergence time will be larger than that)
+  --  Default is 100
+  minAccumulatedObs = 1000,
   -- parameter for parallel dynamics stage
   --  checkDynamics is the frequency (in steps) at which to verify if the system left the current state
   checkDynamics = 2500, -- 5 ps
@@ -194,7 +199,7 @@ simulation =
 -- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
 --  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
 --  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
---  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
 --
 -- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
 --  a copy of coordinates and velocities after minimisation.
@@ -203,6 +208,9 @@ simulation =
 --  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
 --  NOTE lua indexing, starting from 1
 --
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
 -- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
 --  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
 --
diff --git a/mol/ala2vac/input_parRep.lua b/mol/ala2vac/input_parRep.lua
index 9808848..acb560e 100755
--- a/mol/ala2vac/input_parRep.lua
+++ b/mol/ala2vac/input_parRep.lua
@@ -184,7 +184,7 @@ simulation =
 -- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
 --  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
 --  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
---  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
 --
 -- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
 --  a copy of coordinates and velocities after minimisation.
@@ -193,6 +193,9 @@ simulation =
 --  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
 --  NOTE lua indexing, starting from 1
 --
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
 -- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
 --  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
 --
diff --git a/mol/ala2vac_transient/input_generalized_parRep.lua b/mol/ala2vac_transient/input_generalized_parRep.lua
index 88d5d57..7475812 100755
--- a/mol/ala2vac_transient/input_generalized_parRep.lua
+++ b/mol/ala2vac_transient/input_generalized_parRep.lua
@@ -27,8 +27,8 @@ MD_Engine = "OpenMM"
 --  OCL  : on cpu or gpu of any brand, or any accelerating device available
 --  CUDA : on nvidia gpu only, usually the fastest
 -- OMMplatform = "AUTO"
--- OMMplatform = "REF"
-OMMplatform = "CPU"
+OMMplatform = "REF"
+-- OMMplatform = "CPU"
 -- OMMplatform = "OCL"
 -- OMMplatform = "CUDA"
 
@@ -42,7 +42,7 @@ OMMplatform = "CPU"
 -- ...
 
 -- CPU platform properties : Threads = "1" is equivalent to defining OPENMM_CPU_THREADS=1 in the environnment
-OMMplatform_properties = { Threads = "1"}
+-- OMMplatform_properties = { Threads = "1"}
 
 -- OpenCL platform properties
 -- OMMplatform_properties = { 
@@ -64,15 +64,15 @@ OMMplatform_properties = { Threads = "1"}
 -- load the integrator parameters from a serialised OpenMM XML file ;
 -- no default, error if undefined
 --  adapt the path to the file in the following line
-integrator = { xml = "./mol/ala2vac/Integrator.xml" }
+integrator = { xml = "./mol/ala2vac_transient/Integrator.xml" }
 
 -- load the OpenMM System from a serialised XML file
 -- no default, error if undefined
-system = { xml = "./mol/ala2vac/System.xml" }
+system = { xml = "./mol/ala2vac_transient/System.xml" }
 
 -- load the OpenMM State from a serialised XML file
 -- no default, error if undefined
-state = { xml = "./mol/ala2vac/State.xml" }
+state = { xml = "./mol/ala2vac_transient/State.xml" }
 
 -- parameters for energy minimisation : tolerance and maximum number of steps (if 0 : no limit, continue until tolerance satsfied)
 -- defaults are : minimisation={Tolerance=1e-6,MaxSteps=0}
@@ -108,6 +108,11 @@ simulation =
   checkFV = 250, -- 0.5 ps
   -- a frequency (in steps) at which to evaluate convergence using the Gelman-Rubin statistics
   checkGR = 10, -- 20 fs
+  -- check FV convergence if at least minAccumulatedObs have already been accumulated 
+  --  This may be useful if there is a risk of early pseudo-convergence for some of the observables when only a few samples have been accumulated
+  --  Therefore the minimum FV convergence time will be minAccumulatedObs*checkGR*dt (here 1000*10*dt = 20000 fs = 20 ps, but it is likely that the convergence time will be larger than that)
+  --  Default is 100
+  minAccumulatedObs = 1000,
   -- parameter for parallel dynamics stage
   --  checkDynamics is the frequency (in steps) at which to verify if the system left the current state
   checkDynamics = 2500, -- 5 ps
@@ -187,7 +192,7 @@ simulation =
 -- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
 --  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
 --  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
---  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
 --
 -- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
 --  a copy of coordinates and velocities after minimisation.
@@ -196,6 +201,9 @@ simulation =
 --  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
 --  NOTE lua indexing, starting from 1
 --
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
 -- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
 --  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
 --
diff --git a/mol/ala2vac_transient/input_parRep.lua b/mol/ala2vac_transient/input_parRep.lua
index 73c2228..5f27bcb 100755
--- a/mol/ala2vac_transient/input_parRep.lua
+++ b/mol/ala2vac_transient/input_parRep.lua
@@ -64,15 +64,15 @@ OMMplatform_properties = { Threads = "1"}
 -- load the integrator parameters from a serialised OpenMM XML file ;
 -- no default, error if undefined
 --  adapt the path to the file in the following line
-integrator = { xml = "./mol/ala2vac/Integrator.xml" }
+integrator = { xml = "./mol/ala2vac_transient/Integrator.xml" }
 
 -- load the OpenMM System from a serialised XML file
 -- no default, error if undefined
-system = { xml = "./mol/ala2vac/System.xml" }
+system = { xml = "./mol/ala2vac_transient/System.xml" }
 
 -- load the OpenMM State from a serialised XML file
 -- no default, error if undefined
-state = { xml = "./mol/ala2vac/State.xml" }
+state = { xml = "./mol/ala2vac_transient/State.xml" }
 
 -- parameters for energy minimisation : tolerance and maximum number of steps (if 0 : no limit, continue until tolerance satsfied)
 -- defaults are : minimisation={Tolerance=1e-6,MaxSteps=0}
@@ -184,7 +184,7 @@ simulation =
 -- get_minimised_energy(tolerance,maxSteps) : this function returns the minimised energy of the system, using the OpenMM L-BFGS minimiser
 --  note that coordinates are not affected, it just returns the minimum epot of the bassin in which dynamics currently evolves
 --  it returns a 3-tuple ep,ek,et (potential, kinetic and total energy)
---  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
+--  the tolerance and maxSteps can be the above defined minimisation.Tolerance and minimisation.MaxSteps
 --
 -- get_minimised_crdvels(tolerance,maxSteps) : this function returns a 2-tuple (crds,vels) containing
 --  a copy of coordinates and velocities after minimisation.
@@ -193,6 +193,9 @@ simulation =
 --  the tolerance and maxSteps can be the above defined simulation.minimisationTolerance and simulation.minimisationMaxSteps
 --  NOTE lua indexing, starting from 1
 --
+-- get_minimised_energy_crdvels(tolerance,maxSteps) : this returns a 5-tuple (ep,ek,et,crds,vels) 
+--  This does the same as the two previous functions but with only one call
+--
 -- hr_timer() : returns a variable representing a c++ high precision timer : can be used for measuring execution time.
 --  do not try to modify it or even read it, it should only be used as argument for the following hr_timediff_* functions.
 --
diff --git a/parRep.doxy b/parRep.doxy
index 3dd01ff..c127685 100644
--- a/parRep.doxy
+++ b/parRep.doxy
@@ -38,7 +38,7 @@ PROJECT_NAME           = "gen.parRep"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = v1.1.0
+PROJECT_NUMBER         = v1.2.0
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
diff --git a/run/run.bash.sh b/run/run.bash.sh
index 8755464..a4f2786 100755
--- a/run/run.bash.sh
+++ b/run/run.bash.sh
@@ -1,25 +1,38 @@
 #!/bin/bash
 
+# --------------------------------------------------------------
+# create a local tmp dir for the run 
+
 ORIG_DIR=$PWD
 TMP_DIR=$(mktemp --tmpdir=$ORIG_DIR -u)
 mkdir -p $TMP_DIR && echo "Running in tmp dir $TMP_DIR"
 
 cd $TMP_DIR
-ln -s $ORIG_DIR/mol
+ln -s $ORIG_DIR/../mol
+
+# --------------------------------------------------------------
+
+# use one of the following input files
 
-#ln -s $ORIG_DIR/mol/ala2vac/input_parRep.lua input.lua
-#ln -s $ORIG_DIR/mol/ala2vac/input_generalized_parRep.lua input.lua
+#ln -s mol/ala2vac/input_parRep.lua input.lua
+#ln -s mol/ala2vac/input_generalized_parRep.lua input.lua
 
-#ln -s $ORIG_DIR/mol/ala2vac_transient/input_parRep.lua input.lua
-#ln -s $ORIG_DIR/mol/ala2vac_transient/input_generalized_parRep.lua input.lua
+#ln -s mol/ala2vac_transient/input_parRep.lua input.lua
+ln -s mol/ala2vac_transient/input_generalized_parRep.lua input.lua
 
-ln -s $ORIG_DIR/mol/1d7h_i/input_generalized_parRep.lua input.lua
+#ln -s mol/1d7h_i/input_generalized_parRep.lua input.lua
 
-ln -s $ORIG_DIR/parRep
+ln -s $ORIG_DIR/../build/parRep
 
+# When using the OMM CPU platform this decides how many CPU threads per replica to use
 export OPENMM_CPU_THREADS=1
+# if the following is not present only the slow Reference OMM platform will be available
 export OPENMM_PLUGIN_DIR=$ORIG_DIR/../build/openmm-7.3.0/lib/plugins
 
-mpirun -np 1  ./parRep -i input.lua -log dbg : \
-       -np 7  ./parRep -i input.lua -log dbg -o out.txt -e err.txt
+# to save random seeds to a file for later use
+mpirun -np 1  ./parRep -i input.lua -log dbg --out-seeds seeds.bin : \
+       -np 7  ./parRep -i input.lua -log dbg -o out.txt -e err.txt --out-seeds seeds.bin
 
+# to run using random seeds from a previous execution for reproducibility (not guaranteed to work on all platforms or if the software environnment is not exactly the same)
+# mpirun -np 1  ./parRep -i input.lua -log dbg --inp-seeds seeds.bin : \
+#        -np 7  ./parRep -i input.lua -log dbg -o out.txt -e err.txt --inp-seeds seeds.bin
diff --git a/run/run.slurm.sh b/run/run.slurm.sh
index a284f04..f8a3678 100755
--- a/run/run.slurm.sh
+++ b/run/run.slurm.sh
@@ -12,12 +12,12 @@ TMP_DIR=$(mktemp --tmpdir=$ORIG_DIR -u)
 mkdir -p $TMP_DIR && echo "Running in tmp dir $TMP_DIR"
 
 cd $TMP_DIR
-ln -s $ORIG_DIR/mol
-ln -s $ORIG_DIR/mol/ala2vac_transient/input_generalized_parRep.lua input.lua
-ln -s $ORIG_DIR/parRep
+ln -s $ORIG_DIR/../mol
+ln -s mol/ala2vac_transient/input_generalized_parRep.lua input.lua
+ln -s $ORIG_DIR/../build/parRep
 
 export OPENMM_CPU_THREADS=1
-export OPENMM_PLUGIN_DIR=/home/hedin/prog/parRep/build/openmm-7.3.0/lib/plugins
+export OPENMM_PLUGIN_DIR=$ORIG_DIR/../build/openmm-7.3.0/lib/plugins
 
 srun --mpi=pmi2 ./parRep -i input.lua -log warn -o out.txt -e err.txt
 
diff --git a/src/dyna/GelmanRubin.cpp b/src/dyna/GelmanRubin.cpp
index b91aacb..26f5cf9 100644
--- a/src/dyna/GelmanRubin.cpp
+++ b/src/dyna/GelmanRubin.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <cstdio>
@@ -19,10 +19,10 @@
 
 using namespace std;
 
-GelmanRubinAnalysis::GelmanRubinAnalysis(const uint32_t _num_chains, const uint32_t _discard)
+GelmanRubinAnalysis::GelmanRubinAnalysis(const uint32_t _num_chains, const uint32_t _no_convergence_if_less_than)
 {
   num_chains = _num_chains;
-  discard_first = _discard;
+  min_num_observations_before_check = _no_convergence_if_less_than;
   
   // allocate one map<string,Observable> per chain
   observablesList = unique_ptr<std::map<std::string,Observable>[]>(new map<string,Observable>[num_chains]);
@@ -40,6 +40,7 @@ void GelmanRubinAnalysis::registerObservable(const Observable& obs, const double
   }
   
   tolerance[obs.get_name()] = tol;
+  
   obsTypes.push_back(obs.get_name());
 }
 
@@ -116,7 +117,8 @@ void GelmanRubinAnalysis::addNObservations(const std::string& obName,
                                            const uint32_t chain_id)
 {
   map<string,Observable>& chainObsList = observablesList[chain_id];
-  chainObsList[obName].add(from,to);
+  const vector<double> tmp(from,to);
+  chainObsList[obName].add(tmp);
 }
 
 void GelmanRubinAnalysis::updateStatistics()
@@ -180,7 +182,7 @@ void GelmanRubinAnalysis::describeChain(const uint32_t chain_id)
 
 void GelmanRubinAnalysis::describe()
 {
-  LOG_PRINT(LOG_DEBUG,"GelmanRubinAnalysis desciption :\n");
+  LOG_PRINT(LOG_INFO,"GelmanRubinAnalysis desciption :\n");
   
   for(string& name : obsTypes)
   {
@@ -193,18 +195,23 @@ void GelmanRubinAnalysis::describe()
 
 double GelmanRubinAnalysis::get_ratio(string& obName)
 {
-  double lratio = ratio[obName].back();
+  const double lratio = ratio[obName].back();
   return lratio;
 }
 
 bool GelmanRubinAnalysis::check_convergence(const string& obName)
 {
   
-  vector<double>& rvec = ratio[obName];
+  const vector<double>& rvec = ratio[obName];
+  const Observable& obs = observablesList[0][obName];
+  const uint32_t num_obs_accumulated = (uint32_t) obs.get_numObs();
   
   bool check;
-  if(rvec.size() < discard_first)
+  if(num_obs_accumulated < min_num_observations_before_check)
+  {
+    LOG_PRINT(LOG_DEBUG,"Only %u observations available for observable %s, but the user required to only calculate the ratio when at least %u are available, therefore convergence is forced to be false.",num_obs_accumulated,obName.c_str(),min_num_observations_before_check);
     check = false;
+  }
   else
     check = (fabs(1.0-rvec.back()) < tolerance[obName])? true : false ;
   
@@ -266,4 +273,7 @@ void GelmanRubinAnalysis::reset_all_chains()
     ratio[st].resize(0);
     ratio[st].shrink_to_fit();
   }
+  
+  
+  
 }
diff --git a/src/dyna/observable.cpp b/src/dyna/observable.cpp
index 6e272a4..f061c97 100644
--- a/src/dyna/observable.cpp
+++ b/src/dyna/observable.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <numeric>
diff --git a/src/dyna/parRep.cpp b/src/dyna/parRep.cpp
index 83582e6..24ed0f9 100644
--- a/src/dyna/parRep.cpp
+++ b/src/dyna/parRep.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <cstdint>
diff --git a/src/dyna/parRep_FV.cpp b/src/dyna/parRep_FV.cpp
index 4a81d47..d9e6a89 100644
--- a/src/dyna/parRep_FV.cpp
+++ b/src/dyna/parRep_FV.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <utility>
@@ -23,7 +23,6 @@
 
 using namespace std;
 
-// NOTE to be used by a derived class only
 ParRepFV::ParRepFV(DATA& _dat,
                    unique_ptr<ATOM[]>& _at,
                    unique_ptr<MD_interface>& _md,
diff --git a/src/dyna/parRep_FV_multiExits.cpp b/src/dyna/parRep_FV_multiExits.cpp
index 806aaa9..ae6c021 100644
--- a/src/dyna/parRep_FV_multiExits.cpp
+++ b/src/dyna/parRep_FV_multiExits.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <cstdlib>
diff --git a/src/dyna/runSim.cpp b/src/dyna/runSim.cpp
index f1c21a5..d0896c3 100644
--- a/src/dyna/runSim.cpp
+++ b/src/dyna/runSim.cpp
@@ -32,7 +32,7 @@
 
 using namespace std;
 
-void run_simulation(const string& inpf)
+void run_simulation(const string& inpf, const std::string& seeds_inp_file, const std::string& seeds_out_file)
 {
   /* find out MY process ID, and how many processes were started. */
   int32_t my_id     = -1;
@@ -43,27 +43,46 @@ void run_simulation(const string& inpf)
   /*
    * Each MPI rank will initialise its own random numbers generator ; see rand.hpp
    */
-  init_rand();
   
-  /*
-   * This is how to initialize the random generators providing seeds manually (at least 5 seeds of type uint64_t encodes as a comma separated string)
-   * 
-   * ATTENTION Using the directly the following line would be wrong because all the random generators on all
-   *           MPI ranks would be initialised with the same seeds !!! Each rank should use a modified vrsion of those seeds.
-   * 
-   *   const string ten_seeds = "12813764096581876017,1660537369292506360,10652021971297968194,"
-   *    "1632182917282226681,2681620832946811191,11894265520984226376,"
-   *    "8987030001799979889,7928564290642933391,6948006017539476486,1814061233159141676";
-   * 
-   *    init_rand(ten_seeds);
-   * 
-   */
+  const bool load_seeds = (seeds_inp_file != NULLFILE);
+  const bool save_seeds = (seeds_out_file != NULLFILE);
 
+  try
+  {
+    if(load_seeds)
+    {
+      // seeds for all the ranks will be read from the file 'seeds_inp_file'
+      init_rand(SEEDS_IO::LOAD_FROM_FILE,seeds_inp_file);
+      fprintf(stdout,"All seeds for all ranks have been read from file %s\n",seeds_inp_file.c_str());
+    }
+    else if(save_seeds)
+    {
+      //Seeds from all the ranks will be written to the file 'seeds_out_file' for re-use
+      init_rand(SEEDS_IO::SAVE_TO_FILE,seeds_out_file);
+      fprintf(stdout,"All seeds from all ranks have been saved to file %s\n",seeds_out_file.c_str());
+    }
+    else
+    {
+      init_rand(SEEDS_IO::NONE);
+      fprintf(stdout,       "Random Seeds internally generated but not saved to a file, no reproducibility will be possible for this run !!\n");
+      LOG_PRINT(LOG_WARNING,"Random Seeds internally generated but not saved to a file, no reproducibility will be possible for this run !!\n");
+    }
+  }
+  catch(exception& e)
+  {
+    fprintf(stderr,"std::exception captured on rank %d when initialising random numbers generator !\n",my_id);
+    fprintf(stderr,"Error message is : %s\n",e.what());
+    fprintf(stderr,"Now flushing files and terminating all other MPI ranks...\n");
+    LOG_FLUSH_ALL();
+    MPI_CUSTOM_ABORT_MACRO();
+  }
+  
   fprintf(stdout,"Rank %d properly initialised its mt19937 random numbers generator\n",my_id);
+  
   fprintf(stdout,"5 random uint32_t from rank %d : %u %u %u %u %u\n",my_id,get_uint32(),
           get_uint32(),get_uint32(),get_uint32(),get_uint32());
   
-  MPI_Barrier(MPI_COMM_WORLD);
+//   MPI_Barrier(MPI_COMM_WORLD);
   
   // stores simulation parameters
   DATA dat;
@@ -219,6 +238,10 @@ void run_simulation(const string& inpf)
     MPI_CUSTOM_ABORT_MACRO();
   }
 
+  // ready to run sim, flush IO files before and let's start !
+  LOG_FLUSH_ALL();
+  MPI_Barrier(MPI_COMM_WORLD);
+  
   try
   {
     simulation->run();
diff --git a/src/logger.cpp b/src/logger.cpp
index 55900cf..33e5a67 100644
--- a/src/logger.cpp
+++ b/src/logger.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <string>
diff --git a/src/main.cpp b/src/main.cpp
index eb9f00b..7261f9d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <string>
@@ -115,7 +115,9 @@ int main(int argc, char* argv[])
   }
   MPI_Barrier(MPI_COMM_WORLD);
   
-  char inpf[FILENAME_MAX] = "";
+  string input_file = NULLFILE;
+  string seeds_inp_file = NULLFILE;
+  string seeds_out_file = NULLFILE;
 
   // arguments parsing
   for (uint32_t i=1; i<(uint32_t)argc; i++)
@@ -123,7 +125,7 @@ int main(int argc, char* argv[])
     // get name of input file
     if(!strcasecmp(argv[i],"-i"))
     {
-      sprintf(inpf,"%s",argv[++i]);
+      input_file = string(argv[++i]);
     }
     /*
      * reopen stdout to unique file based on the user specified template name ;
@@ -167,6 +169,15 @@ int main(int argc, char* argv[])
       else
         fprintf(stdout,"[Warning] Unknown log level '%s' : default value used.\n\n",argv[i]);
     }
+    // in or out files where random seeds are read/written
+    else if( !strcasecmp(argv[i],"--inp-seeds") )
+    {
+      seeds_inp_file = string(argv[++i]);
+    }
+    else if( !strcasecmp(argv[i],"--out-seeds") )
+    {
+      seeds_out_file = string(argv[++i]);
+    }
     // print help and proper exit
     else if( !strcasecmp(argv[i],"-h") || !strcasecmp(argv[i],"-help") || !strcasecmp(argv[i],"--help") )
     {
@@ -208,8 +219,7 @@ int main(int argc, char* argv[])
   }
 
   // run the simulation
-  const string input(inpf);
-  run_simulation(input);
+  run_simulation(input_file,seeds_inp_file,seeds_out_file);
 
   // closing log files is the last thing to do as errors may occur at the end
   close_logfiles();
@@ -237,9 +247,11 @@ int main(int argc, char* argv[])
 void help(char *argv[])
 {
   fprintf(stdout,"Need at least one argument : %s -i an_input_file\n",argv[0]);
-  fprintf(stdout,"optional args : -o [stdout_to_file] -e [stderr_to_file] -log [logging level, one of { no | warn | info | dbg }] \n");
-  fprintf(stdout,"Example : \n %s -i input_file -o out.txt -log info \n\n",argv[0]);
+  fprintf(stdout,"optional args : -o [stdout_to_file] -e [stderr_to_file] -log [logging level, one of { no | warn | info | dbg }] --inp-seeds [seed_file] --out-seeds [seed_file]\n");
+  fprintf(stdout,"Examples : \n\t%s -i input_file -o out.txt -log info --inp-seeds seeds.bin \n",argv[0]);
+  fprintf(stdout,"\t%s -i input_file -o out.txt -log info --out-seeds seeds.bin \n",argv[0]);
   fprintf(stdout,"The default logging level is 'warn' \n");
+  fprintf(stdout,"See the documentation for more details. \n");
 }
 
 /*!
@@ -257,6 +269,9 @@ void help(char *argv[])
  *
  * This software is a new implementation of the Generalized parallel replica method, targeting
  * frequently encountered metastable biochemical systems.
+ * It was used in version v1.0.0 for producing all the simulations published in : 
+ * 
+ *  <a href="https://doi.org/10.1016/j.cpc.2019.01.005">https://doi.org/10.1016/j.cpc.2019.01.005</a>
  * 
  * \image html  main.png
  * \image latex main.pdf
diff --git a/src/md/md_interface.cpp b/src/md/md_interface.cpp
index d8ca6c4..7a7fc14 100644
--- a/src/md/md_interface.cpp
+++ b/src/md/md_interface.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include "md_interface.hpp"
diff --git a/src/md/omm_interface.cpp b/src/md/omm_interface.cpp
index 3a8de17..e7398bd 100644
--- a/src/md/omm_interface.cpp
+++ b/src/md/omm_interface.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <cstdio>
@@ -109,13 +109,6 @@ OMM_interface::OMM_interface(DATA& _dat,
   // deserialize the integrator
   integrator = unique_ptr<OpenMM::Integrator>(&integr);
   
-  // deserialize an OpenMM state : coordinates velocities forces energy
-  addPlatform(platformDesired,platformProperties,state);
-  
-  state.getPeriodicBoxVectors(pbc[0],pbc[1],pbc[2]);
-  
-  memcpy(dat.pbc.data(),pbc.data(),sizeof(pbc));
-  
   // re-seed the integrator
   if(dynamic_cast<OpenMM::LangevinIntegrator*>(integrator.get()))
   {
@@ -138,14 +131,22 @@ OMM_interface::OMM_interface(DATA& _dat,
     throw runtime_error("Error when re-seeding the OMM integrator : integrator type is neither OpenMM::LangevinIntegrator or OpenMM::BrownianIntegrator !\n");
   }
   
+  // from system and integrator, create a platform and a context using the state
+  addPlatform(platformDesired,platformProperties,state);
+  
+  // retireve pbc data and store it in dat
+  state.getPeriodicBoxVectors(pbc[0],pbc[1],pbc[2]);
+  memcpy(dat.pbc.data(),pbc.data(),sizeof(pbc));
+  
   // fill dat structure with unserialised data
   dat.natom    = system->getNumParticles();
   dat.timestep = integrator->getStepSize();
 
+  // retrieve initial positions and velocities
   pos = vector<Vec3>(dat.natom);
   vel = vector<Vec3>(dat.natom);
   
-  /**
+  /*
    * calculate the number of degrees of freedom of the system
    * it is 3 times the number of particles (not virtual ones for which mass=0)
    * minus the number of constraints
@@ -232,7 +233,7 @@ void OMM_interface::doNsteps(uint32_t numSteps)
 void OMM_interface::minimise(double tol, uint32_t maxsteps, ENERGIES& ener)
 {
   // minimisation with L-BGFS
-  OpenMM::LocalEnergyMinimizer::minimize(*context, (int32_t)tol, maxsteps);
+  OpenMM::LocalEnergyMinimizer::minimize(*context,tol,(int32_t)maxsteps);
   
   // get back data from context and extract energies
   const int infoMask = OpenMM::State::Energy;
@@ -295,6 +296,8 @@ void OMM_interface::minimiseWithCopy(double tol, uint32_t maxsteps, ENERGIES& en
   context->setPositions(lpos);
   context->setVelocities(lvel);
   
+  context->applyConstraints(integrator->getConstraintTolerance());
+  context->applyVelocityConstraints(integrator->getConstraintTolerance());
 }
 
 void OMM_interface::minimiseWithCopy(double tol, uint32_t maxsteps, ENERGIES& ener,
@@ -327,6 +330,9 @@ void OMM_interface::minimiseWithCopy(double tol, uint32_t maxsteps, ENERGIES& en
   context->setPositions(lpos);
   context->setVelocities(lvel);
   
+  context->applyConstraints(integrator->getConstraintTolerance());
+  context->applyVelocityConstraints(integrator->getConstraintTolerance());
+  
 }
 
 void OMM_interface::setSimClockTime(double t)
@@ -346,9 +352,13 @@ void OMM_interface::setCrdsVels(ATOM at[])
   memcpy(pbc.data(),dat.pbc.data(),sizeof(pbc));
   
   context->setPeriodicBoxVectors(pbc[0],pbc[1],pbc[2]);
+  
   context->setPositions(pos);
   context->setVelocities(vel);
   
+  context->applyConstraints(integrator->getConstraintTolerance());
+  context->applyVelocityConstraints(integrator->getConstraintTolerance());
+  
 }
 
 void OMM_interface::randomiseVelocities()
diff --git a/src/rand.cpp b/src/rand.cpp
index 6b34824..c7bd62d 100644
--- a/src/rand.cpp
+++ b/src/rand.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <random>
@@ -12,12 +12,14 @@
 #include <limits>
 #include <iostream>
 #include <sstream>
+#include <stdexcept>
 
 #include <cstdio>
 
+#include "global.hpp"
 #include "logger.hpp"
+#include "mpi_utils.hpp"
 #include "rand.hpp"
-#include "global.hpp"
 
 using namespace std;
 
@@ -40,54 +42,141 @@ static uniform_int_distribution<int32_t>  int32_t_dist(numeric_limits<int32_t>::
 static uniform_int_distribution<uint32_t> uint32_t_dist(numeric_limits<uint32_t>::min(),
                                                         numeric_limits<uint32_t>::max());
 
-void init_rand()
+void init_rand(SEEDS_IO io_type, const string& seeds_file_name)
 {
-  random_device rd;
-  LOG_PRINT(LOG_INFO,"Entropy of 'std::random_device' on this machine is : %lf\n",rd.entropy());
   
-  /*
-   * Each MPI ranks reads 512 uint64_t 'seeds' from random_device
-   */
-  array<uint64_t,512> seeds;
-  for(size_t n=0; n<512; n++)
-    seeds[n] = (uint64_t) rd();
-
-  // then each ranks initialises its own mt19937 with the seed sequence
-  seed_seq seq(seeds.begin(),seeds.end());
-  generator.seed(seq);
-  
-  // finally for each generator we discard some random numbers to be sure to avoid any correlation
-  generator.discard(4*generator.state_size);
+  bool save_seeds = false;
+  bool load_seeds = false;
   
-}
+  switch(io_type)
+  {
+    case SEEDS_IO::NONE :
+      break;
+      
+    case SEEDS_IO::SAVE_TO_FILE :
+      save_seeds = true;
+      break;
+      
+    case SEEDS_IO::LOAD_FROM_FILE :
+      load_seeds = true;
+      break;
+      
+    default:
+      throw runtime_error("Error : unsupported SEEDS_IO mode in function '" + string(__func__) + "' !!!");
+      break;
+  }
 
-void init_rand(const string& seeds_str)
-{
-  istringstream split_stream(seeds_str);
-  string token;
-  
-  vector<uint64_t> seeds;
-  
   /*
-   * Exctract the seeds from the comma separated string ;
-   * NOTE It is crucial to use a different string on each MPI rank otherwise the generators will provide the same numbers on each rank !!
+   * Each MPI ranks requires 512 uint32_t 'seeds' for initialising its mt19937_64 generator
    */
-  while(getline(split_stream, token, ','))
+  array<uint32_t,512> seeds;
+  
+  if(!load_seeds)
   {
-    seeds.push_back((uint64_t)stoul(token));
+    random_device rd;
+    LOG_PRINT(LOG_INFO,"Entropy of 'std::random_device' on this machine is : %lf\n",rd.entropy());
+    
+    for(size_t n=0; n<512; n++)
+      seeds[n] = (uint32_t) rd();
   }
-  
-  if(seeds.size()<5)
+  else
   {
-    throw runtime_error("Error : when providing a string encoded list of seeds for the random generator, you need to provide at least 5 seeds, but it appears that you have provided only " + to_string(seeds.size()) + " seeds ! \n");
+    // First enforce sync with a barrier
+    MPI_Barrier(MPI_COMM_WORLD);
+    
+    int32_t myrank = -1, numranks = -1;
+    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
+    MPI_Comm_size(MPI_COMM_WORLD, &numranks);
+    
+    // see comments in the following block "if(save_seeds)..." below
+    
+    MPI_File myfile;
+    
+    MPI_File_open(MPI_COMM_WORLD, seeds_file_name.c_str(),
+                  MPI_MODE_RDONLY, MPI_INFO_NULL,
+                  &myfile);
+    
+    MPI_Offset disp = myrank*512*sizeof(uint32_t);
+    
+    MPI_File_set_view(myfile, disp,
+                      MPI_UINT32_T, MPI_UINT32_T, 
+                      "native", MPI_INFO_NULL);
+    
+    MPI_File_read(myfile, seeds.data(),
+                  512, MPI_UINT32_T,
+                  MPI_STATUS_IGNORE);
+    
+    MPI_File_close(&myfile);
+    
+    MPI_Barrier(MPI_COMM_WORLD);
   }
   
+  /*
+   * Save seeds to a unique binary file for reproducibility
+   */
+  if(save_seeds)
+  {
+    // First enforce sync with a barrier
+    MPI_Barrier(MPI_COMM_WORLD);
+    
+    int32_t myrank = -1, numranks = -1;
+    MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
+    MPI_Comm_size(MPI_COMM_WORLD, &numranks);
+    
+    /* Shared file */
+    MPI_File myfile;
+    
+    /*
+     * Open the file : it will be a binary file open in write mode, with overwritting
+     * 
+     * int MPI_File_open(MPI_Comm comm, const char *filename,
+     *                   int amode, MPI_Info info,
+     *                   MPI_File *fh)
+     */
+    MPI_File_open(MPI_COMM_WORLD, seeds_file_name.c_str(),
+                  MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL,
+                  &myfile);
+    
+    // displacment == absolute displacement in the file where the data will be written (from beginning of file)
+    MPI_Offset disp = myrank*512*sizeof(uint32_t);
+    
+    /*
+     * Set the file view : tels at which value of disp data will be written
+     * 
+     * MPI_File_set_view(MPI_File fh, MPI_Offset disp,
+     *                   MPI_Datatype etype, MPI_Datatype filetype,
+     *                   const char *datarep, MPI_Info info)
+     */
+    MPI_File_set_view(myfile, disp,
+                      MPI_UINT32_T, MPI_UINT32_T, 
+                      "native", MPI_INFO_NULL);
+    /*
+     * Write buf to the file : 
+     * 
+     int MPI_File_write(MPI_File fh, const void *buf,
+                        int count, MPI_Datatype datatype,
+                        MPI_Status *status)
+     */
+    MPI_File_write(myfile, seeds.data(),
+                   512, MPI_UINT32_T,
+                   MPI_STATUS_IGNORE);
+    
+    /*
+     * Close the file
+     */
+    MPI_File_close(&myfile);
+    
+    MPI_Barrier(MPI_COMM_WORLD);
+  }
+
+  // then each ranks initialises its own mt19937 with the seed sequence
   seed_seq seq(seeds.begin(),seeds.end());
   generator.seed(seq);
   
   // finally for each generator we discard some random numbers to be sure to avoid any correlation
   generator.discard(4*generator.state_size);
   
+  MPI_Barrier(MPI_COMM_WORLD);
 }
 
 double get_double_unif_0_1()
diff --git a/src/utils/lua_interface.cpp b/src/utils/lua_interface.cpp
index 0ea5570..b636816 100644
--- a/src/utils/lua_interface.cpp
+++ b/src/utils/lua_interface.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <iostream>
@@ -13,7 +13,6 @@
 #include <exception>
 
 #include "sol.hpp"
-#include "rand.hpp"
 #include "logger.hpp"
 #include "lua_interface.hpp"
 #include "omm_interface.hpp"
@@ -34,6 +33,7 @@ typedef tuple<double,double,double> VelsTuple;
 typedef tuple<double,double,double> COMTuple;
 typedef tuple<double,double,double> ENETuple;
 typedef tuple<sol::table,sol::table> SolTabl2Tuple;
+typedef tuple<double,double,double,sol::table,sol::table> ENETupleSolTablTuple;
 
 luaInterface::luaInterface(const string _inputFile,
                            DATA& _dat,
@@ -165,12 +165,12 @@ void luaInterface::parse_lua_file()
 
   if(pvMap["algorithm"] == "PARREP")
   {
-    fprintf(stdout,"Parsing ParRep simulation parameters.");
+    fprintf(stdout,"Parsing ParRep simulation parameters.\n");
     parse_parrep_params();
   }
   else if(pvMap["algorithm"] == "PARREP_FV")
   {
-    fprintf(stdout,"Parsing ParRep FV simulation parameters.");
+    fprintf(stdout,"Parsing ParRep FV simulation parameters.\n");
     parse_parrepFV_params();
     
     uint32_t num_ex_events = (uint32_t) stoul(pvMap.at("allowedExitEvents"));
@@ -473,20 +473,6 @@ void luaInterface::register_default_lua_functions()
                      return ENETuple(minE.epot(),minE.ekin(),minE.etot());
                    }
   );
-  
-  // returns energy contribution 
-  lua.set_function("get_group_epot",[&](int32_t groups) -> double
-                                    {
-                                      if(md->engine_supports_groups_splitting()==false)
-                                      {
-                                        runtime_error ex("Lua script called get_group_energy(...) but it seems that the currently active MD engine does not support the group splitting feature ! Aborting program, please fix your input file accordingly...");
-                                        throw ex;
-                                      }
-                                      ENERGIES e;
-                                      md->getSimData(nullptr,&e,nullptr,nullptr,groups);
-                                      return e.epot();
-                                    }
-  );
 
   // perform energy minimisation, and return the coordinates and velocities
   lua.set_function("get_minimised_crdvels",
@@ -521,13 +507,63 @@ void luaInterface::register_default_lua_functions()
 
                      return SolTabl2Tuple(crds,vels);
                    }
-);
+  );
+  
+  lua.set_function("get_minimised_energy_crdvels",
+                   [&](double tolerance, uint32_t maxSteps) -> ENETupleSolTablTuple
+                   {
+                     ENERGIES minE;
+                     vector<XYZ> coordinates;
+                     vector<XYZ> velocities;
+                     
+                     md->minimiseWithCopy(tolerance,maxSteps,minE,coordinates,velocities);
+                     
+                     sol::table crds = lua.create_table();
+                     crds["x"] = lua.create_table();
+                     crds["y"] = lua.create_table();
+                     crds["z"] = lua.create_table();
+                     
+                     sol::table vels = lua.create_table();
+                     vels["x"] = lua.create_table();
+                     vels["y"] = lua.create_table();
+                     vels["z"] = lua.create_table();
+                     
+                     for(uint32_t i=1; i<=dat.natom; i++)
+                     {
+                       crds["x"][i] = coordinates[i-1][0];
+                       crds["y"][i] = coordinates[i-1][1];
+                       crds["z"][i] = coordinates[i-1][2];
+                       
+                       vels["x"][i] = velocities[i-1][0];
+                       vels["y"][i] = velocities[i-1][1];
+                       vels["z"][i] = velocities[i-1][2];
+                     }
+                     
+                     return ENETupleSolTablTuple(minE.epot(),minE.ekin(),minE.etot(),crds,vels);
+                   }
+  );
+  
   
   // get the current temperature (from kinetic energy)
   lua.set_function("get_temperature",
                    [&]()->double{ return md->getTemperature();}
   );
 
+  // returns energy contribution 
+  lua.set_function("get_group_epot",
+                   [&](int32_t groups) -> double
+                   {
+                     if(md->engine_supports_groups_splitting()==false)
+                     {
+                       runtime_error ex("Lua script called get_group_energy(...) but it seems that the currently active MD engine does not support the group splitting feature ! Aborting program, please fix your input file accordingly...");
+                       throw ex;
+                     }
+                     ENERGIES e;
+                     md->getSimData(nullptr,&e,nullptr,nullptr,groups);
+                     return e.epot();
+                   }
+  );
+  
 }
 
 void luaInterface::register_default_lua_variables()
@@ -541,7 +577,7 @@ void luaInterface::register_default_lua_variables()
   lua["temperature"] = 0.0;
   
   lua.create_named_table("minimisation");
-  lua["minimisation"]["Tolerance"] = 1e-6;
+  lua["minimisation"]["Tolerance"] = 10.0;
   lua["minimisation"]["MaxSteps"]  = 0;
   
   lua["equilibrationSteps"] = 50e3;
@@ -559,13 +595,13 @@ void luaInterface::register_default_lua_variables()
   
   // expose dummy lambda functions ; they do nothing
   //  and are the default in case the user does not override them in the lua script
-  lua["SQLiteDB"]["open"].set_function([](){fprintf(stderr,"Dummy lambda db_open\n");});
+  lua["SQLiteDB"]["open"].set_function([](){fprintf(stdout,"Dummy lambda db_open\n");});
   
-  lua["SQLiteDB"]["close"].set_function([](){fprintf(stderr,"Dummy lambda db_close\n");});
+  lua["SQLiteDB"]["close"].set_function([](){fprintf(stdout,"Dummy lambda db_close\n");});
   
-  lua["SQLiteDB"]["insert_state"].set_function([](){fprintf(stderr,"Dummy lambda db_insert\n");});
+  lua["SQLiteDB"]["insert_state"].set_function([](){fprintf(stdout,"Dummy lambda db_insert\n");});
   
-  lua["SQLiteDB"]["backup_to_file"].set_function([](){fprintf(stderr,"Dummy lambda db_backup\n");});
+  lua["SQLiteDB"]["backup_to_file"].set_function([](){fprintf(stdout,"Dummy lambda db_backup\n");});
   
 }
 
@@ -588,7 +624,7 @@ void luaInterface::parse_omm_params()
     p = OMM_interface::omm_platforms_names.at(PLATFORMS::CUDA);
   else
   {
-    throw runtime_error(string("Error when parsing OMM platform : '" + p + "' is not a valid platform type !"));
+    throw runtime_error(string("Error when parsing OMM platform : '" + p + "' is not a valid platform type !\n"));
   }
   
   fprintf(stdout,"Parsed OMM platform (or default if missing) : %s\n",pvMap["platform"].c_str());
@@ -650,7 +686,7 @@ void luaInterface::parse_omm_params()
   }
   else
   {
-    fprintf(stdout,"No specific OpenMM platform properties defined by the user...");
+    fprintf(stdout,"No specific OpenMM platform properties defined by the user...\n");
   }
 }
 
@@ -699,7 +735,7 @@ void luaInterface::parse_parrepFV_params()
   }
   else
   {
-    throw runtime_error( "Error when trying to read simulation.GRobservables which is required for a 'parrepFV' simulation !\n");
+    throw runtime_error( "Error when trying to read 'simulation.GRobservables' which is required for a 'parrepFV' simulation !\n");
   }
   
   pvMap["GRtol"] = to_string(simulation_def["GRtol"].get_or(0.01));
diff --git a/src/utils/mpi_utils.cpp b/src/utils/mpi_utils.cpp
index c6283e0..9cf093f 100644
--- a/src/utils/mpi_utils.cpp
+++ b/src/utils/mpi_utils.cpp
@@ -4,7 +4,7 @@
  * \author Florent Hédin
  * \author Tony Lelièvre
  * \author École des Ponts - ParisTech
- * \date 2016-2018
+ * \date 2016-2019
  */
 
 #include <cstdint>
-- 
GitLab