Index: trunk/LMDZ.PLUTO/libf/muphypluto/lint_core.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/lint_core.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/lint_core.F90	(revision 3560)
@@ -0,0 +1,112 @@
+! Copyright (c) Université de Reims Champagnne-Ardenne (2010-2015)
+! Contributor: Jérémie Burgalat (jeremie.burgalat@univ-reims.fr)
+! 
+! This software is a computer program whose purpose is to compute multi-variate
+! linear interpolation.
+! 
+! This software is governed by the CeCILL-B license under French law and
+! abiding by the rules of distribution of free software.  You can  use, 
+! modify and/ or redistribute the software under the terms of the CeCILL-B
+! license as circulated by CEA, CNRS and INRIA at the following URL
+! "http://www.cecill.info". 
+! 
+! As a counterpart to the access to the source code and  rights to copy,
+! modify and redistribute granted by the license, users are provided only
+! with a limited warranty  and the software's author,  the holder of the
+! economic rights,  and the successive licensors  have only  limited
+! liability. 
+! 
+! In this respect, the user's attention is drawn to the risks associated
+! with loading,  using,  modifying and/or developing or reproducing the
+! software by the user in light of its specific status of free software,
+! that may mean  that it is complicated to manipulate,  and  that  also
+! therefore means  that it is reserved for developers  and  experienced
+! professionals having in-depth computer knowledge. Users are therefore
+! encouraged to load and test the software's suitability as regards their
+! requirements in conditions enabling the security of their systems and/or 
+! data to be ensured and,  more generally, to use and operate it in the 
+! same conditions as regards security. 
+! 
+! The fact that you are presently reading this means that you have had
+! knowledge of the CeCILL-B license and that you accept its terms.
+
+!! File:    lint_core.F90
+!! Summary: Linear interpolation core function file
+!! Author:  J. Burgalat
+!! Date:    2010-2014
+
+MODULE LINT_CORE
+    !! Core module of the library.
+    !! 
+    !! This module contains a single function that performs the linear 
+    !! interpolation of a single _N_-D point between \(2^{N}\) adjacents 
+    !! points.
+    USE LINT_PREC
+    IMPLICIT NONE
+  
+    PRIVATE :: wp ! from LINT_PREC
+  
+    INTERFACE
+      FUNCTION locate(value,vector) RESULT(idx)
+        !! Locate the nearest default value in vector
+        !!
+        !! the method should search the subscript of the nearest value by default in
+        !! in the input vector.
+        IMPORT wp
+        REAL(kind=wp), INTENT(in)               :: value   !! value to search 
+        REAL(kind=wp), INTENT(in), DIMENSION(:) :: vector  !! Vector to search in
+        INTEGER :: idx                                     !! Subscript of the nearest value in vector
+      END FUNCTION locate
+    END INTERFACE
+  
+  
+    CONTAINS 
+  
+    FUNCTION lintc_(point,grid) RESULT(res)
+      !! Multivariate linear interpolation core function
+      !! 
+      !! The method computes multivariate linear interpolation at the given __point__ using its 
+      !! neighbours given in __grid__.
+      !!
+      !! @warning
+      !! In order to get a correct result, __grid__ must be ordered so first dimensions vary first 
+      !! (see [Generic method](page/index.html/#generic-method) section of main documentation).
+      !!
+      !! @warning 
+      !! The method in its current version does not check array boundaries. This operation should be 
+      !! performed in wrappers of the function !
+      INTEGER, PARAMETER :: np = 2 
+      REAL(kind=wp), INTENT(in), DIMENSION(:)   :: point 
+        !! Coordinates of the point to compute.
+        !!
+        !! For __N__-D interpolation, ut should be a vector of __N__ points.
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:) :: grid
+        !! Grid of values used for interpolation. 
+        !!
+        !! For __N__-D interpolation, it should be a 2D-array of \(2^{N}\) rows and \(N+1\) columns. 
+        !! Each row corresponds to a point with N coordinates, the last column is reserved for the
+        !! value of the point.
+      REAL(kind=wp) :: res
+        !! Interpolated value
+      REAL(kind=wp), DIMENSION(:),ALLOCATABLE :: val
+      REAL(kind=wp)                           :: cd
+      INTEGER                                 :: nv,mi,ngp,cp,i,j,k
+      nv = SIZE(point) ; mi = np**nv
+      ALLOCATE(val(2*mi-1)) 
+      val(1:mi) = grid(:,nv+1) ; val(mi+1:2*mi-1) = 0._wp
+      ! Computes the QnD linear interpolation
+      cp = 0
+      DO i=1,nv
+        cd = (point(i)-grid(1,i))/(grid(mi,i)-grid(1,i))
+        k = 1 ; ngp = np**(nv-i+1) ; cp = cp + ngp
+        DO j=1,ngp,np 
+          val(cp+k) = val(j+cp-ngp) * (1._wp - cd) + val(j+cp-ngp+1)*cd
+          k = k + 1
+        ENDDO
+      ENDDO
+      res = val(cp+k-1) 
+      DEALLOCATE(val) ! useless normally
+      RETURN
+    END FUNCTION lintc_
+  
+  END MODULE LINT_CORE  
Index: trunk/LMDZ.PLUTO/libf/muphypluto/lint_datasets.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/lint_datasets.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/lint_datasets.F90	(revision 3560)
@@ -0,0 +1,1730 @@
+! Copyright (c) Université de Reims Champagnne-Ardenne (2010-2015)
+! Contributor: Jérémie Burgalat (jeremie.burgalat@univ-reims.fr)
+! 
+! This software is a computer program whose purpose is to compute multi-variate
+! linear interpolation.
+! 
+! This software is governed by the CeCILL-B license under French law and
+! abiding by the rules of distribution of free software.  You can  use, 
+! modify and/ or redistribute the software under the terms of the CeCILL-B
+! license as circulated by CEA, CNRS and INRIA at the following URL
+! "http://www.cecill.info". 
+! 
+! As a counterpart to the access to the source code and  rights to copy,
+! modify and redistribute granted by the license, users are provided only
+! with a limited warranty  and the software's author,  the holder of the
+! economic rights,  and the successive licensors  have only  limited
+! liability. 
+! 
+! In this respect, the user's attention is drawn to the risks associated
+! with loading,  using,  modifying and/or developing or reproducing the
+! software by the user in light of its specific status of free software,
+! that may mean  that it is complicated to manipulate,  and  that  also
+! therefore means  that it is reserved for developers  and  experienced
+! professionals having in-depth computer knowledge. Users are therefore
+! encouraged to load and test the software's suitability as regards their
+! requirements in conditions enabling the security of their systems and/or 
+! data to be ensured and,  more generally, to use and operate it in the 
+! same conditions as regards security. 
+! 
+! The fact that you are presently reading this means that you have had
+! knowledge of the CeCILL-B license and that you accept its terms.
+
+!! File:    lint_datasets.F90
+!! Summary: Dataset module definition file
+!! Author:  J. Burgalat
+!! Date:    2014-2015
+
+MODULE LINT_DATASETS 
+    !! dataset definitions module
+    !!
+    !! This module defines simple derived types that encapsulate data set variables. For a N-dimension 
+    !! dataset, N vectors of \(n_{i}\) elements and a single N-dimensional array of 
+    !! \(\prod_{i}^{N} n_{i}\) elements are defined.
+    !!
+    !! If the data set is to be used within [[lint_dset(module)]] module then each coordinate values of the 
+    !! dataset must be sorted either in ascending or descending order with no duplicates. The module does 
+    !! not provide functionnality to sort and to check such requirements.
+    !!
+    !! The module also provides two interfaces to initialize data sets from input data file which can be 
+    !! a NetCDF file (if NetCDF support is enabled at compilation time) or an ASCII file. In the latter 
+    !! case, the file must contain a header which is formatted as follows:
+    !!
+    !! - The first line must contain only one value which is the number of coordinates (__N__)
+    !! - The second line must contain all the dimension sizes (that is __N__ values)
+    !! - Each other lines should contain __N+1__ columns with, for the __N__ first columns, the 
+    !!   coordinates values and finally the point value in the last column.
+    !! 
+    !! @note
+    !! Note that for ASCII file, data must be ordered so first dimensions vary first. Same requirement 
+    !! is needed for NetCDF file but in most cases, it is implicitly done (if dimensions are ordered).
+    USE NETCDF
+    IMPLICIT NONE
+  
+    PRIVATE 
+  
+    PUBLIC :: read_dset, write_dset, clear_dset, is_in, has_data, debug
+  
+    LOGICAL :: debug = .false.  !! A control flag to enable verbose mode
+  
+    !> Initialize a data set from either an ASCII or a NetCDF file
+    !!
+    !! Whatever the kind of file, this interface assumes that you know the dimensionnality of the 
+    !! extracted variable. If file content is not compatible with the kind of data set given as 
+    !! output argument, or if some I/O error occured, the dataset is cleared.
+    !! @note 
+    !! Netcdf reader interface is available only if the library has been compiled with NetCDF support.
+    INTERFACE read_dset 
+      MODULE PROCEDURE ncdf_rd_1d,ncdf_rd_2d,ncdf_rd_3d,ncdf_rd_4d,ncdf_rd_5d
+#if HAVE_NC4_FTN
+      MODULE PROCEDURE ncdf4_rd_1d,ncdf4_rd_2d,ncdf4_rd_3d,ncdf4_rd_4d,ncdf4_rd_5d
+#endif
+      MODULE PROCEDURE ascii_rd_1d,ascii_rd_2d,ascii_rd_3d,ascii_rd_4d,ascii_rd_5d
+    END INTERFACE
+  
+    !> Write a dataset in a netcdf (classic) file.
+    INTERFACE write_dset 
+      MODULE PROCEDURE nc_wr_1d,nc_wr_2d,nc_wr_3d,nc_wr_4d,nc_wr_5d
+    END INTERFACE
+  
+    !> Clear the given data set
+    INTERFACE clear_dset 
+      MODULE PROCEDURE clr_1d_set,clr_2d_set,clr_3d_set,clr_4d_set,clr_5d_set
+    END INTERFACE
+  
+    !> Check if given point is within the data set
+    INTERFACE is_in 
+      MODULE PROCEDURE is_in_1d,is_in_2d,is_in_3d,is_in_4d,is_in_5d
+    END INTERFACE
+  
+    !> Private interface to netcdf informations getters
+    INTERFACE get_nc_info
+      MODULE PROCEDURE get_nc3_info
+#if HAVE_NC4_FTN
+      MODULE PROCEDURE get_nc4_info 
+#endif
+    END INTERFACE
+  
+    INTERFACE has_data
+      MODULE PROCEDURE has_d_1d,has_d_2d,has_d_3d,has_d_4d,has_d_5d
+    END INTERFACE
+  
+  
+    TYPE, PUBLIC :: DSET1D
+      !! A 1D data set
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: x              !! X coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: data           !! Tabulated function's value at each coordinate
+      CHARACTER(len=NF90_MAX_NAME)            :: xname = "X"    !! Name of the X coordinate
+      CHARACTER(len=NF90_MAX_NAME)            :: dname = "data" !! Name of the data block
+    END TYPE DSET1D
+  
+    TYPE, PUBLIC :: DSET2D
+      !! A 2D data set
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE   :: x              !! X coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE   :: y              !! Y coordinate tabulated values
+      REAL(kind=8), DIMENSION(:,:), ALLOCATABLE :: data           !! Tabulated function's value at each coordinate
+      CHARACTER(len=NF90_MAX_NAME)              :: xname = "X"    !! Name of the X coordinate
+      CHARACTER(len=NF90_MAX_NAME)              :: yname = "Y"    !! Name of the Y coordinate
+      CHARACTER(len=NF90_MAX_NAME)              :: dname = "data" !! Name of the data block
+    END TYPE DSET2D
+  
+    TYPE, PUBLIC :: DSET3D
+      !! A 3D data set
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE     :: x              !! X coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE     :: y              !! Y coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE     :: z              !! Z coordinate tabulated values
+      REAL(kind=8), DIMENSION(:,:,:), ALLOCATABLE :: data           !! Tabulated function's value at each coordinate
+      CHARACTER(len=NF90_MAX_NAME)                :: xname = "X"    !! Name of the X coordinate
+      CHARACTER(len=NF90_MAX_NAME)                :: yname = "Y"    !! Name of the Y coordinate
+      CHARACTER(len=NF90_MAX_NAME)                :: zname = "Z"    !! Name of the Z coordinate
+      CHARACTER(len=NF90_MAX_NAME)                :: dname = "data" !! Name of the data block
+    END TYPE DSET3D
+  
+    TYPE, PUBLIC :: DSET4D
+      !! A 4D data set
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE       :: x              !! X coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE       :: y              !! Y coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE       :: z              !! Z coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE       :: t              !! T coordinate tabulated values
+      REAL(kind=8), DIMENSION(:,:,:,:), ALLOCATABLE :: data           !! Tabulated function's value at each coordinate
+      CHARACTER(len=NF90_MAX_NAME)                  :: xname = "X"    !! Name of the X coordinate
+      CHARACTER(len=NF90_MAX_NAME)                  :: yname = "Y"    !! Name of the Y coordinate
+      CHARACTER(len=NF90_MAX_NAME)                  :: zname = "Z"    !! Name of the Z coordinate
+      CHARACTER(len=NF90_MAX_NAME)                  :: tname = "T"    !! Name of the T coordinate
+      CHARACTER(len=NF90_MAX_NAME)                  :: dname = "data" !! Name of the data block
+    END TYPE DSET4D
+  
+    TYPE, PUBLIC :: DSET5D
+      !! A 5D data set
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE         :: x              !! X coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE         :: y              !! Y coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE         :: z              !! Z coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE         :: t              !! T coordinate tabulated values
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE         :: w              !! W coordinate tabulated values
+      REAL(kind=8), DIMENSION(:,:,:,:,:), ALLOCATABLE :: data           !! Tabulated function's value at each coordinate
+      CHARACTER(len=NF90_MAX_NAME)                    :: xname = "X"    !! Name of the X coordinate
+      CHARACTER(len=NF90_MAX_NAME)                    :: yname = "Y"    !! Name of the Y coordinate
+      CHARACTER(len=NF90_MAX_NAME)                    :: zname = "Z"    !! Name of the Z coordinate
+      CHARACTER(len=NF90_MAX_NAME)                    :: tname = "T"    !! Name of the T coordinate
+      CHARACTER(len=NF90_MAX_NAME)                    :: wname = "W"    !! Name of the W coordinate
+      CHARACTER(len=NF90_MAX_NAME)                    :: dname = "data" !! Name of the data block
+    END TYPE DSET5D
+  
+    CONTAINS
+  
+  
+    FUNCTION is_in_1d(set,x) RESULT(ret)
+      !! Check if point is in the 1D data set
+      TYPE(DSET1D), INTENT(in) :: set !! Dataset object to search in
+      REAL(kind=8), INTENT(in) :: x   !! coordinate of the point to check
+      LOGICAL :: ret                   !! .true. if the point is in the data set, .false. otherwise
+      REAL(kind=8) :: l,u
+      ret=.true.
+      l  = set%x(1) ; u= set%x(size(set%x))
+      IF ((x>=l.EQV.x<=u).OR.(x<=l.EQV.x>=u)) ret=.false.
+      RETURN
+    END FUNCTION is_in_1d
+  
+    FUNCTION is_in_2d(set,x,y) RESULT(ret)
+      !! Check if point is in the 2D data set
+      TYPE(DSET2D), INTENT(in) :: set !! Dataset object to search in
+      REAL(kind=8), INTENT(in) :: x   !! X coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: y   !! Y coordinate of the point to check
+      LOGICAL :: ret                   !! .true. if the point is in the data set, .false. otherwise
+      REAL(kind=8) :: l,u
+      ret=.false.
+      l  = set%x(1) ; u= set%x(size(set%x))
+      IF ((x>l.AND.x>u).OR.(x<l.AND.x<u)) RETURN
+      IF ((x>l.AND.x>u).OR.(x<l.AND.x<u)) RETURN 
+      l  = set%y(1) ; u= set%y(size(set%y))
+      IF ((y>l.AND.y>u).OR.(y<l.AND.y<u)) RETURN
+      ret=.true.
+      RETURN
+    END FUNCTION is_in_2d
+  
+    FUNCTION is_in_3d(set,x,y,z) RESULT(ret)
+      !! Check if point is in the 3D data set
+      TYPE(DSET3D), INTENT(in) :: set !! Dataset object to search in
+      REAL(kind=8), INTENT(in) :: x   !! X coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: y   !! Y coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: z   !! Z coordinate of the point to check
+      LOGICAL :: ret                   !! .true. if the point is in the data set, .false. otherwise
+      REAL(kind=8) :: l,u
+      ret=.false.
+      l  = set%x(1) ; u= set%x(size(set%x))
+      IF ((x>l.AND.x>u).OR.(x<l.AND.x<u)) RETURN 
+      l  = set%y(1) ; u= set%y(size(set%y))
+      IF ((y>l.AND.y>u).OR.(y<l.AND.y<u)) RETURN
+      l  = set%z(1) ; u= set%z(size(set%z))
+      IF ((z>l.AND.z>u).OR.(z<l.AND.z<u)) RETURN
+      ret=.true.
+      RETURN
+    END FUNCTION is_in_3d
+  
+    FUNCTION is_in_4d(set,x,y,z,t) RESULT(ret)
+      !! Check if point is in the 4D data set
+      TYPE(DSET4D), INTENT(in) :: set !! Dataset object to search in
+      REAL(kind=8), INTENT(in) :: x   !! X coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: y   !! Y coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: z   !! Z coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: t   !! T coordinate of the point to check
+      LOGICAL :: ret                   !! .true. if the point is in the data set, .false. otherwise
+      REAL(kind=8) :: l,u
+      ret=.false.
+      l  = set%x(1) ; u= set%x(size(set%x))
+      IF ((x>l.AND.x>u).OR.(x<l.AND.x<u)) RETURN 
+      l  = set%y(1) ; u= set%y(size(set%y))
+      IF ((y>l.AND.y>u).OR.(y<l.AND.y<u)) RETURN
+      l  = set%z(1) ; u= set%z(size(set%z))
+      IF ((z>l.AND.z>u).OR.(z<l.AND.z<u)) RETURN
+      l  = set%t(1) ; u= set%t(size(set%t))
+      IF ((t>l.AND.t>u).OR.(t<l.AND.t<u)) RETURN
+      ret=.true.
+      RETURN
+    END FUNCTION is_in_4d
+  
+    FUNCTION is_in_5d(set,x,y,z,t,w) RESULT(ret)
+      !! Check if point is in the 4D data set
+      TYPE(DSET5D), INTENT(in) :: set !! Dataset object to search in
+      REAL(kind=8), INTENT(in) :: x   !! X coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: y   !! Y coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: z   !! Z coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: t   !! T coordinate of the point to check
+      REAL(kind=8), INTENT(in) :: w   !! W coordinate of the point to check
+      LOGICAL :: ret                   !! .true. if the point is in the data set, .false. otherwise
+      REAL(kind=8) :: l,u
+      ret=.false.
+      l  = set%x(1) ; u= set%x(size(set%x))
+      IF ((x>l.AND.x>u).OR.(x<l.AND.x<u)) RETURN 
+      l  = set%y(1) ; u= set%y(size(set%y))
+      IF ((y>l.AND.y>u).OR.(y<l.AND.y<u)) RETURN
+      l  = set%z(1) ; u= set%z(size(set%z))
+      IF ((z>l.AND.z>u).OR.(z<l.AND.z<u)) RETURN
+      l  = set%t(1) ; u= set%t(size(set%t))
+      IF ((t>l.AND.t>u).OR.(t<l.AND.t<u)) RETURN
+      l  = set%w(1) ; u= set%w(size(set%w))
+      IF ((w>l.AND.w>u).OR.(w<l.AND.w<u)) RETURN
+      ret=.true.
+      RETURN
+    END FUNCTION is_in_5d
+  
+    FUNCTION ascii_header(path,ds,dp) RESULT(ret)
+      !! Read ASCII file header
+      !! 
+      !! The method assumes the header is on two lines :
+      !!
+      !! - the first line must contain a single value which is the number of
+      !!   dimensions.
+      !! - the second must contain N values with the size of each dimensions (N
+      !!   referring to the first line number).
+      !!
+      !! The file remains open in the logical unit 666 unless some error occured. 
+      CHARACTER(len=*), INTENT(in)       :: path !! Path of the ASCII file to read
+      INTEGER, INTENT(out), DIMENSION(:) :: ds   !! Size of each dimensions
+      INTEGER, INTENT(out), DIMENSION(:) :: dp   !! Product of each lower dimension size (1 for 1st)
+      LOGICAL :: ret                             !! .true. if no error occured, .false. otherwise
+      INTEGER           :: nd,i,e
+      CHARACTER(len=15) :: i2s
+      INQUIRE(666,OPENED=ret)
+      IF (ret) THEN
+        WRITE(*,*) 'ERROR: LUN 666 already used...'
+        ret = .false. ; RETURN
+      ENDIF
+      ret = .false.
+      OPEN(666,file=TRIM(path))
+      READ(666,*) nd
+      IF (nd /= SIZE(ds)) THEN
+        WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+        WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be "//TRIM(i2s)//"D"
+        RETURN
+      ENDIF
+      READ(666,*,iostat=e) ds
+      IF (e /= 0) THEN
+        WRITE(*,'(a)') 'ERROR: Cannot get dimensions size'
+        CLOSE(666) ; RETURN
+      ENDIF
+      dp(1) = 1
+      DO i=2,nd
+        dp(i)=PRODUCT(ds(:i-1))
+      ENDDO
+      ret = .true.
+    END FUNCTION ascii_header
+  
+    FUNCTION nc_get_dim_by_name(fid,dn,values,ds,did) RESULT(ret)
+      !! Get informations about a dimension.
+      !!
+      !! The method gets the values and size of a given dimension.
+      !!
+      !! Errors occur if:
+      !!
+      !! - the given name is not a dimension name.
+      !! - the size of the dimension is less than 2 (the method assumes dimension has an afferent variable with values).
+      !! - the method can not retrieve the values of the afferent variable.
+      INTEGER, INTENT(in)                                  :: fid
+        !! Id of the NetCDF file
+      CHARACTER(len=NF90_MAX_NAME), INTENT(in)             :: dn
+        !! Name of the dimension.
+      REAL(kind=8), INTENT(out), DIMENSION(:), ALLOCATABLE :: values
+        !! Values of the dimension
+      INTEGER, INTENT(out)                                 :: ds 
+        !! Size of __values__
+      INTEGER, INTENT(out)                                 :: did
+        !! Id of the NetCDF dimension 
+      LOGICAL :: ret
+        !! .true. if no error(s) occured, .false. otherwise
+      INTEGER                      :: vid,err
+      CHARACTER(len=15)            :: i2s
+      ret = .false.
+      ! --- Get dimension informations
+      err = NF90_INQ_DIMID(fid,dn,did)
+      IF (err /= NF90_NOERR) RETURN
+      err = NF90_INQUIRE_DIMENSION(fid,did,len=ds)
+      IF (err /= NF90_NOERR) RETURN
+      IF (ds < 2) RETURN
+      err = NF90_INQ_VARID(fid,TRIM(dn),vid)
+      IF (err /= NF90_NOERR) RETURN
+      ALLOCATE(values(ds))
+      err = NF90_GET_VAR(fid,vid,values)
+      IF (err /= NF90_NOERR) RETURN
+      ret = .true.
+    END FUNCTION nc_get_dim_by_name
+  
+    FUNCTION nc_get_dim_by_id(fid,did,values,ds,dn) RESULT(ret)
+      !! Get informations about a dimension.
+      !!
+      !! The method gets the values and size of a given dimension.
+      !!
+      !! Errors occur if:
+      !!
+      !! - the given id is not a dimension identifier.
+      !! - the size of the dimension is less than 2 (the method assumes dimension has an afferent variable with values).
+      !! - the method can not retrieve the values of the afferent variable.
+      INTEGER, INTENT(in)                                  :: fid
+        !! Id of the NetCDF file
+      INTEGER, INTENT(in)                                  :: did
+        !! Id of the NetCDF dimension 
+      REAL(kind=8), INTENT(out), DIMENSION(:), ALLOCATABLE :: values
+        !! Values of the dimension
+      INTEGER, INTENT(out)                                 :: ds 
+        !! Size of __values__
+      CHARACTER(len=NF90_MAX_NAME), INTENT(out)            :: dn
+        !! Name of the dimension.
+      LOGICAL :: ret
+        !! .true. if no error(s) occured, .false. otherwise
+      INTEGER                      :: vid,err
+      CHARACTER(len=15)            :: i2s
+      ret = .false.
+      ! --- Get dimension informations
+      IF (NF90_INQUIRE_DIMENSION(fid,did,dn,ds) /= NF90_NOERR) RETURN
+      IF (ds < 2) RETURN
+      IF (NF90_INQ_VARID(fid,TRIM(dn),vid) /= NF90_NOERR) RETURN
+      ALLOCATE(values(ds))
+      IF (NF90_GET_VAR(fid,vid,values) /= NF90_NOERR) RETURN
+      ret = .true.
+    END FUNCTION nc_get_dim_by_id
+  
+    FUNCTION get_nc3_info(path,variable,ncid,vid,dimids,verbose) RESULT(ret)
+      !! Get variable informations from NetCDF file
+      !! 
+      !! The method attempts to read the given NetCDF file header and retrieves
+      !! variable's informations from it:
+      !!
+      !! - parent file ID
+      !! - variable ID
+      !! - variable's dimensions ID
+      !! - variable's dimensions sizes
+      !!
+      !! The method always opens and closes the file. If the file cannot be opened,
+      !! __ncid__ is set to -1.
+      CHARACTER(len=*), INTENT(in)                    :: path
+        !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in)                    :: variable
+        !! Name of the NetCDF variable to extract 
+      INTEGER, INTENT(out)                            :: ncid
+        !! NetCDF file ID 
+      INTEGER, INTENT(out)                            :: vid
+        !! NetCDF Variable ID
+      INTEGER, INTENT(out), DIMENSION(:), ALLOCATABLE :: dimids
+        !! Id of the variable's dimensions. __dimids__ is not allocated on error.
+      LOGICAL, INTENT(in), OPTIONAL                   :: verbose
+        !! True to print out message on error (default to False).
+      LOGICAL :: ret
+        !! .true. if no errors occured, .false. otherwise
+      INTEGER :: fid,ty,nc,err
+      LOGICAL :: zlog
+      zlog = .false. ; IF (PRESENT(verbose)) zlog = verbose
+      ret = .false.
+      ncid = -1
+      ! Opens file
+      IF (NF90_OPEN(TRIM(path),NF90_NOWRITE,fid) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'ERROR: Cannot open '//trim(path)
+        RETURN
+      ENDIF
+      ncid = fid
+      ! Searches for variable
+      IF (NF90_INQ_VARID(ncid,TRIM(variable),vid) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'Cannot find '//TRIM(variable)//' in '//trim(path)
+        nc = NF90_CLOSE(fid)
+        RETURN
+      ENDIF
+      ! Get variable infos
+      ! 1st call to get type and number of dimensions)
+      IF (NF90_INQUIRE_VARIABLE(ncid,vid,xtype=ty,ndims=nc) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'Cannot access to '//TRIM(variable)//' informations'
+        nc = NF90_CLOSE(fid)
+        RETURN
+      ELSE
+        ! Checks type
+        IF (ty == NF90_CHAR) THEN
+          IF (zlog) WRITE(*,'(a)') 'Inconsistent variable type (should be numeric)'
+          nc = NF90_CLOSE(fid)
+          RETURN
+        ENDIF
+        ALLOCATE(dimids(nc))
+      ENDIF
+      ! Gets variable's dimensions informations
+      ! first get dimensions id
+      IF (NF90_INQUIRE_VARIABLE(ncid,vid,dimids=dimids) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'Cannot access to '//TRIM(variable)//' informations'
+        nc = NF90_CLOSE(fid)
+        DEALLOCATE(dimids)
+        RETURN
+      ENDIF
+      nc = NF90_CLOSE(fid)
+      ret = .true. 
+    END FUNCTION get_nc3_info
+  
+#if HAVE_NC4_FTN 
+    FUNCTION get_nc4_info(path,variable,group,ncid,vid,dimids,verbose) RESULT(ret)
+      !! Get variable informations from NetCDF4 file
+      !!
+      !! The method attempts to read the given NetCDF file header and retrieves
+      !! variable's informations from it :
+      !!
+      !! - parent group/file ID
+      !! - variable ID
+      !! - variable's dimensions ID
+      !! - variable's dimensions sizes
+      !!
+      !! The method always opens and closes the file. If the file cannot be opened,
+      !! __ncid__ is set to -1.
+      CHARACTER(len=*), INTENT(in)                    :: path
+        !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in)                    :: variable
+        !! Name of the NetCDF variable to extract 
+      CHARACTER(len=*), INTENT(in)                    :: group
+        !! Fullname of the variable's parent group (should be set to empty string for root group)
+      INTEGER, INTENT(out)                            :: ncid
+        !! NetCDF group ID 
+      INTEGER, INTENT(out)                            :: vid
+        !! NetCDF Variable ID
+      INTEGER, INTENT(out), DIMENSION(:), ALLOCATABLE :: dimids
+        !! Id of the variable's dimensions. On error, __dimids__ is not allocated. 
+      LOGICAL, INTENT(in), OPTIONAL                   :: verbose
+        !! True to print out message on error (default to False).
+      LOGICAL :: ret
+        !! .true. if no errors occured, .false. otherwise
+      INTEGER :: fid,ty,nc,err
+      LOGICAL :: zlog
+      zlog = .false. ; IF (PRESENT(verbose)) zlog = verbose
+      ret = .false.
+      ncid = -1
+      ! Opens file
+      IF (NF90_OPEN(TRIM(path),NF90_NOWRITE,fid) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'ERROR: Cannot open '//trim(path)
+        RETURN
+      ENDIF
+      ! Searches for group based on its fullname
+      IF (LEN_TRIM(group) == 0) THEN
+        nc = NF90_NOERR ; ncid = fid
+      ELSE
+        nc = NF90_INQ_GRP_FULL_NCID(fid,TRIM(group),ncid)
+      ENDIF
+      SELECT CASE(nc)
+        ! NF90_ENOGRP is missing from Netcdf-Fortran4.2 : its value=-125
+        CASE(-125)
+          IF (zlog) WRITE(*,'(a)') TRIM(group)//' does not exist in '//TRIM(path)
+          nc = NF90_CLOSE(fid) ; RETURN
+        CASE(NF90_ENOTNC4,NF90_ESTRICTNC3)
+          IF (zlog) WRITE(*,'(a)') TRIM(path)//' is not a NetCDF-4 file with "group" feature'
+          nc = NF90_CLOSE(fid) ; RETURN
+        CASE(NF90_EHDFERR)
+          IF (zlog) WRITE(*,'(a)') "Too bad, an HDF5 error has been reported..."
+          nc = NF90_CLOSE(fid) ; RETURN
+      END SELECT
+      ! Searches for variable
+      IF (NF90_INQ_VARID(ncid,TRIM(variable),vid) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'Cannot find '//TRIM(variable)//' in '//trim(path)
+        nc = NF90_CLOSE(fid)
+        RETURN
+      ENDIF
+      ! Get variable infos
+      ! 1st call to get type and number of dimensions)
+      IF (NF90_INQUIRE_VARIABLE(ncid,vid,xtype=ty,ndims=nc) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'Cannot access to '//TRIM(variable)//' informations'
+        nc = NF90_CLOSE(fid)
+        RETURN
+      ELSE
+        ! Checks type
+        IF (ty == NF90_CHAR) THEN
+          IF (zlog) WRITE(*,'(a)') 'Inconsistent variable type (should be numeric)'
+          nc = NF90_CLOSE(fid)
+          RETURN
+        ENDIF
+        ALLOCATE(dimids(nc))
+      ENDIF
+      ! Gets variable's dimensions informations
+      ! first get dimensions id
+      IF (NF90_INQUIRE_VARIABLE(ncid,vid,dimids=dimids) /= NF90_NOERR) THEN
+        IF (zlog) WRITE(*,'(a)') 'Cannot access to '//TRIM(variable)//' informations'
+        nc = NF90_CLOSE(fid)
+        DEALLOCATE(dimids)
+        RETURN
+      ENDIF
+      nc = NF90_CLOSE(fid)
+      ret = .true. 
+    END FUNCTION get_nc4_info
+#endif
+  
+    !-------------------------
+    ! NetCDF data file readers
+    !-------------------------
+  
+    FUNCTION ncdf_rd_1d(path,variable,set) RESULT(ret)
+      !! Read a 1D data set from a NetCDF file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET1D), INTENT(out)    :: set      !! Output dataset object
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=NF90_MAX_NAME)       :: dn
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,variable,fi,vi,di)) RETURN
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 1) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get coordinates values 
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf_rd_1d
+  
+    FUNCTION ncdf_rd_2d(path,variable,set) RESULT(ret)
+      !! Read a 2D data set from a NetCDF file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET2D), INTENT(out)    :: set      !! Output dataset object
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,variable,fi,vi,di)) RETURN 
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 2) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf_rd_2d
+  
+    FUNCTION ncdf_rd_3d(path,variable,set) RESULT(ret)
+      !! Read a 3D data set from a NetCDF file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET3D), INTENT(out)    :: set      !! Output dataset object
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,variable,fi,vi,di)) RETURN 
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 3) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Z coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(3),set%z,ds(3),set%zname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2),ds(3)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf_rd_3d
+  
+    FUNCTION ncdf_rd_4d(path,variable,set) RESULT(ret)
+      !! Read a 4D data set from a NetCDF file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET4D), INTENT(out)    :: set      !! Output dataset object
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,variable,fi,vi,di)) RETURN 
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 4) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Z coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(3),set%z,ds(3),set%zname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ T coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(4),set%t,ds(4),set%tname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2),ds(3),ds(4)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf_rd_4d
+  
+    FUNCTION ncdf_rd_5d(path,variable,set) RESULT(ret)
+      !! Read a 5D data set from a NetCDF file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET5D), INTENT(out)    :: set      !! Output dataset object
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,variable,fi,vi,di)) RETURN
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 5) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Z coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(3),set%z,ds(3),set%zname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ T coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(4),set%t,ds(4),set%tname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ W coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(5),set%w,ds(5),set%wname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2),ds(3),ds(4),ds(5)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf_rd_5d
+  
+    ! NetCDF4 with groups !
+#if HAVE_NC4_FTN 
+  
+    FUNCTION ncdf4_rd_1d(path,group,variable,set) RESULT(ret)
+      !! Read a 1D data set from a NetCDF4 file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: group    !! Parent group of the variable (empty for root)
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET1D), INTENT(out)    :: set      !! Output dataset
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=NF90_MAX_NAME)       :: dn
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,group,variable,fi,vi,di)) RETURN 
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 1) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get coordinates values 
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf4_rd_1d
+  
+    FUNCTION ncdf4_rd_2d(path,group,variable,set) RESULT(ret)
+      !! Read a 2D data set from a NetCDF4 file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: group    !! Parent group of the variable (empty for root)
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET2D), INTENT(out)    :: set      !! Output dataset
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,group,variable,fi,vi,di)) rETURN
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 2) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf4_rd_2d
+  
+    FUNCTION ncdf4_rd_3d(path,group,variable,set) RESULT(ret)
+      !! Read a 3D data set from a NetCDF4 file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: group    !! Parent group of the variable (empty for root)
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET3D), INTENT(out)    :: set      !! Output dataset
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,group,variable,fi,vi,di)) RETURN 
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 3) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Z coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(3),set%z,ds(3),set%zname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2),ds(3)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf4_rd_3d
+  
+    FUNCTION ncdf4_rd_4d(path,group,variable,set) RESULT(ret)
+      !! Read a 4D data set from a NetCDF4 file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: group    !! Parent group of the variable (empty for root)
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET4D), INTENT(out)    :: set      !! Output dataset
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,group,variable,fi,vi,di)) RETURN 
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 4) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Z coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(3),set%z,ds(3),set%zname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ T coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(4),set%t,ds(4),set%tname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2),ds(3),ds(4)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf4_rd_4d
+  
+    FUNCTION ncdf4_rd_5d(path,group,variable,set) RESULT(ret)
+      !! Read a 5D data set from a NetCDF4 file
+      CHARACTER(len=*), INTENT(in) :: path     !! Path of the NetCDF file
+      CHARACTER(len=*), INTENT(in) :: group    !! Parent group of the variable (empty for root)
+      CHARACTER(len=*), INTENT(in) :: variable !! Name of the NetCDF variable to extract 
+      TYPE(DSET5D), INTENT(out)    :: set      !! Output dataset
+      LOGICAL :: ret                           !! .true. if no errors occured, .false. otherwise
+      INTEGER                            :: fi,vi,nd,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE :: di,ds
+      CHARACTER(len=15)                  :: i2s
+      ret = .false.
+      ! --- Reset the data set
+      CALL clear_dset(set)
+      ! --- Check NetCDF file info
+      IF (.NOT.get_nc_info(path,group,variable,fi,vi,di)) RETURN
+      iret = NF90_OPEN(path,NF90_NOWRITE,fi)
+      ! --- Check dimension size
+      nd = SIZE(di)
+      IF (nd /= 5) THEN
+        IF (debug) THEN
+          WRITE(i2s,*) nd ; i2s=ADJUSTL(i2s)
+          WRITE(*,'(a)') "ERROR: Incompatible size, DSET should be"//TRIM(i2s)//"D"
+        ENDIF
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Get dimensions informations
+      ALLOCATE(ds(nd))
+      ! ------ X coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(1),set%x,ds(1),set%xname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Y coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(2),set%y,ds(2),set%yname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ Z coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(3),set%z,ds(3),set%zname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ T coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(4),set%t,ds(4),set%tname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! ------ W coordinate
+      IF (.NOT.nc_get_dim_by_id(fi,di(5),set%w,ds(5),set%wname)) THEN
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+        CALL clear_dset(set) ; RETURN
+      ENDIF
+      ! --- Read data
+      ALLOCATE(set%data(ds(1),ds(2),ds(3),ds(4),ds(5)))
+      IF (NF90_GET_VAR(fi,vi,set%data) /= NF90_NOERR) THEN
+        IF (debug) WRITE(*,'(a)') "ERROR:"//TRIM(variable)//": Cannot get values"
+        nd = NF90_CLOSE(fi) ; CALL clear_dset(set) ; RETURN
+      ENDIF
+      nd = NF90_CLOSE(fi)
+      set%dname = variable
+      ret = .true.
+      RETURN
+    END FUNCTION ncdf4_rd_5d
+#endif
+  
+    !------------------------
+    ! ASCII data file readers
+    !------------------------
+  
+    FUNCTION ascii_rd_1d(path,set) RESULT(ret)
+      !! Read a 1D data set from a ASCII file
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the ASCII file to read
+      TYPE(dset1d), INTENT(out)    :: set  !! output 1D dataset
+      LOGICAL :: ret                       !! .true. if no error occured, .false. otherwise
+      INTEGER                    :: i,e
+      REAL(kind=8), DIMENSION(2) :: vl
+      INTEGER, DIMENSION(1)      :: cc,ds,dp
+      ret = .false.
+      CALL clear_dset(set)
+      IF (.NOT.ascii_header(path,ds,dp)) RETURN
+      ALLOCATE(set%x(ds(1)), &
+               set%data(ds(1)))
+      cc(:) = 1
+      DO i=1,ds(1)
+        READ(666,*) vl(:)
+        set%x(cc(1)) = vl(1)
+        set%data(cc(1)) = vl(2)
+        cc(1) = cc(1) + 1
+      ENDDO
+      READ(666,*,iostat=e) vl(1) 
+      IF (e == 0) THEN
+        IF (debug) WRITE(*,*) 'ERROR: Extra value found...'
+        ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+      ENDIF
+      ret = .true.
+      CLOSE(666)
+      RETURN
+    END FUNCTION ascii_rd_1d
+  
+    FUNCTION ascii_rd_2d(path,set) RESULT(ret)
+      !! Read a 2D data set from a ASCII file
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the ASCII file to read
+      TYPE(dset2d), INTENT(out)    :: set  !! output 2D dataset
+      LOGICAL :: ret                       !! .true. if no error occured, .false. otherwise
+      INTEGER                    :: i,e
+      REAL(kind=8), DIMENSION(3) :: vl 
+      INTEGER, DIMENSION(2)      :: cc,ds,dp
+      ret = .false.
+      CALL clear_dset(set)
+      IF (.NOT.ascii_header(path,ds,dp)) RETURN
+      ALLOCATE(set%x(ds(1)),set%y(ds(2)), &
+               set%data(ds(1),ds(2)))
+      cc(:) = 1
+      DO i=1,PRODUCT(ds) 
+        READ(666,*,iostat=e) vl     ! Reads the line
+        IF (e /= 0) THEN
+          IF (debug) WRITE(*,'(a)') 'ERROR: Malformed file, line: ',i+2
+          ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+        ENDIF
+        ! --- X coordinate
+        set%x(cc(1)) = vl(1)
+        ! --- Y coordinate
+        set%y(cc(2)) = vl(2)
+        ! --- Data
+        set%data(cc(1),cc(2)) = vl(3)
+        ! - Update counters
+        IF (mod(i,dp(1))==0) cc(1) = cc(1)+1 ; IF (cc(1) > ds(1)) cc(1)=1
+        IF (mod(i,dp(2))==0) cc(2) = cc(2)+1 ; IF (cc(2) > ds(2)) cc(2)=1
+      ENDDO
+      READ(666,*,iostat=e) vl(1)
+      IF (e == 0) THEN
+        IF (debug) WRITE(*,'(a)') 'ERROR: Extra value found'
+        ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+      ENDIF
+      ret = .true.
+      CLOSE(666)
+      RETURN
+    END FUNCTION ascii_rd_2d
+  
+    FUNCTION ascii_rd_3d(path,set) RESULT(ret)
+      !! Read a 3D data set from a ASCII file
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the ASCII file to read
+      TYPE(dset3d), INTENT(out)    :: set  !! output 3D dataset
+      LOGICAL :: ret                       !! .true. if no error occured, .false. otherwise
+      INTEGER                    :: i,e
+      REAL(kind=8), DIMENSION(4) :: vl 
+      INTEGER, DIMENSION(3)      :: cc,ds,dp
+      ret = .false.
+      CALL clear_dset(set)
+      IF (.NOT.ascii_header(path,ds,dp)) RETURN
+      ALLOCATE(set%x(ds(1)),set%y(ds(2)),set%z(ds(3)), &
+               set%data(ds(1),ds(2),ds(3)))
+      cc(:) = 1
+      DO i=1,PRODUCT(ds) 
+        READ(666,*,iostat=e) vl     ! Reads the line
+        IF (e /= 0) THEN
+          IF (debug) WRITE(*,'(a)') 'ERROR: Malformed file, line: ',i+2
+          ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+        ENDIF
+        ! --- X coordinate
+        set%x(cc(1)) = vl(1)
+        ! --- Y coordinate
+        set%y(cc(2)) = vl(2)
+        ! --- Z coordinate
+        set%z(cc(3)) = vl(3)
+        ! --- Data
+        set%data(cc(1),cc(2),cc(3)) = vl(4)
+        ! - Update counters
+        IF (mod(i,dp(1))==0) cc(1) = cc(1)+1 ; IF (cc(1) > ds(1)) cc(1)=1
+        IF (mod(i,dp(2))==0) cc(2) = cc(2)+1 ; IF (cc(2) > ds(2)) cc(2)=1
+        IF (mod(i,dp(3))==0) cc(3) = cc(3)+1 ; IF (cc(3) > ds(3)) cc(3)=1
+      ENDDO
+      READ(666,*,iostat=e) vl(1)
+      IF (e == 0) THEN
+        IF (debug) WRITE(*,'(a)') 'ERROR: Extra value found'
+        ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+      ENDIF
+      ret = .true.
+      CLOSE(666)
+      RETURN
+    END FUNCTION ascii_rd_3d
+  
+    FUNCTION ascii_rd_4d(path,set) RESULT(ret)
+      !! Read a 4D data set from a ASCII file
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the ASCII file to read
+      TYPE(dset4d), INTENT(out)    :: set  !! output 4D dataset
+      LOGICAL :: ret                       !! .true. if no error occured, .false. otherwise
+      INTEGER                    :: i,e
+      REAL(kind=8), DIMENSION(5) :: vl 
+      INTEGER, DIMENSION(4)      :: cc,ds,dp
+      ret = .false.
+      CALL clear_dset(set)
+      IF (.NOT.ascii_header(path,ds,dp)) RETURN
+      ALLOCATE(set%x(ds(1)),set%y(ds(2)),set%z(ds(3)),set%t(ds(4)), &
+               set%data(ds(1),ds(2),ds(3),ds(4)))
+      cc(:) = 1
+      DO i=1,PRODUCT(ds) 
+        READ(666,*,iostat=e) vl     ! Reads the line
+        IF (e /= 0) THEN
+          IF (debug) WRITE(*,'(a)') 'ERROR: Malformed file, line: ',i+2
+          ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+        ENDIF
+        ! --- X coordinate
+        set%x(cc(1)) = vl(1)
+        ! --- Y coordinate
+        set%y(cc(2)) = vl(2)
+        ! --- Z coordinate
+        set%z(cc(3)) = vl(3)
+        ! --- T coordinate
+        set%t(cc(4)) = vl(4)
+        ! --- Data
+        set%data(cc(1),cc(2),cc(3),cc(4)) = vl(5)
+        ! - Update counters
+        IF (mod(i,dp(1))==0) cc(1) = cc(1)+1 ; IF (cc(1) > ds(1)) cc(1)=1
+        IF (mod(i,dp(2))==0) cc(2) = cc(2)+1 ; IF (cc(2) > ds(2)) cc(2)=1
+        IF (mod(i,dp(3))==0) cc(3) = cc(3)+1 ; IF (cc(3) > ds(3)) cc(3)=1
+        IF (mod(i,dp(4))==0) cc(4) = cc(4)+1 ; IF (cc(4) > ds(4)) cc(4)=1
+      ENDDO
+      READ(666,*,iostat=e) vl(1)
+      IF (e == 0) THEN
+        IF (debug) WRITE(*,'(a)') 'ERROR: Extra value found'
+        ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+      ENDIF
+      CLOSE(666)
+      ret = .true.
+      RETURN
+    END FUNCTION ascii_rd_4d
+  
+    FUNCTION ascii_rd_5d(path,set) RESULT(ret)
+      !! Read a 5D data set from a ASCII file
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the ASCII file to read
+      TYPE(dset5d), INTENT(out)    :: set  !! output 5D dataset
+      LOGICAL :: ret                       !! .true. if no error occured, .false. otherwise
+      INTEGER                    :: i,e
+      REAL(kind=8), DIMENSION(6) :: vl 
+      INTEGER, DIMENSION(5)      :: cc,ds,dp
+      ret = .false.
+      CALL clear_dset(set)
+      IF (.NOT.ascii_header(path,ds,dp)) RETURN
+      ALLOCATE(set%x(ds(1)),set%y(ds(2)),set%z(ds(3)),set%t(ds(4)),set%w(ds(5)), &
+               set%data(ds(1),ds(2),ds(3),ds(4),ds(5)))
+      cc(:) = 1
+      DO i=1,PRODUCT(ds) 
+        READ(666,*,iostat=e) vl     ! Reads the line
+        IF (e /= 0) THEN
+          IF (debug) WRITE(*,'(a)') 'ERROR: Malformed file, line: ',i+2
+          ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+        ENDIF
+        ! --- X coordinate
+        set%x(cc(1)) = vl(1)
+        ! --- Y coordinate
+        set%y(cc(2)) = vl(2)
+        ! --- Z coordinate
+        set%z(cc(3)) = vl(3)
+        ! --- T coordinate
+        set%t(cc(4)) = vl(4)
+        ! --- W coordinate
+        set%w(cc(5)) = vl(5)
+        ! --- Data
+        set%data(cc(1),cc(2),cc(3),cc(4),cc(5)) = vl(6)
+        ! - Update counters
+        IF (mod(i,dp(1)) == 0) cc(1)=cc(1)+1 ; IF (cc(1) > ds(1)) cc(1)=1
+        IF (mod(i,dp(2)) == 0) cc(2)=cc(2)+1 ; IF (cc(2) > ds(2)) cc(2)=1
+        IF (mod(i,dp(3)) == 0) cc(3)=cc(3)+1 ; IF (cc(3) > ds(3)) cc(3)=1
+        IF (mod(i,dp(4)) == 0) cc(4)=cc(4)+1 ; IF (cc(4) > ds(4)) cc(4)=1
+        IF (mod(i,dp(5)) == 0) cc(5)=cc(5)+1 ; IF (cc(5) > ds(5)) cc(5)=1
+      ENDDO
+      READ(666,*,iostat=e) vl(1)
+      IF (e == 0) THEN
+        IF (debug) WRITE(*,'(a)') 'ERROR: Extra value found'
+        ret = .false. ; call clear_dset(set) ; CLOSE(666) ; RETURN
+      ENDIF
+      CLOSE(666)
+      ret = .true.
+      RETURN
+    END FUNCTION ascii_rd_5d
+  
+    SUBROUTINE clr_1d_set(set)
+      !! Clear the given 1D data set
+      TYPE(DSET1D), INTENT(inout) :: set !! dataset object to clear
+      IF (ALLOCATED(set%data)) DEALLOCATE(set%data)
+      IF (ALLOCATED(set%x))    DEALLOCATE(set%x)
+      RETURN
+    END SUBROUTINE clr_1d_set
+  
+    SUBROUTINE clr_2d_set(set)
+      !! Clear the given 2D data set
+      TYPE(DSET2D), INTENT(inout) :: set !! dataset object to clear
+      IF (ALLOCATED(set%data)) DEALLOCATE(set%data)
+      IF (ALLOCATED(set%x))    DEALLOCATE(set%x)
+      IF (ALLOCATED(set%y))    DEALLOCATE(set%y)
+      RETURN
+    END SUBROUTINE clr_2d_set
+  
+    SUBROUTINE clr_3d_set(set)
+      !! Clear the given 3D data set
+      TYPE(DSET3D), INTENT(inout) :: set !! dataset object to clear
+      IF (ALLOCATED(set%data)) DEALLOCATE(set%data)
+      IF (ALLOCATED(set%x))    DEALLOCATE(set%x)
+      IF (ALLOCATED(set%y))    DEALLOCATE(set%y)
+      IF (ALLOCATED(set%z))    DEALLOCATE(set%z)
+      RETURN
+    END SUBROUTINE clr_3d_set
+  
+    SUBROUTINE clr_4d_set(set)
+      !! Clear the given 4D data set
+      TYPE(DSET4D), INTENT(inout) :: set !! dataset object to clear
+      IF (ALLOCATED(set%data)) DEALLOCATE(set%data)
+      IF (ALLOCATED(set%x))    DEALLOCATE(set%x)
+      IF (ALLOCATED(set%y))    DEALLOCATE(set%y)
+      IF (ALLOCATED(set%z))    DEALLOCATE(set%z)
+      IF (ALLOCATED(set%t))    DEALLOCATE(set%t)
+      RETURN
+    END SUBROUTINE clr_4d_set
+  
+    SUBROUTINE clr_5d_set(set)
+      !! Clear the given 5D data set
+      TYPE(DSET5D), INTENT(inout) :: set !! dataset object to clear 
+      IF (ALLOCATED(set%data)) DEALLOCATE(set%data)
+      IF (ALLOCATED(set%x))    DEALLOCATE(set%x)
+      IF (ALLOCATED(set%y))    DEALLOCATE(set%y)
+      IF (ALLOCATED(set%z))    DEALLOCATE(set%z)
+      IF (ALLOCATED(set%t))    DEALLOCATE(set%t)
+      IF (ALLOCATED(set%w))    DEALLOCATE(set%w)
+      RETURN
+    END SUBROUTINE clr_5d_set
+  
+    FUNCTION has_d_1d(dset) RESULT(yes)
+      !! Check whether or not the dataset has data.
+      TYPE(DSET1D), INTENT(in)     :: dset !! Dataset to check
+      LOGICAL                      :: yes  !! return status
+      yes =  (ALLOCATED(dset%data).AND. &
+              ALLOCATED(dset%x))
+    END FUNCTION has_d_1d
+  
+    FUNCTION has_d_2d(dset) RESULT(yes)
+      !! Check whether or not the dataset has data.
+      TYPE(DSET2D), INTENT(in)     :: dset !! Dataset to check
+      LOGICAL                      :: yes  !! return status
+      yes =  (ALLOCATED(dset%data).AND. &
+              ALLOCATED(dset%x)   .AND. &   
+              ALLOCATED(dset%y))
+    END FUNCTION has_d_2d
+  
+    FUNCTION has_d_3d(dset) RESULT(yes)
+      !! Check whether or not the dataset has data.
+      TYPE(DSET3D), INTENT(in)     :: dset !! Dataset to check
+      LOGICAL                      :: yes  !! return status
+      yes =  (ALLOCATED(dset%data).AND. &
+              ALLOCATED(dset%x)   .AND. &   
+              ALLOCATED(dset%y)   .AND. &
+              ALLOCATED(dset%z))
+    END FUNCTION has_d_3d
+  
+    FUNCTION has_d_4d(dset) RESULT(yes)
+      !! Check whether or not the dataset has data.
+      TYPE(DSET4D), INTENT(in)     :: dset !! Dataset to check
+      LOGICAL                      :: yes  !! return status
+      yes =  (ALLOCATED(dset%data).AND. &
+              ALLOCATED(dset%x)   .AND. &   
+              ALLOCATED(dset%y)   .AND. &
+              ALLOCATED(dset%z)   .AND. &
+              ALLOCATED(dset%t))
+    END FUNCTION has_d_4d
+  
+    FUNCTION has_d_5d(dset) RESULt(yes)
+      !! Check whether or not the dataset has data.
+      TYPE(DSET5D), INTENT(in)     :: dset !! Dataset to check
+      LOGICAL                      :: yes  !! return status
+      yes =  (ALLOCATED(dset%data).AND. &
+              ALLOCATED(dset%x)   .AND. &   
+              ALLOCATED(dset%y)   .AND. &
+              ALLOCATED(dset%z)   .AND. &
+              ALLOCATED(dset%t)   .AND. &
+              ALLOCATED(dset%w)) 
+    END FUNCTION has_d_5d
+  
+    FUNCTION nc_wr_1d(path,dset) RESULT(ret)
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the netcdf file.
+      TYPE(DSET1D), INTENT(in)     :: dset !! Dataset to write
+      LOGICAL                      :: ret  !! Return status
+      INTEGER                                 :: fi,vi,nd,ds,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE      :: di
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: tv
+      CHARACTER(len=15)                       :: i2s
+      LOGICAL                                 :: hxd,ok
+      INTEGER                                 :: xid,vid 
+      ret = has_data(dset)
+      IF (.NOT.ret) RETURN
+      ret = .false.
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (ok) THEN
+        IF (NF90_OPEN(TRIM(path), NF90_WRITE, fi) /= NF90_NOERR) RETURN
+      ELSE
+        IF (NF90_CREATE(TRIM(path), NF90_NOCLOBBER, fi) /= NF90_NOERR) RETURN
+      ENDIF
+      iret = NF90_CLOSE(fi)
+      ! if variable already exist: get out of here !
+      IF (get_nc_info(path,dset%dname,fi,vi,di)) RETURN
+  
+      iret = NF90_OPEN(path,NF90_WRITE,fi) 
+  
+      hxd = nc_get_dim_by_name(fi,dset%xname,tv,ds,xid) 
+      IF (hxd) THEN
+        IF(.NOT.compare(dset%x,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+  
+      IF (.NOT.hxd) THEN
+        ret = nc_set_dim(fi,dset%xname,dset%x,xid)
+        IF (.NOT.ret) THEN ; iret= NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      ret = .false.
+      iret = nf90_redef(fi)
+      iret = nf90_def_var(fi, TRIM(dset%dname), nf90_double,(/xid/),vid) 
+      IF (iret /= 0) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      iret = nf90_enddef(fi)
+      iret = nf90_put_var(fi,vid,dset%data)
+      ret = (iret == 0)
+      iret=NF90_CLOSE(fi)
+      ret = .true.
+      RETURN
+    END FUNCTION nc_wr_1d
+  
+    FUNCTION nc_wr_2d(path,dset) RESULT(ret)
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the netcdf file.
+      TYPE(DSET2D), INTENT(in)     :: dset !! Dataset to write
+      LOGICAL                      :: ret  !! Return status
+      INTEGER                                 :: fi,vi,nd,ds,iret,nferr
+      INTEGER, DIMENSION(:), ALLOCATABLE      :: di
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: tv
+      CHARACTER(len=15)                       :: i2s
+      LOGICAL                                 :: hxd,hyd,ok
+      INTEGER                                 :: xid,yid,vid 
+      ret = has_data(dset)
+      IF (.NOT.ret) RETURN
+      ret = .false.
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (ok) THEN
+        IF (NF90_OPEN(TRIM(path), NF90_WRITE, fi) /= NF90_NOERR) RETURN
+      ELSE
+        IF (NF90_CREATE(TRIM(path), NF90_NOCLOBBER, fi) /= NF90_NOERR) RETURN
+      ENDIF
+      iret = NF90_CLOSE(fi)
+  
+      ! if variable already exist: get out of here !
+      IF (get_nc_info(path,dset%dname,fi,vi,di)) RETURN
+  
+      iret = NF90_OPEN(path,NF90_WRITE,fi) 
+  
+      hxd = nc_get_dim_by_name(fi,dset%xname,tv,ds,xid) 
+      IF (hxd) THEN
+        IF(.NOT.compare(dset%x,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hyd = nc_get_dim_by_name(fi,dset%yname,tv,ds,yid) 
+      IF (hyd) THEN
+        IF(.NOT.compare(dset%y,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+  
+      IF (.NOT.hxd) THEN
+        ret = nc_set_dim(fi,dset%xname,dset%x,xid)
+        IF (.NOT.ret) THEN ; iret= NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hyd) THEN
+        ret = nc_set_dim(fi,dset%yname,dset%y,yid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      ret = .false.
+      iret = nf90_redef(fi)
+      iret = nf90_def_var(fi, TRIM(dset%dname), nf90_double,(/xid,yid/),vid) 
+      IF (iret /= 0) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      iret = nf90_enddef(fi)
+      iret = nf90_put_var(fi,vid,dset%data)
+      ret = (iret == 0)
+      iret=NF90_CLOSE(fi)
+      ret = .true.
+      RETURN
+    END FUNCTION nc_wr_2d
+  
+    FUNCTION nc_wr_3d(path,dset) RESULT(ret)
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the netcdf file.
+      TYPE(DSET3D), INTENT(in)     :: dset !! Dataset to write
+      LOGICAL                      :: ret  !! Return status
+      INTEGER                                 :: fi,vi,nd,ds,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE      :: di
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: tv
+      CHARACTER(len=15)                       :: i2s
+      LOGICAL                                 :: hxd,hyd,hzd,ok
+      INTEGER                                 :: xid,yid,zid,vid 
+      ret = has_data(dset)
+      IF (.NOT.ret) RETURN
+      ret = .false.
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (ok) THEN
+        IF (NF90_OPEN(TRIM(path), NF90_WRITE, fi) /= NF90_NOERR) RETURN
+      ELSE
+        IF (NF90_CREATE(TRIM(path), NF90_NOCLOBBER, fi) /= NF90_NOERR) RETURN
+      ENDIF
+      iret = NF90_CLOSE(fi)
+  
+      ! if variable already exist: get out of here !
+      IF (get_nc_info(path,dset%dname,fi,vi,di)) RETURN
+  
+      iret = NF90_OPEN(path,NF90_WRITE,fi) 
+  
+      hxd = nc_get_dim_by_name(fi,dset%xname,tv,ds,xid) 
+      IF (hxd) THEN
+        IF(.NOT.compare(dset%x,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hyd = nc_get_dim_by_name(fi,dset%yname,tv,ds,yid) 
+      IF (hyd) THEN
+        IF(.NOT.compare(dset%y,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hzd = nc_get_dim_by_name(fi,dset%zname,tv,ds,zid) 
+      IF (hzd) THEN 
+        IF(.NOT.compare(dset%z,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+  
+      IF (.NOT.hxd) THEN
+        ret = nc_set_dim(fi,dset%xname,dset%x,xid)
+        IF (.NOT.ret) THEN ; iret= NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hyd) THEN
+        ret = nc_set_dim(fi,dset%yname,dset%y,yid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hzd) THEN
+        ret = nc_set_dim(fi,dset%zname,dset%z,zid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      ret = .false.
+      iret = nf90_redef(fi)
+      iret = nf90_def_var(fi, TRIM(dset%dname), nf90_double,(/xid,yid,zid/),vid) 
+      IF (iret /= 0) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      iret = nf90_enddef(fi)
+      iret = nf90_put_var(fi,vid,dset%data)
+      ret = (iret == 0)
+      iret=NF90_CLOSE(fi)
+      ret = .true.
+      RETURN
+    END FUNCTION nc_wr_3d
+  
+    FUNCTION nc_wr_4d(path,dset) RESULT(ret)
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the netcdf file.
+      TYPE(DSET4D), INTENT(in)     :: dset !! Dataset to write
+      LOGICAL                      :: ret  !! Return status
+      INTEGER                                 :: fi,vi,nd,ds,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE      :: di
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: tv
+      CHARACTER(len=15)                       :: i2s
+      LOGICAL                                 :: hxd,hyd,hzd,htd,ok
+      INTEGER                                 :: xid,yid,zid,tid,vid 
+      ret = has_data(dset)
+      IF (.NOT.ret) RETURN
+      ret = .false.
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (ok) THEN
+        IF (NF90_OPEN(TRIM(path), NF90_WRITE, fi) /= NF90_NOERR) RETURN
+      ELSE
+        IF (NF90_CREATE(TRIM(path), NF90_NOCLOBBER, fi) /= NF90_NOERR) RETURN
+      ENDIF
+      iret = NF90_CLOSE(fi)
+  
+      ! if variable already exist: get out of here !
+      IF (get_nc_info(path,dset%dname,fi,vi,di)) RETURN
+  
+      iret = NF90_OPEN(path,NF90_WRITE,fi) 
+  
+      hxd = nc_get_dim_by_name(fi,dset%xname,tv,ds,xid) 
+      IF (hxd) THEN
+        IF(.NOT.compare(dset%x,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hyd = nc_get_dim_by_name(fi,dset%yname,tv,ds,yid) 
+      IF (hyd) THEN
+        IF(.NOT.compare(dset%y,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hzd = nc_get_dim_by_name(fi,dset%zname,tv,ds,zid) 
+      IF (hzd) THEN 
+        IF(.NOT.compare(dset%z,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      htd = nc_get_dim_by_name(fi,dset%tname,tv,ds,tid) 
+      IF (htd) THEN
+        IF(.NOT.compare(dset%t,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+  
+      IF (.NOT.hxd) THEN
+        ret = nc_set_dim(fi,dset%xname,dset%x,xid)
+        IF (.NOT.ret) THEN ; iret= NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hyd) THEN
+        ret = nc_set_dim(fi,dset%yname,dset%y,yid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hzd) THEN
+        ret = nc_set_dim(fi,dset%zname,dset%z,zid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.htd) THEN
+        ret = nc_set_dim(fi,dset%tname,dset%t,tid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      ret = .false.
+      iret = nf90_redef(fi)
+      iret = nf90_def_var(fi, TRIM(dset%dname), nf90_double,(/xid,yid,zid,tid/),vid) 
+      IF (iret /= 0) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      iret = nf90_enddef(fi)
+      iret = nf90_put_var(fi,vid,dset%data)
+      ret = (iret == 0)
+      iret=NF90_CLOSE(fi)
+      ret = .true.
+      RETURN
+    END FUNCTION nc_wr_4d
+  
+    FUNCTION nc_wr_5d(path,dset) RESULT(ret)
+      CHARACTER(len=*), INTENT(in) :: path !! Path of the netcdf file.
+      TYPE(DSET5D), INTENT(in)     :: dset !! Dataset to write
+      LOGICAL                      :: ret  !! Return status
+      INTEGER                                 :: fi,vi,nd,ds,iret
+      INTEGER, DIMENSION(:), ALLOCATABLE      :: di
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE :: tv
+      CHARACTER(len=15)                       :: i2s
+      LOGICAL                                 :: hxd,hyd,hzd,htd,hwd,ok 
+      INTEGER                                 :: xid,yid,zid,tid,wid,vid 
+      ret = has_data(dset)
+      IF (.NOT.ret) RETURN
+      ret = .false.
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (ok) THEN
+        IF (NF90_OPEN(TRIM(path), NF90_WRITE, fi) /= NF90_NOERR) RETURN
+      ELSE
+        IF (NF90_CREATE(TRIM(path), NF90_NOCLOBBER, fi) /= NF90_NOERR) RETURN
+      ENDIF
+      iret = NF90_CLOSE(fi)
+  
+      ! if variable already exist: get out of here !
+      IF (get_nc_info(path,dset%dname,fi,vi,di)) RETURN
+  
+      iret = NF90_OPEN(path,NF90_WRITE,fi) 
+  
+      hxd = nc_get_dim_by_name(fi,dset%xname,tv,ds,xid) 
+      IF (hxd) THEN
+        IF(.NOT.compare(dset%x,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hyd = nc_get_dim_by_name(fi,dset%yname,tv,ds,yid) 
+      IF (hyd) THEN
+        IF(.NOT.compare(dset%y,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hzd = nc_get_dim_by_name(fi,dset%zname,tv,ds,zid) 
+      IF (hzd) THEN 
+        IF(.NOT.compare(dset%z,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      htd = nc_get_dim_by_name(fi,dset%tname,tv,ds,tid) 
+      IF (htd) THEN
+        IF(.NOT.compare(dset%t,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+      hwd = nc_get_dim_by_name(fi,dset%wname,tv,ds,wid) 
+      IF (hwd) THEN 
+        IF (.NOT.compare(dset%w,tv)) THEN ; ret = .false. ; RETURN ; ENDIF
+      ENDIF
+  
+      IF (.NOT.hxd) THEN
+        ret = nc_set_dim(fi,dset%xname,dset%x,xid)
+        IF (.NOT.ret) THEN ; iret= NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hyd) THEN
+        ret = nc_set_dim(fi,dset%yname,dset%y,yid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hzd) THEN
+        ret = nc_set_dim(fi,dset%zname,dset%z,zid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.htd) THEN
+        ret = nc_set_dim(fi,dset%tname,dset%t,tid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      IF (.NOT.hwd) THEN
+        ret = nc_set_dim(fi,dset%wname,dset%w,wid)
+        IF (.NOT.ret) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      ENDIF
+      ret = .false.
+      iret = nf90_redef(fi)
+      iret = nf90_def_var(fi, TRIM(dset%dname), nf90_double,(/xid,yid,zid,tid,wid/),vid) 
+      IF (iret /= 0) THEN ; iret = NF90_CLOSE(fi) ; RETURN ; ENDIF
+      iret = nf90_enddef(fi)
+      iret = nf90_put_var(fi,vid,dset%data)
+      ret = (iret == 0)
+      iret=NF90_CLOSE(fi)
+      ret = .true.
+      RETURN
+    END FUNCTION nc_wr_5d
+  
+    FUNCTION nc_set_dim(fid,name,values,dimid) RESULT(ret)
+      !! Set a new dimension (and its associated values) in a NetCDF file.
+      INTEGER, INTENT(in)                    :: fid    !! Netcdf file id.
+      CHARACTER(len=*), INTENT(in)           :: name   !! Name of the dimension to set.
+      REAL(kind=8), DIMENSION(:), INTENT(in) :: values !! Associated values.
+      INTEGER, INTENT(out)                   :: dimid  !! Output dimension id.
+      LOGICAL                                :: ret    !! Return status
+      INTEGER :: iret,vid
+      ret = .false.
+      iret = nf90_redef(fid)
+      iret = nf90_def_dim(fid, TRIM(name), size(values), dimid)
+      WRITE(*,'(a)') "nc_set_dim: "//TRIM(name)//" --> "//TRIM(nf90_strerror(iret))
+      IF (iret /=0) RETURN
+      iret = nf90_def_var(fid, TRIM(name), nf90_double,(/dimid/),vid) 
+      IF (iret /=0) RETURN
+      iret = nf90_enddef(fid)
+      iret = nf90_put_var(fid, vid,values)
+      ret = .true.
+    END FUNCTION nc_set_dim
+  
+    FUNCTION compare(vec1,vec2,tol) RESULT(ret)
+      !! Compare two vector of double.
+      !!
+      !! The test is checked against the difference (element wise) of the two vector compared to
+      !! to a given tolerance.
+      REAL(kind=8), DIMENSION(:), INTENT(in) :: vec1 !! First vector to compare
+      REAL(kind=8), DIMENSION(:), INTENT(in) :: vec2 !! Second vector to compare
+      REAL(kind=8), INTENT(in), OPTIONAL     :: tol  !! Tolerance to apply for the comparison
+      LOGICAL :: ret                                 !! .true. if both vectors are equal, .false. otherwise.
+      REAL(kind=8) :: ztol
+      INTEGER      :: i
+      ztol = 1d-10 ; IF (PRESENT(tol)) ztol = abs(tol)
+      ret = .false.
+      IF (size(vec1) /= size(vec2)) RETURN
+  
+      DO i=1,size(vec1) ; IF (abs(vec1(i)-vec2(i)) > ztol) RETURN ; ENDDO
+      ret = .true.
+    END FUNCTION compare
+  
+  END MODULE LINT_DATASETS
Index: trunk/LMDZ.PLUTO/libf/muphypluto/lint_dset.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/lint_dset.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/lint_dset.F90	(revision 3560)
@@ -0,0 +1,848 @@
+! Copyright (c) Université de Reims Champagnne-Ardenne (2010-2015)
+! Contributor: Jérémie Burgalat (jeremie.burgalat@univ-reims.fr)
+! 
+! This software is a computer program whose purpose is to compute multi-variate
+! linear interpolation.
+! 
+! This software is governed by the CeCILL-B license under French law and
+! abiding by the rules of distribution of free software.  You can  use, 
+! modify and/ or redistribute the software under the terms of the CeCILL-B
+! license as circulated by CEA, CNRS and INRIA at the following URL
+! "http://www.cecill.info". 
+! 
+! As a counterpart to the access to the source code and  rights to copy,
+! modify and redistribute granted by the license, users are provided only
+! with a limited warranty  and the software's author,  the holder of the
+! economic rights,  and the successive licensors  have only  limited
+! liability. 
+! 
+! In this respect, the user's attention is drawn to the risks associated
+! with loading,  using,  modifying and/or developing or reproducing the
+! software by the user in light of its specific status of free software,
+! that may mean  that it is complicated to manipulate,  and  that  also
+! therefore means  that it is reserved for developers  and  experienced
+! professionals having in-depth computer knowledge. Users are therefore
+! encouraged to load and test the software's suitability as regards their
+! requirements in conditions enabling the security of their systems and/or 
+! data to be ensured and,  more generally, to use and operate it in the 
+! same conditions as regards security. 
+! 
+! The fact that you are presently reading this means that you have had
+! knowledge of the CeCILL-B license and that you accept its terms.
+
+!! File:    lint_dset.F90
+!! Summary: Linear interpolation generic interfaces
+!! Author:  J. Burgalat
+!! Date:    2010-2014 
+
+MODULE LINT_DSET
+    !! Generic linear interpolation using simple [[datasets(module)]]
+    USE LINT_PREC
+    USE LINT_CORE
+    USE LINT_DATASETS
+    IMPLICIT NONE
+  
+    PUBLIC  :: lintdset, hdcd_lint_dset,              &
+               dset1d, dset2d, dset3d, dset4d, dset5d, &
+               read_dset, clear_dset, is_in
+    PRIVATE 
+  
+    !> Interface to multi-variate linear interpolation using data set
+    !!
+    !! For __n__, the number of variables, user must provide:
+    !!
+    !! - __n__ scalar(s)/vector(s) with the coordinate(s) of the point(s) to 
+    !!   interpolate.
+    !! - A single __n__-D data set with the tabulated function.
+    !! - A function, satisfying the [[lintcore(module):locate(interface)]] interface, that
+    !!   should locate the neighbourhood of the point to interpolate in the given data
+    !!   set. 
+    !! - Either a scalar or vector which stores the interpolated values.
+    !! 
+    !! This interface also provides vectorized version of the linear
+    !! interpolation. In this case, input coordinates should be vector of the same
+    !! size as well as the output argument.
+    INTERFACE lintdset
+      MODULE PROCEDURE l1d_dset_sc, l2d_dset_sc, l3d_dset_sc, l4d_dset_sc, &
+                       l5d_dset_sc 
+      MODULE PROCEDURE l1d_dset_ve, l2d_dset_ve, l3d_dset_ve, l4d_dset_ve, &
+                       l5d_dset_ve 
+    END INTERFACE 
+  
+    !> Interface to multi-variate hard-coded linear interpolation using data set
+    !!
+    !! Same remarks as in [[lint_dset(module):lintdset(interface)]] apply here.
+    INTERFACE hdcd_lint_dset
+      MODULE PROCEDURE hdcdl1d_dset_sc, hdcdl2d_dset_sc, hdcdl3d_dset_sc, &
+                       hdcdl4d_dset_sc, hdcdl5d_dset_sc
+      MODULE PROCEDURE hdcdl1d_dset_ve, hdcdl2d_dset_ve, hdcdl3d_dset_ve, &
+                       hdcdl4d_dset_ve, hdcdl5d_dset_ve
+    END INTERFACE
+  
+    CONTAINS
+  
+    FUNCTION l1d_dset_sc(x,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 1D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      TYPE(dset1d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(2,2) :: g1d 
+      INTEGER                       :: i,ix
+      ret = .false.
+      res = HUGE(res)
+      ix = locator(x,set%x) ; IF (ix == 0) RETURN
+      DO i=0,1
+        g1d(i+1,1) = set%x(ix+i)
+        g1d(i+1,2) = set%data(ix+i)
+      ENDDO
+      res = lintc_((/x/),g1d) 
+      ret = .true.
+    END FUNCTION l1d_dset_sc
+  
+    FUNCTION l2d_dset_sc(x,y,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 2D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      TYPE(dset2d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(4,3) :: g2d 
+      INTEGER                       :: i,ix0,iy0,a,b
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      DO i=1,4
+        a=ix0+MOD((i-1),2)   ; g2d(i,1) = set%x(a)
+        b=iy0+MOD((i-1)/2,2) ; g2d(i,2) = set%y(b) 
+        g2d(i,3) = set%data(a,b)
+      ENDDO
+      res = lintc_((/x,y/),g2d) 
+      ret = .true.
+    END FUNCTION l2d_dset_sc
+  
+    FUNCTION l3d_dset_sc(x,y,z,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 3D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: z       !! Z coordinate of the point to interpolate
+      TYPE(dset3d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(8,4) :: g3d 
+      INTEGER                       :: i,ix0,iy0,iz0,a,b,c
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,set%z) ; IF (iz0 == 0) RETURN
+      DO i=1,8
+        a=ix0+MOD((i-1),2)   ; g3d(i,1) = set%x(a)
+        b=iy0+MOD((i-1)/2,2) ; g3d(i,2) = set%y(b) 
+        c=iz0+MOD((i-1)/4,2) ; g3d(i,3) = set%z(c)
+        g3d(i,4) = set%data(a,b,c)
+      ENDDO
+      res = lintc_((/x,y,z/),g3d) 
+      ret = .true.
+    END FUNCTION l3d_dset_sc
+  
+    FUNCTION l4d_dset_sc(x,y,z,t,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 4D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: t       !! T coordinate of the point to interpolate
+      TYPE(dset4d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(16,5) :: g4d 
+      INTEGER                        :: i,ix0,iy0,iz0,it0,a,b,c,d
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,set%z) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,set%t) ; IF (it0 == 0) RETURN
+      DO i=1,16
+        a=ix0+MOD((i-1),2)   ; g4d(i,1) = set%x(a)
+        b=iy0+MOD((i-1)/2,2) ; g4d(i,2) = set%y(b) 
+        c=iz0+MOD((i-1)/4,2) ; g4d(i,3) = set%z(c)
+        d=it0+MOD((i-1)/8,2) ; g4d(i,4) = set%t(d)
+        g4d(i,5) = set%data(a,b,c,d)
+      ENDDO
+      res = lintc_((/x,y,z,t/),g4d) 
+      ret = .true.
+    END FUNCTION l4d_dset_sc
+  
+    FUNCTION l5d_dset_sc(x,y,z,t,w,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 5D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: t       !! T coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: w       !! W coordinate of the point to interpolate
+      TYPE(dset5d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(32,6) :: g5d 
+      INTEGER                        :: i,ix0,iy0,iz0,it0,iw0,a,b,c,d,e
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,set%z) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,set%t) ; IF (it0 == 0) RETURN
+      iw0 = locator(w,set%w) ; IF (iw0 == 0) RETURN
+      DO i=1,32
+        a=ix0+MOD((i-1),2)    ; g5d(i,1) = set%x(a)
+        b=iy0+MOD((i-1)/2,2)  ; g5d(i,2) = set%y(b) 
+        c=iz0+MOD((i-1)/4,2)  ; g5d(i,3) = set%z(c)
+        d=it0+MOD((i-1)/8,2)  ; g5d(i,4) = set%t(d)
+        e=iw0+MOD((i-1)/16,2) ; g5d(i,5) = set%w(e)
+        g5d(i,6) = set%data(a,b,c,d,e)
+      ENDDO
+      res = lintc_((/x,y,z,t,w/),g5d) 
+      ret = .true.
+    END FUNCTION l5d_dset_sc
+  
+    FUNCTION l1d_dset_ve(x,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 1D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      TYPE(dset1d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(2,2) :: g1d 
+      INTEGER                       :: nv,i,j,ix0
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x) 
+        IF (ix0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=0,1
+          g1d(i+1,1) = set%x(ix0+i)
+          g1d(i+1,2) = set%data(ix0+i)
+        ENDDO
+        res(j) = lintc_((/x(j)/),g1d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l1d_dset_ve
+  
+    FUNCTION l2d_dset_ve(x,y,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 2D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      TYPE(dset2d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(4,3) :: g2d 
+      INTEGER                       :: nv,i,j,ix0,iy0,a,b
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x) 
+        iy0 = locator(y(j),set%y)
+        IF (ix0 == 0 .OR. iy0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,4
+          a=ix0+MOD((i-1),2)   ; g2d(i,1) = set%x(a)
+          b=iy0+MOD((i-1)/2,2) ; g2d(i,2) = set%y(b) 
+          g2d(i,3) = set%data(a,b)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j)/),g2d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l2d_dset_ve
+  
+    FUNCTION l3d_dset_ve(x,y,z,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 3D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      TYPE(dset3d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(8,4) :: g3d 
+      INTEGER                       :: nv,i,j,ix0,iy0,iz0,a,b,c
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x) 
+        iy0 = locator(y(j),set%y)
+        iz0 = locator(z(j),set%z)
+        IF (ix0==0 .OR. iy0==0 .OR. iz0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,8
+          a=ix0+MOD((i-1),2)   ; g3d(i,1) = set%x(a)
+          b=iy0+MOD((i-1)/2,2) ; g3d(i,2) = set%y(b) 
+          c=iz0+MOD((i-1)/4,2) ; g3d(i,3) = set%z(c)
+          g3d(i,4) = set%data(a,b,c)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j),z(j)/),g3d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l3d_dset_ve
+  
+    FUNCTION l4d_dset_ve(x,y,z,t,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 4D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      TYPE(dset4d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(16,5) :: g4d 
+      INTEGER                        :: nv,i,j,ix0,iy0,iz0,it0,a,b,c,d
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        iy0 = locator(y(j),set%y)
+        iz0 = locator(z(j),set%z)
+        it0 = locator(t(j),set%t)
+        IF (ix0==0 .OR. iy0==0 .OR. iz0==0 .OR. it0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,16
+          a=ix0+MOD((i-1),2)   ; g4d(i,1) = set%x(a)
+          b=iy0+MOD((i-1)/2,2) ; g4d(i,2) = set%y(b) 
+          c=iz0+MOD((i-1)/4,2) ; g4d(i,3) = set%z(c)
+          d=it0+MOD((i-1)/8,2) ; g4d(i,4) = set%t(d)
+          g4d(i,5) = set%data(a,b,c,d)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j),z(j),t(j)/),g4d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l4d_dset_ve
+  
+    FUNCTION l5d_dset_ve(x,y,z,t,w,set,locator,res) RESULT(ret)
+      !! Wrapper interface for 5D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: w       !! W coordinate of the points to interpolate
+      TYPE(dset5d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(32,6) :: g5d 
+      INTEGER                        :: nv,i,j,ix0,iy0,iz0,it0,iw0,a,b,c,d,e
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        iy0 = locator(y(j),set%y)
+        iz0 = locator(z(j),set%z)
+        it0 = locator(t(j),set%t)
+        iw0 = locator(w(j),set%w)
+        IF (ix0==0 .OR. iy0==0 .OR. iz0==0 .OR. it0==0 .OR. iw0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,32
+          a=ix0+MOD((i-1),2)    ; g5d(i,1) = set%x(a)
+          b=iy0+MOD((i-1)/2,2)  ; g5d(i,2) = set%y(b) 
+          c=iz0+MOD((i-1)/4,2)  ; g5d(i,3) = set%z(c)
+          d=it0+MOD((i-1)/8,2)  ; g5d(i,4) = set%t(d)
+          e=iw0+MOD((i-1)/16,2) ; g5d(i,5) = set%w(e)
+          g5d(i,6) = set%data(a,b,c,d,e)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j),z(j),t(j),w(j)/),g5d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l5d_dset_ve
+  
+    !--------------------!
+    ! HARD CODED VERSION !
+    !--------------------!
+  
+    FUNCTION hdcdl1d_dset_sc(x,set, locator,res) RESULT(ret)
+      !! Hard-coded for 1D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      TYPE(dset1d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd
+      INTEGER       :: ix0,ix1
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      ix1 = ix0+1
+      xd = (x-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+      res = set%data(ix0)*(1d0-xd)+set%data(ix1)*xd
+      ret = .true.
+    END FUNCTION hdcdl1d_dset_sc
+  
+    FUNCTION hdcdl2d_dset_sc(x,y,set,locator,res) RESULT(ret)
+      !! Hard-coded for 2D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      TYPE(dset2d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd
+      REAL(kind=wp) :: f0,f1
+      INTEGER       :: ix0,iy0,ix1,iy1
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      ix1 = ix0 + 1 ; iy1 = iy0 + 1
+      xd  = (x-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+      yd  = (y-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+      f0  = set%data(ix0,iy0)*(1d0-xd)+set%data(ix1,iy0)*xd
+      f1  = set%data(ix0,iy1)*(1d0-xd)+set%data(ix1,iy1)*xd
+      res = f0*(1d0-yd)+f1*yd
+      ret = .true.
+    END FUNCTION hdcdl2d_dset_sc 
+  
+    FUNCTION hdcdl3d_dset_sc(x,y,z,set,locator,res) RESULT(ret)
+      !! Hard-coded for 3D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: z       !! Z coordinate of the point to interpolate
+      TYPE(dset3d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd
+      REAL(kind=wp) :: f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,ix1,iy1,iz1
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,set%z) ; IF (iz0 == 0) RETURN
+      ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1
+      xd = (x-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+      yd = (y-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+      zd = (z-set%z(iz0))/(set%z(iz1)-set%z(iz0))
+  
+      f00 = set%data(ix0,iy0,iz0)*(1d0-xd)+set%data(ix1,iy0,iz0)*xd
+      f10 = set%data(ix0,iy1,iz0)*(1d0-xd)+set%data(ix1,iy1,iz0)*xd
+      f01 = set%data(ix0,iy0,iz1)*(1d0-xd)+set%data(ix1,iy0,iz1)*xd
+      f11 = set%data(ix0,iy1,iz1)*(1d0-xd)+set%data(ix1,iy1,iz1)*xd
+  
+      f0 = f00 *(1d0-yd)+f10*yd
+      f1 = f00 *(1d0-yd)+f10*yd
+  
+      res = f0*(1d0-zd)+f1*zd
+      ret = .true.
+    END FUNCTION hdcdl3d_dset_sc
+  
+    FUNCTION hdcdl4d_dset_sc(x,y,z,t,set,locator,res) RESULT(ret)
+      !! Hard-coded for 4D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: t       !! T coordinate of the point to interpolate
+      TYPE(dset4d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td
+      REAL(kind=wp) :: f000,f100,f010,f110,f001,f101,f011,f111, &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,it0,ix1,iy1,iz1,it1
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,set%z) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,set%t) ; IF (it0 == 0) RETURN
+      
+      ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 ; it1=it0+1
+      xd = (x-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+      yd = (y-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+      zd = (z-set%z(iz0))/(set%z(iz1)-set%z(iz0))
+      td = (t-set%t(it0))/(set%t(it1)-set%t(it0))
+  
+      f000 = set%data(ix0,iy0,iz0,it0)*(1d0-xd)+set%data(ix1,iy0,iz0,it0)*xd
+      f100 = set%data(ix0,iy1,iz0,it0)*(1d0-xd)+set%data(ix1,iy1,iz0,it0)*xd
+      f010 = set%data(ix0,iy0,iz1,it0)*(1d0-xd)+set%data(ix1,iy0,iz1,it0)*xd
+      f110 = set%data(ix0,iy1,iz1,it0)*(1d0-xd)+set%data(ix1,iy1,iz1,it0)*xd
+      f001 = set%data(ix0,iy0,iz0,it1)*(1d0-xd)+set%data(ix1,iy0,iz0,it1)*xd
+      f101 = set%data(ix0,iy1,iz0,it1)*(1d0-xd)+set%data(ix1,iy1,iz0,it1)*xd
+      f011 = set%data(ix0,iy0,iz1,it1)*(1d0-xd)+set%data(ix1,iy0,iz1,it1)*xd
+      f111 = set%data(ix0,iy1,iz1,it1)*(1d0-xd)+set%data(ix1,iy1,iz1,it1)*xd
+  
+      f00 = f000*(1d0-yd)+f100*yd
+      f10 = f010*(1d0-yd)+f110*yd 
+      f01 = f001*(1d0-yd)+f101*yd 
+      f11 = f011*(1d0-yd)+f111*yd 
+  
+      f0 = f00 *(1d0-zd)+f10*zd
+      f1 = f01 *(1d0-zd)+f11*zd
+  
+      res = f0*(1d0-td)+f1*td
+      ret = .true.
+    END FUNCTION hdcdl4d_dset_sc
+  
+    FUNCTION hdcdl5d_dset_sc(x,y,z,t,w,set,locator,res) RESULT(ret)
+      !! Hard-coded for 5D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)  :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: t       !! T coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)  :: w       !! W coordinate of the point to interpolate
+      TYPE(dset5d), INTENT(in)   :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out) :: res     !! Interpolated value
+      PROCEDURE(locate)          :: locator !! Locator function
+      LOGICAL :: ret                        !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td,wd
+      REAL(kind=wp) :: f0000,f1000,f0100,f1100,f0010,f1010,f0110,f1110, & 
+                       f0001,f1001,f0101,f1101,f0011,f1011,f0111,f1111, &
+                       f000,f100,f010,f110,f001,f101,f011,f111,         &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,it0,iw0,ix1,iy1,iz1,it1,iw1
+      ret = .false.
+      res = HUGE(res)
+      ix0 = locator(x,set%x) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,set%y) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,set%z) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,set%t) ; IF (it0 == 0) RETURN
+      iw0 = locator(w,set%w) ; IF (iw0 == 0) RETURN
+      
+      ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 
+      xd = (x-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+      yd = (y-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+      zd = (z-set%z(iz0))/(set%z(iz1)-set%z(iz0))
+      td = (t-set%t(it0))/(set%t(it1)-set%t(it0))
+      wd = (w-set%w(it0))/(set%w(it1)-set%w(it0))
+  
+      f0000 = set%data(ix0,iy0,iz0,it0,iw0)*(1d0-xd)+set%data(ix1,iy0,iz0,it0,iw0)*xd
+      f1000 = set%data(ix0,iy1,iz0,it0,iw0)*(1d0-xd)+set%data(ix0,iy1,iz0,it0,iw0)*xd
+      f0100 = set%data(ix0,iy0,iz1,it0,iw0)*(1d0-xd)+set%data(ix1,iy0,iz1,it0,iw0)*xd
+      f1100 = set%data(ix0,iy1,iz1,it0,iw0)*(1d0-xd)+set%data(ix1,iy1,iz1,it0,iw0)*xd
+      f0010 = set%data(ix0,iy0,iz0,it1,iw0)*(1d0-xd)+set%data(ix1,iy0,iz0,it1,iw0)*xd
+      f1010 = set%data(ix0,iy1,iz0,it1,iw0)*(1d0-xd)+set%data(ix1,iy1,iz0,it1,iw0)*xd
+      f0110 = set%data(ix0,iy0,iz1,it1,iw0)*(1d0-xd)+set%data(ix1,iy0,iz1,it1,iw0)*xd
+      f1110 = set%data(ix0,iy1,iz1,it1,iw0)*(1d0-xd)+set%data(ix1,iy1,iz1,it1,iw0)*xd
+      f0001 = set%data(ix0,iy0,iz0,it0,iw1)*(1d0-xd)+set%data(ix1,iy0,iz0,it0,iw1)*xd
+      f1001 = set%data(ix0,iy1,iz0,it0,iw1)*(1d0-xd)+set%data(ix0,iy1,iz0,it0,iw1)*xd
+      f0101 = set%data(ix0,iy0,iz1,it0,iw1)*(1d0-xd)+set%data(ix1,iy0,iz1,it0,iw1)*xd
+      f1101 = set%data(ix0,iy1,iz1,it0,iw1)*(1d0-xd)+set%data(ix1,iy1,iz1,it0,iw1)*xd
+      f0011 = set%data(ix0,iy0,iz0,it1,iw1)*(1d0-xd)+set%data(ix1,iy0,iz0,it1,iw1)*xd
+      f1011 = set%data(ix0,iy1,iz0,it1,iw1)*(1d0-xd)+set%data(ix1,iy1,iz0,it1,iw1)*xd
+      f0111 = set%data(ix0,iy0,iz1,it1,iw1)*(1d0-xd)+set%data(ix1,iy0,iz1,it1,iw1)*xd
+      f1111 = set%data(ix0,iy1,iz1,it1,iw1)*(1d0-xd)+set%data(ix1,iy1,iz1,it1,iw1)*xd
+  
+      f000 = f0000*(1d0-yd) + f1000*yd
+      f100 = f0100*(1d0-yd) + f1100*yd
+      f010 = f0010*(1d0-yd) + f1010*yd
+      f110 = f0110*(1d0-yd) + f1110*yd
+      f101 = f0101*(1d0-yd) + f1101*yd
+      f011 = f0011*(1d0-yd) + f1011*yd
+      f111 = f0111*(1d0-yd) + f1111*yd
+  
+      f00 = f000*(1d0-zd)+f100*zd
+      f10 = f010*(1d0-zd)+f110*zd 
+      f01 = f001*(1d0-zd)+f101*zd 
+      f11 = f011*(1d0-zd)+f111*zd 
+  
+      f0 = f00 *(1d0-td)+f10*td
+      f1 = f01 *(1d0-td)+f11*td
+  
+      res = f0*(1d0-wd)+f1*wd
+      ret = .true.
+    END FUNCTION hdcdl5d_dset_sc
+  
+    FUNCTION hdcdl1d_dset_ve(x,set,locator,res) RESULT(ret)
+      !! Hard-coded for 1D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      TYPE(dset1d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd
+      INTEGER       :: nv,j,ix0,ix1
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        IF (ix0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1 = ix0+1
+        xd = (x(j)-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+        res(j) = set%data(ix0)*(1d0-xd)+set%data(ix1)*xd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl1d_dset_ve
+  
+    FUNCTION hdcdl2d_dset_ve(x,y,set,locator,res) RESULT(ret)
+      !! Hard-coded for 2D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      TYPE(dset2d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd
+      REAL(kind=wp) :: f0,f1
+      INTEGER      :: nv,j,ix0,iy0,ix1,iy1
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        iy0 = locator(y(j),set%y)
+        IF (ix0==0 .OR. iy0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1 = ix0 + 1 ; iy1 = iy0 + 1
+        xd  = (x(j)-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+        yd  = (y(j)-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+        f0  = set%data(ix0,iy0)*(1d0-xd)+set%data(ix1,iy0)*xd
+        f1  = set%data(ix0,iy1)*(1d0-xd)+set%data(ix1,iy1)*xd
+        res(j) = f0*(1d0-yd)+f1*yd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl2d_dset_ve 
+  
+    FUNCTION hdcdl3d_dset_ve(x,y,z,set,locator,res) RESULT(ret)
+      !! Hard-coded for 3D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      TYPE(dset3d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd
+      REAL(kind=wp) :: f00,f10,f01,f11,f0,f1
+      INTEGER       :: nv,j,ix0,iy0,iz0,ix1,iy1,iz1
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        iy0 = locator(y(j),set%y)
+        iz0 = locator(z(j),set%z)
+  
+        IF (ix0==0 .OR. iy0==0 .OR. iz0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+  
+        ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1
+        xd = (x(j)-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+        yd = (y(j)-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+        zd = (z(j)-set%z(iz0))/(set%z(iz1)-set%z(iz0))
+  
+        f00 = set%data(ix0,iy0,iz0)*(1d0-xd)+set%data(ix1,iy0,iz0)*xd
+        f10 = set%data(ix0,iy1,iz0)*(1d0-xd)+set%data(ix1,iy1,iz0)*xd
+        f01 = set%data(ix0,iy0,iz1)*(1d0-xd)+set%data(ix1,iy0,iz1)*xd
+        f11 = set%data(ix0,iy1,iz1)*(1d0-xd)+set%data(ix1,iy1,iz1)*xd
+  
+        f0 = f00 *(1d0-yd)+f10*yd
+        f1 = f00 *(1d0-yd)+f10*yd
+  
+        res(j) = f0*(1d0-zd)+f1*zd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl3d_dset_ve
+  
+    FUNCTION hdcdl4d_dset_ve(x,y,z,t,set,locator,res) RESULT(ret)
+      !! Hard-coded for 4D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      TYPE(dset4d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td
+      REAL(kind=wp) :: f000,f100,f010,f110,f001,f101,f011,f111, &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: nv,j,ix0,iy0,iz0,it0,ix1,iy1,iz1,it1
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        iy0 = locator(y(j),set%y)
+        iz0 = locator(z(j),set%z)
+        it0 = locator(t(j),set%t)
+      
+        IF (ix0==0 .OR. iy0==0 .OR. iz0==0 .OR. it0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+  
+        ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 ; it1=it0+1
+        xd = (x(j)-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+        yd = (y(j)-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+        zd = (z(j)-set%z(iz0))/(set%z(iz1)-set%z(iz0))
+        td = (t(j)-set%t(it0))/(set%t(it1)-set%t(it0))
+  
+        f000 = set%data(ix0,iy0,iz0,it0)*(1d0-xd)+set%data(ix1,iy0,iz0,it0)*xd
+        f100 = set%data(ix0,iy1,iz0,it0)*(1d0-xd)+set%data(ix1,iy1,iz0,it0)*xd
+        f010 = set%data(ix0,iy0,iz1,it0)*(1d0-xd)+set%data(ix1,iy0,iz1,it0)*xd
+        f110 = set%data(ix0,iy1,iz1,it0)*(1d0-xd)+set%data(ix1,iy1,iz1,it0)*xd
+        f001 = set%data(ix0,iy0,iz0,it1)*(1d0-xd)+set%data(ix1,iy0,iz0,it1)*xd
+        f101 = set%data(ix0,iy1,iz0,it1)*(1d0-xd)+set%data(ix1,iy1,iz0,it1)*xd
+        f011 = set%data(ix0,iy0,iz1,it1)*(1d0-xd)+set%data(ix1,iy0,iz1,it1)*xd
+        f111 = set%data(ix0,iy1,iz1,it1)*(1d0-xd)+set%data(ix1,iy1,iz1,it1)*xd
+  
+        f00 = f000*(1d0-yd)+f100*yd
+        f10 = f010*(1d0-yd)+f110*yd 
+        f01 = f001*(1d0-yd)+f101*yd 
+        f11 = f011*(1d0-yd)+f111*yd 
+  
+        f0 = f00 *(1d0-zd)+f10*zd
+        f1 = f01 *(1d0-zd)+f11*zd
+  
+        res(j) = f0*(1d0-td)+f1*td
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl4d_dset_ve
+  
+    FUNCTION hdcdl5d_dset_ve(x,y,z,t,w,set,locator,res) RESULT(ret)
+      !! Hard-coded for 5D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: w       !! W coordinate of the points to interpolate
+      TYPE(dset5d), INTENT(in)                              :: set     !! Dataset with the tabulated function
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td,wd
+      REAL(kind=wp) :: f0000,f1000,f0100,f1100,f0010,f1010,f0110,f1110, & 
+                       f0001,f1001,f0101,f1101,f0011,f1011,f0111,f1111, &
+                       f000,f100,f010,f110,f001,f101,f011,f111,         &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: nv,j,ix0,iy0,iz0,it0,iw0,ix1,iy1,iz1,it1,iw1
+      ret = .false.
+      nv = SIZE(x) ; ALLOCATE(res(nv))
+      DO j=1,nv
+        ix0 = locator(x(j),set%x)
+        iy0 = locator(y(j),set%y)
+        iz0 = locator(z(j),set%z)
+        it0 = locator(t(j),set%t)
+        iw0 = locator(w(j),set%w)
+  
+        IF (ix0==0 .OR. iy0==0 .OR. iz0==0 .OR. it0==0 .OR. iw0==0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+      
+        ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 
+        xd = (x(j)-set%x(ix0))/(set%x(ix1)-set%x(ix0))
+        yd = (y(j)-set%y(iy0))/(set%y(iy1)-set%y(iy0))
+        zd = (z(j)-set%z(iz0))/(set%z(iz1)-set%z(iz0))
+        td = (t(j)-set%t(it0))/(set%t(it1)-set%t(it0))
+        wd = (w(j)-set%w(it0))/(set%w(it1)-set%w(it0))
+  
+        f0000 = set%data(ix0,iy0,iz0,it0,iw0)*(1d0-xd)+set%data(ix1,iy0,iz0,it0,iw0)*xd
+        f1000 = set%data(ix0,iy1,iz0,it0,iw0)*(1d0-xd)+set%data(ix0,iy1,iz0,it0,iw0)*xd
+        f0100 = set%data(ix0,iy0,iz1,it0,iw0)*(1d0-xd)+set%data(ix1,iy0,iz1,it0,iw0)*xd
+        f1100 = set%data(ix0,iy1,iz1,it0,iw0)*(1d0-xd)+set%data(ix1,iy1,iz1,it0,iw0)*xd
+        f0010 = set%data(ix0,iy0,iz0,it1,iw0)*(1d0-xd)+set%data(ix1,iy0,iz0,it1,iw0)*xd
+        f1010 = set%data(ix0,iy1,iz0,it1,iw0)*(1d0-xd)+set%data(ix1,iy1,iz0,it1,iw0)*xd
+        f0110 = set%data(ix0,iy0,iz1,it1,iw0)*(1d0-xd)+set%data(ix1,iy0,iz1,it1,iw0)*xd
+        f1110 = set%data(ix0,iy1,iz1,it1,iw0)*(1d0-xd)+set%data(ix1,iy1,iz1,it1,iw0)*xd
+        f0001 = set%data(ix0,iy0,iz0,it0,iw1)*(1d0-xd)+set%data(ix1,iy0,iz0,it0,iw1)*xd
+        f1001 = set%data(ix0,iy1,iz0,it0,iw1)*(1d0-xd)+set%data(ix0,iy1,iz0,it0,iw1)*xd
+        f0101 = set%data(ix0,iy0,iz1,it0,iw1)*(1d0-xd)+set%data(ix1,iy0,iz1,it0,iw1)*xd
+        f1101 = set%data(ix0,iy1,iz1,it0,iw1)*(1d0-xd)+set%data(ix1,iy1,iz1,it0,iw1)*xd
+        f0011 = set%data(ix0,iy0,iz0,it1,iw1)*(1d0-xd)+set%data(ix1,iy0,iz0,it1,iw1)*xd
+        f1011 = set%data(ix0,iy1,iz0,it1,iw1)*(1d0-xd)+set%data(ix1,iy1,iz0,it1,iw1)*xd
+        f0111 = set%data(ix0,iy0,iz1,it1,iw1)*(1d0-xd)+set%data(ix1,iy0,iz1,it1,iw1)*xd
+        f1111 = set%data(ix0,iy1,iz1,it1,iw1)*(1d0-xd)+set%data(ix1,iy1,iz1,it1,iw1)*xd
+  
+        f000 = f0000*(1d0-yd) + f1000*yd
+        f100 = f0100*(1d0-yd) + f1100*yd
+        f010 = f0010*(1d0-yd) + f1010*yd
+        f110 = f0110*(1d0-yd) + f1110*yd
+        f101 = f0101*(1d0-yd) + f1101*yd
+        f011 = f0011*(1d0-yd) + f1011*yd
+        f111 = f0111*(1d0-yd) + f1111*yd
+  
+        f00 = f000*(1d0-zd)+f100*zd
+        f10 = f010*(1d0-zd)+f110*zd 
+        f01 = f001*(1d0-zd)+f101*zd 
+        f11 = f011*(1d0-zd)+f111*zd 
+  
+        f0 = f00 *(1d0-td)+f10*td
+        f1 = f01 *(1d0-td)+f11*td
+  
+        res(j) = f0*(1d0-wd)+f1*wd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl5d_dset_ve
+  
+  END MODULE LINT_DSET  
Index: trunk/LMDZ.PLUTO/libf/muphypluto/lint_gen.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/lint_gen.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/lint_gen.F90	(revision 3560)
@@ -0,0 +1,905 @@
+! Copyright (c) Université de Reims Champagnne-Ardenne (2010-2015)
+! Contributor: Jérémie Burgalat (jeremie.burgalat@univ-reims.fr)
+! 
+! This software is a computer program whose purpose is to compute multi-variate
+! linear interpolation.
+! 
+! This software is governed by the CeCILL-B license under French law and
+! abiding by the rules of distribution of free software.  You can  use, 
+! modify and/ or redistribute the software under the terms of the CeCILL-B
+! license as circulated by CEA, CNRS and INRIA at the following URL
+! "http://www.cecill.info". 
+! 
+! As a counterpart to the access to the source code and  rights to copy,
+! modify and redistribute granted by the license, users are provided only
+! with a limited warranty  and the software's author,  the holder of the
+! economic rights,  and the successive licensors  have only  limited
+! liability. 
+! 
+! In this respect, the user's attention is drawn to the risks associated
+! with loading,  using,  modifying and/or developing or reproducing the
+! software by the user in light of its specific status of free software,
+! that may mean  that it is complicated to manipulate,  and  that  also
+! therefore means  that it is reserved for developers  and  experienced
+! professionals having in-depth computer knowledge. Users are therefore
+! encouraged to load and test the software's suitability as regards their
+! requirements in conditions enabling the security of their systems and/or 
+! data to be ensured and,  more generally, to use and operate it in the 
+! same conditions as regards security. 
+! 
+! The fact that you are presently reading this means that you have had
+! knowledge of the CeCILL-B license and that you accept its terms.
+
+!! File:    lint_gen.F90
+!! Summary: Linear interpolation generic interfaces
+!! Author:  J. Burgalat
+!! Date:    2010-2014 
+
+MODULE LINT_GEN
+    !! Generic linear interpolation module
+    USE LINT_PREC
+    USE LINT_CORE
+    IMPLICIT NONE
+  
+    PUBLIC  :: lintgen, hdcd_lint_gen
+    PRIVATE 
+  
+    !> Interface to multi-variate linear interpolation
+    !! 
+    !! For __n__, the number of function's variables, user must provide: 
+    !!
+    !! - __n__ scalar(s) with the coordinate(s) of the point to interpolate.
+    !! - __n__ vector(s) with the tabulated values of each coordinate. 
+    !! - a single __n__-D array with the value of tabulated function at each
+    !!   tabulated coordinate.
+    !! - A function, satisfying [[lintcore(module):locate(interface)]] interface, 
+    !!   that locates the neighbourhood of the point to interpolate.
+    !! - A scalar that stores the output value.
+    INTERFACE lintgen
+      MODULE PROCEDURE l1d_gen_sc,l2d_gen_sc,l3d_gen_sc,l4d_gen_sc,l5d_gen_sc
+      MODULE PROCEDURE l1d_gen_ve,l2d_gen_ve,l3d_gen_ve,l4d_gen_ve,l5d_gen_ve
+    END INTERFACE 
+  
+    !> Interface to multi-variate hard-coded linear interpolation
+    !!
+    !! Same remarks as in [[lint_gen(module):lintgen(interface)]] apply here.
+    INTERFACE hdcd_lint_gen
+      MODULE PROCEDURE hdcdl1d_gen_sc, hdcdl2d_gen_sc, hdcdl3d_gen_sc, &
+                       hdcdl4d_gen_sc, hdcdl5d_gen_sc
+      MODULE PROCEDURE hdcdl1d_gen_ve, hdcdl2d_gen_ve, hdcdl3d_gen_ve, &
+                       hdcdl4d_gen_ve, hdcdl5d_gen_ve
+    END INTERFACE
+  
+    CONTAINS
+  
+    ! GENERIC versions
+    ! ----------------
+  
+    FUNCTION l1d_gen_sc(x,cx,cv, locator,res) RESULT(ret)
+      !! Wrapper interface for 1D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)               :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)              :: res     !! Interpolated value
+      PROCEDURE(locate)                       :: locator !! Locator function
+      LOGICAL :: ret                                     !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(2,2) :: g1d 
+      INTEGER                       :: i,ix
+      ret = .false. 
+      ix = locator(x,cx) ; IF (ix == 0) RETURN
+      DO i=0,1
+        g1d(i+1,1) = cx(ix+i)
+        g1d(i+1,2) = cv(ix+i)
+      ENDDO
+      res = lintc_((/x/),g1d) 
+      ret = .true.
+    END FUNCTION l1d_gen_sc
+  
+    FUNCTION l2d_gen_sc(x,y,cx,cy,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 2D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                 :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                 :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)   :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)   :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                :: res     !! Interpolated value
+      PROCEDURE(locate)                         :: locator !! Locator function
+      LOGICAL :: ret                                       !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(4,3) :: g2d 
+      INTEGER                       :: ix0,iy0,i,a,b
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      DO i=1,4
+        a=ix0+MOD((i-1),2)   ; g2d(i,1) = cx(a)
+        b=iy0+MOD((i-1)/2,2) ; g2d(i,2) = cy(b) 
+        g2d(i,3) = cv(a,b)
+      ENDDO
+      res = lintc_((/x,y/),g2d) 
+      ret = .true.
+    END FUNCTION l2d_gen_sc
+  
+    FUNCTION l3d_gen_sc(x,y,z,cx,cy,cz,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 3D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                   :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                   :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                   :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)     :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)     :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)     :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                  :: res     !! Interpolated value
+      PROCEDURE(locate)                           :: locator !! Locator function
+      LOGICAL :: ret                                         !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(8,4) :: g3d 
+      INTEGER                      :: ix0,iy0,iz0,i,a,b,c
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,cz) ; IF (iz0 == 0) RETURN
+      DO i=1,8
+        a=ix0+MOD((i-1),2)   ; g3d(i,1) = cx(a)
+        b=iy0+MOD((i-1)/2,2) ; g3d(i,2) = cy(b) 
+        c=iz0+MOD((i-1)/4,2) ; g3d(i,3) = cz(c)
+        g3d(i,4) = cv(a,b,c)
+      ENDDO
+      res = lintc_((/x,y,z/),g3d) 
+      ret = .true.
+    END FUNCTION l3d_gen_sc
+  
+    FUNCTION l4d_gen_sc(x,y,z,t,cx,cy,cz,ct,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 4D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                     :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                     :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                     :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                     :: t       !! T coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                    :: res     !! Interpolated value
+      PROCEDURE(locate)                             :: locator !! Locator function
+      LOGICAL :: ret                                           !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(16,5) :: g4d 
+      INTEGER                        :: ix0,iy0,iz0,it0
+      INTEGER                        :: i,a,b,c,d
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,cz) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,ct) ; IF (it0 == 0) RETURN
+      DO i=1,16
+        a=ix0+MOD((i-1),2)   ; g4d(i,1) = cx(a)
+        b=iy0+MOD((i-1)/2,2) ; g4d(i,2) = cy(b) 
+        c=iz0+MOD((i-1)/4,2) ; g4d(i,3) = cz(c)
+        d=it0+MOD((i-1)/8,2) ; g4d(i,4) = ct(d)
+        g4d(i,5) = cv(a,b,c,d)
+      ENDDO
+      res = lintc_((/x,y,z,t/),g4d) 
+      ret = .true.
+    END FUNCTION l4d_gen_sc
+  
+    FUNCTION l5d_gen_sc(x,y,z,t,w,cx,cy,cz,ct,cw,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 5D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                       :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: t       !! T coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: w       !! W coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cw      !! Tabulated function W coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                      :: res     !! Interpolated value
+      PROCEDURE(locate)                               :: locator !! Locator function
+      LOGICAL :: ret                                             !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(32,6) :: g5d 
+      INTEGER                        :: ix0,iy0,iz0,it0,iw0
+      INTEGER                        :: i,a,b,c,d,e
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,cz) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,ct) ; IF (it0 == 0) RETURN
+      iw0 = locator(w,cw) ; IF (iw0 == 0) RETURN
+      DO i=1,32
+        a=ix0+MOD((i-1),2)    ; g5d(i,1) = cx(a)
+        b=iy0+MOD((i-1)/2,2)  ; g5d(i,2) = cy(b) 
+        c=iz0+MOD((i-1)/4,2)  ; g5d(i,3) = cz(c)
+        d=it0+MOD((i-1)/8,2)  ; g5d(i,4) = ct(d)
+        e=iw0+MOD((i-1)/16,2) ; g5d(i,5) = cw(e)
+        g5d(i,6) = cv(a,b,c,d,e)
+      ENDDO
+      res = lintc_((/x,y,z,t,w/),g5d) 
+      ret = .true.
+    END FUNCTION l5d_gen_sc
+  
+    ! HARD-CODED versions
+    ! -------------------
+  
+    FUNCTION hdcdl1d_gen_sc(x,cx,cv,locator,res) RESULT(ret)
+      !! Hard-coded 1D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)               :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)              :: res     !! Interpolated value
+      PROCEDURE(locate)                       :: locator !! Locator function
+      LOGICAL :: ret                                     !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd
+      INTEGER       :: ix0,ix1
+      ret = .false. 
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      ix1 = ix0+1
+      xd = (x-cx(ix0))/(cx(ix1)-cx(ix0))
+      res = cv(ix0)*(1d0-xd)+cv(ix1)*xd
+      ret = .true.
+    END FUNCTION hdcdl1d_gen_sc
+  
+    FUNCTION hdcdl2d_gen_sc(x,y,cx,cy,cv,locator,res) RESULT(ret)
+      !! Hard-coded 2D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                 :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                 :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)   :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)   :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                :: res     !! Interpolated value
+      PROCEDURE(locate)                         :: locator !! Locator function
+      LOGICAL :: ret                                       !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd
+      REAL(kind=wp) :: f0,f1
+      INTEGER       :: ix0,iy0,ix1,iy1
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      ix1 = ix0 + 1 ; iy1 = iy0 + 1
+      xd  = (x-cx(ix0))/(cx(ix1)-cx(ix0))
+      yd  = (y-cy(iy0))/(cy(iy1)-cy(iy0))
+      f0  = cv(ix0,iy0)*(1d0-xd)+cv(ix1,iy0)*xd
+      f1  = cv(ix0,iy1)*(1d0-xd)+cv(ix1,iy1)*xd
+      res = f0*(1d0-yd)+f1*yd
+      ret = .true.
+    END FUNCTION hdcdl2d_gen_sc 
+  
+    FUNCTION hdcdl3d_gen_sc(x,y,z,cx,cy,cz,cv,locator,res) RESULT(ret)
+      !! Hard-coded 3D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                   :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                   :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                   :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)     :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)     :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)     :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                  :: res     !! Interpolated value
+      PROCEDURE(locate)                           :: locator !! Locator function
+      LOGICAL :: ret                                         !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd
+      REAL(kind=wp) :: f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,ix1,iy1,iz1
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,cz) ; IF (iz0 == 0) RETURN
+      ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1
+      xd = (x-cx(ix0))/(cx(ix1)-cx(ix0))
+      yd = (y-cy(iy0))/(cy(iy1)-cy(iy0))
+      zd = (z-cz(iz0))/(cz(iz1)-cz(iz0))
+  
+      f00 = cv(ix0,iy0,iz0)*(1d0-xd)+cv(ix1,iy0,iz0)*xd
+      f10 = cv(ix0,iy1,iz0)*(1d0-xd)+cv(ix1,iy1,iz0)*xd
+      f01 = cv(ix0,iy0,iz1)*(1d0-xd)+cv(ix1,iy0,iz1)*xd
+      f11 = cv(ix0,iy1,iz1)*(1d0-xd)+cv(ix1,iy1,iz1)*xd
+  
+      f0 = f00 *(1d0-yd)+f10*yd
+      f1 = f00 *(1d0-yd)+f10*yd
+  
+      res = f0*(1d0-zd)+f1*zd
+      ret = .true.
+    END FUNCTION hdcdl3d_gen_sc
+  
+    FUNCTION hdcdl4d_gen_sc(x,y,z,t,cx,cy,cz,ct,cv,locator,res) RESULT(ret)
+      !! Hard-coded 4D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                     :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                     :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                     :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                     :: t       !! T coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)       :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                    :: res     !! Interpolated value
+      PROCEDURE(locate)                             :: locator !! Locator function
+      LOGICAL :: ret                                           !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td
+      REAL(kind=wp) :: f000,f100,f010,f110,f001,f101,f011,f111, &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,it0,ix1,iy1,iz1,it1
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,cz) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,ct) ; IF (it0 == 0) RETURN
+      
+      ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 ; it1=it0+1
+      xd = (x-cx(ix0))/(cx(ix1)-cx(ix0))
+      yd = (y-cy(iy0))/(cy(iy1)-cy(iy0))
+      zd = (z-cz(iz0))/(cz(iz1)-cz(iz0))
+      td = (t-ct(it0))/(ct(it1)-ct(it0))
+  
+      f000 = cv(ix0,iy0,iz0,it0)*(1d0-xd)+cv(ix1,iy0,iz0,it0)*xd
+      f100 = cv(ix0,iy1,iz0,it0)*(1d0-xd)+cv(ix1,iy1,iz0,it0)*xd
+      f010 = cv(ix0,iy0,iz1,it0)*(1d0-xd)+cv(ix1,iy0,iz1,it0)*xd
+      f110 = cv(ix0,iy1,iz1,it0)*(1d0-xd)+cv(ix1,iy1,iz1,it0)*xd
+      f001 = cv(ix0,iy0,iz0,it1)*(1d0-xd)+cv(ix1,iy0,iz0,it1)*xd
+      f101 = cv(ix0,iy1,iz0,it1)*(1d0-xd)+cv(ix1,iy1,iz0,it1)*xd
+      f011 = cv(ix0,iy0,iz1,it1)*(1d0-xd)+cv(ix1,iy0,iz1,it1)*xd
+      f111 = cv(ix0,iy1,iz1,it1)*(1d0-xd)+cv(ix1,iy1,iz1,it1)*xd
+  
+      f00 = f000*(1d0-yd)+f100*yd
+      f10 = f010*(1d0-yd)+f110*yd 
+      f01 = f001*(1d0-yd)+f101*yd 
+      f11 = f011*(1d0-yd)+f111*yd 
+  
+      f0 = f00 *(1d0-zd)+f10*zd
+      f1 = f01 *(1d0-zd)+f11*zd
+  
+      res = f0*(1d0-td)+f1*td
+      ret = .true.
+    END FUNCTION hdcdl4d_gen_sc
+  
+    FUNCTION hdcdl5d_gen_sc(x,y,z,t,w,cx,cy,cz,ct,cw,cv,locator,res) RESULT(ret)
+      !! Hard-coded 5D linear interpolation (scalar)
+      !!
+      !! @warning
+      !! On error, __res__ output value is undefined.
+      REAL(kind=wp), INTENT(in)                       :: x       !! X coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: y       !! Y coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: z       !! Z coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: t       !! T coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in)                       :: w       !! W coordinate of the point to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)         :: cw      !! Tabulated function W coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:,:) :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out)                      :: res     !! Interpolated value
+      PROCEDURE(locate)                               :: locator !! Locator function
+      LOGICAL :: ret                                             !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td,wd
+      REAL(kind=wp) :: f0000,f1000,f0100,f1100,f0010,f1010,f0110,f1110, & 
+                       f0001,f1001,f0101,f1101,f0011,f1011,f0111,f1111, &
+                       f000,f100,f010,f110,f001,f101,f011,f111,         &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,it0,iw0,ix1,iy1,iz1,it1,iw1
+      ret = .false.
+      ix0 = locator(x,cx) ; IF (ix0 == 0) RETURN
+      iy0 = locator(y,cy) ; IF (iy0 == 0) RETURN
+      iz0 = locator(z,cz) ; IF (iz0 == 0) RETURN
+      it0 = locator(t,ct) ; IF (it0 == 0) RETURN
+      iw0 = locator(w,cw) ; IF (iw0 == 0) RETURN
+      
+      ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 ; it1=it0+1 ; iw1=iw0+1
+      xd = (x-cx(ix0))/(cx(ix1)-cx(ix0))
+      yd = (y-cy(iy0))/(cy(iy1)-cy(iy0))
+      zd = (z-cz(iz0))/(cz(iz1)-cz(iz0))
+      td = (t-ct(it0))/(ct(it1)-ct(it0))
+      wd = (w-cw(iw0))/(cw(iw1)-cw(iw0))
+  
+      f0000 = cv(ix0,iy0,iz0,it0,iw0)*(1d0-xd)+cv(ix1,iy0,iz0,it0,iw0)*xd
+      f1000 = cv(ix0,iy1,iz0,it0,iw0)*(1d0-xd)+cv(ix0,iy1,iz0,it0,iw0)*xd
+      f0100 = cv(ix0,iy0,iz1,it0,iw0)*(1d0-xd)+cv(ix1,iy0,iz1,it0,iw0)*xd
+      f1100 = cv(ix0,iy1,iz1,it0,iw0)*(1d0-xd)+cv(ix1,iy1,iz1,it0,iw0)*xd
+      f0010 = cv(ix0,iy0,iz0,it1,iw0)*(1d0-xd)+cv(ix1,iy0,iz0,it1,iw0)*xd
+      f1010 = cv(ix0,iy1,iz0,it1,iw0)*(1d0-xd)+cv(ix1,iy1,iz0,it1,iw0)*xd
+      f0110 = cv(ix0,iy0,iz1,it1,iw0)*(1d0-xd)+cv(ix1,iy0,iz1,it1,iw0)*xd
+      f1110 = cv(ix0,iy1,iz1,it1,iw0)*(1d0-xd)+cv(ix1,iy1,iz1,it1,iw0)*xd
+      f0001 = cv(ix0,iy0,iz0,it0,iw1)*(1d0-xd)+cv(ix1,iy0,iz0,it0,iw1)*xd
+      f1001 = cv(ix0,iy1,iz0,it0,iw1)*(1d0-xd)+cv(ix0,iy1,iz0,it0,iw1)*xd
+      f0101 = cv(ix0,iy0,iz1,it0,iw1)*(1d0-xd)+cv(ix1,iy0,iz1,it0,iw1)*xd
+      f1101 = cv(ix0,iy1,iz1,it0,iw1)*(1d0-xd)+cv(ix1,iy1,iz1,it0,iw1)*xd
+      f0011 = cv(ix0,iy0,iz0,it1,iw1)*(1d0-xd)+cv(ix1,iy0,iz0,it1,iw1)*xd
+      f1011 = cv(ix0,iy1,iz0,it1,iw1)*(1d0-xd)+cv(ix1,iy1,iz0,it1,iw1)*xd
+      f0111 = cv(ix0,iy0,iz1,it1,iw1)*(1d0-xd)+cv(ix1,iy0,iz1,it1,iw1)*xd
+      f1111 = cv(ix0,iy1,iz1,it1,iw1)*(1d0-xd)+cv(ix1,iy1,iz1,it1,iw1)*xd
+  
+      f000 = f0000*(1d0-yd) + f1000*yd
+      f100 = f0100*(1d0-yd) + f1100*yd
+      f010 = f0010*(1d0-yd) + f1010*yd
+      f110 = f0110*(1d0-yd) + f1110*yd
+      f101 = f0101*(1d0-yd) + f1101*yd
+      f011 = f0011*(1d0-yd) + f1011*yd
+      f111 = f0111*(1d0-yd) + f1111*yd
+  
+      f00 = f000*(1d0-zd)+f100*zd
+      f10 = f010*(1d0-zd)+f110*zd 
+      f01 = f001*(1d0-zd)+f101*zd 
+      f11 = f011*(1d0-zd)+f111*zd 
+  
+      f0 = f00 *(1d0-td)+f10*td
+      f1 = f01 *(1d0-td)+f11*td
+  
+      res = f0*(1d0-wd)+f1*wd
+      ret = .true.
+    END FUNCTION hdcdl5d_gen_sc
+  
+    !--------------------
+    ! VECTORIZED VERSIONS
+    !--------------------
+  
+    ! GENERIC versions
+    ! ----------------
+  
+    FUNCTION l1d_gen_ve(x,cx,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 1D linear interpolation (vector) 
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(2,2) :: g1d 
+      INTEGER                       :: n,j,i,ix
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix = locator(x(j),cx) 
+        IF (ix == 0) THEN
+          DEALLOCATE(res) ; RETURN 
+        ENDIF
+        DO i=0,1
+          g1d(i+1,1) = cx(ix+i)
+          g1d(i+1,2) = cv(ix+i)
+        ENDDO
+        res(j) = lintc_((/x(j)/),g1d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l1d_gen_ve
+  
+    FUNCTION l2d_gen_ve(x,y,cx,cy,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 2D linear interpolation (vector) 
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:)             :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(4,3) :: g2d 
+      INTEGER                       :: n,j,ix0,iy0,i,a,b
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy) 
+        IF (ix0 == 0 .OR. iy0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,4
+          a=ix0+MOD((i-1),2)   ; g2d(i,1) = cx(a)
+          b=iy0+MOD((i-1)/2,2) ; g2d(i,2) = cy(b) 
+          g2d(i,3) = cv(a,b)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j)/),g2d) 
+     ENDDO
+      ret = .true.
+    END FUNCTION l2d_gen_ve
+  
+    FUNCTION l3d_gen_ve(x,y,z,cx,cy,cz,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 3D linear interpolation (vector) 
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:)           :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(8,4) :: g3d 
+      INTEGER                       :: ix0,iy0,iz0
+      INTEGER                       :: n,j,i,a,b,c
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        iz0 = locator(z(j),cz)
+        IF (ix0 == 0 .OR. iy0 == 0 .OR. iz0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,8
+          a=ix0+MOD((i-1),2)   ; g3d(i,1) = cx(a)
+          b=iy0+MOD((i-1)/2,2) ; g3d(i,2) = cy(b) 
+          c=iz0+MOD((i-1)/4,2) ; g3d(i,3) = cz(c)
+          g3d(i,4) = cv(a,b,c)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j),z(j)/),g3d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l3d_gen_ve
+  
+    FUNCTION l4d_gen_ve(x,y,z,t,cx,cy,cz,ct,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 4D linear interpolation (vector) 
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:)         :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(16,5) :: g4d 
+      INTEGER                        :: ix0,iy0,iz0,it0
+      INTEGER                        :: n,j,i,a,b,c,d
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        iz0 = locator(z(j),cz)
+        it0 = locator(t(j),ct)
+        IF (ix0 == 0 .OR. iy0 == 0 .OR. iz0 == 0 .OR. it0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,16
+          a=ix0+MOD((i-1),2)   ; g4d(i,1) = cx(a)
+          b=iy0+MOD((i-1)/2,2) ; g4d(i,2) = cy(b) 
+          c=iz0+MOD((i-1)/4,2) ; g4d(i,3) = cz(c)
+          d=it0+MOD((i-1)/8,2) ; g4d(i,4) = ct(d)
+          g4d(i,5) = cv(a,b,c,d)
+        ENDDO
+        res(j) = lintc_((/x(j),y(j),z(j),t(j)/),g4d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l4d_gen_ve
+  
+    FUNCTION l5d_gen_ve(x,y,z,t,w,cx,cy,cz,ct,cw,cv,locator,res) RESULT(ret)
+      !! Wrapper interface for 5D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: w       !! W coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cw      !! Tabulated function W coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:,:)       :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp), DIMENSION(32,6) :: g5d 
+      INTEGER                        :: ix0,iy0,iz0,it0,iw0
+      INTEGER                        :: n,j,i,a,b,c,d,e
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        iz0 = locator(z(j),cz)
+        it0 = locator(t(j),ct)
+        iw0 = locator(w(j),cw)
+        IF (ix0 == 0 .OR. iy0 == 0 .OR. iz0 == 0 .OR. it0 == 0 .OR. iw0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        DO i=1,32
+          a=ix0+MOD((i-1),2)    ; g5d(i,1) = cx(a)
+          b=iy0+MOD((i-1)/2,2)  ; g5d(i,2) = cy(b) 
+          c=iz0+MOD((i-1)/4,2)  ; g5d(i,3) = cz(c)
+          d=it0+MOD((i-1)/8,2)  ; g5d(i,4) = ct(d)
+          e=iw0+MOD((i-1)/16,2) ; g5d(i,5) = cw(e)
+          g5d(i,6) = cv(a,b,c,d,e)
+        ENDDO
+        res(j) = lintc_((/x,y,z,t,w/),g5d) 
+      ENDDO
+      ret = .true.
+    END FUNCTION l5d_gen_ve
+  
+    ! HARD-CODED versions
+    ! -------------------
+  
+    FUNCTION hdcdl1d_gen_ve(x,cx,cv,locator,res) RESULT(ret)
+      !! Hard-coded 1D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd
+      INTEGER       :: ix0,ix1
+      INTEGER       :: n,j
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        IF (ix0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1 = ix0+1
+        xd = (x(j)-cx(ix0))/(cx(ix1)-cx(ix0))
+        res(j) = cv(ix0)*(1d0-xd)+cv(ix1)*xd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl1d_gen_ve
+  
+    FUNCTION hdcdl2d_gen_ve(x,y,cx,cy,cv,locator,res) RESULT(ret)
+      !! Hard-coded 2D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:)             :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd
+      REAL(kind=wp) :: f0,f1
+      INTEGER       :: ix0,iy0,ix1,iy1
+      INTEGER       :: n,j
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        IF (ix0 == 0 .OR. iy0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1 = ix0 + 1 ; iy1 = iy0 + 1
+        xd  = (x(j)-cx(ix0))/(cx(ix1)-cx(ix0))
+        yd  = (y(j)-cy(iy0))/(cy(iy1)-cy(iy0))
+        f0  = cv(ix0,iy0)*(1d0-xd)+cv(ix1,iy0)*xd
+        f1  = cv(ix0,iy1)*(1d0-xd)+cv(ix1,iy1)*xd
+        res(j) = f0*(1d0-yd)+f1*yd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl2d_gen_ve 
+  
+    FUNCTION hdcdl3d_gen_ve(x,y,z,cx,cy,cz,cv,locator,res) RESULT(ret)
+      !! Hard-coded 3D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:)           :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd
+      REAL(kind=wp) :: f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,ix1,iy1,iz1
+      INTEGER       :: n,j
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        iz0 = locator(z(j),cz)
+        IF (ix0 == 0 .OR. iy0 == 0 .OR. iz0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1
+        xd = (x(j)-cx(ix0))/(cx(ix1)-cx(ix0))
+        yd = (y(j)-cy(iy0))/(cy(iy1)-cy(iy0))
+        zd = (z(j)-cz(iz0))/(cz(iz1)-cz(iz0))
+  
+        f00 = cv(ix0,iy0,iz0)*(1d0-xd)+cv(ix1,iy0,iz0)*xd
+        f10 = cv(ix0,iy1,iz0)*(1d0-xd)+cv(ix1,iy1,iz0)*xd
+        f01 = cv(ix0,iy0,iz1)*(1d0-xd)+cv(ix1,iy0,iz1)*xd
+        f11 = cv(ix0,iy1,iz1)*(1d0-xd)+cv(ix1,iy1,iz1)*xd
+  
+        f0 = f00 *(1d0-yd)+f10*yd
+        f1 = f00 *(1d0-yd)+f10*yd
+  
+        res(j) = f0*(1d0-zd)+f1*zd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl3d_gen_ve
+  
+    FUNCTION hdcdl4d_gen_ve(x,y,z,t,cx,cy,cz,ct,cv,locator,res) RESULT(ret)
+      !! Hard-coded 4D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:)         :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td
+      REAL(kind=wp) :: f000,f100,f010,f110,f001,f101,f011,f111, &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,it0,ix1,iy1,iz1,it1
+      INTEGER       :: n,j
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        iz0 = locator(z(j),cz)
+        it0 = locator(t(j),ct)
+        IF (ix0 == 0 .OR. iy0 == 0 .OR. iz0 == 0 .OR. it0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 ; it1=it0+1
+        xd = (x(j)-cx(ix0))/(cx(ix1)-cx(ix0))
+        yd = (y(j)-cy(iy0))/(cy(iy1)-cy(iy0))
+        zd = (z(j)-cz(iz0))/(cz(iz1)-cz(iz0))
+        td = (t(j)-ct(it0))/(ct(it1)-ct(it0))
+  
+        f000 = cv(ix0,iy0,iz0,it0)*(1d0-xd)+cv(ix1,iy0,iz0,it0)*xd
+        f100 = cv(ix0,iy1,iz0,it0)*(1d0-xd)+cv(ix1,iy1,iz0,it0)*xd
+        f010 = cv(ix0,iy0,iz1,it0)*(1d0-xd)+cv(ix1,iy0,iz1,it0)*xd
+        f110 = cv(ix0,iy1,iz1,it0)*(1d0-xd)+cv(ix1,iy1,iz1,it0)*xd
+        f001 = cv(ix0,iy0,iz0,it1)*(1d0-xd)+cv(ix1,iy0,iz0,it1)*xd
+        f101 = cv(ix0,iy1,iz0,it1)*(1d0-xd)+cv(ix1,iy1,iz0,it1)*xd
+        f011 = cv(ix0,iy0,iz1,it1)*(1d0-xd)+cv(ix1,iy0,iz1,it1)*xd
+        f111 = cv(ix0,iy1,iz1,it1)*(1d0-xd)+cv(ix1,iy1,iz1,it1)*xd
+  
+        f00 = f000*(1d0-yd)+f100*yd
+        f10 = f010*(1d0-yd)+f110*yd 
+        f01 = f001*(1d0-yd)+f101*yd 
+        f11 = f011*(1d0-yd)+f111*yd 
+  
+        f0 = f00 *(1d0-zd)+f10*zd
+        f1 = f01 *(1d0-zd)+f11*zd
+    
+        res(j) = f0*(1d0-td)+f1*td
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl4d_gen_ve
+  
+    FUNCTION hdcdl5d_gen_ve(x,y,z,t,w,cx,cy,cz,ct,cw,cv,locator,res) RESULT(ret)
+      !! Hard-coded 5D linear interpolation (vector)
+      !!
+      !! @warning
+      !! On error, __res__ output vector is undefined (i.e. not allocated).
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: x       !! X coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: y       !! Y coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: z       !! Z coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: t       !! T coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: w       !! W coordinate of the points to interpolate
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cx      !! Tabulated function X coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cy      !! Tabulated function Y coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cz      !! Tabulated function Z coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: ct      !! Tabulated function T coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:)               :: cw      !! Tabulated function W coordinates
+      REAL(kind=wp), INTENT(in), DIMENSION(:,:,:,:,:)       :: cv      !! Tabulated function values
+      REAL(kind=wp), INTENT(out), DIMENSION(:), ALLOCATABLE :: res     !! Interpolated values
+      PROCEDURE(locate)                                     :: locator !! Locator function
+      LOGICAL :: ret                                                   !! .true. on success, .false. otherwise
+      REAL(kind=wp) :: xd,yd,zd,td,wd
+      REAL(kind=wp) :: f0000,f1000,f0100,f1100,f0010,f1010,f0110,f1110, & 
+                       f0001,f1001,f0101,f1101,f0011,f1011,f0111,f1111, &
+                       f000,f100,f010,f110,f001,f101,f011,f111,         &
+                       f00,f10,f01,f11,f0,f1
+      INTEGER       :: ix0,iy0,iz0,it0,iw0,ix1,iy1,iz1,it1,iw1
+      INTEGER       :: n,j
+      ret = .false.
+      n = SIZE(x) ; ALLOCATE(res(n))
+      DO j=1,n 
+        ix0 = locator(x(j),cx)
+        iy0 = locator(y(j),cy)
+        iz0 = locator(z(j),cz)
+        it0 = locator(t(j),ct)
+        iw0 = locator(w(j),cw)
+        IF (ix0 == 0 .OR. iy0 == 0 .OR. iz0 == 0 .OR. it0 == 0 .OR. iw0 == 0) THEN
+          DEALLOCATE(res) ; RETURN
+        ENDIF
+        ix1=ix0+1 ; iy1=iy0+1 ; iz1=iz0+1 ; 
+        xd = (x(j)-cx(ix0))/(cx(ix1)-cx(ix0))
+        yd = (y(j)-cy(iy0))/(cy(iy1)-cy(iy0))
+        zd = (z(j)-cz(iz0))/(cz(iz1)-cz(iz0))
+        td = (t(j)-ct(it0))/(ct(it1)-ct(it0))
+        wd = (w(j)-cw(it0))/(cw(it1)-cw(it0))
+  
+        f0000 = cv(ix0,iy0,iz0,it0,iw0)*(1d0-xd)+cv(ix1,iy0,iz0,it0,iw0)*xd
+        f1000 = cv(ix0,iy1,iz0,it0,iw0)*(1d0-xd)+cv(ix0,iy1,iz0,it0,iw0)*xd
+        f0100 = cv(ix0,iy0,iz1,it0,iw0)*(1d0-xd)+cv(ix1,iy0,iz1,it0,iw0)*xd
+        f1100 = cv(ix0,iy1,iz1,it0,iw0)*(1d0-xd)+cv(ix1,iy1,iz1,it0,iw0)*xd
+        f0010 = cv(ix0,iy0,iz0,it1,iw0)*(1d0-xd)+cv(ix1,iy0,iz0,it1,iw0)*xd
+        f1010 = cv(ix0,iy1,iz0,it1,iw0)*(1d0-xd)+cv(ix1,iy1,iz0,it1,iw0)*xd
+        f0110 = cv(ix0,iy0,iz1,it1,iw0)*(1d0-xd)+cv(ix1,iy0,iz1,it1,iw0)*xd
+        f1110 = cv(ix0,iy1,iz1,it1,iw0)*(1d0-xd)+cv(ix1,iy1,iz1,it1,iw0)*xd
+        f0001 = cv(ix0,iy0,iz0,it0,iw1)*(1d0-xd)+cv(ix1,iy0,iz0,it0,iw1)*xd
+        f1001 = cv(ix0,iy1,iz0,it0,iw1)*(1d0-xd)+cv(ix0,iy1,iz0,it0,iw1)*xd
+        f0101 = cv(ix0,iy0,iz1,it0,iw1)*(1d0-xd)+cv(ix1,iy0,iz1,it0,iw1)*xd
+        f1101 = cv(ix0,iy1,iz1,it0,iw1)*(1d0-xd)+cv(ix1,iy1,iz1,it0,iw1)*xd
+        f0011 = cv(ix0,iy0,iz0,it1,iw1)*(1d0-xd)+cv(ix1,iy0,iz0,it1,iw1)*xd
+        f1011 = cv(ix0,iy1,iz0,it1,iw1)*(1d0-xd)+cv(ix1,iy1,iz0,it1,iw1)*xd
+        f0111 = cv(ix0,iy0,iz1,it1,iw1)*(1d0-xd)+cv(ix1,iy0,iz1,it1,iw1)*xd
+        f1111 = cv(ix0,iy1,iz1,it1,iw1)*(1d0-xd)+cv(ix1,iy1,iz1,it1,iw1)*xd
+  
+        f000 = f0000*(1d0-yd) + f1000*yd
+        f100 = f0100*(1d0-yd) + f1100*yd
+        f010 = f0010*(1d0-yd) + f1010*yd
+        f110 = f0110*(1d0-yd) + f1110*yd
+        f101 = f0101*(1d0-yd) + f1101*yd
+        f011 = f0011*(1d0-yd) + f1011*yd
+        f111 = f0111*(1d0-yd) + f1111*yd
+  
+        f00 = f000*(1d0-zd)+f100*zd
+        f10 = f010*(1d0-zd)+f110*zd 
+        f01 = f001*(1d0-zd)+f101*zd 
+        f11 = f011*(1d0-zd)+f111*zd 
+  
+        f0 = f00 *(1d0-td)+f10*td
+        f1 = f01 *(1d0-td)+f11*td
+  
+        res(j) = f0*(1d0-wd)+f1*wd
+      ENDDO
+      ret = .true.
+    END FUNCTION hdcdl5d_gen_ve
+  
+  END MODULE LINT_GEN  
Index: trunk/LMDZ.PLUTO/libf/muphypluto/lint_locators.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/lint_locators.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/lint_locators.F90	(revision 3560)
@@ -0,0 +1,158 @@
+! Copyright (c) Université de Reims Champagnne-Ardenne (2010-2015)
+! Contributor: Jérémie Burgalat (jeremie.burgalat@univ-reims.fr)
+! 
+! This software is a computer program whose purpose is to compute multi-variate
+! linear interpolation.
+! 
+! This software is governed by the CeCILL-B license under French law and
+! abiding by the rules of distribution of free software.  You can  use, 
+! modify and/ or redistribute the software under the terms of the CeCILL-B
+! license as circulated by CEA, CNRS and INRIA at the following URL
+! "http://www.cecill.info". 
+! 
+! As a counterpart to the access to the source code and  rights to copy,
+! modify and redistribute granted by the license, users are provided only
+! with a limited warranty  and the software's author,  the holder of the
+! economic rights,  and the successive licensors  have only  limited
+! liability. 
+! 
+! In this respect, the user's attention is drawn to the risks associated
+! with loading,  using,  modifying and/or developing or reproducing the
+! software by the user in light of its specific status of free software,
+! that may mean  that it is complicated to manipulate,  and  that  also
+! therefore means  that it is reserved for developers  and  experienced
+! professionals having in-depth computer knowledge. Users are therefore
+! encouraged to load and test the software's suitability as regards their
+! requirements in conditions enabling the security of their systems and/or 
+! data to be ensured and,  more generally, to use and operate it in the 
+! same conditions as regards security. 
+! 
+! The fact that you are presently reading this means that you have had
+! knowledge of the CeCILL-B license and that you accept its terms.
+
+!! File:    lint_locators.F90
+!! Summary: Locator functions definition file
+!! Author:  J. Burgalat 
+!! Date:    2010-2014
+
+MODULE LINT_LOCATORS
+    !! Locator functions definition module.
+    !! 
+    !! This module defines some locator functions which search value in ordered vectors (with no 
+    !! duplicates). Two algorithms are provided :
+    !!
+    !! - The binary search algorithm.
+    !! - A simple algorithm for direct access assuming searched vector is regularly
+    !!   spaced.
+    !!
+    !! All these functions satisfy the interface required for the __locator__ argument of linear 
+    !! interpolation functions.
+    USE LINT_PREC
+    IMPLICIT NONE
+  
+    PRIVATE :: wp ! from LINT_PREC
+  
+    CONTAINS
+  
+    FUNCTION locate(value,vector) RESULT(res)
+      !! Basic binary search algorithm
+      REAL(kind=wp), INTENT(in)               :: value  !! Value to search
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: vector !! Input vector to search in
+      INTEGER :: res
+        !! Lowest subscript of the nearest value or __0__ if value is out of range.
+      REAL(kind=wp) :: l,u
+      INTEGER       :: jl,jm,ju, nv
+      res = 0 ; nv = SIZE(vector) ; l = vector(1) ; u = vector(nv)
+      ! Check for out of range value
+      IF ((value>l.AND.value>u).OR.(value<l.AND.value<u)) RETURN
+      ! Search in the array
+      jl=0 ; ju=nv+1
+      DO WHILE (ju-jl > 1)
+        res=(ju+jl)/2 
+        IF (res == 0) RETURN   ! should never happen
+        IF((u>=l).EQV.(value >= vector(res))) THEN
+          jl=res
+        ELSE
+          ju=res
+        ENDIF
+      ENDDO
+      res = jl
+      RETURN
+    END FUNCTION locate
+  
+    FUNCTION locate_ext(value,vector) RESULT(res)
+      !! Basic binary search algorithm with extrapolation
+      !! 
+      !! The function performs the same computation than [[locators(module):locate(function)]] except
+      !! that if __value__ is out of range, __1__ or __SIZE(vector)-1__ is returned with respect 
+      !! to the nearest __vector__'s extremum.
+      REAL(kind=wp), INTENT(in)               :: value  !! Value to search
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: vector !! Input vector to search in
+      INTEGER :: res                                    !! Lowest subscript of the nearest value 
+      REAL(kind=wp) :: l,u
+      INTEGER       :: jl,jm,ju, nv
+      nv = SIZE(vector) ; l = vector(1) ; u= vector(nv)
+      ! Check for out of range value
+      IF ((value>l.AND.value>u).OR.(value<l.AND.value<u)) THEN 
+        res=1 ; IF (ABS(l-value) > ABS(u-value)) res=nv-1
+        RETURN
+      ENDIF
+      ! Search in the array
+      jl=0 ; ju=nv+1
+      DO WHILE (ju-jl > 1)
+        res=(ju+jl)/2
+        IF (res == 0) RETURN   ! should never happen
+        IF((u>=l).EQV.(value >= vector(res))) THEN
+          jl=res
+        ELSE
+          ju=res
+        ENDIF
+      ENDDO
+      res = jl
+      RETURN
+    END FUNCTION locate_ext
+  
+    FUNCTION locate_reg(value,vector) RESULT(res)
+      !! Direct subscript access locator method
+      !! 
+      !! The function assumes __vector__ is regularly spaced and computes directly
+      !! the lowest subscript using __vector__ step increment.
+      REAL(kind=wp), INTENT(in)               :: value  !! Value to search
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: vector !! Input vector to search in
+      INTEGER :: res                                    
+        !! Lowest subscript of the nearest value or __0__ if value is out of range.
+      INTEGER       :: nv
+      REAL(kind=wp) :: step,l,u
+      res = 0 
+      nv = SIZE(vector) 
+      l = vector(1) ; u= vector(nv)
+      IF ((value>l.AND.value>u).OR.(value<l.AND.value<u)) RETURN 
+      step = (vector(nv)-vector(1))/(nv-1.)
+      res = MIN(1+FLOOR((value-l)/step),nv-1)
+      RETURN
+    END FUNCTION locate_reg 
+  
+    FUNCTION locate_reg_ext(value,vector) RESULT(res)
+      !! Direct subscript access locator method with extrapolation
+      !!  
+      !! The function performs the same computation than [[locators(module):locate_reg(function)]] 
+      !! except that if __value__ is out of range, __1__ or __SIZE(vector)-1__ is returned 
+      !! with respect to the nearest __vector__'s extremum.
+      REAL(kind=wp), INTENT(in)               :: value  !! Value to search
+      REAL(kind=wp), INTENT(in), DIMENSION(:) :: vector !! Input vector to search in
+      INTEGER :: res                                    !! Lowest subscript of the nearest value 
+      INTEGER       :: nv
+      REAL(kind=wp) :: step,l,u
+      res = 0 
+      nv = SIZE(vector) 
+      l = vector(1) ; u= vector(nv)
+      IF ((value>l.AND.value>u).OR.(value<l.AND.value<u)) THEN 
+        res=1 ; IF (ABS(l-value) > ABS(u-value)) res = nv -1
+        RETURN
+      ENDIF 
+      step = (vector(nv)-vector(1))/(nv-1.)
+      res = MIN(1+FLOOR((value-l)/step),nv-1)
+      RETURN
+    END FUNCTION locate_reg_ext 
+  
+  END MODULE LINT_LOCATORS  
Index: trunk/LMDZ.PLUTO/libf/muphypluto/lint_prec.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/lint_prec.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/lint_prec.F90	(revision 3560)
@@ -0,0 +1,61 @@
+! Copyright (c) Université de Reims Champagnne-Ardenne (2010-2015)
+! Contributor: Jérémie Burgalat (jeremie.burgalat@univ-reims.fr)
+! 
+! This software is a computer program whose purpose is to compute multi-variate
+! linear interpolation.
+! 
+! This software is governed by the CeCILL-B license under French law and
+! abiding by the rules of distribution of free software.  You can  use, 
+! modify and/ or redistribute the software under the terms of the CeCILL-B
+! license as circulated by CEA, CNRS and INRIA at the following URL
+! "http://www.cecill.info". 
+! 
+! As a counterpart to the access to the source code and  rights to copy,
+! modify and redistribute granted by the license, users are provided only
+! with a limited warranty  and the software's author,  the holder of the
+! economic rights,  and the successive licensors  have only  limited
+! liability. 
+! 
+! In this respect, the user's attention is drawn to the risks associated
+! with loading,  using,  modifying and/or developing or reproducing the
+! software by the user in light of its specific status of free software,
+! that may mean  that it is complicated to manipulate,  and  that  also
+! therefore means  that it is reserved for developers  and  experienced
+! professionals having in-depth computer knowledge. Users are therefore
+! encouraged to load and test the software's suitability as regards their
+! requirements in conditions enabling the security of their systems and/or 
+! data to be ensured and,  more generally, to use and operate it in the 
+! same conditions as regards security. 
+! 
+! The fact that you are presently reading this means that you have had
+! knowledge of the CeCILL-B license and that you accept its terms.
+
+!! File:    lint_prec.F90
+!! Summary: Library floating point computations precision module.
+!! Author:  J. Burgalat
+!! Date:    2010-2014
+
+#undef PREC
+#define PREC 64
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+MODULE LINT_PREC
+  !! Library floating point computations precision module.
+  !!
+  !! This module only defines a single variable [[lint_prec(module):wp(variable)]] which 
+  !! set the kind of floating point value used in all other parts of the library.
+  IMPLICIT NONE
+
+#if (PREC == 32)
+  !> Size of floating point variables in the library (single).
+  INTEGER, PUBLIC, PARAMETER :: wp = SELECTED_REAL_KIND(p=6)  ! 32 bits
+#elif (PREC == 64)
+  !> Size of floating point variables in the library (double).
+  INTEGER, PUBLIC, PARAMETER :: wp = SELECTED_REAL_KIND(p=15) ! 64 bits 
+#elif (PREC == 80)
+  !> Size of floating point variables in the library (extended-double).
+  INTEGER, PUBLIC, PARAMETER :: wp = SELECTED_REAL_KIND(p=18) ! 80 bits
+#endif
+END MODULE LINT_PREC
Index: trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_globals.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_globals.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_globals.F90	(revision 3560)
@@ -0,0 +1,1074 @@
+MODULE MP2M_GLOBALS
+    !============================================================================
+    !
+    !     Purpose
+    !     -------
+    !     Parameters and global variables module.
+    !     Defines and initialize all the parameters and global variables that are
+    !     common to all other modules of the library.
+    !
+    !     It is separated in two parts :
+    !        - Main parameters and global saved variables. Most of these variables should be initialized
+    !          once and should hold the same value during run-time. These variables are completly public and
+    !          initialized by [[mm_globals(module):mm_global_init(interface)]] method.
+    !        - The second part defines a set of vectors that defines the vertical structure of the atmosphere.
+    !          Each time a new atmospheric column has to be computed (either on a new timestep or on a new couple
+    !          of longitude/latitude), these vectors should be intialized with new values by calling
+    !          [[mm_globals(module):mm_column_init(function)]] method.
+    !
+    !     @note
+    !     All the vectors that represent the vertical structure of the atmosphere (altitude, pressure and
+    !     temperature...) are oriented from the __TOP__ of the atmosphere to the __GROUND__.
+    !
+    !     Global variables overview:
+    !        - Protected variables
+    !        - Control flags
+    !        - Related free parameters
+    !        - Physical constants
+    !        - Free parameters
+    !        - Miscellaneous parameters
+    !        - Vertical structure part
+    !
+    !     The module also contains thirteen methods:
+    !        - mm_global_init_0
+    !        - mm_global_init_1
+    !        - mm_column_init
+    !        - mm_aerosols_init
+    !        - mm_alpha_s, mm_alpha_f
+    !        - mm_effg
+    !        - mm_set_moments_thresholds
+    !        - mm_get_rcs, mm_get_rcf
+    !        - mm_dump_parameters
+    !        - check_r1, check_l1, check_i1, check_s1
+    !
+    !     Authors
+    !     -------
+    !     B. de Batz de Trenquelléon, J. Burgalat (11/2024)
+    !
+    !============================================================================
+    USE MP2M_MPREC
+    ! From lint
+    USE LINT_DATASETS
+    ! From swift
+    USE SWIFT_CFGPARSE
+    USE SWIFT_STRING_OP
+    USE SWIFT_ERRORS
+    IMPLICIT NONE
+
+    PUBLIC
+
+    PRIVATE :: check_r1,check_i1,check_l1,check_s1
+
+    ! ~~~~~~~~~~~~~~~~~~~
+    ! Protected variables
+    ! ~~~~~~~~~~~~~~~~~~~
+    ! The following variables are read-only outside this module.
+    ! One must call the afferent subroutine to update them.
+    
+    ! Initialization control flags (cannot be updated)
+    PROTECTED :: mm_ini,mm_ini_col,mm_ini_aer
+    ! Model parameters (mm_global_init)
+    PROTECTED :: mm_dt,mm_rhoaer,mm_df,mm_rm,mm_haze_prod_pCH4,mm_p_prod,mm_rc_prod,mm_tx_prod,mm_rpla,mm_g0,mm_rb2ra
+    ! Atmospheric vertical structure (mm_column_init)
+    PROTECTED :: mm_nla,mm_nle,mm_zlay,mm_zlev,mm_play,mm_plev,mm_temp,mm_rhoair,mm_btemp,mm_dzlev,mm_dzlay
+    ! Moments parameters (mm_aerosols_init)
+    PROTECTED :: mm_m0aer_s, mm_m3aer_s, mm_m0aer_f, mm_m3aer_f
+    ! Moments parameters (derived, are updated with moments parameters)
+    PROTECTED :: mm_rcs, mm_rcf
+    ! Thresholds parameters
+    PROTECTED :: mm_m0as_min, mm_m3as_min, mm_rcs_min, mm_m0af_min, mm_m3af_min, mm_rcf_min
+
+    ! ~~~~~~~~~~~~~
+    ! Control flags
+    ! ~~~~~~~~~~~~~
+    ! Enable/Disable haze production.
+    LOGICAL, SAVE :: mm_w_haze_prod = .true.
+    ! Enable/Disable haze production from CH4 photolysis.
+    LOGICAL, SAVE :: mm_haze_prod_pCH4 = .true.
+    ! Enable/Disable haze sedimentation.
+    LOGICAL, SAVE :: mm_w_haze_sed = .true.
+    ! Enable/Disable haze coagulation.
+    LOGICAL, SAVE :: mm_w_haze_coag = .true.
+    ! Force all aerosols moments to fall at M0 settling velocity.
+    LOGICAL, SAVE :: mm_wsed_m0 = .false.
+    ! Force all aerosols moments to fall at M3 settling velocity.
+    LOGICAL, SAVE :: mm_wsed_m3 = .false.
+    ! Enable/Disable spherical probability transfert.
+    LOGICAL, SAVE :: mm_w_ps2s = .true.
+    ! Enable/Disable aerosol electric charge correction.
+    LOGICAL, SAVE :: mm_w_qe   = .true.
+
+    ! Enable/Disable QnD debug mode (can be used for devel).
+    LOGICAL, SAVE :: mm_debug = .false.
+    ! Enable/Disable log mode (for configuration only).
+    LOGICAL, SAVE :: mm_log   = .false.
+    ! Enable/Disable effective G for computations.
+    LOGICAL, SAVE :: mm_use_effg = .true.
+
+    ! Initialization control flag [[mm_globals(module):mm_global_init(interface)]].
+    LOGICAL, PUBLIC, SAVE :: mm_ini     = .false.
+    ! Initialization control flag [[mm_globals(module):mm_column_init(function)]].
+    LOGICAL, PUBLIC, SAVE :: mm_ini_col = .false.
+    ! Initialization control flag [[mm_globals(module):mm_aerosols_init(function)]].
+    LOGICAL, PUBLIC, SAVE :: mm_ini_aer = .false.
+  
+    ! ~~~~~~~~~~~~~~~~~~~~~~~
+    ! Related free parameters
+    ! ~~~~~~~~~~~~~~~~~~~~~~~
+    ! No mode interaction for coagulation (i.e. no coagulation at all).
+    INTEGER, PARAMETER :: mm_coag_no = 0
+    ! SS mode interaction for coagulation.
+    INTEGER, PARAMETER :: mm_coag_ss = 1
+    ! SF mode interaction for coagulation.
+    INTEGER, PARAMETER :: mm_coag_sf = 2
+    ! FF mode interaction for coagulation.
+    INTEGER, PARAMETER :: mm_coag_ff = 4
+    ! Default interactions to activate (all by default).
+    INTEGER, SAVE      :: mm_coag_choice = mm_coag_ss+mm_coag_sf+mm_coag_ff
+
+    ! ~~~~~~~~~~~~~~~~~~
+    ! Physical constants
+    ! ~~~~~~~~~~~~~~~~~~
+    ! Pi number.
+    REAL(kind=mm_wp), PARAMETER :: mm_pi = 4._mm_wp*atan(1._mm_wp)
+    ! Avogadro number (mol-1).
+    REAL(kind=mm_wp), PARAMETER :: mm_navo = 6.0221367e23_mm_wp
+    ! Boltzmann constant (J.K-1).
+    REAL(kind=mm_wp), PARAMETER :: mm_kboltz = 1.3806488e-23_mm_wp
+    ! Perfect gas constant (J.mol-1.K-1).
+    REAL(kind=mm_wp), PARAMETER :: mm_rgas = mm_kboltz * mm_navo
+    ! Approximated slip-flow correction coefficient.
+    REAL(kind=mm_wp), PARAMETER :: mm_akn = 1.591_mm_wp
+
+    ! ~~~~~~~~~~~~~~~
+    ! Free parameters
+    ! ~~~~~~~~~~~~~~~
+    ! Spherical aerosol production pressure level (Pa).
+    REAL(kind=mm_wp), SAVE :: mm_p_prod = 1.e-2_mm_wp
+    ! Spherical aerosol production rate (kg.m-2.s-1).
+    REAL(kind=mm_wp), SAVE :: mm_tx_prod = 9.8e-14_mm_wp
+    ! Spherical aerosol equivalent radius production (m)
+    REAL(kind=mm_wp), SAVE :: mm_rc_prod = 1.e-9_mm_wp
+    ! Monomer radius (m).
+    REAL(kind=mm_wp), SAVE :: mm_rm = 1.e-8_mm_wp  
+    ! Fractal dimension of fractal aerosols.
+    REAL(kind=mm_wp), SAVE :: mm_df = 2._mm_wp
+    ! Aerosol density (kg.m-3).
+    REAL(kind=mm_wp), SAVE :: mm_rhoaer = 8.e2_mm_wp
+
+    ! Total number of aerosols minimum threshold for the spherical mode.
+    REAL(kind=mm_wp), SAVE :: mm_m0as_min = 1.e-8_mm_wp
+    ! Total volume of aerosols minimum threshold for the spherical mode.
+    REAL(kind=mm_wp), SAVE :: mm_m3as_min = 1.e-35_mm_wp
+    ! Characteristic radius minimum threshold for the spherical mode.
+    REAL(kind=mm_wp), SAVE :: mm_rcs_min = 1.e-9_mm_wp
+    ! Total number of aerosols minimum threshold for the fractal mode.
+    REAL(kind=mm_wp), SAVE :: mm_m0af_min = 1.e-8_mm_wp
+    ! Total volume of aerosols minimum threshold for the fractal mode.
+    REAL(kind=mm_wp), SAVE :: mm_m3af_min = 1.e-35_mm_wp
+    ! Characteristic radius minimum threshold for the fractal mode.
+    REAL(kind=mm_wp), SAVE :: mm_rcf_min = 1.e-9_mm_wp
+  
+    ! Planet radius (m) and gravity acceleration (m.s-2).
+    ! WARNING: initialization for Pluto.
+    REAL(kind=mm_wp), SAVE :: mm_rpla     = 1187000._mm_wp
+    REAL(kind=mm_wp), SAVE :: mm_g0       = 0.617_mm_wp 
+    ! Air molecules mean radius (m), molar mass (kg.mol-1), and molecular mass (kg).
+    ! WARNING: initialization for N2.
+    REAL(kind=mm_wp), SAVE :: mm_air_rad  = 1.75e-10_mm_wp
+    REAL(kind=mm_wp), SAVE :: mm_air_mmol = 28.e-3_mm_wp
+    REAL(kind=mm_wp), SAVE :: mm_air_mmas = 28.e-3_mm_wp / mm_navo
+    ! Microphysical time-step (s).
+    REAL(kind=mm_wp), SAVE :: mm_dt       = 180._mm_wp
+    
+    ! ~~~~~~~~~~~~~~~~~~~~~~~~
+    ! Miscellaneous parameters
+    ! ~~~~~~~~~~~~~~~~~~~~~~~~
+    
+    ! Bulk to apparent radius
+    ! ~~~~~~~~~~~~~~~~~~~~~~~
+    ! Bulk to apparent radius conversion pre-factor (m^X).
+    !
+    ! With r_a = r_b^(3/Df) . r_m^((Df-3)/(Df))
+    ! Then rb2ra = r_m^((Df-3)/(Df))
+    REAL(kind=mm_wp), SAVE :: mm_rb2ra = 1._mm_wp
+
+    ! Inter-moment relation
+    ! ~~~~~~~~~~~~~~~~~~~~~
+    ! Alpha function parameters. 
+    ! It stores the parameters of the inter-moments relation functions.
+    !
+    ! The inter-moments relation function is represented by the sum of exponential quadratic expressions:
+    ! alpha(k) = Sum_{i=1}^{n} exp(a_i.k^2 + bi.k^2 + c_i) 
+    TYPE, PUBLIC :: aprm
+      ! Quadratic coefficients of the quadratic expressions.
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: a
+      ! Linear coefficients of the quadratic expressions.
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: b
+      ! Free term of the quadratic expressions.
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: c
+    END TYPE
+    ! Inter-moment relation set of parameters for the spherical mode.
+    TYPE(aprm), PUBLIC, SAVE :: mm_asp
+    ! Inter-moment relation set of parameters for the fractal mode.
+    TYPE(aprm), PUBLIC, SAVE :: mm_afp
+
+    ! Transfert probabilities (S --> F)
+    ! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    ! Data set for linear interpolation of transfert probability (M0/CO).
+    TYPE(dset1d), PUBLIC, SAVE, TARGET :: mm_pco0p
+    ! Data set for linear interpolation of transfert probability (M3/CO).
+    TYPE(dset1d), PUBLIC, SAVE, TARGET :: mm_pco3p
+    ! Data set for linear interpolation of transfert probability (M0/FM).
+    TYPE(dset1d), PUBLIC, SAVE, TARGET :: mm_pfm0p
+    ! Data set for linear interpolation of transfert probability (M3/FM). 
+    TYPE(dset1d), PUBLIC, SAVE, TARGET :: mm_pfm3p
+  
+    ! Mean electric correction
+    ! ~~~~~~~~~~~~~~~~~~~~~~~~
+    ! Data set for Q_SF(M0).
+    TYPE(dset2d), PUBLIC, SAVE, TARGET             :: mm_qbsf0
+    ! Extended values of [[mm_gcm(module):mm_qbsf0(variable)]] dataset.
+    REAL(kind=mm_wp), PUBLIC, SAVE, DIMENSION(2,2) :: mm_qbsf0_e
+    ! Data set for Q_SF(M3).
+    TYPE(dset2d), PUBLIC, SAVE, TARGET             :: mm_qbsf3
+    ! Extended values of [[mm_gcm(module):mm_qbsf3(variable)]] dataset.
+    REAL(kind=mm_wp), PUBLIC, SAVE, DIMENSION(2,2) :: mm_qbsf3_e
+    ! Data set for Q_FF(M0).
+    TYPE(dset2d), PUBLIC, SAVE, TARGET             :: mm_qbff0
+    ! Extended values of [[mm_gcm(module):mm_qbff0(variable)]] dataset.
+    REAL(kind=mm_wp), PUBLIC, SAVE, DIMENSION(2,2) :: mm_qbff0_e
+
+    ! btk coefficients
+    ! ~~~~~~~~~~~~~~~~
+    ! Coefficients for Free-molecular regime kernel approximation (b_0(t)).
+    REAL(kind=mm_wp), PUBLIC, SAVE, DIMENSION(5) :: mm_bt0 = (/1._mm_wp,1._mm_wp,1._mm_wp,1._mm_wp,1._mm_wp/)
+    ! Coefficients for Free-molecular regime kernel approximation (b_3(t)).
+    REAL(kind=mm_wp), PUBLIC, SAVE, DIMENSION(5) :: mm_bt3 = (/1._mm_wp,1._mm_wp,1._mm_wp,1._mm_wp,1._mm_wp/)
+
+    ! ~~~~~~~~~~~~~~~~~~~~~~~
+    ! Vertical structure part
+    ! ~~~~~~~~~~~~~~~~~~~~~~~
+    ! Number of vertical layers.
+    INTEGER, SAVE :: mm_nla = -1
+    ! Number of vertical levels.
+    INTEGER, SAVE :: mm_nle = -1
+  
+    ! Altitude layers (m).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_zlay
+    ! Altitude levels (m).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_zlev
+    !> Pressure layers (Pa).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_play
+    ! Pressure levels (Pa).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_plev
+    ! Temperature vertical profile (K).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_temp
+    ! Air density vertical profile (kg.m-3).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_rhoair
+    ! Temperature vertical profil at interfaces (K).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_btemp
+
+    ! Atmospheric levels thickness (m).
+    ! @note: __mm_dzlev__ is defined on the total number of layers and actually
+    ! corresponds to the thickness of a given layer.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_dzlev
+    ! Atmospheric layers thickness (m).
+    ! @note: __mm_dzlay__ is defined on the total number of layers. The last
+    ! value of __mm_dzlay__ is set to twice the altitude of the ground layer.
+    ! This value corresponds to the thickness between the center of the
+    ! __GROUND__ layer and below the surface.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_dzlay
+
+    ! Spherical mode - 0th order moment (m-3).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m0aer_s
+    ! Spherical mode - 3rd order moment (m3.m-3).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m3aer_s
+    ! Spherical mode - characteristic radius (m).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_rcs
+    ! Fractal mode - 0th order moment (m-3).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m0aer_f
+    ! Fractal mode - 3rd order moment (m3.m-3).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m3aer_f
+    ! Fractal mode - characteristic radius (m).
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_rcf
+
+    ! Spherical aerosol precipitation (kg.m-2).
+    REAL(kind=mm_wp), SAVE :: mm_aers_prec = 0._mm_wp
+    ! Fractal aerosol precipitation (kg.m-2).
+    REAL(kind=mm_wp), SAVE :: mm_aerf_prec = 0._mm_wp
+  
+    ! Spherical mode (M0) settling velocity (m.s-1).
+    ! @note: This variable is always negative.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m0as_vsed
+    ! Spherical mode (M3) settling velocity (m.s-1).
+    ! @note: This variable is always negative.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m3as_vsed
+    ! Fractal mode (M0) settling velocity (m.s-1).
+    ! @note: This variable is always negative.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m0af_vsed
+    ! Fractal mode (M3) settling velocity (m.s-1).
+    ! @note: This variable is always negative.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_m3af_vsed
+
+    ! Spherical aerosol mass fluxes (kg.m-2.s-1).
+    ! @note: This variable is always negative.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_aer_s_flux
+    ! Fractal aerosol mass fluxes (kg.m-2.s-1).
+    ! @note: This variable is always negative.
+    REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE, SAVE :: mm_aer_f_flux
+
+    ! All variables related to column computations should be private to each thread
+    !$OMP THREADPRIVATE(mm_ini_col,mm_ini_aer)
+    !$OMP THREADPRIVATE(mm_zlay,mm_zlev,mm_play,mm_plev,mm_temp,mm_rhoair,mm_btemp,mm_dzlev,mm_dzlay)
+    !$OMP THREADPRIVATE(mm_m0aer_s,mm_m3aer_s,mm_m0aer_f,mm_m3aer_f)
+    !$OMP THREADPRIVATE(mm_rcs,mm_rcf)
+    !$OMP THREADPRIVATE(mm_m0as_vsed,mm_m3as_vsed,mm_m0af_vsed,mm_m3af_vsed)
+    !$OMP THREADPRIVATE(mm_aer_s_flux,mm_aer_f_flux)
+    !$OMP THREADPRIVATE(mm_m0as_min,mm_m3as_min,mm_rcs_min,mm_m0af_min,mm_m3af_min,mm_rcf_min)
+    !$OMP THREADPRIVATE(mm_nla,mm_nle)
+
+    ! Interface to global initialization.
+    ! The method performs the global initialization of the model.
+    INTERFACE mm_global_init
+      MODULE PROCEDURE mm_global_init_0,mm_global_init_1
+    END INTERFACE mm_global_init
+  
+    ! Check an option from the configuration system.
+    ! The method checks for an option in the configuration system and optionally
+    ! set a default value if the option is not found. This is an overloaded method
+    ! that can take in input either a floating point, integer, logical or string
+    ! option value.
+    INTERFACE mm_check_opt
+      MODULE PROCEDURE check_r1,check_i1,check_l1,check_s1
+    END INTERFACE mm_check_opt
+
+
+    CONTAINS
+
+
+    !============================================================================
+    !                      INITIALIZATION METHODS
+    !============================================================================
+
+    FUNCTION mm_global_init_0(dt,df,rm,rho_aer,haze_prod_pCH4,p_prod,tx_prod,rc_prod,&
+                              rplanet,g0,air_rad,air_mmol,                           &
+                              coag_interactions,w_haze_prod,w_haze_sed,w_haze_coag,  &
+                              force_wsed_to_m0,force_wsed_to_m3,                     &
+                              m0as_min,rcs_min,m0af_min,rcf_min,debug) RESULT(err)
+      !! Initialize global parameters of the model.
+      !!
+      !! The function initializes all the global parameters of the model from direct input.
+      !! Store input values in global variables
+      !!
+      !! @note
+      !! If the method fails to initialize parameters the model should be aborted as the
+      !! global variables of the model will not be correctly setup.
+      !!
+      ! Microphysical timestep (s).
+      REAL(kind=mm_wp), INTENT(in) :: dt
+      ! Fractal dimension of fractal aerosol (-).
+      REAL(kind=mm_wp), INTENT(in) :: df
+      ! Monomer radius (m).
+      REAL(kind=mm_wp), INTENT(in) :: rm
+      ! Aerosol density(kg.m-3).
+      REAL(kind=mm_wp), INTENT(in) :: rho_aer
+      ! Enable/Disable production from CH4 photolysis.
+      LOGICAL, INTENT(in)          :: haze_prod_pCH4
+      ! Aerosol production pressure level (Pa).
+      REAL(kind=mm_wp), INTENT(in) :: p_prod
+      ! Spherical aerosol production rate kg.m-2.s-1).
+      REAL(kind=mm_wp), INTENT(in) :: tx_prod
+      ! Spherical aerosol characteristic radius of production (m).
+      REAL(kind=mm_wp), INTENT(in) :: rc_prod
+      ! Planet radius (m).
+      REAL(kind=mm_wp), INTENT(in) :: rplanet
+      ! Planet gravity acceleration at ground level (m.s-2).
+      REAL(kind=mm_wp), INTENT(in) :: g0
+      ! Radius of air molecules (m).
+      REAL(kind=mm_wp), INTENT(in) :: air_rad
+      ! Molar mass of air molecules (kg.mol-1).
+      REAL(kind=mm_wp), INTENT(in) :: air_mmol
+
+      ! Coagulation interactions process control flag.
+      INTEGER, INTENT(in) :: coag_interactions
+      ! Haze production process control flag.
+      LOGICAL, INTENT(in) :: w_haze_prod
+      ! Haze sedimentation process control flag.
+      LOGICAL, INTENT(in) :: w_haze_sed
+      ! Haze coagulation process control flag.
+      LOGICAL, INTENT(in) :: w_haze_coag
+      ! Force __all__ aerosols moments to fall at M0 settling velocity.
+      LOGICAL, INTENT(in) :: force_wsed_to_m0
+      ! Force __all__ aerosols moments to fall at M3 settling velocity
+      LOGICAL, INTENT(in) :: force_wsed_to_m3
+
+      ! Minimum threshold for M0 of the spherical mode (m-3).
+      REAL(kind=mm_wp), INTENT(in) :: m0as_min
+      ! Minimum threshold for the characteristic radius of the spherical mode (m).
+      REAL(kind=mm_wp), INTENT(in) :: rcs_min
+      ! Minimum threshold for M0 of the factal mode (m-3).
+      REAL(kind=mm_wp), INTENT(in) :: m0af_min
+      ! Minimum threshold for the characteristic radius of the fractal mode (m).
+      REAL(kind=mm_wp), INTENT(in) :: rcf_min
+
+      ! Debug mode control flag.
+      LOGICAL, INTENT(in) :: debug
+      
+      ! Error status of the function.
+      TYPE(error) :: err
+
+      err = noerror
+
+      ! Sanity check:
+      IF (mm_ini) THEN
+        err = error("mm_global_init: YAMMS global initialization already performed !",-1)
+        RETURN
+      ENDIF
+  
+      ! Free parameters:
+      mm_dt             = dt
+      mm_df             = df
+      mm_rm             = rm
+      mm_rhoaer         = rho_aer
+      mm_haze_prod_pCH4 = haze_prod_pCH4
+      mm_p_prod         = p_prod
+      mm_tx_prod        = tx_prod
+      mm_rc_prod        = rc_prod
+      mm_rpla           = rplanet
+      mm_g0             = g0
+      mm_air_rad        = air_rad
+      mm_air_mmol       = air_mmol
+      mm_air_mmas       = air_mmol / mm_navo
+      
+      ! Microphysical processes:
+      mm_coag_choice = coag_interactions
+      IF (mm_coag_choice < 0 .OR. mm_coag_choice > 7) THEN
+        err = error("mm_global_init: Invalid choice for coagulation interactions activation",-1)
+        RETURN
+      ENDIF
+      mm_w_haze_prod = w_haze_prod
+      mm_w_haze_sed  = w_haze_sed
+      mm_w_haze_coag = w_haze_coag
+      mm_wsed_m0     = force_wsed_to_m0
+      mm_wsed_m3     = force_wsed_to_m3
+      
+      ! Moment threshold flags:
+      mm_m0as_min = MAX(0._mm_wp,m0as_min)
+      mm_rcs_min  = MAX(1.e-10_mm_wp,rcs_min)
+      mm_m0af_min = MAX(0._mm_wp,m0af_min)
+      mm_rcf_min  = MAX(mm_rm,rcf_min)
+      
+      ! Debug mode:
+      mm_debug = debug
+      
+      ! Computes M3 thresholds from user-defined thresholds:
+      mm_m3as_min = mm_m0as_min*mm_alpha_s(3._mm_wp) * mm_rcs_min**3._mm_wp
+      mm_m3af_min = mm_m0af_min*mm_alpha_f(3._mm_wp) * mm_rcf_min**3._mm_wp
+
+      ! Computes conversion factor for bulk to apparent radius:
+      mm_rb2ra = mm_rm**((mm_df-3._mm_wp)/mm_df)
+
+      ! Sanity check for settling velocity:
+      IF (mm_wsed_m0 .AND. mm_wsed_m3) THEN
+        err = error("'wsed_m0' and 'wsed_m3' options are mutually exclusive",-1)
+      ENDIF
+
+      ! End of initialization:
+      mm_ini = err == noerror
+
+    END FUNCTION mm_global_init_0
+
+
+    FUNCTION mm_global_init_1(cfg) RESULT(err)
+      !! Set global configuration from a configuration file.
+      !!
+      !! @note:
+      !! See [[mm_globals(module):mm_global_init_0(function)]].
+      !!
+      
+      ! Configuration file path.
+      TYPE(cfgparser), INTENT(in) :: cfg
+      ! Error status of the function.
+      TYPE(error) :: err
+      
+      err = noerror
+      
+      ! Sanity check:
+      IF (mm_ini) THEN
+        err = error("mm_global_init: YAMMS global initialization already performed !",-1)
+        RETURN
+      ENDIF
+  
+      ! Free parameters:
+      err = mm_check_opt(cfg_get_value(cfg,"timestep",mm_dt),mm_dt,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"df",mm_df),mm_df,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"rm",mm_rm),mm_rm,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"rho_aer",mm_rhoaer),mm_rhoaer,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"call_haze_prod",mm_haze_prod_pCH4),mm_haze_prod_pCH4,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"p_prod",mm_p_prod),mm_p_prod,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"tx_prod",mm_tx_prod),mm_tx_prod,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"rc_prod",mm_rc_prod),mm_rc_prod,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"planet_radius",mm_rpla),mm_rpla,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"g0",mm_g0),mm_g0,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"air_radius",mm_air_rad),mm_air_rad,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"air_molarmass",mm_air_mmol),mm_air_mmol,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"air_molecularmass",mm_air_mmas),mm_air_mmas,wlog=mm_log)
+      IF (err/=0) RETURN
+
+      ! Microphysical processes:
+      err = mm_check_opt(cfg_get_value(cfg,"haze_coag_interactions",mm_coag_choice),mm_coag_choice,wlog=mm_log)
+      IF (err/=0) RETURN
+      IF (mm_coag_choice < 0 .OR. mm_coag_choice > 7) THEN
+        err = error("mm_global_init: Invalid choice for coagulation interactions activation",-1)
+        RETURN
+      ENDIF
+      err = mm_check_opt(cfg_get_value(cfg,"haze_production",mm_w_haze_prod),mm_w_haze_prod,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"haze_sedimentation",mm_w_haze_sed),mm_w_haze_sed,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"haze_coagulation",mm_w_haze_coag),mm_w_haze_coag,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"wsed_m0",mm_wsed_m0),mm_wsed_m0,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"wsed_m3",mm_wsed_m3),mm_wsed_m3,wlog=mm_log)
+      IF (err/=0) RETURN
+      
+      ! Moment threshold flags:
+      err = mm_check_opt(cfg_get_value(cfg,"m0as_min",mm_m0as_min),mm_m0as_min,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"rcs_min",mm_rcs_min),mm_rcs_min,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"m0af_min",mm_m0af_min),mm_m0af_min,wlog=mm_log)
+      IF (err/=0) RETURN
+      err = mm_check_opt(cfg_get_value(cfg,"rcf_min",mm_rcf_min),mm_rcf_min,wlog=mm_log)
+      IF (err/=0) RETURN
+      
+      ! Debug mode:
+      err = mm_check_opt(cfg_get_value(cfg,"debug",mm_debug),mm_debug,wlog=mm_log)
+      IF (err/=0) RETURN
+      
+      ! Computes M3 thresholds from user-defined thresholds:
+      mm_m0as_min = MAX(0._mm_wp,mm_m0as_min)
+      mm_rcs_min  = MAX(1.e-10_mm_wp,mm_rcs_min)
+      mm_m0af_min = MAX(0._mm_wp,mm_m0af_min)
+      mm_rcf_min  = MAX(mm_rm,mm_rcf_min)
+      mm_m3as_min = mm_m0as_min*mm_alpha_s(3._mm_wp) * mm_rcs_min**3._mm_wp
+      mm_m3af_min = mm_m0af_min*mm_alpha_f(3._mm_wp) * mm_rcf_min**3._mm_wp
+
+      ! Computes conversion factor for bulk to apparent radius:
+      mm_rb2ra = mm_rm**((mm_df-3._mm_wp)/mm_df)
+
+      ! Sanity check for settling velocity:
+      IF (mm_wsed_m0 .AND. mm_wsed_m3) THEN
+        err = error("'wsed_m0' and 'wsed_m3' options are mutually exclusive",-1)
+      ENDIF
+
+      ! End of initialization:
+      mm_ini = err == noerror
+
+    END FUNCTION mm_global_init_1
+
+
+    FUNCTION mm_column_init(plev,zlev,play,zlay,temp) RESULT(err)
+      !! Initialize vertical atmospheric fields.
+      !!
+      !! This subroutine initializes vertical fields needed by the microphysics:
+      !!    1. Save reversed input field into "local" array
+      !!    2. Compute thicknesses layers and levels
+      !!    3. Interpolate temperature at levels
+      !!
+      !! @warning
+      !! The method should be called whenever the vertical structure of the atmosphere changes.
+      !! All the input vectors should be defined from __GROUND__ to __TOP__ of the atmosphere,
+      !! otherwise nasty things will occur in computations.
+      !!
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: plev ! Pressure levels (Pa).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: zlev ! Altitude levels (m).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: play ! Pressure layers (Pa).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: zlay ! Altitude at the center of each layer (m).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: temp ! Temperature at the center of each layer (K).
+      TYPE(error) :: err                                 ! Error status of the function.
+      INTEGER :: i
+
+      err = noerror
+      mm_ini_col = .false.
+      
+      ! Global initialization must be done before:
+      IF (.NOT.mm_ini) THEN
+        err = error("mm_column_init: Global initialization not done yet",-1)
+        RETURN
+      ENDIF
+
+      ! Check number of vertical layers:
+      IF (mm_nla < 0) THEN
+        mm_nla = SIZE(play)
+      ELSE
+        IF (mm_nla /= SIZE(play)) THEN
+          err = error("mm_column_init: mm_nla cannot be modified dynamically within the run",-1)
+          RETURN
+        ENDIF
+      ENDIF
+      
+      ! Check number of vertical levels:
+      IF (mm_nle < 0) THEN
+        mm_nle = SIZE(plev)
+      ELSE
+        IF (mm_nle /= SIZE(plev)) THEN
+          err = error("mm_column_init: mm_nle cannot be modified dynamically within the run",-1)
+          RETURN
+        ENDIF
+      ENDIF
+
+      ! Sanity check:
+      IF (mm_nla+1 /= mm_nle) THEN
+        err = error("mm_column_init: Inconsistent number of layers/levels",-1)
+        RETURN
+      ENDIF
+
+      ! Allocates if required:
+      IF (.NOT.ALLOCATED(mm_plev))   ALLOCATE(mm_plev(mm_nle))
+      IF (.NOT.ALLOCATED(mm_zlev))   ALLOCATE(mm_zlev(mm_nle))
+      IF (.NOT.ALLOCATED(mm_play))   ALLOCATE(mm_play(mm_nla))
+      IF (.NOT.ALLOCATED(mm_zlay))   ALLOCATE(mm_zlay(mm_nla))
+      IF (.NOT.ALLOCATED(mm_temp))   ALLOCATE(mm_temp(mm_nla))
+      IF (.NOT.ALLOCATED(mm_btemp))  ALLOCATE(mm_btemp(mm_nle))
+      IF (.NOT.ALLOCATED(mm_dzlev))  ALLOCATE(mm_dzlev(mm_nla))
+      IF (.NOT.ALLOCATED(mm_dzlay))  ALLOCATE(mm_dzlay(mm_nla))
+      IF (.NOT.ALLOCATED(mm_rhoair)) ALLOCATE(mm_rhoair(mm_nla))
+      
+      ! Saves reversed input vectors:
+      mm_plev = plev(mm_nle:1:-1)
+      mm_zlev = zlev(mm_nle:1:-1)
+      mm_play = play(mm_nla:1:-1)
+      mm_zlay = zlay(mm_nla:1:-1)
+      mm_temp = temp(mm_nla:1:-1)      
+      
+      ! Computes temperature vertical profil at interfaces:
+      mm_btemp(2:mm_nla)   = (mm_temp(1:mm_nla-1) + mm_temp(2:mm_nla)) / 2._mm_wp
+      mm_btemp(1)          = mm_temp(1)
+      mm_btemp(mm_nle)     = mm_temp(mm_nla) + (mm_temp(mm_nla) - mm_temp(mm_nla-1)) / 2._mm_wp
+      
+      ! Computes atmospheric levels thickness:
+      mm_dzlev(1:mm_nla)   = mm_zlev(1:mm_nle-1)-mm_zlev(2:mm_nle)
+      
+      ! Computes atmospheric layers thickness :
+      mm_dzlay(1:mm_nla-1) = mm_zlay(1:mm_nla-1)-mm_zlay(2:mm_nla)
+      mm_dzlay(mm_nla)     = mm_dzlay(mm_nla-1)
+      
+      ! Hydrostatic equilibrium:
+      mm_rhoair(1:mm_nla) = (mm_plev(2:mm_nle)-mm_plev(1:mm_nla)) / (mm_effg(mm_zlay)*mm_dzlev)
+      
+      ! Write out profiles for debug and log:
+      IF (mm_log.AND.mm_debug) THEN
+        WRITE(*,'(a)') '# TEMP             PLAY             ZLAY             DZLAY            RHOAIR'
+        DO i=1,mm_nla
+          WRITE(*,'(5(ES15.7,2X))') mm_temp(i),mm_play(i),mm_zlay(i),mm_dzlay(i), mm_rhoair(i)
+        ENDDO
+        WRITE(*,'(a)') '# TEMP             PLEV             ZLEV             DZLEV'
+        DO i=1,mm_nle
+          IF (i /= mm_nle) THEN
+            WRITE(*,'(4(ES15.7,2X))') mm_btemp(i),mm_plev(i),mm_zlev(i),mm_dzlev(i)
+          ELSE
+            WRITE(*,'(3(ES15.7,2X))') mm_btemp(i),mm_plev(i),mm_zlev(i)
+          ENDIF
+        ENDDO
+      ENDIF
+
+      ! End of initialization:
+      mm_ini_col = .true.
+
+      RETURN
+    END FUNCTION mm_column_init
+
+
+    FUNCTION mm_aerosols_init(m0aer_s,m3aer_s,m0aer_f,m3aer_f) RESULT(err)
+      !! Initialize aerosol tracers vertical grid.
+      !!
+      !! The subroutine initializes aerosols microphysics tracers columns. It allocates variables if
+      !! required and stores input vectors in reversed order. It also computes the characteristic radii
+      !! of each mode.
+      !!
+      !! @warning
+      !! The method should be called after mm_global_init and mm_column_init. Moreover, it should be called
+      !! whenever the vertical structure of the atmosphere changes.
+      !! All the input vectors should be defined from __GROUND__ to __TOP__ of the atmosphere,
+      !! otherwise nasty things will occur in computations.
+      !!
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: m0aer_s ! 0th order moment of the spherical mode (m-2).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: m3aer_s ! 3rd order moment of the spherical mode (m3.m-2).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: m0aer_f ! 0th order moment of the fractal mode (m-2).
+      REAL(kind=mm_wp), DIMENSION(:), INTENT(in) :: m3aer_f ! 3rd order moment of the fractal mode (m3.m-2).
+      TYPE(error) :: err                                    ! Error status of the function.
+      
+      err = noerror
+      
+      ! Global initialization must be done before:
+      IF (.NOT.mm_ini) THEN
+        err = error("mm_aerosols_init: Global initialization not done yet",-1) ; RETURN
+      ENDIF
+      
+      ! Column initialization must be done before:
+      IF (.NOT.mm_ini_col) THEN
+        err = error("mm_aerosols_init: Column initialization not done yet",-1) ; RETURN
+      ENDIF
+
+      ! Sanity check:
+      IF (SIZE(m0aer_s) /= mm_nla) THEN
+        err = error("mm_aerosols_init: Invalid size for input arrays",-1) ; RETURN
+      ENDIF
+  
+      ! Allocate variable if required:
+      IF (.NOT.ALLOCATED(mm_m0aer_s)) ALLOCATE(mm_m0aer_s(mm_nla))
+      IF (.NOT.ALLOCATED(mm_m3aer_s)) ALLOCATE(mm_m3aer_s(mm_nla))
+      IF (.NOT.ALLOCATED(mm_m0aer_f)) ALLOCATE(mm_m0aer_f(mm_nla))
+      IF (.NOT.ALLOCATED(mm_m3aer_f)) ALLOCATE(mm_m3aer_f(mm_nla))
+      IF (.NOT.ALLOCATED(mm_rcs))     ALLOCATE(mm_rcs(mm_nla))
+      IF (.NOT.ALLOCATED(mm_rcf))     ALLOCATE(mm_rcf(mm_nla))
+      
+      ! Allocate memory for diagnostics if required:
+      IF (.NOT.ALLOCATED(mm_m0as_vsed)) THEN
+        ALLOCATE(mm_m0as_vsed(mm_nla))
+        mm_m0as_vsed(:) = 0._mm_wp
+      ENDIF
+      IF (.NOT.ALLOCATED(mm_m3as_vsed)) THEN
+        ALLOCATE(mm_m3as_vsed(mm_nla))
+        mm_m3as_vsed(:) = 0._mm_wp
+      ENDIF
+      IF (.NOT.ALLOCATED(mm_m0af_vsed)) THEN
+        ALLOCATE(mm_m0af_vsed(mm_nla))
+        mm_m0af_vsed(:) = 0._mm_wp
+      ENDIF
+      IF (.NOT.ALLOCATED(mm_m3af_vsed)) THEN
+        ALLOCATE(mm_m3af_vsed(mm_nla))
+        mm_m3af_vsed(:) = 0._mm_wp
+      ENDIF
+      IF (.NOT.ALLOCATED(mm_aer_s_flux)) THEN
+        ALLOCATE(mm_aer_s_flux(mm_nla))
+        mm_aer_s_flux(:) = 0._mm_wp
+      ENDIF
+      IF (.NOT.ALLOCATED(mm_aer_f_flux)) THEN
+        ALLOCATE(mm_aer_f_flux(mm_nla))
+        mm_aer_f_flux(:) = 0._mm_wp
+      ENDIF
+      
+      ! Initialization of aerosol tracers:
+      ! @note: mm_dzlev is already from top to ground
+      mm_m0aer_s = m0aer_s(mm_nla:1:-1)/mm_dzlev(:)
+      mm_m3aer_s = m3aer_s(mm_nla:1:-1)/mm_dzlev(:)
+      mm_m0aer_f = m0aer_f(mm_nla:1:-1)/mm_dzlev(:)
+      mm_m3aer_f = m3aer_f(mm_nla:1:-1)/mm_dzlev(:)
+  
+      ! Setup threshold (useful for preventing bugs):
+      call mm_set_moments_thresholds()
+  
+      ! Initialization of spherical aerosol characteristic radii:
+      WHERE(mm_m3aer_s > 0._mm_wp .AND. mm_m0aer_s > 0._mm_wp)
+        mm_rcs = mm_get_rcs(mm_m0aer_s,mm_m3aer_s)
+      ELSEWHERE
+        mm_rcs = 0._mm_wp
+      ENDWHERE
+
+      ! Initialization of fractal aerosol characteristic radii:
+      WHERE(mm_m3aer_f > 0._mm_wp .AND. mm_m0aer_f > 0._mm_wp)
+        mm_rcf = mm_get_rcf(mm_m0aer_f,mm_m3aer_f)
+      ELSEWHERE
+        mm_rcf = 0._mm_wp
+      ENDWHERE
+
+      ! End of initialization:
+      mm_ini_aer = .true.
+
+    END FUNCTION mm_aerosols_init
+
+
+    !============================================================================
+    !                      INTER-MOMENT RELATION METHODS
+    !============================================================================
+    
+    PURE FUNCTION mm_alpha_s(k) RESULT (res)
+      !! Inter-moment relation for spherical aerosols size distribution law.
+      !! Mk / M0 = rc^k . alpha(k)
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: k ! k order of the moment.
+      REAL(kind=mm_wp) :: sigma         ! Standard deviation.
+      REAL(kind=mm_wp) :: res           ! Alpha value.
+
+      ! Titan's case
+      !~~~~~~~~~~~~~
+      ! res = SUM(dexp(mm_asp%a*k**2 + mm_asp%b*k + mm_asp%c))
+
+      ! Pluto's case
+      !~~~~~~~~~~~~~
+      sigma = 0.2_mm_wp
+      res = exp(k**2 * sigma**2 / 2._mm_wp)
+
+      RETURN
+    END FUNCTION mm_alpha_s
+
+
+    PURE FUNCTION mm_alpha_f(k) RESULT (res)
+      !! Inter-moment relation for fractal aerosols size distribution law.
+      !! Mk / M0 = rc^k . alpha(k)
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: k ! k order of the moment.
+      REAL(kind=mm_wp) :: sigma         ! Standard deviation.
+      REAL(kind=mm_wp) :: res           ! Alpha value.
+
+      ! Titan's case
+      !~~~~~~~~~~~~~
+      ! res = SUM(dexp(mm_afp%a*k**2 + mm_afp%b*k + mm_afp%c))
+      
+      ! Pluto's case
+      !~~~~~~~~~~~~~
+      sigma = 0.35_mm_wp
+      res = exp(k**2 * sigma**2 / 2._mm_wp)
+      
+      RETURN
+    END FUNCTION mm_alpha_f
+
+
+    !============================================================================
+    !                          MISCELLANEOUS METHODS
+    !============================================================================
+
+    ELEMENTAL FUNCTION mm_effg(z) RESULT(effg)
+      !! Compute effective gravitational acceleration.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: z ! Altitude (m)
+      REAL(kind=mm_wp) :: effg          ! Effective gravitational acceleration (m.s-2)
+      effg = mm_g0
+      IF (mm_use_effg) THEN
+        effg = effg * (mm_rpla/(mm_rpla+z))**2
+      ENDIF
+      RETURN
+    END FUNCTION mm_effg
+
+
+    SUBROUTINE mm_set_moments_thresholds()
+      !! Apply minimum threshold for the aerosols moments.
+      !!
+      !! The method resets moments (for both modes and orders, 0 and 3) values to zero if
+      !! their current value is below the minimum threholds.
+      !!
+      INTEGER :: i
+      DO i=1,mm_nla
+        IF ((mm_m0aer_s(i) < mm_m0as_min) .OR. (mm_m3aer_s(i) < mm_m3as_min)) THEN
+          mm_m0aer_s(i) = 0._mm_wp
+          mm_m3aer_s(i) = 0._mm_wp
+        ENDIF
+        IF ((mm_m0aer_f(i) < mm_m0af_min) .OR. (mm_m3aer_f(i) < mm_m3af_min)) THEN
+          mm_m0aer_f(i) = 0._mm_wp
+          mm_m3aer_f(i) = 0._mm_wp
+        ENDIF
+      ENDDO
+    END SUBROUTINE mm_set_moments_thresholds
+
+
+    ELEMENTAL FUNCTION mm_get_rcs(m0,m3) RESULT(res)
+      !! Get the characteristic radius for the spherical aerosols size distribution.
+      !!
+      !! The method computes the characteristic radius of the spherical aerosol size distribution
+      !! law according to its moments and its inter-moments relation.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: m0 ! 0th order moment
+      REAL(kind=mm_wp), INTENT(in) :: m3 ! 3rd order moment
+      REAL(kind=mm_wp) :: res            ! Radius
+      res = (m3 / (m0*mm_alpha_s(3._mm_wp)))**(1._mm_wp/3._mm_wp)
+    END FUNCTION mm_get_rcs
+
+
+    ELEMENTAL FUNCTION mm_get_rcf(m0,m3) RESULT(res)
+      !! Get the characteristic radius for the fractal aerosols size distribution.
+      !!
+      !! The method computes the characteristic radius of the fractal aerosol size distribution
+      !! law according to its moments and its inter-moments relation.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: m0 ! 0th order moment
+      REAL(kind=mm_wp), INTENT(in) :: m3 ! 3rd order moment
+      REAL(kind=mm_wp) :: res            ! Radius
+      res = (m3 / (m0*mm_alpha_f(3._mm_wp)))**(1._mm_wp/3._mm_wp)
+    END FUNCTION mm_get_rcf
+
+
+    SUBROUTINE mm_dump_parameters()
+      !! Dump global parameters on stdout.
+      !!
+      WRITE(*,'(a)')         "========= YAMMS PARAMETERS ============"
+      WRITE(*,'(a,a)')       "mm_fp_precision            : ", mm_wp_s
+      WRITE(*,'(a,L2)')      "mm_debug                   : ", mm_debug
+      WRITE(*,'(a)')         "---------------------------------------"
+      WRITE(*,'(a)')         "Microphysical control flags"
+      
+      ! Haze production:
+      WRITE(*,'(a,L2)')      "mm_w_haze_prod             : ", mm_w_haze_prod
+      WRITE(*,'(a,L2)')      "mm_haze_prod_pCH4          : ", mm_haze_prod_pCH4
+      IF (.NOT. mm_haze_prod_pCH4) THEN
+        WRITE(*,'(a,ES14.7)')  "   --> mm_p_prod (Pa)         : ", mm_p_prod
+        WRITE(*,'(a,ES14.7)')  "   --> mm_tx_prod (kg.m-2.s-1): ", mm_tx_prod
+        WRITE(*,'(a,ES14.7)')  "   --> mm_rc_prod (m)         : ", mm_rc_prod
+      ENDIF
+      
+      ! Haze coagulation:
+      WRITE(*,'(a,L2)')      "mm_w_haze_coag             : ", mm_w_haze_coag
+      IF (mm_w_haze_coag) THEN
+        WRITE(*,'(a,I2.2)')    "   --> mm_coag_interactions   : ", mm_coag_choice
+      ENDIF
+      
+      ! Haze sedimentation:
+      WRITE(*,'(a,L2)')      "mm_w_haze_sed              : ", mm_w_haze_sed
+      IF (mm_w_haze_sed) THEN
+        WRITE(*,'(a,L2)')      "   --> mm_wsed_m0             : ", mm_wsed_m0
+        WRITE(*,'(a,L2)')      "   --> mm_wsed_m3             : ", mm_wsed_m3
+      ENDIF
+      WRITE(*,'(a)')         "---------------------------------------"
+      
+      ! Haze threshold:
+      WRITE(*,'(a)')         "Spherical aerosol thresholds"
+      WRITE(*,'(a,ES14.7)')  "  mm_m0as_min (m-3)        : ", mm_m0as_min
+      WRITE(*,'(a,ES14.7)')  "  mm_rcs_min (m)           : ", mm_rcs_min
+      WRITE(*,'(a)')         "Fractal aerosol thresholds"
+      WRITE(*,'(a,ES14.7)')  "  mm_m0af_min (m-3)        : ", mm_m0af_min
+      WRITE(*,'(a,ES14.7)')  "  mm_rcf_min (m)           : ", mm_rcf_min
+      WRITE(*,'(a)')         "---------------------------------------"
+      
+      ! Free parameters:
+      WRITE(*,'(a)')         "Free parameters"
+      WRITE(*,'(a,ES14.7)')  "mm_rm (m)                  : ", mm_rm
+      WRITE(*,'(a,ES14.7)')  "mm_df (-)                  : ", mm_df
+      WRITE(*,'(a,ES14.7)')  "mm_rhoaer (kg.m-3)         : ", mm_rhoaer      
+      WRITE(*,'(a,ES14.7)')  "mm_rplanete (m)            : ", mm_rpla
+      WRITE(*,'(a,ES14.7)')  "mm_g0 (m.s-2)              : ", mm_g0
+      WRITE(*,'(a,ES14.7)')  "mm_air_rad (m)             : ", mm_air_rad
+      WRITE(*,'(a,ES14.7)')  "mm_air_mmol (kg.mol-1)     : ", mm_air_mmol
+      WRITE(*,'(a,ES14.7)')  "mm_air_mmas (kg)           : ", mm_air_mmas
+      WRITE(*,'(a,ES14.7)')  "mm_dt (s)                  : ", mm_dt
+      IF (mm_nla > -1) THEN
+        WRITE(*,'(a,I3.3)')    "mm_nla                   : ", mm_nla
+      ELSE
+        WRITE(*,'(a)')         "mm_nla                   : not initialized yet"
+      ENDIF
+      WRITE(*,'(a)')         "======================================="
+    END SUBROUTINE mm_dump_parameters
+
+
+    ! =========================================================================
+    ! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+    !                CONFIGURATION PARSER checking methods
+    ! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+    ! =========================================================================
+  
+    FUNCTION check_r1(err,var,def,wlog) RESULT(ret)
+      !! Check an option value (float).
+      !!
+      !! The method checks an option value and optionally set a default value, __def__ to initialize
+      !! __var__ on error if given.
+      !!
+      TYPE(error), INTENT(in)                :: err  ! Error object from value getter.
+      REAL(kind=mm_wp), INTENT(inout)        :: var  ! Input/output option value.
+      REAL(kind=mm_wp), INTENT(in), OPTIONAL :: def  ! Default value to set.
+      LOGICAL, INTENT(in), OPTIONAL          :: wlog ! .true. to print warning/error message.
+      TYPE(error) :: ret                             ! Input error.
+      CHARACTER(len=*), PARAMETER :: defmsg = '... Using default value: '
+      LOGICAL                     :: zlog
+      ret = err
+      zlog = .false. ; IF (PRESENT(wlog)) zlog = wlog
+      IF (err == 0) RETURN
+      IF (PRESENT(def)) THEN
+        var = def
+        IF (zlog) WRITE(*,'(a,a,a)') error_to_string(err,'',.true.),defmsg,to_string(var)
+        ret = noerror
+      ELSE
+        IF (zlog) WRITE(*,'(a)') error_to_string(err,'',.true.)
+      ENDIF
+    END FUNCTION check_r1
+  
+    FUNCTION check_l1(err,var,def,wlog) RESULT(ret)
+      !! Check an option value (logical).
+      !!
+      !! The method checks an option value and optionally set a default value, __def__ to initialize
+      !! __var__ on error if given.
+      !!
+      TYPE(error), INTENT(in)       :: err  ! Error object from value getter.
+      LOGICAL, INTENT(inout)        :: var  ! Input/output option value.
+      LOGICAL, INTENT(in), OPTIONAL :: def  ! Default value to set.
+      LOGICAL, INTENT(in), OPTIONAL :: wlog ! .true. to print warning/error message.
+      TYPE(error) :: ret                    ! Input error.
+      CHARACTER(len=*), PARAMETER :: defmsg = '... Using default value: '
+      LOGICAL                     :: zlog
+      ret = err
+      zlog = .false. ; IF (PRESENT(wlog)) zlog = wlog
+      IF (err == 0) RETURN
+      IF (PRESENT(def)) THEN
+        var = def
+        IF (zlog) WRITE(*,'(a,a,a)') error_to_string(err,'',.true.),defmsg,to_string(var)
+        ret = noerror
+      ELSE
+        IF (zlog) WRITE(*,'(a)') error_to_string(err,'',.true.)
+      ENDIF
+    END FUNCTION check_l1
+  
+    FUNCTION check_i1(err,var,def,wlog) RESULT(ret)
+      !! Check an option value (integer).
+      !!
+      !! The method checks an option value and optionally set a default value, __def__ to initialize
+      !! __var__ on error if given.
+      !!
+      TYPE(error), INTENT(in)       :: err  ! Error object from value getter.
+      INTEGER, INTENT(inout)        :: var  ! Input/output option value.
+      INTEGER, INTENT(in), OPTIONAL :: def  ! Default value to set.
+      LOGICAL, INTENT(in), OPTIONAL :: wlog ! .true. to print warning/error message.
+      TYPE(error) :: ret                    ! Input error.
+      CHARACTER(len=*), PARAMETER :: defmsg = '... Using default value: '
+      LOGICAL                     :: zlog
+      ret = err
+      zlog = .false. ; IF (PRESENT(wlog)) zlog = wlog
+      IF (err == 0) RETURN
+      IF (PRESENT(def)) THEN
+        var = def
+        IF (zlog) WRITE(*,'(a,a,a)') error_to_string(err,'',.true.),defmsg,to_string(var)
+        ret = noerror
+      ELSE
+        IF (zlog) WRITE(*,'(a)') error_to_string(err,'',.true.)
+      ENDIF
+    END FUNCTION check_i1
+  
+    FUNCTION check_s1(err,var,def,wlog) RESULT(ret)
+      !! Check an option value (string).
+      !!
+      !! The method checks an option value and optionally set a default value, __def__ to initialize
+      !! __var__ on error if given.
+      !!
+      TYPE(error), INTENT(in)                      :: err  ! Error object from value getter.
+      CHARACTER(len=*), INTENT(inout)              :: var  ! Input/output option value.
+      CHARACTER(len=*), INTENT(in), OPTIONAL       :: def  ! Default value to set.
+      LOGICAL, INTENT(in), OPTIONAL                :: wlog ! .true. to print warning/error message.
+      TYPE(error) :: ret                                   ! Input error.
+      CHARACTER(len=*), PARAMETER :: defmsg = '... Using default value: '
+      LOGICAL                     :: zlog
+      ret = err
+      zlog = .false. ; IF (PRESENT(wlog)) zlog = wlog
+      IF (err == 0) RETURN
+      IF (PRESENT(def)) THEN
+        var = TRIM(def)
+        IF (zlog) WRITE(*,'(a,a,a)') error_to_string(err,'',.true.),defmsg,var
+        ret = noerror
+      ELSE
+        IF (zlog) WRITE(*,'(a)') error_to_string(err,'')
+      ENDIF
+      RETURN
+    END FUNCTION check_s1
+
+END MODULE MP2M_GLOBALS
Index: trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_haze.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_haze.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_haze.F90	(revision 3560)
@@ -0,0 +1,923 @@
+MODULE MP2M_HAZE
+    !============================================================================
+    !
+    !     Purpose
+    !     -------
+    !     Haze microphysics module.
+    !
+    !     This module contains all definitions of the microphysics processes related to
+    !     aerosols (production, coagulation, sedimentation).
+    !
+    !     @Warning
+    !     The tendencies returned by the method are always defined over the vertical grid
+    !     from __TOP__ to __GROUND__.
+    !
+    !     The module also contains sixteen method:
+    !        - mm_haze_microphysics
+    !        - mm_haze_production
+    !        - mm_haze_sedimentation
+    !           * get_weff
+    !           * let_me_fall_in_peace
+    !        - mm_haze_coagulation
+    !           * g0ssco
+    !           * g0sfco
+    !           * g0ffco
+    !           * g3ssco
+    !           * g3sfco
+    !           * g0ssfm
+    !           * g0sffm
+    !           * g0fffm
+    !           * g3ssfm
+    !           * g3sffm
+    !
+    !     Authors
+    !     -------
+    !     B. de Batz de Trenquelléon, J. Burgalat (11/2024)
+    !
+    !============================================================================
+
+    USE MP2M_MPREC
+    USE MP2M_GLOBALS
+    USE MP2M_METHODS
+    IMPLICIT NONE
+
+    PRIVATE
+
+    PUBLIC :: mm_haze_microphysics,  &
+              mm_haze_production,    &
+              mm_haze_sedimentation, &
+              mm_haze_coagulation
+
+    CONTAINS
+  
+    !============================================================================
+    ! HAZE MICROPHYSICS INTERFACE SUBROUTINE
+    !============================================================================
+  
+    SUBROUTINE mm_haze_microphysics(m3a_s_prod,dm0a_s,dm3a_s,dm0a_f,dm3a_f)
+      !! Get the evolution of moments tracers through haze microphysics processes.
+      !!
+      !! The subroutine is a wrapper to the haze microphysics methods. It computes the tendencies
+      !! of moments tracers for production, sedimentation, and coagulation processes for the
+      !! atmospheric column.
+      !!
+
+      ! Production of the 3rd order moment of the spherical mode distribution from CH4 photolysis (m3.m-3).
+      REAL(kind=mm_wp), INTENT(in), DIMENSION(:)  :: m3a_s_prod
+      ! Tendency of the 0th order moment of the spherical mode distribution (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0a_s
+      ! Tendency of the 3rd order moment of the spherical mode distribution (m3.m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3a_s
+      ! Tendency of the 0th order moment of the fractal mode distribution (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0a_f
+      ! Tendency of the 3rd order moment of the fractal mode distribution (m3.m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3a_f
+      
+      ! Local variables:
+      !~~~~~~~~~~~~~~~~~
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: zdm0as
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: zdm3as
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: zdm0af
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: zdm3af
+  
+      ! Initialization:
+      !~~~~~~~~~~~~~~~~
+      dm0a_s = 0._mm_wp
+      dm3a_s = 0._mm_wp
+      dm0a_f = 0._mm_wp
+      dm3a_f = 0._mm_wp
+  
+      ALLOCATE(zdm0as(mm_nla),zdm3as(mm_nla),zdm0af(mm_nla),zdm3af(mm_nla))
+      zdm0as(1:mm_nla) = 0._mm_wp
+      zdm3as(1:mm_nla) = 0._mm_wp
+      zdm0af(1:mm_nla) = 0._mm_wp
+      zdm3af(1:mm_nla) = 0._mm_wp
+  
+
+      ! Microphysical processes:
+      !~~~~~~~~~~~~~~~~~~~~~~~~~
+      
+      ! Coagulation
+      IF (mm_w_haze_coag) THEN
+        call mm_haze_coagulation(dm0a_s,dm3a_s,dm0a_f,dm3a_f)
+      ENDIF
+  
+      ! Sedimentation
+      IF (mm_w_haze_sed) THEN
+        call mm_haze_sedimentation(zdm0as,zdm3as,zdm0af,zdm3af)
+  
+        ! Computes precipitations
+        mm_aers_prec = SUM(zdm3as*mm_dzlev*mm_rhoaer)
+        mm_aerf_prec = SUM(zdm3af*mm_dzlev*mm_rhoaer)
+  
+        ! Updates tendencies
+        dm0a_s = dm0a_s + zdm0as
+        dm3a_s = dm3a_s + zdm3as
+        dm0a_f = dm0a_f + zdm0af
+        dm3a_f = dm3a_f + zdm3af
+      ENDIF
+  
+      ! Production
+      IF (mm_w_haze_prod) THEN
+        ! Production by photolysis of CH4
+        IF (mm_haze_prod_pCH4) THEN
+          dm0a_s = dm0a_s + (m3a_s_prod / (mm_rc_prod**3 * mm_alpha_s(3._mm_wp)))
+          dm3a_s = dm3a_s + m3a_s_prod
+        ELSE
+          call mm_haze_production(zdm0as,zdm3as)
+          dm0a_s = dm0a_s + zdm0as
+          dm3a_s = dm3a_s + zdm3as
+        ENDIF
+      ENDIF
+  
+      RETURN
+    END SUBROUTINE mm_haze_microphysics
+
+
+    !============================================================================
+    ! PRODUCTION PROCESS RELATED METHOD
+    !============================================================================
+    
+    SUBROUTINE mm_haze_production(dm0s,dm3s)
+      !! Compute the production of aerosols.
+      !!
+      !! @warning
+      !! Spherical aerosols are created at one pressure level. No other source is taken into account.
+      !! This process is controled by two parameters, the pressure level of production and the production rate.
+      !! Then both M0 and M3 of the spherical aerosols distribution are updated in the production zone by addition
+      !! of the production rate along a gaussian shape.
+      !!
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0s !! Tendency of M0 (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3s !! Tendency of M3 (m3.m-3).
+      
+      ! Local variables:
+      !~~~~~~~~~~~~~~~~~
+      INTEGER                     :: i
+      REAL(kind=mm_wp)            :: zprod
+      REAL(kind=mm_wp), PARAMETER :: sigz  = 20e3_mm_wp, &
+                                     znorm = dsqrt(2._mm_wp)*sigz
+      
+      ! Locates production altitude
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      zprod = -1._mm_wp
+      DO i=1,mm_nla-1
+        IF (mm_plev(i) < mm_p_prod.AND.mm_plev(i+1) >= mm_p_prod) THEN
+          zprod = mm_zlay(i)
+          EXIT
+        ENDIF
+      ENDDO
+      
+      ! Sanity check
+      !~~~~~~~~~~~~~
+      IF (zprod < 0._mm_wp) THEN
+        WRITE(*,'(a)') "cannot find aerosols production altitude"
+        call EXIT(11)
+      ENDIF
+  
+      ! Computes production rate
+      !~~~~~~~~~~~~~~~~~~~~~~~~~
+      dm3s(:) = mm_tx_prod  * (0.75_mm_wp * mm_dt) / (mm_pi * mm_rhoaer) / (2._mm_wp * dsqrt(2._mm_wp) * mm_dzlev(1:mm_nla)) * &
+               (erf((mm_zlev(1:mm_nla)-zprod)/znorm) - erf((mm_zlev(2:mm_nla+1)-zprod)/znorm))
+      dm0s(:) = dm3s(:) / (mm_rc_prod**3*mm_alpha_s(3._mm_wp))
+    END SUBROUTINE mm_haze_production
+
+
+    !============================================================================
+    ! SEDIMENTATION PROCESS RELATED METHODS
+    !============================================================================
+  
+    SUBROUTINE mm_haze_sedimentation(dm0s,dm3s,dm0f,dm3f)
+      !! Interface to sedimentation algorithm.
+      !!
+      !! The subroutine computes the evolution of each moment of the aerosols tracers
+      !! through sedimentation process and returns their tendencies for a timestep.
+      !!
+      ! Tendency of the 0th order moment of the spherical mode (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0s
+      ! Tendency of the 3rd order moment of the spherical mode (m3.m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3s
+      ! Tendency of the 0th order moment of the fractal mode (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0f
+      ! Tendency of the 3rd order moment of the fractal mode (m3.m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3f
+
+      ! Local variables:
+      !~~~~~~~~~~~~~~~~~
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: wth
+  
+      ALLOCATE(wth(mm_nle))
+  
+      ! Force settling velocity to M0
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      IF (mm_wsed_m0) THEN
+        ! Spherical particles
+        call get_weff(0._mm_wp,3._mm_wp,mm_rcs,mm_dt,mm_alpha_s,wth)
+        
+        call let_me_fall_in_peace(mm_m0aer_s,-wth,mm_dt,dm0s)
+        call let_me_fall_in_peace(mm_m3aer_s,-wth,mm_dt,dm3s)
+
+        ! Get settling velocity and mass flux
+        mm_m0as_vsed(:)  = wth(1:mm_nla)
+        mm_m3as_vsed(:)  = wth(1:mm_nla)
+        mm_aer_s_flux(:) = (mm_m3aer_s * mm_rhoaer) * wth(2:)
+        
+        ! Fractal particles
+        call get_weff(0._mm_wp,mm_df,mm_rcf,mm_dt,mm_alpha_f,wth)
+        
+        call let_me_fall_in_peace(mm_m0aer_f,-wth,mm_dt,dm0f)
+        call let_me_fall_in_peace(mm_m3aer_f,-wth,mm_dt,dm3f)
+        
+        ! Get settling velocity and mass flux
+        mm_m0af_vsed(:)  = wth(1:mm_nla)
+        mm_m3af_vsed(:)  = wth(1:mm_nla)
+        mm_aer_f_flux(:) = (mm_m3aer_f * mm_rhoaer) * wth(2:)
+      
+      ! Force settling velocity to M3
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      ELSEIF (mm_wsed_m3) THEN
+        ! Spherical particles
+        call get_weff(3._mm_wp,3._mm_wp,mm_rcs,mm_dt,mm_alpha_s,wth)
+        
+        call let_me_fall_in_peace(mm_m3aer_s,-wth,mm_dt,dm3s)
+        call let_me_fall_in_peace(mm_m0aer_s,-wth,mm_dt,dm0s)
+        
+        ! Get settling velocity and mass flux
+        mm_m3as_vsed(:)  = wth(1:mm_nla)
+        mm_m0as_vsed(:)  = wth(1:mm_nla)
+        mm_aer_s_flux(:) = (mm_m3aer_s * mm_rhoaer) * wth(2:)
+        
+        ! Fractal particles
+        call get_weff(3._mm_wp,mm_df,mm_rcf,mm_dt,mm_alpha_f,wth)
+        
+        call let_me_fall_in_peace(mm_m3aer_f,-wth,mm_dt,dm3f)
+        call let_me_fall_in_peace(mm_m0aer_f,-wth,mm_dt,dm0f)
+
+        ! Get settling velocity and mass flux
+        mm_m3af_vsed(:) = wth(1:mm_nla)
+        mm_m0af_vsed(:) = wth(1:mm_nla)
+        mm_aer_f_flux(:) = (mm_m3aer_f * mm_rhoaer) * wth(2:)
+      
+      ! No forcing of settling velocity (might be a problem...)
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      ELSE
+        ! Spherical particles
+        call get_weff(0._mm_wp,3._mm_wp,mm_rcs,mm_dt,mm_alpha_s,wth)
+        call let_me_fall_in_peace(mm_m0aer_s,-wth,mm_dt,dm0s)
+        
+        ! Get settling velocity
+        mm_m0as_vsed(:) = wth(1:mm_nla)
+        
+        call get_weff(3._mm_wp,3._mm_wp,mm_rcs,mm_dt,mm_alpha_s,wth)
+        call let_me_fall_in_peace(mm_m3aer_s,-wth,mm_dt,dm3s)
+        
+        ! Get settling velocity
+        mm_m3as_vsed(:) = wth(1:mm_nla)
+        
+        ! Get mass flux
+        mm_aer_s_flux(:) = (mm_m3aer_s * mm_rhoaer) * wth(2:)
+        
+        ! Fractal aerosols
+        call get_weff(0._mm_wp,mm_df,mm_rcf,mm_dt,mm_alpha_f,wth)
+        call let_me_fall_in_peace(mm_m0aer_f,-wth,mm_dt,dm0f)
+        
+        ! Get settling velocity
+        mm_m0af_vsed(:) = wth(1:mm_nla)
+        
+        call get_weff(3._mm_wp,mm_df,mm_rcf,mm_dt,mm_alpha_f,wth)
+        call let_me_fall_in_peace(mm_m3aer_f,-wth,mm_dt,dm3f)
+        
+        ! Get settling velocity
+        mm_m3af_vsed(:) = wth(1:mm_nla)
+        
+        ! Get mass flux
+        mm_aer_f_flux(:) = (mm_m3aer_f * mm_rhoaer) * wth(2:)
+      ENDIF
+
+      DEALLOCATE(wth)
+      
+      RETURN
+    END SUBROUTINE mm_haze_sedimentation
+
+
+    SUBROUTINE get_weff(k,df,rc,dt,afun,wth)
+      !! Get the effective settling velocity for aerosols moments.
+      !!
+      !! This method computes the effective settling velocity of the kth order moment of aerosol tracers.
+      !! The basic settling velocity (weff(Mk)) is computed using the following equation:
+      !!     Phi_sed (Mk) = \int_0^infty (n(r) . r**k . w(r) . dr)
+      !!                  = Mk . weff (Mk)
+      !!
+      ! The order of the moment.
+      REAL(kind=mm_wp), INTENT(in)                     :: k
+      ! Fractal dimension of the aersols.
+      REAL(kind=mm_wp), INTENT(in)                     :: df
+      ! Characteristic radius associated to the moment at each layer (m).
+      REAL(kind=mm_wp), INTENT(in), DIMENSION(mm_nla)  :: rc
+      ! Time step (s).
+      REAL(kind=mm_wp), INTENT(in)                     :: dt
+      ! Theoretical effective settling velocity at each vertical __levels__ (m.s-1).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(mm_nle) :: wth
+      
+      ! Inter-moment relation function.
+      INTERFACE
+        FUNCTION afun(order)
+          IMPORT mm_wp
+          REAL(kind=mm_wp), INTENT(in) :: order ! Order of the moment.
+          REAL(kind=mm_wp)             :: afun  ! Alpha value.
+        END FUNCTION afun
+      END INTERFACE
+      
+      ! Local variables:
+      !~~~~~~~~~~~~~~~~~
+      INTEGER          :: i
+      REAL(kind=mm_wp) :: af1,af2,ar1,ar2
+      REAL(kind=mm_wp) :: mrcoef, brhoair, thermal_w
+      REAL(kind=mm_wp) :: csto,cslf
+      REAL(kind=mm_wp) :: rb2ra
+      
+      wth(:) = 0._mm_wp
+
+      ! Molecular's case
+      !~~~~~~~~~~~~~~~~~
+      mrcoef = 0.74_mm_wp
+
+      ar1 = (3._mm_wp*df - 6._mm_wp) / df
+      af1 = (df*(k+3._mm_wp) - 6._mm_wp) / df
+      
+      rb2ra = mm_rm**((6._mm_wp-2._mm_wp*df)/df)
+
+      DO i=1,mm_nle
+        thermal_w = sqrt((8._mm_wp * mm_kboltz * mm_btemp(i)) / (mm_pi * mm_air_mmas))
+      
+        IF (i == 1) THEN
+          IF (rc(i) <= 0._mm_wp) CYCLE
+          brhoair = mm_rhoair(i)
+          wth(i) = - (mrcoef * mm_effg(mm_zlev(i)) * (mm_rhoaer/brhoair) / thermal_w) * rb2ra * &
+                   (afun(af1)/afun(k)) * rc(i)**ar1
+        ELSEIF (i == mm_nle) THEN
+          IF (rc(i-1) <= 0._mm_wp) CYCLE
+          brhoair = mm_rhoair(mm_nla) + (mm_rhoair(mm_nla) - mm_rhoair(mm_nla-1)) / 2._mm_wp
+          wth(i) = - (mrcoef * mm_effg(mm_zlev(i)) * (mm_rhoaer/brhoair) / thermal_w) * rb2ra * &
+                   (afun(af1)/afun(k)) * rc(i-1)**ar1
+        ELSE
+          IF (rc(i-1) <= 0._mm_wp) CYCLE
+          brhoair = (mm_rhoair(i-1) + mm_rhoair(i)) / 2._mm_wp
+          wth(i) = - (mrcoef * mm_effg(mm_zlev(i)) * (mm_rhoaer/brhoair) / thermal_w) * rb2ra * &
+                   (afun(af1)/afun(k)) * rc(i-1)**ar1
+        ENDIF
+      ENDDO
+
+      ! Hydrodynamical's case
+      ! In fact: transitory case which is the general case
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      !ar1 = (3._mm_wp*df - 3._mm_wp) / df
+      !ar2 = (3._mm_wp*df - 6._mm_wp) / df
+      !
+      !af1 = (df*(k+3._mm_wp) - 3._mm_wp) / df
+      !af2 = (df*(k+3._mm_wp) - 6._mm_wp) / df
+      !
+      !rb2ra = mm_rm**((df-3._mm_wp)/df)
+      !
+      !DO i=1,mm_nle
+      !  csto = (2._mm_wp*mm_rhoaer*mm_effg(mm_zlev(i))) / (9._mm_wp*mm_eta_air(mm_btemp(i)))
+      !  cslf = mm_akn * mm_lambda_air(mm_btemp(i),mm_plev(i)) / rb2ra
+      !  
+      !  IF (i == 1) THEN
+      !    IF (rc(i) <= 0._mm_wp) CYCLE
+      !    wth(i) = - csto/(rb2ra*afun(k)) * &
+      !             ((afun(af1)*rc(i)**ar1) + (cslf*afun(af2)*rc(i)**ar2))
+      !  ELSE
+      !    IF (rc(i-1) <= 0._mm_wp) CYCLE
+      !    wth(i) = - csto/(rb2ra*afun(k)) * &
+      !             ((afun(af1)*rc(i-1)**ar1) + (cslf*afun(af2)*rc(i-1)**ar2))
+      !  ENDIF
+      !ENDDO
+
+      RETURN
+    END SUBROUTINE get_weff
+  
+    
+    SUBROUTINE let_me_fall_in_peace(mk,ft,dt,dmk)
+      !! Compute the tendency of the moment through sedimentation process.
+      !!
+      !! The method computes the time evolution of the kth order moment through sedimentation:
+      !! dM(k) / dt = Phi(k) / dz
+      !!
+      !! The equation is resolved using a [Crank-Nicolson algorithm](http://en.wikipedia.org/wiki/Crank-Nicolson_method).
+      !!
+      !! Sedimentation algorithm is quite messy. It appeals to the dark side of the Force and uses evil black magic spells
+      !! from ancient times. It is based on \cite{toon1988b,fiadeiro1977,turco1979} and is an update of the algorithm
+      !! originally implemented in the LMDZ-Titan 2D GCM.
+      !!
+      REAL(kind=mm_wp), INTENT(in), DIMENSION(:)  :: mk  !! kth order moment to sediment (m^k.m^-3).
+      REAL(kind=mm_wp), INTENT(in)                :: dt  !! Time step (s).
+      REAL(kind=mm_wp), INTENT(in), DIMENSION(:)  :: ft  !! Downward sedimentation flux (effective velocity of the moment).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dmk !! Tendency of kth order moment (m^k.m^-3).
+      
+      ! Local variables:
+      !~~~~~~~~~~~~~~~~~
+      INTEGER :: i
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: as,bs,cs,mko
+      
+      ALLOCATE(as(mm_nla), bs(mm_nla), cs(mm_nla), mko(mm_nla))
+
+      mko(:) = 0._mm_wp
+      cs(1:mm_nla) = ft(2:mm_nla+1) - mm_dzlay(1:mm_nla)/dt
+      
+      IF (ANY(cs > 0._mm_wp)) THEN
+        ! Implicit case
+        as(1:mm_nla) = ft(1:mm_nla)
+        bs(1:mm_nla) = -(ft(2:mm_nle) + mm_dzlay(1:mm_nla)/dt)
+        cs(1:mm_nla) = -mm_dzlay(1:mm_nla) / dt * mk(1:mm_nla)
+        ! (Tri)diagonal matrix inversion
+        mko(1) = cs(1) / bs(1)
+        DO i=2,mm_nla ; mko(i) = (cs(i)-mko(i-1)*as(i))/bs(i) ; ENDDO
+      
+      ELSE
+        ! Explicit case
+        as(1:mm_nla) = -mm_dzlay(1:mm_nla) / dt
+        bs(1:mm_nla) = -ft(1:mm_nla)
+        ! Boundaries
+        mko(1) = cs(1) * mk(1) / as(1)
+        mko(mm_nla) = (bs(mm_nla)*mk(mm_nla-1) + cs(mm_nla)*mk(mm_nla)) / as(mm_nla)
+        ! Interior
+        mko(2:mm_nla-1) = (bs(2:mm_nla-1)*mk(1:mm_nla-2) + cs(2:mm_nla-1)*mk(2:mm_nla-1)) &
+                          /as(2:mm_nla-1)
+      ENDIF
+      
+      dmk = mko - mk
+      DEALLOCATE(as,bs,cs,mko)
+      
+      RETURN
+    END SUBROUTINE let_me_fall_in_peace
+  
+    
+    !============================================================================
+    ! COAGULATION PROCESS RELATED METHODS
+    !============================================================================
+  
+    SUBROUTINE mm_haze_coagulation(dM0s,dM3s,dM0f,dM3f)
+      !! Get the evolution of the aerosols moments vertical column due to coagulation process.
+      !!
+      !! This is the main method of the coagulation process:
+      !!    1. Computes gamma pre-factor for each parts of the coagulation equation(s).
+      !!    2. Applies the electic correction on the gamma pre-factor.
+      !!    3. Computes the specific flow regime kernels (molecular or hydrodynamic).
+      !!    4. Computes the harmonic mean of the kernels (transitory regime).
+      !!    5. Finally computes the tendencies of the moments.
+      !!
+      !! @Warning
+      !! If the transfert probabilities are set to 1 for the two flow regimes (pco and pfm),
+      !! a floating point exception occured (i.e. a NaN) as we perform a division by zero
+      !!
+      ! Tendency of the 0th order moment of the spherical size-distribution over a time step (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dM0s
+      ! Tendency of the 3rd order moment of the spherical size-distribution (m3.m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dM3s
+      ! Tendency of the 0th order moment of the fractal size-distribution over a time step (m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dM0f
+      ! Tendency of the 3rd order moment of the fractal size-distribution over a time step (m3.m-3).
+      REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dM3f
+
+      ! Local variables:
+      !~~~~~~~~~~~~~~~~~
+      INTEGER :: i
+
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: kco,kfm,slf
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: g_co,g_fm,pco,pfm,mq,tmp
+      REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: a_ss,a_sf,b_ss,b_ff,c_ss,c_sf
+
+      ! Sanity check:
+      IF (mm_coag_choice < 0 .OR. mm_coag_choice > mm_coag_ss+mm_coag_sf+mm_coag_ff) &
+          STOP "invalid choice for coagulation mode interaction activation"
+  
+      ! Allocates local arrays:
+      ALLOCATE(kco(mm_nla),kfm(mm_nla),slf(mm_nla))
+      ALLOCATE(g_co(mm_nla),g_fm(mm_nla), &
+               pco(mm_nla),pfm(mm_nla),   &
+               mq(mm_nla), tmp(mm_nla))
+      ALLOCATE(a_ss(mm_nla),a_sf(mm_nla), &
+               b_ss(mm_nla),b_ff(mm_nla), &
+               c_ss(mm_nla),c_sf(mm_nla))
+  
+      a_ss(:) = 0._mm_wp ; a_sf(:) = 0._mm_wp
+      b_ss(:) = 0._mm_wp ; b_ff(:) = 0._mm_wp
+      c_ss(:) = 0._mm_wp ; c_sf(:) = 0._mm_wp
+  
+
+      ! 1. Gamma pre-factor:
+      !~~~~~~~~~~~~~~~~~~~~~
+      ! gets kco, kfm pre-factors
+      kco(:) = mm_get_kco(mm_temp) ; kfm(:) = mm_get_kfm(mm_temp)
+      ! get slf (slip-flow factor)
+      slf(:) = mm_akn * mm_lambda_air(mm_temp,mm_play)
+  
+
+      ! 2,3,4. Electic correction, Flow regime kernels, Harmonic mean:
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      DO i=1,mm_nla
+        
+        ! SS interactions
+        !~~~~~~~~~~~~~~~~
+        IF (mm_rcs(i) > mm_rcs_min .AND. IAND(mm_coag_choice,mm_coag_ss) /= 0) THEN
+          ! Probability S --> S for M0/CO and M0/FM
+          pco(i) = mm_ps2s(mm_rcs(i),0,0)
+          pfm(i) = mm_ps2s(mm_rcs(i),0,1)
+          
+          ! Gamma (kernel dependent)
+          g_co(i) = g0ssco(mm_rcs(i),slf(i),kco(i))
+          g_fm(i) = g0ssfm(mm_rcs(i),kfm(i))
+          
+          ! Molecular's case
+          !~~~~~~~~~~~~~~~~~
+          a_ss(i) = (pfm(i) - 2._mm_wp) * g_fm(i)
+          b_ss(i) = (1._mm_wp - pfm(i)) * g_fm(i)
+
+          ! Hydrodynamical's case
+          ! In fact: transitory case which is the general case
+          !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          ! (A_SS_CO x A_SS_FM) / (A_SS_CO + A_SS_FM)
+          !IF (g_co(i)*(pco(i)-2._mm_wp)+g_fm(i)*(pfm(i)-2._mm_wp) /= 0._mm_wp) THEN
+          !  a_ss(i) = (g_co(i)*(pco(i)-2._mm_wp)*g_fm(i)*(pfm(i)-2._mm_wp))/(g_co(i)*(pco(i)-2._mm_wp)+g_fm(i)*(pfm(i)-2._mm_wp))
+          !ENDIF
+          ! (B_SS_CO x B_SS_FM) / (B_SS_CO + B_SS_FM)
+          !IF (g_co(i)*(1._mm_wp-pco(i))+g_fm(i)*(1._mm_wp-pfm(i)) /= 0._mm_wp) THEN
+          !  b_ss(i) = (g_co(i)*(1._mm_wp-pco(i))*g_fm(i)*(1._mm_wp-pfm(i)))/(g_co(i)*(1._mm_wp-pco(i))+g_fm(i)*(1._mm_wp-pfm(i)))
+          !ENDIF
+
+          ! Eletric charge correction for M0/SS interactions
+          mq(i) = mm_qmean(mm_rcs(i),mm_rcs(i),0,'SS',mm_temp(i))
+          a_ss(i) = a_ss(i) * mq(i)
+          b_ss(i) = b_ss(i) * mq(i)
+          
+          ! Probability S --> S for M3/CO and M3/FM
+          pco(i) = mm_ps2s(mm_rcs(i),3,0)
+          pfm(i) = mm_ps2s(mm_rcs(i),3,1)
+
+          ! Gamma (kernel dependent)
+          g_co(i) = g3ssco(mm_rcs(i),slf(i),kco(i)) * (pco(i)-1._mm_wp)
+          g_fm(i) = g3ssfm(mm_rcs(i),kfm(i)) * (pfm(i)-1._mm_wp)
+          
+          ! Molecular's case
+          !~~~~~~~~~~~~~~~~~
+          c_ss(i) = g_fm(i)
+
+          ! Hydrodynamical's case
+          ! In fact: transitory case which is the general case
+          !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          ! (C_SS_CO x C_SS_FM) / (C_SS_CO + C_SS_FM)
+          !IF (g_co(i) + g_fm(i) /= 0._mm_wp) THEN
+          !  c_ss(i) = (g_co(i) * g_fm(i)) / (g_co(i) + g_fm(i))
+          !ENDIF
+
+          IF (b_ss(i) <= 0._mm_wp) c_ss(i) = 0._mm_wp
+          
+          ! Eletric charge correction for M3/SS interactions
+          mq(i) = mm_qmean(mm_rcs(i),mm_rcs(i),3,'SS',mm_temp(i))
+          c_ss(i) = c_ss(i) * mq(i)
+        ENDIF ! end SS interactions
+  
+        ! SF interactions
+        !~~~~~~~~~~~~~~~~
+        IF (mm_rcs(i) > mm_rcs_min .AND. mm_rcf(i) > mm_rcf_min .AND. IAND(mm_coag_choice,mm_coag_sf) /= 0) THEN
+          ! Gamma (kernel dependent)
+          g_co(i) = g0sfco(mm_rcs(i),mm_rcf(i),slf(i),kco(i))
+          g_fm(i) = g0sffm(mm_rcs(i),mm_rcf(i),kfm(i))
+          
+          ! Molecular's case
+          !~~~~~~~~~~~~~~~~~
+          a_sf(i) = g_fm(i)
+
+          ! Hydrodynamical's case
+          ! In fact: transitory case which is the general case
+          !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          ! (A_SF_CO x A_SF_FM) / (A_SF_CO + A_SF_FM)
+          !IF(g_co(i) + g_fm(i) /= 0._mm_wp) THEN
+          !  a_sf(i) = (g_co(i) * g_fm(i)) / (g_co(i) + g_fm(i))
+          !ENDIF
+
+          ! Eletric charge correction for M0/SF interactions
+          mq(i) = mm_qmean(mm_rcs(i),mm_rcf(i),0,'SF',mm_temp(i))
+          a_sf(i) = a_sf(i) * mq(i)
+          
+          ! Gamma (kernel dependent)
+          g_co(i) = g3sfco(mm_rcs(i),mm_rcf(i),slf(i),kco(i))
+          g_fm(i) = g3sffm(mm_rcs(i),mm_rcf(i),kfm(i))
+          
+          ! Molecular's case
+          !~~~~~~~~~~~~~~~~~
+          c_sf(i) = g_fm(i)
+
+          ! Hydrodynamical's case
+          ! In fact: transitory case which is the general case
+          !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          ! (C_SF_CO x C_SF_FM) / (C_SF_CO + C_SF_FM)
+          !IF (g_co(i) + g_fm(i) /= 0._mm_wp) THEN
+          !  c_sf(i) = (g_co(i) * g_fm(i)) / (g_co(i) + g_fm(i))
+          !ENDIF
+          
+          ! Eletric charge correction for M3/SF interactions
+          mq(i) = mm_qmean(mm_rcs(i),mm_rcf(i),3,'SF',mm_temp(i))
+          c_sf(i) = c_sf(i) * mq(i)
+        ENDIF ! end SF interactions
+        
+        ! FF interactions
+        !~~~~~~~~~~~~~~~~
+        IF(mm_rcf(i) > mm_rcf_min .AND. IAND(mm_coag_choice,mm_coag_sf) /= 0) THEN
+          ! Gamma (kernel dependent)
+          g_co(i) = g0ffco(mm_rcf(i),slf(i),kco(i))
+          g_fm(i) = g0fffm(mm_rcf(i),kfm(i))
+
+          ! Molecular's case
+          !~~~~~~~~~~~~~~~~~
+          b_ff(i) = g_fm(i)
+
+          ! Hydrodynamical's case
+          ! In fact: transitory case which is the general case
+          !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+          ! (B_FF_CO x B_FF_FM) / (B_FF_CO + B_FF_FM)
+          !IF (g_co(i) + g_fm(i) /= 0._mm_wp) THEN
+          !  b_ff(i) = (g_co(i) * g_fm(i)) / (g_co(i) + g_fm(i))
+          !ENDIF
+          
+          ! Eletric charge correction for M0/FF interactions
+          mq(i) = mm_qmean(mm_rcf(i),mm_rcf(i),0,'FF',mm_temp(i))
+          b_ff(i) = b_ff(i) * mq(i)
+        ENDIF ! end FF interactions
+      
+      ENDDO ! end n_lay
+  
+      DEALLOCATE(g_co,g_fm,kco,kfm,pco,pfm,slf)
+  
+      ! 5. Tendencies of the moments:
+      !~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+      ! Now we will use the kharm two by two to compute :
+      ! dM_0_S/mm_dt = kharm(1) * M_0_S^2 - kharm(2) * M_0_S * m_0_F
+      ! dM_0_F/mm_dt = kharm(3) * M_0_S^2 - kharm(4) * M_0_F^2
+      ! dM_3_S/mm_dt = kharm(5) * M_3_S^2 - kharm(6) * M_3_S * m_3_F
+      ! dM_3_F/mm_dt = - dM_3_S/mm_dt
+      !
+      ! We use a (semi) implicit scheme:
+      ! When X appears as square we set one X at t+1, the other a t.
+      ! --- dM0s
+      tmp(:) = mm_dt * (a_ss*mm_m0aer_s - a_sf*mm_m0aer_f)
+      dm0s(:) =  mm_m0aer_s * (tmp / (1._mm_wp - tmp))
+      ! --- dM0f
+      tmp(:) = mm_dt * b_ff * mm_m0aer_f
+      dm0f(:) = (b_ss*mm_dt*mm_m0aer_s**2 - tmp*mm_m0aer_f) / (1._mm_wp + tmp)
+      ! --- dM3s
+      tmp(:) = mm_dt * (c_ss*mm_m3aer_s - c_sf*mm_m3aer_f)
+      dm3s(:) =  mm_m3aer_s * (tmp / (1._mm_wp - tmp))
+      ! --- dM3f
+      dm3f(:) = -dm3s
+  
+      DEALLOCATE(a_ss,a_sf,b_ss,b_ff,c_ss,c_sf,tmp)
+  
+      RETURN
+    END SUBROUTINE mm_haze_coagulation
+  
+  
+    ! =====================
+    ! Hydrodynamical's case
+    ! =====================
+    ELEMENTAL FUNCTION g0ssco(rcs,slf,kco) RESULT(res)
+      !! Get gamma pre-factor for the 0th order moment with SS interactions in the Continuous flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: slf !! Slip-Flow correction pre-factor.
+      REAL(kind=mm_wp), INTENT(in) :: kco !! Thermodynamic continuous flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp) RETURN
+      ! Computes mm_alpha coefficients
+      a1 = mm_alpha_s(1._mm_wp)
+      a2 = mm_alpha_s(-1._mm_wp)
+      a3 = mm_alpha_s(-2._mm_wp)
+      ! Computes gamma pre-factor
+      res = kco * (1._mm_wp + a1*a2 + slf/rcs*(a2+a1*a3))
+      RETURN
+    END FUNCTION g0ssco
+  
+
+    ELEMENTAL FUNCTION g0sfco(rcs,rcf,slf,kco) RESULT(res)
+      !! Get gamma pre-factor for the 0th order moment with SF interactions in the Continuous flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: rcf !! Characteristic radius of the fractal size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: slf !! Slip-Flow correction pre-factor.
+      REAL(kind=mm_wp), INTENT(in) :: kco !! Thermodynamic continuous flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, a6, e, rcff
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp .OR. rcf <= 0._mm_wp) RETURN
+      ! Computes mm_alpha and other coefficients
+      e = 3._mm_wp/mm_df
+      rcff = rcf**e * mm_rb2ra
+      a1 = mm_alpha_s(1._mm_wp)  ; a2 = mm_alpha_f(-e)        ; a3 = mm_alpha_f(e)
+      a4 = mm_alpha_s(-1._mm_wp) ; a5 = mm_alpha_s(-2._mm_wp) ; a6 = mm_alpha_f(-2._mm_wp*e)
+      ! Computes gamma pre-factor
+      res = kco * (2._mm_wp + a1*a2*rcs/rcff + &
+                   a4*a3*rcff/rcs +            &
+                   slf * (a4/rcs + a2/rcff +   &
+                          a5*a3*rcff/rcs**2 +  &
+                          a1*a6*rcs/rcff**2))
+      RETURN
+    END FUNCTION g0sfco
+  
+
+    ELEMENTAL FUNCTION g0ffco(rcf,slf,kco) RESULT(res)
+      !! Get gamma pre-factor for the 0th order moment with FF interactions in the Continuous flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcf !! Characteristic radius of the fractal size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: slf !! Slip-Flow correction pre-factor.
+      REAL(kind=mm_wp), INTENT(in) :: kco !! Thermodynamic continuous flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, e, rcff
+      res = 0._mm_wp ; IF (rcf <= 0._mm_wp) RETURN
+      ! Computes mm_alpha and other coefficients
+      e = 3._mm_wp/mm_df
+      rcff = rcf**e * mm_rb2ra
+      a1 = mm_alpha_f(e) ; a2 = mm_alpha_f(-e) ; a3 = mm_alpha_f(-2._mm_wp*e)
+      ! Computes gamma pre-factor
+      res = kco * (1._mm_wp + a1*a2 + slf/rcff *(a2+a1*a3))
+      RETURN
+    END FUNCTION g0ffco
+
+
+    ELEMENTAL FUNCTION g3ssco(rcs, slf, kco) RESULT(res)
+      !! Get gamma pre-factor for the 3rd order moment with SS interactions in the Continuous flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: slf !! Slip-Flow correction pre-factor.
+      REAL(kind=mm_wp), INTENT(in) :: kco !! Thermodynamic continuous flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, a6
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp) RETURN
+      ! Computes mm_alpha coefficients
+      a1 = mm_alpha_s(3._mm_wp) ; a2 = mm_alpha_s(2._mm_wp)  ; a3 = mm_alpha_s(1._mm_wp)
+      a4 = mm_alpha_s(4._mm_wp) ; a5 = mm_alpha_s(-1._mm_wp) ; a6 = mm_alpha_s(-2._mm_wp)
+      ! Computes gamma pre-factor
+      res = kco/(a1**2*rcs**3) * (2._mm_wp*a1 + a2*a3 + a4*a5 + &
+                                  slf/rcs*(a3**2 + a4*a6 + a1*a5 + a2))
+      RETURN
+    END FUNCTION g3ssco
+
+
+    ELEMENTAL FUNCTION g3sfco(rcs, rcf, slf, kco) RESULT(res)
+      !! Get gamma pre-factor for the 3rd order moment with SF interactions in the Continuous flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: rcf !! Characteristic radius of the fractal size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: slf !! Slip-Flow correction pre-factor.
+      REAL(kind=mm_wp), INTENT(in) :: kco !! Thermodynamic continuous flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, a6, a7, a8, e, rcff
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp .OR. rcf <= 0._mm_wp) RETURN
+      ! Computes mm_alpha and other coefficients
+      e = 3._mm_wp/mm_df
+      rcff = rcf**e * mm_rb2ra
+      a1 = mm_alpha_s(3._mm_wp)    ; a2 = mm_alpha_s(4._mm_wp) ; a3 = mm_alpha_f(-e)
+      a4 = mm_alpha_s(2._mm_wp)    ; a5 = mm_alpha_f(e)        ; a6 = mm_alpha_s(1._mm_wp)
+      a7 = mm_alpha_f(-2._mm_wp*e) ; a8 = mm_alpha_f(3._mm_wp)
+      ! Computes gamma pre-factor
+      res = kco/(a1*a8*(rcs*rcf)**3) * (2._mm_wp*a1*rcs**3 + a2*rcs**4*a3/rcff + a4*rcs**2*a5*rcff + &
+                                        slf * (a4*rcs**2 + a1*rcs**3*a3/rcff +                       &
+                                               a6*rcs*a5*rcff +                                      &
+                                               a2*rcs**4*a7/rcff**2))
+      RETURN
+    END FUNCTION g3sfco
+
+
+    ! ================
+    ! Molecular's case
+    ! ================
+    ELEMENTAL FUNCTION g0ssfm(rcs, kfm) RESULT(res)
+      !! Get gamma pre-factor for the 0th order moment with SS interactions in the Free Molecular flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: kfm !! Thermodynamic free molecular flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp) RETURN
+      ! Computes mm_alpha coefficients
+      a1 = mm_alpha_s(0.5_mm_wp) ; a2 = mm_alpha_s(1._mm_wp)   ; a3 = mm_alpha_s(-0.5_mm_wp)
+      a4 = mm_alpha_s(2._mm_wp)  ; a5 = mm_alpha_s(-1.5_mm_wp)
+      ! Computes gamma pre-factor
+      res = kfm * (rcs**0.5_mm_wp*mm_get_btk(1,0)) * (a1 + 2._mm_wp*a2*a3 + a4*a5)
+      RETURN
+    END FUNCTION g0ssfm
+  
+
+    ELEMENTAL FUNCTION g0sffm(rcs, rcf, kfm) RESULT(res)
+      !! Get gamma pre-factor for the 0th order moment with SF interactions in the Free Molecular flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: rcf !! Characteristic radius of the fractal size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: kfm !! Thermodynamic free molecular flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10
+      REAL(kind=mm_wp) :: e1, e2, e3, e4
+      REAL(kind=mm_wp) :: rcff1, rcff2, rcff3, rcff4
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp .OR. rcf <= 0._mm_wp) RETURN
+      ! Computes mm_alpha and other coefficients
+      e1 = 3._mm_wp/mm_df
+      e2 = 6._mm_wp/mm_df
+      e3 = (6._mm_wp-3._mm_wp*mm_df)/(2._mm_wp*mm_df)
+      e4 = (12._mm_wp-3._mm_wp*mm_df)/(2._mm_wp*mm_df)
+  
+      rcff1 = rcf**e1 * mm_rb2ra
+      rcff2 = rcff1**2
+      rcff3 = rcf**e3 * mm_rb2ra
+      rcff4 = rcf**e4 * mm_rb2ra**2
+  
+      a1 = mm_alpha_s(0.5_mm_wp)  ; a2 = mm_alpha_s(-0.5_mm_wp) ; a3 = mm_alpha_f(e1)
+      a4 = mm_alpha_s(-1.5_mm_wp) ; a5 = mm_alpha_f(e2)         ; a6 = mm_alpha_s(2._mm_wp)
+      a7 = mm_alpha_f(-1.5_mm_wp) ; a8 = mm_alpha_s(1._mm_wp)   ; a9 = mm_alpha_f(e3)
+      a10 = mm_alpha_f(e4)
+  
+      ! Computes gamma pre-factor
+      res = kfm * mm_get_btk(4,0) * (a1*rcs**0.5_mm_wp + 2._mm_wp*a2*a3*rcff1/rcs**0.5_mm_wp + &
+                                     a4*a5*rcff2/rcs**1.5_mm_wp +                              &
+                                     a6*a7*rcs**2/rcf**1.5_mm_wp +                             &
+                                     2._mm_wp*a8*a9*rcs*rcff3 +                                &
+                                     a10*rcff4)
+      RETURN
+    END FUNCTION g0sffm
+
+
+    ELEMENTAL FUNCTION g0fffm(rcf, kfm) RESULT(res)
+      !! Get gamma pre-factor for the 0th order moment with FF interactions in the Free Molecular flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcf !! Characteristic radius of the fractal size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: kfm !! Thermodynamic free molecular flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, e1, e2, e3, rcff
+      res = 0._mm_wp ; IF (rcf <= 0._mm_wp) RETURN
+      ! Computes mm_alpha and other coefficients
+      e1 = 3._mm_wp/mm_df
+      e2 = (6_mm_wp-3._mm_wp*mm_df)/(2._mm_wp*mm_df)
+      e3 = (12._mm_wp-3._mm_wp*mm_df)/(2._mm_wp*mm_df)
+      rcff = rcf**e3 * mm_rb2ra**2
+      a1 = mm_alpha_f(e3)         ; a2 = mm_alpha_f(e1)          ; a3 = mm_alpha_f(e2)
+      a4 = mm_alpha_f(-1.5_mm_wp) ; a5 = mm_alpha_f(2._mm_wp*e1)
+      ! Computes gamma pre-factor
+      res = kfm * (rcff*mm_get_btk(3,0)) * (a1 + 2._mm_wp*a2*a3 + a4*a5)
+      RETURN
+    END FUNCTION g0fffm
+
+
+    ELEMENTAL FUNCTION g3ssfm(rcs, kfm) RESULT(res)
+      !! Get gamma pre-factor for the 3rd order moment with SS interactions in the Free Molecular flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: kfm !! Thermodynamic free molecular flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp) RETURN
+      ! Computes mm_alpha coefficients
+      a1 = mm_alpha_s(3.5_mm_wp)  ; a2 = mm_alpha_s(1._mm_wp)  ; a3 = mm_alpha_s(2.5_mm_wp)
+      a4 = mm_alpha_s(2._mm_wp)   ; a5 = mm_alpha_s(1.5_mm_wp) ; a6 = mm_alpha_s(5._mm_wp)
+      a7 = mm_alpha_s(-1.5_mm_wp) ; a8 = mm_alpha_s(4._mm_wp)  ; a9 = mm_alpha_s(-0.5_mm_wp)
+      a10 = mm_alpha_s(3._mm_wp)  ; a11 = mm_alpha_s(0.5_mm_wp)
+      ! Computes gamma pre-factor
+      res = kfm * (mm_get_btk(1,3)/(a10**2*rcs**2.5_mm_wp)) * (a1 + 2._mm_wp*a2*a3 + &
+                                                               a4*a5 + a6*a7 +       &
+                                                               2._mm_wp*a8*a9 + a10*a11)
+      RETURN
+    END FUNCTION g3ssfm
+
+
+    ELEMENTAL FUNCTION g3sffm(rcs, rcf, kfm) RESULT(res)
+      !! Get gamma pre-factor for the 3rd order moment with SF interactions in the Free Molecular flow regime.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rcs !! Characteristic radius of the spherical size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: rcf !! Characteristic radius of the fractal size-distribution.
+      REAL(kind=mm_wp), INTENT(in) :: kfm !! Thermodynamic free molecular flow regime pre-factor.
+      REAL(kind=mm_wp) :: res             !! Gamma coagulation kernel pre-factor.
+      REAL(kind=mm_wp) :: a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12
+      REAL(kind=mm_wp) :: e1, e2, e3, rcff1, rcff2, rcff3
+      res = 0._mm_wp ; IF (rcs <= 0._mm_wp .OR. rcf <= 0._mm_wp) RETURN
+      ! Computes mm_alpha and other coefficients
+      e1 = 3._mm_wp/mm_df
+      e2 = (6._mm_wp-3._mm_wp*mm_df)/(2._mm_wp*mm_df)
+      e3 = (12._mm_wp-3._mm_wp*mm_df)/(2._mm_wp*mm_df)
+
+      rcff1 = rcf**e1 * mm_rb2ra
+      rcff2 = rcf**e2 * mm_rb2ra
+      rcff3 = rcf**e3 * mm_rb2ra**2
+      
+      a1 = mm_alpha_s(3.5_mm_wp)  ; a2 = mm_alpha_s(2.5_mm_wp)   ; a3 = mm_alpha_f(e1)
+      a4 = mm_alpha_s(1.5_mm_wp)  ; a5 = mm_alpha_f(2._mm_wp*e1) ; a6 = mm_alpha_s(5._mm_wp)
+      a7 = mm_alpha_f(-1.5_mm_wp) ; a8 = mm_alpha_s(4._mm_wp)    ; a9 = mm_alpha_f(e2)
+      a10 = mm_alpha_s(3._mm_wp)  ; a11 = mm_alpha_f(e3)         ; a12 = mm_alpha_f(3._mm_wp)
+      
+      ! Computes gamma pre-factor
+      res = kfm * (mm_get_btk(4,3)/(a10*a12*(rcs*rcf)**3)) * (a1*rcs**3.5_mm_wp + &
+                                                              2._mm_wp*a2*a3*rcs**2.5_mm_wp*rcff1 + &
+                                                              a4*a5*rcs**1.5_mm_wp*rcff1**2 +       &
+                                                              a6*a7*rcs**5/rcf**1.5_mm_wp +         &
+                                                              2._mm_wp*a8*a9*rcs**4*rcff2 +         &
+                                                              a10*a11*rcs**3*rcff3)
+      RETURN
+    END FUNCTION g3sffm
+
+END MODULE MP2M_HAZE
Index: trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_intgcm.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_intgcm.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_intgcm.F90	(revision 3560)
@@ -0,0 +1,253 @@
+MODULE MP2M_INTGCM
+    !============================================================================
+    !
+    !     Purpose
+    !     -------
+    !     Interface to YAMMS for the LMDZ GCM.
+    !        --> It initializes all the parameters of the model from direct input (mm_global_init).
+    !
+    !     The module also contains three methods:
+    !        - mm_initialize(dt,haze_prod_pCH4,p_prod,tx_prod,rc_prod,rm,df,rho_aer,rplanet,g0,air_rad,air_mmol,cfgpath)
+    !        - read_aprm(parser,sec,pp)
+    !        - abort_program(err)
+    !
+    !     Authors
+    !     -------
+    !     B. de Batz de Trenquelléon, J. Burgalat (11/2024)
+    !
+    !============================================================================
+
+    USE MP2M_MPREC
+    USE MP2M_GLOBALS
+    USE MP2M_MICROPHYSICS
+    USE MP2M_HAZE
+    USE MP2M_METHODS
+    USE SWIFT_CFGPARSE
+    USE LINT_DATASETS
+    IMPLICIT NONE
+
+    CONTAINS
+
+    SUBROUTINE mm_initialize(dt,haze_prod_pCH4,p_prod,tx_prod,rc_prod,rm,df,rho_aer,rplanet,g0,air_rad,air_mmol,cfgpath)
+        !! Initialize global parameters of the model.
+        !!
+        !! The function initializes all the global parameters of the model from direct input.
+        !! Boolean parameters are optional as they are rather testing parameters.
+        !!
+        !! @note
+        !! If the subroutine fails to initialize parameters, the run is aborted.
+        !!
+        !! @warning
+        !! If OpenMP is activated, this subroutine must be called in an $OMP SINGLE statement as it
+        !! initializes global variable that are not thread private.
+        !!
+        
+        ! Microphysics timestep (s).
+        REAL(kind=mm_wp), INTENT(in)           :: dt
+        ! Enable/Disable production from CH4 photolysis.
+        LOGICAL                                :: haze_prod_pCH4
+        ! Aerosol production pressure level (Pa).
+        REAL(kind=mm_wp), INTENT(in)           :: p_prod
+        ! Spherical aerosol production rate (kg.m-2.s-1).
+        REAL(kind=mm_wp), INTENT(in)           :: tx_prod
+        ! Spherical aerosol characteristic radius production (m).
+        REAL(kind=mm_wp), INTENT(in)           :: rc_prod
+        ! Monomer radius (m).
+        REAL(kind=mm_wp), INTENT(in)           :: rm
+        ! Fractal dimension of aerosols (-).
+        REAL(kind=mm_wp), INTENT(in)           :: df
+        ! Aerosol density (kg.m-3).
+        REAL(kind=mm_wp), INTENT(in)           :: rho_aer
+        ! Planet radius (m).
+        REAL(kind=mm_wp), INTENT(in)           :: rplanet
+        ! Planet gravity acceleration at ground level (m.s-2).
+        REAL(kind=mm_wp), INTENT(in)           :: g0
+        ! Mean radius of air molecules (m).
+        REAL(kind=mm_wp), INTENT(in)           :: air_rad
+        ! Mean molar mass of air molecules (kg.mol-1).
+        REAL(kind=mm_wp), INTENT(in)           :: air_mmol
+        ! Internal microphysics configuration file.
+        CHARACTER(len=*), INTENT(in), OPTIONAL :: cfgpath
+    
+        ! Microphysical configuration file.
+        TYPE(cfgparser)        :: cparser
+        ! Look-up tables (transfert probabilities, mean electric correction).
+        CHARACTER(len=st_slen) :: pssfile,mqfile
+        ! Enable/disable Haze process.
+        LOGICAL                :: w_h_prod,w_h_sed,w_h_coag,fwsed_m0,fwsed_m3
+        ! Coagulation interactions
+        INTEGER                :: coag_choice
+        ! Thresholds related parameters.
+        REAL(kind=mm_wp)       :: m0as_min,rcs_min,m0af_min,rcf_min
+        ! Debug mode control flag (may print lot of stuff if enabled).
+        LOGICAL                :: wdebug
+
+        ! Local variables.
+        TYPE(error)                                 :: err
+        REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: tmp
+
+        !-----------------------------
+        ! 1. Parameters initialization
+        !-----------------------------
+        w_h_prod    = .true.
+        w_h_sed     = .true.
+        w_h_coag    = .true.
+        fwsed_m0    = .true.
+        fwsed_m3    = .false.
+        coag_choice = 7
+        m0as_min    = 1e-8_mm_wp
+        rcs_min     = 1e-9_mm_wp
+        m0af_min    = 1e-8_mm_wp
+        rcf_min     = 1e-9_mm_wp
+        wdebug      = .false.
+    
+        WRITE(*,'(a)') "##### MM_GCM SPEAKING #####"
+        WRITE(*,'(a)') "I will initialize the microphysics model in moments YAMMS"
+        WRITE(*,'(a)') "On error I will simply abort the program. Stay near your computer !"
+        WRITE(*,*)
+        WRITE(*,'(a)') "Reading muphys configuration file ("//trim(cfgpath)//")."
+        err = cfg_read_config(cparser,TRIM(cfgpath),.true.)
+        IF (err /= 0) THEN
+            WRITE(*,'(a)') "Error when reading muphys configuration file in mm_initialize..."
+            call abort_program(err)
+        ENDIF
+    
+        ! YAMMS internal parameters:
+        ! ~~~~~~~~~~~~~~~~~~~~~~~~~~
+        ! The following parameters are primarily used to test and debug YAMMS.
+        ! They are set in an optional configuration file and default to suitable values for production runs.
+        err = mm_check_opt(cfg_get_value(cparser,"haze_production",w_h_prod)          ,w_h_prod   ,.true.    ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"haze_sedimentation",w_h_sed)        ,w_h_sed    ,.true.    ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"haze_coagulation",w_h_coag)         ,w_h_coag   ,.true.    ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"wsed_m0",fwsed_m0)                  ,fwsed_m0   ,.true.    ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"wsed_m3",fwsed_m3)                  ,fwsed_m3   ,.false.   ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"haze_coag_interactions",coag_choice),coag_choice,7         ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"m0as_min",m0as_min)                 ,m0as_min   ,1e-8_mm_wp,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"rcs_min",rcs_min)                   ,rcs_min    ,1e-9_mm_wp,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"m0af_min",m0af_min)                 ,m0af_min   ,1e-8_mm_wp,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"rcf_min",rcf_min)                   ,rcf_min    ,rm        ,mm_log)
+        err = mm_check_opt(cfg_get_value(cparser,"debug",wdebug)                      ,wdebug     ,.false.   ,mm_log)
+    
+        ! Alpha function: 
+        ! ~~~~~~~~~~~~~~~
+        ! Spherical mode inter-moments function parameters.
+        IF (.NOT.cfg_has_section(cparser,'alpha_s')) call abort_program(error("Cannot find [alpha_s] section",-1))
+        err = read_aprm(cparser,'alpha_s',mm_asp)
+        IF (err /= 0) call abort_program(error("alpha_s: "//TRIM(err%msg),-1))
+        ! Fractal mode inter-moments function parameters.
+        IF (.NOT.cfg_has_section(cparser,'alpha_f')) call abort_program(error("Cannot find [alpha_f] section",-1))
+        err = read_aprm(cparser,'alpha_f',mm_afp)
+        IF (err /= 0) call abort_program(error("alpha_s: "//TRIM(err%msg),-1))
+
+        ! Transfert probabilities (S --> F):
+        ! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        err = mm_check_opt(cfg_get_value(cparser, "transfert_probability", mm_w_ps2s), mm_w_ps2s, wlog=mm_log)
+        IF (err/=0) call abort_program(err)
+
+        IF (mm_w_haze_coag .AND. mm_w_ps2s) THEN
+            err = mm_check_opt(cfg_get_value(cparser, "ps2s_file", pssfile), pssfile)
+            IF (err /= 0) call abort_program(err)
+    
+            IF (.NOT.read_dset(pssfile,'p_m0_co',mm_pco0p)) THEN
+            call abort_program(error("Cannot get 'p_m0_co' from "//pssfile,-1))
+            ENDIF
+            IF (.NOT.read_dset(pssfile,'p_m3_co',mm_pco3p)) THEN
+            call abort_program(error("Cannot get 'p_m3_co' from "//pssfile,-1))
+            ENDIF
+            IF (.NOT.read_dset(pssfile,'p_m0_fm',mm_pfm0p)) THEN
+            call abort_program(error("Cannot get 'p_m0_fm' from "//pssfile,-1))
+            ENDIF
+            IF (.NOT.read_dset(pssfile,'p_m3_fm',mm_pfm3p)) THEN
+            call abort_program(error("Cannot get 'p_m3_fm' from "//pssfile,-1))
+            ENDIF
+        ENDIF
+
+        ! Mean electric correction:
+        ! ~~~~~~~~~~~~~~~~~~~~~~~~~
+        err = mm_check_opt(cfg_get_value(cparser, "electric_charging", mm_w_qe), mm_w_qe, wlog=mm_log)
+        IF (err/=0) call abort_program(err)
+
+        IF (mm_w_haze_coag .AND. mm_w_qe) THEN
+            err = mm_check_opt(cfg_get_value(cparser, "mq_file", mqfile), mqfile)
+            IF (err /= 0) call abort_program(err)
+    
+            IF (.NOT.read_dset(mqfile,'qbsf0',mm_qbsf0)) THEN
+            call abort_program(error("Cannot get 'qbsf0' from "//mqfile,-1))
+            ELSE
+            mm_qbsf0_e(1,1) = MINVAL(mm_qbsf0%x)
+            mm_qbsf0_e(1,2) = MAXVAL(mm_qbsf0%x)
+            mm_qbsf0_e(2,1) = MINVAL(mm_qbsf0%y)
+            mm_qbsf0_e(2,2) = MAXVAL(mm_qbsf0%y)
+            ENDIF
+            IF (.NOT.read_dset(mqfile,'qbsf3',mm_qbsf3)) THEN
+            call abort_program(error("Cannot get 'qbsf3' from "//mqfile,-1))
+            ELSE
+            mm_qbsf3_e(1,1) = MINVAL(mm_qbsf3%x)
+            mm_qbsf3_e(1,2) = MAXVAL(mm_qbsf3%x)
+            mm_qbsf3_e(2,1) = MINVAL(mm_qbsf3%y)
+            mm_qbsf3_e(2,2) = MAXVAL(mm_qbsf3%y)
+            ENDIF
+            IF (.NOT.read_dset(mqfile,'qbff0',mm_qbff0)) THEN
+            call abort_program(error("Cannot get 'qbff0' from "//mqfile,-1))
+            ELSE
+            mm_qbff0_e(1,1) = MINVAL(mm_qbff0%x)
+            mm_qbff0_e(1,2) = MAXVAL(mm_qbff0%x)
+            mm_qbff0_e(2,1) = MINVAL(mm_qbff0%y)
+            mm_qbff0_e(2,2) = MAXVAL(mm_qbff0%y)
+            ENDIF
+        ENDIF
+    
+        ! btk coefficients:
+        ! ~~~~~~~~~~~~~~~~~
+        IF (.NOT.cfg_has_section(cparser,'btks')) call abort_program(error("Cannot find [btks] section",-1))
+
+        err = cfg_get_value(cparser,"btks/bt0",tmp) ; IF (err/=0) call abort_program(error("bt0: "//TRIM(err%msg),-1))
+        IF (SIZE(tmp) /= 5)  call abort_program(error("bt0: Inconsistent number of coefficients",-1))
+        mm_bt0 = tmp
+        err = cfg_get_value(cparser,"btks/bt3",tmp) ; IF (err/=0) call abort_program(error("bt3: "//TRIM(err%msg),-1))
+        IF (SIZE(tmp) /= 5)  call abort_program(error("bt3: Inconsistent number of coefficients",-1))
+        mm_bt3 = tmp
+    
+        !------------------------
+        ! 2. YAMMS initialization
+        !------------------------
+        err = mm_global_init_0(dt,df,rm,rho_aer,haze_prod_pCH4,p_prod,tx_prod,rc_prod, &
+                               rplanet,g0,air_rad,air_mmol,coag_choice,                &
+                               w_h_prod,w_h_sed,w_h_coag,fwsed_m0,fwsed_m3,            &
+                               m0as_min,rcs_min,m0af_min,rcf_min,wdebug)
+        IF (err /= 0) call abort_program(err)
+        
+        ! Dump parameters.
+        WRITE(*,'(a)')        "========= MUPHYS PARAMETERS ==========="
+        WRITE(*,'(a,L2)')     "transfert_probability: ", mm_w_ps2s
+        WRITE(*,'(a,L2)')     "electric_charging    : ", mm_w_qe
+        call mm_dump_parameters()
+        
+    END SUBROUTINE mm_initialize
+    
+
+    FUNCTION read_aprm(parser,sec,pp) RESULT(err)
+        !! Read and store alpha function parameters.
+        !!
+        TYPE(cfgparser), INTENT(in)  :: parser !! Configuration parser
+        CHARACTER(len=*), INTENT(in) :: sec    !! Name of the section that contains the parameters.
+        TYPE(aprm), INTENT(out)      :: pp     !! Object that stores the parameters values.
+        TYPE(error) :: err                     !! Error status of the function.
+        err = cfg_get_value(parser,TRIM(sec)//'/a',pp%a) ; IF (err /= 0) RETURN
+        err = cfg_get_value(parser,TRIM(sec)//'/b',pp%b) ; IF (err /= 0) RETURN
+        err = cfg_get_value(parser,TRIM(sec)//'/c',pp%c) ; IF (err /= 0) RETURN
+        IF (SIZE(pp%a) /= SIZE(pp%b) .OR. SIZE(pp%a) /= SIZE(pp%c)) &
+        err = error("Inconsistent number of coefficients (a,b, and c must have the same size)",-1)
+        RETURN
+    END FUNCTION read_aprm
+
+
+    SUBROUTINE abort_program(err)
+        !! Dump error message and abort the program.
+        !!
+        TYPE(error), INTENT(in) :: err !! Error object.
+        WRITE(stderr,'(a)') "ERROR: "//TRIM(err%msg) 
+        CALL EXIT(err%id)
+    END SUBROUTINE abort_program
+
+END MODULE MP2M_INTGCM
Index: trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_methods.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_methods.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_methods.F90	(revision 3560)
@@ -0,0 +1,264 @@
+MODULE MP2M_METHODS
+    !============================================================================
+    !
+    !     Purpose
+    !     -------
+    !     Model miscellaneous methods module.
+    !
+    !     The module contains miscellaneous methods used in the haze of the model.
+    !     The module contains nine methods:
+    !        - mm_lambda_air
+    !        - mm_eta_air
+    !        - mm_ps2s
+    !        - mm_qmean
+    !        - mm_get_btk
+    !        - mm_get_kco
+    !        - mm_get_kfm
+    !
+    !     Authors
+    !     -------
+    !     B. de Batz de Trenquelléon, J. Burgalat (11/2024)
+    !
+    !============================================================================
+
+    USE MP2M_MPREC
+    USE MP2M_GLOBALS
+    USE LINT_DSET
+    USE LINT_LOCATORS
+    IMPLICIT NONE
+
+    PRIVATE
+
+    PUBLIC  :: mm_lambda_air, mm_eta_air, mm_ps2s, mm_qmean, mm_get_btk, mm_get_kfm, mm_get_kco
+
+
+    CONTAINS
+
+    !============================================================================
+    !                          GENERAL METHODS
+    !============================================================================
+
+    ELEMENTAL FUNCTION mm_lambda_air(T,P) RESULT(res)
+      !! Get the air mean free path at given temperature and pressure.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: T ! Temperature (K).
+      REAL(kind=mm_wp), INTENT(in) :: P ! Pressure level (Pa).
+      REAL(kind=mm_wp) :: res           ! Air mean free path (m).
+
+      res = (mm_kboltz*T) / (dsqrt(2._mm_wp)*mm_pi*(2._mm_wp*mm_air_rad)**2*P)
+      
+      RETURN
+    END FUNCTION mm_lambda_air
+
+    
+    ELEMENTAL FUNCTION mm_eta_air(T) RESULT (res)
+      !! Get the air dynamical viscosity at a given temperature using Sutherland method.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: T ! Temperature (K).
+      REAL(kind=mm_wp) :: res           ! Air viscosity at given temperature (Pa.s-1).
+
+      REAL(kind=mm_wp), PARAMETER :: eta0 = 1.74e-5_mm_wp
+      REAL(kind=mm_wp), PARAMETER :: Tsut = 109._mm_wp
+      REAL(kind=mm_wp), PARAMETER :: Tref = 293._mm_wp
+      
+      res = eta0 * dsqrt(T/Tref) * ((1._mm_wp + Tsut/Tref) / (1._mm_wp + Tsut/T))
+      
+      RETURN
+    END FUNCTION mm_eta_air
+
+
+    !============================================================================
+    !                      AEROSOL COAGULATION METHODS
+    !============================================================================
+    
+    FUNCTION mm_ps2s(rcs,k,flow) RESULT(res)
+      !! Get the proportion of aerosols that remains in the spherical mode during SS coagulation.
+      !!
+      !! From __k__ and __flow__ values, the method selects one of the four probability datasets
+      !! in mm_globals(module) module (for instance mm_pco0p) and interpolates linearly probability
+      !! for the given value of __rcs__, __T__ and __P__.
+      !!
+      !! @Warning
+      !! Here, the method assumes the datasets define the probability for __spherical__ particles to
+      !! be transferred in the __fractal__ mode, but returns the proportion of particles that remains
+      !! in the mode (which is expected by MP2M model).
+      !!
+      ! Characteristic radius of the spherical size-distribution (m).
+      REAL(kind=mm_wp), INTENT(in) :: rcs
+      ! Order of the moment (0 or 3 expected).
+      INTEGER, INTENT(in)          :: k
+      ! Flow regime indicator (0: Continuous - Kn << 1, 1: Free-Molecular - Kn >> 1).
+      INTEGER, INTENT(in)          :: flow
+      
+      ! Proportion of spherical particles that stay in the spherical mode during SS coagulation.
+      REAL(kind=mm_wp) :: res
+      
+      ! Local variable.
+      TYPE(dset1d), POINTER :: pp
+      
+      res = 1._mm_wp
+      IF (rcs <= 0.0_mm_wp .OR. .NOT.mm_w_ps2s) RETURN
+      
+      SELECT CASE(k+flow)
+        CASE(0)      ; pp => mm_pco0p ! 0 = 0 + 0 -> M0 / CO
+        CASE(1)      ; pp => mm_pfm0p ! 1 = 0 + 1 -> M0 / FM
+        CASE(3)      ; pp => mm_pco3p ! 3 = 3 + 0 -> M3 / CO
+        CASE(4)      ; pp => mm_pfm3p ! 4 = 3 + 1 -> M3 / FM
+        CASE DEFAULT ; RETURN
+      END SELECT
+
+      IF (.NOT.hdcd_lint_dset(rcs,pp,locate_reg_ext,res)) THEN
+        WRITE(*,'(a)') "mp2m_methods:ps2s_sc: Cannot interpolate transfert probability"
+        call EXIT(10)
+      ELSE
+        ! Sanity check: bound probability value between 0 and 1.
+        res = MAX(0.0_mm_wp,MIN(res,1.0_mm_wp))
+        ! We have interpolated f = 1 - p and we need p !
+        res = 1._mm_wp - res
+      ENDIF
+    END FUNCTION mm_ps2s
+
+
+    FUNCTION mm_qmean(rc1,rc2,order,modes,T) RESULT(res)
+      !! Get the electric correction for coagulation kernel.
+      !!
+      !! The method computes the eletric charging correction to apply to the coagulation
+      !! kernel as a function of the temperature and the characteristic radius of the
+      !! mode involved in the coagulation.
+      !! Here the electric charging correction is computed using linear interpolation from
+      !! pre-tabulated values.
+      !!
+      !! @Warning:
+      !! Modes are referred by a two letters uppercase string with the combination of:
+      !!    - S : spherical mode
+      !!    - F : fractal mode
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: rc1   ! Characteristic radius of the first mode (m).
+      REAL(kind=mm_wp), INTENT(in) :: rc2   ! Characteristic radius of the the second mode (m).
+      INTEGER, INTENT(in)          :: order ! Moment's order (0 or 3 expected).
+      CHARACTER(len=2), INTENT(in) :: modes ! Interaction mode (combination of [S,F]).
+      REAL(kind=mm_wp), INTENT(in) :: T     ! Temperature (K).
+      
+      ! Electric charging correction.
+      REAL(kind=mm_wp) :: res
+
+      ! Local variable.
+      INTEGER          :: chx
+      REAL(kind=mm_wp) :: r_tmp, t_tmp
+      
+      chx = 0
+      IF (.NOT.mm_w_qe) THEN
+        res = 1._mm_wp
+        RETURN
+      ENDIF
+    
+      IF (SCAN(modes(1:1),"sS") /= 0) chx = chx + 1
+      IF (SCAN(modes(2:2),"sS") /= 0) chx = chx + 1
+      IF (SCAN(modes(1:1),"fF") /= 0) chx = chx + 3
+      IF (SCAN(modes(2:2),"fF") /= 0) chx = chx + 3
+      
+      chx = chx + order
+      
+      SELECT CASE(chx)
+        ! M0/SS:
+        CASE(2)
+          res = 1._mm_wp
+        ! M0/SF:
+        CASE(4)     
+          ! Fix max values of input parameters
+          r_tmp = MAX(MIN(log(rc1),mm_qbsf0_e(2,2)),mm_qbsf0_e(2,1))
+          t_tmp = MAX(MIN(T,mm_qbsf0_e(1,2)),mm_qbsf0_e(1,1))
+          ! Interpolates values
+          IF (.NOT.hdcd_lint_dset(t_tmp,r_tmp,mm_qbsf0,locate_reg,res)) THEN
+            WRITE(*,'(a)') "mp2m_methods:mm_qmean: Cannot interpolate mean Qelec"
+            call EXIT(10)
+          ENDIF
+        ! M3/SS:
+        CASE(5)
+          res = 1._mm_wp
+        ! M0/FF:
+        CASE(6)
+          ! Fix max values of input parameters
+          r_tmp = MAX(MIN(log(rc1),mm_qbff0_e(2,2)),mm_qbff0_e(2,1))
+          t_tmp = MAX(MIN(T,mm_qbff0_e(1,2)),mm_qbff0_e(1,1))
+          ! Interpolates values
+          IF (.NOT.hdcd_lint_dset(t_tmp,r_tmp,mm_qbff0,locate_reg,res)) THEN
+            WRITE(*,'(a)') "mp2m_methods:mm_qmean: Cannot interpolate mean Qelec"
+            call EXIT(10)
+          ENDIF
+        ! M3/SF:
+        CASE(7)
+          ! Fix max values of input parameters
+          r_tmp = MAX(MIN(log(rc1),mm_qbsf3_e(2,2)),mm_qbsf3_e(2,1))
+          t_tmp = MAX(MIN(T,mm_qbsf3_e(1,2)),mm_qbsf3_e(1,1))
+          ! Interpolates values
+          IF (.NOT.hdcd_lint_dset(t_tmp,r_tmp,mm_qbsf3,locate_reg,res)) THEN
+            WRITE(*,'(a)') "mp2m_methods:mm_qmean: Cannot interpolate mean Qelec"
+            call EXIT(10)
+          ENDIF
+        ! Anything else:
+        CASE DEFAULT
+          res = 1._mm_wp
+      END SELECT
+      
+      RETURN
+    END FUNCTION mm_qmean
+
+
+    PURE FUNCTION mm_get_btk(t,k) RESULT(res)
+      !! Get the value of the Free-Molecular regime coagulation pre-factor b^t_k.
+      !!
+      !! @Note
+      !! __k__ can only be one of the following value: 0 or 3. __t__ ranges only from 1 to 5.
+      !!
+      INTEGER, INTENT(in) :: t ! Index of the b^t_k coefficient.
+      INTEGER, INTENT(in) :: k ! Moment order of the b^t_k coefficient.
+      
+      ! b^t_k coefficient.
+      REAL(kind=mm_wp) :: res
+
+      ! Sanity check:
+      IF (.NOT.(k == 3 .OR. k == 0)) THEN
+        res = 0._mm_wp
+      ENDIF
+      
+      ! Sanity check:
+      IF (t > 5 .OR. t < 1) THEN
+        res = 0._mm_wp
+      ENDIF
+      
+      IF (k == 0) THEN
+        res = mm_bt0(t)
+
+      ELSE IF (k == 3) THEN
+        res = mm_bt3(t)
+      ENDIF
+      
+      RETURN
+    END FUNCTION mm_get_btk
+
+
+    ELEMENTAL FUNCTION mm_get_kco(T) RESULT(res)
+      !! Get the Continuous regime (Kn << 1) thermodynamics pre-factor of the coagulation kernel.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: T ! Temperature (K).
+      REAL(kind=mm_wp) :: res           ! Continuous regime thermodynamics pre-factor (m3.s-1).
+
+      res = (2._mm_wp*mm_kboltz*T) / (3._mm_wp*mm_eta_air(T))
+      
+      RETURN
+    END FUNCTION mm_get_kco
+
+
+    ELEMENTAL FUNCTION mm_get_kfm(T) RESULT(res)
+      !! Get the Free-Molecular regime (Kn >> 1) thermodynamics pre-factor of the coagulation kernel.
+      !!
+      REAL(kind=mm_wp), INTENT(in) :: T ! Temperature (K).
+      REAL(kind=mm_wp) :: res           ! Free-Molecular regime thermodynamics pre-factor (m^(5/2).s-1).
+
+      res = (6._mm_wp*mm_kboltz*T / mm_rhoaer)**(0.5_mm_wp)
+      
+      RETURN
+    END FUNCTION mm_get_kfm
+
+END MODULE MP2M_METHODS
Index: trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_microphysics.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_microphysics.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_microphysics.F90	(revision 3560)
@@ -0,0 +1,129 @@
+MODULE MP2M_MICROPHYSICS
+    !============================================================================
+    !
+    !     Purpose
+    !     -------
+    !     Interface to main microphysics subroutine.
+    !     The interface computes all aerosols microphysics processes in a single call.
+    !
+    !     The module contains two methods:
+    !        - mm_muphys(m3as_prod,dm0a_s,dm3a_s,dm0a_f,dm3a_f)
+    !        - mm_diagnostics(dt,aer_prec,aer_s_w,aer_f_w,aer_s_flux,aer_f_flux,rc_sph,rc_fra)
+    !
+    !     Authors
+    !     -------
+    !     B. de Batz de Trenquelléon, J. Burgalat (11/2024)
+    !
+    !============================================================================
+
+    USE MP2M_MPREC
+    USE MP2M_GLOBALS
+    USE MP2M_HAZE
+    USE MP2M_METHODS
+    IMPLICIT NONE
+    
+    PUBLIC :: mm_muphys, mm_diagnostics
+  
+    CONTAINS
+  
+    FUNCTION mm_muphys(m3as_prod,dm0a_s,dm3a_s,dm0a_f,dm3a_f) RESULT(ret)
+        !! Compute the evolution of moments tracers through haze microphysics processes.
+        !!
+        !! This method computes the evolution of all the microphysics tracers, given under the form
+        !! of moments during a time step.
+        !!
+        !! The method requires that global variables of the model (i.e. variables declared in mm_globals
+        !! module) are initialized/updated correctly (see mm_global_init, mm_column_init, and mm_aerosols_init).
+        !!
+        !! The tendencies returned by the method are defined on the vertical __layers__ of the model from the __GROUND__ to
+        !! the __TOP__ of the atmosphere. They should be added to the input variables used in the initialization methods
+        !! before the latter are called to initialize a new step.
+        !!
+
+        ! Production of the 3rd order moment of the spherical mode distribution (m3.m-2).
+        REAL(kind=mm_wp), INTENT(in), DIMENSION(:) :: m3as_prod
+        ! Tendency of the 0th order moment of the spherical mode distribution (m-2).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0a_s
+        ! Tendency of the 3rd order moment of the spherical mode distribution (m3.m-2).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3a_s
+        ! Tendency of the 0th order moment of the fractal mode distribution (m-2).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm0a_f
+        ! Tendency of the 3rd order moment of the fractal mode distribution (m3.m-2).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:) :: dm3a_f
+
+        ! .true. on succes (i.e. model has been initialized at least once previously), .false. otherwise.
+        LOGICAL :: ret
+        
+        ! Local variables.
+        ! Production of the spherical aerosols (m3.m-3).
+        REAL(kind=mm_wp), DIMENSION(:), ALLOCATABLE :: m3a_s_prod
+        ALLOCATE(m3a_s_prod(mm_nla))
+        
+        ret = (mm_ini_col.AND.mm_ini_aer)
+        
+        IF (.NOT.ret) RETURN
+      
+        ! Reverse vectors so they go from top to ground
+        ! @note: mm_dzlev is already from top to ground
+        m3a_s_prod = m3as_prod(mm_nla:1:-1) / mm_dzlev(:)
+
+        ! Calls haze microphysics
+        call mm_haze_microphysics(m3a_s_prod,dm0a_s,dm3a_s,dm0a_f,dm3a_f)
+        
+        ! Reverse vectors so they go from ground to top
+        dm0a_s = dm0a_s(mm_nla:1:-1) * mm_dzlev(mm_nla:1:-1)
+        dm3a_s = dm3a_s(mm_nla:1:-1) * mm_dzlev(mm_nla:1:-1)
+        dm0a_f = dm0a_f(mm_nla:1:-1) * mm_dzlev(mm_nla:1:-1)
+        dm3a_f = dm3a_f(mm_nla:1:-1) * mm_dzlev(mm_nla:1:-1)
+        
+        RETURN
+    END FUNCTION mm_muphys
+
+
+    SUBROUTINE mm_diagnostics(dt,aer_s_prec,aer_f_prec,aer_s_w,aer_f_w,aer_s_flux,aer_f_flux,rc_sph,rc_fra)
+        !! Get various diagnostic fields of the microphysics.
+        !!
+        !! @note
+        !! Fluxes values are always negative as they account for sedimentation fluxes. They are set as
+        !! vector and are ordered from __GROUND__ to __TOP__.
+        !!
+        !! @note
+        !! Precipitations are always positive and defined in kg.m-2.s-1.
+        !!
+
+        ! Physics timestep (s).
+        REAL(kind=8), INTENT(IN) :: dt
+
+        ! Aerosol precipitation (kg.m-2.s-1).
+        REAL(kind=mm_wp), INTENT(out), OPTIONAL :: aer_s_prec
+        REAL(kind=mm_wp), INTENT(out), OPTIONAL :: aer_f_prec
+
+        ! Aerosol settling velocity (m.s-1).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:), OPTIONAL :: aer_s_w
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:), OPTIONAL :: aer_f_w
+
+        ! Aerosol mass flux (kg.m-2.s-1).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:), OPTIONAL :: aer_s_flux
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:), OPTIONAL :: aer_f_flux
+
+        ! Aerosol characteristic radius (m).
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:), OPTIONAL :: rc_sph
+        REAL(kind=mm_wp), INTENT(out), DIMENSION(:), OPTIONAL :: rc_fra
+
+        IF (PRESENT(aer_s_prec)) aer_s_prec = ABS(mm_aers_prec) / dt
+        IF (PRESENT(aer_f_prec)) aer_f_prec = ABS(mm_aerf_prec) / dt
+        IF (PRESENT(aer_s_w))    aer_s_w    = -mm_m3as_vsed(mm_nla:1:-1)
+        IF (PRESENT(aer_f_w))    aer_f_w    = -mm_m3af_vsed(mm_nla:1:-1)
+        IF (PRESENT(aer_s_flux)) aer_s_flux = -mm_aer_s_flux(mm_nla:1:-1)
+        IF (PRESENT(aer_f_flux)) aer_f_flux = -mm_aer_f_flux(mm_nla:1:-1)
+
+        IF (mm_ini_aer) THEN
+            IF (PRESENT(rc_sph)) rc_sph = mm_rcs(mm_nla:1:-1)
+            IF (PRESENT(rc_fra)) rc_fra = mm_rcf(mm_nla:1:-1)
+        ELSE
+            IF (PRESENT(rc_sph)) rc_sph = 0._mm_wp
+            IF (PRESENT(rc_fra)) rc_fra = 0._mm_wp
+        ENDIF    
+    END SUBROUTINE mm_diagnostics
+
+END MODULE MP2M_MICROPHYSICS  
Index: trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_mprec.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_mprec.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/mp2m_mprec.F90	(revision 3560)
@@ -0,0 +1,45 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifndef PREC
+#define PREC 64
+#elif (PREC != 32 && PREC != 64 && PREC != 80)
+#undef PREC
+#define PREC 64
+#endif
+
+MODULE MP2M_MPREC
+    !============================================================================
+    !
+    !     Purpose
+    !     -------
+    !     Library floating point computations precision module.
+    !
+    !     This module only defines a single variable [[mm_mprec(module):mm_wp(variable)]] which sets
+    !     the kind of floating point value used in all other part of the library (REAL(kind=mm_wp)
+    !     declaration statement).
+    !
+    !     Authors
+    !     -------
+    !     B. de Batz de Trenquelléon, J. Burgalat (11/2024)
+    !
+    !============================================================================
+
+    IMPLICIT NONE
+
+#if (PREC == 32)
+    ! Size of floating point variables in the library (single).
+    INTEGER, PUBLIC, PARAMETER          :: mm_wp = SELECTED_REAL_KIND(p=6)  ! 32 bits
+    CHARACTER(len=è), PUBLIC, PARAMETER :: mm_wp_s = "32 bits"
+#elif (PREC == 64)
+    ! Size of floating point variables in the library (double).
+    INTEGER, PUBLIC, PARAMETER          :: mm_wp = SELECTED_REAL_KIND(p=15) ! 64 bits
+    CHARACTER(len=7), PUBLIC, PARAMETER :: mm_wp_s = "64 bits"
+#elif (PREC == 80)
+    ! Size of floating point variables in the library (extended-double).
+    INTEGER, PUBLIC, PARAMETER          :: mm_wp = SELECTED_REAL_KIND(p=18) ! 80 bits
+    CHARACTER(len=7), PUBLIC, PARAMETER :: mm_wp_s = "80 bits"
+#endif
+
+END MODULE MP2M_MPREC
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_argparse.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_argparse.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_argparse.F90	(revision 3560)
@@ -0,0 +1,2938 @@
+! Copyright (c) Jeremie Burgalat (2013-2022)
+! Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+!
+! This file is part of SWIFT
+!
+! Permission is hereby granted, free of charge, to any person obtaining a copy of
+! this software and associated documentation files (the "Software"), to deal in
+! the Software without restriction, including without limitation the rights to
+! use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+! the Software, and to permit persons to whom the Software is furnished to do so,
+! subject to the following conditions:
+!
+! The above copyright notice and this permission notice shall be included in all
+! copies or substantial portions of the Software.
+!
+! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+! FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+! COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+!! File:    swift_argparse.F90
+!! Summary: Command-line parser source file.
+!! Author:  J. Burgalat
+!! Date:    2013-2022
+
+#include "swift_defined.h"
+
+MODULE SWIFT_ARGPARSE
+  !> Command-line parsing module
+  !!
+  !! Here are described all the public members of argparse module.
+  !! For your own sanity, private methods that call Ancient Gods powers through
+  !! evil black magic rituals are not described here.
+  !!
+  !! If you only wish to have an overview of argparse usage, you'd better go
+  !! [here](|url|/page/swift/p01_argparse.html).
+
+  USE, INTRINSIC :: ISO_FORTRAN_ENV, ONLY : stdout=>OUTPUT_UNIT, stderr=>ERROR_UNIT
+  USE SWIFT_ERRORS
+  USE SWIFT_FSYSTEM, ONLY : fs_termsize
+  USE SWIFT_STRING_OP, getpar => format_paragraph, splitstr  => format_string
+  IMPLICIT NONE
+
+  PRIVATE
+
+  ! Public members and imported features
+  ! from errors: export everything (operator are exported latter)
+  PUBLIC :: noerror,error, error_to_string,aborting
+  ! from strings
+  PUBLIC :: st_slen, st_llen
+  PUBLIC :: stderr, stdout
+  ! argparse module
+  PUBLIC :: new_argparser,              &
+            argparser_clear,            &
+            argparser_add_option,       &
+            argparser_add_positionals,  &
+            argparser_throw_error,      &
+            argparser_parse,            &
+            argparser_help,             &
+            argparser_get_positional,   &
+            argparser_get_value,        &
+            argparser_reset_values,     &
+            argparser_found,            &
+            argparser_get_num_values,   &
+            argparser_found_positional, &
+            argparser_get_num_positional
+
+  PUBLIC :: OPERATOR(==), OPERATOR(/=), ASSIGNMENT(=)
+
+  ! ===========================
+  ! PARAMETERS (INTRISIC TYPES)
+  ! ===========================
+
+  INTEGER, PARAMETER, PUBLIC :: ap_string  = st_string
+    !! String value type identifier.
+  INTEGER, PARAMETER, PUBLIC :: ap_complex = st_complex
+    !! Complex value type identifier.
+  INTEGER, PARAMETER, PUBLIC :: ap_logical = st_logical
+    !! Logical value type identifier.
+  INTEGER, PARAMETER, PUBLIC :: ap_integer = st_integer
+    !! Integer value type identifier.
+  INTEGER, PARAMETER, PUBLIC :: ap_real = st_real
+    !! Real value type identifier.
+
+  !> List of all available actions
+
+  INTEGER, PARAMETER, PUBLIC :: ap_store   = 1
+    !! store action ID : Each time the option is seen, values are replaced.
+  INTEGER, PARAMETER, PUBLIC :: ap_append  = 2
+    !! append action ID : Each time the option is seen, values are appended.
+  INTEGER, PARAMETER, PUBLIC :: ap_count   = 3
+    !! count action ID : increase a counter each time the option is seen.
+  INTEGER, PARAMETER, PUBLIC :: ap_help    = 4
+    !! help action ID : help is requested !
+  INTEGER, PARAMETER, PUBLIC :: ap_version = 5
+    !! version action ID : version is requested !
+
+  !> List of all available actions
+  INTEGER, DIMENSION(5), PARAMETER, PRIVATE :: ap_actions = (/ap_store,  &
+                                                              ap_append, &
+                                                              ap_count,  &
+                                                              ap_help,   &
+                                                              ap_version/)
+  !> List of all recognized types by the parser
+  INTEGER, DIMENSION(5), PARAMETER, PRIVATE :: ap_types = (/ap_string,  &
+                                                            ap_logical, &
+                                                            ap_complex, &
+                                                            ap_integer, &
+                                                            ap_real/)
+  !> The unknown flag
+  !!
+  !! This flag is only intended to initialize flags. It is set by default during initialization
+  !! and quielty replaced by default flags, if user does not provide the relevant feature.
+  INTEGER, PARAMETER :: ap_undef = -1
+
+  !> Add an option to the parser
+  !!
+  !! ```
+  !! FUNCTION argparser_add_option(this,dest,sflag,lflag,type,action,default,nrec,help,meta) RESULT(err)
+  !!          argparser_add_option(this,dest,flag,type,action,default,nrec,help,meta) RESULT(err)
+  !! ```
+  !!
+  !! The function defines a new argument based on input parameters, checks it and finally sets it
+  !! in the parser.
+  !!
+  !! In its first version both short (`sflag`) and long (`lflag`) options flags are mandatory. In its second
+  !! form, a single flag (`flag`) is expected: the method will automatically deduce if it belongs to short or
+  !! a long option flag based on the number of hyphens given.
+  !!
+  !! `type` value should be one of the following module constants (which are aliases from [[string_op(module)]]):
+  !!
+  !! - `ap_string` ([[string_op(module):st_string(variable)]])
+  !! - `ap_complex` ([[string_op(module):st_complex(variable)]])
+  !! - `ap_logical` ([[string_op(module):st_logical(variable)]])
+  !! - `ap_integer` ([[string_op(module):st_integer(variable)]])
+  !! - `ap_real` ([[string_op(module):st_real(variable)]])
+  !!
+  !! `action` value should be one of the following module constants:
+  !!
+  !! - [[argparse(module):ap_store(variable)]]
+  !! - [[argparse(module):ap_append(variable)]]
+  !! - [[argparse(module):ap_count(variable)]]
+  !! - [[argparse(module):ap_help(variable)]]
+  !!
+  !! `nrec` string can take the following forms:
+  !!
+  !! tag   | description
+  !! :---: | : -------------
+  !!  "?"  | zero or one argument's value
+  !!  "*"  | any number of arguments
+  !!  "+"  | one or more argument's value(s)
+  !!  "X"  | Exactly X values. Where X is the string representation of an integer (0 is accepted).
+  !!
+  !! On success, `noerror` is returned. Otherwise -9 error code is returned. Errors are only
+  !! produced by misuse of the function arguments. In such case, the program should be
+  !! stopped: note that such error should not occur in _released_ programs.
+  INTERFACE argparser_add_option
+    MODULE PROCEDURE ap_add_option_1, ap_add_option_2
+  END INTERFACE
+
+  !> Get positional argument value(s)
+  INTERFACE argparser_get_positional
+    MODULE PROCEDURE ap_get_positional_sc, ap_get_positional_ve
+  END INTERFACE
+
+  !> Get optional argument value(s)
+  !!
+  !! ```
+  !! FUNCTION argparser_get_value(this,name,output) RESULT(err)
+  !! ```
+  !!
+  !! This is the generic method that can be used to retrieve any kind of argument value(s) from the parser for a given
+  !! argument name (as defined by the `dest` argument of [[argparse(module):argparser_add_option(interface)]].
+  !! All the methods have the same dummy arguments only `output` dummy argument differs in type and shape.
+  !!
+  !! @note
+  !! For string vector, `output` is expected to be an allocatable vector of **assumed length**
+  !! strings (thus string length is left to user responsability).
+  !! A good compromise for strings length is to use the [[string_op(module):st_slen(variable)]]
+  !! parameter.
+  INTERFACE argparser_get_value
+    MODULE PROCEDURE ap_get_dv_sc, ap_get_rv_sc, ap_get_iv_sc, ap_get_lv_sc, &
+                     ap_get_cv_sc, ap_get_sv_sc, ap_get_dv_ve, ap_get_rv_ve, &
+                     ap_get_iv_ve, ap_get_lv_ve, ap_get_cv_ve, ap_get_sv_ve
+  END INTERFACE
+
+  !> Interface to [[argparse(module):argc(type)]] getters
+  !!
+  !! All the functions have the same prototype, only kind and type of arguments changed
+  !! from a function to the other.
+  INTERFACE argc_get_value
+    MODULE PROCEDURE ac_get_dv_sc, ac_get_rv_sc, ac_get_iv_sc, ac_get_lv_sc, &
+                     ac_get_cv_sc, ac_get_sv_sc, ac_get_dv_ve, ac_get_rv_ve, &
+                     ac_get_iv_ve, ac_get_lv_ve, ac_get_cv_ve, ac_get_sv_ve
+  END INTERFACE
+
+  !> argc equality operator
+  INTERFACE OPERATOR(==)
+    MODULE PROCEDURE ac_equals_arg
+  END INTERFACE
+
+  !> argc inequality operator
+  INTERFACE OPERATOR(/=)
+    MODULE PROCEDURE ac_differs_arg
+  END INTERFACE
+
+  !> argc assignment statement
+  INTERFACE ASSIGNMENT(=)
+    MODULE PROCEDURE ac_affect_arg
+    MODULE PROCEDURE ap_affect_parser
+  END INTERFACE
+
+  !> argc *destructors* interface
+  INTERFACE clear_argc
+    MODULE PROCEDURE ac_clear_arg_sc, ac_clear_arg_ve
+  END INTERFACE
+
+
+  TYPE, PRIVATE :: argc
+    !! Defines a command-line argument.
+    !!
+    !! An [[argparse(module):argc(type)]] object stores all information about a command-line
+    !! argument, that is:
+    !!
+    !! - its name
+    !! - its optional flags
+    !! - its type
+    !! - its action
+    !! - and finally its values
+    PRIVATE
+    INTEGER          :: ptype = ap_logical
+      !! Type flag (an integer from enum argparse::ap_types)
+    INTEGER          :: paction = ap_store
+      !! Action flag (an integer from enum argparse::ap_actions)
+    INTEGER          :: nrec = 0
+      !! Number of values
+    LOGICAL          :: fnd = .false.
+      !! A boolean flag set to `.true.` if argument has been found in the command-line.
+    TYPE(words)      :: values
+      !! Values of the argument
+    CHARACTER(len=2) :: sflag = "  "
+      !! Short flag option
+    TYPE(words)      :: meta
+      !! Meta variable name(s) of the argument
+#if HAVE_FTNDTSTR
+    CHARACTER(len=:), ALLOCATABLE :: default
+      !! Default flag
+    CHARACTER(len=:), ALLOCATABLE :: name
+      !! Name of the argument (needed to check and retrieve its value(s))
+    CHARACTER(len=:), ALLOCATABLE :: lflag
+      !! Long flag option (st_short_len max chars !)
+    CHARACTER(len=:), ALLOCATABLE :: help
+      !! Help about the argument
+#else
+    CHARACTER(len=st_slen) :: default = ""
+      !! Default flag
+    CHARACTER(len=st_slen) :: name
+      !! Name of the argument (needed to check and retrieve its value(s))
+    CHARACTER(len=st_slen) :: lflag = ""
+      !! Long flag option (st_short_len max chars !)
+    CHARACTER(len = 200)   :: help = ""
+      !! Help about the argument
+#endif
+  END TYPE argc
+
+
+  TYPE, PUBLIC :: argparser
+    !! Command-line parser
+    !!
+    !! This is the main object of the module. It stores definitions of CLI arguments and
+    !! their value(s) once the command-line have been parsed.
+    TYPE(argc), PRIVATE, ALLOCATABLE, DIMENSION(:) :: args
+      !! List of defined arguments
+    INTEGER, PRIVATE :: nargs = 0
+      !! Size of args
+    INTEGER, PRIVATE :: parsed = -1
+      !! Parsing control flag
+      !!
+      !! The parsing flag determines if the command line have been parsed :
+      !!
+      !!   - -1 : not parsed yet
+      !!   - 0  : unsuccessfully parsed
+      !!   - 1  : successfully parsed
+    TYPE(argc), PRIVATE :: posals
+      !! Positionals arguments (defined as a single argc object)
+    LOGICAL, PRIVATE :: have_posal = .false.
+      !! Positional control flag
+#if HAVE_FTNDTSTR
+    CHARACTER(len=:), PRIVATE, ALLOCATABLE :: usg
+      !! Program command usage
+    CHARACTER(len=:), PRIVATE, ALLOCATABLE :: descr
+      !! Program help description
+    CHARACTER(len=:), PRIVATE, ALLOCATABLE :: eplg
+      !! Program help epilog
+    CHARACTER(len=:), PRIVATE, ALLOCATABLE :: vers
+      !! Application version string
+#else
+    CHARACTER(len=st_llen), PRIVATE :: usg
+      !! Program command usage
+    CHARACTER(len=st_llen), PRIVATE :: descr
+      !! Program help description
+    CHARACTER(len=st_llen), PRIVATE :: eplg
+      !! Program help epilog
+    CHARACTER(len=st_llen), PRIVATE :: vers
+      !! Application version string
+#endif
+    INTEGER, PRIVATE :: mxhlpos = 20
+      !! Position of the short help for options
+    INTEGER, PRIVATE :: width = 0
+      !! Maximum width of the help
+    LOGICAL, PRIVATE :: init = .false.
+      !! Initialization control flag
+#if HAVE_FTNPROC
+
+    CONTAINS
+    PROCEDURE, PRIVATE :: ap_add_option_1
+    PROCEDURE, PRIVATE :: ap_add_option_2
+    PROCEDURE, PRIVATE :: ap_get_positional_sc
+    PROCEDURE, PRIVATE :: ap_get_positional_ve
+    PROCEDURE, PRIVATE :: ap_get_dv_sc
+    PROCEDURE, PRIVATE :: ap_get_rv_sc
+    PROCEDURE, PRIVATE :: ap_get_iv_sc
+    PROCEDURE, PRIVATE :: ap_get_lv_sc
+    PROCEDURE, PRIVATE :: ap_get_cv_sc
+    PROCEDURE, PRIVATE :: ap_get_sv_sc
+    PROCEDURE, PRIVATE :: ap_get_dv_ve
+    PROCEDURE, PRIVATE :: ap_get_rv_ve
+    PROCEDURE, PRIVATE :: ap_get_iv_ve
+    PROCEDURE, PRIVATE :: ap_get_lv_ve
+    PROCEDURE, PRIVATE :: ap_get_cv_ve
+    PROCEDURE, PRIVATE :: ap_get_sv_ve
+    PROCEDURE, PRIVATE :: ap_check_state
+    PROCEDURE, PUBLIC  :: throw_error        => argparser_throw_error
+      !! Throw an error and exit the program
+    PROCEDURE, PUBLIC  :: parse              => argparser_parse
+      !! Parse the command-line (or the given input string).
+    PROCEDURE, PUBLIC  :: help               => argparser_help
+      !! Compute and print help
+    PROCEDURE, PUBLIC  :: version            => argparser_version
+      !! print version string
+    PROCEDURE, PUBLIC  :: found              => argparser_found
+      !! Check if an optional argument has been found on the command-line
+    PROCEDURE, PUBLIC  :: get_num_values     => argparser_get_num_values
+      !! Get the actual number of values stored in an argument.
+    PROCEDURE, PUBLIC  :: found_positional   => argparser_found_positional
+      !! Check if positional argument(s) has been found on the command-line
+    PROCEDURE, PUBLIC  :: get_num_positional => argparser_get_num_positional
+      !! Get the actual number of values stored as positionals.
+    PROCEDURE, PUBLIC  :: add_positionals    => argparser_add_positionals
+      !! Add positionals definitions in the parser.
+    !> Add optional argument definition in the parser.
+    GENERIC, PUBLIC    :: add_option         => ap_add_option_1, &
+                                                ap_add_option_2
+    !> Get the values of the positionals stored in the parser.
+    GENERIC, PUBLIC    :: get_positional     => ap_get_positional_sc, &
+                                                ap_get_positional_ve
+    !> Get the value(s) of the given argument stored in the parser.
+    GENERIC, PUBLIC    :: get_value          => ap_get_dv_sc, &
+                                                ap_get_rv_sc, &
+                                                ap_get_iv_sc, &
+                                                ap_get_lv_sc, &
+                                                ap_get_cv_sc, &
+                                                ap_get_sv_sc, &
+                                                ap_get_dv_ve, &
+                                                ap_get_rv_ve, &
+                                                ap_get_iv_ve, &
+                                                ap_get_lv_ve, &
+                                                ap_get_cv_ve, &
+                                                ap_get_sv_ve
+#endif
+  END TYPE argparser
+
+  CONTAINS
+
+  ! argparser main methods (public)
+  ! -------------------------------
+
+  FUNCTION new_argparser(usg, dsc, epg, add_help, add_version, version_string,width, max_help_pos) RESULT(this)
+    !! Initialize an argparser object.
+    !!
+    !! The method initializes (properly) an [[argparse(module):argparser(type)]] object.
+    !! Even if all the arguments are optional, it is mandatory to **call** the method
+    !! before using an argparser object.
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: usg
+      !! An optional string with the command line usage of the program. If it is not given
+      !! command-line usage is automatically built from informations set in the parser.
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: dsc
+      !! An optional string with the short description of the program.
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: epg
+      !! An optional string with the epilog of the program's help
+    LOGICAL, INTENT(in), OPTIONAL          :: add_help
+      !! An optional boolean flag with `.true.` to automatically set an option for program's help.
+      !! Note that, the option flags `-h` and `--help` are no more available in that case.
+    LOGICAL, INTENT(in), OPTIONAL          :: add_version
+      !! An optional boolean flag with `.true.` to automatically set an option for program's help.
+      !! Note that, the option flags `-V` and `--version` are no more available in that case.
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: version_string
+      !! An optional string with the text to display if the *version* option is found on the
+      !! command line. The text is displayed without any format.
+    INTEGER, INTENT(in), OPTIONAL          :: width
+      !! An optional integer with the maximum width the help text.
+    INTEGER, INTENT(in), OPTIONAL          :: max_help_pos
+      !! An optional integer with the maximum position of the help string for each option of
+      !! the program when help is requested. Note that this value is just an indicator. The
+      !! helper computes the minimum position between this value and the maximum length of the
+      !! options flags.
+    TYPE(argparser) :: this
+      !! An initialized argparse object.
+    INTEGER     :: zh
+    TYPE(error) :: err
+    ! We always clear the parser
+    CALL argparser_clear(this)
+    ! Set keywords
+    IF (PRESENT(usg)) THEN ; this%usg=usg   ; ELSE ; this%usg=''   ; ENDIF
+    IF (PRESENT(dsc)) THEN ; this%descr=dsc ; ELSE ; this%descr='' ; ENDIF
+    IF (PRESENT(epg)) THEN ; this%eplg=epg  ; ELSE ; this%eplg=''  ; ENDIF
+    CALL fs_termsize(zh,this%width)
+    IF (PRESENT(width)) this%width = MAX(width,50)
+    IF(PRESENT(max_help_pos)) this%mxhlpos = MAX(5,max_help_pos)
+    this%init = .true. ! before adding option !!!
+    IF (PRESENT(add_help)) THEN
+      IF (add_help) &
+        err = argparser_add_option(this,'help',sflag='-h',lflag='--help', &
+                    action=ap_help, help="Print this help and quit")
+    ENDIF
+    IF (PRESENT(add_version)) THEN
+      IF (add_version) &
+        err = argparser_add_option(this,'version',sflag='-V',lflag='--version', &
+                    action=ap_version, help="Print the application version and quit")
+      this%vers = ''
+      if (PRESENT(version_string)) this%vers = TRIM(version_string)
+    ENDIF
+    RETURN
+  END FUNCTION new_argparser
+
+  SUBROUTINE argparser_clear(this)
+    !! Clear the parser
+    !! The subroutine is used as finalization subroutine of argparser type.
+    !! Once the method is called, the object is no more usable until it is
+    !! (re)initialized by calling argparse::new_argparser.
+    !! @note If **fccp** has not been built with support for finalization subroutine,
+    !! it should be called whenever the argparser object is no more used.
+    TYPE(argparser), INTENT(inout) :: this
+      !! An argparser object
+    IF (ALLOCATED(this%args)) THEN
+      CALL clear_argc(this%args)
+      DEALLOCATE(this%args)
+    ENDIF
+    ! maybe we should should set this%posals as allocatable
+    CALL clear_argc(this%posals)
+    this%nargs      = 0
+    this%parsed     = -1
+    this%have_posal = .false.
+    this%usg        = ''
+    this%descr      = ''
+    this%eplg       = ''
+    this%mxhlpos    = 20
+    this%width      = 0
+    this%init       = .false.
+  END SUBROUTINE argparser_clear
+
+  SUBROUTINE argparser_reset_values(this)
+    !! Reset all arguments values in the parser
+    !! The method only deletes arguments value(s) currently stored in the parser.
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    ! Check if list is empty
+    IF (this%nargs == 0) RETURN
+    CALL words_clear(this%args(:)%values)
+    CALL words_clear(this%posals%values)
+  END SUBROUTINE argparser_reset_values
+
+  FUNCTION argparser_add_positionals(this,nargs,meta,help) RESULT(err)
+    !! Add positional arguments definition to the parser.
+    !!
+    !! The method initializes the entry for positional arguments in the parser.
+    !! Positional arguments are always seen by the parser as strings and the
+    !! default associated action is 'store'.
+    OBJECT(argparser), INTENT(inout)                     :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                         :: nargs
+      !! A string with the expected number of specified values for the option
+    CHARACTER(len=*), INTENT(in), DIMENSION(:), OPTIONAL :: meta
+      !! A vector of strings with the the displayed value name(s) of the positionals in the help command
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: help
+      !! An optional string with a short description of the positional argument(s)
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process.
+    INTEGER                       :: ty,ac
+    CHARACTER(len=:), ALLOCATABLE :: sf,lf,de
+    err = noerror
+    IF (.NOT.this%init) THEN
+      err = error("argparse: parser not initialized yet",-1)
+      RETURN
+    ENDIF
+    IF (this%have_posal) THEN
+      err = error('argparse: positionals arguments already defined',-8)
+      RETURN
+    ELSE
+      this%posals%name = 'positional'
+      sf = '  ' ; lf = '' ; ty = ap_string ; ac = ap_store ; de = ''
+      IF (PRESENT(help)) THEN
+        this%posals%help = TRIM(help)
+      ELSE
+        this%posals%help = ''
+      ENDIF
+      IF (PRESENT(meta)) THEN
+        err = ac_check_and_set(this%posals,sf,lf,ty,ac,de,nargs,meta,.false.)
+      ELSE
+        err = ac_check_and_set(this%posals,sf,lf,ty,ac,de,nargs,check_flag=.false.)
+      ENDIF
+      IF (err /= noerror) THEN
+        RETURN
+      ENDIF
+      this%have_posal = this%posals%nrec /= 0
+    ENDIF
+    RETURN
+  END FUNCTION argparser_add_positionals
+
+  FUNCTION argparser_parse(this,cmd_line,auto) RESULT(err)
+    !! Parse the command line
+    !! The method parses either the command-line or the string `cmd_line`
+    !! given as optional argument and fills the parser's arguments.
+    !! @note
+    !! If `cmd_line` is provided it should not contains the name of the program or
+    !! the parsing process will certainly failed: program name will be seen as the
+    !! first positional argument and all tokens of the string will then be seen as
+    !! positional.
+    OBJECT(argparser), INTENT(inout)       :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: cmd_line
+      !! An optional string to parse that substitute for the actual command-line.
+    LOGICAL, INTENT(in), OPTIONAL          :: auto
+      !! An optional boolean flag with `.true.` to instruct the parser wether to perform
+      !! automatic actions or not when error occur during parsing. If `auto` is enabled,
+      !! then the parser dumps program's usage and stops the program on error.
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process.
+    CHARACTER(len=:), ALLOCATABLE :: cline,z
+    LOGICAL                       :: zauto
+    LOGICAL                       :: rhelp
+    LOGICAL                       :: rvers
+    INTEGER                       :: l
+    TYPE(words)                   :: cmd_tokens
+    err = noerror
+    IF (.NOT.this%init)  THEN
+      err = error("parser not initialized yet",-1) ; RETURN
+    ENDIF
+    rhelp = .false. ; rvers = .false.
+    zauto = .false. ; IF (PRESENT(auto)) zauto = auto
+    IF (PRESENT(cmd_line)) THEN
+      ALLOCATE(cline,source=cmd_line)
+    ELSE
+      CALL GET_COMMAND(length=l)
+      ALLOCATE(CHARACTER(len=l) :: z) ; CALL GET_COMMAND(z)
+      CALL GET_COMMAND_ARGUMENT(0,length=l)
+      IF (l >= LEN_TRIM(z)) THEN ; cline='' ; ELSE ; cline=z(l+1:LEN(z)) ; ENDIF
+      DEALLOCATE(z)
+    ENDIF
+    ! reset parsing status
+    this%parsed = -1
+    CALL argparser_reset_values(this)
+    DO
+      ! Do we have to launch the turbine ?
+      IF (LEN_TRIM(cline) == 0) THEN
+        IF (this%have_posal) err=error("Wrong number of arguments",-16)
+        EXIT ! ... No :)
+      ELSE
+        err = ap_split_cmd(this,cline,cmd_tokens,rhelp)
+        ! we only stops processing if :
+        !   - the internal error (string length) is raised
+        !   - help flag found AND auto has been set
+        IF (err /= noerror .OR. (rhelp.AND.zauto)) EXIT
+      ENDIF
+      CALL words_reset(cmd_tokens) ! not mandatory... at least theoretically
+      ! Parses the options
+      err = ap_parse_options(this,cmd_tokens,rhelp,rvers)
+      IF (err /= noerror) EXIT
+      ! exit loop if help or version is requested. Parser is not completely filled but we
+      ! expect someone to use the help or version actions...
+      IF (rhelp.OR.rvers) EXIT
+      ! Parses positionals
+      err = ap_parse_positionals(this,cmd_tokens)
+      EXIT ! A one iterated loop :)
+    ENDDO
+    IF (err /= 0) THEN
+      CALL argparser_reset_values(this)
+    ELSE
+      this%parsed = 1
+    ENDIF
+    IF (zauto) THEN
+      IF (rhelp) CALL argparser_help(this)
+      IF (rvers) CALL argparser_version(this)
+      IF (err /= 0) CALL argparser_throw_error(this,err,2)
+    ENDIF
+    RETURN
+  END FUNCTION argparser_parse
+
+  SUBROUTINE argparser_version(this)
+    !! Print version string and exit program
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    WRITE(stdout,'(a)') TRIM(this%vers)
+    CALL argparser_clear(this)
+    CALL EXIT(0)
+  END SUBROUTINE argparser_version
+
+  SUBROUTINE argparser_help(this)
+    !! Print help and exit program
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    CHARACTER(len=:), ALLOCATABLE :: helpme
+    !!!! WARNING we set no indication here !!!
+    IF (.NOT.this%init) RETURN
+    helpme = ap_gen_help(this)
+
+    WRITE(stdout,'(a)') helpme
+
+    CALL argparser_clear(this)
+    ! Finally we exit the program
+    CALL EXIT(0)
+  END SUBROUTINE argparser_help
+
+  SUBROUTINE argparser_throw_error(this,err,exit_id)
+    !! Dump error on standard error and exit
+    !!
+    !! The method performs the following actions:
+    !!
+    !! - Print the usage command of the program
+    !! - Dump the provided @p error message
+    !! - Call parser's clean-up subroutine (if a *cleaner* callback has been given during the
+    !!   parser's initialization, see [[argparse(module):new_argparser(function)]] documentation
+    !! - Stop the program
+    !!
+    !! The error message is always printed in standard error output.
+    !! @note
+    !! If errors::error::id is 0 the method does nothing.
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    TYPE(error), INTENT(in)          :: err
+      !! An error object with the error to print
+    INTEGER, INTENT(in), OPTIONAL    :: exit_id
+      !! An optional integer with the exit code (default to 2)
+    CHARACTER(len=:), ALLOCATABLE    :: pgn
+    TYPE(error) :: zerr
+    IF (err == 0) RETURN
+    zerr = error(err%msg,2)
+    IF (PRESENT(exit_id)) zerr%id=exit_id
+    CALL ap_format_usage(this)
+    WRITE(stderr,'(a)') TRIM(this%usg)//NEW_LINE('A')
+    ! clean the parser
+    CALL argparser_clear(this)
+    pgn = get_progname()
+    WRITE(stderr,'(a)') pgn//": error: "//TRIM(err%msg)
+    CALL EXIT(err%id)
+  END SUBROUTINE argparser_throw_error
+
+  FUNCTION argparser_found(this,argname) RESULT(found)
+    !! Check wether an argument has been found in the command-line.
+    !! @note
+    !! Keep in mind that arguments in the parser always have a default
+    !! value. This method is not intended to check if an argument has a value but
+    !! only if it has been seen on the command-line !
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: argname
+      !! Name of the argument to check
+    LOGICAL :: found
+      !! `.true.` if the option has been parsed, `.false.` otherwise
+    INTEGER  :: idx
+    idx = ap_get_arg_index(this,argname)
+    IF (idx == -1) THEN
+      found = .false.
+    ELSE
+      found = ac_found(this%args(idx))
+    ENDIF
+  END FUNCTION argparser_found
+
+  FUNCTION argparser_get_num_values(this,argname) RESULT(num)
+    !! Get the actual number of values stored in an argument.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: argname
+      !! Name of the argument to check.
+    INTEGER :: num
+      !! The number of actual values stored in the argument
+    INTEGER  :: idx
+    idx = ap_get_arg_index(this,argname)
+    IF (idx == -1) THEN
+      num = 0
+    ELSE
+      num = words_length(this%args(idx)%values)
+    ENDIF
+  END FUNCTION argparser_get_num_values
+
+  FUNCTION argparser_found_positional(this) RESULT(ret)
+    !! Check if positional(s) has been found in the command line.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    LOGICAL :: ret
+    !! `.true.` if found, `.false.` otherwise
+    TYPE(error) :: err
+    ret = .false.
+    IF (this%have_posal) THEN
+      ret = this%posals%fnd
+    ENDIF
+  END FUNCTION argparser_found_positional
+
+  FUNCTION argparser_get_num_positional(this) RESULT(ret)
+    !! Get the actual number of positional argument values stored in the parser .
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    INTEGER :: ret
+      !! The number of actual positionals arguments
+    ret = 0
+    IF (this%have_posal) THEN
+      ret = words_length(this%posals%values)
+    ENDIF
+  END FUNCTION argparser_get_num_positional
+
+  ! argparser private methods
+  ! -------------------------
+
+  FUNCTION ap_check_state(this) RESULT(err)
+    !! Check current parser state
+    !! The method returns an error based on the current parser's state:
+    !! - Parser is ready (0)
+    !! - parsing not done yet (-19)
+    !! - parsing (already) failed (-20)
+    !! - parser is **NOT** initialized (-1)
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    TYPE(error) :: err
+      !! Error object with the *status* of the parser
+    err = noerror
+    IF (this%parsed == -1) THEN
+      err = error("argparse: Command-line not parsed (yet)",-19)
+    ELSE IF (this%parsed == 0) THEN
+      err = error("argparse: command-line parsing failed",-20)
+    ELSE IF (.NOT.this%init) THEN
+      err = error("argparse: parser not initialized yet",-1)
+    ENDIF
+    RETURN
+  END FUNCTION ap_check_state
+
+  SUBROUTINE ap_append_arg(this,arg)
+    !! Append an argument to the list of arguments.
+    TYPE(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    TYPE(argc), INTENT(in)         :: arg
+      !! An [[argparse(module):argc(type)]] object with the argument to add
+    INTEGER                               :: i
+    TYPE(argc), ALLOCATABLE, DIMENSION(:) :: tmp
+    TYPE(error) :: err
+    IF (.NOT.this%init)  THEN
+      err = error("parser not initialized yet",-1) ; RETURN
+    ENDIF
+    ! Empty list : we create it
+    IF (this%nargs == 0) THEN
+      ALLOCATE(this%args(1))
+      this%args(1) = arg
+      this%nargs = 1
+      RETURN
+    ENDIF
+    ! Adds a new argument to the vector of arguments
+    ! we will make a test with move_alloc but i'm not sure it does everything
+    ! the way we want (keep in mind that there is some pointer in argc members
+    ! !!!)
+    ALLOCATE(tmp(this%nargs))
+    DO i=1,this%nargs ; tmp(i) = this%args(i) ; ENDDO
+    CALL clear_argc(this%args)
+    DEALLOCATE(this%args)
+    this%nargs = this%nargs+1 ; ALLOCATE(this%args(this%nargs))
+    DO i=1,this%nargs-1 ; this%args(i) = tmp(i) ; ENDDO
+    CALL clear_argc(tmp)
+    DEALLOCATE(tmp)
+    this%args(i) = arg
+    RETURN
+  END SUBROUTINE ap_append_arg
+
+  FUNCTION ap_get_arg_index(this, name, sflag, lflag)  RESULT(idx)
+    !! Get an argument by name or option flag in the parser.
+    TYPE(argparser), INTENT(in), TARGET    :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: name
+      !! An optional string with the name of the argument
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: lflag
+      !! An optional string with the long option flag of the argument
+    CHARACTER(len=2), INTENT(in), OPTIONAL :: sflag
+      !! An optional string with the short option flag of the argument
+    INTEGER :: idx
+      !! Index of the argument in the internal vector of argument or -1 if no argument is found.
+    INTEGER                       :: i,nn,nl,ns
+    CHARACTER(LEN=:), ALLOCATABLE :: lna, llf
+    CHARACTER(LEN=2)              :: lsf
+    idx = -1
+    IF (.NOT.this%init) RETURN
+    lna="" ; IF(PRESENT(name)) lna = name   ; nn = LEN_TRIM(lna)
+    lsf="" ; IF(PRESENT(sflag)) lsf = sflag ; ns = LEN_TRIM(lsf)
+    llf="" ; IF(PRESENT(lflag)) llf = lflag ; nl = LEN_TRIM(llf)
+    IF (nn == 0 .AND. ns == 0 .AND. nl == 0) RETURN
+    ! empty parser
+    IF (this%nargs == 0) RETURN
+    DO i=1, this%nargs
+      IF ((nn /= 0 .AND. TRIM(this%args(i)%name)  == TRIM(lna)) .OR. &
+          (ns /= 0 .AND. TRIM(this%args(i)%sflag) == TRIM(lsf)) .OR. &
+          (nl /= 0 .AND. TRIM(this%args(i)%lflag) == TRIM(llf))) THEN
+          idx = i ; RETURN
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION ap_get_arg_index
+
+
+  FUNCTION ap_add_option_1(this,dest,sflag,lflag,type,action,default,nrec,help,meta) RESULT(err)
+    !! Add an argument to the parser (interface #1)
+    !!
+    !! The function defines a new argument based on input parameters, checks it and finally sets it
+    !! in the parser. Both **short and long options flags** are mandatory input arguments of the function.
+    !!
+    !!  `type` value should be one of the following module constants (which are aliases from [[string_op(module)]]):
+    !!  - ap_string ([[string_op(module):st_string(variable)]])
+    !!  - ap_complex ([[string_op(module):st_complex(variable)]])
+    !!  - ap_logical ([[string_op(module):st_logical(variable)]])
+    !!  - ap_integer ([[string_op(module):st_integer(variable)]])
+    !!  - ap_real ([[string_op(module):st_real(variable)]])
+    !!
+    !!  `action` value should be one of the following module constants:
+    !!  - [[argparse(module):ap_store(variable)]]
+    !!  - [[argparse(module):ap_append(variable)]]
+    !!  - [[argparse(module):ap_count(variable)]]
+    !!  - [[argparse(module):ap_help(variable)]]
+    !!
+    !!  `nrec` string can take the following forms:
+    !!  tag | description
+    !!  :-: | : -------------
+    !!   ?  | zero or one argument's value
+    !!   *  | any number of arguments
+    !!   +  | one or more argument's value(s)
+    !!   X  | Exactly X values. Where X is the string representation of an integer (0 is accepted).
+    !!
+    !! See also ap_add_option_2 documentation.
+    OBJECT(argparser), INTENT(inout)                     :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                         :: dest
+      !! A string with the name of the argument
+    CHARACTER(len=2), INTENT(in)                         :: sflag
+      !! A two characters string with the short option flag of the argument
+    CHARACTER(len=*), INTENT(in)                         :: lflag
+      !! A string (3 characters minimum) with the long option flag of the argument
+    INTEGER, INTENT(in), OPTIONAL                        :: type
+      !! An integer with the type of the argument
+    INTEGER, INTENT(in), OPTIONAL                        :: action
+      !! An integer with the action of the argument
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: default
+      !! A string with the default value of the argument if not provided in the CLI
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: nrec
+      !! A string with the expected number of specified values for the argument in the CLI.
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: help
+      !! A string with a short description of the argument
+    CHARACTER(len=*), DIMENSION(:), INTENT(in), OPTIONAL :: meta
+      !! A vector of strings with the displayed value's name(s) of the argument in the help command
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process.
+    CHARACTER(len=:), ALLOCATABLE :: de,na,he
+    INTEGER                       :: ty,ac
+    TYPE(argc)                    :: arg
+    err = noerror
+    he = ""       ; IF (PRESENT(help))    he = TRIM(help)
+    na = ""       ; IF (PRESENT(nrec))    na = TRIM(nrec)
+    ty = ap_undef ; IF (PRESENT(type))    ty = type
+    ac = ap_undef ; IF (PRESENT(action))  ac = action
+    de =''        ; IF (PRESENT(default)) de = TRIM(default)
+    IF (.NOT.this%init)  THEN
+      err = error("argparse: parser not initialized yet",-1)
+      RETURN
+    ENDIF
+    IF (LEN_TRIM(dest) == 0) THEN
+      err = error("argparse: Invalid argument (empty dest)",-2)
+      RETURN
+    ENDIF
+    arg%name = TRIM(dest) ; arg%help = TRIM(he)
+    IF (PRESENT(meta)) THEN
+      err = ac_check_and_set(arg,sflag,lflag,ty,ac,de,na,meta)
+    ELSE
+      err = ac_check_and_set(arg,sflag,lflag,ty,ac,de,na)
+    ENDIF
+    IF (err /= noerror) RETURN
+    err = ap_check_in_parser(this,arg)
+    IF (err /= noerror) RETURN
+    CALL ap_append_arg(this,arg)
+    CALL clear_argc(arg)
+  END FUNCTION ap_add_option_1
+
+  FUNCTION ap_add_option_2(this,dest,flag,type,action,default,nrec,help,meta) RESULT(err)
+    !! Add an argument to the parser (interface #2)
+    !!
+    !! The function is a wrapper to ap_add_option_1. In this version,
+    !! only one option flag is required. The method only checks for the (trimmed) length of **flag** in
+    !! order to choose wether it is a **short** or **long** option flag. Then the function simply calls
+    !! ap_add_option_1 to set the argument.
+    !!
+    !! Other dummy arguments have the same meaning as in ap_add_option_1.
+    OBJECT(argparser), INTENT(inout)                     :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                         :: dest
+      !! A string with the name of the argument
+    CHARACTER(len=*), INTENT(in)                         :: flag
+      !! A string with either the short or long option flag
+    INTEGER, INTENT(in), OPTIONAL                        :: type
+      !! A string with the type of the argument
+    INTEGER, INTENT(in), OPTIONAL                        :: action
+      !! A string with the action of the argument
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: default
+      !! A string with the default value of the argument if not provided in the CLI
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: nrec
+      !! A string with the expected number of specified values for the argument in the CLI.
+    CHARACTER(len=*), INTENT(in), OPTIONAL               :: help
+      !! A string with a short description of the argument
+    CHARACTER(len=*), DIMENSION(:), INTENT(in), OPTIONAL :: meta
+      !! A vector of strings with the displayed value's name(s) of the argument in the help command
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process.
+    CHARACTER(len=:), ALLOCATABLE :: sf,lf,de,na,he
+    INTEGER                       :: ty,ac
+    err = noerror
+    sf = '  ' ; lf = ''
+    IF (LEN_TRIM(flag) == 2) THEN
+      sf = TRIM(flag)
+    ELSE
+      lf = TRIM(flag)
+    ENDIF
+    he = ""       ; IF (PRESENT(help))    he = TRIM(help)
+    na = ""       ; IF (PRESENT(nrec))    na = TRIM(nrec)
+    ty = ap_undef ; IF (PRESENT(type))    ty = type
+    ac = ap_undef ; IF (PRESENT(action))  ac = action
+    de = ''       ; IF (PRESENT(default)) de = TRIM(default)
+    ! Calling "true" function
+    IF (PRESENT(meta)) THEN
+      err = ap_add_option_1(this,dest,sf,lf,ty,ac,de,na,he,meta)
+    ELSE
+      err = ap_add_option_1(this,dest,sf,lf,ty,ac,de,na,he)
+    ENDIF
+    RETURN
+  END FUNCTION ap_add_option_2
+
+  FUNCTION ap_check_in_parser(this,arg) RESULT(err)
+    !! Check if an argument is already set in the parser
+    !! An argument is assumed to be already set in the parser if either its name,
+    !! short or long option flag is already defined (in the parser).
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    TYPE(argc), INTENT(in)        :: arg
+      !! An argc object to check
+    TYPE(error) :: err
+      !! Error object with -8 id if object is already set, no error otherwise.
+    INTEGER :: i
+    err = noerror
+    IF (this%nargs == 0 ) RETURN
+    DO i=1,this%nargs
+    ! note : we could have use the == operator  but we want to get an specific
+    ! error message)
+      IF (TRIM(this%args(i)%name) == TRIM(arg%name)) THEN
+        err = error("argparse: argument '"//TRIM(arg%name)//"' already defined",-8) ; RETURN
+      ENDIF
+      IF (LEN_TRIM(this%args(i)%sflag) > 0 .AND. &
+          TRIM(this%args(i)%sflag) == TRIM(arg%sflag)) THEN
+        err = error("argparse: flag '"//TRIM(arg%sflag)//"' already defined",-8) ; RETURN
+      ENDIF
+      IF (LEN_TRIM(this%args(i)%lflag) > 0 .AND. &
+          TRIM(this%args(i)%lflag) == TRIM(arg%lflag)) THEN
+        err = error("argparse: flag '"//TRIM(arg%lflag)//"' already defined",-8) ; RETURN
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION ap_check_in_parser
+
+  FUNCTION ap_parse_options(this,cmd,help_req,vers_req) RESULT(err)
+    !! Parse options of the internal command line
+    !! This (internal) function manages the parsing of the command line options.
+    OBJECT(argparser), INTENT(inout), TARGET :: this
+      !! An argparser object reference
+    TYPE(words), INTENT(inout)               :: cmd
+      !! A [[string_op(module):words(type)]] object with the command-line to parse
+    LOGICAL, INTENT(out)                     :: help_req
+      !! An output logical flag with `.true.` if help option has been found, `.false.` otherwise
+    LOGICAL, INTENT(out)                     :: vers_req
+      !! An output logical flag with `.true.` if version option has been found, `.false.` otherwise
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=1), PARAMETER   :: sq = CHAR(39)
+    CHARACTER(len=:), ALLOCATABLE :: elt
+    INTEGER                       :: arg_idx
+    INTEGER                       :: i,nv,ic
+    err = noerror ; arg_idx = -1
+    help_req = .false.
+    DO WHILE(words_valid(cmd))
+      ! get current element
+      elt = words_current(cmd)
+      ! check element kind: is it an option flag (-1/0) or a value (1)?
+      ic = ap_check_string(this,elt,arg_idx)
+      IF (ic <= 0) THEN
+        IF (arg_idx /= -1) THEN
+          err = ap_fill_argument(this,cmd,this%args(arg_idx))
+          IF (err == 0) THEN
+            IF (this%args(arg_idx)%paction == ap_help) THEN
+              this%parsed = 1
+              err = argparser_get_value(this,'help',help_req)
+              EXIT
+            ELSE IF (this%args(arg_idx)%paction == ap_version) THEN
+              this%parsed = 1
+              err = argparser_get_value(this,'version',vers_req)
+              EXIT
+            ENDIF
+          ENDIF
+          IF (err /= 0) EXIT
+        ELSE
+          err = error("Unknown argument ('"//elt//"')",-9)
+          EXIT
+        ENDIF
+      ELSE
+        ! We are in the positionals   !!!
+        IF (TRIM(elt) == '--') CALL words_next(cmd)
+        EXIT
+      ENDIF
+      ! iterates to next value
+      CALL words_next(cmd)
+    ENDDO
+
+    ! Do we need to check for error here ?
+    IF (err /= 0) THEN
+      this%parsed = 0
+      arg_idx = -1
+      RETURN
+    ELSE IF (help_req) THEN
+      arg_idx = -1
+      RETURN
+    ENDIF
+
+    ! Must check here for argument with append action if they have the correct
+    ! number of argument
+    DO i=1,this%nargs
+      nv = words_length(this%args(i)%values)
+      IF (this%args(i)%fnd                  .AND. &
+          this%args(i)%paction == ap_append .AND. &
+          this%args(i)%nrec > 0             .AND. &
+          nv /= this%args(i)%nrec) THEN
+        IF (this%args(i)%nrec < nv) THEN
+          err = ac_fmt_val_err(this%args(i),-18) ! extra values
+        ELSE
+          err = ac_fmt_val_err(this%args(i),-17) ! missing values
+        ENDIF
+      ENDIF
+    ENDDO
+    IF (err /= 0) this%parsed = 0
+    RETURN
+  END FUNCTION ap_parse_options
+
+  FUNCTION ap_parse_positionals(this,cmd) RESULT(err)
+    !! Parse positional arguments of the internal command line
+    !! This (internal) function manages the parsing of the command line positional arguments.
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    TYPE(words), INTENT(inout)       :: cmd
+      !! A [[string_op(module):words(type)]] object with the command-line to parse
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process
+    INTEGER                       :: na
+    CHARACTER(len=:), ALLOCATABLE :: elt
+    err = noerror
+    ! cmd iterator is located at the first positional argument
+
+    ! no positional required but current word is valid
+    ! Either : no positional required but valid element is present
+    !     Or : positional required but no valid element is present
+    IF ((this%have_posal.AND..NOT.words_valid(cmd)) .OR. &
+        (.NOT.this%have_posal.AND.words_valid(cmd))) THEN
+      err = error("Wrong number of arguments",-16) ; RETURN
+    ENDIF
+    ! ugly patch : we must clear this%posals%values because of the automatic
+    ! setting of default value
+    CALL words_clear(this%posals%values)
+    this%posals%fnd = .true.
+    DO
+      na = words_length(this%posals%values)
+      IF (words_valid(cmd)) THEN
+        ! Gets the element value
+        elt = words_current(cmd)
+        ! and add it
+        CALL words_append(this%posals%values,elt)
+      ELSE
+        ! no more elements: check against the number of expected records
+        ! 1 or + elements expected but nothing has been saved
+        IF (this%posals%nrec == -3 .AND. na > 1) THEN
+          err = error("Extra value(s) found (positionals arguments)",-18)
+        ELSE IF (this%posals%nrec == -2 .AND. na == 0) THEN
+          err = error("Missing values (positionals arguments)",-17)
+        ELSE IF (this%posals%nrec > 0 .AND. na /= this%posals%nrec) THEN
+          IF (na > this%posals%nrec) THEN
+            err = error("Extra value(s) found (positionals arguments)",-18)
+          ELSE
+            err = error("Missing values (positionals arguments)",-17)
+          ENDIF
+        ENDIF
+        EXIT
+      ENDIF
+      ! get to the next element
+      CALL words_next(cmd)
+    ENDDO
+    IF (err /= noerror) THEN
+      this%posals%fnd = .false.
+      this%parsed = 0
+    ENDIF
+    RETURN
+  END FUNCTION ap_parse_positionals
+
+  FUNCTION ap_fill_argument(this,cmd,arg) RESULT(err)
+    !! Fill an argument with values
+    !!
+    !! The function parses remaining parts of the command line from the position of
+    !! the given argument and attempts to retrieve its value(s) (if any).
+    !! Several tests are performed and may raise errors. The function always stops
+    !! at the first error encountered which can be one of the following :
+    !! - Invalid value type (conversion check failed)
+    !! - Missing values (can not get as much values as defined in the argument)
+    !! - Extra values found (more values can be retrieved from the command line
+    !!   than defined in the argument)
+    OBJECT(argparser), INTENT(inout)  :: this
+      !! An argparser object reference
+    TYPE(words), INTENT(inout)        :: cmd
+      !! The command line
+    TYPE(argc), INTENT(inout), TARGET :: arg
+      !! An argc object reference with the argument currently processed.
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process
+    INTEGER                       :: ca, isopt, itmp
+    LOGICAL                       :: ltmp
+    CHARACTER(len=:), ALLOCATABLE :: elt
+    err = noerror
+    ! We always reset arguments value if we are in store mode.
+    ! If one wants to set an option several times and keeps track of the
+    ! previous values, she must use 'append'
+    IF (arg%paction == ap_store) CALL words_clear(arg%values)
+    ! Gets values as a function of the expected number of records
+    IF (arg%nrec == 0) THEN
+      ! argparser_parse (main parsing method) reset all values: for 0 nrec
+      ! we should have at least one value saved which is the default.
+      ! if it is not the case we set default as values of the argument
+      IF (words_length(arg%values) == 0) CALL words_append(arg%values,arg%default)
+      ! NOW HANDLING 0 records case:
+      ! we do not have to consume any argument but :
+      !  - trigger a boolean (set it to .not.default)
+      !  - increase a counter (if action == 'count')
+      IF (arg%paction == ap_count) THEN
+        elt = words_pop(arg%values)
+        READ(elt,*) itmp ; itmp = itmp + 1
+        CALL words_append(arg%values,TO_STRING(itmp))
+      ELSE IF (arg%ptype == ap_logical) THEN
+        elt = words_pop(arg%values)
+        READ(elt,*) ltmp
+        CALL words_append(arg%values,TO_STRING(.NOT.ltmp))
+      ENDIF
+    ELSE
+      ! For any other case, the algorithm is quite simple :
+      ! We consume tokens of the command-line until either the end or
+      ! the next option (or '--' separator)
+      ! When the exit condition is met we perform some tests based on the
+      ! expected argument and sets the returned error object...
+      ca = 0 ; IF(arg%paction == ap_append) ca = words_length(arg%values)
+      DO
+        CALL words_next(cmd) ; elt = words_current(cmd) ; isopt = ap_check_string(this,elt)
+        ! We have a "valid" value
+        IF (((.NOT.words_valid(cmd)).EQV.(isopt<=0)).AND.TRIM(elt) /= '--') THEN
+          ! we have a value that we should check for conversion !
+          err = ac_check_value(arg,elt)
+          IF (err /= 0) THEN
+            err = ac_fmt_val_err(arg,-2)
+          ELSE
+            CALL words_append(arg%values,elt) ; ca = ca + 1
+          ENDIF
+        ELSE
+          ! 3 cases are possible :
+          !    1) we have consumed all argument of command line
+          !    2) current argument is not a value !
+          !    3) current argument the separator '--'
+          IF (isopt <= 0 .OR. TRIM(elt)=='--') CALL words_previous(cmd)
+          IF (arg%nrec == -2 .AND. words_length(arg%values) == 0) &
+            err = ac_fmt_val_err(arg,-17)
+          IF (arg%paction /= ap_append .AND. arg%nrec > 0 .AND. ca /= arg%nrec) &
+          THEN
+            IF (ca > arg%nrec) THEN
+              err = ac_fmt_val_err(arg,-18)
+            ELSE
+              err = ac_fmt_val_err(arg,-17)
+            ENDIF
+          ENDIF
+          EXIT
+        ENDIF
+        IF (err /= noerror) EXIT
+        IF (arg%nrec == -3) EXIT
+        IF (ca == arg%nrec) EXIT
+      ENDDO
+    ENDIF
+    arg%fnd = (err == noerror)
+    RETURN
+  END FUNCTION ap_fill_argument
+
+  FUNCTION ap_split_cmd(this,string,new_cmd,rhelp) RESULT(err)
+    !! Preprocess the command line
+    !! The function reads and splits the given string so merged options/values
+    !! are splitted and saves the resulting string elements in a list of words.
+    !! @warning
+    !! For compilers that does not support allocatable strings in derived types,
+    !! computation are highly dependent of [[string_op(module):st_slen(variable):
+    !! tokens length are limited by this parameter.
+    IMPLICIT NONE
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: string
+      !! A string to process
+    TYPE(words), INTENT(out)      :: new_cmd
+      !! An output [[string_op(module):words(type)]] object with the processed command line
+    LOGICAL, INTENT(out)          :: rhelp
+      !! An output boolean flag with `.true.` if help is requested, `.false.` otherwise
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process
+    INTEGER                       :: isopt,j,tl,res
+    CHARACTER(len=:), ALLOCATABLE :: elt
+    TYPE(words)                   :: splitted
+    INTEGER                       :: arg_idx
+    err = noerror ; rhelp = .false.
+    IF (LEN_TRIM(string) == 0) THEN
+      err = error('internal error (empty string)',-255)
+      RETURN
+    ENDIF
+    ! split input command line in words !
+    call words_extend(splitted,string," ",.true.)
+    ! reset iterator
+    CALL words_reset(splitted)
+    DO WHILE(words_valid(splitted))
+      elt = words_current(splitted) ; tl = LEN_TRIM(elt)
+      isopt = ap_check_string(this,TRIM(elt),arg_idx)
+      ! we have a short option : maybe we need to split it
+      IF (isopt == -1 ) THEN
+        DO j=2,tl
+          res = ap_check_string(this,"-"//elt(j:j),arg_idx)
+          ! great we have another short option flag
+          IF (res == -1 .AND. arg_idx /= -1) THEN ! another short option !
+            rhelp = (this%args(arg_idx)%paction == ap_help.OR.rhelp)
+            ! we must not set some argument's values here  for help argument
+            ! if auto is not set the parse method will disable the option
+            ! during next! parsing process
+            CALL words_append(new_cmd,"-"//elt(j:j))
+          ELSE
+            IF (j == 2) THEN ! no more option flag !
+              CALL words_append(new_cmd,TRIM(elt(j-1:)))
+            ELSE
+              CALL words_append(new_cmd,TRIM(elt(j:)))
+            ENDIF
+            EXIT ! WE MUST EXIT !!!!!!!
+          ENDIF
+        ENDDO
+      ELSE
+        IF (isopt == 0.AND.arg_idx /= -1) &
+        rhelp = (this%args(arg_idx)%paction == ap_help.OR.rhelp)
+        ! we must not set some argument's values here for help argument
+        ! if auto is not set the parse method will disable the option during
+        ! next parsing process
+        CALL words_append(new_cmd,TRIM(elt))
+      ENDIF
+      ! Iterates to next word
+      CALL words_next(splitted)
+    ENDDO
+    CALL words_clear(splitted)
+    RETURN
+  END FUNCTION ap_split_cmd
+
+  FUNCTION ap_check_string(this,string,idx) RESULT(ret)
+    !! Check if a string is an option flag
+    !! The function checks if the given string is either a short, long option flag or a value.
+    !! @warning
+    !! An empty string is considered as a value !
+    OBJECT(argparser), INTENT(in)              :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)               :: string
+      !! A string to check
+    INTEGER, INTENT(out), OPTIONAL             :: idx
+      !! An optional output intger with the index of the afferent argument in the parser (-1 if not found)
+    INTEGER :: ret
+      !! Return code with the following possible values:
+      !! - -1 if the string is a SHORT option flag
+      !! - 0 if the string is a LONG option flag
+      !! - 1 if it considered as a value
+    CHARACTER(len=52), PARAMETER :: alpha = "abcdefghijklmnopqrstuvwxyz&
+                                            &ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    ! return code can be either :
+    !  -1 : it's a short option flag
+    !   0 : it's a long option flag
+    !   1 : it's a value
+    ! The combination of the (optional) output index and the return code
+    ! allows to check if an option is known or not
+    ret = 1
+    ! '--' is special : it is a separator that is seen as a value.
+    IF (TRIM(string) == '--') RETURN
+    IF (PRESENT(idx)) idx = -1
+    ! First we will check the two first characters of the string
+    IF (LEN_TRIM(string) >= 2) THEN
+      IF (string(1:1) == ACHAR(45)) THEN
+        ! It's a long option flag !
+        IF (string(2:2) == ACHAR(45)) THEN
+          ret = 0
+          IF (PRESENT(idx)) idx = ap_get_arg_index(this,lflag=TRIM(string))
+        ELSE
+          ! It's a short option flag !
+          IF (INDEX(alpha, string(2:2)) /= 0) THEN
+            ret = -1
+            IF (PRESENT(idx)) idx = ap_get_arg_index(this,sflag=string(1:2))
+          ENDIF
+        ENDIF
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_check_string
+
+  FUNCTION ap_gen_help(this) RESULT(hlp)
+    !! Generate the help string
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    CHARACTER(len=:), ALLOCATABLE :: hlp
+      !! The formatted help message
+    CHARACTER(len=:), ALLOCATABLE :: copt,spc,opts,text
+    INTEGER                       :: i,j,ia,optmw,i1,io,n,zw,zh
+    IF (this%width == 0) CALL fs_termsize(zh,this%width)
+    zw = this%width
+    ! Sets usage
+    CALL ap_format_usage(this,optmw)
+    ! computes the help position
+    optmw = optmw +2 + 2 ! +2 for indentation + 2 for spaces after option
+    optmw = MIN(MAX(5,this%mxhlpos-1),optmw)
+    hlp = TRIM(this%usg)//NEW_LINE('A')//NEW_LINE('A')
+    ! Sets description
+    IF (LEN_TRIM(this%descr) /= 0) &
+      hlp=hlp//getpar(this%descr,zw,2)//NEW_LINE('A')//NEW_LINE('A')
+    ! Sets positionals
+    IF (this%have_posal) THEN
+      IF (LEN_TRIM(this%posals%help) /= 0) THEN
+        hlp=hlp//'positionals:'//NEW_LINE('A')
+        copt = ac_get_opt_str(this%posals) ; n = LEN_TRIM(copt)
+        hlp=hlp//getpar(copt,zw,idt1=2)
+        ! we must put help on a new line : no need to extends opt with spaces
+        IF (n > optmw-2) THEN
+          hlp=hlp//NEW_LINE('A')//REPEAT(CHAR(32),optmw)
+        ELSE
+          hlp=hlp//REPEAT(CHAR(32),optmw-n-2)
+        ENDIF
+        spc = REPEAT(CHAR(32),optmw)
+        text = getpar(this%posals%help,zw-optmw,0,0)
+        j=1
+        DO WHILE(j <= LEN(text))
+          i = INDEX(text(j:),NEW_LINE('A'))
+          IF (i /= 0) THEN
+            hlp=hlp//text(j:j+i-1)//spc
+            j=j+i
+          ELSE
+            hlp=hlp//text(j:) ; EXIT
+          ENDIF
+        ENDDO
+        hlp=hlp//NEW_LINE('A')//NEW_LINE('A')
+      ENDIF
+    ENDIF
+    ! Sets options
+    IF (this%nargs > 0) THEN
+      opts=''
+      DO ia=1, this%nargs
+        IF (LEN_TRIM(this%args(ia)%help) /= 0) THEN
+          copt = ac_get_opt_str(this%args(ia)) ; n = LEN_TRIM(copt)
+          opts=opts//getpar(copt,zw,idt1=2)
+          ! we must put help on a new line : no need to extends opt with spaces
+          IF (n > optmw-2) THEN
+            opts=opts//NEW_LINE('A')//REPEAT(CHAR(32),optmw)
+          ELSE
+            opts=opts//REPEAT(CHAR(32),optmw-n-2)
+          ENDIF
+          spc = REPEAT(CHAR(32),optmw)
+          text = getpar(this%args(ia)%help,zw-optmw,0,0)
+          j=1
+          DO WHILE(j <= LEN(text))
+            i = INDEX(text(j:),NEW_LINE('A'))
+            IF (i /= 0) THEN
+              opts=opts//text(j:j+i-1)//spc
+              j=j+i
+            ELSE
+              opts=opts//text(j:) ; EXIT
+            ENDIF
+          ENDDO
+          opts=opts//NEW_LINE('A')
+        ENDIF
+      ENDDO
+      IF (LEN_TRIM(opts) > 0) hlp=hlp//'options:'//NEW_LINE('A')//opts
+    ENDIF
+    IF (LEN_TRIM(this%eplg) /= 0) THEN
+      hlp=hlp//NEW_LINE('A')//getpar(this%eplg,zw,2)//NEW_LINE('A')
+    ENDIF
+    RETURN
+  END FUNCTION ap_gen_help
+
+  SUBROUTINE ap_format_usage(this,optmw)
+    !! Format command line usage.
+    !!
+    !! The subroutine creates and formats the command line usage of the
+    !! given argparser object. If [[argparser(type):usg(variable)]] is already set (i.e. not empty)
+    !! then the method only computes the maximum width of the option part of the usage command
+    !! (see `optmw` argument description) if needed. In the other case, the method builds the usage
+    !! line based on the arguments stored in the parser.
+    OBJECT(argparser), INTENT(inout) :: this
+      !! An argparser object reference
+    INTEGER, INTENT(out), OPTIONAL   :: optmw
+      !! An optional integer with the maximum length of the option part of the usage command.
+      !! This variable is intended to set a fancy indentation while printing option in the helper.
+    CHARACTER(len=:), ALLOCATABLE :: usage, idts, copt,pgn
+    INTEGER                       :: i,omw,idt,cl,wopt
+    ! Get/Checks/Sets maximum width
+    ! get program name
+    pgn = get_progname()
+    omw = 0
+    IF (LEN_TRIM(this%usg) == 0) THEN
+      idt = 8 + LEN_TRIM(pgn)
+      ALLOCATE(CHARACTER(LEN=idt+1) :: idts)
+      idts(1:1) = NEW_LINE('A') ; idts(2:idt+1) = CHAR(32)
+      ! Builds the usage string
+      usage = "usage: "//TRIM(pgn)//CHAR(32)
+      ! Looping over arguments
+      cl = idt
+      DO i=1,this%nargs
+        ! gets max length for help options
+        copt = ac_get_opt_str(this%args(i)) ; wopt=LEN_TRIM(copt)
+        IF (LEN_TRIM(this%args(i)%help) /= 0.AND. wopt > omw) omw = wopt
+        !IF (LEN_TRIM(copt) > omw) omw = LEN_TRIM(copt) ; copt=""
+        ! Retrieve current argument usage string
+        copt = ac_get_usg_opt_str(this%args(i))
+        ! Set a new line if it does not hold width
+        IF (cl+LEN_TRIM(copt) > this%width) THEN
+          cl = idt ; usage = usage(:)//idts(:)
+        ENDIF
+        ! Write the argument usage + a blank space !
+        usage = usage(:)//TRIM(copt)//CHAR(32)
+        cl = cl + LEN_TRIM(copt)+1
+      ENDDO
+      IF (PRESENT(optmw)) optmw = omw
+      ! handling positional
+      IF (this%have_posal) THEN
+        copt = ac_get_usg_opt_str(this%posals)
+        IF (LEN_TRIM(copt) > 0) THEN
+          ! print newline if it cannot fit
+          IF (cl+LEN_TRIM(copt) > this%width) usage = usage(:)//idts(:)
+          usage = usage(:)//TRIM(copt)
+        ENDIF
+      ENDIF
+      this%usg = usage
+    ELSE
+      IF (PRESENT(optmw)) THEN
+        DO i=1,this%nargs
+          copt = ac_get_opt_str(this%args(i)) ; wopt=LEN_TRIM(copt)
+          IF (LEN_TRIM(this%args(i)%help) /= 0.AND.wopt > omw) omw = wopt
+          copt = ""
+        ENDDO
+        optmw = omw
+      ENDIF
+    ENDIF
+  END SUBROUTINE ap_format_usage
+
+  SUBROUTINE ap_affect_parser(this,other)
+    !! Argparser assignment operator subroutine
+    !! The subroutine assigns `other` to `this`
+    TYPE(argparser), INTENT(out) :: this
+      !! An argparser object to be assigned
+    TYPE(argparser), INTENT(in)  :: other
+      !! An argparser object to assign
+    INTEGER :: i
+    IF (other%nargs > 0) THEN
+      this%nargs = other%nargs
+      ALLOCATE(this%args(this%nargs))
+      DO i=1,this%nargs ; this%args(i) = other%args(i) ; ENDDO
+    ENDIF
+    this%have_posal = other%have_posal
+    this%posals     = other%posals
+    this%parsed     = other%parsed
+#if HAVE_FTNDTSTR
+    IF (ALLOCATED(other%usg))   this%usg   = other%usg
+    IF (ALLOCATED(other%descr)) this%descr = other%descr
+    IF (ALLOCATED(other%eplg))  this%eplg  = other%eplg
+    IF (ALLOCATED(other%vers))  this%vers  = other%vers
+#else
+    this%usg   = other%usg
+    this%descr = other%descr
+    this%eplg  = other%eplg
+    this%vers  = other%vers
+#endif
+    this%mxhlpos = other%mxhlpos
+    this%width   = other%width
+    this%init    = other%init
+  END SUBROUTINE ap_affect_parser
+
+  ! argc methods
+  ! ------------
+
+  SUBROUTINE ac_affect_arg(this,other)
+    !! Argc object assignment operator subroutine
+    !! The subroutine assigns `other` to `this`
+    TYPE(argc), INTENT(out) :: this
+      !! An argc object to be assigned
+    TYPE(argc), INTENT(in)  :: other
+      !! An argc object to assign
+    this%nrec   = other%nrec
+    this%paction = other%paction
+    this%ptype   = other%ptype
+    this%fnd  = other%fnd
+    this%sflag  = other%sflag
+#if HAVE_FTNDTSTR
+    IF (ALLOCATED(other%name))    this%name    = other%name
+    IF (ALLOCATED(other%lflag))   this%lflag   = other%lflag
+    IF (ALLOCATED(other%help))    this%help    = other%help
+    IF (ALLOCATED(other%default)) this%default = other%default
+#else
+    this%name    = other%name
+    this%lflag   = other%lflag
+    this%default = other%default
+    this%help    = other%help
+#endif
+    CALL words_clear(this%values)
+    CALL words_clear(this%meta)
+    this%values = other%values
+    this%meta   = other%meta
+    RETURN
+  END SUBROUTINE ac_affect_arg
+
+  FUNCTION ac_equals_arg(this,other) RESULT(ret)
+    !! Check if two arguments are identical
+    !! The method checks if two arguments are equal based on their name, short option flag and long
+    !! option flag.Two arguments are considered equals if at least one of these three members is equal
+    !! and not empty.
+    TYPE(argc), INTENT(in) :: this
+      !! First argc object to compare
+    TYPE(argc), INTENT(in) :: other
+      !! Second argc object to compare
+    LOGICAL :: ret
+      !! `.true.` if the two objects are equal, `.false.` otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: tna,ona,tsf,osf,tlf,olf
+    INTEGER                       :: tn,ts,tl,on,os,ol
+    ret = .false.
+    tsf = TRIM(this%sflag) ; osf = TRIM(this%sflag)
+#if HAVE_FTNDTSTR
+    tna="" ; IF (ALLOCATED(this%name))   tna=TRIM(this%name)
+    ona="" ; IF (ALLOCATED(other%name))  ona=TRIM(other%name)
+    tlf="" ; IF (ALLOCATED(this%lflag))  tlf=TRIM(this%lflag)
+    olf="" ; IF (ALLOCATED(other%lflag)) olf=TRIM(other%lflag)
+#else
+    tna=TRIM(this%name)  ; ona=TRIM(other%name)
+    tlf=TRIM(this%lflag) ; olf=TRIM(other%lflag)
+#endif
+    tn=LEN_TRIM(tna) ; on=LEN_TRIM(ona)
+    tl=LEN_TRIM(tlf) ; ol=LEN_TRIM(olf)
+    ts=LEN_TRIM(tsf) ; os=LEN_TRIM(osf)
+    ! check on name :
+    ! Returns True if at least of name, sflag and lflag is set to non-empty
+    ! string and is indentical for the two argument !
+    ret = ((tn/=0 .AND. on==tn) .AND. tna == ona) .OR. &
+          ((tl/=0 .AND. ol==tl) .AND. tlf == olf) .OR. &
+          ((ts/=0 .AND. os==ol) .AND. tsf == osf)
+    DEALLOCATE(tna,ona,tsf,osf,tlf,olf)
+  END FUNCTION ac_equals_arg
+
+  FUNCTION ac_differs_arg(this,other) RESULT(ret)
+    !! Check if two arguments are different
+    !! The method checks if two arguments are different based on their names, short option flag
+    !! and long option flag.
+    !! @note
+    !! This function is the extact contrary of [[argparse(module):ac_equals_arg(function)]] !
+    TYPE(argc), INTENT(in) :: this
+      !! First argc object to compare
+    TYPE(argc), INTENT(in) :: other
+      !! Second argc object to compare
+    LOGICAL :: ret
+      !! `.true.` if the two objects are different, `.false.` otherwise.
+    ret = .NOT. ac_equals_arg(this,other)
+  END FUNCTION ac_differs_arg
+
+  SUBROUTINE ac_clear_arg_sc(arg)
+    !! argc destructor (scalar)
+    !! The subroutine frees all memory used by an argc object and resets its member to
+    !! default values.
+    TYPE(argc), INTENT(inout) :: arg
+      !! An argc object to free
+    CALL words_clear(arg%values)
+    CALL words_clear(arg%meta)
+    arg%ptype   = ap_logical
+    arg%paction = ap_store
+    arg%nrec    = 0
+    arg%fnd     = .false.
+    arg%sflag   = "  "
+#if HAVE_FTNDTSTR
+    IF (ALLOCATED(arg%default)) DEALLOCATE(arg%default)
+    IF (ALLOCATED(arg%name))    DEALLOCATE(arg%name)
+    IF (ALLOCATED(arg%lflag))   DEALLOCATE(arg%lflag)
+    IF (ALLOCATED(arg%help))    DEALLOCATE(arg%help)
+#else
+    arg%default = ""
+    arg%name    = ""
+    arg%help    = ""
+    arg%lflag   = ""
+#endif
+  END SUBROUTINE ac_clear_arg_sc
+
+  SUBROUTINE ac_clear_arg_ve(args)
+    !! argc destructor (vector)
+    TYPE(argc), INTENT(inout), DIMENSION(:), TARGET :: args
+      !! A vector of argc objects to free
+    INTEGER :: i
+    CALL words_clear(args%values)
+    CALL words_clear(args%meta)
+    DO i=1, SIZE(args)
+      args(i)%ptype   = ap_logical
+      args(i)%paction = ap_store
+      args(i)%nrec    = 0
+      args(i)%fnd     = .false.
+      args(i)%sflag    = "  "
+#if HAVE_FTNDTSTR
+      IF (ALLOCATED(args(i)%default)) DEALLOCATE(args(i)%default)
+      IF (ALLOCATED(args(i)%name))    DEALLOCATE(args(i)%name)
+      IF (ALLOCATED(args(i)%lflag))   DEALLOCATE(args(i)%lflag)
+      IF (ALLOCATED(args(i)%help))    DEALLOCATE(args(i)%help)
+#else
+      args(i)%default = ""
+      args(i)%name    = ""
+      args(i)%help    = ""
+      args(i)%lflag   = ""
+#endif
+    ENDDO
+  END SUBROUTINE ac_clear_arg_ve
+
+  FUNCTION ac_found(this) RESULT(yes)
+    !! Check if argument has been found in the command line
+    TYPE(argc), INTENT(in) :: this
+      !! An argc object
+    LOGICAL :: yes
+      !! `.true.` if the option has been parsed, `.false.` otherwise
+    yes = this%fnd
+  END FUNCTION ac_found
+
+  FUNCTION ac_get_num_values(this) RESULT(num)
+    !! Get the actual number of values stored in the argument
+    TYPE(argc), INTENT(in) :: this
+      !! An argc object
+    INTEGER :: num
+      !! The number of values stored in the argument
+    num = words_length(this%values)
+  END FUNCTION ac_get_num_values
+
+  FUNCTION ac_get_usg_opt_str(arg) RESULT(line)
+    !! Build and format the option string for the usage part of the help message
+    !! The function is private part of the help builder. It creates the
+    !! option string part of a given argument.
+    TYPE(argc), INTENT(in), TARGET :: arg
+      !! An argc object
+    CHARACTER(len=:), ALLOCATABLE :: line,meta
+      !! Allocated string with the option flags string
+    line=""
+    IF (LEN_TRIM(arg%sflag) > 0) THEN
+      line="["//TRIM(arg%sflag)
+    ELSE IF (LEN_TRIM(arg%lflag) > 0) THEN
+      line="["//TRIM(arg%lflag)
+    ENDIF
+    meta = TRIM(words_get(arg%meta,1))
+    SELECT CASE (arg%nrec)
+      CASE (-3)
+        line=line(:)//" ["//meta//"]"
+      CASE (-2)
+        line=line(:)//CHAR(32)//meta//" [...]"
+      CASE (-1)
+        line=line(:)//" ["//meta//" [...]]"
+      CASE (0)
+        ! DO NOTHING BUT MUST BE EXPLICITELY SET
+      CASE DEFAULT
+        meta = words_to_string(arg%meta," ")
+        line = line(:)//CHAR(32)//meta
+    END SELECT
+    IF (line(1:1) == "[") line=line(:)//"]"
+    RETURN
+  END FUNCTION ac_get_usg_opt_str
+
+  FUNCTION ac_get_opt_str(arg) RESULT(line)
+    !! Build and format the option flag string for the option part of the help message
+    !! The function is private part of the help builder. It creates the
+    !! option string part of a given argument for the options part of the help.
+    TYPE(argc), INTENT(in), TARGET :: arg
+      !! An argc object
+    CHARACTER(len=:), ALLOCATABLE :: line
+      !! Allocated string with the option flags string
+    CHARACTER(len=:), ALLOCATABLE :: values,m
+    ! creates values string
+    m = TRIM(words_get(arg%meta,1))
+    SELECT CASE (arg%nrec)
+      CASE(-3)
+        values="["//m//"]"
+      CASE(-2)
+        values=m//" [...]"
+      CASE(-1)
+        values="["//m//" [...]]"
+      CASE (0)
+        values=""
+      CASE DEFAULT
+        values = words_to_string(arg%meta," ")
+     END SELECT
+     ! build the final string
+     ! -s values, --long values
+     line=""
+     IF (LEN_TRIM(arg%sflag) > 0) THEN
+       line=TRIM(arg%sflag)//CHAR(32)//TRIM(values)
+       IF (LEN_TRIM(arg%lflag) > 0) line = line(:)//", "
+     ENDIF
+     IF (LEN_TRIM(arg%lflag) > 0) THEN
+         line = line(:)//TRIM(arg%lflag)//CHAR(32)//TRIM(values)
+     ENDIF
+     ! this line handles positionals :)
+     IF (arg%name == 'positional') line = TRIM(values)
+    RETURN
+  END FUNCTION ac_get_opt_str
+
+  FUNCTION ac_fmt_val_err(arg,id) RESULT(ret)
+    !! Format specific error for argc values
+    !! The function formats argparse specific errors when extra (missing) values
+    !! are (not) set or when given values are not consistent with argument's type.
+    !! For each of these errors, the basic error is updated with a more precise
+    !! message.
+    TYPE(argc), INTENT(in) :: arg
+      !! An argc object
+    INTEGER, INTENT(in)    :: id
+      !! Integer with the error id (-3, -15 or -16 in fact)
+    TYPE(error) :: ret
+      !! Error object with the updated error message.
+    CHARACTER(len=:), ALLOCATABLE :: msg,nv
+    IF (LEN_TRIM(arg%sflag) /= 0) THEN
+      msg=' ('//arg%sflag
+    ELSE ! note that normally if no sflag are present lflag should be set
+      msg=' ('//TRIM(arg%lflag)
+    ENDIF
+    nv = to_string(arg%nrec) ; IF (arg%nrec < 0) nv='1'
+    ! we only handle ids : -2 (invalid arg), -17 (missing values), -18 (extra values)
+    SELECT CASE(id)
+      CASE (-2)  ! invalid value: cannot cast ->
+        msg = msg//" option expects '"//apt2str(arg%ptype)//"' values)"
+      CASE (-17) ! missing values -> -17
+        IF (arg%nrec == -2) THEN
+          msg = msg//' takes at least '//nv//' value(s))'
+        ELSE
+          msg = msg//' takes exactly '//nv//' value(s))'
+        ENDIF
+      CASE (-18) ! extra values -> -18
+        IF (arg%nrec == -3) THEN
+          msg = msg//' takes at most '//nv//' value(s))'
+        ELSE
+          msg = msg//' takes exactly '//nv//' value(s))'
+        ENDIF
+      CASE DEFAULT
+        msg=''
+    END SELECT
+    ret = error(msg,id)
+  END FUNCTION ac_fmt_val_err
+
+  FUNCTION ac_check_and_set(this,sf,lf,ty,ac,de,na,meta,check_flag) RESULT(ret)
+    !! Interface to all argc's member tests
+    !! The function calls all the tests to perform on argc members. Some of these tests can
+    !! alter argc's member values to fit argparser's requirements.
+    !!
+    !! On success, `noerror` is returned. Otherwise -9 error code is returned. Errors are only
+    !! produced by misuse of the function arguments. In such case, the program should be
+    !! stopped: note that such ezrror should not occur in _released_ programs.
+    TYPE(argc), INTENT(inout)                            :: this
+      !! An argc object
+    CHARACTER(len=2), INTENT(in)                         :: sf
+      !! A string (2 chars wide) with the short option flag
+    CHARACTER(len=*), INTENT(in)                         :: lf
+      !! A string with the long option flag
+    INTEGER, INTENT(in)                                  :: ty
+      !! An integer with the type of the argument
+    INTEGER, INTENT(in)                                  :: ac
+      !! An integer with the action of the argument
+    CHARACTER(len=*), INTENT(in)                         :: de
+      !! A string with the default value of the argument
+    CHARACTER(len=*), INTENT(in)                         :: na
+      !! A string pattern with the expected number of values for the argument
+    CHARACTER(len=*), INTENT(in), DIMENSION(:), OPTIONAL :: meta
+      !! An optional vector of strings with the Meta-name of the values
+    LOGICAL, INTENT(in), OPTIONAL                        :: check_flag
+      !! An optional boolean flag hat instructs the method wether to check for option flag
+      !! or not. By default this test is enabled but it should be disabled if one wants to
+      !! check for POSITIONAL arguments as they do not have option flags.
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    LOGICAL :: zflag
+    zflag = .true. ; IF (PRESENT(check_flag)) zflag = check_flag
+    ! 1) CHECKING FLAG SYNTAX
+    IF (zflag) THEN
+      ret = ac_check_flags(this,sf,lf) ; IF (ret /= 0) RETURN
+    ELSE
+      this%sflag = sf ; this%lflag = lf
+    ENDIF
+    ! 2) CHECKING AND SETTING action, type, default value and number of record
+    ret = ac_check_ac_ty_de_na(this,ac,ty,de,na) ; IF (ret /= 0) RETURN
+    ! 3) Sets/updates meta name
+    IF (PRESENT(meta)) THEN
+      CALL ac_set_meta(this,meta)
+    ELSE
+      CALL ac_set_meta(this)
+    ENDIF
+    RETURN
+  END FUNCTION ac_check_and_set
+
+  FUNCTION ac_check_ac_ty_de_na(this,ac,ty,de,na) RESULT(ret)
+    !! Check and set argc's action, type and default value
+    !! The method checks if input argument's options are valid and update the argc object
+    !! consequently.
+    !!
+    !! On success, `noerror` is returned. Otherwise -9 error code is returned. Errors are only
+    !! produced by misuse of the function arguments.
+    TYPE(argc), INTENT(inout)  :: this
+      !! An argc object to update
+    INTEGER, INTENT(in)          :: ac
+      !! An integer with the action to set and check
+    INTEGER, INTENT(in)          :: ty
+      !! An integer with the type to set and check
+    CHARACTER(len=*), INTENT(in) :: de
+      !! A string with the default value to set and check
+    CHARACTER(len=*), INTENT(in) :: na
+      !! A string with the expected number of value to set and check
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=:), ALLOCATABLE :: eprf,zna
+    TYPE(error)                   :: uty,ina
+    ret = noerror
+    eprf = 'argparse: '//"Invalid argument `"//TRIM(this%name)//"'"
+    uty = error(eprf//" (type)",-9)
+    ina = error(eprf//" (inconsistent nargs)",-9)
+    zna = TRIM(na)
+    ! Checks action
+    IF(ANY(ap_actions == ac).OR.ac == ap_undef) THEN
+      this%paction = ac
+    ELSE
+      ret = error(eprf//" (action)",-9) ; RETURN
+    ENDIF
+    ! Checks and sets type and default as a function of the action
+    SELECT CASE(this%paction)
+      ! HELP: fixed in any case:
+      CASE(ap_help)
+        this%default = 'F'
+        this%ptype = ap_logical
+        this%nrec = 0
+      ! VERSION: fixed in any case:
+      CASE(ap_version)
+        this%default = 'F'
+        this%ptype = ap_logical
+        this%nrec = 0
+      ! COUNT:
+      ! we always use "hard-coded" stuff and do not warn if dev has made mistakes...
+      CASE(ap_count)
+        ! default settings of the count action
+        this%ptype = ap_integer ; this%default = '0' ; this%nrec = 0
+        ! check and set default value
+        ret = set_def_val()
+      ! STORE, APPEND actions
+      CASE (ap_store, ap_append)
+        ! set type
+        IF (ty == ap_undef) THEN
+          this%ptype= ap_integer
+        ELSEIF (ANY(ap_types == ty)) THEN
+          this%ptype = ty
+        ELSE
+          ret = uty ; RETURN
+        ENDIF
+        ! check and set default value
+        ret = set_def_val() ; IF (ret /= 0) RETURN
+        ! check for nargs (if na is empty then we set "*")
+        ret = set_nrec("*")
+      ! UNDEFINED:
+      !   -> 1) set to action to store
+      !   ->
+      ! try to define a logical trigger and modifies it with user-defined
+      ! characteristics
+      CASE (ap_undef)
+        ! 1) always define store action
+        this%paction = ap_store
+        ! 2) set type and nrec:
+        !    2.1) If type is undef:
+        !         - to default value type if default is given
+        !         - ap_logical otherwiset type
+        !    2.2) set nrec
+        !        - if final type is ap_logical set nrec to 0
+        !        - otherwise to *
+        If (ty == ap_undef) THEN
+          ! no explicit type : define logical trigger first
+          this%ptype = ap_logical ; this%nrec = 0
+          ! icheck the default value given
+          IF (LEN_TRIM(de) > 0) THEN
+            this%ptype = ap_types(string_is(de)+1)
+          ELSE
+            this%ptype = ap_logical
+          ENDIF
+          IF (this%ptype == ap_logical) THEN
+            ret = set_nrec("0")
+          ELSE
+            ret = set_nrec("1")
+          ENDIF
+          ret = set_def_val()
+          IF (ret /= 0) RETURN
+        ! type is given
+        ELSE IF (ANY(ty == ap_types)) THEN
+          ! known type given :
+          !  check default value and nrec: -> if na not given set "*"
+          this%ptype = ty
+          ret = set_def_val() ; IF (ret /= 0) RETURN
+          IF (this%ptype == ap_logical) THEN
+            ret = set_nrec("0")
+          ELSE
+            ret = set_nrec("1")
+          ENDIF
+        ELSE
+          ! unknown type => bad end !
+          ret = uty ; RETURN
+        ENDIF
+    END SELECT
+    ! set default value as first value if ret is noerror ....
+    IF (ret == 0) CALL words_append(this%values,this%default)
+    RETURN
+
+    CONTAINS
+
+      FUNCTION set_nrec(base) RESULT(terr)
+        !! Check and set argument's expected number of records
+        !! The method compares `na` value with the expected and known flags and decides
+        !! wether to raise an error or defines nrec member of the argument object. If `na`
+        !! is empty then `base` is used.
+        CHARACTER(len=1),INTENT(in) :: base
+          !! Base value of nrec
+        TYPE(error) :: terr
+          !! Error object with the return status of the function
+        terr = noerror
+        ! check for nargs:
+        IF (LEN(zna) == 0) zna=base
+        SELECT CASE(zna)
+          CASE("*") ; this%nrec = -1
+          CASE("+") ; this%nrec = -2
+          CASE("?") ; this%nrec = -3
+            IF (this%paction == ap_append) terr = ina
+          CASE DEFAULT ; this%nrec = 0
+            ! check numeric characters
+            IF (VERIFY(zna,"0123456789")==0) READ(zna,*) this%nrec
+            IF (this%nrec == 0) terr = ina
+        END SELECT
+      END FUNCTION set_nrec
+
+      FUNCTION set_def_val() RESULT(terr)
+        !! Check and set argument's default value
+        !! The method compares `de` value with the type already stored in the argument and
+        !! decides wether to raise an error or to save `de` as argument's default value.
+        !! If `de` is empty then it sets a default value according to argument's type.
+        TYPE(error) :: terr
+          !! Error object with the return status of the function
+        INTEGER :: t
+        terr = noerror
+        IF (LEN_TRIM(de) /= 0) THEN
+          this%default = de ; t = string_is(de)
+          IF (t /= this%ptype) THEN
+            terr = error(eprf//" (inconsistent default value: expected '"// &
+                           TRIM(st_type_names(this%ptype))//"', found '"// &
+                           TRIM(st_type_names(t))//"')",-9)
+            RETURN
+          ENDIF
+        ELSE
+          SELECT CASE (this%ptype)
+            CASE(ap_string)  ; this%default = ''
+            CASE(ap_logical) ; this%default = 'F'
+            CASE(ap_complex) ; this%default = '(0d0,0d0)'
+            CASE(ap_integer) ; this%default = '0'
+            CASE(ap_real)    ; this%default = '0d0'
+          END SELECT
+        ENDIF
+        RETURN
+      END FUNCTION set_def_val
+  END FUNCTION ac_check_ac_ty_de_na
+
+  FUNCTION ac_check_flags(this,sflag,lflag) RESULT(ret)
+    !! Set and check if argument's option flags are valid
+    !! The method first sets the given flags in the argc object and then checks if
+    !! its option flags are valid :
+    !!    - A valid short option flag (`sflag`) starts with `-' followed by the
+    !!      regex pattern : @p [a-zA-Z]
+    !!    - A valid long option flag (`lflag`) starts with `--' followed by the
+    !!      regex pattern : @p [-a-zA-Z_0-9]+
+    !! @note
+    !! Although the two arguments are mandatory one can be an empty string
+    TYPE(argc), INTENT(inout)    :: this
+      !! An argc object
+    CHARACTER(len=2), INTENT(in) :: sflag
+      !! A 2-characters wide string with the short option flag
+    CHARACTER(len=*), INTENT(in) :: lflag
+      !! A string (at least 3 characters wide) with the long option flag
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: ic
+    ret = noerror
+    this%sflag = sflag ; this%lflag = lflag
+    ! 1) Check null strings !
+    IF (LEN_TRIM(this%sflag) == 0 .AND. LEN_TRIM(this%lflag) == 0) THEN
+      ret = error("argparse: Invalid argument (empty option flags)",-2) ; RETURN
+    ENDIF
+    ! 2) Check short option flag:
+    IF (LEN_TRIM(this%sflag) > 0) THEN
+      IF (this%sflag(1:1) /= achar(45)) THEN
+        ret = error("argparse: Invalid argument (short option)",-2) ; RETURN
+      ELSE
+        SELECT CASE(iachar(this%sflag(2:2)))
+          CASE(97:122,65:90) ; ret = noerror
+          CASE DEFAULT
+            ret = error("argparse: Invalid argument (short option)",-2) ; RETURN
+        END SELECT
+      ENDIF
+    ENDIF
+    ! 3) Check long option flag
+    IF (LEN_TRIM(this%lflag) > 0) THEN
+      ! We must have at least 3 chars !
+      IF (LEN_TRIM(this%lflag) < 3) THEN
+        ret = error("argparse: Invalid argument (long option)",-2) ; RETURN
+      ELSE IF (this%lflag(1:2) /= achar(45)//achar(45)) THEN
+        ret = error("argparse: Invalid argument (long option)",-2) ; RETURN
+      ELSE
+        DO ic=3,LEN_TRIM(this%lflag)
+          SELECT CASE(iachar(this%lflag(ic:ic)))
+            !  corresponds to [-a-zA-Z0-9_]
+            CASE(45,48:57,65:90,95,97:122) ; ret = noerror
+            CASE DEFAULT
+              ret = error("argparse: Invalid argument (long option)",-2) ; RETURN
+              EXIT
+          END SELECT
+        ENDDO
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ac_check_flags
+
+  SUBROUTINE ac_set_meta(this, meta)
+    !! Set meta-variable of the given argc object
+    !! The method set meta-variable in the argc object. If no `meta` are given, the method
+    !! uses argument's name to set the values.
+    !! @warning
+    !! To be effective, this subroutine must be called after argparse::chk_opt_nargs
+    TYPE(argc), INTENT(inout)                            :: this
+      !! An argc object reference
+    CHARACTER(len=*), INTENT(in), DIMENSION(:), OPTIONAL :: meta
+      !! An optional vector of strings with the meta-variable(s) to set
+    INTEGER                       :: i,j,ms,blk
+    CHARACTER(len=:), ALLOCATABLE :: zmeta
+    ! clear meta values (not needed normally)
+    CALL words_clear(this%meta)
+    IF (PRESENT(meta)) THEN
+      SELECT CASE(this%nrec)
+        CASE(-3,-2,-1)
+          zmeta = to_upper(meta(1))
+          blk = INDEX(TRIM(zmeta),CHAR(32)) - 1
+          IF (blk <= 0) blk=LEN_TRIM(zmeta)
+          CALL words_append(this%meta,zmeta(1:blk))
+        CASE(0)
+          CALL words_append(this%meta,"")
+        CASE DEFAULT
+          ms = SIZE(meta) ; j = 0
+          DO i=1,this%nrec
+            j=j+1 ; IF (j>ms) j=1
+            zmeta = to_upper(meta(j))
+            blk=INDEX(TRIM(zmeta),CHAR(32))-1
+            IF (blk <= 0) blk=LEN_TRIM(zmeta)
+            CALL words_append(this%meta,zmeta(1:blk))
+          ENDDO
+      END SELECT
+    ELSE
+      zmeta = to_upper(TRIM(this%name))
+      SELECT CASE(this%nrec)
+        CASE(-3,-2,-1)
+          CALL words_append(this%meta,zmeta)
+        CASE DEFAULT
+          DO i=1,this%nrec
+            CALL words_append(this%meta,zmeta)
+          ENDDO
+      END SELECT
+    ENDIF
+    RETURN
+  END SUBROUTINE ac_set_meta
+
+  FUNCTION ac_check_value(this,str) RESULT(err)
+    !! Check if given string is a valid value for the argument
+    TYPE(argc), INTENT(in)       :: this
+      !! An argc object reference
+    CHARACTER(len=*), INTENT(in) :: str
+      !! A string with the value to check
+    TYPE(error) :: err
+      !! Error object with the first error encountered in the process
+    INTEGER :: ty
+    err = noerror
+    ! Special conditions for strings: any kind of value is ok if we asked for
+    ! strings
+    IF (this%ptype == ap_string) RETURN
+    ty = string_is(str)
+    ! special handling for integer: they can be seen as real
+    IF (this%ptype == ap_real) THEN
+      IF (ty < 3)  err = error("Cannot cast value",-10)
+    ELSE
+      IF (ty /= this%ptype) err = error("Cannot cast value",-10)
+    ENDIF
+    RETURN
+  END FUNCTION ac_check_value
+
+  !===========
+  !  GETTERS
+  !===========
+
+  ! argparser getters
+  ! -----------------
+
+  FUNCTION ap_get_positional_sc(this,idx,value) RESULT(ret)
+    !! Get positional arguments value at given index
+    !! @warning
+    !! On error, the status of `value` is undefined.
+    OBJECT(argparser), INTENT(in)              :: this
+      !! An argparser object reference
+    INTEGER, INTENT(in)                        :: idx
+      !! Subscript of the positional argument value to get
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: value
+      !! Output raw value of the positional. If `idx` is out of range, `value` is set to an
+      !! empty string.
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    IF (.NOT.this%have_posal) THEN
+      ret = error("argparse: No positional argument(s) defined",-7)
+    ELSE IF (idx <= 0 .OR. idx > words_length(this%posals%values)) THEN
+      ret = error("argparse: index out of range", -3)
+    ELSE
+      value = words_get(this%posals%values,idx)
+      ret = noerror
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_positional_sc
+
+  FUNCTION ap_get_positional_ve(this,values) RESULT(ret)
+    !! Get all positional arguments value
+    !! @warning
+    !! On error, the status of `values` is undefined.
+    OBJECT(argparser), INTENT(in)                                  :: this
+      !! An argparser object reference
+    CHARACTER(len=st_slen), INTENT(out), ALLOCATABLE, DIMENSION(:) :: values
+      !! An allocatable vector of **assumed length** strings with the value(s) of all
+      !! positionals arguments found.
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    LOGICAL :: ok
+    ret = noerror
+    IF (.NOT.this%have_posal) THEN
+      ret = error("argparse: No positional argument(s) defined",-7)
+    ELSE IF (words_length(this%posals%values) == 0) THEN
+      ret = error("argparse: No positional argument(s) values found",-6)
+    ELSE
+      ok = words_to_vector(this%posals%values,values)
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_positional_ve
+
+  FUNCTION ap_get_dv_sc(this, name, output) RESULT(ret)
+    !! Get a scalar `REAL(kind=8)` value from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` value is undefined.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: name
+      !! Name of the argument
+    REAL(kind=8), INTENT(out)     :: output
+      !! A scalar with the first value of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_dv_sc
+
+  FUNCTION ap_get_rv_sc(this, name, output) RESULT(ret)
+    !! Get a scalar `REAL(kind=4)` value from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` value is undefined.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: name
+      !! Name of the argument
+    REAL(kind=4), INTENT(out)     :: output
+      !! A scalar with the first value of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_rv_sc
+
+  FUNCTION ap_get_iv_sc(this, name, output) RESULT(ret)
+    !! Get a scalar `INTEGER` value from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` value is undefined.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: name
+      !! Name of the argument
+    INTEGER, INTENT(out)          :: output
+      !! A scalar with the first value of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_iv_sc
+
+  FUNCTION ap_get_lv_sc(this, name, output) RESULT(ret)
+    !! Get a scalar `LOGICAL` value from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` value is undefined.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: name
+      !! Name of the argument
+    LOGICAL, INTENT(out)          :: output
+      !! A scalar with the first value of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_lv_sc
+
+  FUNCTION ap_get_cv_sc(this, name, output) RESULT(ret)
+    !! Get a scalar `COMPLEX` value from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` value is undefined.
+    OBJECT(argparser), INTENT(in) :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)  :: name
+      !! Name of the argument
+    COMPLEX, INTENT(out)          :: output
+      !! A scalar with the first value of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_cv_sc
+
+  FUNCTION ap_get_sv_sc(this, name, output) RESULT(ret)
+    !! Get a scalar `STRING` value from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)              :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)               :: name
+      !! Name of the argument
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: output
+      !! An allocatable string with the first value of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION ap_get_sv_sc
+
+  FUNCTION ap_get_dv_ve(this, name, output) RESULT(ret)
+    !! Get a vector of `REAL(kind=8)` values from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)                        :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                         :: name
+      !! Name of the argument
+    REAL(kind=8), INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! An allocatable vector with the values of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+  END FUNCTION ap_get_dv_ve
+
+  FUNCTION ap_get_rv_ve(this, name, output) RESULT(ret)
+    !! Get a vector of `REAL(kind=4)` values from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)                        :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                         :: name
+      !! Name of the argument
+    REAL(kind=4), INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! An allocatable vector with the values of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+  END FUNCTION ap_get_rv_ve
+
+  FUNCTION ap_get_iv_ve(this, name, output) RESULT(ret)
+    !! Get a vector of `INTEGER` values from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)                   :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                    :: name
+      !! Name of the argument
+    INTEGER, INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! An allocatable vector with the values of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+  END FUNCTION ap_get_iv_ve
+
+  FUNCTION ap_get_lv_ve(this, name, output) RESULT(ret)
+    !! Get a vector of `LOGICAL` values from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)                   :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                    :: name
+      !! Name of the argument
+    LOGICAL, INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! An allocatable vector with the values of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+  END FUNCTION ap_get_lv_ve
+
+  FUNCTION ap_get_cv_ve(this, name, output) RESULT(ret)
+    !! Get a vector of `COMPLEX` values from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)                   :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                    :: name
+      !! Name of the argument
+    COMPLEX, INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! An allocatable vector with the values of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+  END FUNCTION ap_get_cv_ve
+
+  FUNCTION ap_get_sv_ve(this, name, output) RESULT(ret)
+    !! Get a vector of `STRING` values from given argument
+    !! The error returned by the method can be either:
+    !! -  0  : No error
+    !! - -1  : parser has not been initialized
+    !! - -7  : argument not found (i.e. does not set in the parser)
+    !! - -19 : parsing not done yet
+    !! - -20 : (previous) parsing failed
+    !! - -21 : inconsistent destination type
+    !! @note
+    !! If no error occured, the function always set a value which is the default value
+    !! if the argument has not been parsed. Otherwise, `output` status is undefined.
+    OBJECT(argparser), INTENT(in)                            :: this
+      !! An argparser object reference
+    CHARACTER(len=*), INTENT(in)                             :: name
+      !! Name of the argument
+    CHARACTER(len=*), INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! An allocatable vector of **assumed length** strings with the values of the argument
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    INTEGER :: idx
+    ret = ap_check_state(this)
+    IF (ret == 0) THEN
+      idx = ap_get_arg_index(this,name)
+      IF (idx == -1) THEN
+        ret = error("argparse: Argument not found ("//TRIM(name)//")",-7)
+      ELSE
+        ret = argc_get_value(this%args(idx),output)
+      ENDIF
+    ENDIF
+  END FUNCTION ap_get_sv_ve
+
+  ! argc getters
+  ! ------------
+
+  !> Gets a scalar @p REAL(kind=8) value from given argument
+  !! @param[in,out] this An argc object
+  !! @param[out] output A scalar with the first value of the argument
+  !! @return An errors::error object with -21 if the destination variable's type
+  !! is inconsistent, 0 otherwise.
+  FUNCTION ac_get_dv_sc(this, output) RESULT(ret)
+    !! Get a scalar `REAL(kind=8)` value from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` value is undefined.
+    TYPE(argc), INTENT(in)  :: this
+      !! An argc object
+    REAL(kind=8), INTENT(out) :: output
+      !! Output value
+    TYPE(error) :: ret
+      !! Error object with the -21 if the destination variable's type is inconsistent, 0 otherwise
+    ret = noerror
+    IF (this%ptype /= ap_real) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got REAL(kind=8))",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      IF (.NOT.from_string(this%default,output)) &
+        ret = error("Cannot cast value",-10)
+    ELSE
+      IF (.NOT.from_string(words_get(this%values,1), output)) &
+        ret = error("Cannot cast value",-10)
+    ENDIF
+    RETURN
+  END FUNCTION ac_get_dv_sc
+
+  FUNCTION ac_get_rv_sc(this, output) RESULT(ret)
+    !! Get a scalar `REAL(kind=4)` value from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` value is undefined.
+    TYPE(argc), INTENT(in)    :: this
+      !! An argc object
+    REAL(kind=4), INTENT(out) :: output
+      !! Output value
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    ret = noerror
+    IF (this%ptype /= ap_real) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got REAL(kind=4))",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      IF (.NOT.from_string(this%default,output)) &
+        ret = error("Cannot cast value",-10)
+    ELSE
+      IF (.NOT.from_string(words_get(this%values,1), output)) &
+        ret = error("Cannot cast value",-10)
+    ENDIF
+    RETURN
+  END FUNCTION ac_get_rv_sc
+
+  FUNCTION ac_get_iv_sc(this, output) RESULT(ret)
+    !! Get a scalar `INTEGER` value from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` value is undefined.
+    TYPE(argc), INTENT(in) :: this
+      !! An argc object
+    INTEGER, INTENT(out)   :: output
+      !! Output value
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    ret = noerror
+    IF (this%ptype /= ap_integer) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got INTEGER)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      IF (.NOT.from_string(this%default,output)) &
+        ret = error("Cannot cast value",-10)
+    ELSE
+      IF (.NOT.from_string(words_get(this%values,1), output)) &
+        ret = error("Cannot cast value",-10)
+    ENDIF
+    RETURN
+  END FUNCTION ac_get_iv_sc
+
+  FUNCTION ac_get_lv_sc(this, output) RESULT(ret)
+    !! Get a scalar `INTEGER` value from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` value is undefined.
+    TYPE(argc), INTENT(in) :: this
+      !! An argc object
+    LOGICAL, INTENT(out)   :: output
+      !! Output value
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    ret = noerror
+    IF (this%ptype /= ap_logical) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got LOGICAL)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      IF (.NOT.from_string(this%default,output)) &
+        ret = error("Cannot cast value",-10)
+    ELSE
+      IF (.NOT.from_string(words_get(this%values,1), output)) &
+        ret = error("Cannot cast value",-10)
+    ENDIF
+    RETURN
+  END FUNCTION ac_get_lv_sc
+
+  FUNCTION ac_get_cv_sc(this, output) RESULT(ret)
+    !! Get a scalar `COMPLEX` value from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` value is undefined.
+    TYPE(argc), INTENT(in) :: this
+      !! An argc object
+    COMPLEX, INTENT(out)   :: output
+      !! Ouput value
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    ret = noerror
+    IF (this%ptype /= ap_complex) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got COMPLEX)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      IF (.NOT.from_string(this%default,output)) &
+        ret = error("Cannot cast value",-10)
+    ELSE
+      IF (.NOT.from_string(words_get(this%values,1), output)) &
+        ret = error("Cannot cast value",-10)
+    ENDIF
+    RETURN
+  END FUNCTION ac_get_cv_sc
+
+  FUNCTION ac_get_sv_sc(this, output) RESULT(ret)
+    !! Get a scalar `STRING` value from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                     :: this
+      !! An argc object
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: output
+      !! Output value
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    ret = noerror
+    IF (this%ptype /= ap_string) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got STRING)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      output = this%default
+    ELSE
+      output = words_get(this%values,1)
+    ENDIF
+  END FUNCTION ac_get_sv_sc
+
+  FUNCTION ac_get_dv_ve(this, output) RESULT(ret)
+    !! Get a vector of `REAL(kind=8)` values from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                               :: this
+      !! Argc object
+    REAL(kind=8), INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! Output values
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=st_slen), ALLOCATABLE, DIMENSION(:) :: tmp
+    LOGICAL                                           :: ok
+    ret = noerror
+    IF (this%ptype /= ap_real) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got REAL(kind=8))",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      ALLOCATE(output(MAX(this%nrec,1)))
+      ok = from_string(this%default,output(1))
+      output(1:SIZE(output)) = output(1)
+    ELSE
+      IF (ALLOCATED(output)) DEALLOCATE(output)
+      ALLOCATE(output(words_length(this%values)))
+      ok = words_to_vector(this%values,tmp)
+      ok = from_string(tmp, output)
+    ENDIF
+  END FUNCTION ac_get_dv_ve
+
+  FUNCTION ac_get_rv_ve(this, output) RESULT(ret)
+    !! Get a vector of `REAL(kind=4)` values from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                               :: this
+      !! Argc object
+    REAL(kind=4), INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! Output values
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=st_slen), ALLOCATABLE, DIMENSION(:) :: tmp
+    LOGICAL                                           :: ok
+    ret = noerror
+    IF (this%ptype /= ap_real) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got REAL(kind=4))",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      ALLOCATE(output(MAX(this%nrec,1)))
+      ok = from_string(this%default,output(1))
+      output(1:SIZE(output)) = output(1)
+    ELSE
+      IF (ALLOCATED(output)) DEALLOCATE(output)
+      ALLOCATE(output(words_length(this%values)))
+      ok = words_to_vector(this%values,tmp)
+      ok = from_string(tmp, output)
+    ENDIF
+  END FUNCTION ac_get_rv_ve
+
+  FUNCTION ac_get_iv_ve(this, output) RESULT(ret)
+    !! Get a vector of `INTEGER` values from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                          :: this
+      !! Argc object
+    INTEGER, INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! Output values
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=st_slen), ALLOCATABLE, DIMENSION(:) :: tmp
+    LOGICAL                                           :: ok
+    ret = noerror
+    IF (this%ptype /= ap_integer) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got INTEGER)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      ALLOCATE(output(MAX(this%nrec,1)))
+      ok = from_string(this%default,output(1))
+      output(1:SIZE(output)) = output(1)
+    ELSE
+      IF (ALLOCATED(output)) DEALLOCATE(output)
+      ALLOCATE(output(words_length(this%values)))
+      ok = words_to_vector(this%values,tmp)
+      ok = from_string(tmp, output)
+    ENDIF
+  END FUNCTION ac_get_iv_ve
+
+  FUNCTION ac_get_lv_ve(this, output) RESULT(ret)
+    !! Get a vector of `LOGICAL` values from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                          :: this
+      !! Argc object
+    LOGICAL, INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! Output values
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=st_slen), ALLOCATABLE, DIMENSION(:) :: tmp
+    LOGICAL                                           :: ok
+    ret = noerror
+    IF (this%ptype /= ap_logical) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got LOGICAL)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      ALLOCATE(output(MAX(this%nrec,1)))
+      ok = from_string(this%default,output(1))
+      output(1:SIZE(output)) = output(1)
+    ELSE
+      IF (ALLOCATED(output)) DEALLOCATE(output)
+      ALLOCATE(output(words_length(this%values)))
+      ok = words_to_vector(this%values,tmp)
+      ok = from_string(tmp, output)
+    ENDIF
+  END FUNCTION ac_get_lv_ve
+
+  FUNCTION ac_get_cv_ve(this, output) RESULT(ret)
+    !! Get a vector of `COMPLEX` values from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                          :: this
+      !! Argc object
+    COMPLEX, INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! Output values
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    CHARACTER(len=st_slen), ALLOCATABLE, DIMENSION(:) :: tmp
+    LOGICAL                                           :: ok
+    ret = noerror
+    IF (this%ptype /= ap_complex) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got COMPLEX)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      ALLOCATE(output(MAX(this%nrec,1)))
+      ok = from_string(this%default,output(1))
+      output(1:SIZE(output)) = output(1)
+    ELSE
+      IF (ALLOCATED(output)) DEALLOCATE(output)
+      ALLOCATE(output(words_length(this%values)))
+      ok = words_to_vector(this%values,tmp)
+      ok = from_string(tmp, output)
+    ENDIF
+  END FUNCTION ac_get_cv_ve
+
+  FUNCTION ac_get_sv_ve(this, output) RESULT(ret)
+    !! Get a vector of `STRING` values from given argument
+    !! If no error occured, the function always returns at least a value (whatever the parser's
+    !! state is) which is the default value if no specific values are set in the argument.
+    !! Otherwise, `output` status is undefined.
+    TYPE(argc), INTENT(in)                                         :: this
+      !! Argc object
+    CHARACTER(len=st_slen), INTENT(out), ALLOCATABLE, DIMENSION(:) :: output
+      !! Output values
+    TYPE(error) :: ret
+      !! Error object with the first error encountered in the process
+    ret = noerror
+    IF (this%ptype /= ap_string) THEN
+      ret = error("argparse: invalid type for output (expected `"// &
+                  apt2str(this%ptype)//"' got STRING)",-21)
+    ELSEIF (words_length(this%values) == 0) THEN
+      ALLOCATE(output(MAX(this%nrec,1)))
+      output(1:SIZE(output)) = TRIM(this%default)
+    ELSE
+      IF (.NOT.words_to_vector(this%values,output)) DEALLOCATE(output)
+    ENDIF
+  END FUNCTION ac_get_sv_ve
+
+  ! miscellaneous methods
+  ! ---------------------
+
+  FUNCTION apt2str(ap_t) RESULT(str)
+    !! Get the string representation of argparse types constants
+    INTEGER, INTENT(in) :: ap_t
+      !! One of ap_logical, ap_integer, ap_real, ap_complex or ap_string module constants.
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! String representation of the type.
+    SELECT CASE(ap_t)
+      CASE(ap_logical) ; str = 'logical'
+      CASE(ap_integer) ; str = 'integer'
+      CASE(ap_real)    ; str = 'real'
+      CASE(ap_complex) ; str = 'complex'
+      CASE(ap_string)  ; str = 'string'
+      CASE DEFAULT     ; str = 'unknown'
+    END SELECT
+    RETURN
+  END FUNCTION apt2str
+
+  FUNCTION apa2str(ap_a) RESULT(str)
+    !! Get the string representation of argparse actions constants
+    INTEGER, INTENT(in) :: ap_a
+     !! One of ap_store, ap_append,cap_count or ap_help module constants
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! String representation of the action.
+    SELECT CASE(ap_a)
+      CASE(ap_store)  ; str = 'store'
+      CASE(ap_append) ; str = 'append'
+      CASE(ap_count)  ; str = 'count'
+      CASE(ap_help)   ; str = 'help'
+      CASE(ap_version); str = 'version'
+      CASE DEFAULT    ; str = 'unknown'
+    END SELECT
+    RETURN
+  END FUNCTION apa2str
+
+  FUNCTION get_progname() RESULT(name)
+    !> Get the name of the program
+    CHARACTER(len=:), ALLOCATABLE :: name !! The name of the program
+    INTEGER :: c
+    CALL GET_COMMAND_ARGUMENT(0,length=c)
+    ALLOCATE(CHARACTER(len=c) :: name)
+    CALL GET_COMMAND_ARGUMENT(0,value=name)
+    c = INDEX(name, "/", back=.true.)
+    IF (c /= 0.AND.c /= LEN_TRIM(name)) name = TRIM(name(c+1:))
+  END FUNCTION get_progname
+
+END MODULE SWIFT_ARGPARSE
+
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_asciiread.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_asciiread.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_asciiread.F90	(revision 3560)
@@ -0,0 +1,344 @@
+! Copyright (c) Jeremie Burgalat (2013-2022)
+! Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+!
+! This file is part of SWIFT
+!
+! Permission is hereby granted, free of charge, to any person obtaining a copy of
+! this software and associated documentation files (the "Software"), to deal in
+! the Software without restriction, including without limitation the rights to
+! use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+! the Software, and to permit persons to whom the Software is furnished to do so,
+! subject to the following conditions:
+!
+! The above copyright notice and this permission notice shall be included in all
+! copies or substantial portions of the Software.
+!
+! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+! FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+! COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+!! File:    swift_asciiread.F90
+!! Summary: ASCII data file reader source file
+!! Author:  J. Burgalat
+!! Date:    2013-2022
+
+MODULE SWIFT_ASCIIREAD
+    !! ASCII data file reader module
+    !!
+    !! This module provides a single generic method that can be used to read 2D/3D
+    !! data array from ASCII file.
+    !!
+    !! ```
+    !! FUNCTION read_data(path,data) RESULT(err)
+    !! ```
+    !!
+    !! Where:
+    !!
+    !! - __path__ is a string with the path of the data file.
+    !! - __data__ is an output __allocatable__ 2D/3D array of real(kind=8) values.
+    !!
+    !! ## Expected Format of the data file
+    !!
+    !! The input file:
+    !!   - must use blank space(s) as value delimiter.
+    !!   - must have a regular number of columns, that is each data line must
+    !!     have the same number of columns.
+    !!   - can contains any number of empty lines and/or comment line (i.e. line
+    !!     where first non-blank character is "#"). All other lines are assumed
+    !!     to be data.
+    !! Moreover, in the case of 3D data, it must use a SINGLE empty line for "depth" block separator.
+    !!
+    !! Error occured when:
+    !!
+    !! - path does not refer to a existing file (1)
+    !! - No free logical unit available (1)
+    !! - the file does not have regular data-columns number (5)
+    !! - at least a value cannot be cast in double precision (5)
+    !!
+    !! On error, __data__ array is not allocated.
+    !!
+    !! ## 3D data structure
+    !!
+    !! In the case of 3D data the method assumes that the input files consists in _D_ blocks
+    !! of _R_ lines with _C_ columns. Each block must be separated by a single empty line
+    !! and each columns must separated by one or more blank spaces (no tabulation ALLOWED).
+    !!
+    !! On success, the shape of the 3D output array will be _data(R,C,D)_.
+    USE, INTRINSIC :: ISO_FORTRAN_ENV, ONLY: IOSTAT_END, IOSTAT_EOR
+    !USE STRING_OP, ONLY: tokenize,from_string, st_slen
+    USE SWIFT_STRING_OP
+    USE SWIFT_ERRORS
+    IMPLICIT NONE
+  
+    PRIVATE
+    PUBLIC :: noerror,error, error_to_string,aborting
+    PUBLIC :: readline,read_data, OPERATOR(/=), OPERATOR(==)
+  
+    !! Global interface to reading methods
+    INTERFACE read_data
+      MODULE PROCEDURE read_data_2d, read_data_3d
+    END INTERFACE
+  
+    CONTAINS
+  
+  
+    FUNCTION read_data_3d(path,data3d,delimiter) RESULT(err)
+      !! Read an input ASCII data file and stores its content in a 3D array
+      !!
+      !! The function reads an ASCII file and saves its values in a real(kind=8) 3D-array.
+      !!
+      !! The input file:
+      !!
+      !! - must have a regular number of columns, that is each data line must have the same number
+      !!   of columns (according to the delimiter used).
+      !! - must use a SINGLE empty line for "depth" block separator.
+      !! - can contains any number of comment lines (i.e. line where first non-blank character is "#").
+      !!   All other lines (except empty lines) are assumed to be data.
+      !!
+      !! Error occured when:
+      !!    - Path does not refer to a existing file (-11)
+      !!    - No free logical unit available (-1)
+      !!    - The file does not have regular data-columns number (-5)
+      !!    - At least a value cannot be cast in double precision (-10)
+      !!
+      !! The method assumes the input files consists in _D_ block of _R_ lines
+      !! with _C_ columns. Each block must be separated by a single empty line and
+      !! each columns must separated by one or more blank spaces (no tabulation ALLOWED).
+      !!
+      !! On success, the shape of the 3D output array will be _output(R,C,D)_.
+      !! On error, the 3D output array is __not allocated__.
+      CHARACTER(len=*), INTENT(in)                             :: path      !! Path of the input data file
+      REAL(kind=8), INTENT(out), DIMENSION(:,:,:), ALLOCATABLE :: data3d    !! 3D-array with the output values (double precision)
+      CHARACTER(len=*), INTENT(in), OPTIONAL                   :: delimiter !! Optional column delimiter(s)
+      TYPE(error) :: err                                                    !! Error status of the function
+      LOGICAL                                           :: ok
+      INTEGER                                           :: i,lc,tlc
+      INTEGER                                           :: ndr,ndc,ndd
+      INTEGER                                           :: ir,jc,kd,lu
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE           :: tmp
+      CHARACTER(len=5)                                  :: slc
+      CHARACTER(len=15)                                 :: i2s
+      CHARACTER(len=:), ALLOCATABLE                     :: line,lm1,zdelim
+      CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: wrds
+  
+      zdelim = CHAR(9)//CHAR(32)
+      IF (PRESENT(delimiter)) zdelim = delimiter
+      ! Check file status
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (.NOT.ok) THEN
+        err = error(trim(path)//": no such file",-1) ; RETURN
+      ENDIF
+      lu = free_lun()
+      IF (lu == -1) THEN ; err = error("Cannot find available logical unit...",-1) ; RETURN ; ENDIF
+      ! Open file
+      OPEN(lu,FILE=TRIM(path),STATUS='OLD',ACTION='READ')
+  
+      ! First pass :
+      ! ------------
+      !   - get size (rows, columns, depth)
+      !   - check size consistendcy
+      !   - check value type
+      lc = 0 ; tlc = 0
+      ndr = -1 ; ndc = -1 ; ndd = 1
+      DO WHILE(readline(lu,line))
+        lm1 = line
+        ! Read the line
+        lc = lc + 1 ; WRITE(slc,'(I5)') lc ; slc = ADJUSTL(slc)
+        ! skip comment line
+        IF (INDEX(TRIM(ADJUSTL(line)),"#") == 1) CYCLE
+        ! An empty line: new 2D block
+        IF (LEN_TRIM(line) == 0) THEN
+          ndd = ndd + 1
+          IF (ndr < 0) THEN
+            ndr = tlc
+          ELSEIF (tlc /= ndr) THEN
+            WRITE(i2s,'(I15)') ndd ; i2s = ADJUSTL(i2s)
+            err = error(trim(path)//":Invalid number of lines in block #"//i2s//"(line "//TRIM(slc)//")",-5)
+            RETURN
+          ENDIF
+          tlc = 0
+          CYCLE
+        ENDIF
+        tlc = tlc + 1
+        ! Splits line in words
+        IF (.NOT.tokenize(line,wrds,zdelim,.true.)) THEN
+          ! cannot tokenize
+          err = error(trim(path)//": Cannot parse line "//TRIM(slc),-5)
+          RETURN
+        ELSEIF (.NOT.from_string(wrds,tmp)) THEN
+          ! cannot cast values
+          err = error(trim(path)//": Cannot cast values at line "//TRIM(slc),-5)
+          RETURN
+        ELSEIF (ndc > 0 .AND. ndc /= SIZE(wrds)) THEN
+          ! current number of columns not equal to last one
+          err = error(trim(path)//": Invalid number of columns (line "//TRIM(slc)//")",-5)
+          RETURN
+        ENDIF
+        IF (ndc == -1) ndc = SIZE(wrds)
+        IF (ALLOCATED(wrds)) DEALLOCATE(wrds)
+        IF (ALLOCATED(tmp)) DEALLOCATE(tmp)
+      ENDDO
+  
+      ! NOTE:
+      ! there is a possible bug if data file ends by an empty line:
+      ! we will have an extra empty bloc !
+      !   current patch: we save the last line of the file and check it:
+      !     - if we have empty line, we reduce ndd by one.
+      IF (LEN_TRIM(lm1) == 0) ndd = ndd-1
+  
+      ! Rewind input data file
+      REWIND(lu)
+      ! Allocate memory
+      ALLOCATE(data3d(ndr,ndc,ndd))
+      ir = 0 ; kd = 1 ;
+      DO WHILE(readline(lu,line))
+        IF (INDEX(TRIM(ADJUSTL(line)),"#") == 1) CYCLE
+        ir = ir + 1
+        ! empty line update block subscripts
+        IF (LEN_TRIM(line) == 0) THEN
+          kd = kd + 1 ; ir = 0 ; CYCLE
+        ENDIF
+        ok = tokenize(line,wrds,zdelim,.true.)
+        DO jc = 1,ndc ; ok = from_string(wrds(jc),data3d(ir,jc,kd)) ; ENDDO
+        IF (ALLOCATED(wrds)) DEALLOCATE(wrds)
+      ENDDO
+      CLOSE(lu)
+    END FUNCTION read_data_3d
+  
+    FUNCTION read_data_2d(path,data2d,delimiter) RESULT(err)
+      !! Read an input ASCII data file and stores its content in a 2D array
+      !!
+      !! The function reads an ASCII file and saves its values in a real(kind=8) 2D-array.
+      !!
+      !! The input file:
+      !!
+      !! - can contains any number of empty lines and/or comment line (i.e. line where first
+      !!   non-blank character is "#"). All other lines are assumed to be data.
+      !! - must have a regular number of columns, that is each data line must have the same
+      !!   number of columns.
+      !! - must use blank space(s) as value delimiter.
+      !!
+      !! Error occured when:
+      !!
+      !! - Path does not refer to a existing file (-1)
+      !! - No free logical unit available (-1)
+      !! - The file does not have regular data-columns number (-5)
+      !! - At least a value cannot be cast in double precision (-5)
+      !!
+      !! On error, the 2D output array is __not allocated__.
+      USE SWIFT_FSYSTEM
+      CHARACTER(len=*), INTENT(in)                           :: path      !! Path of the input data file
+      REAL(kind=8), INTENT(out), DIMENSION(:,:), ALLOCATABLE :: data2d    !! 2D-array with the output values (double precision)
+      CHARACTER(len=*), INTENT(in), OPTIONAL                 :: delimiter !! Optional column delimiter(s)
+      TYPE(error) :: err                                                  !! Error status of function.
+      LOGICAL                                           :: ok
+      INTEGER                                           :: i,e,vc,lc
+      INTEGER                                           :: nl,nc,lu
+      REAL(kind=8), DIMENSION(:), ALLOCATABLE           :: tmp
+      CHARACTER(len=5)                                  :: slc
+      CHARACTER(len=:), ALLOCATABLE                     :: line,zdelim
+      CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: wrds
+      zdelim = CHAR(9)//CHAR(32)
+      IF (PRESENT(delimiter)) zdelim = delimiter
+      INQUIRE(FILE=TRIM(path),EXIST=ok)
+      IF (.NOT.ok) THEN
+        err = error(trim(path)//": no such file",-1) ; RETURN
+      ENDIF
+      lu = free_lun()
+      IF (lu == -1) THEN ; err = error("Cannot find available logical unit...",-1) ; RETURN ; ENDIF
+      OPEN(lu,FILE=TRIM(path),STATUS='OLD',ACTION='READ')
+      vc = 0 ; lc=0 ; ok = .true.
+      ! Read the file twice :)
+      lc=0 ; vc = 0 ; nc=-1
+      ! First pass : get number of row values and checks everything !
+      DO
+        ! Read the line
+        IF (.NOT.readline(lu,line)) EXIT
+        lc = lc + 1 ; WRITE(slc,'(I5)') lc ; slc = ADJUSTL(slc)
+        ! skip empty/comment line
+        IF (INDEX(TRIM(ADJUSTL(line)),"#") == 1.OR.LEN_TRIM(line) == 0) CYCLE
+        ! update row counter
+        vc = vc + 1
+        IF (.NOT.tokenize(line,wrds,zdelim,.true.)) THEN
+          ! cannot tokenize
+          err = error(trim(path)//": Cannot parse line "//TRIM(slc),-5)
+          RETURN
+        ELSEIF (.NOT.from_string(wrds,tmp)) THEN
+          ! cannot cast values
+          err = error(trim(path)//": Cannot cast values at line "//TRIM(slc),-5)
+          RETURN
+        ELSEIF (nc > 0 .AND. nc /= SIZE(tmp)) THEN
+          ! current number of columns not equal to last one
+          err = error(trim(path)//": Invalid number of columns (line "//TRIM(slc)//")",-5)
+          RETURN
+        ENDIF
+        IF (nc == -1) nc = SIZE(wrds)
+        IF (ALLOCATED(wrds)) DEALLOCATE(wrds)
+        IF (ALLOCATED(tmp)) DEALLOCATE(tmp)
+      ENDDO
+      ! Rewind input data file
+      REWIND(lu)
+      nl = vc
+      ! allocate memory
+      ALLOCATE(data2d(nl,nc))
+      ! Second pass : saves values :)
+      vc = 0
+      DO WHILE(vc <= nl)
+        ! Reads the line
+        IF (.NOT.readline(lu,line)) EXIT
+        ! Check if we have comment or null string
+        IF (INDEX(TRIM(ADJUSTL(line)),"#") == 1.OR.LEN_TRIM(line) == 0) CYCLE
+        vc = vc + 1
+        ok = tokenize(line,wrds,zdelim,.true.)
+        DO i = 1,nc
+          ok = from_string(wrds(i),data2d(vc,i))
+        ENDDO
+        IF (ALLOCATED(wrds)) DEALLOCATE(wrds)
+      ENDDO
+      CLOSE(lu)
+      RETURN
+    END FUNCTION read_data_2d
+  
+    FUNCTION readline(lun,line) RESULT(not_eof)
+      !! Read a complete line
+      !!
+      !! Each time, it is called, the function reads a complete of the file opened in __lun__
+      !! logical unit and returns .false. if EOF has been reached, .true. otherwise.
+      !!
+      !! The function is intended to read a file line by line:
+      !!
+      !! ```
+      !! lu = 1
+      !! open(lu,file="path/to/the/file/to/read")
+      !! l=0   ! A line counter
+      !! DO WHILE(readline(lu,line))
+      !!   l = l + 1
+      !!   WRITE(*,'("L",I2.2,": ",(a))') il,line
+      !! ENDDO
+      !! CLOSE(1)
+      !! ```
+      INTEGER, INTENT(in)                        :: lun  !! Logical unit with the opened file to read.
+      CHARACTER(len=:), ALLOCATABLE, INTENT(out) :: line !! Processed line
+      LOGICAL  :: not_eof                                !! .true. if EOF has NOT been reached yet, .false. otherwise
+      CHARACTER(len=50) :: buf
+      INTEGER           :: e,sz
+      not_eof = .true. ; line = ''
+      DO
+        READ(lun,'(a)',ADVANCE="no",SIZE=sz,IOSTAT=e) buf
+        IF (e == IOSTAT_END) THEN
+          not_eof = .false.
+          IF (sz > 0) line=line//buf(1:sz)
+          EXIT
+        ELSE IF (e == IOSTAT_EOR) THEN
+          line = line//buf(1:sz)
+          EXIT
+        ELSE
+          line = line//buf
+        ENDIF
+      ENDDO
+    END FUNCTION readline
+  
+  END MODULE SWIFT_ASCIIREAD  
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_cfgparse.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_cfgparse.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_cfgparse.F90	(revision 3560)
@@ -0,0 +1,2112 @@
+! Copyright (c) Jeremie Burgalat (2013-2022)
+! Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+!
+! This file is part of SWIFT
+!
+! Permission is hereby granted, free of charge, to any person obtaining a copy of
+! this software and associated documentation files (the "Software"), to deal in
+! the Software without restriction, including without limitation the rights to
+! use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+! the Software, and to permit persons to whom the Software is furnished to do so,
+! subject to the following conditions:
+!
+! The above copyright notice and this permission notice shall be included in all
+! copies or substantial portions of the Software.
+!
+! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+! FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+! COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+!! File:    swift_cfgparse.F90
+!! Summary: Configuration file parser source file.
+!! Author:  J. Burgalat
+!! Date:    2013-2022
+
+#include "swift_defined.h"
+
+MODULE SWIFT_CFGPARSE
+  !! Configuration file parsing module
+  !!
+  !! This module defines a set of derived types as well as methods to parse configuration files.
+  !!
+  !! If you only wish to have an overview of cfgparse usage, you'd better go
+  !! [here](|url|/page/swift/p02_cfgparse.html).
+  !! @todo
+  !! Add interpolation from environment and/or parser options.
+  USE, INTRINSIC :: ISO_FORTRAN_ENV
+  USE SWIFT_ERRORS
+  USE SWIFT_STRING_OP
+  USE SWIFT_FSYSTEM
+  IMPLICIT NONE
+
+  PRIVATE
+
+  PUBLIC :: cfg_clear, cfg_read_config, cfg_write_config, &
+            cfg_get_value, cfg_set_value, cfg_count, cfg_check_name, &
+            cfg_has_option, cfg_has_section, &
+            cfg_option_names,cfg_section_names, &
+            cfg_remove_option, cfg_remove_section, &
+            cfg_sort_options
+
+  PUBLIC :: noerror,error, error_to_string, aborting
+
+  ! some public definitions from other modules
+  ! from strings
+  PUBLIC :: to_lower,st_slen, st_llen
+
+  PUBLIC :: OPERATOR(==), OPERATOR(/=), ASSIGNMENT(=)
+
+  TYPE, PUBLIC :: option
+    !! Define an option
+    CHARACTER(len=st_slen), PRIVATE :: name = ""       !! Name of the option
+    CHARACTER(len=st_slen), PRIVATE :: section = ""    !! Associated section name.
+    TYPE(words), PRIVATE            :: values          !! Values of the option
+  END TYPE option
+
+  TYPE, PUBLIC :: cfgparser
+    !! Define a parser of options
+    !!
+    !! A [[cfgparser(type)]] stores [[option(type)]] objects.
+    TYPE(option), DIMENSION(:), ALLOCATABLE  :: options !! list of options.
+#if HAVE_FTNPROC
+    CONTAINS
+    PROCEDURE, PRIVATE :: cp_get_rv_sc
+    PROCEDURE, PRIVATE :: cp_get_dv_sc
+    PROCEDURE, PRIVATE :: cp_get_iv_sc
+    PROCEDURE, PRIVATE :: cp_get_lv_sc
+    PROCEDURE, PRIVATE :: cp_get_cv_sc
+    PROCEDURE, PRIVATE :: cp_get_sv_sc
+    PROCEDURE, PRIVATE :: cp_get_rv_ve
+    PROCEDURE, PRIVATE :: cp_get_dv_ve
+    PROCEDURE, PRIVATE :: cp_get_iv_ve
+    PROCEDURE, PRIVATE :: cp_get_lv_ve
+    PROCEDURE, PRIVATE :: cp_get_cv_ve
+    PROCEDURE, PRIVATE :: cp_get_sv_ve
+    PROCEDURE, PRIVATE :: cp_set_rv_sc
+    PROCEDURE, PRIVATE :: cp_set_dv_sc
+    PROCEDURE, PRIVATE :: cp_set_iv_sc
+    PROCEDURE, PRIVATE :: cp_set_lv_sc
+    PROCEDURE, PRIVATE :: cp_set_cv_sc
+    PROCEDURE, PRIVATE :: cp_set_sv_sc
+    PROCEDURE, PRIVATE :: cp_set_rv_ve
+    PROCEDURE, PRIVATE :: cp_set_dv_ve
+    PROCEDURE, PRIVATE :: cp_set_iv_ve
+    PROCEDURE, PRIVATE :: cp_set_lv_ve
+    PROCEDURE, PRIVATE :: cp_set_cv_ve
+    PROCEDURE, PRIVATE :: cp_set_sv_ve
+    !> Read configuration file
+    PROCEDURE, PUBLIC  :: read_config => cfg_read_config
+    !> Write configuration file
+    PROCEDURE, PUBLIC  :: write_config => cfg_write_config
+    !> Get the number of sections in the parser
+    PROCEDURE, PUBLIC  :: count => cfg_count
+    !> Clean the parser (delete all options, free memory)
+    PROCEDURE, PUBLIC  :: clear => cfg_clear
+    !> Get the names of the user-defined sections in the parser
+    PROCEDURE, PUBLIC  :: section_names => cfg_section_names
+    !> Get the list of options names
+    PROCEDURE, PUBLIC  :: option_names => cfg_option_names
+    !> Check if parser has option by name
+    PROCEDURE, PUBLIC  :: has_option => cfg_has_option
+    !> Check if parser has section by name
+    PROCEDURE, PUBLIC  :: has_section => cfg_has_section
+    !> Remove an option from the parser.
+    PROCEDURE, PUBLIC :: remove_option  => cfg_remove_option
+    !> Remove a section (and all the associated options) from the parser.
+    PROCEDURE, PUBLIC :: remove_section => cfg_remove_section
+    !> Get value(s) of an option in the parser by name
+    !!
+    !! ```
+    !! FUNCTION cfg_get_value(this,name,output) RESULT(error)
+    !! ```
+    !!
+    !! The method attempts to get the value(s) of an option that matches _name_ in _this_ parser.
+    !!
+    !! On error, __output__ argument is undefined (that is, left unchanged
+    !! for scalar versions, **unallocated** for vector version).
+    !!
+    !! Errors occur in the following situations:
+    !! - The option has no value (-6)
+    !! - The option does not exist (-7)
+    !! - The option's value cannot be cast in the desired type (-10)
+    GENERIC, PUBLIC :: get_value     => cp_get_rv_sc,cp_get_dv_sc,cp_get_iv_sc, &
+                                        cp_get_lv_sc,cp_get_cv_sc,cp_get_sv_sc, &
+                                        cp_get_rv_ve,cp_get_dv_ve,cp_get_iv_ve, &
+                                        cp_get_lv_ve,cp_get_cv_ve,cp_get_sv_ve
+    !> Set value(s) of an option in the parser by name
+    !!
+    !! ```
+    !! FUNCTION cfg_set_value(this,name,input,create) RESULT(error)
+    !! ```
+    !!
+    !! The method searches for the option matching the given _name_ in _this_ parser and sets new
+    !! values.
+    !!
+    !! If _create_ is set to .true. (default to .false.) the method creates the option if does not
+    !! exist in _this_ parser.
+    !! @warning
+    !! In such case, if the given is not valid, an assertion is raised !
+    !!
+    !! On error (i.e. no option matches the given _name_), no values are set.
+    GENERIC, PUBLIC :: set_value     => cp_set_rv_sc,cp_set_dv_sc,cp_set_iv_sc, &
+                                        cp_set_lv_sc,cp_set_cv_sc,cp_set_sv_sc, &
+                                        cp_set_rv_ve,cp_set_dv_ve,cp_set_iv_ve, &
+                                        cp_set_lv_ve,cp_set_cv_ve,cp_set_sv_ve
+#endif
+
+  END TYPE cfgparser
+
+  !> Get value(s) of an option in the parser by name.
+  !!
+  !! ```
+  !! FUNCTION cfg_get_value(parser,name,output) RESULT(error)
+  !! ```
+  !!
+  !! The method attempts to get the value(s) of an option that matches _name_ in _this_ parser.
+  !!
+  !! On error, __output__ argument is undefined (that is, left unchanged
+  !! for scalar versions, **unallocated** for vector version).
+  !!
+  !! Errors occur in the following situations:
+  !! - The option has no value (-6)
+  !! - The option does not exist (-7)
+  !! - The option's value cannot be cast in the desired type (-10)
+  INTERFACE cfg_get_value
+    MODULE PROCEDURE cp_get_rv_sc,cp_get_dv_sc,cp_get_iv_sc, &
+                     cp_get_lv_sc,cp_get_cv_sc,cp_get_sv_sc, &
+                     cp_get_rv_ve,cp_get_dv_ve,cp_get_iv_ve, &
+                     cp_get_lv_ve,cp_get_cv_ve,cp_get_sv_ve
+  END INTERFACE
+
+    !> Set value(s) of an option in the parser by name
+    !!
+    !! ```
+    !! FUNCTION set_value(this,name,input,create) RESULT(error)
+    !! ```
+    !!
+    !! The method searches for the option matching the given _name_ in _this_ parser  and sets new
+    !! values.
+    !!
+    !! If _create_ is set to .true. (default to .false.) the method quietly create the option if does not
+    !! exist in _this_ parser.
+    !! @warning
+    !! In such case, if the given __name__ is not valid, an error is raised !
+    !!
+    !! On error (i.e. no option matches the given _name_), no values are set.
+    INTERFACE cfg_set_value
+      MODULE PROCEDURE :: cp_set_rv_sc,cp_set_dv_sc,cp_set_iv_sc, &
+                          cp_set_lv_sc,cp_set_cv_sc,cp_set_sv_sc, &
+                          cp_set_rv_ve,cp_set_dv_ve,cp_set_iv_ve, &
+                          cp_set_lv_ve,cp_set_cv_ve,cp_set_sv_ve
+    END INTERFACE
+
+    !> Derived type assignment operator
+    !!
+    !! This interface defines the assignment operator for the containers defined in the module.
+    INTERFACE ASSIGNMENT(=)
+      MODULE PROCEDURE cp_affect_sc, op_affect_sc
+    END INTERFACE
+
+  CONTAINS
+
+  SUBROUTINE op_affect_sc(this,other)
+    !! Option object assignment operator subroutine
+    !!
+    !! The subroutine assigns __other__ to __this__.
+    TYPE(option), INTENT(inout) :: this  !! An option object to be assigned
+    TYPE(option), INTENT(in)    :: other !! An option object to assign
+    CALL words_clear(this%values) ! normally not needed
+    this%name = other%name
+    this%section = other%section
+    this%values = other%values
+  END SUBROUTINE op_affect_sc
+
+  FUNCTION op_valid(opt) RESULT(ok)
+    !! Check whether or not the option is valid (i.e. has name)
+    TYPE(option), INTENT(in)      :: opt  !! An option object
+    LOGICAL :: ok !! True if the option is valid, false otherwise.
+    ok = LEN_TRIM(opt%name) > 0
+  END FUNCTION op_valid
+
+  SUBROUTINE op_clear(opt)
+    !! Clear and invalid the given option.
+    TYPE(option), INTENT(inout)      :: opt  !! An option object
+    opt%name = ''
+    opt%section = ''
+    CALL words_clear(opt%values)
+  END SUBROUTINE op_clear
+
+  FUNCTION op_full_name(opt) RESULT(name)
+    !! Get the full name of an option.
+    !!
+    !! @note
+    !! If no section is defined in the option (that should not happen), "__default__" is used
+    !! as the section part of the full name.
+    TYPE(option), INTENT(in)      :: opt  !! An option object
+    CHARACTER(len=:), ALLOCATABLE :: name !! The fullname of the option
+    IF (LEN_TRIM(opt%section) == 0) THEN
+      name = "__default__/"//TRIM(opt%name)
+    ELSE
+      name = TRIM(opt%section)//"/"//TRIM(opt%name)
+    ENDIF
+  END FUNCTION op_full_name
+
+  FUNCTION op_split_name(fname,sname,pname) RESULT(err)
+    !> Split a full name in section and option names
+    !!
+    !! The method splits a full name into (section,option) names:
+    !!
+    !! - Option basename is always set in lower-case.
+    !! - If any, section name case is left unmodified.
+    !!
+    !! A full name simply consists in a section name and an option name separated by a single "/".
+    !!
+    !! The method never checks the validity of the output names. Consider using [[cfg_check_name(function)]]
+    !! to do so.
+    !! @note
+    !! If _fname_ does not contains any "/", the method sets the special name "\_\_default\_\_" for the output
+    !! section name.
+    !! @note
+    !! On failure, output arguments are set to empty strings.
+    !! @warning
+    !! If _fname_ ends with a "/", an error (-9, invalid name) is raised: the method always assumes it can
+    !! find an option part in the name.
+    CHARACTER(len=*), INTENT(in)               :: fname    !! A name to split
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: sname, & !! Section part of the name
+                                                  pname    !! Option part of the name
+    TYPE(error)                                :: err      !! Error status of the method
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: tfname
+    err = noerror ; pname = "" ; sname = ""
+    tfname = op_format(fname,sname,pname)
+    IF (LEN_TRIM(tfname) == 0) err = error("Invalid option name ("//TRIM(fname)//")",-9)
+  END FUNCTION op_split_name
+
+  FUNCTION op_format(name,sname,pname) RESULT(oname)
+    !! Format the input name to be consistent with character case requirements.
+    !!
+    !! Given a **name**, the method tries to split in section/option names.
+    !! Then it converts the option part in lower-case.
+    !!
+    !! If no section part is found (not '/' or set as first character of **name**), the
+    !! special section name `__default__` is set.
+    !!
+    !! If **name** ends with a '/', it is an error and the method returns an empty string.
+    CHARACTER(len=*), INTENT(in)  :: name  !! Name to format.
+    CHARACTER(len=:), ALLOCATABLE, INTENT(out), OPTIONAL :: sname !! Section part of the name (optional output)
+    CHARACTER(len=:), ALLOCATABLE, INTENT(out), OPTIONAL :: pname !! Option part of the name (optional output)
+    CHARACTER(len=:), ALLOCATABLE :: oname                        !! Formatted full option name.
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: zsname,zpname
+    zpname = "" ; zsname = ""
+    ! splits input name in sname, pname
+    idx = INDEX(name,'/')
+    IF (idx == LEN_TRIM(name)) THEN
+      oname = ''
+      IF (PRESENT(sname)) sname = ''
+      IF (PRESENT(pname)) pname = ''
+      RETURN
+    ELSE IF (idx <= 1) THEN
+      zsname = "__default__" ; zpname = to_lower(TRIM(name))
+      IF (idx == 1) zpname=zpname(2:)
+    ELSE
+      zsname = name(:idx-1)
+      zpname = to_lower(name(idx+1:LEN_TRIM(name)))
+    ENDIF
+    oname = zsname//"/"//zpname
+    IF (PRESENT(sname)) sname = zsname
+    IF (PRESENT(pname)) pname = zpname
+  END FUNCTION op_format
+
+  FUNCTION op_greater_than(left,right) RESULT(ret)
+    !> greater than operator for option derived type.
+    !!
+    !! the comparison is made on section and option name (in alphabetical order).
+    TYPE(option), INTENT(in) :: left  !! LHS option.
+    TYPE(option), INTENT(in) :: right !! RHS option.
+    LOGICAL :: ret
+      !! .true. if LHS is _greater_ than RHS (based on section and option name)
+    ret = LGT(op_full_name(left),op_full_name(right))
+  END FUNCTION op_greater_than
+
+  FUNCTION op_less_than(left,right) RESULT(ret)
+    !> Less than operator for option derived type.
+    !!
+    !! the comparison is made on section and option name (in alphabetical order).
+    TYPE(option), INTENT(in) :: left  !! LHS option.
+    TYPE(option), INTENT(in) :: right !! RHS option.
+    LOGICAL :: ret
+      !! .true. if LHS is _less_ than RHS (based on section and option name)
+    ret = LLT(op_full_name(left),op_full_name(right))
+  END FUNCTION op_less_than
+
+  FUNCTION op_to_str(opt,num_values) RESULT(str)
+    !! Get the string representation of a option object
+    !! @note
+    !! If the object is not valid an empty string is returned.
+    TYPE(option), INTENT(in) :: opt
+      !! A option object
+    INTEGER, INTENT(in), OPTIONAL :: num_values
+      !! Optional integer with the number of values to print per line
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! An allocated string with the representation of the option object
+    LOGICAL                                           :: ret
+    INTEGER                                           :: nv,np,i
+    CHARACTER(len=:), ALLOCATABLE                     :: nspcs
+    CHARACTER(len=st_slen), ALLOCATABLE, DIMENSION(:) :: vec
+    nv = 8 ; IF (PRESENT(num_values)) nv = MAX(1,num_values)
+    str = ""
+    str = TRIM(opt%name)//" = " ; np = LEN(str)
+    ALLOCATE(CHARACTER(len=np) :: nspcs) ; nspcs(1:) = " "
+    ! stores the error but do not check...
+    ret = words_to_vector(opt%values,vec)
+    IF (.NOT.ALLOCATED(vec)) RETURN
+    DO i=1,SIZE(vec)
+      IF (string_is(TRIM(vec(i))) == st_string.AND.TRIM(vec(i))/="NULL") THEN
+        str = str//'"'//remove_quotes(TRIM(vec(i)))//'",'
+      ELSE
+        str = str//TRIM(vec(i))//','
+      ENDIF
+      IF (MOD(i,nv) == 0) THEN
+        str = str//NEW_LINE('A')//nspcs
+      ELSE
+        str = str//CHAR(32)
+      ENDIF
+    ENDDO
+    str = TRIM(str)
+    IF (str(LEN(str):) == ",") str=str(:LEN(str)-1)
+  END FUNCTION op_to_str
+
+  !-------------------------------
+  ! DERIVED TYPE cfgparser METHODS
+  !-------------------------------
+
+  SUBROUTINE cfg_clear(parser)
+    !! Clear the cfgparser object ("destructor")
+    !!
+    !! This subroutine clears the given parser (deallocates memory).
+    OBJECT(cfgparser), INTENT(inout) :: parser !! A cfgparser object to clear
+    INTEGER :: i
+    IF (ALLOCATED(parser%options)) THEN
+      DO i = 1, SIZE(parser%options)
+        CALL op_clear(parser%options(i))
+      ENDDO
+      DEALLOCATE(parser%options)
+    ENDIF
+  END SUBROUTINE cfg_clear
+
+
+  FUNCTION cfg_check_name(name) RESULT(valid)
+    !! Check if a name is valid.
+    !!
+    !! If **name** contains a '/' it is assumed to be a full option name. In such case
+    !! both parts of the name are checked against section/option names requirements (see below).
+    !!
+    !! Otherwise it is assumed to be the basename of the option.
+    !!
+    !! A valid option (base) name is an alphanumeric sequence in lower-case that always begin by
+    !! a letter.
+    !!
+    !! A valid section name is and alphanumeric sequence (in any case) that always begins by
+    !! by a letter.
+    CHARACTER(len=*), INTENT(in) :: name !! A string with the name to check.
+    LOGICAL :: valid                     !! .true. if the name is valid, .false. otherwise
+    INTEGER                       :: i
+    CHARACTER(len=26), PARAMETER  :: alpha  = "abcdefghijklmnopqrstuvwxyz"
+    CHARACTER(len=26), PARAMETER  :: ualpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    CHARACTER(len=12), PARAMETER  :: num    = "0123456789_"
+    CHARACTER(len=:), ALLOCATABLE :: pname,sname
+    TYPE(error)                   :: err
+    valid = .false.
+    i = INDEX(TRIM(name),"/")
+    IF (i /= 0) THEN
+      err = op_split_name(name,sname,pname)
+      IF (err /= 0) THEN
+        RETURN
+      ENDIF
+    ELSE
+      pname = to_lower(TRIM(name))
+      sname = "__default__"
+    ENDIF
+    ! check option:
+    i = INDEX(pname,CHAR(32))
+    IF (i /= 0.OR.LEN_TRIM(pname) <= 0) RETURN
+    valid = (VERIFY(pname(1:1),alpha) == 0 .AND.VERIFY(TRIM(pname),alpha//num) == 0)
+    IF (.NOT.valid) RETURN
+    ! check section
+    IF (sname == "__default__") THEN
+      valid = .true.
+      RETURN
+    ELSE
+      i = INDEX(sname,CHAR(32))
+      IF (i /= 0.OR.LEN_TRIM(sname) <= 0) RETURN
+      valid = (VERIFY(sname(1:1),ualpha//alpha) == 0 .AND.VERIFY(TRIM(sname),ualpha//alpha//num) == 0)
+    ENDIF
+  END FUNCTION cfg_check_name
+
+  FUNCTION cfg_count(this,section) RESULT(num)
+    !! Get the total number of option in the parser.
+    !!
+    !! If a section name is given in argument, the method returns the count for the given section only.
+    !!
+    !! To get the number of top-level options (i.e. that belongs to the default section) the keyword \_\_default\_\_
+    !! should be set for the section argument.
+    !!
+    !! If __section__ is not defined in the parser, the method returns 0.
+    !!
+    !! @internal
+    !! If no options are defined, then it implies that the internal vector of options is
+    !! not allocated.
+    OBJECT(cfgparser), INTENT(in)          :: this    !! A cfgparser object to search in
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: section !! Optional section name to search in.
+    INTEGER :: num                                    !! Number of current options registered in the parser.
+    INTEGER :: i
+    num = 0
+    IF(.NOT.ALLOCATED(this%options)) RETURN
+    IF (.NOT.PRESENT(section)) THEN
+      num = SIZE(this%options)
+    ELSE
+      DO i=1, SIZE(this%options)
+        IF (this%options(i)%section == section) num = num+1
+      ENDDO
+    ENDIF
+  END FUNCTION cfg_count
+
+  FUNCTION cfg_section_names(this) RESULT(list)
+    !! Get the list of user-defined section names
+    !! @note
+    !! If the parser does not have user-defined sections, the vector is still
+    !! allocated but with 0 elements.
+    OBJECT(cfgparser), INTENT(in)                     :: this !! A cfgparser object to process.
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: list !! List of section names.
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tmp
+    INTEGER :: i,j,k,no
+    LOGICAL :: found
+    no = cfg_count(this)
+    IF (no == 0) THEN
+      ALLOCATE(list(0))
+      RETURN
+    ENDIF
+    ALLOCATE(tmp(no))
+    tmp(1) = ''
+    ! get the first non-default section
+    DO i=1,no
+      IF (this%options(i)%section /= "__default__") THEN
+        tmp(1) = this%options(i)%section
+        EXIT
+      ENDIF
+    ENDDO
+    ! no user defined section
+    IF (LEN_TRIM(tmp(1)) == 0) THEN
+      DEALLOCATE(tmp)
+      ALLOCATE(list(0))
+      RETURN
+    ENDIF
+    k = 1
+    DO i=1,no
+      found = .false.
+      DO j=1,k
+        ! Found a match so start looking again
+        found = (tmp(j) == this%options(i)%section .OR. &
+                 this%options(i)%section == "__default__")
+        IF (found) EXIT
+      ENDDO
+      IF (.NOT.found) THEN
+        k = k + 1
+        tmp(k) = this%options(i)%section
+      ENDIF
+    ENDDO
+    ALLOCATE(list(k))
+    list(1:k) = tmp(1:k)
+    DEALLOCATE(tmp)
+  END FUNCTION cfg_section_names
+
+  FUNCTION cfg_option_names(this,secname) RESULT(list)
+    !! Get the list of option names.
+    !!
+    !! @note
+    !! If the parser does not have options, the vector is still allocated but with 0 elements.
+    OBJECT(cfgparser), INTENT(in)                     :: this    !! A cfgparser object to process.
+    CHARACTER(len=*), INTENT(in), OPTIONAL            :: secname !! Optional section name to search in.
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: list    !! List of option names.
+    INTEGER               :: j,i,no,nso
+    no = cfg_count(this)
+    IF (no == 0) THEN
+       ALLOCATE(list(no)) ; RETURN
+    ENDIF
+    IF (PRESENT(secname)) THEN
+      IF (.NOT.cfg_has_section(this,TRIM(secname))) THEN
+        ALLOCATE(list(no)) ; RETURN
+      ELSE
+        nso = 0
+        DO i=1,no ; IF (this%options(i)%section == TRIM(secname)) nso = nso + 1 ; ENDDO
+        ALLOCATE(list(nso))
+        IF (nso == 0) RETURN
+        j = 1
+        DO i=1,no
+          IF (this%options(i)%section == TRIM(secname)) THEN
+            list(j) = TRIM(this%options(i)%section)//"/"//TRIM(this%options(i)%name) ; j=j+1
+          ENDIF
+        ENDDO
+      ENDIF
+    ELSE
+      ALLOCATE(list(no))
+      DO i=1,no
+        IF (this%options(i)%section == "__default__") THEN
+          list(i) = TRIM(this%options(i)%name)
+        ELSE
+          list(i) = TRIM(this%options(i)%section)//"/"//TRIM(this%options(i)%name)
+        ENDIF
+      ENDDO
+    ENDIF
+  END FUNCTION cfg_option_names
+
+  FUNCTION cfg_has_section(this,name) RESULT(yes)
+    !! Check if parser has section by name
+    !!
+    !! @note
+    !! Keep in mind that section name in the configuration are case-sensitive.
+    OBJECT(cfgparser), INTENT(in) :: this !! cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name !! Name of the section to search
+    LOGICAL :: yes                        !! .true. if the section exists, .false. otherwise
+    INTEGER                       :: i,no
+    yes = .false.
+    no = cfg_count(this)
+    IF (no == 0) RETURN
+    DO i = 1,no
+      IF (this%options(i)%section == name) THEN
+        yes = .true.
+        RETURN
+      ENDIF
+    ENDDO
+  END FUNCTION cfg_has_section
+
+  FUNCTION cfg_has_option(this,name) RESULT(yes)
+    !! Check if parser has option by name
+    OBJECT(cfgparser), INTENT(in) :: this !! A cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name !! (extended) Name of the option to search
+    LOGICAL :: yes                        !! .true. if the option is found, .false. otherwise
+    CHARACTER(len=:), ALLOCATABLE :: pname,zname
+    INTEGER                       :: i,no,iscan
+    yes = .false.
+    no = cfg_count(this)
+    IF (no == 0) RETURN
+    zname = op_format(name)
+    IF (LEN_TRIM(zname) == 0) RETURN
+    DO i = 1,no
+      pname = op_full_name(this%options(i))
+      IF (pname == zname) THEN
+        yes = .true.
+        RETURN
+      ENDIF
+    ENDDO
+  END FUNCTION cfg_has_option
+
+  SUBROUTINE cfg_sort_options(this)
+    !! Sort the options in the parser (alphabetiCALLy).
+    OBJECT(cfgparser), INTENT(inout) :: this !! A cfgparser object
+    INTEGER :: no
+    no = cfg_count(this)
+    IF (no == 0) RETURN
+    CALL insertionSort(this%options)
+  END SUBROUTINE cfg_sort_options
+
+  SUBROUTINE cfg_remove_option(this,name)
+    !! Remove an option from parser by name.
+    OBJECT(cfgparser), INTENT(inout) :: this !! A cfgparser object to search in
+    CHARACTER(len=*), INTENT(in)     :: name !! The name of the option to remove
+    INTEGER                                 :: no,idx,i,j
+    TYPE(option), DIMENSION(:), ALLOCATABLE :: tmp
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) RETURN
+    no = cfg_count(this)
+    ! only one opt
+    IF (no == 1) THEN
+      CALL op_clear(this%options(1))
+      DEALLOCATE(this%options)
+      RETURN
+    ENDIF
+    ALLOCATE(tmp(no-1))
+    j = 1
+    DO i=1,no
+      IF (i /= idx) THEN
+        tmp(j) = this%options(i)
+        j= j+1
+      ENDIF
+      CALL op_clear(this%options(i))
+    ENDDO
+    DEALLOCATE(this%options)
+    ALLOCATE(this%options(no-1))
+    DO i=1,no-1
+      this%options(i) = tmp(i)
+      CALL op_clear(tmp(i))
+    ENDDO
+    DEALLOCATE(tmp)
+  END SUBROUTINE cfg_remove_option
+
+  SUBROUTINE cfg_remove_section(this,name)
+    !! Remove a section from parser by name.
+    !!
+    !! The method removes all the options that belong to the given section name.
+    OBJECT(cfgparser), INTENT(inout) :: this
+      !! A cfgparser object to search in
+    CHARACTER(len=*), INTENT(in)     :: name
+      !! The name of the section to remove
+    INTEGER                                 :: no,i,j,icount
+    INTEGER, DIMENSION(:), ALLOCATABLE      :: idxs,itmp
+    TYPE(option), DIMENSION(:), ALLOCATABLE :: tmp
+    no = cfg_count(this)
+    IF (no == 0) RETURN
+    ALLOCATE(itmp(no))
+    itmp(:) = -1
+    icount = 0
+    DO i=1,no
+      IF (TRIM(this%options(i)%section) == TRIM(name)) THEN
+        itmp(icount+1) = i
+        icount = icount + 1
+      ENDIF
+    ENDDO
+    IF (icount == 0) RETURN
+    ALLOCATE(idxs(icount))
+    idxs(:) = itmp(1:icount)
+    !DEALLOCATE(itmp)
+    ! all options matches (should be rarely the case): remove all
+    IF (icount == no) THEN
+       DO i=1,no ; CALL op_clear(this%options(i)); ENDDO
+       DEALLOCATE(this%options)
+       RETURN
+    ENDIF
+    ALLOCATE(tmp(icount))
+    j = 1
+    DO i=1,no
+      IF (ANY(idxs == i)) THEN
+        tmp(j) = this%options(i)
+        j = j + 1
+      ENDIF
+      CALL op_clear(this%options(i))
+    ENDDO
+    DEALLOCATE(idxs)
+    DEALLOCATE(this%options)
+    ALLOCATE(this%options(icount))
+    DO i=1,icount
+      this%options(i) = tmp(i)
+      CALL op_clear(tmp(i))
+    ENDDO
+    DEALLOCATE(tmp)
+  END SUBROUTINE cfg_remove_section
+
+  FUNCTION cfg_read_config(parser,path,override) RESULT(err)
+    !! Read configuration file
+    !!
+    !! @note
+    !! If the library support C bindings, the method can read included files which are defined
+    !! by the __#include <...>__ directive (see [p_cfgparse](here) from more details).
+    OBJECT(cfgparser), INTENT(inout) :: parser
+      !! A cfgparser object that will store the configuration
+    CHARACTER(len=*), INTENT(in)     :: path
+      !! Path of the configuration file
+    LOGICAL, INTENT(in), OPTIONAL    :: override
+      !! An optional boolean flag with .true. to override previous value of duplicated options instead of raising an error.
+    TYPE(error) :: err
+      !! An error with the first error encountered
+    INTEGER                       :: i
+    LOGICAL                       :: zoverride,ok
+    TYPE(words)                   :: incfiles
+    CHARACTER(len=:), ALLOCATABLE :: name
+    CHARACTER(len=st_slen)        :: isec
+    err = noerror
+    zoverride = .false. ; IF (PRESENT(override)) zoverride = override
+    isec = "__default__"
+    name = TRIM(path)
+    i = INDEX(name,'/',.true.) ; IF (i /= 0) name = name(i+1:)
+    IF (i == 0) THEN
+      name = fs_realpath("./"//path)
+    ELSE
+      name = fs_realpath(path)
+    ENDIF
+    CALL words_append(incfiles,name)
+    INQUIRE(FILE=TRIM(path), EXIST=ok)
+    IF (.NOT.ok) THEN
+      err = error(TRIM(path)//": no such file",-11)
+    ELSE
+      err = read_include(parser,TRIM(path),isec,incfiles,zoverride)
+    ENDIF
+    call words_clear(incfiles)
+  END FUNCTION cfg_read_config
+
+  FUNCTION cfg_write_config(this,lu,num_values) RESULT(err)
+    !> Write the content of the parser in the logical unit.
+    !!
+    !! the method expects the logical unit to be opened and does not close it
+    !! at the end of the process.
+    OBJECT(cfgparser), INTENT(inout) :: this
+      !! Parser to write.
+    INTEGER, INTENT(in) :: lu
+      !! Logical unit. It should be already opened or an error is raised.
+    INTEGER, INTENT(in), OPTIONAL :: num_values
+      !! Optional integer with the number of values to print per line for options (default to 8).
+    TYPE(error) :: err
+      !! Error status
+    CHARACTER(len=st_slen) :: sname
+    LOGICAL                :: ok
+    INTEGER                :: nv,no,i
+    err = noerror
+    INQUIRE(UNIT=lu,OPENED=ok)
+    IF (.NOT.ok) THEN
+      err = error("Logical unit not opened",-15)
+      RETURN
+    ENDIF
+    no = cfg_count(this)
+    IF (no == 0) THEN
+      err = error("No options to write",-7)
+      RETURN
+    ENDIF
+    ! sort options.
+    CALL cfg_sort_options(this)
+    nv = 8 ; IF (PRESENT(num_values)) nv = MAX(1,num_values)
+    sname = this%options(1)%section
+    IF (sname /= "__default__") &
+      WRITE(lu,'(a)') "[ "//TRIM(sname)//" ]"
+    DO i=1,no
+      IF (this%options(i)%section /= sname) THEN
+        sname = this%options(i)%section
+        ! write section first
+        WRITE(lu,*)
+        WRITE(lu,'(a)') "[ "//TRIM(sname)//" ]"
+      ENDIF
+      WRITE(lu,'(a)') op_to_str(this%options(i),nv)
+    ENDDO
+  END FUNCTION cfg_write_config
+
+  ! internal (private methods)
+
+  SUBROUTINE cp_affect_sc(this,other)
+    !! cfgparser object assignment operator subroutine
+    !!
+    !! The subroutine assigns __other__ to __this__.
+    TYPE(cfgparser), INTENT(inout) :: this  !! A cfgparser object to be assigned
+    TYPE(cfgparser), INTENT(in)    :: other !! A cfgparser object to assign
+    INTEGER :: i,ono
+    CALL cfg_clear(this)
+    ono = cfg_count(other)
+    IF (ono == 0) RETURN
+    ALLOCATE(this%options(ono))
+    DO i=1,ono
+      this%options(i) = other%options(i)
+    ENDDO
+    RETURN
+  END SUBROUTINE cp_affect_sc
+
+  FUNCTION cp_get_opt_idx(this,name) RESULT(idx)
+    !! Get the index of an option by name in the parser.
+    !!
+    !! The method searches in the parser for the option with the given (full) __name__.
+    !! If found, it returns the index of the option in the internal vector of options. Otherwise
+    !! -1 is returned.
+    OBJECT(cfgparser), INTENT(in) :: this !! A cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name !! A string with the name of the option
+    INTEGER :: idx                        !! Index of the option (-1 if not found).
+    CHARACTER(len=:), ALLOCATABLE :: zname,pname
+    INTEGER                       :: no,i
+    idx = -1
+    no = cfg_count(this)
+    IF (no == 0) RETURN
+    zname = op_format(name) ! prepare the name to search.
+    IF (LEN_TRIM(zname) == 0) RETURN
+    DO i=1,no
+      pname = op_full_name(this%options(i))
+      IF (pname == zname) THEN
+        idx = i
+        RETURN
+      ENDIF
+    ENDDO
+  END FUNCTION cp_get_opt_idx
+
+  FUNCTION cp_update_opt(this,sname,pname,values) RESULT(err)
+    !! Update an option in the parser.
+    !!
+    !! The method attempts to update the option in the parser.
+    !!
+    !! If __sname__ is set to empty string, the method searches for the option
+    !! in the default section.
+    !!
+    !! If no option is found, The the option is appended in the parser. Otherwise it is updated
+    !! with the content of __values__.
+    !!
+    !! If the option name is not valid, the method does nothing and -9 error status is returned.
+    !!
+    !! @internal
+    !! The method performs the same kind of operations than the setters except that it
+    !! expects raw data ([[string_op(module):words(type)]]).
+    OBJECT(cfgparser), INTENT(inout) :: this   !! cfgparser object to process.
+    CHARACTER(len=*), INTENT(in)     :: sname  !! Name of the section.
+    CHARACTER(len=*), INTENT(in)     :: pname  !! Basename of the option.
+    TYPE(words), INTENT(in)          :: values !! Raw values.
+    TYPE(error)                      :: err    !! Error status.
+    CHARACTER(len=:), ALLOCATABLE :: zsname,fname
+    INTEGER                       :: i
+    err = noerror
+    zsname = TRIM(sname)
+    IF (LEN_TRIM(sname) == 0) zsname = "__default__"
+    fname = zsname//"/"//to_lower(TRIM(pname))
+    IF (.NOT.cfg_check_name(fname)) THEN
+       err = error("Invalid option (no name)",-9)
+       RETURN
+    ENDIF
+    i = cp_get_opt_idx(this,fname)
+    IF (i /= -1) THEN
+      CALL words_clear(this%options(i)%values)
+      this%options(i)%values = values
+    ELSE
+      err = cp_add_opt(this,zsname,pname,values)
+    ENDIF
+  END FUNCTION cp_update_opt
+
+  FUNCTION cp_add_opt(this,sname,pname,values) RESULT(err)
+    !! Add an option to the parser.
+    !!
+    !! In order to add an option to the default section, _sname_ should be left empty or set to "\_\_default\_\_".
+    !!
+    !! The following error code can be returned:
+    !!  - 0, no error.
+    !!  - -8, the option already exists.
+    !!  - -9, option name is not valid.
+    OBJECT(cfgparser), INTENT(inout) :: this
+      !! A cfgparser object to process.
+    CHARACTER(len=*), INTENT(in)     :: sname
+      !! Section name.
+    CHARACTER(len=*), INTENT(in)     :: pname
+      !! Option basename.
+    TYPE(words), INTENT(in)          :: values
+      !! Values to set.
+    TYPE(error) :: err
+      !! Return error status.
+    CHARACTER(len=:), ALLOCATABLE           :: zsname,fname
+    TYPE(option), DIMENSION(:), ALLOCATABLE :: tmp
+    INTEGER                                 :: no,i
+    TYPE(option)                            :: sca
+
+    err = noerror
+    zsname = sname
+    no = cfg_count(this)
+    IF (LEN_TRIM(zsname) == 0) zsname = "__default__"
+    fname = TRIM(zsname)//"/"//to_lower(TRIM(pname))
+    ! check name
+    IF (.NOT.cfg_check_name(fname)) THEN
+      err = error("Invalid option name '"//fname//"'",-9)
+      RETURN
+    ENDIF
+    ! check if opt exists in the parser
+    IF (no > 0) THEN
+      IF (cp_get_opt_idx(this,fname) /= -1) THEN
+        err = error("Duplicate option '"//TRIM(pname)//"' in "//TRIM(zsname),-8)
+        RETURN
+      ENDIF
+    ENDIF
+
+    ! build option
+    CALL op_clear(sca)
+    sca%name = to_lower(TRIM(pname))
+    sca%section = zsname
+    sca%values = values
+
+    IF (no == 0) THEN
+      ! no options yet -> allocate
+      ALLOCATE(this%options(1))
+    ELSE
+      ! parser has options: increase this%options size (ugly copy).
+      ALLOCATE(tmp(no))
+      DO i =1,no
+        tmp(i) = this%options(i)
+        CALL op_clear(this%options(i))
+      ENDDO
+      DEALLOCATE(this%options)
+      ALLOCATE(this%options(no+1))
+      DO i =1,no
+        this%options(i) = tmp(i)
+        CALL op_clear(tmp(i))
+      ENDDO
+      DEALLOCATE(tmp)
+    ENDIF
+    ! always add the option at the end.
+    this%options(no+1) = sca
+    CALL op_clear(sca)
+  END FUNCTION cp_add_opt
+
+  FUNCTION cp_get_rv_sc(this,name,output) RESULT(err)
+    !! Get the first value of an option in the parser by name (real/scalar)
+    !!
+    !! The following error status can be returned by the method:
+    !!  - -7, no option matches the given name.
+    !!  - -6, the option does not have value(s).
+    !!  - -10, the value cannot be converted in the output type.
+    OBJECT(cfgparser), INTENT(in) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name   !! (Full) Name of the option to get
+    REAL(kind=4), INTENT(out)     :: output !! Output value
+    TYPE(error) :: err
+      !! Error status
+    INTEGER :: idx
+    CHARACTER(len=:), ALLOCATABLE :: tmp
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values)== 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      tmp = TRIM(ADJUSTL(words_get(this%options(idx)%values,1)))
+      IF (LEN(tmp) == 0) THEN
+        err = error("Option "//TRIM(name)//" has no value",-6)
+      ELSE
+        IF(.NOT.from_string(tmp,output)) &
+        err = error(TRIM(name)//": Cannot convert "//tmp//" to real.",-10)
+      ENDIF
+    ENDIF
+  END FUNCTION cp_get_rv_sc
+
+  FUNCTION cp_get_dv_sc(this,name,output) RESULT(err)
+    !! Get the first value of an option in the parser by name (double/scalar)
+    !!
+    !! The following error status can be returned by the method:
+    !!  - -7, no option matches the given name.
+    !!  - -6, the option does not have value(s).
+    !!  - -10, the value cannot be converted in the output type.
+    OBJECT(cfgparser), INTENT(in) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name   !! (Full) Name of the option to get
+    REAL(kind=8), INTENT(out)     :: output !! Output value
+    TYPE(error) :: err
+      !! Error status
+    INTEGER :: idx
+    CHARACTER(len=:), ALLOCATABLE :: tmp
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values)== 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      tmp = TRIM(ADJUSTL(words_get(this%options(idx)%values,1)))
+      IF (LEN(tmp) == 0) THEN
+        err = error("Option "//TRIM(name)//" has no value",-6)
+      ELSE
+        IF(.NOT.from_string(tmp,output)) &
+        err = error(TRIM(name)//": Cannot convert "//tmp//" to double.",-10)
+      ENDIF
+    ENDIF
+  END FUNCTION cp_get_dv_sc
+
+  FUNCTION cp_get_iv_sc(this,name,output) RESULT(err)
+    !! Get the first value of an option in the parser by name (integer/scalar)
+    !!
+    !! The following error status can be returned by the method:
+    !!  - -7, no option matches the given name.
+    !!  - -6, the option does not have value(s).
+    !!  - -10, the value cannot be converted in the output type.
+    OBJECT(cfgparser), INTENT(in) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name   !! (Full) Name of the option to get
+    INTEGER, INTENT(out)          :: output !! Output value
+    TYPE(error) :: err
+      !! Error status
+    INTEGER :: idx
+    CHARACTER(len=:), ALLOCATABLE :: tmp
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values)== 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      tmp = TRIM(ADJUSTL(words_get(this%options(idx)%values,1)))
+      IF (LEN(tmp) == 0) THEN
+        err = error("Option "//TRIM(name)//" has no value",-6)
+      ELSE
+        IF(.NOT.from_string(tmp,output)) &
+        err = error(TRIM(name)//": Cannot convert "//tmp//" to integer.",-10)
+      ENDIF
+    ENDIF
+  END FUNCTION cp_get_iv_sc
+
+  FUNCTION cp_get_lv_sc(this,name,output) RESULT(err)
+    !! Get the first value of an option in the parser by name (logical/scalar)
+    !!
+    !! The following error status can be returned by the method:
+    !!  - -7, no option matches the given name.
+    !!  - -6, the option does not have value(s).
+    !!  - -10, the value cannot be converted in the output type.
+    OBJECT(cfgparser), INTENT(in) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name   !! (Full) Name of the option to get
+    LOGICAL, INTENT(out)          :: output !! Output value
+    TYPE(error) :: err
+      !! Error status
+    INTEGER :: idx
+    CHARACTER(len=:), ALLOCATABLE :: tmp
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values)== 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      tmp = TRIM(ADJUSTL(words_get(this%options(idx)%values,1)))
+      IF (LEN(tmp) == 0) THEN
+        err = error("Option "//TRIM(name)//" has no value",-6)
+      ELSE
+        IF(.NOT.from_string(tmp,output)) &
+        err = error(TRIM(name)//": Cannot convert "//tmp//" to logical.",-10)
+      ENDIF
+    ENDIF
+  END FUNCTION cp_get_lv_sc
+
+  FUNCTION cp_get_cv_sc(this,name,output) RESULT(err)
+    !! Get the first value of an option in the parser by name (complex/scalar)
+    !!
+    !! The following error status can be returned by the method:
+    !!  - -7, no option matches the given name.
+    !!  - -6, the option does not have value(s).
+    !!  - -10, the value cannot be converted in the output type.
+    OBJECT(cfgparser), INTENT(in) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name   !! (Full) Name of the option to get
+    COMPLEX, INTENT(out)          :: output !! Output value
+    TYPE(error) :: err
+      !! Error status
+    INTEGER :: idx
+    CHARACTER(len=:), ALLOCATABLE :: tmp
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values)== 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      tmp = TRIM(ADJUSTL(words_get(this%options(idx)%values,1)))
+      IF (LEN(tmp) == 0) THEN
+        err = error("Option "//TRIM(name)//" has no value",-6)
+      ELSE
+        IF(.NOT.from_string(tmp,output)) &
+        err = error(TRIM(name)//": Cannot convert "//tmp//" to complex.",-10)
+      ENDIF
+    ENDIF
+  END FUNCTION cp_get_cv_sc
+
+  FUNCTION cp_get_sv_sc(this,name,output) RESULT(err)
+    !! Get the first value of an option in the parser by name (string/scalar)
+    !!
+    !! The following error status can be returned by the method:
+    !!  - -7, no option matches the given name.
+    !!  - -6, the option does not have value(s).
+    OBJECT(cfgparser), INTENT(in) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)  :: name   !! (Full) Name of the option to get
+    CHARACTER(len=*), INTENT(out) :: output !! Output value
+    TYPE(error) :: err
+      !! Error status
+    INTEGER :: idx
+    !CHARACTER(len=:), ALLOCATABLE :: tmp
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values)== 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      output = TRIM(ADJUSTL(words_get(this%options(idx)%values,1)))
+      err = noerror
+    ENDIF
+  END FUNCTION cp_get_sv_sc
+
+  FUNCTION cp_get_rv_ve(this,name,output) RESULT(err)
+    !! Get the value(s) of an option in the parser by name (real/vector)
+    !!
+    !! On error, the output vector is not allocated.
+    OBJECT(cfgparser), INTENT(in)                        :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)                         :: name   !! (Full) Name of the option to get
+    REAL(kind=4), INTENT(out), DIMENSION(:), ALLOCATABLE :: output !! Output values
+    TYPE(error) :: err
+      !! Error status of the method (see [[cfgparser(type):get_value(bound)]] documentation)
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tmp
+    LOGICAL                                           :: ok
+    INTEGER                                           :: i,idx
+    CHARACTER(len=15)                                 :: i2s
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values) == 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      ALLOCATE(output(words_length(this%options(idx)%values)))
+      ok = words_to_vector(this%options(idx)%values,tmp)
+      DO i=1, SIZE(tmp)
+        WRITE(i2s,'(I15)') i ; i2s=ADJUSTL(i2s)
+        IF (LEN_TRIM(ADJUSTL(tmp(i))) == 0) THEN
+          err = error("Cannot get value #"//TRIM(i2s)//" from option "//TRIM(name),-6)
+          DEALLOCATE(output) ; EXIT
+        ELSE IF (.NOT.from_string(tmp(i), output(i))) THEN
+          err = error("Cannot convert value #"//TRIM(i2s)//" from option "//TRIM(name),-10)
+          DEALLOCATE(output) ; EXIT
+        ENDIF
+      ENDDO
+    ENDIF
+    DEALLOCATE(tmp)
+    RETURN
+  END FUNCTION cp_get_rv_ve
+
+  FUNCTION cp_get_dv_ve(this,name,output) RESULT(err)
+    !! Get the value(s) of an option in the parser by name (double/vector)
+    !!
+    !! On error, the output vector is not allocated.
+    OBJECT(cfgparser), INTENT(in)                        :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)                         :: name   !! (Full) Name of the option to get
+    REAL(kind=8), INTENT(out), DIMENSION(:), ALLOCATABLE :: output !! Output values
+    TYPE(error) :: err
+      !! Error status of the method (see [[cfgparser(type):get_value(bound)]] documentation)
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tmp
+    LOGICAL                                           :: ok
+    INTEGER                                           :: i,idx
+    CHARACTER(len=15)                                 :: i2s
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values) == 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      ALLOCATE(output(words_length(this%options(idx)%values)))
+      ok = words_to_vector(this%options(idx)%values,tmp)
+      DO i=1, SIZE(tmp)
+        WRITE(i2s,'(I15)') i ; i2s=ADJUSTL(i2s)
+        IF (LEN_TRIM(ADJUSTL(tmp(i))) == 0) THEN
+          err = error("Cannot get value #"//TRIM(i2s)//" from option "//TRIM(name),-6)
+          DEALLOCATE(output) ; EXIT
+        ELSE IF (.NOT.from_string(tmp(i), output(i))) THEN
+          err = error("Cannot convert value #"//TRIM(i2s)//" from option "//TRIM(name),-10)
+          DEALLOCATE(output) ; EXIT
+        ENDIF
+      ENDDO
+    ENDIF
+    DEALLOCATE(tmp)
+    RETURN
+  END FUNCTION cp_get_dv_ve
+
+  FUNCTION cp_get_iv_ve(this,name,output) RESULT(err)
+    !! Get the value(s) of an option in the parser by name (integer/vector)
+    !!
+    !! On error, the output vector is not allocated.
+    OBJECT(cfgparser), INTENT(in)                   :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)                    :: name   !! (Full) Name of the option to get
+    INTEGER, INTENT(out), DIMENSION(:), ALLOCATABLE :: output !! Output values
+    TYPE(error) :: err
+      !! Error status of the method (see [[cfgparser(type):get_value(bound)]] documentation)
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tmp
+    LOGICAL                                           :: ok
+    INTEGER                                           :: i,idx
+    CHARACTER(len=15)                                 :: i2s
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values) == 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      ALLOCATE(output(words_length(this%options(idx)%values)))
+      ok = words_to_vector(this%options(idx)%values,tmp)
+      DO i=1, SIZE(tmp)
+        WRITE(i2s,'(I15)') i ; i2s=ADJUSTL(i2s)
+        IF (LEN_TRIM(ADJUSTL(tmp(i))) == 0) THEN
+          err = error("Cannot get value #"//TRIM(i2s)//" from option "//TRIM(name),-6)
+          DEALLOCATE(output) ; EXIT
+        ELSE IF (.NOT.from_string(tmp(i), output(i))) THEN
+          err = error("Cannot convert value #"//TRIM(i2s)//" from option "//TRIM(name),-10)
+          DEALLOCATE(output) ; EXIT
+        ENDIF
+      ENDDO
+    ENDIF
+    DEALLOCATE(tmp)
+    RETURN
+  END FUNCTION cp_get_iv_ve
+
+  FUNCTION cp_get_lv_ve(this,name,output) RESULT(err)
+    !! Get the value(s) of an option in the parser by name (logical/vector)
+    !!
+    !! On error, the output vector is not allocated.
+    OBJECT(cfgparser), INTENT(in)                   :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)                    :: name   !! (Full) Name of the option to get
+    LOGICAL, INTENT(out), DIMENSION(:), ALLOCATABLE :: output !! Output values
+    TYPE(error) :: err
+      !! Error status of the method (see [[cfgparser(type):get_value(bound)]] documentation)
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tmp
+    LOGICAL                                           :: ok
+    INTEGER                                           :: i,idx
+    CHARACTER(len=15)                                 :: i2s
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values) == 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      ALLOCATE(output(words_length(this%options(idx)%values)))
+      ok = words_to_vector(this%options(idx)%values,tmp)
+      DO i=1, SIZE(tmp)
+        WRITE(i2s,'(I15)') i ; i2s=ADJUSTL(i2s)
+        IF (LEN_TRIM(ADJUSTL(tmp(i))) == 0) THEN
+          err = error("Cannot get value #"//TRIM(i2s)//" from option "//TRIM(name),-6)
+          DEALLOCATE(output) ; EXIT
+        ELSE IF (.NOT.from_string(tmp(i), output(i))) THEN
+          err = error("Cannot convert value #"//TRIM(i2s)//" from option "//TRIM(name),-10)
+          DEALLOCATE(output) ; EXIT
+        ENDIF
+      ENDDO
+    ENDIF
+    DEALLOCATE(tmp)
+    RETURN
+  END FUNCTION cp_get_lv_ve
+
+  FUNCTION cp_get_cv_ve(this,name,output) RESULT(err)
+    !! Get the value(s) of an option in the parser by name (complex/vector)
+    !!
+    !! On error, the output vector is not allocated.
+    OBJECT(cfgparser), INTENT(in)                   :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)                    :: name   !! (Full) Name of the option to get
+    COMPLEX, INTENT(out), DIMENSION(:), ALLOCATABLE :: output !! Output values
+    TYPE(error) :: err
+      !! Error status of the method (see [[cfgparser(type):get_value(bound)]] documentation)
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tmp
+    LOGICAL                                           :: ok
+    INTEGER                                           :: i,idx
+    CHARACTER(len=15)                                 :: i2s
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values) == 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      ALLOCATE(output(words_length(this%options(idx)%values)))
+      ok = words_to_vector(this%options(idx)%values,tmp)
+      DO i=1, SIZE(tmp)
+        WRITE(i2s,'(I15)') i ; i2s=ADJUSTL(i2s)
+        IF (LEN_TRIM(ADJUSTL(tmp(i))) == 0) THEN
+          err = error("Cannot get value #"//TRIM(i2s)//" from option "//TRIM(name),-6)
+          DEALLOCATE(output) ; EXIT
+        ELSE IF (.NOT.from_string(tmp(i), output(i))) THEN
+          err = error("Cannot convert value #"//TRIM(i2s)//" from option "//TRIM(name),-10)
+          DEALLOCATE(output) ; EXIT
+        ENDIF
+      ENDDO
+    ENDIF
+    DEALLOCATE(tmp)
+    RETURN
+  END FUNCTION cp_get_cv_ve
+
+  FUNCTION cp_get_sv_ve(this,name,output) RESULT(err)
+    !! Get the value(s) of an option in the parser by name (string/vector)
+    !!
+    !! On error, the output vector is not allocated.
+    OBJECT(cfgparser), INTENT(in)                            :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)                             :: name   !! (Full) Name of the option to get
+    CHARACTER(len=*), INTENT(out), DIMENSION(:), ALLOCATABLE :: output !! Output values
+    TYPE(error) :: err                                                 !! Error status of the method (see [[cfgparser(type):get_value(bound)]] documentation)
+    LOGICAL :: ok
+    INTEGER :: idx
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    IF (idx == -1) THEN
+      err = error("Option "//TRIM(name)//" does not exist",-7)
+      RETURN
+    ENDIF
+    IF (words_length(this%options(idx)%values) == 0) THEN
+      err = error("Option "//TRIM(name)//" has no value",-6)
+    ELSE
+      ok = words_to_vector(this%options(idx)%values,output)
+    ENDIF
+    RETURN
+  END FUNCTION cp_get_sv_ve
+
+  FUNCTION cp_set_rv_sc(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (real/scalar)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)     :: name   !! (Full) Name of the option to set
+    REAL(kind=4), INTENT(in)         :: input  !! Input value
+    LOGICAL, INTENT(in), OPTIONAL    :: create !! .true. to create option if it does not exist (default to false).
+    TYPE(error)                      :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    CALL words_append(values,to_string(input))
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_rv_sc
+
+  FUNCTION cp_set_dv_sc(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (double/scalar)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)     :: name   !! (Full) Name of the option to set
+    REAL(kind=8), INTENT(in)         :: input  !! Input value
+    LOGICAL, INTENT(in), OPTIONAL    :: create !! .true. to create option if it does not exist (default to false).
+    TYPE(error)                      :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    CALL words_append(values,to_string(input))
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_dv_sc
+
+  FUNCTION cp_set_iv_sc(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (double/scalar)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)     :: name   !! (Full) Name of the option to set
+    INTEGER, INTENT(in)              :: input  !! Input value
+    LOGICAL, INTENT(in), OPTIONAL    :: create !! .true. to create option if it does not exist (default to false).
+    TYPE(error)                      :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    CALL words_append(values,to_string(input))
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_iv_sc
+
+  FUNCTION cp_set_lv_sc(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (logical/scalar)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)     :: name   !! (Full) Name of the option to set
+    LOGICAL, INTENT(in)              :: input  !! Input value
+    LOGICAL, INTENT(in), OPTIONAL    :: create !! .true. to create option if it does not exist (default to false).
+    TYPE(error)                      :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    CALL words_append(values,to_string(input))
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_lv_sc
+
+  FUNCTION cp_set_cv_sc(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (complex/scalar)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)     :: name   !! (Full) Name of the option to set
+    COMPLEX, INTENT(in)              :: input  !! Input value
+    LOGICAL, INTENT(in), OPTIONAL    :: create !! .true. to create option if it does not exist (default to false).
+    TYPE(error)                      :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    CALL words_append(values,to_string(input))
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_cv_sc
+
+  FUNCTION cp_set_sv_sc(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (string/scalar)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout) :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)     :: name   !! (Full) Name of the option to set
+    CHARACTER(len=*), INTENT(in)     :: input  !! Input value
+    LOGICAL, INTENT(in), OPTIONAL    :: create !! .true. to create option if it does not exist (default to false).
+    TYPE(error)                      :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    CALL words_append(values,input)
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_sv_sc
+
+  FUNCTION cp_set_rv_ve(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (real/vector)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout)       :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)           :: name   !! (Full) Name of the option to get
+    REAL(kind=4), INTENT(in), DIMENSION(:) :: input  !! Input values
+    LOGICAL, INTENT(in), OPTIONAL          :: create !! .true. to create option if it does not exist (default to false)
+    TYPE(error)                            :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: i,idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    DO i=1,SIZE(input) ; CALL words_append(values,to_string(input(i))); ENDDO
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_rv_ve
+
+  FUNCTION cp_set_dv_ve(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (double/vector))
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout)       :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)           :: name   !! (Full) Name of the option to get
+    REAL(kind=8), INTENT(in), DIMENSION(:) :: input  !! Input values
+    LOGICAL, INTENT(in), OPTIONAL          :: create !! .true. to create option if it does not exist (default to false)
+    TYPE(error)                            :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: i,idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    DO i=1,SIZE(input) ; CALL words_append(values,to_string(input(i))); ENDDO
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_dv_ve
+
+  FUNCTION cp_set_iv_ve(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (integer/vector)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout)  :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)      :: name   !! (Full) Name of the option to get
+    INTEGER, INTENT(in), DIMENSION(:) :: input  !! Input values
+    LOGICAL, INTENT(in), OPTIONAL     :: create !! .true. to create option if it does not exist (default to false)
+    TYPE(error)                       :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: i,idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    DO i=1,SIZE(input) ; CALL words_append(values,to_string(input(i))); ENDDO
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_iv_ve
+
+  FUNCTION cp_set_lv_ve(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (logical/vector)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout)  :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)      :: name   !! (Full) Name of the option to get
+    LOGICAL, INTENT(in), DIMENSION(:) :: input  !! Input values
+    LOGICAL, INTENT(in), OPTIONAL     :: create !! .true. to create option if it does not exist (default to false)
+    TYPE(error)                       :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: i,idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    DO i=1,SIZE(input) ; CALL words_append(values,to_string(input(i))); ENDDO
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_lv_ve
+
+  FUNCTION cp_set_cv_ve(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (complex/vector)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout)  :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)      :: name   !! (Full) Name of the option to get
+    COMPLEX, INTENT(in), DIMENSION(:) :: input  !! Input values
+    LOGICAL, INTENT(in), OPTIONAL     :: create !! .true. to create option if it does not exist (default to false)
+    TYPE(error)                       :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: i,idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    DO i=1,SIZE(input) ; CALL words_append(values,to_string(input(i))); ENDDO
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_cv_ve
+
+  FUNCTION cp_set_sv_ve(this,name,input,create) RESULT(err)
+    !! Set new value for the given option by name (complex/vector)
+    !!
+    !! If _create_ is given to .true., the method will add a new option if it does not exist in
+    !! the parser.
+    !! In such case, an error ((-9, invalid name)) is raised if the option name is not valid.
+    !!
+    !! In other case, if the option is not defined in the parser the error status is set to -7.
+    OBJECT(cfgparser), INTENT(inout)           :: this   !! Cfgparser object
+    CHARACTER(len=*), INTENT(in)               :: name   !! (Full) Name of the option to get
+    CHARACTER(len=*), INTENT(in), DIMENSION(:) :: input  !! Input values
+    LOGICAL, INTENT(in), OPTIONAL              :: create !! .true. to create option if it does not exist (default to false)
+    TYPE(error)                                :: err    !! Error status
+    LOGICAL                       :: zcreate
+    INTEGER                       :: i,idx
+    CHARACTER(len=:), ALLOCATABLE :: sname,pname
+    TYPE(words) :: values
+    zcreate = .false. ; IF (PRESENT(create)) zcreate = create
+    err = noerror
+    idx = cp_get_opt_idx(this,name)
+    DO i=1,SIZE(input) ; CALL words_append(values,trim(input(i))); ENDDO
+    IF (idx == -1) THEN
+      IF (zcreate) THEN
+        err = op_split_name(name,sname,pname)
+        IF (err == 0) err = cp_add_opt(this,sname,pname,values)
+      ELSE
+        err = error("Option "//TRIM(name)//" does not exist",-7)
+      ENDIF
+    ELSE
+      this%options(idx)%values = values
+    ENDIF
+    CALL words_clear(values)
+  END FUNCTION cp_set_sv_ve
+
+  ! i/o functions
+  !--------------
+
+  RECURSIVE FUNCTION read_include(parser,path,isec,ipaths,override) RESULT(err)
+    !! Read and parse an included configuration file (internal)
+    !! @note
+    !! On error, the cfgparser object is left unchanged.
+    TYPE(cfgparser), INTENT(inout)        :: parser
+      !! A cfgparser object that will store the configuration
+    CHARACTER(len=*), INTENT(in)          :: path
+      !! A string with the path of the input file to read
+    CHARACTER(len=st_slen), INTENT(inout) :: isec
+      !! Current section name
+    TYPE(words), INTENT(inout)            :: ipaths
+      !! List of paths of already included files
+    LOGICAL, INTENT(in), OPTIONAL         :: override
+      !! An optional boolean flag with .true. to override previous value of duplicated options instead of raising an error.
+    TYPE(error) :: err
+      !! An error with the first error encountered
+    TYPE(option)                  :: curopt
+    LOGICAL                       :: zoverride,ok,has_opt
+    INTEGER                       :: lineno,lu,i
+    CHARACTER(len=2), PARAMETER   :: space = CHAR(32)//","    ! check if , is really wanted... A: YES space are the delimiter of the words internal object !
+    CHARACTER(len=2), PARAMETER   :: blanks = CHAR(9)//CHAR(32) ! currently not used because blanks truncate.
+    CHARACTER(len=15)             :: sln
+    CHARACTER(len=:), ALLOCATABLE :: fulp,dirp,basp
+    CHARACTER(len=:), ALLOCATABLE :: curval,ipath
+    CHARACTER(len=:), ALLOCATABLE :: line,name,value
+    INTEGER, PARAMETER            :: cfg_UNKNOWN = -1, &
+                                     cfg_SECTION =  0, &
+                                     cfg_OPTION  =  1
+
+    zoverride = .false. ; IF (PRESENT(override)) zoverride = override
+    ! initialize local variables
+    curval = '' ; line   = '' ; name  = '' ; value = ''
+    lineno = 0  ; lu = free_lun()
+    IF (LEN_TRIM(isec) == 0) isec = "__default__"
+    i = INDEX(TRIM(path),"/",.true.)
+    IF (i == 0) THEN
+      fulp = fs_realpath("./"//TRIM(ADJUSTL(path)))
+    ELSE
+      fulp = fs_realpath(TRIM(ADJUSTL(path)))
+    ENDIF
+    basp = fs_basename(fulp)
+    dirp = fs_dirname(fulp)
+    ! check for file
+    INQUIRE(FILE=TRIM(path),EXIST=ok)
+    IF (.NOT.ok) THEN
+      err = error(TRIM(path)//": no such file",-11)
+      RETURN
+    ENDIF
+    ! check for lun
+    IF (lu == -1) THEN ; err = error("No available logical unit",-12) ; RETURN ; ENDIF
+    OPEN(lu,FILE=TRIM(path),STATUS='old',ACTION='READ')
+    DO WHILE(readline(lu,line))
+      lineno = lineno + 1
+      WRITE(sln,'(I15)') lineno ; sln=ADJUSTL(sln)
+      ! comment or blank line ?
+      IF (is_comment(line,ipath)) THEN
+        ! check for includes
+        IF (LEN(ipath) > 0) THEN
+          ! 1) get relative path
+          ipath = fs_relpath(ipath,dirp)
+          ! 2) compute asbolute path
+          ipath = TRIM(dirp)//"/"//TRIM(ipath)
+          ipath = fs_realpath(ipath)
+          IF (.NOT.check_include(ipaths,ipath)) THEN
+            ipath = fs_basename(ipath)
+            err = error(basp//'(L'//TRIM(sln)//"): Circular include &
+                        &reference to "//ipath,-14)
+            EXIT
+          ENDIF
+          IF (op_valid(curopt) .AND. LEN(curval) > 0) THEN
+            CALL words_extend(curopt%values,TRIM(ADJUSTL(curval)),space,.true.,.true.)
+            IF (zoverride) THEN
+              err = cp_update_opt(parser,curopt%section,curopt%name,curopt%values)
+            ELSE
+              err = cp_add_opt(parser,curopt%section,curopt%name,curopt%values)
+            ENDIF
+            CALL op_clear(curopt); curval = ''
+          ENDIF
+          err = read_include(parser,ipath,isec,ipaths,zoverride)
+          IF (err /= 0) EXIT
+        ENDIF
+        CYCLE
+      ENDIF
+      ! continuation line ?
+      IF (SCAN(line(1:1),blanks) /= 0 .AND. op_valid(curopt)) THEN
+          IF (LEN(curval) == 0) THEN
+            curval = strip_comment(line)
+          ELSE
+            curval = curval//CHAR(32)//strip_comment(line)
+          ENDIF
+      ELSE
+       ! 1. Remove comment part and left adjust line
+       line = strip_comment(line)
+       ! a section header or option header?
+       SELECT CASE (get_kind(line,name,value))
+         CASE(cfg_SECTION)
+           ! 1. add current value to current option (if any)
+           ! 2. update "isec" variable
+           IF (op_valid(curopt)) THEN
+              IF (LEN(curval) > 0) &
+              CALL words_extend(curopt%values,TRIM(ADJUSTL(curval)),space,.true.,.true.)
+              IF (zoverride) THEN
+                err = cp_update_opt(parser,curopt%section,curopt%name,curopt%values)
+              ELSE
+                err = cp_add_opt(parser,curopt%section,curopt%name,curopt%values)
+              ENDIF
+           ENDIF
+           CALL op_clear(curopt) ; curval = ''
+           IF (cfg_has_section(parser,name) .AND. &
+               TRIM(name)/="__default__"    .AND. &
+               .NOT.zoverride) THEN
+             err = error(basp//'(L'//TRIM(sln)//"): Duplicate section '"//name,-8)
+             EXIT
+           ENDIF
+           isec = TRIM(name)
+         CASE(cfg_OPTION)
+           ! 1. add current value to current option (if any)
+           ! 2. search for option in cursect:
+           !    --> duplicate option error if it exists
+           !    --> create new option if it does not exist (using curopt)
+           ! 3. curval is set to value
+           ! 4. update curval
+           IF (op_valid(curopt)) THEN
+              IF (LEN(curval) > 0) &
+              CALL words_extend(curopt%values,TRIM(ADJUSTL(curval)),space,.true.,.true.)
+              IF (zoverride) THEN
+                err = cp_update_opt(parser,curopt%section,curopt%name,curopt%values)
+              ELSE
+                err = cp_add_opt(parser,curopt%section,curopt%name,curopt%values)
+              ENDIF
+           ENDIF
+           CALL op_clear(curopt) ; curval = ''
+           has_opt = cfg_has_option(parser,TRIM(isec)//"/"//TRIM(name))
+
+           IF (has_opt.AND..NOT.zoverride) THEN
+             ! it is an error: no duplicate allowed
+             err = error(basp//'(L'//TRIM(sln)//"): Duplicate option '"//TRIM(name)//"' in "//isec,-8)
+             EXIT
+           ENDIF
+           curopt%name = TRIM(name)
+           curopt%section = TRIM(isec)
+           CALL words_clear(curopt%values)
+           curval = value
+         CASE(cfg_UNKNOWN)
+           ! unknown handles also invalid name: it is a critical error
+           IF (err == -9) EXIT
+       END SELECT
+      ENDIF
+    ENDDO
+    IF (op_valid(curopt)) THEN
+      IF (LEN(curval) > 0) &
+      CALL words_extend(curopt%values,TRIM(ADJUSTL(curval)),space,.true.,.true.)
+      IF (zoverride) THEN
+        err = cp_update_opt(parser,curopt%section,curopt%name,curopt%values)
+      ELSE
+        err = cp_add_opt(parser,curopt%section,curopt%name,curopt%values)
+      ENDIF
+    ENDIF
+    CALL op_clear(curopt) ; curval = ''
+
+    CLOSE(lu)
+
+  CONTAINS
+    FUNCTION get_kind(string,name,value) RESULT(kind)
+      !! Split input line and attempt to guess its relevant kind of statement
+      !!
+      !! The input line is searched for section header format or option assignment.
+      !!
+      !! - If line begins with '[', has ']' and no '=#' between '[' and ']'
+      !!   it is a section header.
+      !! - Otherwise, if line has '=', without '#' before '=', it is an option.
+      !!
+      !! Then the method returns an integer with the kind flag of the statement which is one of
+      !! -1 (cfg_UNKNOWN), 0 (cfg_SECTION) or 1 (cfg_OPTION).
+      CHARACTER(len=*), INTENT(in)               :: string  !! Input string to process
+      CHARACTER(len=:), INTENT(out), ALLOCATABLE :: name, & !! Name of the relevant option/section if any, otherwise an empty string.
+                                                    value   !! Value of the relevant option (if any), otherwise an empty string
+      INTEGER :: kind                                       !! An integer with the kind of statement.
+      CHARACTER(len=:), ALLOCATABLE :: copy
+      INTEGER                       :: bi,ei
+      CHARACTER(len=2), PARAMETER   :: quotes=CHAR(34)//CHAR(39)
+      kind = cfg_UNKNOWN
+      ! get a trimmed (and left adjusted) copy
+      copy = TRIM(string)
+      ! Is it a section ?
+      !   ---> search for subscripts of '[' and ']'
+      !   ---> check that '[' is 1st char and ']' is last char
+      bi = INDEX(copy,'[') ; ei = INDEX(copy,']')
+      IF (bi == 1 .AND. ei == LEN(copy) .AND. bi < ei) THEN
+        ! it is a section header
+        kind = cfg_SECTION
+        ! get section name: adjust and trim to remove extra blank spaces
+        name = TRIM(ADJUSTL(copy(bi+1:ei-1)))
+        ! hack cfg_check_name: append '/a' to get a valid option part to test
+        IF (TRIM(name) /= "__default__" .AND. .NOT.cfg_check_name(name//"/a")) THEN
+          kind = cfg_UNKNOWN
+          err = error("Invalid section name ("//name//")",-9)
+          RETURN
+        ENDIF
+        value = ''
+      ELSE
+        ! Is it an option ?
+        !   --> search for '=' and check if it is set before
+        !       1st quote (if any)
+        bi = INDEX(copy,"=")
+        ! search for quotes
+        ei = SCAN(copy,quotes) ; IF (ei==0) ei = LEN(copy)+1
+        IF (bi /= 0 .AND. bi < ei) THEN
+          kind = cfg_OPTION
+          name = to_lower(TRIM(copy(1:bi-1)))
+          IF (.NOT.cfg_check_name(name)) THEN
+            kind = cfg_UNKNOWN
+            err = error("Invalid option name ("//TRIM(name)//")",-9)
+            RETURN
+          ENDIF
+          IF (bi == LEN(copy)) THEN
+            value = ''
+          ELSE
+            value = TRIM(copy(bi+1:))
+          ENDIF
+        ELSE
+          ! not an option and not a section: malformed statement !
+          err = error('Malformed statement at line '//TRIM(sln),-13)
+        ENDIF
+      ENDIF
+      RETURN
+    END FUNCTION get_kind
+
+    FUNCTION strip_comment(line) RESULT(stripped)
+      !! Replace comments part of a string by blank spaces
+      !! The method replaces every characters after '#' (included) by spaces.
+      !! @note
+      !! Output string is also left adjusted, thus only trailing blank can be present.
+      CHARACTER(len=*), INTENT(in) :: line !! A string to process
+      CHARACTER(len=LEN(line)) :: stripped !! A string of same length than 'line' but without comment(s)
+
+      INTEGER :: idx
+      stripped = ADJUSTL(line)
+      idx = INDEX(stripped,"#")
+      IF (idx > 0) stripped(idx:) = REPEAT(CHAR(32),LEN(line)-idx+1)
+      RETURN
+    END FUNCTION strip_comment
+
+    FUNCTION readline(lun,string) RESULT(not_eof)
+      !! Read a complete line
+      !!
+      !! Each time it is CALLed, the function reads a complete of the file opened in 'lun' logical
+      !! unit and returns .false. if EOF has been reached, .true. otherwise.
+      !!
+      !! The function is intended to read a file line by line:
+      !!
+      !! ```
+      !! lu = 1
+      !! open(lu,file="path/to/the/file/to/read")
+      !! l=0   ! A line counter
+      !! DO WHILE(readline(lu,line))
+      !!   l = l + 1
+      !!   WRITE(*,'("L",I2.2,": ",(a))') il,line
+      !! ENDDO
+      !! CLOSE(1)
+      !! ```
+      INTEGER, INTENT(in)                        :: lun     !! Logical unit with the opened file to read.
+      CHARACTER(len=:), INTENT(out), ALLOCATABLE :: string  !! Output processed line
+      LOGICAL                                    :: not_eof !! .true. if EOF has NOT been reached yet, .false. otherwise
+      CHARACTER(len=50) :: buf
+      INTEGER           :: e,sz
+      not_eof = .true. ; string = ''
+      DO
+        READ(lun,'(a)',ADVANCE="no",SIZE=sz,IOSTAT=e) buf
+        IF (e == IOSTAT_END) THEN
+          not_eof = .false.
+          IF (sz > 0) THEN
+            string=string//buf(1:sz)
+          ENDIF
+          EXIT
+        ELSE IF (e == IOSTAT_EOR) THEN
+          string = string//buf(1:sz)
+          EXIT
+        ELSE
+          string = string//TRIM(buf)
+        ENDIF
+      ENDDO
+    END FUNCTION readline
+
+    FUNCTION is_comment(str,incpath) RESULT(res)
+      !! Check if line is a comment or an empty string
+      !! @warning
+      !! Currently, if an '#include' statement is found, the method assumes a single path is set after the directive.
+      CHARACTER(len=*), INTENT(in)               :: str
+        !! The string to check
+      CHARACTER(len=:), INTENT(out), ALLOCATABLE :: incpath
+        !! A string with the filepath to be included if '#include' statement is found, empty string otherwise
+      LOGICAL :: res
+        !! .true. if line is a comment or an empty string, .false. otherwise
+      CHARACTER(len=:), ALLOCATABLE :: copy
+      res = .false. ; incpath = ''
+      copy = TRIM(ADJUSTL(str))
+      IF (LEN(copy) == 0) THEN
+        res = .true.
+      ELSE IF (INDEX(copy,"#") == 1) THEN
+        res = .true.
+        ! search for include statement
+        ! IMPORTANT: assume that there is only a path after include statement
+        IF (INDEX(copy,"#include ") == 1) incpath = remove_quotes(TRIM(ADJUSTL(copy(10:))))
+      ENDIF
+      RETURN
+    END FUNCTION is_comment
+
+    FUNCTION check_include(list,incpath) RESULT(ok)
+      !! Check if path is not in list
+      !! @note
+      !! If path is not in list it is added to the list.
+      TYPE(words), INTENT(inout)   :: list    !! A list of paths
+      CHARACTER(len=*), INTENT(in) :: incpath !! Path to check in list
+      LOGICAL :: ok                           !! .true. if 'path' is __not__ in list, .false. otherwise
+      CALL words_reset(list)
+      ok = .true.
+      DO WHILE(words_valid(list))
+        IF (TRIM(incpath) == TRIM(words_current(list))) THEN
+          ok = .false. ; EXIT
+        ENDIF
+        CALL words_next(list)
+      ENDDO
+      IF (ok) CALL words_append(list,TRIM(incpath))
+    END FUNCTION check_include
+
+  END FUNCTION read_include
+
+  ! insertion sort... internal
+
+  SUBROUTINE insertionSort(opts)
+    !! Sort an array of Options using insertion sort algorithm
+    TYPE(option), INTENT(inout), DIMENSION(:) :: opts !! Array to sort.
+    TYPE(option) :: temp
+    INTEGER :: i, j
+    DO i = 2, SIZE(opts)
+      j = i - 1
+      temp = opts(i)
+      DO WHILE (j>=1) ! .AND. op_greater_than(opts(j),temp))
+        IF (op_greater_than(opts(j),temp)) THEN
+        opts(j+1) = opts(j)
+        j = j - 1
+        ELSE
+          EXIT
+        ENDIF
+      ENDDO
+      opts(j+1) = temp
+      CALL op_clear(temp)
+    ENDDO
+  END SUBROUTINE insertionSort
+
+END MODULE SWIFT_CFGPARSE
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_csystem.c
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_csystem.c	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_csystem.c	(revision 3560)
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) Jeremie Burgalat (2013-2022)
+ * Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+ *
+ * This file is part of SWIFT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <string.h>
+#include <libgen.h>
+#include <sys/param.h>  // MAXPATHLEN
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <pwd.h>
+#include <grp.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#include "swift_csystem.h"
+
+// Hack to get MAXPATHLEN
+#ifndef MAXPATHLEN
+#define MAXPATHLEN 4096
+#endif
+
+/* Get errno */
+int c_get_errno(){
+  int ret = errno;
+  return ret;
+}
+
+/* Get the current working directory */
+char* c_getcwd(){
+  char *tmp;
+  tmp = (char *) malloc((MAXPATHLEN+1)*sizeof(char));
+  memset(tmp,'\0',MAXPATHLEN+1);
+  if (getcwd(tmp,MAXPATHLEN) == NULL) {
+    free(tmp);
+    tmp = NULL;
+  }
+  return tmp;
+}
+
+
+/* Get the realpath of input path and saves it in output path */
+char* c_realpath(const char *input){
+  if (!strlen(input)) {
+    return c_getcwd();
+  }
+  return realpath(input,NULL);
+}
+
+
+/* Get directory name of input path */
+char* c_dirname(const char *in){
+  char *tmp = strdup(in);
+  char *ou = strdup(dirname(tmp));
+  free(tmp);
+  return ou;
+}
+
+/* Get base name of input path */
+char* c_basename(const char *in){
+  char* tmp = strdup(in);
+  char *ou = strdup(basename(tmp));
+  free(tmp);
+  return ou;
+}
+
+/* Get the corresponding name of the given user id */
+char* c_uname(int uid){
+  struct passwd  *pwd;
+  char *name;
+  name = NULL;
+  if ((pwd = getpwuid(uid)) !=  NULL){
+    name = strdup(pwd->pw_name);
+  }
+  return name;
+}
+
+/* Get the corresponding name of the given group id */
+char* c_gname(int gid){
+  struct group *grp;
+  char *name;
+  name = NULL;
+  if ((grp = getgrgid(gid)) != NULL){
+    name = strdup(grp->gr_name);
+  }
+  return name;
+}
+
+/* Get the error message of the given error id */
+char* c_strerror(int err){
+  char *tmp = strdup(strerror(err));
+  if (errno != 0)
+    tmp = "Unknown error\0";
+  return tmp;
+}
+
+
+/* Remove a directory and its contents recursively */
+int c_rmdir_f(const char *path) {
+  DIR *d = opendir(path);
+  size_t path_len = strlen(path);
+  int r = -1;
+  if (d) {
+    struct dirent *p;
+    r = 0;
+    while (!r && (p=readdir(d))) {
+      int r2 = -1;
+      char *buf;
+      size_t len;
+      /* Skip the names "." and ".." as we don't want to recurse on them. */
+      if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) {
+        continue;
+      }
+      len = path_len + strlen(p->d_name) + 2;
+      buf = malloc(len);
+      if (buf) {
+        struct stat statbuf;
+        snprintf(buf, len, "%s/%s", path, p->d_name);
+        if (!stat(buf, &statbuf)) {
+          if (S_ISDIR(statbuf.st_mode)) {
+            r2 = c_rmdir_f(buf);
+          } else {
+            r2 = unlink(buf);
+          }
+        }
+        free(buf);
+      }
+      r = r2;
+    }
+    closedir(d);
+  }
+  if (!r) {
+    r = rmdir(path);
+  return r?errno:0;
+  }
+  return r;
+}
+
+/* Get some file informations */
+int c_fstat(const char *p, int *pe, int *nl, int *ty, int *ui, int *gi,
+            long *si, char a[20], char m[20], char c[20]){
+  struct stat stb;
+  struct tm *t;
+  int ret ;
+  //char *tmp;
+  char tmp[20];
+  *pe = *ty = *ui = *gi = *si = -1 ;
+  if (stat(p, &stb) != -1) {
+    *pe = (int)(stb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+    *nl = (int)(stb.st_nlink);
+    if (S_ISREG(stb.st_mode)) *ty = 0 + S_ISLNK(stb.st_mode);
+    else if (S_ISDIR(stb.st_mode)) *ty = 2 + S_ISLNK(stb.st_mode) ;
+    else *ty = 4;
+    *ui = (int)stb.st_uid ; *gi = (int)stb.st_gid ; *si = (long)stb.st_size ;
+    t = localtime(&stb.st_atime) ; ret = strftime(tmp, 20, "%F,%T", t);
+    if(ret != 0) {strncpy(a,tmp,20); a[19] = '\0';}else{a[0] = '\0';}
+    t = localtime(&stb.st_mtime) ; ret = strftime(tmp, 20, "%F,%T", t);
+    if(ret != 0) {strncpy(m,tmp,20); m[19] = '\0';}else{m[0] = '\0';}
+    t = localtime(&stb.st_ctime) ; ret = strftime(tmp, 20, "%F,%T", t);
+    if(ret != 0) {strncpy(c,tmp,20); c[19] = '\0';}else{c[0] = '\0';}
+    return 0;
+  }
+  return errno;
+}
+
+/* Create a directory or a file in given path */
+int c_create(const char* path, mode_t mode, int astype, int forced){
+  char *p, *d;
+  mode_t perm, cmask;
+  int eval;
+  // we build a directory
+  if (!astype){
+    if(forced){eval=c_mkdirp(path,mode);}else{eval=c_mkdir(path,mode);}
+  }else{
+    // we build a file
+    if (forced){
+      p = strdup(path) ; if(p == NULL) {return errno;}
+      d = dirname(p) ; free(p) ;
+      if(d == NULL) {return -9;}
+      // we attempts to create parent directory first
+      cmask = umask(0);
+      perm =((S_IRWXU | S_IRWXG | S_IRWXO) & ~(cmask & ~(S_IWUSR | S_IXUSR)));
+      (void)umask(cmask) ;
+      eval = c_mkdirp(d,perm);
+      if(eval){return eval;}
+    }
+    eval = open(path,O_CREAT|O_EXCL,mode);
+    if (eval == -1) {eval=errno;}else{close(eval);eval=0;}
+  }
+  return eval ;
+}
+
+/* Change current working directory */
+int c_chdir(const char *path){
+  if (chdir(path) != 0) return errno ;
+  return 0;
+}
+
+/* Remove a directory */
+int c_rmdir(const char *path){
+ if ( rmdir(path) != 0) return errno;
+  return 0;
+}
+
+/* Check if path is accessible */
+int c_access(const char *path, int perm) {
+  int ret;
+  if (perm<0 && perm >7) perm=0;
+  ret = access(path,perm);
+  if (ret) return errno;
+  return 0;
+}
+
+/* Rename a path */
+int c_rename(const char *old, const char *new){
+  if (rename(old,new) != 0) return errno ;
+  return 0;
+}
+
+/* Change path permissions */
+int c_chmod(const char *path, mode_t mode){
+  if (chmod(path,mode) != 0) return errno ;
+  return 0;
+}
+
+/* Create directory */
+int c_mkdir(const char *path, mode_t mode){
+  if(mkdir(path,mode) == 0) {
+    if (chmod(path, mode) == -1) return errno ;
+    return 0;
+  }else{
+    return errno;
+  }
+}
+
+
+int c_copy(const char *to, const char *from) {
+  int fd_to, fd_from;
+  char buf[4096];
+  ssize_t nread;
+  int saved_errno;
+
+  fd_from = open(from, O_RDONLY);
+  if (fd_from < 0)
+    return -1;
+
+  fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666);
+  if (fd_to < 0)
+    goto out_error;
+
+  while (nread = read(fd_from, buf, sizeof buf), nread > 0) {
+    char *out_ptr = buf;
+    ssize_t nwritten;
+
+    do {
+      nwritten = write(fd_to, out_ptr, nread);
+
+      if (nwritten >= 0) {
+        nread -= nwritten;
+        out_ptr += nwritten;
+      } else if (errno != EINTR) {
+        goto out_error;
+      }
+    } while (nread > 0);
+  }
+
+  if (nread == 0) {
+    if (close(fd_to) < 0) {
+      fd_to = -1;
+      goto out_error;
+    }
+    close(fd_from);
+    /* Success! */
+    return 0;
+  }
+
+out_error:
+  saved_errno = errno;
+
+  close(fd_from);
+  if (fd_to >= 0)
+    close(fd_to);
+
+  errno = saved_errno;
+  return 1;
+}
+
+/* Remove file from filesytem */
+int c_remove(const char *path){
+  if (remove(path) != 0) return errno ;
+  return 0;
+}
+
+/* Get terminal size */
+int c_termsize(int *rows,int *cols){
+  struct winsize max;
+  int retval ;
+  retval = ioctl(0, TIOCGWINSZ , &max);
+  if (retval != 0) {
+    retval = errno ;
+    *rows = 20 ; *cols = 80 ;
+  }else{
+    *rows = max.ws_row ;
+    *cols = max.ws_col ;
+  }
+  return retval;
+}
+
+/* Get the current umask (in decimal system) */
+int c_umask(){
+  mode_t mask ;
+  mask = umask(S_IRWXU | S_IRWXG | S_IRWXO); (void) umask(mask) ;
+  return (int)mask;
+}
+
+/* ------------------ THIRD PARTY ------------------ */
+
+/*
+ *  Copyright (c) 1983, 1992, 1993
+ *   The Regents of the University of California.  All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions
+ *  are met:
+ *  1. Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *  3. All advertising materials mentioning features or use of this software
+ *     must display the following acknowledgement:
+ *   This product includes software developed by the University of
+ *  California, Berkeley and its contributors.
+ *  4. Neither the name of the University nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ *  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *  ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ *  SUCH DAMAGE.
+ */
+/* Creates directories recursively */
+int c_mkdirp(const char *path, mode_t mode) {
+  struct stat sb;
+  mode_t numask, oumask;
+  int first, last, retval;
+  char *cpath, *p;
+  cpath=strdup(path); if (cpath == NULL) return -1;
+  p = cpath; oumask = 0; retval = 0;
+  if (p[0] == '/') ++p;    /* Skip leading '/'. */
+  for (first = 1, last = 0; !last ; ++p) {
+    if (p[0] == '\0') last = 1;
+    else if (p[0] != '/') continue;
+    *p = '\0';
+    //if (! strlen(p) && p[1] == '\0') last = 1;
+    if (first) {
+      oumask = umask(0);
+      numask = oumask & ~(S_IWUSR | S_IXUSR);
+      (void)umask(numask);
+      first = 0;
+    }
+    if (last) (void)umask(oumask);
+    if (mkdir(cpath, last ? mode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
+      if (errno == EEXIST || errno == EISDIR) {
+        if (stat(cpath, &sb) < 0) {
+          retval = 1; break;
+        } else if (!S_ISDIR(sb.st_mode)) {
+          if (last) errno = EEXIST; else errno = ENOTDIR;
+          retval = 1; break;
+        }
+      } else {
+        retval = 1; break;
+      }
+    }
+    if (!last) *p = '/';
+  }
+  if (!first && !last) (void)umask(oumask);
+  if (!retval){
+    if (chmod(path, mode) == -1)  retval = errno ;
+  } else {
+   retval = errno;
+  }
+  free(cpath);
+  return retval ;
+}
+
+/*
+ * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
+ * Reserved.  This file contains Original Code and/or Modifications of
+ * Original Code as defined in and that are subject to the Apple Public
+ * Source License Version 1.0 (the 'License').  You may not use this file
+ * except in compliance with the License.  Please obtain a copy of the
+ * License at http://www.apple.com/publicsource and read it before using
+ * this file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
+ * License for the specific language governing rights and limitations
+ * under the License."
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ *
+ * Modified version: J. Burgalat (2015)
+ *
+ */
+
+char* c_relpath(const char *path, const char *reldir) {
+  char start_path[MAXPATHLEN+1];
+  char end_path[MAXPATHLEN+1];
+  char *buf;
+  struct stat st;
+  int i,mc;
+  int last_elem;
+  int prev_path;
+  char *tmp,*cur;
+  buf = NULL;
+
+  if (realpath(reldir,start_path) == NULL)
+    return buf;
+
+  if (realpath(path,end_path) == NULL)
+    return buf;
+
+  // stat the starting path
+  if (stat(start_path, &st) < 0)
+    return buf;
+
+  if ((st.st_mode & S_IFMT) != S_IFDIR) {
+    errno = ENOTDIR;
+    return buf;
+  }
+  if (start_path[strlen(start_path) - 1] != '/')
+    strcat(start_path, "/");
+
+  // stat the ending path path
+  if (stat(end_path, &st) < 0)
+    return buf;
+
+  if ((st.st_mode & S_IFMT) == S_IFDIR
+      && end_path[strlen(end_path) - 1] != '/')
+    strcat(end_path, "/");
+
+  tmp = (char *) malloc((2*MAXPATHLEN+1)*sizeof(char));
+  memset(tmp,'\0',2*MAXPATHLEN+1);
+  cur = tmp;
+  /* strip common prefix */
+  i = 0;
+  last_elem = 0;
+  while (start_path[i] && start_path[i] == end_path[i]) {
+    if (start_path[i] == '/')
+      last_elem = i + 1;
+    i += 1;
+  }
+  prev_path = 0;
+  for (i = last_elem; start_path[i]; i += 1) {
+    if (start_path[i] == '/') {
+      if (prev_path){
+        *cur = '/'; cur++;
+      }
+      strncpy(cur,"..",2) ; cur +=2;
+      prev_path = 1;
+    }
+  }
+  if (end_path[last_elem]) {
+    if (prev_path) {
+      *cur = '/'; cur++;
+    }
+    prev_path = 1;
+    while (end_path[strlen(end_path) - 1] == '/')
+      end_path[strlen(end_path) - 1] = '\0';
+    strcpy(cur,&end_path[last_elem]) ;
+    cur += strlen(&end_path[last_elem]);
+  }
+  if (! prev_path){
+    *cur = '.' ; cur++ ;
+  }
+  // Normally here tmp contains our path. We just need to copy it in buf
+  mc = strlen(tmp)+1;
+  buf = (char *)malloc(mc*sizeof(char));
+  memset(buf,'\0',mc);
+  strncpy(buf,tmp,strlen(tmp));
+  cur = buf;
+  i=0;
+  while (*cur != '\0' && i < mc){
+    cur++;
+    i++;
+  }
+  free(tmp);
+  errno = 0;
+  return buf;
+}
+
+/*
+ * Author:  David Robert Nadeau
+ * Site:    http://NadeauSoftware.com/
+ * License: Creative Commons Attribution 3.0 Unported License
+ *          http://creativecommons.org/licenses/by/3.0/deed.en_US
+ *
+ * Cross-plateform way to get memory usage at a given time in fucking bytes !!
+ *
+ * Usage:
+ *
+ * size_t currentSize = getCurrentRSS( );
+ * size_t peakSize    = getPeakRSS( );
+ */
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <psapi.h>
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+#include <sys/resource.h>
+#if defined(__APPLE__) && defined(__MACH__)
+#include <mach/mach.h>
+#elif(defined(_AIX) || defined(__TOS__AIX__)) ||                                                                               \
+    (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
+#include <procfs.h>
+#endif
+#endif
+
+/**
+ * Returns the peak (maximum so far) resident set size (physical
+ * memory use) measured in bytes, or zero if the value cannot be
+ * determined on this OS.
+ */
+size_t c_getPeakRSS() {
+#if defined(_WIN32)
+    /* Windows -------------------------------------------------- */
+    PROCESS_MEMORY_COUNTERS info;
+    GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
+    return (size_t)info.PeakWorkingSetSize;
+
+#elif(defined(_AIX) || defined(__TOS__AIX__)) ||                                                                               \
+    (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
+    /* AIX and Solaris ------------------------------------------ */
+    struct psinfo psinfo;
+    int fd = -1;
+    if ((fd = open("/proc/self/psinfo", O_RDONLY)) == -1)
+        return (size_t)0L; /* Can't open? */
+    if (read(fd, &psinfo, sizeof(psinfo)) != sizeof(psinfo)) {
+        close(fd);
+        return (size_t)0L; /* Can't read? */
+    }
+    close(fd);
+    return (size_t)(psinfo.pr_rssize * 1024L);
+
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+    /* BSD, Linux, and OSX -------------------------------------- */
+    struct rusage rusage;
+    getrusage(RUSAGE_SELF, &rusage);
+#if defined(__APPLE__) && defined(__MACH__)
+    return (size_t)rusage.ru_maxrss;
+#else
+    return (size_t)(rusage.ru_maxrss * 1024L);
+#endif
+
+#else
+    /* Unknown OS ----------------------------------------------- */
+    return (size_t)0L; /* Unsupported. */
+#endif
+}
+
+/**
+ * Returns the current resident set size (physical memory use) measured
+ * in bytes, or zero if the value cannot be determined on this OS.
+ */
+size_t c_getCurrentRSS() {
+#if defined(_WIN32)
+    /* Windows -------------------------------------------------- */
+    PROCESS_MEMORY_COUNTERS info;
+    GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
+    return (size_t)info.WorkingSetSize;
+
+#elif defined(__APPLE__) && defined(__MACH__)
+    /* OSX ------------------------------------------------------ */
+    struct task_basic_info info;
+    mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT;
+    if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &infoCount) != KERN_SUCCESS)
+        return (size_t)0L; /* Can't access? */
+    return (size_t)info.resident_size;
+
+#elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
+    /* Linux ---------------------------------------------------- */
+    long rss = 0L;
+    FILE *fp = NULL;
+    if ((fp = fopen("/proc/self/statm", "r")) == NULL)
+        return (size_t)0L; /* Can't open? */
+    if (fscanf(fp, "%*s%ld", &rss) != 1) {
+        fclose(fp);
+        return (size_t)0L; /* Can't read? */
+    }
+    fclose(fp);
+    return (size_t)rss * (size_t)sysconf(_SC_PAGESIZE);
+
+#else
+    /* AIX, BSD, Solaris, and Unknown OS ------------------------ */
+    return (size_t)0L; /* Unsupported. */
+#endif
+}
+
+
+
+/* Get some informatiosn about the OS memory usage (from /proc/meminfo) */
+int c_getSystemMemory(long long int *m_total,long long int *m_available,long long int *m_free){
+    FILE* fp;
+    char buf[1024];
+    char *tmp,*p;
+    int ts;
+    long long int mem1 = 0L,mem2 = 0L,mem3 = 0L;
+    if (m_total) (*m_total) = mem1;
+    if (m_available) (*m_free) = mem2;
+    if (m_free) (*m_free) = mem3;
+    if ((fp = fopen("/proc/meminfo", "r")) == NULL)
+        return 1;
+    while (fgets(buf, sizeof(buf), fp) != NULL){
+        ts = strlen(buf) - 1; buf[ts] = '\0';
+        tmp = strdup(&buf[0]);
+        //tmp = strndup(&buf[0],ts); // GNU extension
+        // check our 3 cases
+        if (ts >= 9 && !strncmp(tmp,"MemTotal:",9)){
+            // extract the two first tokens.
+            p=strtok(tmp, " "); p=strtok(NULL, " ");
+            // convert the value.
+            mem1 = strtoll(p,NULL,10);
+        }
+        if (ts >= 13 && !strncmp(tmp,"MemAvailable:",13)){
+            p=strtok(tmp, " "); p=strtok(NULL, " ");
+            mem2 = strtoll(p,NULL,10);
+        }
+        if (ts >= 8 && !strncmp(tmp,"MemFree:",8)){
+            p=strtok(tmp, " "); p=strtok(NULL, " ");
+            mem3 = strtoll(p,NULL,10);
+        }
+        free(tmp);
+    }
+    fclose(fp);
+    if (m_total) (*m_total) = mem1;
+    if (m_available) (*m_available) = mem2;
+    if (m_free) (*m_free) = mem3;
+    return 0;
+}
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_csystem.h
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_csystem.h	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_csystem.h	(revision 3560)
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) Jeremie Burgalat (2013-2022)
+ * Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+ *
+ * This file is part of SWIFT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+
+/* ------------------ WRAPPERS METHODS FOR FORTRAN BINDINGS ------------------ */
+
+/**
+ * Gets the current umask (in decimal system)
+ * @return An int with the current umask set for the current session
+ */
+int c_umask();
+
+/**
+ * Get directory name of input path
+ * @param[in] in A C string with the input path
+ * @return A pointer to a char array with the directory name of @bti{input} path.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char * c_dirname(const char *in);
+
+/**
+ * Get base name of input path
+ * @param[in] in A C string with the input path
+ * @return A pointer to a char array with the base name of @bti{input} path.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_basename(const char *in);
+
+/**
+ * Get the current working directory.
+ * @return A pointer to a char array with the current workind directory.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_getcwd();
+
+
+/**
+ * Get the realpath of input path.
+ * @param[in] input A C string with the input path
+ * @return A pointer to a char array with the realpath of @bti{input} path.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_realpath(const char *input);
+
+/**
+ * Get the relative path of two file paths
+ * @details The method computes the relative path of can_fname to can_reldir if
+ * possible.
+ * @param path string with the path to compute in relative representation
+ * @param reldir a directory path from which output should be relative to
+ * @return A pointer to a char array with the relative path.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_relpath(const char *path, const char *reldir) ;
+
+/**
+ * Get the corresponding name of the given user id
+ * @param[in] uid An integer with a user id
+ * @return A pointer to a char array with the user name.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_uname(int uid);
+
+/**
+ * Get the corresponding name of the given group id
+ * @param[in] gid An integer with a group id
+ * @return A pointer to a char array with the group name.
+ * @note On error, a NULL pointer is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_gname(int gid);
+
+/**
+ * Get last errno from C library
+ * @return An int with last errno saved in C library.
+ */
+int c_get_errno();
+
+/**
+ * Get the error message of the given error id
+ * @param err An integer with the error id
+ * @return A pointer to a char array with the group name.
+ * @note On error, the hard-coded message "Unknown error" is returned.
+ * @warning In any case, the returned pointer must be freed in the Fortran counterpart
+ * (using fsystem::free_c).
+ */
+char* c_strerror(int err);
+
+/**
+ * Creates directories recursively
+ * @note The method is simple stripped copy of mkdir tool source code.
+ * @param[in] path A C string with the path of the directory to create
+ * @param[in] mode A mode_t structure with the permission of the directory
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_mkdirp(const char *path, mode_t mode);
+
+/**
+ * Rename a path
+ * @param old A string with the (valid) path to rename
+ * @param new A string with the new name of the path
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_rename(const char *old, const char *new);
+
+/**
+ * Change path permissions
+ * @param path A string with the path
+ * @param mode A integer with the new permissions to set
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_chmod(const char *path, mode_t mode);
+
+/**
+ * Change current working directory
+ * @param path A C string with the new path
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_chdir(const char *path);
+
+/**
+ * Create directory
+ * @param[in] path A C string with the path of the directory to create
+ * @param[in] mode A mode_t structure with the permission of the directory
+ *                 (as well as all the parent directorie created, if any).
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_mkdir(const char *path, mode_t mode);
+
+
+/**
+ * Copy file to another.
+ * @param to A C string with the new filepath
+ * @param from A C string with the filepath to copy
+ * @return An integer with 0 on success, 1 on failure.
+ */
+int c_copy(const char *to, const char *from);
+
+/**
+ * Remove file from filesytem
+ * @note Also works for directory (if empty)
+ * @param path A C string with the filepath to remove
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_remove(const char *path);
+
+/**
+ * Remove a directory
+ * @param path A C string with the path of the directory to remove.
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_rmdir(const char *path);
+
+/**
+ * Remove a directory and its contents recursively
+ *
+ * This method mimics 'rm -rf' command.
+ * @param path A C string with the path of the directory to remove.
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_rmdir_f(const char *path) ;
+
+/**
+ * Get some file informations
+ * @note If the path cannot be "stat", most of the output parameters are set
+ * to -1.
+ * @param[in] p A C string with the path of a file (or directory)
+ * @param[out] pe An int with the permissions of the path
+ * @param[out] nl An int with the inumber of links
+ * @param[out] ty An int with the type of the file :
+ *    - 0 -> file
+ *    - 1 -> link to a file
+ *    - 2 -> directory
+ *    - 3 -> link to a directory
+ *    - 4 -> Other (fifo, socket, block special, char special ...)
+ * @param[out] ui An int with the user id of the path
+ * @param[out] gi An int with the group id of the path
+ * @param[out] si An int with the size of the path
+ * @param[out] a A C string (20 chars wide, including NULL character) with the
+ * last access date
+ * @param[out] m A C string (20 chars wide, including NULL character) with the
+ * last modification date
+ * @param[out] c A C string (20 chars wide, including NULL character) with the
+ * creation date
+ * @return An integer with 0 on success, last errno on failure
+ */
+int c_fstat(const char *p, int *pe, int *nl, int *ty, int *ui, int *gi,
+            long *si, char a[20], char m[20], char c[20]);
+
+/**
+ * Check if path is accessible
+ * @param[in] path A C string with the path to check
+ * @param[in] perm An integer with the user's permission to check :
+ *   - 0 do not check for permissions
+ *   - 1 check for execute permission
+ *   - 2 check for write permission
+ *   - 4 check for read permission
+ * @return An int with 0 on success, errno on failure
+ */
+int c_access(const char *path, int perm) ;
+
+/**
+ * Create a directory or a file in given path
+ * @param[in] path Path to be created
+ * @param[in] mode Permission of the path
+ * @param[in] astype A boolean with False to create a directory, True to create a file.
+ * @param[in] forced A boolean with True to force creation of intermediate directory
+ * @return 0 on success, -9 on allocation failure, last errno otherwise
+ */
+int c_create(const char* path, mode_t mode, int astype, int forced);
+
+/**
+ * Create a directory or a file in given path
+ * @param[out] rows Number of rows of the current terminal window
+ * @param[out] cols Number of columns of the current terminal window
+ * @return An int with 0 on success, errno on failure. On failure, rows is set
+ * to 80 and cols to 20.
+ */
+int c_termsize(int *rows,int *cols);
+
+/**
+ * Get the current resident set size memory used by the program.
+ */
+size_t c_getCurrentRSS();
+
+/**
+ * Get the peak resident set size memory used by the program.
+ */
+size_t c_getPeakRSS();
+
+/**
+ * Get global memory usage informations.
+ *
+ * Note: The method attempts to read /proc/meminfo. If the file does not exists, all the given output arguments are
+ * set to zero.
+ */
+int c_getSystemMemory(long long int *m_total,long long int *m_available,long long int *m_free);
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_defined.h
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_defined.h	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_defined.h	(revision 3560)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) Jeremie Burgalat (2013-2022)
+ * Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+ *
+ * This file is part of SWIFT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+ /**
+  * @file swift_defined.h
+  * @brief CPP macro definitions files
+  * @details This header defines few CPP symbols and macros that are used
+  * in the library source code.
+  */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/** @def ASSIGN_DTSTR(in,out)
+ *  Performs string assignment
+ *
+ *  This macro definition depends on compiler's support for allocatable string
+ *  in derived type:
+ *    - If it actually supports this feature, the macro defines an allocation
+ *      statement.
+ *    - Otherwise it defines a simple assignment statement.
+ */
+#if ! HAVE_FTNDTSTR
+#define ASSIGN_DTSTR(in,out) out = in
+#else
+#define ASSIGN_DTSTR(in,out) ALLOCATE(out,source=in)
+#endif
+
+/** @def OBJECT(name)
+ *  Derived type declaration
+ *
+ *  This macro definition depends on compiler's support for Bounded procedures
+ *  in derived type (more precisely, Fortran 2003 PROCEDURE keyword support):
+ *    - If it actually supports this feature, the macro defines derived type
+ *      declaration as dummy argument of subroutine/function using CLASS keyword.
+ *    - Otherwise, derived type dummy argument are declared using TYPE keyword.
+ */
+#if ! HAVE_FTNPROC
+#define OBJECT(name) TYPE(name)
+#else
+#define OBJECT(name) CLASS(name)
+#endif
+
+/* Defines SSLEN if needed */
+#ifndef SSLEN
+#define SSLEN 250
+#endif
+
+/* Defines SLLEN if needed */
+#ifndef SLLEN
+#define SLLEN 2500
+#endif
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_errors.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_errors.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_errors.F90	(revision 3560)
@@ -0,0 +1,258 @@
+! Copyright (c) Jeremie Burgalat (2013-2022)
+! Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+!
+! This file is part of SWIFT
+!
+! Permission is hereby granted, free of charge, to any person obtaining a copy of
+! this software and associated documentation files (the "Software"), to deal in
+! the Software without restriction, including without limitation the rights to
+! use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+! the Software, and to permit persons to whom the Software is furnished to do so,
+! subject to the following conditions:
+!
+! The above copyright notice and this permission notice shall be included in all
+! copies or substantial portions of the Software.
+!
+! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+! FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+! COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+!! File:    swift_errors.F90
+!! Summary: Errors handling source file.
+!! Author:  J. Burgalat
+!! Date:    2013-2022
+
+#include "swift_defined.h"
+
+MODULE SWIFT_ERRORS
+  !! Error handler module
+  !!
+  !! This module provides a single derived type, [[error(type)]] which is used in all
+  !! other parts of the library in order to handle errors.
+  USE, INTRINSIC :: ISO_C_BINDING
+  USE, INTRINSIC :: ISO_FORTRAN_ENV, ONLY : stdout=>OUTPUT_UNIT, stderr=>ERROR_UNIT
+
+  IMPLICIT NONE
+
+  PUBLIC
+
+  PRIVATE :: error_equals,error_equals_int,error_differs,error_differs_int, &
+             msg_length
+
+
+  INTEGER, PARAMETER :: msg_length = 250 !! Length of error message.
+
+  TYPE, PUBLIC :: error
+    !! Define an error
+    !!
+    !! The following derived type represents in the simplest way (I believe) an error which
+    !! stores:
+    !!
+    !! - An integer to numerically identify the error
+    !! - A string (250 chars max) with an appropriate error message
+    !! - A bounded procedure to get a string representation of the error (if bounded
+    !!   procedures are supported by the library).
+    !! - internal subroutines for derived type IO WRITE statement (if Derived IO
+    !!   subroutines are supported by the library).
+    !!
+    !! error type comes also with two operators ("==", "/=") to compare error type with
+    !! another one or an integer.
+    !! If an error is not initialized explicitly, then it is set to [[errors(module):noerror(variable)]].
+    CHARACTER(len=msg_length) :: msg = "No error"
+      !! Message associated to the error
+      !! @note
+      !! The message should be short (250 characters maximum) and explicit.
+    INTEGER :: id = 0
+      !! Numerical identifier of the error
+      !! @note
+      !! The error identifier is used to test the equality/inequality of two error objects.
+#if HAVE_FTNPROC
+    CONTAINS
+      PROCEDURE, PUBLIC :: to_string => error_to_string
+        !! Get a string representation of the error
+#endif
+  END TYPE error
+
+  INTERFACE
+    !! Clean subroutine interface
+    SUBROUTINE clean_callback(err)
+      !! A subroutine that may perform cleaning computation(s) before exit
+      IMPORT error
+      IMPLICIT NONE
+      TYPE(error), INTENT(in) :: err
+        !! An error object with the input error
+    END SUBROUTINE clean_callback
+  END INTERFACE
+
+  INTERFACE
+    subroutine abort_() bind(C, name="abort")
+    end subroutine
+  END INTERFACE
+
+  INTERFACE assert
+    !! _Raise_ an assertion.
+    !!
+    !! An assertion can be understood as a development error that should be raised in production mode.
+    MODULE PROCEDURE :: assert_r,assert_w
+  END INTERFACE assert
+
+  !> error equality operator
+  INTERFACE OPERATOR(==)
+    MODULE PROCEDURE error_equals, error_equals_int
+  END INTERFACE
+
+  !> error inequality operator
+  INTERFACE OPERATOR(/=)
+    MODULE PROCEDURE error_differs, error_differs_int
+  END INTERFACE
+
+  !> The no error error !
+  TYPE(error), PUBLIC, PARAMETER :: noerror = error("No error",0)
+
+  CONTAINS
+
+!===============================================================================
+! error TYPE RELATED METHODS
+!===============================================================================
+
+  FUNCTION error_equals(this, other) RESULT(res)
+    !! Check if two error objects are equivalent
+    TYPE(error), INTENT(in) :: this, & !! The first error object to compare
+                               other   !! The second error object to compare
+    LOGICAL :: res                     !! .true. if __this__ and __other__ identifiers are the same, .false. otherwise
+    res = (this%id == other%id)
+    RETURN
+  END FUNCTION error_equals
+
+  FUNCTION error_equals_int(this, id) RESULT(res)
+    !! Check if an error id is equal to a given integer
+    TYPE(error), INTENT(in) :: this !! An error object reference
+    INTEGER, INTENT(in)     :: id   !! An integer to compare to __this__ identifier
+    LOGICAL :: res                  !! .true. if __this__ identifier and __id__ have the same value, .false. otherwise
+    res = (this%id == id)
+    RETURN
+  END FUNCTION error_equals_int
+
+  FUNCTION error_differs(this, other) RESULT(res)
+    !! Check if two error objects are different
+    TYPE(error), INTENT(in) :: this, & !! The first error object to compare
+                               other   !! The second error object to compare
+    LOGICAL :: res                     !! .false. if __this__ and __other__ identifiers are the same, .true. otherwise
+    res = (this%id /= other%id)
+    RETURN
+  END FUNCTION error_differs
+
+  FUNCTION error_differs_int(this, id) RESULT(res)
+    !! Check if an error id is different from a given integer
+    TYPE(error), INTENT(in) :: this !! An error object reference
+    INTEGER, INTENT(in)     :: id   !! An integer to compare to __this__ identifier
+    LOGICAL :: res                  !! .false. if __this__ identifier and __id__ have the same value, .true. otherwise
+    res = (this%id /= id)
+    RETURN
+  END FUNCTION error_differs_int
+
+  FUNCTION error_to_string(this,progname,as_warning) RESULT(str)
+    !! (simple) String representation of the error
+    !!
+    !! The function returns a very simple formatted string with the error.
+    OBJECT(error), INTENT(in)              :: this
+      !! An error object reference
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: progname
+      !! An optional string with the name of the program
+    LOGICAL, INTENT(in), OPTIONAL          :: as_warning
+      !! An optional boolean flag to print the message as warning rather than as error (default to .false.).
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! An allocatable string with the string representation of the error
+    CHARACTER(len=:), ALLOCATABLE :: pref
+    pref = "error: "
+    IF (PRESENT(as_warning)) THEN ; IF (as_warning) pref = "warning: " ; ENDIF
+    IF (PRESENT(progname)) THEN
+      IF (LEN_TRIM(progname) /=0) THEN
+        str = TRIM(progname)//': '//pref//TRIM(this%msg)
+      ELSE
+        str = pref//TRIM(this%msg)
+      ENDIF
+    ELSE
+      str = pref//TRIM(this%msg)
+    ENDIF
+    RETURN
+  END FUNCTION error_to_string
+
+  SUBROUTINE aborting(err)
+    !! Abort the program with specific exit code
+    !!
+    !! The method prints the message of the given error object and
+    !! stops the program using exit() subroutine.
+    TYPE(error), INTENT(in) :: err
+      !! An error object
+    IF (err /= 0) THEN
+      WRITE(*,'(a)') error_to_string(err)
+      CALL EXIT(err%id)
+    ENDIF
+  END SUBROUTINE aborting
+
+  SUBROUTINE assert_r(test,reason)
+    !! _Raise_ an assertion.
+    !!
+    !! The method raises an assertion and stops the execution if __test__ is .false.
+    !!
+    !! @note
+    !! If ISO_C_BINDING module is available, the method calls the method abort from the C standard library. Doing so,
+    !! developer is able to debug the source code by getting the backtrace of the execution.
+    !! In other situation, the method simply uses the Fortran STOP statement which makes its usage... useless.
+   LOGICAL, INTENT(in)          :: test
+     !! Expression to test.
+   CHARACTER(len=*), INTENT(in) :: reason
+     !! Optional assertion reason.
+   IF (.NOT.test) THEN
+     WRITE(stderr,'(a)') "assertion: "//reason
+     call abort_()
+   ENDIF
+  END SUBROUTINE assert_r
+
+  SUBROUTINE assert_w(test,where,reason)
+    !! _Raise_ an assertion.
+    !!
+    !! The method raises an assertion and stops the execution if __test__ is .false.
+    !!
+    !! See [[errors(module):assert_r(subroutine)]] remark.
+    LOGICAL, INTENT(in)         :: test
+     !! Expression to test.
+   CHARACTER(len=*), INTENT(in) :: where
+     !! Optional _location_ of the assertion.
+   CHARACTER(len=*), INTENT(in) :: reason
+     !! Optional assertion reason.
+   IF (.NOT.test) THEN
+     WRITE(stderr,'(a)') "assertion in "//where//": "//reason
+     call abort_()
+   ENDIF
+  END SUBROUTINE assert_w
+
+  FUNCTION free_lun() RESULT(lu)
+    !> Get the first free logical unit
+    !!
+    !! The function loops from 7 to 9999 and returns the first free logical unit.
+    !! @note
+    !! According to Fortran standard, the maximum value for a lun is processor
+    !! dependent. I just assume that [7,9999] is a valid range and I believe that
+    !! 9992 files to be opened is far enough for any program !
+    !! @note
+    !! If you intend to use loggers object from this library, you should keep in
+    !! mind that loggers open files with the first free logical unit. Consequently
+    !! if you need to perform I/O operations you should use this function to get a
+    !! free lun instead of just randomly set a lun !
+    INTEGER :: lu
+      !! First free logical unit in the range [7,9999]  or -1 if no lun is available
+    INTEGER, PARAMETER :: mxlu = 9999
+    LOGICAL :: notfree
+    lu = 6 ; notfree = .true.
+    DO WHILE(notfree.AND.lu<=mxlu)
+      lu=lu+1 ; INQUIRE(unit=lu,OPENED=notfree)
+    ENDDO
+    IF (lu >= mxlu) lu = -1
+  END FUNCTION free_lun
+
+END MODULE SWIFT_ERRORS
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_fsystem.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_fsystem.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_fsystem.F90	(revision 3560)
@@ -0,0 +1,1282 @@
+! Copyright (c) Jeremie Burgalat (2013-2022)
+! Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+!
+! This file is part of SWIFT
+!
+! Permission is hereby granted, free of charge, to any person obtaining a copy of
+! this software and associated documentation files (the "Software"), to deal in
+! the Software without restriction, including without limitation the rights to
+! use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+! the Software, and to permit persons to whom the Software is furnished to do so,
+! subject to the following conditions:
+!
+! The above copyright notice and this permission notice shall be included in all
+! copies or substantial portions of the Software.
+!
+! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+! FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+! COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+!! File:    swift_fsystem.F90
+!! Summary: File system methods source file.
+!! Author:  J. Burgalat
+!! Date:    2013-2022
+
+#include "swift_defined.h"
+
+MODULE SWIFT_FSYSTEM
+  !! File system methods module
+  USE, INTRINSIC :: ISO_C_BINDING
+  USE SWIFT_ERRORS
+  IMPLICIT NONE
+
+  PUBLIC
+
+  PRIVATE :: get_umask
+  PRIVATE :: c2t
+
+  INTEGER, PARAMETER :: MAX_PATH = 512 !! Maximum length of a path
+
+  TYPE, PUBLIC :: chrono
+    !! Define a simple chronometer
+    !!
+    !! This object can be used to get an approximation of the execution of some piece of code.
+    REAL(kind=8), PRIVATE    :: cpu_start = 0d0
+      !! Starting CPU time
+    INTEGER(kind=8), PRIVATE :: clock_start = 0d0
+      !! Starting clock time
+    LOGICAL, PRIVATE         :: on_run = .false.
+      !! Chrono running state.
+#if HAVE_FTNPROC
+    CONTAINS
+      PROCEDURE :: is_running => chrono_is_running
+      PROCEDURE :: start      => chrono_start
+      PROCEDURE :: stop       => chrono_stop
+      PROCEDURE :: reset      => chrono_reset
+      PROCEDURE :: get        => chrono_get
+#endif
+  END TYPE chrono
+
+#ifndef FORD_DOC
+  ! C interfaces
+  INTERFACE
+    FUNCTION strlen_c(s) RESULT(length) bind(C,name="strlen")
+      !! Get length of C-string up to (but not including) the terminator
+      IMPORT C_PTR, C_SIZE_T
+      TYPE(C_PTR), INTENT(in), VALUE :: s !! C string (a C_PTR type)
+      INTEGER(kind=C_SIZE_T) :: length    !! An integer with the size of the string.
+    END FUNCTION strlen_c
+
+    SUBROUTINE free_c(ptr) bind(C,name="free")
+      !! Free memory used by a C pointer
+      IMPORT C_PTR
+      TYPE(C_PTR), INTENT(in), VALUE :: ptr !! TYPE(C_PTR) object with the underlying C pointer to free
+    END SUBROUTINE free_c
+
+    FUNCTION errno_c() BIND(C,name="c_get_errno")
+      !! Get last error numero
+      IMPORT C_INT
+      INTEGER(kind=C_INT) :: errno_c !! Last errno
+    END FUNCTION errno_c
+
+    FUNCTION usleep_c(usec) BIND(C,name="usleep")
+      !! (attemps to) Sleep for a given number of microseconds
+      IMPORT C_INT
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: usec !! Number of microseconds to sleep
+      INTEGER(kind=C_INT) :: usleep_c !! An integer with 0 on success, last errno otherwise
+    END FUNCTION usleep_c
+
+    FUNCTION getgid_c() BIND(C, name="getgid")
+      !! Get Group ID
+      IMPORT C_INT
+      INTEGER(kind=C_INT) :: getgid_c !! Group identifier
+    END FUNCTION getgid_c
+
+    FUNCTION getpid_c() BIND(C, name="getpid")
+      !! Get Process ID
+      IMPORT C_INT
+      INTEGER(kind=C_INT) :: getpid_c !! Current process identifier
+    END FUNCTION getpid_c
+
+    FUNCTION getuid_c() BIND(C, name="getuid")
+      !! Get User ID
+      IMPORT C_INT
+      INTEGER(kind=C_INT) :: getuid_c !! User identifier
+    END FUNCTION getuid_c
+
+    FUNCTION umask_c() BIND(C,name="c_umask")
+      !! Get the current umask of the session
+      IMPORT C_INT
+      INTEGER(kind=C_INT) :: umask_c !! Current umask value in decimal system
+    END FUNCTION umask_c
+
+    FUNCTION access_c(path,perm) BIND(C,name="c_access")
+      !! Check if path is accessible for current user
+      IMPORT c_char, C_INT
+      CHARACTER(len=c_char), INTENT(in)      :: path(*)  !! Path to check
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: perm     !! User's permission to check
+      INTEGER(kind=C_INT)                    :: access_c !! 0 on success, last errno on failure
+    END FUNCTION access_c
+
+    FUNCTION create_c(path,mode,asfile,forced) BIND(C,name="c_create")
+      !! Create a directory or a file in given path
+      IMPORT c_char, C_INT
+      CHARACTER(len=c_char), INTENT(in)      :: path(*)   !! Path to create
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: mode,   & !! Decimal permission of the path
+                                                asfile, & !! 0 to create a directory, any other value to create file
+                                                forced    !! non-zero value to force the creation of intermediate directories
+      INTEGER(kind=C_INT)                    :: create_c  !! 0 on success, last errno otherwise
+    END FUNCTION create_c
+
+    FUNCTION uname_c(uid) BIND(C, name="c_uname")
+      !! Get the name of the given user id
+      IMPORT C_INT, c_ptr
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: uid     !! User id
+      TYPE(C_PTR)                            :: uname_c !! C_PTR to the underlying char* pointer storing user name
+    END FUNCTION uname_c
+
+    FUNCTION gname_c(gid) BIND(C, name="c_gname")
+      !! Get the name of the given group id
+      IMPORT C_INT, c_ptr
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: gid     !! Group id
+      TYPE(C_PTR)                            :: gname_c !! C_PTR to the underlying char* pointer storing group name
+    END FUNCTION gname_c
+
+    FUNCTION dirname_c(path) BIND(C,name="c_dirname")
+      !! Get the directory name of the path
+      IMPORT c_char, c_ptr
+      CHARACTER(kind=c_char), INTENT(in)  :: path(*)   !! Input path
+      TYPE(C_PTR)                         :: dirname_c !! C_PTR to the underlying char* pointer storing dirname
+    END FUNCTION dirname_c
+
+    FUNCTION basename_c(path) BIND(C,name="c_basename")
+      !! Get the basename of the path
+      IMPORT c_char, c_ptr
+      CHARACTER(kind=c_char), INTENT(in)  :: path(*)    !! Input path
+      TYPE(C_PTR)                         :: basename_c !! C_PTR to the underlying char* pointer sotring basename
+    END FUNCTION basename_c
+
+    FUNCTION getcwd_c() BIND(C,name="c_getcwd")
+      !! Get the current working directory
+      IMPORT c_ptr
+      TYPE(C_PTR) :: getcwd_c !! C_PTR to the underlying char* pointer storing current working directory
+    END FUNCTION getcwd_c
+
+    FUNCTION realpath_c(path) BIND(C, name="c_realpath")
+      !! Get the real path from given path
+      IMPORT c_char, c_ptr
+      CHARACTER(kind=c_char), INTENT(in)  :: path(*)    !! The path to expand
+      TYPE(C_PTR)                         :: realpath_c !! C_PTR to the underlying char* pointer storing realpath
+    END FUNCTION realpath_c
+
+    FUNCTION relpath_c(fname,reldir) BIND(C, name="c_relpath")
+      !! Get the relative path of path from another
+      IMPORT c_char, c_ptr
+      CHARACTER(kind=c_char), INTENT(in) :: fname(*), & !! Path to process
+                                            reldir(*)   !! New base path
+      TYPE(C_PTR)                        :: relpath_c   !! C_PTR to the underlying char* pointer storing relative path
+    END FUNCTION
+
+    FUNCTION rename_c(input,output) BIND(C,name="c_rename")
+      !! Rename a path
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in) :: input(*)  !! Path to rename
+      CHARACTER(kind=c_char), INTENT(in) :: output(*) !! New name of the path
+      INTEGER(kind=C_INT)                :: rename_c  !! 0 on success, last errno on failure
+    END FUNCTION rename_c
+
+    FUNCTION chmod_c(path,mode) BIND(C,name="c_chmod")
+      !! Change file/directory permissions
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in)     :: path(*) !! Path to modify
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: mode    !! New decimal permissions of the path to set
+      INTEGER(kind=C_INT)                    :: chmod_c !! 0 on success, last errno on failure
+    END FUNCTION chmod_c
+
+    FUNCTION chdir_c(new) BIND(C,name="c_chdir")
+      !! Change current directory
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in)  :: new(*)  !! Path of the new working directory
+      INTEGER(kind=C_INT)                 :: chdir_c !! 0 on success, last errno on failure
+    END FUNCTION chdir_c
+
+    FUNCTION mkdir_c(dirname,mode) BIND(C,name="c_mkdir")
+      !! Create directory
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in)     :: dirname(*) !! Path of the directory to create
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: mode       !! Decimal permission to set
+      INTEGER(kind=C_INT)                    :: mkdir_c    !! 0 on success, last errno on failure
+    END FUNCTION mkdir_c
+
+    FUNCTION mkdirp_c(dirname,mode) BIND(C,name="c_mkdirp")
+      !! Create directory recursively
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in)     :: dirname(*) !! Path of the directory to create
+      INTEGER(kind=C_INT), INTENT(in), VALUE :: mode       !! Decimal permission to set
+      INTEGER(kind=C_INT)                    :: mkdirp_c   !! 0 on success, last errno on failure
+    END FUNCTION mkdirp_c
+
+    FUNCTION copy_c(to,from) BIND(C,name="c_copy")
+      !! Copy a file.
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in)  :: to(*)    !! Destination path.
+      CHARACTER(kind=c_char), INTENT(in)  :: from(*)  !! Input file path to copy.
+      INTEGER(kind=C_INT)                 :: copy_c !! 0 on success, 1 on failure.
+    END FUNCTION copy_c
+
+    FUNCTION remove_c(path) BIND(C,name="c_remove")
+      !! Remove a file (or a directory) from the filesystem
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in)  :: path(*)  !! Path to delete
+      INTEGER(kind=C_INT)                 :: remove_c !! 0 on success, last errno on failure
+    END FUNCTION remove_c
+
+    FUNCTION rmdir_c(dirpath) BIND(C,name="c_rmdir")
+      !! Remove empty directory
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in) :: dirpath(*) !! Directory to delete
+      INTEGER(kind=C_INT)                :: rmdir_c    !! 0 on success, last errno on failure
+    END FUNCTION rmdir_c
+
+    FUNCTION rmdirf_c(dirpath) BIND(C,name="c_rmdir_f")
+      !! Remove directory (forced)
+      IMPORT c_char, C_INT
+      CHARACTER(kind=c_char), INTENT(in) :: dirpath(*) !! Directory to delete
+      INTEGER(kind=C_INT)                :: rmdirf_c   !! 0 on success, last errno on failure
+    END FUNCTION rmdirf_c
+
+    FUNCTION fstat_c(pa, pe, ln, ty, ui, gi, si, at, mt, ct) BIND(C, name='c_fstat')
+      !! Get file informations
+      IMPORT c_char, c_long, C_INT
+      CHARACTER(kind=c_char), INTENT(in)  :: pa(*)   !! Path of the file
+      INTEGER(kind=C_INT), INTENT(out)    :: pe      !! Decimal permission of the path
+      INTEGER(kind=C_INT), INTENT(out)    :: ln      !! Number of links
+      INTEGER(kind=C_INT), INTENT(out)    :: ty      !! Type of the path
+      INTEGER(kind=C_INT), INTENT(out)    :: ui      !! User ID of the path
+      INTEGER(kind=C_INT), INTENT(out)    :: gi      !! Group ID of the path
+      INTEGER(kind=c_long), INTENT(out)   :: si      !! Size of the path
+      CHARACTER(kind=c_char), INTENT(out) :: at(20)  !! Last access date
+      CHARACTER(kind=c_char), INTENT(out) :: mt(20)  !! Last modification date
+      CHARACTER(kind=c_char), INTENT(out) :: ct(20)  !! Creation date
+      INTEGER(kind=C_INT)                 :: fstat_c !! 0 on success, last errno on failure
+    END FUNCTION fstat_c
+
+    FUNCTION termsize_c(r,c) BIND(C, name='c_termsize')
+      !! Get terminal window size
+      IMPORT C_INT
+      INTEGER(kind=C_INT), INTENT(out) :: r, &       !! Number of rows
+                                          c          !! Number of columns
+      INTEGER(kind=C_INT)              :: termsize_c !! 0 on success, last errno on failure
+    END FUNCTION termsize_c
+
+    FUNCTION getCurrentRSS_c() BIND(C, name="c_getCurrentRSS")
+      !! Get the current resident set size memory in bytes.
+      IMPORT  C_SIZE_T
+      INTEGER(kind=C_SIZE_T) :: getCurrentRSS_c !! Current resident set size in bytes (0 if not available).
+    END FUNCTION getCurrentRSS_c
+
+    FUNCTION getPeakRSS_c() BIND(C, name="c_getPeakRSS")
+      !! Get the peak resident set size memory in bytes.
+      IMPORT  C_SIZE_T
+      INTEGER(kind=C_SIZE_T) :: getPeakRSS_c !! Peak resident set size in bytes (0 if not available).
+    END FUNCTION getPeakRSS_c
+
+    FUNCTION getSystemMemory_c(total,avail,free) BIND(C, name='c_getSystemMemory')
+      !! Get global memory informations.
+      IMPORT C_LONG_LONG,C_INT
+      INTEGER(kind=C_LONG_LONG), INTENT(out) :: total             !! Total available memory.
+      INTEGER(kind=C_LONG_LONG), INTENT(out) :: avail             !! Current available memory.
+      INTEGER(kind=C_LONG_LONG), INTENT(out) :: free              !! Current free memory.
+      INTEGER(kind=C_INT)                    :: getSystemMemory_c !! status, 0 on success, 1 otherwise.
+    END FUNCTION getSystemMemory_c
+  END INTERFACE
+#endif
+
+  CONTAINS
+
+  FUNCTION fstring(string) RESULT(str)
+    !! Convert C string to  Fortran string
+    !!
+    !! The method copies the input C string up to the last C_NULL_CHAR found (not including it),
+    !! and returns the converted Fortran string.
+    !! All other C_NULL_CHAR found in the C string are removed.
+    CHARACTER(len=*), INTENT(in) :: string !! A string from C
+    CHARACTER(len=:), ALLOCATABLE :: str   !! Converted fortran string
+    INTEGER :: i,idx
+    str = ""
+    idx = INDEX(string,C_NULL_CHAR,.true.)
+    IF (idx == 0) THEN
+      str = string
+    ELSE
+      DO i=1,idx-1
+        IF (string(i:i) /= C_NULL_CHAR) str = str//string(i:i)
+      ENDDO
+    ENDIF
+    str = TRIM(str)
+  END FUNCTION fstring
+
+  FUNCTION cstr2fstr(cstr) RESULT(fstr)
+    !! Get a Fortran (allocatable) string from a C string
+    !!
+    !! The method build the fortran string from a TYPE(C_PTR) object that represent a
+    !! C char\* pointer string.
+    !! @note
+    !! If __cstr__ is not allocated (i.e. the C_PTR is not associated) or if it is set
+    !! to a C empty string (i.e. '\0') then the method returns an empty string.
+    !! @attention
+    !! The method does not free the underlying C string and it should be free using
+    !! the subroutine free_c(_cstr_).
+    TYPE(C_PTR), INTENT(in) :: cstr
+      !! A TYPE(C_PTR) that represent the pointer to the C char array.
+    CHARACTER(len=:), ALLOCATABLE :: fstr
+      !! An allocatable Fortran string with the content of the input char array.
+    CHARACTER(len=1,kind=C_CHAR), DIMENSION(:), POINTER :: pchars
+    INTEGER                                             :: i,length
+    IF (.NOT.C_ASSOCIATED(cstr)) THEN
+      fstr = ""
+      RETURN
+    ENDIF
+    length = INT(strlen_c(cstr), kind=4)
+    IF (length ==0) THEN
+      fstr = ""
+      RETURN
+    ENDIF
+    CALL C_F_POINTER(cstr,pchars,(/length/))
+    ALLOCATE(CHARACTER(len=length) :: fstr)
+    DO i=1,length
+      fstr(i:i) = pchars(i)
+    ENDDO
+  END FUNCTION cstr2fstr
+
+
+  FUNCTION cstring(string) RESULT(str)
+    !> convert Fortran string to cstring
+    !!
+    !! The method returns a copy of the input string suitable for C functions argument.
+    !! @note
+    !! Input string is trimmed during computations
+    CHARACTER(len=*), INTENT(in) :: string
+      !! A fortran string
+    CHARACTER(len=:,kind=C_CHAR), ALLOCATABLE :: str
+      !! Same string as __string__ except that C_NULL_CHAR is appended at the end
+    INTEGER :: slen
+    slen = LEN_TRIM(string)
+    ALLOCATE(CHARACTER(len=slen+1,kind=C_CHAR) :: str)
+    str(:slen) = TRIM(string) ; str(slen+1:slen+1) = C_NULL_CHAR
+  END FUNCTION cstring
+
+!===============================================================================
+! C WRAPPER FUNCTIONS/SUBROUTINES
+!===============================================================================
+
+  FUNCTION fs_getgid() RESULT(ret)
+    !! Get Group ID
+    INTEGER(kind=4) :: ret !! An integer with the group identifier
+    ret = INT(getgid_c(),kind=4)
+    RETURN
+  END FUNCTION fs_getgid
+
+  FUNCTION fs_getpid() RESULT(ret)
+    !! Get Process ID
+    INTEGER(kind=4) :: ret !! An integer with the current process identifier
+    ret = INT(getpid_c(),kind=4)
+    RETURN
+  END FUNCTION fs_getpid
+
+  FUNCTION fs_getuid() RESULT(ret)
+    !! Get User ID
+    INTEGER(kind=4) :: ret !! An integer with the user identifier
+    ret = INT(getuid_c(),kind=4)
+    RETURN
+  END FUNCTION fs_getuid
+
+  FUNCTION fs_gname(gid) RESULT(gname)
+    !! Get a group name from a group id
+    INTEGER, INTENT(in) :: gid             !! User id to check
+    CHARACTER(len=:), ALLOCATABLE :: gname !! A string with the name of the group id
+    TYPE(C_PTR) :: zname
+    zname = gname_c(gid)
+    IF (.NOT.C_ASSOCIATED(zname)) THEN
+      gname = ""
+    ELSE
+      gname = cstr2fstr(zname)
+    ENDIF
+    CALL free_c(zname)
+  END FUNCTION fs_gname
+
+  FUNCTION fs_uname(uid) RESULT(uname)
+    !! Get a user name from a user id
+    INTEGER, INTENT(in) :: uid             !! User id to check
+    CHARACTER(len=:), ALLOCATABLE :: uname !! A string with the name of the user id
+    TYPE(C_PTR) :: zname
+    zname = gname_c(uid)
+    IF (.NOT.C_ASSOCIATED(zname)) THEN
+      uname = ""
+    ELSE
+      uname = cstr2fstr(zname)
+    ENDIF
+    CALL free_c(zname)
+  END FUNCTION fs_uname
+
+  FUNCTION fs_dirname(path)  RESULT(opath)
+    !! Get the parent directory path of the given path
+    CHARACTER(len=*), INTENT(in)  :: path
+      !! A string with a (valid) path
+    CHARACTER(len=:), ALLOCATABLE :: opath
+      !! A Fortran allocated string with the parent directory path or an empty string if method fails
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    TYPE(C_PTR) :: zpath
+    IF (LEN_TRIM(path) == 0) THEN
+      opath = ""
+      RETURN
+    ENDIF
+    cpath = cstring(ADJUSTL(path))
+    zpath = dirname_c(cpath)
+    IF (.NOT.C_ASSOCIATED(zpath)) THEN
+      opath = ""
+    ELSE
+      opath = cstr2fstr(zpath)
+    ENDIF
+    CALL free_c(zpath)
+  END FUNCTION fs_dirname
+
+  FUNCTION fs_basename(path) RESULT(opath)
+    !! Get the base name of the path
+    CHARACTER(len=*), INTENT(in)  :: path
+      !! A string with a (valid) path
+    CHARACTER(len=:), ALLOCATABLE :: opath
+      !! The basename of the path or an empty string if method fails
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    TYPE(C_PTR) :: zpath
+    IF (LEN_TRIM(path) == 0) THEN
+      opath = ""
+      RETURN
+    ENDIF
+    cpath = cstring(ADJUSTL(path))
+    zpath = basename_c(cpath)
+    IF (.NOT.C_ASSOCIATED(zpath)) THEN
+      opath = ""
+    ELSE
+      opath = cstr2fstr(zpath)
+    ENDIF
+    CALL free_c(zpath)
+  END FUNCTION fs_basename
+
+  FUNCTION fs_realpath(path) RESULT(opath)
+    !! Get the real path of the path
+    !!
+    !! The method computes the absolute path of the given path using C realpath function.
+    !! @note
+    !! If the input path is empty then current working directory is returned.
+    CHARACTER(len=*), INTENT(in)  :: path
+      !! A string with a (valid) path
+    CHARACTER(len=:), ALLOCATABLE :: opath
+      !! The absolute of the path or an empty string if method fails
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    TYPE(C_PTR) :: zpath
+    cpath = cstring(ADJUSTL(path))
+    zpath = realpath_c(cpath)
+    IF (.NOT.C_ASSOCIATED(zpath)) THEN
+      opath = ""
+    ELSE
+      opath = cstr2fstr(zpath)
+    ENDIF
+    CALL free_c(zpath)
+  END FUNCTION fs_realpath
+
+  FUNCTION fs_relpath(path,reldir) RESULT(res)
+    !! Get the relative representation of two paths
+    !!
+    !! The method computes the relative representation of __path__ from __reldir__ if possible.
+    !! If no common prefix is found, the method returns __path__.
+    CHARACTER(len=*), INTENT(in) :: path, & !! Path to be computed relative to reldir
+                                    reldir  !! A directory path from which output should be relative to
+    CHARACTER(len=:), ALLOCATABLE :: res    !! An allocated string with the resulting path
+    CHARACTER(len=:), ALLOCATABLE :: cpath1,cpath2
+    TYPE(C_PTR) :: zpath
+    cpath1 = cstring(ADJUSTL(path))
+    cpath2 = cstring(ADJUSTL(reldir))
+    zpath = relpath_c(cpath1,cpath2)
+    IF (.NOT.C_ASSOCIATED(zpath)) THEN
+      res = TRIM(ADJUSTL(path))
+    ELSE
+      res = cstr2fstr(zpath)
+    ENDIF
+    CALL free_c(zpath)
+  END FUNCTION fs_relpath
+
+  FUNCTION fs_getcwd() RESULT(path)
+    !! Get the current working directory
+    CHARACTER(len=:), ALLOCATABLE :: path
+      !! The current working directory or an empty string if method fails
+    TYPE(C_PTR) :: zpath
+    zpath = getcwd_c()
+    IF (C_ASSOCIATED(zpath)) THEN
+      path = cstr2fstr(zpath)
+    ELSE
+      path = ""
+    ENDIF
+    CALL free_c(zpath)
+    RETURN
+  END FUNCTION fs_getcwd
+
+  FUNCTION fs_copy(input,output) RESULT(ret)
+    !! Copy input file into output file.
+    CHARACTER(len=*), INTENT(in)  :: input  !! Input file path to copy.
+    CHARACTER(len=*), INTENT(in)  :: output !! Output file path destination.
+    LOGICAL :: ret                          !! True on success, false otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: cpath1,cpath2
+
+    IF (LEN_TRIM(input) == 0 .OR. LEN_TRIM(output) == 0 .OR. input == output) THEN
+      ret = .false.
+    ELSE
+      cpath1 = cstring(ADJUSTL(output))
+      cpath2 = cstring(ADJUSTL(input))
+      ret = INT(copy_c(cpath1,cpath2)) == 0
+    ENDIF
+    RETURN
+  END FUNCTION fs_copy
+
+  FUNCTION fs_remove(path) RESULT(ret)
+    !! Delete the file/directory pointed by the given path
+    CHARACTER(len=*), INTENT(in)  :: path !! A string with the (valid) file path to delete
+    LOGICAL :: ret                        !! True on success, false otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.
+    ELSE
+      cpath = cstring(ADJUSTL(path))
+      ret = INT(remove_c(cpath)) == 0
+    ENDIF
+    RETURN
+  END FUNCTION fs_remove
+
+  FUNCTION fs_rename(old, new) RESULT(ret)
+    !! Rename a path
+    CHARACTER(len=*), INTENT(in) :: old, & !! A string with the (valid) path to rename
+                                    new    !! A string with the new name of the path
+    LOGICAL :: ret                         !! True on success, false otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: cpath1,cpath2
+    IF (LEN_TRIM(old) == 0.OR.LEN_TRIM(new) == 0) THEN
+      ret = .false.
+    ELSE
+      cpath1 = cstring(ADJUSTL(old))
+      cpath2 = cstring(ADJUSTL(new))
+      ret = INT(rename_c(cpath1,cpath2)) == 0
+    ENDIF
+    RETURN
+  END FUNCTION fs_rename
+
+  FUNCTION fs_chmod(path, mode) RESULT(ret)
+    !! Change file/directory permissions
+    CHARACTER(len=*), INTENT(in) :: path !! Path to modify
+    INTEGER, INTENT(in)          :: mode !! New octal permissions of the file
+    LOGICAL  :: ret                      !! True on success, false otherwise.
+    INTEGER(kind=C_INT) :: zmode
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.
+    ELSE
+      zmode = INT(oct_2_dec(mode),kind=C_INT)
+      cpath = cstring(ADJUSTL(path))
+      ret = INT(chmod_c(cpath, zmode)) == 0
+    ENDIF
+    RETURN
+  END FUNCTION fs_chmod
+
+  FUNCTION fs_chdir(path) RESULT(ret)
+    !! Change current working directory
+    CHARACTER(len=*), INTENT(in) :: path !! Path of the new working directory
+    LOGICAL :: ret                       !! True on success, false otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.
+    ELSE
+      cpath = cstring(ADJUSTL(path))
+      ret = INT(chdir_c(cpath)) == 0
+    ENDIF
+    RETURN
+  END FUNCTION fs_chdir
+
+  FUNCTION fs_mkdir(path, mode, permissive) RESULT(ret)
+    !! Create directory
+    !!
+    !! The method attempts to create a new directory pointed by __path__ with the permission
+    !! given by mode.
+    CHARACTER(len=*), INTENT(in)  :: path
+      !! The path to modify
+    INTEGER, INTENT(in), OPTIONAL :: mode
+      !! Optional octal permission to set for the new directory
+    LOGICAL, INTENT(in), OPTIONAL :: permissive
+      !! Optional boolean with .true. to create intermediate directories in the path
+    LOGICAL :: ret
+      !! True on success, false otherwise.
+    INTEGER :: zmode
+    LOGICAL :: zperm
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.
+    ELSE
+      zmode = oct_2_dec(744)
+      IF (PRESENT(mode)) THEN
+        IF (.NOT.chk_pm(mode)) THEN
+          ret = .false. ; RETURN
+        ENDIF
+        zmode = oct_2_dec(mode)
+      ENDIF
+      cpath = cstring(ADJUSTL(path))
+      zperm = .false. ; IF (PRESENT(permissive)) zperm = permissive
+      IF (zperm) THEN
+        ret = INT(mkdirp_c(cpath,INT(zmode,kind=C_INT))) == 0
+      ELSE
+        ret = INT(mkdir_c(cpath,INT(zmode,kind=C_INT))) == 0
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION fs_mkdir
+
+  FUNCTION fs_rmdir(path,forced) RESULT(ret)
+    !! Remove directory
+    !!
+    !! By default, the function removes an __empty__ directory. If __forced__ is given and set
+    !! to .true. then the function recursively deletes the directory and __ALL__ its content.
+    CHARACTER(len=*), INTENT(in)  :: path
+      !! The path of the directory to delete
+    LOGICAL, INTENT(in), OPTIONAL :: forced
+      !! Optional boolean with @ti{.true.} to remove all contents of the directory.
+    LOGICAL :: ret
+      !! True on success, false otherwise.
+    LOGICAL :: zforce
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.
+    ELSE
+      zforce = .false. ; IF (PRESENT(forced)) zforce = forced
+      cpath = cstring(ADJUSTL(path))
+      IF (.NOT.zforce) THEN
+        ret = INT(rmdir_c(cpath)) == 0
+      ELSE
+        ret = INT(rmdirf_c(cpath)) == 0
+      ENDIF
+    ENDIF
+    RETURN
+  END FUNCTION fs_rmdir
+
+  FUNCTION fs_stat(path,type,perm,nlnks,uid,gid,fsize,atime,mtime,ctime) RESULT(ret)
+    !! Get some informations about a path
+    !!
+    !! The method retrieves various informations about the input path using fstat C function.
+    !! The type of path as returned in __type__ argument is can take the following values:
+    !!
+    !! - 0, a file
+    !! - 1, a link to a file
+    !! - 2, a directory
+    !! - 3, a link to a directory
+    !! - 4, other (fifo, socket, block special, char special...)
+    CHARACTER(len=*), INTENT(in)             :: path     !! Input path
+    INTEGER, INTENT(out), OPTIONAL           :: type,  & !! Optional type of path (see function documentation).
+                                                perm,  & !! Optional permission of the path
+                                                nlnks, & !! Optional number of links to the path
+                                                uid,   & !! Optional user id
+                                                gid      !! Optional group id
+    INTEGER(kind=8), INTENT(out), OPTIONAL   :: fsize    !! Optional file size
+    CHARACTER(len=19), INTENT(out), OPTIONAL :: atime, & !! Optional last access time
+                                                mtime, & !! Optional last modification time
+                                                ctime    !! Optional creation time
+    LOGICAL :: ret                                       !! True on success, false otherwise.
+    INTEGER                       :: ty,pe,ln,ud,gd
+    INTEGER(kind=8)               :: fs
+    CHARACTER(len=:), ALLOCATABLE :: at,mt,ct
+    INTEGER(kind=C_INT)           :: p,l,t,u,g
+    INTEGER(kind=c_long)          :: f
+    CHARACTER(len=20,kind=C_CHAR) :: ta,tm,tc
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.; RETURN
+    ELSE IF (.NOT.(PRESENT(type)  .OR. PRESENT(perm)  .OR. PRESENT(nlnks) .OR. &
+                   PRESENT(uid)   .OR. PRESENT(gid)   .OR. PRESENT(fsize) .OR. &
+                   PRESENT(atime) .OR. PRESENT(mtime) .OR. PRESENT(ctime))) THEN
+      ret = .true.
+    ELSE
+      ! set default values
+      pe=-1 ; ty=-1 ; ud=-1 ; gd=-1 ; fs=-1 ; at="" ; mt="" ; ct=""
+      cpath = cstring(ADJUSTL(path))
+      ret = INT(fstat_c(cpath,p,l,t,u,g,f,ta,tm,tc)) == 0
+      IF (ret) THEN
+        pe=INT(p) ; ln=INT(l) ; ty=INT(t) ; ud=INT(u) ; gd=INT(g)
+        fs=INT(f,kind=8)
+        at = fstring(ta)
+        mt = fstring(tm)
+        ct = fstring(tc)
+      ENDIF
+      IF (PRESENT(type))  type  = ty
+      IF (PRESENT(perm))  perm  = pe
+      IF (PRESENT(nlnks)) nlnks = ln
+      IF (PRESENT(uid))   uid   = ud
+      IF (PRESENT(gid))   gid   = gd
+      IF (PRESENT(fsize)) fsize = fs
+      IF (PRESENT(atime)) atime = at
+      IF (PRESENT(mtime)) mtime = mt
+      IF (PRESENT(ctime)) ctime = ct
+    ENDIF
+    RETURN
+  END FUNCTION fs_stat
+
+  FUNCTION fs_isdir(path) RESULT (ret)
+    !! Check if a path is a directory
+    !!
+    !! The method is just a wrapper of [[fsystem(module):fs_stat(function)]] to get only specific
+    !! information about __path__ type.
+    CHARACTER(len=*), INTENT(in) :: path !! The path to check
+    LOGICAL :: ret                       !! .true. if the path is a directory, .false. otherwise.
+    INTEGER :: ty
+    ret = fs_stat(path,type=ty)
+    ret = ret.AND.(ty==2.or.ty==3)
+    RETURN
+  END FUNCTION fs_isdir
+
+  FUNCTION fs_isfile(path) RESULT (ret)
+    !! Check if a path is a file
+    !!
+    !! The method is just a wrapper of [[fsystem(module):fs_stat(function)]] to get only specific
+    !! information about __path__ type.
+    CHARACTER(len=*), INTENT(in) :: path !! The path to check
+    LOGICAL :: ret                       !! .true. if the path is a file, .false. otherwise.
+    INTEGER :: ty
+    ret=fs_stat(path,type=ty)
+    ret = ret.and.(ty==0.or.ty==1)
+    RETURN
+  END FUNCTION fs_isfile
+
+  FUNCTION fs_islink(path) RESULT (ret)
+    !! Check if a path is a link
+    !!
+    !! The method is just a wrapper of [[fsystem(module):fs_stat(function)]] to get only specific
+    !! information about __path__ type.
+    CHARACTER(len=*), INTENT(in) :: path !! The path to check
+    LOGICAL :: ret                       !! .true. if the path is a link, .false. otherwise.
+    INTEGER :: ty
+    ret=fs_stat(path,type=ty)
+    ret = ret.and.(ty==1.or.ty==3)
+    RETURN
+  END FUNCTION fs_islink
+
+  FUNCTION fs_access(path,permission) RESULT(ret)
+    !! Check if a path is accessible for current user
+    !!
+    !! The method checks if the given path is accessible for the current user. By default,
+    !! it does not check for specific permissions. If __permission__ is given it should be
+    !! an integer between 0 and 7 resulting from the possible combinations:
+    !!
+    !! - 0 : Checks for path existence (default)
+    !! - 1 : Checks for EXECUTE permission
+    !! - 2 : Checks for WRITE permission
+    !! - 4 : Checks for READ permission
+    CHARACTER(len=*), INTENT(in)  :: path       !! Path to check
+    INTEGER, INTENT(in), OPTIONAL :: permission !! Optional permission to check
+    LOGICAL :: ret                              !! True on success, false otherwise.
+    INTEGER(kind=C_INT) :: zp
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    IF (LEN_TRIM(path) == 0) THEN
+      ret = .false.
+    ELSE
+      zp = 0 ; IF (PRESENT(permission)) zp = INT(permission,kind=C_INT)
+      ! Defaults are set in the C function.
+      cpath = cstring(ADJUSTL(path))
+      ret = INT(access_c(cpath,zp)) == 0
+    ENDIF
+    RETURN
+  END FUNCTION fs_access
+
+  FUNCTION fs_split_ext(path, base, ext, absolute) RESULT(ret)
+    !! Split given path into base,extension
+    !!
+    !! The __base__ of a path is conventionnally defined as all characters before the last dot of the path.
+    !! The extension (__ext__) of the path gathers consequently all characters from the last dot to the end
+    !! of the string.
+    !! @note
+    !! If the basename of the path begins by a dot then the path is assumed to be an hidden file (directory).
+    !! __ext__ will then be empty.
+    CHARACTER(len=*), INTENT(in)               :: path     !! Path to split
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: base, &  !! Output base of the path
+                                                  ext      !! Output extension of the path
+    LOGICAL, INTENT(in), OPTIONAL              :: absolute !! .true. to return absolute path
+    LOGICAL                       :: ret                   !! .true. on success, .false. otherwise.
+    LOGICAL                       :: zabs
+    INTEGER                       :: p
+    CHARACTER(len=:), ALLOCATABLE :: d,b,apath
+    base = "" ; ext = ""
+    ret = .false.
+    IF (LEN_TRIM(path) == 0) THEN
+      RETURN
+    ENDIF
+    zabs = .false. ; IF (PRESENT(absolute)) zabs = absolute
+    apath = TRIM(path)
+    IF (zabs) THEN
+      apath = fs_realpath(path) ; IF (LEN_TRIM(apath) == 0) RETURN
+    ENDIF
+    d = fs_dirname(apath) ; IF (LEN_TRIM(d) == 0) RETURN
+    b = fs_basename(apath) ; IF (LEN_TRIM(b) == 0) RETURN
+    p = INDEX(b,".",.true.)
+    ! If dot is set as first char of basename : it's an hidden file
+    IF (p > 1) THEN
+      ext = b(p:) ; base = TRIM(d)//"/"//b(:p-1)
+    ELSE
+      base = TRIM(apath)
+    ENDIF
+    ret = .true.
+    RETURN
+  END FUNCTION fs_split_ext
+
+  FUNCTION fs_create(path, mode, type, permissive) RESULT(ret)
+    !! Create a directory/file
+    !!
+    !! The method creates the file/directory pointed by given __path__.
+    !! If __type__ is not given, the method builds the path as :
+    !!
+    !! -# A file if the basename of the path contains an extension
+    !! -# A directory in any other cases.
+    !!
+    !! Otherwise __type__ should be set to "f" for file or "d" for directory.
+    !!
+    !! Unless __permissive__ is set to .true., the method will fails if intermediate
+    !! directories in the path do not exist.
+    CHARACTER(len=*), INTENT(in)           :: path        !! Path to create
+    INTEGER, INTENT(in), OPTIONAL          :: mode        !! Optional octal permisions to set
+    CHARACTER(len=1), INTENT(in), OPTIONAL :: type        !! Optional type of path to create
+    LOGICAL, INTENT(in), OPTIONAL          :: permissive  !! .true. to create intermediate directories if not existing
+    LOGICAL :: ret                                        !! True on success, false otherwise.
+    INTEGER                       :: zmd,zt,zp
+    CHARACTER(len=:), ALLOCATABLE :: b,e
+    CHARACTER(len=:), ALLOCATABLE :: cpath
+    ret = .false.
+    ! Checking for existence
+    IF (LEN_TRIM(path) == 0) THEN
+      RETURN
+    ELSE IF (fs_access(path)) THEN
+      RETURN
+    ENDIF
+    ! Set type of path
+    IF (PRESENT(type)) THEN
+      IF (.NOT.(type(1:1)=="f".OR.type(1:1)=="d")) THEN
+        RETURN
+      ELSE
+        zt=0 ; IF (type(1:1)=="f") zt = 1
+      ENDIF
+    ELSE
+      IF(.NOT.fs_split_ext(path,b,e)) RETURN
+      zt = 0 ; IF (LEN_TRIM(e) /= 0) zt=1
+    ENDIF
+    ! set permissions according to type
+    IF (zt == 0) THEN
+      zmd = oct_2_dec(777)-get_umask()
+    ELSE
+      zmd = oct_2_dec(666) -get_umask()
+    ENDIF
+    ! Check mode argument if present
+    IF (PRESENT(mode)) THEN
+      IF(.NOT.chk_pm(mode)) THEN
+        ! not a valid permission : We raise an error and abort
+        RETURN
+      ELSE
+        zmd = oct_2_dec(mode)
+      ENDIF
+    ENDIF
+    zp = 0 ; IF(PRESENT(permissive)) THEN ; IF(permissive) zp=1 ; ENDIF
+
+    cpath = cstring(ADJUSTL(path))
+    ret = INT(create_c(cpath,INT(zmd,kind=C_INT),INT(zt,kind=C_INT),INT(zp,kind=C_INT))) == 0
+    RETURN
+  END FUNCTION fs_create
+
+  FUNCTION fs_get_parent(path, n) RESULT(opath)
+    !! Get the nth parent of the given path
+    !!
+    !! The method first resolves the given path using [[fsystem(module):fs_realpath(function)]]
+    !! to get an absolute path.
+    !! @note
+    !! If __n__ is greater than the maximum parent level of the path, "/" is returned.
+    CHARACTER(len=*), INTENT(in)  :: path
+      !! Input path
+    INTEGER, INTENT(in), OPTIONAL :: n
+      !! The level of the parent to get
+    CHARACTER(len=:), ALLOCATABLE :: opath
+      !! The nth parent of the given path, or an empty string if the parent can not be computed
+    CHARACTER(len=:), ALLOCATABLE :: zp
+    INTEGER                       :: i,mx,zl,mzl
+    opath = ""
+    zl = 1 ; IF (PRESENT(n)) zl = MAX(n,1)
+    IF (LEN_TRIM(path) == 0) THEN
+      RETURN
+    ENDIF
+    ! Gets the absolute path
+    zp = fs_realpath(TRIM(ADJUSTL(path)))
+    IF (LEN_TRIM(zp) == 0) RETURN
+    ! removing trailing / (only if it's not the first ^^)
+    mx = LEN_TRIM(zp) ; IF (zp(mx:mx)=="/".AND.mx/=1) zp(mx:mx) = ""
+    ! compute maximum level
+    mzl = 1 ; DO i=1,mx ; IF(zp(i:i) == '/') mzl=mzl+1 ; ENDDO
+    i=0
+    DO
+      mx = INDEX(zp(1:mx),'/',.true.) ; i=i+1
+      IF (mx==0.OR.i>=zl.OR.i>=mzl) EXIT
+      mx = mx - 1
+    ENDDO
+    IF (mx >= 1) THEN
+      opath = zp(1:MAX(1,mx-1))
+    ELSE
+      opath = "/"
+    ENDIF
+    RETURN
+  END FUNCTION fs_get_parent
+
+  SUBROUTINE fs_termsize(row, column)
+    !! Get the current terminal window size
+    !! @attention
+    !! If the program is redirected to a file (and maybe some other device), the C
+    !! function can raise an error. In that case, the default values (20,80) are
+    !! returned by the C function and thus the subroutine !
+    INTEGER, INTENT(out) :: row,   &  !! Number of rows of the window
+                            column    !! Number of columns of the window
+    INTEGER(kind=C_INT) :: r, c, ret
+    ret = termsize_c(r,c)
+    row = INT(r) ; column = INT(c)
+    RETURN
+  END SUBROUTINE fs_termsize
+
+  SUBROUTINE fs_usleep(usec)
+    !! Sleep for a given number of microseconds
+    !! @note
+    !! Currently if C usleep function failed, the system... does not sleep !
+    INTEGER, INTENT(in) :: usec !! The number of microseconds to sleep for
+    INTEGER(kind=C_INT) :: ret
+    ! usleep expects useconds_t (unsigned int) which is given here as a 4-bytes int
+    ret = usleep_c(INT(usec,kind=C_INT))
+  END SUBROUTINE fs_usleep
+
+  SUBROUTINE fs_msleep(msec)
+    !! Sleep for a given number of milliseconds
+    INTEGER, INTENT(in) :: msec !! The number of milliseconds to sleep for
+    CALL fs_usleep(msec*1000)
+  END SUBROUTINE fs_msleep
+
+  FUNCTION fs_get_memory(peak,units) RESULT(mem)
+    !! Get the memory usage of the current process.
+    LOGICAL, INTENT(in), OPTIONAL          :: peak  !! True to retrieve the peak RSS memory, otherwise retrieve the current RSS memory. Default to False.
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: units !! Output units: either 'B' (Bytes),'KB' (Kilo-),'MB' (Mega-),'GB' (Giga-). Default to 'B'.
+    REAL(kind=8)                           :: mem   !! Memory usage.
+    LOGICAL          :: zpeak
+    CHARACTER(len=2) :: zunits
+    zpeak = .false. ; IF (PRESENT(peak)) zpeak = peak
+    zunits = 'B '   ; IF (PRESENT(units)) zunits = units
+    IF (zunits /= 'B' .AND. zunits /= 'KB' .AND. zunits /= 'MB' .AND. zunits /= 'GB') zunits = 'B '
+    IF (zpeak) THEN
+      mem = REAL(getPeakRSS_c(),kind=8)
+    ELSE
+      mem = REAL(getCurrentRSS_c(),kind=8)
+    ENDIF
+    IF (zunits == 'KB') THEN
+      mem = mem / 1024d0
+    ELSE IF (zunits == 'MB') THEN
+      mem = mem / 1048576d0
+    ELSE IF (zunits == 'GB') THEN
+      mem = mem / 1073741824d0
+    ENDIF
+    RETURN
+  END FUNCTION fs_get_memory
+
+  FUNCTION fs_get_system_memory(total,available,free,units) RESULT(ret)
+    !! Get informations about system memory.
+    !!
+    !! If no informations is available, output arguments are set to 0 and the method returns false.
+    REAL(kind=8), INTENT(out), OPTIONAL    :: total      !! Total available memory.
+    REAL(kind=8), INTENT(out), OPTIONAL    :: available  !! Current available memory.
+    REAL(kind=8), INTENT(out), OPTIONAL    :: free       !! Current free memory.
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: units      !! Output units: either 'B' (Bytes),'KB' (Kilo-),'MB' (Mega-),'GB' (Giga-). Default to 'B'.
+    LOGICAL                                :: ret        !! True on success, false otherwise.
+    LOGICAL          :: zpeak
+    CHARACTER(len=2) :: zunits
+    INTEGER(kind=8)  :: ztot,zava,zfre
+
+    zunits = 'B '   ; IF (PRESENT(units)) zunits = units
+    IF (zunits /= 'B' .AND. zunits /= 'KB' .AND. zunits /= 'MB' .AND. zunits /= 'GB') zunits = 'B '
+    ret = INT(getSystemMemory_c(ztot,zava,zfre),kind=4) == 0
+    ztot = ztot * 1024 ; zava = zava * 1024 ; zfre = zfre * 1024
+
+    IF (PRESENT(total))     total     = ztot
+    IF (PRESENT(available)) available = zava
+    IF (PRESENT(free))      free      = zfre
+    IF (.NOT.ret) RETURN
+
+    IF (zunits == 'KB') THEN
+      IF (PRESENT(total))     total     = ztot / 1024d0
+      IF (PRESENT(available)) available = zava / 1024d0
+      IF (PRESENT(free))      free      = zfre / 1024d0
+    ELSE IF (zunits == 'MB') THEN
+      IF (PRESENT(total))     total     = ztot / 1048576d0
+      IF (PRESENT(available)) available = zava / 1048576d0
+      IF (PRESENT(free))      free      = zfre / 1048576d0
+    ELSE IF (zunits == 'GB') THEN
+      IF (PRESENT(total))     total     = ztot / 1073741824d0
+      IF (PRESENT(available)) available = zava / 1073741824d0
+      IF (PRESENT(free))      free      = zfre / 1073741824d0
+    ENDIF
+    RETURN
+  END FUNCTION fs_get_system_memory
+
+
+!===============================================================================
+! MODULE MISCELLANEOUS METHODS
+!===============================================================================
+
+  FUNCTION oct_2_dec(octal) RESULT(res)
+    !> Octal to decimal conversion
+    !!
+    !! The method converts the octal number ranging from 0 to 777 in the decimal system.
+    !! @attention
+    !! If the __octal__ number is out of range then the method returns 384 (600 in octal).
+    INTEGER, INTENT(in) :: octal !! The octal value to convert
+    INTEGER :: res               !! The converted decimal value
+    INTEGER :: o,d,i
+    IF (octal < 0 .OR. octal > 777) THEN
+      res = 384 ; RETURN ! --> 600 in octal : rw-------
+    ENDIF
+    d = 0 ; i = 0 ; o =  octal
+    DO WHILE(o/=0)
+      d=d+mod(o,10)*8**i ; i=i+1 ; o=o/10
+    ENDDO
+    res=d
+    RETURN
+  END FUNCTION oct_2_dec
+
+  FUNCTION dec_2_oct(decimal) RESULT(res)
+    !! Decimal to octal conversion
+    !! The method converts the decimal number ranging from 0 to 511 in the octal system.
+    !! @attention
+    !! If the __decimal__ number is out of range, then it the method returns 600 (384 in decimal).
+    INTEGER, INTENT(in) :: decimal !! The decimal value to convert
+    INTEGER :: res                 !! The converted octal value
+    ! - LOCAL
+    INTEGER :: o,d,i,m
+    IF (decimal < 0 .OR. decimal > 511) THEN
+      res = 600 ;  RETURN ! --> 384 in decimal : rw-------
+    ENDIF
+    o=0 ; d = decimal ; i=0 ; m=0
+    DO WHILE(d/=0)
+      d=d/8 ; m=m+1
+    ENDDO
+    m=m-1 ; d=decimal
+    DO i=0,m
+      o=o+mod(d,8)*10**i ; d=d/8
+    ENDDO
+    res = o
+    RETURN
+  END FUNCTION dec_2_oct
+
+  FUNCTION sp_2_op(str) RESULT(oct)
+    !! Get octal number of string representation's permission
+    CHARACTER(len=3),INTENT(in) :: str !! The permission to convert
+    INTEGER :: oct                     !! Octal value of the string permission on succes, -1 otherwise.
+    oct = -1
+    IF (LEN_TRIM(str) /= 3) RETURN
+    SELECT CASE(str)
+      CASE("---")  ; oct = 0
+      CASE("--x")  ; oct = 1
+      CASE("-w-")  ; oct = 2
+      CASE("-wx")  ; oct = 3
+      CASE("r--")  ; oct = 4
+      CASE("r-x")  ; oct = 5
+      CASE("rw-")  ; oct = 6
+      CASE("rwx")  ; oct = 7
+      CASE DEFAULT
+        oct = -1 ; RETURN
+    END SELECT
+    RETURN
+  END FUNCTION sp_2_op
+
+  FUNCTION op_2_sp(oct) RESULT(str)
+    !! Get string representation of the octal number's permission
+    INTEGER, INTENT(in) :: oct !! Octal number to convert
+    CHARACTER(len=3) :: str    !! String representation of the octal number on succes, 'ukn' otherwise
+    SELECT CASE(oct)
+      CASE(0) ; str="---"
+      CASE(1) ; str="--x"
+      CASE(2) ; str="-w-"
+      CASE(3) ; str="-wx"
+      CASE(4) ; str="r--"
+      CASE(5) ; str="r-x"
+      CASE(6) ; str="rw-"
+      CASE(7) ; str="rwx"
+      CASE DEFAULT
+        str='ukn' ; RETURN
+    END SELECT
+    RETURN
+  END FUNCTION op_2_sp
+
+  FUNCTION str_perm(oct_perm) RESULT(ret)
+    !! Get the string representation of the given permission mask
+    INTEGER, INTENT(in) :: oct_perm !! The octal representation of the permission
+    CHARACTER(len=9) :: ret      !! String representation of the octal number on succes, 'ukn' otherwise
+    INTEGER :: u,g,o
+    IF (.NOT.chk_pm(oct_perm)) THEN
+      ret = "ukn" ; RETURN
+    ENDIF
+    u=int(oct_perm/100) ; g=int((oct_perm-u*100)/10) ; o=int(oct_perm-u*100-g*10)
+    ret(1:3) = op_2_sp(u) ; ret(4:6) = op_2_sp(g) ; ret(7:9) = op_2_sp(o)
+    RETURN
+  END FUNCTION str_perm
+
+  FUNCTION oct_perm(str) RESULT(ret)
+    !! Get the string representation of the given permission mask
+    CHARACTER(len=9), INTENT(in) :: str !! The string representation of the permission
+    INTEGER :: ret                      !! Octal permission on success, -1 otherwise
+    ! - LOCAL
+    INTEGER :: u,g,o
+    u = sp_2_op(str(1:3)) ; g = sp_2_op(str(4:6)) ; o = sp_2_op(str(7:9))
+    IF (u==-1.OR.g==-1.OR.o==-1) THEN
+      ret = -1 ; RETURN
+    ELSE
+      ret = u*100 + g*10 + o
+    ENDIF
+    RETURN
+  END FUNCTION oct_perm
+
+  FUNCTION chk_pm(perm) RESULT(valid)
+    !! Check if the given permission is valid
+    INTEGER, INTENT(in) :: perm !! Octal permission mask
+    LOGICAL :: valid            !! .true. if the permission mask is valid, .false. otherwise
+    INTEGER :: u,g,o
+    u=int(perm/100) ; g=int((perm-u*100)/10) ; o=int(perm-u*100-g*10)
+    valid = (u>=0.AND.u<=7).AND.(g>=0.AND.g<=7).AND.(o>=0.AND.o<=7)
+    RETURN
+  END FUNCTION chk_pm
+
+  FUNCTION get_umask() RESULT(mask)
+    !! Get the umask value of the current session
+    INTEGER :: mask !! Current umask value in decimal system
+    mask = INT(umask_c())
+    RETURN
+  END FUNCTION get_umask
+
+  FUNCTION sz2str(file_size) RESULT(fstr)
+    !! Get a human readable file size
+    INTEGER(kind=8), INTENT(in) :: file_size !! File size (assumed to be bytes)
+    CHARACTER(len=50) :: fstr                !! Size in a human readable format
+    ! - LOCAL
+    INTEGER                                   :: cc
+    REAL(kind=8)                              :: zfs
+    CHARACTER(len=2), DIMENSION(6), PARAMETER :: sn =  &
+                       (/'B ','KB','MB','GB','TB','PB'/)
+    zfs=DBLE(file_size)
+    DO cc=1,size(sn)-1 ; IF (zfs<1024.) EXIT ; zfs=zfs/1024. ; ENDDO
+    IF (MOD(zfs,1.0) == 0) THEN
+      WRITE(fstr,'(I50)') INT(zfs) ; fstr = TRIM(ADJUSTL(fstr))//sn(cc)
+    ELSE
+      WRITE(fstr,'(F50.2)') zfs ; fstr = TRIM(ADJUSTL(fstr))//sn(cc)
+    ENDIF
+    RETURN
+  END FUNCTION sz2str
+
+  FUNCTION chrono_is_running(this) RESULT (ret)
+    !! Get chrono's state.
+    OBJECT(chrono), INTENT(in) :: this !! Chrono object reference.
+    LOGICAL :: ret                    !! Running state.
+    ret = this%on_run
+    RETURN
+  END FUNCTION chrono_is_running
+
+  SUBROUTINE chrono_start(this)
+    !! Start the chrono.
+    !!
+    !! @note
+    !! Calling the method multiple times without explicitly stopping the chrono
+    !! [[chrono(type):stop(bound)]] does nothing (except for the first called).
+    OBJECT(chrono), INTENT(inout) :: this  !! Chrono object reference.
+    IF (.NOT.this%on_run) THEN
+      CALL CPU_TIME(this%cpu_start)
+      CALL SYSTEM_CLOCK(this%clock_start)
+    ENDIF
+    this%on_run = .true.
+  END SUBROUTINE chrono_start
+
+  SUBROUTINE chrono_stop(this)
+    !! Stop the chrono.
+    OBJECT(chrono), INTENT(inout) :: this !! Chrono object reference.
+    REAL(kind=8)    :: ecpu
+    INTEGER(kind=8) :: eclk,nbm,nbr
+    this%on_run = .false.
+  END SUBROUTINE chrono_stop
+
+  SUBROUTINE chrono_reset(this)
+    !! Reset the chrono's internal elapsed times.
+    OBJECT(chrono), INTENT(inout) :: this !! Chrono object reference.
+    CALL CPU_TIME(this%cpu_start)
+    CALL SYSTEM_CLOCK(this%clock_start)
+  END SUBROUTINE chrono_reset
+
+  SUBROUTINE chrono_get(this,cpu,clock,units)
+    !! Get elapsed time since last call of start or reset methods.
+    !!
+    !! The method computes the time elapsed in two ways :
+    !!
+    !! - If the [[fsystem(module):chrono(type)]] is not running, the method retruns 0.
+    !! - Otherwise, elapsed time since the last call of
+    !!   [[chrono(type):start(bound)]] (or [[chrono(type):reset(bound)]]).
+    OBJECT(chrono), INTENT(in)             :: this
+      !! Chrono object reference.
+    REAL(kind=8), INTENT(out), OPTIONAL    :: cpu
+      !! Elapsed cpu time in seconds by default (see units argument).
+    REAL(kind=8), INTENT(out), OPTIONAL    :: clock
+      !! Elapsed system clock time in seconds by default (see units argument).
+    CHARACTER(len=2), INTENT(in), OPTIONAL :: units
+      !! A two characters wide string with the units to convert in. Units should
+      !! be one of the following : 'ms', 's' (default), 'm', 'h' or 'd'.
+    CHARACTER(len=2) :: zu
+    REAL(kind=8)     :: cu, fact
+    INTEGER(kind=8)  :: ck, r, m
+    IF (this%on_run) THEN
+      IF (PRESENT(cpu)) THEN
+        CALL CPU_TIME(cu)
+        cpu = (cu - this%cpu_start)
+      ENDIF
+      IF (PRESENT(clock)) THEN
+        CALL SYSTEM_CLOCK(ck,r,m)
+        clock = c2t(ck,this%clock_start,r,m)
+      ENDIF
+    ELSE
+      IF (PRESENT(cpu))   cpu = 0d0
+      IF (PRESENT(clock)) clock = 0d0
+    ENDIF
+    fact = 1d0
+    zu = 's'
+    IF (PRESENT(units))  THEN
+      zu = units
+      SELECT CASE(zu)
+        CASE ('d') ; fact = 3600d0*24.
+        CASE ('h') ; fact = 3600d0
+        CASE ('m') ; fact = 60d0
+        CASE ('ms') ; fact = 1d-3
+        CASE DEFAULT ; fact = 1d0
+      END SELECT
+    ENDIF
+    IF (PRESENT(cpu)) cpu = cpu / fact
+    IF (PRESENT(clock)) clock = clock / fact
+  END SUBROUTINE chrono_get
+
+  FUNCTION c2t(e,i,r,m) RESULT(time)
+    !! Get the real-time between two clock counts from system_clock.
+    INTEGER(kind=8), INTENT(in) :: e !! Final clock count
+    INTEGER(kind=8), INTENT(in) :: i !! Initial clock count
+    INTEGER(kind=8), INTENT(in) :: r !! Clock count rate
+    INTEGER(kind=8), INTENT(in) :: m !! Maximum Clock count value
+    REAL(kind=8)    :: time          !! Time in seconds
+    INTEGER(kind=8) :: nc
+    nc = e-i ; IF (e < i) nc = nc+m
+    time = REAL(nc,kind=8)/r
+    RETURN
+  END FUNCTION c2t
+END MODULE SWIFT_FSYSTEM
Index: trunk/LMDZ.PLUTO/libf/muphypluto/swift_string_op.F90
===================================================================
--- trunk/LMDZ.PLUTO/libf/muphypluto/swift_string_op.F90	(revision 3560)
+++ trunk/LMDZ.PLUTO/libf/muphypluto/swift_string_op.F90	(revision 3560)
@@ -0,0 +1,2534 @@
+! Copyright (c) Jeremie Burgalat (2013-2022)
+! Contributor: Jeremie Burgalat (jeremie.burgalat@univ-reims.fr).
+!
+! This file is part of SWIFT
+!
+! Permission is hereby granted, free of charge, to any person obtaining a copy of
+! this software and associated documentation files (the "Software"), to deal in
+! the Software without restriction, including without limitation the rights to
+! use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+! the Software, and to permit persons to whom the Software is furnished to do so,
+! subject to the following conditions:
+!
+! The above copyright notice and this permission notice shall be included in all
+! copies or substantial portions of the Software.
+!
+! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+! FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+! COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+! IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+! CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+!! File:    swift_string_op.F90
+!! Summary: Strings manipulation source file
+!! Author:  J. Burgalat
+!! Date:    2013-2022
+
+#include "swift_defined.h"
+
+MODULE SWIFT_STRING_OP
+  !! Fortran strings manipulation module
+  !!
+  !! This module provides methods and objects to manipulate Fortran (allocatable) strings. It defines
+  !! a doubly linked-list of strings, [[string_op(module):words(type)]] and several methods to format
+  !! strings or convert them in other intrinsic types.
+  USE SWIFT_ERRORS
+  IMPLICIT NONE
+
+  PRIVATE
+
+  PUBLIC ::  str2dble_sc,str2dble_ve,str2real_sc,str2real_ve
+  ! errors module (not used but propagated)
+  PUBLIC :: stdout,stderr,noerror,error, error_to_string,aborting
+
+  ! misc module methods
+  PUBLIC :: to_string, from_string, string_is, remove_quotes, format_string,     &
+            format_paragraph, strip_newline, tokenize, str_length, endswith, &
+            startswith, to_lower, to_upper, add_csi,      &
+            del_csi, reset_csi, str_remove, str_replace,&
+            fancy
+
+  ! words object related methods
+  PUBLIC :: words_length, words_insert, words_append, words_prepend, words_get, &
+            words_set, words_get_max_width, words_get_total_width, words_pop,   &
+            words_remove, words_next, words_previous, words_reset,              &
+            words_valid, words_current, words_extend, words_reverse,            &
+            words_reversed, words_dump, words_to_string, words_to_vector,       &
+            words_clear
+
+  PRIVATE :: fis_affect_int, fis_affect_bool, fis_affect_real,        &
+             fis_affect_double, fis_affect_cplx, fis_affect_dcplx,    &
+             fis_cat_int, fis_cat_bool, fis_cat_real, fis_cat_double, &
+             fis_cat_cplx, fis_cat_dcplx, fis_cat_int_inv,            &
+             fis_cat_bool_inv, fis_cat_real_inv, fis_cat_double_inv,  &
+             fis_cat_cplx_inv, fis_cat_dcplx_inv
+
+  ! Operators
+  PUBLIC :: ASSIGNMENT(=), OPERATOR(/=), OPERATOR(==)
+
+  INTEGER, PUBLIC, PARAMETER :: st_string  = 1 !! String type ID
+  INTEGER, PUBLIC, PARAMETER :: st_logical = 2 !! Logical type ID
+  INTEGER, PUBLIC, PARAMETER :: st_complex = 3 !! Complex type ID
+  INTEGER, PUBLIC, PARAMETER :: st_integer = 4 !! Integer type ID
+  INTEGER, PUBLIC, PARAMETER :: st_real    = 5 !! Real type ID
+
+  !> List of types names
+  CHARACTER(len=*), DIMENSION(5), PARAMETER, PUBLIC :: st_type_names = &
+  (/ 'string ', 'logical', 'complex', 'integer', 'real   '/)
+
+  INTEGER, PUBLIC, PARAMETER :: st_slen = SSLEN !! Maximum short string length
+  INTEGER, PUBLIC, PARAMETER :: st_llen = SLLEN !! Maximum long string length
+
+
+
+  INTEGER, PUBLIC, PARAMETER :: FC_BLACK     = 30 !! Black foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_RED       = 31 !! Red foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_GREEN     = 32 !! Green foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_YELLOW    = 33 !! Yellow foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_BLUE      = 34 !! Blue foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_MAGENTA   = 35 !! Magenta foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_CYAN      = 36 !! Cyan foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: FC_WHITE     = 37 !! White foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_BLACK     = 40 !! Black foreground csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_RED       = 41 !! Black background csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_GREEN     = 42 !! Green background csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_YELLOW    = 43 !! Yellow background csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_BLUE      = 44 !! Blue background csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_MAGENTA   = 45 !! Magenta background csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_CYAN      = 46 !! Cyan background csi code
+  INTEGER, PUBLIC, PARAMETER :: BG_WHITE     = 47 !! White background csi code
+  INTEGER, PUBLIC, PARAMETER :: ST_NORMAL    =  0 !! Normal (regular) attribute
+  INTEGER, PUBLIC, PARAMETER :: ST_BOLD      =  1 !! Bold (brighter) attribute
+  INTEGER, PUBLIC, PARAMETER :: ST_ITALIC    =  3 !! Italic attribute (sometimes reverse video or underline)
+  INTEGER, PUBLIC, PARAMETER :: ST_UNDERLINE =  4 !! Underline attribute
+  INTEGER, PUBLIC, PARAMETER :: ST_BLINK     =  5 !! Slow blink mode
+  !> List of all attributes in a vector
+  INTEGER, PUBLIC, PARAMETER, DIMENSION(21) :: attributes = [FC_BLACK,     &
+                                                             FC_RED,       &
+                                                             FC_GREEN,     &
+                                                             FC_YELLOW,    &
+                                                             FC_BLUE,      &
+                                                             FC_MAGENTA,   &
+                                                             FC_CYAN,      &
+                                                             FC_WHITE,     &
+                                                             BG_BLACK,     &
+                                                             BG_RED,       &
+                                                             BG_GREEN,     &
+                                                             BG_YELLOW,    &
+                                                             BG_BLUE,      &
+                                                             BG_MAGENTA,   &
+                                                             BG_CYAN,      &
+                                                             BG_WHITE,     &
+                                                             ST_NORMAL,    &
+                                                             ST_BOLD,      &
+                                                             ST_ITALIC,    &
+                                                             ST_UNDERLINE, &
+                                                             ST_BLINK      &
+                                                            ]
+
+  !> Aliases for CSI codes.
+  CHARACTER(len=2), DIMENSION(21), PARAMETER, PUBLIC :: csis =(/ &
+                "fk", "fr", "fg", "fy", "fb", "fm", "fc", "fw", &
+                "bk", "br", "bg", "by", "bb", "bm", "bc", "bw", &
+                 "sn", "sb", "si", "su", "sk"/)
+
+  !> [[words(type)]] object assignement interface
+  INTERFACE ASSIGNMENT(=)
+    MODULE PROCEDURE ws_affect
+  END INTERFACE
+
+  !> Clear either a scalar or a vector of list of [[words(type)]]
+  !!
+  !! The interface encapsulates words _destructors_, that deallocate memory used
+  !! by the given list(s) of words. This method should be called anytime words
+  !! object(s) is no longer used to avoid memory leaks.
+  !! @note
+  !! If the library support Derived type finalization, calling destructor is not
+  !! mandatory.
+  INTERFACE words_clear
+    MODULE PROCEDURE ws_clear_sc, ws_clear_ve
+  END INTERFACE
+
+  !> Extend a given [[words(type)]] object either by another or by a string
+  !!
+  !! The interface encapsulates two subroutines:
+  !!
+  !! - [[ws_extend_ws(subroutine)]](this,other) which extends __this__ by __other__
+  !!   (both are words objects).
+  !! - [[ws_extend_str(subroutine)]](this,str,delimiter,merge) which splits __str__
+  !!   according to __delimiter__ (and optionally __merge__) and then extends
+  !!   __this__ with the resulting tokens.
+  INTERFACE words_extend
+    MODULE PROCEDURE ws_extend_ws,ws_extend_str
+  END INTERFACE
+
+  !> Convert an intrinsic type value to a string
+  !!
+  !! This (very) generic interface provides conversion functions from
+  !! intrinsic types to ALLOCATED string.
+  !!
+  !! ```
+  !! (1)  FUNCTION to_string(value)     RESULT(str)
+  !! (2)  FUNCTION to_string(value,fmt) RESULT(str)
+  !! ```
+  !! Where :
+  !!
+  !! - __value__ is the value to convert
+  !! - __fmt__ is a string the format descriptor of the output string. Surrounding
+  !!   parenthesis can be omitted.
+  !! - __str__ is an allocatable string with the converted value in output, or an empty
+  !!   string if the conversion failed.
+  INTERFACE to_string
+    MODULE PROCEDURE int2str_as,int2str_fs
+    MODULE PROCEDURE log2str_as,log2str_fs
+    MODULE PROCEDURE real2str_as,real2str_fs
+    MODULE PROCEDURE dble2str_as,dble2str_fs
+    MODULE PROCEDURE cplx2str_as,cplx2str_fs
+    MODULE PROCEDURE dcplx2str_as,dcplx2str_fs
+  END INTERFACE
+
+  !> Convert a string into an intrisinc type
+  !!
+  !! All methods defined in the interface are functions which take in arguments,
+  !! a string (input) and an output variable with the relevant type (or vectors of both).
+  !! They always return an error object which is set to -5 error code (i.e. cannot cast value)
+  !! on error, otherwise [[errors(module):noerror(variable)]].
+  INTERFACE from_string
+    MODULE PROCEDURE str2int_sc,str2log_sc,str2real_sc,str2dble_sc,str2cplx_sc
+    MODULE PROCEDURE str2int_ve,str2log_ve,str2real_ve,str2dble_ve,str2cplx_ve
+  END INTERFACE
+
+  !> Compute a fancy string
+  !!
+  !! The generic interface adds CSI codes to the given value and returns a fortran intrinsic string.
+  !!
+  !! This is convinient wrapper to [[string_op(module):to_string(interface)]] and
+  !! [[string_op(module):add_csi(function)]].
+  !!
+  !! ```fortran
+  !! FUNCTION fancy(value, flags) RESULT(str)
+  !! ```
+  !!
+  !! **value** can be either a INTEGER,  REAL, REAL(kind=8), COMPLEX, COMPLEX(kind=8) or STRING.
+  !!
+  !! **flags is a vector of (string) attributes that can take the values as defined in the second
+  !! column of the [list of csi attributes](|url|/page/swift/p02_strings.html#fancy-strings).
+  INTERFACE fancy
+    MODULE PROCEDURE fancy_fstr, fancy_int, fancy_real, fancy_double, fancy_cplx, fancy_dcplx
+  END INTERFACE
+
+  !> Overloaded string assignment operator interface
+  INTERFACE ASSIGNMENT(=)
+    MODULE PROCEDURE fis_affect_int, fis_affect_bool, fis_affect_real,    &
+                     fis_affect_double, fis_affect_cplx, fis_affect_dcplx
+  END INTERFACE
+
+  !> Overloaded string concatentation operator interface
+  INTERFACE OPERATOR(//)
+    MODULE PROCEDURE fis_cat_int, fis_cat_bool, fis_cat_real, fis_cat_double, &
+                     fis_cat_cplx, fis_cat_dcplx
+    MODULE PROCEDURE fis_cat_int_inv, fis_cat_bool_inv, fis_cat_real_inv,     &
+                     fis_cat_double_inv, fis_cat_cplx_inv, fis_cat_dcplx_inv
+  END INTERFACE
+
+  !> Define a linked word
+  !!
+  !! Linked words are only intended to be used within a words type.
+  !! It's part of the doubly linked list words.
+  TYPE, PUBLIC :: word
+#if HAVE_FTNDTSTR
+    CHARACTER(len=:), ALLOCATABLE :: value !! Value of the word
+#else
+    !> Value of the word
+    !!
+    !! @warning
+    !! It is always limited to [[string_op(module):st_slen(variable)]] characters.
+    CHARACTER(len=st_slen)        :: value = ''
+#endif
+    TYPE(word), PRIVATE, POINTER  :: next => null() !! Next word in the list of words
+    TYPE(word), PRIVATE, POINTER  :: prev => null() !! Previous word in the list of words
+  END TYPE word
+
+  !> Define a list of words
+  TYPE, PUBLIC :: words
+    INTEGER, PRIVATE             :: nw = 0         !! Number of word in the list
+    TYPE(word), PRIVATE, POINTER :: head => null() !! First word in the list
+    TYPE(word), PRIVATE, POINTER :: tail => null() !! Last word in the list
+    TYPE(word), PRIVATE, POINTER :: iter => null() !! Current word (iterator)
+#if HAVE_FTNPROC
+    CONTAINS
+    PROCEDURE, PRIVATE :: ws_extend_ws
+    PROCEDURE, PRIVATE :: ws_extend_str
+    PROCEDURE, PUBLIC :: length      => words_length
+      !! Get the number of words in the list
+    PROCEDURE, PUBLIC :: insert      => words_insert
+      !! Insert a word at given index
+    PROCEDURE, PUBLIC :: append      => words_append
+      !! Append a word at the end of the list
+    PROCEDURE, PUBLIC :: prepend     => words_prepend
+      !! Prepend a word at the beginning of the list
+    PROCEDURE, PUBLIC :: get         => words_get
+      !! Get the word at given index
+    PROCEDURE, PUBLIC :: set         => words_set
+      !! Set a word at given index
+    PROCEDURE, PUBLIC :: max_width   => words_get_max_width
+      !! Get the width of the biggest word in the list
+    PROCEDURE, PUBLIC :: total_width => words_get_total_width
+      !! Get the total width of the words stored in the list
+    PROCEDURE, PUBLIC :: reverse     => words_reverse
+      !! Reverse the list in place
+    PROCEDURE, PUBLIC :: reversed    => words_reversed
+      !! Get a reversed copy of the list
+    PROCEDURE, PUBLIC :: dump        => words_dump
+      !! Dump words of the list (on per line)
+    PROCEDURE, PUBLIC :: tostring    => words_to_string
+      !! Convert the list in a single string
+    PROCEDURE, PUBLIC :: to_vector   => words_to_vector
+      !! Convert the list in a vector
+    PROCEDURE, PUBLIC :: pop         => words_pop
+      !! Pop a word from the list and returns it
+    PROCEDURE, PUBLIC :: remove      => words_remove
+      !! Remove a word from the list
+    PROCEDURE, PUBLIC :: next        => words_next
+      !! Go to the next word in the list
+    PROCEDURE, PUBLIC :: previous    => words_previous
+      !! Go to the previous word in the list
+    PROCEDURE, PUBLIC :: reset       => words_reset
+      !! Reset the list's iterator
+    PROCEDURE, PUBLIC :: valid       => words_valid
+      !! Check if iterator position is valid
+    PROCEDURE, PUBLIC :: current     => words_current
+      !! Get the current word in the list
+    GENERIC, PUBLIC :: extend => ws_extend_ws,ws_extend_str
+      !! Extend a list with either a string or another list of words
+#endif
+  END TYPE words
+
+  CONTAINS
+
+  FUNCTION word_length(this) RESULT(lgth)
+    !! Get the trimmed length of the word object
+    TYPE(word), INTENT(in) :: this
+      !! A word object
+    INTEGER :: lgth
+      !! The length of the word's value (without trailing spaces)
+#if HAVE_FTNDTSTR
+    IF (.NOT.ALLOCATED(this%value)) THEN
+      lgth = 0 ; RETURN
+    ENDIF
+#endif
+    lgth = LEN_TRIM(this%value)
+    RETURN
+  END FUNCTION word_length
+
+  SUBROUTINE disconnect_word(this)
+    !! Disconnect a word object
+    !!
+    !! The object is no more connected to its neighbours which are connected together.
+    !! @note
+    !! After this method is called the object is no longer connected to its parent words
+    !! object and should be deallocated in order to avoid memory leaks.
+    TYPE(word), INTENT(inout) :: this
+      !! A word object to disconnect
+    TYPE(word), POINTER :: pw,nw
+    nw => this%next ; pw => this%prev
+    IF (ASSOCIATED(nw)) nw%prev => pw
+    IF (ASSOCIATED(pw)) pw%next => nw
+    RETURN
+  END SUBROUTINE disconnect_word
+
+  SUBROUTINE ws_affect(this,other)
+    !! words object assignment operator subroutine
+    TYPE(words), INTENT(out) :: this
+      !! A words object to be assigned
+    TYPE(words), INTENT(in)  :: other
+      !! A words object to assign
+    TYPE(word), POINTER :: cur
+    CALL ws_clear_sc(this)
+    IF (other%nw == 0) THEN
+      RETURN
+    ELSE
+      cur => other%head
+      DO WHILE(associated(cur))
+#if HAVE_FTNDTSTR
+        IF (.NOT.ALLOCATED(cur%value)) THEN
+          CALL words_append(this,"")
+        ELSE
+          CALL words_append(this,cur%value)
+        ENDIF
+#else
+        CALL words_append(this,cur%value)
+#endif
+        IF (ASSOCIATED(cur,other%iter)) this%iter => this%tail
+        cur => cur%next
+      ENDDO
+    ENDIF
+    RETURN
+  END SUBROUTINE ws_affect
+
+  SUBROUTINE ini_word(this,value)
+    !! Initialize the first word of a list of words
+    !!
+    !! This subroutine is not a constructor. It is only intended to set the first word
+    !! object in a words object.
+    TYPE(words), INTENT(inout)   :: this
+      !! A words object reference
+    CHARACTER(len=*), INTENT(in) :: value
+      !! A string with the word used to initialize the list
+    ALLOCATE(this%head)
+    this%tail => this%head
+    ASSIGN_DTSTR(value,this%tail%value)
+    this%nw = 1
+    RETURN
+  END SUBROUTINE ini_word
+
+  SUBROUTINE ws_clear_sc(obj)
+    !! Clear a list of words
+    !!
+    !! This subroutine deallocates all memory used by the given words object.
+    !! @warning
+    !! The subroutine should be called whenever a words is no more used (e.g. at
+    !! the end of the current scope), otherwise memory leaks could occur.
+    TYPE(words),INTENT(inout), TARGET :: obj
+      !! A words object to clear
+    TYPE(word), POINTER :: cur,next
+    IF (obj%nw == 0) RETURN
+    cur => obj%head
+    DO WHILE(ASSOCIATED(cur))
+      next => cur%next
+      CALL disconnect_word(cur)
+#if HAVE_FTNDTSTR
+      IF (ALLOCATED(cur%value)) DEALLOCATE(cur%value)
+#endif
+      DEALLOCATE(cur)
+      cur => next
+    ENDDO
+    obj%nw = 0
+    obj%head => null() ; obj%tail => null() ; obj%iter => null()
+  END SUBROUTINE ws_clear_sc
+
+  SUBROUTINE ws_clear_ve(objs)
+    !! Clear a vector of list of words
+    !!
+    !! This subroutine deallocates all memory used by the given vector of words objects.
+    !! @warning
+    !! The subroutine should be called whenever a words is no more used (e.g. at the end
+    !! of the current scope), otherwise memory leaks could occur.
+    TYPE(words),INTENT(inout), DIMENSION(:) :: objs
+      !! A vector of words objects to clear
+    TYPE(word), POINTER :: cur,next
+    INTEGER             :: i
+    DO i=1,SIZE(objs)
+      call ws_clear_sc(objs(i))
+    ENDDO
+  END SUBROUTINE ws_clear_ve
+
+  SUBROUTINE ws_extend_ws(this, other)
+    !! Extend a list of words with another one
+    OBJECT(words), INTENT(inout) :: this
+      !! A words object to extend
+    TYPE(words), INTENT(in)     :: other
+      !! A words object to extend with
+    TYPE(word), POINTER :: cw
+    IF (other%nw == 0) RETURN
+    cw => other%head
+    DO WHILE(ASSOCIATED(cw))
+      CALL words_append(this,cw%value) ; cw => cw%next
+    ENDDO
+    RETURN
+  END SUBROUTINE ws_extend_ws
+
+  SUBROUTINE ws_extend_str(this,str,delimiter,merge,protect)
+    !> Extend a list of word with a given string
+    !! @details The method adds a new list of words to the current list by
+    !! splitting a string using a set of delimiters.
+    !!
+    !!   - If __delimiter__ is not given, THEN blank space is used.
+    !!   - __delimiter__ can be a string of any length, but each character of
+    !!     the sequence is seen as a single delimiter. Each time one of these
+    !!     special character is seen on the string, it is splitted.
+    !!   - If __protect__ is set to .true. THEN delimiter enclosed by
+    !!     either single or double quotes are protected.
+    !!   - The optional argument __merge__ instructs the method wether to merge
+    !!     or not successive delimiters in the string.
+    !!
+    !! For example, considering the following string:
+    !! <center>@verbatim "I like coffee and bananas." @endverbatim</center>
+    !!   - Used with only __delimiter__ = " e", the method returns the list:
+    !!     <center>"I","lik","","coff","","","and","bananas"</center>
+    !!   - Used with both __delimiter__ = " e" and __merge__ = .true. :
+    !!     <center>"I","lik","coff","and","bananas"</center>
+    !! @warning
+    !! The method does not trim or adjust the input string. Consequently, it can
+    !! add several empty words at the end of the list if the string is not well
+    !! defined.
+    !! @warning To avoid such problems, consider using TRIM() and ADJUSTL()
+    !! function on __str__ actual argument when calling this subroutine.
+    OBJECT(words), INTENT(inout), TARGET   :: this
+      !! A words object to extend
+    CHARACTER(len=*), INTENT(in)           :: str
+      !! A string to split in words
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: delimiter
+      !! An optional string with the words delimiters (default to blank space).
+    LOGICAL, INTENT(in), OPTIONAL          :: merge
+      !! An optional boolean control flag that instructs the method
+      !! wether to merge or not successive delimiters (default to .false.)
+    LOGICAL, INTENT(in), OPTIONAL          :: protect
+      !! An optional boolean flag with .true. to indicate that
+      !! delimiter characters between quotes are protected
+    ! - LOCAL
+    INTEGER                       :: sl,p,i,j,stat
+    LOGICAL                       :: zmerge,zprotect,indq,insq,outer
+    CHARACTER(len=:), ALLOCATABLE :: seps
+    CHARACTER(len=:), ALLOCATABLE :: curw
+    CHARACTER(len=1), PARAMETER   :: sq = CHAR(39) ! single quote ascii code
+    CHARACTER(len=1), PARAMETER   :: dq = CHAR(34) ! double quotes ascii code
+    stat=0 ; p=1 ; indq = .false. ; insq = .false.
+    seps = ' '
+    zmerge = .false. ; IF (PRESENT(merge)) zmerge = merge
+    zprotect = .true. ; IF (PRESENT(protect)) zprotect = protect
+    IF (PRESENT(delimiter)) THEN
+      IF (LEN(delimiter) > 0) seps = delimiter
+    ENDIF
+    sl = LEN(str) ; IF (sl == 0) RETURN
+    outer =     (INDEX(str,sq) == 1 .AND. INDEX(str,sq,.true.) == LEN(str)) &
+            .OR.(INDEX(str,dq) == 1 .AND. INDEX(str,dq,.true.) == LEN(str))
+    ! no delimiter found or (have outer quotes and should protect)
+    IF (SCAN(str,seps) == 0.OR.(outer.AND.zprotect)) THEN
+      CALL words_append(this,remove_quotes(str))
+      RETURN
+    ENDIF
+    ! We have to loop...
+    i = 1 ; curw=''
+    DO
+      IF (i > sl) EXIT
+      p = SCAN(str(i:),seps) ! position du delimiteur
+      IF (p == 0) THEN
+        ! a gerer
+        curw = curw//TRIM(str(i:))
+        CALL words_append(this,TRIM(str(i:))) ; EXIT
+        curw=''
+      ELSE
+        IF (zprotect) THEN
+          j=i
+          ! starting state
+          DO WHILE(j<i+p)
+            IF (str(j:j) == sq.AND. .NOT.indq) insq = .NOT.insq
+            IF (str(j:j) == dq.AND. .NOT.insq) indq = .NOT.indq
+            j = j+1
+          ENDDO
+          IF ((insq.AND.INDEX(str(j:),"'")/=0) .OR. &
+              (indq.AND.INDEX(str(j:),'"')/=0)) THEN
+            curw=curw//str(i:i+p-1)
+            i=i+p ; CYCLE
+          ENDIF
+        ENDIF
+        IF (p == 1) THEN
+          IF (.NOT.zmerge) THEN
+            curw=''
+            CALL words_append(this,curw)
+          ENDIF
+          i = i + 1 ; CYCLE
+        ELSE
+          curw=curw//str(i:i+p-2)
+          CALL words_append(this,curw)
+          curw = ''
+          i = i + p
+        ENDIF
+      ENDIF
+    ENDDO
+    DEALLOCATE(curw) ; DEALLOCATE(seps)
+    !IF (zprotect) THEN
+    !  ! catching unbalanced quotes
+    !  IF (insq .OR. indq) &
+    !  WRITE(*,'(a)') "extends:warning: unbalanced quotes"
+    !ENDIF
+    RETURN
+  END SUBROUTINE ws_extend_str
+
+  FUNCTION ws_get_ptr(this,idx) RESULT(pted)
+    !! Get the pointer of the word object at given index
+    !!
+    !! The method returns the pointer of the word object at the given index.
+    !! If index is out of range a null poitner is returned.
+    OBJECT(words), INTENT(in) :: this
+      !! A words object
+    INTEGER, INTENT(in)       :: idx
+      !! An integer with the index of the desired object in __this__
+    TYPE(word), POINTER :: pted
+      !! A pointer to the selected word object.
+    INTEGER :: i
+    pted => null()
+    IF (idx < 1 .OR. idx > words_length(this)) THEN
+      RETURN
+    ENDIF
+    IF (idx > (this%nw+1)/2) THEN
+      pted => this%tail
+      DO i=1,this%nw - idx ; pted => pted%prev ; ENDDO
+    ELSE
+      pted => this%head
+      DO i=1,idx-1 ; pted => pted%next ; ENDDO
+    ENDIF
+    RETURN
+  END FUNCTION ws_get_ptr
+
+  FUNCTION words_length(this) RESULT(res)
+    !! Get the size of the words object.
+    !!
+    !! The method returns the number of words stored in the given list of words.
+    OBJECT(words), INTENT(in) :: this !! A words object.
+    INTEGER :: res                    !! The number of words in the object.
+    res = this%nw
+    RETURN
+  END FUNCTION words_length
+
+  SUBROUTINE words_insert(this, idx, value)
+    !! Insert a word before given index in a list of words.
+    !!
+    !! The method inserts a new word before the given index in the list of words. If the given index is out
+    !! of range, the method prepends/appends the object based on the index value.
+    OBJECT(words), INTENT(inout)  :: this
+      !! A words object.
+    INTEGER, INTENT(in)          :: idx
+      !! An integer with the index of an object in the list. The new object will be inserted before that index.
+    CHARACTER(len=*), INTENT(in) :: value
+      !! A string with the word to insert in the list.
+    TYPE(word), POINTER :: welt,nx,pv
+    INTEGER             :: i
+    welt => null() ; nx => null() ; pv => null()
+    IF (this%nw == 0) THEN
+      CALL ini_word(this,value)
+    ELSE IF (idx > this%nw) THEN
+      this%nw = this%nw + 1
+      welt => this%tail
+      allocate(this%tail)
+      ASSIGN_DTSTR(value,this%tail%value)
+      this%tail%prev => welt
+      this%tail%prev%next => this%tail
+    ELSE IF (idx <= 1) THEN
+      this%nw = this%nw + 1
+      welt => this%head
+      allocate(this%head)
+      ASSIGN_DTSTR(value,this%head%value)
+      this%head%next => welt
+      this%head%next%prev => this%head
+    ELSE
+      IF (idx > (this%nw+1)/2) THEN
+        nx => this%tail
+        DO i=1, this%nw - idx ; nx => nx%prev ; ENDDO
+      ELSE
+        nx => this%head
+        DO i=1, idx-1 ; nx => nx%next ; ENDDO
+      ENDIF
+      pv => nx%prev
+      allocate(welt)
+      ASSIGN_DTSTR(value,welt%value)
+      welt%prev => pv ; welt%next => nx
+      pv%next => welt ; nx%prev => welt
+      this%nw = this%nw + 1
+    ENDIF
+    RETURN
+  END SUBROUTINE words_insert
+
+  SUBROUTINE words_append(this,value)
+    !! Append a word to the list of word
+    !!
+    !! The method appends a word to the list of word. This is a convinient wrapper to
+    !! [[string_op(module)::words_insert(subroutine)]] to add a new word at the beginning of the list.
+    OBJECT(words), INTENT(inout) :: this  !! A words object
+    CHARACTER(len=*), INTENT(in) :: value !! A string to append
+    !CALL words_insert(this,this%nw+1,value)
+    type(word), pointer :: np
+
+    call words_insert(this,this%nw+1,value)
+
+    !! If the list is empty
+    !if (this%nw == 0) then
+    !  call ini_word(this, value)
+    !  return
+    !end if
+    !! Add new element to the end
+    !this%nw = this%nw + 1
+    !np => this%tail
+    !allocate(this%tail)
+    !this%tail%value = TRIM(value)
+    !this%tail%prev => np
+    !this%tail%prev%next => this%tail
+    RETURN
+  END SUBROUTINE words_append
+
+  SUBROUTINE words_prepend(this,value)
+    !! Prepend a word to the list of word
+    !!
+    !! The method prepends a word to the list of word. This is a convinient wrapper to
+    !! [[string_op(module)::words_insert(subroutine)]] to add a new word at the end of the list.
+    OBJECT(words), INTENT(inout) :: this  !! A words object
+    CHARACTER(len=*), INTENT(in) :: value !! A string to prepend
+    CALL words_insert(this,0,value)
+    RETURN
+  END SUBROUTINE words_prepend
+
+  FUNCTION words_get(this,idx,case) RESULT (res)
+    !! Get the word's value at given index
+    !!
+    !! The method attempts to get the word's value at the given index. If index is out of range
+    !! an empty string is returned.
+    !! @note
+    !! The returned string is always trimmed.
+    OBJECT(words), INTENT(in)              :: this
+      !! A words object reference
+    INTEGER, INTENT(in)                    :: idx
+      !! An integer with the index of a word in the list
+    CHARACTER(len=5), INTENT(in), OPTIONAL :: case
+      !! An optional string with either 'upper' or 'lower' to get the value converted in the relevant case
+    CHARACTER(len=:), ALLOCATABLE :: res
+      !! The value of the word stored at given index in the list of words
+    TYPE(word), POINTER :: cur
+    cur => ws_get_ptr(this,idx)
+    IF (.not.associated(cur)) THEN
+      res = '' ; RETURN
+    ENDIF
+    IF (PRESENT(case)) THEN
+      IF (case == "upper") res = to_upper(cur%value)
+      IF (case == "lower") res = to_lower(cur%value)
+    ELSE
+      res = TRIM(cur%value)
+    ENDIF
+    RETURN
+  END FUNCTION words_get
+
+  SUBROUTINE words_set(this,idx,value)
+    !! Set a new value to a word object in the list of words at given index
+    !!
+    !! The method sets a new word at given index. If index is out of range, the method simply does nothing.
+    OBJECT(words), INTENT(inout) :: this  !! A words object
+    INTEGER, INTENT(in)          :: idx   !! An integer with the index of the word object to modify in the list
+    CHARACTER(len=*), INTENT(in) :: value !! A string with the new value to set
+    TYPE(word), POINTER :: cur
+    cur => ws_get_ptr(this,idx)
+    IF (.NOT.ASSOCIATED(cur)) RETURN
+    cur%value = value
+  END SUBROUTINE words_set
+
+  FUNCTION words_get_max_width(this) RESULT(res)
+    !! Get the longest word's width in the words object
+    !!
+    !! The method computes and returns the longest (trimmed) word's width in the words object.
+    OBJECT(words), INTENT(in) :: this !! A words object
+    INTEGER :: res                    !! An integer with the maximum width (0 if the list is empty)
+    TYPE(word), POINTER :: cur
+    res = 0
+    IF (this%nw == 0) RETURN
+    cur => this%head ; res = word_length(cur)
+    DO WHILE(ASSOCIATED(cur%next))
+      cur => cur%next
+      IF (word_length(cur) > res) res = word_length(cur)
+    ENDDO
+    RETURN
+  END FUNCTION words_get_max_width
+
+  FUNCTION words_get_total_width(this) RESULT(width)
+    !! Get the total width of all words stored in the list of words
+    !!
+    !! The method computes and returns the total width of all words stored in
+    !! the list of words.
+    !! @note
+    !! Total width is computed using strings::word_length so it only takes
+    !! into account trimmed words (without trailing blanks)
+    !! @note
+    !! If csi codes have been added to words elements they are counted in the width.
+    OBJECT(words), INTENT(in) :: this !! A words object
+    INTEGER :: width                  !! Total length of the list of words
+    TYPE(word), POINTER :: cur
+    width = 0
+    IF (this%nw == 0) RETURN
+    cur => this%head ; width = word_length(cur)
+    DO WHILE(ASSOCIATED(cur%next))
+      cur => cur%next
+      width = width + word_length(cur)
+    ENDDO
+    cur => null()
+    RETURN
+  END FUNCTION words_get_total_width
+
+  SUBROUTINE words_reverse(this)
+    !! Reverse the list of words in-place
+    OBJECT(words), INTENT(inout) :: this
+      !! A words object to reverse
+    TYPE(word), POINTER :: loop,iwc,iwp
+    IF (this%nw <= 1) RETURN
+    loop => this%head ; iwc=> this%head ; iwp=> null()
+    DO WHILE(ASSOCIATED(loop%next))
+      loop => loop%next
+      iwp => iwc%prev ; iwc%prev => iwc%next ; iwc%next => iwp
+      iwc => loop
+    ENDDO
+    iwp=>this%tail%prev ; this%tail%prev=>this%tail%next ; this%tail%next=>iwp
+    iwc => this%head ; this%head => this%tail ; this%tail => iwc
+    loop => null() ; iwc => null() ; iwp => null()
+    RETURN
+  END SUBROUTINE words_reverse
+
+  FUNCTION words_reversed(this) RESULT(res)
+    !! Get a reversed copy of the list of words
+    OBJECT(words), INTENT(in) :: this
+      !! A words object to reverse
+    TYPE(words) :: res
+      !! A reversed copy of the input list of words
+    TYPE(word),POINTER  :: cur
+    IF(this%nw == 0) RETURN
+    cur => this%tail
+    DO WHILE(ASSOCIATED(cur))
+      CALL words_append(res,cur%value)
+      IF (ASSOCIATED(cur,this%iter)) res%iter => res%tail
+      cur => cur%prev
+    ENDDO
+    cur => null()
+    RETURN
+  END FUNCTION words_reversed
+
+  SUBROUTINE words_dump(this,lun)
+    !! Dump the list of words
+    !!
+    !! The method dumps on the given logical unit the elements of the list one by line.
+    OBJECT(words), INTENT(in)     :: this
+      !! A words object to dump
+    INTEGER, INTENT(in), OPTIONAL :: lun
+      !! An optional integer with the printing logical unit. If not given, the list is dumped on
+      !! standard output stream.
+    TYPE(word), POINTER :: cur
+    INTEGER             :: lu
+    IF (this%nw == 0) RETURN
+    lu=6 ; IF (PRESENT(lun)) lu = lun
+    cur => this%head
+    DO WHILE(ASSOCIATED(cur))
+      WRITE(lu,'(a)') TRIM(cur%value)
+      cur => cur%next
+    ENDDO
+    cur => null()
+    RETURN
+  END SUBROUTINE words_dump
+
+  FUNCTION words_to_string(this, delimiter,protect) RESULT(str)
+    !! Convert the list of words into a string
+    !!
+    !! The method converts the list of words into a string. In output, string is always
+    !! allocated even if the list is empty.
+    !!
+    !! If `protect` is set to .true. (default to .false.), each word is enclose between
+    !! double quotes. This option can be used to perform operation on the string before
+    !! setting it back as values of the list of words.
+    OBJECT(words), INTENT(in)              :: this
+      !! A words object
+    CHARACTER(len=*), INTENT(in), OPTIONAL :: delimiter
+      !! An optional string used as delimiter between each words
+    LOGICAL, INTENT(in), OPTIONAL :: protect
+      !! Optional control flag with .true. to protect each word during concatentation.
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! An allocatable string with the list of words joined by the given delimiter (if any)
+    TYPE(word), POINTER :: cur
+    LOGICAL :: zprotect
+    zprotect = .false. ; IF (PRESENT(protect)) zprotect = protect
+    str = ''
+    IF (this%nw == 0) RETURN
+    cur => this%head
+    DO WHILE(ASSOCIATED(cur))
+      IF (zprotect) THEN
+        str=str//'"'//TRIM(cur%value)//'"'
+      ELSE
+        str=str//TRIM(cur%value)
+      ENDIF
+      IF (PRESENT(delimiter).AND..NOT.ASSOCIATED(cur,this%tail)) &
+        str=str//delimiter
+      cur => cur%next
+    ENDDO
+    RETURN
+  END FUNCTION words_to_string
+
+  FUNCTION words_to_vector(this,ret) RESULT(ok)
+    !! Convert the list of words into a vector of strings
+    !!
+    !! The method attempts to convert the list of words in a vector of strings.
+    !! If _this_ list of words is empty, the output vector is allocated with 0 elements and the method returns
+    !! .false., otherwise it returns .true.
+    !! @note
+    !! If elements in __this__ words object are wider than [[string_op(module):st_slen(variable)]], output
+    !! values will be truncated.
+    OBJECT(words), INTENT(in)                                      :: this
+      !! A words object reference
+    CHARACTER(len=st_slen), INTENT(out), ALLOCATABLE, DIMENSION(:) :: ret
+      !! An allocatable vector of assumed length string with the words of __this__
+    LOGICAL             :: ok
+      !! Return status.
+    INTEGER             :: l,mw
+    TYPE(word), POINTER :: iw
+    ok = .true.
+    l = words_length(this)
+    IF (l == 0) THEN
+      ALLOCATE(ret(0))
+      ok = .false.
+      RETURN
+    ENDIF
+    ALLOCATE(ret(l)) ; mw = LEN(ret(l))
+    ret(1:l) = ' ' ! really needed ?
+    iw => this%head ; l=1
+    DO WHILE(ASSOCIATED(iw))
+       ret(l) = TRIM(iw%value) ; l=l+1 ; iw => iw%next
+    ENDDO
+  END FUNCTION words_to_vector
+
+  FUNCTION words_pop(this,idx,move_forward) RESULT(value)
+    !! Pop a word in the list of words
+    !!
+    !! The method removes the word of the list at given index and returns it. If no index is given,
+    !! last word of the list is removed.
+    !!
+    !! If the index is out of range, the method does nothing and returns an empty string.
+    !!
+    !! By default, if the iterator is located on the item to be removed, it is moved backward before
+    !! deletion occurs. If __move\_forward__ is set to .true., the iterator is moved forward.
+    OBJECT(words), INTENT(inout)  :: this
+     !! A words object
+    INTEGER, INTENT(in), OPTIONAL :: idx
+      !! Optional index of the word to delete
+    LOGICAL, INTENT(in), OPTIONAL :: move_forward
+      !! Move the iterator forward if needed. By default the iterator is moved backward.
+    CHARACTER(len=:), ALLOCATABLE :: value
+      !! The word's value at given index
+    LOGICAL             :: zforward
+    INTEGER             :: zidx
+    TYPE(word), POINTER :: cur
+    zidx=words_length(this) ; IF (PRESENT(idx)) zidx = idx
+    zforward = .false. ; IF (PRESENT(move_forward)) zforward = move_forward
+    cur => ws_get_ptr(this,zidx)
+    IF (.NOT.ASSOCIATED(cur)) THEN
+      value = '' ; RETURN
+    ELSE IF (ASSOCIATED(cur,this%iter)) THEN
+      IF (zforward) THEN
+        CALL words_next(this)
+      ELSE
+        CALL words_previous(this)
+      ENDIF
+    ENDIF
+    value = TRIM(cur%value)
+    CALL disconnect_word(cur)
+    DEALLOCATE(cur)
+    this%nw = this%nw - 1
+    RETURN
+  END FUNCTION words_pop
+
+  SUBROUTINE words_remove(this,idx,move_forward)
+    !! Remove the word of the list at given index
+    !!
+    !! The method removes the word of the list at given index. If no index is given, last word
+    !! of the list is removed.
+    !!
+    !! If the index is out of range, the method does nothing.
+    !!
+    !! By default, if the iterator is located on the item to be removed, it is moved backward before
+    !! deletion occurs. If __move\_forward__ is set to .true., the iterator is moved forward.
+    OBJECT(words), INTENT(inout)  :: this
+      !! A words object
+    INTEGER, INTENT(in), OPTIONAL :: idx
+      !! Index of the word to delete
+    LOGICAL, INTENT(in), OPTIONAL :: move_forward
+      !! Move the iterator forward if needed. By default the iterator is moved backward.
+    LOGICAL             :: zforward
+    INTEGER             :: zidx
+    TYPE(word), POINTER :: cur
+    zidx=words_length(this) ; IF(PRESENT(idx)) zidx = idx
+    zforward = .false. ; IF (PRESENT(move_forward)) zforward = move_forward
+    cur => ws_get_ptr(this,idx)
+    IF (.NOT.ASSOCIATED(cur)) THEN
+      RETURN
+    ELSE IF (ASSOCIATED(cur,this%iter)) THEN
+      IF (zforward) THEN
+        CALL words_next(this)
+      ELSE
+        CALL words_previous(this)
+      ENDIF
+    ENDIF
+    CALL disconnect_word(cur)
+    DEALLOCATE(cur)
+    this%nw = this%nw - 1
+    RETURN
+  END SUBROUTINE words_remove
+
+  SUBROUTINE words_next(this)
+    !! Go to the next word in the list
+    OBJECT(words), INTENT(inout) :: this !! A words object
+    IF (ASSOCIATED(this%iter)) this%iter => this%iter%next
+  END SUBROUTINE words_next
+
+  SUBROUTINE words_previous(this)
+    !! Go to the previous word in the list
+    OBJECT(words), INTENT(inout) :: this !! A words object
+    IF (ASSOCIATED(this%iter)) this%iter => this%iter%prev
+  END SUBROUTINE words_previous
+
+  FUNCTION words_valid(this) RESULT(ret)
+    !! Check if the current iterated word is valid
+    OBJECT(words), INTENT(in) :: this !! A words object
+    LOGICAL :: ret                    !! A logical flag with .true. if the current iterated word is valid
+    ret = associated(this%iter)
+  END FUNCTION words_valid
+
+  FUNCTION words_current(this) RESULT(wrd)
+    !! Get current word value
+    OBJECT(words), INTENT(in) :: this
+      !! A words object
+    CHARACTER(len=:), ALLOCATABLE :: wrd
+      !! A string with the value of the current word or __an unallocated string__ if current word
+      !! is not valid (see [[string_op(module):words_valid(function)]]).
+    IF (ASSOCIATED(this%iter)) THEN
+      wrd = this%iter%value
+    ENDIF
+  END FUNCTION words_current
+
+  SUBROUTINE words_reset(this,to_end)
+    !! Reset the iterator
+    !!
+    !! The method resets the iterator either at the beginning or at the end of the list of words
+    !! (if __to_end__ is set to .true.).
+    OBJECT(words), INTENT(inout)  :: this   !! A words object
+    LOGICAL, INTENT(in), OPTIONAL :: to_end !! An optional logical flag with .true. to reset the iterator at the end of the list
+    this%iter => this%head
+    IF (PRESENT(to_end)) THEN
+      IF (to_end) this%iter => this%tail
+    ENDIF
+  END SUBROUTINE words_reset
+
+  ! Fancy string methods
+  ! --------------------
+
+  FUNCTION tokenize(str,vector,delimiter,merge,protect) RESULT(ok)
+    !! Tokenize a string.
+    CHARACTER(len=*), INTENT(in)                                   :: str
+      !! A string to tokenize
+    CHARACTER(len=st_slen), INTENT(out), DIMENSION(:), ALLOCATABLE :: vector
+      !! An allocatable vector of strings with the tokens found. If string cannot be tokenized,
+      !! the vector is __allocated to 0 elements__ and the method returns .false..
+    CHARACTER(len=*), INTENT(in), OPTIONAL                         :: delimiter
+      !! An optional string with the words delimiters. It is set to blank space by default.
+      !! Note that each character is seen as a single delimiter.
+    LOGICAL, INTENT(in), OPTIONAL                                  :: merge
+      !! An optional boolean control flag with .true. that instructs the method whether to
+      !! merge or not successive delimiters. Default to .false.
+    LOGICAL, INTENT(in), OPTIONAL                                  :: protect
+      !! An optional boolean flag with .true. to indicate that delimiter characters between
+      !! quotes are protected. Default to .true.
+    LOGICAL :: ok
+      !! Return status (.true. on success).
+    CHARACTER(len=:), ALLOCATABLE :: seps
+    TYPE(words)                   :: tmp
+    LOGICAL                       :: zmerge,zprotect
+    TYPE(word), POINTER           :: wrd
+    integer                       :: i,nw
+    ok = .true.
+    zmerge = .false. ; zprotect = .true. ; seps = ' '
+    IF (PRESENT(merge)) zmerge = merge
+    IF (PRESENT(protect)) zprotect = protect
+    IF (PRESENT(delimiter)) THEN
+      IF (LEN(delimiter) > 0 ) seps = delimiter
+    ENDIF
+    call words_extend(tmp,str,seps,zmerge,zprotect)
+    nw = tmp%nw
+    i =  1
+    ALLOCATE(vector(tmp%nw))
+    IF (nw > 0) THEN
+      wrd => tmp%head
+      DO WHILE(ASSOCIATED(wrd))
+        vector(i) = TRIM(wrd%value)
+        wrd => wrd%next
+        i = i+1
+      ENDDO
+    ELSE
+      ok = .false.
+    ENDIF
+    call words_clear(tmp)
+    RETURN
+  END FUNCTION tokenize
+
+  FUNCTION remove_quotes(str) RESULT(ostr)
+    !! Strips outer quotes from string
+    !!
+    !! The function removes only external quotes from the input string
+    !! and returns the result in an allocatable string.
+    !! Quotes are removed only if they are the first and last non blank
+    !! characters. Either double and single quotes are stripped without distinction.
+    !! The output string is trimmed from leading and trailing blank spaces (after quotes removal !)
+    CHARACTER(len=*), INTENT(in)  :: str  !! A string to check
+    CHARACTER(len=:), ALLOCATABLE :: ostr !! A string without external quotes (if any).
+    CHARACTER(len=1), PARAMETER   :: sq=CHAR(39), dq=CHAR(34)
+    CHARACTER(len=2), PARAMETER   :: dsq=CHAR(39)//CHAR(34)
+    INTEGER                       :: i, j
+    IF (LEN_TRIM(str) == 0) RETURN
+    ostr = TRIM(ADJUSTL(str))
+    i = SCAN(ostr,sq//dq) ; j = SCAN(ostr,sq//dq,.true.)
+    IF (i == j) RETURN
+    IF (i /= 1) i = 0
+    IF (j /= LEN(ostr)) j = LEN(ostr)+1
+    ostr = TRIM(ostr(i+1:j-1))
+    RETURN
+  END FUNCTION remove_quotes
+
+  FUNCTION string_is(str) RESULT(ret)
+    !! Check if string represents an intrinsic type
+    !!
+    !! The method checks if the given string represents an intrinsic type. Both logical and complex type
+    !! are checked in a strict way :
+    !!
+    !! - A string is a logical if it is one of the following value: __.false.__, __.true.__, __F__, __T__.
+    !! - A string is potentially a complex if it has the following format: __(\*\*\*,\*\*\*)__ where
+    !!   __\*\*\*__ is checked to see wether it is numerical or not.
+    !!
+    !! Valid numerical values can take the following forms:
+    !! ```
+    !!   [0-9]
+    !!   [0-9]*.?[0-9]*?([ed][+-]?[0-9]+)?
+    !! ```
+    !! Obviously if returned value is greater than 3, the string can be converted in
+    !! floating point value.
+    !!
+    !! Empty input string is simply considered to be of string type !
+    CHARACTER(len=*), INTENT(in) :: str
+      !! A string to check
+    INTEGER :: ret
+      !! An integer with the intrinsic type related to the string.
+      !!
+      !! Types are one of the following parameters
+      !!
+      !! - [[string_op(module):st_string(variable)]] (1) for string
+      !! - [[string_op(module):st_logical(variable)]] (2) for logical
+      !! - [[string_op(module):st_complex(variable)]] (3) for complex
+      !! - [[string_op(module):st_integer(variable)]] (4) for integer
+      !! - [[string_op(module):st_real(variable)]] (5) for floating point value
+    CHARACTER(len=:), ALLOCATABLE :: zs,zzs
+    INTEGER :: j,l
+    ret = 1 ; IF (LEN_TRIM(str) == 0) RETURN
+    zs = to_lower(TRIM(ADJUSTL(str))) ; j = INDEX(zs,',') ; l = len(zs)
+    IF (zs(1:1)=='('.AND.zs(l:l) == ')'.AND.j==INDEX(zs,',')) THEN
+      IF (j == 2 .OR. j == l-1) RETURN
+      zzs = TRIM(ADJUSTL(zs(2:j-1))) ; IF (what_(zzs) < 3) RETURN
+      zzs = TRIM(ADJUSTL(zs(j+1:l-1))) ; ret = what_(zzs)
+      IF (ret > 3) THEN ; ret = 3 ; ELSE ; ret = 1 ; ENDIF
+    ELSE
+      ret = what_(zs)
+    ENDIF
+    CONTAINS
+      FUNCTION what_(s) RESULT(is)
+        !! Check if the given string is numerical, logical or a simple string
+        !! @note
+        !! Input string should be in lower case, otherwise, the method will give a a wrong result.
+        !! @warning
+        !! The test performed for logical checking is quite strict : A string is considered as logical
+        !! if and only if it is one of the following values : __.false.__, __.true.__, __F__, __T__.
+        CHARACTER(len=*), INTENT(in) :: s
+          !! A string to check
+        INTEGER :: is
+          !! An integer with : __1__ for string, __2__ for logical, __4__ for integer and __5__ for real
+        LOGICAL                      :: dec,fdot,fexp
+        INTEGER                      :: i
+        CHARACTER(len=24), PARAMETER :: aset='abcfghijklmnopqrstuvwxyz'
+        CHARACTER(len=10), PARAMETER :: iset='1234567890'
+        CHARACTER(len=2),  PARAMETER :: dset='ed'
+        CHARACTER(len=2),  PARAMETER :: sset='+-'
+        CHARACTER(len=7),  PARAMETER :: slog(4) = (/'.true. ','.false.',&
+                                                    't      ','f      '/)
+        is = -1 ; dec = .false. ; fdot = dec ; fexp = fdot
+        DO i = 1,LEN(s)
+          IF (i == 1) THEN
+            ! string does not start by [+-\.\d]
+            IF (VERIFY(s(i:i),'.'//iset//sset) /= 0) THEN
+              is = 1 ; EXIT
+            ENDIF
+            ! update control flag for decimal part
+            dec = s(i:i) == '.' ; fdot = dec
+          ELSE
+            ! check if char is in [a-z]
+            IF(VERIFY(s(i:i),aset) == 0) THEN
+              dec=.false. ; is = 1 ; EXIT
+            ELSE IF (s(i:i) == '.') THEN
+              ! check for dot in decimal/exponent part (==> not a number
+              IF (fdot.OR.fexp) THEN
+                dec = .false. ; is = 1 ; EXIT
+              ENDIF
+            ELSE IF (VERIFY(s(i:i),dset)==0) THEN
+              IF (fexp) THEN
+                dec = .false. ; is = 1 ; EXIT
+              ENDIF
+            ELSE IF (VERIFY(s(i:i),sset) == 0) THEN
+              IF (VERIFY(s(i-1:i-1),dset) /= 0) THEN
+                dec = .false. ; is = 1 ; EXIT
+              ENDIF
+            ENDIF
+            fdot = (fdot .OR. s(i:i) == '.')
+            fexp = (fexp .OR. VERIFY(s(i:i), dset) == 0)
+          ENDIF
+        ENDDO
+        ! it is a string
+        IF (is == 1) THEN
+          ! but have the format of a logical
+          IF (any(slog == s)) is = 2
+        ELSE
+          IF ((fexp.AND.SCAN(s(LEN(s):LEN(s)),dset) /= 0)) THEN
+            is = 1
+          ELSE
+            is = 4
+            IF (fdot.OR.fexp) is = 5
+          ENDIF
+        ENDIF
+      END FUNCTION what_
+  END FUNCTION string_is
+
+  FUNCTION format_string(str,idt1,idto) RESULT(output)
+    !! Format the given string
+    !!
+    !! This function only replaces all '\\n' escape sequence in the given string by NEW_LINE() character.
+    !! The output string is eventually indented if optional arguments are set.
+    !! @warning
+    !! __idto__ is relative to __idt1__ !
+    CHARACTER(len=*), INTENT(in)  :: str     !! The string to format
+    INTEGER, INTENT(in), OPTIONAL :: idt1, & !! An optional integer with the indentation level of the first output line (default to 0)
+                                     idto    !! An optional integer with the indentation level of all other output lines (default to 0)
+    CHARACTER(len=:), ALLOCATABLE :: output  !! An allocatable string with the output formatted string.
+    ! - LOCAL
+    INTEGER :: i,c,ti,mx
+    CHARACTER(len=:), ALLOCATABLE :: idts
+    IF (LEN_TRIM(str) == 0) THEN
+      ALLOCATE(output,source='') ; RETURN
+    ENDIF
+    i=0 ; IF (PRESENT(idt1)) i = MAX(i,idt1)
+    ALLOCATE(CHARACTER(len=i) :: output)
+    IF (i > 0) output(1:i) = CHAR(32)
+    ! i0 is relative to i1 and must be >= 0
+    IF (PRESENT(idto)) i = MAX(i+idto,0)
+    ALLOCATE(CHARACTER(len=i+1) :: idts)
+    idts(1:1) = NEW_LINE('A') ; IF (i>1) idts(2:) = CHAR(32)
+    ! Builds output string
+    c=1 ; mx = LEN_TRIM(str)
+    i = INDEX(str(c:),'\n') ; ti = c+i-1
+    IF (i == 0) THEN
+      output=output//TRIM(str(ti+1:mx))
+    ELSE
+      output=output//TRIM(str(c:ti-1)) ; c=ti+2
+      DO
+        i = INDEX(str(c:),"\n") ; ti = c+i-1
+        IF (i == 0) THEN
+          output=output//TRIM(str(ti+1:mx)) ; c = mx+1
+        ELSE
+          output=output//idts//str(c:ti-1) ; c = ti+2
+        ENDIF
+        IF (c > mx) EXIT
+      ENDDO
+    ENDIF
+    ! print a newline if we have \n at the end of the string
+    IF (INDEX(TRIM(str),'\n',.true.) == mx-1.AND.TRIM(str) /= '\n') &
+    output=output//idts(1:1)
+  END FUNCTION format_string
+
+  FUNCTION format_paragraph(str,width,idt1,idto) RESULT(output)
+    !! Split and format a string over several lines
+    !!
+    !! The function splits an input string in words so output lines fit (almost) in __width__ characters.
+    !! The method handles indentation level (defined as leading blank spaces). It also accounts for known
+    !! csi (see [[string_op(module):attributes(variable)]]).
+    !! @note
+    !! Words are considered indivisible and thus output lines can sometimes exceed the maximum width if
+    !! there is not enough space to put a word (with the associated indentation if given). The default
+    !! behavior in that case is to print the word in a new line (with the correct leading blank spaces).
+    !! @warning
+    !! If __width__, __idt1__ and/or __idto__ have inconsistent values (e.g. __width__ <= __idt1__), the
+    !! method still computes the paragraph, but each words will be set on a new line with the appropriate
+    !! indentation.
+    CHARACTER(len=*), INTENT(in)  :: str    !! string with the content to split
+    INTEGER, INTENT(in)           :: width  !! An positive integer with the maximum width of a line
+    INTEGER, INTENT(in), OPTIONAL :: idt1   !! An optional integer with the indentation level of the first output line
+    INTEGER, INTENT(in), OPTIONAL :: idto   !! An optional integer with the indentation level of the other output lines
+    CHARACTER(len=:), ALLOCATABLE :: output !! An allocatable string with the output content
+    CHARACTER(len=:), ALLOCATABLE :: idts,zs
+    INTEGER                       :: l1,lo,zmx,zw,cc,j,jj,l
+    zw = abs(width) ; zs = strip_newline(str)
+    zmx = LEN_TRIM(zs)
+    IF (zmx == 0) THEN
+      ALLOCATE(output,source='') ; RETURN
+    ENDIF
+    l1=0 ; IF (PRESENT(idt1)) l1 = MAX(l1,idt1)
+    ALLOCATE(CHARACTER(len=l1) :: output)
+    IF (l1 > 0) output(1:l1) = CHAR(32)
+    lo=l1 ; IF (PRESENT(idto)) lo = MAX(l1+idto,0)
+    ALLOCATE(CHARACTER(len=lo+1) :: idts)
+    idts(1:1) = NEW_LINE('A') ; IF (lo>=1) idts(2:len(idts)) = CHAR(32)
+    ! Prints a message if user is just stupid...
+    IF (lo+1 > zw .OR. l1+1 > zw) THEN
+      output = str ; RETURN
+    ENDIF
+    ! check if can just return the string as is
+    IF (zmx + l1 <= zw) THEN
+      output=output//TRIM(zs) ; RETURN
+    ENDIF
+    j=1 ; jj=1+l1
+    DO
+      ! Gets next blank in input string
+      cc = INDEX(TRIM(zs(j:)),CHAR(32))
+      ! no more blank
+      ! Gets total length of csi between zs(j:j+cc-1)
+      ! this value will be substracted to each length test
+      IF (cc == 0) THEN
+        l = csis_length(zs(j:))
+        IF (jj-1+LEN_TRIM(zs(j:))-l > zw) THEN
+          output = output//idts
+        ENDIF
+        output=output//TRIM(zs(j:))
+        EXIT ! we are at the last word : we must exit the infinite loop !
+      ELSE
+        l = csis_length(zs(j:j+cc-1))
+        IF (cc+jj-1-l > zw) THEN
+          output=output//idts//zs(j:j+cc-1) ; jj = lo+1+cc+1 - l
+        ELSE
+          output=output//zs(j:j+cc-1) ; jj = jj + cc - l
+        ENDIF
+      ENDIF
+      j = j + cc
+    ENDDO
+    CONTAINS
+    FUNCTION csis_length(str) RESULT(value)
+      ! - DUMMY
+      CHARACTER(len=*), INTENT(in) :: str
+      ! - RESULT
+      INTEGER :: value
+      ! - LOCAL
+      INTEGER :: jc,iesc,im
+      LOGICAL :: tcsi
+      value = 0
+      jc=1
+      DO
+        IF (jc>LEN(str)) EXIT
+        ! search for escape
+        iesc = INDEX(str(jc:),CHAR(27))
+        IF (iesc == 0) EXIT
+        ! search for m
+        im = INDEX(str(jc+iesc:),"m")
+        ! no m in the string after ESC --> this could not be a csi
+        IF (im == 0) EXIT
+        ! check if this is really a csi and updates length
+        tcsi = is_csi(str(jc+iesc-1:jc+iesc+im-1))
+        jc = jc + iesc
+        IF (tcsi) THEN
+          value=value+im+1
+          jc=jc+im
+        ENDIF
+      ENDDO
+    END FUNCTION csis_length
+  END FUNCTION format_paragraph
+
+  FUNCTION strip_newline(str,rpl) RESULT(stripped)
+    !! Replace newline escape sequences by spaces
+    !!
+    !! The function replaces newline (both '\\n' escape sequence and Fortran NEW_LINE() character) in the
+    !! given string and returns the resulting string.
+    CHARACTER(len=*), INTENT(in)           :: str !! A string to process
+    CHARACTER(len=1), INTENT(in), OPTIONAL :: rpl !! A optional single character used as substitution of escape sequences (blank space by default)
+    CHARACTER(len=:), ALLOCATABLE :: stripped     !! An allocatable string with all newline sequences replaced by blank space or __rpl__ if given
+    CHARACTER(len=1) :: zrp
+    INTEGER          :: i, j, ns
+    zrp = CHAR(32) ; IF(PRESENT(rpl)) zrp = rpl
+    IF (str == NEW_LINE('A')) THEN
+      stripped = zrp ; RETURN
+    ENDIF
+    ns = LEN_TRIM(str)
+    IF (ns == 0) THEN
+      ALLOCATE(stripped,source='') ; RETURN
+    ENDIF
+    ALLOCATE(CHARACTER(len=ns) :: stripped) ; stripped(1:ns) = CHAR(32)
+    i=1 ; j=1
+    DO
+      IF (str(i:i) == NEW_LINE('A')) THEN
+        stripped(j:j) = zrp
+      ELSE IF (i < ns) THEN
+          IF (str(i:i+1) == "\n") THEN
+            stripped(j:j) = zrp ; i=i+1
+          ELSE
+            stripped(j:j) = str(i:i)
+          ENDIF
+      ELSE
+        stripped(j:j) = str(i:i)
+      ENDIF
+      j=j+1 ; i=i+1
+      IF (i > ns .OR. j > ns) EXIT
+    ENDDO
+    IF (j < ns) stripped = stripped(1:j)
+    RETURN
+  END FUNCTION strip_newline
+
+  FUNCTION str_length(str) RESULT(res)
+    !! Get the length of the string object
+    !!
+    !! The method computes the length of the string. It differs from LEN intrinsic function as
+    !! it does not account for extra-characters of csi codes.
+    CHARACTER(len=*), INTENT(in) :: str !! String to process
+    INTEGER :: res                      !! The actual length of string (i.e. does not account for csi codes)
+    CHARACTER(len=:), ALLOCATABLE :: tmp
+    res = 0
+    IF (LEN(str) /= 0) THEN
+      tmp = reset_csi(str)
+      res = LEN(tmp)
+      DEALLOCATE(tmp)
+    ENDIF
+    RETURN
+  END FUNCTION str_length
+
+  FUNCTION to_lower(str1) RESULT(str)
+    !! Convert the string in lower case
+    !!
+    !! The method converts the input string in lower case and accounts for
+    !! possible csi codes in the string.
+    CHARACTER(len=*), INTENT(in) :: str1 !! Input string to convert
+    CHARACTER(len=:), ALLOCATABLE :: str !! A copy of the string in lower case
+    INTEGER :: i,ic
+    IF (LEN(str1) /= 0) THEN
+      str = str1
+      DO i = 1, len(str1)
+        ic = ichar(str1(i:i))
+        IF (ic >= 65 .AND. ic < 90) str(i:i) = char(ic + 32)
+      ENDDO
+    ELSE
+      str=''
+    ENDIF
+  END FUNCTION to_lower
+
+  FUNCTION to_upper(str1) RESULT(str)
+    !! Convert the string in upper case
+    !!
+    !! The method converts the input string in upper case and accounts for
+    !! possible csi codes in the string.
+    CHARACTER(len=*), INTENT(in) :: str1 !! Input string to convert
+    CHARACTER(len=:), ALLOCATABLE :: str !! A copy of the string in upper case
+    INTEGER :: j,i,ic,icsi,lcsi
+    IF (LEN(str1) > 0) THEN
+      str = str1
+      i = 1
+      DO
+        IF (i > LEN(str)) EXIT
+        icsi = str_index_of_csi(str(i:),lcsi)
+        IF (icsi == 0) THEN
+          ! no more csi the end of string is upper case converted
+          DO j=i,LEN(str)
+            ic = ichar(str(j:j))
+            IF (ic >= 97 .AND. ic < 122) str(j:j) = char(ic-32)
+          ENDDO
+          RETURN
+        ELSE IF (icsi == 1) THEN
+          i = i + lcsi
+        ELSE IF (icsi > 1) THEN
+          ! csi is not the first word: we convert in upper case until its
+          ! position THEN copy the csi and get back in the loop
+          DO j=i,i+icsi-2
+            ic = ichar(str(j:j))
+            IF (ic >= 97 .AND. ic < 122) str(j:j) = char(ic-32)
+          ENDDO
+          i = i + icsi + lcsi-1
+        ENDIF
+      ENDDO
+    ELSE
+      str=''
+    ENDIF
+  END FUNCTION to_upper
+
+ FUNCTION str_remove(string,substring,back,all) RESULT(str)
+   !! Remove substring from current string
+   !!
+   !! The function removes the first occurence of __substring__ in __string__ or all
+   !! its occurences if __all__ is explicitly set to .true..
+    CHARACTER(len=*), INTENT(in)  :: string    !! A string to search in
+    CHARACTER(len=*), INTENT(in)  :: substring !! A string to search and removes from __string__
+    LOGICAL, INTENT(in), OPTIONAL :: back, &   !! An optional boolean flag with .true. to begin search at the end of the string
+                                     all       !! An optional boolean flag with .true. to remove all occurences of __substring__
+    CHARACTER(len=:), ALLOCATABLE :: str       !! An allocatable string with __substring__ occurence(s) removed
+    LOGICAL :: zb,za
+    INTEGER :: is,j,zboff
+    str=''
+    zb = .false. ; za = .false.
+    IF (PRESENT(back)) zb = back
+    IF (PRESENT(all)) za = all
+    IF (za) zb=.false.
+    zboff = 0 ; IF (zb) zboff = 1
+    IF (LEN(string) == 0) RETURN
+    j=1
+    DO
+      IF (j>LEN(string)) EXIT
+      ! search for substring
+      is = INDEX(string(j:),substring,back=zb)
+      IF (is == 0) THEN
+        ! substring is not found : we get the last part of the string and return
+        str = str//string(j:) ; RETURN
+      ELSE IF (is == 1) THEN
+        j = j + LEN(substring)
+      ELSE
+        ! substring is not at the begin of the string : saves the string
+        str = str//string(j:j+is-2)
+        j = j + is+LEN(substring)-1
+      ENDIF
+      ! if we only want to str_remove ONE occurence we exit if substring
+      ! has been found
+      IF (.NOT.(is==0.OR.za)) EXIT
+    ENDDO
+    IF (j <= LEN(string).AND..NOT.zb) str=str//string(j:)
+    RETURN
+  END FUNCTION str_remove
+
+ FUNCTION str_replace(string,old,new,back,all) RESULT(str)
+    !! Replace substring from current string
+    !!
+    !! The function replaces the first occurence of __old__ in __string__ by
+    !! __new__ or all its occurence(s) if __all__ is explicitly set to .true..
+    CHARACTER(len=*), INTENT(in)  :: string  !! A string to search in
+    CHARACTER(len=*), INTENT(in)  :: old,  & !! A string to search and replace
+                                     new     !! A string to substitute to __old__
+    LOGICAL, INTENT(in), OPTIONAL :: back, & !! An optional boolean flag with .true. to begin search at the end of the string
+                                     all     !! An optional boolean flag with .true. to replace all occurences of __old__
+    CHARACTER(len=:), ALLOCATABLE :: str     !! An allocatable string with occurence(s) of __old__ replaced by __new__
+    LOGICAL :: zb,za
+    INTEGER :: is,j
+    str=''
+    zb = .false. ; za = .false.
+    IF (PRESENT(back)) zb = back
+    IF (PRESENT(all)) za = all
+    IF (za) zb = .NOT.za
+    IF (LEN(string) == 0) RETURN
+    j=1
+    DO
+      IF (j>LEN(string)) EXIT
+      ! search for "old"
+      is = INDEX(string(j:),old,back=zb)
+      IF (is == 0) THEN
+        ! "old" is not found : we get the last part of the string and return
+        str = str//string(j:) ; RETURN
+      ELSE IF (is == 1) THEN
+        str = str//new
+        j = j + LEN(old)
+      ELSE
+        ! "old" is not at the begin of the string : saves the string
+        str = str//string(j:j+is-2)//new
+        j = j + is + LEN(old) - 1
+      ENDIF
+      IF (.NOT.(is==0.OR.za)) EXIT
+    ENDDO
+    IF (j <= LEN(str)) str=str//string(j:)
+    RETURN
+  END FUNCTION str_replace
+
+  FUNCTION endswith(string,substring,icase) RESULT(ret)
+    !! Check if string ends by substring
+    CHARACTER(len=*), INTENT(in)  :: string
+      !! @param[in] string A string to check
+    CHARACTER(len=*), INTENT(in)  :: substring
+      !! A string to search in __string__
+    LOGICAL, INTENT(in), OPTIONAL :: icase
+      !! An optional boolean flag with .true. to perform insensitive case search
+    LOGICAL :: ret
+      !! .true. if __string__ ends by __substring__, .false. otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: zthis,zstr
+    INTEGER                       :: idx
+    LOGICAL                       :: noc
+    ret = .false.
+    noc = .false. ; IF (PRESENT(icase)) noc = icase
+    IF (LEN(string) == 0 .OR. LEN(substring) == 0) RETURN
+    zthis = reset_csi(string) ; zstr=reset_csi(substring)
+    IF (noc) THEN
+      idx = INDEX(to_lower(zthis),to_lower(zstr),.true.)
+    ELSE
+      idx = INDEX(zthis,zstr,.true.)
+    ENDIF
+    IF (idx == 0.OR.idx+str_length(zstr)-1 /= str_length(zthis)) RETURN
+    ret=.true.
+  END FUNCTION endswith
+
+  FUNCTION startswith(string,substring,icase) RESULT(ret)
+    !! Check if string starts by substring
+    CHARACTER(len=*), INTENT(in)  :: string
+      !! A string to check
+    CHARACTER(len=*), INTENT(in)  :: substring
+      !! A string to search in __string__
+    LOGICAL, INTENT(in), OPTIONAL :: icase
+      !! An optional boolean flag with .true. to perform insensitive case search
+    LOGICAL :: ret
+      !! .true. if __string__ starts by __substring__, .false. otherwise.
+    CHARACTER(len=:), ALLOCATABLE :: zthis,zstr
+    INTEGER                       :: idx
+    LOGICAL                       :: noc
+    ret = .false.
+    noc = .false. ; IF (PRESENT(icase)) noc = icase
+    IF (LEN(string) == 0 .OR. LEN(substring) == 0) RETURN
+    zthis = reset_csi(string) ; zstr=reset_csi(substring)
+    IF (noc) THEN
+      idx = INDEX(to_lower(zthis),to_lower(zstr))
+    ELSE
+      idx = INDEX(zthis,zstr)
+    ENDIF
+    IF (idx /= 1) RETURN
+    ret=.true.
+  END FUNCTION startswith
+
+  ! CSI related functions
+  ! ---------------------
+
+  FUNCTION add_csi(string,attrs) RESULT(str)
+    !! Set csi attributes to the given string object
+    !!
+    !! The function adds csi (ANSI escape sequences) to the given string and
+    !! returns a copy of it.
+    CHARACTER(len=*), INTENT(in)      :: string
+      !! @param[in] string A string object reference
+    INTEGER, INTENT(in), DIMENSION(:) :: attrs
+      !! A vector of integers with the code to add. Each __attrs__ value should refers to one i
+      !! of [[string_op(module):attributes(variable)]] values.
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! An allocatable string with new csi codes added.
+    INTEGER                       :: j,iesc,im
+    CHARACTER(len=:), ALLOCATABLE :: tmp,csi
+    CHARACTER(len=4), PARAMETER   :: rcsi = CHAR(27)//"[0m"
+    str=''
+    ! 1) Check for input string
+    IF (LEN(string) == 0) RETURN
+    ! 2) Removes last <ESC>[0m if any and initializes output string
+    ! we must remove only the last <ESC>[0m if any
+    IF (INDEX(string,rcsi,.true.) == LEN(string)-3) THEN
+      tmp = str_remove(string,rcsi,back=.true.)
+    ELSE
+      tmp = string
+    ENDIF
+    ! 3) Add all the given csi preceded by <ESC>[0m at the beginning of the string
+    !    if it does not start by an ANSI sequence
+    IF (INDEX(tmp,CHAR(27)//"[") /= 1) &
+    tmp = str_add_to_csi(rcsi,attrs)//tmp
+    ! Loops on new string and updates csi codes
+    j=1
+    DO
+      IF (j>LEN(tmp)) EXIT
+      ! search for escape
+      iesc = INDEX(tmp(j:),CHAR(27))
+      IF (iesc == 0) THEN
+        ! no more ESC : cat until end of input string and exit
+        str = str//tmp(j:) ; EXIT
+      ELSE IF (iesc > 1) THEN
+        ! ESC is not first char: copy until ESC
+        str = str//tmp(j:j+iesc-2)
+      ENDIF
+      ! search for m
+      im = INDEX(tmp(j+iesc:),"m")
+      ! no m in the string after ESC --> copy string (INCLUDING ESC) and leave
+      IF (im == 0) THEN
+        str = str//tmp(j+iesc-1:)
+        RETURN
+      ENDIF
+      csi = tmp(j+iesc-1:j+iesc+im-1)
+      ! we have a csi: we add new codes to it
+      IF (is_csi(csi)) THEN
+        csi = str_add_to_csi(csi,attrs)
+      ENDIF
+      str = str//csi
+      j = j + iesc + im
+    ENDDO
+    IF (INDEX(str,rcsi,.true.) /= LEN(str)-3) str = str//rcsi
+    RETURN
+  END FUNCTION add_csi
+
+  FUNCTION del_csi(string,attrs) RESULT(str)
+    !! Remove attributes to the given string
+    !!
+    !! The function removes list of csi (ANSI escape sequences) from the given
+    !! string and returns a copy of it.
+    CHARACTER(len=*), INTENT(in)      :: string
+      !! Input string
+    INTEGER, INTENT(in), DIMENSION(:) :: attrs
+      !! A vector of integers with the code to remove. Each __attrs__ value should
+      !! refers to one of [[string_op(module):attributes(variable)]] values.
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! An allocatable string with csi codes from __list__ removed
+    LOGICAL                                           :: ok
+    INTEGER                                           :: j,iesc,im
+    CHARACTER(len=:), ALLOCATABLE                     :: tmp,csi,csis
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: tks
+    CHARACTER(len=4), PARAMETER                       :: rcsi = CHAR(27)//"[0m"
+    str=''
+    IF (LEN(string) == 0) RETURN
+    ! remove last <ESC>[0m if found at the end of the string
+    IF (INDEX(string,rcsi,.true.) == LEN(string)-3) THEN
+      tmp = str_remove(string,rcsi,back=.true.)
+    ELSE
+      tmp = string
+    ENDIF
+    ! Loops on new string and updates csi codes
+    j=1 ; csis=""
+    DO
+      IF (j>LEN(tmp)) EXIT
+      ! search for escape
+      iesc = INDEX(tmp(j:),CHAR(27))
+      IF (iesc == 0) THEN
+        ! no more ESC : cat until end of input string and exit
+        str = str//tmp(j:) ; EXIT
+      ELSE IF (iesc > 1) THEN
+        ! ESC is not first char: copy until ESC
+        str = str//tmp(j:j+iesc-2)
+      ENDIF
+      ! search for m
+      im = INDEX(tmp(j+iesc:),"m")
+      ! no m in the string after ESC --> copy string (INCLUDING ESC) and leave
+      IF (im == 0) THEN
+        str = str//tmp(j+iesc-1:)
+        RETURN
+      ENDIF
+      csi = tmp(j+iesc-1:j+iesc+im-1)
+      ! we have a csi: we add new codes to it
+      IF (is_csi(csi)) THEN
+        csi = str_del_from_csi(csi,attrs)
+      ENDIF
+      csis=csis//csi//"|"
+      str = str//csi
+      j = j + iesc + im
+    ENDDO
+    ! Add <ESC>[0m at the end of string if not found
+    IF (INDEX(str,rcsi,.true.) /= LEN(str)-3) str = str//rcsi
+    ! resets all attributes if we only have <ESC>[0m in final list
+    ok = tokenize(csis(1:LEN(csis)-1),tks,"|")
+    IF (ALL(tks == rcsi)) str = reset_csi(str)
+    DEALLOCATE(tks)
+    RETURN
+  END FUNCTION del_csi
+
+  FUNCTION reset_csi(string) RESULT(str)
+    !! Reset all csi codes of the string
+    !!
+    !! The method removes __all__ the known escape sequences from the input string.
+    CHARACTER(len=*), INTENT(in) :: string
+      !! Input string
+    CHARACTER(len=:), ALLOCATABLE :: str
+      !! An allocatable string with the copy of input string stripped off csi codes.
+    INTEGER :: j,iesc,im
+    LOGICAL :: tcsi
+    str = ""
+    IF (LEN(string) == 0) RETURN
+    j=1
+    DO
+      IF (j>LEN(string)) EXIT
+      ! search for escape
+      iesc = INDEX(string(j:),CHAR(27))
+      IF (iesc == 0) THEN
+        str = str//string(j:) ; EXIT
+      ENDIF
+      ! search for m
+      im = INDEX(string(j+iesc:),"m")
+      ! no m in the string after ESC --> copy string (INCLUDING ESC) and leave
+      IF (im == 0) THEN
+        str = str//string(j+iesc-1:)
+        RETURN
+      ENDIF
+      ! csi includes everything between ESC and m (excluding them):
+      ! to check for csi it should begin by [ and then be a list of integers
+      ! separated by ;
+      tcsi = is_csi(string(j+iesc-1:j+iesc+im-1))
+      IF (iesc > 1) THEN
+        str = str//string(j:j+iesc-2)
+      ENDIF
+      j = j + iesc ; IF (tcsi) j=j+im
+    ENDDO
+    RETURN
+  END FUNCTION reset_csi
+
+  FUNCTION is_csi(value) RESULT(yes)
+    !! Check if string is a known csi
+    !!
+    !! The function only check for known csi code which are defined in [[string_op(module):attributes(variable)]].
+    CHARACTER(len=*), INTENT(in) :: value
+      !! A Fortran intrinsic string to check
+    LOGICAL :: yes
+      !! .true. if it is a known csi, .false. otherwise
+    LOGICAL                                           :: ok
+    CHARACTER(len=:), ALLOCATABLE                     :: tmp
+    TYPE(words)                                       :: wtks
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE :: stks
+    INTEGER, DIMENSION(:), ALLOCATABLE                :: nums
+    INTEGER                                           :: i
+    yes = .false.
+    IF (LEN(value) < 4) RETURN
+    tmp = value(3:len(value)-1)
+    call words_extend(wtks,tmp,';')
+    ok = words_to_vector(wtks,stks)
+    CALL ws_clear_sc(wtks)
+    IF (.NOT.ok) RETURN
+    ! if we cannot convert strings to integers : it is not a csi
+    IF (.NOT.from_string(stks,nums)) RETURN
+    DEALLOCATE(stks)
+    DO i=1, SIZE(nums)
+      IF (.NOT.ANY(attributes == nums(i))) RETURN
+    ENDDO
+    yes = .true.
+  END FUNCTION is_csi
+
+  FUNCTION str_add_to_csi(csi,list) RESULT(ncsi)
+    !! Add a new list of codes to the input csi string
+    !!
+    !! The method adds all the csi codes given in __list__ that are known by the module and not
+    !! already present in the input csi.
+    CHARACTER(len=*), INTENT(in)      :: csi
+      !! A string with the input csi. It __must__ begin with "<ESC>[" and ends with "m".
+    INTEGER, INTENT(in), DIMENSION(:) :: list
+      !! A vector of integers with the csi code to add. Each value of __list__ should be one of
+      !! [[string_op(module):attributes(variable)]] values. All unknown values are filtered out as well
+      !! as csi code already present in input __csi__.
+    CHARACTER(len=:), ALLOCATABLE :: ncsi
+      !! A new csi string or the input __csi__ if some "errors" occured (the input csi could not
+      !! be tokenized or none of __list__ values are left after filtering).
+    LOGICAL                                            :: ok
+    CHARACTER(len=st_slen), DIMENSION(:), ALLOCATABLE  :: tks
+    CHARACTER(len=:), ALLOCATABLE                      :: tmp
+    INTEGER, DIMENSION(:), ALLOCATABLE                 :: zlist,nums
+    INTEGER                                            :: i,j,ni,no
+    ! 1) Filter input list :
+    ! 1.1) Gets the list of current csi codes
+    ncsi = csi(3:len(csi)-1)
+    ok = tokenize(ncsi,tks,"; ",merge=.true.)
+    IF (.NOT.from_string(tks,nums)) THEN
+      ncsi = csi
+      RETURN
+    ENDIF
+    DEALLOCATE(tks)
+    ! 1.2) Filter input list of new flags to add
+    ! counts number of valid flags
+    j=0
+    DO i=1,SIZE(list)
+      ! new flags must be in attributes but NOT in nums
+      IF (ANY(attributes==list(i).AND..NOT.ANY(nums == list(i)))) j=j+1
+    ENDDO
+    ! No "valid" flags -> returns old csi
+    IF (j == 0) THEN ; ncsi = csi ; RETURN ; ENDIF
+    ni = SIZE(nums) ; no = j + ni
+    ALLOCATE(zlist(no)) ; zlist(1:ni) = nums(:) ; j = ni
+    DO i=1,SIZE(list)
+      ! new flags must be in attributes but NOT in nums
+      IF (ANY(attributes==list(i).AND..NOT.ANY(nums == list(i)))) THEN
+        j=j+1 ; zlist(j) = list(i)
+      ENDIF
+    ENDDO
+    DEALLOCATE(nums)
+    ! 2) Builds new csi
+    !    Here we explictly set the first flag to 0 (i.e. reset attributes)...
+    ncsi = CHAR(27)//"[0;"
+    DO i=1,no
+      ! ... So we get rid of all "0" flag in the list
+      IF (zlist(i) /= 0) THEN
+        tmp = to_string(zlist(i))
+        IF (LEN_TRIM(tmp) == 0) THEN
+          ncsi = csi ; RETURN
+        ENDIF
+        ncsi = ncsi//tmp
+        IF (i /= no) ncsi = ncsi//";"
+      ENDIF
+    ENDDO
+    ncsi = ncsi//"m"
+  END FUNCTION str_add_to_csi
+
+  FUNCTION str_del_from_csi(csi,list) RESULT(ncsi)
+    !! Remove a list of codes from the input csi string
+    !!
+    !! The method removes all the csi codes given in __list__ that are known by the
+    !! module and already present in the input csi.
+    CHARACTER(len=*), INTENT(in)      :: csi
+      !! An intrinsic Fortran string with the input csi. It __must__ begin with "<ESC>[" and ends with "m".
+    INTEGER, INTENT(in), DIMENSION(:) :: list
+      !! A vector of integers with the csi code to remove. Each value of __list__ should be one of
+      !! [[string_op(module):attributes(variable)]] values. All unknown values are filtered out.
+    CHARACTER(len=:), ALLOCATABLE :: ncsi
+      !! A new csi string or the input __csi__ if some "errors" occured (the input csi could not
+      !! be tokenized or none of __list__ values are left after filtering).
+    LOGICAL                                            :: ok
+    CHARACTER(len=LEN(csi)), DIMENSION(:), ALLOCATABLE :: tks
+    CHARACTER(len=:), ALLOCATABLE                      :: tmp
+    INTEGER, DIMENSION(:), ALLOCATABLE                 :: nums
+    INTEGER                                            :: i
+    ncsi = csi(3:len(csi)-1)
+    ok = tokenize(ncsi,tks,"; ",merge=.true.)
+    IF (.NOT.from_string(tks,nums)) THEN
+      ncsi = csi
+      RETURN
+    ENDIF
+    DEALLOCATE(tks)
+    tmp=""
+    DO i=1, SIZE(nums)
+      IF (ALL(nums(i) /= list).AND.nums(i) /= 0) THEN
+        ! no need to check for to_string status : it is always ok !
+        tmp=tmp//to_string(nums(i))//";"
+      ENDIF
+    ENDDO
+    IF (LEN_TRIM(tmp) /= 0) THEN
+      ncsi=CHAR(27)//"[0;"//tmp(1:LEN(tmp)-1)//"m"
+    ELSE
+      ncsi=CHAR(27)//"[0m"
+    ENDIF
+  END FUNCTION str_del_from_csi
+
+  FUNCTION str_index_of_csi(str,length) RESULT(pos)
+    !! Get the position of the first known csi in string
+    !!
+    !! The method searches for the first known csi in string. The csi must contain known codes
+    !! (i.e. values of [[string_op(module):attributes(variable)]]).
+    CHARACTER(len=*), INTENT(in) :: str    !! A string to search in
+    INTEGER, INTENT(out)         :: length !! Length of the csi in the string
+    INTEGER                      :: pos    !! Position of the first csi found. It is set to 0 if no csi has been found.
+    INTEGER :: iesc,im
+    pos = 0 ; length = 0
+    ! we need at least 4 chars to create a csi
+    IF (LEN_TRIM(str) < 4) RETURN
+    iesc = INDEX(str,CHAR(27))
+    IF (iesc == 0) RETURN
+    ! search for m
+    im = INDEX(str(iesc:),"m")
+    ! no m in the string after ESC --> copy string (INCLUDING ESC) and leave
+    IF (im == 0) RETURN
+    IF (.NOT.is_csi(str(iesc:iesc+im-1))) RETURN
+    pos = iesc ; length = im
+  END FUNCTION str_index_of_csi
+
+  ! String conversion functions
+  ! ---------------------------
+
+  FUNCTION str2int_sc(str, value) RESULT(ret)
+    !! Convert string value to integer value (scalar)
+    CHARACTER(len=*), INTENT(in) :: str   !! String to convert
+    INTEGER, INTENT(out)         :: value !! Output value
+    LOGICAL :: ret                        !! Return status (.true. on success)
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; zs = remove_quotes(str)
+    IF (string_is(zs) /= st_integer) THEN
+      ret = .false.
+    ELSE
+      READ(zs, *) value
+    ENDIF
+    RETURN
+  END FUNCTION str2int_sc
+
+  FUNCTION str2log_sc(str, value) RESULT(ret)
+    !! Convert string value to logical value (scalar)
+    CHARACTER(len=*), INTENT(in) :: str   !! String to convert
+    LOGICAL, INTENT(out)         :: value !! Output value
+    LOGICAL :: ret                        !! Return status (.true. on success)
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    integer :: r
+    ret = .true. ; zs = remove_quotes(str)
+    r = string_is(zs)
+    IF (string_is(zs) /= st_logical) THEN
+      ret = .false.
+    ELSE
+      READ(zs, *) value
+    ENDIF
+    RETURN
+  END FUNCTION str2log_sc
+
+  FUNCTION str2real_sc(str, value) RESULT(ret)
+    !! Convert string value to simple precision floating precision value (scalar)
+    CHARACTER(len=*), INTENT(in) :: str   !! String to convert
+    REAL(kind=4), INTENT(out)    :: value !! Output value
+    LOGICAL :: ret                        !! Return status (.true. on success)
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true.; zs = remove_quotes(str)
+    IF (string_is(zs) < st_integer) THEN
+      ret = .false.
+    ELSE
+      READ(zs, *) value
+    ENDIF
+    RETURN
+  END FUNCTION str2real_sc
+
+  FUNCTION str2dble_sc(str, value) RESULT(ret)
+    !! Convert string value to double precision floating precision value (scalar)
+    CHARACTER(len=*), INTENT(in) :: str   !! String to convert
+    REAL(kind=8), INTENT(out)    :: value !! Output value
+    LOGICAL :: ret                        !! Return status (.true. on success)
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; zs = remove_quotes(str)
+    IF (string_is(zs) < st_integer) THEN
+      ret = .false.
+    ELSE
+      READ(zs, *) value
+    ENDIF
+    RETURN
+  END FUNCTION str2dble_sc
+
+  FUNCTION str2cplx_sc(str, value) RESULT(ret)
+    !! Convert string value to complex value (scalar)
+    CHARACTER(len=*), INTENT(in) :: str   !! String to convert
+    COMPLEX(kind=4), INTENT(out) :: value !! Output value
+    LOGICAL :: ret                        !! Return status (.true. on success)
+    ! - LOCAL
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; zs = remove_quotes(str)
+    IF (string_is(zs) /= st_complex) THEN
+      ret = .false.
+    ELSE
+      READ(zs, *) value
+    ENDIF
+    RETURN
+  END FUNCTION str2cplx_sc
+
+  FUNCTION str2int_ve(str, value) RESULT(ret)
+    !! Convert strings values to integer values (vector)
+    CHARACTER(len=*), INTENT(in), DIMENSION(:)      :: str   !! Vector of strings to convert
+    INTEGER, INTENT(out), DIMENSION(:), ALLOCATABLE :: value !! Vector of output values
+    LOGICAL :: ret                                           !! Return status (.true. on success)
+    INTEGER                       :: i,ns
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; ns = SIZE(str) ; ALLOCATE(value(ns))
+    DO i=1,ns
+      zs = remove_quotes(str(i))
+      IF (string_is(zs) /= st_integer) THEN
+        ret = .false. ; DEALLOCATE(value) ; RETURN
+      ELSE
+        READ(zs, *) value(i)
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION str2int_ve
+
+  FUNCTION str2log_ve(str, value) RESULT(ret)
+    !! Convert strings values to logical values (vector)
+    CHARACTER(len=*), INTENT(in), DIMENSION(:)      :: str   !! Vector of strings to convert
+    LOGICAL, INTENT(out), DIMENSION(:), ALLOCATABLE :: value !! Vector of output values
+    LOGICAL :: ret                                           !! Return status (.true. on success)
+    INTEGER                       :: i,ns
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; ns = SIZE(str) ; ALLOCATE(value(ns))
+    DO i=1,ns
+      zs = remove_quotes(str(i))
+      IF (string_is(zs) /= st_logical) THEN
+        ret = .false. ; DEALLOCATE(value) ; RETURN
+      ELSE
+        READ(zs, *) value(i)
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION str2log_ve
+
+  FUNCTION str2real_ve(str, value) RESULT(ret)
+    !! Convert strings values to simple precision floating point values (vector)
+    CHARACTER(len=*), INTENT(in), DIMENSION(:)           :: str   !! Vector of strings to convert
+    REAL(kind=4), INTENT(out), DIMENSION(:), ALLOCATABLE :: value !! Vector of output values
+    LOGICAL :: ret                                                !! Return status (.true. on success)
+    INTEGER                       :: i,ns
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; ns = SIZE(str) ; ALLOCATE(value(ns))
+    DO i=1,ns
+      IF (string_is(zs) < st_integer) THEN
+        ret = .false. ; DEALLOCATE(value) ; RETURN
+      ELSE
+        READ(zs, *) value(i)
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION str2real_ve
+
+  FUNCTION str2dble_ve(str, value) RESULT(ret)
+    !! Convert strings values to double precision floating point values (vector)
+    CHARACTER(len=*), INTENT(in), DIMENSION(:)           :: str   !! Vector of strings to convert
+    REAL(kind=8), INTENT(out), DIMENSION(:), ALLOCATABLE :: value !! Vector of output values
+    LOGICAL :: ret                                                !! Return status (.true. on success)
+    INTEGER                       :: i,ns
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; ns = SIZE(str) ; ALLOCATE(value(ns))
+    DO i=1,ns
+      zs = remove_quotes(str(i))
+      IF (string_is(zs) < st_integer) THEN
+        ret = .false. ; DEALLOCATE(value) ; RETURN
+      ELSE
+        READ(zs, *) value(i)
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION str2dble_ve
+
+  FUNCTION str2cplx_ve(str, value) RESULT(ret)
+    !! Convert strings values to complex values (vector)
+    CHARACTER(len=*), INTENT(in), DIMENSION(:)              :: str   !! Vector of strings to convert
+    COMPLEX(kind=4), INTENT(out), DIMENSION(:), ALLOCATABLE :: value !! Vector of output values
+    LOGICAL :: ret                                                   !! Return status (.true. on success)
+    INTEGER                       :: i,ns
+    CHARACTER(len=:), ALLOCATABLE :: zs
+    ret = .true. ; ns = SIZE(str) ; ALLOCATE(value(ns))
+    DO i=1,ns
+      zs = remove_quotes(str(i))
+      IF (string_is(zs) /= st_complex) THEN
+        ret = .false. ; DEALLOCATE(value) ; RETURN
+      ELSE
+        READ(zs, *) value(i)
+      ENDIF
+    ENDDO
+    RETURN
+  END FUNCTION str2cplx_ve
+
+  FUNCTION int2str_as(value) RESULT(str)
+    !! Convert an integer value to string (auto format / string result)
+    INTEGER, INTENT(in)           :: value !! Value to convert
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=DIGITS(value)) :: str)
+    WRITE(str,*,iostat=err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION int2str_as
+
+  FUNCTION log2str_as(value) RESULT(str)
+    !! Convert a logical value to string (auto format / string result)
+    LOGICAL, INTENT(in)           :: value !! Value to convert
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=2) :: str)
+    WRITE(str, *, IOSTAT = err) value
+    str=TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION log2str_as
+
+  FUNCTION real2str_as(value) RESULT(str)
+    !! Convert a simple precision floating point value to string (auto format / string result)
+    REAL(kind=4), INTENT(in)      :: value !! Value to convert
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=DIGITS(value)) ::str)
+    WRITE(str,*, IOSTAT = err) value
+    str=TRIM(ADJUSTL(str))
+    IF (err /= 0)  str = ''
+    RETURN
+  END FUNCTION real2str_as
+
+  FUNCTION dble2str_as(value) RESULT(str)
+    !! Convert a double precision floating point value to string (auto format / string result)
+    REAL(kind=8), INTENT(in)      :: value !! Value to convert
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=DIGITS(value)) ::str)
+    WRITE(str,*, IOSTAT = err) value
+    str=TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION dble2str_as
+
+  FUNCTION cplx2str_as(value) RESULT(str)
+    !! Convert a complex value to string (auto format / string result)
+    COMPLEX(kind=4), INTENT(in)   :: value !! Value to convert
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err,sl
+    sl = DIGITS(REAL(value))*2+3
+    ALLOCATE(CHARACTER(len=sl) :: str)
+    WRITE(str, *, IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION cplx2str_as
+
+  FUNCTION dcplx2str_as(value) RESULT(str)
+    !! Convert a complex value to string (auto format / string result)
+    COMPLEX(kind=8), INTENT(in)   :: value !! Value to convert
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err,sl
+    sl = DIGITS(REAL(value))*2+3
+    ALLOCATE(CHARACTER(len=sl) :: str)
+    WRITE(str, *, IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION dcplx2str_as
+
+  FUNCTION int2str_fs(value, fmt) RESULT(str)
+    !! Convert an integer value to string (user format / string result)
+    INTEGER, INTENT(in)           :: value !! Value to convert
+    CHARACTER(len=*), INTENT(in)  :: fmt   !! String format
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=st_slen) :: str)
+    WRITE(str, '('//fmt//')', IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION int2str_fs
+
+  FUNCTION log2str_fs(value, fmt) RESULT(str)
+    !! Convert a logical value to string (user format / string result)
+    LOGICAL, INTENT(in)           :: value !! Value to convert
+    CHARACTER(len=*), INTENT(in)  :: fmt   !! String format
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=st_slen) :: str)
+    WRITE(str, '('//fmt//')', IOSTAT = err) value
+    str=TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION log2str_fs
+
+  FUNCTION real2str_fs(value, fmt) RESULT(str)
+    !! Convert a simple precision floating point value to string (user format / string result)
+    REAL(kind=4), INTENT(in)      :: value !! Value to convert
+    CHARACTER(len=*), INTENT(in)  :: fmt   !! String format
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=st_slen) :: str)
+    WRITE(str, '('//fmt//')', IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION real2str_fs
+
+  FUNCTION dble2str_fs(value, fmt) RESULT(str)
+    !! Convert a double precision floating point value to string (user format / string result)
+    REAL(kind=8), INTENT(in)      :: value !! Value to convert
+    CHARACTER(len=*), INTENT(in)  :: fmt   !! String format
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=st_slen) :: str)
+    WRITE(str, '('//fmt//')', IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION dble2str_fs
+
+  FUNCTION cplx2str_fs(value, fmt) RESULT(str)
+    !! Convert a complex value to string (user format / string result)
+    COMPLEX(kind=4), INTENT(in)   :: value !! Value to convert
+    CHARACTER(len=*), INTENT(in)  :: fmt   !! String format
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=st_slen) :: str)
+    WRITE(str, '('//fmt//')', IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION cplx2str_fs
+
+  FUNCTION dcplx2str_fs(value, fmt) RESULT(str)
+    !! Convert a complex value to string (user format / string result)
+    COMPLEX(kind=8), INTENT(in)   :: value !! Value to convert
+    CHARACTER(len=*), INTENT(in)  :: fmt   !! String format
+    CHARACTER(len=:), ALLOCATABLE :: str   !! String with the converted value in output
+    INTEGER :: err
+    ALLOCATE(CHARACTER(len=st_slen) :: str)
+    WRITE(str, '('//fmt//')', IOSTAT = err) value
+    str = TRIM(ADJUSTL(str))
+    IF (err /= 0) str = ''
+    RETURN
+  END FUNCTION dcplx2str_fs
+
+  ! Extended strings features
+  ! ---------------------------
+
+  FUNCTION fis_cat_int(str1,int2) RESULT(str)
+    !! Concatenate a fortran intrinsic string with a integer.
+    CHARACTER(len=*), INTENT(in)  :: str1 !! String to concatenate
+    INTEGER, INTENT(in)           :: int2 !! Integer to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str  !! Output string
+    ALLOCATE(CHARACTER(len=DIGITS(int2)) :: str)
+    WRITE(str,*) int2 ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str1//str
+    RETURN
+  END FUNCTION fis_cat_int
+
+    !! @param[in] int2 An integer to concatenate
+    !! @param[in] str1 A string to concatenate
+    !! @return An allocatable string with the concatenation of input values.
+  FUNCTION fis_cat_int_inv(int2,str1) RESULT(str)
+    !! Concatenate a fortran intrinsic string with a integer (reversed).
+    INTEGER, INTENT(in)           :: int2 !! Integer to concatenate
+    CHARACTER(len=*), INTENT(in)  :: str1 !! String to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str  !! Output string
+    ALLOCATE(CHARACTER(len=DIGITS(int2)) :: str)
+    WRITE(str,*) int2 ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_int_inv
+
+  FUNCTION fis_cat_bool(str1,bool2) RESULT(str)
+    !! Concatenate a string with a logical
+    CHARACTER(len=*), INTENT(in)  :: str1  !! String to concatenate
+    LOGICAL, INTENT(in)           :: bool2 !! Logical to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str   !! Output string
+    CHARACTER(len=2) ::tmp
+    WRITE(tmp,*) bool2
+    str=TRIM(ADJUSTL(tmp))
+    IF (LEN(str1) /= 0) str = str1//str
+    RETURN
+  END FUNCTION fis_cat_bool
+
+  FUNCTION fis_cat_bool_inv(bool2,str1) RESULT(str)
+    !! Concatenate a string with a logical (reversed)
+    LOGICAL, INTENT(in)           :: bool2 !! Logical to concatenate
+    CHARACTER(len=*), INTENT(in)  :: str1    !! String to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str     !! Output string
+    CHARACTER(len=2) ::tmp
+    WRITE(tmp,*) bool2
+    str = TRIM(ADJUSTL(tmp))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_bool_inv
+
+  FUNCTION fis_cat_real(str1,real2) RESULT(str)
+    !! Concatenate a string with a real simple precision
+    CHARACTER(len=*), INTENT(in)  :: str1    !! String to concatenate
+    REAL(kind=4), INTENT(in)      :: real2 !! Simple precision real to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str     !! Output string
+    ALLOCATE(CHARACTER(len=DIGITS(real2)) :: str)
+    WRITE(str,*) real2 ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str=str1//str
+    RETURN
+  END FUNCTION fis_cat_real
+
+  FUNCTION fis_cat_real_inv(real2,str1) RESULT(str)
+    !! Concatenate a string with a real simple precision (reversed)
+    REAL(kind=4), INTENT(in)      :: real2 !! Simple precision real to concatenate
+    CHARACTER(len=*), INTENT(in)  :: str1  !! String to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str   !! Output string
+    ALLOCATE(CHARACTER(len=DIGITS(real2)) :: str)
+    WRITE(str,*) real2  ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_real_inv
+
+  FUNCTION fis_cat_double(str1,double2) RESULT(str)
+    !! Concatenate a string with a real double precision
+    CHARACTER(len=*), INTENT(in)  :: str1    !! String to concatenate
+    REAL(kind=8), INTENT(in)      :: double2 !! Double precision real to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str     !! Output string
+    ALLOCATE(CHARACTER(len=DIGITS(double2)) :: str)
+    WRITE(str,*) double2 ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str=str1//str
+    RETURN
+  END FUNCTION fis_cat_double
+
+  FUNCTION fis_cat_double_inv(double2,str1) RESULT(str)
+    !! Concatenate a string with a real double precision (reversed)
+    REAL(kind=8), INTENT(in)      :: double2 !! Double precision real to concatenate
+    CHARACTER(len=*), INTENT(in)  :: str1    !! String to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str     !! Output string
+    ALLOCATE(CHARACTER(len=DIGITS(double2)) :: str)
+    WRITE(str,*) double2  ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_double_inv
+
+  FUNCTION fis_cat_cplx(str1,cplx2) RESULT(str)
+    !! Concatenate a string with a complex
+    CHARACTER(len=*), INTENT(in)  :: str1  !! String to concatenate
+    COMPLEX(kind=4), INTENT(in)   :: cplx2 !! Complex value to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str   !! Output string
+    INTEGER :: sl
+    sl = DIGITS(REAL(cplx2))*2+3
+    ALLOCATE(CHARACTER(len=sl) :: str)
+    WRITE(str,*) cplx2 ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_cplx
+
+  FUNCTION fis_cat_cplx_inv(cplx2,str1) RESULT(str)
+    !! Concatenate a string with a complex (reversed)
+    COMPLEX(kind=4), INTENT(in)   :: cplx2 !! Complex value to concatenate
+    CHARACTER(len=*), INTENT(in)  :: str1  !! String to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str   !! Output string
+    INTEGER :: sl
+    sl = DIGITS(REAL(cplx2))*2+3
+    ALLOCATE(CHARACTER(len=sl) :: str)
+    WRITE(str,*) cplx2
+    str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_cplx_inv
+
+  FUNCTION fis_cat_dcplx(str1,dcplx2) RESULT(str)
+    !! Concatenate a string with a double precision complex
+    CHARACTER(len=*), INTENT(in)  :: str1   !! String to concatenate
+    COMPLEX(kind=8), INTENT(in)   :: dcplx2 !! Complex value to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str    !! Output string
+    INTEGER :: sl
+    sl = DIGITS(REAL(dcplx2))*2+3
+    ALLOCATE(CHARACTER(len=sl) :: str)
+    WRITE(str,*) dcplx2 ; str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_dcplx
+
+  FUNCTION fis_cat_dcplx_inv(dcplx2,str1) RESULT(str)
+    !! Concatenate a string with a double precision complex (reversed)
+    COMPLEX(kind=8), INTENT(in)   :: dcplx2 !! Complex value to concatenate
+    CHARACTER(len=*), INTENT(in)  :: str1   !! string to concatenate
+    CHARACTER(len=:), ALLOCATABLE :: str    !! Output string
+    INTEGER :: sl
+    sl = DIGITS(REAL(dcplx2))*2+3
+    ALLOCATE(CHARACTER(len=sl) :: str)
+    WRITE(str,*) dcplx2
+    str = TRIM(ADJUSTL(str))
+    IF (LEN(str1) /= 0) str = str//str1
+    RETURN
+  END FUNCTION fis_cat_dcplx_inv
+
+  SUBROUTINE fis_affect_int(str,int)
+    !! Assignment subroutine (using intrinsic integer)
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: str !! Output string to be assigned
+    INTEGER, INTENT(in)                        :: int !! Input value to assign
+    str = fis_cat_int('',int)
+  END SUBROUTINE fis_affect_int
+
+  SUBROUTINE fis_affect_bool(str,bool)
+    !! Assignment subroutine (using intrinsic logical)
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: str  !! Output string to be assigned
+    LOGICAL, INTENT(in)                        :: bool !! Input value to assign
+    str = fis_cat_bool('',bool)
+  END SUBROUTINE fis_affect_bool
+
+  SUBROUTINE fis_affect_real(str,float)
+    !! Assignment subroutine (using intrinsic real)
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: str   !! Output string to be assigned
+    REAL(kind=4), INTENT(in)                   :: float !! Input value to assign
+    str = fis_cat_real('',float)
+  END SUBROUTINE fis_affect_real
+
+  SUBROUTINE fis_affect_double(str,double)
+    !! Assignment subroutine (using intrinsic real(kind=8))
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: str    !! Output string to be assigned
+    REAL(kind=8), INTENT(in)                   :: double !! Input value to assign
+    str = fis_cat_double('',double)
+  END SUBROUTINE fis_affect_double
+
+  SUBROUTINE fis_affect_cplx(str,cplx)
+    !! Assignment subroutine (using intrinsic complex)
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: str  !! Output string to be assigned
+    COMPLEX(kind=4), INTENT(in)                :: cplx !! Input value to assign
+    str = fis_cat_cplx('',cplx)
+  END SUBROUTINE fis_affect_cplx
+
+  SUBROUTINE fis_affect_dcplx(str,dcplx)
+    !! Assignment subroutine (using intrinsic complex(kind=8))
+    CHARACTER(len=:), INTENT(out), ALLOCATABLE :: str   !! Output string to be assigned
+    COMPLEX(kind=8), INTENT(in)                :: dcplx !! Input value to assign
+    str = fis_cat_dcplx('',dcplx)
+  END SUBROUTINE fis_affect_dcplx
+
+  FUNCTION get_attrs_indexes(flags) RESULT(codes)
+    !! Convert a list of csi flags into a csi codes.
+    !!
+    !! Only know CSI codes are returned. If no known CSI are found the outputput vector is
+    !! allocated with 0 elements.
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags !! CSI attributes flags
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes         !! CSI codes.
+    INTEGER :: i,j,n
+    n = 0
+    ALLOCATE(codes(SIZE(flags)))
+    codes(:) = -1
+    DO i = 1, SIZE(flags)
+      DO j = 1, SIZE(attributes)
+        IF (to_lower(flags(i)) == csis(j)) THEN
+          n = n + 1
+          codes(n) = attributes(j)
+          EXIT
+        ENDIF
+      ENDDO
+    ENDDO
+    IF (n > 0) THEN
+      codes = codes(1:n)
+    ELSE
+      DEALLOCATE(codes)
+      ALLOCATE(codes(0))
+    ENDIF
+  END FUNCTION get_attrs_indexes
+
+  FUNCTION fancy_fstr(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given (fortran intrinsic) string.
+    CHARACTER(len=*), INTENT(in)               :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format (unused for this overload)
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    codes = get_attrs_indexes(flags)
+    IF (SIZE(codes) == 0) THEN
+      output = value ; RETURN
+    ELSE
+      output = add_csi(value,codes)
+    ENDIF
+  END FUNCTION fancy_fstr
+
+  FUNCTION fancy_int(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given integer value.
+    INTEGER, INTENT(in)                        :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format. If given it must be a valid Fortran format.
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    CHARACTER(len=:), ALLOCATABLE      :: tmp
+    codes = get_attrs_indexes(flags)
+    IF (PRESENT(fmt)) THEN ; tmp = to_string(value,fmt) ; ELSE ; tmp = to_string(value) ; ENDIF
+    IF (SIZE(codes) /= 0) THEN ; output = add_csi(tmp,codes) ; ELSE ; output = tmp ; ENDIF
+  END FUNCTION fancy_int
+
+  FUNCTION fancy_bool(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given logical value.
+    LOGICAL, INTENT(in)                        :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format. If given it must be a valid Fortran format.
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    CHARACTER(len=:), ALLOCATABLE      :: tmp
+    codes = get_attrs_indexes(flags)
+    IF (PRESENT(fmt)) THEN ; tmp = to_string(value,fmt) ; ELSE ; tmp = to_string(value) ; ENDIF
+    IF (SIZE(codes) /= 0) THEN ; output = add_csi(tmp,codes) ; ELSE ; output = tmp ; ENDIF
+  END FUNCTION fancy_bool
+
+  FUNCTION fancy_real(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given real value (simple precision).
+    REAL(kind=4), INTENT(in)                   :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format. If given it must be a valid Fortran format.
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    CHARACTER(len=:), ALLOCATABLE      :: tmp
+    codes = get_attrs_indexes(flags)
+    IF (PRESENT(fmt)) THEN ; tmp = to_string(value,fmt) ; ELSE ; tmp = to_string(value) ; ENDIF
+    IF (SIZE(codes) /= 0) THEN ; output = add_csi(tmp,codes) ; ELSE ; output = tmp ; ENDIF
+  END FUNCTION fancy_real
+
+  FUNCTION fancy_double(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given real value (double precision).
+    REAL(kind=8), INTENT(in)                   :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format. If given it must be a valid Fortran format.
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    CHARACTER(len=:), ALLOCATABLE      :: tmp
+    codes = get_attrs_indexes(flags)
+    IF (PRESENT(fmt)) THEN ; tmp = to_string(value,fmt) ; ELSE ; tmp = to_string(value) ; ENDIF
+    IF (SIZE(codes) /= 0) THEN ; output = add_csi(tmp,codes) ; ELSE ; output = tmp ; ENDIF
+  END FUNCTION fancy_double
+
+  FUNCTION fancy_cplx(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given complex value (simple precision).
+    COMPLEX(kind=4), INTENT(in)                :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format. If given it must be a valid Fortran format.
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    CHARACTER(len=:), ALLOCATABLE      :: tmp
+    codes = get_attrs_indexes(flags)
+    IF (PRESENT(fmt)) THEN ; tmp = to_string(value,fmt) ; ELSE ; tmp = to_string(value) ; ENDIF
+    IF (SIZE(codes) /= 0) THEN ; output = add_csi(tmp,codes) ; ELSE ; output = tmp ; ENDIF
+  END FUNCTION fancy_cplx
+
+  FUNCTION fancy_dcplx(value,flags,fmt) RESULT(output)
+    !! Compute a fancy string from the given complex value (double precision).
+    COMPLEX(kind=8), INTENT(in)                :: value  !! String object reference
+    CHARACTER(len=2), DIMENSION(:), INTENT(in) :: flags  !! CSI attributes flags
+    CHARACTER(len=*), INTENT(in), OPTIONAL     :: fmt    !! Optional format. If given it must be a valid Fortran format.
+    CHARACTER(len=:), ALLOCATABLE              :: output !! Output fortran instrinsic string
+    INTEGER, DIMENSION(:), ALLOCATABLE :: codes
+    CHARACTER(len=:), ALLOCATABLE      :: tmp
+    codes = get_attrs_indexes(flags)
+    IF (PRESENT(fmt)) THEN ; tmp = to_string(value,fmt) ; ELSE ; tmp = to_string(value) ; ENDIF
+    IF (SIZE(codes) /= 0) THEN ; output = add_csi(tmp,codes) ; ELSE ; output = tmp ; ENDIF
+  END FUNCTION fancy_dcplx
+
+END MODULE SWIFT_STRING_OP
