Repository: libKriging/libKriging (language: python)

Issue:
Impl predict mean & stdev deriv



Discussion / hints:

Fix patch:
diff --git a/.git-hooks/install.sh b/.git-hooks/install.sh
index 423606fc..6c95e65a 100755
--- a/.git-hooks/install.sh
+++ b/.git-hooks/install.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
 
-cd ${BASEDIR}/../.git/hooks
+cd "${BASEDIR}"/../.git/hooks
 ln -sf ../../.git-hooks/pre-commit.py pre-commit
diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml
index 8ddd9c90..4caf0c20 100644
--- a/.github/workflows/analysis.yml
+++ b/.github/workflows/analysis.yml
@@ -168,14 +168,6 @@ jobs:
           fi
         if: runner.os == 'Linux' # or use $RUNNER_OS
 
-      - name: pre-install-macos
-        shell: bash
-        run: |
-          if [ "${ENABLE_R_BINDING}" == "on" ]; then
-            brew install coreutils # for readlink -f : FIXME usefull ?
-          fi
-        if: runner.os == 'macOS'
-
       - name: install
         shell: bash
         run: .travis-ci/${BUILD_NAME}/install.sh
diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml
index 50317eb7..e9261ed7 100644
--- a/.github/workflows/bench.yml
+++ b/.github/workflows/bench.yml
@@ -91,14 +91,6 @@ jobs:
           fi
         if: runner.os == 'Linux' # or use $RUNNER_OS
 
-      - name: pre-install-macos
-        shell: bash
-        run: |
-          if [ "${ENABLE_R_BINDING}" == "on" ]; then
-            brew install coreutils # for readlink -f : FIXME usefull ?
-          fi
-        if: runner.os == 'macOS'
-
       - name: pre-install-windows for Python
         shell: bash
         run: |
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5bed2df5..fe61eec5 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -176,14 +176,6 @@ jobs:
           fi
         if: runner.os == 'Linux' # or use $RUNNER_OS
 
-      - name: pre-install-macos
-        shell: bash
-        run: |
-          if [ "${ENABLE_R_BINDING}" == "on" ]; then
-            brew install coreutils # for readlink -f : FIXME usefull ?
-          fi
-        if: runner.os == 'macOS'
-
       - name: pre-install-matlab
         uses: matlab-actions/setup-matlab@v1
         if: matrix.enable_matlab == 'on'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4663b07e..1bbe41a8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -317,12 +317,6 @@ jobs:
           sudo apt install -qy r-base liblapack-dev gfortran
         if: runner.os == 'Linux' # or use $RUNNER_OS
 
-      - name: pre-install-macos
-        shell: bash
-        run: |
-          brew install coreutils # for readlink -f : FIXME usefull ?
-        if: runner.os == 'macOS'
-
       - name: install
         shell: bash
         run: .travis-ci/${BUILD_NAME}/install.sh
diff --git a/.gitmodules b/.gitmodules
index dac567aa..ca677c6e 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -17,3 +17,6 @@
 [submodule "dependencies/optim"]
 	path = dependencies/optim
 	url = https://github.com/libKriging/optim.git
+[submodule "dependencies/lbfgsb_cpp"]
+	path = dependencies/lbfgsb_cpp
+	url = https://github.com/libKriging/lbfgsb_cpp.git
diff --git a/.travis-ci/common/before_script.sh b/.travis-ci/common/before_script.sh
index 49ee7811..9d76efd2 100755
--- a/.travis-ci/common/before_script.sh
+++ b/.travis-ci/common/before_script.sh
@@ -25,6 +25,15 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 
     echo "------------------------------------"
 
+    if ( command -v gfortran >/dev/null 2>&1 ); then
+      echo "Fortran config: $(command -v gfortran)"
+      gfortran --version 2>&1 | sed 's/^/  /'
+    else
+      echo "No Fortran compiler command found"
+    fi
+
+    echo "------------------------------------"
+
     if ( command -v clang-format >/dev/null 2>&1 ); then
       echo "clang-format config: $(command -v clang-format)"
       clang-format --version 2>&1 | sed 's/^/  /'
diff --git a/.travis-ci/linux-macos/build.sh b/.travis-ci/linux-macos/build.sh
index 302dc2fc..6d6eed9e 100755
--- a/.travis-ci/linux-macos/build.sh
+++ b/.travis-ci/linux-macos/build.sh
@@ -19,6 +19,10 @@ export ENABLE_MEMCHECK=${ENABLE_MEMCHECK:-off}
 export ENABLE_STATIC_ANALYSIS=${ENABLE_STATIC_ANALYSIS:-off}
 export SANITIZE=${SANITIZE:-off}
 
+BASEDIR=$(dirname "$0")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
+
 if [[ -n ${TRAVIS_BUILD_DIR:+x} ]]; then
     cd "${TRAVIS_BUILD_DIR}"
 fi
@@ -47,8 +51,8 @@ case "$COMPILER" in
     ;;
 esac
 
-mkdir -p build
-cd build
+mkdir -p ${BUILD_DIR:-build}
+cd ${BUILD_DIR:-build}
 cmake \
   -G "Unix Makefiles" \
   -DCMAKE_BUILD_TYPE="${MODE}" \
diff --git a/.travis-ci/linux-macos/install.sh b/.travis-ci/linux-macos/install.sh
index f4a5adb6..5fa78219 100755
--- a/.travis-ci/linux-macos/install.sh
+++ b/.travis-ci/linux-macos/install.sh
@@ -5,6 +5,21 @@ if [[ "$DEBUG_CI" == "true" ]]; then
   set -x
 fi
 
+case "$(uname -s)" in
+ Darwin)
+   brew install gfortran
+   ;;
+
+ Linux)
+   sudo apt install gfortran 
+   ;;
+
+ *)
+   echo 'Unknown OS'
+   exit 1 
+   ;;
+esac
+
 if [[ "$ENABLE_PYTHON_BINDING" == "on" ]]; then
   if ( command -v python3 >/dev/null 2>&1 && 
        python3 -m pip --version >/dev/null 2>&1 ); then
diff --git a/.travis-ci/linux-macos/loadenv.sh b/.travis-ci/linux-macos/loadenv.sh
new file mode 100755
index 00000000..c04817c9
--- /dev/null
+++ b/.travis-ci/linux-macos/loadenv.sh
@@ -0,0 +1,3 @@
+if [ "$(uname -s)" == "Darwin" ]; then
+  export PATH=$(brew --prefix gfortran)/bin:$PATH
+fi
\ No newline at end of file
diff --git a/.travis-ci/octave-windows/build.sh b/.travis-ci/octave-windows/build.sh
index 22fe1a02..b1b11329 100644
--- a/.travis-ci/octave-windows/build.sh
+++ b/.travis-ci/octave-windows/build.sh
@@ -6,15 +6,14 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 # Default configuration when used out of travis-ci
 if [[ -n ${TRAVIS_BUILD_DIR:+x} ]]; then
     cd "${TRAVIS_BUILD_DIR}"
 fi
 
-. ${BASEDIR}/loadenv.sh
-
 CC=gcc CXX=g++ \
     EXTRA_CMAKE_OPTIONS="-DBUILD_SHARED_LIBS=${MAKE_SHARED_LIBS} -DEXTRA_SYSTEM_LIBRARY_PATH=${EXTRA_SYSTEM_LIBRARY_PATH}" \
     "${BASEDIR}"/../linux-macos/build.sh
diff --git a/.travis-ci/octave-windows/install.sh b/.travis-ci/octave-windows/install.sh
index 48a4cd54..6a831990 100644
--- a/.travis-ci/octave-windows/install.sh
+++ b/.travis-ci/octave-windows/install.sh
@@ -6,7 +6,8 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 "${BASEDIR}"/../windows/install.sh
 
diff --git a/.travis-ci/r-linux-macos/build.sh b/.travis-ci/r-linux-macos/build.sh
index 55a7ae31..60cf12ef 100755
--- a/.travis-ci/r-linux-macos/build.sh
+++ b/.travis-ci/r-linux-macos/build.sh
@@ -5,6 +5,10 @@ if [[ "$DEBUG_CI" == "true" ]]; then
   set -x
 fi
 
+BASEDIR=$(dirname "$0")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
+
 # Default configuration when used out of travis-ci
 if [[ -n ${TRAVIS_BUILD_DIR:+x} ]]; then
     cd "${TRAVIS_BUILD_DIR}"
@@ -19,12 +23,6 @@ if [[ "$(uname -s)" == "Linux" ]]; then
   MAKE_SHARED_LIBS=off # Quick workaround for not found armadillo lib
 fi
 
-# to get readlink on MacOS (no effect on Linux)
-if [[ -e /usr/local/opt/coreutils/libexec/gnubin/readlink ]]; then
-    export PATH=/usr/local/opt/coreutils/libexec/gnubin:$PATH
-fi
-BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
 MODE=${MODE:-Release}
 
 BUILD_TEST=false \
@@ -34,10 +32,15 @@ BUILD_TEST=false \
     EXTRA_CMAKE_OPTIONS="-DBUILD_SHARED_LIBS=${MAKE_SHARED_LIBS} ${EXTRA_CMAKE_OPTIONS}" \
     "${BASEDIR}"/../linux-macos/build.sh
 
-export LIBKRIGING_PATH=${PWD}/build/installed
- 
+export LIBKRIGING_PATH=${PWD}/${BUILD_DIR:-build}/installed
+
+NPROC=1
+if ( command -v nproc >/dev/null 2>&1 ); then
+  NPROC=$(nproc)
+fi
+
 cd bindings/R
 make uninstall || true
 make clean
-MAKEFLAGS=-j$(nproc)
+MAKEFLAGS=-j${NPROC}
 MAKE_SHARED_LIBS=${MAKE_SHARED_LIBS} make
diff --git a/.travis-ci/r-linux-macos/install.sh b/.travis-ci/r-linux-macos/install.sh
index 00e98331..3e4aa231 100755
--- a/.travis-ci/r-linux-macos/install.sh
+++ b/.travis-ci/r-linux-macos/install.sh
@@ -5,11 +5,8 @@ if [[ "$DEBUG_CI" == "true" ]]; then
   set -x
 fi
 
-# to get readlink on MacOS (no effect on Linux)
-if [[ -e /usr/local/opt/coreutils/libexec/gnubin/readlink ]]; then
-    export PATH=/usr/local/opt/coreutils/libexec/gnubin:$PATH
-fi
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 "${BASEDIR}"/../linux-macos/install.sh
diff --git a/.travis-ci/r-windows/build.sh b/.travis-ci/r-windows/build.sh
index f52aef16..5e375440 100755
--- a/.travis-ci/r-windows/build.sh
+++ b/.travis-ci/r-windows/build.sh
@@ -6,15 +6,14 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 # Default configuration when used out of travis-ci
 if [[ -n ${TRAVIS_BUILD_DIR:+x} ]]; then
     cd "${TRAVIS_BUILD_DIR}"
 fi
 
-. ${BASEDIR}/loadenv.sh
-
 # OpenBLAS installation
 export EXTRA_SYSTEM_LIBRARY_PATH=${HOME}/Miniconda3/Library/lib
 
@@ -25,10 +24,11 @@ MAKE_SHARED_LIBS=on
 BUILD_TEST=false \
     CC=$(R CMD config CC) \
     CXX=$(R CMD config CXX) \
+    FC=$(R CMD config FC) \
     EXTRA_CMAKE_OPTIONS="-DBUILD_SHARED_LIBS=${MAKE_SHARED_LIBS} -DEXTRA_SYSTEM_LIBRARY_PATH=${EXTRA_SYSTEM_LIBRARY_PATH}" \
     "${BASEDIR}"/../linux-macos/build.sh
 
-export LIBKRIGING_PATH=${PWD}/build/installed
+export LIBKRIGING_PATH=${PWD}/${BUILD_DIR:-build}/installed
 export PATH=${LIBKRIGING_PATH}/bin:${PATH}
 
 cd bindings/R
diff --git a/.travis-ci/r-windows/install.sh b/.travis-ci/r-windows/install.sh
index 6f7d2143..b3b755a4 100755
--- a/.travis-ci/r-windows/install.sh
+++ b/.travis-ci/r-windows/install.sh
@@ -6,7 +6,7 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
 
 "${BASEDIR}"/../windows/install.sh
 
@@ -17,7 +17,7 @@ choco install -y --no-progress make --version 4.3
 if [ "${GITHUB_ACTIONS:=false}" == "false" ]; then
 
   # Allow to fail
-  REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe") || true
+  REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe" |& grep -v "Permission denied") || true
   
   if [ -z "$REXE" ]; then
     echo "Missing R program"
@@ -29,7 +29,11 @@ if [ "${GITHUB_ACTIONS:=false}" == "false" ]; then
   fi
   
   # Could be updated after install; cannot fail
-  REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe")
+  REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe" |& grep -v "Permission denied") || true
+  if [ -z "$REXE" ]; then
+    echo "Cannot find R executable" >&2
+    exit 1
+  fi
   RVERSION=$("$REXE" --version 2>&1 | grep -o "R version [[:digit:]]\.[[:digit:]]")
   
   # https://cran.r-project.org/bin/windows/Rtools
@@ -61,4 +65,5 @@ fi
 # For R packaging
 choco install -y --no-progress zip
 
-. ${BASEDIR}/loadenv.sh
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
+
diff --git a/.travis-ci/r-windows/loadenv.sh b/.travis-ci/r-windows/loadenv.sh
index 4919fda0..3079deee 100644
--- a/.travis-ci/r-windows/loadenv.sh
+++ b/.travis-ci/r-windows/loadenv.sh
@@ -1,7 +1,11 @@
 # make
 export PATH="/c/Program Files/make/make-4.3/bin":${PATH}
 
-REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe")
+REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe" |& grep -v "Permission denied") || true
+if [ -z "$REXE" ]; then
+  echo "Cannot find R executable" >&2
+  exit 1
+fi
 RDIR=$(dirname "$REXE")
 export PATH="$RDIR":$PATH
 
diff --git a/.travis-ci/r-windows/set_makevars.sh b/.travis-ci/r-windows/set_makevars.sh
index b8d28651..d6affeb8 100755
--- a/.travis-ci/r-windows/set_makevars.sh
+++ b/.travis-ci/r-windows/set_makevars.sh
@@ -1,6 +1,10 @@
 #!/usr/bin/env bash
 set -eo pipefail
 
+if [[ "$DEBUG_CI" == "true" ]]; then
+  set -x
+fi
+
 if [ "${GITHUB_ACTIONS:=false}" == "false" ]; then
   
   # ~/.R/Makevars will override values in $(R_HOME}/etc$(R_ARCH)/Makeconf
@@ -10,7 +14,11 @@ if [ "${GITHUB_ACTIONS:=false}" == "false" ]; then
   mkdir -p ~/.R
   
   # Could be updated after install; cannot fail
-  REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe")
+  REXE=$(which R 2>/dev/null) || REXE=$(find "/c/Program Files" -wholename "*/bin/x64/R.exe" |& grep -v "Permission denied" || true)
+  if [ -z "$REXE" ]; then
+    echo "Cannot find R executable" >&2
+    exit 1
+  fi
   RVERSION=$("$REXE" --version 2>&1 | grep -o "R version [[:digit:]]\.[[:digit:]]")
   
   # https://cran.r-project.org/bin/windows/Rtools
diff --git a/.travis-ci/release/octave-release.sh b/.travis-ci/release/octave-release.sh
index 8dfd3385..6adc1ac0 100755
--- a/.travis-ci/release/octave-release.sh
+++ b/.travis-ci/release/octave-release.sh
@@ -25,7 +25,7 @@ if [ -z "${GIT_TAG}" ]; then
 fi
 echo "Release tag '${GIT_TAG}' in branch '$(git branch --show-current)'"
 
-PREFIX=build/bindings/Octave
+PREFIX=${BUILD_DIR:-build}/bindings/Octave
 
 PACKAGE_DIR=octave-package
 [ -d "${PACKAGE_DIR}" ] && rm -fr "${PACKAGE_DIR}"
@@ -49,4 +49,4 @@ case $ARCH in
 esac
 
 RELEASE_FILE="${PACKAGE_DIR}"/mLibKriging_${GIT_TAG#v}_${ARCHZ}.tgz
-tar czvf "${RELEASE_FILE}" -C build/installed/bindings/Octave .
+tar czvf "${RELEASE_FILE}" -C ${BUILD_DIR:-build}/installed/bindings/Octave .
diff --git a/.travis-ci/release/r-release.sh b/.travis-ci/release/r-release.sh
index eb3fc9f0..0b558bde 100755
--- a/.travis-ci/release/r-release.sh
+++ b/.travis-ci/release/r-release.sh
@@ -27,7 +27,7 @@ echo "Release tag '${GIT_TAG}' in branch '$(git branch --show-current)'"
 
 PREFIX="${PWD}"/bindings/R/rlibkriging
 
-TMP_DIR=build/tmp
+TMP_DIR=${BUILD_DIR:-build}/tmp
 [ -d "${TMP_DIR}" ] && rm -fr "${TMP_DIR}"
 mkdir -p "${TMP_DIR}"
 
@@ -40,8 +40,8 @@ case $ARCH in
   Linux)
     ARCHZ="${ARCH}-$(uname -m)"
     tar xzvf "${PREFIX}_${RVER}_R_$(uname -m)-pc-linux-gnu.tar.gz" -C "${TMP_DIR}"
-    cp -a build/installed/lib/libarmadillo.so.* "${TMP_DIR}"/rlibkriging/libs/ || echo "No armadillo shared lib to copy"
-    cp -a build/installed/lib/libKriging.so.* "${TMP_DIR}"/rlibkriging/libs/ || echo "No libKriging shared lib to copy"
+    cp -a ${BUILD_DIR:-build}/installed/lib/libarmadillo.so.* "${TMP_DIR}"/rlibkriging/libs/ || echo "No armadillo shared lib to copy"
+    cp -a ${BUILD_DIR:-build}/installed/lib/libKriging.so.* "${TMP_DIR}"/rlibkriging/libs/ || echo "No libKriging shared lib to copy"
     RELEASE_FILE=${PREFIX}_${GIT_TAG#v}_${ARCHZ}.tgz
     tar czvf "${RELEASE_FILE}" -C "${TMP_DIR}" .
     ;;
@@ -49,16 +49,16 @@ case $ARCH in
     MACOS_VERSION=$(/usr/libexec/PlistBuddy -c "Print:ProductVersion" /System/Library/CoreServices/SystemVersion.plist)
     ARCHZ="macOS${MACOS_VERSION}-$(uname -m)"
     tar xzvf "${PREFIX}_${RVER}.tgz" -C "${TMP_DIR}"
-    cp -a build/installed/lib/libarmadillo.*.dylib "${TMP_DIR}"/rlibkriging/libs/ || echo "Warning: no armadillo shared lib to copy"
-    cp -a build/installed/lib/libKriging.*.dylib "${TMP_DIR}"/rlibkriging/libs/ || echo "Warning: no libKriging shared lib to copy"
+    cp -a ${BUILD_DIR:-build}/installed/lib/libarmadillo.*.dylib "${TMP_DIR}"/rlibkriging/libs/ || echo "Warning: no armadillo shared lib to copy"
+    cp -a ${BUILD_DIR:-build}/installed/lib/libKriging.*.dylib "${TMP_DIR}"/rlibkriging/libs/ || echo "Warning: no libKriging shared lib to copy"
     RELEASE_FILE=${PREFIX}_${GIT_TAG#v}_${ARCHZ}.tgz
     tar czvf "${RELEASE_FILE}" -C "${TMP_DIR}" .
     ;;
   MSYS_NT*|MINGW64_NT*) # Windows
     ARCHZ="$(uname -s | awk -F- '{ print $1$2 }')-$(uname -m)" # remove trailing release
     unzip "${PREFIX}_${RVER}.zip" -d "${TMP_DIR}"
-    cp -a build/installed/bin/libarmadillo.dll "${TMP_DIR}"/rlibkriging/libs/x64/ || echo "Warning: no armadillo shared lib to copy"
-    cp -a build/installed/bin/libKriging.dll "${TMP_DIR}"/rlibkriging/libs/x64/ || echo "Warning: no libKriging shared lib to copy"
+    cp -a ${BUILD_DIR:-build}/installed/bin/libarmadillo.dll "${TMP_DIR}"/rlibkriging/libs/x64/ || echo "Warning: no armadillo shared lib to copy"
+    cp -a ${BUILD_DIR:-build}/installed/bin/libKriging.dll "${TMP_DIR}"/rlibkriging/libs/x64/ || echo "Warning: no libKriging shared lib to copy"
     for extralib in openblas.dll libomp.dll flangrti.dll flang.dll
     do
       cp "${HOME}"/Miniconda3/Library/bin/$extralib "${TMP_DIR}"/rlibkriging/libs/x64/ || echo "Warning: no $extralib lib to copy"
diff --git a/.travis-ci/windows/build.sh b/.travis-ci/windows/build.sh
index 126e18dd..021abc2a 100755
--- a/.travis-ci/windows/build.sh
+++ b/.travis-ci/windows/build.sh
@@ -6,9 +6,6 @@ if [[ "$DEBUG_CI" == "true" ]]; then
   set -x
 fi
 
-BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
-
 # Default configuration when used out of travis-ci
 MODE=${MODE:-Debug}
 EXTRA_CMAKE_OPTIONS=${EXTRA_CMAKE_OPTIONS:-}
@@ -18,12 +15,14 @@ export ENABLE_OCTAVE_BINDING=${ENABLE_OCTAVE_BINDING:-auto}
 export ENABLE_MATLAB_BINDING=${ENABLE_MATLAB_BINDING:-auto}
 export ENABLE_PYTHON_BINDING=${ENABLE_PYTHON_BINDING:-auto}
 
+BASEDIR=$(dirname "$0")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
+
 if [[ -n ${TRAVIS_BUILD_DIR:+x} ]]; then
     cd "${TRAVIS_BUILD_DIR}"
 fi
 
-. ${BASEDIR}/loadenv.sh
-
 # OpenBLAS installation
 export EXTRA_SYSTEM_LIBRARY_PATH=${HOME}/Miniconda3/Library/lib
 
@@ -31,9 +30,10 @@ export EXTRA_SYSTEM_LIBRARY_PATH=${HOME}/Miniconda3/Library/lib
 # export EXTRA_SYSTEM_LIBRARY_PATH="C:/Program Files/MATLAB/R2022a/extern/lib/win64/microsoft"
 # EXTRA_CMAKE_OPTIONS="${EXTRA_CMAKE_OPTIONS} -DBLAS_NAMES=libmwblas -DLAPACK_NAMES=libmwlapack"
 
-mkdir -p build
-cd build
+mkdir -p ${BUILD_DIR:-build}
+cd ${BUILD_DIR:-build}
 cmake \
+  -DCMAKE_Fortran_COMPILER=$(command -v gfortran) \
   -DCMAKE_GENERATOR_PLATFORM=x64 \
   -DEXTRA_SYSTEM_LIBRARY_PATH="${EXTRA_SYSTEM_LIBRARY_PATH}" \
   -DENABLE_OCTAVE_BINDING=${ENABLE_OCTAVE_BINDING} \
diff --git a/.travis-ci/windows/install.sh b/.travis-ci/windows/install.sh
index f4b840c7..f21a8a4b 100755
--- a/.travis-ci/windows/install.sh
+++ b/.travis-ci/windows/install.sh
@@ -6,7 +6,8 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 if [[ "${ENABLE_COVERAGE}" == "on" ]]; then
     echo "Coverage not supported for Windows"
@@ -16,15 +17,15 @@ fi
 if [ ! -f "$HOME/Miniconda3/condabin/conda.bat" ]; then
 	curl -s -o ${HOME}/Downloads/Miniconda3-latest-Windows-x86_64.exe https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe
 	pushd ${HOME}/Downloads 
-	${BASEDIR}/install_conda.bat
+	"${BASEDIR}"/install_conda.bat
 	popd
 fi
 $HOME/Miniconda3/condabin/conda.bat update -y -n base -c defaults conda
 
 # https://anaconda.org/search?q=blas
 $HOME/Miniconda3/condabin/conda.bat install -y --quiet -n base -c conda-forge openblas liblapack
-
-. ${BASEDIR}/loadenv.sh
+# $HOME/Miniconda3/condabin/conda.bat install -c conda-forge fortran-compiler
+choco install mingw
 
 if [[ "$ENABLE_PYTHON_BINDING" == "on" ]]; then
   # Check if python is available (it could be a python wrapper given by Windows)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9ab06b29..207f7056 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,6 +27,11 @@ project(libKriging
 
 #------------------------------------------------------
 
+enable_language(CXX)
+enable_language(Fortran)
+
+#------------------------------------------------------
+
 # Prevent from root system installation
 if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
     set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/installed" CACHE PATH "default install path" FORCE)
@@ -163,6 +168,9 @@ add_definitions(-DOPTIM_ENABLE_ARMA_WRAPPERS)
 include(cmake/installOptimLibHeaders.cmake)
 include_directories(SYSTEM ${CMAKE_BINARY_DIR}/dependencies/optim/header_only_version)
 
+add_library(blas ALIAS armadillo) # to use armadillo blas inside lbfgsb_cpp
+add_subdirectory(dependencies/lbfgsb_cpp)
+
 #------------------------------------------------------
 
 # Default behaviour is to build shared library
diff --git a/README.md b/README.md
index b9ccdd23..8c8e34ce 100644
--- a/README.md
+++ b/README.md
@@ -122,7 +122,7 @@ library(rlibkriging)
 k_R <- Kriging(y, X, "gauss")
 print(k_R)
 # you can also check logLikelhood using:
-# ll = function(t) logLikelihoodFun(k_R,t)$logLikelihoodFun; plot(ll)
+# ll = function(t) logLikelihoodFun(k_R,t)$logLikelihood; plot(ll)
 x <- as.matrix(seq(0, 1, , 100))
 p <- predict(k_R, x, TRUE, FALSE)
 
diff --git a/bindings/Octave/Kriging_binding.cpp b/bindings/Octave/Kriging_binding.cpp
index aa18cbf3..a72885e6 100644
--- a/bindings/Octave/Kriging_binding.cpp
+++ b/bindings/Octave/Kriging_binding.cpp
@@ -97,15 +97,19 @@ void predict(int nlhs, mxArray** plhs, int nrhs, const mxArray** prhs) {
   MxMapper input{"Input",
                  nrhs,
                  const_cast<mxArray**>(prhs),  // NOLINT(cppcoreguidelines-pro-type-const-cast)
-                 RequiresArg::Range{2, 4}};
-  MxMapper output{"Output", nlhs, plhs, RequiresArg::Range{1, 3}};
+                 RequiresArg::Range{2, 5}};
+  MxMapper output{"Output", nlhs, plhs, RequiresArg::Range{1, 5}};
   auto* km = input.getObject<0, Kriging>("Kriging reference");
   const bool withStd = flag_output_compliance<2>(input, "withStd", output, 1);
   const bool withCov = flag_output_compliance<3>(input, "withCov", output, 2);
-  auto [y_pred, stderr_v, cov_m] = km->predict(input.get<1, arma::mat>("matrix"), withStd, withCov);
+  const bool withDeriv = flag_output_compliance<4>(input, "withDeriv", output, 3);
+  auto [y_pred, stderr_v, cov_m, mean_deriv_m, stderr_deriv_m]
+      = km->predict(input.get<1, arma::mat>("matrix"), withStd, withCov, withDeriv);
   output.set<0>(y_pred, "predicted response");
   output.setOptional<1>(stderr_v, "stderr vector");
   output.setOptional<2>(cov_m, "cov matrix");
+  output.setOptional<3>(mean_deriv_m, "predicted mean deriv matrix");
+  output.setOptional<4>(stderr_deriv_m, "predicted stdev deriv matrix");
 }
 
 void simulate(int nlhs, mxArray** plhs, int nrhs, const mxArray** prhs) {
diff --git a/bindings/Octave/NuggetKriging_binding.cpp b/bindings/Octave/NuggetKriging_binding.cpp
index ff0afc30..02f34ad2 100644
--- a/bindings/Octave/NuggetKriging_binding.cpp
+++ b/bindings/Octave/NuggetKriging_binding.cpp
@@ -99,15 +99,19 @@ void predict(int nlhs, mxArray** plhs, int nrhs, const mxArray** prhs) {
   MxMapper input{"Input",
                  nrhs,
                  const_cast<mxArray**>(prhs),  // NOLINT(cppcoreguidelines-pro-type-const-cast)
-                 RequiresArg::Range{2, 4}};
-  MxMapper output{"Output", nlhs, plhs, RequiresArg::Range{1, 3}};
+                 RequiresArg::Range{2, 5}};
+  MxMapper output{"Output", nlhs, plhs, RequiresArg::Range{1, 5}};
   auto* km = input.getObject<0, NuggetKriging>("NuggetKriging reference");
   const bool withStd = flag_output_compliance<2>(input, "withStd", output, 1);
   const bool withCov = flag_output_compliance<3>(input, "withCov", output, 2);
-  auto [y_pred, stderr_v, cov_m] = km->predict(input.get<1, arma::mat>("matrix"), withStd, withCov);
+  const bool withDeriv = flag_output_compliance<4>(input, "withDeriv", output, 3);
+  auto [y_pred, stderr_v, cov_m, mean_deriv_m, stderr_deriv_m]
+      = km->predict(input.get<1, arma::mat>("matrix"), withStd, withCov, withDeriv);
   output.set<0>(y_pred, "predicted response");
   output.setOptional<1>(stderr_v, "stderr vector");
   output.setOptional<2>(cov_m, "cov matrix");
+  output.setOptional<3>(mean_deriv_m, "predicted mean deriv matrix");
+  output.setOptional<4>(stderr_deriv_m, "predicted stdev deriv matrix");
 }
 
 void simulate(int nlhs, mxArray** plhs, int nrhs, const mxArray** prhs) {
@@ -145,11 +149,11 @@ void logLikelihoodFun(int nlhs, mxArray** plhs, int nrhs, const mxArray** prhs)
   MxMapper input{"Input",
                  nrhs,
                  const_cast<mxArray**>(prhs),  // NOLINT(cppcoreguidelines-pro-type-const-cast)
-                 RequiresArg::Range{2, 4}};
+                 RequiresArg::Range{2, 3}};
   MxMapper output{"Output", nlhs, plhs, RequiresArg::Range{1, 2}};
   auto* km = input.getObject<0, NuggetKriging>("NuggetKriging reference");
   const bool want_grad = flag_output_compliance<2>(input, "want_grad", output, 1);
-  auto [ll, llgrad] = km->logLikelihoodFun(input.get<1, arma::vec>("theta"), want_grad);
+  auto [ll, llgrad] = km->logLikelihoodFun(input.get<1, arma::vec>("theta_alpha"), want_grad);
   output.set<0>(ll, "ll");                  // FIXME better name
   output.setOptional<1>(llgrad, "llgrad");  // FIXME better name
 }
diff --git a/bindings/Python/src/_pylibkriging/Kriging_binding.cpp b/bindings/Python/src/_pylibkriging/Kriging_binding.cpp
index 24720117..f6fc96d1 100644
--- a/bindings/Python/src/_pylibkriging/Kriging_binding.cpp
+++ b/bindings/Python/src/_pylibkriging/Kriging_binding.cpp
@@ -38,12 +38,16 @@ void PyKriging::fit(const py::array_t<double>& y,
   m_internal->fit(mat_y, mat_X, regmodel, normalize, optim, objective, parameters);
 }
 
-std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>>
-PyKriging::predict(const py::array_t<double>& X, bool withStd, bool withCov) {
+std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>>
+PyKriging::predict(const py::array_t<double>& X, bool withStd, bool withCov, bool withDeriv) {
   arma::mat mat_X = carma::arr_to_mat_view<double>(X);
-  auto [y_predict, y_stderr, y_cov] = m_internal->predict(mat_X, withStd, withCov);
-  return std::make_tuple(
-      carma::col_to_arr(y_predict, true), carma::col_to_arr(y_stderr, true), carma::mat_to_arr(y_cov, true));
+  auto [y_predict, y_stderr, y_cov, y_mean_deriv, y_stderr_deriv]
+      = m_internal->predict(mat_X, withStd, withCov, withDeriv);
+  return std::make_tuple(carma::col_to_arr(y_predict, true),
+                         carma::col_to_arr(y_stderr, true),
+                         carma::mat_to_arr(y_cov, true),
+                         carma::mat_to_arr(y_mean_deriv, true),
+                         carma::mat_to_arr(y_stderr_deriv, true));
 }
 
 py::array_t<double> PyKriging::simulate(const int nsim, const int seed, const py::array_t<double>& Xp) {
@@ -99,3 +103,83 @@ std::tuple<double, py::array_t<double>> PyKriging::logMargPostFun(const py::arra
 double PyKriging::logMargPost() {
   return m_internal->logMargPost();
 }
+
+std::string PyKriging::kernel() {
+  return m_internal->kernel();
+}
+
+std::string PyKriging::optim() {
+  return m_internal->optim();
+}
+
+std::string PyKriging::objective() {
+  return m_internal->objective();
+}
+
+py::array_t<double> PyKriging::X() {
+  return carma::mat_to_arr(m_internal->X());
+}
+
+py::array_t<double> PyKriging::centerX() {
+  return carma::row_to_arr(m_internal->centerX());
+}
+
+py::array_t<double> PyKriging::scaleX() {
+  return carma::row_to_arr(m_internal->scaleX());
+}
+
+py::array_t<double> PyKriging::y() {
+  return carma::col_to_arr(m_internal->y());
+}
+
+double PyKriging::centerY() {
+  return m_internal->centerY();
+}
+
+double PyKriging::scaleY() {
+  return m_internal->scaleY();
+}
+
+std::string PyKriging::regmodel() {
+  return Trend::toString(m_internal->regmodel());
+}
+
+py::array_t<double> PyKriging::F() {
+  return carma::mat_to_arr(m_internal->F());
+}
+
+py::array_t<double> PyKriging::T() {
+  return carma::mat_to_arr(m_internal->T());
+}
+
+py::array_t<double> PyKriging::M() {
+  return carma::mat_to_arr(m_internal->M());
+}
+
+py::array_t<double> PyKriging::z() {
+  return carma::col_to_arr(m_internal->z());
+}
+
+py::array_t<double> PyKriging::beta() {
+  return carma::col_to_arr(m_internal->beta());
+}
+
+bool PyKriging::is_beta_estim() {
+  return m_internal->is_beta_estim();
+}
+
+py::array_t<double> PyKriging::theta() {
+  return carma::col_to_arr(m_internal->theta());
+}
+
+bool PyKriging::is_theta_estim() {
+  return m_internal->is_theta_estim();
+}
+
+double PyKriging::sigma2() {
+  return m_internal->sigma2();
+}
+
+bool PyKriging::is_sigma2_estim() {
+  return m_internal->is_sigma2_estim();
+}
\ No newline at end of file
diff --git a/bindings/Python/src/_pylibkriging/Kriging_binding.hpp b/bindings/Python/src/_pylibkriging/Kriging_binding.hpp
index 65bce42f..e56e97d3 100644
--- a/bindings/Python/src/_pylibkriging/Kriging_binding.hpp
+++ b/bindings/Python/src/_pylibkriging/Kriging_binding.hpp
@@ -37,9 +37,8 @@ class PyKriging {
   // see
   // - https://docs.python.org/3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields
   // - https://github.com/pybind/pybind11/issues/1244
-  std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>> predict(const py::array_t<double>& X,
-                                                                                    bool withStd,
-                                                                                    bool withCov);
+  std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>>
+  predict(const py::array_t<double>& X, bool withStd, bool withCov, bool withDeriv);
 
   py::array_t<double> simulate(const int nsim, const int seed, const py::array_t<double>& Xp);
 
@@ -59,6 +58,27 @@ class PyKriging {
   double leaveOneOut();
   double logMargPost();
 
+  std::string kernel();
+  std::string optim();
+  std::string objective();
+  py::array_t<double> X();
+  py::array_t<double> centerX();
+  py::array_t<double> scaleX();
+  py::array_t<double> y();
+  double centerY();
+  double scaleY();
+  std::string regmodel();  // Trend::toString(km->regmodel())
+  py::array_t<double> F();
+  py::array_t<double> T();
+  py::array_t<double> M();
+  py::array_t<double> z();
+  py::array_t<double> beta();
+  bool is_beta_estim();
+  py::array_t<double> theta();
+  bool is_theta_estim();
+  double sigma2();
+  bool is_sigma2_estim();
+
  private:
   std::unique_ptr<Kriging> m_internal;
 };
diff --git a/bindings/Python/src/_pylibkriging/NuggetKriging_binding.cpp b/bindings/Python/src/_pylibkriging/NuggetKriging_binding.cpp
index 6aecdfa6..fa2c03e7 100644
--- a/bindings/Python/src/_pylibkriging/NuggetKriging_binding.cpp
+++ b/bindings/Python/src/_pylibkriging/NuggetKriging_binding.cpp
@@ -39,12 +39,16 @@ void PyNuggetKriging::fit(const py::array_t<double>& y,
   m_internal->fit(mat_y, mat_X, regmodel, normalize, optim, objective, parameters);
 }
 
-std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>>
-PyNuggetKriging::predict(const py::array_t<double>& X, bool withStd, bool withCov) {
+std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>>
+PyNuggetKriging::predict(const py::array_t<double>& X, bool withStd, bool withCov, bool withDeriv) {
   arma::mat mat_X = carma::arr_to_mat_view<double>(X);
-  auto [y_predict, y_stderr, y_cov] = m_internal->predict(mat_X, withStd, withCov);
-  return std::make_tuple(
-      carma::col_to_arr(y_predict, true), carma::col_to_arr(y_stderr, true), carma::mat_to_arr(y_cov, true));
+  auto [y_predict, y_stderr, y_cov, y_mean_deriv, y_stderr_deriv]
+      = m_internal->predict(mat_X, withStd, withCov, withDeriv);
+  return std::make_tuple(carma::col_to_arr(y_predict, true),
+                         carma::col_to_arr(y_stderr, true),
+                         carma::mat_to_arr(y_cov, true),
+                         carma::mat_to_arr(y_mean_deriv, true),
+                         carma::mat_to_arr(y_stderr_deriv, true));
 }
 
 py::array_t<double> PyNuggetKriging::simulate(const int nsim, const int seed, const py::array_t<double>& Xp) {
@@ -63,30 +67,112 @@ std::string PyNuggetKriging::summary() const {
   return m_internal->summary();
 }
 
-std::tuple<double, py::array_t<double>, py::array_t<double>> PyNuggetKriging::logLikelihoodFun(
-    const py::array_t<double>& theta,
-    const bool want_grad) {
-  arma::vec vec_theta = carma::arr_to_col<double>(theta);
-  auto [llo, grad] = m_internal->logLikelihoodFun(vec_theta, want_grad);
-  return {
-      llo,
-      carma::col_to_arr(grad),
-      // carma::mat_to_arr(hess)  // FIXME error in hessian transmission
-      {}  //
-  };
+std::tuple<double, py::array_t<double>> PyNuggetKriging::logLikelihoodFun(const py::array_t<double>& theta_alpha,
+                                                                          const bool want_grad) {
+  arma::vec vec_theta_alpha = carma::arr_to_col<double>(theta_alpha);
+  auto [llo, grad] = m_internal->logLikelihoodFun(vec_theta_alpha, want_grad);
+  return {llo, carma::col_to_arr(grad)};
 }
 
 double PyNuggetKriging::logLikelihood() {
   return m_internal->logLikelihood();
 }
 
-std::tuple<double, py::array_t<double>> PyNuggetKriging::logMargPostFun(const py::array_t<double>& theta,
+std::tuple<double, py::array_t<double>> PyNuggetKriging::logMargPostFun(const py::array_t<double>& theta_alpha,
                                                                         const bool want_grad) {
-  arma::vec vec_theta = carma::arr_to_col<double>(theta);
-  auto [lmp, grad] = m_internal->logMargPostFun(vec_theta, want_grad);
+  arma::vec vec_theta_alpha = carma::arr_to_col<double>(theta_alpha);
+  auto [lmp, grad] = m_internal->logMargPostFun(vec_theta_alpha, want_grad);
   return {lmp, carma::col_to_arr(grad)};
 }
 
 double PyNuggetKriging::logMargPost() {
   return m_internal->logMargPost();
 }
+
+std::string PyNuggetKriging::kernel() {
+  return m_internal->kernel();
+}
+
+std::string PyNuggetKriging::optim() {
+  return m_internal->optim();
+}
+
+std::string PyNuggetKriging::objective() {
+  return m_internal->objective();
+}
+
+py::array_t<double> PyNuggetKriging::X() {
+  return carma::mat_to_arr(m_internal->X());
+}
+
+py::array_t<double> PyNuggetKriging::centerX() {
+  return carma::row_to_arr(m_internal->centerX());
+}
+
+py::array_t<double> PyNuggetKriging::scaleX() {
+  return carma::row_to_arr(m_internal->scaleX());
+}
+
+py::array_t<double> PyNuggetKriging::y() {
+  return carma::col_to_arr(m_internal->y());
+}
+
+double PyNuggetKriging::centerY() {
+  return m_internal->centerY();
+}
+
+double PyNuggetKriging::scaleY() {
+  return m_internal->scaleY();
+}
+
+std::string PyNuggetKriging::regmodel() {
+  return Trend::toString(m_internal->regmodel());
+}
+
+py::array_t<double> PyNuggetKriging::F() {
+  return carma::mat_to_arr(m_internal->F());
+}
+
+py::array_t<double> PyNuggetKriging::T() {
+  return carma::mat_to_arr(m_internal->T());
+}
+
+py::array_t<double> PyNuggetKriging::M() {
+  return carma::mat_to_arr(m_internal->M());
+}
+
+py::array_t<double> PyNuggetKriging::z() {
+  return carma::col_to_arr(m_internal->z());
+}
+
+py::array_t<double> PyNuggetKriging::beta() {
+  return carma::col_to_arr(m_internal->beta());
+}
+
+bool PyNuggetKriging::is_beta_estim() {
+  return m_internal->is_beta_estim();
+}
+
+py::array_t<double> PyNuggetKriging::theta() {
+  return carma::col_to_arr(m_internal->theta());
+}
+
+bool PyNuggetKriging::is_theta_estim() {
+  return m_internal->is_theta_estim();
+}
+
+double PyNuggetKriging::sigma2() {
+  return m_internal->sigma2();
+}
+
+bool PyNuggetKriging::is_sigma2_estim() {
+  return m_internal->is_sigma2_estim();
+}
+
+double PyNuggetKriging::nugget() {
+  return m_internal->nugget();
+}
+
+bool PyNuggetKriging::is_nugget_estim() {
+  return m_internal->is_nugget_estim();
+}
\ No newline at end of file
diff --git a/bindings/Python/src/_pylibkriging/NuggetKriging_binding.hpp b/bindings/Python/src/_pylibkriging/NuggetKriging_binding.hpp
index 08850bc0..a7aab332 100644
--- a/bindings/Python/src/_pylibkriging/NuggetKriging_binding.hpp
+++ b/bindings/Python/src/_pylibkriging/NuggetKriging_binding.hpp
@@ -37,9 +37,8 @@ class PyNuggetKriging {
   // see
   // - https://docs.python.org/3/library/collections.html#namedtuple-factory-function-for-tuples-with-named-fields
   // - https://github.com/pybind/pybind11/issues/1244
-  std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>> predict(const py::array_t<double>& X,
-                                                                                    bool withStd,
-                                                                                    bool withCov);
+  std::tuple<py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>, py::array_t<double>>
+  predict(const py::array_t<double>& X, bool withStd, bool withCov, bool withDeriv);
 
   py::array_t<double> simulate(const int nsim, const int seed, const py::array_t<double>& Xp);
 
@@ -47,14 +46,37 @@ class PyNuggetKriging {
 
   std::string summary() const;
 
-  std::tuple<double, py::array_t<double>, py::array_t<double>> logLikelihoodFun(const py::array_t<double>& theta,
-                                                                                const bool want_grad);
+  std::tuple<double, py::array_t<double>> logLikelihoodFun(const py::array_t<double>& theta_alpha,
+                                                           const bool want_grad);
 
-  std::tuple<double, py::array_t<double>> logMargPostFun(const py::array_t<double>& theta, const bool want_grad);
+  std::tuple<double, py::array_t<double>> logMargPostFun(const py::array_t<double>& theta_alpha, const bool want_grad);
 
   double logLikelihood();
   double logMargPost();
 
+  std::string kernel();
+  std::string optim();
+  std::string objective();
+  py::array_t<double> X();
+  py::array_t<double> centerX();
+  py::array_t<double> scaleX();
+  py::array_t<double> y();
+  double centerY();
+  double scaleY();
+  std::string regmodel();  // Trend::toString(km->regmodel())
+  py::array_t<double> F();
+  py::array_t<double> T();
+  py::array_t<double> M();
+  py::array_t<double> z();
+  py::array_t<double> beta();
+  bool is_beta_estim();
+  py::array_t<double> theta();
+  bool is_theta_estim();
+  double sigma2();
+  bool is_sigma2_estim();
+  double nugget();
+  bool is_nugget_estim();
+
  private:
   std::unique_ptr<NuggetKriging> m_internal;
 };
diff --git a/bindings/Python/src/_pylibkriging/pylibkriging.cpp b/bindings/Python/src/_pylibkriging/pylibkriging.cpp
index 5251c473..9b62c63a 100644
--- a/bindings/Python/src/_pylibkriging/pylibkriging.cpp
+++ b/bindings/Python/src/_pylibkriging/pylibkriging.cpp
@@ -110,7 +110,30 @@ PYBIND11_MODULE(_pylibkriging, m) {
       .def("summary", &PyKriging::summary)
       .def("leaveOneOutFun", &PyKriging::leaveOneOutFun)
       .def("logLikelihoodFun", &PyKriging::logLikelihoodFun)
-      .def("logMargPostFun", &PyKriging::logMargPostFun);
+      .def("logMargPostFun", &PyKriging::logMargPostFun)
+      .def("logLikelihood", &PyKriging::logLikelihood)
+      .def("logMargPost", &PyKriging::logMargPost)
+
+      .def("kernel", &PyKriging::kernel)
+      .def("optim", &PyKriging::optim)
+      .def("objective", &PyKriging::objective)
+      .def("X", &PyKriging::X)
+      .def("centerX", &PyKriging::centerX)
+      .def("scaleX", &PyKriging::scaleX)
+      .def("y", &PyKriging::y)
+      .def("centerY", &PyKriging::centerY)
+      .def("scaleY", &PyKriging::scaleY)
+      .def("regmodel", &PyKriging::regmodel)
+      .def("F", &PyKriging::F)
+      .def("T", &PyKriging::T)
+      .def("M", &PyKriging::M)
+      .def("z", &PyKriging::z)
+      .def("beta", &PyKriging::beta)
+      .def("is_beta_estim", &PyKriging::is_beta_estim)
+      .def("theta", &PyKriging::theta)
+      .def("is_theta_estim", &PyKriging::is_theta_estim)
+      .def("sigma2", &PyKriging::sigma2)
+      .def("is_sigma2_estim", &PyKriging::is_sigma2_estim);
 
   // Automated mapper
   py::class_<Kriging>(m, "PyKriging")
@@ -201,7 +224,32 @@ PYBIND11_MODULE(_pylibkriging, m) {
       .def("update", &PyNuggetKriging::update)
       .def("summary", &PyNuggetKriging::summary)
       .def("logLikelihoodFun", &PyNuggetKriging::logLikelihoodFun)
-      .def("logMargPostFun", &PyNuggetKriging::logMargPostFun);
+      .def("logMargPostFun", &PyNuggetKriging::logMargPostFun)
+      .def("logLikelihood", &PyNuggetKriging::logLikelihood)
+      .def("logMargPost", &PyNuggetKriging::logMargPost)
+
+      .def("kernel", &PyNuggetKriging::kernel)
+      .def("optim", &PyNuggetKriging::optim)
+      .def("objective", &PyNuggetKriging::objective)
+      .def("X", &PyNuggetKriging::X)
+      .def("centerX", &PyNuggetKriging::centerX)
+      .def("scaleX", &PyNuggetKriging::scaleX)
+      .def("y", &PyNuggetKriging::y)
+      .def("centerY", &PyNuggetKriging::centerY)
+      .def("scaleY", &PyNuggetKriging::scaleY)
+      .def("regmodel", &PyNuggetKriging::regmodel)
+      .def("F", &PyNuggetKriging::F)
+      .def("T", &PyNuggetKriging::T)
+      .def("M", &PyNuggetKriging::M)
+      .def("z", &PyNuggetKriging::z)
+      .def("beta", &PyNuggetKriging::beta)
+      .def("is_beta_estim", &PyNuggetKriging::is_beta_estim)
+      .def("theta", &PyNuggetKriging::theta)
+      .def("is_theta_estim", &PyNuggetKriging::is_theta_estim)
+      .def("sigma2", &PyNuggetKriging::sigma2)
+      .def("is_sigma2_estim", &PyNuggetKriging::is_sigma2_estim)
+      .def("nugget", &PyNuggetKriging::nugget)
+      .def("is_nugget_estim", &PyNuggetKriging::is_nugget_estim);
 
   // Automated mapper
   py::class_<NuggetKriging>(m, "PyNuggetKriging")
diff --git a/bindings/Python/src/pylibkriging/__init__.py b/bindings/Python/src/pylibkriging/__init__.py
index 038e3167..cccb73c8 100644
--- a/bindings/Python/src/pylibkriging/__init__.py
+++ b/bindings/Python/src/pylibkriging/__init__.py
@@ -28,5 +28,5 @@
 
 # Type alias to switch to the right binding
 Kriging = WrappedPyKriging
-NuggetKriging = WrappedPyNuggetKriging
-LinearRegression = WrappedPyLinearRegression
\ No newline at end of file
+NuggetKriging = PyNuggetKriging
+LinearRegression = PyLinearRegression
\ No newline at end of file
diff --git a/bindings/R/Makefile b/bindings/R/Makefile
index 8a1b7021..68ab1946 100644
--- a/bindings/R/Makefile
+++ b/bindings/R/Makefile
@@ -11,7 +11,7 @@ build: ./Rlibs/.prepared
 	mkdir -p ./Rlibs
 	## --no-multiarch is only usefull for windows with visual studio mode
 	export R_LIBS=$(PWD)/Rlibs && Rscript -e "Rcpp::compileAttributes(pkgdir = 'rlibkriging', verbose = TRUE)"
-	export R_LIBS=$(PWD)/Rlibs && Rscript -e "roxygen2::roxygenise(package.dir = 'rlibkriging')"
+	# export R_LIBS=$(PWD)/Rlibs && Rscript -e "roxygen2::roxygenise(package.dir = 'rlibkriging')"
 	export R_LIBS=$(PWD)/Rlibs && R CMD INSTALL --no-multiarch -l ./Rlibs --build rlibkriging
 	uname -a > ./Rlibs/rlibkriging/.installed
 	date >> ./Rlibs/rlibkriging/.installed
diff --git a/bindings/R/bench_perf-full.Rmd b/bindings/R/bench_perf-full.Rmd
index 74be9cd2..e8d5d896 100644
--- a/bindings/R/bench_perf-full.Rmd
+++ b/bindings/R/bench_perf-full.Rmd
@@ -157,9 +157,9 @@ N <- 100
 ```
 
 
-### logLikelihoodFun (& deriv.)
+### logLikelihood (& deriv.)
 
-```{r logLikelihoodFun}
+```{r logLikelihood}
 times <- list(R=rep(NA, length(logn)), cpp=rep(NA, length(logn)))
 
 theta <- 0.5
@@ -184,7 +184,7 @@ for (i in 1:length(logn)) {
              theta=matrix(k@covariance@range.val,ncol=d)))
   times$cpp[i] = system.time(
     # try({ll2x <- bench_LogLik(N,r,rep(theta,ncol(X)))})
-    try({for (t in 1:N) ll2x <- rlibkriging::logLikelihoodFun(r,rep(theta,ncol(X)))$logLikelihoodFun[1]}) # Loop should be done inside c++, not from R...
+    try({for (t in 1:N) ll2x <- rlibkriging::logLikelihoodFun(r,rep(theta,ncol(X)))$logLikelihood[1]}) # Loop should be done inside c++, not from R...
   )[1]
   
   if (abs((llx-ll2x)/llx)>1E-3) warning("LL is not identical bw C++/R")
@@ -194,7 +194,7 @@ plot_times(times,N,"logLikelihood")
 ```
 
 
-```{r logLikelihoodFun-grad}
+```{r logLikelihood-grad}
 times <- list(R=rep(NA, length(logn)), cpp=rep(NA, length(logn)))
 
 theta <- 0.5
@@ -226,7 +226,7 @@ for (i in 1:length(logn)) {
   if (max(abs((gllx-gll2x)/gllx))>1E-3) warning("grad LL is not identical bw C++/R")
 }
 
-plot_times(times,N,"logLikelihoodFun Grad")
+plot_times(times,N,"logLikelihood Grad")
 ```
 
 
@@ -307,9 +307,9 @@ plot_times(times,N,"leaveOneOut Grad")
 
 ### fit
 
-#### logLikelihoodFun / BFGS
+#### logLikelihood / BFGS
 
-```{r logLikelihoodFun-BFGS}
+```{r logLikelihood-BFGS}
 times <- list(R=rep(NA, length(logn)), cpp=rep(NA, length(logn)))
 
 for (i in 1:length(logn)) {
@@ -336,7 +336,7 @@ for (i in 1:length(logn)) {
                                     parameters=list(theta=matrix(k@parinit,ncol=d))))
   )[1]
   
-  ll_cpp <- logLikelihoodFun(r, t(as.list(r)$theta))$logLikelihoodFun[1]
+  ll_cpp <- logLikelihoodFun(r, t(as.list(r)$theta))$logLikelihood[1]
   e <- new.env()
   ll_R <- DiceKriging::logLikFun(k@covariance@range.val, k, e)
   
@@ -344,7 +344,7 @@ for (i in 1:length(logn)) {
   gll_R <- DiceKriging::logLikGrad(k@covariance@range.val, k, e)
   
   if (abs(ll_cpp - DiceKriging::logLikFun(param=as.numeric(as.list(r)$theta),model=k))/ll_cpp>.1)
-    warning("LL function is not the same bw DiceKriging/libKriging: ",logLikelihoodFun(r,t(as.list(r)$theta))$logLikelihoodFun[1]," vs. ",DiceKriging::logLikFun(param=as.numeric(as.list(r)$theta),model=k))  
+    warning("LL function is not the same bw DiceKriging/libKriging: ",logLikelihoodFun(r,t(as.list(r)$theta))$logLikelihood[1]," vs. ",DiceKriging::logLikFun(param=as.numeric(as.list(r)$theta),model=k))  
   
   if ((ll_cpp - ll_R)/ll_R < -.01 )
     warning("libKriging LL ",ll_cpp," << DiceKriging LL ",ll_R)
@@ -353,7 +353,7 @@ for (i in 1:length(logn)) {
     warning("DiceKriging LL ",ll_R," << libKriging LL ",ll_cpp)
 }
 
-plot_times(times,N,"fit logLikelihoodFun / BFGS")
+plot_times(times,N,"fit logLikelihood / BFGS")
 ```
 
 
@@ -408,9 +408,9 @@ plot_times(times,N,"fit leaveOneOut / BFGS")
 ```
 
 
-#### logLikelihoodFun / Newton
+#### logLikelihood / Newton
 
-```{r logLikelihoodFun-Newton}
+```{r logLikelihood-Newton}
 times <- list(R=rep(NA, length(logn)), cpp=rep(NA, length(logn)))
 
 for (i in 1:length(logn)) {
@@ -437,7 +437,7 @@ for (i in 1:length(logn)) {
                                     parameters=list(theta=matrix(k@parinit,ncol=d))))
   )[1]
   
-  ll_cpp <- logLikelihoodFun(r, t(as.list(r)$theta))$logLikelihoodFun[1]
+  ll_cpp <- logLikelihoodFun(r, t(as.list(r)$theta))$logLikelihood[1]
   e <- new.env()
   ll_R <- DiceKriging::logLikFun(k@covariance@range.val, k, e)
   
@@ -454,7 +454,7 @@ for (i in 1:length(logn)) {
     warning("DiceKriging LL ",ll_R," << libKriging LL ",ll_cpp)
 }
 
-plot_times(times,N,"fit logLikelihoodFun / Newton")
+plot_times(times,N,"fit logLikelihood / Newton")
 ```
 
 
diff --git a/bindings/R/rlibkriging/NAMESPACE b/bindings/R/rlibkriging/NAMESPACE
index eb36ad7e..216681ad 100644
--- a/bindings/R/rlibkriging/NAMESPACE
+++ b/bindings/R/rlibkriging/NAMESPACE
@@ -34,7 +34,6 @@ export(bench_cholsym)
 export(bench_invsympd)
 export(bench_qr)
 export(bench_solvetri)
-export(get_num_nugget)
 export(leaveOneOut)
 export(leaveOneOutFun)
 export(linalg_get_num_nugget)
@@ -43,9 +42,22 @@ export(logLikelihood)
 export(logLikelihoodFun)
 export(logMargPost)
 export(logMargPostFun)
+export(optim_get_gradient_tolerance)
+export(optim_get_max_iteration)
+export(optim_get_objective_rel_tolerance)
+export(optim_get_theta_lower_factor)
+export(optim_get_theta_upper_factor)
+export(optim_is_reparametrized)
+export(optim_log)
+export(optim_set_gradient_tolerance)
+export(optim_set_max_iteration)
+export(optim_set_objective_rel_tolerance)
+export(optim_set_theta_lower_factor)
+export(optim_set_theta_upper_factor)
+export(optim_use_reparametrize)
+export(optim_use_variogram_bounds_heuristic)
+export(optim_variogram_bounds_heuristic_used)
 export(random_reset_seed)
-export(reset_seed)
-export(set_num_nugget)
 exportMethods(predict)
 exportMethods(simulate)
 exportMethods(update)
diff --git a/bindings/R/rlibkriging/R/KMClass.R b/bindings/R/rlibkriging/R/KMClass.R
index 448b382a..b2ecad7f 100644
--- a/bindings/R/rlibkriging/R/KMClass.R
+++ b/bindings/R/rlibkriging/R/KMClass.R
@@ -96,7 +96,7 @@ if (requireNamespace("DiceKriging", quietly = TRUE))
 #' @param coef.var Optional value for a fixed variance. If given, no
 #'     optimization is done.
 #'
-#' @param nugget,nugget.estim,nugget.var Not implemented yet. 
+#' @param nugget,nugget.estim,noise.var Not implemented yet. 
 #'
 #' @param estim.method Estimation criterion. \code{"MLE"} for
 #'     Maximum-Likelihood or \code{"LOO"} for Leave-One-Out
@@ -142,7 +142,7 @@ if (requireNamespace("DiceKriging", quietly = TRUE))
 KM <- function(formula = ~1, design, response,
                covtype = c("matern5_2", "gauss", "matern3_2", "exp"),
                coef.trend = NULL, coef.cov = NULL, coef.var = NULL,
-               nugget = NULL, nugget.estim = FALSE, nugget.var = NULL,
+               nugget = NULL, nugget.estim = FALSE, noise.var = NULL,
                estim.method = c("MLE", "LOO"), penalty = NULL,
                optim.method = "BFGS",
                lower = NULL, upper = NULL, parinit = NULL,
@@ -159,16 +159,16 @@ KM <- function(formula = ~1, design, response,
     if (!is.null(penalty)) {
         stop("The formal arg 'penalty' can not be used for now.")
     }
-    if (!is.null(nugget) || nugget.estim || !is.null(nugget.var)) {
-        stop("The formal args 'nugget', 'nugget.estim' and 'nugget.var' ",
-             "can not be used for now.")
+    if (nugget.estim) {
+        stop("The formal args 'nugget.estim' ",
+             "can only be used with NKM()")
     }
-    if (!is.null(lower) || !is.null(upper)) {
-        stop("The formal args 'lower', 'upper' and 'parinit' ",
+    if (!is.null(nugget) || !is.null(noise.var)) {
+        stop("The formal args 'nugget' and 'noise.var' ",
              "can not be used for now.")
     }
-    if ((multistart != 1) || !is.null(control) || !gr || iso) {
-         stop("The formal args 'multistart', 'control', 'gr' ",
+    if (!is.null(control) || !gr || iso) {
+         stop("The formal args'control', 'gr' ",
               "and 'iso' can not be used for now.")
     }
     if (scaling || !is.null(knots) || !is.null(kernel)) {
@@ -213,14 +213,30 @@ KM <- function(formula = ~1, design, response,
     }
     if (length(parameters) == 0) parameters <- NULL
     
+    # DiceKriging standard bounds for theta
+    bounds_heuristic = rlibkriging:::optim_variogram_bounds_heuristic_used()
+    rlibkriging:::optim_use_variogram_bounds_heuristic(FALSE)
+    theta_lower_factor = rlibkriging:::optim_get_theta_lower_factor()
+    if (is.null(lower)) lower = 1E-10
+    rlibkriging:::optim_set_theta_lower_factor(lower)
+    if (is.null(upper)) upper = 2.0
+    theta_upper_factor = rlibkriging:::optim_get_theta_upper_factor()
+    rlibkriging:::optim_set_theta_upper_factor(upper)
+
+    if (multistart<=1) multistart=""
     r <- rlibkriging::Kriging(y = response, X = design, kernel = covtype,
                               regmodel = formula,
-                              normalize = FALSE, 
-                              objective = estim.method, optim = optim.method,
+                              normalize = FALSE,
+                              objective = estim.method,
+                              optim = paste0(optim.method, multistart),
                               parameters = parameters)
     
+    # Back to previous setup
+    rlibkriging:::optim_use_variogram_bounds_heuristic(bounds_heuristic)
+    rlibkriging:::optim_set_theta_lower_factor(theta_lower_factor)
+    rlibkriging:::optim_set_theta_upper_factor(theta_upper_factor)
+
     return(as.km.Kriging(r, .call = match.call()))
-    
 }
 
 ## *****************************************************************************
diff --git a/bindings/R/rlibkriging/R/KrigingClass.R b/bindings/R/rlibkriging/R/KrigingClass.R
index 64ae27e0..b87f6c81 100644
--- a/bindings/R/rlibkriging/R/KrigingClass.R
+++ b/bindings/R/rlibkriging/R/KrigingClass.R
@@ -298,6 +298,9 @@ print.Kriging <- function(x, ...) {
 #' @param cov \code{Logical}. If \code{TRUE} the covariance matrix of
 #'     the predictions is returned.
 #'
+#' @param deriv \code{Logical}. If \code{TRUE} the derivatives of mean and sd
+#'     of the predictions are returned.
+#'
 #' @param ... Ignored.
 #'
 #' @return A list containing the element \code{mean} and possibly
@@ -327,15 +330,16 @@ print.Kriging <- function(x, ...) {
 #' lines(x, p_x$mean, col = "blue")
 #' lines(x, p_x$mean - 2 * p_x$stdev, col = "blue")
 #' lines(x, p_x$mean + 2 * p_x$stdev, col = "blue")
-predict.Kriging <- function(object, x, stdev = TRUE, cov = FALSE, ...) {
+predict.Kriging <- function(object, x, stdev = TRUE, cov = FALSE, deriv = FALSE, ...) {
     if (length(L <- list(...)) > 0) warnOnDots(L)
     k <- kriging_model(object)
     ## manage the data frame case. Ideally we should then warn
+    if (is.data.frame(x)) x = data.matrix(x)
     if (!is.matrix(x)) x=matrix(x,ncol=ncol(k$X))
     if (ncol(x) != ncol(k$X))
         stop("Input x must have ", ncol(k$X), " columns (instead of ",
              ncol(x), ")")
-    return(kriging_predict(object, x, stdev, cov))
+    return(kriging_predict(object, x, stdev, cov, deriv))
 }
 
 ## predict <- function (...) UseMethod("predict")
@@ -389,6 +393,7 @@ predict.Kriging <- function(object, x, stdev = TRUE, cov = FALSE, ...) {
 simulate.Kriging <- function(object, nsim = 1, seed = 123, x,  ...) {
     if (length(L <- list(...)) > 0) warnOnDots(L)
     k <- kriging_model(object) 
+    if (is.data.frame(x)) x = data.matrix(x)
     if (!is.matrix(x)) x = matrix(x, ncol = ncol(k$X))
     if (ncol(x) != ncol(k$X))
         stop("Input x must have ", ncol(k$X), " columns (instead of ",
@@ -457,8 +462,10 @@ simulate.Kriging <- function(object, nsim = 1, seed = 123, x,  ...) {
 update.Kriging <- function(object, newy, newX, ...) {
     
     if (length(L <- list(...)) > 0) warnOnDots(L)
-    k <- kriging_model(object) 
+    k <- kriging_model(object)
+    if (is.data.frame(newX)) newX = data.matrix(newX)
     if (!is.matrix(newX)) newX <- matrix(newX, ncol = ncol(k$X))
+    if (is.data.frame(newy)) newy = data.matrix(newy)
     if (!is.matrix(newy)) newy <- matrix(newy, ncol = ncol(k$y))
     if (ncol(newX) != ncol(k$X))
         stop("Object 'newX' must have ", ncol(k$X), " columns (instead of ",
@@ -510,27 +517,28 @@ update.Kriging <- function(object, newy, newX, ...) {
 #' 
 logLikelihoodFun.Kriging <- function(object, theta,
                                   grad = FALSE, hess = FALSE, ...) {
-  k <- kriging_model(object) 
-  if (!is.matrix(theta)) theta <- matrix(theta, ncol = ncol(k$X))
-  if (ncol(theta) != ncol(k$X))
-      stop("Input theta must have ", ncol(k$X), " columns (instead of ",
-           ncol(theta),")")
-  out <- list(logLikelihood = matrix(NA, nrow = nrow(theta)),
-              logLikelihoodGrad = matrix(NA,nrow=nrow(theta),
-                                         ncol = ncol(theta)),
-              logLikelihoodHess = array(NA, dim = c(nrow(theta), ncol(theta),
-                                                    ncol(theta))))
-  for (i in 1:nrow(theta)) {
-      ll <- kriging_logLikelihoodFun(object, theta[i, ],
-                                  grad = isTRUE(grad), hess = isTRUE(hess))
-      out$logLikelihood[i] <- ll$logLikelihood
-      if (isTRUE(grad)) out$logLikelihoodGrad[i, ] <- ll$logLikelihoodGrad
-      if (isTRUE(hess)) out$logLikelihoodHess[i, , ] <- ll$logLikelihoodHess
-  }
-  if (!isTRUE(grad)) out$logLikelihoodGrad <- NULL
-  if (!isTRUE(hess)) out$logLikelihoodHess <- NULL
-
-  return(out)
+    k <- kriging_model(object)
+    if (is.data.frame(theta)) theta = data.matrix(theta)
+    if (!is.matrix(theta)) theta <- matrix(theta, ncol = ncol(k$X))
+    if (ncol(theta) != ncol(k$X))
+        stop("Input theta must have ", ncol(k$X), " columns (instead of ",
+             ncol(theta),")")
+    out <- list(logLikelihood = matrix(NA, nrow = nrow(theta)),
+                logLikelihoodGrad = matrix(NA,nrow=nrow(theta),
+                                           ncol = ncol(theta)),
+                logLikelihoodHess = array(NA, dim = c(nrow(theta), ncol(theta),
+                                                      ncol(theta))))
+    for (i in 1:nrow(theta)) {
+        ll <- kriging_logLikelihoodFun(object, theta[i, ],
+                                    grad = isTRUE(grad), hess = isTRUE(hess))
+        out$logLikelihood[i] <- ll$logLikelihood
+        if (isTRUE(grad)) out$logLikelihoodGrad[i, ] <- ll$logLikelihoodGrad
+        if (isTRUE(hess)) out$logLikelihoodHess[i, , ] <- ll$logLikelihoodHess
+    }
+    if (!isTRUE(grad)) out$logLikelihoodGrad <- NULL
+    if (!isTRUE(hess)) out$logLikelihoodHess <- NULL
+  
+    return(out)
 }
 
 ## ****************************************************************************
@@ -605,6 +613,7 @@ logLikelihood.Kriging <- function(object, ...) {
 #' abline(v = as.list(r)$theta, col = "blue")
 leaveOneOutFun.Kriging <- function(object, theta, grad = FALSE, ...) {
     k <- kriging_model(object) 
+    if (is.data.frame(theta)) theta = data.matrix(theta)
     if (!is.matrix(theta)) theta <- matrix(theta,ncol=ncol(k$X))
     if (ncol(theta) != ncol(k$X))
         stop("Input theta must have ", ncol(k$X), " columns (instead of ",
@@ -688,7 +697,8 @@ leaveOneOut.Kriging <- function(object, ...) {
 #' plot(t, lmp(t), type = "l")
 #' abline(v = as.list(r)$theta, col = "blue")
 logMargPostFun.Kriging <- function(object, theta, grad = FALSE, ...) {
-    k <- kriging_model(object) 
+    k <- kriging_model(object)
+    if (is.data.frame(theta)) theta = data.matrix(theta)
     if (!is.matrix(theta)) theta <- matrix(theta,ncol=ncol(k$X))
     if (ncol(theta) != ncol(k$X))
         stop("Input theta must have ", ncol(k$X), " columns (instead of ",
diff --git a/bindings/R/rlibkriging/R/NKMClass.R b/bindings/R/rlibkriging/R/NKMClass.R
index 15c00164..32a59c99 100644
--- a/bindings/R/rlibkriging/R/NKMClass.R
+++ b/bindings/R/rlibkriging/R/NKMClass.R
@@ -96,7 +96,7 @@ if (requireNamespace("DiceKriging", quietly = TRUE))
 #' @param coef.var Optional value for a fixed variance. If given, no
 #'     optimization is done.
 #'
-#' @param nugget,nugget.estim,nugget.var Not implemented yet. 
+#' @param nugget,nugget.estim,noise.var Not implemented yet. 
 #'
 #' @param estim.method Estimation criterion. \code{"MLE"} for
 #'     Maximum-Likelihood or \code{"LOO"} for Leave-One-Out
@@ -143,7 +143,7 @@ if (requireNamespace("DiceKriging", quietly = TRUE))
 NKM <- function(formula = ~1, design, response,
                covtype = c("matern5_2", "gauss", "matern3_2", "exp"),
                coef.trend = NULL, coef.cov = NULL, coef.var = NULL,
-               nugget = NULL, nugget.estim = TRUE, nugget.var = NULL,
+               nugget = NULL, nugget.estim = TRUE, noise.var = NULL,
                estim.method = c("MLE", "LOO"), penalty = NULL,
                optim.method = "BFGS",
                lower = NULL, upper = NULL, parinit = NULL,
@@ -160,12 +160,16 @@ NKM <- function(formula = ~1, design, response,
     if (!is.null(penalty)) {
         stop("The formal arg 'penalty' can not be used for now.")
     }
-    if (!is.null(lower) || !is.null(upper)) {
-        stop("The formal args 'lower', 'upper' and 'parinit' ",
+    if (!nugget.estim) {
+        stop("The formal args 'nugget.estim=FALSE' ",
+             "can only be used with KM()")
+    }
+    if (!is.null(nugget) || !is.null(noise.var)) {
+        stop("The formal args 'nugget' and 'noise.var' ",
              "can not be used for now.")
     }
-    if ((multistart != 1) || !is.null(control) || !gr || iso) {
-         stop("The formal args 'multistart', 'control', 'gr' ",
+    if (!is.null(control) || !gr || iso) {
+         stop("The formal args 'control', 'gr' ",
               "and 'iso' can not be used for now.")
     }
     if (scaling || !is.null(knots) || !is.null(kernel)) {
@@ -214,14 +218,30 @@ NKM <- function(formula = ~1, design, response,
     }
     if (length(parameters) == 0) parameters <- NULL
     
+    # DiceKriging standard bounds for theta
+    bounds_heuristic = rlibkriging:::optim_variogram_bounds_heuristic_used()
+    rlibkriging:::optim_use_variogram_bounds_heuristic(FALSE)
+    theta_lower_factor = rlibkriging:::optim_get_theta_lower_factor()
+    if (is.null(lower)) lower = 1E-10
+    rlibkriging:::optim_set_theta_lower_factor(lower)
+    if (is.null(upper)) upper = 2.0
+    theta_upper_factor = rlibkriging:::optim_get_theta_upper_factor()
+    rlibkriging:::optim_set_theta_upper_factor(upper)
+
+    if (multistart<=1) multistart=""
     r <- rlibkriging::NuggetKriging(y = response, X = design, kernel = covtype,
                               regmodel = formula,
-                              normalize = FALSE, 
-                              objective = estim.method, optim = optim.method,
+                              normalize = FALSE,
+                              objective = estim.method,
+                              optim = paste0(optim.method, multistart),
                               parameters = parameters)
     
+    # Back to previous setup
+    rlibkriging:::optim_use_variogram_bounds_heuristic(bounds_heuristic)
+    rlibkriging:::optim_set_theta_lower_factor(theta_lower_factor)
+    rlibkriging:::optim_set_theta_upper_factor(theta_upper_factor)
+
     return(as.km.NuggetKriging(r, .call = match.call()))
-    
 }
 
 ## *****************************************************************************
diff --git a/bindings/R/rlibkriging/R/NuggetKrigingClass.R b/bindings/R/rlibkriging/R/NuggetKrigingClass.R
index a4c5424b..036327cf 100644
--- a/bindings/R/rlibkriging/R/NuggetKrigingClass.R
+++ b/bindings/R/rlibkriging/R/NuggetKrigingClass.R
@@ -298,6 +298,9 @@ print.NuggetKriging <- function(x, ...) {
 #' @param cov \code{Logical}. If \code{TRUE} the covariance matrix of
 #'     the predictions is returned.
 #'
+#' @param deriv \code{Logical}. If \code{TRUE} the derivatives of mean and sd
+#'     of the predictions are returned.
+#'
 #' @param ... Ignored.
 #'
 #' @return A list containing the element \code{mean} and possibly
@@ -327,15 +330,16 @@ print.NuggetKriging <- function(x, ...) {
 #' lines(x, p_x$mean, col = "blue")
 #' lines(x, p_x$mean - 2 * p_x$stdev, col = "blue")
 #' lines(x, p_x$mean + 2 * p_x$stdev, col = "blue")
-predict.NuggetKriging <- function(object, x, stdev = TRUE, cov = FALSE, ...) {
+predict.NuggetKriging <- function(object, x, stdev = TRUE, cov = FALSE, deriv = FALSE, ...) {
     if (length(L <- list(...)) > 0) warnOnDots(L)
     k <- nuggetkriging_model(object)
     ## manage the data frame case. Ideally we should then warn
+    if (is.data.frame(x)) x = data.matrix(x)
     if (!is.matrix(x)) x=matrix(x,ncol=ncol(k$X))
     if (ncol(x) != ncol(k$X))
         stop("Input x must have ", ncol(k$X), " columns (instead of ",
              ncol(x), ")")
-    return(nuggetkriging_predict(object, x, stdev, cov))
+    return(nuggetkriging_predict(object, x, stdev, cov, deriv))
 }
 
 ## predict <- function (...) UseMethod("predict")
@@ -389,6 +393,7 @@ predict.NuggetKriging <- function(object, x, stdev = TRUE, cov = FALSE, ...) {
 simulate.NuggetKriging <- function(object, nsim = 1, seed = 123, x,  ...) {
     if (length(L <- list(...)) > 0) warnOnDots(L)
     k <- nuggetkriging_model(object) 
+    if (is.data.frame(x)) x = data.matrix(x)
     if (!is.matrix(x)) x = matrix(x, ncol = ncol(k$X))
     if (ncol(x) != ncol(k$X))
         stop("Input x must have ", ncol(k$X), " columns (instead of ",
@@ -458,7 +463,9 @@ update.NuggetKriging <- function(object, newy, newX, ...) {
     
     if (length(L <- list(...)) > 0) warnOnDots(L)
     k <- nuggetkriging_model(object) 
+    if (is.data.frame(newX)) newX = data.matrix(newX)
     if (!is.matrix(newX)) newX <- matrix(newX, ncol = ncol(k$X))
+    if (is.data.frame(newy)) newy = data.matrix(newy)
     if (!is.matrix(newy)) newy <- matrix(newy, ncol = ncol(k$y))
     if (ncol(newX) != ncol(k$X))
         stop("Object 'newX' must have ", ncol(k$X), " columns (instead of ",
@@ -510,23 +517,24 @@ update.NuggetKriging <- function(object, newy, newX, ...) {
 #' 
 logLikelihoodFun.NuggetKriging <- function(object, theta_alpha,
                                   grad = FALSE, ...) {
-  k <- nuggetkriging_model(object) 
-  if (!is.matrix(theta_alpha)) theta_alpha <- matrix(theta_alpha, ncol = ncol(k$X)+1)
-  if (ncol(theta_alpha) != ncol(k$X)+1)
-      stop("Input theta_alpha must have ", ncol(k$X)+1, " columns (instead of ",
-           ncol(theta_alpha),")")
-  out <- list(logLikelihood = matrix(NA, nrow = nrow(theta_alpha)),
-              logLikelihoodGrad = matrix(NA,nrow=nrow(theta_alpha),
-                                         ncol = ncol(theta_alpha)))
-  for (i in 1:nrow(theta_alpha)) {
-      ll <- nuggetkriging_logLikelihoodFun(object, theta_alpha[i, ],
-                                  grad = isTRUE(grad))
-      out$logLikelihood[i] <- ll$logLikelihood
-      if (isTRUE(grad)) out$logLikelihoodGrad[i, ] <- ll$logLikelihoodGrad
-  }
-  if (!isTRUE(grad)) out$logLikelihoodGrad <- NULL
-
-  return(out)
+    k <- nuggetkriging_model(object) 
+    if (is.data.frame(theta_alpha)) theta_alpha = data.matrix(theta_alpha)
+    if (!is.matrix(theta_alpha)) theta_alpha <- matrix(theta_alpha, ncol = ncol(k$X)+1)
+    if (ncol(theta_alpha) != ncol(k$X)+1)
+        stop("Input theta_alpha must have ", ncol(k$X)+1, " columns (instead of ",
+             ncol(theta_alpha),")")
+    out <- list(logLikelihood = matrix(NA, nrow = nrow(theta_alpha)),
+                logLikelihoodGrad = matrix(NA,nrow=nrow(theta_alpha),
+                                           ncol = ncol(theta_alpha)))
+    for (i in 1:nrow(theta_alpha)) {
+        ll <- nuggetkriging_logLikelihoodFun(object, theta_alpha[i, ],
+                                    grad = isTRUE(grad))
+        out$logLikelihood[i] <- ll$logLikelihood
+        if (isTRUE(grad)) out$logLikelihoodGrad[i, ] <- ll$logLikelihoodGrad
+    }
+    if (!isTRUE(grad)) out$logLikelihoodGrad <- NULL
+  
+    return(out)
 }
 
 
@@ -598,7 +606,8 @@ logLikelihood.NuggetKriging <- function(object, ...) {
 #' plot(t, lmp(t), type = "l")
 #' abline(v = as.list(r)$theta, col = "blue")
 logMargPostFun.NuggetKriging <- function(object, theta_alpha, grad = FALSE, ...) {
-    k <- nuggetkriging_model(object) 
+    k <- nuggetkriging_model(object)
+    if (is.data.frame(theta_alpha)) theta_alpha = data.matrix(theta_alpha)
     if (!is.matrix(theta_alpha)) theta_alpha <- matrix(theta_alpha,ncol=ncol(k$X)+1)
     if (ncol(theta_alpha) != ncol(k$X)+1)
         stop("Input theta_alpha must have ", ncol(k$X)+1, " columns (instead of ",
diff --git a/bindings/R/rlibkriging/R/miscUtils.R b/bindings/R/rlibkriging/R/miscUtils.R
index b1becab3..e637cbd4 100644
--- a/bindings/R/rlibkriging/R/miscUtils.R
+++ b/bindings/R/rlibkriging/R/miscUtils.R
@@ -42,50 +42,12 @@ regmodel2formula = function(regmodel) {
   else stop("Unsupported regmodel ",regmodel)
 }
 
-
-## Setup computing parameters used in \code{Kriging} Object
-
-## ****************************************************************************
-#' Setup numerical nugget used in Cholesky decompositions
-#'
-#' @author Yann Richet \email{yann.richet@irsn.fr}
-#' 
-#' @param x Numerical nugget value.
-#' @param ... Ignored
-#' 
-#' @export
-set_num_nugget <- function(x, ...) {
-    if (length(L <- list(...)) > 0) warnOnDots(L)
-    linalg_set_num_nugget(x)
-}
-
-
-## ****************************************************************************
-#' Get numerical nugget used in Cholesky decompositions
-#'
-#' @author Yann Richet \email{yann.richet@irsn.fr}
-#' 
-#' @param ... Ignored
-#' 
-#' @return numerical nugget used.
-#' 
-#' @export
-get_num_nugget <- function(...) {
-    if (length(L <- list(...)) > 0) warnOnDots(L)
-    return(linalg_get_num_nugget())
-}
-
-
-## ****************************************************************************
-#' Set random ssed used in fit/optim
-#'
-#' @author Yann Richet \email{yann.richet@irsn.fr}
-#' 
-#' @param x Seed for mt19937 RNG
-#' @param ... Ignored
-#' #' 
-#' @export
-reset_seed <- function(x,...) {
-    if (length(L <- list(...)) > 0) warnOnDots(L)
-    random_reset_seed(x)
+as_numeric_matrix = function(x) {
+  if (is.matrix(x) & is.numeric(x))
+    return(x)
+  if (is.vector(x) & is.numeric(x))
+    return(matrix(x,nrow=1))
+  if (is.data.frame(x))
+    return(data.matrix(x))
+  stop(paste0("Data no convertible to numeric matrix: ", typeof(x)))
 }
\ No newline at end of file
diff --git a/bindings/R/rlibkriging/man/KM.Rd b/bindings/R/rlibkriging/man/KM.Rd
index bfb83e49..6cba3e61 100644
--- a/bindings/R/rlibkriging/man/KM.Rd
+++ b/bindings/R/rlibkriging/man/KM.Rd
@@ -14,7 +14,7 @@ KM(
   coef.var = NULL,
   nugget = NULL,
   nugget.estim = FALSE,
-  nugget.var = NULL,
+  noise.var = NULL,
   estim.method = c("MLE", "LOO"),
   penalty = NULL,
   optim.method = "BFGS",
@@ -51,7 +51,7 @@ value. If given, no optimization is done.}
 \item{coef.var}{Optional value for a fixed variance. If given, no
 optimization is done.}
 
-\item{nugget, nugget.estim, nugget.var}{Not implemented yet.}
+\item{nugget, nugget.estim, noise.var}{Not implemented yet.}
 
 \item{estim.method}{Estimation criterion. \code{"MLE"} for
 Maximum-Likelihood or \code{"LOO"} for Leave-One-Out
diff --git a/bindings/R/rlibkriging/man/NKM.Rd b/bindings/R/rlibkriging/man/NKM.Rd
index af61fb25..c7c9dd81 100644
--- a/bindings/R/rlibkriging/man/NKM.Rd
+++ b/bindings/R/rlibkriging/man/NKM.Rd
@@ -14,7 +14,7 @@ NKM(
   coef.var = NULL,
   nugget = NULL,
   nugget.estim = TRUE,
-  nugget.var = NULL,
+  noise.var = NULL,
   estim.method = c("MLE", "LOO"),
   penalty = NULL,
   optim.method = "BFGS",
@@ -51,7 +51,7 @@ value. If given, no optimization is done.}
 \item{coef.var}{Optional value for a fixed variance. If given, no
 optimization is done.}
 
-\item{nugget, nugget.estim, nugget.var}{Not implemented yet.}
+\item{nugget, nugget.estim, noise.var}{Not implemented yet.}
 
 \item{estim.method}{Estimation criterion. \code{"MLE"} for
 Maximum-Likelihood or \code{"LOO"} for Leave-One-Out
diff --git a/bindings/R/rlibkriging/man/get_num_nugget.Rd b/bindings/R/rlibkriging/man/get_num_nugget.Rd
deleted file mode 100644
index 183b02f3..00000000
--- a/bindings/R/rlibkriging/man/get_num_nugget.Rd
+++ /dev/null
@@ -1,20 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/miscUtils.R
-\name{get_num_nugget}
-\alias{get_num_nugget}
-\title{Get numerical nugget used in Cholesky decompositions}
-\usage{
-get_num_nugget(...)
-}
-\arguments{
-\item{...}{Ignored}
-}
-\value{
-numerical nugget used.
-}
-\description{
-Get numerical nugget used in Cholesky decompositions
-}
-\author{
-Yann Richet \email{yann.richet@irsn.fr}
-}
diff --git a/bindings/R/rlibkriging/man/reset_seed.Rd b/bindings/R/rlibkriging/man/reset_seed.Rd
deleted file mode 100644
index a64298f8..00000000
--- a/bindings/R/rlibkriging/man/reset_seed.Rd
+++ /dev/null
@@ -1,20 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/miscUtils.R
-\name{reset_seed}
-\alias{reset_seed}
-\title{Set random ssed used in fit/optim}
-\usage{
-reset_seed(x, ...)
-}
-\arguments{
-\item{x}{Seed for mt19937 RNG}
-
-\item{...}{Ignored
-#'}
-}
-\description{
-Set random ssed used in fit/optim
-}
-\author{
-Yann Richet \email{yann.richet@irsn.fr}
-}
diff --git a/bindings/R/rlibkriging/man/set_num_nugget.Rd b/bindings/R/rlibkriging/man/set_num_nugget.Rd
deleted file mode 100644
index b77fd948..00000000
--- a/bindings/R/rlibkriging/man/set_num_nugget.Rd
+++ /dev/null
@@ -1,19 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/miscUtils.R
-\name{set_num_nugget}
-\alias{set_num_nugget}
-\title{Setup numerical nugget used in Cholesky decompositions}
-\usage{
-set_num_nugget(x, ...)
-}
-\arguments{
-\item{x}{Numerical nugget value.}
-
-\item{...}{Ignored}
-}
-\description{
-Setup numerical nugget used in Cholesky decompositions
-}
-\author{
-Yann Richet \email{yann.richet@irsn.fr}
-}
diff --git a/bindings/R/rlibkriging/src/Makevars b/bindings/R/rlibkriging/src/Makevars
index e97a857e..cd3cf3f3 100644
--- a/bindings/R/rlibkriging/src/Makevars
+++ b/bindings/R/rlibkriging/src/Makevars
@@ -34,7 +34,7 @@ CXX_STD = CXX11
 #PKG_CFLAGS=
 PKG_CPPFLAGS=-I${LIBKRIGING_PATH}/include -include "libKriging/utils/lkalloc.hpp" -DARMA_ALIEN_MEM_ALLOC_FUNCTION=lkalloc::malloc -DARMA_ALIEN_MEM_FREE_FUNCTION=lkalloc::free
 # https://stackoverflow.com/questions/49154506/understanding-makevars-for-linking-to-external-c-library-in-r-package
-PKG_LIBS=-L${LIBKRIGING_PATH}/lib -lKriging -larmadillo $(EXTRA_PKG_LIBS) $(ARMA_LIBS)
+PKG_LIBS=-L${LIBKRIGING_PATH}/lib -lKriging -larmadillo -llbfgsb_cpp $(EXTRA_PKG_LIBS) $(ARMA_LIBS)
 MAKEFLAGS=-j$(nproc)
 
 $(SHLIB): .check ${LIBKRIGING_PATH}/lib/libKriging.$(LIB_SUFFIX) ${LIBKRIGING_PATH}/lib/libarmadillo.$(LIB_SUFFIX)
diff --git a/bindings/R/rlibkriging/src/Makevars.win b/bindings/R/rlibkriging/src/Makevars.win
index d8d1a989..611ea118 100644
--- a/bindings/R/rlibkriging/src/Makevars.win
+++ b/bindings/R/rlibkriging/src/Makevars.win
@@ -15,7 +15,7 @@ PKG_CFLAGS=
 PKG_CPPFLAGS=-I${LIBKRIGING_PATH}/include -include "libKriging/utils/lkalloc.hpp" -DARMA_ALIEN_MEM_ALLOC_FUNCTION=lkalloc::malloc -DARMA_ALIEN_MEM_FREE_FUNCTION=lkalloc::free
 PKG_CXXFLAGS=-std=c++11 
 # https://stackoverflow.com/questions/49154506/understanding-makevars-for-linking-to-external-c-library-in-r-package
-PKG_LIBS=-L${LIBKRIGING_PATH}/lib -lKriging -larmadillo ${EXTRA_SYSTEM_LIBRARY_PATH}/openblas.lib $(EXTRA_PKG_LIBS)
+PKG_LIBS=-L${LIBKRIGING_PATH}/lib -lKriging -larmadillo -llbfgsb_cpp ${EXTRA_SYSTEM_LIBRARY_PATH}/openblas.lib $(EXTRA_PKG_LIBS)
 
 $(SHLIB): .check ${LIBKRIGING_PATH}/lib/libKriging.$(LIB_SUFFIX) ${LIBKRIGING_PATH}/lib/libarmadillo.$(LIB_SUFFIX)
 
diff --git a/bindings/R/rlibkriging/src/kriging_binding.cpp b/bindings/R/rlibkriging/src/kriging_binding.cpp
index a9841147..b6d383ab 100644
--- a/bindings/R/rlibkriging/src/kriging_binding.cpp
+++ b/bindings/R/rlibkriging/src/kriging_binding.cpp
@@ -149,25 +149,28 @@ std::string kriging_summary(Rcpp::List k) {
 }
 
 // [[Rcpp::export]]
-Rcpp::List kriging_predict(Rcpp::List k, arma::mat X, bool stdev = true, bool cov = false) {
+Rcpp::List kriging_predict(Rcpp::List k, arma::mat X, bool stdev = true, bool cov = false, bool deriv = false) {
   if (!k.inherits("Kriging"))
     Rcpp::stop("Input must be a Kriging object.");
   SEXP impl = k.attr("object");
 
   Rcpp::XPtr<Kriging> impl_ptr(impl);
 
-  auto pred = impl_ptr->predict(X, stdev, cov);
-  if (stdev & cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred),
-                              Rcpp::Named("stdev") = std::get<1>(pred),
-                              Rcpp::Named("cov") = std::get<2>(pred));
-  } else if (stdev & !cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred), Rcpp::Named("stdev") = std::get<1>(pred));
-  } else if (!stdev & cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred), Rcpp::Named("cov") = std::get<2>(pred));
-  } else if (!stdev & !cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred));
+  auto pred = impl_ptr->predict(X, stdev, cov, deriv);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred));
+  if (stdev) {
+    ret.push_back(std::get<1>(pred), "stdev");
+  }
+  if (cov) {
+    ret.push_back(std::get<2>(pred), "cov");
+  }
+  if (deriv) {
+    ret.push_back(std::get<3>(pred), "mean_deriv");
+    ret.push_back(std::get<4>(pred), "stdev_deriv");
   }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
@@ -206,16 +209,16 @@ Rcpp::List kriging_logLikelihoodFun(Rcpp::List k, arma::vec theta, bool grad = f
   Rcpp::XPtr<Kriging> impl_ptr(impl);
 
   std::tuple<double, arma::vec, arma::mat> ll = impl_ptr->logLikelihoodFun(theta, grad, hess);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll));
+  if (grad) {
+    ret.push_back(std::get<1>(ll), "logLikelihoodGrad");
+  }
   if (hess) {
-    return Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll),
-                              Rcpp::Named("logLikelihoodGrad") = std::get<1>(ll),
-                              Rcpp::Named("logLikelihoodHess") = std::get<2>(ll));
-  } else if (grad & !hess) {
-    return Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll),
-                              Rcpp::Named("logLikelihoodGrad") = std::get<1>(ll));
-  } else if (!grad & !hess) {
-    return Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll));
+    ret.push_back(std::get<2>(ll), "logLikelihoodHess");
   }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
@@ -238,12 +241,13 @@ Rcpp::List kriging_leaveOneOutFun(Rcpp::List k, arma::vec theta, bool grad = fal
   Rcpp::XPtr<Kriging> impl_ptr(impl);
 
   std::tuple<double, arma::vec> loo = impl_ptr->leaveOneOutFun(theta, grad);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("leaveOneOut") = std::get<0>(loo));
   if (grad) {
-    return Rcpp::List::create(Rcpp::Named("leaveOneOut") = std::get<0>(loo),
-                              Rcpp::Named("leaveOneOutGrad") = std::get<1>(loo));
-  } else {
-    return Rcpp::List::create(Rcpp::Named("leaveOneOut") = std::get<0>(loo));
+    ret.push_back(std::get<1>(loo), "leaveOneOutGrad");
   }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
@@ -266,12 +270,13 @@ Rcpp::List kriging_logMargPostFun(Rcpp::List k, arma::vec theta, bool grad = fal
   Rcpp::XPtr<Kriging> impl_ptr(impl);
 
   std::tuple<double, arma::vec> lmp = impl_ptr->logMargPostFun(theta, grad);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("logMargPost") = std::get<0>(lmp));
   if (grad) {
-    return Rcpp::List::create(Rcpp::Named("logMargPost") = std::get<0>(lmp),
-                              Rcpp::Named("logMargPostGrad") = std::get<1>(lmp));
-  } else {
-    return Rcpp::List::create(Rcpp::Named("logMargPost") = std::get<0>(lmp));
+    ret.push_back(std::get<1>(lmp), "logMargPostGrad");
   }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
diff --git a/bindings/R/rlibkriging/src/nuggetkriging_binding.cpp b/bindings/R/rlibkriging/src/nuggetkriging_binding.cpp
index fd67ccad..fac103a3 100644
--- a/bindings/R/rlibkriging/src/nuggetkriging_binding.cpp
+++ b/bindings/R/rlibkriging/src/nuggetkriging_binding.cpp
@@ -171,25 +171,28 @@ std::string nuggetkriging_summary(Rcpp::List k) {
 }
 
 // [[Rcpp::export]]
-Rcpp::List nuggetkriging_predict(Rcpp::List k, arma::mat X, bool stdev = true, bool cov = false) {
+Rcpp::List nuggetkriging_predict(Rcpp::List k, arma::mat X, bool stdev = true, bool cov = false, bool deriv = false) {
   if (!k.inherits("NuggetKriging"))
     Rcpp::stop("Input must be a NuggetKriging object.");
   SEXP impl = k.attr("object");
 
   Rcpp::XPtr<NuggetKriging> impl_ptr(impl);
 
-  auto pred = impl_ptr->predict(X, stdev, cov);
-  if (stdev & cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred),
-                              Rcpp::Named("stdev") = std::get<1>(pred),
-                              Rcpp::Named("cov") = std::get<2>(pred));
-  } else if (stdev & !cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred), Rcpp::Named("stdev") = std::get<1>(pred));
-  } else if (!stdev & cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred), Rcpp::Named("cov") = std::get<2>(pred));
-  } else if (!stdev & !cov) {
-    return Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred));
+  auto pred = impl_ptr->predict(X, stdev, cov, deriv);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("mean") = std::get<0>(pred));
+  if (stdev) {
+    ret.push_back(std::get<1>(pred), "stdev");
+  }
+  if (cov) {
+    ret.push_back(std::get<2>(pred), "cov");
   }
+  if (deriv) {
+    ret.push_back(std::get<3>(pred), "mean_deriv");
+    ret.push_back(std::get<4>(pred), "stdev_deriv");
+  }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
@@ -228,12 +231,13 @@ Rcpp::List nuggetkriging_logLikelihoodFun(Rcpp::List k, arma::vec theta_alpha, b
   Rcpp::XPtr<NuggetKriging> impl_ptr(impl);
 
   std::tuple<double, arma::vec> ll = impl_ptr->logLikelihoodFun(theta_alpha, grad);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll));
   if (grad) {
-    return Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll),
-                              Rcpp::Named("logLikelihoodGrad") = std::get<1>(ll));
-  } else {
-    return Rcpp::List::create(Rcpp::Named("logLikelihood") = std::get<0>(ll));
+    ret.push_back(std::get<1>(ll), "logLikelihoodGrad");
   }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
@@ -256,12 +260,13 @@ Rcpp::List nuggetkriging_logMargPostFun(Rcpp::List k, arma::vec theta, bool grad
   Rcpp::XPtr<NuggetKriging> impl_ptr(impl);
 
   std::tuple<double, arma::vec> lmp = impl_ptr->logMargPostFun(theta, grad);
+
+  Rcpp::List ret = Rcpp::List::create(Rcpp::Named("logMargPost") = std::get<0>(lmp));
   if (grad) {
-    return Rcpp::List::create(Rcpp::Named("logMargPost") = std::get<0>(lmp),
-                              Rcpp::Named("logMargPostGrad") = std::get<1>(lmp));
-  } else {
-    return Rcpp::List::create(Rcpp::Named("logMargPost") = std::get<0>(lmp));
+    ret.push_back(std::get<1>(lmp), "logMargPostGrad");
   }
+
+  return ret;
 }
 
 // [[Rcpp::export]]
diff --git a/bindings/R/rlibkriging/src/optim.cpp b/bindings/R/rlibkriging/src/optim.cpp
new file mode 100644
index 00000000..411aad30
--- /dev/null
+++ b/bindings/R/rlibkriging/src/optim.cpp
@@ -0,0 +1,128 @@
+// clang-format off
+// Must before any other include
+#include "libKriging/utils/lkalloc.hpp"
+
+#include <RcppArmadillo.h>
+// clang-format on
+
+#include "libKriging/Optim.hpp"
+
+//' @export
+// [[Rcpp::export]]
+bool optim_is_reparametrized() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->is_reparametrized();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_use_reparametrize(bool reparametrize) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->use_reparametrize(reparametrize);
+}
+
+//' @export
+// [[Rcpp::export]]
+double optim_get_theta_lower_factor() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->get_theta_lower_factor();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_set_theta_lower_factor(double theta_lower_factor) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->set_theta_lower_factor(theta_lower_factor);
+}
+
+//' @export
+// [[Rcpp::export]]
+double optim_get_theta_upper_factor() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->get_theta_upper_factor();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_set_theta_upper_factor(double theta_upper_factor) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->set_theta_upper_factor(theta_upper_factor);
+}
+
+//' @export
+// [[Rcpp::export]]
+bool optim_variogram_bounds_heuristic_used() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->variogram_bounds_heuristic_used();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_use_variogram_bounds_heuristic(bool variogram_bounds_heuristic) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->use_variogram_bounds_heuristic(variogram_bounds_heuristic);
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_log(int l) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->log(l);
+}
+
+//' @export
+// [[Rcpp::export]]
+int optim_get_max_iteration() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->get_max_iteration();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_set_max_iteration(int max_iteration) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->set_max_iteration(max_iteration);
+}
+
+//' @export
+// [[Rcpp::export]]
+double optim_get_gradient_tolerance() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->get_gradient_tolerance();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_set_gradient_tolerance(double gradient_tolerance) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->set_gradient_tolerance(gradient_tolerance);
+}
+
+//' @export
+// [[Rcpp::export]]
+double optim_get_objective_rel_tolerance() {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  return impl_ptr->get_objective_rel_tolerance();
+}
+
+//' @export
+// [[Rcpp::export]]
+void optim_set_objective_rel_tolerance(double objective_rel_tolerance) {
+  Optim* la = new Optim();
+  Rcpp::XPtr<Optim> impl_ptr(la);
+  impl_ptr->set_objective_rel_tolerance(objective_rel_tolerance);
+}
diff --git a/dependencies/lbfgsb_cpp b/dependencies/lbfgsb_cpp
new file mode 160000
index 00000000..d5bb0a6e
--- /dev/null
+++ b/dependencies/lbfgsb_cpp
@@ -0,0 +1,1 @@
+Subproject commit d5bb0a6e6fdf7681f21102aae11784ca0963afe2
diff --git a/dependencies/pybind11 b/dependencies/pybind11
index 787d2c88..ffa34686 160000
--- a/dependencies/pybind11
+++ b/dependencies/pybind11
@@ -1,1 +1,1 @@
-Subproject commit 787d2c88cafa4d07fb38c9519c485a86323cfcf4
+Subproject commit ffa346860b306c9bbfb341aed9c14c067751feb8
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index 8ee8a9cd..af77f2d6 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -8,6 +8,7 @@ add_library(Kriging
         LinearRegression.cpp include/libKriging/LinearRegression.hpp
         LinearRegressionOptim.cpp include/libKriging/LinearRegressionOptim.hpp
         Trend.cpp include/libKriging/Trend.hpp
+        Optim.cpp include/libKriging/Optim.hpp
         Random.cpp include/libKriging/Random.hpp
         LinearAlgebra.cpp include/libKriging/LinearAlgebra.hpp
         Covariance.cpp include/libKriging/Covariance.hpp
@@ -18,6 +19,7 @@ add_library(Kriging
         Bench.cpp include/libKriging/Bench.hpp
         CacheFunction.cpp include/libKriging/CacheFunction.hpp include/libKriging/utils/custom_hash_function.hpp include/libKriging/utils/cache_details.hpp
         include/libKriging/utils/LinearHashStorage.hpp
+        include/libKriging/utils/data_from_arma_vec.hpp
         include/libKriging/version.hpp ${VERSION_FILE})
 
 if (CXX_CLANG_TIDY)
@@ -26,7 +28,7 @@ if (CXX_CLANG_TIDY)
             CXX_CLANG_TIDY ${CXX_CLANG_TIDY})
 endif ()
 
-target_link_libraries(Kriging PUBLIC armadillo)
+target_link_libraries(Kriging PUBLIC armadillo lbfgsb_cpp)
 
 # TODO check if this method is useful
 # * https://atomheartother.github.io/c++/2018/07/12/CPPDynLib.html
diff --git a/src/lib/Covariance.cpp b/src/lib/Covariance.cpp
index 0480cd84..81b58d27 100644
--- a/src/lib/Covariance.cpp
+++ b/src/lib/Covariance.cpp
@@ -19,53 +19,94 @@
 //' @ref https://github.com/cran/DiceKriging/blob/master/src/CovFuns.c
 // Covariance function on normalized data
 
-std::function<double(const arma::vec&)> Covariance::CovNorm_fun_gauss = [](const arma::vec& _dist_norm) {
-  const double temp = arma::dot(_dist_norm, _dist_norm);
-  return exp(-0.5 * temp);
-};
-
-std::function<arma::vec(const arma::vec&)> Covariance::Dln_CovNorm_gauss
-    = [](const arma::vec& _dist_norm) { return _dist_norm % _dist_norm; };
-
-std::function<double(const arma::vec&)> Covariance::CovNorm_fun_exp
-    = [](const arma::vec& _dist_norm) { return exp(-arma::sum(arma::abs(_dist_norm))); };
-
-std::function<arma::vec(const arma::vec&)> Covariance::Dln_CovNorm_exp
-    = [](const arma::vec& _dist_norm) { return arma::abs(_dist_norm); };
+std::function<double(const arma::vec&, const arma::vec&)> Covariance::Cov_gauss
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        const arma::vec& dXnorm = _dX / _theta;
+        return exp(-0.5 * arma::dot(dXnorm, dXnorm));
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDtheta_gauss
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        return arma::conv_to<arma::colvec>::from(_dX % _dX / arma::pow(_theta, 3));
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDx_gauss
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        return arma::conv_to<arma::colvec>::from(-_dX / arma::square(_theta));
+      };
+
+std::function<double(const arma::vec&, const arma::vec&)> Covariance::Cov_exp
+    = [](const arma::vec& _dX, const arma::vec& _theta) { return exp(-arma::sum(arma::abs(_dX / _theta))); };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDtheta_exp
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        return arma::conv_to<arma::colvec>::from(arma::abs(_dX / arma::square(_theta)));
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDx_exp
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        return arma::conv_to<arma::colvec>::from(-arma::sign(_dX) / _theta);
+      };
 
 const double SQRT_3 = std::sqrt(3.0);
 
-std::function<double(const arma::vec&)> Covariance::CovNorm_fun_matern32 = [](const arma::vec& _dist_norm) {
-  arma::vec d = SQRT_3 * arma::abs(_dist_norm);
-  return exp(-arma::sum(d - arma::log1p(d)));
-};
+std::function<double(const arma::vec&, const arma::vec&)> Covariance::Cov_matern32
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        arma::vec d = SQRT_3 * arma::abs(_dX / _theta);
+        return exp(-arma::sum(d - arma::log1p(d)));
+      };
 
-std::function<arma::vec(const arma::vec&)> Covariance::Dln_CovNorm_matern32 = [](const arma::vec& _dist_norm) {
-  arma::vec d = SQRT_3 * arma::abs(_dist_norm);
-  return arma::conv_to<arma::vec>::from((d % d) / (1 + d));
-};
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDtheta_matern32
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        arma::vec d = SQRT_3 * arma::abs(_dX / _theta);
+        return arma::conv_to<arma::colvec>::from((d % d) / (1 + d) / _theta);
+      };
 
-const double SQRT_5 = std::sqrt(5.0);
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDx_matern32
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        arma::vec d = SQRT_3 * arma::abs(_dX / _theta);
+        return arma::conv_to<arma::colvec>::from(-SQRT_3 * arma::sign(_dX) % d / (1 + d) / _theta);
+      };
 
-std::function<double(const arma::vec&)> Covariance::CovNorm_fun_matern52 = [](const arma::vec& _dist_norm) {
-  arma::vec d = SQRT_5 * arma::abs(_dist_norm);
-  return exp(-arma::sum(d - arma::log1p(d + (d % d) / 3)));
-};
+const double SQRT_5 = std::sqrt(5.0);
 
-std::function<arma::vec(const arma::vec&)> Covariance::Dln_CovNorm_matern52 = [](const arma::vec& _dist_norm) {
-  arma::vec d = SQRT_5 * arma::abs(_dist_norm);
-  arma::vec a = 1 + d;
-  arma::vec b = (d % d) / 3;
-  return arma::conv_to<arma::vec>::from((a % b) / (a + b));
-};
+std::function<double(const arma::vec&, const arma::vec&)> Covariance::Cov_matern52
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        arma::vec d = SQRT_5 * arma::abs(_dX / _theta);
+        return exp(-arma::sum(d - arma::log1p(d + (d % d) / 3)));
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDtheta_matern52
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        arma::vec d = SQRT_5 * arma::abs(_dX / _theta);
+        arma::vec a = 1 + d;
+        arma::vec b = (d % d) / 3;
+        return arma::conv_to<arma::colvec>::from((a % b) / (a + b) / _theta);
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDx_matern52
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        arma::vec d = SQRT_5 * arma::abs(_dX / _theta);
+        arma::vec a = 1 + d;
+        arma::vec b = d / 3;
+        return arma::conv_to<arma::colvec>::from(-SQRT_5 * arma::sign(_dX) % (a % b) / (a + d % b) / _theta);
+      };
 
 const double EPSILON = 1E-13;
 
-std::function<double(const arma::vec&)> Covariance::CovNorm_fun_whitenoise = [](const arma::vec& _dist_norm) {
-  if (arma::sum(arma::abs(_dist_norm)) < EPSILON)
-    return 1.0;
-  return 0.0;
-};
-
-std::function<arma::vec(const arma::vec&)> Covariance::Dln_CovNorm_whitenoise
-    = [](const arma::vec& _dist_norm) { return arma::vec(_dist_norm.n_elem); };
+std::function<double(const arma::vec&, const arma::vec&)> Covariance::Cov_whitenoise
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        if (arma::sum(arma::abs(_dX / _theta)) < EPSILON)
+          return 1.0;
+        return 0.0;
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDtheta_whitenoise
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        return arma::vec(_dX.n_elem);  // TBD
+      };
+
+std::function<arma::vec(const arma::vec&, const arma::vec&)> Covariance::DlnCovDx_whitenoise
+    = [](const arma::vec& _dX, const arma::vec& _theta) {
+        return arma::vec(_dX.n_elem);  // TBD
+      };
\ No newline at end of file
diff --git a/src/lib/Kriging.cpp b/src/lib/Kriging.cpp
index 823f0a16..d4d057ab 100644
--- a/src/lib/Kriging.cpp
+++ b/src/lib/Kriging.cpp
@@ -7,17 +7,19 @@
 #include "libKriging/utils/lk_armadillo.hpp"
 
 //#include "libKriging/Bench.hpp"
-#include "libKriging/Trend.hpp"
 #include "libKriging/CacheFunction.hpp"
 #include "libKriging/Covariance.hpp"
+#include "libKriging/Kriging.hpp"
 #include "libKriging/KrigingException.hpp"
 #include "libKriging/LinearAlgebra.hpp"
+#include "libKriging/Optim.hpp"
 #include "libKriging/Random.hpp"
+#include "libKriging/Trend.hpp"
 #include "libKriging/utils/custom_hash_function.hpp"
-#include "libKriging/Kriging.hpp"
+#include "libKriging/utils/data_from_arma_vec.hpp"
 
 #include <cassert>
-#include <optim.hpp>
+#include <lbfgsb_cpp/lbfgsb.hpp>
 #include <tuple>
 #include <vector>
 
@@ -29,21 +31,25 @@
 void Kriging::make_Cov(const std::string& covType) {
   m_covType = covType;
   if (covType.compare("gauss") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_gauss;
-    Dln_CovNorm = Covariance::Dln_CovNorm_gauss;
-    CovNorm_pow = 2;
+    Cov = Covariance::Cov_gauss;
+    DlnCovDtheta = Covariance::DlnCovDtheta_gauss;
+    DlnCovDx = Covariance::DlnCovDx_gauss;
+    Cov_pow = 2;
   } else if (covType.compare("exp") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_exp;
-    Dln_CovNorm = Covariance::Dln_CovNorm_exp;
-    CovNorm_pow = 1;
+    Cov = Covariance::Cov_exp;
+    DlnCovDtheta = Covariance::DlnCovDtheta_exp;
+    DlnCovDx = Covariance::DlnCovDx_exp;
+    Cov_pow = 1;
   } else if (covType.compare("matern3_2") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_matern32;
-    Dln_CovNorm = Covariance::Dln_CovNorm_matern32;
-    // CovNorm_pow = 1.5;
+    Cov = Covariance::Cov_matern32;
+    DlnCovDtheta = Covariance::DlnCovDtheta_matern32;
+    DlnCovDx = Covariance::DlnCovDx_matern32;
+    Cov_pow = 1.5;
   } else if (covType.compare("matern5_2") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_matern52;
-    Dln_CovNorm = Covariance::Dln_CovNorm_matern52;
-    // CovNorm_pow = 2.5;
+    Cov = Covariance::Cov_matern52;
+    DlnCovDtheta = Covariance::DlnCovDtheta_matern52;
+    DlnCovDx = Covariance::DlnCovDx_matern52;
+    Cov_pow = 2.5;
   } else
     throw std::invalid_argument("Unsupported covariance kernel: " + covType);
 
@@ -63,6 +69,10 @@ LIBKRIGING_EXPORT Kriging::Kriging(const arma::colvec& y,
                                    const std::string& optim,
                                    const std::string& objective,
                                    const Parameters& parameters) {
+  if (y.n_elem != X.n_rows)
+    throw std::runtime_error("Dimension of new data should be the same:\n X: (" + std::to_string(X.n_rows) + "x"
+                             + std::to_string(X.n_cols) + "), y: (" + std::to_string(y.n_elem) + ")");
+
   make_Cov(covType);
   fit(y, X, regmodel, normalize, optim, objective, parameters);
 }
@@ -110,7 +120,7 @@ double Kriging::_logLikelihood(const arma::vec& _theta,
   for (arma::uword i = 0; i < n; i++) {
     R.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      R.at(i, j) = R.at(j, i) = CovNorm_fun(m_dX.col(i * n + j) / _theta);
+      R.at(i, j) = R.at(j, i) = Cov(m_dX.col(i * n + j), _theta);
     }
   }
   // t0 = Bench::toc("Rvfast        ", t0);
@@ -190,7 +200,7 @@ double Kriging::_logLikelihood(const arma::vec& _theta,
     arma::cube gradR = arma::cube(d, n, n);
     for (arma::uword i = 0; i < n; i++) {
       for (arma::uword j = 0; j < i; j++) {
-        gradR.slice(i).col(j) = R.at(i, j) * Dln_CovNorm(m_dX.col(i * n + j) / _theta);
+        gradR.slice(i).col(j) = R.at(i, j) * DlnCovDtheta(m_dX.col(i * n + j), _theta);
       }
     }
     // t0 = Bench::toc(" gradR              ", t0);
@@ -204,7 +214,6 @@ double Kriging::_logLikelihood(const arma::vec& _theta,
           gradR_k.at(i, j) = gradR_k.at(j, i) = gradR.slice(i).col(j)[k];
         }
       }
-      gradR_k /= _theta.at(k);
       // t0 = Bench::toc(" gradR_k      ", t0);
 
       // should make a fast function trace_prod(A,B) -> sum_i(sum_j(Ai,j*Bj,i))
@@ -247,9 +256,8 @@ double Kriging::_logLikelihood(const arma::vec& _theta,
             for (arma::uword i = 0; i < n; i++) {
               hessR_k_l.at(i, i) = 0;
               for (arma::uword j = 0; j < i; j++) {
-                double dln_k = gradR.slice(i).col(j)[k] / _theta.at(k);
-                hessR_k_l.at(i, j) = hessR_k_l.at(j, i)
-                    = dln_k * (dln_k / R.at(i, j) - (CovNorm_pow + 1) / _theta.at(k));
+                double dln_k = gradR.slice(i).col(j)[k];
+                hessR_k_l.at(i, j) = hessR_k_l.at(j, i) = dln_k * (dln_k / R.at(i, j) - (Cov_pow + 1) / _theta.at(k));
                 // !! NO: it just work for exp type kernels. Matern MUST have a special treatment !!!
               }
             }
@@ -258,7 +266,7 @@ double Kriging::_logLikelihood(const arma::vec& _theta,
               hessR_k_l.at(i, i) = 0;
               for (arma::uword j = 0; j < i; j++) {
                 hessR_k_l.at(i, j) = hessR_k_l.at(j, i)
-                    = gradR.slice(i).col(j)[k] / _theta.at(k) * gradR.slice(i).col(j)[l] / _theta.at(l) / R.at(i, j);
+                    = gradR.slice(i).col(j)[k] * gradR.slice(i).col(j)[l] / R.at(i, j);
               }
             }
           }
@@ -372,7 +380,7 @@ double Kriging::_leaveOneOut(const arma::vec& _theta, arma::vec* grad_out, Krigi
   for (arma::uword i = 0; i < n; i++) {
     R.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      R.at(i, j) = R.at(j, i) = CovNorm_fun(m_dX.col(i * n + j) / _theta);
+      R.at(i, j) = R.at(j, i) = Cov(m_dX.col(i * n + j), _theta);
     }
   }
   // t0 = Bench::toc("R             ", t0);
@@ -433,7 +441,7 @@ double Kriging::_leaveOneOut(const arma::vec& _theta, arma::vec* grad_out, Krigi
     arma::cube gradR = arma::cube(d, n, n);
     for (arma::uword i = 0; i < n; i++) {
       for (arma::uword j = 0; j < i; j++) {
-        gradR.slice(i).col(j) = R.at(i, j) * Dln_CovNorm(m_dX.col(i * n + j) / _theta);
+        gradR.slice(i).col(j) = R.at(i, j) * DlnCovDtheta(m_dX.col(i * n + j), _theta);
       }
     }
     // t0 = Bench::toc(" gradR              ", t0);
@@ -447,7 +455,6 @@ double Kriging::_leaveOneOut(const arma::vec& _theta, arma::vec* grad_out, Krigi
           gradR_k.at(i, j) = gradR_k.at(j, i) = gradR.slice(i).col(j)[k];
         }
       }
-      gradR_k /= _theta.at(k);
 
       arma::colvec diagdQ = -DiagABA(Q, gradR_k);
       // t0 = Bench::toc(" diagdQ       ", t0);
@@ -542,7 +549,7 @@ double Kriging::_logMargPost(const arma::vec& _theta, arma::vec* grad_out, Krigi
   for (arma::uword i = 0; i < n; i++) {
     R.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      R.at(i, j) = R.at(j, i) = CovNorm_fun(m_dX.col(i * n + j) / _theta);
+      R.at(i, j) = R.at(j, i) = Cov(m_dX.col(i * n + j), _theta);
     }
   }
   // t0 = Bench::toc("R             ", t0);
@@ -655,7 +662,7 @@ double Kriging::_logMargPost(const arma::vec& _theta, arma::vec* grad_out, Krigi
     arma::cube gradR = arma::cube(d, n, n);
     for (arma::uword i = 0; i < n; i++) {
       for (arma::uword j = 0; j < i; j++) {
-        gradR.slice(i).col(j) = R.at(i, j) * Dln_CovNorm(m_dX.col(i * n + j) / _theta);
+        gradR.slice(i).col(j) = R.at(i, j) * DlnCovDtheta(m_dX.col(i * n + j), _theta);
       }
     }
     // t0 = Bench::toc(" gradR              ", t0);
@@ -670,7 +677,6 @@ double Kriging::_logMargPost(const arma::vec& _theta, arma::vec* grad_out, Krigi
           gradR_k.at(i, j) = gradR_k.at(j, i) = gradR.slice(i).col(j)[k];
         }
       }
-      gradR_k /= _theta.at(k);
       // t0 = Bench::toc(" gradR_k", t0);
 
       Wb_k = trans(solve(
@@ -728,13 +734,14 @@ double optim_newton(std::function<double(arma::vec& x, arma::vec* grad_out, arma
                     arma::vec& x_0,
                     const arma::vec& x_lower,
                     const arma::vec& x_upper) {
-  // arma::cout << "x_0: " << x_0 << " ";
+  if (Optim::log_level > 0)
+    arma::cout << "x_0: " << x_0 << " ";
 
   double delta = 0.1;
-  double x_tol = 0.001;
-  double y_tol = 1;
-  double g_tol = 0.01;
-  int max_iteration = 20;
+  double x_tol = 0.01;  // Optim::solution_rel_tolerance;
+  double y_tol = Optim::objective_rel_tolerance;
+  double g_tol = Optim::gradient_tolerance;
+  int max_iteration = Optim::max_iteration;
 
   arma::vec x_previous(x_0.n_elem);
   arma::vec x_best(x_0.n_elem);
@@ -747,28 +754,34 @@ double optim_newton(std::function<double(arma::vec& x, arma::vec* grad_out, arma
   arma::mat hess(x.n_elem, x.n_elem);
   int i = 0;
   while (i < max_iteration) {
-    // arma::cout << "iteration: " << i << arma::endl;
-    // arma::cout << "  x: " << x << arma::endl;
+    if (Optim::log_level > 0) {
+      arma::cout << "iteration: " << i << arma::endl;
+      arma::cout << "  x: " << x << arma::endl;
+    }
 
     f_previous = f_new;
     f_new = f(x, &grad, &hess);
-    // arma::cout << "    f_new: " << f_new << arma::endl;
+    if (Optim::log_level > 1)
+      arma::cout << "    f_new: " << f_new << arma::endl;
     if (f_best > f_new) {
       f_best = f_new;
       x_best = x;
     }
 
-    if (std::abs(f_new - f_previous) < y_tol) {
-      // arma::cout << "  X f_new ~ f_previous" << arma::endl;
+    if (std::abs((f_new - f_previous) / f_previous) < y_tol) {
+      if (Optim::log_level > 0)
+        arma::cout << "  X f_new ~ f_previous" << arma::endl;
       break;
     }
     if (arma::abs(grad).max() < g_tol) {
-      // arma::cout << "  X grad ~ 0" << arma::endl;
+      if (Optim::log_level > 0)
+        arma::cout << "  X grad ~ 0" << arma::endl;
       break;
     }
 
     arma::vec delta_x(x.n_elem);
-    // arma::cout << "  eig(hess)" << arma::eig_sym(hess) << arma::endl;
+    if (Optim::log_level > 2)
+      arma::cout << "  eig(hess)" << arma::eig_sym(hess) << arma::endl;
     if (arma::all(arma::eig_sym(hess) > 0)) {
       // try to fit a second order polynom to use its minimizer. Otherwise, just iterate with conjugate gradient
       // arma::cout << "!";
@@ -776,28 +789,35 @@ double optim_newton(std::function<double(arma::vec& x, arma::vec* grad_out, arma
     } else {
       delta_x = delta * grad / std::sqrt(arma::sum(arma::cdot(grad, grad)));
     }
-    // arma::cout << "  delta_x: " << delta_x << arma::endl;
+    if (Optim::log_level > 2)
+      arma::cout << "  delta_x: " << delta_x << arma::endl;
 
     arma::vec x_next = x - delta_x;
-    // arma::cout << "  x_next: " << x_next << arma::endl;
+    if (Optim::log_level > 1)
+      arma::cout << "  x_next: " << x_next << arma::endl;
 
     for (int j = 0; j < x_next.n_elem; j++) {
       if (x_next[j] < x_lower[j]) {
-        // arma::cout << "    <" << x_lower[j] << arma::endl;
+        if (Optim::log_level > 2)
+          arma::cout << "    <" << x_lower[j] << arma::endl;
         delta_x = delta_x * (x[j] - x_lower[j]) / (x[j] - x_next[j]) / 2;
         x_next = x - delta_x;
       }
       if (x_next[j] > x_upper[j]) {
-        // arma::cout << "    >" << x_upper[j] << arma::endl;
+        if (Optim::log_level > 2)
+          arma::cout << "    >" << x_upper[j] << arma::endl;
         delta_x = delta_x * (x_upper[j] - x[j]) / (x_next[j] - x[j]) / 2;
         x_next = x - delta_x;
       }
     }
-    // arma::cout << "    delta_x: " << delta << arma::endl;
-    // arma::cout << "    x_next: " << x_next << arma::endl;
-
-    if (arma::abs(x - x_next).max() < x_tol) {
-      // arma::cout << "  X x_0 ~ x_next" << arma::endl;
+    if (Optim::log_level > 2)
+      arma::cout << "    delta_x: " << delta << arma::endl;
+    if (Optim::log_level > 2)
+      arma::cout << "    x_next: " << x_next << arma::endl;
+
+    if (arma::abs((x - x_next) / x).max() < x_tol) {
+      if (Optim::log_level > 1)
+        arma::cout << "  X x_0 ~ x_next" << arma::endl;
       break;
     }
 
@@ -806,11 +826,13 @@ double optim_newton(std::function<double(arma::vec& x, arma::vec* grad_out, arma
 
     // TODO : keep best result instead of last one
     ++i;
-    // arma::cout << "  f_best: " << f_best << arma::endl;
+    if (Optim::log_level > 0)
+      arma::cout << "  f_best: " << f_best << arma::endl;
   }
 
   x_0 = x_best;
-  // arma::cout << " " << x_0 << " " << f_best << arma::endl;
+  if (Optim::log_level > 0)
+    arma::cout << " " << x_0 << " " << f_best << arma::endl;
 
   return f_best;
 }
@@ -831,55 +853,115 @@ LIBKRIGING_EXPORT void Kriging::fit(const arma::colvec& y,
                                     const std::string& optim,
                                     const std::string& objective,
                                     const Parameters& parameters) {
-  arma::uword n = X.n_rows;
-  arma::uword d = X.n_cols;
-
-  arma::vec theta_lower = 1e-3 * trans(max(X, 0) - min(X, 0));
-  arma::vec theta_upper = 2 * trans(max(X, 0) - min(X, 0));
+  const arma::uword n = X.n_rows;
+  const arma::uword d = X.n_cols;
 
   std::function<double(const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data)>
       fit_ofn;
   m_optim = optim;
   m_objective = objective;
   if (objective.compare("LL") == 0) {
-    fit_ofn = CacheFunction{
-        [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
-          // Change variable for opt: . -> 1/exp(.)
-          arma::vec _theta = 1 / arma::exp(_gamma);
-          double ll = this->_logLikelihood(_theta, grad_out, hess_out, okm_data);
-          if (grad_out != nullptr)
-            *grad_out = *grad_out % _theta;
-          if (hess_out != nullptr)
-            *hess_out = -*grad_out + *hess_out % _theta;
-          return -ll;
-        }};
-
+    if (Optim::reparametrize) {
+      fit_ofn = CacheFunction{
+          [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
+            // Change variable for opt: . -> 1/exp(.)
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> gamma: " << _gamma << arma::endl;
+            const arma::vec _theta = 1 / arma::exp(_gamma);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> theta: " << _theta << arma::endl;
+            double ll = this->_logLikelihood(_theta, grad_out, hess_out, okm_data);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "  > ll: " << ll << arma::endl;
+            if (grad_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad ll: " << grad_out << arma::endl;
+              *grad_out = *grad_out % _theta;
+            }
+            if (hess_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > hess ll: " << hess_out << arma::endl;
+              *hess_out = -*grad_out + *hess_out % _theta;
+            }
+            return -ll;
+          }};
+    } else {
+      fit_ofn = CacheFunction{
+          [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
+            const arma::vec _theta = _gamma;
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> theta: " << _theta << arma::endl;
+            double ll = this->_logLikelihood(_theta, grad_out, hess_out, okm_data);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "  > ll: " << ll << arma::endl;
+            if (grad_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad ll: " << grad_out << arma::endl;
+              *grad_out = -*grad_out;
+            }
+            if (hess_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > hess ll: " << hess_out << arma::endl;
+              *hess_out = -*hess_out;
+            }
+            return -ll;
+          }};
+    }
   } else if (objective.compare("LOO") == 0) {
-    fit_ofn = CacheFunction{
-        [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
-          // Change variable for opt: . -> 1/exp(.)
-          arma::vec _theta = 1 / arma::exp(_gamma);
-          double loo = this->_leaveOneOut(_theta, grad_out, okm_data);
-          if (grad_out != nullptr)
-            *grad_out = -*grad_out % _theta;
-          return loo;
-        }};
-
+    if (Optim::reparametrize) {
+      fit_ofn = CacheFunction{
+          [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
+            // Change variable for opt: . -> 1/exp(.)
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> gamma: " << _gamma << arma::endl;
+            const arma::vec _theta = 1 / arma::exp(_gamma);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> theta: " << _theta << arma::endl;
+            double loo = this->_leaveOneOut(_theta, grad_out, okm_data);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "  > loo: " << loo << arma::endl;
+            if (grad_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad ll: " << grad_out << arma::endl;
+              *grad_out = -*grad_out % _theta;
+            }
+            return loo;
+          }};
+    } else {
+      fit_ofn = CacheFunction{
+          [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
+            const arma::vec _theta = _gamma;
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> theta: " << _theta << arma::endl;
+            double loo = this->_leaveOneOut(_theta, grad_out, okm_data);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "  > loo: " << loo << arma::endl;
+            // if (grad_out != nullptr) {
+            //   if (Optim::log_level>3) arma::cout << "  > grad ll: " << grad_out << arma::endl;
+            //   *grad_out = *grad_out; // so not necessary
+            //  }
+            return loo;
+          }};
+    }
   } else if (objective.compare("LMP") == 0) {
     // Our impl. of https://github.com/cran/RobustGaSP/blob/5cf21658e6a6e327be6779482b93dfee25d24592/R/rgasp.R#L303
     //@see Mengyang Gu, Xiao-jing Wang and Jim Berger, 2018, Annals of Statistics.
-    fit_ofn = CacheFunction{
-        [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
-          // Change variable for opt: . -> 1/exp(.)
-          const arma::vec& _theta = 1 / arma::exp(_gamma);
-          double lmp = this->_logMargPost(_theta, grad_out, okm_data);
-          if (grad_out != nullptr)
-            *grad_out = *grad_out % _theta;
-          return -lmp;
-        }};
-
+    if (Optim::reparametrize) {
+      fit_ofn = CacheFunction{
+          [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
+            // Change variable for opt: . -> 1/exp(.)
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> gamma: " << _gamma << arma::endl;
+            const arma::vec& _theta = 1 / arma::exp(_gamma);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> theta: " << _theta << arma::endl;
+            double lmp = this->_logMargPost(_theta, grad_out, okm_data);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "  > lmp: " << lmp << arma::endl;
+            if (grad_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad lmp: " << grad_out << arma::endl;
+              *grad_out = *grad_out % _theta;
+            }
+            return -lmp;
+          }};
+    } else {
+      fit_ofn = CacheFunction{
+          [this](const arma::vec& _gamma, arma::vec* grad_out, arma::mat* hess_out, Kriging::OKModel* okm_data) {
+            const arma::vec _theta = _gamma;
+            // DEBUG: if (Optim::log_level>3) arma::cout << "> theta: " << _theta << arma::endl;
+            double lmp = this->_logMargPost(_theta, grad_out, okm_data);
+            // DEBUG: if (Optim::log_level>3) arma::cout << "  > lmp: " << lmp << arma::endl;
+            if (grad_out != nullptr) {
+              // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad lmp: " << grad_out << arma::endl;
+              *grad_out = -*grad_out;
+            }
+            return -lmp;
+          }};
+    }
   } else
-    throw std::invalid_argument("Unsupported fit objective: " + objective);
+    throw std::invalid_argument("Unsupported fit objective: " + objective + " (supported are: LL, LOO, LMP)");
 
   arma::rowvec centerX(d);
   arma::rowvec scaleX(d);
@@ -923,7 +1005,7 @@ LIBKRIGING_EXPORT void Kriging::fit(const arma::colvec& y,
 
   // Define regression matrix
   m_regmodel = regmodel;
-  m_F = Trend::regressionModelMatrix(regmodel, m_X, n, d);
+  m_F = Trend::regressionModelMatrix(regmodel, m_X);
 
   arma::mat theta0 = parameters.theta;
   if (parameters.has_theta) {
@@ -952,7 +1034,13 @@ LIBKRIGING_EXPORT void Kriging::fit(const arma::colvec& y,
 
     Kriging::OKModel okm_data{T, M, z, beta, parameters.is_beta_estim, sigma2, parameters.is_sigma2_estim};
 
-    double min_ofn_tmp = fit_ofn(-arma::log(m_theta), nullptr, nullptr, &okm_data);
+    arma::vec gamma_tmp = arma::vec(d);
+    gamma_tmp = m_theta;
+    if (Optim::reparametrize) {
+      gamma_tmp = -arma::log(m_theta);
+    }
+
+    double min_ofn_tmp = fit_ofn(gamma_tmp, nullptr, nullptr, &okm_data);
 
     m_T = std::move(okm_data.T);
     m_M = std::move(okm_data.M);
@@ -962,133 +1050,255 @@ LIBKRIGING_EXPORT void Kriging::fit(const arma::colvec& y,
     m_sigma2 = okm_data.sigma2;
     m_est_sigma2 = parameters.is_sigma2_estim;
 
-  } else if (optim.rfind("BFGS", 0) == 0) {
-    Random::init();
-
-    // FIXME parameters.has needs to implemtented (no use case in current code)
-    if (!parameters.has_theta) {  // no theta given, so draw 10 random uniform starting values
-      int multistart = 1;
-      try {
-        multistart = std::stoi(optim.substr(4));
-      } catch (std::invalid_argument) {
-        // let multistart = 1
+  } else {
+    arma::vec theta_lower = Optim::theta_lower_factor * trans(max(X, 0) - min(X, 0));
+    arma::vec theta_upper = Optim::theta_upper_factor * trans(max(X, 0) - min(X, 0));
+
+    if (Optim::variogram_bounds_heuristic) {
+      arma::vec dy2 = arma::zeros(n * n);
+      for (arma::uword ij = 0; ij < dy2.n_elem; ij++) {
+        int i = (int)ij / n;
+        int j = ij % n;  // i,j <-> i*n+j
+        if (i < j) {
+          dy2[ij] = m_y.at(i) - m_y.at(j);
+          dy2[ij] *= dy2[ij];
+          dy2[j * n + i] = dy2[ij];
+        }
       }
-      theta0 = arma::abs(0.5 + Random::randn_mat(multistart, d) / 6.0)
-               % arma::repmat(max(m_X, 0) - min(m_X, 0), multistart, 1);
-    } else {  // just use given theta(s) as starting values for multi-bfgs
-      theta0 = arma::mat(parameters.theta);
+      // dy2 /= arma::var(m_y);
+      arma::vec dy2dX2_slope = dy2 / arma::sum(m_dX % m_dX, 0).t();
+      // arma::cout << "dy2dX_slope:" << dy2dX_slope << arma::endl;
+      dy2dX2_slope.replace(arma::datum::nan, 0.0);  // we are not interested in same points where dX=0, and dy=0
+      arma::vec w = dy2dX2_slope / sum(dy2dX2_slope);
+      arma::mat steepest_dX_mean = arma::abs(m_dX) * w;
+      // arma::cout << "steepest_dX_mean:" << steepest_dX_mean << arma::endl;
+
+      theta_lower = arma::max(theta_lower, Optim::theta_lower_factor / steepest_dX_mean);
+      theta_upper = arma::min(theta_upper, Optim::theta_upper_factor / steepest_dX_mean);
+      theta_lower = arma::min(theta_lower, theta_upper);
+      theta_upper = arma::max(theta_lower, theta_upper);
+
+      // arma::vec steepest_dX = arma::abs(m_dX.col(arma::index_max(dy2dX_slope))); // worst slope dX point
+      // arma::cout << "steepest_dX:" << steepest_dX << arma::endl;
+      // dy2dX_slope.replace(-1.0, std::numeric_limits<double>::infinity()); // we are not interested in same points
+      // where dX=0, and dy=0 arma::vec smoothest_dX = arma::abs(m_dX.col(arma::index_min(dy2dX_slope))); // worst slope
+      // dX point arma::cout << "smoothest_dX:" << smoothest_dX << arma::endl; arma::vec weighted_mean_dX = (arma::mean(
+      // m_dX % trans(arma::repmat(dy2, 1, m_dX.n_rows)), 1)) / arma::sum(dy2); // weighted mean dX point arma::cout <<
+      // "weighted_mean_dX:" << weighted_mean_dX << arma::endl; theta_lower =
+      // arma::max(theta_lower,Optim::theta_lower_factor * steepest_dX); theta_upper =
+      // arma::min(theta_upper,Optim::theta_upper_factor * steepest_dX);
     }
+    // arma::cout << "theta_lower:" << theta_lower << arma::endl;
+    // arma::cout << "theta_upper:" << theta_upper << arma::endl;
+
+    if (optim.rfind("BFGS", 0) == 0) {
+      Random::init();
+
+      // FIXME parameters.has needs to implemtented (no use case in current code)
+      if (!parameters.has_theta) {  // no theta given, so draw 10 random uniform starting values
+        int multistart = 1;
+        try {
+          multistart = std::stoi(optim.substr(4));
+        } catch (std::invalid_argument) {
+          // let multistart = 1
+        }
+        theta0 = arma::repmat(trans(theta_lower), multistart, 1)
+                 + Random::randu_mat(multistart, d) % arma::repmat(trans(theta_upper - theta_lower), multistart, 1);
+        // theta0 = arma::abs(0.5 + Random::randn_mat(multistart, d) / 6.0)
+        //          % arma::repmat(max(m_X, 0) - min(m_X, 0), multistart, 1);
+      } else {  // just use given theta(s) as starting values for multi-bfgs
+        theta0 = arma::mat(parameters.theta);
+      }
+      // arma::cout << "theta0:" << theta0 << arma::endl;
 
-    // arma::cout << "theta0:" << theta0 << arma::endl;
-
-    optim::algo_settings_t algo_settings;
-    algo_settings.print_level = 0;
-    algo_settings.iter_max = 20;
-    algo_settings.rel_sol_change_tol = 0.01;
-    algo_settings.grad_err_tol = 1e-8;
-    algo_settings.vals_bound = true;
-    algo_settings.lower_bounds = -arma::log(theta_upper);
-    algo_settings.upper_bounds = -arma::log(theta_lower);
-    double min_ofn = std::numeric_limits<double>::infinity();
-
-    for (arma::uword i = 0; i < theta0.n_rows; i++) {  // TODO: use some foreach/pragma to let OpenMP work.
-      arma::vec gamma_tmp = -arma::log(theta0.row(i)).t();
-      // arma::cout << "gamma_tmp:" << gamma_tmp << arma::endl;
-      arma::mat T;
-      arma::mat M;
-      arma::colvec z;
-      arma::colvec beta;
-      if (parameters.has_beta)
-        beta = parameters.beta;
-      double sigma2 = -1;
-      if (parameters.has_sigma2)
-        sigma2 = parameters.sigma2;
-
-      Kriging::OKModel okm_data{T, M, z, beta, parameters.is_beta_estim, sigma2, parameters.is_sigma2_estim};
-
-      bool bfgs_ok = optim::lbfgs(
-          gamma_tmp,
-          [&okm_data, this, &fit_ofn](const arma::vec& vals_inp, arma::vec* grad_out, void*) -> double {
-            return fit_ofn(vals_inp, grad_out, nullptr, &okm_data);
-          },
-          nullptr,
-          algo_settings);
-
-      double min_ofn_tmp
-          = fit_ofn(gamma_tmp,
-                    nullptr,
-                    nullptr,
-                    &okm_data);  // this last call also ensure that T and z are up-to-date with solution found.
-
-      if (min_ofn_tmp < min_ofn) {
-        m_theta = 1 / arma::exp(gamma_tmp);
-        m_est_theta = true;
-        min_ofn = min_ofn_tmp;
-        m_T = std::move(okm_data.T);
-        m_M = std::move(okm_data.M);
-        m_z = std::move(okm_data.z);
-        m_beta = std::move(okm_data.beta);
-        m_est_beta = parameters.is_beta_estim;
-        m_sigma2 = okm_data.sigma2;
-        m_est_sigma2 = parameters.is_sigma2_estim;
+      if (Optim::reparametrize) {
+        arma::vec theta_lower_tmp = theta_lower;
+        theta_lower = -arma::log(theta_upper);
+        theta_upper = -arma::log(theta_lower_tmp);
       }
-    }
-  } else if (optim.rfind("Newton", 0) == 0) {
-    Random::init();
-
-    // FIXME parameters.has needs to implemtented (no use case in current code)
-    if (!parameters.has_theta) {  // no theta given, so draw 10 random uniform starting values
-      int multistart = 1;
-      try {
-        multistart = std::stoi(optim.substr(4));
-      } catch (std::invalid_argument) {
-        // let multistart = 1
+
+      double min_ofn = std::numeric_limits<double>::infinity();
+
+      for (arma::uword i = 0; i < theta0.n_rows; i++) {  // TODO: use some foreach/pragma to let OpenMP work.
+        arma::vec gamma_tmp = theta0.row(i).t();
+        if (Optim::reparametrize)
+          gamma_tmp = -arma::log(theta0.row(i).t());
+
+        theta_lower = arma::min(gamma_tmp, theta_lower);
+        theta_upper = arma::max(gamma_tmp, theta_upper);
+
+        if (Optim::log_level > 0) {
+          arma::cout << "BFGS:" << arma::endl;
+          arma::cout << "  max iterations: " << Optim::max_iteration << arma::endl;
+          arma::cout << "  null gradient tolerance: " << Optim::gradient_tolerance << arma::endl;
+          arma::cout << "  constant objective tolerance: " << Optim::objective_rel_tolerance << arma::endl;
+          arma::cout << "  reparametrize: " << Optim::reparametrize << arma::endl;
+          arma::cout << "  start_point: " << gamma_tmp.t() << " ";
+          arma::cout << "  lower_bounds: " << theta_lower.t() << " ";
+          arma::cout << "  upper_bounds: " << theta_upper.t() << " ";
+        }
+
+        arma::mat T;
+        arma::mat M;
+        arma::colvec z;
+        arma::colvec beta;
+        if (parameters.has_beta)
+          beta = parameters.beta;
+        double sigma2 = -1;
+        if (parameters.has_sigma2)
+          sigma2 = parameters.sigma2;
+
+        Kriging::OKModel okm_data{T, M, z, beta, parameters.is_beta_estim, sigma2, parameters.is_sigma2_estim};
+
+        //// Using OptimLib
+        // optim::algo_settings_t algo_settings;
+        // algo_settings.print_level = Optim::log_level;
+        // algo_settings.iter_max = Optim::max_iteration;
+        // algo_settings.rel_objfn_change_tol = Optim::objective_rel_tolerance;
+        // algo_settings.grad_err_tol = Optim::gradient_tolerance;
+        // algo_settings.lbfgs_settings.par_M=5;
+        // algo_settings.vals_bound = true;
+        // algo_settings.lower_bounds = theta_lower;
+        // algo_settings.upper_bounds = theta_upper;
+        //// turn-around for optimlib::lbfgs boundary bad behavior !!!
+        // algo_settings.lower_bounds = arma::max(arma::zeros(algo_settings.lower_bounds.n_elem),
+        // algo_settings.lower_bounds - 1E-3 * (algo_settings.upper_bounds - algo_settings.lower_bounds));
+        // algo_settings.upper_bounds = algo_settings.upper_bounds + 1E-1 * (algo_settings.upper_bounds -
+        // algo_settings.lower_bounds);
+        //
+        // bool bfgs_ok = optim::lbfgs(
+        //    gamma_tmp,
+        //    [&okm_data, this, &fit_ofn](const arma::vec& vals_inp, arma::vec* grad_out, void*) -> double {
+        //      return fit_ofn(vals_inp, grad_out, nullptr, &okm_data);
+        //    },
+        //    nullptr,
+        //    algo_settings);
+
+        lbfgsb::Optimizer optimizer{d};
+        optimizer.iprint = Optim::log_level - 2;
+        optimizer.max_iter = Optim::max_iteration;
+        optimizer.pgtol = Optim::gradient_tolerance;
+        optimizer.factr = Optim::objective_rel_tolerance / 1E-13;
+        arma::ivec bounds_type{d, arma::fill::value(2)};  // means both upper & lower bounds
+
+        auto result = optimizer.minimize(
+            [&okm_data, this, &fit_ofn](const arma::vec& vals_inp, arma::vec& grad_out) -> double {
+              return fit_ofn(vals_inp, &grad_out, nullptr, &okm_data);
+            },
+            gamma_tmp,
+            theta_lower.memptr(),
+            theta_upper.memptr(),
+            bounds_type.memptr());
+        if (Optim::log_level > 1)
+          result.print();
+
+        // this last call of fit_ofn is to ensure that T and z are up-to-date with solution found.
+        double min_ofn_tmp = fit_ofn(gamma_tmp, nullptr, nullptr, &okm_data);
+
+        if (Optim::log_level > 0) {
+          arma::cout << "  best objective: " << min_ofn_tmp << arma::endl;
+          arma::cout << "  best solution: " << gamma_tmp.t() << " ";
+        }
+
+        if (min_ofn_tmp < min_ofn) {
+          m_theta = gamma_tmp;
+          if (Optim::reparametrize)
+            m_theta = 1 / arma::exp(gamma_tmp);
+          m_est_theta = true;
+          min_ofn = min_ofn_tmp;
+          m_T = std::move(okm_data.T);
+          m_M = std::move(okm_data.M);
+          m_z = std::move(okm_data.z);
+          m_beta = std::move(okm_data.beta);
+          m_est_beta = parameters.is_beta_estim;
+          m_sigma2 = okm_data.sigma2;
+          m_est_sigma2 = parameters.is_sigma2_estim;
+        }
+      }
+    } else if (optim.rfind("Newton", 0) == 0) {
+      Random::init();
+
+      // FIXME parameters.has needs to implemtented (no use case in current code)
+      if (!parameters.has_theta) {  // no theta given, so draw 10 random uniform starting values
+        int multistart = 1;
+        try {
+          multistart = std::stoi(optim.substr(4));
+        } catch (std::invalid_argument) {
+          // let multistart = 1
+        }
+        theta0 = arma::repmat(trans(theta_lower), multistart, 1)
+                 + Random::randu_mat(multistart, d) % arma::repmat(trans(theta_upper - theta_lower), multistart, 1);
+      } else {  // just use given theta(s) as starting values for multi-bfgs
+        theta0 = arma::mat(parameters.theta);
       }
-      theta0 = Random::randu_mat(multistart, d) % (max(m_X, 0) - min(m_X, 0));
-    } else {  // just use given theta(s) as starting values for multi-bfgs
-      theta0 = arma::mat(parameters.theta);
-    }
 
-    // arma::cout << "theta0:" << theta0 << arma::endl;
-
-    double min_ofn = std::numeric_limits<double>::infinity();
-    for (arma::uword i = 0; i < theta0.n_rows; i++) {  // TODO: use some foreach/pragma to let OpenMP work.
-      arma::vec gamma_tmp = -arma::log(theta0.row(i)).t();
-      arma::mat T;
-      arma::mat M;
-      arma::colvec z;
-      arma::colvec beta;
-      if (parameters.has_beta)
-        beta = parameters.beta;
-      double sigma2 = -1;
-      if (parameters.has_sigma2)
-        sigma2 = parameters.sigma2;
-
-      Kriging::OKModel okm_data{T, M, z, beta, parameters.is_beta_estim, sigma2, parameters.is_sigma2_estim};
-
-      double min_ofn_tmp = optim_newton(
-          [&okm_data, this, &fit_ofn](const arma::vec& vals_inp, arma::vec* grad_out, arma::mat* hess_out) -> double {
-            return fit_ofn(vals_inp, grad_out, hess_out, &okm_data);
-          },
-          gamma_tmp,
-          -arma::log(2 * trans(max(m_X, 0) - min(m_X, 0))) * d,
-          -arma::log(1e-10 * arma::ones<arma::vec>(d)));
-
-      if (min_ofn_tmp < min_ofn) {
-        m_theta = 1 / arma::exp(gamma_tmp);
-        m_est_theta = true;
-        min_ofn = min_ofn_tmp;
-        m_T = std::move(okm_data.T);
-        m_M = std::move(okm_data.M);
-        m_z = std::move(okm_data.z);
-        m_beta = std::move(okm_data.beta);
-        m_est_beta = parameters.is_beta_estim;
-        m_sigma2 = okm_data.sigma2;
-        m_est_sigma2 = parameters.is_sigma2_estim;
+      // arma::cout << "theta0:" << theta0 << arma::endl;
+
+      if (Optim::reparametrize) {
+        arma::vec theta_lower_tmp = theta_lower;
+        theta_lower = -arma::log(theta_upper);
+        theta_upper = -arma::log(theta_lower_tmp);
       }
-    }
-  } else
-    throw std::runtime_error("Not a suitable optim: " + optim);
+
+      double min_ofn = std::numeric_limits<double>::infinity();
+
+      for (arma::uword i = 0; i < theta0.n_rows; i++) {  // TODO: use some foreach/pragma to let OpenMP work.
+        arma::vec gamma_tmp = theta0.row(i).t();
+        if (Optim::reparametrize)
+          gamma_tmp = -arma::log(theta0.row(i).t());
+
+        theta_lower = arma::min(gamma_tmp, theta_lower);
+        theta_upper = arma::max(gamma_tmp, theta_upper);
+
+        if (Optim::log_level > 0) {
+          arma::cout << "Newton:" << arma::endl;
+          arma::cout << "  max iterations: " << Optim::max_iteration << arma::endl;
+          arma::cout << "  null gradient tolerance: " << Optim::gradient_tolerance << arma::endl;
+          arma::cout << "  constant objective tolerance: " << Optim::objective_rel_tolerance << arma::endl;
+          arma::cout << "  reparametrize: " << Optim::reparametrize << arma::endl;
+          arma::cout << "  start_point: " << gamma_tmp.t() << " ";
+          arma::cout << "  lower_bounds: " << theta_lower.t() << " ";
+          arma::cout << "  upper_bounds: " << theta_upper.t() << " ";
+        }
+
+        arma::mat T;
+        arma::mat M;
+        arma::colvec z;
+        arma::colvec beta;
+        if (parameters.has_beta)
+          beta = parameters.beta;
+        double sigma2 = -1;
+        if (parameters.has_sigma2)
+          sigma2 = parameters.sigma2;
+
+        Kriging::OKModel okm_data{T, M, z, beta, parameters.is_beta_estim, sigma2, parameters.is_sigma2_estim};
+
+        double min_ofn_tmp = optim_newton(
+            [&okm_data, this, &fit_ofn](const arma::vec& vals_inp, arma::vec* grad_out, arma::mat* hess_out) -> double {
+              return fit_ofn(vals_inp, grad_out, hess_out, &okm_data);
+            },
+            gamma_tmp,
+            theta_upper,
+            theta_lower);
+
+        if (min_ofn_tmp < min_ofn) {
+          m_theta = gamma_tmp;
+          if (Optim::reparametrize)
+            m_theta = 1 / arma::exp(gamma_tmp);
+          m_est_theta = true;
+          min_ofn = min_ofn_tmp;
+          m_T = std::move(okm_data.T);
+          m_M = std::move(okm_data.M);
+          m_z = std::move(okm_data.z);
+          m_beta = std::move(okm_data.beta);
+          m_est_beta = parameters.is_beta_estim;
+          m_sigma2 = okm_data.sigma2;
+          m_est_sigma2 = parameters.is_sigma2_estim;
+        }
+      }
+    } else
+      throw std::runtime_error("Unsupported optim: " + optim + " (supported are: none, BFGS*, Newton*)");
+  }
 
   if (!parameters.has_sigma2)
     m_sigma2 *= scaleY * scaleY;
@@ -1102,88 +1312,161 @@ LIBKRIGING_EXPORT void Kriging::fit(const arma::colvec& y,
  * @param cov is true if return also cov matrix between Xp
  * @return output prediction: m means, [m standard deviations], [m*m full covariance matrix]
  */
-LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat> Kriging::predict(const arma::mat& Xp,
-                                                                                     bool withStd,
-                                                                                     bool withCov) {
+LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat, arma::mat, arma::mat>
+Kriging::predict(const arma::mat& Xp, bool withStd, bool withCov, bool withDeriv) {
   arma::uword m = Xp.n_rows;
   arma::uword n = m_X.n_rows;
+  arma::uword d = m_X.n_cols;
+  if (Xp.n_cols != d)
+    throw std::runtime_error("Predict locations have wrong dimension: " + std::to_string(Xp.n_cols) + " instead of "
+                             + std::to_string(d));
+
   arma::colvec pred_mean(m);
-  arma::colvec pred_stdev(m);
-  arma::mat pred_cov(m, m);
-  pred_stdev.zeros();
-  pred_cov.zeros();
+  arma::colvec pred_stdev = arma::zeros(m);
+  arma::mat pred_cov = arma::zeros(m, m);
+  arma::mat pred_mean_deriv = arma::zeros(m, d);
+  arma::mat pred_stdev_deriv = arma::zeros(m, d);
 
-  arma::mat Xtnorm = trans(m_X);
-  Xtnorm.each_col() /= m_theta;
+  arma::mat Xtnorm = trans(m_X);  // already normalized if needed
   arma::mat Xpnorm = Xp;
   // Normalize Xp
   Xpnorm.each_row() -= m_centerX;
   Xpnorm.each_row() /= m_scaleX;
 
+  /** @ref: https://github.com/cran/DiceKriging/blob/master/R/kmStruct.R#L191 */
+  // beta <- object@trend.coef
+
   // Define regression matrix
-  arma::uword d = m_X.n_cols;
-  arma::mat Ftest = Trend::regressionModelMatrix(m_regmodel, Xpnorm, m, d);
+  // F.newdata <- model.matrix(object@trend.formula, data = data.frame(newdata))
+  arma::mat F_p = Trend::regressionModelMatrix(m_regmodel, Xpnorm);
+  Xpnorm = trans(Xpnorm);
 
   // Compute covariance between training data and new data to predict
-  arma::mat R = arma::mat(n, m);
-  Xpnorm = trans(Xpnorm);
-  Xpnorm.each_col() /= m_theta;
+  // c.newdata <- covMat1Mat2(object@covariance, X1 = X, X2 = newdata,
+  //                          nugget.flag = object@covariance@nugget.flag)
+  // ## compute c(x) for x = newdata ; remark that for prediction (or filtering), cov(Yi, Yj)=0
+  // ## even if Yi and Yj are the outputs related to the equal points xi and xj.
+  arma::mat R_pred = arma::mat(n, m);
   for (arma::uword i = 0; i < n; i++) {
     for (arma::uword j = 0; j < m; j++) {
-      R.at(i, j) = CovNorm_fun(Xtnorm.col(i) - Xpnorm.col(j));
+      R_pred.at(i, j) = Cov((Xtnorm.col(i) - Xpnorm.col(j)), m_theta);
     }
   }
-  arma::mat Tinv_newdata = solve(m_T, R, arma::solve_opts::fast);
-  pred_mean = Ftest * m_beta + trans(Tinv_newdata) * m_z;
+
+  // Tinv.c.newdata <- backsolve(t(T), c.newdata, upper.tri=FALSE)
+  arma::mat Tinv_pred = solve(m_T, R_pred, arma::solve_opts::fast);
+  // y.predict.trend <- F.newdata%*%beta
+  // y.predict.complement <- t(Tinv.c.newdata)%*%z
+  // y.predict <- y.predict.trend + y.predict.complement
+  pred_mean = F_p * m_beta + trans(Tinv_pred) * m_z;
   // Un-normalize predictor
   pred_mean = m_centerY + m_scaleY * pred_mean;
 
-  if (withStd) {
-    double total_sd2 = m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
-    // s2.predict.1 <- apply(Tinv.c.newdata, 2, crossprod)
-    arma::colvec s2_predict_1 = total_sd2 * trans(sum(Tinv_newdata % Tinv_newdata, 0));
+  arma::mat s2_predict_mat;
+  arma::mat FinvMtM;
+  double total_sd2;
+  if (withStd | withCov) {  // Will use chol(t(M)%*%M) in all these cases
+    total_sd2 = m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
     // Type = "UK"
     // T.M <- chol(t(M)%*%M)
-    arma::mat TM = trans(chol(trans(m_M) * m_M));
+    arma::mat TM = trans(chol(trans(m_M) * m_M));  // same that arma::qr_econ(Q, TM, m_M);
     // s2.predict.mat <- backsolve(t(T.M), t(F.newdata - t(Tinv.c.newdata)%*%M) , upper.tri = FALSE)
-    arma::mat s2_predict_mat = solve(TM, trans(Ftest - trans(Tinv_newdata) * m_M), arma::solve_opts::fast);
+    s2_predict_mat = solve(TM, trans(F_p - trans(Tinv_pred) * m_M), arma::solve_opts::fast);
+
+    if (withDeriv) {
+      arma::mat m = trans(F_p - trans(Tinv_pred) * m_M);
+      arma::mat invMtM = inv_sympd(m_M.t() * m_M);
+      FinvMtM = (F_p - trans(Tinv_pred) * m_M) * inv_sympd(m_M.t() * m_M);
+    }
+  }
+
+  if (withStd) {
+    // s2.predict.1 <- apply(Tinv.c.newdata, 2, crossprod)
+    arma::colvec s2_predict_1 = trans(sum(Tinv_pred % Tinv_pred, 0));
+    s2_predict_1.transform([](double val) {
+      return (val > 1.0 ? 1.0 : val);
+    });  // constrain this first part to not be negative (rationale: it is the whole stdev for simple kriging)
+
     // s2.predict.2 <- apply(s2.predict.mat, 2, crossprod)
-    arma::colvec s2_predict_2 = total_sd2 * trans(sum(s2_predict_mat % s2_predict_mat, 0));
+    arma::colvec s2_predict_2 = trans(sum(s2_predict_mat % s2_predict_mat, 0));
     // s2.predict <- pmax(total.sd2 - s2.predict.1 + s2.predict.2, 0)
-    arma::mat s2_predict = total_sd2 - s2_predict_1 + s2_predict_2;
-    s2_predict.elem(find(pred_stdev < 0)).zeros();
-    s2_predict.transform([](double val) { return (std::isnan(val) ? 0.0 : val); });
+
+    arma::mat s2_predict = total_sd2 * (1.0 - s2_predict_1 + s2_predict_2);
+    s2_predict.transform([](double val) { return (std::isnan(val) || val < 0 ? 0.0 : val); });
     pred_stdev = sqrt(s2_predict);
-    if (withCov) {
-      // C.newdata <- covMatrix(object@covariance, newdata)[[1]]
-      arma::mat C_newdata = arma::mat(m, m);
-      for (arma::uword i = 0; i < m; i++) {
-        C_newdata.at(i, i) = 1;
-        for (arma::uword j = 0; j < i; j++) {
-          C_newdata.at(i, j) = C_newdata.at(j, i) = CovNorm_fun(Xpnorm.col(i) - Xpnorm.col(j));
-        }
-      }
-      // cond.cov <- C.newdata - crossprod(Tinv.c.newdata)
-      // cond.cov <- cond.cov + crossprod(s2.predict.mat)
-      pred_cov = total_sd2 * (C_newdata - trans(Tinv_newdata) * Tinv_newdata + trans(s2_predict_mat) * s2_predict_mat);
-    }
-  } else if (withCov) {
-    double total_sd2 = m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
+  }
 
-    arma::mat C_newdata = arma::mat(m, m);
+  if (withCov) {
+    arma::mat R_predpred = arma::mat(m, m);
     for (arma::uword i = 0; i < m; i++) {
-      C_newdata.at(i, i) = 1;
+      R_predpred.at(i, i) = 1;
       for (arma::uword j = 0; j < i; j++) {
-        C_newdata.at(j, i) = C_newdata.at(i, j) = CovNorm_fun(Xpnorm.col(i) - Xpnorm.col(j));
+        R_predpred.at(i, j) = R_predpred.at(j, i) = Cov((Xpnorm.col(i) - Xpnorm.col(j)), m_theta);
+      }
+    }
+
+    pred_cov = total_sd2 * (R_predpred - trans(Tinv_pred) * Tinv_pred + trans(s2_predict_mat) * s2_predict_mat);
+  }
+
+  if (withDeriv) {
+    /** @ref: https://github.com/cran/DiceOptim/blob/master/R/EI.grad.R#L156 */
+    // # Compute derivatives of the covariance and trend functions
+    for (arma::uword i = 0; i < m; i++) {  // for each Xp predict point... should be parallel ?
+
+      // dc <- covVector.dx(x=newdata.num, X=X, object=covStruct, c=c)
+      arma::mat dc = arma::mat(n, d);
+      for (arma::uword j = 0; j < n; j++) {
+        dc.row(j) = R_pred.at(j, i) * trans(DlnCovDx(Xpnorm.col(i) - Xtnorm.col(j), m_theta));
+      }
+
+      // f.deltax <- trend.deltax(x=newdata.num, model=model)
+      /** @ref: https://github.com/cran/DiceKriging/blob/master/R/trend.deltax.R#L69 */
+      // // A <- matrix(x, nrow=d, ncol=d, byrow=TRUE)
+      // // Apos <- A+h*diag(d)
+      // // Aneg <- A-h*diag(d)
+      // // newpoints <- data.frame(rbind(Apos, Aneg))
+      // // f.newdata <- model.matrix(model@trend.formula, data = newpoints)
+      // // f.deltax <- (f.newdata[1:d,]-f.newdata[(d+1):(2*d),])/(2*h)
+      const double h = 1.0E-5;  // Value is sensitive only for non linear trends. Otherwise, it gives exact results.
+      arma::mat tXpn_i_repd = arma::trans(Xpnorm.col(i) * arma::ones(1, d));  // just duplicate Xp.row(i) d times
+
+      arma::mat F_dx = (Trend::regressionModelMatrix(m_regmodel, tXpn_i_repd + h * arma::eye(d, d))
+                        - Trend::regressionModelMatrix(m_regmodel, tXpn_i_repd - h * arma::eye(d, d)))
+                       / (2 * h);
+
+      //# Compute gradients of the kriging mean and variance
+      // W <- backsolve(t(T), dc, upper.tri=FALSE)
+      arma::mat W = solve(m_T, dc, LinearAlgebra::default_solve_opts);
+
+      // kriging.mean.grad <- t(W)%*%z + t(model@trend.coef%*%f.deltax)
+      pred_mean_deriv.row(i) = trans(F_dx * m_beta + trans(W) * m_z);
+
+      //...
+      // F.newdata <- model.matrix(model@trend.formula, data=newdata)
+      // v <- predx$Tinv.c
+      // c <- predx$c
+      // ...
+      //  if (type=="UK")
+      //  { tuuinv <- solve(t(u)%*%u)
+      //  kriging.sd2.grad <-  t( -2*t(v)%*%W +
+      //                            2*(F.newdata - t(v)%*%u )%*% tuuinv %*%
+      //                            (f.deltax - t(t(W)%*%u) ))
+      //  kriging.sd.grad <- kriging.sd2.grad / (2*kriging.sd)
+      if (withStd) {
+        arma::mat pred_stdev_deriv_noTrend = Tinv_pred.t() * W;
+        pred_stdev_deriv.row(i) = (-pred_stdev_deriv_noTrend.row(i) + FinvMtM.row(i) * (F_dx.t() - trans(m_M) * W))
+                                  * total_sd2 / pred_stdev.at(i);
       }
     }
-    // Need to compute matrices computed in withStd case
-    arma::mat TM = trans(chol(trans(m_M) * m_M));
-    arma::mat s2_predict_mat = solve(TM, trans(Ftest - trans(Tinv_newdata) * m_M), arma::solve_opts::fast);
-    pred_cov = total_sd2 * (C_newdata - trans(Tinv_newdata) * Tinv_newdata + trans(s2_predict_mat) * s2_predict_mat);
+    pred_mean_deriv *= m_scaleY;
+    pred_stdev_deriv *= m_scaleY;
   }
 
-  return std::make_tuple(std::move(pred_mean), std::move(pred_stdev), std::move(pred_cov));
+  return std::make_tuple(std::move(pred_mean),
+                         std::move(pred_stdev),
+                         std::move(pred_cov),
+                         std::move(pred_mean_deriv),
+                         std::move(pred_stdev_deriv));
   /*if (withStd)
     if (withCov)
       return std::make_tuple(std::move(pred_mean), std::move(pred_stdev), std::move(pred_cov));
@@ -1204,6 +1487,10 @@ LIBKRIGING_EXPORT arma::mat Kriging::simulate(const int nsim, const int seed, co
   // Here nugget.sim = 1e-10 to avoid chol failures of Sigma_cond)
   arma::uword m = Xp.n_rows;
   arma::uword n = m_X.n_rows;
+  arma::uword d = m_X.n_cols;
+  if (Xp.n_cols != d)
+    throw std::runtime_error("Simulate locations have wrong dimension: " + std::to_string(Xp.n_cols) + " instead of "
+                             + std::to_string(d));
 
   arma::mat Xpnorm = Xp;
   // Normalize Xp
@@ -1211,23 +1498,20 @@ LIBKRIGING_EXPORT arma::mat Kriging::simulate(const int nsim, const int seed, co
   Xpnorm.each_row() /= m_scaleX;
 
   // Define regression matrix
-  arma::uword d = m_X.n_cols;
-  arma::mat F_newdata = Trend::regressionModelMatrix(m_regmodel, Xpnorm, m, d);
+  arma::mat F_p = Trend::regressionModelMatrix(m_regmodel, Xpnorm);
+  Xpnorm = trans(Xpnorm);
+  // t0 = Bench::toc("Xpnorm         ", t0);
 
   // auto t0 = Bench::tic();
-  arma::colvec y_trend = F_newdata * m_beta;  // / std::sqrt(m_sigma2);
+  arma::colvec y_trend = F_p * m_beta;  // / std::sqrt(m_sigma2);
   // t0 = Bench::toc("y_trend        ", t0);
 
-  Xpnorm = trans(Xpnorm);
-  Xpnorm.each_col() /= m_theta;
-  // t0 = Bench::toc("Xpnorm         ", t0);
-
   // Compute covariance between new data
   arma::mat Sigma = arma::mat(m, m);
   for (arma::uword i = 0; i < m; i++) {
     Sigma.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      Sigma.at(i, j) = Sigma.at(j, i) = CovNorm_fun(Xpnorm.col(i) - Xpnorm.col(j));
+      Sigma.at(i, j) = Sigma.at(j, i) = Cov((Xpnorm.col(i) - Xpnorm.col(j)), m_theta);
     }
   }
   // t0 = Bench::toc("Sigma          ", t0);
@@ -1235,12 +1519,11 @@ LIBKRIGING_EXPORT arma::mat Kriging::simulate(const int nsim, const int seed, co
   // arma::mat T_newdata = chol(Sigma);
   // Compute covariance between training data and new data to predict
   // Sigma21 <- covMat1Mat2(object@covariance, X1 = object@X, X2 = newdata, nugget.flag = FALSE)
-  arma::mat Xtnorm = trans(m_X);
-  Xtnorm.each_col() /= m_theta;
+  arma::mat Xtnorm = trans(m_X);  // already normalized if needed
   arma::mat Sigma21(n, m);
   for (arma::uword i = 0; i < n; i++) {
     for (arma::uword j = 0; j < m; j++) {
-      Sigma21.at(i, j) = CovNorm_fun(Xtnorm.col(i) - Xpnorm.col(j));
+      Sigma21.at(i, j) = Cov((Xtnorm.col(i) - Xpnorm.col(j)), m_theta);
     }
   }
   // t0 = Bench::toc("Sigma21        ", t0);
@@ -1268,8 +1551,7 @@ LIBKRIGING_EXPORT arma::mat Kriging::simulate(const int nsim, const int seed, co
   // t0 = Bench::toc("Sigma_cond     ", t0);
 
   // T.cond <- chol(Sigma.cond + diag(nugget.sim, m, m))
-  Sigma_cond.diag() += LinearAlgebra::num_nugget;
-  arma::mat tT_cond = chol(Sigma_cond, "lower");
+  arma::mat tT_cond = LinearAlgebra::safe_chol_lower(Sigma_cond);
   // t0 = Bench::toc("T_cond         ", t0);
 
   // white.noise <- matrix(rnorm(m*nsim), m, nsim)
@@ -1297,6 +1579,10 @@ LIBKRIGING_EXPORT arma::mat Kriging::simulate(const int nsim, const int seed, co
  * @param optim_objective is 'loo' or 'loglik'. Ignored if optim_method=='none'.
  */
 LIBKRIGING_EXPORT void Kriging::update(const arma::vec& newy, const arma::mat& newX) {
+  if (newy.n_elem != newX.n_rows)
+    throw std::runtime_error("Dimension of new data should be the same:\n X: (" + std::to_string(newX.n_rows) + "x"
+                             + std::to_string(newX.n_cols) + "), y: (" + std::to_string(newy.n_elem) + ")");
+
   // rebuild starting parameters
   Parameters parameters{this->m_sigma2,
                         true,
diff --git a/src/lib/LinearAlgebra.cpp b/src/lib/LinearAlgebra.cpp
index 7c3820fe..a8d65d7e 100644
--- a/src/lib/LinearAlgebra.cpp
+++ b/src/lib/LinearAlgebra.cpp
@@ -23,6 +23,15 @@ LIBKRIGING_EXPORT double LinearAlgebra::get_num_nugget() {
 };
 
 LIBKRIGING_EXPORT arma::mat LinearAlgebra::safe_chol_lower(arma::mat X) {
+  return LinearAlgebra::safe_chol_lower(X, 0);
+}
+
+bool LinearAlgebra::warn_chol = false;
+
+// Recursive turn-around for ill-condition of correlation matrix. Used in *Kriging::fit & *Kriging::simulate
+//' @ref: Andrianakis, I. and Challenor, P. G. (2012). The effect of the nugget on Gaussian pro-cess emulators of
+// computer models. Comput. Stat. Data Anal., 56(12):4215–4228.
+arma::mat LinearAlgebra::safe_chol_lower(arma::mat X, int inc_cond) {
   arma::mat R = arma::mat(X.n_rows, X.n_cols);
   bool ok = arma::chol(R, X, "lower");
   if (!ok) {
@@ -30,11 +39,15 @@ LIBKRIGING_EXPORT arma::mat LinearAlgebra::safe_chol_lower(arma::mat X) {
       throw std::runtime_error("[ERROR] Cannot add anumerical nugget which is not strictly positive: "
                                + std::to_string(LinearAlgebra::num_nugget));
     else {
-      arma::cout << "[WARNING]: chol failed, adding numerical nugget (on diagonal)" << arma::endl;
       X /= 1.0 + LinearAlgebra::num_nugget;
       X.diag().ones();
-      return LinearAlgebra::safe_chol_lower(X);
+      return LinearAlgebra::safe_chol_lower(X, inc_cond + 1);
     }
-  } else
+  } else {
+    if (inc_cond > 0)
+      if (warn_chol)
+        arma::cout << "[WARNING] Added " << inc_cond << " numerical nugget to force Cholesky decomposition"
+                   << arma::endl;
     return R;
+  }
 }
diff --git a/src/lib/NuggetKriging.cpp b/src/lib/NuggetKriging.cpp
index fa0fc663..613e87b2 100644
--- a/src/lib/NuggetKriging.cpp
+++ b/src/lib/NuggetKriging.cpp
@@ -6,17 +6,19 @@
 
 #include "libKriging/utils/lk_armadillo.hpp"
 
-#include "libKriging/Trend.hpp"
 #include "libKriging/CacheFunction.hpp"
 #include "libKriging/Covariance.hpp"
 #include "libKriging/KrigingException.hpp"
 #include "libKriging/LinearAlgebra.hpp"
+#include "libKriging/NuggetKriging.hpp"
+#include "libKriging/Optim.hpp"
 #include "libKriging/Random.hpp"
+#include "libKriging/Trend.hpp"
 #include "libKriging/utils/custom_hash_function.hpp"
-#include "libKriging/NuggetKriging.hpp"
+#include "libKriging/utils/data_from_arma_vec.hpp"
 
 #include <cassert>
-#include <optim.hpp>
+#include <lbfgsb_cpp/lbfgsb.hpp>
 #include <tuple>
 #include <vector>
 
@@ -28,21 +30,25 @@
 void NuggetKriging::make_Cov(const std::string& covType) {
   m_covType = covType;
   if (covType.compare("gauss") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_gauss;
-    Dln_CovNorm = Covariance::Dln_CovNorm_gauss;
-    CovNorm_pow = 2;
+    Cov = Covariance::Cov_gauss;
+    DlnCovDtheta = Covariance::DlnCovDtheta_gauss;
+    DlnCovDx = Covariance::DlnCovDx_gauss;
+    Cov_pow = 2;
   } else if (covType.compare("exp") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_exp;
-    Dln_CovNorm = Covariance::Dln_CovNorm_exp;
-    CovNorm_pow = 1;
+    Cov = Covariance::Cov_exp;
+    DlnCovDtheta = Covariance::DlnCovDtheta_exp;
+    DlnCovDx = Covariance::DlnCovDx_exp;
+    Cov_pow = 1;
   } else if (covType.compare("matern3_2") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_matern32;
-    Dln_CovNorm = Covariance::Dln_CovNorm_matern32;
-    // CovNorm_pow = 1.5;
+    Cov = Covariance::Cov_matern32;
+    DlnCovDtheta = Covariance::DlnCovDtheta_matern32;
+    DlnCovDx = Covariance::DlnCovDx_matern32;
+    Cov_pow = 1.5;
   } else if (covType.compare("matern5_2") == 0) {
-    CovNorm_fun = Covariance::CovNorm_fun_matern52;
-    Dln_CovNorm = Covariance::Dln_CovNorm_matern52;
-    // CovNorm_pow = 2.5;
+    Cov = Covariance::Cov_matern52;
+    DlnCovDtheta = Covariance::DlnCovDtheta_matern52;
+    DlnCovDx = Covariance::DlnCovDx_matern52;
+    Cov_pow = 2.5;
   } else
     throw std::invalid_argument("Unsupported covariance kernel: " + covType);
 
@@ -62,6 +68,10 @@ LIBKRIGING_EXPORT NuggetKriging::NuggetKriging(const arma::colvec& y,
                                                const std::string& optim,
                                                const std::string& objective,
                                                const Parameters& parameters) {
+  if (y.n_elem != X.n_rows)
+    throw std::runtime_error("Dimension of new data should be the same:\n X: (" + std::to_string(X.n_rows) + "x"
+                             + std::to_string(X.n_cols) + "), y: (" + std::to_string(y.n_elem) + ")");
+
   make_Cov(covType);
   fit(y, X, regmodel, normalize, optim, objective, parameters);
 }
@@ -83,24 +93,23 @@ LIBKRIGING_EXPORT NuggetKriging::NuggetKriging(const arma::colvec& y,
 double NuggetKriging::_logLikelihood(const arma::vec& _theta_alpha,
                                      arma::vec* grad_out,
                                      NuggetKriging::OKModel* okm_data) const {
-  // arma::cout << " theta, alpha: " << _theta_alpha.head(m_X.n_cols) << "," << _theta_alpha.at(m_X.n_cols) <<
-  // arma::endl;
+  // arma::cout << " theta, alpha: " << _theta_alpha.t() << arma::endl;
   //' @ref https://github.com/cran/DiceKriging/blob/master/R/logLikFun.R
-  //  model@covariance <- vect2covparam(model@covariance, param[1:(nparam - 1)])
-  //  model@covariance@sd2 <- 1
-  //  model@covariance@nugget <- 0
-  //  alpha <- param[nparam]
+  //   model@covariance <- vect2covparam(model@covariance, param[1:(nparam - 1)])
+  //   model@covariance@sd2 <- 1
+  //   model@covariance@nugget <- 0
+  //   alpha <- param[nparam]
   //
-  //  aux <- covMatrix(model@covariance, model@X)
-  //  R0 <- aux[[1]] - diag(aux[[2]])
-  //  R <- alpha * R0 + (1 - alpha) * diag(model@n)
+  //   aux <- covMatrix(model@covariance, model@X)
+  //   R0 <- aux[[1]] - diag(aux[[2]])
+  //   R <- alpha * R0 + (1 - alpha) * diag(model@n)
   //
-  //  T <- chol(R)
-  //  x <- backsolve(t(T), model@y, upper.tri = FALSE)
-  //  M <- backsolve(t(T), model@F, upper.tri = FALSE)
-  //  z <- compute.z(x=x, M=M, beta=beta)
-  //  v <- compute.sigma2.hat(z)
-  //  logLik <- -0.5 * (model@n * log(2 * pi * v) + 2*sum(log(diag(T))) + model@n)
+  //   T <- chol(R)
+  //   x <- backsolve(t(T), model@y, upper.tri = FALSE)
+  //   M <- backsolve(t(T), model@F, upper.tri = FALSE)
+  //   z <- compute.z(x=x, M=M, beta=beta)
+  //   v <- compute.sigma2.hat(z)
+  //   logLik <- -0.5 * (model@n * log(2 * pi * v) + 2*sum(log(diag(T))) + model@n)
 
   NuggetKriging::OKModel* fd = okm_data;
   arma::uword n = m_X.n_rows;
@@ -113,7 +122,7 @@ double NuggetKriging::_logLikelihood(const arma::vec& _theta_alpha,
   for (arma::uword i = 0; i < n; i++) {
     R.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      R.at(i, j) = R.at(j, i) = CovNorm_fun(m_dX.col(i * n + j) / _theta) * _alpha;
+      R.at(i, j) = R.at(j, i) = Cov(m_dX.col(i * n + j), _theta) * _alpha;
     }
   }
 
@@ -180,7 +189,7 @@ double NuggetKriging::_logLikelihood(const arma::vec& _theta_alpha,
     arma::cube gradC = arma::cube(d, n, n);
     for (arma::uword i = 0; i < n; i++) {
       for (arma::uword j = 0; j < i; j++) {
-        gradC.slice(i).col(j) = R.at(i, j) * Dln_CovNorm(m_dX.col(i * n + j) / _theta);
+        gradC.slice(i).col(j) = R.at(i, j) * DlnCovDtheta(m_dX.col(i * n + j), _theta);
       }
     }
 
@@ -192,7 +201,6 @@ double NuggetKriging::_logLikelihood(const arma::vec& _theta_alpha,
           gradC_k.at(i, j) = gradC_k.at(j, i) = gradC.slice(i).col(j)[k];
         }
       }
-      gradC_k /= _theta.at(k);
 
       // should make a fast function trace_prod(A,B) -> sum_i(sum_j(Ai,j*Bj,i))
       term1.at(k)
@@ -302,7 +310,7 @@ double NuggetKriging::_logMargPost(const arma::vec& _theta_alpha,
   for (arma::uword i = 0; i < n; i++) {
     R.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      R.at(i, j) = R.at(j, i) = CovNorm_fun(m_dX.col(i * n + j) / _theta) * _alpha;
+      R.at(i, j) = R.at(j, i) = Cov(m_dX.col(i * n + j), _theta) * _alpha;
     }
   }
 
@@ -398,7 +406,7 @@ double NuggetKriging::_logMargPost(const arma::vec& _theta_alpha,
     arma::cube gradR = arma::cube(d, n, n);
     for (arma::uword i = 0; i < n; i++) {
       for (arma::uword j = 0; j < i; j++) {
-        gradR.slice(i).col(j) = R.at(i, j) * Dln_CovNorm(m_dX.col(i * n + j) / _theta);
+        gradR.slice(i).col(j) = R.at(i, j) * DlnCovDtheta(m_dX.col(i * n + j), _theta);
       }
     }
 
@@ -411,7 +419,6 @@ double NuggetKriging::_logMargPost(const arma::vec& _theta_alpha,
           gradR_k.at(i, j) = gradR_k.at(j, i) = gradR.slice(i).col(j)[k];
         }
       }
-      gradR_k /= _theta.at(k);
 
       Wb_k = trans(solve(
                  trans(L), solve(L, gradR_k, LinearAlgebra::default_solve_opts), LinearAlgebra::default_solve_opts))
@@ -492,44 +499,76 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
                                           const std::string& optim,
                                           const std::string& objective,
                                           const Parameters& parameters) {
-  arma::uword n = X.n_rows;
-  arma::uword d = X.n_cols;
-
-  arma::vec theta_lower = 1e-3 * trans(max(X, 0) - min(X, 0));
-  arma::vec theta_upper = 2 * trans(max(X, 0) - min(X, 0));
+  const arma::uword n = X.n_rows;
+  const arma::uword d = X.n_cols;
 
   std::function<double(const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data)> fit_ofn;
   m_optim = optim;
   m_objective = objective;
   if (objective.compare("LL") == 0) {
-    fit_ofn = CacheFunction{[this](const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data) {
-      // Change variable for opt: . -> 1/exp(.)
-      arma::vec _theta_alpha = 1 / arma::exp(_gamma);
-      _theta_alpha[_theta_alpha.n_elem - 1] = _gamma[_theta_alpha.n_elem - 1];  // opt!
-      double ll = this->_logLikelihood(_theta_alpha, grad_out, okm_data);
-      if (grad_out != nullptr) {
-        (*grad_out).head(_theta_alpha.n_elem - 1) %= _theta_alpha.head(_theta_alpha.n_elem - 1);
-        (*grad_out).at(_theta_alpha.n_elem - 1) *= -1;
-      }
-      return -ll;
-    }};
+    if (Optim::reparametrize) {
+      fit_ofn = CacheFunction{[this](const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data) {
+        // Change variable for opt: . -> 1/exp(.)
+        // DEBUG: if (Optim::log_level>3) arma::cout << "> gamma: " << _gamma << arma::endl;
+        arma::vec _theta_alpha = 1 / arma::exp(_gamma);
+        _theta_alpha[_theta_alpha.n_elem - 1] = 1 / exp(_gamma[_theta_alpha.n_elem - 1]);  // opt!
+        // DEBUG: if (Optim::log_level>3) arma::cout << "> theta_alpha: " << _theta_alpha << arma::endl;
+        double ll = this->_logLikelihood(_theta_alpha, grad_out, okm_data);
+        // DEBUG: if (Optim::log_level>3) arma::cout << "  > ll: " << ll << arma::endl;
+        if (grad_out != nullptr) {
+          // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad ll: " << grad_out << arma::endl;
+          (*grad_out).head(_theta_alpha.n_elem - 1) %= _theta_alpha.head(_theta_alpha.n_elem - 1);
+          (*grad_out).at(_theta_alpha.n_elem - 1) *= _theta_alpha.at(_theta_alpha.n_elem - 1);
+        }
+        return -ll;
+      }};
+    } else {
+      fit_ofn = CacheFunction{[this](const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data) {
+        const arma::vec _theta_alpha = _gamma;
+        // DEBUG: if (Optim::log_level>3) arma::cout << "> theta_alpha: " << _theta_alpha << arma::endl;
+        double ll = this->_logLikelihood(_theta_alpha, grad_out, okm_data);
+        // DEBUG: if (Optim::log_level>3) arma::cout << "  > ll: " << ll << arma::endl;
+        if (grad_out != nullptr) {
+          // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad ll: " << grad_out << arma::endl;
+          *grad_out = -*grad_out;
+        }
+        return -ll;
+      }};
+    }
   } else if (objective.compare("LMP") == 0) {
     // Our impl. of https://github.com/cran/RobustGaSP/blob/5cf21658e6a6e327be6779482b93dfee25d24592/R/rgasp.R#L303
     //@see Mengyang Gu, Xiao-jing Wang and Jim Berger, 2018, Annals of Statistics.
-    fit_ofn = CacheFunction{[this](const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data) {
-      // Change variable for opt: . -> 1/exp(.)
-      arma::vec _theta_alpha = 1 / arma::exp(_gamma);
-      _theta_alpha[_theta_alpha.n_elem - 1] = _gamma[_theta_alpha.n_elem - 1];  // opt!
-      double lmp = this->_logMargPost(_theta_alpha, grad_out, okm_data);
-      if (grad_out != nullptr) {
-        (*grad_out).head(_theta_alpha.n_elem - 1) %= _theta_alpha.head(_theta_alpha.n_elem - 1);
-        (*grad_out).at(_theta_alpha.n_elem - 1) *= -1;
-      }
-      return -lmp;
-    }};
-
+    if (Optim::reparametrize) {
+      fit_ofn = CacheFunction{[this](const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data) {
+        // Change variable for opt: . -> 1/exp(.)
+        // DEBUG: if (Optim::log_level>3) arma::cout << "> gamma: " << _gamma << arma::endl;
+        arma::vec _theta_alpha = 1 / arma::exp(_gamma);
+        _theta_alpha[_theta_alpha.n_elem - 1] = 1 / exp(_gamma[_theta_alpha.n_elem - 1]);  // opt!
+        // DEBUG: if (Optim::log_level>3) arma::cout << "> theta_alpha: " << _theta_alpha << arma::endl;
+        double lmp = this->_logMargPost(_theta_alpha, grad_out, okm_data);
+        // DEBUG: if (Optim::log_level>3) arma::cout << "  > lmp: " << lmp << arma::endl;
+        if (grad_out != nullptr) {
+          // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad lmp: " << grad_out << arma::endl;
+          (*grad_out).head(_theta_alpha.n_elem - 1) %= _theta_alpha.head(_theta_alpha.n_elem - 1);
+          (*grad_out).at(_theta_alpha.n_elem - 1) *= _theta_alpha.at(_theta_alpha.n_elem - 1);
+        }
+        return -lmp;
+      }};
+    } else {
+      fit_ofn = CacheFunction{[this](const arma::vec& _gamma, arma::vec* grad_out, NuggetKriging::OKModel* okm_data) {
+        const arma::vec _theta_alpha = _gamma;
+        // DEBUG: if (Optim::log_level>3) arma::cout << "> theta_alpha: " << _theta_alpha << arma::endl;
+        double lmp = this->_logMargPost(_theta_alpha, grad_out, okm_data);
+        // DEBUG: if (Optim::log_level>3) arma::cout << "  > lmp: " << lmp << arma::endl;
+        if (grad_out != nullptr) {
+          // DEBUG: if (Optim::log_level>3) arma::cout << "  > grad lmp: " << grad_out << arma::endl;
+          *grad_out = -*grad_out;
+        }
+        return -lmp;
+      }};
+    }
   } else
-    throw std::invalid_argument("Unsupported fit objective: " + objective);
+    throw std::invalid_argument("Unsupported fit objective: " + objective + " (supported are: LL, LMP)");
 
   arma::rowvec centerX(d);
   arma::rowvec scaleX(d);
@@ -573,7 +612,7 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
 
   // Define regression matrix
   m_regmodel = regmodel;
-  m_F = Trend::regressionModelMatrix(regmodel, m_X, n, d);
+  m_F = Trend::regressionModelMatrix(regmodel, m_X);
 
   arma::mat theta0 = parameters.theta;
   if (parameters.has_theta) {
@@ -619,8 +658,13 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
                                     nugget + sigma2};
 
     arma::vec gamma_tmp = arma::vec(d + 1);
-    gamma_tmp.head(d) = -arma::log(m_theta);
+    gamma_tmp.head(d) = m_theta;
     gamma_tmp.at(d) = sigma2 / (nugget + sigma2);
+    if (Optim::reparametrize) {
+      gamma_tmp.head(d) = -arma::log(m_theta);
+      gamma_tmp.at(d) = -log(sigma2 / (nugget + sigma2));
+    }
+
     double min_ofn_tmp = fit_ofn(gamma_tmp, nullptr, &okm_data);
 
     m_T = std::move(okm_data.T);
@@ -636,6 +680,35 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
   } else if (optim.rfind("BFGS", 0) == 0) {
     Random::init();
 
+    arma::vec theta_lower = Optim::theta_lower_factor * trans(max(X, 0) - min(X, 0));
+    arma::vec theta_upper = Optim::theta_upper_factor * trans(max(X, 0) - min(X, 0));
+
+    if (Optim::variogram_bounds_heuristic) {
+      arma::vec dy2 = arma::zeros(n * n);
+      for (arma::uword ij = 0; ij < dy2.n_elem; ij++) {
+        int i = (int)ij / n;
+        int j = ij % n;  // i,j <-> i*n+j
+        if (i < j) {
+          dy2[ij] = m_y.at(i) - m_y.at(j);
+          dy2[ij] *= dy2[ij];
+          dy2[j * n + i] = dy2[ij];
+        }
+      }
+      // dy2 /= arma::var(m_y);
+      arma::vec dy2dX2_slope = dy2 / arma::sum(m_dX % m_dX, 0).t();
+      // arma::cout << "dy2dX_slope:" << dy2dX_slope << arma::endl;
+      dy2dX2_slope.replace(arma::datum::nan, 0.0);  // we are not interested in same points where dX=0, and dy=0
+      arma::vec w = dy2dX2_slope / sum(dy2dX2_slope);
+      arma::mat steepest_dX_mean = arma::abs(m_dX) * w;
+
+      theta_lower = arma::max(theta_lower, Optim::theta_lower_factor / steepest_dX_mean);
+      theta_upper = arma::min(theta_upper, Optim::theta_upper_factor / steepest_dX_mean);
+      theta_lower = arma::min(theta_lower, theta_upper);
+      theta_upper = arma::max(theta_lower, theta_upper);
+    }
+    // arma::cout << "theta_lower:" << theta_lower << arma::endl;
+    // arma::cout << "theta_upper:" << theta_upper << arma::endl;
+
     // FIXME parameters.has needs to implemtented (no use case in current code)
     if (!parameters.has_theta) {  // no theta given, so draw 10 random uniform starting values
       int multistart = 1;
@@ -644,50 +717,76 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
       } catch (std::invalid_argument) {
         // let multistart = 1
       }
-      theta0 = arma::abs(0.5 + Random::randn_mat(multistart, d) / 6.0)
-               % arma::repmat(max(m_X, 0) - min(m_X, 0), multistart, 1);
+      theta0 = arma::repmat(trans(theta_lower), multistart, 1)
+               + Random::randu_mat(multistart, d) % arma::repmat(trans(theta_upper - theta_lower), multistart, 1);
+      // theta0 = arma::abs(0.5 + Random::randn_mat(multistart, d) / 6.0)
+      //          % arma::repmat(max(m_X, 0) - min(m_X, 0), multistart, 1);
     } else {  // just use given theta(s) as starting values for multi-bfgs
       theta0 = arma::mat(parameters.theta);
     }
-
     // arma::cout << "theta0:" << theta0 << arma::endl;
 
+    if (Optim::reparametrize) {
+      arma::vec theta_lower_tmp = theta_lower;
+      theta_lower = -arma::log(theta_upper);
+      theta_upper = -arma::log(theta_lower_tmp);
+    }
+
+    double alpha_upper = 1.0;
+    double alpha_lower = 1E-3;
+
     arma::vec alpha0;
     if (parameters.has_sigma2 & parameters.has_nugget) {
       alpha0 = arma::vec(parameters.sigma2.n_elem * parameters.nugget.n_elem);
       for (size_t i = 0; i < parameters.sigma2.n_elem; i++) {
         for (size_t j = 0; j < parameters.nugget.n_elem; j++) {
-          if (parameters.sigma2[i] <= 0 | parameters.nugget[j] <= 0)
-            alpha0[i + j * parameters.sigma2.n_elem] = Random::randu();
+          if (parameters.sigma2[i] < 0 | parameters.nugget[j] < 0 | parameters.sigma2[i] + parameters.nugget[j] < 0)
+            alpha0[i + j * parameters.sigma2.n_elem] = alpha_lower + (alpha_upper - alpha_lower) * Random::randu();
           else
             alpha0[i + j * parameters.sigma2.n_elem]
                 = parameters.sigma2[i] / (parameters.sigma2[i] + parameters.nugget[j]);
         }
       }
     } else {
-      alpha0 = Random::randu_vec(theta0.n_rows);
+      alpha0 = alpha_lower + (alpha_upper - alpha_lower) * Random::randu_vec(theta0.n_rows);
     }
-
     // arma::cout << "alpha0:" << alpha0 << arma::endl;
 
-    optim::algo_settings_t algo_settings;
-    algo_settings.print_level = 0;
-    algo_settings.iter_max = 20;
-    algo_settings.rel_sol_change_tol = 0.01;
-    algo_settings.grad_err_tol = 1e-8;
-    algo_settings.vals_bound = true;
-    algo_settings.lower_bounds = arma::vec(d + 1);
-    algo_settings.lower_bounds.head(d) = -arma::log(theta_upper);
-    algo_settings.lower_bounds.at(d) = 0;
-    algo_settings.upper_bounds = arma::vec(d + 1);
-    algo_settings.upper_bounds.head(d) = -arma::log(theta_lower);
-    algo_settings.upper_bounds.at(d) = 1;
+    if (Optim::reparametrize) {
+      double alpha_lower_tmp = alpha_lower;
+      alpha_lower = -log(alpha_upper);
+      alpha_upper = -log(alpha_lower_tmp);
+    }
+
     double min_ofn = std::numeric_limits<double>::infinity();
 
     for (arma::uword i = 0; i < theta0.n_rows; i++) {
       arma::vec gamma_tmp = arma::vec(d + 1);
-      gamma_tmp.head(d) = -arma::log(theta0.row(i)).t();
+      gamma_tmp.head(d) = theta0.row(i).t();
       gamma_tmp.at(d) = alpha0[i % alpha0.n_elem];
+      if (Optim::reparametrize) {
+        gamma_tmp.head(d) = -arma::log(theta0.row(i)).t();
+        gamma_tmp.at(d) = -log(alpha0[i % alpha0.n_elem]);
+      }
+
+      theta_lower = arma::min(gamma_tmp.head(d), theta_lower);
+      theta_upper = arma::max(gamma_tmp.head(d), theta_upper);
+      alpha_lower = std::min(gamma_tmp.at(d), alpha_lower);
+      alpha_upper = std::max(gamma_tmp.at(d), alpha_upper);
+
+      if (Optim::log_level > 0) {
+        arma::cout << "BFGS:" << arma::endl;
+        arma::cout << "  max iterations: " << Optim::max_iteration << arma::endl;
+        arma::cout << "  null gradient tolerance: " << Optim::gradient_tolerance << arma::endl;
+        arma::cout << "  constant objective tolerance: " << Optim::objective_rel_tolerance << arma::endl;
+        arma::cout << "  reparametrize: " << Optim::reparametrize << arma::endl;
+        arma::cout << "  start_point: " << gamma_tmp.t() << "";
+        arma::cout << "  lower_bounds: " << theta_lower.t() << "";
+        arma::cout << "                " << alpha_lower << arma::endl;
+        arma::cout << "  upper_bounds: " << theta_upper.t() << "";
+        arma::cout << "                " << alpha_upper << arma::endl;
+      }
+
       arma::mat T;
       arma::mat M;
       arma::colvec z;
@@ -712,21 +811,63 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
                                       parameters.is_nugget_estim,
                                       nugget + sigma2};
 
-      bool bfgs_ok = optim::lbfgs(
-          gamma_tmp,
-          [&okm_data, this, fit_ofn](const arma::vec& vals_inp, arma::vec* grad_out, void*) -> double {
-            return fit_ofn(vals_inp, grad_out, &okm_data);
+      //// Using OptimLib
+      // optim::algo_settings_t algo_settings;
+      // algo_settings.print_level = 0;
+      // algo_settings.iter_max = 20;
+      // algo_settings.rel_sol_change_tol = 0.01;
+      // algo_settings.grad_err_tol = 1e-8;
+      // algo_settings.vals_bound = true;
+      // algo_settings.lower_bounds = arma::vec(d + 1);
+      // algo_settings.lower_bounds.head(d) = -arma::log(theta_upper);
+      // algo_settings.lower_bounds.at(d) = 0;
+      // algo_settings.upper_bounds = arma::vec(d + 1);
+      // algo_settings.upper_bounds.head(d) = -arma::log(theta_lower);
+      // algo_settings.upper_bounds.at(d) = 1;
+      //
+      // bool bfgs_ok = optim::lbfgs(
+      //     gamma_tmp,
+      //     [&okm_data, this, fit_ofn](const arma::vec& vals_inp, arma::vec* grad_out, void*) -> double {
+      //       return fit_ofn(vals_inp, grad_out, &okm_data);
+      //     },
+      //     nullptr,
+      //     algo_settings);
+
+      lbfgsb::Optimizer optimizer{d + 1};
+      optimizer.iprint = Optim::log_level - 2;
+      optimizer.max_iter = Optim::max_iteration;
+      optimizer.pgtol = Optim::gradient_tolerance;
+      optimizer.factr = Optim::objective_rel_tolerance / 1E-13;
+      arma::ivec bounds_type{d + 1, arma::fill::value(2)};  // means both upper & lower bounds
+      arma::vec theta_alpha_lower = arma::vec(d + 1);
+      theta_alpha_lower.head(d) = theta_lower;
+      theta_alpha_lower.at(d) = alpha_lower;
+      arma::vec theta_alpha_upper = arma::vec(d + 1);
+      theta_alpha_upper.head(d) = theta_upper;
+      theta_alpha_upper.at(d) = alpha_upper;
+      auto result = optimizer.minimize(
+          [&okm_data, this, &fit_ofn](const arma::vec& vals_inp, arma::vec& grad_out) -> double {
+            return fit_ofn(vals_inp, &grad_out, &okm_data);
           },
-          nullptr,
-          algo_settings);
-
-      double min_ofn_tmp
-          = fit_ofn(gamma_tmp,
-                    nullptr,
-                    &okm_data);  // this last call also ensure that T and z are up-to-date with solution found.
+          gamma_tmp,
+          theta_alpha_lower.memptr(),
+          theta_alpha_upper.memptr(),
+          bounds_type.memptr());
+      if (Optim::log_level > 1)
+        result.print();
+
+      // this last call also ensure that T and z are up-to-date with solution found.
+      double min_ofn_tmp = fit_ofn(gamma_tmp, nullptr, &okm_data);
+
+      if (Optim::log_level > 0) {
+        arma::cout << "  best objective: " << min_ofn_tmp << arma::endl;
+        arma::cout << "  best solution: " << gamma_tmp.t() << "";
+      }
 
       if (min_ofn_tmp < min_ofn) {
-        m_theta = 1 / arma::exp(gamma_tmp.head(d));
+        m_theta = gamma_tmp.head(d);
+        if (Optim::reparametrize)
+          m_theta = 1 / arma::exp(gamma_tmp.head(d));
         m_est_theta = true;
         min_ofn = min_ofn_tmp;
         m_T = std::move(okm_data.T);
@@ -741,7 +882,7 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
       }
     }
   } else
-    throw std::runtime_error("Not a suitable optim: " + optim);
+    throw std::runtime_error("Unsupported optim: " + optim + " (supported are: none, BFGS**)");
 
   if (!parameters.has_sigma2)
     m_sigma2 *= scaleY * scaleY;
@@ -757,97 +898,132 @@ LIBKRIGING_EXPORT void NuggetKriging::fit(const arma::colvec& y,
  * @param cov is true if return also cov matrix between Xp
  * @return output prediction: m means, [m standard deviations], [m*m full covariance matrix]
  */
-LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat> NuggetKriging::predict(const arma::mat& Xp,
-                                                                                           bool withStd,
-                                                                                           bool withCov) {
+LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat, arma::mat, arma::mat>
+NuggetKriging::predict(const arma::mat& Xp, bool withStd, bool withCov, bool withDeriv) {
   arma::uword m = Xp.n_rows;
   arma::uword n = m_X.n_rows;
+  arma::uword d = m_X.n_cols;
+  if (Xp.n_cols != d)
+    throw std::runtime_error("Predict locations have wrong dimension: " + std::to_string(Xp.n_cols) + " instead of "
+                             + std::to_string(d));
+
   arma::colvec pred_mean(m);
-  arma::colvec pred_stdev(m);
-  arma::mat pred_cov(m, m);
-  pred_stdev.zeros();
-  pred_cov.zeros();
+  arma::colvec pred_stdev = arma::zeros(m);
+  arma::mat pred_cov = arma::zeros(m, m);
+  arma::mat pred_mean_deriv = arma::zeros(m, d);
+  arma::mat pred_stdev_deriv = arma::zeros(m, d);
 
-  arma::mat Xtnorm = trans(m_X);
-  Xtnorm.each_col() /= m_theta;
+  arma::mat Xtnorm = trans(m_X);  // already normalized if needed
   arma::mat Xpnorm = Xp;
   // Normalize Xp
   Xpnorm.each_row() -= m_centerX;
   Xpnorm.each_row() /= m_scaleX;
 
   // Define regression matrix
-  arma::uword d = m_X.n_cols;
-  arma::mat Ftest = Trend::regressionModelMatrix(m_regmodel, Xpnorm, m, d);
+  arma::mat F_p = Trend::regressionModelMatrix(m_regmodel, Xpnorm);
+  Xpnorm = trans(Xpnorm);
 
   // Compute covariance between training data and new data to predict
-  arma::mat R = arma::ones(n, m);
+  arma::mat R_pred = arma::ones(n, m);
   // double total_sd2 = m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0) + m_nugget;
   double total_sd2 = (m_sigma2 + m_nugget) * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
   // R *= m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0) / total_sd2;
-  R *= m_sigma2 / total_sd2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
-  Xpnorm = trans(Xpnorm);
-  Xpnorm.each_col() /= m_theta;
+  R_pred *= m_sigma2 / total_sd2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
   for (arma::uword i = 0; i < n; i++) {
     for (arma::uword j = 0; j < m; j++) {
       arma::vec dij = Xtnorm.col(i) - Xpnorm.col(j);
       if (arma::all(dij == 0))
-        R.at(i, j) = 1.0;  // m_sigma2 + m_nugget;
+        R_pred.at(i, j) = 1.0;  // m_sigma2 + m_nugget;
       else
-        R.at(i, j) *= CovNorm_fun(dij);
+        R_pred.at(i, j) *= Cov(dij, m_theta);
     }
   }
-  arma::mat Tinv_newdata = solve(m_T, R, arma::solve_opts::fast);
-  pred_mean = Ftest * m_beta + trans(Tinv_newdata) * m_z;
+  arma::mat Tinv_pred = solve(m_T, R_pred, arma::solve_opts::fast);
+  pred_mean = F_p * m_beta + trans(Tinv_pred) * m_z;
   // Un-normalize predictor
   pred_mean = m_centerY + m_scaleY * pred_mean;
 
+  arma::mat s2_predict_mat;
+  arma::mat FinvMtM;
+  if (withStd | withCov) {
+    arma::mat TM = trans(chol(trans(m_M) * m_M));
+    s2_predict_mat = solve(TM, trans(F_p - trans(Tinv_pred) * m_M), arma::solve_opts::fast);
+
+    if (withDeriv) {
+      arma::mat m = trans(F_p - trans(Tinv_pred) * m_M);
+      arma::mat invMtM = inv_sympd(m_M.t() * m_M);
+      FinvMtM = (F_p - trans(Tinv_pred) * m_M) * inv_sympd(m_M.t() * m_M);
+    }
+  }
   if (withStd) {
     // s2.predict.1 <- apply(Tinv.c.newdata, 2, crossprod)
-    arma::colvec s2_predict_1 = total_sd2 * trans(sum(Tinv_newdata % Tinv_newdata, 0));
-    // Type = "UK"
-    // T.M <- chol(t(M)%*%M)
-    arma::mat TM = trans(chol(trans(m_M) * m_M));
-    // s2.predict.mat <- backsolve(t(T.M), t(F.newdata - t(Tinv.c.newdata)%*%M) , upper.tri = FALSE)
-    arma::mat s2_predict_mat = solve(TM, trans(Ftest - trans(Tinv_newdata) * m_M), arma::solve_opts::fast);
+    arma::colvec s2_predict_1 = trans(sum(Tinv_pred % Tinv_pred, 0));
+    s2_predict_1.transform([](double val) {
+      return (val > 1.0 ? 1.0 : val);
+    });  // constrain this first part to not be negative (rationale: it is the whole stdev for simple kriging)
+
     // s2.predict.2 <- apply(s2.predict.mat, 2, crossprod)
-    arma::colvec s2_predict_2 = total_sd2 * trans(sum(s2_predict_mat % s2_predict_mat, 0));
+    arma::colvec s2_predict_2 = trans(sum(s2_predict_mat % s2_predict_mat, 0));
     // s2.predict <- pmax(total.sd2 - s2.predict.1 + s2.predict.2, 0)
-    arma::mat s2_predict = total_sd2 - s2_predict_1 + s2_predict_2;
-    s2_predict.elem(find(pred_stdev < 0)).zeros();
-    s2_predict.transform([](double val) { return (std::isnan(val) ? 0.0 : val); });
+
+    arma::mat s2_predict = total_sd2 * (1.0 - s2_predict_1 + s2_predict_2);
+    s2_predict.transform([](double val) { return (std::isnan(val) || val < 0 ? 0.0 : val); });
     pred_stdev = sqrt(s2_predict);
-    if (withCov) {
-      // C.newdata <- covMatrix(object@covariance, newdata)[[1]]
-      arma::mat C_newdata = arma::ones(m, m);
-      // C_newdata *= m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0) / total_sd2;
-      C_newdata *= m_sigma2 / total_sd2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
-      for (arma::uword i = 0; i < m; i++) {
-        C_newdata.at(i, i) = 1;
-        for (arma::uword j = 0; j < i; j++) {
-          C_newdata.at(i, j) = C_newdata.at(j, i) *= CovNorm_fun(Xpnorm.col(i) - Xpnorm.col(j));
-        }
-      }
-      // cond.cov <- C.newdata - crossprod(Tinv.c.newdata)
-      // cond.cov <- cond.cov + crossprod(s2.predict.mat)
-      pred_cov = total_sd2 * (C_newdata - trans(Tinv_newdata) * Tinv_newdata + trans(s2_predict_mat) * s2_predict_mat);
-    }
-  } else if (withCov) {
-    arma::mat C_newdata = arma::ones(m, m);
+  }
+
+  if (withCov) {
+    // C.newdata <- covMatrix(object@covariance, newdata)[[1]]
+    arma::mat R_predpred = arma::ones(m, m);
     // C_newdata *= m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0) / total_sd2;
-    C_newdata *= m_sigma2 / total_sd2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
+    R_predpred *= m_sigma2 / total_sd2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0);
     for (arma::uword i = 0; i < m; i++) {
-      C_newdata.at(i, i) = 1;
+      R_predpred.at(i, i) = 1;
       for (arma::uword j = 0; j < i; j++) {
-        C_newdata.at(j, i) = C_newdata.at(i, j) *= CovNorm_fun(Xpnorm.col(i) - Xpnorm.col(j));
+        R_predpred.at(i, j) = R_predpred.at(j, i) *= Cov((Xpnorm.col(i) - Xpnorm.col(j)), m_theta);
       }
     }
-    // Need to compute matrices computed in withStd case
-    arma::mat TM = trans(chol(trans(m_M) * m_M));
-    arma::mat s2_predict_mat = solve(TM, trans(Ftest - trans(Tinv_newdata) * m_M), arma::solve_opts::fast);
-    pred_cov = total_sd2 * (C_newdata - trans(Tinv_newdata) * Tinv_newdata + trans(s2_predict_mat) * s2_predict_mat);
+    // cond.cov <- C.newdata - crossprod(Tinv.c.newdata)
+    // cond.cov <- cond.cov + crossprod(s2.predict.mat)
+
+    pred_cov = total_sd2 * (R_predpred - trans(Tinv_pred) * Tinv_pred + trans(s2_predict_mat) * s2_predict_mat);
   }
 
-  return std::make_tuple(std::move(pred_mean), std::move(pred_stdev), std::move(pred_cov));
+  if (withDeriv) {
+    // # Compute derivatives of the covariance and trend functions
+    for (arma::uword i = 0; i < m; i++) {  // for each Xp predict point... should be parallel ?
+
+      arma::mat dc = arma::mat(n, d);
+      for (arma::uword j = 0; j < n; j++) {
+        dc.row(j) = R_pred.at(j, i) * trans(DlnCovDx(Xpnorm.col(i) - Xtnorm.col(j), m_theta));
+      }
+
+      const double h = 1.0E-5;  // Value is sensitive only for non linear trends. Otherwise, it gives exact results.
+      arma::mat tXpn_i_repd = arma::trans(Xpnorm.col(i) * arma::ones(1, d));  // just duplicate Xp.row(i) d times
+
+      arma::mat F_dx = (Trend::regressionModelMatrix(m_regmodel, tXpn_i_repd + h * arma::eye(d, d))
+                        - Trend::regressionModelMatrix(m_regmodel, tXpn_i_repd - h * arma::eye(d, d)))
+                       / (2 * h);
+
+      //# Compute gradients of the kriging mean and variance
+      arma::mat W = solve(m_T, dc, LinearAlgebra::default_solve_opts);
+
+      pred_mean_deriv.row(i) = trans(F_dx * m_beta + trans(W) * m_z);
+
+      if (withStd) {
+        arma::mat pred_stdev_deriv_noTrend = Tinv_pred.t() * W;
+        pred_stdev_deriv.row(i) = (-pred_stdev_deriv_noTrend.row(i) + FinvMtM.row(i) * (F_dx.t() - trans(m_M) * W))
+                                  * total_sd2 / pred_stdev.at(i);
+      }
+    }
+    pred_mean_deriv *= m_scaleY;
+    pred_stdev_deriv *= m_scaleY;
+  }
+
+  return std::make_tuple(std::move(pred_mean),
+                         std::move(pred_stdev),
+                         std::move(pred_cov),
+                         std::move(pred_mean_deriv),
+                         std::move(pred_stdev_deriv));
   /*if (withStd)
     if (withCov)
       return std::make_tuple(std::move(pred_mean), std::move(pred_stdev), std::move(pred_cov));
@@ -866,9 +1042,12 @@ LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat> NuggetKrigin
  */
 LIBKRIGING_EXPORT arma::mat NuggetKriging::simulate(const int nsim, const int seed, const arma::mat& Xp) {
   // Here nugget.sim = 1e-10 to avoid chol failures of Sigma_cond)
-  double nugget_sim = 1e-10;
   arma::uword m = Xp.n_rows;
   arma::uword n = m_X.n_rows;
+  arma::uword d = m_X.n_cols;
+  if (Xp.n_cols != d)
+    throw std::runtime_error("Simulate locations have wrong dimension: " + std::to_string(Xp.n_cols) + " instead of "
+                             + std::to_string(d));
 
   arma::mat Xpnorm = Xp;
   // Normalize Xp
@@ -876,13 +1055,13 @@ LIBKRIGING_EXPORT arma::mat NuggetKriging::simulate(const int nsim, const int se
   Xpnorm.each_row() /= m_scaleX;
 
   // Define regression matrix
-  arma::uword d = m_X.n_cols;
-  arma::mat F_newdata = Trend::regressionModelMatrix(m_regmodel, Xpnorm, m, d);
-
-  arma::colvec y_trend = F_newdata * m_beta;  // / std::sqrt(m_sigma2);
-
+  arma::mat F_p = Trend::regressionModelMatrix(m_regmodel, Xpnorm);
   Xpnorm = trans(Xpnorm);
-  Xpnorm.each_col() /= m_theta;
+  // t0 = Bench::toc("Xpnorm         ", t0);
+
+  // auto t0 = Bench::tic();
+  arma::colvec y_trend = F_p * m_beta;  // / std::sqrt(m_sigma2);
+  // t0 = Bench::toc("y_trend        ", t0);
 
   // Compute covariance between new data
   arma::mat Sigma = arma::ones(m, m);
@@ -893,7 +1072,7 @@ LIBKRIGING_EXPORT arma::mat NuggetKriging::simulate(const int nsim, const int se
   for (arma::uword i = 0; i < m; i++) {
     Sigma.at(i, i) = 1;
     for (arma::uword j = 0; j < i; j++) {
-      Sigma.at(i, j) = Sigma.at(j, i) *= CovNorm_fun(Xpnorm.col(i) - Xpnorm.col(j));
+      Sigma.at(i, j) = Sigma.at(j, i) *= Cov((Xpnorm.col(i) - Xpnorm.col(j)), m_theta);
     }
   }
 
@@ -901,15 +1080,13 @@ LIBKRIGING_EXPORT arma::mat NuggetKriging::simulate(const int nsim, const int se
   // Compute covariance between training data and new data to predict
   // Sigma21 <- covMat1Mat2(object@covariance, X1 = object@X, X2 = newdata, nugget.flag = FALSE)
   arma::mat Xtnorm = trans(m_X);
-  Xtnorm.each_col() /= m_theta;
   arma::mat Sigma21(n, m);
   for (arma::uword i = 0; i < n; i++) {
     for (arma::uword j = 0; j < m; j++) {
-      arma::vec dij = Xtnorm.col(i) - Xpnorm.col(j);
       // if (arma::all(dij==0))
       //  Sigma21.at(i, j) = 1.0;//m_sigma2 + m_nugget;
       // else
-      Sigma21.at(i, j) = CovNorm_fun(dij);
+      Sigma21.at(i, j) = Cov((Xtnorm.col(i) - Xpnorm.col(j)), m_theta);
     }
   }
   Sigma21 *= m_sigma2 * (m_objective.compare("LMP") == 0 ? (n - d) / (n - d - 2) : 1.0) / total_sd2;
@@ -935,8 +1112,7 @@ LIBKRIGING_EXPORT arma::mat NuggetKriging::simulate(const int nsim, const int se
   }
 
   // T.cond <- chol(Sigma.cond + diag(nugget.sim, m, m))
-  Sigma_cond.diag() += LinearAlgebra::num_nugget;
-  arma::mat tT_cond = chol(Sigma_cond, "lower");
+  arma::mat tT_cond = LinearAlgebra::safe_chol_lower(Sigma_cond);
 
   // white.noise <- matrix(rnorm(m*nsim), m, nsim)
   // y.rand.cond <- t(T.cond) %*% white.noise
@@ -960,6 +1136,10 @@ LIBKRIGING_EXPORT arma::mat NuggetKriging::simulate(const int nsim, const int se
  * @param optim_objective is 'loo' or 'loglik'. Ignored if optim_method=='none'.
  */
 LIBKRIGING_EXPORT void NuggetKriging::update(const arma::vec& newy, const arma::mat& newX) {
+  if (newy.n_elem != newX.n_rows)
+    throw std::runtime_error("Dimension of new data should be the same:\n X: (" + std::to_string(newX.n_rows) + "x"
+                             + std::to_string(newX.n_cols) + "), y: (" + std::to_string(newy.n_elem) + ")");
+
   // rebuild starting parameters
   Parameters parameters{arma::vec(1, arma::fill::value(this->m_nugget)),
                         true,
diff --git a/src/lib/Optim.cpp b/src/lib/Optim.cpp
new file mode 100644
index 00000000..f54162fc
--- /dev/null
+++ b/src/lib/Optim.cpp
@@ -0,0 +1,85 @@
+// clang-format off
+// MUST BE at the beginning before any other <cmath> include (e.g. in armadillo's headers)
+#define _USE_MATH_DEFINES // required for Visual Studio
+#include <cmath>
+// clang-format on
+
+#include "libKriging/Optim.hpp"
+
+#include "libKriging/utils/lk_armadillo.hpp"
+
+bool Optim::reparametrize = true;
+
+LIBKRIGING_EXPORT void Optim::use_reparametrize(bool do_reparametrize) {
+  Optim::reparametrize = do_reparametrize;
+};
+
+LIBKRIGING_EXPORT bool Optim::is_reparametrized() {
+  return Optim::reparametrize;
+};
+
+double Optim::theta_lower_factor = 0.02;
+
+LIBKRIGING_EXPORT void Optim::set_theta_lower_factor(double _theta_lower_factor) {
+  Optim::theta_lower_factor = _theta_lower_factor;
+};
+
+LIBKRIGING_EXPORT double Optim::get_theta_lower_factor() {
+  return Optim::theta_lower_factor;
+};
+
+double Optim::theta_upper_factor = 10.0;
+
+LIBKRIGING_EXPORT void Optim::set_theta_upper_factor(double _theta_upper_factor) {
+  Optim::theta_upper_factor = _theta_upper_factor;
+};
+
+LIBKRIGING_EXPORT double Optim::get_theta_upper_factor() {
+  return Optim::theta_upper_factor;
+};
+
+bool Optim::variogram_bounds_heuristic = true;
+
+LIBKRIGING_EXPORT void Optim::use_variogram_bounds_heuristic(bool _variogram_bounds_heuristic) {
+  Optim::variogram_bounds_heuristic = _variogram_bounds_heuristic;
+};
+
+LIBKRIGING_EXPORT bool Optim::variogram_bounds_heuristic_used() {
+  return Optim::variogram_bounds_heuristic;
+};
+
+int Optim::log_level = 0;
+
+LIBKRIGING_EXPORT void Optim::log(int l) {
+  Optim::log_level = l;
+};
+
+int Optim::max_iteration = 20;
+
+LIBKRIGING_EXPORT void Optim::set_max_iteration(int max_iteration_val) {
+  Optim::max_iteration = max_iteration_val;
+};
+
+LIBKRIGING_EXPORT int Optim::get_max_iteration() {
+  return Optim::max_iteration;
+};
+
+double Optim::gradient_tolerance = 0.01;
+
+LIBKRIGING_EXPORT void Optim::set_gradient_tolerance(double gradient_tolerance_val) {
+  Optim::gradient_tolerance = gradient_tolerance_val;
+};
+
+LIBKRIGING_EXPORT double Optim::get_gradient_tolerance() {
+  return Optim::gradient_tolerance;
+};
+
+double Optim::objective_rel_tolerance = 0.001;
+
+LIBKRIGING_EXPORT void Optim::set_objective_rel_tolerance(double objective_rel_tolerance_val) {
+  Optim::objective_rel_tolerance = objective_rel_tolerance_val;
+};
+
+LIBKRIGING_EXPORT double Optim::get_objective_rel_tolerance() {
+  return Optim::objective_rel_tolerance;
+};
\ No newline at end of file
diff --git a/src/lib/Trend.cpp b/src/lib/Trend.cpp
index 68b65d3d..ab13c8c3 100644
--- a/src/lib/Trend.cpp
+++ b/src/lib/Trend.cpp
@@ -6,8 +6,8 @@
 
 #include "libKriging/utils/lk_armadillo.hpp"
 
-#include "libKriging/Trend.hpp"
 #include "libKriging/KrigingException.hpp"
+#include "libKriging/Trend.hpp"
 
 #include <cassert>
 
@@ -31,10 +31,9 @@ std::string Trend::toString(const Trend::RegressionModel& e) {
   return Trend::enum_RegressionModel_strings[static_cast<int>(e)];
 }
 
-arma::mat Trend::regressionModelMatrix(const Trend::RegressionModel& regmodel,
-                                       const arma::mat& newX,
-                                       arma::uword n,
-                                       arma::uword d) {
+arma::mat Trend::regressionModelMatrix(const Trend::RegressionModel& regmodel, const arma::mat& newX) {
+  arma::uword n = newX.n_rows;
+  arma::uword d = newX.n_cols;
   arma::mat F;  // uses modern RTO to avoid returned object copy
   switch (regmodel) {
     case Trend::RegressionModel::Constant: {
diff --git a/src/lib/include/libKriging/Covariance.hpp b/src/lib/include/libKriging/Covariance.hpp
index 498126c4..fe5e9a9f 100644
--- a/src/lib/include/libKriging/Covariance.hpp
+++ b/src/lib/include/libKriging/Covariance.hpp
@@ -7,25 +7,35 @@
 
 class Covariance {
  public:
-  static std::function<double(const arma::vec&)> CovNorm_fun_gauss;
+  static std::function<double(const arma::vec&, const arma::vec&)> Cov_gauss;
 
-  static std::function<arma::vec(const arma::vec&)> Dln_CovNorm_gauss;
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta_gauss;
 
-  static std::function<double(const arma::vec&)> CovNorm_fun_exp;
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx_gauss;
 
-  static std::function<arma::vec(const arma::vec&)> Dln_CovNorm_exp;
+  static std::function<double(const arma::vec&, const arma::vec&)> Cov_exp;
 
-  static std::function<double(const arma::vec&)> CovNorm_fun_matern32;
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta_exp;
 
-  static std::function<arma::vec(const arma::vec&)> Dln_CovNorm_matern32;
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx_exp;
 
-  static std::function<double(const arma::vec&)> CovNorm_fun_matern52;
+  static std::function<double(const arma::vec&, const arma::vec&)> Cov_matern32;
 
-  static std::function<arma::vec(const arma::vec&)> Dln_CovNorm_matern52;
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta_matern32;
 
-  static std::function<double(const arma::vec&)> CovNorm_fun_whitenoise;
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx_matern32;
 
-  static std::function<arma::vec(const arma::vec&)> Dln_CovNorm_whitenoise;
+  static std::function<double(const arma::vec&, const arma::vec&)> Cov_matern52;
+
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta_matern52;
+
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx_matern52;
+
+  static std::function<double(const arma::vec&, const arma::vec&)> Cov_whitenoise;
+
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta_whitenoise;
+
+  static std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx_whitenoise;
 };
 
 #endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_COVARIANCE_HPP
\ No newline at end of file
diff --git a/src/lib/include/libKriging/Kriging.hpp b/src/lib/include/libKriging/Kriging.hpp
index dc38084f..13bc6ca7 100644
--- a/src/lib/include/libKriging/Kriging.hpp
+++ b/src/lib/include/libKriging/Kriging.hpp
@@ -90,10 +90,10 @@ class Kriging {
   double m_sigma2;
   bool m_est_sigma2;
 
-  std::function<double(const arma::vec&)>
-      CovNorm_fun;  // dist_norm is L1 distance between to points of X, divided by theta
-  std::function<arma::vec(const arma::vec&)> Dln_CovNorm;
-  double CovNorm_pow;  // power factor used in hessian
+  std::function<double(const arma::vec&, const arma::vec&)> Cov;
+  std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta;
+  std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx;
+  double Cov_pow;
 
   // This will create the dist(xi,xj) function above. Need to parse "kernel".
   void make_Cov(const std::string& covType);
@@ -162,9 +162,10 @@ class Kriging {
    * @param cov is true if return also cov matrix between Xp
    * @return output prediction: m means, [m standard deviations], [m*m full covariance matrix]
    */
-  LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat> predict(const arma::mat& Xp,
-                                                                              bool withStd,
-                                                                              bool withCov);
+  LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat, arma::mat, arma::mat> predict(const arma::mat& Xp,
+                                                                                                    bool withStd,
+                                                                                                    bool withCov,
+                                                                                                    bool withDeriv);
 
   /** Draw sample trajectories of kriging at given points X'
    * @param Xp is m*d matrix of points where to simulate output
diff --git a/src/lib/include/libKriging/LinearAlgebra.hpp b/src/lib/include/libKriging/LinearAlgebra.hpp
index c60ddda4..3430a4b1 100644
--- a/src/lib/include/libKriging/LinearAlgebra.hpp
+++ b/src/lib/include/libKriging/LinearAlgebra.hpp
@@ -8,10 +8,14 @@
 class LinearAlgebra {
  public:
   static const arma::solve_opts::opts default_solve_opts;
+
   static double num_nugget;
   LIBKRIGING_EXPORT static void set_num_nugget(double nugget);
   LIBKRIGING_EXPORT static double get_num_nugget();
+
+  static bool warn_chol;
   LIBKRIGING_EXPORT static arma::mat safe_chol_lower(arma::mat X);
+  static arma::mat safe_chol_lower(arma::mat X, int warn);
 };
 
-#endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_LINEARALGEBRA_HPP
\ No newline at end of file
+#endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_LINEARALGEBRA_HPP
diff --git a/src/lib/include/libKriging/NuggetKriging.hpp b/src/lib/include/libKriging/NuggetKriging.hpp
index 1d0c640f..35f8d99b 100644
--- a/src/lib/include/libKriging/NuggetKriging.hpp
+++ b/src/lib/include/libKriging/NuggetKriging.hpp
@@ -113,10 +113,10 @@ class NuggetKriging {
   bool m_est_sigma2;
   double m_nugget;
   bool m_est_nugget;
-  std::function<double(const arma::vec&)>
-      CovNorm_fun;  // dist_norm is L1 distance between to points of X, divided by theta
-  std::function<arma::vec(const arma::vec&)> Dln_CovNorm;
-  double CovNorm_pow;  // power factor used in hessian
+  std::function<double(const arma::vec&, const arma::vec&)> Cov;
+  std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDtheta;
+  std::function<arma::vec(const arma::vec&, const arma::vec&)> DlnCovDx;
+  double Cov_pow;  // power factor used in hessian
 
   // This will create the dist(xi,xj) function above. Need to parse "kernel".
   void make_Cov(const std::string& covType);
@@ -179,9 +179,10 @@ class NuggetKriging {
    * @param cov is true if return also cov matrix between Xp
    * @return output prediction: m means, [m standard deviations], [m*m full covariance matrix]
    */
-  LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat> predict(const arma::mat& Xp,
-                                                                              bool withStd,
-                                                                              bool withCov);
+  LIBKRIGING_EXPORT std::tuple<arma::colvec, arma::colvec, arma::mat, arma::mat, arma::mat> predict(const arma::mat& Xp,
+                                                                                                    bool withStd,
+                                                                                                    bool withCov,
+                                                                                                    bool withDeriv);
 
   /** Draw sample trajectories of kriging at given points X'
    * @param Xp is m*d matrix of points where to simulate output
diff --git a/src/lib/include/libKriging/Optim.hpp b/src/lib/include/libKriging/Optim.hpp
new file mode 100644
index 00000000..db860c5a
--- /dev/null
+++ b/src/lib/include/libKriging/Optim.hpp
@@ -0,0 +1,42 @@
+#ifndef LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_OPTIM_HPP
+#define LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_OPTIM_HPP
+
+#include "libKriging/utils/lk_armadillo.hpp"
+
+#include "libKriging/libKriging_exports.h"
+
+class Optim {
+ public:
+  static bool reparametrize;
+  LIBKRIGING_EXPORT static void use_reparametrize(bool do_reparametrize);
+  LIBKRIGING_EXPORT static bool is_reparametrized();
+
+  static double theta_lower_factor;
+  LIBKRIGING_EXPORT static void set_theta_lower_factor(double _theta_lower_factor);
+  LIBKRIGING_EXPORT static double get_theta_lower_factor();
+
+  static double theta_upper_factor;
+  LIBKRIGING_EXPORT static void set_theta_upper_factor(double _theta_upper_factor);
+  LIBKRIGING_EXPORT static double get_theta_upper_factor();
+
+  static bool variogram_bounds_heuristic;
+  LIBKRIGING_EXPORT static void use_variogram_bounds_heuristic(bool _variogram_bounds_heuristic);
+  LIBKRIGING_EXPORT static bool variogram_bounds_heuristic_used();
+
+  static int log_level;
+  LIBKRIGING_EXPORT static void log(int t);
+
+  static int max_iteration;
+  LIBKRIGING_EXPORT static void set_max_iteration(int max_iteration_val);
+  LIBKRIGING_EXPORT static int get_max_iteration();
+
+  static double gradient_tolerance;
+  LIBKRIGING_EXPORT static void set_gradient_tolerance(double gradient_tolerance_val);
+  LIBKRIGING_EXPORT static double get_gradient_tolerance();
+
+  static double objective_rel_tolerance;
+  LIBKRIGING_EXPORT static void set_objective_rel_tolerance(double objective_rel_tolerance_val);
+  LIBKRIGING_EXPORT static double get_objective_rel_tolerance();
+};
+
+#endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_LINLIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_OPTIM_HPPEARALGEBRA_HPP
\ No newline at end of file
diff --git a/src/lib/include/libKriging/Trend.hpp b/src/lib/include/libKriging/Trend.hpp
index 35deb509..f52fe587 100644
--- a/src/lib/include/libKriging/Trend.hpp
+++ b/src/lib/include/libKriging/Trend.hpp
@@ -14,10 +14,7 @@ class Trend {
   LIBKRIGING_EXPORT static RegressionModel fromString(const std::string& s);
   LIBKRIGING_EXPORT static std::string toString(const RegressionModel& m);
 
-  LIBKRIGING_EXPORT static arma::mat regressionModelMatrix(const RegressionModel& regmodel,
-                                                           const arma::mat& newX,
-                                                           arma::uword n,
-                                                           arma::uword d);
+  LIBKRIGING_EXPORT static arma::mat regressionModelMatrix(const RegressionModel& regmodel, const arma::mat& newXt);
 };
 
 #endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_TREND_HPP
diff --git a/src/lib/include/libKriging/utils/custom_hash_function.hpp b/src/lib/include/libKriging/utils/custom_hash_function.hpp
index 1fe75fcd..d34fff56 100644
--- a/src/lib/include/libKriging/utils/custom_hash_function.hpp
+++ b/src/lib/include/libKriging/utils/custom_hash_function.hpp
@@ -4,6 +4,7 @@
 #include <armadillo>
 #include <functional>
 #include "libKriging/Kriging.hpp"
+#include "libKriging/NuggetKriging.hpp"
 #include "libKriging/utils/cache_details.hpp"
 
 template <>
@@ -73,4 +74,27 @@ struct std::hash<Kriging::OKModel*> {
   }
 };
 
+template <>
+struct std::hash<NuggetKriging::OKModel*> {
+  std::size_t operator()(const NuggetKriging::OKModel* s) const noexcept {
+    if (s != nullptr) {
+      struct OKModel {
+        arma::mat T;
+        arma::mat M;
+        arma::colvec z;
+        arma::colvec beta;
+        bool is_beta_estim;
+        double sigma2;
+        bool is_sigma2_estim;
+        double nugget;
+        bool is_nugget_estim;
+      };
+
+      //      return std::hash<arma::mat>{}(*s);
+    } else {
+      return 0;
+    }
+  }
+};
+
 #endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_HASH_FUNCTION_HPP
diff --git a/src/lib/include/libKriging/utils/data_from_arma_vec.hpp b/src/lib/include/libKriging/utils/data_from_arma_vec.hpp
new file mode 100644
index 00000000..cd93f53b
--- /dev/null
+++ b/src/lib/include/libKriging/utils/data_from_arma_vec.hpp
@@ -0,0 +1,10 @@
+#ifndef LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_UTILS_DATA_FROM_ARMA_VEC_HPP
+#define LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_UTILS_DATA_FROM_ARMA_VEC_HPP
+
+namespace std {
+inline double* data(arma::vec& x) {
+  return x.memptr();
+}
+}  // namespace std
+
+#endif  // LIBKRIGING_SRC_LIB_INCLUDE_LIBKRIGING_UTILS_DATA_FROM_ARMA_VEC_HPP


Test patch:
diff --git a/.travis-ci/checks/test.sh b/.travis-ci/checks/test.sh
index a167a0ae..272870f6 100755
--- a/.travis-ci/checks/test.sh
+++ b/.travis-ci/checks/test.sh
@@ -5,11 +5,8 @@ if [[ "$DEBUG_CI" == "true" ]]; then
   set -x
 fi
 
-# to get readlink on MacOS (no effect on Linux)
-if [[ -e /usr/local/opt/coreutils/libexec/gnubin/readlink ]]; then
-    export PATH=/usr/local/opt/coreutils/libexec/gnubin:$PATH
-fi
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 "${BASEDIR}"/check-git-clang-format-output.sh
\ No newline at end of file
diff --git a/.travis-ci/linux-macos/test.sh b/.travis-ci/linux-macos/test.sh
index 84a7652b..67203416 100755
--- a/.travis-ci/linux-macos/test.sh
+++ b/.travis-ci/linux-macos/test.sh
@@ -8,7 +8,7 @@ else
   CTEST_FLAGS=--output-on-failure
 fi
 
-cd build
+cd ${BUILD_DIR:-build}
 
 if [[ "$ENABLE_COVERAGE" == "on" ]]; then
     cmake --build . --target coverage --config "${MODE}"
diff --git a/.travis-ci/octave-windows/test.sh b/.travis-ci/octave-windows/test.sh
index a9d6a497..ebff8ca0 100644
--- a/.travis-ci/octave-windows/test.sh
+++ b/.travis-ci/octave-windows/test.sh
@@ -6,8 +6,7 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
-
-. ${BASEDIR}/loadenv.sh
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 "${BASEDIR}"/../linux-macos/test.sh
diff --git a/.travis-ci/r-windows/test.sh b/.travis-ci/r-windows/test.sh
index 054eb7aa..2d522642 100755
--- a/.travis-ci/r-windows/test.sh
+++ b/.travis-ci/r-windows/test.sh
@@ -6,16 +6,15 @@ if [[ "$DEBUG_CI" == "true" ]]; then
 fi
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 # Default configuration when used out of travis-ci
 if [[ -n ${TRAVIS_BUILD_DIR:+x} ]]; then
     cd "${TRAVIS_BUILD_DIR}"
 fi
 
-. ${BASEDIR}/loadenv.sh
-
-export LIBKRIGING_PATH=${PWD}/build/installed
+export LIBKRIGING_PATH=${PWD}/${BUILD_DIR:-build}/installed
 export PATH=${LIBKRIGING_PATH}/bin:${PATH}
 
 cd bindings/R
diff --git a/.travis-ci/windows/test.sh b/.travis-ci/windows/test.sh
index dd59b5f6..9ac98393 100755
--- a/.travis-ci/windows/test.sh
+++ b/.travis-ci/windows/test.sh
@@ -2,7 +2,8 @@
 set -eo pipefail
 
 BASEDIR=$(dirname "$0")
-BASEDIR=$(readlink -f "${BASEDIR}")
+BASEDIR=$(cd "$BASEDIR" && pwd -P)
+test -f "${BASEDIR}"/loadenv.sh && . "${BASEDIR}"/loadenv.sh 
 
 if [[ "$DEBUG_CI" == "true" ]]; then
   CTEST_FLAGS="--verbose --output-on-failure"
@@ -18,9 +19,7 @@ if [[ "$ENABLE_COVERAGE" == "on" ]]; then
     travis_terminate 1
 fi
 
-. ${BASEDIR}/loadenv.sh
-
-cd build
+cd ${BUILD_DIR:-build}
 
 # Cleanup compiled libs to check right path finding
 rm -fr src/lib
diff --git a/bindings/Python/tests/WrappedPyKriging_parametric_test.py b/bindings/Python/tests/WrappedPyKriging_parametric_test.py
index a97afc9d..c5b585db 100644
--- a/bindings/Python/tests/WrappedPyKriging_parametric_test.py
+++ b/bindings/Python/tests/WrappedPyKriging_parametric_test.py
@@ -17,7 +17,7 @@ def test_kriging_f_order(n, m):
 
     rl = lk.WrappedPyKriging(y, X, "gauss")
 
-    y_pred, _stderr, _cov = rl.predict(X, True, True)
+    y_pred, _stderr, _cov, mean_deriv, stderr_deriv = rl.predict(X, True, True, True)
 
 
 # @pytest.mark.skip(reason="Needs to be stabilized; got runtime error: chol(): decomposition failed")
@@ -31,4 +31,4 @@ def test_kriging_c_order(n, m):
 
     rl = lk.WrappedPyKriging(y, X, "gauss")
 
-    y_pred, _stderr, _cov = rl.predict(X, True, True)
+    y_pred, _stderr, _cov, mean_deriv, stderr_deriv = rl.predict(X, True, True, True)
diff --git a/bindings/Python/tests/pylibkriging_demo.py b/bindings/Python/tests/pylibkriging_demo.py
index 5fd5c1f9..efea82f1 100644
--- a/bindings/Python/tests/pylibkriging_demo.py
+++ b/bindings/Python/tests/pylibkriging_demo.py
@@ -11,8 +11,8 @@ def test_kriging():
     print(k_py.summary())
 
     x = np.arange(0, 1, 1 / 99)
-    p = k_py.predict(x, True, False)
-    p = {"mean": p[0], "stdev": p[1], "cov": p[2]}  # This should be done by predict
+    p = k_py.predict(x, True, False, False)
+    p = {"mean": p[0], "stdev": p[1], "cov": p[2], "mean_deriv": p[3], "stdev_deriv": p[4]}  # This should be done by predict
 
     try:
         import matplotlib.pyplot as pyplot
diff --git a/bindings/R/rlibkriging/tests/testthat/notest-RobustGaSP.R b/bindings/R/rlibkriging/tests/testthat/notest-RobustGaSP.R
deleted file mode 100644
index 29f87d6c..00000000
--- a/bindings/R/rlibkriging/tests/testthat/notest-RobustGaSP.R
+++ /dev/null
@@ -1,176 +0,0 @@
-library(testthat)
-
-library(RobustGaSP)
-
-context("RobustGaSP / Fit: 1D")
-
-f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
-#plot(f)
-n <- 5
-set.seed(123)
-X <- as.matrix(runif(n))
-y = f(X)
-#points(X,y)
-k = RobustGaSP::rgasp(design=X,response=y)
-library(rlibkriging)
-r <- Kriging(y, X,
-  kernel="matern5_2",
-  regmodel = "constant", normalize = FALSE,
-  optim = "BFGS",
-  objective = "LMP")
-# m = as.list(r)
-
-
-lmp_rgasp = function(X, model=k) {if (!is.matrix(X)) X = matrix(X,ncol=1);
-                  # print(dim(X));
-                  apply(X,1,
-                    function(x) {
-                      #y=-logMargPostFun(r,matrix(unlist(x),ncol=2))$logMargPost
-                      y=RobustGaSP:::neg_log_marginal_post_approx_ref(param=exp(x),nugget=0, nugget.est=model@nugget.est, 
-                                        R0=model@R0,X=model@X, zero_mean=model@zero_mean,output=model@output, 
-                                        CL=model@CL, 
-                                        a=0.2,
-                                        b=1/(length(model@output))^{1/dim(as.matrix(model@input))[2]}*(0.2+dim(as.matrix(model@output))[2]),
-                                        kernel_type=rep(as.integer(3),ncol(X)),alpha=model@alpha
-                                        )
-                      y})}
-plot(lmp_rgasp,xlim=c(0.01,0.9))
-abline(v=log(log(k@beta_hat)))
-
-lmp_lk = function(X) {if (!is.matrix(X)) X = matrix(X,ncol=1);
-                  # print(dim(X));
-                  apply(X,1,
-                    function(x) {
-                      y=-logMargPostFun(r,matrix(unlist(exp(-exp(x))),ncol=1))$logMargPost
-                      y})}
-lines(seq(0.1,0.9,,101),lmp_lk(seq(0.1,0.9,,101)),col='red')
-
-precision <- 1e-3
-test_that(desc=paste0("fitted theta is the same that RobustGaSP one"),
-          expect_equal(as.list(r)$theta[1],1/k@beta_hat,tol = precision))
-
-ntest <- 100
-Xtest <- seq(0,1,,ntest)
-Ytest_rgasp <- predict(k,matrix(Xtest,ncol=1))
-Ytest_libK <- predict(r,Xtest)
-
-plot(f)
-points(X,y)
-lines(Xtest,Ytest_rgasp$mean,col='blue')
-polygon(c(Xtest,rev(Xtest)),
-        c(Ytest_rgasp$mean+2*Ytest_rgasp$sd,rev(Ytest_rgasp$mean-2*Ytest_rgasp$sd)),
-        col=rgb(0,0,1,0.1), border=NA)
-
-lines(Xtest,Ytest_libK$mean,col='red')
-polygon(c(Xtest,rev(Xtest)),
-        c(Ytest_libK$mean+2*Ytest_libK$stdev,rev(Ytest_libK$mean-2*Ytest_libK$stdev)),
-        col=rgb(1,0,0,0.1), border=NA)
-
-precision <- 1e-3
-test_that(desc=paste0("pred mean is the same that RobustGaSP one"),
-          expect_equal(predict(r,0.7)$mean[1],predict(k,matrix(0.7))$mean,tol = precision))
-test_that(desc=paste0("pred sd is the same that RobustGaSP one"),
-          expect_equal(predict(r,0.7)$stdev[1],predict(k,matrix(0.7))$sd,tol = precision))
-
-
-## RobustGaSP examples
-
-  #---------------------------------------
-  # a 1 dimensional example 
-  #---------------------------------------
-context("RobustGaSP / 1 dimensional example")
-
-
-  input=10*seq(0,1,1/14)
-  output<-higdon.1.data(input)
-  #the following code fit a GaSP with zero mean by setting zero.mean="Yes"
-  model<- rgasp(design = input, response = output, zero.mean="No")
-  model
-  
-  testing_input = as.matrix(seq(0,10,1/100))
-  model.predict<-predict(model,testing_input)
-  names(model.predict)
-  
-  #########plot predictive distribution
-  testing_output=higdon.1.data(testing_input)
-  plot(testing_input,model.predict$mean,type='l',col='blue',
-       xlab='input',ylab='output')
-  polygon( c(testing_input,rev(testing_input)),c(model.predict$lower95,
-        rev(model.predict$upper95)),col =  "grey80", border = FALSE)
-  lines(testing_input, testing_output)
-  lines(testing_input,model.predict$mean,type='l',col='blue')
-  lines(input, output,type='p')
-  
-  ## mean square erros
-  mean((model.predict$mean-testing_output)^2)
-
-model_libK = Kriging(matrix(output,ncol=1), matrix(input,ncol=1), 
-  kernel="matern5_2", 
-  regmodel = "constant", normalize = FALSE, 
-  optim = "BFGS", 
-  objective = "LMP", parameters = NULL)
-
-    lines(testing_input,predict(model_libK,testing_input)$mean,type='l',col='red')
-    polygon( 
-      c(testing_input,rev(testing_input)),
-      c(
-        predict(model_libK,testing_input)$mean+2*predict(model_libK,testing_input)$stdev,
-        rev(predict(model_libK,testing_input)$mean-2*predict(model_libK,testing_input)$stdev)),
-      col = rgb(1,0,0,0.1), border = FALSE)
-
-precision <- 1e-3
-test_that(desc=paste0("pred mean is the same that RobustGaSP one"),
-          expect_equal(predict(model_libK,0.7)$mean[1],predict(model,matrix(0.7))$mean,tol = precision))
-test_that(desc=paste0("pred sd is the same that RobustGaSP one"),
-          expect_equal(predict(model_libK,0.7)$stdev[1],predict(model,matrix(0.7))$sd,tol = precision))
-
-
-
-
-
-context("RobustGaSP / Fit: 2D (Branin)")
-
-f = function(X) apply(X,1,DiceKriging::branin)
-n <- 15
-set.seed(1234)
-X <- cbind(runif(n),runif(n))
-y = f(X)
-model = NULL
-r = NULL
-model = rgasp(design=X,response=y)
-library(rlibkriging)
-r <- Kriging(y, X, "matern5_2", objective="LMP", optim="BFGS10")
-
-lmp_rgasp = function(X) {if (!is.matrix(X)) X = matrix(X,ncol=2);
-                  # print(dim(X));
-                  apply(X,1,
-                    function(x) {
-                      #y=-logMargPostFun(r,matrix(unlist(x),ncol=2))$logMargPost
-                      y=RobustGaSP:::neg_log_marginal_post_approx_ref(param=exp(x),nugget=0, nugget.est=model@nugget.est, 
-                                        R0=model@R0,X=model@X, zero_mean=model@zero_mean,output=model@output, 
-                                        CL=model@CL, 
-                                        a=0.2,
-                                        b=1/(length(model@output))^{1/dim(as.matrix(model@input))[2]}*(0.2+dim(as.matrix(model@output))[2]),
-                                        kernel_type=rep(as.integer(3),ncol(X)),alpha=model@alpha
-                                        )
-                      y})}
-
-lmp_lk = function(X,deriv=FALSE) {if (!is.matrix(X)) X = matrix(X,ncol=2);
-                  # print(dim(X));
-                  apply(X,1,
-                    function(x) {
-                      y=-logMargPostFun(r,matrix(unlist(exp(-exp(x))),ncol=2),deriv)$logMargPost
-                      y})}
-
-
-#DiceView::contourview(ll,xlim=c(0.01,2),ylim=c(0.01,2))
-x=seq(-1,1,,51)
-contour(x,x,matrix(lmp_rgasp(as.matrix(expand.grid(x,x))),nrow=length(x)),levels=seq(50,200,,51))
-points(log(model@beta_hat[1]),log(model@beta_hat[2]))
-
-contour(x,x,matrix(lmp_lk(as.matrix(expand.grid(x,x))),nrow=length(x)),levels = seq(50,200,,51),col='red')
-points(as.list(r)$theta[1],as.list(r)$theta[2],col='red')
-
-test_that(desc="Fit: 2D (Branin) / fit of theta 2D is _quite_ the same that DiceKriging one",
-          expect_equal(ll(array(as.list(r)$theta)), ll(k@covariance@range.val), tol=1e-1))
-
diff --git a/bindings/R/rlibkriging/tests/testthat/test-KrigingCholCrash.R b/bindings/R/rlibkriging/tests/testthat/test-KrigingCholCrash.R
index 8e1d70a8..d906e65d 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-KrigingCholCrash.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-KrigingCholCrash.R
@@ -1,8 +1,8 @@
 library(testthat)
-library(rlibkriging)
-packageDescription("rlibkriging")
-rlibkriging:::set_num_nugget(1E-10)
-default_nugget = rlibkriging:::get_num_nugget()
+#library(rlibkriging, lib.loc="bindings/R/Rlibs")
+#packageDescription("rlibkriging")
+rlibkriging:::linalg_set_num_nugget(1E-10)
+default_nugget = rlibkriging:::linalg_get_num_nugget()
 
 #############################################################
 
@@ -31,8 +31,8 @@ try( cholR <- chol(R) ) # Should not fail
 
 test_that(desc="Kriging fit withOUT num nugget would also pass", expect_true(!is.null(cholR)))
 
-rlibkriging:::set_num_nugget(0.0)
-rlibkriging:::get_num_nugget()
+rlibkriging:::linalg_set_num_nugget(0.0)
+rlibkriging:::linalg_get_num_nugget()
 
 r_nonugget = NULL
 try( r_nonugget <- Kriging(y, X, "gauss","constant",FALSE,"BFGS","LL") )
@@ -46,7 +46,7 @@ try( cholR_nonugget <- chol(R_nonugget) ) # Should not fail
 
 test_that(desc="Kriging fit withOUT num nugget would also pass", expect_true(!is.null(cholR_nonugget)))
 
-rlibkriging:::set_num_nugget(default_nugget)
+rlibkriging:::linalg_set_num_nugget(default_nugget)
 
 #############################################################
 
@@ -106,8 +106,8 @@ try( cholR <- chol(R) ) # Should not fail
 
 test_that(desc="Kriging fit withOUT num nugget would also pass", expect_true(!is.null(cholR)))
 
-rlibkriging:::set_num_nugget(0.0)
-rlibkriging:::get_num_nugget()
+rlibkriging:::linalg_set_num_nugget(0.0)
+rlibkriging:::linalg_get_num_nugget()
 
 r_nonugget = NULL
 # This will crash "chol(): decomposition failed before adding numerical nugget to R mat
@@ -126,4 +126,4 @@ test_that(desc="Kriging fit withOUT num nugget would also pass", expect_true(!is
 test_that(desc="Kriging fit mith and without num nugget are identical", 
 expect_true(all(capture.output(print(r_nonugget)) == capture.output(print(r)))))
 
-rlibkriging:::set_num_nugget(default_nugget)
+rlibkriging:::linalg_set_num_nugget(default_nugget)
diff --git a/bindings/R/rlibkriging/tests/testthat/test-KrigingFit.R b/bindings/R/rlibkriging/tests/testthat/test-KrigingFit.R
index b55e8a53..a1420ffa 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-KrigingFit.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-KrigingFit.R
@@ -1,11 +1,14 @@
 library(testthat)
-library(rlibkriging)
+#library(rlibkriging, lib.loc="bindings/R/Rlibs")
+#rlibkriging:::optim_log(2)
+#rlibkriging:::optim_use_reparametrize(FALSE)
+#rlibkriging:::optim_set_theta_lower_factor(0.02)
 
 context("Fit: 1D")
 
 f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
 n <- 5
-set.seed(1234)
+set.seed(123)
 X <- as.matrix(runif(n))
 y = f(X)
 k = NULL
@@ -14,15 +17,15 @@ k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F
 r <- Kriging(y, X, "gauss")
 
 ll = Vectorize(function(x) logLikelihoodFun(r,x)$logLikelihood)
-plot(ll,xlim=c(0.000001,1))
-  for (x in seq(0.000001,1,,11)){
+plot(ll,xlim=c(0.000001,10))
+  for (x in seq(0.000001,10,,11)){
     envx = new.env()
     ll2x = logLikelihoodFun(r,x)$logLikelihood
     gll2x = logLikelihoodFun(r,x,grad = T)$logLikelihoodGrad
     arrows(x,ll2x,x+.1,ll2x+.1*gll2x,col='red')
   }
 
-theta_ref = optimize(ll,interval=c(0.001,1),maximum=T)$maximum
+theta_ref = optimize(ll,interval=c(0.001,10),maximum=T)$maximum
 abline(v=theta_ref,col='black')
 abline(v=as.list(r)$theta,col='red')
 abline(v=k@covariance@range.val,col='blue')
@@ -103,7 +106,7 @@ k <- tryCatch( # needed to catch warning due to %dopar% usage when using multist
 r <- Kriging(y, X, "gauss", parameters=list(theta=parinit))
 l = as.list(r)
 
-save(list=ls(),file="fit-2d-multistart.Rdata")
+# save(list=ls(),file="fit-2d-multistart.Rdata")
 
 ll = function(X) {if (!is.matrix(X)) X = matrix(X,ncol=2);
 # print(dim(X));
@@ -158,9 +161,15 @@ contour(x,x,matrix(mll_fun(expand.grid(x,x)),nrow=length(x)),nlevels = 30)
 # use same startup point for convergence
 r <- Kriging(y, X, "gauss","constant",FALSE,"BFGS","LL",
              parameters=list(theta=matrix(k@parinit,ncol=2)))
+#mll2_fun <- function(x) -apply(x,1,
+#                              function(theta)
+#                                r$logLikelihoodFun(theta)$logLikelihood
+#)
+#contour(x,x,matrix(mll2_fun(expand.grid(x,x)),nrow=length(x)),nlevels = 30)
+
 l = as.list(r)
 
-save(list=ls(),file="fit-2d.Rdata")
+# save(list=ls(),file="fit-2d.Rdata")
 
 points(as.list(r)$theta[1],as.list(r)$theta[2],col='red')
 points(k@covariance@range.val[1],k@covariance@range.val[2],col='blue')
@@ -190,7 +199,7 @@ k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F
 r <- Kriging(y, X, "gauss",parameters=list(theta=matrix(c(0.25,10),ncol=2)))
 l = as.list(r)
 
-save(list=ls(),file="fit-2d-not01.Rdata")
+# save(list=ls(),file="fit-2d-not01.Rdata")
 
 ll_r = function(X) {if (!is.matrix(X)) X = matrix(X,ncol=2);
 # print(dim(X));
diff --git a/bindings/R/rlibkriging/tests/testthat/test-KrigingPredict.R b/bindings/R/rlibkriging/tests/testthat/test-KrigingPredict.R
index 5c005c77..9f4462e4 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-KrigingPredict.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-KrigingPredict.R
@@ -1,51 +1,210 @@
 library(testthat)
+library(rlibkriging, lib.loc="bindings/R/Rlibs")
 
-f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
-#plot(f)
-n <- 5
-set.seed(123)
-X <- as.matrix(runif(n))
-y = f(X)
-#points(X,y)
-k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F))
-library(rlibkriging)
-r <- Kriging(y,X,"gauss","constant",FALSE,"none","LL",
-             parameters=list(sigma2=k@covariance@sd2,has_sigma2=TRUE,
-             theta=matrix(k@covariance@range.val),has_theta=TRUE))
-# m = as.list(r)
-
-ntest <- 100
-Xtest <- as.matrix(runif(ntest))
-ptest <- DiceKriging::predict(k,Xtest,type="UK",cov.compute = TRUE,checkNames=F)
-Yktest <- ptest$mean
-sktest <- ptest$sd
-cktest <- c(ptest$cov)
-Ytest <- predict(r,Xtest,TRUE,TRUE)
-
-plot(f)
-points(X,y)
-points(Xtest,Yktest,col='blue')
-points(Xtest,Ytest$mean,col='red')
-
-precision <- 1e-5
-test_that(desc=paste0("pred mean is the same that DiceKriging one:\n ",paste0(collapse=",",Yktest),"\n ",paste0(collapse=",",Ytest$mean)),
-          expect_equal(array(Yktest),array(Ytest$mean),tol = precision))
-
-test_that(desc="pred sd is the same that DiceKriging one", 
-          expect_equal(array(sktest),array(Ytest$stdev) ,tol = precision))
-
-test_that(desc="pred cov is the same that DiceKriging one", 
-          expect_equal(cktest,c(Ytest$cov) ,tol = precision))
-
-#plot(f)
-#points(X,y)
-#x=seq(0,1,,101)
-#lines(x,predict(r,x)$mean,lty=2)
-#polygon(
-#    c(x,rev(x)),
-#    c(predict(r,x)$mean-2*predict(r,x)$stdev,
-#      predict(r,rev(x))$mean+2*predict(r,rev(x))$stdev),
-#      col=rgb(0,0,0,.1),border=NA)
-#
-#for (i in 1:10)
-#    lines(x,simulate(r,x=x,seed=i),col='grey')
+for (kernel in c("gauss","exp","matern3_2","matern5_2")) {
+  context(paste0("Check predict 1D for kernel ",kernel))
+
+  f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
+  #plot(f)
+  n <- 5
+  set.seed(123)
+  X <- as.matrix(runif(n))
+  y = f(X)
+  #points(X,y)
+  k = DiceKriging::km(design=X,response=y,covtype = kernel,control = list(trace=F))
+  library(rlibkriging)
+  r <- Kriging(y,X,kernel,"constant",FALSE,"none","LL",
+               parameters=list(sigma2=k@covariance@sd2,has_sigma2=TRUE,
+               theta=matrix(k@covariance@range.val),has_theta=TRUE))
+  # m = as.list(r)
+  
+  ntest <- 100
+  Xtest <- as.matrix(runif(ntest))
+  ptest <- DiceKriging::predict(k,Xtest,type="UK",cov.compute = TRUE,checkNames=F)
+  Yktest <- ptest$mean
+  sktest <- ptest$sd
+  cktest <- c(ptest$cov)
+  Ytest <- predict(r,Xtest,TRUE,TRUE)
+  
+  plot(f)
+  points(X,y)
+  points(Xtest,Yktest,col='blue')
+  points(Xtest,Ytest$mean,col='red')
+  
+  precision <- 1e-5
+  test_that(desc=paste0("pred mean is the same that DiceKriging one:\n ",paste0(collapse=",",Yktest),"\n ",paste0(collapse=",",Ytest$mean)),
+            expect_equal(array(Yktest),array(Ytest$mean),tol = precision))
+  
+  test_that(desc="pred sd is the same that DiceKriging one", 
+            expect_equal(array(sktest),array(Ytest$stdev) ,tol = precision))
+  
+  test_that(desc="pred cov is the same that DiceKriging one", 
+            expect_equal(cktest,c(Ytest$cov) ,tol = precision))
+  
+  plot(f)
+  points(X,y)
+  x=seq(0,1,,101)
+  lines(x,predict(r,x)$mean,lty=2)
+  polygon(
+      c(x,rev(x)),
+      c(predict(r,x)$mean-predict(r,x)$stdev,
+        predict(r,rev(x))$mean+predict(r,rev(x))$stdev),
+        col=rgb(0,0,0,.1),border=NA)
+  #
+  #for (i in 1:10)
+  #    lines(x,simulate(r,x=x,seed=i),col='grey')
+  
+  .x = seq(0.1,0.9,,11)
+  p_allx = predict(r,.x,stdev=TRUE, cov=FALSE, deriv=TRUE)
+  for (i in 1:length(.x)) {
+    # ref from DiceOptim::EI.grad
+    newdata = .x[i]
+    model = k
+    T <- model@T
+    X <- model@X
+    z <- model@z
+    u <- model@M
+    covStruct <- model@covariance
+    predx <- predict(object=model, newdata=newdata, type="UK", checkNames = FALSE,se.compute=TRUE,cov.compute=FALSE)
+    kriging.mean <- predx$mean
+    kriging.sd <- predx$sd
+    v <- predx$Tinv.c
+    c <- predx$c
+    dc <- DiceKriging::covVector.dx(x=newdata, X=X, object=covStruct, c=c)  
+    #d = model@d
+    #h=sqrt(.Machine$double.eps)
+    #A <- matrix(newdata, nrow=d, ncol=d, byrow=TRUE)
+    #Apos <- A+h*diag(d)
+    #Aneg <- A-h*diag(d)
+    #newpoints <- data.frame(rbind(Apos, Aneg))
+    #f.newdata <- model.matrix(model@trend.formula, data = newpoints)
+    #f.deltax <- (f.newdata[1:d,]-f.newdata[(d+1):(2*d),])/(2*h)
+    f.deltax <- DiceKriging::trend.deltax(x=newdata, model=model)
+    W <- backsolve(t(T), dc, upper.tri=FALSE)
+    kriging.mean.grad <- t(W)%*%z + t(model@trend.coef%*%f.deltax)
+    tuuinv <- solve(t(u)%*%u)
+    F.newdata <- model.matrix(model@trend.formula, data=as.data.frame(newdata))
+    kriging.sd2.grad <-  t( -2*t(v)%*%W +
+                                  2*(F.newdata - t(v)%*%u )%*% tuuinv %*%
+                                  (f.deltax - t(t(W)%*%u) ))
+    kriging.sd.grad <- kriging.sd2.grad / (2*kriging.sd)
+                       
+    p = predict(r,.x[i],stdev=TRUE, cov=FALSE, deriv=TRUE)
+  
+    test_that(desc=paste0("vect pred mean deriv is ok:\n ",paste0(collapse=",",p_allx$mean_deriv[i]),"\n ",paste0(collapse=",",p$mean_deriv)),
+             expect_equal(array(p_allx$mean_deriv[i]),array(p$mean_deriv),tol = precision))
+    test_that(desc=paste0("vect pred sd deriv is ok:\n ",paste0(collapse=",",p_allx$stdev_deriv[i]),"\n ",paste0(collapse=",",p$stdev_deriv)),
+             expect_equal(array(p_allx$stdev_deriv[i]),array(p$stdev_deriv),tol = precision))
+
+  arrows(.x[i],p$mean, .x[i]+0.1, p$mean+0.1*p$mean_deriv)
+  arrows(.x[i],p$mean+p$stdev, .x[i]+0.1, p$mean+p$stdev+0.1*p$mean_deriv+0.1*p$stdev_deriv, col='darkgrey')
+  
+    test_that(desc=paste0("pred mean deriv is the same that DiceKriging one:\n ",paste0(collapse=",",kriging.mean.grad),"\n ",paste0(collapse=",",p$mean_deriv)),
+              expect_equal(array(kriging.mean.grad),array(p$mean_deriv),tol = precision))
+    
+    test_that(desc=paste0("pred sd deriv is the same that DiceKriging one:\n ",paste0(collapse=",",kriging.sd.grad),"\n ",paste0(collapse=",",p$stdev_deriv)),
+              expect_equal(array(kriging.sd.grad),array(p$stdev_deriv),tol = precision))
+              
+  }
+}
+
+
+#### dim > 1
+
+for (kernel in c("gauss","exp","matern3_2","matern5_2")) {
+  context(paste0("Check predict 1D for kernel ",kernel))
+
+  f <- function(X) apply(X, 1, function(x)
+    prod(sin(2*pi*( x * (seq(0,1,l=1+length(x))[-1])^2 )))
+  )
+
+  .x=seq(0,1,,31); contour(.x,.x,matrix(f(expand.grid(.x,.x)),nrow=length(.x)))
+  n <- 20
+  set.seed(123)
+  X <- matrix(runif(2*n),ncol=2)
+  y = f(X)
+  points(X)
+  k = DiceKriging::km(design=X,response=y,covtype = kernel,control = list(trace=F))
+  library(rlibkriging)
+  r <- Kriging(y,X,kernel,"constant",FALSE,"none","LL",
+               parameters=list(sigma2=k@covariance@sd2,has_sigma2=TRUE,
+               theta=matrix(k@covariance@range.val),has_theta=TRUE))
+  # m = as.list(r)
+
+  f_predict = function(X)
+    predict(r,data.matrix(X))
+    #DiceKriging::predict(k,X,type="UK",cov.compute = TRUE,checkNames=F)
+  contour(.x,.x,matrix(f(expand.grid(.x,.x)),nrow=length(.x)))
+  contour(.x,.x,matrix(f_predict(expand.grid(.x,.x))$mean,nrow=length(.x)),add=TRUE, col='blue')
+  points(X,col='blue')
+
+  ntest <- 100
+  Xtest <- matrix(runif(2*ntest),ncol=2)
+  ptest <- DiceKriging::predict(k,Xtest,type="UK",cov.compute = TRUE,checkNames=F)
+  Yktest <- ptest$mean
+  sktest <- ptest$sd
+  cktest <- c(ptest$cov)
+  Ytest <- predict(r,Xtest,TRUE,TRUE)
+
+  precision <- 1e-5
+  test_that(desc=paste0("pred mean is the same that DiceKriging one:\n ",paste0(collapse=",",Yktest),"\n ",paste0(collapse=",",Ytest$mean)),
+            expect_equal(array(Yktest),array(Ytest$mean),tol = precision))
+  
+  test_that(desc="pred sd is the same that DiceKriging one", 
+            expect_equal(array(sktest),array(Ytest$stdev) ,tol = precision))
+  
+  test_that(desc="pred cov is the same that DiceKriging one", 
+            expect_equal(cktest,c(Ytest$cov) ,tol = precision))
+  
+  .x = seq(0.1,0.9,,5)
+  p_allx = predict(r,expand.grid(.x,.x), stdev=TRUE, cov=FALSE, deriv=TRUE)
+  for (i in 1:length(.x)) { for (j in 1:length(.x)) {
+    # ref from DiceOptim::EI.grad
+    newdata = matrix(c(.x[i],.x[j]),ncol=2) # just check diagonal points
+    model = k
+    T <- model@T
+    X <- model@X
+    z <- model@z
+    u <- model@M
+    covStruct <- model@covariance
+    predx <- predict(object=model, newdata=newdata, type="UK", checkNames = FALSE,se.compute=TRUE,cov.compute=FALSE)
+    kriging.mean <- predx$mean
+    kriging.sd <- predx$sd
+    v <- predx$Tinv.c
+    c <- predx$c
+    dc <- DiceKriging::covVector.dx(x=newdata, X=X, object=covStruct, c=c)  
+    #d = model@d
+    #h=sqrt(.Machine$double.eps)
+    #A <- matrix(newdata, nrow=d, ncol=d, byrow=TRUE)
+    #Apos <- A+h*diag(d)
+    #Aneg <- A-h*diag(d)
+    #newpoints <- data.frame(rbind(Apos, Aneg))
+    #f.newdata <- model.matrix(model@trend.formula, data = newpoints)
+    #f.deltax <- (f.newdata[1:d,]-f.newdata[(d+1):(2*d),])/(2*h)
+    f.deltax <- DiceKriging::trend.deltax(x=newdata, model=model)
+    W <- backsolve(t(T), dc, upper.tri=FALSE)
+    kriging.mean.grad <- t(W)%*%z + t(model@trend.coef%*%f.deltax)
+    tuuinv <- solve(t(u)%*%u)
+    F.newdata <- model.matrix(model@trend.formula, data=as.data.frame(newdata))
+    kriging.sd2.grad <-  t( -2*t(v)%*%W +
+                                  2*(F.newdata - t(v)%*%u )%*% tuuinv %*%
+                                  (f.deltax - t(t(W)%*%u) ))
+    kriging.sd.grad <- kriging.sd2.grad / (2*kriging.sd)
+                       
+    p = predict(r,c(.x[i],.x[j]),TRUE, cov=FALSE, deriv=TRUE)
+  
+    test_that(desc=paste0("vect pred mean deriv is ok:\n ",paste0(collapse=",",p_allx$mean_deriv[(j-1)*length(.x)+i,]),"\n ",paste0(collapse=",",p$mean_deriv)),
+             expect_equal(array(p_allx$mean_deriv[(j-1)*length(.x)+i,]),array(p$mean_deriv),tol = precision))
+    test_that(desc=paste0("vect pred sd deriv is ok:\n ",paste0(collapse=",",p_allx$stdev_deriv[(j-1)*length(.x)+i,]),"\n ",paste0(collapse=",",p$stdev_deriv)),
+             expect_equal(array(p_allx$stdev_deriv[(j-1)*length(.x)+i,]),array(p$stdev_deriv),tol = precision))
+
+  arrows(.x[i],.x[j],.x[i]+0.1*p$mean_deriv[1],.x[j]+0.1*p$mean_deriv[2], length = 0.1, col='blue')
+  
+    test_that(desc=paste0("pred mean deriv is the same that DiceKriging one:\n ",paste0(collapse=",",kriging.mean.grad),"\n ",paste0(collapse=",",p$mean_deriv)),
+              expect_equal(array(kriging.mean.grad),array(p$mean_deriv),tol = precision))
+    
+    test_that(desc=paste0("pred sd deriv is the same that DiceKriging one:\n ",paste0(collapse=",",kriging.sd.grad),"\n ",paste0(collapse=",",p$stdev_deriv)),
+              expect_equal(array(kriging.sd.grad),array(p$stdev_deriv),tol = precision))
+              
+  }}
+}
\ No newline at end of file
diff --git a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingFit.R b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingFit.R
index 6843360d..3437446a 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingFit.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingFit.R
@@ -1,11 +1,12 @@
 library(testthat)
-library(rlibkriging)
+#library(rlibkriging, lib.loc="bindings/R/Rlibs")
+#rlibkriging:::optim_log(3)
 
 context("Fit: 1D")
 
 f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
 n <- 5
-set.seed(1234)
+set.seed(123)
 X <- as.matrix(runif(n))
 y = f(X)
 k = NULL
@@ -14,7 +15,7 @@ k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F
 r <- NuggetKriging(y, X, "gauss", optim = "BFGS10")
 l = as.list(r)
 
-save(list=ls(),file="fit-nugget-1d.Rdata")
+# save(list=ls(),file="fit-nugget-1d.Rdata")
 
 
 alpha_k = k@covariance@sd2/(k@covariance@sd2+k@covariance@nugget)
@@ -50,10 +51,11 @@ y = f(X)
 k = NULL
 r = NULL
 k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F),nugget.estim=T,optim.method='BFGS',multistart = 20)
-r <- NuggetKriging(y, X, "gauss", optim = "BFGS10")
+r <- NuggetKriging(y, X, "gauss", optim = "BFGS20")
+#plot(Vectorize(function(a) r$logLikelihoodFun(c(r$theta(),a))$logLikelihood))
 l = as.list(r)
 
-save(list=ls(),file="fit-nugget-2d.Rdata")
+# save(list=ls(),file="fit-nugget-2d.Rdata")
 
 alpha_k = k@covariance@sd2/(k@covariance@sd2+k@covariance@nugget)
 alpha_r = as.list(r)$sigma2/(as.list(r)$sigma2+as.list(r)$nugget)
@@ -114,7 +116,7 @@ k <- tryCatch( # needed to catch warning due to %dopar% usage when using multist
 r <- NuggetKriging(y, X, "gauss", parameters=list(theta=parinit))
 l = as.list(r)
 
-save(list=ls(),file="fit-nugget-multistart.Rdata")
+# save(list=ls(),file="fit-nugget-multistart.Rdata")
 
 alpha_k = k@covariance@sd2/(k@covariance@sd2+k@covariance@nugget)
 alpha_r = as.list(r)$sigma2/(as.list(r)$sigma2+as.list(r)$nugget)
@@ -140,7 +142,7 @@ points(as.list(r)$theta[1],as.list(r)$theta[2],col='red')
 points(k@covariance@range.val[1],k@covariance@range.val[2],col='blue')
 
 test_that(desc="Nugget / Fit: 2D (Branin) multistart / fit of theta 2D is _quite_ the same that DiceKriging one",
-          expect_equal(ll(array(as.list(r)$theta)), ll(k@covariance@range.val), tol= 1e-3))
+          expect_equal(ll(array(as.list(r)$theta)), ll(k@covariance@range.val), tol= 1e-1))
 
 
 ################################################################################
@@ -165,7 +167,7 @@ k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F
 r <- NuggetKriging(y, X, "gauss",, optim = "BFGS10")#, parameters=list(theta=matrix(c(0.5,5),ncol=2)))
 l = as.list(r)
 
-save(list=ls(),file="fit-nugget-2d-not01.Rdata")
+# save(list=ls(),file="fit-nugget-2d-not01.Rdata")
 
 alpha_k = k@covariance@sd2/(k@covariance@sd2+k@covariance@nugget)
 alpha_r = as.list(r)$sigma2/(as.list(r)$sigma2+as.list(r)$nugget)
diff --git a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingLogLik.R b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingLogLik.R
index e3f20a9c..b8be77e2 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingLogLik.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingLogLik.R
@@ -1,6 +1,6 @@
-#install.packages("bindings/R/rlibkriging_0.3-2_R_x86_64-pc-linux-gnu.tar.gz")
-
 library(testthat)
+#library(rlibkriging, lib.loc="bindings/R/Rlibs")
+#rlibkriging:::optim_log(3)
 
 kernel="gauss"
 for (kernel in c("exp","matern3_2","matern5_2","gauss")) {
diff --git a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingMethods.R b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingMethods.R
index f287cd53..e190e4b1 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingMethods.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingMethods.R
@@ -1,7 +1,7 @@
 library(testthat)
 
 # install.packages("../rlib0.1-10_R_x86_64-pc-linux-gnu.tar.gz",repos=NULL)
-library(rlibkriging)
+#library(rlibkriging)
 
 # f <- function(X) apply(X, 1, function(x) prod(sin((x-.5)^2)))
 f <- function(X) apply(X, 1, function(x)
@@ -10,7 +10,7 @@ f <- function(X) apply(X, 1, function(x)
 n <- 20
 set.seed(123)
 X <- cbind(runif(n),runif(n))
-y <- f(X)
+y <- f(X) + rnorm(n,0,0.01)
 d = ncol(X)
 
 x=seq(0,1,,51)
@@ -34,12 +34,12 @@ l= as.list(r)
 # }
 # }
 
-context("print")
+context("nugget / print")
 
 p = capture.output(print(r))
 
 
-context("logLikelihood")
+context("nugget / logLikelihood")
 
 ll = function(Theta){apply(Theta,1,function(theta) logLikelihoodFun(r,c(theta,l$sigma2/(l$nugget+l$sigma2)))$logLikelihood)}
 t=seq(0.01,2,,51)
@@ -47,56 +47,56 @@ contour(t,t,matrix(ll(as.matrix(expand.grid(t,t))),nrow=length(t)),nlevels = 30)
 points(as.list(r)$theta[1],as.list(r)$theta[2])
 # logLikelihoodFun(r,t(as.list(r)$theta))
 
-test_that("logLikelihoodFun returned",
+test_that("nugget / logLikelihoodFun returned",
           expect_equal(names(logLikelihoodFun(r,runif(d+1))),c("logLikelihood")))
-test_that("logLikelihoodFun logLikelihoodGrad returned",
+test_that("nugget / logLikelihoodFun logLikelihoodGrad returned",
           expect_equal(names(logLikelihoodFun(r,runif(d+1),grad=T)),c("logLikelihood","logLikelihoodGrad")))
 
-test_that("logLikelihoodFun dim",
+test_that("nugget / logLikelihoodFun dim",
           expect_equal(dim(logLikelihoodFun(r,rbind(runif(d+1),runif(d+1)))$logLikelihood),c(2,1)))
-test_that("logLikelihoodGrad dim",
+test_that("nugget / logLikelihoodGrad dim",
           expect_equal(dim(logLikelihoodFun(r,rbind(runif(d+1),runif(d+1)),grad=T)$logLikelihoodGrad),c(2,d+1)))
 
 
-context("predict")
+context("nugget / predict")
 
-test_that("predict mean stdev returned",
+test_that("nugget / predict mean stdev returned",
           expect_equal(names(predict(r,runif(d))),c("mean","stdev")))
-test_that("predict mean returned",
+test_that("nugget / predict mean returned",
           expect_equal(names(predict(r,runif(d),stdev=F)),c("mean")))
-test_that("predict mean stdev cov returned",
+test_that("nugget / predict mean stdev cov returned",
           expect_equal(names(predict(r,runif(d),cov=T)),c("mean","stdev","cov")))
 
-test_that("predict mean dim",
+test_that("nugget / predict mean dim",
           expect_equal(dim(predict(r,rbind(runif(d),runif(d)))$mean),c(2,1)))
-test_that("predict stdev dim",
+test_that("nugget / predict stdev dim",
           expect_equal(dim(predict(r,rbind(runif(d),runif(d)))$stdev),c(2,1)))
-test_that("predict cov dim",
+test_that("nugget / predict cov dim",
           expect_equal(dim(predict(r,rbind(runif(d),runif(d)),cov=T)$cov),c(2,2)))
 
 
-context("simulate")
+context("nugget / simulate")
 
-test_that("simulate dim",
+test_that("nugget / simulate dim",
           expect_equal(dim(simulate(r,x=rbind(runif(d),runif(d)))),c(2,1)))
-test_that("simulate nsim dim",
+test_that("nugget / simulate nsim dim",
           expect_equal(dim(simulate(r,x=rbind(runif(d),runif(d)),nsim=10)),c(2,10)))
 
-test_that("simulate mean X",
+test_that("nugget / simulate mean X",
           expect_equal(mean(simulate(r,nsim = 100, x=X[1,]+0.00005)),y[1],tolerance = 0.01))
 set.seed(12345)
 x = runif(d)
-test_that("simulate mean",
+test_that("nugget / simulate mean",
           expect_equal(mean(simulate(r,nsim = 100, x=x)),predict(r,x)$mean[1],tolerance = 0.01))
-test_that("simulate sd",
+test_that("nugget / simulate sd",
           expect_equal(sd(simulate(r,nsim = 100, x=x)),predict(r,x)$stdev[1],tolerance = 0.01))
 
 
-context("update")
+context("nugget / update")
 
 set.seed(1234)
 X2 = matrix(runif(d*10),ncol=d)
-y2 = f(X2)
+y2 = f(X2) + rnorm(nrow(X2),0,0.01)
 x=seq(0,1,,51)
 contour(x,x,matrix(f(as.matrix(expand.grid(x,x))),nrow=length(x)),nlevels = 30)
 points(X)
@@ -124,8 +124,14 @@ t=seq(0.01,2,,51)
 contour(t,t,matrix(ll(as.matrix(expand.grid(t,t))),nrow=length(t)),nlevels = 30)
 points(as.list(r)$theta[1],as.list(r)$theta[2],pch=20)
 
+#cat("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","\n")
+#cat(paste0(collapse="\n",p),"\n")
+#cat(r$logLikelihood(),"\n")
+#
+#rlibkriging:::optim_log(1)
+
 set.seed(1234)
-r2 <- NuggetKriging(c(y,y2), rbind(X,X2),"gauss", parameters = list(theta=matrix(as.list(r)$theta,ncol=2,nrow=40,byrow = T)+matrix(0.01*runif(80),ncol=2)))
+r2 <- NuggetKriging(c(y,y2), rbind(X,X2),"gauss", parameters = list(theta=matrix(as.list(r)$theta,ncol=2),nugget=0,sigma2=1))
 ll2 = function(Theta){apply(Theta,1,function(theta) logLikelihoodFun(r2,c(theta,as.list(r2)$sigma2/(as.list(r2)$sigma2+as.list(r2)$nugget)))$logLikelihood)}
 contour(t,t,matrix(ll2(as.matrix(expand.grid(t,t))),nrow=length(t)),nlevels = 30,add=T,col='red')
 points(as.list(r2)$theta[1],as.list(r2)$theta[2],col='red',pch=20)
@@ -139,7 +145,15 @@ points(as.list(r)$theta[1],as.list(r)$theta[2],col='blue',pch=20)
 
 pu = capture.output(print(r))
 
-test_that("update",
+test_that("nugget / update",
           expect_false(all(p == pu)))
-test_that("update almost converge",
+
+# cat(paste0(collapse="\n",p2),"\n")
+# cat(r2$logLikelihood(),"\n")
+# cat(paste0(collapse="\n",capture.output(print( r2$logLikelihoodFun(c(r2$theta(),r2$sigma2()/(r2$sigma2()+r2$nugget())),TRUE) ))),"\n")
+# cat(paste0(collapse="\n",pu),"\n")
+# cat(r$logLikelihood(),"\n")
+# cat(paste0(collapse="\n",capture.output(print( r$logLikelihoodFun(c(r$theta(),r$sigma2()/(r$sigma2()+r$nugget())),TRUE) ))),"\n")
+
+test_that("nugget / update almost converge",
           expect_equal(as.list(r2)$theta, as.list(r)$theta, tolerance = 2E-2))
diff --git a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingPredict.R b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingPredict.R
index 57b0c621..b8169661 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingPredict.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-NuggetKrigingPredict.R
@@ -1,39 +1,113 @@
 library(testthat)
+#library(rlibkriging, lib.loc="bindings/R/Rlibs")
 
-f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
-#plot(f)
-n <- 5
-set.seed(123)
-X <- as.matrix(runif(n))
-y = f(X)
-#points(X,y)
-k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F), nugget=0,nugget.estim = TRUE)
-library(rlibkriging)
-r <- NuggetKriging(y,X,"gauss","constant",FALSE,"none","LL",
-             parameters=list(sigma2=k@covariance@sd2,has_sigma2=TRUE, is_sigma2_estim=FALSE,
-             theta=matrix(k@covariance@range.val),has_theta=TRUE, is_theta_estim=FALSE,
-             nugget=k@covariance@nugget,has_nugget=TRUE, is_nugget_estim=FALSE))
-# m = as.list(r)
+for (kernel in c("gauss","exp","matern3_2","matern5_2")) {
+  context(paste0("Check predict 1D for kernel ",kernel))
 
-ntest <- 100
-Xtest <- as.matrix(runif(ntest))
-ptest <- DiceKriging::predict(k,Xtest,type="UK",cov.compute = TRUE,checkNames=F)
-Yktest <- ptest$mean
-sktest <- ptest$sd
-cktest <- c(ptest$cov)
-Ytest <- predict(r,Xtest,TRUE,TRUE)
+  f = function(x) 1-1/2*(sin(12*x)/(1+x)+2*cos(7*x)*x^5+0.7)
+  #plot(f)
+  n <- 5
+  set.seed(123)
+  X <- as.matrix(runif(n))
+  y = f(X)
+  #points(X,y)
+  k = DiceKriging::km(design=X,response=y,covtype = "gauss",control = list(trace=F), nugget=0,nugget.estim = TRUE)
+  library(rlibkriging)
+  r <- NuggetKriging(y,X,"gauss","constant",FALSE,"none","LL",
+               parameters=list(sigma2=k@covariance@sd2,has_sigma2=TRUE, is_sigma2_estim=FALSE,
+               theta=matrix(k@covariance@range.val),has_theta=TRUE, is_theta_estim=FALSE,
+               nugget=k@covariance@nugget,has_nugget=TRUE, is_nugget_estim=FALSE))
+  # m = as.list(r)
+  
+  ntest <- 100
+  Xtest <- as.matrix(runif(ntest))
+  ptest <- DiceKriging::predict(k,Xtest,type="UK",cov.compute = TRUE,checkNames=F)
+  Yktest <- ptest$mean
+  sktest <- ptest$sd
+  cktest <- c(ptest$cov)
+  Ytest <- predict(r,Xtest,TRUE,TRUE)
+  
+  plot(f)
+  points(X,y)
+  points(Xtest,Yktest,col='blue')
+  points(Xtest,Ytest$mean,col='red')
+  
+  precision <- 1e-5
+  test_that(desc=paste0("pred mean is the same that DiceKriging one:\n ",paste0(collapse=",",Yktest),"\n ",paste0(collapse=",",Ytest$mean)),
+            expect_equal(array(Yktest),array(Ytest$mean),tol = precision))
+  
+  test_that(desc="pred sd is the same that DiceKriging one", 
+            expect_equal(array(sktest),array(Ytest$stdev) ,tol = precision))
+              
+  test_that(desc="pred cov is the same that DiceKriging one", 
+            expect_equal(cktest,c(Ytest$cov) ,tol = precision))
+  
+  plot(f)
+  points(X,y)
+  x=seq(0,1,,101)
+  lines(x,predict(r,x)$mean,lty=2)
+  polygon(
+      c(x,rev(x)),
+      c(predict(r,x)$mean-predict(r,x)$stdev,
+        predict(r,rev(x))$mean+predict(r,rev(x))$stdev),
+        col=rgb(0,0,0,.1),border=NA)
+  #
+  #for (i in 1:10)
+  #    lines(x,simulate(r,x=x,seed=i),col='grey')
+  
+  .x = seq(0.1,0.9,,11)
+  p_allx = predict(r,.x,TRUE, cov=FALSE, deriv=TRUE)
+  for (i in 1:length(.x)) {
+    # ref from DiceOptim::EI.grad
+    newdata = .x[i]
+    model = k
+    T <- model@T
+    X <- model@X
+    z <- model@z
+    u <- model@M
+    covStruct <- model@covariance
+    predx <- predict(object=model, newdata=newdata, type="UK", checkNames = FALSE,se.compute=TRUE,cov.compute=FALSE)
+    kriging.mean <- predx$mean
+    kriging.sd <- predx$sd
+    v <- predx$Tinv.c
+    c <- predx$c
+    dc <- DiceKriging::covVector.dx(x=newdata, X=X, object=covStruct, c=c)  
+    #d = model@d
+    #h=sqrt(.Machine$double.eps)
+    #A <- matrix(newdata, nrow=d, ncol=d, byrow=TRUE)
+    #Apos <- A+h*diag(d)
+    #Aneg <- A-h*diag(d)
+    #newpoints <- data.frame(rbind(Apos, Aneg))
+    #f.newdata <- model.matrix(model@trend.formula, data = newpoints)
+    #f.deltax <- (f.newdata[1:d,]-f.newdata[(d+1):(2*d),])/(2*h)
+    f.deltax <- DiceKriging::trend.deltax(x=newdata, model=model)
+    W <- backsolve(t(T), dc, upper.tri=FALSE)
+    kriging.mean.grad <- t(W)%*%z + t(model@trend.coef%*%f.deltax)
+    tuuinv <- solve(t(u)%*%u)
+    F.newdata <- model.matrix(model@trend.formula, data=as.data.frame(newdata))
+    kriging.sd2.grad <-  t( -2*t(v)%*%W +
+                                  2*(F.newdata - t(v)%*%u )%*% tuuinv %*%
+                                  (f.deltax - t(t(W)%*%u) ))
+    kriging.sd.grad <- kriging.sd2.grad / (2*kriging.sd)
+                       
+    p = predict(r,.x[i],TRUE, cov=FALSE, deriv=TRUE)
+  
+    test_that(desc=paste0("vect pred mean deriv is ok:\n ",paste0(collapse=",",p_allx$mean_deriv[i]),"\n ",paste0(collapse=",",p$mean_deriv)),
+             expect_equal(array(p_allx$mean_deriv[i]),array(p$mean_deriv),tol = precision))
+    test_that(desc=paste0("vect pred sd deriv is ok:\n ",paste0(collapse=",",p_allx$stdev_deriv[i]),"\n ",paste0(collapse=",",p$stdev_deriv)),
+             expect_equal(array(p_allx$stdev_deriv[i]),array(p$stdev_deriv),tol = precision))
 
-plot(f)
-points(X,y)
-points(Xtest,Yktest,col='blue')
-points(Xtest,Ytest$mean,col='red')
-
-precision <- 1e-5
-test_that(desc=paste0("pred mean is the same that DiceKriging one:\n ",paste0(collapse=",",Yktest),"\n ",paste0(collapse=",",Ytest$mean)),
-          expect_equal(array(Yktest),array(Ytest$mean),tol = precision))
-
-test_that(desc="pred sd is the same that DiceKriging one", 
-          expect_equal(array(sktest),array(Ytest$stdev) ,tol = precision))
+  arrows(.x[i],p$mean, .x[i]+0.1, p$mean+0.1*p$mean_deriv)
+  arrows(.x[i],p$mean+p$stdev, .x[i]+0.1, p$mean+p$stdev+0.1*p$mean_deriv+0.1*p$stdev_deriv, col='darkgrey')
+  
+    test_that(desc=paste0("pred mean deriv is the same that DiceKriging one:\n ",paste0(collapse=",",kriging.mean.grad),"\n ",paste0(collapse=",",p$mean_deriv)),
+              expect_equal(array(kriging.mean.grad),array(p$mean_deriv),tol = precision))
+    
+    test_that(desc=paste0("pred sd deriv is the same that DiceKriging one:\n ",paste0(collapse=",",kriging.sd.grad),"\n ",paste0(collapse=",",p$stdev_deriv)),
+              expect_equal(array(kriging.sd.grad),array(p$stdev_deriv),tol = precision))
+              
+  }
+}
 
 test_that(desc="pred cov is the same that DiceKriging one", 
           expect_equal(cktest,c(Ytest$cov) ,tol = precision))
diff --git a/bindings/R/rlibkriging/tests/testthat/test-asDiceKriging.R b/bindings/R/rlibkriging/tests/testthat/test-asDiceKriging.R
index 9afa4e89..ca734724 100644
--- a/bindings/R/rlibkriging/tests/testthat/test-asDiceKriging.R
+++ b/bindings/R/rlibkriging/tests/testthat/test-asDiceKriging.R
@@ -1,4 +1,5 @@
 library(testthat)
+#library(rlibkriging, lib.loc="bindings/R/Rlibs")
 
 ## install.packages("../rlibkriging_0.1-10_R_x86_64-pc-linux-gnu.tar.gz",repos=NULL)
 ## library(rlibkriging)
@@ -126,6 +127,10 @@ set.seed(123)
 X <- as.matrix(runif(5))
 y <- f(X)
 points(X, y, col = 'blue')
+
+#rlibkriging:::optim_log(2)
+#rlibkriging:::optim_use_variogram_bounds_heuristic(TRUE)
+
 r <- Kriging(y, X, kernel = "gauss")
 x <- seq(from = 0, to = 1, length.out = 101)
 s_x <- simulate(r, nsim = 3, x = x)