diff --git a/.github/workflows/build-CI-full.yml b/.github/workflows/build-CI-full.yml new file mode 100644 index 00000000..8aeaf94a --- /dev/null +++ b/.github/workflows/build-CI-full.yml @@ -0,0 +1,388 @@ +name: CI (full) + +on: + workflow_dispatch: + +env: + BUILD_DIR: _build + PIP_PACKAGES: >- + meson!=1.8.0 + cmake + ninja + PIP_EXTRAS: >- + pkgconfig + numpy + ase + matplotlib + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + # ---- Linux GCC + OpenBLAS — CMake debug -------------------------- + - { os: ubuntu-latest, build-type: debug, + toolchain: { compiler: gcc, version: '12', build: cmake } } + - { os: ubuntu-latest, build-type: debug, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # ---- Linux GCC + OpenBLAS — Meson debugoptimized ----------------- + - { os: ubuntu-latest, build-type: debugoptimized, + toolchain: { compiler: gcc, version: '12', build: meson } } + - { os: ubuntu-latest, build-type: debugoptimized, + toolchain: { compiler: gcc, version: '14', build: meson } } + + # ---- Linux Intel (ifx/icx) + MKL — CMake debug ------------------ + - { os: ubuntu-latest, build-type: debug, + toolchain: { compiler: intel, version: '2025.1', mkl_version: '2025.1', build: cmake } } + + # ---- Linux Intel (ifx/icx) + MKL — Meson debugoptimized --------- + - { os: ubuntu-latest, build-type: debugoptimized, + toolchain: { compiler: intel, version: '2025.1', mkl_version: '2025.1', build: meson } } + + # ---- macOS GCC + OpenBLAS — CMake debug (GNU only) --------------- + - { os: macos-latest, build-type: debug, + toolchain: { compiler: gcc, version: '12', build: cmake } } + - { os: macos-latest, build-type: debug, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + defaults: + run: + shell: bash + + steps: + # ---------------------------------------------------------------------- + # Setup + # ---------------------------------------------------------------------- + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + # ---------------------------------------------------------------------- + # Compiler setup + # ---------------------------------------------------------------------- + + - name: Install GCC using setup-fortran + if: ${{ contains(matrix.toolchain.compiler, 'gcc') }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install libopenblas (Linux GCC builds) + if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'gcc') }} + run: | + sudo apt-get update + sudo apt-get install -y libopenblas-dev + + - name: Install OpenBLAS (macOS) + if: ${{ contains(matrix.os, 'macos') }} + run: | + brew update + brew install openblas + echo "PKG_CONFIG_PATH=/usr/local/opt/openblas/lib/pkgconfig:/opt/homebrew/opt/openblas/lib/pkgconfig" >> $GITHUB_ENV + echo "LDFLAGS=-L/usr/local/opt/openblas/lib -L/opt/homebrew/opt/openblas/lib" >> $GITHUB_ENV + echo "CPPFLAGS=-I/usr/local/opt/openblas/include -I/opt/homebrew/opt/openblas/include" >> $GITHUB_ENV + + - name: Prepare for Intel cache restore + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + run: | + sudo mkdir -p /opt/intel + sudo chown $USER /opt/intel + + - name: Cache Intel installation + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + id: cache-install + uses: actions/cache@v4 + with: + path: /opt/intel/oneapi + key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.toolchain.mkl_version }}-${{ matrix.os }} + + - name: Install Intel Compiler + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install Intel MKL + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + run: | + sudo apt-get install -y \ + intel-oneapi-mkl-${{ matrix.toolchain.mkl_version }} \ + intel-oneapi-mkl-devel-${{ matrix.toolchain.mkl_version }} + + - name: Setup Intel oneAPI environment + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + run: | + source /opt/intel/oneapi/setvars.sh --force + printenv >> $GITHUB_ENV + + - name: Set compiler environment variables + run: | + if [ ! -n "$FC" ]; then + if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then + echo "FC=gfortran" >> $GITHUB_ENV + echo "CC=gcc" >> $GITHUB_ENV + elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then + echo "FC=ifx" >> $GITHUB_ENV + echo "CC=icx" >> $GITHUB_ENV + fi + fi + echo "COMPILER_VERSION=${{ matrix.toolchain.version }}" >> $GITHUB_ENV + + # ---------------------------------------------------------------------- + # Dependencies & submodules + # ---------------------------------------------------------------------- + + - name: Git submodules checkout + run: git submodule update --init + + - name: Install build and test dependencies + run: | + pip3 install ${{ env.PIP_PACKAGES }} ${{ env.PIP_EXTRAS }} + + # ---------------------------------------------------------------------- + # Configure + # ---------------------------------------------------------------------- + + - name: Configure build (CMake, debug) + if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'debug' }} + run: > + cmake -B${{ env.BUILD_DIR }} + -GNinja + -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DCMAKE_INSTALL_LIBDIR=lib + + - name: Configure build (CMake, debugoptimized) + if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'debugoptimized' }} + run: > + cmake -B${{ env.BUILD_DIR }} + -GNinja + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DCMAKE_INSTALL_LIBDIR=lib + + - name: Configure build (Meson, GNU) + if: ${{ matrix.toolchain.build == 'meson' && matrix.toolchain.compiler == 'gcc' }} + run: > + meson setup ${{ env.BUILD_DIR }} + --buildtype=${{ matrix.build-type }} + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + -Dlapack=openblas + + - name: Configure build (Meson, Intel) + if: ${{ matrix.toolchain.build == 'meson' && contains(matrix.toolchain.compiler, 'intel') }} + run: > + meson setup ${{ env.BUILD_DIR }} + --buildtype=${{ matrix.build-type }} + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + --native-file=config/intel-llvm.ini + -Dlapack=mkl + + # ---------------------------------------------------------------------- + # Build / test / install + # ---------------------------------------------------------------------- + + - name: Build project + run: ninja -C ${{ env.BUILD_DIR }} + + - name: Run unit tests (ctest) + if: ${{ matrix.toolchain.build == 'cmake' && contains(matrix.toolchain.compiler, 'gcc') }} + run: | + ctest --output-on-failure --parallel 2 -R '^crest/' + working-directory: ${{ env.BUILD_DIR }} + env: + OMP_NUM_THREADS: 1,2,1 + + - name: Install project + run: | + ninja -C ${{ env.BUILD_DIR }} install + echo "CREST_PREFIX=$PWD/_dist" >> $GITHUB_ENV + + build-static: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + # Linux x86_64 — GNU static (CMake) + - { os: ubuntu-latest, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # Linux x86_64 — Intel LLVM static (Meson; better ifx static support) + - { os: ubuntu-latest, build-type: static, + toolchain: { compiler: intel, version: '2025.1', mkl_version: '2025.1', build: meson } } + + # Linux aarch64 — GNU static (CMake; Intel oneAPI is x86-only) + - { os: ubuntu-24.04-arm, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # macOS arm64 — GNU mostly-static (CMake) + # Note: macOS does not support fully-static executables; system libs remain dynamic. + - { os: macos-latest, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + defaults: + run: + shell: bash + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + # --- Compiler setup ---------------------------------------------------- + + - name: Install GCC using setup-fortran + if: ${{ contains(matrix.toolchain.compiler, 'gcc') }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install libopenblas (Linux GCC builds) + if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'gcc') }} + run: | + sudo apt-get update + sudo apt-get install -y libopenblas-dev + + - name: Install OpenBLAS (macOS) + if: ${{ contains(matrix.os, 'macos') }} + run: | + brew update + brew install openblas + echo "PKG_CONFIG_PATH=/usr/local/opt/openblas/lib/pkgconfig:/opt/homebrew/opt/openblas/lib/pkgconfig" >> $GITHUB_ENV + echo "LDFLAGS=-L/usr/local/opt/openblas/lib -L/opt/homebrew/opt/openblas/lib" >> $GITHUB_ENV + echo "CPPFLAGS=-I/usr/local/opt/openblas/include -I/opt/homebrew/opt/openblas/include" >> $GITHUB_ENV + + - name: Prepare for Intel cache restore + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + run: | + sudo mkdir -p /opt/intel + sudo chown $USER /opt/intel + + - name: Cache Intel installation + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + id: cache-install + uses: actions/cache@v4 + with: + path: /opt/intel/oneapi + key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.toolchain.mkl_version }}-${{ matrix.os }} + + - name: Install Intel Compiler + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install Intel MKL + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + run: | + sudo apt-get install -y \ + intel-oneapi-mkl-${{ matrix.toolchain.mkl_version }} \ + intel-oneapi-mkl-devel-${{ matrix.toolchain.mkl_version }} + + - name: Setup Intel oneAPI environment + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + run: | + source /opt/intel/oneapi/setvars.sh --force + printenv >> $GITHUB_ENV + + - name: Set compiler environment variables + run: | + if [ ! -n "$FC" ]; then + if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then + echo "FC=gfortran" >> $GITHUB_ENV + echo "CC=gcc" >> $GITHUB_ENV + elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then + echo "FC=ifx" >> $GITHUB_ENV + echo "CC=icx" >> $GITHUB_ENV + fi + fi + echo "COMPILER_VERSION=${{ matrix.toolchain.version }}" >> $GITHUB_ENV + + # --- Dependencies & submodules ----------------------------------------- + + - name: Git submodules checkout + run: git submodule update --init + + - name: Install build dependencies + run: | + pip3 install ${{ env.PIP_PACKAGES }} ${{ env.PIP_EXTRAS }} + + # --- Configure --------------------------------------------------------- + + - name: Configure build (CMake, static) + if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'static' }} + run: > + cmake -B${{ env.BUILD_DIR }} + -GNinja + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DCMAKE_INSTALL_LIBDIR=lib + -DWITH_TESTS=OFF + -DSTATICBUILD=ON + + - name: Configure build (Meson, Intel static) + if: ${{ matrix.toolchain.build == 'meson' && matrix.build-type == 'static' }} + run: > + meson setup ${{ env.BUILD_DIR }} + --buildtype=release + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + --native-file=config/intel-llvm.ini + -Dstatic=true + -Dlapack=mkl + -Dtests=false + + # --- Build / install --------------------------------------------------- + + - name: Build project + run: ninja -C ${{ env.BUILD_DIR }} + + - name: Install project + run: | + ninja -C ${{ env.BUILD_DIR }} install + echo "CREST_PREFIX=$PWD/_dist" >> $GITHUB_ENV + + - name: Create package + run: | + mkdir crest + cp COPYING crest/LICENSE + cp COPYING.LESSER crest/LICENSE.LESSER + cp _dist/bin/crest crest/ + ARCH=$(uname -m) + UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]') + COMPILER_NAME="${{ matrix.toolchain.compiler }}" + [ "$COMPILER_NAME" = "gcc" ] && COMPILER_NAME="gnu" + [ "$COMPILER_NAME" = "intel" ] && COMPILER_NAME="intel" + OUTPUT="crest-${COMPILER_NAME}-${{ matrix.toolchain.version }}-${UNAME_OS}-${ARCH}.tar" + tar cvf "$OUTPUT" crest + xz -T0 "$OUTPUT" + echo "CREST_OUTPUT=${OUTPUT}.xz" >> $GITHUB_ENV + + - name: Upload package + uses: actions/upload-artifact@v4 + with: + name: ${{ env.CREST_OUTPUT }} + path: ${{ env.CREST_OUTPUT }} diff --git a/.github/workflows/build-CI.yml b/.github/workflows/build-CI.yml index b40f62b0..174e5def 100644 --- a/.github/workflows/build-CI.yml +++ b/.github/workflows/build-CI.yml @@ -2,7 +2,15 @@ name: CI on: push: + branches: + - master + - experimental + - '*-maintenance' pull_request: + branches: + - master + - '*-maintenance' + workflow_dispatch: env: BUILD_DIR: _build @@ -15,11 +23,6 @@ env: numpy ase matplotlib - LINUX_INTEL_COMPONENTS: >- - intel-oneapi-compiler-fortran-2023.1.0 - intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.1.0 - intel-oneapi-mkl-2023.1.0 - intel-oneapi-mkl-devel-2023.1.0 jobs: build: @@ -28,29 +31,17 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest] - build-type: [debug] - toolchain: - - { compiler: gcc, version: '11', build: cmake } - - { compiler: gcc, version: '12', build: cmake } - - { compiler: gcc, version: '14', build: cmake } - - { compiler: intel, version: '2023.1.0', build: cmake } - include: - # ---- Linux GCC CMake debugoptimized build ------------------------ + # ---- Linux GCC 14 + OpenBLAS — CMake debugoptimized -------------- - { os: ubuntu-latest, build-type: debugoptimized, toolchain: { compiler: gcc, version: '14', build: cmake } } - - # ---- Linux static builds ----------------------------------------- - - { os: ubuntu-latest, build-type: static, - toolchain: { compiler: gcc, version: '12', build: cmake } } - - { os: ubuntu-latest, build-type: static, - toolchain: { compiler: intel, version: '2023.1.0', build: meson } } - - # ---- macOS GCC CMake debug builds -------------------------------- - - { os: macos-latest, build-type: debug, - toolchain: { compiler: gcc, version: '12', build: cmake } } - - { os: macos-latest, build-type: debug, + + # ---- Linux Intel (ifx/icx) + MKL — Meson debugoptimized --------- + - { os: ubuntu-latest, build-type: debugoptimized, + toolchain: { compiler: intel, version: '2025.1', mkl_version: '2025.1', build: meson } } + + # ---- macOS GCC 14 + OpenBLAS — CMake debugoptimized -------------- + - { os: macos-latest, build-type: debugoptimized, toolchain: { compiler: gcc, version: '14', build: cmake } } defaults: @@ -67,20 +58,20 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.10" # ---------------------------------------------------------------------- - # Compiler setup (GCC via setup-fortran, Intel via oneAPI on Linux) + # Compiler setup # ---------------------------------------------------------------------- - name: Install GCC using setup-fortran if: ${{ contains(matrix.toolchain.compiler, 'gcc') }} uses: fortran-lang/setup-fortran@v1 with: - compiler: ${{ matrix.toolchain.compiler }} # "gcc" - version: ${{ matrix.toolchain.version }} # e.g. "12" + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} - - name: Install libopenblas (Linux GNU builds only) + - name: Install libopenblas (Linux GCC builds) if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'gcc') }} run: | sudo apt-get update @@ -96,48 +87,49 @@ jobs: echo "CPPFLAGS=-I/usr/local/opt/openblas/include -I/opt/homebrew/opt/openblas/include" >> $GITHUB_ENV - name: Prepare for Intel cache restore - if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'intel') }} + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} run: | sudo mkdir -p /opt/intel - sudo chown "$USER" /opt/intel + sudo chown $USER /opt/intel - - name: Cache Intel oneAPI install - if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'intel') }} + - name: Cache Intel installation + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} id: cache-install uses: actions/cache@v4 with: path: /opt/intel/oneapi - key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.os }} + key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.toolchain.mkl_version }}-${{ matrix.os }} - - name: Install Intel oneAPI (compiler + MKL) - if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} - run: | - KEY=GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - wget https://apt.repos.intel.com/intel-gpg-keys/$KEY - sudo apt-key add $KEY - rm $KEY + - name: Install Intel Compiler + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} - echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list - sudo apt-get update - sudo apt-get install -y $PKG - env: - PKG: ${{ env.LINUX_INTEL_COMPONENTS }} + - name: Install Intel MKL + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + run: | + sudo apt-get install -y \ + intel-oneapi-mkl-${{ matrix.toolchain.mkl_version }} \ + intel-oneapi-mkl-devel-${{ matrix.toolchain.mkl_version }} - name: Setup Intel oneAPI environment - if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'intel') }} + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} run: | - source /opt/intel/oneapi/setvars.sh + source /opt/intel/oneapi/setvars.sh --force printenv >> $GITHUB_ENV - name: Set compiler environment variables run: | - if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then - echo "FC=gfortran" >> $GITHUB_ENV - echo "CC=gcc" >> $GITHUB_ENV - elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then - # adjust to ifx/ifort if we want to change it in the future - echo "FC=ifort" >> $GITHUB_ENV - echo "CC=icx" >> $GITHUB_ENV + if [ ! -n "$FC" ]; then + if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then + echo "FC=gfortran" >> $GITHUB_ENV + echo "CC=gcc" >> $GITHUB_ENV + elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then + echo "FC=ifx" >> $GITHUB_ENV + echo "CC=icx" >> $GITHUB_ENV + fi fi echo "COMPILER_VERSION=${{ matrix.toolchain.version }}" >> $GITHUB_ENV @@ -156,15 +148,6 @@ jobs: # Configure # ---------------------------------------------------------------------- - - name: Configure build (Meson) - if: ${{ matrix.toolchain.build == 'meson' }} - run: > - meson setup ${{ env.BUILD_DIR }} - --buildtype=debugoptimized - --prefix=$PWD/_dist - --libdir=lib - --warnlevel=0 - - name: Configure build (CMake, debug) if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'debug' }} run: > @@ -183,16 +166,26 @@ jobs: -DCMAKE_INSTALL_PREFIX=$PWD/_dist -DCMAKE_INSTALL_LIBDIR=lib - - name: Configure build (CMake, static) - if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'static' }} + - name: Configure build (Meson, GNU) + if: ${{ matrix.toolchain.build == 'meson' && matrix.toolchain.compiler == 'gcc' }} run: > - cmake -B${{ env.BUILD_DIR }} - -GNinja - -DCMAKE_BUILD_TYPE=RelWithDebInfo - -DCMAKE_INSTALL_PREFIX=$PWD/_dist - -DCMAKE_INSTALL_LIBDIR=lib - -DWITH_TESTS=OFF - -DSTATICBUILD=ON + meson setup ${{ env.BUILD_DIR }} + --buildtype=${{ matrix.build-type }} + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + -Dlapack=openblas + + - name: Configure build (Meson, Intel) + if: ${{ matrix.toolchain.build == 'meson' && contains(matrix.toolchain.compiler, 'intel') }} + run: > + meson setup ${{ env.BUILD_DIR }} + --buildtype=${{ matrix.build-type }} + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + --native-file=config/intel-llvm.ini + -Dlapack=mkl # ---------------------------------------------------------------------- # Build / test / install @@ -202,7 +195,7 @@ jobs: run: ninja -C ${{ env.BUILD_DIR }} - name: Run unit tests (ctest) - if: ${{ matrix.toolchain.build == 'cmake' && contains(matrix.toolchain.compiler, 'gcc') }} + if: ${{ matrix.toolchain.build == 'cmake' }} run: | ctest --output-on-failure --parallel 2 -R '^crest/' working-directory: ${{ env.BUILD_DIR }} @@ -213,4 +206,3 @@ jobs: run: | ninja -C ${{ env.BUILD_DIR }} install echo "CREST_PREFIX=$PWD/_dist" >> $GITHUB_ENV - diff --git a/.github/workflows/build-static-experimental.yml b/.github/workflows/build-static-experimental.yml new file mode 100644 index 00000000..74177d29 --- /dev/null +++ b/.github/workflows/build-static-experimental.yml @@ -0,0 +1,204 @@ +name: Static builds (experimental branch) + +on: + workflow_dispatch: + +env: + BUILD_DIR: _build + PIP_PACKAGES: >- + meson!=1.8.0 + cmake + ninja + PIP_EXTRAS: >- + pkgconfig + numpy + ase + matplotlib + +jobs: + build-static: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + # Linux x86_64 — GNU static (CMake) + - { os: ubuntu-latest, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # Linux x86_64 — Intel LLVM static (Meson; better ifx static support) + - { os: ubuntu-latest, build-type: static, + toolchain: { compiler: intel, version: '2025.3', mkl_version: '2025.3', build: meson } } + + # Linux aarch64 — GNU static (CMake; Intel oneAPI is x86-only) + - { os: ubuntu-24.04-arm, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # macOS arm64 — dynamic release (CMake) + # macOS has no static libpthread/libSystem, so fully-static builds are + # not possible. This produces a dynamic binary; users need Homebrew GCC + # and OpenBLAS (see release notes). + - { os: macos-latest, build-type: release, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + defaults: + run: + shell: bash + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + # --- Compiler setup ---------------------------------------------------- + + - name: Install GCC using setup-fortran + if: ${{ contains(matrix.toolchain.compiler, 'gcc') }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install libopenblas (Linux GCC builds) + if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'gcc') }} + run: | + sudo apt-get update + sudo apt-get install -y libopenblas-dev + + - name: Install OpenBLAS (macOS) + if: ${{ contains(matrix.os, 'macos') }} + run: | + brew update + brew install openblas + echo "PKG_CONFIG_PATH=/usr/local/opt/openblas/lib/pkgconfig:/opt/homebrew/opt/openblas/lib/pkgconfig" >> $GITHUB_ENV + echo "LDFLAGS=-L/usr/local/opt/openblas/lib -L/opt/homebrew/opt/openblas/lib" >> $GITHUB_ENV + echo "CPPFLAGS=-I/usr/local/opt/openblas/include -I/opt/homebrew/opt/openblas/include" >> $GITHUB_ENV + + - name: Prepare for Intel cache restore + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + run: | + sudo mkdir -p /opt/intel + sudo chown $USER /opt/intel + + - name: Cache Intel installation + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + id: cache-install + uses: actions/cache@v4 + with: + path: /opt/intel/oneapi + key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.toolchain.mkl_version }}-${{ matrix.os }} + + - name: Install Intel Compiler + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Install Intel MKL + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + run: | + sudo apt-get install -y \ + intel-oneapi-mkl-${{ matrix.toolchain.mkl_version }} \ + intel-oneapi-mkl-devel-${{ matrix.toolchain.mkl_version }} + + - name: Setup Intel oneAPI environment + if: ${{ contains(matrix.toolchain.compiler, 'intel') }} + run: | + source /opt/intel/oneapi/setvars.sh --force + printenv >> $GITHUB_ENV + + - name: Set compiler environment variables + run: | + if [ ! -n "$FC" ]; then + if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then + echo "FC=gfortran" >> $GITHUB_ENV + echo "CC=gcc" >> $GITHUB_ENV + elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then + echo "FC=ifx" >> $GITHUB_ENV + echo "CC=icx" >> $GITHUB_ENV + fi + fi + echo "COMPILER_VERSION=${{ matrix.toolchain.version }}" >> $GITHUB_ENV + + # --- Dependencies & submodules ----------------------------------------- + + - name: Git submodules checkout + run: git submodule update --init + + - name: Install build dependencies + run: | + pip3 install ${{ env.PIP_PACKAGES }} ${{ env.PIP_EXTRAS }} + + # --- Configure --------------------------------------------------------- + + - name: Configure build (CMake, static) + if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'static' }} + run: > + cmake -B${{ env.BUILD_DIR }} + -GNinja + -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DCMAKE_INSTALL_LIBDIR=lib + -DWITH_TESTS=OFF + -DSTATICBUILD=ON + + - name: Configure build (CMake, macOS dynamic release) + if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'release' }} + run: > + cmake -B${{ env.BUILD_DIR }} + -GNinja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DCMAKE_INSTALL_LIBDIR=lib + -DWITH_TESTS=OFF + + - name: Configure build (Meson, Intel static) + if: ${{ matrix.toolchain.build == 'meson' && matrix.build-type == 'static' }} + run: > + meson setup ${{ env.BUILD_DIR }} + --buildtype=release + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + --native-file=config/intel-llvm.ini + -Dstatic=true + -Dlapack=mkl + -Dtests=false + + # --- Build / install --------------------------------------------------- + + - name: Build project + run: ninja -C ${{ env.BUILD_DIR }} + + - name: Install project + run: | + ninja -C ${{ env.BUILD_DIR }} install + echo "CREST_PREFIX=$PWD/_dist" >> $GITHUB_ENV + + - name: Create package + run: | + mkdir crest + cp COPYING crest/LICENSE + cp COPYING.LESSER crest/LICENSE.LESSER + cp _dist/bin/crest crest/ + ARCH=$(uname -m) + UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]') + COMPILER_NAME="${{ matrix.toolchain.compiler }}" + [ "$COMPILER_NAME" = "gcc" ] && COMPILER_NAME="gnu" + [ "$COMPILER_NAME" = "intel" ] && COMPILER_NAME="intel" + OUTPUT="crest-${COMPILER_NAME}-${{ matrix.toolchain.version }}-${UNAME_OS}-${ARCH}.tar" + tar cvf "$OUTPUT" crest + xz -T0 "$OUTPUT" + echo "CREST_OUTPUT=${OUTPUT}.xz" >> $GITHUB_ENV + + - name: Upload package + uses: actions/upload-artifact@v4 + with: + name: ${{ env.CREST_OUTPUT }} + path: ${{ env.CREST_OUTPUT }} diff --git a/.github/workflows/build-upload.yml b/.github/workflows/build-upload.yml index 344072d5..50ac0b9e 100644 --- a/.github/workflows/build-upload.yml +++ b/.github/workflows/build-upload.yml @@ -1,4 +1,4 @@ -name: Continuous release (static Linux) +name: Continuous release (static builds) on: push: @@ -16,11 +16,6 @@ env: numpy ase matplotlib - LINUX_INTEL_COMPONENTS: >- - intel-oneapi-compiler-fortran-2023.1.0 - intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.1.0 - intel-oneapi-mkl-2023.1.0 - intel-oneapi-mkl-devel-2023.1.0 jobs: build-static: @@ -29,13 +24,25 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest] - build-type: [static] - toolchain: - # GNU static CMake build - - { compiler: gcc, version: '12', build: cmake } - # Intel static Meson build - - { compiler: intel, version: '2023.1.0', build: meson } + include: + # Linux x86_64 — GNU static (CMake) + - { os: ubuntu-latest, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # Linux x86_64 — Intel LLVM static (Meson; better ifx static support) + - { os: ubuntu-latest, build-type: static, + toolchain: { compiler: intel, version: '2025.3', mkl_version: '2025.3', build: meson } } + + # Linux aarch64 — GNU static (CMake; Intel oneAPI is x86-only) + - { os: ubuntu-24.04-arm, build-type: static, + toolchain: { compiler: gcc, version: '14', build: cmake } } + + # macOS arm64 — dynamic release (CMake) + # macOS has no static libpthread/libSystem, so fully-static builds are + # not possible. This produces a dynamic binary; users need Homebrew GCC + # and OpenBLAS (see release notes). + - { os: macos-latest, build-type: release, + toolchain: { compiler: gcc, version: '14', build: cmake } } defaults: run: @@ -48,69 +55,80 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: "3.9" + python-version: "3.10" # --- Compiler setup ---------------------------------------------------- - - name: Install GCC (Linux) using setup-fortran + - name: Install GCC using setup-fortran if: ${{ contains(matrix.toolchain.compiler, 'gcc') }} uses: fortran-lang/setup-fortran@v1 with: compiler: ${{ matrix.toolchain.compiler }} version: ${{ matrix.toolchain.version }} - - name: Install libopenblas (Linux GNU builds only) - if: ${{ contains(matrix.toolchain.compiler, 'gcc') }} + - name: Install libopenblas (Linux GCC builds) + if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'gcc') }} run: | sudo apt-get update sudo apt-get install -y libopenblas-dev + - name: Install OpenBLAS (macOS) + if: ${{ contains(matrix.os, 'macos') }} + run: | + brew update + brew install openblas + echo "PKG_CONFIG_PATH=/usr/local/opt/openblas/lib/pkgconfig:/opt/homebrew/opt/openblas/lib/pkgconfig" >> $GITHUB_ENV + echo "LDFLAGS=-L/usr/local/opt/openblas/lib -L/opt/homebrew/opt/openblas/lib" >> $GITHUB_ENV + echo "CPPFLAGS=-I/usr/local/opt/openblas/include -I/opt/homebrew/opt/openblas/include" >> $GITHUB_ENV + - name: Prepare for Intel cache restore if: ${{ contains(matrix.toolchain.compiler, 'intel') }} run: | sudo mkdir -p /opt/intel - sudo chown "$USER" /opt/intel + sudo chown $USER /opt/intel - - name: Cache Intel oneAPI install + - name: Cache Intel installation if: ${{ contains(matrix.toolchain.compiler, 'intel') }} id: cache-install uses: actions/cache@v4 with: path: /opt/intel/oneapi - key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.os }} + key: install-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }}-${{ matrix.toolchain.mkl_version }}-${{ matrix.os }} - - name: Install Intel oneAPI (compiler + MKL) - if: ${{ contains(matrix.os, 'ubuntu') && contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} - run: | - KEY=GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB - wget https://apt.repos.intel.com/intel-gpg-keys/$KEY - sudo apt-key add $KEY - rm $KEY + - name: Install Intel Compiler + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + uses: fortran-lang/setup-fortran@v1 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} - echo "deb https://apt.repos.intel.com/oneapi all main" | sudo tee /etc/apt/sources.list.d/oneAPI.list - sudo apt-get update - sudo apt-get install -y $PKG - env: - PKG: ${{ env.LINUX_INTEL_COMPONENTS }} + - name: Install Intel MKL + if: ${{ contains(matrix.toolchain.compiler, 'intel') && steps.cache-install.outputs.cache-hit != 'true' }} + run: | + sudo apt-get install -y \ + intel-oneapi-mkl-${{ matrix.toolchain.mkl_version }} \ + intel-oneapi-mkl-devel-${{ matrix.toolchain.mkl_version }} - name: Setup Intel oneAPI environment if: ${{ contains(matrix.toolchain.compiler, 'intel') }} run: | - source /opt/intel/oneapi/setvars.sh + source /opt/intel/oneapi/setvars.sh --force printenv >> $GITHUB_ENV - name: Set compiler environment variables run: | - if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then - echo "FC=gfortran" >> $GITHUB_ENV - echo "CC=gcc" >> $GITHUB_ENV - elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then - echo "FC=ifort" >> $GITHUB_ENV - echo "CC=icx" >> $GITHUB_ENV + if [ ! -n "$FC" ]; then + if [ "${{ matrix.toolchain.compiler }}" = "gcc" ]; then + echo "FC=gfortran" >> $GITHUB_ENV + echo "CC=gcc" >> $GITHUB_ENV + elif [ "${{ matrix.toolchain.compiler }}" = "intel" ]; then + echo "FC=ifx" >> $GITHUB_ENV + echo "CC=icx" >> $GITHUB_ENV + fi fi echo "COMPILER_VERSION=${{ matrix.toolchain.version }}" >> $GITHUB_ENV - # --- Dependencies & submodules --------------------------------------- + # --- Dependencies & submodules ----------------------------------------- - name: Git submodules checkout run: git submodule update --init @@ -119,16 +137,7 @@ jobs: run: | pip3 install ${{ env.PIP_PACKAGES }} ${{ env.PIP_EXTRAS }} - # --- Configure -------------------------------------------------------- - - - name: Configure build (Meson, static-ish) - if: ${{ matrix.toolchain.build == 'meson' }} - run: > - meson setup ${{ env.BUILD_DIR }} - --buildtype=debugoptimized - --prefix=$PWD/_dist - --libdir=lib - --warnlevel=0 + # --- Configure --------------------------------------------------------- - name: Configure build (CMake, static) if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'static' }} @@ -141,7 +150,30 @@ jobs: -DWITH_TESTS=OFF -DSTATICBUILD=ON - # --- Build / (optional) test / install -------------------------------- + - name: Configure build (CMake, macOS dynamic release) + if: ${{ matrix.toolchain.build == 'cmake' && matrix.build-type == 'release' }} + run: > + cmake -B${{ env.BUILD_DIR }} + -GNinja + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_INSTALL_PREFIX=$PWD/_dist + -DCMAKE_INSTALL_LIBDIR=lib + -DWITH_TESTS=OFF + + - name: Configure build (Meson, Intel static) + if: ${{ matrix.toolchain.build == 'meson' && matrix.build-type == 'static' }} + run: > + meson setup ${{ env.BUILD_DIR }} + --buildtype=release + --prefix=$PWD/_dist + --libdir=lib + --warnlevel=0 + --native-file=config/intel-llvm.ini + -Dstatic=true + -Dlapack=mkl + -Dtests=false + + # --- Build / install --------------------------------------------------- - name: Build project run: ninja -C ${{ env.BUILD_DIR }} @@ -157,17 +189,16 @@ jobs: cp COPYING crest/LICENSE cp COPYING.LESSER crest/LICENSE.LESSER cp _dist/bin/crest crest/ + ARCH=$(uname -m) + UNAME_OS=$(uname -s | tr '[:upper:]' '[:lower:]') COMPILER_NAME="${{ matrix.toolchain.compiler }}" - # Map GCC → gnu for backwards-compatible file names - if [ "$COMPILER_NAME" = "gcc" ]; then - COMPILER_NAME="gnu" - fi - OUTPUT="crest-${COMPILER_NAME}-${{ matrix.toolchain.version }}-${{ matrix.os }}.tar" + [ "$COMPILER_NAME" = "gcc" ] && COMPILER_NAME="gnu" + [ "$COMPILER_NAME" = "intel" ] && COMPILER_NAME="intel" + OUTPUT="crest-${COMPILER_NAME}-${{ matrix.toolchain.version }}-${UNAME_OS}-${ARCH}.tar" tar cvf "$OUTPUT" crest xz -T0 "$OUTPUT" echo "CREST_OUTPUT=${OUTPUT}.xz" >> $GITHUB_ENV - - name: Upload package uses: actions/upload-artifact@v4 with: @@ -264,4 +295,3 @@ jobs: --name "$f" \ --file "$f" done - diff --git a/.gitignore b/.gitignore index 631e65ab..77dcaf67 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,10 @@ *.mod *.tgz *.i90 +*.bak *__genmod.f90 +.s* +.c* github_bin/ build_majestix build_commands @@ -10,3 +13,7 @@ _build* _dist* src/crest bin/ +subprojects/.wraplock +meson_build/ +meson_build_* +utils/ diff --git a/.gitmodules b/.gitmodules index ed86f56b..749a9dba 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,4 +23,28 @@ branch = main [submodule "subprojects/pvol"] path = subprojects/pvol - url = https://github.com/neudecker-group/libpvol.git + url = https://github.com/pprcht/libpvol.git + branch = build-update +[submodule "subprojects/fmlip_relay"] + path = subprojects/fmlip_relay + url = https://github.com/pprcht/fmlip-relay.git +[submodule "subprojects/dftd4"] + path = subprojects/dftd4 + url = https://github.com/dftd4/dftd4 + branch = main +[submodule "subprojects/mctc-lib"] + path = subprojects/mctc-lib + url = https://github.com/grimme-lab/mctc-lib.git +[submodule "subprojects/mstore"] + path = subprojects/mstore + url = https://github.com/grimme-lab/mstore +[submodule "subprojects/multicharge"] + path = subprojects/multicharge + url = https://github.com/grimme-lab/multicharge +[submodule "subprojects/s-dftd3"] + path = subprojects/s-dftd3 + url = https://github.com/dftd3/simple-dftd3 +[submodule "subprojects/ddx"] + path = subprojects/ddx + url = https://github.com/ddsolvation/ddX.git + branch = main diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..262a496a --- /dev/null +++ b/BUILD.md @@ -0,0 +1,292 @@ +# Building crest with Meson + +## Prerequisites + +| Tool | Minimum version | Notes | +|------|----------------|-------| +| [Meson](https://mesonbuild.com) | 0.57.0 | `pip install meson` or distro package | +| [Ninja](https://ninja-build.org) | 1.10 | usually installed alongside meson | +| Fortran compiler | — | gfortran ≥ 10, ifort ≥ 2021, or ifx ≥ 2023 | +| C compiler | — | gcc or the matching Intel C compiler | +| LAPACK + BLAS | — | OpenBLAS, Intel MKL, or netlib | +| OpenMP | — | libgomp (GNU) or libomp/libiomp5 (Intel) | + +Optional (auto-detected or pulled from `subprojects/`): + +- tblite, toml-f, GFN-FF, GFN0-xTB, libpvol, lwONIOM, fmlip-relay +- test-drive (only for `--tests`) + +--- + +## Quick start + +```sh +# Configure (defaults: release build, OpenBLAS/auto LAPACK, OpenMP on, all +# optional libs auto-detected, unit tests enabled) +meson setup build + +# Compile +ninja -C build + +# Run tests +ninja -C build test + +# Install to /usr/local +ninja -C build install +``` + +--- + +## Compiler selection + +The build system auto-detects whatever Fortran/C compilers are first on +`$PATH`. Use the native-file templates in `config/` to pin a specific +toolchain: + +```sh +# Pure GNU (gfortran + gcc) +meson setup build --native-file config/gnu.ini + +# Intel LLVM (ifx + icx) — oneAPI 2023+ +source /opt/intel/oneapi/setvars.sh +meson setup build --native-file config/intel-llvm.ini + +# Intel classic (ifort + icc) — oneAPI 2022 or earlier +meson setup build --native-file config/intel-classic.ini + +# Mixed: Intel Fortran (ifx) + GNU C (gcc) +meson setup build --native-file config/intel-fortran-gnu-c.ini +``` + +Or set compilers directly via environment variables (older style): + +```sh +FC=gfortran CC=gcc meson setup build +FC=ifort CC=icc meson setup build +FC=ifx CC=icx meson setup build +``` + +--- + +## Build options + +Pass options with `-D` at configure time, or modify with `meson configure`: + +| Option | Default | Description | +|--------|---------|-------------| +| `openmp` | `true` | Enable OpenMP parallelisation | +| `lapack` | `auto` | LAPACK/BLAS provider: `auto`, `openblas`, `mkl`, `netlib`, `custom` | +| `lapack_libs` | `[]` | Library names for `lapack=custom` | +| `blas_libs` | `[]` | Library names for `lapack=custom` | +| `static` | `false` | Link a fully static binary | +| `tblite` | `auto` | tblite semiempirical library | +| `toml-f` | `auto` | TOML-Fortran (file-based input) | +| `gfn0` | `auto` | GFN0-xTB library | +| `gfnff` | `auto` | GFN-FF library | +| `libpvol` | `auto` | libpvol (volume computation) | +| `lwoniom` | `auto` | lwONIOM | +| `fmlip-relay` | `auto` | fmlip-relay ML/IP interface | +| `tests` | `true` | Build unit tests | + +Feature options (`auto` / `enabled` / `disabled`): + +- `auto` — use it if found, silently skip if not +- `enabled` — require it; fail the build if not found +- `disabled` — never use it even if installed + +Examples: + +```sh +# Disable all optional libraries (minimal build) +meson setup build -Dtblite=disabled -Dtoml-f=disabled \ + -Dgfn0=disabled -Dgfnff=disabled \ + -Dlibpvol=disabled -Dlwoniom=disabled \ + -Dfmlip-relay=disabled + +# Require tblite (fail if not found) +meson setup build -Dtblite=enabled + +# Debug build with bounds checking +meson setup build --buildtype=debug + +# Change an option after configuration +meson configure build -Dopenmp=false +``` + +--- + +## LAPACK / BLAS selection + +### Auto (default) + +- For Intel compilers: tries MKL first, then OpenBLAS, then netlib +- For GNU: tries OpenBLAS first, then MKL (with `mkl_gnu_thread`), then netlib + +```sh +meson setup build -Dlapack=auto # (this is the default) +``` + +### OpenBLAS + +```sh +meson setup build -Dlapack=openblas +``` + +OpenBLAS bundles both BLAS and LAPACK. Make sure `libopenblas-dev` (Debian/Ubuntu) +or `openblas-devel` (RHEL/Fedora) is installed, or that `pkg-config --exists openblas` +succeeds. + +### Intel MKL + +```sh +source /opt/intel/oneapi/setvars.sh +meson setup build -Dlapack=mkl +``` + +The build system selects the correct threading layer automatically: + +| Fortran compiler | MKL threading layer | OpenMP runtime | +|-----------------|---------------------|----------------| +| gfortran | `mkl_gnu_thread` | libgomp | +| ifort / ifx | `mkl_intel_thread` | libiomp5 / libomp | + +**Do not mix** `mkl_gnu_thread` with Intel OpenMP or vice versa — this causes +silent wrong results or crashes. + +If `pkg-config` can see `mkl-sdl`, that single-dynamic-library interface is +used instead and no threading-layer selection is needed. + +### Custom libraries + +For non-standard LAPACK installations (e.g. a vendor-tuned LAPACK on a +cluster module): + +```sh +meson setup build -Dlapack=custom \ + -Dlapack_libs=lapack,blas \ + -Dblas_libs=blas +# or with full paths via pkg-config / LIBRARY_PATH +``` + +--- + +## Fully static binary + +A static binary embeds all libraries including the OpenMP runtime and LAPACK. +This is the most portable output for distribution on HPC clusters. + +### GNU static + +Requires: `libgfortran.a`, `libgomp.a`, `libopenblas.a` (or `liblapack.a` + +`libblas.a`) to be available as static `.a` archives. On Debian/Ubuntu +install `gfortran-static`, `libgomp1` (usually comes with `libgomp-staticdev`), +and `libopenblas-dev`. + +```sh +meson setup build_static \ + --buildtype=release \ + --native-file config/gnu.ini \ + -Dstatic=true \ + -Dlapack=openblas +ninja -C build_static +# Result: build_static/crest — fully self-contained ELF +ldd build_static/crest # should print "not a dynamic executable" +``` + +### Intel classic static + +```sh +source /opt/intel/oneapi/setvars.sh +meson setup build_static \ + --native-file config/intel-classic.ini \ + -Dstatic=true \ + -Dlapack=mkl +ninja -C build_static +``` + +Intel's `-static-intel -qopenmp-link=static` flags are applied automatically. +The Intel static libraries (`libifcore.a`, `libimf.a`, `libsvml.a`, +`libiomp5.a`) must be present — they are typically in +`$ONEAPI_ROOT/compiler/latest/linux/compiler/lib/intel64_lin/`. + +### Intel LLVM (ifx) static + +```sh +source /opt/intel/oneapi/setvars.sh +meson setup build_static \ + --native-file config/intel-llvm.ini \ + -Dstatic=true \ + -Dlapack=mkl +ninja -C build_static +``` + +\`-static-intel -qopenmp-link=static\` are applied automatically for ifx. + +--- + +## Subprojects + +Optional chemistry libraries are resolved in this order: + +1. **System-installed** — found via `pkg-config` or in standard library paths +2. **Git submodule** — if `subprojects//` exists and contains a + `meson.build` (populate with `git submodule update --init --recursive`) +3. **Wrap file** — `subprojects/.wrap` tells Meson to clone the repo on + first use with `meson subprojects download` + +To pre-fetch all wrap-defined subprojects: + +```sh +meson subprojects download +``` + +To update existing subproject clones: + +```sh +meson subprojects update +``` + +--- + +## Metadata generation + +At configure time Meson fills `assets/template/metadata.f90` and writes the +result to `/crest_metadata.fh`. The placeholders populated are: + +| Placeholder | Value | +|-------------|-------| +| `@version@` | project version from `meson.build` | +| `@commit@` | short git hash (or `unknown-commit`) | +| `@date@` | configure timestamp | +| `@author@` | `$USER` / `$USERNAME` | +| `@origin@` | hostname | +| `@fcid@` | Fortran compiler name | +| `@fcver@` | Fortran compiler version | +| `@ccid@` | C compiler name | +| `@ccver@` | C compiler version | +| `@bsystem@` | `meson ` | +| `@tomlfvar@` | `true` / `false` | +| `@gfn0var@` | `true` / `false` | +| `@gfnffvar@` | `true` / `false` | +| `@tblitevar@` | `true` / `false` | +| `@libpvolvar@` | `true` / `false` | +| `@lwoniomvar@` | `true` / `false` | + +--- + +## Compiler / LAPACK cross-compatibility reference + +``` +Fortran compiler │ C compiler │ Recommended LAPACK │ OpenMP runtime +─────────────────┼────────────┼────────────────────┼─────────────── +gfortran │ gcc │ OpenBLAS (default) │ libgomp +gfortran │ gcc │ MKL │ libgomp + mkl_gnu_thread +ifort / ifx │ icc / icx │ MKL ← best match │ libiomp5 / libomp +ifort / ifx │ gcc │ MKL │ libiomp5 (Intel wins link) +ifort / ifx │ icc / icx │ OpenBLAS (seq.) │ libiomp5 +``` + +**Rule of thumb:** the compiler that drives the *final link step* owns the +OpenMP runtime. With mixed toolchains, the Fortran compiler always drives the +link step here (crest is a Fortran-primary project), so use the LAPACK +threading layer that matches the *Fortran* compiler. diff --git a/CMakeLists.txt b/CMakeLists.txt index 72d773cc..62064ebd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,11 +26,15 @@ endif() # Setup the crest Project project( crest - LANGUAGES "C" "Fortran" - VERSION 3.0.2 + LANGUAGES "C" "CXX" "Fortran" + VERSION 3.1.0 DESCRIPTION "A tool for the exploration of low-energy chemical space" ) +# Apply debug flags when building in Debug mode +set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} \ + -g -O0 -fcheck=all -fbacktrace -finit-real=snan -finit-integer=-999") + # Follow GNU conventions for installing directories include(GNUInstallDirs) @@ -60,21 +64,34 @@ if(NOT TARGET "OpenMP::OpenMP_Fortran" AND WITH_OpenMP) message(STATUS "OpenMP::OpenMP_Fortran is linking the following libraries:") foreach(lib ${OpenMP_Fortran_LIBRARIES}) message(STATUS "${lib}") - endforeach() + endforeach() +endif() +# Propagate OpenMP link flags globally so subproject executables (e.g. pvol tests) +# that bypass CMake target dependencies still link the OpenMP runtime correctly. +if(WITH_OpenMP AND OpenMP_Fortran_FOUND) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_Fortran_FLAGS}") endif() # Check if we are using OpenBLAS (need a precompiler definition if yes) -if(LAPACK_LIBRARIES) - string(FIND "${LAPACK_LIBRARIES}" "openblas" _openblas_in_lapack) +# Match "libopenblas" specifically to avoid false positives from conda env +# path names that contain "openblas" (e.g. crest-gnu-openblas/lib/libgomp.so). +if(LAPACK_LIBRARIES OR BLAS_LIBRARIES) + set(_all_blas_libs "${LAPACK_LIBRARIES};${BLAS_LIBRARIES}") + string(FIND "${_all_blas_libs}" "libopenblas" _openblas_in_lapack) if(NOT _openblas_in_lapack EQUAL -1) - message(STATUS "libopenblas was found as part of LAPACK") + message(STATUS "libopenblas was found as part of LAPACK/BLAS") add_compile_definitions(WITH_OPENBLAS) endif() endif() -# Fortran unit test interface (also used by other subprojects) -if(NOT TARGET "test-drive::test-drive" AND WITH_TESTS) +# Fortran unit test interface. Imported unconditionally (not gated on +# WITH_TESTS): several subprojects build their test suites without a guard and +# pull in test-drive themselves. By resolving the bundled subprojects/test-drive +# here first, the `test-drive::test-drive` target already exists when those +# subprojects look for it, so they reuse the local copy instead of falling back +# to fetching/downloading it. +if(NOT TARGET "test-drive::test-drive") find_package("test-drive" REQUIRED) endif() @@ -86,8 +103,25 @@ endif() # tblite if(NOT TARGET "tblite::tblite" AND WITH_TBLITE) - find_package("tblite" REQUIRED) + find_package("mctc-lib" REQUIRED) + find_package("mstore" REQUIRED) + find_package("multicharge" REQUIRED) + find_package("dftd4" REQUIRED) + find_package("s-dftd3" REQUIRED) + find_package("ddx" REQUIRED) + find_package("tblite" REQUIRED) add_compile_definitions(WITH_TBLITE) + # ddX is a hard requirement of this block, so its modules are available to + # crest sources here; WITH_DDX guards the standalone solvation components. + add_compile_definitions(WITH_DDX) +endif() + +# g-xTB via tblite +if(WITH_GXTB) + if(NOT WITH_TBLITE) + message(FATAL_ERROR "WITH_GXTB requires WITH_TBLITE to be enabled") + endif() + add_compile_definitions(WITH_GXTB) endif() # GFN-FF @@ -114,6 +148,11 @@ if(NOT TARGET "lwoniom::lwoniom" AND WITH_LWONIOM) add_compile_definitions(WITH_LWONIOM) endif() +if(NOT TARGET "fmlip_relay:fmlip_relay" AND WITH_FMLIP_RELAY) + find_package("fmlip_relay" REQUIRED) + add_compile_definitions(WITH_FMLIP_RELAY) +endif() + # Sources: initialize program sources (prog) and library sources (srcs) empty set(prog) set(srcs) @@ -153,6 +192,7 @@ if(WITH_OBJECT AND NOT STATICBUILD) $<$:pvol::pvol> $<$:toml-f::toml-f> $<$:lwoniom::lwoniom> + $<$:fmlip_relay::fmlip_relay> $<$:OpenMP::OpenMP_Fortran> ) @@ -204,6 +244,7 @@ target_link_libraries( $<$:pvol::pvol> $<$:toml-f::toml-f> $<$:lwoniom::lwoniom> + $<$:fmlip_relay::fmlip_relay> $<$:-static> ) @@ -249,6 +290,7 @@ if (WITH_OBJECT AND NOT STATICBUILD) $<$:pvol::pvol> $<$:toml-f::toml-f> $<$:lwoniom::lwoniom> + $<$:fmlip_relay::fmlip_relay> ) set_target_properties( diff --git a/README.md b/README.md index 86b2065c..a42484e5 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,17 @@ The conda-forge distribution is based on a *dynamically linked* CMake/GNU build. ![CI workflow](https://github.com/crest-lab/crest/actions/workflows/build.yml/badge.svg) -Working and tested builds of CREST (mostly on Ubuntu 20.04 LTS): +Working and tested builds of CREST: -| Build System | Compiler | Linear Algebra Backend | Build type | Status | Note | -|--------------|----------|------------------------|:--------------:|:----------:|:----:| -| CMake 3.30.2 | GNU (gcc 14.1.0) | [libopenblas 0.3.27](https://anaconda.org/conda-forge/libopenblas) | dynamic | ✅ || -| CMake 3.30.2 | GNU (gcc 12.3.0) | [libopenblas-dev](https://packages.debian.org/stable/libdevel/libopenblas-dev) | static | ✅ | [![Download (GNU)](https://img.shields.io/badge/download-GNU_build_binary-green)](https://github.com/crest-lab/crest/releases/download/latest/crest-gnu-12-ubuntu-latest.tar.xz)| -| CMake 3.28.3 | [Intel (`ifort`/`icc` 2021.9.0)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html) | [MKL static (oneAPI 2023.1)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | dynamic | ⚠️ | OpenMP/MKL problem ([#285](https://github.com/crest-lab/crest/issues/285)) | -| Meson 1.2.0 | [Intel (`ifort`/`icx` 2023.1.0)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html) | [MKL static (oneAPI 2023.1)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | static | ✅ | [![Download (ifort)](https://img.shields.io/badge/download-ifort_build_binary-blue.svg)](https://github.com/crest-lab/crest/releases/download/latest/crest-intel-2023.1.0-ubuntu-latest.tar.xz) | +| Build System | Compiler | Linear Algebra Backend | Build type | Note | +|--------------|----------|------------------------|:----------:|:----:| +| CMake 3.31.6 | GNU (gcc 14.3.0, conda-forge) | [libopenblas 0.3.31](https://anaconda.org/conda-forge/libopenblas) (conda-forge) | dynamic | [![Download (GNU)](https://img.shields.io/badge/download-GNU_build_binary-green)](https://github.com/crest-lab/crest/releases/download/latest/crest-gnu-12-ubuntu-latest.tar.xz) | +| CMake 3.31.6 | GNU (gcc 14.3.0, conda-forge) | [libopenblas 0.3.31](https://anaconda.org/conda-forge/libopenblas) (conda-forge) | static | | +| CMake 3.31.6 | [Intel (`ifx`/`icx` 2025.3)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html) | [MKL (oneAPI 2025.3)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | static | | +| Meson 1.10.1 | GNU (gcc 14.3.0, conda-forge) | [libopenblas 0.3.31](https://anaconda.org/conda-forge/libopenblas) (conda-forge) | dynamic | | +| Meson 1.10.1 | GNU (gcc 14.3.0, conda-forge) | [libopenblas 0.3.31](https://anaconda.org/conda-forge/libopenblas) (conda-forge) | static | | +| Meson 1.10.1 | [Intel (`ifx`/`icx` 2025.3)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html) | [MKL (oneAPI 2025.3)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | dynamic | | +| Meson 1.10.1 | [Intel (`ifx`/`icx` 2025.3)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/toolkits.html) | [MKL (oneAPI 2025.3)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | static | [![Download (ifx)](https://img.shields.io/badge/download-ifort_build_binary-blue.svg)](https://github.com/crest-lab/crest/releases/download/latest/crest-intel-2023.1.0-ubuntu-latest.tar.xz) | Generally, subprojects should be initialized for the *default* build options, which can be done by @@ -107,8 +110,8 @@ For more information about builds including subprojects see [here](./subprojects Some basic build instructions can be found in the following dropdown tabs: - - + + + +

CMake build

@@ -126,29 +129,34 @@ make -C _build ```bash make test -C _build ``` -The `CMake` build typically requires access to shared libraries of LAPACK and OpenMP. They must be present in the library paths at compile and runtime. -Alternatively, a static build can be selected by using `-DSTATICBUILD=true` in the CMake setup step. The current static build with GNU compilers is available from the [**continous release page**](https://github.com/crest-lab/crest/releases/tag/latest). +The `CMake` build requires shared libraries of LAPACK/BLAS (e.g. OpenBLAS) and OpenMP at compile and runtime. +Alternatively, a static build can be selected by using `-DSTATICBUILD=true` in the CMake setup step. The current static build with GNU compilers is available from the [**continous release page**](https://github.com/crest-lab/crest/releases/tag/latest).
- +

meson build

-For the setup an configuration of meson see also the [meson setup](https://github.com/grimme-lab/xtb/blob/master/meson/README.adoc) page hosted at the `xtb` repository. -The chain of commands to build CREST with meson is: +For the setup and configuration of meson see also the [meson setup](https://github.com/grimme-lab/xtb/blob/master/meson/README.adoc) page hosted at the `xtb` repository. +**Intel (`ifx`/`icx`) + MKL** (recommended for static release binaries): ```bash -export FC=ifort CC=icc -meson setup _build --prefix=$PWD/_dist +source /opt/intel/oneapi/setvars.sh +meson setup _build -Dlapack=mkl --prefix=$PWD/_dist meson install -C _build ``` -The `meson` build of CREST is mainly focused on and tested with the Intel `ifort`/`icc` compilers. -When using newer versions of Intel's oneAPI, replacing `icc` with `icx` should work. Please refrain from using `ifx` instead of `ifort`, however. -When attempting to build with `gfortran` and `gcc`, add `-Dla_backend=mkl` to the meson setup command. Compatibility with the GNU compilers might be limited. We recommend the CMake build (see the corresponding section) in this instance. +**GNU (`gfortran`/`gcc`) + OpenBLAS**: +```bash +export FC=gfortran CC=gcc +meson setup _build -Dlapack=openblas --prefix=$PWD/_dist +meson install -C _build +``` -By default the `meson` build will create a **statically** linked binary.
+
--- diff --git a/assets/template/metadata.f90 b/assets/template/metadata.f90 index e9254292..3e4a5c0a 100644 --- a/assets/template/metadata.f90 +++ b/assets/template/metadata.f90 @@ -9,5 +9,7 @@ character(len=*),parameter :: gfn0var = "@gfn0var@" character(len=*),parameter :: gfnffvar = "@gfnffvar@" character(len=*),parameter :: tblitevar = "@tblitevar@" +character(len=*),parameter :: ddxvar = "@ddxvar@" character(len=*),parameter :: libpvolvar = "@libpvolvar@" character(len=*),parameter :: lwoniomvar = "@lwoniomvar@" +character(len=*),parameter :: fmliprelayvar = "@fmliprelayvar@" diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 7800906a..9878756f 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -31,11 +31,13 @@ install( # Options for enabling or disabling features option(WITH_OpenMP "Enable OpenMP support" TRUE) option(WITH_TBLITE "Enable support for tblite" TRUE) +option(WITH_GXTB "Enable g-xTB via tblite (requires WITH_TBLITE)" FALSE) option(WITH_TOMLF "Enable support for toml-f" TRUE) option(WITH_GFN0 "Enable support for GFN0-xTB" TRUE) option(WITH_GFNFF "Enable support for GFN-FF" TRUE) option(WITH_LIBPVOL "Enable support for LIBPVOL" TRUE) option(WITH_LWONIOM "Enable support for lwONIOM" TRUE) +option(WITH_FMLIP_RELAY "Enable fmlip-relay interface" TRUE) option(WITH_TESTS "Enable unit tests" TRUE) option(STATICBUILD "Attempt to link everything statically" FALSE) @@ -101,8 +103,11 @@ set(tomlfvar "${WITH_TOMLF}") set(gfn0var "${WITH_GFN0}") set(gfnffvar "${WITH_GFNFF}") set(tblitevar "${WITH_TBLITE}") +# ddX is a hard requirement of the tblite block, so it tracks WITH_TBLITE here +set(ddxvar "${WITH_TBLITE}") set(libpvolvar "${WITH_LIBPVOL}") set(lwoniomvar "${WITH_LWONIOM}") +set(fmliprelayvar "${WITH_FMLIP_RELAY}") configure_file( "${PROJECT_SOURCE_DIR}/assets/template/metadata.f90" diff --git a/config/gnu.ini b/config/gnu.ini new file mode 100644 index 00000000..f003b492 --- /dev/null +++ b/config/gnu.ini @@ -0,0 +1,16 @@ +# machine file: gnu.ini +# Usage: meson setup build --native-file cross/gnu.ini +# +# Selects the GNU toolchain explicitly. Useful when both GCC and Intel +# compilers are installed and the environment default is not what you want. + +[binaries] +c = 'gcc' +fortran = 'gfortran' +ar = 'ar' +strip = 'strip' + +[built-in options] +# Fortran standard — free-form F2018 is safe for crest +# (gfortran accepts it without an explicit flag, but being explicit helps) +fortran_args = ['-std=f2018'] diff --git a/config/intel-classic.ini b/config/intel-classic.ini new file mode 100644 index 00000000..76da11dc --- /dev/null +++ b/config/intel-classic.ini @@ -0,0 +1,18 @@ +# machine file: intel-classic.ini +# Usage: meson setup build --native-file cross/intel-classic.ini +# +# Uses the classic Intel compilers (ifort, icc). +# Note: icc was deprecated in oneAPI 2023 in favour of icx. +# For new installations prefer intel-llvm.ini instead. +# Source the Intel environment first: +# source /opt/intel/oneapi/setvars.sh + +[binaries] +c = 'icc' +fortran = 'ifort' +ar = 'ar' +strip = 'strip' + +#[built-in options] +# -warn all is ifort's equivalent of -Wall +#fortran_args = ['-warn', 'all'] diff --git a/config/intel-fortran-gnu-c.ini b/config/intel-fortran-gnu-c.ini new file mode 100644 index 00000000..2f79e2c4 --- /dev/null +++ b/config/intel-fortran-gnu-c.ini @@ -0,0 +1,25 @@ +# machine file: intel-fortran-gnu-c.ini +# Usage: meson setup build --native-file cross/intel-fortran-gnu-c.ini +# +# Mixes Intel Fortran (ifx) with GNU C (gcc). +# This is a common HPC pattern where only the Fortran compiler is licensed +# as Intel but C utilities default to GCC. +# +# IMPORTANT — OpenMP runtime compatibility: +# ifx links against Intel's libomp (libiomp5). +# gcc links against GNU's libgomp. +# The two runtimes must NOT both be loaded in the same process. +# Resolution: let ifx drive the final link step so it picks up libomp. +# Do NOT pass -fopenmp to gcc translation units; instead use +# -Dopenmp=true which the build system applies only to Fortran sources. +# +# IMPORTANT — MKL threading layer: +# With this mixed toolchain, use mkl_intel_thread (not mkl_gnu_thread) +# because the Fortran driver (ifx/ifort) owns the OpenMP runtime. +# Set: -Dlapack=mkl + +[binaries] +c = 'gcc' +fortran = 'ifx' +ar = 'ar' +strip = 'strip' diff --git a/config/intel-llvm.ini b/config/intel-llvm.ini new file mode 100644 index 00000000..3865e9bb --- /dev/null +++ b/config/intel-llvm.ini @@ -0,0 +1,15 @@ +# machine file: intel-llvm.ini +# Usage: meson setup build --native-file cross/intel-llvm.ini +# +# Uses the Intel LLVM-based compilers (ifx, icx) from oneAPI 2023+. +# Source the Intel environment first: +# source /opt/intel/oneapi/setvars.sh + +[binaries] +c = 'icx' +fortran = 'ifx' +ar = 'ar' +strip = 'strip' + +#[built-in options] +#fortran_args = ['-warn', 'all'] diff --git a/config/meson.build b/config/meson.build index 99616924..45554499 100644 --- a/config/meson.build +++ b/config/meson.build @@ -1,299 +1,256 @@ # This file is part of crest. # SPDX-Identifier: LGPL-3.0-or-later # -# crest is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# Included from the root meson.build via subdir('config'). +# Runs in the same variable scope as the root — all variables set here +# are visible in the root after this file returns. # -# crest is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with crest. If not, see . +# Responsibilities: +# - Compiler identification and friendly display names +# - Per-compiler build flags (release and debug) +# - Static-binary link arguments +# - Metadata header generation (crest_metadata.fh) +# - inc_dirs (build include path carrying the generated header) +# ═══════════════════════════════════════════════════════════════════════════════ +# Compiler identification +# ═══════════════════════════════════════════════════════════════════════════════ fc = meson.get_compiler('fortran') cc = meson.get_compiler('c') -fc_id = fc.get_id() + +fc_id = fc.get_id() # 'gcc' | 'intel' | 'intel-llvm' | ... cc_id = cc.get_id() -if fc.get_id() != cc.get_id() - warning('FC and CC are not from the same vendor') +_fc_name_map = { + 'gcc' : 'GNU Fortran', + 'intel' : 'Intel Fortran (Classic)', + 'intel-llvm' : 'Intel Fortran (LLVM/ifx)', + 'flang' : 'Flang', + 'flang-new' : 'Flang-new', +} +_cc_name_map = { + 'gcc' : 'GNU C', + 'intel' : 'Intel C (Classic)', + 'intel-llvm' : 'Intel C (LLVM/icx)', + 'clang' : 'Clang', +} + +fcid_str = _fc_name_map.get(fc_id, fc_id) +ccid_str = _cc_name_map.get(cc_id, cc_id) + +if fc_id not in ['gcc', 'intel', 'intel-llvm'] + warning( + 'Unsupported Fortran compiler "@0@". Proceeding, but results may be wrong.' + .format(fc_id), + ) endif -## ========================================= ## -## Compiler specific default arguments -## ========================================= ## +# ═══════════════════════════════════════════════════════════════════════════════ +# Compiler flags +# ═══════════════════════════════════════════════════════════════════════════════ +_is_debug = get_option('buildtype') in ['debug', 'debugoptimized'] + +# --- GNU (gfortran / gcc) ----------------------------------------------------- if fc_id == 'gcc' add_project_arguments( '-ffree-line-length-none', '-fbacktrace', - '-Wno-maybe-uninitialized', - '-Wno-uninitialized', - '-Wno-unused-variable', - '-Wno-unused-dummy-argument', - '-Wno-unused-function', - language: 'fortran', - ) -elif fc_id == 'intel' - add_project_link_arguments( - '-Wl,--allow-multiple-definition', - language: 'fortran', + language : 'fortran', ) - add_global_link_arguments( - '-Wl,--allow-multiple-definition', - language: 'fortran', - ) - add_project_arguments( - '-traceback', - language: 'fortran', - ) - add_project_arguments( - '-DLINUX', - language: 'c', - ) -elif fc_id == 'pgi' or fc_id == 'nvidia_hpc' + if _is_debug + add_project_arguments( + '-fcheck=all', + '-ffpe-trap=invalid,zero,overflow', + '-fbounds-check', + '-finit-real=snan', + '-finit-integer=-999', + '-Wall', + '-Wextra', + language : 'fortran', + ) + endif +endif + +# --- Intel classic (ifort / icc) ----------------------------------------------- +if fc_id == 'intel' add_project_arguments( - '-Mbackslash', - '-Mallocatable=03', + '-r8', # treat all REAL as REAL(8) + '-align', 'array64byte', '-traceback', - language: 'fortran', + language : 'fortran', ) -elif fc_id == 'flang' - add_project_arguments( - '-Mbackslash', - '-Mallocatable=03', - language: 'fortran', - ) -endif - -add_project_arguments('-D_Float128=__float128', language: 'c') - - -## ========================================= ## -## build type option arguments -## ========================================= ## -if ( get_option('default_library') == 'static') - message('Static linking selected') - add_project_link_arguments('-static', language: 'fortran') - add_project_link_arguments('-static', language: 'c') # icc will do linking -endif - - -## ========================================= ## -## OpenMP -## ========================================= ## -if get_option('openmp') - omp_dep = dependency('openmp', required: fc.get_id() != 'intel') - if not omp_dep.found() - omp_dep = declare_dependency( - compile_args: '-qopenmp', - link_args: '-fopenmp', + if _is_debug + add_project_arguments( + '-check', 'all', + '-fpe0', + language : 'fortran', ) endif - exe_deps += omp_dep endif -## ======================================== ## -## Linear Algebra Libraries -## ======================================== ## -la_backend = get_option('la_backend') -message('Linear algebra backend: '+get_option('la_backend')) -if la_backend == 'mkl' or la_backend == 'mkl-static' - add_project_arguments('-DWITH_MKL', language: 'fortran') - if la_backend == 'mkl-static' - add_project_link_arguments('-static', language: 'fortran') - add_project_link_arguments('-static', language: 'c') # icc will do linking - endif - - if get_option('default_library') == 'shared' - mkl_rt_dep = cc.find_library('mkl_rt', required: true) - exe_deps += mkl_rt_dep - else - if fc.get_id() == 'gcc' - libmkl_exe = [cc.find_library('mkl_gf_lp64')] - if get_option('openmp') - libmkl_exe += cc.find_library('mkl_gnu_thread') - endif - elif fc.get_id() == 'intel' or fc.get_id() == 'intel-cl' - libmkl_exe = [cc.find_library('mkl_intel_lp64')] - if get_option('openmp') - libmkl_exe += cc.find_library('mkl_intel_thread') - endif - elif fc.get_id() == 'pgi' or fc.get_id() == 'nvidia_hpc' - libmkl_exe = [cc.find_library('mkl_intel_lp64')] - if get_option('openmp') - libmkl_exe += cc.find_library('mkl_pgi_thread') - endif - endif - if not get_option('openmp') - libmkl_exe += cc.find_library('mkl_sequential') - endif - libmkl_exe += cc.find_library('mkl_core') - exe_deps += libmkl_exe - endif - -elif la_backend == 'mkl-rt' - add_project_arguments('-DWITH_MKL', language: 'fortran') - - mkl_rt_dep = fc.find_library('mkl_rt', required: true) - if fc.get_id() == 'intel' - exe_deps += fc.find_library('ifcore') - endif - - exe_deps += mkl_rt_dep - -elif la_backend == 'openblas' - # search for OpenBLAS - blas_dep = dependency('openblas', required: false) - if not blas_dep.found() - blas_dep = fc.find_library('openblas', required: true) - endif - exe_deps += blas_dep - # some OpenBLAS versions can provide lapack, check if we can find dsygvd - openblas_provides_lapack = fc.links( - 'external dsygvd; call dsygvd(); end', - dependencies: blas_dep, +# --- Intel LLVM (ifx / icx) ---------------------------------------------------- +if fc_id == 'intel-llvm' + add_project_arguments( + '-r8', + '-align', 'array64byte', + language : 'fortran', ) - # otherwise we fall back to LAPACK - if not openblas_provides_lapack - lapack_dep = dependency('lapack', required: false) - if not lapack_dep.found() - lapack_dep = fc.find_library('lapack', required: true) - endif - exe_deps += lapack_dep + if _is_debug + add_project_arguments( + '-traceback', + '-check', 'all', + '-fpe0', + language : 'fortran', + ) endif +endif -elif la_backend == 'custom' - foreach lib: get_option('custom_libraries') - exe_deps += fc.find_library(lib) - endforeach +static_build = get_option('static') -else - # Find BLAS (usually netlib, but in conda also OpenBLAS/MKL) - blas_dep = dependency('blas', required: false) - if not blas_dep.found() - blas_dep = fc.find_library('blas', required: true) - endif - exe_deps += blas_dep - # Find LAPACK (usually netlib, but in conda also MKL) - lapack_dep = dependency('lapack', required: false) - if not lapack_dep.found() - lapack_dep = fc.find_library('lapack', required: true) +# Static subproject linking: selects static archives for subproject deps +# (tblite, gfn0, toml-f, …) without the fully-static `-static` syslib linking. +# A fully static build (-Dstatic) implies it. +link_deps_static = static_build or get_option('static-deps') + +if fc_id == 'intel-llvm' + ## -lifport : POSIX intrinsics (getcwd, chdir, …) not auto-linked by ifx. + ## In static builds -static-intel already bundles libifport.a, so + ## adding a bare -lifport here would cause the linker to resolve it + ## to the shared library (before -static-intel takes effect). + ## libgcc_s : was previously added to supply _Unwind_* symbols for libifcore's + ## signal handler. No longer needed since signal registration was + ## moved to crest_install_signal() via ISO_C_BINDING, bypassing + ## libifcore's handler entirely. + _libgcc_s = run_command('gcc', '-print-file-name=libgcc_s.so', + check : false).stdout().strip() + _gcc_s_link = (_libgcc_s != 'libgcc_s.so') ? [_libgcc_s] : [] + if _gcc_s_link.length() == 0 + warning('intel-llvm: libgcc_s.so not found — _Unwind_* symbols will be NULL, ' + + 'Intel Fortran runtime will SIGSEGV at startup.') endif - exe_deps += lapack_dep + # For static builds libgcc_s.so is not needed (signal handler uses ISO_C_BINDING + # and no longer goes through libifcore's _Unwind_* path), and adding an explicit + # .so path would defeat -static. Only add it for dynamic builds. + _lifport_arg = static_build ? [] : ['-lifport'] + _gcc_s_arg = static_build ? [] : _gcc_s_link + add_project_link_arguments(_lifport_arg + _gcc_s_arg, language : 'fortran') endif +# ═══════════════════════════════════════════════════════════════════════════════ +# Static-binary link arguments +# ═══════════════════════════════════════════════════════════════════════════════ +# Injected globally so they propagate to every link step (library + executable). +# See BUILD.md for notes on required system .a archives. -## ========================================= ## -## External subprojects -## ========================================= ## +if static_build + if fc_id == 'gcc' + # Full static: bundles glibc, libgfortran, libgomp, libopenblas/lapack. + # Requires libgfortran-static and glibc-static system packages. + add_project_link_arguments('-static', language : 'fortran') + add_project_link_arguments('-static', language : 'c') -# GFN1-xTB and GFN2-xTB via TBLITE -if get_option('WITH_TBLITE') - add_project_arguments('-DWITH_TBLITE', language: 'fortran') - tblite_dep = dependency( - 'tblite', - version: '>=0.2', - fallback: ['tblite', 'tblite_dep'], - default_options: ['default_library=static', 'api=false'], - ) - exe_deps += tblite_dep -endif + elif fc_id == 'intel' + # -static-intel : statically links Intel runtime (ifcore, imf, svml) + # -qopenmp-link=static : statically links Intel OpenMP (libiomp5) + # -static : forces all -l flags (including MKL) to resolve to .a + add_project_link_arguments( + '-static-intel', + '-qopenmp-link=static', + '-static', + language : 'fortran', + ) -# TOML-F -if get_option('WITH_TOMLF') - add_project_arguments('-DWITH_TOMLF', language: 'fortran') - tomlf_dep = dependency( - 'toml-f', - version: '>=0.2.0', - fallback: ['toml-f', 'tomlf_dep'], - default_options: ['default_library=static'], - ) - exe_deps += tomlf_dep + elif fc_id == 'intel-llvm' + # -static only: with LIBRARY_PATH including the Intel compiler lib dir, + # the linker finds libifcore.a/libimf.a/libiomp5.a directly. + # -static-intel + -qopenmp-link=static are NOT used because combined with + # -fopenmp link args from non-Intel subprojects they cause libifcore.a vs + # libifcoremt.a duplicate-symbol errors (ifx 2025.3). Allow-multiple- + # definition suppresses the remaining conflict (same pattern as ifort above). + add_project_link_arguments( + '-static', + '-Wl,--allow-multiple-definition', + language : 'fortran', + ) + endif endif -# GFN0-xTB -if get_option('WITH_GFN0') - add_project_arguments('-DWITH_GFN0', language: 'fortran') - gfn0_dep = dependency( - 'gfn0', -# version: '>=0.2', - fallback: ['gfn0', 'gfn0_dep'], - default_options: ['default_library=static','with_gbsa=true'], - ) - exe_deps += gfn0_dep +# ═══════════════════════════════════════════════════════════════════════════════ +# Metadata: fill assets/template/metadata.f90 → /crest_metadata.fh +# ═══════════════════════════════════════════════════════════════════════════════ +_git = find_program('git', required : false) +_commit = 'unknown-commit' +if _git.found() + _r = run_command(_git, 'show', '-s', '--format=%h', check : false) + if _r.returncode() == 0 + _commit = _r.stdout().strip() + endif endif -# GFN-FF -if get_option('WITH_GFNFF') - add_project_arguments('-DWITH_GFNFF', language: 'fortran') - gfnff_dep = dependency( - 'gfnff', - fallback: ['gfnff', 'gfnff_dep'], - default_options: ['default_library=static','with_gbsa=true'], - ) - exe_deps += gfnff_dep +_py = find_program('python3', 'python', required : false) +_date = 'unknown-date' +if _py.found() + _r = run_command( + _py, '-c', + 'import datetime; print(datetime.datetime.now().strftime("%a, %d %B %H:%M:%S, %m/%d/%Y"))', + check : false, + ) + if _r.returncode() == 0 + _date = _r.stdout().strip() + endif endif - -# LIBPVOL -if get_option('WITH_LIBPVOL') - add_project_arguments('-DWITH_LIBPVOL', language: 'fortran') - libpvol_dep = dependency( - 'libpvol', - fallback: ['libpvol', 'libpvol_dep'], - default_options: ['default_library=static'], - ) - exe_deps += libpvol_dep +_hostname = 'unknown-host' +_hostname_prog = find_program('hostname', required : false) +if _hostname_prog.found() + _r = run_command(_hostname_prog, check : false) + if _r.returncode() == 0 + _hostname = _r.stdout().strip() + endif endif - -# lwONIOM -if get_option('WITH_LWONIOM') - add_project_arguments('-DWITH_LWONIOM', language: 'fortran') - lwoniom_dep = dependency( - 'lwoniom', - fallback: ['lwoniom', 'lwoniom_dep'], - default_options: ['default_library=static'], - ) - exe_deps += lwoniom_dep +_sh = find_program('sh', required : false) +_user = 'unknown' +if _sh.found() + _r = run_command( + _sh, '-c', 'printf "%s" "${USER:-${USERNAME:-unknown}}"', + check : false, + ) + if _r.returncode() == 0 and _r.stdout().strip() != '' + _user = _r.stdout().strip() + endif endif +_conf = configuration_data() +_conf.set('version', meson.project_version()) +_conf.set('commit', _commit) +_conf.set('date', _date) +_conf.set('author', _user) +_conf.set('origin', _hostname) +_conf.set('fcid', fcid_str) +_conf.set('fcver', fc.version()) +_conf.set('ccid', ccid_str) +_conf.set('ccver', cc.version()) +_conf.set('bsystem', 'meson ' + meson.version()) +# The with_* booleans are set in the root after optional deps are resolved; +# use string placeholders here and let the root patch them in if needed. +# In practice configure_file runs at configure-time so these are all known +# by the time the root calls subdir('config') — see note below. +# +# NOTE: the with_* variables (with_tomlf, with_tblite, …) are set in the root +# meson.build AFTER this file runs, so they are not yet available here. +# The root therefore calls configure_file() itself using this _conf object +# after appending the feature flags. We expose _conf so the root can do that. +_conf.set('tomlfvar', 'false') +_conf.set('gfn0var', 'false') +_conf.set('gfnffvar', 'false') +_conf.set('tblitevar', 'false') +_conf.set('libpvolvar', 'false') +_conf.set('lwoniomvar', 'false') +_conf.set('fmliprelayvar', 'false') -## ========================================= ## -## populate the data for crest_metadata.fh -## ========================================= ## -commit = 'unknown commit' -git = find_program('git', required: false) -if git.found() - git_commit = run_command(git, 'show', '-s', '--format=%h',check:true) - if git_commit.returncode() == 0 - commit = git_commit.stdout().strip() - endif -endif -# create configuration data -config = configuration_data({ - 'name': meson.project_name(), - 'version': meson.project_version(), - 'description': 'Conformer Rotamer Ensemble Sampling Tool', - 'commit': commit, - 'date': run_command('date',check:true).stdout(). strip(), - 'author': run_command('id','-u','-n', check:true).stdout().strip(), - 'origin': run_command('hostname', check:true).stdout().strip(), - 'fcid': fc.get_id(), - 'fcver': fc.version(), - 'ccid': cc.get_id(), - 'ccver': cc.version(), - 'bsystem': 'meson '+meson.version(), - 'tomlfvar': get_option('WITH_TOMLF'), - 'gfn0var': get_option('WITH_GFN0'), - 'gfnffvar': get_option('WITH_GFNFF'), - 'tblitevar': get_option('WITH_TBLITE'), - 'libpvolvar': get_option('WITH_LIBPVOL'), - 'lwoniomvar': get_option('WITH_LWONIOM'), -}) +# Expose _conf to the root so it can set the with_* keys and then call +# configure_file(). The root owns the final configure_file() call. +metadata_conf = _conf diff --git a/config/modules/Findddx.cmake b/config/modules/Findddx.cmake new file mode 100644 index 00000000..e5d06b3c --- /dev/null +++ b/config/modules/Findddx.cmake @@ -0,0 +1,44 @@ +# This file is part of crest. +# SPDX-Identifier: LGPL-3.0-or-later +# +# crest is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# crest is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with crest. If not, see . + +set(_lib "ddx") +set(_pkg "DDX") +set(_url "https://github.com/ddsolvation/ddX") +set(_branch "4d79e3d9caeae5e602683572a71cb550414f9b09") + +if(NOT DEFINED "${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") + +# ddX builds its examples, tests and driver by default; only the library is +# needed here. The options are forced off for the subproject build. +set(EXAMPLES OFF CACHE BOOL "Disable ddx examples" FORCE) +set(TESTS OFF CACHE BOOL "Disable ddx tests" FORCE) +set(WARNING_FLAGS OFF CACHE BOOL "Disable ddx warning flags" FORCE) +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") + +set(found FALSE) +if(TARGET "ddx::ddx") + set (found TRUE) +endif() +message(STATUS "Found ddx: ${found}") + +unset(_lib) +unset(_pkg) +unset(_url) +unset(_branch) diff --git a/config/modules/Finddftd4.cmake b/config/modules/Finddftd4.cmake new file mode 100644 index 00000000..6e8e58c0 --- /dev/null +++ b/config/modules/Finddftd4.cmake @@ -0,0 +1,43 @@ +# This file is part of crest. +# SPDX-Identifier: LGPL-3.0-or-later +# +# crest is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# crest is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with crest. If not, see . + +set(_lib "dftd4") +set(_pkg "DFTD4") +set(_url "https://github.com/dftd4/dftd4") +set(_branch "6e1f59c3f39d919a2dbef0601d2576727c8b30e8") + +if(NOT DEFINED "${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") + +set(temp_with_tests ${WITH_TESTS}) # Save the current value of WITH_TESTS +set(WITH_TESTS FALSE CACHE BOOL "Temporarily disable tests for the dftd4 subproject" FORCE) +set(WITH_API FALSE) +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") + +set(found FALSE) +if(TARGET "dftd4::dftd4") + set (found TRUE) +endif() +message(STATUS "Found dftd4: ${found}") + +set(WITH_TESTS ${temp_with_tests} CACHE BOOL "Enable tests for the main project" FORCE) + +unset(_lib) +unset(_pkg) +unset(_url) diff --git a/config/modules/Findfmlip_relay.cmake b/config/modules/Findfmlip_relay.cmake new file mode 100644 index 00000000..384832b4 --- /dev/null +++ b/config/modules/Findfmlip_relay.cmake @@ -0,0 +1,26 @@ +set(_lib "fmlip_relay") +set(_pkg "FMLIP_RELAY") +set(_url "https://github.com/pprcht/fmlip-relay") +set(_branch "06608dea8b7deb53f4a25630f6dcb8be0cdc6e76") + +# Discovery method order can be overridden by the parent project, e.g.: +# set(FMLIP_RELAY_FIND_METHOD "subproject" "cmake") +if(NOT DEFINED "${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") +endif() + +# Reuse whichever utils macro your main project already provides. +# Replace "crest-utils" with the actual name if yours differs. +include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") + +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") + +set(found FALSE) +if(TARGET "fmlip_relay::fmlip_relay") + set(found TRUE) +endif() +message(STATUS "Found fmlip_relay: ${found}") + +unset(_lib) +unset(_pkg) +unset(_url) diff --git a/config/modules/Findgfn0.cmake b/config/modules/Findgfn0.cmake index a44f8c8d..af60fe62 100644 --- a/config/modules/Findgfn0.cmake +++ b/config/modules/Findgfn0.cmake @@ -17,6 +17,7 @@ set(_lib "gfn0") set(_pkg "GFN0") set(_url "https://github.com/pprcht/gfn0") +set(_branch "4fbf39bf6790eaef74e06fbb49b98f676db66a2b") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") @@ -24,7 +25,7 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") set (found FALSE) if(TARGET "gfn0::gfn0") diff --git a/config/modules/Findgfnff.cmake b/config/modules/Findgfnff.cmake index faf16024..7bc54ed2 100644 --- a/config/modules/Findgfnff.cmake +++ b/config/modules/Findgfnff.cmake @@ -17,6 +17,7 @@ set(_lib "gfnff") set(_pkg "GFNFF") set(_url "https://github.com/pprcht/gfnff") +set(_branch "b846775c42526a769242be5607d0cde6983764d1") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") @@ -24,7 +25,7 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") set(found FALSE) if(TARGET "gfnff::gfnff") diff --git a/config/modules/Findlibpvol.cmake b/config/modules/Findlibpvol.cmake index 2e756b9e..407a55f9 100644 --- a/config/modules/Findlibpvol.cmake +++ b/config/modules/Findlibpvol.cmake @@ -16,7 +16,8 @@ set(_lib "pvol") set(_pkg "PVOL") -set(_url "https://github.com/neudecker-group/libpvol.git") +set(_url "https://github.com/pprcht/libpvol.git") +set(_branch "010bddaa8766a03e023a977aeebaa6454629c947") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf" ) @@ -24,7 +25,7 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") set(found FALSE) if(TARGET "pvol::pvol") diff --git a/config/modules/Findlwoniom.cmake b/config/modules/Findlwoniom.cmake index 1f825b13..de073c21 100644 --- a/config/modules/Findlwoniom.cmake +++ b/config/modules/Findlwoniom.cmake @@ -17,6 +17,7 @@ set(_lib "lwoniom") set(_pkg "LWONIOM") set(_url "https://github.com/crest-lab/lwoniom") +set(_branch "ab66c7ebc3066328a8fc313dc783aec9b773cad2") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") @@ -24,7 +25,7 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") set(found FALSE) if(TARGET "lwoniom::lwoniom") diff --git a/config/modules/Findmctc-lib.cmake b/config/modules/Findmctc-lib.cmake index 1ba16d81..4d704175 100644 --- a/config/modules/Findmctc-lib.cmake +++ b/config/modules/Findmctc-lib.cmake @@ -1,47 +1,26 @@ -# This file is part of crest. -# SPDX-Identifier: LGPL-3.0-or-later -# -# crest is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# crest is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with crest. If not, see . - set(_lib "mctc-lib") set(_pkg "MCTCLIB") set(_url "https://github.com/grimme-lab/mctc-lib") +set(_branch "8cd0cb4489537fd28bfb2e8094f1647f3e0da284") +# Discovery method order can be overridden by the parent project, e.g.: +# set(mctc-lib_FIND_METHOD "subproject" "cmake") if(NOT DEFINED "${_pkg}_FIND_METHOD") - if(DEFINED "${PROJECT_NAME}-dependency-method") - set("${_pkg}_FIND_METHOD" "${${PROJECT_NAME}-dependency-method}") - else() - set("${_pkg}_FIND_METHOD" "cmake" "pkgconf" "subproject" "fetch") - endif() - set("_${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") endif() +# Reuse whichever utils macro your main project already provides. +# Replace "crest-utils" with the actual name if yours differs. include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") +set(found FALSE) if(TARGET "mctc-lib::mctc-lib") - set (found TRUE) -else() - set (found FALSE) + set(found TRUE) endif() -message("-- Found mctc-lib: ${found}") +message(STATUS "Found mctc-lib: ${found}") -if(DEFINED "_${_pkg}_FIND_METHOD") - unset("${_pkg}_FIND_METHOD") - unset("_${_pkg}_FIND_METHOD") -endif() unset(_lib) unset(_pkg) unset(_url) diff --git a/config/modules/Findmstore.cmake b/config/modules/Findmstore.cmake new file mode 100644 index 00000000..e857fe9e --- /dev/null +++ b/config/modules/Findmstore.cmake @@ -0,0 +1,26 @@ +set(_lib "mstore") +set(_pkg "MSTORE") +set(_url "https://github.com/grimme-lab/mstore") +set(_branch "10a3437b3634dd4464557580ae36c1ed72535f6c") + +# Discovery method order can be overridden by the parent project, e.g.: +# set(mstore_FIND_METHOD "subproject" "cmake") +if(NOT DEFINED "${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") +endif() + +# Reuse whichever utils macro your main project already provides. +# Replace "crest-utils" with the actual name if yours differs. +include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") + +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") + +set(found FALSE) +if(TARGET "mstore::mstore") + set(found TRUE) +endif() +message(STATUS "Found mstore: ${found}") + +unset(_lib) +unset(_pkg) +unset(_url) diff --git a/config/modules/Findmulticharge.cmake b/config/modules/Findmulticharge.cmake new file mode 100644 index 00000000..45b4348c --- /dev/null +++ b/config/modules/Findmulticharge.cmake @@ -0,0 +1,26 @@ +set(_lib "multicharge") +set(_pkg "MULTICHARGE") +set(_url "https://github.com/grimme-lab/multicharge") +set(_branch "6a5d63f9e9e29dcf13cc47cc27f33bf9015681bf") + +# Discovery method order can be overridden by the parent project, e.g.: +# set(multicharge_FIND_METHOD "subproject" "cmake") +if(NOT DEFINED "${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") +endif() + +# Reuse whichever utils macro your main project already provides. +# Replace "crest-utils" with the actual name if yours differs. +include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") + +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") + +set(found FALSE) +if(TARGET "multicharge::multicharge") + set(found TRUE) +endif() +message(STATUS "Found multicharge: ${found}") + +unset(_lib) +unset(_pkg) +unset(_url) diff --git a/config/modules/Finds-dftd3.cmake b/config/modules/Finds-dftd3.cmake new file mode 100644 index 00000000..e6c2561c --- /dev/null +++ b/config/modules/Finds-dftd3.cmake @@ -0,0 +1,43 @@ +# This file is part of crest. +# SPDX-Identifier: LGPL-3.0-or-later +# +# crest is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# crest is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with crest. If not, see . + +set(_lib "s-dftd3") +set(_pkg "SDFTD3") +set(_url "https://github.com/dftd3/simple-dftd3") +set(_branch "6f0b06fbfa8653a23ca55c453772ce3af4420706") + +if(NOT DEFINED "${_pkg}_FIND_METHOD") + set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") +endif() + +include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") + +set(temp_with_tests ${WITH_TESTS}) # Save the current value of WITH_TESTS +set(WITH_TESTS FALSE CACHE BOOL "Temporarily disable tests for the s-dftd3 subproject" FORCE) +set(WITH_API FALSE) +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") + +set(found FALSE) +if(TARGET "s-dftd3::s-dftd3") + set (found TRUE) +endif() +message(STATUS "Found s-dftd3: ${found}") + +set(WITH_TESTS ${temp_with_tests} CACHE BOOL "Enable tests for the main project" FORCE) + +unset(_lib) +unset(_pkg) +unset(_url) diff --git a/config/modules/Findtblite.cmake b/config/modules/Findtblite.cmake index 6789f3d1..7134650d 100644 --- a/config/modules/Findtblite.cmake +++ b/config/modules/Findtblite.cmake @@ -17,7 +17,7 @@ set(_lib "tblite") set(_pkg "TBLITE") set(_url "https://github.com/tblite/tblite") -set(_branch "xtb_solvation") +set(_branch "5dc1c97858eb585111affc6627783800bafb87cc") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") @@ -28,6 +28,7 @@ include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") set(temp_with_tests ${WITH_TESTS}) # Save the current value of WITH_TESTS set(WITH_TESTS FALSE CACHE BOOL "Temporarily disable tests for the tblite subproject" FORCE) set(WITH_API FALSE) +set(WITH_DDX TRUE) crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") set(found FALSE) diff --git a/config/modules/Findtest-drive.cmake b/config/modules/Findtest-drive.cmake index ed96bae6..8bfd3727 100644 --- a/config/modules/Findtest-drive.cmake +++ b/config/modules/Findtest-drive.cmake @@ -17,6 +17,7 @@ set(_lib "test-drive") set(_pkg "TEST-DRIVE") set(_url "https://github.com/fortran-lang/test-drive") +set(_branch "e8b7ca492c647ed384c9845d2caed04192af7d02") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") @@ -24,7 +25,11 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +# test-drive is imported unconditionally (see CMakeLists.txt) so that other +# subprojects reuse the bundled copy. Disable its own test suite here so the +# import only contributes the library target, never its self-tests. +set(TEST_DRIVE_BUILD_TESTING OFF CACHE BOOL "Disable test-drive self-tests" FORCE) +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") if(TARGET "${_lib}::${_lib}") set (found TRUE) diff --git a/config/modules/Findtoml-f.cmake b/config/modules/Findtoml-f.cmake index ca1da4cb..1258d2e2 100644 --- a/config/modules/Findtoml-f.cmake +++ b/config/modules/Findtoml-f.cmake @@ -17,6 +17,7 @@ set(_lib "toml-f") set(_pkg "TOML-F") set(_url "https://github.com/toml-f/toml-f") +set(_branch "d5e92701d28b647323ce05ecbcbf302dd19792f7") if(NOT DEFINED "${_pkg}_FIND_METHOD") set("${_pkg}_FIND_METHOD" "subproject" "cmake" "fetch" "pkgconf") @@ -24,7 +25,11 @@ endif() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") -crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +# toml-f gates its test suite on the standard CTest BUILD_TESTING flag +# (include(CTest) defaults it to ON); pre-set it OFF so the subproject skips +# its tests. crest's own tests use WITH_TESTS and are unaffected. +set(BUILD_TESTING OFF CACHE BOOL "Disable subproject test suites" FORCE) +crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}" "${_branch}") if(TARGET "toml-f::toml-f") set (found TRUE) diff --git a/config/modules/crest-utils.cmake b/config/modules/crest-utils.cmake index 340d26e4..c8475a0c 100644 --- a/config/modules/crest-utils.cmake +++ b/config/modules/crest-utils.cmake @@ -97,8 +97,10 @@ macro( "${${_pkg_uc}_BINARY_DIR}" ) - add_library("${package}::${package}" INTERFACE IMPORTED) - target_link_libraries("${package}::${package}" INTERFACE "${package}") + if(NOT TARGET "${package}::${package}") + add_library("${package}::${package}" INTERFACE IMPORTED) + target_link_libraries("${package}::${package}" INTERFACE "${package}") + endif() # We need the module directory in the subproject before we finish the configure stage if(NOT EXISTS "${${_pkg_uc}_BINARY_DIR}/include") @@ -122,8 +124,10 @@ macro( ) FetchContent_MakeAvailable("${_pkg_lc}") - add_library("${package}::${package}" INTERFACE IMPORTED) - target_link_libraries("${package}::${package}" INTERFACE "${package}") + if(NOT TARGET "${package}::${package}") + add_library("${package}::${package}" INTERFACE IMPORTED) + target_link_libraries("${package}::${package}" INTERFACE "${package}") + endif() # We need the module directory in the subproject before we finish the configure stage FetchContent_GetProperties("${_pkg_lc}" SOURCE_DIR "${_pkg_uc}_SOURCE_DIR") diff --git a/docs/man/crest.adoc b/docs/man/crest.adoc index b59a4d1b..75b41ce2 100644 --- a/docs/man/crest.adoc +++ b/docs/man/crest.adoc @@ -14,10 +14,10 @@ // You should have received a copy of the GNU Lesser General Public License // along with crest. If not, see . = crest(1) -P.Pracht; S.Grimme; Universitaet Bonn, MCTC +P.Pracht; S.Grimme; C.Bannwarth; F.Bohle; S.Ehlert; G.Feldmann; J.Gorges; C.Plett; S.Spicher; P.Steinbach; P.Wesolowski; F.Zeller :doctype: manpage // This attribute should be set from the build system: -:release-version: +:release-version: :man manual: User Commands :man source: Crest {release-version} :page-layout: base @@ -28,352 +28,584 @@ crest - Conformer-Rotamer Ensemble Sampling Tool based on the GFN methods == SYNOPSIS -*crest* [_INPUT_] [_OPTION_]... +*crest* [_INPUT_] [_OPTIONS_]... + +*crest* *--input* _file.toml_ == DESCRIPTION -Conformer-Rotamer Ensemble Sampling Tool based on the GFN methods. +CREST is a conformer/rotamer ensemble sampling tool that interfaces with +various quantum chemistry backends to explore molecular potential energy +surfaces. +It supports conformational searches, protonation/tautomer workflows, solvation +cluster growth, mass-spectral fragmentation, and various standalone utility +tasks. -Using the xTB program. Compatible with xTB version 6.4.0. +The _INPUT_ argument can be a coordinate file in TM (*coord*, Bohr) or +Xmol (*\*.xyz*, Angstrom) format. +If omitted, CREST searches the working directory for a file named *coord*. +Versions 3.0 and later also accept a TOML input file either as the first +positional argument or via *--input*. .Cite work conducted with this code as [quote] ---- P. Pracht, F. Bohle, S. Grimme, PCCP, 2020, 22, 7169-7192. - - and S. Grimme, JCTC, 2019, 15, 2847-2862. - - with help from: - F.Bohle, S.Ehlert, S.Grimme, P.Pracht + S. Grimme, JCTC, 2019, 15, 2847-2862. + P. Pracht et al., J. Chem. Phys., 2024, 160, 114110. ---- -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +For QCG calculations additionally cite: + +[quote] +---- + S. Spicher, C. Plett, P. Pracht, A. Hansen, S. Grimme, + JCTC, 2022, 18 (5), 3174-3189. + C. Plett, S. Grimme, + Angew. Chem. Int. Ed. 2023, 62, e202214477. +---- == OPTIONS -The FIRST argument CAN be a coordinate file in the TM **(coord, Bohr)** or Xmol **(*.xyz, Ang.)** format. -If no such file is present as the first argument **crest** will automatically search for a file called "`**coord**`" in the TM format. +=== Run modes -=== General and technical options +*--sp*:: + Single-point energy calculation. -*-v1*:: - Use the MF-MD-GC workflow. - (_OUTDATED_) +*--opt*, *--optimize*:: + Geometry optimization. -*-v2*:: - Use the MTD-GC workflow. - (_OUTDATED_) +*--hess*, *--numhess*:: + Numerical Hessian / vibrational frequencies. -*-v3* (or *-v2i*):: - Use the iMTD-GC workflow. - [_default_] +*--md*, *--dynamics*:: + Stand-alone molecular dynamics simulation. -*-v4*:: - Use the iMTD-sMTD workflow. +*--v3*, *--v2i*:: + iMTD-GC iterative conformational search. + [_default_ conformer search] -*-entropy*:: - The same workflow as with "`**-v4**`", specialized for the calculation of conformational entropy. +*--v4*, *--entropy*:: + iMTD-sMTD entropy-focused conformational search. -*-xnam* _bin_:: - Specify name of the **xtb**(1) binary that should be used. +*--mdopt* [_file_]:: + Optimize every structure in an ensemble file. -*-niceprint*:: - Progress bar printout for optimizations. +*--screen* [_file_]:: + Multi-level energy screening of an ensemble. -*-dry*:: - Perform a "`dry run`". - Only prints the settings that would be applied with the CMD input and stops the run before any calculations +*--protonate*:: + Automated protonation site search. -*-T* _int_:: - Set total number of CPUs (threads) to be used. - Parallel settings are then determined automatically for each step. - If not set by "`**-T**`", this number is read from the **OMP_NUM_THREADS** global variable. +*--deprotonate*:: + Automated deprotonation site search. -=== Calculation options +*--tautomerize*:: + Automated tautomer search. -*-g* _string_:: - Use GBSA implicit solvent for solvent _string_. +*--qcg* [_solvent_]:: + Quantum Cluster Growth solvation workflows. -*-alpb* _string_:: - Use ALPB implicit solvent for solvent _string_. +*--msreact*:: + Mass-spectral fragment generator. -*-chrg* _int_:: - Set the molecules`' charge. +*--bh*, *--GMIN*:: + Basin-hopping global optimization. -*-uhf* _int_:: - Set _int_=**N alpha - N beta** electrons - -*-nozs*:: - Do not perform z-mat sorting. - [_default_] +*--sort*:: + Ensemble sorting via CREGEN. -*-opt* _lev_:: - Set optimization level for **ALL** GFN-xTB optimizations. - [_default_: **vtight**] +=== Method selection - * _lev_ = **vloose**, **loose**, **normal**, **tight**, **vtight** +*-gfn2*:: + Use GFN2-xTB. + [_default_] *-gfn1*:: Use GFN1-xTB. -*-gfn2*:: - Use GFN2-xTB. - [_default_] +*-gfn0*:: + Use GFN0-xTB. *-gff*, *-gfnff*:: - Use GFN-FF (requires **xtb**(1) 6.3 or newer). - (For GFN-FF searches bond constraints are applied automatically.) + Use GFN-FF. + Bond constraints are applied automatically for GFN-FF searches. + +*-gxtb*:: + Use g-xTB (requires a special build). *-gfn2//gfnff*:: - GFN2-xTB//GFN-FF composite mode. + GFN-FF trajectories with GFN2-xTB energy reweighting. + +*-refine* _method_:: + Post-process conformers at a higher level of theory. + +*-optlev* _level_:: + Optimization convergence level for all semiempirical calculations. + _level_ = *crude*, *vloose*, *loose*, *normal*, *tight*, *vtight*, *extreme*. + +*-dscal* [_factor_]:: + Scale the dispersion energy in MD/MTD simulations. + +=== Molecular system + +*-T* _int_:: + Number of CPU threads. + If not set, read from *OMP_NUM_THREADS*. + +*-chrg* _int_:: + Molecular charge. -*Adding additional constraints to the calculations:* +*-uhf* _int_:: + Number of unpaired electrons (N_alpha - N_beta). + +*-g* _solvent_, *-gbsa* _solvent_:: + GBSA implicit solvation. + +*-alpb* _solvent_:: + ALPB implicit solvation. + +*-efield* _Ex_ _Ey_ _Ez_:: + Apply a homogeneous external electric field in V/Ang along x, y, z. + +*-charges* [_file_]:: + Read atomic partial charges from _file_ (default: *charges*). + +=== Technical options + +*--input* _file_:: + Specify a TOML input file with detailed settings. + +*-xnam* _bin_:: + Path to the xtb executable (when using xtb as the backend). + +*-noopt*:: + Skip pre-optimization of the input structure. + +*-niceprint*:: + Show an animated progress bar during long runs. + +*-dry*:: + Parse all arguments, print the resolved settings, then exit without + running any calculation. + +*-legacy*:: + Force CREST < 3.0 behaviour. -The user is able to include additional constraints to **ALL** xtb**(1)** calculations that are conducted by CREST. +=== Constraints + +The following flags apply constraints to *all* calculations made by CREST. *-cinp* _file_:: - Read in a file containing the constraints. - Constraints have to be in the same format as in xtb**(1)**. - (This was done previously via the "`**.constrains**`" file.) + Read constraints from _file_ (same format as in *xtb*(1); + formerly passed via *.constrains*). + +*-cbonds* [_fc_]:: + Constrain all bonds globally (derived from topology). + Optional force constant _fc_ in Eh. -*-cbonds*:: - Define automatic bond constraints (set up from topology). +*-cbonds_md* [_fc_]:: + Constrain all bonds during MDs/MTDs only, not optimizations. *-nocbonds*:: - Turn off **-cbonds**. (For GFN-FF, mainly. See above.) + Disable automatic bond constraints. *-fc* _float_:: - Define force constant for defined constraints (**-cbonds**). + Global force constant for bond constraints. -=== Options for ensemble comparisons +=== Ensemble comparison and sorting (CREGEN) -*-cregen* _file_:: - Use **ONLY** the CREGEN subroutine to sort a given ensemble file. +*-cregen* [_file_]:: + Run CREGEN standalone to sort and deduplicate an ensemble file. *-ewin* _real_:: - Set energy window in kcal/mol. - [_default_: **6.0** kcal/mol] + Energy window in kcal/mol. + [_default_: *6.0* kcal/mol] *-rthr* _real_:: - Set RMSD threshold in Ang. - [_default_: **0.125** Ang] + RMSD threshold in Ang. + [_default_: *0.125* Ang] *-ethr* _real_:: - Set E threshold in kcal/mol. - [_default_: **0.05** kcal/mol] + Energy threshold in kcal/mol. + [_default_: *0.05* kcal/mol] *-bthr* _real_:: - Set Rot. const. threshold. - [_default_: **0.01** (= 1%)] + Rotational constant threshold. + [_default_: *0.01* (= 1%)] *-pthr* _real_:: - Boltzmann population threshold. - [_default_: **0.05** (= 5%)] + Boltzmann population threshold (0-1). + [_default_: *0.05*] *-temp* _real_:: - Set temperature in **CREGEN**. - [_default_: **298.15** K] + Temperature for Boltzmann weighting in K. + [_default_: *298.15* K] -*-prsc*:: - Create a **scoord.*** file for each conformer. +*-topo*, *-notopo*:: + Enable or disable the topology change check. -*-nowr*:: - Don't write new ensemble files. +*-ezcheck*:: + Enable E/Z double-bond isomer check. -*-eqv*,*-nmr*,*-entropy*:: - Compare nuclear equivalences (requires rotamers). +*-heavy*:: + Use heavy-atom-only RMSD. + +*-allrot*:: + Use all three rotational constants (A, B, C) for duplicate detection. + +*-eqv*, *-nmr*:: + NMR nuclear equivalence analysis (requires rotamers). *-cluster* _int_:: - PCA and k-Means clustering of sorted ensemble. - Works as extenstion to the **CREGEN** sorting. - _int_ is the number of clusters to be formed. + PCA + k-Means clustering of the sorted ensemble. + _int_ is the number of clusters. -*-notopo*:: - Turn off any topology checks in **CREGEN**. +*-prsc*:: + Write *scoord.** files for each conformer. -=== Options for the iMTD-GC workflows +*-nowr*:: + Skip writing the sorted ensemble file. -*-cross*:: - Do the GC part. +*-osdf*:: + Write the output ensemble in SDF format in addition to XYZ. + +=== Conformer search / sampling + +*-v3*, *-v2i*:: + iMTD-GC (iterative MTD-GC). [_default_] -*-nocross*:: - Don't do the GC part. +*-v4*, *-entropy*:: + iMTD-sMTD, specialized for entropy/free-energy sampling. + +*-len* _t_[*x*], *-mdlen* _t_[*x*]:: + MD/MTD simulation length in ps. + Append *x* to scale the default length by a factor instead. + +*-tstep* _float_:: + MD integration timestep in fs. + [_default_: *5* fs] *-shake* _int_:: - Set SHAKE mode for MD. - (**0**=off, **1**=H-only, **2**=all bonds) - [_default_: **2**] + SHAKE constraint mode: *0* = off, *1* = X-H bonds only, *2* = all bonds. + [_default_: *1*] -*-tstep* _int_:: - Set MD time step in fs. - [_default_: **5** fs] +*-mdtemp* _float_:: + Temperature for MTD simulations in K. -*-mdlen/-len* _real_:: - Set MD length (all MTDs) in ps. - Also possible are multiplicative factors for the default MD length with "`**x**_real_`". +*-tnmd* _float_:: + Temperature for additional normal (unbiased) MDs in K. *-mddump* _int_:: - xyz dumpstep to Trajectory in fs. - [_default_: **100** fs] + Trajectory structure dump interval in fs. + [_default_: *100* fs] *-vbdump* _real_:: - Set Vbias dump frequency in ps. - [_default_: 1.0 ps] + Vbias (MTD bias) dump frequency in ps. + [_default_: *1.0* ps] -*-tnmd* _real_:: - Set temperature for additional normal MDs. - [_default_: 400 K] +*-nmtd* _int_:: + Number of MTD simulations per search cycle. -*-norotmd*:: - Don't do the regular MDs after the second multilevel optimization step. +*-cross*, *-nocross*:: + Enable or disable the genetic structure crossing step. + [_default_: *-cross*] -*-quick*:: - Perform a search with reduced settings for a crude ensemble. +*-gcmax* _int_:: + Maximum number of structures fed into the genetic crossing. -*-squick*:: - Perform a even further reduced search. +*-nozs*:: + Disable z-matrix sorting. + +*-normmd* [_n_ [_T_]]:: + Run additional unbiased MDs on the lowest-energy conformers. -*-mquick*:: - Perform a search with maximum reduced settings. - (Do not reduce the settings more than that.) +*-quick*, *-squick*, *-mquick*:: + Progressively reduced search settings for fast, approximate ensembles. *-origin*:: - Track the step of generation for each conformer/rotamer. + Track the MTD step of origin for each conformer. [_default_] *-keepdir*:: - Keep sub-directories of the conformer generation step. + Keep temporary working directories after the run. + +*-NCI*:: + NCI cluster mode: flat-bottom wall potential and specialised MTD settings + suited for weakly-bound complexes. -*-nci*:: - Generate an ellipsoide potential around the input structure and add it to the MTD simulation. - This can be used to find aggregates of NCI complexes. +*-wscal* _float_:: + Scale the wall potential sphere radius. -*-wscal* _real_:: - Scale the ellipsoide potential axes by factor _real_. +*-hflip*, *-noflip*:: + Enable or disable OH proton flipping after MTD. + [_default_: *-noflip*] -=== Thermostatistical options (used in entropy mode) +*-maxflip* _int_:: + Maximum number of OH flip attempts. + [_default_: *1000*] -*-trange* _lower_ _upper_ _step_:: - Entropies are calculated for different temperatures. - These are calculated in a temperature range from _lower_ to _upper_ with _step_ in between. - [_default_: **280**K-**380**K in **10**K steps] +=== Thermostatistical options + +*-trange* _Tmin_ _Tmax_ _Tstep_:: + Compute entropies over a temperature range. + [_default_: *280*-*380* K in *10* K steps] + +*-tread* _file_:: + Read a list of temperatures (one per line) from _file_. *-fscal* _float_:: - Frequency scaling factor. - [_default_: 1.0] + Vibrational frequency scaling factor. + [_default_: *1.0*] -*-sthr* _float_:: - Vibrational/rotational entropy interpolation threshold (tau). - [_default_: **25.0** cm^-1] +*-sthr* _float_, *-rotorcut* _float_:: + Rotor cutoff in cm^-1: modes below this are treated as free rotors. + [_default_: *25.0* cm^-1] *-ithr* _float_:: Imaginary mode inversion cutoff. - [_default_: **-50.0** cm^-1] + [_default_: *-50.0* cm^-1] *-ptot* _float_:: - Sum of population for structures considered in msRRHO average. - [_default_: **0.9** (= 90%)] + Cumulative Boltzmann population threshold for msRRHO averaging. + [_default_: *0.9*] -=== options for MSREACT automated mass spectra fragment generator +*-pcap* _int_:: + Maximum number of structures used in property (Hessian) calculations. - *-msreact*:: - start the msreact mode +*-printpop*:: + Print Boltzmann populations at every temperature in the range. - *-msnoattrh*:: - deactivate attractive potential between hydrogen and LMO centers +*-avbhess*:: + Use a Boltzmann-averaged Hessian in the rrhoav property calculation. - *-msnshifts* _int_:: - perform n optimizations with randomly shifted atom postions (default 0) +=== Quantum Cluster Growth (QCG) - *-msnshifts* _int_:: - perform n optimizations with randomly shifted atom postions and repulsive potential applied to bonds (default 0) +General usage: *crest* _solute_ *-qcg* _solvent_ [_options_] - *-msnbonds* _int_:: - maximum number of bonds between atoms pairs for applying repulsive potential (default 3) +*-grow*:: + Cluster generation run type. - *-msmolbar*:: - sort out topological duplicates by molbar codes (requires sourced "molbar") +*-nsolv* _int_:: + Number of solvent molecules to add. - *-msinchi*:: - sort out topological duplicates by inchi codes (requires sourced "obabel") +*-fixsolute*:: + Fix the solute during cluster growth (recommended for rigid molecules; + applied automatically for water). - *-msnfrag* _int_:: - number of fragments that are printed by msreact (random selection) - - *-msiso*:: - print only non-dissociated structures (isomers) +*-nofix*:: + Do not fix the solute during growth (override for water). - *-msnoiso*:: - print only dissociated structures +*-nopreopt*:: + Skip pre-optimization. - *-mslargeprint*:: - do not remove temporary files and MSDIR with constrained optimizations +*-xtbiff*:: + Use the xTB-IFF standalone program for solvent docking. - *-chrg* _int_:: - set the molecules´ charge - - *-ewin* _float_:: - set energy window in for sorting out fragments kcal/mol, [default: 200.0 kcal/mol] - -=== Other tools for standalone use +*-normdock*:: + Perform a more extensive docking step during growth. -*-zsort*:: - Use only the **zsort** subroutine to sort the z-matrix of the input coordinate file. +*-maxsolv*:: + Set the convergence limit when *-nsolv* is not given. + [_default_: *150*] + +*-wscal* _float_:: + Scaling factor for the outer wall potential. + +*-samerand*:: + Use the same random seed for every xTB-IFF run. + +*-directed* _file_:: + Directed solvation: place solvent at positions defined in _file_. + +*-fin_opt_gfn2*:: + Perform final GFN2-xTB optimization of grow and ensemble structures. + +*-ensemble*:: + Ensemble generation run type. + +*-qcgmtd*, *-ncimtd*:: + NCI-MTD CREST ensemble generation. + [_default_] + +*-mtd*:: + MTD for QCG ensemble generation. + +*-md*:: + Normal MD for QCG ensemble search. + +*-enslvl* [_method_]:: + Method for ensemble search (all GFN methods supported). + +*-clustering*:: + Enable clustering for ensemble search (qcgmtd/ncimtd only). + +*-esolv*:: + Reference cluster generation and solvation energy calculation. + +*-gsolv*:: + Reference cluster generation and solvation free energy calculation. + +*-nclus*:: + Number of clusters for reference generation. + [_default_: *4*] + +*-nocff*:: + Disable the CFF algorithm. + +*-freqscal*:: + Frequency scale factor (output only). + +*-freqlvl* [_method_]:: + Method for frequency computation. + +=== Mass spectral fragment generator (MSReact) + +General usage: *crest* _input_ *-msreact* [_options_] + +*-msnoattrh*:: + Deactivate attractive potential between H atoms and LMO centers. + +*-msnshifts* _int_:: + Number of optimizations with randomly shifted atom positions. + [_default_: *0*] + +*-msnshifts2* _int_:: + Same as *-msnshifts* but with bond-repulsive potential applied. + [_default_: *0*] + +*-msnbonds* _int_:: + Maximum bond distance for the repulsive potential. + [_default_: *3*] + +*-msmolbar*:: + Deduplicate fragments by molbar codes (requires *molbar* in PATH). + +*-msinchi*:: + Deduplicate fragments by InChI codes (requires *obabel*(1) in PATH). + +*-msnfrag* _int_:: + Number of fragments to print (random selection). + +*-msiso*:: + Print only non-dissociated structures (isomers). -*-mdopt* _file_:: - Optimize along trajectory or ensemble file in the XYZ format. - Each point on the file is optimized. +*-msnoiso*:: + Print only dissociated structures. -*-screen* _file_:: - Optimize along ensemble file in the XYZ format. - A multilevel optimization is performed with continiously increasing thresholds. - After each step the ensemble file is sorted. +*-mslargeprint*:: + Keep all temporary files and the MSDIR directory. + +*-ewin* _real_:: + Energy window for fragment sorting in kcal/mol. + [_default_: *200.0* kcal/mol] + +*-msinput* _file_:: + Read special MSReact settings from _file_. + +=== Other standalone tools + +*-thermo* _file_:: + Compute thermochemistry from existing Hessian data. + Requires a *vibspectrum* file in TM format in the working directory. + +*-entropy* [_T_]:: + Compute conformational entropy from an ensemble. + Optional temperature _T_ in K. + +*-sort*:: + Sort ensemble structures by energy (CREGEN). + +*-symmetries*:: + Symmetry analysis of all structures in an ensemble. + +*-printboltz*:: + Print Boltzmann population weights for each structure. + +*-compare* _f1_ _f2_:: + Compare two ensembles _f1_ and _f2_ for structural overlap. + Both files must share the same atom ordering. + + *-maxcomp* _int_::: + Maximum number of conformers taken from each ensemble for comparison. + [_default_: *10*] + +*-splitfile* _file_ [_i_] [_j_]:: + Split an ensemble into per-structure directories under *SPLIT/*. + _i_ and _j_ optionally select a range of structures. + +*-rmsd* _f1_ _f2_:: + RMSD between two structures (coordinates auto-converted to Angstrom). + +*-rmsdheavy* _f1_ _f2_:: + Heavy-atom RMSD between two structures. *-protonate*:: - Find a molecule's protomes by using a LMO pi- or LP-center approach. + Automated protonation site search via LMO pi/LP-center approach. *-deprotonate*:: - Find a molecule's deprotomers. + Automated deprotonation site search. *-tautomerize*:: - Combine the protonation and deprotonation to find prototropic tautomers. + Find prototropic tautomers (protonation + deprotonation). *-trev*::: - Do first the deprotonation and then the protonation in the *-tautomerize* mode, i.e., reverse of the default procedure. + Deprotonate first, then protonate (reverse order). *-iter* _int_::: - Set number of protonation/deprotonation cycles in the tautomerization script. - [_default_: 2] + Number of protonation/deprotonation cycles. + [_default_: *2*] -*-compare* _f1_ _f2_:: - Compare two ensembles _f1_ and _f2_. - Both ensembles must have the same order of atoms of the molecule and should contain rotamers. +*-cregen* [_file_]:: + CREGEN ensemble sorting (see also _Ensemble comparison_ above). - *-maxcomp* _int_::: - Select the lowest _int_ conformers out of each ensemble to be compared with "`*-compare*`". - [_default_: 10] +*-zsort*:: + Z-matrix sorting of the input coordinate file. *-testtopo* _file_:: - Analyze some stuctural info (topology) for a given file. + Topology / bond connectivity analysis for a given file. *-constrain* _atoms_:: - Write example file "`*.xcontrol.sample*`" for constraints in crest. - (See *-cinp* option above.) + Write an example constraint file *.xcontrol.sample*. -*-thermo* _file_:: - Calculate thermo data for given structure. - Also requires vibrational frequencies in the TM format, saved as file called "`*vibspectrum*`". +=== TOML input files -*-rmsd*,*-rmsdheavy* _file1_ _file2_:: - Calculate RMSD or heavy atom RMSD between two structures. - Input coords are automatically transformed to Angstroem. +CREST 3.0+ accepts a TOML file as a flexible alternative to CLI flags. -*-splitfile* _file_ [*from*] [*to*]:: - Split an ensemble from _file_ into seperate directories for each structure. - *from* and *to* can be used to select specific structures from the file. - The new directories are collected in the *SPLIT* directory. + crest structure.xyz --input settings.toml + crest settings.toml # structure path given inside the file + +A minimal TOML input file: + +---- +input = "struc.xyz" +runtype = "iMTD-GC" +threads = 4 + +[calculation] + [[calculation.level]] + method = "gfn2" + chrg = 0 + gbsa = "h2o" +---- + +Key root-level settings: *input*/*structure* (coordinate file), +*runtype* (workflow), *threads* (CPU count), *preopt* (bool), +*constraints* (constraint file path). + +Main blocks: *[calculation]* / *[[calculation.level]]* / +*[[calculation.constraint]]* — method, charge, solvent, geometric +constraints; *[dynamics]* / *[[dynamics.meta]]* — MD and metadynamics +settings; *[cregen]* — sorting thresholds; *[thermo]* — thermochemistry. == NOTES -View literature references with **--cite**. +View full literature references with *--cite*. + +For the full option reference and TOML keyword documentation see: +https://crest-lab.github.io/crest-docs/ + +== SEE ALSO + +https://crest-lab.github.io/crest-docs/ diff --git a/examples/README.md b/examples/README.md index 36bd25ad..84003904 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,34 +1,53 @@ # Example applications of the CREST program -This directory contains several examples for -standard applications of the `crest` program. +This directory contains examples covering the most common workflows +of the `crest` program. -Each example directory contains a input structure -(typically called `struc.xyz`) and a bash script -called `run.sh` that includes some information about -the example and will execute the calculation upon -execution. +Each example directory contains an input structure (`struc.xyz` or +similar), a shell script `run.sh`, and a TOML input file `input.toml`. -To run the example scripts simply go to the respective -directory and execute it from the command line: +## Running an example + +Go to the example directory and execute the script: ```bash +cd expl-6 ./run.sh ``` -It is assumed that the `xtb` and `crest` binaries -are present in the *PATH* variable as such. -The `run.sh` scripts will check for this, however. +The `run.sh` scripts show CLI usage. Alternatively, every example can +be run through its TOML input file: +```bash +crest input.toml +``` + +TOML files are detected automatically by their `.toml` extension. They +offer the same settings as the CLI flags but in a structured, documented +format that is easier to modify and reuse. + +It is assumed that the `crest` binary is available in `$PATH`. ## Examples -0. *dry run* of the `crest` program -1. default conformational search (iMTD-GC) -2. example for different CMD settings -3. sorting an ensemble file (CREGEN) -4. constrained conformational sampling -5. standalone optimization along a trajectory -6. NCI sampling mode (iMTD-NCI) -7. protonation site sampling -8. modified protonation site sampling -9. tautomer sampling +| # | Topic | Molecule | +|---|-------|---------| +| **0** | *Dry run* — print settings without computing | 1-propanol | +| **1** | Single-point energy | 1-propanol | +| **2** | Geometry optimization | 1-propanol | +| **3** | Optimization + Hessian (vibrational frequencies) | 1-propanol | +| **4** | Standalone MD simulation | 1-propanol | +| **5** | Default iMTD-GC conformer search | 1-propanol | +| **6** | Two-level conformer search (GFN2//GFN-FF, A//B) | 1-propanol | +| **7** | iMTD-GC with ALPB implicit solvation (GFN2) | 1-propanol | +| **8** | Quick iMTD-GC conformer search (with -finalhess) | 1-propanol | +| **9** | Standalone CREGEN ensemble sorting | 1-propanol | +| **10** | Constrained conformer search | 1-propanol | +| **11** | Ensemble optimization (mdopt) | 1-propanol | +| **12** | NCI sampling mode (iMTD-NCI) | water trimer | +| **13** | Protonation site sampling | uracil | +| **14** | Metal/ion adducts (Cs+) | alpha-D-glucose | +| **15** | Tautomer screening | guanine | +| **16** | fmlip-relay: geometry optimisation with LJ potential | Ar4 cluster | +| **17** | fmlip-relay: geometry optimisation with FairChem UMA model | caffeine | +| **18** | fmlip-relay: geometry optimisation with MACE-OFF23 model | caffeine | +| **19** | Implicit-solvation add-on (ddX/EEQ-BC composite, GFN2 parent) | 1-propanol | diff --git a/examples/expl-0/run.sh b/examples/expl-0/run.sh index 881b7737..3745edd7 100755 --- a/examples/expl-0/run.sh +++ b/examples/expl-0/run.sh @@ -1,27 +1,12 @@ #!/bin/bash +# Dry run of CREST: prints settings and thresholds without running any calculation. +# Use this to preview the iMTD-GC setup before committing to a full run. +# Note: -dry is a CLI-only flag; see input.toml for the equivalent full-run settings. -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } +# --- CLI run --- +crest struc.xyz -dry -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -dry - else - $crst struc.xyz -dry -xnam $xtbin - fi - -# Before starting any calculation, settings -# can be checked with the '-dry' flag. -# This will only print a summary about the -# selected settings and thresholds to the -# consol and check for the xtb binary. -# -# Every time the input file (struc.xyz) is -# something else than 'coord', a file called -# 'coord' will be (over-)written, containing -# the atomic coordinates in Bohr. CREST will -# then continue to use and overwrite this -# coord file for all further calculations. +# --- TOML run (equivalent full run without -dry) --- +# crest input.toml diff --git a/examples/expl-1/input.toml b/examples/expl-1/input.toml new file mode 100644 index 00000000..8be94e2a --- /dev/null +++ b/examples/expl-1/input.toml @@ -0,0 +1,6 @@ +# Single-point energy calculation +runtype = "singlepoint" +input = "struc.xyz" # input structure file (1-propanol) + +[[calculation.level]] +method = "gfn2" # GFN2-xTB semi-empirical Hamiltonian diff --git a/examples/expl-1/run.sh b/examples/expl-1/run.sh index 015ccee7..425cd9ca 100755 --- a/examples/expl-1/run.sh +++ b/examples/expl-1/run.sh @@ -1,27 +1,11 @@ #!/bin/bash +# Single-point GFN2-xTB energy evaluation of 1-propanol. +# Output: energy printed to stdout; no structure files are written. -xtbin='xtb' -crst='crest' - -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -ewin 2.0 - else - $crst struc.xyz -ewin 2.0 -xnam $xtbin - fi - - -# This will execute a conformational search with default settings -# for the 1-propanol molecule. -# The energy window is set to 2.0 kcal/mol with the '-ewin' flag -# (instead of the default 6.0 kcal/mol window) -# Within this window there should be 4 conformers for 1-propanol -# in the gas phase. -# The 4 unique conformers can be found in the file 'crest_conformers.xyz'. -# All degenerate conformers (rotamers, pseudo-enantiomers) of the 4 structures -# can be found in the file 'crest_rotamers.xyz' +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } +# --- CLI run --- +crest struc.xyz -sp +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-10/input.toml b/examples/expl-10/input.toml new file mode 100644 index 00000000..ba5279e7 --- /dev/null +++ b/examples/expl-10/input.toml @@ -0,0 +1,13 @@ +# Constrained iMTD-GC: atoms 1-4 (C-C-C-O backbone) frozen in Cartesian space; +# only OH dihedral angles are sampled. +runtype = "imtd-gc" +input = "struc.xyz" # input structure file (1-propanol) + +[[calculation.level]] +method = "gfnff" # GFN-FF force field + +[calculation] +freeze = "1-4" # freeze atoms 1-4 at their input Cartesian coordinates + +[cregen] +ewin = 2.0 # keep conformers within 2.0 kcal/mol of the lowest diff --git a/examples/expl-10/run.sh b/examples/expl-10/run.sh new file mode 100755 index 00000000..862d3278 --- /dev/null +++ b/examples/expl-10/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# Constrained iMTD-GC conformer search of 1-propanol: +# the C-C-C-O backbone (atoms 1-4) is frozen; only the OH dihedral is sampled. + +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} + +# --- CLI run --- +crest struc.xyz -gfnff -freeze 1-4 -ewin 2.0 --imtdgc + +# --- TOML run (constraints defined directly in the input file) --- +# crest input.toml diff --git a/examples/expl-2.5/struc.xyz b/examples/expl-10/struc.xyz similarity index 100% rename from examples/expl-2.5/struc.xyz rename to examples/expl-10/struc.xyz diff --git a/examples/expl-11/input.toml b/examples/expl-11/input.toml new file mode 100644 index 00000000..dabcad9f --- /dev/null +++ b/examples/expl-11/input.toml @@ -0,0 +1,6 @@ +# Optimize every structure in an ensemble or trajectory file +runtype = "mdopt" +ensemble = "xtb.trj" # input trajectory or multi-structure XYZ file + +[[calculation.level]] +method = "gfn2" # GFN2-xTB for geometry optimization diff --git a/examples/expl-11/run.sh b/examples/expl-11/run.sh new file mode 100755 index 00000000..ac133c72 --- /dev/null +++ b/examples/expl-11/run.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Optimize all structures in a trajectory/ensemble file with GFN2-xTB. +# Output: crest_ensemble.xyz (optimized structures, not sorted) + +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } + +# --- CLI run --- +crest -mdopt xtb.trj + +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-3/xtb.trj b/examples/expl-11/xtb.trj similarity index 100% rename from examples/expl-3/xtb.trj rename to examples/expl-11/xtb.trj diff --git a/examples/expl-12/input.toml b/examples/expl-12/input.toml new file mode 100644 index 00000000..af5fac97 --- /dev/null +++ b/examples/expl-12/input.toml @@ -0,0 +1,6 @@ +# Non-covalent interaction (NCI) conformer sampling +runtype = "nci" +input = "struc.xyz" # input structure file (water trimer) + +[[calculation.level]] +method = "gfnff" # GFN-FF for fast NCI sampling diff --git a/examples/expl-12/run.sh b/examples/expl-12/run.sh new file mode 100755 index 00000000..28a4cc08 --- /dev/null +++ b/examples/expl-12/run.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Non-covalent interaction (NCI / iMTD-NCI) conformer sampling of a water trimer. +# A wall potential is generated automatically to prevent cluster dissociation. +# Output: crest_conformers.xyz, crest_rotamers.xyz + +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} + +# --- CLI run --- +crest struc.xyz --gfnff --imtdgc -nci + +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-12/struc.xyz b/examples/expl-12/struc.xyz new file mode 100644 index 00000000..5553af95 --- /dev/null +++ b/examples/expl-12/struc.xyz @@ -0,0 +1,11 @@ + 9 +FINAL HEAT OF FORMATION = 0.000000 + H -1.091354 2.083948 0.561412 + O -0.873213 1.360333 -0.037725 + H -1.126153 -0.540943 0.037240 + H 0.094609 1.245770 0.037306 + O 1.614744 0.076014 -0.037729 + O -0.741528 -1.436357 -0.037711 + H -1.259101 -1.987119 0.561339 + H 2.350402 -0.096756 0.561513 + H 1.031565 -0.704748 0.037192 diff --git a/examples/expl-13/input.toml b/examples/expl-13/input.toml new file mode 100644 index 00000000..f4f3915f --- /dev/null +++ b/examples/expl-13/input.toml @@ -0,0 +1,9 @@ +# Protonation site sampling: generates protomers by adding H+ to basic sites +runtype = "protonate" +input = "struc.xyz" # input structure file (uracil) + +[[calculation.level]] +method = "gfn2" # GFN2-xTB for protonation site ranking + +[protonation] +ewin = 30.0 # energy window for protomer selection (kcal/mol) diff --git a/examples/expl-13/run.sh b/examples/expl-13/run.sh new file mode 100755 index 00000000..0195d57a --- /dev/null +++ b/examples/expl-13/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Protonation site sampling of uracil with GFN2-xTB. +# Generates protomers by adding H+ to basic sites on the molecule. +# Expected output: 3 major protomers in protonated.xyz within 30 kcal/mol. + +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } + +# --- CLI run --- +crest struc.xyz -protonate + +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-13/struc.xyz b/examples/expl-13/struc.xyz new file mode 100644 index 00000000..73524a28 --- /dev/null +++ b/examples/expl-13/struc.xyz @@ -0,0 +1,14 @@ +12 + energy: -24.614946947602 gnorm: 0.000456827525 xtb: 6.2.2 (89a525f) +O 1.01382029544030 0.03880681993718 0.26260657568622 +C 2.21194959420030 0.00777551853774 0.13894624850894 +N 2.97633014247497 1.15928881874167 0.06760158128732 +C 4.33128423994495 1.14641376546424 -0.07421492522129 +C 5.02090106234646 -0.00303350704424 -0.15435476958224 +C 4.32754207903802 -1.27903972647711 -0.09166688984675 +O 4.83244816077160 -2.37420082989590 -0.15422059425404 +N 2.94450163810970 -1.14763022456257 0.05629094590508 +H 2.46829084494664 2.03038084438831 0.12488082938105 +H 4.81038312241915 2.11362921965719 -0.11757996835012 +H 6.09011013882395 -0.02137897779485 -0.26646726330202 +H 2.41547828301905 -2.01176145142300 0.10488837035251 diff --git a/examples/expl-14/input.toml b/examples/expl-14/input.toml new file mode 100644 index 00000000..32727569 --- /dev/null +++ b/examples/expl-14/input.toml @@ -0,0 +1,9 @@ +# Metal-ion adduct generation: replace H+ with Cs+ to form adducts +runtype = "protonate" +input = "struc.xyz" # input structure file (alpha-D-glucose) + +[[calculation.level]] +method = "gfn2" # GFN2-xTB for adduct ranking + +[protonation] +swel = "Cs+" # replace added H+ with Cs+ ion (also accepts Na+, Li+, Ca2+, …) diff --git a/examples/expl-14/run.sh b/examples/expl-14/run.sh new file mode 100755 index 00000000..efce1960 --- /dev/null +++ b/examples/expl-14/run.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Metal/ion adduct generation for alpha-D-glucose with GFN2-xTB. +# Replaces H+ with Cs+ (via -swel) to generate Cs+ adducts. +# Other ions (Na+, Li+, Ca2+, …) can be used the same way. +# Output: protonated.xyz + +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } + +# --- CLI run --- +crest struc.xyz -protonate -swel Cs+ + +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-14/struc.xyz b/examples/expl-14/struc.xyz new file mode 100644 index 00000000..a3c58227 --- /dev/null +++ b/examples/expl-14/struc.xyz @@ -0,0 +1,26 @@ + 24 +FINAL HEAT OF FORMATION = 0.000000 + O -1.240000 0.500286 0.389534 + O -3.422179 -1.988021 -1.200876 + O -1.030343 -2.500198 1.371244 + O -0.269585 -1.376682 -2.050459 + O 0.714096 0.825174 -0.948699 + O -4.564244 0.596025 -1.237038 + C -2.604750 -1.434635 -0.186435 + C -1.275745 -2.232516 0.002738 + C -2.309462 0.069148 -0.458819 + C -0.090913 -1.462394 -0.648941 + C 0.038626 -0.020969 -0.037264 + C -3.562580 0.927621 -0.299600 + H -3.175049 -1.514611 0.751562 + H -1.394054 -3.206789 -0.495459 + H -1.941229 0.164323 -1.490086 + H 0.840456 -2.024192 -0.479114 + H 0.639496 -0.057270 0.885117 + H -3.985456 0.780616 0.699514 + H -3.317920 1.993716 -0.382206 + H -4.113958 -1.329897 -1.403262 + H -1.021769 -1.655987 1.868817 + H 0.187245 -0.582574 -2.396515 + H 1.659435 0.569359 -1.034662 + H -4.311180 0.903728 -2.134995 diff --git a/examples/expl-15/input.toml b/examples/expl-15/input.toml new file mode 100644 index 00000000..b4f52c28 --- /dev/null +++ b/examples/expl-15/input.toml @@ -0,0 +1,9 @@ +# Tautomer screening via protonation/deprotonation sequences +runtype = "tautomerize" +input = "struc.xyz" # input structure file (guanine) + +[[calculation.level]] +method = "gfn2" # GFN2-xTB for tautomer ranking + +[cregen] +ewin = 10.0 # energy window for tautomer selection (kcal/mol) diff --git a/examples/expl-15/run.sh b/examples/expl-15/run.sh new file mode 100755 index 00000000..60f8ee69 --- /dev/null +++ b/examples/expl-15/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Tautomer screening of guanine via protonation/deprotonation sequences. +# Explores prototropic tautomers at GFN2-xTB level. +# Expected output: 5 major tautomers within 10 kcal/mol in tautomers.xyz. + +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } + +# --- CLI run --- +crest struc.xyz -tautomerize -ewin 10.0 + +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-15/struc.xyz b/examples/expl-15/struc.xyz new file mode 100644 index 00000000..725a04d3 --- /dev/null +++ b/examples/expl-15/struc.xyz @@ -0,0 +1,18 @@ +16 + energy: -31.983681788270 gnorm: 0.000372144832 xtb: 6.2.2 (89a525f) +N 1.46226284012671 0.18335744247169 -0.07855270314916 +C 1.31781205014203 -1.15293393777405 0.05955064599303 +C 1.54055132778996 -2.13428714261960 -0.91007075980005 +C 1.98029754954570 -1.74761862944617 -2.21865239727883 +N 2.10914370306048 -0.33402358897783 -2.27674973147307 +C 1.86775867472018 0.54390043382692 -1.26343764079100 +N 1.27680953207984 -3.36757233307534 -0.37748854905490 +C 0.91461226786592 -3.14740906594264 0.84460715972356 +N 0.91768716624572 -1.81594469141732 1.17440471889163 +O 2.23471850070944 -2.39670706107528 -3.20454497820690 +N 2.11460025487082 1.85836120568312 -1.49845077783225 +H 0.67952040591559 -1.39514835256529 2.05664466916869 +H 0.63659476510547 -3.91144927326480 1.54245387480526 +H 2.43918920019300 0.01672894983906 -3.16809193535854 +H 1.78950116764777 2.49655065642122 -0.79277065540295 +H 2.15173216814906 2.19392916334893 -2.44513710915798 diff --git a/examples/expl-16/input.toml b/examples/expl-16/input.toml new file mode 100644 index 00000000..e5c98cc8 --- /dev/null +++ b/examples/expl-16/input.toml @@ -0,0 +1,12 @@ +# Geometry optimisation of an Ar4 cluster using a Lennard-Jones potential +# served through the fmlip-relay persistent Python backend. +# The LJ backend requires only ASE, no ML framework needed. +runtype = "optimize" +input = "struc.xyz" # input structure file (Ar4 cluster, slightly compressed) + +[calculation] +optlev = "normal" # convergence level: crude/loose/normal/tight/vtight + +[[calculation.level]] +method = "mlip" # ML/classical potential via fmlip-relay socket server +mlip_backend = "lj" # Lennard-Jones backend — default Ar parameters (ε=0.0104 eV, σ=3.40 Å) diff --git a/examples/expl-16/run.sh b/examples/expl-16/run.sh new file mode 100755 index 00000000..3c8f4a63 --- /dev/null +++ b/examples/expl-16/run.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Geometry optimisation of an Ar4 cluster using the Lennard-Jones potential +# served through the fmlip-relay persistent Python backend. +# fmlip-relay spawns a persistent server process; CREST communicates with it +# over a local TCP socket, avoiding repeated Python startup overhead. +# Output: crest_best.xyz (optimised Ar4 geometry near the LJ minimum ~3.82 Å) + +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} + +# Check for the fmlip-relay server; suggest pip install if absent +if ! command -v fmlip-relay-server >/dev/null 2>&1; then + echo >&2 "" + echo >&2 "ERROR: 'fmlip-relay-server' not found." + echo >&2 "Install fmlip-relay from the CREST subproject directory:" + echo >&2 "" + echo >&2 " pip install ../../subprojects/fmlip_relay" + echo >&2 "" + echo >&2 "For a user-local install add '--user', or activate a virtual environment first." + exit 1 +fi + +# --- TOML run --- +# (No CLI equivalent; mlip settings are TOML-only.) +crest input.toml diff --git a/examples/expl-16/struc.xyz b/examples/expl-16/struc.xyz new file mode 100644 index 00000000..5de5f994 --- /dev/null +++ b/examples/expl-16/struc.xyz @@ -0,0 +1,6 @@ +4 +Ar4 cluster (compressed, for LJ optimization) +Ar 0.000 0.000 0.000 +Ar 3.300 0.000 0.000 +Ar 1.650 2.860 0.000 +Ar 1.650 0.953 2.694 diff --git a/examples/expl-17/input.toml b/examples/expl-17/input.toml new file mode 100644 index 00000000..6f262c4f --- /dev/null +++ b/examples/expl-17/input.toml @@ -0,0 +1,25 @@ +# Geometry optimisation of a small molecule using Meta FAIR's UMA foundation +# model (fairchem-core v2), served through the fmlip-relay persistent backend. +# +# UMA is a single multi-task model; the active domain is chosen via the task +# head. The "omol" task (organic/inorganic molecules) honours the total charge +# and the spin multiplicity, which CREST forwards to the server per evaluation +# from the 'chrg' and 'mult' keys below. +# +# Requirements: pip install "../../subprojects/fmlip_relay[uma]" +# The UMA checkpoints are gated on the Hugging Face Hub — request access on the +# model page and authenticate (huggingface-cli login or $HF_TOKEN) before use. +runtype = "optimize" +input = "struc.xyz" # input structure file (caffeine, C8H10N4O2) + +[calculation] +optlev = "normal" # convergence level: crude/loose/normal/tight/vtight + +[[calculation.level]] +method = "mlip" # ML/classical potential via fmlip-relay socket server +mlip_backend = "uma" # FairChem UMA foundation model +mlip_uma_model = "uma-s-1p2" # checkpoint: uma-s-1 | uma-s-1p1 | uma-s-1p2 (default) | uma-m-1 +mlip_uma_task = "omol" # task head: omol | omat | omc | oc20 | odac +mlip_device = "cpu" # torch device: cpu | cuda | cuda:0 (UMA runs best on GPU) +chrg = 0 # total molecular charge (forwarded to the omol task) +mult = 1 # spin multiplicity 2S+1 (forwarded to the omol task) diff --git a/examples/expl-17/run.sh b/examples/expl-17/run.sh new file mode 100755 index 00000000..6fa30163 --- /dev/null +++ b/examples/expl-17/run.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Geometry optimisation of the caffeine molecule using Meta FAIR's UMA foundation +# model (fairchem-core v2), served through the fmlip-relay persistent backend. +# fmlip-relay spawns a persistent server process; CREST communicates with it +# over a local TCP socket, avoiding repeated Python startup overhead. +# Output: crest_best.xyz (optimised geometry) + +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} + +# Check for the fmlip-relay server; suggest pip install if absent +if ! command -v fmlip-relay-server >/dev/null 2>&1; then + echo >&2 "" + echo >&2 "ERROR: 'fmlip-relay-server' not found." + echo >&2 "Install fmlip-relay (with UMA extras) from the CREST subproject directory:" + echo >&2 "" + echo >&2 " pip install \"../../subprojects/fmlip_relay[uma]\"" + echo >&2 "" + echo >&2 "UMA checkpoints are gated on the Hugging Face Hub: request access on the" + echo >&2 "model page and authenticate (huggingface-cli login or \$HF_TOKEN) first." + exit 1 +fi + +# --- TOML run --- +# (No CLI equivalent; mlip settings are TOML-only.) +crest input.toml diff --git a/examples/expl-17/struc.xyz b/examples/expl-17/struc.xyz new file mode 100644 index 00000000..a6c8f5c1 --- /dev/null +++ b/examples/expl-17/struc.xyz @@ -0,0 +1,26 @@ +24 +caffeine (C8H10N4O2) +C -3.24741 -1.13338 0.03085 +N -2.26564 -0.07883 0.00538 +C -2.52079 1.26675 -0.00557 +N -1.40686 1.97296 -0.00910 +C -0.41405 1.04003 -0.00300 +C -0.90847 -0.22864 0.00092 +C -0.08494 -1.38655 0.00646 +O -0.54163 -2.52584 0.00868 +N 1.27859 -1.08004 0.00992 +C 1.83002 0.21839 0.00766 +O 3.05306 0.38762 0.01088 +N 0.93714 1.29070 0.00305 +C 1.42504 2.65762 0.00864 +C 2.22515 -2.17795 0.02143 +H -4.24995 -0.69925 0.00482 +H -3.11700 -1.70536 0.95309 +H -3.09907 -1.77036 -0.84451 +H -3.52341 1.67566 -0.00787 +H 2.51704 2.70044 0.03492 +H 1.03541 3.17325 0.89211 +H 1.07712 3.16545 -0.89652 +H 1.73595 -3.15507 0.02232 +H 2.85353 -2.09533 0.91437 +H 2.86777 -2.10333 -0.86183 diff --git a/examples/expl-18/input.toml b/examples/expl-18/input.toml new file mode 100644 index 00000000..b43c6368 --- /dev/null +++ b/examples/expl-18/input.toml @@ -0,0 +1,21 @@ +# Geometry optimisation of a small molecule using the MACE-OFF23 organic +# force field, served through the fmlip-relay persistent backend. +# +# MACE-OFF23 is parameterised for neutral organic molecules over 10 elements +# (H, C, N, O, P, S, F, Cl, Br, I) in gas/liquid phase or organic crystals. +# Unlike UMA's "omol" task it does not take a per-molecule charge/spin, so the +# 'chrg'/'mult' keys are omitted here (neutral closed-shell systems only). +# +# Requirements: pip install "../../subprojects/fmlip_relay[mace]" +# Models download automatically on first use and cache in ~/.cache/mace. +runtype = "optimize" +input = "struc.xyz" # input structure file (caffeine, C8H10N4O2) + +[calculation] +optlev = "normal" # convergence level: crude/loose/normal/tight/vtight + +[[calculation.level]] +method = "mlip" # ML/classical potential via fmlip-relay socket server +mlip_backend = "mace_off" # MACE-OFF23 organic force field +mlip_modelsize = "medium" # model size: small | medium (default) | large +mlip_device = "cpu" # torch device: cpu | cuda | cuda:0 (GPU recommended) diff --git a/examples/expl-18/run.sh b/examples/expl-18/run.sh new file mode 100755 index 00000000..f5824e0c --- /dev/null +++ b/examples/expl-18/run.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Geometry optimisation of the caffeine molecule using the MACE-OFF23 organic force +# field, served through the fmlip-relay persistent backend. +# fmlip-relay spawns a persistent server process; CREST communicates with it +# over a local TCP socket, avoiding repeated Python startup overhead. +# Output: crest_best.xyz (optimised geometry) + +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} + +# Check for the fmlip-relay server; suggest pip install if absent +if ! command -v fmlip-relay-server >/dev/null 2>&1; then + echo >&2 "" + echo >&2 "ERROR: 'fmlip-relay-server' not found." + echo >&2 "Install fmlip-relay (with MACE extras) from the CREST subproject directory:" + echo >&2 "" + echo >&2 " pip install \"../../subprojects/fmlip_relay[mace]\"" + echo >&2 "" + echo >&2 "MACE-OFF23 checkpoints download automatically on first use (cache: ~/.cache/mace)." + exit 1 +fi + +# --- TOML run --- +# (No CLI equivalent; mlip settings are TOML-only.) +crest input.toml diff --git a/examples/expl-18/struc.xyz b/examples/expl-18/struc.xyz new file mode 100644 index 00000000..a6c8f5c1 --- /dev/null +++ b/examples/expl-18/struc.xyz @@ -0,0 +1,26 @@ +24 +caffeine (C8H10N4O2) +C -3.24741 -1.13338 0.03085 +N -2.26564 -0.07883 0.00538 +C -2.52079 1.26675 -0.00557 +N -1.40686 1.97296 -0.00910 +C -0.41405 1.04003 -0.00300 +C -0.90847 -0.22864 0.00092 +C -0.08494 -1.38655 0.00646 +O -0.54163 -2.52584 0.00868 +N 1.27859 -1.08004 0.00992 +C 1.83002 0.21839 0.00766 +O 3.05306 0.38762 0.01088 +N 0.93714 1.29070 0.00305 +C 1.42504 2.65762 0.00864 +C 2.22515 -2.17795 0.02143 +H -4.24995 -0.69925 0.00482 +H -3.11700 -1.70536 0.95309 +H -3.09907 -1.77036 -0.84451 +H -3.52341 1.67566 -0.00787 +H 2.51704 2.70044 0.03492 +H 1.03541 3.17325 0.89211 +H 1.07712 3.16545 -0.89652 +H 1.73595 -3.15507 0.02232 +H 2.85353 -2.09533 0.91437 +H 2.86777 -2.10333 -0.86183 diff --git a/examples/expl-19/input.toml b/examples/expl-19/input.toml new file mode 100644 index 00000000..ec215aa2 --- /dev/null +++ b/examples/expl-19/input.toml @@ -0,0 +1,25 @@ +# Single-point energy with a method-independent implicit-solvation add-on. +# +# The total energy is stitched from two calculation levels that CREST sums: +# 1) a GFN2-xTB gas-phase parent potential +# 2) an implicit-solvation contribution built from EEQ-BC atomic charges, +# the ddX continuum model (CPCM), and a GFN2/ALPB-parametrized nonpolar +# term (surface tension + charge-dependent hydrogen bonding). +# +# Because the solvation level only needs the geometry (it generates its own +# charges), it can be stacked on top of *any* parent method the same way -- +# semi-empirical, force field or an ML potential. +runtype = "singlepoint" +input = "struc.xyz" # input structure file (1-propanol) + +[calculation] + +[[calculation.level]] +method = "gfn2" # gas-phase parent Hamiltonian + +[[calculation.level]] +method = "solvation" # composite implicit-solvation contribution +solvent = "water" # solvent name (tblite solvent database) +solv_model = "cpcm" # continuum model: cosmo | cpcm | pcm +solv_charges = "eeqbc" # charge model: eeq | eeqbc +solv_hbond = true # include the charge-dependent H-bond term diff --git a/examples/expl-19/run.sh b/examples/expl-19/run.sh new file mode 100755 index 00000000..e1919096 --- /dev/null +++ b/examples/expl-19/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Single-point energy of 1-propanol with a method-independent implicit-solvation +# add-on: a GFN2-xTB gas-phase parent plus a composite solvation contribution +# (EEQ-BC charges + ddX/CPCM continuum + GFN2/ALPB nonpolar term) in water. +# +# The composite solvation calculator is configured through the TOML input only +# (there is no dedicated CLI flag for it). + +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } + +# --- TOML run --- +crest input.toml diff --git a/examples/expl-19/struc.xyz b/examples/expl-19/struc.xyz new file mode 100644 index 00000000..4297fe9a --- /dev/null +++ b/examples/expl-19/struc.xyz @@ -0,0 +1,14 @@ +12 + +C 1.00510 -0.04436 0.07729 +C 2.52196 -0.10014 0.05638 +C 3.03386 -1.52959 -0.04885 +O 4.45512 -1.53382 -0.04957 +H 0.66450 0.99293 0.15400 +H 0.60392 -0.59767 0.93240 +H 0.58435 -0.47325 -0.83778 +H 2.92490 0.36854 0.96213 +H 2.90338 0.49174 -0.78421 +H 2.68484 -2.01184 -0.96764 +H 2.69552 -2.12845 0.80244 +H 4.74911 -1.01511 -0.81774 diff --git a/examples/expl-2.5/run.sh b/examples/expl-2.5/run.sh deleted file mode 100755 index 2215a633..00000000 --- a/examples/expl-2.5/run.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -xtbin='xtb' -crst='crest' - -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -ewin 2.0 -quick -prop ohess - else - $crst struc.xyz -ewin 2.0 -quick -prop ohess -xnam $xtbin - fi - -# Some further calculations can be added automatically -# after the conformer search with the '-prop' command. -# In the above example, after searching for the -# conformers of 1-propanol, each conformer is optimized -# again and frequencies are calculated (ohess). -# The conformer ensemble is then re-ranked with free -# energies from RRHO contributions. -# -# There are also some different 'quick'-modes to run -# the conformational search with reduced settings. -# With these modes the conformational space will be -# explored less extensively, but it will speed up -# the calculation. ('-quick','-squick','-mquick') - diff --git a/examples/expl-2/input.toml b/examples/expl-2/input.toml new file mode 100644 index 00000000..82479488 --- /dev/null +++ b/examples/expl-2/input.toml @@ -0,0 +1,9 @@ +# Geometry optimization +runtype = "optimize" +input = "struc.xyz" # input structure file (1-propanol) + +[calculation] +optlev = "normal" # convergence level: crude/loose/normal/tight/vtight + +[[calculation.level]] +method = "gfn2" # GFN2-xTB semi-empirical Hamiltonian diff --git a/examples/expl-2/run.sh b/examples/expl-2/run.sh index e2da43e9..e681d0ba 100755 --- a/examples/expl-2/run.sh +++ b/examples/expl-2/run.sh @@ -1,30 +1,11 @@ #!/bin/bash +# Geometry optimization of 1-propanol with GFN2-xTB. +# Output: crest_best.xyz (optimized structure) -xtbin='xtb' -crst='crest' - -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -ewin 2.0 -g h2o -gfn2 -T 4 - else - $crst struc.xyz -ewin 2.0 -g h2o -gfn2 -T 4 -xnam $xtbin - fi - - -# This will execute a conformational search with some manually changed -# settings for the 1-propanol molecule. -# The GBSA implicit solvation model for H2O is employed with the -# '-g' flag. -# Furthermore, the use of GFN2-xTB is requested explicitly ('-gfn2') -# and the program is ordered to use 4 CPU threads ('-T'). -# For a 1-propanol the conformers in implicit solvation are the -# same as in the gas phase, but the relative energies should -# differ significantly. -# Unique conformers can be found in the file 'crest_conformers.xyz'. -# All degenerate conformers (rotamers, pseudo-enantiomers) -# can be found in the file 'crest_rotamers.xyz' +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } +# --- CLI run --- +crest struc.xyz -opt +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-3/input.toml b/examples/expl-3/input.toml new file mode 100644 index 00000000..9f176ff0 --- /dev/null +++ b/examples/expl-3/input.toml @@ -0,0 +1,9 @@ +# Geometry optimization followed by Hessian (vibrational frequency) calculation +runtype = "ohess" +input = "struc.xyz" # input structure file (1-propanol) + +[calculation] +optlev = "tight" # tighter convergence before frequency calculation + +[[calculation.level]] +method = "gfn2" # GFN2-xTB semi-empirical Hamiltonian diff --git a/examples/expl-3/run.sh b/examples/expl-3/run.sh index 00d10ba9..7fb84261 100755 --- a/examples/expl-3/run.sh +++ b/examples/expl-3/run.sh @@ -1,24 +1,12 @@ #!/bin/bash +# Geometry optimization followed by numerical Hessian (vibrational frequencies) +# of 1-propanol with GFN2-xTB. +# Output: crest_best.xyz (optimized structure), vibspectrum (frequencies) -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -$crst struc.xyz -cregen xtb.trj -ewin 100.0 - - -# The sorting routine from the CREST conformational search can be -# used as a standalone to sort any .xyz or .trj ensemble file. -# The above command will sort the file xtb.trj according to -# its energy and determine duplicate structures. -# Two files are written analogous to 'crest_conformers.xyz' -# and 'crest_rotamers.xyz'. -# The new file 'crest_ensemble.xyz' will contain only unique -# structures from xtb.trj, while the new file 'xtb.trj.sorted' -# is just a sorted version of the original file (without the -# -ewin flag the default 6.0 kcal/mol window will be used) -# The routine requires a reference structure which is given -# by 'struc.xyz'. +# --- CLI run --- +crest struc.xyz -ohess +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-3/struc.xyz b/examples/expl-3/struc.xyz index 38692b20..4297fe9a 100644 --- a/examples/expl-3/struc.xyz +++ b/examples/expl-3/struc.xyz @@ -1,14 +1,14 @@ - 12 -FINAL HEAT OF FORMATION = -1.651323 - C 1.625257 -0.262628 -0.323273 - C 0.518221 0.706221 -0.335177 - C -0.859313 -0.005477 -0.327969 - H -1.683026 0.654493 -0.607965 - H -0.974493 -0.754480 -1.061658 - O -1.171503 -0.423846 1.012985 - H -0.837975 0.254126 1.683603 - H 0.615912 1.428650 0.457852 - H 0.597843 1.325129 -1.321583 - H 1.504113 -0.866659 0.564875 - H 2.639815 0.049302 -0.353007 - H 1.502315 -1.001480 -1.109649 +12 + +C 1.00510 -0.04436 0.07729 +C 2.52196 -0.10014 0.05638 +C 3.03386 -1.52959 -0.04885 +O 4.45512 -1.53382 -0.04957 +H 0.66450 0.99293 0.15400 +H 0.60392 -0.59767 0.93240 +H 0.58435 -0.47325 -0.83778 +H 2.92490 0.36854 0.96213 +H 2.90338 0.49174 -0.78421 +H 2.68484 -2.01184 -0.96764 +H 2.69552 -2.12845 0.80244 +H 4.74911 -1.01511 -0.81774 diff --git a/examples/expl-4/input.toml b/examples/expl-4/input.toml new file mode 100644 index 00000000..d1378bf1 --- /dev/null +++ b/examples/expl-4/input.toml @@ -0,0 +1,13 @@ +# Standalone molecular dynamics simulation +runtype = "dynamics" +input = "struc.xyz" # input structure file (1-propanol) + +[[calculation.level]] +method = "gfnff" # GFN-FF force field for fast MD + +[dynamics] +length_ps = 20.0 # total simulation length (ps) +tstep = 1.0 # integration timestep (fs) +temperature = 400.0 # thermostat target temperature (K) +dump = 100.0 # trajectory dump interval (fs) +shake = 1 # constrain X-H bonds (0 = off, 1 = X-H, 2 = all bonds) diff --git a/examples/expl-4/run.sh b/examples/expl-4/run.sh index 7f62e3c2..91cd67ad 100755 --- a/examples/expl-4/run.sh +++ b/examples/expl-4/run.sh @@ -1,32 +1,15 @@ #!/bin/bash +# Standalone molecular dynamics (MD) simulation of 1-propanol with GFN-FF. +# Runs a 20 ps NVT trajectory at 400 K. +# Output: crest_dynamics.trj (trajectory), crest_property.out (energies) -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } +# --- CLI run --- +crest struc.xyz -dyn -gfnff -mdtemp 400 -mdlen 20 -tstep 1.0 -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -constrain 1-4 - $crst struc.xyz -cinp .xcontrol.sample - else - $crst struc.xyz -constrain 1-4 - $crst struc.xyz -cinp .xcontorl.sample -xnam $xtbin - fi - - -# Constraint conformational sampling is possible by -# providing the constrainment info as a file -# via the '-cinp' flag. -# For detailed information about the constraining -# options see the online documentation of -# CREST and xTB. -# However, a dummy constraining file '.xcontrol.sample' -# can be written by CREST with a seperate call using -# The '-constrain ' flag. -# In the above example the carbon atoms and the oxygen -# atom of 1-propanol (atoms 1-4) will be constrained. -# In the resulting "ensemble" only conformers resulting -# from different OH dihedral angles will be present -# (2 conformers total) +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-5/input.toml b/examples/expl-5/input.toml new file mode 100644 index 00000000..d2b0a0e2 --- /dev/null +++ b/examples/expl-5/input.toml @@ -0,0 +1,9 @@ +# Default iMTD-GC conformer search +runtype = "imtd-gc" +input = "struc.xyz" # input structure file (1-propanol) + +[[calculation.level]] +method = "gfnff" # GFN-FF force field for fast conformer sampling + +[cregen] +ewin = 2.0 # keep conformers within 2.0 kcal/mol of the lowest diff --git a/examples/expl-5/run.sh b/examples/expl-5/run.sh index f9a722ac..30ad4021 100755 --- a/examples/expl-5/run.sh +++ b/examples/expl-5/run.sh @@ -1,22 +1,11 @@ #!/bin/bash +# Default iMTD-GC conformer search of 1-propanol with GFN-FF. +# Expected output: ~4 unique conformers in crest_conformers.xyz within 2.0 kcal/mol. -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary."; exit 1; } -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } +# --- CLI run --- +crest struc.xyz -imtdgc -gfnff -ewin 2.0 -if [ $xtbin == 'xtb' ] - then - $crst -mdopt xtb.trj - else - $crst -mdopt xtb.trj -xnam $xtbin - fi - - -# A ensemble file (or MD trajectory) can also -# be optimized in a standalone application -# of CREST using the '-mdopt' flag. -# The optimized structures are written to a -# file called 'crest_ensemble.xyz', but will -# not be sorted in any way. +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-5/struc.xyz b/examples/expl-5/struc.xyz new file mode 100644 index 00000000..4297fe9a --- /dev/null +++ b/examples/expl-5/struc.xyz @@ -0,0 +1,14 @@ +12 + +C 1.00510 -0.04436 0.07729 +C 2.52196 -0.10014 0.05638 +C 3.03386 -1.52959 -0.04885 +O 4.45512 -1.53382 -0.04957 +H 0.66450 0.99293 0.15400 +H 0.60392 -0.59767 0.93240 +H 0.58435 -0.47325 -0.83778 +H 2.92490 0.36854 0.96213 +H 2.90338 0.49174 -0.78421 +H 2.68484 -2.01184 -0.96764 +H 2.69552 -2.12845 0.80244 +H 4.74911 -1.01511 -0.81774 diff --git a/examples/expl-6/input.toml b/examples/expl-6/input.toml new file mode 100644 index 00000000..1a28eaf6 --- /dev/null +++ b/examples/expl-6/input.toml @@ -0,0 +1,15 @@ +# iMTD-GC conformer search with two-level (A//B) calculation: +# Level 1 – GFN-FF: fast force field used for MD/MTD sampling +# Level 2 – GFN2: re-ranks the final ensemble with single-point energies +runtype = "imtd-gc" +input = "struc.xyz" # input structure file (1-propanol) + +[[calculation.level]] +method = "gfnff" # GFN-FF force field for fast MD sampling + +[[calculation.level]] +method = "gfn2" # GFN2-xTB for accurate final ranking +refine = "singlepoint" # apply this level as single-point re-ranking step + +[cregen] +ewin = 6.0 # keep conformers within 6.0 kcal/mol of the lowest diff --git a/examples/expl-6/run.sh b/examples/expl-6/run.sh index d507701b..14d1d58c 100755 --- a/examples/expl-6/run.sh +++ b/examples/expl-6/run.sh @@ -1,27 +1,19 @@ #!/bin/bash +# Two-level iMTD-GC conformer search of 1-propanol using the A//B scheme: +# GFN-FF handles the fast MD/MTD sampling phase; GFN2 single-points +# re-rank the final ensemble. This gives good accuracy at reduced cost. +# Output: crest_conformers.xyz -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -nci - else - $crst struc.xyz -nci -xnam $xtbin - fi - - -# This will execute the NCI sampling mode of CREST of the -# water trimer with default settings. -# A wall-potential is automatically generated and added to -# the calculation to prevent dissociation. -# The NCI mode is a special case of the constrained sampling. -# Just like the regular conformational search unique conformers -# can be found in the file 'crest_conformers.xyz'. -# All degenerate conformers (rotamers, pseudo-enantiomers) -# can be found in the file 'crest_rotamers.xyz' +# --- CLI run (A//B: B for sampling, A for single-point re-ranking) --- +crest struc.xyz -imtdgc --gfn2//gfnff -ewin 6.0 +# Alternative: GFN-FF sampling + GFN2 geometry refinement of each conformer: +# crest struc.xyz -imtdgc --gfnff/opt/gfn2 -ewin 6.0 +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-6/struc.xyz b/examples/expl-6/struc.xyz index 5553af95..4297fe9a 100644 --- a/examples/expl-6/struc.xyz +++ b/examples/expl-6/struc.xyz @@ -1,11 +1,14 @@ - 9 -FINAL HEAT OF FORMATION = 0.000000 - H -1.091354 2.083948 0.561412 - O -0.873213 1.360333 -0.037725 - H -1.126153 -0.540943 0.037240 - H 0.094609 1.245770 0.037306 - O 1.614744 0.076014 -0.037729 - O -0.741528 -1.436357 -0.037711 - H -1.259101 -1.987119 0.561339 - H 2.350402 -0.096756 0.561513 - H 1.031565 -0.704748 0.037192 +12 + +C 1.00510 -0.04436 0.07729 +C 2.52196 -0.10014 0.05638 +C 3.03386 -1.52959 -0.04885 +O 4.45512 -1.53382 -0.04957 +H 0.66450 0.99293 0.15400 +H 0.60392 -0.59767 0.93240 +H 0.58435 -0.47325 -0.83778 +H 2.92490 0.36854 0.96213 +H 2.90338 0.49174 -0.78421 +H 2.68484 -2.01184 -0.96764 +H 2.69552 -2.12845 0.80244 +H 4.74911 -1.01511 -0.81774 diff --git a/examples/expl-7/input.toml b/examples/expl-7/input.toml new file mode 100644 index 00000000..effe947c --- /dev/null +++ b/examples/expl-7/input.toml @@ -0,0 +1,11 @@ +# iMTD-GC conformer search with GFN2-xTB and ALPB implicit solvation +runtype = "imtd-gc" +input = "struc.xyz" # input structure file (1-propanol) +threads = 4 # number of parallel threads + +[[calculation.level]] +method = "gfn2" # GFN2-xTB semi-empirical Hamiltonian +alpb = "h2o" # ALPB implicit solvation model, water solvent + +[cregen] +ewin = 2.0 # keep conformers within 2.0 kcal/mol of the lowest diff --git a/examples/expl-7/run.sh b/examples/expl-7/run.sh index d1341045..7f95eff0 100755 --- a/examples/expl-7/run.sh +++ b/examples/expl-7/run.sh @@ -1,21 +1,15 @@ #!/bin/bash +# iMTD-GC conformer search of 1-propanol with GFN2-xTB and ALPB implicit solvation (water). +# Conformers in solution differ in relative energy from the gas phase. +# Uses 4 CPU threads. Output: crest_conformers.xyz -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } +# --- CLI run --- +crest struc.xyz -gfn2 -alpb h2o -T 4 -ewin 2.0 -imtdgc -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -protonate - else - $crst struc.xyz -protonate -xnam $xtbin - fi - - -# This command will create protomers of the uracil molecule. -# The default energy window for this application is 30 kcal/mol -# Only 3 structures should remain in the gas phase at the -# default GFN2-xTB level. -# The structures can be found in the file 'protonated.xyz' +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-7/struc.xyz b/examples/expl-7/struc.xyz index 73524a28..4297fe9a 100644 --- a/examples/expl-7/struc.xyz +++ b/examples/expl-7/struc.xyz @@ -1,14 +1,14 @@ 12 - energy: -24.614946947602 gnorm: 0.000456827525 xtb: 6.2.2 (89a525f) -O 1.01382029544030 0.03880681993718 0.26260657568622 -C 2.21194959420030 0.00777551853774 0.13894624850894 -N 2.97633014247497 1.15928881874167 0.06760158128732 -C 4.33128423994495 1.14641376546424 -0.07421492522129 -C 5.02090106234646 -0.00303350704424 -0.15435476958224 -C 4.32754207903802 -1.27903972647711 -0.09166688984675 -O 4.83244816077160 -2.37420082989590 -0.15422059425404 -N 2.94450163810970 -1.14763022456257 0.05629094590508 -H 2.46829084494664 2.03038084438831 0.12488082938105 -H 4.81038312241915 2.11362921965719 -0.11757996835012 -H 6.09011013882395 -0.02137897779485 -0.26646726330202 -H 2.41547828301905 -2.01176145142300 0.10488837035251 + +C 1.00510 -0.04436 0.07729 +C 2.52196 -0.10014 0.05638 +C 3.03386 -1.52959 -0.04885 +O 4.45512 -1.53382 -0.04957 +H 0.66450 0.99293 0.15400 +H 0.60392 -0.59767 0.93240 +H 0.58435 -0.47325 -0.83778 +H 2.92490 0.36854 0.96213 +H 2.90338 0.49174 -0.78421 +H 2.68484 -2.01184 -0.96764 +H 2.69552 -2.12845 0.80244 +H 4.74911 -1.01511 -0.81774 diff --git a/examples/expl-8/input.toml b/examples/expl-8/input.toml new file mode 100644 index 00000000..33d2fc84 --- /dev/null +++ b/examples/expl-8/input.toml @@ -0,0 +1,11 @@ +# Quick iMTD-GC conformer search with reduced MTD simulation length. +# Uncomment finalhess to add a post-search Hessian and re-rank by Gibbs free energy. +runtype = "mtd_search_quick" # reduced settings; alternatives: mtd_search_squick, mtd_search_mquick +input = "struc.xyz" # input structure file (1-propanol) +# Note: -finalhess (post-search Hessian + free-energy re-ranking) is CLI-only; add it to the crest call above. + +[[calculation.level]] +method = "gfnff" # GFN-FF force field + +[cregen] +ewin = 2.0 # keep conformers within 2.0 kcal/mol of the lowest diff --git a/examples/expl-8/run.sh b/examples/expl-8/run.sh index d28b597c..d3b8404e 100755 --- a/examples/expl-8/run.sh +++ b/examples/expl-8/run.sh @@ -1,26 +1,19 @@ #!/bin/bash +# Quick iMTD-GC conformer search of 1-propanol with reduced MTD simulation length. +# Useful for a fast first survey of conformational space. +# Output: crest_conformers.xyz +# Also available: -squick (super quick) and -mquick (mega quick) modes. +# +# -finalhess adds a post-search Hessian on each conformer in the final ensemble +# and re-ranks by Gibbs free energy. -xtbin='xtb' -crst='crest' - -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -protonate -swel Cs+ - else - $crst struc.xyz -protonate -swel Cs+ -xnam $xtbin - fi +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} -# In a modified version of the protonation tool -# other ionization adducts can be created -# (only mono nuclear ions) -# To do this, the flag '-swel' (short for switch element) -# is used to indicate the new ion and its charge, -# e.g., Na+, Ca2+, Li+, etc. -# -# As a example the alpha-D-glucose-Cs+ adducts -# will be created at the GFN2-xTB level with the above command. -# The adducts can be found in the file 'protonated.xyz' +# --- CLI run (quick search only) --- +crest struc.xyz -quick -gfnff -ewin 2.0 -imtdgc -finalhess +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-8/struc.xyz b/examples/expl-8/struc.xyz index a3c58227..4297fe9a 100644 --- a/examples/expl-8/struc.xyz +++ b/examples/expl-8/struc.xyz @@ -1,26 +1,14 @@ - 24 -FINAL HEAT OF FORMATION = 0.000000 - O -1.240000 0.500286 0.389534 - O -3.422179 -1.988021 -1.200876 - O -1.030343 -2.500198 1.371244 - O -0.269585 -1.376682 -2.050459 - O 0.714096 0.825174 -0.948699 - O -4.564244 0.596025 -1.237038 - C -2.604750 -1.434635 -0.186435 - C -1.275745 -2.232516 0.002738 - C -2.309462 0.069148 -0.458819 - C -0.090913 -1.462394 -0.648941 - C 0.038626 -0.020969 -0.037264 - C -3.562580 0.927621 -0.299600 - H -3.175049 -1.514611 0.751562 - H -1.394054 -3.206789 -0.495459 - H -1.941229 0.164323 -1.490086 - H 0.840456 -2.024192 -0.479114 - H 0.639496 -0.057270 0.885117 - H -3.985456 0.780616 0.699514 - H -3.317920 1.993716 -0.382206 - H -4.113958 -1.329897 -1.403262 - H -1.021769 -1.655987 1.868817 - H 0.187245 -0.582574 -2.396515 - H 1.659435 0.569359 -1.034662 - H -4.311180 0.903728 -2.134995 +12 + +C 1.00510 -0.04436 0.07729 +C 2.52196 -0.10014 0.05638 +C 3.03386 -1.52959 -0.04885 +O 4.45512 -1.53382 -0.04957 +H 0.66450 0.99293 0.15400 +H 0.60392 -0.59767 0.93240 +H 0.58435 -0.47325 -0.83778 +H 2.92490 0.36854 0.96213 +H 2.90338 0.49174 -0.78421 +H 2.68484 -2.01184 -0.96764 +H 2.69552 -2.12845 0.80244 +H 4.74911 -1.01511 -0.81774 diff --git a/examples/expl-9/input.toml b/examples/expl-9/input.toml new file mode 100644 index 00000000..45937e4b --- /dev/null +++ b/examples/expl-9/input.toml @@ -0,0 +1,9 @@ +# Standalone CREGEN ensemble sorting +runtype = "cregen" +ensemble = "xtb.trj" # ensemble/trajectory file to sort + +[cregen] +ewin = 100.0 # energy window for conformer selection (kcal/mol) +rthr = 0.125 # RMSD threshold for duplicate detection (Å) +ethr = 0.05 # energy threshold for duplicate detection (kcal/mol) +bthr = 15.0 # rotational constant threshold (MHz) diff --git a/examples/expl-9/run.sh b/examples/expl-9/run.sh index a487ef16..ce164da2 100755 --- a/examples/expl-9/run.sh +++ b/examples/expl-9/run.sh @@ -1,27 +1,15 @@ #!/bin/bash +# Standalone CREGEN sorting of an ensemble/trajectory file. +# Removes duplicate structures (by RMSD and energy) and sorts by energy. +# Output: crest_conformers.xyz (unique), crest_rotamers.xyz (all), xtb.trj.sorted -xtbin='xtb' -crst='crest' +command -v crest >/dev/null 2>&1 || { + echo >&2 "Cannot find crest binary." + exit 1 +} -command -v $xtbin >/dev/null 2>&1 || { echo >&2 "Cannot find xtb binary. Exit."; exit 1; } -command -v $crst >/dev/null 2>&1 || { echo >&2 "Cannot find crest binary. Exit."; exit 1; } - -if [ $xtbin == 'xtb' ] - then - $crst struc.xyz -tautomerize -ewin 10.0 - else - $crst struc.xyz -tautomerize -ewin 10.0 -xnam $xtbin - fi - -# The -tautomerize flag can be used to request -# a screening of prototropic tautomers. -# The structures are build from a sequence of -# protonating and deprotonating steps of the -# (neutral) input structure. -# In the above example this procedure is -# performed on the guanine molecule to get -# the gas phase tautomers at GFN2-xTB level. -# Within the 10 kcal/mol window 5 tautomers -# should remain at this level. -# The structures can be found in 'tautomers.xyz'. +# --- CLI run --- +crest -cregen xtb.trj -ewin 100.0 +# --- TOML run (equivalent settings) --- +# crest input.toml diff --git a/examples/expl-9/struc.xyz b/examples/expl-9/struc.xyz index 725a04d3..38692b20 100644 --- a/examples/expl-9/struc.xyz +++ b/examples/expl-9/struc.xyz @@ -1,18 +1,14 @@ -16 - energy: -31.983681788270 gnorm: 0.000372144832 xtb: 6.2.2 (89a525f) -N 1.46226284012671 0.18335744247169 -0.07855270314916 -C 1.31781205014203 -1.15293393777405 0.05955064599303 -C 1.54055132778996 -2.13428714261960 -0.91007075980005 -C 1.98029754954570 -1.74761862944617 -2.21865239727883 -N 2.10914370306048 -0.33402358897783 -2.27674973147307 -C 1.86775867472018 0.54390043382692 -1.26343764079100 -N 1.27680953207984 -3.36757233307534 -0.37748854905490 -C 0.91461226786592 -3.14740906594264 0.84460715972356 -N 0.91768716624572 -1.81594469141732 1.17440471889163 -O 2.23471850070944 -2.39670706107528 -3.20454497820690 -N 2.11460025487082 1.85836120568312 -1.49845077783225 -H 0.67952040591559 -1.39514835256529 2.05664466916869 -H 0.63659476510547 -3.91144927326480 1.54245387480526 -H 2.43918920019300 0.01672894983906 -3.16809193535854 -H 1.78950116764777 2.49655065642122 -0.79277065540295 -H 2.15173216814906 2.19392916334893 -2.44513710915798 + 12 +FINAL HEAT OF FORMATION = -1.651323 + C 1.625257 -0.262628 -0.323273 + C 0.518221 0.706221 -0.335177 + C -0.859313 -0.005477 -0.327969 + H -1.683026 0.654493 -0.607965 + H -0.974493 -0.754480 -1.061658 + O -1.171503 -0.423846 1.012985 + H -0.837975 0.254126 1.683603 + H 0.615912 1.428650 0.457852 + H 0.597843 1.325129 -1.321583 + H 1.504113 -0.866659 0.564875 + H 2.639815 0.049302 -0.353007 + H 1.502315 -1.001480 -1.109649 diff --git a/examples/expl-5/xtb.trj b/examples/expl-9/xtb.trj similarity index 100% rename from examples/expl-5/xtb.trj rename to examples/expl-9/xtb.trj diff --git a/meson.build b/meson.build index 22a016de..617a24aa 100644 --- a/meson.build +++ b/meson.build @@ -16,70 +16,466 @@ project( 'crest', - 'fortran', 'c', - version: '3.0.2', - license: 'LGPL-3.0-or-later', - meson_version: '>=0.63', - default_options: [ - 'buildtype=debugoptimized', - 'default_library=static', - 'c_link_args=-static', - 'fortran_link_args=-static', + ['c', 'fortran'], + version : '3.1.0', + license : 'LGPL-3.0-or-later', + meson_version : '>=0.57.0', + default_options : [ + 'buildtype=release', + 'c_std=gnu11', + 'warning_level=0', ], ) -install = not (meson.is_subproject() and get_option('default_library') == 'static') - -# =================================== # -## General configuration information ## -# =================================== # -exe_deps = [] +# ── Compiler flags, static link args, metadata template, inc_dirs ───────────── +# Sets: fc, cc, fc_id, cc_id, fcid_str, ccid_str, static_build, metadata_conf subdir('config') -# create the metadata file with the configured data -configure_file( - input: files('assets/template/metadata.f90'), - output: 'crest_metadata.fh', - configuration : config, +# inc_dirs is defined here rather than in config/meson.build because +# include_directories() rejects absolute paths inside the source/build tree, +# and meson.current_build_dir() only resolves to the *root* build dir when +# called from the root meson.build (subdir() shifts the current dir). +# '.' pairs the source root with its build counterpart automatically, +# which is where configure_file() writes crest_metadata.fh. +inc_dirs = include_directories('include', '.') + +# ═══════════════════════════════════════════════════════════════════════════════ +# OpenMP +# ═══════════════════════════════════════════════════════════════════════════════ +# ifx (intel-llvm) triggers a Meson bug in dependency('openmp') because its +# preprocessor output lacks the delimiter strings Meson searches for. +# Work around it by constructing the dependency manually for that compiler. +if fc_id == 'intel-llvm' + if get_option('openmp') + omp_dep = declare_dependency( + compile_args : ['-qopenmp'], + link_args : ['-qopenmp'], + ) + else + omp_dep = declare_dependency() + endif +else + omp_dep = dependency('openmp', + required : get_option('openmp'), + language : 'fortran', + ) +endif +if omp_dep.found() + add_project_arguments('-DWITH_OMP', language : ['c', 'fortran']) +endif + +_omp_link_dep = omp_dep.found() ? [omp_dep] : [] + +# ═══════════════════════════════════════════════════════════════════════════════ +# LAPACK / BLAS +# ═══════════════════════════════════════════════════════════════════════════════ +# +# Cross-compiler compatibility matrix +# ───────────────────────────────────────────────────────────────────────────── +# Fortran compiler │ Recommended provider │ MKL threading layer +# ──────────────────┼────────────────────────┼────────────────────── +# gfortran │ OpenBLAS (default) │ mkl_gnu_thread +# ifort / ifx │ MKL ← natural match │ mkl_intel_thread +# ifort/ifx + gcc C │ MKL │ mkl_intel_thread (Fortran wins) +# ───────────────────────────────────────────────────────────────────────────── +# The Fortran compiler drives the final link step and therefore owns the OpenMP +# runtime. Choose the MKL threading layer to match the *Fortran* compiler. + +lapack_opt = get_option('lapack') +with_mkl = false +with_openblas = false +lapack_dep = [] +blas_dep = [] + +_prefer_mkl = (fc_id in ['intel', 'intel-llvm']) and (lapack_opt == 'auto') + +# ── MKL ─────────────────────────────────────────────────────────────────────── +if lapack_opt == 'mkl' or _prefer_mkl + _mkl = dependency('', required : false) # not-found sentinel + + # For static builds, mkl-sdl only offers libmkl_rt.so (no .a equivalent). + # Use run_command to get the lib dir from pkg-config WITHOUT resolving the + # dependency (so we can override it for subprojects below). Then look up + # the real .a archives with explicit dirs:. + # iomp5/gomp are provided by -static-intel/-qopenmp-link=static, so we do + # not add them explicitly here. + if static_build + _pc = find_program('pkg-config', required : false) + _mkl_libdir = '' + if _pc.found() + _r = run_command(_pc, '--variable=libdir', 'mkl-sdl', check : false) + if _r.returncode() == 0 + _mkl_libdir = _r.stdout().strip() + endif + endif + if _mkl_libdir != '' + _thread_lib = (fc_id == 'gcc') ? 'mkl_gnu_thread' : 'mkl_intel_thread' + _mkl_sa_parts = [] + foreach lib : ['mkl_intel_lp64', _thread_lib, 'mkl_core'] + _l = fc.find_library(lib, dirs : [_mkl_libdir], required : false, static : true) + if _l.found() + _mkl_sa_parts += [_l] + endif + endforeach + if _mkl_sa_parts.length() == 3 + _mkl = declare_dependency( + dependencies : _mkl_sa_parts, + link_args : ['-lpthread', '-lm', '-ldl'], + ) + endif + endif + endif + + if not _mkl.found() + # Dynamic build (or static path failed): use mkl-sdl single dynamic library. + _mkl = dependency('mkl-sdl', required : false, static : static_build) + endif + + if not _mkl.found() + _thread_lib = (fc_id == 'gcc') ? 'mkl_gnu_thread' : 'mkl_intel_thread' + _omp_lib = (fc_id == 'gcc') ? 'gomp' : 'iomp5' + _mkl_parts = [] + foreach lib : ['mkl_intel_lp64', _thread_lib, 'mkl_core', _omp_lib, 'm', 'dl'] + _l = fc.find_library(lib, required : false, static : static_build) + if _l.found() + _mkl_parts += [_l] + endif + endforeach + if _mkl_parts.length() > 0 + _mkl = declare_dependency(dependencies : _mkl_parts) + endif + endif + + if _mkl.found() + lapack_dep = _mkl + blas_dep = _mkl + with_mkl = true + message('LAPACK/BLAS provider: Intel MKL') + elif lapack_opt == 'mkl' + error('MKL requested but not found. Set PKG_CONFIG_PATH or source setvars.sh.') + else + lapack_opt = 'auto' + endif +endif + +# ── OpenBLAS ────────────────────────────────────────────────────────────────── +if lapack_opt in ['auto', 'openblas'] and not with_mkl + _opa = dependency('openblas', required : false, static : static_build) + if not _opa.found() + _opa = dependency('blas', required : false, static : static_build) + endif + if _opa.found() + lapack_dep = _opa + blas_dep = _opa + with_openblas = true + message('LAPACK/BLAS provider: OpenBLAS') + elif lapack_opt == 'openblas' + error('OpenBLAS requested but not found. Install libopenblas-dev.') + endif +endif + +# ── Netlib reference ────────────────────────────────────────────────────────── +if lapack_opt in ['auto', 'netlib'] and not with_mkl and not with_openblas + _blas = dependency('blas', required : false, static : static_build) + _lapack = dependency('lapack', required : false, static : static_build) + if _blas.found() and _lapack.found() + blas_dep = _blas + lapack_dep = _lapack + message('LAPACK/BLAS provider: netlib reference') + else + _blas = fc.find_library('blas', required : false, static : static_build) + _lapack = fc.find_library('lapack', required : false, static : static_build) + if _blas.found() and _lapack.found() + blas_dep = declare_dependency(dependencies : [_blas]) + lapack_dep = declare_dependency(dependencies : [_lapack]) + message('LAPACK/BLAS provider: system libraries (direct probe)') + elif lapack_opt == 'netlib' + error('Netlib BLAS/LAPACK not found.') + else + error( + 'No LAPACK/BLAS found. Install libopenblas-dev, intel-mkl, or ' + + 'liblapack-dev+libblas-dev, or use -Dlapack=custom.', + ) + endif + endif +endif + +# ── Custom ──────────────────────────────────────────────────────────────────── +if lapack_opt == 'custom' + _ll = get_option('lapack_libs') + _bl = get_option('blas_libs') + if _ll.length() == 0 and _bl.length() == 0 + error('lapack=custom requires lapack_libs and/or blas_libs.') + endif + _deps = [] + foreach lib : _ll + _bl + _deps += [fc.find_library(lib, required : true, static : static_build)] + endforeach + lapack_dep = declare_dependency(dependencies : _deps) + blas_dep = lapack_dep +endif + +if with_mkl + add_project_arguments('-DWITH_MKL', language : ['c', 'fortran']) +endif +if with_openblas + add_project_arguments('-DWITH_OPENBLAS', language : ['c', 'fortran']) +endif + +# ── BLAS/LAPACK pinning for subprojects ─────────────────────────────────────── +# When building statically, subprojects must not resolve BLAS/LAPACK to shared +# libraries. With MKL the pinning is also applied to dynamic builds, so that +# subprojects resolving the generic dependency('lapack')/dependency('blas') +# (e.g. ddX with its 'netlib' backend) link against the same MKL instead of +# mixing the system reference libraries into the link line. This is safe +# because the MKL path above never queried these dependency names itself. +# meson.override_dependency() is global: all subsequent dependency() calls in +# this project AND every subproject get the dep we already found. +if static_build or with_mkl + meson.override_dependency('lapack', lapack_dep) + meson.override_dependency('blas', blas_dep) + if with_openblas + meson.override_dependency('openblas', blas_dep) + endif + if with_mkl and static_build + # Override mkl-sdl so subprojects (gfnff, gfn0, …) don't reintroduce -lmkl_rt + # when we resolved MKL statically (dependency() was never called for mkl-sdl + # in the static path above, so the override is still allowed here). + meson.override_dependency('mkl-sdl', lapack_dep) + endif +endif + +# libgfortran.a (Fortran runtime) references quadmath_* symbols; the GFortran +# driver would add -lquadmath automatically, but meson uses the C linker driver +# for the final link step, so we must add it explicitly for static GCC builds. +_quadmath_dep = [] +if static_build and fc_id == 'gcc' + _qm = fc.find_library('quadmath', required : false, static : true) + if _qm.found() + _quadmath_dep = [_qm] + endif +endif + +# ═══════════════════════════════════════════════════════════════════════════════ +# Optional subproject / external library dependencies +# ═══════════════════════════════════════════════════════════════════════════════ +tomlf_dep = dependency('toml-f', + version : '>=0.2.4', + fallback : ['toml-f', 'tomlf_dep'], + required : get_option('toml-f'), + static : link_deps_static, + default_options : ['tests=false'], ) +with_tomlf = tomlf_dep.found() +if with_tomlf + add_project_arguments('-DWITH_TOMLF', language : ['c', 'fortran']) +endif +# ── ddX continuum solvation library (used by tblite) ────────────────────────── +# Resolved explicitly *before* tblite so that crest controls the subproject +# options: ddX's builtin MKL detection relies on fc.find_library(), which is +# broken with ifx (meson cannot query the compiler's library search dirs). +# The 'netlib' backend instead resolves the generic dependency('lapack') / +# dependency('blas'), which the override above pins to the BLAS/LAPACK +# provider crest already found. +# NOTE: ddX's own CMake build forces -fp-model=precise for Intel compilers, +# but its meson build does not, and meson offers no way to inject the flag +# into a single subproject from here. Under ifx default fast math three of +# ddX's strict self-tests fail (ddx_core/ddx_operators/force_0) while all +# functional solver and gradient tests pass; crest-level results match the +# (precise) CMake build. Known upstream issue, accepted for now. +ddx_dep = dependency('ddx', + version : '>=0.8.0', + fallback : ['ddx', 'ddx_dep'], + required : get_option('tblite'), + static : link_deps_static, + default_options : ['default_library=static', 'lapack=netlib'], +) +with_ddx = ddx_dep.found() -# Documentation -#subdir('docs') +tblite_dep = dependency('tblite', + version : '>=0.3.0', + fallback : ['tblite', 'tblite_dep'], + required : get_option('tblite'), + static : link_deps_static, + default_options: ['api=false', 'ddx=true', 'hdf5=disabled', 'trexio=disabled'], +) +with_tblite = tblite_dep.found() +if with_tblite + add_project_arguments('-DWITH_TBLITE', language : ['c', 'fortran']) +endif -# Collect source of the project -prog = [] +# WITH_DDX guards crest sources that use the ddX modules directly (the +# standalone continuum-solvation components). ddX is pulled in via tblite, so +# it requires tblite to be present as well; this is distinct from WITH_TBLITE +# so that a tblite built without ddX (-Dtblite:ddx=false) does not break them. +if with_ddx and with_tblite + add_project_arguments('-DWITH_DDX', language : ['c', 'fortran']) +endif + +with_gxtb = get_option('gxtb') +if with_gxtb + if not with_tblite + error('gxtb requires tblite to be enabled') + endif + add_project_arguments('-DWITH_GXTB', language : ['c', 'fortran']) +endif + +gfnff_dep = dependency('gfnff', + fallback : ['gfnff', 'gfnff_dep'], + required : get_option('gfnff'), + static : link_deps_static, + default_options : ['tests=false'], +) +with_gfnff = gfnff_dep.found() +if with_gfnff + add_project_arguments('-DWITH_GFNFF', language : ['c', 'fortran']) +endif + +gfn0_dep = dependency('gfn0', + fallback : ['gfn0', 'gfn0_dep'], + required : get_option('gfn0'), + static : link_deps_static, +) +with_gfn0 = gfn0_dep.found() +if with_gfn0 + add_project_arguments('-DWITH_GFN0', language : ['c', 'fortran']) +endif + +libpvol_dep = dependency('libpvol', + fallback : ['pvol', 'libpvol_dep'], + required : get_option('libpvol'), + static : link_deps_static, + default_options : ['tests=false'], +) +with_libpvol = libpvol_dep.found() +if with_libpvol + add_project_arguments('-DWITH_LIBPVOL', language : ['c', 'fortran']) +endif + +lwoniom_dep = dependency('lwoniom', + fallback : ['lwoniom', 'lwoniom_dep'], + required : get_option('lwoniom'), + static : link_deps_static, +) +with_lwoniom = lwoniom_dep.found() +if with_lwoniom + add_project_arguments('-DWITH_LWONIOM', language : ['c', 'fortran']) +endif + +fmlip_dep = dependency('fmlip_relay', + fallback : ['fmlip_relay', 'fmlip_relay_dep'], + required : get_option('fmlip-relay'), + static : link_deps_static, +) +with_fmlip = fmlip_dep.found() +if with_fmlip + add_project_arguments('-DWITH_FMLIP_RELAY', language : ['c', 'fortran']) +endif + +if get_option('tests') + testdrive_dep = dependency('test-drive', + version : '>=0.4.0', + fallback : ['test-drive', 'testdrive_dep'], + required : true, + static : link_deps_static, + ) +endif + +# ═══════════════════════════════════════════════════════════════════════════════ +# Metadata header — finalise and generate +# ═══════════════════════════════════════════════════════════════════════════════ +# config/meson.build populated metadata_conf with system/compiler info but +# deferred the with_* feature flags (not yet known at that point). +# Patch them in now that all deps have been resolved. +metadata_conf.set('tomlfvar', with_tomlf.to_string()) +metadata_conf.set('gfn0var', with_gfn0.to_string()) +metadata_conf.set('gfnffvar', with_gfnff.to_string()) +metadata_conf.set('tblitevar', with_tblite.to_string()) +metadata_conf.set('ddxvar', with_ddx.to_string()) +metadata_conf.set('libpvolvar', with_libpvol.to_string()) +metadata_conf.set('lwoniomvar', with_lwoniom.to_string()) +metadata_conf.set('fmliprelayvar', with_fmlip.to_string()) + +configure_file( + input : 'assets/template/metadata.f90', + output : 'crest_metadata.fh', + configuration : metadata_conf, +) + +# ═══════════════════════════════════════════════════════════════════════════════ +# Source collection +# ═══════════════════════════════════════════════════════════════════════════════ srcs = [] +prog = [] subdir('src') +# ═══════════════════════════════════════════════════════════════════════════════ +# Build targets +# ═══════════════════════════════════════════════════════════════════════════════ +_optional_deps = [] +foreach d : [tblite_dep, gfn0_dep, gfnff_dep, libpvol_dep, lwoniom_dep, tomlf_dep, fmlip_dep] + if d.found() + _optional_deps += [d] + endif +endforeach -# Create library target -crest_lib = library( - meson.project_name(), - sources: srcs, - dependencies: exe_deps, -# include_directories: include_directories('include'), +lib_crest = static_library( + 'crest', + sources : srcs, + include_directories : inc_dirs, + dependencies : [lapack_dep, blas_dep] + _omp_link_dep + _optional_deps + _quadmath_dep, + install : false, + pic : true, ) -# Export as dependency -crest_inc = crest_lib.private_dir_include() -crest_dep = declare_dependency( - link_with: crest_lib, - include_directories: crest_inc, - dependencies: exe_deps, +executable( + 'crest', + sources : prog, + include_directories : inc_dirs, + link_with : lib_crest, + dependencies : [lapack_dep, blas_dep] + _omp_link_dep + _optional_deps + _quadmath_dep, + link_language : 'fortran', + install : true, ) +# ═══════════════════════════════════════════════════════════════════════════════ +# Docs, tests, install +# ═══════════════════════════════════════════════════════════════════════════════ +subdir('docs') -# Create executable target -crest_exe = executable( - meson.project_name(), - sources: prog, - dependencies: crest_dep, - install: install, - link_language: 'fortran', -) +if get_option('tests') + subdir('test') +endif +add_test_setup('crest', + is_default : true, + exclude_suites : ['ddx','toml-f'], +) +install_data( + 'COPYING', 'COPYING.LESSER', + install_dir : get_option('datadir') / 'licenses' / meson.project_name(), +) -# add the testsuite separate meson.build -#subdir('testsuite') #has to be filled with tests, not availabel yet +# ═══════════════════════════════════════════════════════════════════════════════ +# Summary +# ═══════════════════════════════════════════════════════════════════════════════ +summary({ + 'Fortran compiler' : '@0@ @1@'.format(fcid_str, fc.version()), + 'C compiler' : '@0@ @1@'.format(ccid_str, cc.version()), + 'Build type' : get_option('buildtype'), + 'Static binary' : static_build, + 'OpenMP' : omp_dep.found(), + 'MKL' : with_mkl, + 'OpenBLAS' : with_openblas, + 'tblite' : with_tblite, + 'ddX (via tblite)' : with_ddx, + 'toml-f' : with_tomlf, + 'GFN-FF' : with_gfnff, + 'GFN0-xTB' : with_gfn0, + 'libpvol' : with_libpvol, + 'lwONIOM' : with_lwoniom, + 'fmlip-relay' : with_fmlip, + 'Unit tests' : get_option('tests'), +}, section : 'crest build configuration') diff --git a/meson_options.txt b/meson_options.txt index 950190e2..acdfef5d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,75 +1,101 @@ # This file is part of crest. # SPDX-Identifier: LGPL-3.0-or-later -# -# crest is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# crest is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with crest. If not, see . -option( - 'la_backend', - type: 'combo', - value: 'mkl-static', - yield: true, - choices: ['mkl', 'mkl-rt', 'mkl-static', 'openblas', 'netlib', 'custom','none'], - description: 'Linear algebra backend for program.', +# ── Parallelism ──────────────────────────────────────────────────────────────── +option('openmp', + type : 'boolean', + value : true, + description : 'Enable OpenMP parallelisation', ) -option( - 'custom_libraries', - type: 'array', - value: [], - description: 'libraries to load for custom linear algebra backend', + +# ── LAPACK / BLAS provider ───────────────────────────────────────────────────── +# auto → try openblas, then mkl, then netlib in that order +# openblas → OpenBLAS (bundles BLAS+LAPACK) +# mkl → Intel Math Kernel Library +# netlib → Reference LAPACK + BLAS +# custom → set lapack_libs / blas_libs manually on the meson command line +# using -Dlapack_libs=... -Dblas_libs=... +option('lapack', + type : 'combo', + choices : ['auto', 'openblas', 'mkl', 'netlib', 'custom'], + value : 'auto', + description : 'LAPACK/BLAS provider', ) -option( - 'openmp', - type: 'boolean', - value: true, - yield: true, - description: 'use OpenMP parallelisation', + +# Only used when lapack=custom: +option('lapack_libs', + type : 'array', + value : [], + description : 'Extra library names to link for LAPACK (custom mode)', ) -option( - 'WITH_TBLITE', - type: 'boolean', - value: true, - description: 'build with tblite integration', +option('blas_libs', + type : 'array', + value : [], + description : 'Extra library names to link for BLAS (custom mode)', ) -option( - 'WITH_GFN0', - type: 'boolean', - value: true, - description: 'build with GFN0-xTB integration', + +# ── Static binary ───────────────────────────────────────────────────────────── +option('static', + type : 'boolean', + value : false, + description : 'Attempt a fully static binary (includes OpenMP + LAPACK runtime)', ) -option( - 'WITH_GFNFF', - type: 'boolean', - value: true, - description: 'build with GFN-FF integration', + +# ── Static subproject linking ───────────────────────────────────────────────── +# Link subprojects (tblite, gfn0, gfnff, pvol, lwoniom, toml-f, …) as static +# archives without forcing a fully static binary. System runtimes stay dynamic. +# Implied by -Dstatic=true. +option('static-deps', + type : 'boolean', + value : true, + description : 'Link subprojects statically without a fully static binary', ) -option( - 'WITH_TOMLF', - type: 'boolean', - value: true, - description: 'build with toml-f integration', + +# ── Optional computational chemistry libraries ───────────────────────────────── +option('tblite', + type : 'feature', + value : 'auto', + description : 'Enable tblite semiempirical library', ) -option( - 'WITH_LIBPVOL', - type: 'boolean', - value: true, - description: 'build with libpvol integration', +option('gxtb', + type : 'boolean', + value : false, + description : 'Enable g-xTB via tblite (requires tblite)', ) - -option( - 'WITH_LWONIOM', - type: 'boolean', - value: true, - description: 'build with lwONIOM integration', +option('toml-f', + type : 'feature', + value : 'auto', + description : 'Enable TOML-Fortran library (also enables file-based input)', +) +option('gfn0', + type : 'feature', + value : 'auto', + description : 'Enable GFN0-xTB library', +) +option('gfnff', + type : 'feature', + value : 'auto', + description : 'Enable GFN-FF library', +) +option('libpvol', + type : 'feature', + value : 'auto', + description : 'Enable libpvol (volume computation) library', +) +option('lwoniom', + type : 'feature', + value : 'auto', + description : 'Enable lwONIOM library', +) +option('fmlip-relay', + type : 'feature', + value : 'auto', + description : 'Enable fmlip-relay ML/IP interface', ) +# ── Developer options ────────────────────────────────────────────────────────── +option('tests', + type : 'boolean', + value : true, + description : 'Build unit tests (requires test-drive)', +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49c591a2..3d22d8aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ # along with crest. If not, see . +add_subdirectory("molecule") add_subdirectory("parsing") add_subdirectory("optimize") add_subdirectory("calculator") @@ -23,12 +24,14 @@ add_subdirectory("dynamics") add_subdirectory("qcg") add_subdirectory("qmhelpers") add_subdirectory("graphs") -add_subdirectory("rigidconf") +add_subdirectory("molbuilder") add_subdirectory("discretize") add_subdirectory("entropy") add_subdirectory("legacy_algos") add_subdirectory("msreact") - +add_subdirectory("sorting") +add_subdirectory("basinhopping") +add_subdirectory("chelpers") set(dir "${CMAKE_CURRENT_SOURCE_DIR}") @@ -37,28 +40,22 @@ list(APPEND srcs "${dir}/axis_module.f90" "${dir}/biasmerge.f90" "${dir}/bondconstraint.f90" - "${dir}/canonical.f90" - "${dir}/ccegen.f90" "${dir}/choose_settings.f90" "${dir}/classes.f90" "${dir}/cleanup.f90" "${dir}/cn.f90" "${dir}/compress.f90" "${dir}/confparse.f90" - "${dir}/cregen.f90" "${dir}/crest_pars.f90" - "${dir}/ensemblecomp.f90" "${dir}/eval_timer.f90" "${dir}/filemod.f90" "${dir}/flexi.F90" "${dir}/freqmasses.f90" "${dir}/geo.f90" - "${dir}/identifiers.f90" "${dir}/internals.f90" "${dir}/internals2.f90" "${dir}/iomod.F90" "${dir}/legacy_wrappers.f90" - "${dir}/ls_rmsd.f90" "${dir}/marqfit.f90" "${dir}/minitools.f90" "${dir}/miscdata.f90" @@ -66,26 +63,15 @@ list(APPEND srcs "${dir}/ompmklset.F90" "${dir}/printouts.f90" "${dir}/prmat.f90" - "${dir}/propcalc.f90" - "${dir}/quicksort.f90" - "${dir}/readl.f90" "${dir}/restartlog.f90" - "${dir}/rotcompare.f90" "${dir}/scratch.f90" - "${dir}/sdfio.f90" "${dir}/select.f90" - "${dir}/signal.c" - "${dir}/sigterm.f90" - "${dir}/sortens.f90" - "${dir}/strucreader.f90" - "${dir}/symmetry2.f90" - "${dir}/symmetry_i.c" + "${dir}/sigterm.F90" + "${dir}/symmetry_i.f90" "${dir}/timer.f90" "${dir}/trackorigin.f90" "${dir}/utilmod.f90" "${dir}/wallsetup.f90" - "${dir}/zdata.f90" - "${dir}/ztopology.f90" ) list(APPEND prog diff --git a/src/algos/CMakeLists.txt b/src/algos/CMakeLists.txt index 3df8c266..8f68ab9f 100644 --- a/src/algos/CMakeLists.txt +++ b/src/algos/CMakeLists.txt @@ -28,12 +28,17 @@ list(APPEND srcs "${dir}/search_1.f90" "${dir}/search_mecp.f90" "${dir}/setuptest.f90" + "${dir}/sorting.f90" "${dir}/protonate.f90" - "${dir}/hessian_tools.f90" - "${dir}/ConfSolv.F90" "${dir}/search_conformers.f90" "${dir}/search_entropy.f90" "${dir}/parallel.f90" + "${dir}/deform_opt_hess.f90" + "${dir}/queueing.f90" + "${dir}/alkylize.f90" + "${dir}/dryrun.f90" + "${dir}/propcalc.f90" + "${dir}/term_ui.f90" ) set(srcs ${srcs} PARENT_SCOPE) diff --git a/src/algos/ConfSolv.F90 b/src/algos/ConfSolv.F90 deleted file mode 100644 index e0818c47..00000000 --- a/src/algos/ConfSolv.F90 +++ /dev/null @@ -1,577 +0,0 @@ -!================================================================================! -! This file is part of crest. -! -! Copyright (C) 2023 Philipp Pracht -! -! crest is free software: you can redistribute it and/or modify it under -! the terms of the GNU Lesser General Public License as published by -! the Free Software Foundation, either version 3 of the License, or -! (at your option) any later version. -! -! crest is distributed in the hope that it will be useful, -! but WITHOUT ANY WARRANTY; without even the implied warranty of -! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -! GNU Lesser General Public License for more details. -! -! You should have received a copy of the GNU Lesser General Public License -! along with crest. If not, see . -!================================================================================! - -!> A data storage module for hosting ConfSolv as a http server with requests -!> We need to know the server's PORT to create the request -!> CREST can either try to host the server on its own, or the user -!> must provide the PORT. -!> All of this assumes that the ConfSolv submodule was loaded - -module ConfSolv_module - use crest_parameters - use crest_data - use strucrd - use iomod - implicit none - public - - !> ConfSolv helper script PID - integer,allocatable :: cs_pid - !> ConfSolv helper script name - character(len=:),allocatable :: cs_bin - !> ConfSolv port server port - integer,allocatable :: cs_port - !> ConfSolv teardown instruction - logical :: cs_teardown = .false. - !> Keeping track of setup. Has it been called already? - logical :: cs_setup = .false. - - !> ConfSolv parameter location - character(len=:),allocatable :: cs_param - !> ConfSolv solvent & smiles - character(len=:),allocatable :: cs_solvent - character(len=:),allocatable :: cs_solvfile - character(len=:),allocatable :: cs_smiles - - !> n_threshold_mols - integer :: cs_n_threshold_mols = 1 - -!========================================================================================! -!========================================================================================! -contains !> MODULE PROCEDURES START HERE -!========================================================================================! -!========================================================================================! - - subroutine cs_deallocate() - if (allocated(cs_pid)) deallocate (cs_pid) - if (allocated(cs_bin)) deallocate (cs_bin) - if (allocated(cs_port)) deallocate (cs_port) - if (allocated(cs_param)) deallocate (cs_param) - if (allocated(cs_solvent)) deallocate (cs_solvent) - end subroutine cs_deallocate - -!=================================================! - - function cs_running() result(running) - implicit none - logical :: running - integer :: io - character(len=:),allocatable :: job - running = .false. - - io = 0 - if (.not.allocated(cs_bin).or. & - & .not.allocated(cs_pid).or. & - & .not.allocated(cs_port)) then - running = .false. - return - end if - - job = trim(cs_bin)//' --test '//to_str(cs_port) - - call command(job,io) - - end function cs_running - -!=================================================! - subroutine cs_shutdown(io) - implicit none - integer,intent(out) :: io - - io = 0 - if (cs_teardown.and.allocated(cs_pid).and.allocated(cs_port)) then - write (stdout,'(/,a,i0)') 'Shutting down http://localhost/',cs_port - call kill(cs_pid,9,io) - deallocate (cs_pid) - call cs_shutdown2(io) - end if - - end subroutine cs_shutdown - -!=================================================! - subroutine cs_shutdown2(io) - use iomod - implicit none - integer,intent(out) :: io - integer :: ich,ro,pids - character(len=100) :: str - character(len=50) :: str2 - call command('lsof confsolv.out > tmpcs 2>/dev/null',io) - - open(newunit=ich,file='tmpcs') - do - read(ich,'(a)',iostat=ro) str - if(ro < 0 ) exit - !write(*,*) trim(str) - read(str,*,iostat=ro) str2,pids - if(ro == 0)then - ! write(*,*) pids - call kill(pids,9,io) - endif - enddo - close(ich) - - call remove('tmpcs') - end subroutine cs_shutdown2 - -!=================================================! - - subroutine cs_deploy() - implicit none - character(len=:),allocatable :: job - integer :: io,ich - character(len=50) :: atmp - logical :: ex - - if (.not.allocated(cs_bin)) cs_bin = 'confsolvserver' - !call remove('confsolv.out') - call remove('config_template.toml') - - job = 'nohup '//trim(cs_bin)//' -l '//'> confsolv.out 2>/dev/null'//' &' - - write (stdout,'(2x,a,a)') 'Hosting command: ',trim(job) - call command(job,io) - - if (io /= 0) error stop '**ERROR** failed to host ConfSolv server' - !call sleep(3) - do - call sleep(1) - inquire (file='config_template.toml',exist=ex) - if (ex) exit - end do - - !> read port and pid - open (newunit=ich,file='confsolv.out') - read (ich,*) atmp,cs_pid - read (ich,*) atmp,cs_port - close (ich) - cs_teardown = .true. - cs_setup = .false. - write (stdout,'(2x,2(a,i0))') 'ConfSolv server will be running at http://localhost:',cs_port,' with PID ',cs_pid - end subroutine cs_deploy - -!=================================================! - - subroutine cs_write_config(ensname,threads) - implicit none - character(len=*) :: ensname - integer :: threads - integer :: i,j,k,l,ich,io - character(len=1024) :: atmp - - call getcwd(atmp) - open (newunit=ich,file='config.toml') - call wrtoml_int(ich,'port',cs_port) - call wrtoml_int(ich,'pid',cs_pid) - call wrtoml_int(ich,'num_cores',threads) - if (allocated(cs_param)) then - call wrtoml(ich,'model_path',cs_param) - end if - - call wrtoml(ich,'xyz_file',trim(atmp)//'/'//trim(ensname)) - if (allocated(cs_solvfile)) then - call wrtoml(ich,'solvent_file',trim(atmp)//'/'//cs_solvfile) - else - call wrtoml(ich,'solvent_file',trim(atmp)//'/'//'crest_solvents.csv') - end if - - call wrtoml_int(ich,'n_threshold_mols',cs_n_threshold_mols) - close (ich) - contains - subroutine wrtoml(ch,key,val) - integer :: ch - character(len=*) :: key - character(len=*) :: val - write (ch,'(a,a,a,a)') trim(key),' = "',trim(val),'"' - end subroutine wrtoml - subroutine wrtoml_int(ch,key,val) - integer :: ch - character(len=*) :: key - integer :: val - write (ch,'(a,a,i0)') trim(key),' = ',val - end subroutine wrtoml_int - end subroutine cs_write_config - -!========================================================================================! - - subroutine cs_write_solvent_csv(solvent,smiles,ch) -!************************************************************** -!* From CREST's side it makes only sense to define ONE solvent -!* despite ConfSolv being able to handle multiple. -!* ConfSolv will read the solvents from a CSV file with the -!* columns SOLVENT_NAME and SMILES -!************************************************************** - implicit none - character(len=*),intent(in) :: solvent - character(len=*),intent(in),optional :: smiles - integer,intent(in),optional :: ch - integer :: ich - if (.not.present(ch)) then - open (newunit=ich,file='crest_solvents.csv') - else - ich = ch - end if - !> column names - write (ich,'(a,",",a)') 'SOLVENT_NAME','SMILES' - if (present(smiles)) then - write (ich,'(a,",",a)') solvent,smiles - else - !> switch case for available solvents, if no smiles was given - select case (lowercase(solvent)) - case ('acetate') - write (ich,'(a,",",a)') solvent,'CC(=O)[O-]' - case ('acetic acid') - write (ich,'(a,",",a)') solvent,'CC(=O)O' - case ('acetone') - write (ich,'(a,",",a)') solvent,'CC(=O)C' - case ('acetonitrile') - write (ich,'(a,",",a)') solvent,'CC#N' - case ('ammonia') - write (ich,'(a,",",a)') solvent,'N' - case ('ammonium') - write (ich,'(a,",",a)') solvent,'[NH4+]' - case ('benzene') - write (ich,'(a,",",a)') solvent,'c1ccccc1' - case ('benzoate') - write (ich,'(a,",",a)') solvent,'[O-]C(=O)c1ccccc1' - case ('benzylacetate') - write (ich,'(a,",",a)') solvent,'CC(=O)OCc1ccccc1' - case ('butanone','2-butanone') - write (ich,'(a,",",a)') solvent,'CCC(=O)C' - case ('chloride') - write (ich,'(a,",",a)') solvent,'[Cl-]' - case ('trichlormethane') - write (ich,'(a,",",a)') solvent,'C(Cl)(Cl)Cl' - case ('cyclohexane') - write (ich,'(a,",",a)') solvent,'C1CCCCC1' - case ('dibutylamine') - write (ich,'(a,",",a)') solvent,'CC[C@H](C)N[C@H](C)CC' - case ('dichlormethane') - write (ich,'(a,",",a)') solvent,'C(Cl)Cl' - case ('diethanolamine') - write (ich,'(a,",",a)') solvent,'OCCNCCO' - case ('diethanolammonium') - write (ich,'(a,",",a)') solvent,'OCC[NH2+]CCO' - case ('diethylamine') - write (ich,'(a,",",a)') solvent,'CCNCC' - case ('diethylammonium') - write (ich,'(a,",",a)') solvent,'CC[NH2+]CC' - case ('diethylether') - write (ich,'(a,",",a)') solvent,'CCOCC' - case ('heptyloctylether') - write (ich,'(a,",",a)') solvent,'CCCCCCCCOCCCCCCC' - case ('acetamide') - write (ich,'(a,",",a)') solvent,'CC(=O)N(C)C' - case ('diethylformamide') - write (ich,'(a,",",a)') solvent,'CN(C)C=O' - case ('dmso') - write (ich,'(a,",",a)') solvent,'CS(=O)C' - case ('dioxolone','2-dioxolone') - write (ich,'(a,",",a)') solvent,'C1COC(=O)O1' - case ('ethylmethylester') - write (ich,'(a,",",a)') solvent,'CCOC(=O)OC' - case ('ethanol') - write (ich,'(a,",",a)') solvent,'CCO' - case ('ethylacetate') - write (ich,'(a,",",a)') solvent,'CCOC(=O)C' - case ('ethylamine') - write (ich,'(a,",",a)') solvent,'CCN' - case ('ethylaminium') - write (ich,'(a,",",a)') solvent,'CC[NH3+]' - case ('glycol') - write (ich,'(a,",",a)') solvent,'OCCO' - case ('formate') - write (ich,'(a,",",a)') solvent,'C(=O)[O-]' - case ('formic acid') - write (ich,'(a,",",a)') solvent,'C(=O)O' - case ('butyrolacetone') - write (ich,'(a,",",a)') solvent,'O=C1CCCO1' - case ('glycerin') - write (ich,'(a,",",a)') solvent,'OCC(O)CO' - case ('water','h2o') - write (ich,'(a,",",a)') solvent,'O' - case ('sulfuric acid') - write (ich,'(a,",",a)') solvent,'O=S(=O)(O)O' - case ('hexafluorobenzene') - write (ich,'(a,",",a)') solvent,'Fc1c(F)c(F)c(F)c(F)c1F' - case ('isooctane') - write (ich,'(a,",",a)') solvent,'CC(C)CC(C)(C)C' - case ('isopropanol') - write (ich,'(a,",",a)') solvent,'CC(O)C' - case ('methoxide') - write (ich,'(a,",",a)') solvent,'C[O-]' - case ('hexane','n-hexane') - write (ich,'(a,",",a)') solvent,'CCCCCC' - case ('1-nonadecanol','nonadecanol') - write (ich,'(a,",",a)') solvent,'CCCCCCCCCCCCCCCCCCCO' - case ('1-octanol','octanol') - write (ich,'(a,",",a)') solvent,'OCCCCCCCC' - case ('p-dichlorobenzene','dichlorobenzene') - write (ich,'(a,",",a)') solvent,'Clc1ccccc1Cl' - case ('perfluorohexane') - write (ich,'(a,",",a)') solvent,'C(C(C(C(F)(F)F)(F)F)(F)F)(C(C(F)(F)F)(F)F)(F)F' - case ('propanediol') - write (ich,'(a,",",a)') solvent,'C[C@@H](O)CO' - case ('tetraethylammoniom') - write (ich,'(a,",",a)') solvent,'CC[N+](CC)(CC)CC' - case ('thf','tetrahydrofuran') - write (ich,'(a,",",a)') solvent,'O1CCCC1' - case ('toluene') - write (ich,'(a,",",a)') solvent,'Cc1ccccc1' - case ('tributylphosphate') - write (ich,'(a,",",a)') solvent,'O=P(OCCCC)(OCCCC)OCCCC' - case ('triethanolamine','trolamine') - write (ich,'(a,",",a)') solvent,'OCCN(CCO)CCO' - case ('triethanolammonium') - write (ich,'(a,",",a)') solvent,'OCC[NH+](CCO)CCO' - case ('triethylamine','net3') - write (ich,'(a,",",a)') solvent,'CCN(CC)CC' - case ('triethylammonium') - write (ich,'(a,",",a)') solvent,'CC[NH+](CC)CC' - case ('triglyme') - write (ich,'(a,",",a)') solvent,'COCCOCCOCCOC' - case ('urea') - write (ich,'(a,",",a)') solvent,'NC(=O)N' - case default - write (stderr,'(2a)') '**ERROR** failed to find matching solvent SMILES for: ',solvent - error stop - end select - end if - close (ich) - end subroutine cs_write_solvent_csv - -!========================================================================================! - - subroutine confsolv_select_gsoln(nall,ncol,data,gsoln,mapping) -!************************************************ -!* From the matrix of ΔΔGsoln, select the best -!* for each conformer and document which solvent -!* that corresponds to -!************************************************ - implicit none - integer,intent(in) :: nall,ncol - real(wp),intent(in) :: data(ncol,nall) - real(wp),intent(out) :: gsoln(nall) - integer,intent(out) :: mapping(nall) - integer :: i,j,k,l,mink - real(wp) :: dum - mapping(:) = 0 - gsoln(:) = huge(dum) - if (ncol < 3) then -!>--- ConfSolv should put out at least 3 csv columns. The first two are just IDs - write (stderr,'(a)') '**ERROR** dimension mismatch in ConfSolv data processing' - error stop - end if - do i = 1,nall - do j = 3,ncol - k = j-2 - if (data(j,i) < gsoln(i)) then - mink = k - gsoln(i) = data(j,i) - end if - end do - mapping(i) = mink - end do - end subroutine confsolv_select_gsoln - - - subroutine confsolv_dump_gsoln(nall,ncol,gsoln,mapping,headers) -!**************************************************** -!* Dump the selected ΔΔGsoln, and the corresponding -!* solvent for each conformer -!**************************************************** - implicit none - integer,intent(in) :: nall,ncol - real(wp),intent(in) :: gsoln(nall) - integer,intent(in) :: mapping(nall) - character(len=*),intent(in) :: headers(ncol) - integer :: i,j,k,l,mink,ich - real(wp) :: dum - open(newunit=ich,file='confsolv.dat') - do i = 1,nall - k = mapping(i)+1 - write(ich,'(f15.8,1x,a)') gsoln(i),trim(headers(k)) - end do - close(ich) - end subroutine confsolv_dump_gsoln - - -!========================================================================================! -!========================================================================================! -end module ConfSolv_module - -!========================================================================================! -!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< INPUT - character(len=*),intent(in) :: ensname - integer,intent(in) :: nall - integer,intent(in) :: ncpus - !> OUTPUT - real(wp),intent(out) :: gsoln(nall) - integer,intent(out) :: io - !> LOCAL - integer :: i,j,k,l,ich - logical :: pr,wr - character(len=:),allocatable :: job - real(wp),allocatable :: column(:) - real(wp),allocatable :: data(:,:) - integer,allocatable :: mapping(:) - character(len=:),allocatable :: headers(:) - integer :: ncol,nrow - real(wp) :: avg - - io = 0 - gsoln(:) = 0.0_wp - -!>--- setup - if (allocated(cs_pid).and.allocated(cs_port)) then - !> user-provided PID and port (no automatic teardown) - write (stdout,'(2x,a,i0,a,i0)') 'Looking for ConfSolv server (PID: ',cs_pid,') running at '//& - & 'http://localhost:',cs_port - !cs_teardown = .false. - else - !> fallback: automatic host (not recommended) - allocate (cs_pid,cs_port) - call cs_deploy() - end if - if (allocated(cs_param)) then - write (stdout,'(2x,a,/,3x,a)') 'pyTorch checkpoint files located at ',cs_param - else - write (stderr,*) '**ERROR** cannot run ConfSolv without defining checkpoint file location!' - error stop - end if - !> pass the user-defined solvents-csv, or do a single solvent - if (allocated(cs_solvfile)) then - write (stdout,'(2x,a,a)') 'Requested ΔΔGsoln for solvents in ',cs_solvfile - call parse_csv_file_column(cs_solvfile,1,headers) - else - if (allocated(cs_solvent).and.allocated(cs_smiles)) then - write (stdout,'(2x,a,a,3a)') 'Requested ΔΔGsoln for ',cs_solvent,' (SMILES: ',trim(cs_smiles),')' - call cs_write_solvent_csv(cs_solvent,smiles=cs_smiles) - else if (allocated(cs_solvent)) then - write (stdout,'(2x,a,a,a)') 'Requested ΔΔGsoln for ',cs_solvent,' (trying to find SMILES ...)' - call cs_write_solvent_csv(cs_solvent) - end if - allocate(headers(2), source=trim(cs_solvent)) - end if - write (stdout,'(2x,a,a)') 'Processing ensemble file ',trim(ensname) - -!>---- creating the request configuration - write (stdout,'(2x,a)',advance='no') 'Writing config.toml file ...' - flush (stdout) - call cs_write_config(ensname,ncpus) - write (stdout,*) 'done.' - - job = '' - job = trim(job)//' '//cs_bin//' -c config.toml' -!>----- this should only be called once: - if (.not.cs_setup) then - write (stdout,'(2x,a)',advance='no') 'Instructing ConfSolv model setup ...' - flush (stdout) - call command(trim(job)//' -s >> confsolv.out 2>/dev/null',io) - if (io /= 0) then - write (stdout,*) - write (stderr,'(a)') "**ERROR** failed request to ConfSolv server" - call cs_shutdown(io) - error stop - end if - cs_setup = .true. - write (stdout,*) 'done.' - end if - -!>---- and then the actual evaluation - call remove('confsolv.csv') - call remove('confsolv_uncertainty.csv') - write (stdout,'(2x,a)',advance='no') 'Evaluation of ConfSolv D-MPNN ...' - flush (stdout) - call command(trim(job)//' -r >> confsolv.out 2>/dev/null',io) - write (stdout,*) 'done.' - if (io /= 0) then - write (stdout,*) - write (stderr,'(a)') "**ERROR** failed request to ConfSolv server" - call cs_shutdown(io) - error stop - end if - -!>--- read ΔΔGsoln - write (stdout,'(2x,a)',advance='no') 'Reading confsolv.csv ...' - flush (stdout) - call parse_csv_allcolumns('confsolv.csv',data,cols=ncol,rows=nrow) - write (stdout,*) 'done.' - if (nrow == nall) then - if(.not.allocated(mapping)) allocate(mapping(nall)) - call confsolv_select_gsoln(nall,ncol,data,gsoln,mapping) - call confsolv_dump_gsoln(nall,ncol,gsoln,mapping,headers) - else - write (stdout,'(a)') '**ERROR** dimension mismatch in confsolv_request' - call cs_shutdown(io) - error stop - end if - -!>--- read uncertainty - write (stdout,'(2x,a)',advance='no') 'Reading confsolv_uncertainty.csv ...' - flush (stdout) - call parse_csv_allcolumns('confsolv_uncertainty.csv',data) - write (stdout,*) 'done.' - if (size(data,2) == nall) then - avg = 0.0_wp - do i=1,nall - k=mapping(i) + 2 - avg=avg+data(k,i) - enddo - avg = avg / real(nall,wp) - write (stdout,'(2x,a,f25.15)') 'Average uncertainty of ConfSolv prediction:',avg - else - write (stdout,'(a)') '**ERROR** dimension mismatch in confsolv_request' - call cs_shutdown(io) - error stop - end if - - !call cs_shutdown2(i) - - if (allocated(headers)) deallocate(headers) - if (allocated(data)) deallocate(data) - if (allocated(mapping)) deallocate(mapping) - if (allocated(column)) deallocate (column) - return -end subroutine confsolv_request - -!========================================================================================! -!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<. +!================================================================================! + +subroutine crest_setup_alkylize(env) + use crest_parameters + use crest_data + use strucrd + use molbuilder_classify + implicit none + type(systemdata),intent(inout) :: env + type(coord_classify) :: molc + type(coord) :: mol + + integer :: ii,jj,kk,cc + integer :: splt(3) + + call env%ref%to(mol) + + call underline("Analyzing Input Structure") + + call setup_classify(mol,molc) + call functional_group_classify(molc) + if (molc%nfuncs == 0) then + write (stdout,'(a)') 'no relevant substructures found' + return + else + write (stdout,'(a)') 'Found the following substructure parts' + call molc%print_funcgroups(stdout) + end if + + do ii = 1,molc%nfuncs + associate (func => molc%funcgroups(ii)) + if (trim(func%name) == 'alkyl') then + + !> only for propane or longer + if (func%natms > 6) then + write (stdout,'(a)') 'selected alkyl group for fragment dispatching' + splt(:) = 0 + splt(1) = func%attached_to + kk = 1 + do while (kk < 3) + do jj = 1,molc%nat + if (molc%Ah(jj,splt(kk)) == 1.and. & + & .not.any(splt(:) .eq. jj).and. & + & any(func%ids(:) .eq. jj).and.kk < 3) then + kk = kk+1 + splt(kk) = jj + end if + end do + end do + call env%addsplitqueue(splt) + end if + write (stdout,'(2x,a,5(1x,i0))') '> shared atoms:',splt(:) + end if + end associate + end do + + write (stdout,*) +end subroutine crest_setup_alkylize + +subroutine crest_proxy_nalkane(env,doreturn) + use crest_parameters + use crest_data + use strucrd + use molbuilder_classify + use INTERNALS_mod + implicit none + type(systemdata),intent(inout) :: env + logical,intent(out) :: doreturn + type(coord) :: mol,newmol + type(coord_classify) :: molc + integer :: ii,jj + integer,allocatable :: na(:),nb(:),nc(:) + real(wp),allocatable :: zmat(:,:) + integer :: itmp(4) + + doreturn = .false. + + if (env%alkylize) then + call env%ref%to(mol) + call setup_classify(mol,molc) + call functional_group_classify(molc) + + do ii = 1,molc%nfuncs + if (molc%funcgroups(ii)%name == 'alkane'.or. & + & (molc%funcgroups(ii)%name == 'alkyl'.and. & + & molc%funcgroups(ii)%natms >= (molc%nat-3)) & + & ) then + write (stdout,'(a)') '> This substructure contains an n-alkane.' + if (env%alkylizeskip) then + write (stdout,'(a)') '> SKIPPING sampling and writing LINEAR structure.' + doreturn = .true. + else + write (stdout,'(a)') '> Writing LINEAR structure and sampling independently.' + end if + + call molc%get_zmat(.true.) + call molc%print_zmat(stdout) + + !> ZMAT construction to make the molecule linear + do jj = 1,molc%nat + if (molc%ztod(jj) .ne. 0) then + itmp(1) = molc%at(jj) + itmp(2) = molc%at(molc%zmap(jj,1)) + itmp(3) = molc%at(molc%zmap(jj,2)) + itmp(4) = molc%at(molc%zmap(jj,3)) + if (all(itmp(:) > 1)) then + !write(*,*) 'C-C bond:',molc%zmap(jj,1:2) + molc%zmat(3,jj) = -pi + end if + end if + end do + + call molc%print_zmat(stdout) + call molc%from_zmat(newmol) + call newmol%write(conformerfile) + + call env%ref%load(newmol) + exit + end if + end do + + end if + +end subroutine crest_proxy_nalkane + diff --git a/src/algos/deform_opt_hess.f90 b/src/algos/deform_opt_hess.f90 new file mode 100644 index 00000000..afc2cedf --- /dev/null +++ b/src/algos/deform_opt_hess.f90 @@ -0,0 +1,84 @@ +subroutine deform_opt_hess(calc,mol) + use crest_calculator + use strucrd + use irmsd_module + use bh_step_module + use crest_parameters + use optimize_module + use thermochem_module + use hr_utils + use optimize_maths + implicit none + type(calcdata) :: calc + type(coord) :: mol + type(coord) :: molnew,mol_reopt + real(wp) :: energy,stepsize,rmsdval + real(wp) :: grad(3,mol%nat) + logical :: pr,wr + integer :: io, nat3,idx + + real(wp) :: etot + real(wp), allocatable :: h_init(:,:) + + if (allocated(calc%chess)) deallocate(calc%chess) + + !allocate (calc%chess) + !call calc%chess%alloc(mol%nat,calc%hu_steps,calc%hguess,calc%initialize_hr_type, calc%hr_hu_type) !Maybe in future just reset the cash here, or if this works we only call this here + + nat3 = 3*mol%nat + molnew%nat = mol%nat + molnew%at = mol%at + molnew%xyz = mol%xyz + + allocate(h_init(nat3,nat3)) + + stepsize = calc%doh_stepsize + + + call take_fixed_stepsize_cart(molnew,stepsize,calc) + + pr = .true. + wr = .true. + + call optimize_geometry(molnew,mol_reopt,calc,energy,grad,pr,wr,io) + + pr = .true. + wr = .true. + + rmsdval = rmsd(mol,mol_reopt) + + write(stdout,*) 'VALUE OF RMSD FOR REOPTIMISED STRUCTURE',rmsdval + + if (rmsdval .le. 0.1_wp) then + + idx = minloc(calc%chess%order,1) + if (minval(calc%chess%order) .eq. 0) idx = 1 + + write(stdout,*)calc%chess%coords(idx,:,:) - molnew%xyz(:,:) + + call initialize_hessian(calc,calc%chess%initialize_type,molnew%xyz,molnew%nat,molnew%at,calc%chess%hess(:),calc%chess%hguess,pr) !> This hguess is set through the hguess variable of the optimizer and needs to be hardcoded/set explicitly before initialization for benchmarking!! + call dhtosq(nat3,H_init,calc%chess%hess) !> maybe this should all be inside the construct bfgs function later? -> cannot due to circular import!!! + write(stdout,*) !> Hessian type (gfnff,mod,identity) is set through input file and is already encoded into the calc object + write(stdout,*)"THERMO FROM INITIALIZED HESSIAN:" + write(stdout,*) + call calc_thermo_from_hess(molnew,H_init,pr, & + & calc%nt,calc%temperatures,calc%ithr,calc%fscal,calc%sthr,calc%et, & + & calc%ht,calc%gt,calc%stot,etot) + + call calc%chess%construct_hessian() + + write (stdout,*) + write (stdout,*) "THERMO FROM RECONSTRUCTED HESSIAN:" + write (stdout,*) + + call calc_thermo_from_hess(molnew,calc%chess%H(:,:),pr, & + & calc%nt,calc%temperatures,calc%ithr,calc%fscal,calc%sthr,calc%et, & + & calc%ht,calc%gt,calc%stot,etot) + + else + write(stdout,*) "Reoptimised Geometry not equal to initial structure" + + endif + + +end subroutine deform_opt_hess \ No newline at end of file diff --git a/src/algos/dryrun.f90 b/src/algos/dryrun.f90 new file mode 100644 index 00000000..0d6451c1 --- /dev/null +++ b/src/algos/dryrun.f90 @@ -0,0 +1,233 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +subroutine crest_dry_run(env,tim) +!******************************************************************** +!* Dry-run runtype. Prints a formatted summary of all CREST settings +!* and exits cleanly without performing any calculations. +!* +!* Input/Output: +!* env - crest's systemdata object +!* tim - timer object +!******************************************************************** + use crest_parameters + use crest_data + use crest_calculator + use iomod + implicit none + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + character(len=512) :: dumstr + character(len=:),allocatable :: ctmp + logical :: ex +!========================================================================================! + + write (stdout,*) + call drawbox(stdout,'D R Y R U N',charset=6,width=80) + write (stdout,'(1x,a)') 'Dry run was requested.' + write (stdout,'(1x,a)') 'Running CREST with the chosen arguments would result in the following settings:' + write (stdout,*) + +!========================================================================================! +!> INPUT FILE +!========================================================================================! + call drawbox(stdout,'Input',charset=4,padl=2,padr=2) + write (stdout,*) + ex = file_exists(env%inputcoords) + write (stdout,'(2x,a,a)',advance='no') 'Input file : ',trim(env%inputcoords) + if (ex) then + write (stdout,*) + else + write (stdout,'(1x,"( ",a," )")') colorify('NOT FOUND','red') + end if + write (stdout,*) + +!========================================================================================! +!> RUNTYPE +!========================================================================================! + call drawbox(stdout,'Job type',charset=4,padl=2,padr=2) + write (stdout,*) + select case (env%crestver) + case (crest_mfmdgc) + write (stdout,'(2x,a)') 'Conformational search via the MF-MD-GC algorithm ('//colorify("DEPRECATED",'red')//')' + case (crest_imtd) + write (stdout,'(2x,a)') 'Conformational search via the iMTD-GC algorithm' + case (crest_imtd2) + write (stdout,'(2x,a)') 'Conformational search via the iMTD-sMTD algorithm (-v4)' + case (crest_mdopt) + write (stdout,'(2x,a)') 'Ensemble reoptimization (-mdopt)' + case (crest_mdopt2) + write (stdout,'(2x,a)') 'Ensemble reoptimization, variant 2 (-mdopt2)' + case (crest_screen) + write (stdout,'(2x,a)') 'Ensemble screening and reoptimization (-screen)' + case (crest_nano) + write (stdout,'(2x,a)') 'GFNn-xTB nano reactor (-reactor)' + case (crest_sp) + write (stdout,'(2x,a)') 'Standalone singlepoint calculation' + case (crest_optimize) + if (.not.env%crest_ohess) then + write (stdout,'(2x,a)') 'Standalone geometry optimization' + else + write (stdout,'(2x,a)') 'Standalone geometry optimization followed by numerical Hessian' + end if + case (crest_moldyn) + write (stdout,'(2x,a)') 'Standalone molecular dynamics simulation' + case (crest_s1) + write (stdout,'(2x,a)') 'Conformational search (crest_s1)' + case (crest_mecp) + write (stdout,'(2x,a)') 'Minimum energy crossing point (MECP) search' + case (crest_numhessian) + write (stdout,'(2x,a)') 'Numerical Hessian calculation' + case (crest_scanning) + write (stdout,'(2x,a)') 'Coordinate scan' + case (crest_rigcon) + write (stdout,'(2x,a)') 'Rule-based conformer generation' + case (crest_sorting) + write (stdout,'(2x,a)') 'Standalone ensemble sorting (CREGEN)' + case (crest_bh) + write (stdout,'(2x,a)') 'Basin-hopping conformer search' + case (crest_bhpt) + write (stdout,'(2x,a)') 'Basin-hopping with parallel tempering' + case (crest_none) + write (stdout,'(2x,a)') '' + case default + write (stdout,'(2x,a,i0,a)') '' + end select + write (stdout,*) + +!========================================================================================! +!> CALCULATION SETTINGS +!========================================================================================! + call drawbox(stdout,'Calculation settings',charset=4,padl=2,padr=2) + write (stdout,*) + if (associated(env%calc)) then + if (env%calc%ncalculations > 0) then + call env%calc%info(stdout,printhdr=.false.) + else + write (stdout,'(2x,a)') 'Calculation object associated but no levels defined yet.' + end if + else + write (stdout,'(2x,a)') 'Calculation object not associated (legacy mode or not yet set up).' + end if + write (stdout,*) + +!========================================================================================! +!> OPTIMIZATION SETTINGS +!========================================================================================! + call drawbox(stdout,'Optimization settings',charset=4,padl=2,padr=2) + write (stdout,*) + + write (stdout,'(2x,a,t35,": ",a,1x,"(",i0,")")') 'Optimization level',optlevflag(env%optlev),nint(env%optlev) + if (associated(env%calc)) then + block + use optimize_utils,only:get_optthr + real(wp) :: ethr,gthr + integer :: nat,iolev + nat = env%ref%nat + iolev = nint(env%optlev) + call get_optthr(nat,iolev,env%calc,ethr,gthr) + write (stdout,'(2x,a,t35,": ",i0)') 'Max cycles (calc obj)',env%calc%maxcycle + write (stdout,'(2x,a,t35,": ",es12.4)') 'Energy convergence [Eh]',ethr + write (stdout,'(2x,a,t35,": ",es12.4)') 'Gradient convergence [Eh/a0]',gthr + end block + end if + write (stdout,*) + +!========================================================================================! +!> MD / MTD SETTINGS +!========================================================================================! + call drawbox(stdout,'MD / MTD settings',charset=4,padl=2,padr=2) + write (stdout,*) + if (env%mdtime > 0.0_wp) then + write (stdout,'(2x,a,t35,": ",f10.1,a)') 'Simulation length',env%mdtime,' ps' + else + write (stdout,'(2x,a,t35,": ",a)') 'Simulation length','' + end if + write (stdout,'(2x,a,t35,": ",f10.1,a)') 'Time step',env%mdstep,' fs' + write (stdout,'(2x,a,t35,": ",i10)') 'SHAKE mode',env%shake + write (stdout,'(2x,a,t35,": ",f10.2,a)') 'MD temperature',env%mdtemp,' K' + write (stdout,'(2x,a,t35,": ",i10,a)') 'Trajectory dump step',env%mddumpxyz,' fs' + write (stdout,'(2x,a,t35,": ",f10.1,a)') 'MTD Vbias dump',real(env%mddump,wp)/1000.0_wp,' ps' + if (env%mddat%length_ps > 0.0_wp) then + write (stdout,*) + write (stdout,'(2x,a)') 'mddata object (modern MD runtype):' + write (stdout,'(4x,a,t35,": ",f10.1)') 'length_ps',env%mddat%length_ps + write (stdout,'(4x,a,t35,": ",f10.4)') 'tstep [fs]',env%mddat%tstep + write (stdout,'(4x,a,t35,": ",f10.2)') 'T_soll',env%mddat%tsoll + write (stdout,'(4x,a,t35,": ",l6)') 'SHAKE',env%mddat%shake + write (stdout,'(4x,a,t35,": ",a)') 'thermostat',trim(env%mddat%thermotype) + end if + write (stdout,*) + +!========================================================================================! +!> THERMODYNAMICS SETTINGS +!========================================================================================! + call drawbox(stdout,'Thermodynamics settings',charset=4,padl=2,padr=2) + write (stdout,*) + select case (env%thermo%emodel) + case ('grimme') + ctmp = 'Grimme (2012)' + case ('truhlar') + ctmp = 'Truhlar (2011)' + end select + write (stdout,'(2x,a,t35,": ",a15)') 'Vibrational entropy model',ctmp + write (stdout,'(2x,a,t35,": ",f10.2,a)') 'Imaginary freq. threshold',env%thermo%ithr,' cm^-1' + write (stdout,'(2x,a,t35,": ",f10.4)') 'Frequency scaling factor',env%thermo%fscal + write (stdout,'(2x,a,t35,": ",f10.2,a)') 'Rot/vib interpolation threshold',env%thermo%sthr,' cm^-1' + write (stdout,'(2x,a,t35,": ",f6.2,a,f6.2,a,f6.2)') & + & 'T range [K] (start/end/step)', & + & env%thermo%trange(1),'/',env%thermo%trange(2),'/',env%thermo%trange(3) + write (stdout,'(2x,a,t35,": ",i10)') 'Number of temperature points : ',env%thermo%ntemps + write (stdout,*) + +!========================================================================================! +!> SORTING / CREGEN SETTINGS +!========================================================================================! + call drawbox(stdout,'Sorting / CREGEN settings',charset=4,padl=2,padr=2) + write (stdout,*) + write (stdout,'(2x,a,t35,": ",f10.4,a)') 'Energy window ',env%ewin,' kcal/mol' + write (stdout,'(2x,a,t35,": ",f10.4,a)') 'RTHR (RMSD threshold) ',env%rthr,' Å' + write (stdout,'(2x,a,t35,": ",f10.4,a)') 'ETHR (energy threshold)',env%ethr,' kcal/mol' + write (stdout,'(2x,a,t35,": ",f10.2,a)') 'BTHR (rot. threshold) ',env%bthr2*100.0d0,' %' + write (stdout,'(2x,a,t35,": ",f10.2)') 'Boltzmann temperature ',env%tboltz + write (stdout,'(2x,a,t35,": ",l6)') 'Heavy-atom RMSD only ',env%heavyrmsd + write (stdout,'(2x,a,t35,": ",l6)') 'Topology check in CREGEN',env%checktopo + write (stdout,*) + +!========================================================================================! +!> TECHNICAL SETTINGS +!========================================================================================! + call drawbox(stdout,'Technical settings',charset=4,padl=2,padr=2) + write (stdout,*) + call getcwd(dumstr) + write (stdout,'(2x,a,t25,": ",a)') 'Working directory',trim(dumstr) + write (stdout,'(2x,a,t25,": ",i0)') 'CPUs / threads',env%threads + write (stdout,*) + +!========================================================================================! +!> CREST BINARY METADATA (always last) +!========================================================================================! + call drawbox(stdout,'CREST binary info',charset=4,padl=2,padr=2) + write (stdout,*) + call print_crest_metadata() + write (stdout,*) + +!========================================================================================! + call creststop(status_normal) +end subroutine crest_dry_run diff --git a/src/algos/dynamics.f90 b/src/algos/dynamics.f90 index ff351e92..e863b709 100644 --- a/src/algos/dynamics.f90 +++ b/src/algos/dynamics.f90 @@ -24,6 +24,7 @@ subroutine crest_moleculardynamics(env,tim) use strucrd use dynamics_module use shake_module + use iomod,only:colorify implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim @@ -39,17 +40,19 @@ subroutine crest_moleculardynamics(env,tim) real(wp),allocatable :: grad(:,:) character(len=80) :: atmp - character(len=*),parameter :: trjf='crest_dynamics.trj' + character(len=*),parameter :: trjf = 'crest_dynamics.trj.xyz' !========================================================================================! - write(stdout,*) - !call system('figlet dynamics') - write(stdout,*) " _ _ " - write(stdout,*) " __| |_ _ _ __ __ _ _ __ ___ (_) ___ ___ " - write(stdout,*) " / _` | | | | '_ \ / _` | '_ ` _ \| |/ __/ __| " - write(stdout,*) " | (_| | |_| | | | | (_| | | | | | | | (__\__ \ " - write(stdout,*) " \__,_|\__, |_| |_|\__,_|_| |_| |_|_|\___|___/ " - write(stdout,*) " |___/ " - write(stdout,*) + write (stdout,*) + write (stdout,*) " ------------------------------------------------- " + write (stdout,*) " ##### # # # # ## # # # #### #### " + write (stdout,*) " # # # # ## # # # ## ## # # # # " + write (stdout,*) " # # # # # # # # # ## # # # #### " + write (stdout,*) " # # # # # # ###### # # # # # " + write (stdout,*) " # # # # ## # # # # # # # # # " + write (stdout,*) " ##### # # # # # # # # #### #### " + write (stdout,*) " ------------------------------------------------- " + write (stdout,*) + !========================================================================================! call new_ompautoset(env,'max',0,T,Tn) call ompprint_intern() @@ -65,9 +68,9 @@ subroutine crest_moleculardynamics(env,tim) !>--- default settings from env call env_to_mddat(env) mddat = env%mddat - calc = env%calc + call calc%copy(env%calc) !>--- check if we have any MD & calculation settings allocated - if (.not. mddat%requested) then + if (.not.mddat%requested) then write (stdout,*) 'MD requested, but no MD settings present.' env%iostatus_meta = status_config return @@ -78,12 +81,12 @@ subroutine crest_moleculardynamics(env,tim) end if !>--- print calculation info - call calc%info( stdout ) + call calc%info(stdout) !>--- init SHAKE? --> we need connectivity info if (mddat%shake) then calc%calcs(1)%rdwbo = .true. - if(.not.calc%calcs(1)%active) calc%calcs(1)%active=.true. + if (.not.calc%calcs(1)%active) calc%calcs(1)%active = .true. allocate (grad(3,mol%nat),source=0.0_wp) call engrad(mol,calc,energy,grad,io) deallocate (grad) diff --git a/src/algos/hessian_tools.f90 b/src/algos/hessian_tools.f90 deleted file mode 100644 index 0b330903..00000000 --- a/src/algos/hessian_tools.f90 +++ /dev/null @@ -1,580 +0,0 @@ -!================================================================================! -! This file is part of crest. -! -! Copyright (C) 2023 Gereon Feldmann -! -! crest is free software: you can redistribute it and/or modify it under -! the terms of the GNU Lesser General Public License as published by -! the Free Software Foundation, either version 3 of the License, or -! (at your option) any later version. -! -! crest is distributed in the hope that it will be useful, -! but WITHOUT ANY WARRANTY; without even the implied warranty of -! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -! GNU Lesser General Public License for more details. -! -! You should have received a copy of the GNU Lesser General Public License -! along with crest. If not, see . -! Routines were adapted from the xtb code (github.com/grimme-lab/xtb) -! under the Open-source software LGPL-3.0 Licencse. -!================================================================================! - -!========================================================================================! -!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! -!> Routines for the computation of a projected mass-weighted Hessian -!> Routines for the computation of frequencies from the Hessian -!> Rotuines for the computation of the effective Hessian at an MECP based on: DOI:10.1039/A907723E -!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! -!========================================================================================! - -module hessian_tools - use crest_parameters, only:wp,stdout - use crest_data - use crest_calculator - use strucrd - use optimize_module - use optimize_maths - use iomod - - public :: frequencies - -!=========================================================================================! -!=========================================================================================! -contains !> MODULE PROCEDURES START HERE -!=========================================================================================! -!=========================================================================================! - - - subroutine frequencies(nat,at,xyz,nat3,calc,prj_mw_hess,freq,io) -!************************************************* -!* Returns the Frequencies from a Hessian in cm-1 -!************************************************* - implicit none - - integer,intent(in) :: nat - integer,intent(in) :: at(nat) - real(wp),intent(in) :: xyz(3,nat) - type(calcdata) :: calc - real(wp) :: prj_mw_hess(nat3,nat3) - - integer :: io,nat3 - logical :: pr - real(wp) :: energy - real(wp) :: freq(nat3) - real(wp),allocatable :: pmode(:,:) - - integer,allocatable :: iwork(:) - real(wp),allocatable :: work(:) - - integer :: lwork,liwork,info,i - !>LAPCK - external :: dsyevd - - nat3 = nat*3 - - !Parameters for diagonalization - lwork = 1+6*nat3+2*nat3**2 - liwork = 3+5*nat3 - - allocate (work(lwork),iwork(liwork)) - - !Diagonalization - call dsyevd('V','U',nat3,prj_mw_hess,nat3,freq,work,lwork,iwork,liwork,info) - - deallocate (work,iwork) - - !Convert eigenvalues to frequencies - do i = 1,nat3 - if (freq(i) .gt. 0.0_wp) then - freq(i) = sqrt(freq(i))*219474.63_wp - else - freq(i) = -sqrt(abs(freq(i)))*219474.63_wp - end if - end do - - return - - end subroutine frequencies - - - subroutine mass_weight_hess(nat,at,nat3,hess) - use atmasses - implicit none - - !> Mass weighting the Hessian - integer,intent(in) :: nat !Number of atoms - integer,intent(in) :: at(nat) !atomic number of all atoms - - real(wp),intent(inout) :: hess(nat3,nat3) !Hessian matrix - real(wp) :: mass_in_au !Masses of all atoms of the periodic table - integer :: i,j,nat3,i3,i33,j3,j33 - - mass_in_au = (1.66054e-27_wp/9.1094e-31_wp)**2 - - !amv = ams(1:118) - - do i = 1,nat - do j = i,nat - - i3 = 3*(i-1)+1 - i33 = 3*(i-1)+3 - j3 = 3*(j-1)+1 - j33 = 3*(j-1)+3 - - hess(i3:i33,j3:j33) = 1/sqrt(ams(at(i))*ams(at(j))*mass_in_au)*hess(i3:i33,j3:j33) - !Hessian is symmetric hence upper triangular can be copied - hess(j3:j33,i3:i33) = hess(i3:i33,j3:j33) - - end do - end do - - return - end subroutine mass_weight_hess - - subroutine dsqtoh(n,a,b) -!**************************************************** -!* converts upper triangle of a matrix into a vector -!**************************************************** - implicit none - integer,intent(in) :: n - real(wp),intent(in) :: a(n,n) - real(wp),intent(out) :: b(n*(n+1)/2) - integer :: i,j,k - - k = 0 - do i = 1,n - do j = 1,i - k = k+1 - b(k) = a(i,j) - end do - end do - - end subroutine dsqtoh - - subroutine dhtosq(n,a,b) -!********************************************************* -!* converts upper triangle vector into a symmetric matrix -!********************************************************* - implicit none - integer,intent(in) :: n - real(wp),intent(out) :: a(n,n) - real(wp),intent(in) :: b(n*(n+1)/2) - integer :: i,j,k - - k = 0 - do i = 1,n - do j = 1,i - k = k+1 - a(j,i) = b(k) - a(i,j) = b(k) - end do - end do - - return - end subroutine dhtosq - -!=========================================================================================! - - subroutine prj_mw_hess(nat,at,nat3,xyz,hess) -!*************************************************************** -!* Projection of the translational and rotational DOF out of -!* the numerical Hessian plus the mass-weighting of the Hessian -!*************************************************************** - implicit none - - integer,intent(in) :: nat,nat3 - integer :: at(nat) - real(wp),intent(inout) :: hess(nat3,nat3) - real(wp) :: xyz(3,nat) - !real(wp) :: hess_ut(nat3*(nat3+1)/2),pmode(nat3,1) - real(wp),allocatable :: hess_ut(:),pmode(:,:) - - allocate(hess_ut(nat3*(nat3+1)/2), source=0.0_wp) - allocate(pmode(nat3,1), source=0.0_wp) - - !> Transforms matrix of the upper triangle vector - call dsqtoh(nat3,hess,hess_ut) - - !> Projection - call trproj(nat,nat3,xyz,hess_ut,.false.,0,pmode,1) - - !> Transforms vector of the upper triangle into matrix - call dhtosq(nat3,hess,hess_ut) - - !> Mass weighting - call mass_weight_hess(nat,at,nat3,hess) - - deallocate(pmode,hess_ut) - end subroutine prj_mw_hess - -!=========================================================================================! - - subroutine print_vib_spectrum(nat,at,nat3,xyz,freq,dir,fname) -!********************************************************************* -!* Prints the frequencies in Turbomoles "vibspectrum" format -!* The intensity is only artficially set to 1000 for every vibration!! -!********************************************************************** - integer,intent(in) :: nat,nat3 - integer :: at(nat),i - real(wp) :: xyz(3,nat) - real(wp) :: freq(nat3),thr - character(len=*) :: fname - character(len=*) :: dir - - thr = 0.01_wp - if (len_trim(dir) .eq. 0) then - open (newunit=ich,file=fname) - else - if(directory_exist(dir))then - open (newunit=ich,file=dir//'/'//fname) - else - open (newunit=ich,file=fname) - endif - end if - - - write (ich,'("$vibrational spectrum")') - write (ich,'("# mode symmetry wave number IR intensity selection rules")') - write (ich,'("# 1/cm km/mol IR RAMAN")') - - do i = 1,nat3 - if (abs(freq(i)) .lt. thr) then - write (ich,'(i6,9x, f18.2,f16.5,7x," - ",5x," - ")') & - i,freq(i),0.0_wp - else - write (ich,'(i6,8x,"a",f18.2,f16.5,7x,"YES",5x,"YES")') & - i,freq(i),1000.0_wp - end if - end do - - write (ich,'("$end")') - - close (ich) - - end subroutine print_vib_spectrum - -!=========================================================================================! - - subroutine print_g98_fake(nat,at,nat3,xyz,freq,hess,dir,fname) -!**************************************************************** -!* Prints the vibration spectrum of the a system as a g98.out. -!* Routine is adapted from the xtb code. -!**************************************************************** - integer,intent(in) :: nat,nat3 - integer :: at(nat) - integer :: gu,i,j,ka,kb,kc,la,lb,k - - real(wp) :: xyz(3,nat) - real(wp),intent(in) :: hess(nat3,nat3) - real(wp) :: freq(nat3),red_mass(nat3),force(nat3),ir_int(nat3),zero(1),f2(nat3),u(nat3,nat3) - - character(len=2) :: irrep - character(len=*) :: fname - character(len=*) :: dir - - irrep = 'a' - - red_mass = 99.0 - force = 99.0 - ir_int = 99.0 - zero = 0.0 - - k = 0 - - do i = 1,nat3 - if (abs(freq(i)) .gt. 1.d-1) then - k = k+1 - u(1:nat3,k) = hess(1:nat3,i) - f2(k) = freq(i) - end if - end do - - if (len_trim(dir) .eq. 0) then - open (newunit=gu,file=fname) - else - if(directory_exist(dir))then - open (newunit=gu,file=dir//'/'//fname) - else - open (newunit=gu,file=fname) - endif - end if - - write (gu,'('' Entering Gaussian System'')') - write (gu,'('' *********************************************'')') - write (gu,'('' Gaussian 98:'')') - write (gu,'('' frequency output generated by the crest code'')') - write (gu,'('' *********************************************'')') - - write (gu,*) ' Standard orientation:' - write (gu,*) '---------------------------------------------', & - & '-----------------------' - write (gu,*) ' Center Atomic Atomic', & - & ' Coordinates (Angstroms)' - write (gu,*) ' Number Number Type ', & - & ' X Y Z' - write (gu,*) '-----------------------', & - & '---------------------------------------------' - j = 0 - do i = 1,nat - write (gu,111) i,at(i),j,xyz(1:3,i)*0.52917726 - end do - write (gu,*) '----------------------', & - & '----------------------------------------------' - write (gu,*) ' 1 basis functions 1 primitive gaussians' - write (gu,*) ' 1 alpha electrons 1 beta electrons' - write (gu,*) -111 format(i5,i11,i14,4x,3f12.6) - - write (gu,*) 'Harmonic frequencies (cm**-1), IR intensities',' (km*mol⁻¹),' - write (gu,*) 'Raman scattering activities (A**4/amu),', & - & ' Raman depolarization ratios,' - write (gu,*) 'reduced masses (AMU), force constants (mDyne/A)', & - & ' and normal coordinates:' - - ka = 1 - kc = 3 - -60 kb = min0(kc,k) - write (gu,100) (j,j=ka,kb) - write (gu,105) (irrep,j=ka,kb) - write (gu,110) ' Frequencies --', (f2(j),j=ka,kb) - write (gu,110) ' Red. masses --', (red_mass(j),j=ka,kb) - write (gu,110) ' Frc consts --', (force(j),j=ka,kb) - write (gu,110) ' IR Inten --', (ir_int(j),j=ka,kb) - write (gu,110) ' Raman Activ --', (zero,j=ka,kb) - write (gu,110) ' Depolar --', (zero,j=ka,kb) - write (gu,*) 'Atom AN X Y Z X Y', & - & ' Z X Y Z' - la = 1 -70 lb = nat - do i = la,lb - write (gu,130) i,at(i), (u(i*3-2,j),u(i*3-1,j),u(i*3,j),j=ka,kb) - end do - if (lb .eq. nat) go to 90 - go to 70 -90 if (kb .eq. k) then - goto 200 - end if - - ka = kc+1 - kc = kc+3 - go to 60 - -100 format(3(20x,i3)) -105 format(3x,3(18x,a5)) -110 format(a15,f11.4,12x,f11.4,12x,f11.4) -130 format(2i4,3(2x,3f7.2)) -200 continue - write (gu,'(''end of file'')') - close (gu) - - end subroutine print_g98_fake - -!=========================================================================================! - - - subroutine print_hessian(hess,nat3,dir,fname) -!******************************* -!* Prints the numerical hessian -!******************************* - integer :: nat3,i,j,k - real(wp) :: hess(nat3,nat3) - character(len=*) :: fname - character(len=*) :: dir - - if (len_trim(dir) .eq. 0) then - open (newunit=ich,file=fname) - write (stdout,'(1x,a)',advance='no') 'Will be written to file "'//fname//'" ...' - else - if(directory_exist(dir))then - open (newunit=ich,file=dir//'/'//fname) - write (stdout,'(1x,a)',advance='no') 'Will be written to file "'//dir//'/'//fname//'" ...' - else - open (newunit=ich,file=fname) - write (stdout,'(1x,a)',advance='no') 'Will be written to file "'//fname//'" ...' - endif - end if - flush(stdout) - - write (ich,'(1x,a)') '$hessian' - do i = 1,nat3 - k = 0 - do j = 1,nat3 - k = k+1 - if (k .le. 4) then - write (ich,'(f16.8)',advance='no') hess(i,j) - else - write (ich,'(f16.8)') hess(i,j) - k = 0 - end if - end do - if (k .ne. 0) then - write (ich,*) - end if - end do - write (ich,'(1x,a)') '$end' - close (ich) - - write (stdout,*) 'done.' - write (stdout,*) - - end subroutine print_hessian - -!=========================================================================================! - - subroutine effective_hessian(nat,nat3,grad1_i,grad2_i,hess1,hess2,heff) -!****************************************************************** -!* Effective Hessian at an MECP is computed via Eq. 27 and Eq. 28 -!* in https://doi.org/10.1002/qua.25124 -!****************************************************************** - implicit none - integer,intent(in) :: nat,nat3 - integer :: i,j,ii - real(wp),intent(in) :: grad1_i(3,nat3),grad2_i(3,nat3) - real(wp) :: grad1(nat3),grad2(nat3),dot - - real(wp),intent(in) :: hess1(nat3,nat3),hess2(nat3,nat3) - - real(wp) :: gnorm1,gnorm2,grad_diff_norm - real(wp) :: grad_diff(nat3),heff_temp(nat3,nat3) - - real(wp),intent(inout) :: heff(nat3,nat3) - real(wp),allocatable :: proj_vec(:,:) - - real(wp) :: freq(nat3) - - integer,allocatable :: iwork(:) - real(wp),allocatable :: work(:) - - integer :: lwork,liwork,info - - allocate (proj_vec(nat3,nat3),source=0.0_wp) - - grad1 = reshape(grad1_i, (/nat3/)) - grad2 = reshape(grad2_i, (/nat3/)) - - gnorm1 = norm2(grad1) - - gnorm2 = norm2(grad2) - - grad_diff = grad1-grad2 - - grad_diff_norm = norm2(grad_diff) - - dot = dot_product(grad1,grad2) - - if (dot .gt. 0.0_wp) then !sloped: dot > 0.0 --> - | peaked: dot <= 0.0 --> + - - write (stdout,*) 'MECI is considered as a sloped CI' - write (stdout,*) - - heff = (gnorm1*hess2-gnorm2*hess1)/grad_diff_norm - - else - - write (stdout,*) 'MECI is considered as a peaked CI' - write (stdout,*) - - heff = (gnorm1*hess2+gnorm2*hess1)/grad_diff_norm - - end if - - !Outer Product of grad_diff - - !Building projection matrix - - !proj_vec = 1 - (dg/|dg| o dg.T/|dg|) = 1 - (dg o dg.T)/|dg|**2 - - grad_diff_norm = grad_diff_norm**2 - - do i = 1,nat3 - proj_vec(i,:) = -grad_diff(i)*grad_diff/grad_diff_norm - proj_vec(i,i) = proj_vec(i,i)+1 - end do - - !Projection - heff = matmul(matmul(proj_vec,heff),proj_vec) - - !Check if hess1 and hess2 are assigned correctly, otherwise change - lwork = 1+6*nat3+2*nat3**2 - liwork = 3+5*nat3 - allocate (work(lwork),iwork(liwork)) - - heff_temp = heff - - call dsyevd('V','U',nat3,heff_temp,nat3,freq,work,lwork,iwork,liwork,info) - - deallocate (work,iwork) - - if (0 .gt. sum(freq)) then - heff = -heff - end if - - end subroutine effective_hessian - -!=========================================================================================! - - subroutine calculate_frequencies(calc,nat,at,xyz,freq,io,constraints) -!******************************************************* -!* Bundels several routines from this module to -!* calculate the vib. frequencies for a given structure -!* The output frequencies are in cm-1 -!******************************************************* - implicit none - !> INPUT - type(calcdata),intent(inout) :: calc - integer,intent(in) :: nat - integer,intent(in) :: at(nat) - real(wp),intent(in) :: xyz(3,nat) - logical,intent(in),optional :: constraints - !> OUTPUT - integer,intent(out) :: io - real(wp),intent(out) :: freq(3*nat) - !> LOCAL - real(wp),allocatable :: hessian(:,:) - real(wp),allocatable :: chess(:,:) - type(calcdata) :: dummycalc - integer :: nat3,ncalc,i - - io = 0 - freq = 0.0_wp - nat3 = nat*3 - ncalc = calc%ncalculations - - allocate(hessian(nat3,nat3), source = 0.0_wp) - - !>--- Hessian from combined energy and gradient - call numhess1(nat,at,xyz, calc,hessian,io) - if( io /= 0 ) return - - !>--- do we consider contributions from the constraints? - !> (yes, by default, they are in the hessian from numhess1, - !> if we DO NOT want them, we need to take them out again) - if(present(constraints))then - if(.not.constraints)then - dummycalc = calc !> new dummy calculation - dummycalc%id = 0 !> set to zero so that ONLY constraints are considered - dummycalc%ncalculations = 0 - dummycalc%pr_energies = .false. - allocate (chess(nat3,nat3),source=0.0_wp) - call numhess1(nat,at,xyz,dummycalc,chess,io) - hessian(:,:) = hessian(:,:) - chess(:,:) - deallocate( chess ) - endif - endif - - do i = 1,calc%ncalculations - - !>-- Projects and mass-weights the Hessian - call prj_mw_hess(nat,at,nat3,xyz, hessian(:,:)) - - !>-- Computes the Frequencies - call frequencies(nat,at,xyz,nat3, calc, hessian(:,:), freq(:),io) - end do - - deallocate( hessian ) - return - end subroutine calculate_frequencies - -!=========================================================================================! -end module hessian_tools diff --git a/src/algos/meson.build b/src/algos/meson.build index 43977333..5ec15c40 100644 --- a/src/algos/meson.build +++ b/src/algos/meson.build @@ -26,10 +26,15 @@ srcs += files( 'search_1.f90', 'search_mecp.f90', 'setuptest.f90', + 'sorting.f90', 'protonate.f90', - 'hessian_tools.f90', - 'ConfSolv.F90', 'search_conformers.f90', 'search_entropy.f90', 'parallel.f90', + 'queueing.f90', + 'alkylize.f90', + 'deform_opt_hess.f90', + 'dryrun.f90', + 'propcalc.f90', + 'term_ui.f90', ) diff --git a/src/algos/numhess.f90 b/src/algos/numhess.f90 index 84cb22df..0c098076 100644 --- a/src/algos/numhess.f90 +++ b/src/algos/numhess.f90 @@ -32,10 +32,12 @@ subroutine crest_numhess(env,tim) use crest_calculator use strucrd use optimize_module - use hessian_tools + use thermochem_module use gradreader_module use xtb_sc use oniom_hessian + use ir_spectrum + use iomod,only:colorify implicit none type(systemdata),intent(inout) :: env @@ -48,19 +50,25 @@ subroutine crest_numhess(env,tim) real(wp) :: energy real(wp),allocatable :: hess(:,:,:),freq(:,:),grad(:),grad1(:,:),grad2(:,:),heff(:,:) real(wp),allocatable :: ohess(:,:),ofreq(:),grad0(:,:),energies0(:) + real(wp),allocatable :: ir_int(:) character(len=60) :: atmp + !========================================================================================! - call tim%start(15,'Numerical Hessian') -!========================================================================================! - !call system('figlet numhess') + write (stdout,*) - write (stdout,*) " _ " - write (stdout,*) " _ __ _ _ _ __ ___ | |__ ___ ___ ___ " - write (stdout,*) "| '_ \| | | | '_ ` _ \| '_ \ / _ \/ __/ __|" - write (stdout,*) "| | | | |_| | | | | | | | | | __/\__ \__ \" - write (stdout,*) "|_| |_|\__,_|_| |_| |_|_| |_|\___||___/___/" + write (stdout,*) " ------------------------------------------------ " + write (stdout,*) " # # # # # # # # ###### #### ### " + write (stdout,*) " ## # # # ## ## # # # # # " + write (stdout,*) " # # # # # # ## # ###### ##### #### #### " + write (stdout,*) " # # # # # # # # # # # # " + write (stdout,*) " # ## # # # # # # # # # # # " + write (stdout,*) " # # #### # # # # ###### #### #### " + write (stdout,*) " ------------------------------------------------ " write (stdout,*) +!========================================================================================! + call tim%start(15,'Numerical Hessian') + call env%ref%to(mol) write (stdout,*) write (stdout,*) 'Input structure:' @@ -68,7 +76,7 @@ subroutine crest_numhess(env,tim) write (stdout,*) !========================================================================================! - calc = env%calc + call calc%copy(env%calc) !>--- print some info about the calculation call calc%info(stdout) @@ -76,22 +84,21 @@ subroutine crest_numhess(env,tim) !========================================================================================! !>--- start with an initial single point - write(stdout,'(a)') repeat(":",80) + write (stdout,'(a)') repeat(":",80) write (stdout,'(1x,a)') 'Initial singlepoint calculation ...' - allocate(grad0(3,mol%nat),source=0.0_wp) - allocate(energies0( calc%ncalculations ), source=0.0_wp) - - call engrad(mol,calc,energy,grad0,io) - energies0 = calc%etmp - - write(atmp,'("Energy = ",f25.15," Eh")') energy - call smallhead(trim(atmp)) - write(stdout,'(a)') repeat(":",80) - write(stdout,*) - - - deallocate(grad0) - + allocate (grad0(3,mol%nat),source=0.0_wp) + allocate (energies0(calc%ncalculations),source=0.0_wp) + + call engrad(mol,calc,energy,grad0,io) + energies0 = calc%etmp + + write (atmp,'("Energy = ",f25.15," Eh")') energy + call smallhead(trim(atmp)) + write (stdout,'(a)') repeat(":",80) + write (stdout,*) + + deallocate (grad0) + !========================================================================================! nat3 = mol%nat*3 @@ -116,6 +123,11 @@ subroutine crest_numhess(env,tim) allocate (hess(nat3,nat3,calc%ncalculations),source=0.0_wp) allocate (freq(nat3,n_freqs),source=0.0_wp) +! ── auto-enable dipole gradient for single tblite level ────────────────────── + if (calc%ncalculations == 1) then + if (calc%calcs(1)%id == jobtype%tblite) calc%calcs(1)%rddip = .true. + end if + !********************************************************************************* !>--- Computes numerical Hessians and stores them individually for each level call numhess2(mol%nat,mol%at,mol%xyz,calc,hess,io) @@ -146,7 +158,7 @@ subroutine crest_numhess(env,tim) call prj_mw_hess(mol%nat,mol%at,nat3,mol%xyz,heff) !>-- Comp. of Frequencies - call frequencies(mol%nat,mol%at,mol%xyz,nat3,calc,heff,freq(:,n_freqs),io) + call frequencies(mol%nat,mol%at,mol%xyz,nat3,heff,freq(:,n_freqs),io) !>-- Printout of vibspectrum call print_vib_spectrum(mol%nat,mol%at,nat3,mol%xyz,freq(:,n_freqs),'','vibspectrum') @@ -178,34 +190,48 @@ subroutine crest_numhess(env,tim) else - write(atmp,*) i + !> omit numeric suffix when there is only one calculation level + if (calc%ncalculations == 1) then + atmp = '' + else + write (atmp,*) i + atmp = trim(adjustl(atmp)) + end if !>-- Prints Hessian - call print_hessian(hess(:,:,i),nat3,'','numhess'//trim(adjustl(atmp))) + call print_hessian(hess(:,:,i),nat3,'','numhess'//trim(atmp)) !>--- Print dipole gradients (if they exist) - call calc%calcs(i)%dumpdipgrad('dipgrad'//trim(adjustl(atmp))) + call calc%calcs(i)%dumpdipgrad('dipgrad'//trim(atmp)) !>-- Projects and mass-weights the Hessian call prj_mw_hess(mol%nat,mol%at,nat3,mol%xyz,hess(:,:,i)) !>-- Computes the Frequencies - call frequencies(mol%nat,mol%at,mol%xyz,nat3,calc,hess(:,:,i),freq(:,i),io) + call frequencies(mol%nat,mol%at,mol%xyz,nat3,hess(:,:,i),freq(:,i),io) if (io .ne. 0) then write (stdout,*) 'FAILED!' else - !>-- Prints vibspectrum with artifical intensities +! ── project dipole gradient onto normal modes → IR intensities ─────────── + if (allocated(calc%calcs(i)%dipgrad)) then + allocate (ir_int(nat3),source=0.0_wp) + call ir_intensities(mol%nat,mol%at,nat3,hess(:,:,i), & + & calc%calcs(i)%dipgrad,ir_int) + end if + + !>-- Prints vibspectrum call print_vib_spectrum(mol%nat,mol%at,nat3,mol%xyz,freq(:,i), & - & '','vibspectrum'//trim(adjustl(atmp))) + & '','vibspectrum'//trim(atmp),ir_int=ir_int) - !>-- Prints g98.out format file + !>-- Prints g98.out format file (suffix prevents overwrite for empty calcspace) call print_g98_fake(mol%nat,mol%at,nat3,mol%xyz,freq(:,i),hess(:,:,i), & - & calc%calcs(i)%calcspace,'g98.out') + & calc%calcs(i)%calcspace,'g98'//trim(atmp)//'.out',ir_int=ir_int) - write(atmp,*) i - call smallhead("Thermo contributions for [[calculation.level]] "//trim(adjustl(atmp))) + if (allocated(ir_int)) deallocate (ir_int) + + call smallhead("Thermo contributions for [[calculation.level]] "//trim(atmp)) call numhess_thermostat(env,mol,nat3,hess(:,:,i),freq(:,i),energies0(i)) end if @@ -233,7 +259,7 @@ subroutine crest_numhess(env,tim) call prj_mw_hess(mol%nat,mol%at,nat3,mol%xyz,ohess(:,:)) !>-- Computes the Frequencies (in cm^-1) - call frequencies(mol%nat,mol%at,mol%xyz,nat3,calc,ohess(:,:),ofreq(:),io) + call frequencies(mol%nat,mol%at,mol%xyz,nat3,ohess(:,:),ofreq(:),io) !>-- Prints vibspectrum (cm^-1) with artifical intensities call print_vib_spectrum(mol%nat,mol%at,nat3,mol%xyz,ofreq(:), & @@ -250,9 +276,6 @@ subroutine crest_numhess(env,tim) end if !========================================================================================! - - - !========================================================================================! if (allocated(hess)) deallocate (hess) if (allocated(freq)) deallocate (freq) @@ -276,28 +299,30 @@ subroutine numhess_thermostat(env,mol,nat3,hess,freq,etot) use crest_parameters use crest_data use strucrd + use thermochem_module implicit none !> INPUT type(systemdata) :: env - type(coord) :: mol + type(coord),intent(inout) :: mol integer,intent(in) :: nat3 real(wp),intent(in) :: hess(nat3,nat3) - real(wp),intent(in) :: freq(nat3) + real(wp),intent(inout) :: freq(nat3) real(wp),intent(in) :: etot !> LOCAL real(wp) :: ithr,fscal,sthr - integer :: nt,nfreq,nrt + character(len=:),allocatable :: emodel + integer :: nt,nrt real(wp),allocatable :: temps(:),et(:),ht(:),stot(:),gt(:) real(wp) :: zpve - character(len=*),parameter :: outfmt = & - & '(10x,"::",1x,a,f24.12,1x,a,1x,"::")' !> inversion threshold ithr = env%thermo%ithr !> frequency scaling factor fscal = env%thermo%fscal - !> RR-HO interpolation + !> RR-HO interpolation (or cut-off) sthr = env%thermo%sthr + !> Svib model + emodel = env%thermo%emodel if (.not.allocated(env%thermo%temps)) then call env%thermo%get_temps() @@ -308,25 +333,14 @@ subroutine numhess_thermostat(env,mol,nat3,hess,freq,etot) !write(*,*) temps nrt = minloc(temps(:),1) !write(*,*) nrt - temps = env%thermo%temps + temps = env%thermo%temps - !> calcthermo wants input in Angstroem - call calcthermo(mol%nat,mol%at,mol%xyz*autoaa,freq,.true., & - & ithr,fscal,sthr,nt,temps,et,ht,gt,stot) + !> calcthermo wants input in Bohr + call calcthermo(mol%nat,mol%at,mol%xyz,freq,.true., & + & ithr,fscal,sthr,nt,temps,et,ht,gt,stot,emodel=emodel) - !> printout zpve = et(nrt)-ht(nrt) - write(stdout,*) - write(stdout,'(10x,a)') repeat(':',50) - write(stdout,'(10x,"::",7x,a,f12.2,1x,a,8x,"::")') "THERMODYNAMICS at",temps(nrt),'K' - write(stdout,'(10x,a)') repeat(':',50) - write(stdout,outfmt) 'TOTAL FREE ENERGY',etot+gt(nrt),'Eh' - write(stdout,'(10x,a)') '::'//repeat('-',46)//'::' - write(stdout,outfmt) 'total energy ',etot,'Eh' - write(stdout,outfmt) 'ZPVE ',zpve,'Eh' - write(stdout,outfmt) 'G(RRHO) w/o ZPVE ',gt(nrt)-zpve,'Eh' - write(stdout,outfmt) 'G(RRHO) total ',gt(nrt),'Eh' - write(stdout,'(10x,a)') repeat(':',50) + call print_thermo_summary(stdout,temps(nrt),etot,zpve,gt(nrt)) deallocate (stot,gt,ht,et,temps) end subroutine numhess_thermostat @@ -342,6 +356,7 @@ subroutine thermo_standalone(env) use crest_parameters use crest_data use strucrd + use thermochem_module implicit none !> INPUT type(systemdata) :: env @@ -352,57 +367,58 @@ subroutine thermo_standalone(env) real(wp),allocatable :: freq(:) real(wp) :: etot real(wp) :: ithr,fscal,sthr - integer :: nt,nfreq,nrt + character(len=:),allocatable :: emodel + integer :: nt,nrt real(wp),allocatable :: temps(:),et(:),ht(:),stot(:),gt(:) real(wp) :: zpve integer :: ich,i - character(len=*),parameter :: outfmt = & - & '(10x,"::",1x,a,f24.12,1x,a,1x,"::")' !> header - write(stdout,*) " _ _ " - write(stdout,*) "| |_| |__ ___ _ __ _ __ ___ ___ " - write(stdout,*) "| __| '_ \ / _ \ '__| '_ ` _ \ / _ \ " - write(stdout,*) "| |_| | | | __/ | | | | | | | (_) |" - write(stdout,*) " \__|_| |_|\___|_| |_| |_| |_|\___/ " - write(stdout,*) " " - write(stdout,*) "Molecular thermodynamics from the modified and scaled" - write(stdout,*) "rigid-rotor harmonic-oscillator approximation (msRRHO)" - write(stdout,*) "See:" - write(stdout,*) " • S.Grimme, Chem. Eur. J. 2012, 18, 9955–9964." - write(stdout,*) " • P.Pracht, S.Grimme, Chem. Sci., 2021, 12, 6551-6568." - write(stdout,*) - + write (stdout,'(t10,a)') " _ _ " + write (stdout,'(t10,a)') "| |_| |__ ___ _ __ _ __ ___ ___ " + write (stdout,'(t10,a)') "| __| '_ \ / _ \ '__| '_ ` _ \ / _ \ " + write (stdout,'(t10,a)') "| |_| | | | __/ | | | | | | | (_) |" + write (stdout,'(t10,a)') " \__|_| |_|\___|_| |_| |_| |_|\___/ " + write (stdout,'(t10,a)') " " + write (stdout,*) "Molecular thermodynamics from the modified and scaled" + write (stdout,*) "rigid-rotor harmonic-oscillator approximation (msRRHO)" + write (stdout,*) "See:" + write (stdout,*) " • S.Grimme, Chem. Eur. J. 2012, 18, 9955–9964." + write (stdout,*) " • P.Pracht, S.Grimme, Chem. Sci., 2021, 12, 6551-6568." + write (stdout,*) + !> input coords - write(stdout,'(1x,a)',advance='no') 'Reading input coords: ' - if(allocated(env%thermo%coords))then + write (stdout,'(1x,a,t30)',advance='no') 'Reading input coords:' + if (allocated(env%thermo%coords)) then call mol%open(env%thermo%coords) - write(stdout,'(1x,a)') trim(env%thermo%coords) + write (stdout,'(a)') trim(env%thermo%coords) else call mol%open(env%inputcoords) - write(stdout,'(1x,a)') trim(env%inputcoords) - endif - nat3 = mol%nat * 3 - allocate(hess(nat3,nat3),freq(nat3), source=0.0_wp) + write (stdout,'(a)') trim(env%inputcoords) + end if + nat3 = mol%nat*3 + allocate (freq(nat3),source=0.0_wp) !> input frequencies or hessian - if(allocated(env%thermo%vibfile))then - write(stdout,'(1x,a,a)') 'Reading frequencies from: ',trim(env%thermo%vibfile) - call rdfreq(env%thermo%vibfile,nat3,freq) + if (allocated(env%thermo%vibfile)) then + write (stdout,'(1x,a,t30,a)') 'Reading frequencies from:',trim(env%thermo%vibfile) + call rdfreq(mol,env%thermo%vibfile,nat3,freq) else - write(stdout,'(1x,a)') 'No Hessian or vibspectrum file allocated for thermo routine!' + write (stdout,'(1x,a)') 'No Hessian or vibspectrum file allocated for thermo routine!' call creststop(status_input) - endif - write(stdout,*) - + end if + write (stdout,*) + !> energy (maybe read from comment line of xyz) etot = mol%energy !> inversion threshold ithr = env%thermo%ithr !> frequency scaling factor fscal = env%thermo%fscal - !> RR-HO interpolation + !> RR-HO interpolation (or cut-off) sthr = env%thermo%sthr + !> Svib model + emodel = env%thermo%emodel if (.not.allocated(env%thermo%temps)) then call env%thermo%get_temps() @@ -413,38 +429,133 @@ subroutine thermo_standalone(env) !write(*,*) temps nrt = minloc(temps(:),1) !write(*,*) nrt - temps = env%thermo%temps + temps = env%thermo%temps - !> calcthermo wants input in Angstroem - call calcthermo(mol%nat,mol%at,mol%xyz*autoaa,freq,.true., & - & ithr,fscal,sthr,nt,temps,et,ht,gt,stot) + !> calcthermo wants input in Bohr + call calcthermo(mol%nat,mol%at,mol%xyz,freq,.true., & + & ithr,fscal,sthr,nt,temps,et,ht,gt,stot,stdout,emodel=emodel) - !> printout zpve = et(nrt)-ht(nrt) - write(stdout,*) - write(stdout,'(10x,a)') repeat(':',50) - write(stdout,'(10x,"::",7x,a,f12.2,1x,a,8x,"::")') "THERMODYNAMICS at",temps(nrt),'K' - write(stdout,'(10x,a)') repeat(':',50) - write(stdout,outfmt) 'TOTAL FREE ENERGY',etot+gt(nrt),'Eh' - write(stdout,'(10x,a)') '::'//repeat('-',46)//'::' - write(stdout,outfmt) 'total energy ',etot,'Eh' - write(stdout,outfmt) 'ZPVE ',zpve,'Eh' - write(stdout,outfmt) 'G(RRHO) w/o ZPVE ',gt(nrt)-zpve,'Eh' - write(stdout,outfmt) 'G(RRHO) total ',gt(nrt),'Eh' - write(stdout,'(10x,a)') repeat(':',50) + call print_thermo_summary(stdout,temps(nrt),etot,zpve,gt(nrt)) !> for plotting temperature dependencies etc. - write(stdout,*) - write(stdout,*) 'Some output will be written to thermo.dump' - open(newunit=ich, file='thermo.dump') - do i=1,nt - write(ich,'(f12.4,4F20.10)') temps(i),gt(i)+etot,gt(i),ht(i),stot(i) - enddo - close(ich) + write (stdout,*) + write (stdout,*) 'Some output will be written to thermo.dump' + open (newunit=ich,file='thermo.dump') + do i = 1,nt + write (ich,'(f12.4,4F20.10)') temps(i),gt(i)+etot,gt(i),ht(i),stot(i) + end do + close (ich) deallocate (stot,gt,ht,et,temps) end subroutine thermo_standalone +!========================================================================================! + +subroutine print_thermo_summary(iunit,temp,etot,zpve,grrho) +!********************************************************** +!* Print the standard THERMODYNAMICS summary box. +!* Called from numhess_thermostat and thermo_standalone. +!********************************************************** + use crest_parameters,only:wp,stdout + implicit none + integer,intent(in) :: iunit + real(wp),intent(in) :: temp,etot,zpve,grrho + character(len=*),parameter :: outfmt = '(10x,"::",1x,a,f24.12,1x,a,1x,"::")' + write (iunit,*) + write (iunit,'(10x,a)') repeat(':',50) + write (iunit,'(10x,"::",7x,a,f12.2,1x,a,8x,"::")') "THERMODYNAMICS at",temp,'K' + write (iunit,'(10x,a)') repeat(':',50) + write (iunit,outfmt) 'TOTAL FREE ENERGY',etot+grrho,'Eh' + write (iunit,'(10x,a)') '::'//repeat('-',46)//'::' + write (iunit,outfmt) 'total energy ',etot,'Eh' + write (iunit,outfmt) 'ZPVE ',zpve,'Eh' + write (iunit,outfmt) 'G(RRHO) w/o ZPVE ',grrho-zpve,'Eh' + write (iunit,outfmt) 'G(RRHO) total ',grrho,'Eh' + write (iunit,'(10x,a)') repeat(':',50) +end subroutine print_thermo_summary + +!========================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_hessloop requires coordinates in Bohr + xyz = xyz/bohr +!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Back to Angstrom + xyz = xyz*bohr +!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<' + + call dumpenergies('crest.energies',eread) + write (stdout,'(/,a,a,a)') 'List of free energies written to <','crest.energies','>' + + deallocate (eread,at,xyz) +!========================================================================================! + call tim%stop(14) + return +end subroutine crest_ensemble_hessians diff --git a/src/algos/optimization.f90 b/src/algos/optimization.f90 index 775c0414..c93ed831 100644 --- a/src/algos/optimization.f90 +++ b/src/algos/optimization.f90 @@ -21,14 +21,18 @@ subroutine crest_optimization(env,tim) !*********************************************** !* subroutine crest_optimization !* This routine implements a standalone runtype -!* to perform geometry optimization for the -!* specified input file (read from env%ref) +!* to perform geometry optimization for the +!* specified input file (read from env%ref). +!* An optional inline refinement step is run +!* after geometry optimization when refine_queue +!* is allocated (e.g. set by --A//B or --refine). !*********************************************** use crest_parameters,only:wp,stdout,bohr use crest_data use crest_calculator use strucrd use optimize_module + use iomod,only:colorify implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim @@ -43,6 +47,19 @@ subroutine crest_optimization(env,tim) character(len=80) :: atmp character(len=*),parameter :: partial = '∂E/∂' + +! ══════════════════════════════════════════════════════════════════════════════ + write (stdout,*) + write (stdout,*) " -------------------------------------------- " + write (stdout,*) " #### ##### ##### # # # # ###### ###### " + write (stdout,*) " # # # # # # ## ## # # # " + write (stdout,*) " # # # # # # # ## # # # ##### " + write (stdout,*) " # # ##### # # # # # # # " + write (stdout,*) " # # # # # # # # # # " + write (stdout,*) " #### # # # # # # ###### ###### " + write (stdout,*) " -------------------------------------------- " + write (stdout,*) + !========================================================================================! call new_ompautoset(env,'max',0,T,Tn) call ompprint_intern() @@ -57,19 +74,19 @@ subroutine crest_optimization(env,tim) !========================================================================================! allocate (grad(3,mol%nat),source=0.0_wp) - calc = env%calc + call calc%copy(env%calc) !>--- check if we have any calculation settings allocated if (calc%ncalculations < 1) then write (stdout,*) 'no calculations allocated' return else - call calc%info( stdout ) + call calc%info(stdout) end if - write(stdout,'(a)') repeat('-',80) + write (stdout,'(a)') repeat('-',80) !>-- geometry optimization pr = .true. !> stdout printout - wr = .true. !> write crestopt.log + wr = .true. !> write crestopt.log.xyz !>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Optimized geometry written to crestopt.xyz' gnorm = norm2(grad) + +! ── optional inline refinement (e.g. --A//B or --refine) ───────── + if (allocated(env%refine_queue)) then + do i = 1,size(env%refine_queue,1) + select case (env%refine_queue(i)) + case (refine%singlepoint) + write (stdout,'(a)') '> Running SP refinement ...' + calc%refine_stage = refine%singlepoint + call engrad(molnew,calc,energy,grad,io) + calc%refine_stage = 0 + gnorm = norm2(grad) + if (io == 0) write (stdout,'(1x,a,f20.10,a)') 'Refined energy: ',energy,' Eh' + case (refine%geoopt) + write (stdout,'(a)') '> Re-optimizing at higher level ...' + mol = molnew + calc%refine_stage = refine%geoopt + call optimize_geometry(mol,molnew,calc,energy,grad,pr,wr,io) + calc%refine_stage = 0 + gnorm = norm2(grad) + end select + end do + end if + + write (stdout,'(a)') '> Optimized geometry written to crestopt.xyz' write (atmp,'(1x,"Etot=",f16.10,1x,"g norm=",f12.8)') energy,gnorm molnew%comment = trim(atmp) @@ -99,7 +139,7 @@ subroutine crest_optimization(env,tim) else write (stdout,*) 'geometry optimization FAILED!' env%iostatus_meta = status_failed - endif + end if write (stdout,*) write (stdout,'(a)') repeat('=',42) @@ -107,9 +147,9 @@ subroutine crest_optimization(env,tim) write (stdout,'(1x,a,f20.10,a)') 'GRADIENT NORM ',norm2(grad),' Eh/a0' write (stdout,'(a)') repeat('=',42) - if(io /= 0)then + if (io /= 0) then write (stdout,*) 'WARNING: geometry optimization FAILED!' - endif + end if deallocate (grad) !========================================================================================! @@ -117,12 +157,22 @@ subroutine crest_optimization(env,tim) !========================================================================================! !>--- append numerical hessian calculation - if( io == 0 .and. env%crest_ohess )then + if (io == 0.and.env%crest_ohess) then call env%ref%load(molnew) !> load the optimized geometry call crest_numhess(env,tim) !> run the numerical hessian - endif + end if + +!========================================================================================! !========================================================================================! +!>--- append deform opt hessian calculation + if (io == 0.and.calc%deform_opt_hess) then !.and. calc%do_HR )then + call env%ref%load(molnew) !> load the optimized geometry + call deform_opt_hess(calc,molnew) !> run the hessian reconstruction + end if + +!========================================================================================! + return end subroutine crest_optimization @@ -159,7 +209,6 @@ subroutine crest_ensemble_optimization(env,tim) real(wp),allocatable :: eread(:) real(wp),allocatable :: xyz(:,:,:) integer,allocatable :: at(:) - character(len=10),allocatable :: comments(:) character(len=80) :: atmp real(wp) :: percent character(len=52) :: bar @@ -180,16 +229,16 @@ subroutine crest_ensemble_optimization(env,tim) !>---- read the input ensemble call rdensembleparam(ensnam,nat,nall) - if (nall .lt. 1)then + if (nall .lt. 1) then write (stdout,*) '**ERROR** empty ensemble file.' env%iostatus_meta = status_input return - endif + end if allocate (xyz(3,nat,nall),at(nat),eread(nall)) call rdensemble(ensnam,nat,nall,at,xyz,eread) !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_oloop requires coordinates in Bohrs - xyz = xyz / bohr + xyz = xyz/bohr !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- set OMP parallelization @@ -208,29 +257,35 @@ subroutine crest_ensemble_optimization(env,tim) !========================================================================================! !>--- output - write(stdout,'(/,a,a,a)') 'Rewriting ',ensemblefile,' in the correct order'// & + write (stdout,'(/,a,a,a)') 'Rewriting ',ensemblefile,' in the correct order'// & & ' (failed optimizations are assigned an energy of +1.0)' -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Back to Angstroem - xyz = xyz * bohr -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<< 0.0_wp) comments(i) = '!failed' - enddo - call wrensemble(ensemblefile,nat,nall,at,xyz,eread,comments) +!>--- write the optimized ensemble in extxyz format (consistent with the +!> refined ensemble). coord%xyz keeps the internal Bohr convention; the +!> extxyz writer converts to Angstroem on output. + block + type(coord),allocatable :: structures(:) + allocate (structures(nall)) + do i = 1,nall + structures(i)%nat = nat + structures(i)%at = at + structures(i)%xyz = xyz(:,:,i) + structures(i)%energy = eread(i) + structures(i)%wrextxyz = .true. + end do + call wrensemble(ensemblefile,nall,structures) + deallocate (structures) + end block deallocate (eread,at,xyz) - write(stdout,'(/,a,a,a)') 'Optimized ensemble written to <',ensemblefile,'>' + write (stdout,'(/,a,a,a)') 'Optimized ensemble written to <',ensemblefile,'>' !========================================================================================! !>--- (optional) refinement step if (allocated(env%refine_queue)) then - write(stdout,*) + write (stdout,*) call crest_refine(env,ensemblefile,ensemblefile//'.refine') - write(stdout,'(/,a,a,a)') 'Refined ensemble written to <',ensemblefile,'.refine>' - endif + write (stdout,'(/,a,a,a)') 'Refined ensemble written to <',ensemblefile,'.refine>' + end if !========================================================================================! call tim%stop(14) @@ -252,7 +307,7 @@ subroutine crest_ensemble_screening(env,tim) use crest_calculator use strucrd use optimize_module - use iomod + use iomod implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim @@ -291,11 +346,11 @@ subroutine crest_ensemble_screening(env,tim) !>---- read the input ensemble call rdensembleparam(ensnam,nat,nall) - if (nall .lt. 1)then + if (nall .lt. 1) then write (stdout,*) '**ERROR** empty ensemble file.' env%iostatus_meta = status_input return - endif + end if !>--- set OMP parallelization call new_ompautoset(env,'auto',nall,T,Tn) @@ -314,8 +369,8 @@ subroutine crest_ensemble_screening(env,tim) !>--- call the loop call rmrfw('crest_rotamers_') call optlev_to_multilev(3.0d0,multilevel) - call crest_multilevel_oloop(env,ensnam,multilevel) - if(env%iostatus_meta .ne. 0 ) return + call crest_multilevel_oloop(env,ensnam,multilevel,0) + if (env%iostatus_meta .ne. 0) return !>--- printout call catdel('cregen.out.tmp') @@ -326,7 +381,6 @@ subroutine crest_ensemble_screening(env,tim) !>--- clean up call screen_cleanup - !========================================================================================! call tim%stop(14) return diff --git a/src/algos/parallel.f90 b/src/algos/parallel.f90 index 27fa9f01..09611c6b 100644 --- a/src/algos/parallel.f90 +++ b/src/algos/parallel.f90 @@ -57,7 +57,6 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) use strucrd use optimize_module use iomod,only:makedir,directory_exist,remove - use crest_restartlog,only:trackrestart,restart_write_dummy implicit none type(systemdata),target,intent(inout) :: env real(wp),intent(inout) :: xyz(3,nat,nall) @@ -68,6 +67,27 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) type(calcdata),intent(in),target,optional :: customcalc end subroutine crest_oloop end interface + + interface + subroutine crest_hessloop(env,nat,nall,at,xyz,eread,gt_out,stot_out) + use crest_parameters,only:wp,stdout,sep + use crest_calculator + use omp_lib + use crest_data + use strucrd + use thermochem_module + use iomod,only:makedir,directory_exist,remove + implicit none + type(systemdata),intent(inout) :: env + real(wp),intent(inout) :: xyz(3,nat,nall) + integer,intent(in) :: at(nat) + real(wp),intent(inout) :: eread(nall) + integer,intent(in) :: nat,nall + real(wp),optional,intent(out) :: gt_out(:,:) !> (nall, nt_full) + real(wp),optional,intent(out) :: stot_out(:,:) !> (nall, nt_full) + end subroutine crest_hessloop + end interface + end module parallel_interface !========================================================================================! @@ -80,6 +100,7 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) !* subroutine crest_sploop !* This subroutine performs concurrent singlepoint evaluations !* for the given ensemble. Input eread is overwritten +!* xyz must be in Bohrs !*************************************************************** use crest_parameters,only:wp,stdout,sep use crest_calculator @@ -88,6 +109,7 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) use strucrd use optimize_module use iomod,only:makedir,directory_exist,remove + use term_ui,only:progress_init,progress_update,progress_finish implicit none type(systemdata),intent(inout) :: env real(wp),intent(inout) :: xyz(3,nat,nall) @@ -119,12 +141,12 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) call new_ompautoset(env,'auto_nested',nall,T,Tn) nested = env%omp_allow_nested - !>--- prepare objects for parallelization T = env%threads allocate (calculations(T),source=env%calc) allocate (mols(T)) do i = 1,T + call calculations(T)%copy(env%calc) do j = 1,env%calc%ncalculations calculations(i)%calcs(j) = env%calc%calcs(j) !>--- directories and io preparation @@ -134,8 +156,8 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) end if write (atmp,'(a,"_",i0)') sep,i calculations(i)%calcs(j)%calcspace = env%calc%calcs(j)%calcspace//trim(atmp) - if(allocated(calculations(i)%calcs(j)%calcfile)) deallocate(calculations(i)%calcs(j)%calcfile) - if(allocated(calculations(i)%calcs(j)%systemcall)) deallocate(calculations(i)%calcs(j)%systemcall) + if (allocated(calculations(i)%calcs(j)%calcfile)) deallocate (calculations(i)%calcs(j)%calcfile) + if (allocated(calculations(i)%calcs(j)%systemcall)) deallocate (calculations(i)%calcs(j)%systemcall) call calculations(i)%calcs(j)%printid(i,j) end do calculations(i)%pr_energies = .false. @@ -144,12 +166,14 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) !>--- printout directions and timer initialization pr = .false. !> stdout printout - wr = .false. !> write crestopt.log + wr = .false. !> write crestopt.log.xyz call profiler%init(1) call profiler%start(1) -!>--- first progress printout (initializes progress variables) - call crest_oloop_pr_progress(env,nall,0) +!>--- initialize progress bar + call progress_init(env%ps,nall,width=50,prefix=" ↳ ", & + & suffix="",show_time=.true.,show_eta=.false.) + call progress_update(env%ps,0,nall) !>--- shared variables allocate (grads(3,nat,T),source=0.0_wp) @@ -158,6 +182,8 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) z = 0 !> counter to perform optimization in right order (1...nall) eread(:) = 0.0_wp grads(:,:,:) = 0.0_wp +!>--- pre-start server-based calculators before forking OMP threads + call preinit_mlip_parallel(calculations,T) !>--- loop over ensemble !$omp parallel & !$omp shared(env,calculations,nat,nall,at,xyz,eread,grads,c,k,z,pr,wr) & @@ -197,7 +223,7 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) end if k = k+1 !>--- print progress - call crest_oloop_pr_progress(env,nall,k) + call progress_update(env%ps,k,nall) !$omp end critical !$omp end task end do @@ -206,7 +232,7 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) !$omp end parallel !>--- finalize progress printout - call crest_oloop_pr_progress(env,nall,-1) + call progress_finish(env%ps) !>--- stop timer call profiler%stop(1) @@ -230,6 +256,238 @@ subroutine crest_sploop(env,nat,nall,at,xyz,eread) return end subroutine crest_sploop +!========================================================================================! +!========================================================================================! +!> Routines for concurrent singlepoint evaluations +!========================================================================================! +!========================================================================================! +subroutine crest_hessloop(env,nat,nall,at,xyz,eread,gt_out,stot_out) +!*************************************************************** +!* subroutine crest_hessloop +!* Concurrent numerical Hessian evaluations for an ensemble. +!* Input eread is overwritten with Gibbs free energies. +!* xyz must be in Bohrs. +!* eread contains only the gt@RT on output! +!* Optional gt_out/stot_out return G and S at all temperatures +!* from env%thermo; requires pre-allocated (nall,nt) arrays. +!* +!* Parallelization is enabled using numhess1 (OpenMP-compatible). +!* +!*************************************************************** + use crest_parameters,only:wp,stdout,sep + use crest_calculator + use omp_lib + use crest_data + use strucrd + use optimize_module + use thermochem_module + use iomod,only:makedir,directory_exist,remove + use term_ui,only:progress_init,progress_update,progress_finish + implicit none + type(systemdata),intent(inout) :: env + real(wp),intent(inout) :: xyz(3,nat,nall) + integer,intent(in) :: at(nat) + real(wp),intent(inout) :: eread(nall) + integer,intent(in) :: nat,nall + real(wp),optional,intent(out) :: gt_out(:,:) !> (nall, nt_full) + real(wp),optional,intent(out) :: stot_out(:,:) !> (nall, nt_full) + + type(coord),allocatable :: mols(:) + integer :: i,j,k,l,io,ich,ich2,c,z,job_id,zcopy,nat3 + logical :: pr,wr,ex + type(calcdata),allocatable :: calculations(:) + real(wp) :: energy,gnorm + real(wp),allocatable :: grad(:,:),grads(:,:,:) + real(wp),allocatable :: freqs(:,:),hess(:,:,:) + integer :: thread_id,vz,job + character(len=80) :: atmp + real(wp) :: percent,runtime + + integer :: nt,nrt + real(wp),allocatable :: temps(:,:),et(:,:),ht(:,:),gt(:,:),stot(:,:) + real(wp) :: ithr,sthr,fscal,rt + character(len=:),allocatable :: emodel + + type(timer) :: profiler + integer :: T,Tn !> threads and threads per core + logical :: nested + real(wp),parameter :: big = 10e10 + +!>--- check if we have any calculation settings allocated + if (env%calc%ncalculations < 1) then + write (stdout,*) 'no calculations allocated' + return + end if + +!>--- prepare calculation objects for parallelization (one per thread) + call new_ompautoset(env,'auto_nested',nall,T,Tn) + nested = env%omp_allow_nested + +!>--- prepare objects for parallelization + T = env%threads + allocate (calculations(T))!,source=env%calc) + allocate (mols(T)) + nat3 = nat*3 + allocate (freqs(nat3,T),source=0.0_wp) + allocate (hess(nat3,nat3,T),source=0.0_wp) + do i = 1,T + call calculations(i)%copy(env%calc) + do j = 1,env%calc%ncalculations + !calculations(i)%calcs(j) = env%calc%calcs(j) + !>--- directories and io preparation + ex = directory_exist(env%calc%calcs(j)%calcspace) + if (.not.ex) then + io = makedir(trim(env%calc%calcs(j)%calcspace)) + end if + write (atmp,'(a,"_",i0)') sep,i + calculations(i)%calcs(j)%calcspace = env%calc%calcs(j)%calcspace//trim(atmp) + if (allocated(calculations(i)%calcs(j)%calcfile)) deallocate (calculations(i)%calcs(j)%calcfile) + if (allocated(calculations(i)%calcs(j)%systemcall)) deallocate (calculations(i)%calcs(j)%systemcall) + call calculations(i)%calcs(j)%printid(i,j) + end do + calculations(i)%pr_energies = .false. + allocate (mols(i)%at(nat),mols(i)%xyz(3,nat)) + end do + +!>--- thermo settings + !> inversion threshold + ithr = env%thermo%ithr + !> frequency scaling factor + fscal = env%thermo%fscal + !> RR-HO interpolation (or cut-off) + sthr = env%thermo%sthr + !> Svib model + emodel = env%thermo%emodel + if (.not.allocated(env%thermo%temps)) then + call env%thermo%get_temps() + end if + if (present(gt_out)) then + nt = env%thermo%ntemps + allocate (temps(nt,T),et(nt,T),ht(nt,T),gt(nt,T),stot(nt,T),source=0.0_wp) + do i = 1,T + temps(:,i) = env%thermo%temps(:) + end do + rt = env%thermo%get_close_rt(nrt) + else + nt = 1 + allocate (temps(nt,T),et(nt,T),ht(nt,T),gt(nt,T),stot(nt,T),source=0.0_wp) + rt = env%thermo%get_close_rt(nrt) + temps = rt + nrt = 1 + end if + +!>--- printout directions and timer initialization + pr = .false. !> stdout printout + wr = .false. !> write crestopt.log.xyz + call profiler%init(1) + call profiler%start(1) + +!>--- initialize progress bar + call progress_init(env%ps,nall,width=50,prefix=" ↳ ", & + & suffix="",show_time=.true.,show_eta=.false.) + call progress_update(env%ps,0,nall) + +!>--- shared variables + allocate (grads(3,nat,T),source=0.0_wp) + c = 0 !> counter of successfull optimizations + k = 0 !> counter of total optimization (fail+success) + z = 0 !> counter to perform optimization in right order (1...nall) + eread(:) = 0.0_wp + grads(:,:,:) = 0.0_wp +!>--- pre-start server-based calculators before forking OMP threads + call preinit_mlip_parallel(calculations,T) +!>--- loop over ensemble + !$omp parallel & + !$omp shared(env,calculations,nat,nall,at,xyz,eread,grads,c,k,z,pr,wr,nrt) & + !$omp shared(mols,nested,Tn,freqs,hess,temps,et,ht,gt,stot,nat3,ithr,fscal,sthr,nt,emodel) + !$omp single + do i = 1,nall + + call initsignal() + vz = i + !$omp task firstprivate( vz ) private(i,j,job,energy,io,thread_id,zcopy) + call initsignal() + + !>--- OpenMP nested region threads + if (nested) call ompmklset(Tn) + + thread_id = OMP_GET_THREAD_NUM() + job = thread_id+1 + !>--- modify calculation spaces + !$omp critical + z = z+1 + zcopy = z + mols(job)%nat = nat + mols(job)%at(:) = at(:) + mols(job)%xyz(:,:) = xyz(:,:,z) + !$omp end critical + + !>-- engery+gradient call first, for setup + call engrad(mols(job),calculations(job),energy,grads(:,:,job),io) + !>-- then, numerical hessian + call numhess1(mols(job)%nat,mols(job)%at,mols(job)%xyz, & + calculations(job),hess(:,:,job),io) + !!$omp critical + if (io .eq. 0) then + call prj_mw_hess(mols(job)%nat,mols(job)%at,nat3,mols(job)%xyz,hess(:,:,job)) + !>-- Computes the Frequencies + call frequencies(mols(job)%nat,mols(job)%at,mols(job)%xyz, & + nat3,hess(:,:,job),freqs(:,job),io) + end if + + if (io .eq. 0) then + call calcthermo(mols(job)%nat,mols(job)%at,mols(job)%xyz, & + freqs(:,job),.false.,ithr,fscal,sthr,nt, & + temps(:,job),et(:,job),ht(:,job),gt(:,job), & + stot(:,job),emodel=emodel) + end if + !!$omp end critical + + !$omp critical + if (io == 0) then + c = c+1 + eread(zcopy) = gt(nrt,job) + if (present(gt_out)) gt_out(zcopy,:) = gt(:,job) + if (present(stot_out)) stot_out(zcopy,:) = stot(:,job) + else + eread(zcopy) = big + end if + k = k+1 + call progress_update(env%ps,k,nall) + !$omp end critical + !$omp end task + end do + !$omp taskwait + !$omp end single + !$omp end parallel + +!>--- finalize progress printout + call progress_finish(env%ps) + +!>--- stop timer + call profiler%stop(1) + +!>--- prepare some summary printout + percent = float(c)/float(nall)*100.0_wp + write (atmp,'(f5.1,a)') percent,'% success)' + write (stdout,'(">",1x,i0,a,i0,a,a)') c,' of ',nall,' structures successfully evaluated (', & + & trim(adjustl(atmp)) + write (atmp,'(">",1x,a,i0,a)') 'Total runtime for ',nall,' frequency calculations:' + call profiler%write_timing(stdout,1,trim(atmp),.true.) + runtime = profiler%get(1) + write (atmp,'(f16.3,a)') runtime/real(nall,wp),' sec' + write (stdout,'(a,a,a)') '> Corresponding to approximately ',trim(adjustl(atmp)), & + & ' per processed structure' + + deallocate (grads) + call profiler%clear() + deallocate (calculations) + if (allocated(mols)) deallocate (mols) + if (allocated(freqs)) deallocate (freqs) + if (allocated(hess)) deallocate (hess) + return +end subroutine crest_hessloop + !========================================================================================! !========================================================================================! !> Routines for concurrent geometry optimization @@ -243,7 +501,8 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) !* env - contains parallelization and other program settings !* dump - decides on whether to dump an ensemble file !* WARNING: the ensemble file will NOT be in the same order -!* as the input xyz array. However, the overwritten xyz will be! +!* as the input xyz array. However, the overwritten xyz will be! +!* !* customcalc - customized (optional) calculation level data !* !* IMPORTANT: xyz should be in Bohr(!) for this routine @@ -255,7 +514,7 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) use strucrd use optimize_module use iomod,only:makedir,directory_exist,remove - use crest_restartlog,only:trackrestart,restart_write_dummy + use term_ui,only:progress_init,progress_update,progress_finish implicit none type(systemdata),target,intent(inout) :: env real(wp),intent(inout) :: xyz(3,nat,nall) @@ -280,18 +539,12 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) integer :: T,Tn !> threads and threads per core logical :: nested -!>--- decide wether to skip this call - if (trackrestart(env)) then - call restart_write_dummy(ensemblefile) - return - end if - !>--- check which calc to use - if(present(customcalc))then + if (present(customcalc)) then mycalc => customcalc else mycalc => env%calc - endif + end if !>--- check if we have any calculation settings allocated if (mycalc%ncalculations < 1) then @@ -304,23 +557,24 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) nested = env%omp_allow_nested !>--- prepare objects for parallelization - allocate (calculations(T),source=mycalc) + allocate (calculations(T))!,source=mycalc) allocate (mols(T),molsnew(T)) do i = 1,T + call calculations(i)%copy(mycalc) do j = 1,mycalc%ncalculations - calculations(i)%calcs(j) = mycalc%calcs(j) + !calculations(i)%calcs(j) = mycalc%calcs(j) !>--- directories and io preparation ex = directory_exist(mycalc%calcs(j)%calcspace) if (.not.ex) then io = makedir(trim(mycalc%calcs(j)%calcspace)) end if - if(calculations(i)%calcs(j)%id == jobtype%tblite)then - calculations(i)%optnewinit=.true. - endif + if (calculations(i)%calcs(j)%id == jobtype%tblite) then + calculations(i)%optnewinit = .true. + end if write (atmp,'(a,"_",i0)') sep,i calculations(i)%calcs(j)%calcspace = mycalc%calcs(j)%calcspace//trim(atmp) - if(allocated(calculations(i)%calcs(j)%calcfile)) deallocate(calculations(i)%calcs(j)%calcfile) - if(allocated(calculations(i)%calcs(j)%systemcall)) deallocate(calculations(i)%calcs(j)%systemcall) + if (allocated(calculations(i)%calcs(j)%calcfile)) deallocate (calculations(i)%calcs(j)%calcfile) + if (allocated(calculations(i)%calcs(j)%systemcall)) deallocate (calculations(i)%calcs(j)%systemcall) call calculations(i)%calcs(j)%printid(i,j) end do calculations(i)%pr_energies = .false. @@ -330,7 +584,7 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) !>--- printout directions and timer initialization pr = .false. !> stdout printout - wr = .false. !> write crestopt.log + wr = .false. !> write crestopt.log.xyz if (dump) then open (newunit=ich,file=ensemblefile) open (newunit=ich2,file=ensembleelog) @@ -338,8 +592,10 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) call profiler%init(1) call profiler%start(1) -!>--- first progress printout (initializes progress variables) - call crest_oloop_pr_progress(env,nall,0) +!>--- initialize progress bar + call progress_init(env%ps,nall,width=50,prefix=" ↳ ", & + & suffix="",show_time=.true.,show_eta=.false.) + call progress_update(env%ps,0,nall) !>--- shared variables allocate (grads(3,nat,T),source=0.0_wp) @@ -348,6 +604,8 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) z = 0 !> counter to perform optimization in right order (1...nall) eread(:) = 0.0_wp grads(:,:,:) = 0.0_wp +!>--- pre-start server-based calculators before forking OMP threads + call preinit_mlip_parallel(calculations,T) !>--- loop over ensemble !$omp parallel & !$omp shared(env,calculations,nat,nall,at,xyz,eread,grads,c,k,z,pr,wr,dump) & @@ -387,14 +645,14 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) c = c+1 if (dump) then gnorm = norm2(grads(:,:,job)) - write (atmp,'(1x,"Etot=",f16.10,1x,"g norm=",f12.8)') energy,gnorm + write (atmp,'(1x,"energy=",f16.10,1x,"g norm=",f12.8)') energy,gnorm molsnew(job)%comment = trim(atmp) call molsnew(job)%append(ich) call calc_eprint(calculations(job),energy,calculations(job)%etmp,gnorm,ich2) end if eread(zcopy) = energy xyz(:,:,zcopy) = molsnew(job)%xyz(:,:) - else if(io==calculations(job)%maxcycle .and. calculations(job)%anopt) then + else if (io == calculations(job)%maxcycle.and.calculations(job)%anopt) then !>--- allow partial optimization? c = c+1 eread(zcopy) = energy @@ -404,7 +662,7 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) end if k = k+1 !>--- print progress - call crest_oloop_pr_progress(env,nall,k) + call progress_update(env%ps,k,nall) !$omp end critical !$omp end task end do @@ -413,7 +671,7 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) !$omp end parallel !>--- finalize progress printout - call crest_oloop_pr_progress(env,nall,-1) + call progress_finish(env%ps) !>--- stop timer call profiler%stop(1) @@ -444,64 +702,6 @@ subroutine crest_oloop(env,nat,nall,at,xyz,eread,dump,customcalc) return end subroutine crest_oloop -!========================================================================================! -subroutine crest_oloop_pr_progress(env,total,current) -!********************************************* -!* subroutine crest_oloop_pr_progress -!* A subroutine to print and track progress of -!* concurrent geometry optimizations -!********************************************* - use crest_parameters,only:wp,stdout - use crest_data - use iomod,only:to_str - implicit none - type(systemdata),intent(inout) :: env - integer,intent(in) :: total,current - real(wp) :: percent - character(len=5) :: atmp - real(wp),save :: increment - real(wp),save :: progressbarrier - - percent = float(current)/float(total)*100.0_wp - if (current == 0) then !> as a wrapper to start the printout - progressbarrier = 0.0_wp - if (env%niceprint) then - percent = 0.0_wp - call printprogbar(percent) - end if - increment = 10.0_wp - if (total > 1000) increment = 7.5_wp - if (total > 5000) increment = 5.0_wp - if (total > 10000) increment = 2.5_wp - if (total > 20000) increment = 1.0_wp - - else if (current <= total.and.current > 0) then !> the regular printout case - if (env%niceprint) then - call printprogbar(percent) - - else if (.not.env%legacy) then - if (percent >= progressbarrier) then - write (atmp,'(f5.1)') percent - write (stdout,'(1x,a)',advance='no') '|>'//trim(adjustl(atmp))//'%' - progressbarrier = progressbarrier+increment - progressbarrier = min(progressbarrier,100.0_wp) - flush (stdout) - end if - else - write (stdout,'(1x,i0)',advance='no') current - flush (stdout) - end if - - else !> as a wrapper to finalize the printout - if (.not.env%niceprint) then - write (stdout,'(/,1x,a)') 'done.' - else - write (stdout,*) - end if - end if - -end subroutine crest_oloop_pr_progress - !========================================================================================! !========================================================================================! !> Routines for parallel MDs @@ -520,7 +720,6 @@ subroutine crest_search_multimd(env,mol,mddats,nsim) use dynamics_module use iomod,only:makedir,directory_exist,remove use omp_lib - use crest_restartlog,only:trackrestart,restart_write_dummy implicit none type(systemdata),intent(inout) :: env type(mddata) :: mddats(nsim) @@ -540,12 +739,6 @@ subroutine crest_search_multimd(env,mol,mddats,nsim) real(wp),allocatable :: grdtmp(:,:) type(timer) :: profiler !===========================================================! -!>--- decide wether to skip this call - if (trackrestart(env)) then - call restart_write_dummy('crest_dynamics.trj') - return - end if - !>--- check if we have any MD & calculation settings allocated if (.not.env%mddat%requested) then write (stdout,*) 'MD requested, but no MD settings present.' @@ -575,8 +768,8 @@ subroutine crest_search_multimd(env,mol,mddats,nsim) end if write (atmp,'(a,"_",i0)') sep,i calculations(i)%calcs(j)%calcspace = env%calc%calcs(j)%calcspace//trim(atmp) - if(allocated(calculations(i)%calcs(j)%calcfile)) deallocate(calculations(i)%calcs(j)%calcfile) - if(allocated(calculations(i)%calcs(j)%systemcall)) deallocate(calculations(i)%calcs(j)%systemcall) + if (allocated(calculations(i)%calcs(j)%calcfile)) deallocate (calculations(i)%calcs(j)%calcfile) + if (allocated(calculations(i)%calcs(j)%systemcall)) deallocate (calculations(i)%calcs(j)%systemcall) call calculations(i)%calcs(j)%printid(i,j) end do calculations(i)%pr_energies = .false. @@ -588,6 +781,8 @@ subroutine crest_search_multimd(env,mol,mddats,nsim) pr = .false. call profiler%init(nsim) +!>--- pre-start server-based calculators before forking OMP threads + call preinit_mlip_parallel(calculations,T) !>--- run the MDs !$omp parallel & !$omp shared(env,calculations,mddats,mol,pr,percent,ich, nsim, moltmps, nested,Tn) & @@ -643,7 +838,7 @@ subroutine collect(n,mddats) integer :: i,io,ich,ich2 character(len=:),allocatable :: atmp character(len=256) :: btmp - open (newunit=ich,file='crest_dynamics.trj') + open (newunit=ich,file='crest_dynamics.trj.xyz') do i = 1,n atmp = mddats(i)%trajectoryfile inquire (file=atmp,exist=ex) @@ -829,7 +1024,6 @@ subroutine crest_search_multimd2(env,mols,mddats,nsim) use shake_module use iomod,only:makedir,directory_exist,remove use omp_lib - use crest_restartlog,only:trackrestart,restart_write_dummy implicit none !> INPUT type(systemdata),intent(inout) :: env @@ -848,12 +1042,6 @@ subroutine crest_search_multimd2(env,mols,mddats,nsim) integer :: vz,job,thread_id type(timer) :: profiler !===========================================================! -!>--- decide wether to skip this call - if (trackrestart(env)) then - call restart_write_dummy('crest_dynamics.trj') - return - end if - !>--- check if we have any MD & calculation settings allocated if (.not.env%mddat%requested) then write (stdout,*) 'MD requested, but no MD settings present.' @@ -879,8 +1067,8 @@ subroutine crest_search_multimd2(env,mols,mddats,nsim) end if write (atmp,'(a,"_",i0)') sep,i calculations(i)%calcs(j)%calcspace = env%calc%calcs(j)%calcspace//trim(atmp) - if(allocated(calculations(i)%calcs(j)%calcfile)) deallocate(calculations(i)%calcs(j)%calcfile) - if(allocated(calculations(i)%calcs(j)%systemcall)) deallocate(calculations(i)%calcs(j)%systemcall) + if (allocated(calculations(i)%calcs(j)%calcfile)) deallocate (calculations(i)%calcs(j)%calcfile) + if (allocated(calculations(i)%calcs(j)%systemcall)) deallocate (calculations(i)%calcs(j)%systemcall) call calculations(i)%calcs(j)%printid(i,j) end do calculations(i)%pr_energies = .false. @@ -890,6 +1078,8 @@ subroutine crest_search_multimd2(env,mols,mddats,nsim) pr = .false. call profiler%init(nsim) +!>--- pre-start server-based calculators before forking OMP threads + call preinit_mlip_parallel(calculations,T) !>--- run the MDs !$omp parallel & !$omp shared(env,calculations,mddats,mols,pr,percent,ich, moltmps,profiler, nested,Tn) @@ -944,7 +1134,7 @@ subroutine collect(n,mddats) integer :: i,io,ich,ich2 character(len=:),allocatable :: atmp character(len=256) :: btmp - open (newunit=ich,file='crest_dynamics.trj') + open (newunit=ich,file='crest_dynamics.trj.xyz') do i = 1,n atmp = mddats(i)%trajectoryfile inquire (file=atmp,exist=ex) @@ -978,14 +1168,15 @@ subroutine parallel_md_block_printout(MD,vz) use strucrd use dynamics_module use shake_module - use iomod,only:to_str + use iomod,only:to_str,drawbox implicit none type(mddata),intent(in) :: MD integer,intent(in) :: vz - character(len=40) :: atmp - integer :: il + character(len=60) :: atmp + integer,parameter :: bw = 54 !$omp critical + ! ── title ──────────────────────────────────────────────────── if (MD%simtype == type_md) then write (atmp,'(a,1x,i3)') 'starting MD',vz else if (MD%simtype == type_mtd) then @@ -995,37 +1186,43 @@ subroutine parallel_md_block_printout(MD,vz) write (atmp,'(a,1x,i4)') 'starting MTD',vz end if end if - il = (44-len_trim(atmp))/2 - write (stdout,'(2x,a,1x,a,1x,a)') repeat(':',il),trim(atmp),repeat(':',il) - - write (stdout,'(2x,"| MD simulation time :",f8.1," ps |")') MD%length_ps - write (stdout,'(2x,"| target T :",f8.1," K |")') MD%tsoll - write (stdout,'(2x,"| timestep dt :",f8.1," fs |")') MD%tstep - write (stdout,'(2x,"| dump interval(trj) :",f8.1," fs |")') MD%dumpstep + call drawbox(stdout,trim(atmp),width=bw,charset=4,ltab=1,procedual=0) + call drawbox(stdout,trim(atmp),width=bw,charset=4,ltab=1,procedual=1) + call drawbox(stdout,trim(atmp),width=bw,charset=4,ltab=1,procedual=3) + + ! ── simulation parameters ──────────────────────────────────── + write (stdout,'(1x,"│ MD simulation time :",f8.1," ps",16x,"│")') MD%length_ps + write (stdout,'(1x,"│ target T :",f8.1," K",17x,"│")') MD%tsoll + write (stdout,'(1x,"│ timestep dt :",f8.1," fs",16x,"│")') MD%tstep + write (stdout,'(1x,"│ dump interval(trj) :",f8.1," fs",16x,"│")') MD%dumpstep if (MD%shake.and.MD%shk%shake_mode > 0) then if (MD%shk%shake_mode == 2) then - write (stdout,'(2x,"| SHAKE algorithm :",a5," (all bonds) |")') to_str(MD%shake) + write (stdout,'(1x,"│ SHAKE algorithm :",a5," (all bonds)",10x,"│")') to_str(MD%shake) else - write (stdout,'(2x,"| SHAKE algorithm :",a5," (H only) |")') to_str(MD%shake) + write (stdout,'(1x,"│ SHAKE algorithm :",a5," (H only)",13x,"│")') to_str(MD%shake) end if end if if (allocated(MD%active_potentials)) then - write (stdout,'(2x,"| active potentials :",i4," potential |")') size(MD%active_potentials,1) + write (stdout,'(1x,"│ active potentials :",i4," potential(s)",10x,"│")') size(MD%active_potentials,1) end if if (MD%simtype == type_mtd) then if (MD%cvtype(1) == cv_rmsd) then - write (stdout,'(2x,"| dump interval(Vbias) :",f8.2," ps |")') & + write (stdout,'(1x,"│ dump interval(Vbias) :",f8.2," ps",16x,"│")') & & MD%mtd(1)%cvdump_fs/1000.0_wp end if - write (stdout,'(2x,"| Vbias prefactor (k) :",f8.4," Eh |")') & - & MD%mtd(1)%kpush + write (stdout,'(1x,"│ Vbias prefactor (k) :",f8.4," Eh",16x,"│")') MD%mtd(1)%kpush if (MD%cvtype(1) == cv_rmsd.or.MD%cvtype(1) == cv_rmsd_static) then - write (stdout,'(2x,"| Vbias exponent (α) :",f8.4," bohr⁻² |")') MD%mtd(1)%alpha + write (stdout,'(1x,"│ Vbias exponent (α) :",f8.4," bohr⁻²",12x,"│")') MD%mtd(1)%alpha else - write (stdout,'(2x,"| Vbias exponent (α) :",f8.4," |")') MD%mtd(1)%alpha + write (stdout,'(1x,"│ Vbias exponent (α) :",f8.4,19x,"│")') MD%mtd(1)%alpha + end if + if (allocated(MD%mtd(1)%atinclude)) then + write (stdout,'(1x,"│ # active atoms :",i9," atoms",12x,"│")') count(MD%mtd(1)%atinclude,1) end if end if + call drawbox(stdout,'',width=bw,charset=4,ltab=1,procedual=2) + !$omp end critical end subroutine parallel_md_block_printout diff --git a/src/algos/playground.f90 b/src/algos/playground.f90 index bffbf199..82abf58d 100644 --- a/src/algos/playground.f90 +++ b/src/algos/playground.f90 @@ -30,61 +30,68 @@ subroutine crest_playground(env,tim) use crest_parameters use crest_data use crest_calculator - use strucrd - use canonical_mod + use strucrd implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim type(coord) :: mol,molnew - integer :: i,j,k,l,io,ich + integer :: i,j,k,l,io,ich logical :: pr,wr !========================================================================================! type(calcdata) :: calc real(wp) :: accuracy,etemp - + integer :: V,maxgen integer,allocatable :: A(:,:) logical,allocatable :: rings(:,:) integer,allocatable :: tmp(:) - logical :: connected,fail + logical :: connected,fail,doreturn real(wp) :: energy - real(wp),allocatable :: grad(:,:),geo(:,:),csv(:,:) + real(wp),allocatable :: grad(:,:),geo(:,:),csv(:,:),q(:) - type(canonical_sorter) :: can !========================================================================================! - call tim%start(14,'Test implementation') + call tim%start(14,'Test implementation') !========================================================================================! !call system('figlet welcome') - write(*,*) " _ " - write(*,*) "__ _____| | ___ ___ _ __ ___ ___ " - write(*,*) "\ \ /\ / / _ \ |/ __/ _ \| '_ ` _ \ / _ \" - write(*,*) " \ V V / __/ | (_| (_) | | | | | | __/" - write(*,*) " \_/\_/ \___|_|\___\___/|_| |_| |_|\___|" - write(*,*) + write (*,*) " _ " + write (*,*) "__ _____| | ___ ___ _ __ ___ ___ " + write (*,*) "\ \ /\ / / _ \ |/ __/ _ \| '_ ` _ \ / _ \" + write (*,*) " \ V V / __/ | (_| (_) | | | | | | __/" + write (*,*) " \_/\_/ \___|_|\___\___/|_| |_| |_|\___|" + write (*,*) !========================================================================================! call env%ref%to(mol) - write(*,*) - write(*,*) 'Input structure:' + write (*,*) + write (*,*) 'Input structure:' call mol%append(stdout) - write(*,*) + write (*,*) +!!========================================================================================! +! +! allocate (grad(3,mol%nat),source=0.0_wp) +! call env2calc(env,calc,mol) +! calc%calcs(1)%rdwbo = .true. +! call calc%info(stdout) +! +! call engrad(mol,calc,energy,grad,io) +! call calculation_summary(calc,mol,energy,grad) !========================================================================================! - allocate(grad(3,mol%nat), source=0.0_wp) - call env2calc(env,calc,mol) - calc%calcs(1)%rdwbo=.true. - call calc%info(stdout) + allocate(mol%gradient(3,mol%nat), source=1.0_wp) + call mol%write('dummy.extxyz') + - call engrad(mol,calc,energy,grad,io) - call calculation_summary(calc,mol,energy,grad) - + call molnew%open("dummy.extxyz") + call molnew%write("dummy2.extxyz") - write(stdout,*) - write(stdout,*) 'CANGEN algorithm' - call can%init(mol,calc%calcs(1)%wbo,'apsp+') - call can%stereo(mol) - call can%rankprint(mol) + block + type(coord),allocatable :: structures(:) + integer :: nall + call rdensemble(env%inputcoords,nall,structures) + write(*,*) nall,'structures read from ',env%inputcoords + call wrensemble('dummyensemble.xyz',nall,structures) + end block !========================================================================================! call tim%stop(14) return diff --git a/src/algos/propcalc.f90 b/src/algos/propcalc.f90 new file mode 100644 index 00000000..0f33eb5f --- /dev/null +++ b/src/algos/propcalc.f90 @@ -0,0 +1,314 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2018-2020 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +!> Skeleton stub for property calculations on an ensemble. +!> Each mode needs to be implemented via modern algo routines. +!> The legacy implementation (system-call/I/O based) has been removed. +!> See git history for reference. + +subroutine propcalc(iname,imode,env,tim) + use crest_parameters + use crest_data + use cregen_interface + implicit none + character(len=*),intent(in) :: iname + integer,intent(in) :: imode + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + + select case (imode) + case (p_prop_hess) + !> TODO: Hessian calculations for all conformers (was: xtb --hess) + case (p_prop_autoir) + !> TODO: IR spectrum averaging over populated conformers (was: autoir + xtb --ohess) + case (p_prop_ohess) + !> TODO: Optimization + Hessian for all conformers (was: xtb --ohess) + case (p_prop_gsolv) + !> TODO: Free energy in solvation, 2-step (was: xtb --sp + xtb --ohess) + case (p_prop_reopt) + !> TODO: Vtight reoptimization for all conformers (was: xtb --opt vtight) + case (p_prop_finalhess) + call crest_finalhess(iname,env,tim) + + case (p_prop_multilevel:p_prop_multilevel+9) + !> Post-search processing of the conformer ensemble at a higher level. + !> Dispatched by job number; input is typically crest_rotamers.xyz. + block + integer :: saved_stage + saved_stage = env%calc%refine_stage + select case (imode) + case (p_prop_multilevel+1) !> A@B post-search geo-opt (existing) + env%calc%refine_stage = refine%post_opt + call crest_multilevel_reopt(iname,env,tim) + case (p_prop_multilevel+2) !> --rerank post-search SP re-ranking + call crest_rerank_sp(iname,env,tim) + case (p_prop_multilevel+3) !> --reopt post-search geo-opt (standalone) + env%calc%refine_stage = refine%post_reopt + call crest_multilevel_reopt(iname,env,tim) + case default + write (stdout,'(a,i0,a)') 'propcalc: multilevel mode ',imode,' not implemented' + end select + env%calc%refine_stage = saved_stage + end block + case (p_prop_dipole) + !> TODO: Singlepoint + dipole extraction (was: xtb --sp, grep molecular dipole) + case (p_prop_rerank) + !> TODO: Singlepoint + reranking (was: xtb --sp + newcregen) + case default + write (stdout,'(a,i0,a)') 'propcalc: mode ',imode,' not yet implemented' + end select + +end subroutine propcalc + +!========================================================================================! + +subroutine crest_multilevel_reopt(iname,env,tim) +!******************************************************************* +!* Read the ensemble iname, optimize all structures using the +!* calculator whose refine_lvl matches env%calc%refine_stage, +!* sort via CREGEN, and write crest_reopt.xyz. +!* +!* The caller is responsible for setting env%calc%refine_stage +!* to the desired level before this routine is called. +!* +!* Input: +!* iname - path to input ensemble (e.g. crest_rotamers.xyz) +!* Output: +!* crest_reopt.xyz (sorted conformer ensemble at the higher level) +!******************************************************************* + use crest_parameters,only:wp,stdout,bohr + use crest_data + use crest_calculator + use strucrd + use parallel_interface + use cregen_interface + use iomod,only:drawbox,catdel + implicit none + character(len=*),intent(in) :: iname + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + integer :: nat,nall,T,Tn + real(wp),allocatable :: xyz(:,:,:),eread(:) + integer,allocatable :: at(:) + character(len=*),parameter :: outname = 'crest_reopt.xyz' + logical :: ex + + inquire(file=iname,exist=ex) + if (.not.ex) then + write(stdout,'(a,a,a)') '**WARNING** ',trim(iname),' not found, skipping multilevel reopt' + return + end if + + call tim%start(16,'Multilevel reopt') + + call rdensembleparam(iname,nat,nall) + if (nall < 1) then + write(stdout,*) '**WARNING** empty ensemble, skipping multilevel reopt' + call tim%stop(16) + return + end if + allocate(xyz(3,nat,nall),at(nat),eread(nall)) + call rdensemble(iname,nat,nall,at,xyz,eread) +! ── crest_oloop requires Bohr ──────────────────────────────────── + xyz = xyz/bohr + + call new_ompautoset(env,'auto',nall,T,Tn) + + write(stdout,*) + call drawbox(stdout,'MULTILEVEL ENSEMBLE REOPT',charset=7,width=51,ltab=10) + write(stdout,'(1x,a,i0,a,1x,a)') & + & 'Re-optimizing ',nall,' structures of file ',trim(iname) + +! ── refine_stage is set by the caller; run geo-opt ─────────────── + call crest_oloop(env,nat,nall,at,xyz,eread,.true.) + +! ── back to Angstrom, write output ─────────────────────────────── + xyz = xyz*bohr + call wrensemble(outname,nat,nall,at,xyz,eread) + + write(stdout,'(/,a,a,a)') 'Re-optimized ensemble written to <',outname,'>' + +! ── sort via CREGEN ────────────────────────────────────────────── + call newcregen(env,0,outname) + call catdel('cregen.out.tmp') + + deallocate(xyz,at,eread) + call tim%stop(16) +end subroutine crest_multilevel_reopt + +!========================================================================================! + +subroutine crest_rerank_sp(iname,env,tim) +!******************************************************************* +!* Read the ensemble iname, run single-point energies using the +!* calculator tagged with refine_lvl == refine%post_sp (= 11, +!* set by the --rerank keyword), re-sort via CREGEN, and write +!* crest_reopt.xyz. Geometries are not changed. +!* +!* Input: +!* iname - path to input ensemble (e.g. crest_rotamers.xyz) +!* Output: +!* crest_reopt.xyz (ensemble re-ranked by higher-level SP energies) +!******************************************************************* + use crest_parameters,only:wp,stdout,bohr + use crest_data + use crest_calculator + use strucrd + use parallel_interface + use cregen_interface + use iomod,only:drawbox,catdel + implicit none + character(len=*),intent(in) :: iname + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + integer :: nat,nall,T,Tn,old_stage + real(wp),allocatable :: xyz(:,:,:),eread(:) + integer,allocatable :: at(:) + character(len=*),parameter :: outname = 'crest_reopt.xyz' + logical :: ex + + inquire(file=iname,exist=ex) + if (.not.ex) then + write(stdout,'(a,a,a)') '**WARNING** ',trim(iname),' not found, skipping SP rerank' + return + end if + + call tim%start(16,'Post-search SP rerank') + + call rdensembleparam(iname,nat,nall) + if (nall < 1) then + write(stdout,*) '**WARNING** empty ensemble, skipping SP rerank' + call tim%stop(16) + return + end if + allocate(xyz(3,nat,nall),at(nat),eread(nall)) + call rdensemble(iname,nat,nall,at,xyz,eread) +! ── crest_sploop requires coordinates in Bohr ──────────────────── + xyz = xyz/bohr + + call new_ompautoset(env,'auto',nall,T,Tn) + + write(stdout,*) + call drawbox(stdout,'POST-SEARCH SP RE-RANKING',charset=7,width=51,ltab=10) + write(stdout,'(1x,a,i0,a,1x,a)') & + & 'Re-ranking ',nall,' structures of file ',trim(iname) + +! ── activate only the SP reranking calculator ──────────────────── + old_stage = env%calc%refine_stage + env%calc%refine_stage = refine%post_sp + + call crest_sploop(env,nat,nall,at,xyz,eread) + + env%calc%refine_stage = old_stage + +! ── back to Angstrom, write output ─────────────────────────────── + xyz = xyz*bohr + call wrensemble(outname,nat,nall,at,xyz,eread) + + write(stdout,'(/,a,a,a)') 'Re-ranked ensemble written to <',outname,'>' + +! ── sort via CREGEN ────────────────────────────────────────────── + call newcregen(env,0,outname) + call catdel('cregen.out.tmp') + + deallocate(xyz,at,eread) + call tim%stop(16) +end subroutine crest_rerank_sp + +!========================================================================================! + +subroutine crest_finalhess(iname,env,tim) +!******************************************************************* +!* Run Hessians + thermochemistry on the final conformer ensemble, +!* then re-sort via CREGEN using Gibbs free energies. +!* Falls back from crest_conformers.xyz to crest_ensemble.xyz. +!* Uses the main calculator as-is (no separate refine level). +!* Input: +!* iname - primary input file (crest_conformers.xyz) +!* Output: +!* crest_hess.xyz (intermediate), then CREGEN overwrites +!* crest_conformers.xyz / crest_ensemble.xyz sorted by Gfree +!******************************************************************* + use crest_parameters,only:wp,stdout,bohr + use crest_data + use crest_calculator + use strucrd + use parallel_interface + use cregen_interface + use iomod,only:drawbox,catdel + implicit none + character(len=*),intent(in) :: iname + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + character(len=:),allocatable :: infile + integer :: nat,nall,T,Tn + real(wp),allocatable :: xyz(:,:,:),eread(:),etmp(:) + integer,allocatable :: at(:) + character(len=*),parameter :: outname = 'crest_hess.xyz' + logical :: ex + +! ── select input file with fallback to crest_ensemble.xyz ──────── + inquire(file=iname,exist=ex) + if (ex) then + infile = iname + else + inquire(file=ensemblefile,exist=ex) + if (ex) then + infile = ensemblefile + else + write(stdout,'(a)') '**WARNING** no conformer ensemble found, skipping --finalhess' + return + end if + end if + + call tim%start(16,'Final ensemble Hessians') + + call rdensembleparam(infile,nat,nall) + if (nall < 1) then + write(stdout,*) '**WARNING** empty ensemble, skipping --finalhess' + call tim%stop(16) + return + end if + allocate(xyz(3,nat,nall),at(nat),eread(nall),etmp(nall)) + call rdensemble(infile,nat,nall,at,xyz,eread) +! ── crest_hessloop requires coordinates in Bohr ────────────────── + xyz = xyz/bohr + + call new_ompautoset(env,'auto',nall,T,Tn) + + write(stdout,*) + call drawbox(stdout,'FINAL ENSEMBLE HESSIANS',charset=7,width=51,ltab=10) + write(stdout,'(1x,a,i0,a,1x,a)') & + & 'Computing Hessians for ',nall,' structures of file ',trim(infile) + + call crest_hessloop(env,nat,nall,at,xyz,etmp) + eread(:) = eread(:) + etmp(:) + +! ── back to Angstrom, write intermediate file ───────────────────── + xyz = xyz*bohr + call wrensemble(outname,nat,nall,at,xyz,eread) + write(stdout,'(/,a,a,a)') 'Hessian ensemble written to <',outname,'>' + +! ── sort via CREGEN using Gibbs free energies ──────────────────── + call newcregen(env,0,outname) + call catdel('cregen.out.tmp') + + deallocate(xyz,at,eread) + call tim%stop(16) +end subroutine crest_finalhess diff --git a/src/algos/protonate.f90 b/src/algos/protonate.f90 index a058bddf..67a4c979 100644 --- a/src/algos/protonate.f90 +++ b/src/algos/protonate.f90 @@ -54,7 +54,6 @@ subroutine crest_new_protonate(env,tim) real(wp),allocatable :: grad(:,:) type(calcdata),allocatable :: tmpcalc type(calcdata),allocatable :: tmpcalc_ff - type(calculation_settings) :: tmpset real(wp),allocatable :: protxyz(:,:) integer :: natp,pstep,npnew integer,allocatable :: atp(:) @@ -200,18 +199,15 @@ subroutine crest_new_protonate(env,tim) write (stdout,'(a)') ' with defined bond-topology can help circumvent this issue.' write (stdout,'(a)') '> Setting up force-field structure pre-optimization ...' allocate (tmpcalc_ff) - tmpcalc_ff%optnewinit = .true. env%calc%optnewinit = .true. + call tmpcalc_ff%create('gfnff',chrg=env%chrg) tmpcalc_ff%optnewinit = .true. - call tmpset%create('gfnff') - tmpset%chrg = env%chrg - call tmpcalc_ff%add(tmpset) tmpcalc_ff%maxcycle = 10000 call tmpcalc_ff%info(stdout) !>--- Run optimizations call tim%start(15,'Ensemble optimization (FF)') - call print_opt_data(tmpcalc_ff,stdout) + call print_opt_data(tmpcalc_ff,stdout,natoms=natp) write (stdout,'(a,i0,a)') '> ',npnew,' structures to optimize ...' call crest_oloop(env,natp,npnew,atp,xyzp(:,:,1:npnew),ep(1:npnew),.false.,tmpcalc_ff) call tim%stop(15) @@ -301,7 +297,7 @@ subroutine crest_new_protonate(env,tim) call tmpcalc%info(stdout) tmpcalc%optnewinit = .true. call tim%start(20,'Ensemble optimization') - call print_opt_data(env%calc,stdout) + call print_opt_data(env%calc,stdout,natoms=natp) write (stdout,'(a,i0,a)') '> ',npnew,' structures to optimize ...' call crest_oloop(env,natp,npnew,atp,xyzp(:,:,1:npnew),ep,.false.,tmpcalc) call tim%stop(20) @@ -444,6 +440,7 @@ subroutine protonation_prep_canonical(env,refmol,fname) use iomod,only:remove use adjacency use cregen_interface + use term_ui,only:progress_init,progress_update,progress_finish implicit none type(systemdata) :: env type(coord),intent(in) :: refmol @@ -482,7 +479,9 @@ subroutine protonation_prep_canonical(env,refmol,fname) allocate (canon(nall)) write (stdout,'(a,i0,a)') '> Setting up canonical atom order for ',nall,' structures via CN-based molecular graphs ...' - call crest_oloop_pr_progress(env,nall,0) + call progress_init(env%ps,nall,width=50,prefix=" ↳ ", & + & suffix="",show_time=.true.,show_eta=.false.) + call progress_update(env%ps,0,nall) do i = 1,nall call canon(i)%init(structures(i),invtype='apsp+') call canon(i)%stereo(structures(i)) @@ -490,9 +489,9 @@ subroutine protonation_prep_canonical(env,refmol,fname) !call canon(i)%rankprint(structures(i)) call canon(i)%shrink() - call crest_oloop_pr_progress(env,nall,i) + call progress_update(env%ps,i,nall) end do - call crest_oloop_pr_progress(env,nall,-1) + call progress_finish(env%ps) !>--- grouping loop allocate (group(nall),source=0) @@ -588,7 +587,6 @@ subroutine crest_new_deprotonate(env,tim) real(wp),allocatable :: grad(:,:) type(calcdata),allocatable :: tmpcalc type(calcdata),allocatable :: tmpcalc_ff - type(calculation_settings) :: tmpset integer :: natp,pstep,npnew integer,allocatable :: atp(:) real(wp),allocatable :: xyzp(:,:,:) @@ -684,18 +682,15 @@ subroutine crest_new_deprotonate(env,tim) write (stdout,'(a)') ' with defined bond-topology can help circumvent this issue.' write (stdout,'(a)') '> Setting up force-field structure pre-optimization ...' allocate (tmpcalc_ff) - tmpcalc_ff%optnewinit = .true. env%calc%optnewinit = .true. + call tmpcalc_ff%create('gfnff',chrg=env%chrg) tmpcalc_ff%optnewinit = .true. - call tmpset%create('gfnff') - tmpset%chrg = env%chrg - call tmpcalc_ff%add(tmpset) tmpcalc_ff%maxcycle = 10000 call tmpcalc_ff%info(stdout) !>--- Run optimizations call tim%start(15,'Ensemble optimization (FF)') - call print_opt_data(tmpcalc_ff,stdout) + call print_opt_data(tmpcalc_ff,stdout,natoms=natp) write (stdout,'(a,i0,a)') '> ',npnew,' structures to optimize ...' call crest_oloop(env,natp,npnew,atp,xyzp(:,:,1:npnew),ep(1:npnew),.false.,tmpcalc_ff) call tim%stop(15) @@ -784,7 +779,7 @@ subroutine crest_new_deprotonate(env,tim) call tmpcalc%info(stdout) tmpcalc%optnewinit = .true. call tim%start(20,'Ensemble optimization') - call print_opt_data(env%calc,stdout) + call print_opt_data(env%calc,stdout,natoms=natp) write (stdout,'(a,i0,a)') '> ',npnew,' structures to optimize ...' call crest_oloop(env,natp,npnew,atp,xyzp(:,:,1:npnew),ep(1:npnew),.false.,tmpcalc) call tim%stop(20) @@ -996,7 +991,6 @@ subroutine crest_new_tautomerize(env,tim) real(wp),allocatable :: grad(:,:) type(calcdata),allocatable :: tmpcalc type(calcdata),allocatable :: tmpcalc_ff - type(calculation_settings) :: tmpset integer :: natp,pstep,npnew integer :: tautiter,structiter integer,allocatable :: atp(:) @@ -1166,19 +1160,16 @@ subroutine crest_new_tautomerize(env,tim) write (stdout,'(a)') ' with defined bond-topology can help circumvent this issue.' write (stdout,'(a)') '> Setting up force-field structure pre-optimization ...' allocate (tmpcalc_ff) - tmpcalc_ff%optnewinit = .true. env%calc%optnewinit = .true. + call tmpcalc_ff%create('gfnff',chrg=env%chrg) tmpcalc_ff%optnewinit = .true. - call tmpset%create('gfnff') - tmpset%chrg = env%chrg - call tmpcalc_ff%add(tmpset) tmpcalc_ff%maxcycle = 10000 tmpcalc_ff%anopt=.true. call tmpcalc_ff%info(stdout) !>--- Run optimizations call tim%start(15,'Ensemble optimization (FF)') - call print_opt_data(tmpcalc_ff,stdout) + call print_opt_data(tmpcalc_ff,stdout,natoms=natp) write (stdout,'(a,i0,a)') '> ',npnew,' structures to optimize ...' call crest_oloop(env,natp,npnew,atp,xyzp(:,:,1:npnew),ep(1:npnew),.false.,tmpcalc_ff) call tim%stop(15) @@ -1271,7 +1262,7 @@ subroutine crest_new_tautomerize(env,tim) call tmpcalc%info(stdout) tmpcalc%optnewinit = .true. call tim%start(20,'Ensemble optimization') - call print_opt_data(env%calc,stdout) + call print_opt_data(env%calc,stdout,natoms=natp) write (stdout,'(a,i0,a)') '> ',npnew,' structures to optimize ...' call crest_oloop(env,natp,npnew,atp,xyzp(:,:,1:npnew),ep,.false.,tmpcalc) call tim%stop(20) diff --git a/src/algos/queueing.f90 b/src/algos/queueing.f90 new file mode 100644 index 00000000..04492afb --- /dev/null +++ b/src/algos/queueing.f90 @@ -0,0 +1,831 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +subroutine crest_queue_setup(env,iterate) + use crest_parameters + use crest_data + use crest_calculator + use strucrd + use molbuilder_construct_list + use molbuilder_construct_mod + implicit none + type(systemdata),intent(inout) :: env + logical,intent(out) :: iterate + + integer :: splitlayers + integer :: ii,jj,nn,kk,ich + type(coord),pointer :: reference_mol + type(coord),target :: mol + integer,allocatable :: splitatms(:) + integer :: parentlayer,parentnode + character(len=1024) :: thispath + real(wp),allocatable :: qat(:) + integer,allocatable :: lq(:) + + iterate = .true. + + if (allocated(env%splitqueue)) then + + !> check for incompatible runtypes (or rather, whitelist a few) + if (.not.any(env%crestver == [crest_imtd,crest_imtd2,crest_sp, & + & crest_optimize,crest_moldyn,crest_rigcon,crest_trialopt,crest_bh,crest_test])) then + write (stdout,'(a)') '** ERROR ** Selected CREST runtype incompatible with substructure builder' + call creststop(status_config) + end if + if (allocated(env%ONIOM_input).or.allocated(env%ONIOM_toml)) then + write (stdout,'(a)') '** ERROR ** ONIOM incompatible with substructure builder' + call creststop(status_config) + end if + + !> if the program sees no problem, set the global boolean + env%substructure_queue = .true. + splitlayers = size(env%splitqueue,1) + + !> we may need to calculate charges to distribute them: + if (env%chrg .ne. 0) then + call calc_charges(env,qat) + else + allocate (qat(env%ref%nat),source=0.0_wp) + end if + + !> start constructing the splitheap + env%splitheap%nlayer = splitlayers + allocate (env%splitheap%layer(splitlayers)) + associate (heap => env%splitheap,layer => env%splitheap%layer) + + do ii = 1,heap%nlayer + layer%id = ii + + nn = env%splitqueue(ii)%natms + allocate (splitatms(nn)) + splitatms(:) = env%splitqueue(ii)%atms(:) + if (ii == 1) then + call env%ref%to(mol) + reference_mol => mol + else + + call pick_parent(heap,ii,splitatms,parentlayer,parentnode) + if (parentlayer == 0) then + call env%ref%to(mol) + reference_mol => mol + else + mol = heap%layer(parentlayer)%node(parentnode) + reference_mol => mol + heap%layer(ii)%parent = parentlayer + heap%layer(ii)%parentnode = parentnode + end if + end if + layer(ii)%refmol = reference_mol + call reference_mol%get_cn(layer(ii)%refcn) + allocate (layer(ii)%reficn(reference_mol%nat)) + layer(ii)%reficn(:) = nint(layer(ii)%refcn(:)) + call binarysplit(reference_mol,splitatms,layer(ii)%node,layer(ii)%alignmap, & + & ncap=layer(ii)%ncapped,position_mapping=layer(ii)%position_mapping) + deallocate (splitatms) + layer(ii)%nnodes = size(layer(ii)%node,1) + call heap%map_origins_for_layer(ii) + !> determening charges for fragments + call sum_charges_layer(env,heap,ii,qat,lq) + do jj = 1,layer(ii)%nnodes + layer(ii)%node(jj)%chrg = lq(jj) + end do + end do + + call heap%setup_queue() + call getcwd(thispath) + !> some backups + call env%ref%to(heap%originmol) + heap%originmol%chrg = env%chrg + heap%origindir = trim(thispath) + heap%origincalc => env%calc + + end associate + iterate = .true. + end if + + return +contains + subroutine pick_parent(heap,current_layer,splitatms,parentlayer,parentnode) + use molbuilder_construct_list + implicit none + type(construct_heap),intent(inout) :: heap + integer,intent(inout) :: splitatms(:) + integer,intent(in) :: current_layer + integer,intent(out) :: parentlayer,parentnode + integer :: ii,jj,kk,prev_layer + logical :: matching + + parentlayer = 0 + parentnode = 0 + if (current_layer .eq. 1) return + + !> iterate through the previous layer and check which node + !> contains all the split atoms + prev_layer = current_layer-1 + LAYITER: do while (prev_layer >= 1) + do ii = 1,heap%layer(prev_layer)%nnodes + matching = .true. + do jj = 1,size(splitatms,1) + matching = matching.and. & + & any(heap%layer(prev_layer)%origin(ii)%map(:) .eq. splitatms(jj)) + end do + if (matching) then + parentlayer = prev_layer + parentnode = ii + !> on the first match, exit + exit LAYITER + end if + end do + !> if no matching parent node was found, try again in one layer further up + if (parentnode == 0) prev_layer = prev_layer-1 + end do LAYITER + + !> IMPORTANT; we need to update the splitatms with the correctly mapped indices + !> reflecting their position in the selected parent layer + if (parentnode .ne. 0) then + do ii = 1,size(splitatms,1) + jj = splitatms(ii) + call heap%find_current_position(jj,parentlayer,parentnode,kk) + splitatms(ii) = kk + end do + !> we also map the current node as a child node of the selected parent + if (.not.allocated(heap%layer(parentlayer)%childlayer)) then + ii = heap%layer(parentlayer)%nnodes + allocate (heap%layer(parentlayer)%childlayer(ii),source=0) + end if + heap%layer(parentlayer)%childlayer(parentnode) = current_layer + end if + + end subroutine pick_parent + subroutine calc_charges(env,qat) + use tblite_api,only:tblite_quick_ceh_q + implicit none + type(systemdata),intent(inout) :: env + real(wp),intent(out),allocatable :: qat(:) + real(wp),allocatable :: qat0(:) + character(len=256) :: atmp + type(coord) :: mol + integer :: ii + write (atmp,'(a)') 'Calculating atomic charges under consideration of molecular charge' + call underline(trim(atmp)) + write (stdout,'(a,i0)') 'Molecular charge : ',env%chrg + call env%ref%to(mol) + call tblite_quick_ceh_q(mol,qat, & + & chrg=env%chrg,uhf=env%uhf,pr=.true.,prch=stdout) + write (stdout,'(a)') 'Obtained CEH charges for full structure:' + do ii = 1,mol%nat + write (stdout,'(3x,a3,2x,f10.6)') i2e(mol%at(ii)),qat(ii)!,qat0(ii),qat(ii)-qat0(ii) + end do + + write (stdout,'(/,a)') 'NOTE: Total charge for each fragment will be selected automatically by' + write (stdout,'(a)') ' matching the best atomic charge MAE to these charges.' + end subroutine calc_charges + subroutine sum_charges_layer(env,heap,lay,qat,lq) + use tblite_api,only:tblite_quick_ceh_q + implicit none + type(systemdata) :: env + type(construct_heap) :: heap + integer,intent(in) :: lay + real(wp),intent(in) :: qat(:) + integer,intent(out),allocatable :: lq(:) + integer :: ii,jj,nat,nnodes,kk,nnat,sign,cc,cc2,chrgs + integer,allocatable :: ichrgs(:) + real(wp) :: qtmp0,qtmpc + real(wp),allocatable :: qtmp(:) + real(wp),allocatable :: qattmp0(:),qattmpc(:),qattmpref(:),qdum(:) + real(wp),allocatable :: qattmp(:,:) + + nat = size(qat,1) + nnodes = heap%layer(lay)%nnodes + allocate (lq(nnodes),source=0) !> default chrg of 0 + + if (env%chrg == 0) return !> return for neutral systems (may need some implementation for zwitter ions) + + write (stdout,'(a,i0,a)') 'Calculating charges for fragments in layer ',lay,' ...' + sign = 1 + if (env%chrg < 0) sign = -1 + chrgs = abs(env%chrg)+1 + allocate (qtmp(chrgs),source=0.0_wp) + allocate (ichrgs(chrgs),source=0) + cc2 = 0 + do cc = 0,env%chrg,sign + cc2 = cc2+1 + ichrgs(cc2) = cc + end do + + do ii = 1,nnodes + qtmp(:) = 0.0_wp + qtmp0 = 0.0_wp + qtmpc = 0.0_wp + nnat = heap%layer(lay)%node(ii)%nat + allocate (qattmpref(nnat),source=0.0_wp) + allocate (qattmp(nnat,chrgs)) + !> check different charge settings + cc2 = 0 + do cc = 0,env%chrg,sign + cc2 = cc2+1 + call tblite_quick_ceh_q(heap%layer(lay)%node(ii),qdum, & + & chrg=cc,uhf=env%uhf,pr=.false.,prch=stdout) + qattmp(:,cc2) = qdum(:) + do jj = 1,nnat + kk = heap%layer(lay)%origin(ii)%map(jj) + if (kk > 0) then + qattmpref(jj) = qat(kk) + else + qattmp(jj,cc2) = 0.0_wp + end if + qtmp(cc2) = qtmp(cc2)+abs(qattmp(jj,cc2)-qattmpref(jj)) + end do + end do + !> select best charge + cc = minloc(qtmp,1) + lq(ii) = ichrgs(cc) + deallocate (qattmp,qattmpref) + !write (*,*) 'charge MAEs on frag:',qtmp + !write (*,*) 'selected charge:',lq(ii) + end do + write (stdout,'(2x,a)',advance='no') 'determined charges:' + write (stdout,*) lq + end subroutine sum_charges_layer +end subroutine crest_queue_setup + +!=============================================================================! +!#############################################################################! +!=============================================================================! + +subroutine crest_queue_iter(env,iterate) + use crest_parameters + use crest_data + use strucrd + use iomod + use crest_calculator + implicit none + type(systemdata),intent(inout),target :: env + logical,intent(out) :: iterate + integer :: ii,jj,kk,io,nn,ll,lll,ati,atj + type(coord) :: mol + character(len=10) :: atmp + character(len=*),parameter :: dirname = 'crest_queue_' + + iterate = .false. + + if (allocated(env%splitqueue).and.env%splitheap%nqueue > 0) then +!>--- important restoring to initial calc/dir + env%calc => env%splitheap%origincalc + call chdir(env%splitheap%origindir) + + !> next iter + ii = env%queue_iter+1 + env%queue_iter = ii + + write (stdout,'(/,70("§"))') + write (stdout,'(a,i0)') "§§§ QUEUE ITERATION ",ii + write (stdout,'(70("§"))') + + jj = env%splitheap%queue(ii)%layer + kk = env%splitheap%queue(ii)%node + associate (heap => env%splitheap,queue => env%splitheap%queue(ii)) + + !> create a dedicated work directory + write (atmp,'(i0)') ii + queue%workdir = dirname//trim(atmp) + io = makedir(queue%workdir) + call chdir(queue%workdir) + write (stdout,'(a,t28,a,t30,a)') 'Queue work (sub-)directory',':', & + & trim(queue%workdir) + + !> selecting output file depending on runtype + select case (env%crestver) + case (crest_imtd,crest_imtd2) + queue%file = 'crest_ensemble.xyz' + case (crest_optimize) + queue%file = 'crestopt.xyz' + case (crest_moldyn) + queue%file = 'crest_dynamics.trj.xyz' + case (crest_bh) + queue%file = 'crest_quenched.xyz' + case default + queue%file = 'struc.xyz' + end select + write (stdout,'(a,t28,a,t30,a)') 'Selected output file',':',queue%file + +!>--- new calculator setup section and env update + call queue%calc%copy(env%calc,ignore_constraints=.true.) + !> for constraints we must be careful and map them to the new order + call update_constraints_queue(heap,jj,kk,env%calc,queue%calc) + + mol = env%splitheap%layer(jj)%node(kk) + call env%ref%load(mol) + call mol%write('coord') + call queue%calc%set_charge(mol%chrg) !> the nodes may have different charges saved + call queue%calc%info(stdout) + + if (allocated(env%ref%wbo)) deallocate (env%ref%wbo) + env%nat = mol%nat + env%rednat = mol%nat + env%chrg = mol%chrg + if (.not.env%user_mdtime) then + env%mdtime = -1.0_wp + env%mddat%length_ps = -1.0_wp + end if + + env%calc => queue%calc + + end associate + if (ii < env%splitheap%nqueue) then + iterate = .true. + end if + + write (stdout,*) + end if + +contains + subroutine update_constraints_queue(heap,layer,node,refcalc,newcalc) + use molbuilder_construct_list + implicit none + type(construct_heap) :: heap + integer :: layer,node + type(calcdata),intent(in) :: refcalc + type(calcdata),intent(inout) :: newcalc + integer :: nn,ll,lll,ati,atj,nn2 + type(constraint),allocatable :: cons(:) + if (refcalc%nconstraints > 0) then + nn = refcalc%nconstraints + allocate (cons(nn)) + do ll = 1,nn + call cons(ll)%copy(refcalc%cons(ll)) + do lll = 1,cons(ll)%n + ati = cons(ll)%atms(lll) + call heap%find_current_position(ati,layer,node,atj) + cons(ll)%atms(lll) = atj !> overwrite with the current position + end do + if (any(cons(ll)%atms(:) .eq. 0)) then + cons(ll)%active = .false. + end if + end do + !> clean (active) constraints + nn2 = 0 + do ll = 1,nn + if (cons(ll)%active) nn2 = nn2+1 + end do + if (nn2 > 0) then + newcalc%nconstraints = nn2 + allocate (newcalc%cons(nn2)) + lll = 0 + do ll = 1,nn + if (cons(ll)%active) then + lll = lll+1 + call newcalc%cons(lll)%copy(cons(ll)) + end if + end do + end if + end if + end subroutine update_constraints_queue +end subroutine crest_queue_iter + +subroutine crest_queue_iter_resort(env,iterate) + use crest_parameters + use crest_data + use iomod + use cregen_interface + implicit none + type(systemdata),intent(inout) :: env + logical,intent(in) :: iterate + + character(len=:),allocatable :: file + logical :: heavytmp,confgotmp,ex + + if (.not. (allocated(env%splitqueue).and.env%splitheap%nqueue > 0)) return + + select case (env%crestver) + case (crest_imtd,crest_imtd2) + + write (stdout,'(/,75("*"))') + write (stdout,'(a,i0)') "*** CREGEN heavy-atom resorting for QUEUE iteration ",env%queue_iter + write (stdout,'(75("*"))') + ex = .false. + if (file_exists(crefile//'.xyz')) then + ex = .true. + file = crefile//'.xyz' + else if (file_exists(conformerfile)) then + ex = .true. + file = conformerfile + end if + heavytmp = env%heavyrmsd + confgotmp = env%confgo + env%heavyrmsd = .true. + env%confgo = .true. + call newcregen(env,infile=file) + env%heavyrmsd = heavytmp + env%confgo = confgotmp + if (file_exists(file//'.sorted')) then + call rename(file//'.sorted',ensemblefile) + end if + case default + end select + +end subroutine crest_queue_iter_resort + +!=============================================================================! +!#############################################################################! +!=============================================================================! + +subroutine crest_queue_reconstruct(env,tim) + use crest_parameters + use crest_data + use molbuilder_construct_list + use molbuilder_construct_mod + use strucrd + use iomod + use crest_calculator + use utilities,only:checkname_xyz + use term_ui,only:progress_init,progress_update,progress_finish + implicit none + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + type(coord) :: mol + integer :: ii,jj,kk,nall + logical :: ex,multilevel(6) + type(timer) :: timtmp + type(coord),allocatable :: structures(:) + type(calcdata),target :: newcalc + character(len=256) :: inpnam,outnam + character(len=*),parameter :: recfile = 'crest_reconstruct.xyz' + + if (.not. (allocated(env%splitqueue).and.env%splitheap%nqueue > 0)) then + return + end if + + call tim%start(9,'Queue reconstruction') + + write (stdout,'(/,80("#"))') + write (stdout,'(3("#"),t25,a,t78,3("#"))') 'QUEUE STRUCTURE RECONSTRUCTION' + write (stdout,'(80("#"),/)') + + !> reset + mol = env%splitheap%originmol + call env%ref%load(mol) + env%nat = mol%nat + env%rednat = mol%nat + env%chrg = mol%chrg + env%calc => env%splitheap%origincalc + call chdir(env%splitheap%origindir) + + call env%splitheap%fill_inverse_depth() + call recusrive_construct(env,env%splitheap,1) + nall = env%splitheap%layer(1)%nmols + allocate (structures(nall)) + do ii = 1,nall + structures(ii) = env%splitheap%layer(1)%mols(ii) + end do + !deallocate (env%splitheap%layer(1)%mols) + deallocate (env%splitheap%layer) + deallocate (env%splitheap%queue) + + write (stdout,'(/,1x,a)') 'Writing reconstructed structures to: "'//recfile//'"' + call wrensemble(recfile,nall,structures) + write (stdout,*) + + call newcalc%copy(env%calc) + env%calc => newcalc + call env%calc%info(stdout) + + select case (env%crestver) + case (crest_optimize) + call env%ref%load(structures(1)) + call crest_optimization(env,timtmp) + case default + call optlev_to_multilev(env%optlev,multilevel) + call crest_multilevel_oloop(env,recfile,multilevel,0) + if (env%iostatus_meta .ne. 0) return + + call smallheadline('FINAL GEOMETRY OPTIMIZATION IN QUEUE RECONSTRUCTION') + call checkname_xyz(crefile,inpnam,outnam) + call rename(inpnam,recfile) + call crest_multilevel_wrap(env,recfile,0) + + call V2terminating() + end select + + if (.not.env%keepmodef) then + call rmrf('crest_queue_*') + end if + + call tim%stop(9) + +contains + recursive subroutine recusrive_construct(env,heap,targetlayer) + use irmsd_module,only:irmsd,rmsd,rmsd_cache,rmsd_core_cache,min_rmsd + use canonical_mod + use omp_lib + implicit none + type(systemdata),intent(inout) :: env + type(construct_heap),intent(inout) :: heap + integer,intent(in) :: targetlayer + + integer :: ii,jj,kk + integer :: queuepos + character(len=:),allocatable :: basefile,sidefile + type(coord),allocatable :: structures_b(:) + type(coord),allocatable :: structures_s(:) + type(coord) :: mol,moltmp + type(coord),allocatable :: moltmp_arr(:) + integer :: nall_b,nall_s,id_b,id_s + integer :: rr,io,rg,nregions,max_structs + integer :: reg_blo(3),reg_bhi(3),reg_slo(3),reg_shi(3) + integer :: target_bhi,target_shi + integer :: outer_lo,outer_hi,inner_lo,inner_hi,outer_idx,inner_idx + logical :: base_is_outer + integer :: duplicates + logical :: ex,clash,duplicate + real(wp) :: RTHR,rmsval,ETHR,deltaE,depthlimit + real(wp) :: layerfactor_b,layerfactor_s,weight_s,weight_b + type(rmsd_cache),allocatable :: rcache(:) + type(rmsd_core_cache),allocatable :: ccache(:) + type(canonical_sorter) :: canref + real(wp),allocatable :: xyzscratch(:,:,:,:) + logical,allocatable :: mask(:) + integer :: T,Tn,tt + type(timer) :: profiler + + character(len=*),parameter :: subdir_tmp = 'crest_queue_' + character(len=:),allocatable :: subdirfile + character(len=10) :: atmp + character(len=60) :: btmp + + associate (layer => heap%layer(targetlayer)) + if (layer%nnodes > 2) then + write (stdout,'(a)') 'currently unhandled edge-case in layer reconstruction:' + write (stdout,'(a,i0,a)') 'layer ',targetlayer,' was split in more than 2 structures' + stop + end if + + layer%inverse_depth = layer%inverse_depth+1.0_wp + do ii = 1,layer%nnodes + if (allocated(layer%childlayer)) then + jj = layer%childlayer(ii) + else + jj = 0 + end if + if (jj == 0.and.ii == 1) then + + do kk = 1,heap%nqueue + if (heap%queue(kk)%layer == targetlayer.and.heap%queue(kk)%node == ii) then + basefile = heap%queue(kk)%file + id_b = kk + end if + end do + + write (atmp,'(i0)') id_b + subdirfile = subdir_tmp//trim(atmp)//'/'//basefile + inquire (exist=ex,file=subdirfile) + if (ex) then + write (stdout,'(a,t26,a,t30,a)',advance='no') & + & 'Reading fragment(s) from',':',subdirfile + call rdensemble(subdirfile,nall_b,structures_b) + write (stdout,'(1x,a,i0,a)') '--> ',nall_b,' structure(s)' + + end if + layerfactor_b = 1.0_wp + + else if (jj == 0.and.ii == 2) then + + do kk = 1,heap%nqueue + if (heap%queue(kk)%layer == targetlayer.and.heap%queue(kk)%node == ii) then + sidefile = heap%queue(kk)%file + id_s = kk + end if + end do + + write (atmp,'(i0)') id_s + subdirfile = subdir_tmp//trim(atmp)//'/'//sidefile + inquire (exist=ex,file=subdirfile) + if (ex) then + write (stdout,'(a,t26,a,t30,a)',advance='no') & + & 'Reading fragment(s) from',':',subdirfile + call rdensemble(subdirfile,nall_s,structures_s) + write (stdout,'(1x,a,i0,a)') '--> ',nall_s,' structure(s)' + end if + layerfactor_s = 1.0_wp + + else + call recusrive_construct(env,heap,jj) + if (ii == 1) then + nall_b = heap%layer(jj)%nmols + allocate (structures_b(nall_b)) + do kk = 1,nall_b + structures_b(kk) = heap%layer(jj)%mols(kk) + end do + layerfactor_b = heap%layer(jj)%inverse_depth + + else if (ii == 2) then + + nall_s = heap%layer(jj)%nmols + allocate (structures_s(nall_s)) + do kk = 1,nall_s + structures_s(kk) = heap%layer(jj)%mols(kk) + end do + layerfactor_s = heap%layer(jj)%inverse_depth + !deallocate (heap%layer(jj)%mols) + end if + end if + end do + weight_s = layerfactor_s/(layerfactor_s+layerfactor_b) + weight_b = layerfactor_b/(layerfactor_s+layerfactor_b) + + write (stdout,*) + write (stdout,'(a,i0)') 'Reconstructing layer : ',targetlayer + write (stdout,'(2x,a,i0)') 'Base structures : ',nall_b + write (stdout,'(2x,a,i0)') 'Side chain structures : ',nall_s + write (stdout,'(2x,a,es9.2)') 'Max. combinations : ',real(nall_b,wp)*real(nall_s,wp) + write (stdout,'(2x,a,f7.5,a)') 'Similarity threshold : ',env%rthr,' Å' + write (stdout,'(2x,a,f7.5,a)') 'ΔE threshold (ETHR) : ',env%ethr,' kcal/mol' + + layer%nmols = 0 + depthlimit = real(env%queue_maxreconstruct,wp)*(env%queue_depthfac**real(targetlayer-1,wp)) + max_structs = nint(min(real(nall_b,wp)*real(nall_s,wp),depthlimit)) + allocate (layer%mols(max_structs)) + write (stdout,'(2x,a,i0)') 'Capping limit : ',env%queue_maxreconstruct + write (stdout,'(2x,a,f4.2,a)') 'Depth factor : ',env%queue_depthfac,'^(layer-1)' + write (stdout,'(2x,a,i0)') 'Max. new structs stored : ',max_structs + + RTHR = env%rthr*aatoau !> RMSD threshold in Bohr + ETHR = env%ethr/autokcal !> deltaE threshold in hartree + duplicates = 0 + T = 1 + call new_ompautoset(env,'max',max_structs,T,Tn) + write (stdout,'(2x,a,i0)') 'OpenMP threads : ',T + allocate (ccache(T)) + allocate (rcache(T)) + allocate (moltmp_arr(T)) + allocate (mask(layer%refmol%nat),source=.true.) + call canref%init(layer%refmol,invtype='apsp+',heavy=.false.) + + do tt = 1,T + call ccache(tt)%allocate(layer%refmol%nat,scratch=.true.) + call rcache(tt)%allocate(layer%refmol%nat) + rcache(tt)%stereocheck = .not. (canref%hasstereo(layer%refmol)) + rcache(tt)%rank(:,1) = canref%rank(:) + rcache(tt)%rank(:,2) = canref%rank(:) + end do + do ii = 1,layer%refmol%nat + if (layer%refmol%at(ii) == 1) mask(ii) = .false. + end do +! write (stdout,'(2x,a)') 'Recombining under heavy-atom RMSD consideration (this may take a while) ... ' + write (stdout,'(2x,a)') 'Recombining under iRMSD consideration (this may take a while) ... ' + call progress_init(env%ps,max_structs,width=50,prefix=" ↳ ", & + & suffix="",show_time=.true.,show_eta=.false.) + call progress_update(env%ps,0,max_structs) + + call profiler%init(1) + call profiler%start(1) + + ! ── Precompute sampling regions ────────────────────────────── + !> Region 1 targets max_structs combinations in the correct + !> weight ratio. Regions 2–3 expand into remaining structures. + base_is_outer = (nall_b <= nall_s) + nregions = 0 + + target_bhi = nint(sqrt(real(max_structs,wp)*weight_b/weight_s)) + target_shi = nint(sqrt(real(max_structs,wp)*weight_s/weight_b)) + + reg_blo(1) = 1 + reg_slo(1) = 1 + reg_bhi(1) = min(nall_b,target_bhi) + reg_shi(1) = min(nall_s,target_shi) + !> reciprocal fill if one dimension was capped + if (reg_bhi(1) < target_bhi.and.reg_bhi(1) > 0) then + reg_shi(1) = min(nall_s,nint(real(max_structs,wp)/real(reg_bhi(1),wp))) + else if (reg_shi(1) < target_shi.and.reg_shi(1) > 0) then + reg_bhi(1) = min(nall_b,nint(real(max_structs,wp)/real(reg_shi(1),wp))) + end if + nregions = 1 + + !> Region 2: expand whichever dimension wasn't exhausted + if (reg_shi(1) == nall_s.and.reg_bhi(1) < nall_b) then + nregions = 2 + reg_blo(2) = reg_bhi(1)+1 + reg_bhi(2) = nall_b + reg_slo(2) = 1 + reg_shi(2) = nall_s + else if (reg_bhi(1) == nall_b.and.reg_shi(1) < nall_s) then + nregions = 2 + reg_blo(2) = 1 + reg_bhi(2) = nall_b + reg_slo(2) = reg_shi(1)+1 + reg_shi(2) = nall_s + else if (reg_bhi(1) < nall_b.and.reg_shi(1) < nall_s) then + !> Neither exhausted: expand larger dim first, then the other + nregions = 3 + if (base_is_outer) then + reg_blo(2) = 1 + reg_bhi(2) = reg_bhi(1) + reg_slo(2) = reg_shi(1)+1 + reg_shi(2) = nall_s + reg_blo(3) = reg_bhi(1)+1 + reg_bhi(3) = nall_b + reg_slo(3) = 1 + reg_shi(3) = nall_s + else + reg_blo(2) = reg_bhi(1)+1 + reg_bhi(2) = nall_b + reg_slo(2) = 1 + reg_shi(2) = reg_shi(1) + reg_blo(3) = 1 + reg_bhi(3) = nall_b + reg_slo(3) = reg_shi(1)+1 + reg_shi(3) = nall_s + end if + end if + + ! ── Reconstruct by iterating over regions ─────────────────── + regionloop: do rg = 1,nregions + if (base_is_outer) then + outer_lo = reg_blo(rg); outer_hi = reg_bhi(rg) + inner_lo = reg_slo(rg); inner_hi = reg_shi(rg) + else + outer_lo = reg_slo(rg); outer_hi = reg_shi(rg) + inner_lo = reg_blo(rg); inner_hi = reg_bhi(rg) + end if + do outer_idx = outer_lo,outer_hi + do inner_idx = inner_lo,inner_hi + if (base_is_outer) then + ii = outer_idx; jj = inner_idx + else + ii = inner_idx; jj = outer_idx + end if + + call attach(structures_b(ii),structures_s(jj),layer%alignmap,mol, & + & remove_lastx=layer%ncapped,original_map=layer%position_mapping, & + & clash=clash,reficn=layer%reficn) + mol%energy = structures_b(ii)%energy+structures_s(jj)%energy + if (.not.clash) then + duplicate = .false. + + !$omp parallel & + !$omp shared(duplicate,duplicates,mol,ccache,rcache,mask,ETHR,moltmp_arr) & + !$omp private(rr,tt,deltaE,rmsval) + !$omp do schedule(dynamic) + do rr = 1,layer%nmols + if (duplicate) cycle + tt = omp_get_thread_num()+1 + deltaE = abs(mol%energy-layer%mols(rr)%energy) + if (deltaE < ETHR) then + call moltmp_arr(tt)%copy(layer%mols(rr)) + call min_rmsd(mol,moltmp_arr(tt),rcache=rcache(tt),rmsdout=rmsval,align=.false.) + !$omp critical + if (rmsval < RTHR.and..not.duplicate) then + duplicate = .true. + duplicates = duplicates+1 + end if + !$omp end critical + end if + end do + !$omp end do + !$omp end parallel + + if (.not.duplicate) then + layer%nmols = layer%nmols+1 + layer%mols(layer%nmols) = mol + call progress_update(env%ps,layer%nmols,max_structs) + if (layer%nmols == max_structs) exit regionloop + end if + end if + end do + end do + end do regionloop + if (layer%nmols < max_structs) then + call progress_update(env%ps,1,1) + end if + call progress_finish(env%ps) + write (stdout,'(2x,a)') 'done!' + if (duplicates > 0) then + write (stdout,'(2x,a,i0)') 'Avoided duplicates : ',duplicates + end if + write (stdout,'(2x,a,i0)') 'Successful combinations : ',layer%nmols + call profiler%stop(1) + write (btmp,'(2x,a)') 'Total runtime for recombination step:' + call profiler%write_timing(stdout,1,trim(btmp),.true.) + write (stdout,*) + + end associate + end subroutine recusrive_construct + +end subroutine crest_queue_reconstruct + diff --git a/src/algos/refine.f90 b/src/algos/refine.f90 index 71a96b28..59a13d8d 100644 --- a/src/algos/refine.f90 +++ b/src/algos/refine.f90 @@ -49,6 +49,7 @@ subroutine crest_refine(env,input,output) real(wp),allocatable :: xyz(:,:,:) integer,allocatable :: at(:) integer :: nrefine,refine_stage + type(coord),allocatable :: structures(:) !===========================================================! !>--- setup if (present(output)) then @@ -56,20 +57,33 @@ subroutine crest_refine(env,input,output) else outname = input !> overwrite end if - + + if(.not.allocated(env%refine_queue))then + call rename(trim(input),trim(output)) + return + endif + !>--- presorting step, if necessary - if(env%refine_presort)then + if (env%refine_presort) then call newcregen(env,0,input) call rename('crest_ensemble.xyz',input) - endif + end if !>--- read in - call rdensemble(input,nat,nall,at,xyz,eread) - allocate (etmp(nall),source=0.0_wp) -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_sploop requires coordinates in Bohrs - xyz = xyz / bohr -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_sploop requires coordinates in Bohrs +! xyz = xyz/bohr +!!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<< Geometry optimization of ",i0," structures")') nall call crest_oloop(env,nat,nall,at,xyz,eread,.false.) - case(refine%confsolv) - call new_ompautoset(env,'subprocess',1,t1,t2) - write (stdout,'("> ConfSolv: ΔΔGsoln estimation from 3D directed message passing neural networks (D-MPNN)")') - call confsolv_request( input, nall, t2, etmp, io) - if(io == 0)then - eread(:) = etmp(:)*kcaltoau !> since CREGEN deals with Eh energies - endif + case (refine%deltaG) + write (stdout,'("> Free energy correction (δG) for ",i0," structures")') nall + call crest_hessloop(env,nat,nall,at,xyz,etmp) + eread(:) = eread(:)+etmp(:) + end select - write(stdout,*) + write (stdout,*) end do !> reset the refinement stage of the calculator @@ -116,15 +128,17 @@ subroutine crest_refine(env,input,output) end if DO_REFINE !===========================================================! -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: ensemble file must be written in AA - xyz = xyz / angstrom -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- write output ensemble - call wrensemble(outname,nat,nall,at,xyz,eread) +!>--- sync refined energies and coordinates back into coord structures + do j = 1,nall + structures(j)%energy = eread(j) + structures(j)%xyz(1:3,1:nat) = xyz(1:3,1:nat,j) + structures(j)%wrextxyz = .true. + end do +!>--- write output ensemble in extxyz format + call wrensemble(outname,nall,structures) !===========================================================! - deallocate (etmp,eread,xyz,at) + deallocate (etmp,eread,xyz,at,structures) return end subroutine crest_refine !========================================================================================! diff --git a/src/algos/scan.f90 b/src/algos/scan.f90 index 0a40d94d..2460aadf 100644 --- a/src/algos/scan.f90 +++ b/src/algos/scan.f90 @@ -74,7 +74,7 @@ subroutine crest_scan(env,tim) !========================================================================================! allocate (grad(3,mol%nat),source=0.0_wp) - calc = env%calc + call calc%copy(env%calc) calcclean = env%calc !>--- initialize scanning @@ -190,8 +190,6 @@ subroutine initscans(mol,calc) deallocate (tmppoints) end do - write(*,*) 'constraints',calc%nconstraints - !>--- set calculations to 1 for the geometry generation calc%ncalculations = 1 @@ -287,7 +285,7 @@ recursive subroutine runscan(mol,calc,calcclean,current) allocate (grad(3,mol%nat),source=0.0_wp) !>-- geometry optimization pr = .false. !> stdout printout - wr = .true. !> write crestopt.log + wr = .true. !> write crestopt.log.xyz molbackup = mol do j=1,calc%scans(current)%steps !write(*,*) current, calc%scans(current)%steps, j diff --git a/src/algos/search_1.f90 b/src/algos/search_1.f90 index 8b4d9ae9..ece172ff 100644 --- a/src/algos/search_1.f90 +++ b/src/algos/search_1.f90 @@ -43,7 +43,7 @@ subroutine crest_search_1(env,tim) real(wp),allocatable :: eread(:) real(wp),allocatable :: xyz(:,:,:) integer,allocatable :: at(:) - logical :: dump + logical :: dump,doreturn !===========================================================! !>--- printout header @@ -61,10 +61,8 @@ subroutine crest_search_1(env,tim) write (stdout,*) !>--- saftey termination - if(mol%nat .le. 2)then - call catchdiatomic(env) - return - endif + call crest_sampling_skip(env,doreturn) + if (doreturn) return !===========================================================! !>--- Dynamics @@ -82,8 +80,8 @@ subroutine crest_search_1(env,tim) call tim%start(2,'Molecular dynamics (MD)') call crest_search_multimd(env,mol,mddats,nsim) call tim%stop(2) -!>--- a file called crest_dynamics.trj should have been written - ensnam = 'crest_dynamics.trj' +!>--- a file called crest_dynamics.trj.xyz should have been written + ensnam = 'crest_dynamics.trj.xyz' !==========================================================! !>--- Reoptimization of trajectories diff --git a/src/algos/search_conformers.f90 b/src/algos/search_conformers.f90 index abf606d5..0e24d617 100644 --- a/src/algos/search_conformers.f90 +++ b/src/algos/search_conformers.f90 @@ -20,12 +20,12 @@ subroutine crest_search_imtdgc(env,tim) !******************************************************************* !* This is the re-implementation of CREST's iMTD-GC default workflow -!* +!* !* Compared to the legacy implementation, this version !* is separated from the entropy algo to keep things clean !* The entropy algo (sMTD-iMTD) can be found in search_entropy.f90 !******************************************************************* - use crest_parameters, only: wp,stdout + use crest_parameters,only:wp,stdout use crest_data use crest_calculator use strucrd @@ -34,12 +34,13 @@ subroutine crest_search_imtdgc(env,tim) use iomod use utilities use cregen_interface + use crest_restartlog implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim type(coord) :: mol,molnew integer :: i,j,k,l,io,ich,m - logical :: pr,wr + logical :: pr,wr,doreturn !===========================================================! type(calcdata) :: calc type(mddata) :: mddat @@ -59,6 +60,9 @@ subroutine crest_search_imtdgc(env,tim) character(len=80) :: atmp,btmp,str logical :: multilevel(6) logical :: start,lower +!===========================================================! + type(restart_data) :: rdat + logical :: do_restart,skip_mtdloop,skip_collect,firstiter,fex !===========================================================! !>--- printout header write (stdout,*) @@ -67,6 +71,21 @@ subroutine crest_search_imtdgc(env,tim) write (stdout,'(10x,"┕",49("━"),"┙")') write (stdout,*) +! ── restart detection ───────────────────────────────────────────── + do_restart = .false. + skip_mtdloop = .false. + skip_collect = .false. + if (env%allowrestart .and. restart_file_exists()) then + call read_restart_log(rdat) + if (rdat%runtype == crest_imtd .and. rdat%stage /= 'done') then + do_restart = .true. + call print_restart_info(rdat) + !> skip entire mtdloop and collectcre only when past the MTD loop + skip_mtdloop = (rdat%stage == 'post_collect') + skip_collect = (rdat%stage == 'post_collect') + end if + end if + !===========================================================! !>--- setup call env%ref%to(mol) @@ -74,140 +93,191 @@ subroutine crest_search_imtdgc(env,tim) call mol%append(stdout) write (stdout,*) -!>--- saftey termination - if(mol%nat .le. 2)then - call catchdiatomic(env) - return - endif +!>--- saftey terminations + call crest_sampling_skip(env,doreturn) + if (doreturn) return !>--- sets the MD length according to a flexibility measure - call md_length_setup(env) + call md_length_setup(env) !>--- create the MD calculator saved to env call env_to_mddat(env) if (env%performMTD) then !>--- (optional) calculate a short 1ps test MTD to check settings - call tim%start(1,'Trial metadynamics (MTD)') - call trialmd(env) - call tim%stop(1) - if(env%iostatus_meta .ne. 0 ) return + call tim%start(1,'Trial metadynamics (MTD)') + call trialmd(env) + call tim%stop(1) + if (env%iostatus_meta .ne. 0) return end if !===========================================================! -!>--- Start mainloop +!>--- Start mainloop env%nreset = 0 start = .true. - MAINLOOP : do +! ── apply restart state ─────────────────────────────────────────── + if (do_restart) then + env%nreset = rdat%main_iter + env%elowest = rdat%elowest + env%eprivious = rdat%eprivious + env%nmetadyn = rdat%nmetadyn + start = .false. + end if + MAINLOOP: do call printiter - if (.not. start) then + if (do_restart) then +!>--- restart: preserve .cre_*.xyz files, skip cleanup + continue + else if (.not.start) then !>--- clean Dir for new iterations, but leave iteration backup files - call clean_V2i - env%nreset = env%nreset + 1 - else + call clean_V2i + env%nreset = env%nreset+1 + else !>--- at the beginning, wipe directory clean call V2cleanup(.false.) end if !===========================================================! -!>--- Meta-dynamics loop - mtdloop: do i = 1,env%Maxrestart - - write(stdout,*) - write(stdout,'(1x,a)') '------------------------------' - write(stdout,'(1x,a,i0)') 'Meta-Dynamics Iteration ',i - write(stdout,'(1x,a)') '------------------------------' - - nsim = -1 !>--- enambles automatic MTD setup in init routines - call crest_search_multimd_init(env,mol,mddat,nsim) - allocate (mddats(nsim), source=mddat) - call crest_search_multimd_init2(env,mddats,nsim) - - call tim%start(2,'Metadynamics (MTD)') - call crest_search_multimd(env,mol,mddats,nsim) - call tim%stop(2) -!>--- a file called crest_dynamics.trj should have been written - ensnam = 'crest_dynamics.trj' -!>--- deallocate for next iteration - if(allocated(mddats))deallocate(mddats) +!>--- Meta-dynamics loop (skipped on restart to use existing .cre_*.xyz) + if (.not.skip_mtdloop) then + mtdloop: do i = 1,env%Maxrestart + +! ── restart: skip based on stage ────────────────────────────────── + if (do_restart) then + if (rdat%stage == 'mtd_loop' .and. i <= rdat%mtd_iter) cycle mtdloop + if (rdat%stage == 'mtd_trj' .and. i < rdat%mtd_iter) cycle mtdloop + end if + + write (stdout,*) + write (stdout,'(1x,a)') '------------------------------' + write (stdout,'(1x,a,i0)') 'Meta-Dynamics Iteration ',i + write (stdout,'(1x,a)') '------------------------------' + +!==========================================================! +!>--- MTD run (skipped for mtd_trj restart: trajectory already exists) + if (do_restart .and. i == rdat%mtd_iter .and. & + & rdat%stage == 'mtd_trj') then + write (stdout,'(1x,a,i0,a)') 'Restarting iteration ',i, & + & ' from existing trajectory/ensemble' + ensnam = trim(rdat%last_file) + else + nsim = -1 !>--- enambles automatic MTD setup in init routines + call crest_search_multimd_init(env,mol,mddat,nsim) + allocate (mddats(nsim),source=mddat) + call crest_search_multimd_init2(env,mddats,nsim) + + call tim%start(2,'Metadynamics (MTD)') + call crest_search_multimd(env,mol,mddats,nsim) + call tim%stop(2) +!>--- a file called crest_dynamics.trj.xyz should have been written + ensnam = 'crest_dynamics.trj.xyz' + if (allocated(mddats)) deallocate (mddats) +!>--- checkpoint: trajectory ready, optimization about to start + call write_restart_log(crest_imtd,'mtd_trj',env%nreset,i, & + & env%nmetadyn,env%elowest,env%eprivious,ensnam) + end if !==========================================================! !>--- Reoptimization of trajectories - call tim%start(3,'Geometry optimization') - call optlev_to_multilev(env%optlev,multilevel) - call crest_multilevel_oloop(env,ensnam,multilevel) - call tim%stop(3) - if(env%iostatus_meta .ne. 0 ) return + call tim%start(3,'Geometry optimization') + call optlev_to_multilev(env%optlev,multilevel) + call crest_multilevel_oloop(env,ensnam,multilevel,i) + call tim%stop(3) + if (env%iostatus_meta .ne. 0) return !>--- save the CRE under a backup name - call checkname_xyz(crefile,atmp,str) - call checkname_xyz('.cre',str,btmp) - call rename(atmp,btmp) + call checkname_xyz(crefile,atmp,str) + call checkname_xyz('.cre',str,btmp) + call rename(atmp,btmp) !>--- save cregen output - call checkname_tmp('cregen',atmp,btmp) - call rename('cregen.out.tmp',btmp) + call checkname_tmp('cregen',atmp,btmp) + call rename('cregen.out.tmp',btmp) !=========================================================! -!>--- cleanup after first iteration and prepare next - if (i .eq. 1 .and. start) then - start = .false. +!>--- cleanup and state update after first iteration (before checkpoint) + firstiter = (i .eq. 1 .and. start) + if (firstiter) then + start = .false. !>-- obtain a first lowest energy as reference - env%eprivious = env%elowest + env%eprivious = env%elowest !>-- remove the two extreme-value MTDs - if (.not. env%readbias .and. env%runver .ne. 33 .and. & - & env%runver .ne. 787878 ) then - env%nmetadyn = env%nmetadyn - 2 + if (.not.env%readbias.and.env%runver .ne. 33.and. & + & env%runver .ne. 787878) then + env%nmetadyn = env%nmetadyn-2 + end if +!>-- the cleanup + call clean_V2i end if -!>-- the cleanup - call clean_V2i -!>-- and always do two cycles of MTDs - cycle mtdloop - endif +!>--- checkpoint after this MTD iteration (nmetadyn already updated above) + call write_restart_log(crest_imtd,'mtd_loop',env%nreset,i, & + & env%nmetadyn,env%elowest,env%eprivious,trim(str)) +!>-- always do two cycles of MTDs + if (firstiter) cycle mtdloop !=========================================================! !>--- Check for lowest energy - call elowcheck(lower,env) - if (.not. lower) then - exit mtdloop - end if - enddo mtdloop + call elowcheck(lower,env) + if (.not.lower) then + exit mtdloop + end if + end do mtdloop + end if !> end skip_mtdloop guard + skip_mtdloop = .false. + do_restart = .false. !=========================================================! !>--- collect all ensembles from mtdloop and merge - write(stdout,*) - write (stdout,'(''========================================'')') - write (stdout,'('' MTD Simulations done '')') - write (stdout,'(''========================================'')') - write (stdout,'(1x,''Collecting ensmbles.'')') + if (skip_collect) then +!>--- post_collect restart: collectcre already ran, reuse last file + inquire(file=trim(rdat%last_file),exist=fex) + if (.not.fex) then + write (stdout,'(/,a)') '**ERROR** restart ensemble not found: ' & + & //trim(rdat%last_file) + write (stdout,'(a,/)') ' Delete crest.restart and rerun from scratch.' + call creststop(status_safety) + end if + atmp = trim(rdat%last_file) + write (stdout,'(1x,a,a)') 'Restarting from ensemble: ',trim(atmp) + skip_collect = .false. + else + write (stdout,*) + write (stdout,'(''========================================'')') + write (stdout,'('' MTD Simulations done '')') + write (stdout,'(''========================================'')') + write (stdout,'(1x,''Collecting ensmbles.'')') !>-- collecting all ensembles saved as ".cre_*.xyz" - call collectcre(env) - call newcregen(env,0) - call checkname_xyz(crefile,atmp,btmp) + call collectcre(env) + call newcregen(env,0) + call checkname_xyz(crefile,atmp,btmp) +!>--- checkpoint after collection and CREGEN + call write_restart_log(crest_imtd,'post_collect',env%nreset,0, & + & env%nmetadyn,env%elowest,env%eprivious,trim(atmp)) + end if !>--- remaining number of structures - call remaining_in(atmp,env%ewin,nallout) + call remaining_in(atmp,env%ewin,nallout) !=========================================================! !>--- (optional) Perform additional MDs on the lowest conformers - if (env%rotamermds) then - call tim%start(4,'Molecular dynamics (MD)') - call crest_rotamermds(env,conformerfile) - call tim%stop(4) - if(env%iostatus_meta .ne. 0 ) return + if (env%rotamermds) then + call tim%start(4,'Molecular dynamics (MD)') + call crest_rotamermds(env,conformerfile) + call tim%stop(4) + if (env%iostatus_meta .ne. 0) return !>--- Reoptimization of trajectories - call checkname_xyz(crefile,atmp,btmp) - write(stdout,'('' Appending file '',a,'' with new structures'')')trim(atmp) - ensnam = 'crest_dynamics.trj' - call appendto(ensnam,trim(atmp)) - call tim%start(3,'Geometry optimization') - call crest_multilevel_wrap(env,trim(atmp),-1) - call tim%stop(3) - if(env%iostatus_meta .ne. 0 ) return - - call elowcheck(lower,env) - if (lower) then - call checkname_xyz(crefile,atmp,str) - call checkname_xyz('.cre',str,btmp) - call rename(atmp,btmp) - cycle MAINLOOP + call checkname_xyz(crefile,atmp,btmp) + write (stdout,'('' Appending file '',a,'' with new structures'')') trim(atmp) + ensnam = 'crest_dynamics.trj.xyz' + call appendto(ensnam,trim(atmp)) + call tim%start(3,'Geometry optimization') + call crest_multilevel_wrap(env,trim(atmp),-1) + call tim%stop(3) + if (env%iostatus_meta .ne. 0) return + + call elowcheck(lower,env) + if (lower) then + call checkname_xyz(crefile,atmp,str) + call checkname_xyz('.cre',str,btmp) + call rename(atmp,btmp) + cycle MAINLOOP + end if end if - end if !=========================================================! !>--- (optional) Perform GC step @@ -215,7 +285,7 @@ subroutine crest_search_imtdgc(env,tim) call tim%start(5,'Genetic crossing (GC)') call crest_newcross3(env) call tim%stop(5) - if(env%iostatus_meta .ne. 0 ) return + if (env%iostatus_meta .ne. 0) return call confg_chk3(env) call elowcheck(lower,env) @@ -229,32 +299,37 @@ subroutine crest_search_imtdgc(env,tim) !==========================================================! !>--- exit mainloop - exit MAINLOOP - enddo MAINLOOP + exit MAINLOOP + end do MAINLOOP !==========================================================! !>--- final ensemble optimization - write (stdout,'(/)') - write (stdout,'(3x,''================================================'')') - write (stdout,'(3x,''| Final Geometry Optimization |'')') - write (stdout,'(3x,''================================================'')') - call tim%start(3,'Geometry optimization') - call checkname_xyz(crefile,atmp,str) - call crest_multilevel_wrap(env,trim(atmp),0) - call tim%stop(3) - if(env%iostatus_meta .ne. 0 ) return + write (stdout,'(/)') + write (stdout,'(3x,''================================================'')') + write (stdout,'(3x,''| Final Geometry Optimization |'')') + write (stdout,'(3x,''================================================'')') + call tim%start(3,'Geometry optimization') + call checkname_xyz(crefile,atmp,str) + call crest_multilevel_wrap(env,trim(atmp),0) + call tim%stop(3) + if (env%iostatus_meta .ne. 0) return + +!==========================================================! +!>--- checkpoint: run is complete + call write_restart_log(crest_imtd,'done',env%nreset,0, & + & env%nmetadyn,env%elowest,env%eprivious,conformerfile) !==========================================================! !>--- final ensemble sorting -! call newcregen(env,0) +! call newcregen(env,0) !> this is actually done within the last crest_multilevel_ !> call, so I comment it out here !==========================================================! !>--- print CREGEN results and clean up Directory a bit - write (stdout,'(/)') - call smallhead('Final Ensemble Information') - call V2terminating() + write (stdout,'(/)') + call smallhead('Final Ensemble Information') + call V2terminating() !==========================================================! return @@ -269,7 +344,7 @@ subroutine crest_multilevel_wrap(env,ensnam,level) !* wrapper for the multilevel_oloop to select !* only a single optimization level !************************************************* - use crest_parameters, only: wp,stdout,bohr + use crest_parameters,only:wp,stdout,bohr use crest_data use crest_calculator use strucrd @@ -280,52 +355,56 @@ subroutine crest_multilevel_wrap(env,ensnam,level) logical :: multilevel(6) integer :: k multilevel = .false. - select case(level) - case( 1: ) !> explicit selection (level is a positie integer) - k = min(level,6) - multilevel(k) =.true. - case default - !>-- map global variable to multilevel selection (level is 0 or negative) - k = optlevmap_alt(env%optlev) + level + select case (level) + case (1:) !> explicit selection (level is a positie integer) + k = min(level,6) + multilevel(k) = .true. + case default + !>-- map global variable to multilevel selection (level is 0 or negative) + k = optlevmap_alt(env%optlev)+level k = max(1,k) - multilevel(k) =.true. + multilevel(k) = .true. end select - call crest_multilevel_oloop(env,ensnam,multilevel) + call crest_multilevel_oloop(env,ensnam,multilevel,0) end subroutine crest_multilevel_wrap !========================================================================================! -subroutine crest_multilevel_oloop(env,ensnam,multilevel_in) +subroutine crest_multilevel_oloop(env,ensnam,multilevel_in,mtd_iter_in) !******************************************************* !* multilevel optimization loop. !* construct consecutive optimizations starting with -!* crude thresholds to very tight ones +!* crude thresholds to very tight ones. +!* mtd_iter_in: when > 0, writes a mtd_trj restart +!* checkpoint after each CREGEN step (pass 0 to skip). !******************************************************* - use crest_parameters, only: wp,stdout,bohr + use crest_parameters,only:wp,stdout,bohr use crest_data use crest_calculator use strucrd use optimize_module use utilities - use crest_restartlog use parallel_interface + use crest_restartlog implicit none - type(systemdata) :: env + type(systemdata) :: env character(len=*),intent(in) :: ensnam logical,intent(in) :: multilevel_in(6) + integer,intent(in) :: mtd_iter_in integer :: nat,nall real(wp),allocatable :: eread(:) real(wp),allocatable :: xyz(:,:,:) integer,allocatable :: at(:) logical :: dump,pr character(len=128) :: inpnam,outnam - integer :: i,l,k,T,Tn + integer :: i,l,k,T,Tn,j real(wp) :: ewinbackup,rthrbackup real(wp) :: hlowbackup integer :: microbackup integer :: optlevelbackup logical :: multilevel(6) + type(coord),allocatable :: structures(:) - interface + interface subroutine crest_refine(env,input,output) use crest_data implicit none @@ -336,151 +415,171 @@ end subroutine crest_refine end interface !>--- save backup thresholds - ewinbackup = env%ewin - rthrbackup = env%rthr + ewinbackup = env%ewin + rthrbackup = env%rthr optlevelbackup = env%calc%optlev - hlowbackup = env%calc%hlow_opt - microbackup = env%calc%micro_opt + hlowbackup = env%calc%hlow_opt + microbackup = env%calc%micro_opt !>--- set multilevels, or enforce just one multilevel(:) = .false. - if(env%multilevelopt)then - multilevel(:) = multilevel_in(:) + if (env%multilevelopt) then + multilevel(:) = multilevel_in(:) else k = optlevmap_alt(env%optlev) multilevel(k) = .true. - endif + end if pr = .false. l = count(multilevel) - if( l > 1 )then - pr = .true. - write(stdout,*) - write(stdout,'(1x,a)') '======================================' - write(stdout,'(1x,a)') '| Multilevel Ensemble Optimization |' - write(stdout,'(1x,a)') '======================================' - endif - + if (l > 1) then + pr = .true. + write (stdout,*) + write (stdout,'(1x,a)') '======================================' + write (stdout,'(1x,a)') '| Multilevel Ensemble Optimization |' + write (stdout,'(1x,a)') '======================================' + end if + !>--- read ensemble call rdensembleparam(ensnam,nat,nall) if (nall .lt. 1) then - write(stdout,*) '**ERROR** empty ensemble file ',trim(ensnam) + write (stdout,*) '**ERROR** empty ensemble file ',trim(ensnam) env%iostatus_meta = status_failed return - endif + end if allocate (xyz(3,nat,nall),at(nat),eread(nall)) - call rdensemble(ensnam,nat,nall,at,xyz,eread) -!>--- track ensemble for restart - call trackensemble(ensnam,nat,nall,at,xyz,eread) -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_oloop requires coordinates in Bohrs - xyz = xyz / bohr -!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- track ensemble for restart +! !call trackensemble(ensnam,nat,nall,at,xyz,eread) +!!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_oloop requires coordinates in Bohrs +! xyz = xyz/bohr +!!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- sequential optimizations of ensembles dump = .true. !> optimized structures will be written to crest_ensemble.xyz - do i=1,6 - if(multilevel(i))then - !>--- set threads - call new_ompautoset(env,'auto',nall,T,Tn) - !>--- set optimization parameters - call set_multilevel_options(env,i,.true.) - !>--- run parallel optimizations - call crest_oloop(env,nat,nall,at,xyz,eread,dump) - deallocate(eread,at,xyz) - !>--- rename ensemble and sort - call checkname_xyz(crefile,inpnam,outnam) - call rename(ensemblefile,trim(inpnam)) - !>--- check for empty ensemble content - call rdensembleparam(trim(inpnam),nat,nall) - if (nall .lt. 1) then - write(stdout,*) '**ERROR** empty ensemble file',trim(inpnam) - env%iostatus_meta = status_failed - return - endif - - write(stdout,*) - !==========================================================! - !>-- dedicated ensemble refinement step (overwrites inpnam) - call crest_refine(env,trim(inpnam)) - !==========================================================! - - !>--- CREGEN sorting - call sort_and_check(env,trim(inpnam)) - call checkname_xyz(crefile,inpnam,outnam) - !>--- check for empty ensemble content (again) - call rdensembleparam(trim(inpnam),nat,nall) - if (nall .lt. 1) then - write(stdout,*) '**ERROR** empty ensemble file',trim(inpnam) - env%iostatus_meta = status_failed - return - endif - !>--- read new ensemble for next iteration - allocate (xyz(3,nat,nall),at(nat),eread(nall)) - call rdensemble(trim(inpnam),nat,nall,at,xyz,eread) - !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_oloop requires coordinates in Bohrs - xyz = xyz / bohr - !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- restore default sorting thresholds - env%ewin = ewinbackup - env%rthr = rthrbackup - env%calc%optlev = optlevelbackup - env%calc%hlow_opt = hlowbackup - env%calc%micro_opt = microbackup - endif - enddo - - if(allocated(eread)) deallocate(eread) - if(allocated(at)) deallocate(at) - if(allocated(xyz)) deallocate(xyz) + do i = 1,6 + if (multilevel(i)) then + !>--- set threads + call new_ompautoset(env,'auto',nall,T,Tn) + !>--- set optimization parameters + call set_multilevel_options(env,i,.true.) + !>--- run parallel optimizations + call crest_oloop(env,nat,nall,at,xyz,eread,dump) + deallocate (eread,at,xyz) + !>--- rename ensemble and sort + call checkname_xyz(crefile,inpnam,outnam) + call rename(ensemblefile,trim(inpnam)) + !>--- check for empty ensemble content + call rdensembleparam(trim(inpnam),nat,nall) + if (nall .lt. 1) then + write (stdout,*) '**ERROR** empty ensemble file',trim(inpnam) + env%iostatus_meta = status_failed + return + end if + + write (stdout,*) + !==========================================================! + !>-- dedicated ensemble refinement step (overwrites inpnam) + call crest_refine(env,trim(inpnam)) + !==========================================================! + + !>--- CREGEN sorting + call sort_and_check(env,trim(inpnam)) + call checkname_xyz(crefile,inpnam,outnam) +! ── restart checkpoint: intermediate ensemble after this opt. level ── + if (mtd_iter_in > 0) then + call write_restart_log(env%crestver,'mtd_trj',env%nreset, & + & mtd_iter_in,env%nmetadyn,env%elowest,env%eprivious,trim(inpnam)) + end if + !>--- check for empty ensemble content (again) + call rdensembleparam(trim(inpnam),nat,nall) + if (nall .lt. 1) then + write (stdout,*) '**ERROR** empty ensemble file',trim(inpnam) + env%iostatus_meta = status_failed + return + end if + !>--- read new ensemble for next iteration + allocate (xyz(3,nat,nall),at(nat),eread(nall)) + !call rdensemble(trim(inpnam),nat,nall,at,xyz,eread) + !!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: crest_oloop requires coordinates in Bohrs + !xyz = xyz/bohr + !!>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- restore default sorting thresholds + env%ewin = ewinbackup + env%rthr = rthrbackup + env%calc%optlev = optlevelbackup + env%calc%hlow_opt = hlowbackup + env%calc%micro_opt = microbackup + end if + end do + + if (allocated(eread)) deallocate (eread) + if (allocated(at)) deallocate (at) + if (allocated(xyz)) deallocate (xyz) return contains subroutine set_multilevel_options(env,i,pr) implicit none type(systemdata) :: env integer,intent(in) :: i - logical,intent(in) :: pr + logical,intent(in) :: pr - env%calc%hlow_opt = env%hlowopt + env%calc%hlow_opt = env%hlowopt env%calc%micro_opt = nint(env%microopt) - select case( i ) - case( 1 ) - if(pr) call smallhead('crude pre-optimization') - env%calc%optlev = -3 - !> larger thresholds - env%rthr = env%rthr * 2.0d0 - env%ewin = aint(env%ewin * 2.0d0) - case( 2 ) - if(pr) call smallhead('optimization with very loose thresholds') - env%calc%optlev = -2 - env%rthr = env%rthr *1.5d0 - env%ewin = aint(env%ewin * 2.0d0) - case( 3 ) - if(pr) call smallhead('optimization with loose thresholds') - env%calc%optlev = -1 + select case (i) + case (1) + if (pr) call smallhead('crude pre-optimization') + env%calc%optlev = -3 + !> larger thresholds + env%rthr = env%rthr*2.0d0 + env%ewin = aint(env%ewin*2.0d0) + case (2) + if (pr) call smallhead('optimization with very loose thresholds') + env%calc%optlev = -2 + env%rthr = env%rthr*1.5d0 + env%ewin = aint(env%ewin*2.0d0) + case (3) + if (pr) call smallhead('optimization with loose thresholds') + env%calc%optlev = -1 env%ewin = aint(env%ewin*(10.0d0/6.0d0)) - case( 4 ) - if(pr) call smallhead('optimization with regular thresholds') - env%calc%optlev = 0 - case( 5 ) - if(pr) call smallhead('optimization with tight thresholds') - env%calc%optlev = 1 - case( 6 ) - if(pr) call smallhead('optimization with very tight thresholds') - env%calc%optlev = 2 + case (4) + if (pr) call smallhead('optimization with regular thresholds') + env%calc%optlev = 0 + case (5) + if (pr) call smallhead('optimization with tight thresholds') + env%calc%optlev = 1 + case (6) + if (pr) call smallhead('optimization with very tight thresholds') + env%calc%optlev = 2 case default - if(pr) call smallhead('optimization with default thresholds') - env%ewin = 6.0_wp - env%rthr = 0.125_wp - env%calc%optlev = 0 + if (pr) call smallhead('optimization with default thresholds') + env%ewin = 6.0_wp + env%rthr = 0.125_wp + env%calc%optlev = 0 end select - call print_opt_data(env%calc, stdout) + call print_opt_data(env%calc,stdout,natoms=env%ref%nat) end subroutine set_multilevel_options end subroutine crest_multilevel_oloop @@ -491,7 +590,7 @@ subroutine crest_rotamermds(env,ensnam) !* set up and perform several MDs at different temperatures !* on the lowest few conformers !*********************************************************** - use crest_parameters, only: wp,stdout,bohr + use crest_parameters,only:wp,stdout,bohr use crest_data use crest_calculator use strucrd @@ -504,7 +603,7 @@ subroutine crest_rotamermds(env,ensnam) integer :: nsim type(mddata) :: mddat type(mddata),allocatable :: mddats(:) - type(coord) :: mol + type(coord) :: mol type(coord),allocatable :: mols(:) integer :: nat,nall real(wp),allocatable :: eread(:) @@ -513,65 +612,65 @@ subroutine crest_rotamermds(env,ensnam) integer :: nstrucs,i,j,k,io real(wp) :: temp,newtemp character(len=80) :: atmp - + !>--- coord setup call env%ref%to(mol) call rdensembleparam(ensnam,nat,nall) if (nall .lt. 1) then - write(stdout,*) '**ERROR** empty ensemble file',trim(ensnam) + write (stdout,*) '**ERROR** empty ensemble file',trim(ensnam) env%iostatus_meta = status_failed return - endif + end if !>--- determine how many MDs need to be run and setup call adjustnormmd(env) - nstrucs = min(nall, env%nrotammds) - nsim = nstrucs * env%temps + nstrucs = min(nall,env%nrotammds) + nsim = nstrucs*env%temps call crest_search_multimd_init(env,mol,mddat,nsim) - allocate (mddats(nsim), source=mddat) + allocate (mddats(nsim),source=mddat) call crest_search_multimd_init2(env,mddats,nsim) !>--- adjust T's and runtimes k = 0 - do i=1,env%temps + do i = 1,env%temps !> each T block 100K higher - temp = env%nmdtemp + (i-1)*100.0_wp - do j=1,nstrucs - k= k + 1 + temp = env%nmdtemp+(i-1)*100.0_wp + do j = 1,nstrucs + k = k+1 mddats(k)%tsoll = temp !> reduce runtime by 50% compared to MTDs - mddats(k)%length_ps = mddats(k)%length_ps * 0.5_wp + mddats(k)%length_ps = mddats(k)%length_ps*0.5_wp call mdautoset(mddats(k),io) - enddo - enddo + end do + end do !>--- read ensemble and prepare mols allocate (xyz(3,nat,nall),at(nat),eread(nall)) call rdensemble(ensnam,nat,nall,at,xyz,eread) !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Important: mols must be in Bohrs - xyz = xyz / bohr + xyz = xyz/bohr !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<--- print what we are doing - write(stdout,*) - write(atmp,'(''Additional regular MDs on lowest '',i0,'' conformer(s)'')')nstrucs - call smallheadline(trim(atmp)) + write (stdout,*) + write (atmp,'(''Additional regular MDs on lowest '',i0,'' conformer(s)'')') nstrucs + call smallheadline(trim(atmp)) !>--- and finally, run the MDs call crest_search_multimd2(env,mols,mddats,nsim) - if(allocated(mols))deallocate(mols) - if(allocated(mddats))deallocate(mddats) + if (allocated(mols)) deallocate (mols) + if (allocated(mddats)) deallocate (mddats) return end subroutine crest_rotamermds @@ -587,7 +686,7 @@ subroutine crest_newcross3(env) use iomod use utilities implicit none - type(systemdata) :: env + type(systemdata) :: env real(wp) :: ewinbackup integer :: i,imax,tmpconf,nremain character(len=128) :: inpnam,outnam,refnam @@ -603,8 +702,8 @@ subroutine crest_newcross3(env) imax = min(nint(env%mdtime*50.0d0),5000) if (env%setgcmax) then imax = nint(env%gcmax) - else if(imax<0)then - imax=5000 + else if (imax < 0) then + imax = 5000 end if if (env%quick) then imax = nint(float(imax)*0.5d0) @@ -612,7 +711,7 @@ subroutine crest_newcross3(env) !>-- call the crossing routine call checkname_xyz(crefile,refnam,tmppath) - call touch(trim(tmppath)) + call touch(trim(tmppath)) call crest_crossing(env,imax,trim(refnam),env%gcmaxparent) if (imax .lt. 1) then call remove(trim(tmppath)) @@ -626,18 +725,40 @@ subroutine crest_newcross3(env) else multilevel(4) = .true. end if - call crest_multilevel_oloop(env,'confcross.xyz',multilevel) - if(env%iostatus_meta .ne. 0 ) return + call crest_multilevel_oloop(env,'confcross.xyz',multilevel,0) + if (env%iostatus_meta .ne. 0) return !>-- append optimized crossed structures and original to a single file call checkname_xyz(crefile,inpnam,outnam) - write(stdout,'(a,a)')'appending new structures to ',trim(refnam) + write (stdout,'(a,a)') 'appending new structures to ',trim(refnam) call appendto(trim(inpnam),trim(refnam)) - do while(trim(inpnam).ne.trim(refnam)) + do while (trim(inpnam) .ne. trim(refnam)) call remove(trim(inpnam)) call checkname_xyz(crefile,inpnam,outnam) - enddo + end do end do end subroutine crest_newcross3 +!=============================================================================! +!#############################################################################! +!=============================================================================! + +subroutine crest_sampling_skip(env,doreturn) + use crest_data + implicit none + type(systemdata),intent(inout) :: env + logical,intent(out) :: doreturn + + doreturn = .false. + + !> Diatomic molecules need no sampling + if (env%ref%nat .le. 2) then + call catchdiatomic(env) + doreturn = .true. + return + end if + + !> "alkylize" special runtype + call crest_proxy_nalkane(env,doreturn) +end subroutine crest_sampling_skip diff --git a/src/algos/search_entropy.f90 b/src/algos/search_entropy.f90 index 9c1b8c77..7c68015e 100644 --- a/src/algos/search_entropy.f90 +++ b/src/algos/search_entropy.f90 @@ -32,12 +32,13 @@ subroutine crest_search_entropy(env,tim) use iomod use utilities use cregen_interface + use crest_restartlog implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim type(coord) :: mol,molnew integer :: i,j,k,l,io,ich,m - logical :: pr,wr + logical :: pr,wr,doreturn !===========================================================! type(calcdata) :: calc type(mddata) :: mddat @@ -61,6 +62,9 @@ subroutine crest_search_entropy(env,tim) !> Entropy algo variables logical :: stopiter,fail integer :: bref,dum,eit,eit2 +!===========================================================! + type(restart_data) :: rdat + logical :: do_restart,skip_mtdloop,skip_collect,skip_emtdcopy0,firstiter,fex !===========================================================! !>--- printout header write (stdout,*) @@ -73,6 +77,26 @@ subroutine crest_search_entropy(env,tim) write (stdout,'(1x,a)') '• J.Gorges, S.Grimme, A.Hansen, P.Pracht, PCCP, 2022,24, 12249-12259.' write (stdout,*) +! ── restart detection ───────────────────────────────────────────── + do_restart = .false. + skip_mtdloop = .false. + skip_collect = .false. + skip_emtdcopy0 = .false. + if (env%allowrestart .and. restart_file_exists()) then + call read_restart_log(rdat) + if (rdat%runtype == env%crestver .and. rdat%stage /= 'done') then + do_restart = .true. + call print_restart_info(rdat) + !> skip entire mtdloop and collectcre when past the MTD loop + skip_mtdloop = (rdat%stage == 'post_collect' .or. & + & rdat%stage == 'entropy_smtd') + skip_collect = (rdat%stage == 'post_collect' .or. & + & rdat%stage == 'entropy_smtd') + !> additionally skip emtdcopy(iter=0) when that call already ran + skip_emtdcopy0 = (rdat%stage == 'entropy_smtd') + end if + end if + !===========================================================! !>--- setup call env%ref%to(mol) @@ -80,11 +104,9 @@ subroutine crest_search_entropy(env,tim) call mol%append(stdout) write (stdout,*) -!>--- saftey termination - if(mol%nat .le. 2)then - call catchdiatomic(env) - return - endif +!>--- saftey terminations + call crest_sampling_skip(env,doreturn) + if (doreturn) return !>--- sets the MD length according to a flexibility measure call md_length_setup(env) @@ -94,7 +116,7 @@ subroutine crest_search_entropy(env,tim) if (env%performMTD) then !>--- (optional) calculate a short 1ps test MTD to check settings call tim%start(1,'Trial metadynamics (MTD)') - call trialmd(env) + call trialmd(env) call tim%stop(1) if(env%iostatus_meta .ne. 0) return end if @@ -103,9 +125,20 @@ subroutine crest_search_entropy(env,tim) !>--- Start mainloop env%nreset = 0 start = .true. +! ── apply restart state ─────────────────────────────────────────── + if (do_restart) then + env%nreset = rdat%main_iter + env%elowest = rdat%elowest + env%eprivious = rdat%eprivious + env%nmetadyn = rdat%nmetadyn + start = .false. + end if MAINLOOP: do call printiter - if (.not.start) then + if (do_restart) then +!>--- restart: preserve .cre_*.xyz files, skip cleanup + continue + else if (.not.start) then !>--- clean Dir for new iterations, but leave iteration backup files call clean_V2i env%nreset = env%nreset+1 @@ -114,32 +147,50 @@ subroutine crest_search_entropy(env,tim) call V2cleanup(.false.) end if !===========================================================! -!>--- Meta-dynamics loop +!>--- Meta-dynamics loop (skipped on restart to use existing .cre_*.xyz) + if (.not.skip_mtdloop) then mtdloop: do i = 1,env%Maxrestart +! ── restart: skip based on stage ────────────────────────────────── + if (do_restart) then + if (rdat%stage == 'mtd_loop' .and. i <= rdat%mtd_iter) cycle mtdloop + if (rdat%stage == 'mtd_trj' .and. i < rdat%mtd_iter) cycle mtdloop + end if + write (stdout,*) write (stdout,'(1x,a)') '------------------------------' write (stdout,'(1x,a,i0)') 'Meta-Dynamics Iteration ',i write (stdout,'(1x,a)') '------------------------------' - nsim = -1 !>--- enambles automatic MTD setup in init routines - call crest_search_multimd_init(env,mol,mddat,nsim) - allocate (mddats(nsim),source=mddat) - call crest_search_multimd_init2(env,mddats,nsim) - - call tim%start(2,'Metadynamics (MTD)') - call crest_search_multimd(env,mol,mddats,nsim) - call tim%stop(2) -!>--- a file called crest_dynamics.trj should have been written - ensnam = 'crest_dynamics.trj' -!>--- deallocate for next iteration - if (allocated(mddats)) deallocate (mddats) +!==========================================================! +!>--- MTD run (skipped for mtd_trj restart: trajectory already exists) + if (do_restart .and. i == rdat%mtd_iter .and. & + & rdat%stage == 'mtd_trj') then + write (stdout,'(1x,a,i0,a)') 'Restarting iteration ',i, & + & ' from existing trajectory/ensemble' + ensnam = trim(rdat%last_file) + else + nsim = -1 !>--- enambles automatic MTD setup in init routines + call crest_search_multimd_init(env,mol,mddat,nsim) + allocate (mddats(nsim),source=mddat) + call crest_search_multimd_init2(env,mddats,nsim) + + call tim%start(2,'Metadynamics (MTD)') + call crest_search_multimd(env,mol,mddats,nsim) + call tim%stop(2) +!>--- a file called crest_dynamics.trj.xyz should have been written + ensnam = 'crest_dynamics.trj.xyz' + if (allocated(mddats)) deallocate (mddats) +!>--- checkpoint: trajectory ready, optimization about to start + call write_restart_log(env%crestver,'mtd_trj',env%nreset,i, & + & env%nmetadyn,env%elowest,env%eprivious,ensnam) + end if !==========================================================! !>--- Reoptimization of trajectories call tim%start(3,'Geometry optimization') call optlev_to_multilev(env%optlev,multilevel) - call crest_multilevel_oloop(env,ensnam,multilevel) + call crest_multilevel_oloop(env,ensnam,multilevel,i) call tim%stop(3) if(env%iostatus_meta .ne. 0 ) return @@ -152,8 +203,9 @@ subroutine crest_search_entropy(env,tim) call rename('cregen.out.tmp',btmp) !=========================================================! -!>--- cleanup after first iteration and prepare next - if (i .eq. 1.and.start) then +!>--- cleanup and state update after first iteration (before checkpoint) + firstiter = (i .eq. 1 .and. start) + if (firstiter) then start = .false. !>-- obtain a first lowest energy as reference env%eprivious = env%elowest @@ -164,9 +216,12 @@ subroutine crest_search_entropy(env,tim) end if !>-- the cleanup call clean_V2i -!>-- and always do two cycles of MTDs - cycle mtdloop end if +!>--- checkpoint after this MTD iteration (nmetadyn already updated above) + call write_restart_log(env%crestver,'mtd_loop',env%nreset,i, & + & env%nmetadyn,env%elowest,env%eprivious,trim(str)) +!>-- always do two cycles of MTDs + if (firstiter) cycle mtdloop !=========================================================! !>--- Check for lowest energy call elowcheck(lower,env) @@ -174,17 +229,37 @@ subroutine crest_search_entropy(env,tim) exit mtdloop end if end do mtdloop + end if !> end skip_mtdloop guard + skip_mtdloop = .false. + do_restart = .false. !=========================================================! !>--- collect all ensembles from mtdloop and merge - write (stdout,*) - write (stdout,'(''========================================'')') - write (stdout,'('' MTD Simulations done '')') - write (stdout,'(''========================================'')') - write (stdout,'(1x,''Collecting ensmbles.'')') + if (skip_collect) then +!>--- post_collect restart: collectcre already ran, reuse last file + inquire(file=trim(rdat%last_file),exist=fex) + if (.not.fex) then + write (stdout,'(/,a)') '**ERROR** restart ensemble not found: ' & + & //trim(rdat%last_file) + write (stdout,'(a,/)') ' Delete crest.restart and rerun from scratch.' + call creststop(status_safety) + end if + atmp = trim(rdat%last_file) + write (stdout,'(1x,a,a)') 'Restarting from ensemble: ',trim(atmp) + skip_collect = .false. + else + write (stdout,*) + write (stdout,'(''========================================'')') + write (stdout,'('' MTD Simulations done '')') + write (stdout,'(''========================================'')') + write (stdout,'(1x,''Collecting ensmbles.'')') !>-- collecting all ensembles saved as ".cre_*.xyz" - call collectcre(env) - call newcregen(env,0) - call checkname_xyz(crefile,atmp,btmp) + call collectcre(env) + call newcregen(env,0) + call checkname_xyz(crefile,atmp,btmp) +!>--- checkpoint after collection and CREGEN + call write_restart_log(env%crestver,'post_collect',env%nreset,0, & + & env%nmetadyn,env%elowest,env%eprivious,trim(atmp)) + end if !>--- remaining number of structures call remaining_in(atmp,env%ewin,nallout) @@ -195,7 +270,17 @@ subroutine crest_search_entropy(env,tim) !>--- and other entropy mode parameters call adjustnormmd(env) call mtdatoms(env) - call emtdcopy(env,0,stopiter,fail) + if (.not.skip_emtdcopy0) then + call emtdcopy(env,0,stopiter,fail) +! ── checkpoint: entropy rotamer file written, sMTD iterations about to start ── + if (env%crestver == crest_imtd2) then + write (btmp,'(a,i0,a)') 'crest_smtd_',0,'.xyz' + else + write (btmp,'(a,i0,a)') 'crest_entropy_rotamer_',0,'.xyz' + end if + call write_restart_log(env%crestver,'entropy_smtd',env%nreset,0, & + & env%nmetadyn,env%elowest,env%eprivious,trim(btmp)) + end if bref = env%emtd%nbias !>--- sMTD iterations, done until max iterations or convergence @@ -227,7 +312,7 @@ subroutine crest_search_entropy(env,tim) call checkname_xyz(crefile,atmp,btmp) call tim%start(3,'Geometry optimization') multilevel = (/.true.,.false.,.false.,.false.,.false.,.true./) - call crest_multilevel_oloop(env,trim(atmp),multilevel) + call crest_multilevel_oloop(env,trim(atmp),multilevel,0) call tim%stop(3) if(env%iostatus_meta .ne. 0 ) return @@ -242,6 +327,14 @@ subroutine crest_search_entropy(env,tim) eit2 = eit call emtdcopy(env,eit2,stopiter,fail) env%emtd%iterlast = eit2 +! ── checkpoint: update last_file to current entropy rotamer file ────── + if (env%crestver == crest_imtd2) then + write (btmp,'(a,i0,a)') 'crest_smtd_',eit2,'.xyz' + else + write (btmp,'(a,i0,a)') 'crest_entropy_rotamer_',eit2,'.xyz' + end if + call write_restart_log(env%crestver,'entropy_smtd',env%nreset,0, & + & env%nmetadyn,env%elowest,env%eprivious,trim(btmp)) end if if (.not.lower.and.fail.and..not.stopiter) then @@ -263,6 +356,11 @@ subroutine crest_search_entropy(env,tim) exit MAINLOOP end do MAINLOOP +!==========================================================! +!>--- checkpoint: run is complete + call write_restart_log(env%crestver,'done',env%nreset,0, & + & env%nmetadyn,env%elowest,env%eprivious,conformerfile) + !==========================================================! !>--- print CREGEN results and clean up Directory a bit write (stdout,'(/)') @@ -373,10 +471,10 @@ subroutine crest_smtd_mds(env,ensnam) !===================================================================! !>--- and finally, run the sMTDs on the different starting structures call crest_search_multimd2(env,mols,mddats,nsim) -!>--- output will be collected in crest_dynamics.trj +!>--- output will be collected in crest_dynamics.trj.xyz !>--- but the entropy routines look for the crest_rotamers_ files call checkname_xyz(crefile,atmp,btmp) - call rename('crest_dynamics.trj',atmp) + call rename('crest_dynamics.trj.xyz',atmp) !===================================================================! !>--- by default, clean up the directory if (.not.env%keepModef) call cleanMTD diff --git a/src/algos/search_mecp.f90 b/src/algos/search_mecp.f90 index b480fbfe..c0f8ae45 100644 --- a/src/algos/search_mecp.f90 +++ b/src/algos/search_mecp.f90 @@ -72,8 +72,8 @@ subroutine crest_search_mecp(env,tim) call tim%start(2,'Molecular dynamics (MD)') call crest_search_multimd(env,mol,mddats,nsim) call tim%stop(2) - !>--- a file called crest_dynamics.trj should have been written - ensnam = 'crest_dynamics.trj' + !>--- a file called crest_dynamics.trj.xyz should have been written + ensnam = 'crest_dynamics.trj.xyz' !==========================================================! !>--- Reoptimization of trajectories diff --git a/src/algos/search_newnci.f90 b/src/algos/search_newnci.f90 index 2472b28c..c93e531b 100644 --- a/src/algos/search_newnci.f90 +++ b/src/algos/search_newnci.f90 @@ -36,7 +36,7 @@ subroutine crest_search_newnci(env,tim) type(timer),intent(inout) :: tim type(coord) :: mol,molnew integer :: i,j,k,l,io,ich,m - logical :: pr,wr + logical :: pr,wr,doreturn !===========================================================! type(calcdata) :: calc type(mddata) :: mddat @@ -71,11 +71,9 @@ subroutine crest_search_newnci(env,tim) call mol%append(stdout) write (stdout,*) -!>--- saftey termination - if(mol%nat .le. 2)then - call catchdiatomic(env) - return - endif +!>--- saftey terminations + call crest_sampling_skip(env,doreturn) + if (doreturn) return !>--- sets the MD length according to a flexibility measure call md_length_setup(env) @@ -121,8 +119,8 @@ subroutine crest_search_newnci(env,tim) call tim%start(2,'Metadynamics (MTD)') call crest_search_multimd(env,mol,mddats,nsim) call tim%stop(2) -!>--- a file called crest_dynamics.trj should have been written - ensnam = 'crest_dynamics.trj' +!>--- a file called crest_dynamics.trj.xyz should have been written + ensnam = 'crest_dynamics.trj.xyz' !>--- deallocate for next iteration if (allocated(mddats)) deallocate (mddats) @@ -130,7 +128,7 @@ subroutine crest_search_newnci(env,tim) !>--- Reoptimization of trajectories call tim%start(3,'Geometry optimization') call optlev_to_multilev(env%optlev,multilevel) - call crest_multilevel_oloop(env,ensnam,multilevel) + call crest_multilevel_oloop(env,ensnam,multilevel,i) call tim%stop(3) if(env%iostatus_meta .ne. 0 ) return diff --git a/src/algos/setuptest.f90 b/src/algos/setuptest.f90 index 720d73c6..f7fe77d4 100644 --- a/src/algos/setuptest.f90 +++ b/src/algos/setuptest.f90 @@ -60,10 +60,10 @@ subroutine trialMD_calculator(env) type(timer) :: profiler type(calcdata) :: tmpcalc - type(calcdata) :: calcstart + type(calcdata),allocatable :: calcstart(:) real(wp) :: energy real(wp),allocatable :: grd(:,:) - integer :: T,Tn + integer :: T,Tn,ii character(len=*),parameter :: dirnam = 'TRIALMD' !>--- OMP settings (should be set to 1 to simulate max parallelization) @@ -88,13 +88,13 @@ subroutine trialMD_calculator(env) if (allocated(env%ref%wbo)) then !> should be allocated from main program MDSTART%shk%wbo = env%ref%wbo else !> otherwise, obtain from scratch - tmpcalc = env%calc + call tmpcalc%copy(env%calc) mol = molstart tmpcalc%calcs(1)%rdwbo = .true. !> obtain WBOs - allocate(grd(3,mol%nat)) + allocate (grd(3,mol%nat)) call engrad(mol,tmpcalc,energy,grd,io) - call move_alloc(tmpcalc%calcs(1)%wbo, env%ref%wbo) - deallocate(grd) + call move_alloc(tmpcalc%calcs(1)%wbo,env%ref%wbo) + deallocate (grd) call tmpcalc%reset() MDSTART%shk%wbo = env%ref%wbo end if @@ -105,8 +105,6 @@ subroutine trialMD_calculator(env) MTD%mtdtype = cv_rmsd MTD%cvdump_fs = 550.0_wp call MDSTART%add(MTD) - calcstart = env%calc !> Save clean state before loop - pr = .false. !> supress stdout printout of MD !>--- Header @@ -116,6 +114,13 @@ subroutine trialMD_calculator(env) !>--- Iterative loop, since it is also tested if the MD runs at all counter = 1 maxiter = 6 + +!>--- temporary clean calculation storage per iteration + allocate (calcstart(maxiter)) + do ii = 1,maxiter + call calcstart(ii)%copy(env%calc) + end do + tstep = MDSTART%tstep shakemode = MDSTART%shk%shake_mode call profiler%init(maxiter) @@ -123,8 +128,7 @@ subroutine trialMD_calculator(env) !>--- Restore initial starting geometry mol = molstart -!>--- Restore clean calculation state - env%calc = calcstart + !>--- Modify MD output trajectory MD = MDSTART MD%tstep = tstep @@ -137,7 +141,7 @@ subroutine trialMD_calculator(env) io = 1 !================================! call profiler%start(counter) - call dynamics(mol,MD,env%calc,pr,io) + call dynamics(mol,MD,calcstart(counter),pr,io) call profiler%stop(counter) !================================! @@ -181,6 +185,8 @@ subroutine trialMD_calculator(env) !>--- End loop end do iterativ + deallocate (calcstart) + !>--- transfer final settings to global settings env%mdstep = MD%tstep env%mddat%tstep = MD%tstep @@ -302,28 +308,28 @@ subroutine trialOPT_calculator(env) !>--- setup call env%ref%to(mol) call env%ref%to(molopt) - allocate(grd(3,mol%nat), source=0.0_wp) - tmpcalc = env%calc !> create copy of calculator - tmpcalc%optlev = -1 !> set loose convergence thresholds + allocate (grd(3,mol%nat),source=0.0_wp) + call tmpcalc%copy(env%calc) !> create copy of calculator + tmpcalc%optlev = -1 !> set loose convergence thresholds !>--- perform geometry optimization - pr = .false. !> stdout printout - wr = .true. !> write crestopt.log - if(wr)then - call remove('crestopt.log') - endif + pr = env%crestver == crest_trialopt + wr = .true. !> write crestopt.log.xyz + if (wr) then + call remove('crestopt.log.xyz') + end if call optimize_geometry(mol,molopt,tmpcalc,energy,grd,pr,wr,io) -!>--- check success +!>--- check success success = (io == 0) call trialOPT_warning(env,molopt,success) !>--- if the checks were successfull, env%ref is overwritten env%ref%nat = molopt%nat env%ref%at = molopt%at env%ref%xyz = molopt%xyz - env%ref%etot = energy + env%ref%etot = energy - deallocate(grd) + deallocate (grd) end subroutine trialOPT_calculator !========================================================================================! @@ -352,7 +358,7 @@ subroutine trialOPT_warning(env,mol,success) if (.not.success) then write (stdout,*) write (stdout,*) ' Initial geometry optimization failed!' - write (stdout,*) ' Please check your input and, if present, crestopt.log.' + write (stdout,*) ' Please check your input and, if present, crestopt.log.xyz.' call creststop(status_failed) end if write (stdout,*) 'Geometry successfully optimized.' @@ -396,7 +402,7 @@ subroutine trialOPT_warning(env,mol,success) if (env%legacy) then write (stdout,'(1x,a)') 'You can check the optimization trajectory in the "xtbopt.log" file.' else - write (stdout,'(1x,a)') 'You can check the optimization trajectory in the "crestopt.log" file.' + write (stdout,'(1x,a)') 'You can check the optimization trajectory in the "crestopt.log.xyz" file.' end if write (stdout,'(1x,a)') 'Try either of these options:' write (stdout,'(/,4x,a)') 'A) Pre-optimize your input seperately and use the optimized' diff --git a/src/algos/singlepoint.f90 b/src/algos/singlepoint.f90 index 6fb67e27..a5ffa8aa 100644 --- a/src/algos/singlepoint.f90 +++ b/src/algos/singlepoint.f90 @@ -35,6 +35,7 @@ subroutine crest_singlepoint(env,tim) use crest_calculator use strucrd use gradreader_module,only:write_engrad + use iomod,only:colorify implicit none type(systemdata),intent(inout) :: env type(timer),intent(inout) :: tim @@ -52,13 +53,14 @@ subroutine crest_singlepoint(env,tim) character(len=*),parameter :: partial = '∂E/∂' !========================================================================================! write (stdout,*) - !call system('figlet singlepoint') - write (stdout,*) " _ _ _ _ " - write (stdout,*) " ___(_)_ __ __ _| | ___ _ __ ___ (_)_ __ | |_ " - write (stdout,*) "/ __| | '_ \ / _` | |/ _ \ '_ \ / _ \| | '_ \| __|" - write (stdout,*) "\__ \ | | | | (_| | | __/ |_) | (_) | | | | | |_ " - write (stdout,*) "|___/_|_| |_|\__, |_|\___| .__/ \___/|_|_| |_|\__|" - write (stdout,*) " |___/ |_| " + write (stdout,*) " ------------------------------------------------------------------ " + write (stdout,*) " #### # # # #### # ###### ##### #### # # # ##### " + write (stdout,*) " # # ## # # # # # # # # # # ## # # " + write (stdout,*) " #### # # # # # # ##### # # # # # # # # # " + write (stdout,*) " # # # # # # ### # # ##### # # # # # # # " + write (stdout,*) " # # # # ## # # # # # # # # # ## # " + write (stdout,*) " #### # # # #### ###### ###### # #### # # # # " + write (stdout,*) " ------------------------------------------------------------------ " write (stdout,*) !========================================================================================! call new_ompautoset(env,'max',0,T,Tn) @@ -76,7 +78,7 @@ subroutine crest_singlepoint(env,tim) write (stdout,'(a)') allocate (grad(3,mol%nat),source=0.0_wp) - calc = env%calc + call calc%copy(env%calc) calc%calcs(:)%prstdout = .true. !>--- print some info about the calculation @@ -183,7 +185,7 @@ subroutine crest_xtbsp(env,xtblevel,molin) !>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<---- read the input ensemble call rdensembleparam(ensnam,nat,nall) - if (nall .lt. 1)then + if (nall .lt. 1) then write (stdout,*) '**ERROR** empty ensemble file.' env%iostatus_meta = status_input return - endif + end if allocate (xyz(3,nat,nall),at(nat),eread(nall)) call rdensemble(ensnam,nat,nall,at,xyz,eread) !>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<. +!================================================================================! + +!========================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! +!> Implementation for standalone sorting +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! +!========================================================================================! +!> Input/Output: +!> env - crest's systemdata object +!> tim - timer object +!>----------------------------------------------- +subroutine crest_sort(env,tim) + use crest_parameters + use crest_data + use crest_calculator + use strucrd + use cregen_interface + use iomod,only:catdel + implicit none + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + type(coord) :: mol,molnew + integer :: i,j,k,l,io,ich + logical :: pr,wr + external :: CCEGEN +!========================================================================================! + integer :: nall + type(coord),allocatable :: structures(:) + integer,allocatable :: groups(:) + +!========================================================================================! + select case (env%sortmode) + case default + write (stdout,'(a,a,a)',advance='no') '> Read ensemble ',trim(env%ensemblename),' ... ' + flush (stdout) + call rdensemble(env%ensemblename,nall,structures) + allocate (groups(nall),source=0) + write (stdout,'(i0,a)') nall,' structures!' + case ('irmsd','rmsd','hrmsd') + write (stdout,'(a,a)',advance='no') '> Reading files ',trim(env%ensemblename) + flush (stdout) + write (stdout,'(a,a)') ' and ',trim(env%ensemblename2) + case ('cluster') + continue !> ccegen reads the ensemble internally + end select + write (stdout,*) + + env%confgo = .true. +!========================================================================================! + call tim%start(11,'Sorting') + + select case (env%sortmode) + + case ('rmsd') + call quick_rmsd_tool(trim(env%ensemblename),trim(env%ensemblename2),.false.) + stop + + case ('hrmsd') + call quick_rmsd_tool(trim(env%ensemblename),trim(env%ensemblename2),.true.) + stop + + case ('irmsd') + call irmsd_tool(trim(env%ensemblename),trim(env%ensemblename2),env%iinversion) + stop + + case ('compare') + call compare_ensembles(env) + + case ('isort') +!>--- Assigning structures to conformers based on RTHR,with canonical atom IDs + call underline('Assigning conformers based on iRMSD and RTHR') + call cregen_irmsd_sort(env,nall,structures,groups,allcanon=.true.,printlvl=2) + + case ('isort_noid') +!>--- Assigning structures to conformers based on RTHR, WITHOUT canonical atom IDs + call underline('Assigning conformers based on iRMSD and RTHR') + call cregen_irmsd_sort(env,nall,structures,groups,allcanon=.false.,printlvl=2) + + case ('all','allpair') +!>--- all unique pairs of the ensemble (only suitable for small ensembles) + call underline('Running all unique pair RMSDs incl. atom permutation') + call cregen_irmsd_all(nall,structures,printlvl=2,iinversion=env%iinversion) + + case ('cregen') +!>--- the original CREGEN procedure + call newcregen(env,structurelist=structures) + call catdel('cregen.out.tmp') + + case ('cluster') +!>--- PCA and k-means clustering + call CCEGEN(env,.true.,env%ensemblename) + + case default +!>--- all unique pairs of the ensemble (only suitable for small ensembles) + call cregen_irmsd_sort(env,nall,structures,groups,allcanon=.true.,printlvl=2) + end select + +!========================================================================================! + call tim%stop(11) + if (allocated(structures)) deallocate (structures) + return +end subroutine crest_sort + +!=========================================================================================! + +subroutine irmsd_tool(fname1,fname2,iinversion) +!******************************************************* +!* irmsd_tool +!* Standalone implementation to compare two structures +!* with the iRMSD method. +!* This implementation should be called only on its own, +!* for ensemble-based processing see the CREGEN file +!******************************************************* + use crest_parameters + use strucrd + use axis_module + use irmsd_module + use canonical_mod + implicit none + character(len=*),intent(in) :: fname1 + character(len=*),intent(in) :: fname2 + integer,intent(in) :: iinversion + type(coord) :: mol,ref + real(wp) :: rmsdval,tmpd(3),tmpdist + integer :: i,ich + type(rmsd_cache) :: rcache + type(canonical_sorter) :: canmol + type(canonical_sorter) :: canref + logical :: mirror + logical,parameter :: debug = .false. + + write (stdout,*) 'iRMSD algorithm' + write (stdout,*) 'reference: ',trim(fname1) + write (stdout,*) 'processed: ',trim(fname2) + write (stdout,*) + + !> read the geometries + call ref%open(trim(fname1)) + call mol%open(trim(fname2)) + + !> move ref to CMA and align rotational axes + call axis(ref%nat,ref%at,ref%xyz) + + !> allocate memory + call rcache%allocate(ref%nat) + + !> canonical atom ranks + call canref%init(ref,invtype='apsp+',heavy=.false.) + !call canref%add_h_ranks(ref) + rcache%stereocheck = .not. (canref%hasstereo(ref)) + call canref%shrink() + write (stdout,*) 'false enantiomers possible?: ',rcache%stereocheck + select case (iinversion) + case (0) + mirror = .true. + case (1) + mirror = .true. + rcache%stereocheck = .true. + case (2) + mirror = .false. + rcache%stereocheck = .false. + end select + write (stdout,*) 'allow inversion?: ',mirror + + call canmol%init(mol,invtype='apsp+',heavy=.false.) + !call canmol%add_h_ranks(mol) + call canmol%shrink() + + !> check if we can work with the determined ranks + if (checkranks(ref%nat,canref%rank,canmol%rank)) then + write (stdout,*) 'using canonical atom identities as rank backend' + rcache%rank(:,1) = canref%rank(:) + rcache%rank(:,2) = canmol%rank(:) + if (debug) then + write (*,*) 'iRMSD ranks:' + write (*,*) 'atom',' rank('//fname1//')',' rank('//fname2//')' + do i = 1,ref%nat + write (*,*) i,rcache%rank(i,1),rcache%rank(i,2) + end do + write (*,*) + end if + else + !> if not, fall back to atom types + write (stdout,*) 'using atom types as rank backend' + call fallbackranks(ref,mol,ref%nat,rcache%rank) + end if + + call min_rmsd(ref,mol,rcache=rcache,rmsdout=rmsdval,align=.true.) + + !> write the rotated and shifted coordinates to one file + open (newunit=ich,file='irmsd.xyz') + call ref%append(ich) + call mol%append(ich) + close (ich) + write (stdout,*) + write (stdout,*) 'aligned structures written to irmsd.xyz' + write (stdout,*) + + do i = 1,mol%nat + tmpd(:) = (mol%xyz(:,i)-ref%xyz(:,i))**2 + tmpdist = sqrt(sum(tmpd(:)))*autoaa + if (tmpdist > 0.01_wp) then + write (*,*) i,mol%at(i),tmpdist + end if + end do + + rmsdval = rmsdval*autoaa + write (*,'(1x,a,f16.8)') 'Calculated iRMSD (Å):',rmsdval + + return +end subroutine irmsd_tool + diff --git a/src/algos/term_ui.f90 b/src/algos/term_ui.f90 new file mode 100644 index 00000000..b9c2313e --- /dev/null +++ b/src/algos/term_ui.f90 @@ -0,0 +1,516 @@ +module term_ui + use crest_parameters + use iso_c_binding,only:c_int + implicit none + private + + interface + integer(c_int) function c_isatty(fd) bind(c,name="isatty") + use iso_c_binding,only:c_int + integer(c_int),value :: fd + end function c_isatty + end interface + + !> Public API + public :: ansi_enabled,set_ansi_enabled + public :: fg,bg,style,reset,strip_ansi + public :: printc,eprintc + public :: progress_state + public :: progress_init,progress_update,progress_finish + public :: clear_line + + !> ============================================================ + !> Configuration + !> ============================================================ + logical :: ansi_enabled = .true. + + integer,parameter :: C_RESET = -1 + + !> Basic 8-color codes (foreground base 30..37, background base 40..47) + integer,parameter,public :: & + & BLACK = 0,RED = 1,GREEN = 2,YELLOW = 3, & + & BLUE = 4,MAGENTA = 5,CYAN = 6,WHITE = 7 + + !> Style SGR codes + integer,parameter,public :: & + & S_BOLD = 1, & + & S_DIM = 2, & + & S_UNDERLINE = 4, & + & S_BLINK = 5, & + & S_REVERSE = 7, & + & S_HIDDEN = 8 + + !> ============================================================ + !> Progress state + !> ============================================================ + type :: progress_state + real(wp) :: t0 = 0.0_wp + integer :: width = 40 + character(:),allocatable :: prefix + character(:),allocatable :: suffix + character(:),allocatable :: fill_char + character(:),allocatable :: empty_char + character(:),allocatable :: left_cap + character(:),allocatable :: right_cap + integer(int64) :: last_draw_ms = -huge(0_int64) + integer(int64) :: min_interval_ms = 50_int64 !> throttle redraw + logical :: show_time = .true. + logical :: show_eta = .true. + logical :: started = .false. + logical :: tty = .false. !> auto-set by progress_init + real(wp) :: increment = 10.0_wp !> % step for non-TTY mode + real(wp) :: barrier = 0.0_wp !> current % threshold for non-TTY mode + end type progress_state + +!========================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================! + + !> ============================================================ + !> Time helper (seconds as wp) - based on system_clock + !> ============================================================ + function now_seconds() result(t) + real(wp) :: t + integer(int64) :: count,rate + call system_clock(count=count,count_rate=rate) + if (rate > 0_int64) then + t = real(count,wp)/real(rate,wp) + else + t = 0.0_wp + end if + end function now_seconds + + function now_millis_i64() result(ms) + integer(int64) :: ms + integer(int64) :: count,rate + call system_clock(count=count,count_rate=rate) + if (rate > 0_int64) then + ms = int((real(count,wp)*1000.0_wp)/real(rate,wp),int64) + else + ms = 0_int64 + end if + end function now_millis_i64 + + subroutine set_ansi_enabled(flag) + logical,intent(in) :: flag + ansi_enabled = flag + end subroutine set_ansi_enabled + + !> ============================================================ + !> ANSI builders + !> ============================================================ + pure function sgr(code) result(s) + integer,intent(in) :: code + character(:),allocatable :: s + if (.not.ansi_enabled) then + s = "" + else + s = achar(27)//"["//itoa(code)//"m" + end if + end function sgr + + pure function reset() result(s) + character(:),allocatable :: s + s = sgr(0) + end function reset + + pure function fg(color,bright) result(s) + integer,intent(in) :: color + logical,intent(in),optional :: bright + character(:),allocatable :: s + integer :: base + logical :: b + if (.not.ansi_enabled) then + s = "" + return + end if + b = .false.; if (present(bright)) b = bright + if (color == C_RESET) then + s = achar(27)//"[39m" + else + base = 30+max(0,min(7,color)) + if (b) then + !> "Bright" variant using 90..97 (widely supported) + base = 90+max(0,min(7,color)) + end if + s = achar(27)//"["//itoa(base)//"m" + end if + end function fg + + pure function bg(color,bright) result(s) + integer,intent(in) :: color + logical,intent(in),optional :: bright + character(:),allocatable :: s + integer :: base + logical :: b + if (.not.ansi_enabled) then + s = "" + return + end if + b = .false.; if (present(bright)) b = bright + if (color == C_RESET) then + s = achar(27)//"[49m" + else + base = 40+max(0,min(7,color)) + if (b) then + base = 100+max(0,min(7,color)) + end if + s = achar(27)//"["//itoa(base)//"m" + end if + end function bg + + pure function style(code) result(s) + integer,intent(in) :: code + character(:),allocatable :: s + s = sgr(code) + end function style + + !> ============================================================ + !> Printing helpers + !> ============================================================ + subroutine printc(msg,unit,advance) + character(len=*),intent(in) :: msg + integer,intent(in),optional :: unit + logical,intent(in),optional :: advance + integer :: u + logical :: adv + u = stdout; if (present(unit)) u = unit + adv = .true.; if (present(advance)) adv = advance + if (adv) then + write (u,'(a)') msg + else + write (u,'(a)',advance='no') msg + end if + end subroutine printc + + subroutine eprintc(msg,advance) + character(len=*),intent(in) :: msg + logical,intent(in),optional :: advance + call printc(msg,unit=stderr,advance=advance) + end subroutine eprintc + + !> Clears current line and returns carriage to start (no newline). + pure function clear_line() result(s) + character(:),allocatable :: s + if (.not.ansi_enabled) then + s = achar(13) !> CR only + else + !> CR + "erase line" (EL2) is common; some use EL0. We'll do EL2. + s = achar(13)//achar(27)//"[2K" + end if + end function clear_line + + !> ============================================================ + !> Progress bar API + !> ============================================================ + subroutine progress_init(ps,total,width,prefix,suffix,min_interval_ms,show_time,show_eta, & + fill_char,empty_char,left_cap,right_cap) + !*********************************************************** + !* Initialise a progress_state object. + !* Automatically detects whether stdout is a TTY and selects + !* either the fancy ANSI bar or the sequential |>x% mode. + !* total: total number of steps (used to calibrate the non-TTY + !* print interval). + !*********************************************************** + implicit none + type(progress_state),intent(inout) :: ps + integer,intent(in),optional :: total + integer,intent(in),optional :: width + character(len=*),intent(in),optional :: prefix,suffix + integer(int64),intent(in),optional :: min_interval_ms + logical,intent(in),optional :: show_time,show_eta + character(len=*),intent(in),optional :: fill_char,empty_char,left_cap,right_cap + + ps%t0 = now_seconds() + ps%started = .true. + + if (present(width)) ps%width = max(5,width) + if (present(min_interval_ms)) ps%min_interval_ms = max(0_int64,min_interval_ms) + if (present(show_time)) ps%show_time = show_time + if (present(show_eta)) ps%show_eta = show_eta + + if (present(prefix)) then + ps%prefix = prefix + else + ps%prefix = "" + end if + + if (present(suffix)) then + ps%suffix = suffix + else + ps%suffix = "" + end if + + if (present(fill_char)) then + ps%fill_char = fill_char + else + ps%fill_char = "█" + end if + + if (present(empty_char)) then + ps%empty_char = empty_char + else + ps%empty_char = "░" + end if + + if (present(left_cap)) then + ps%left_cap = left_cap + else + ps%left_cap = "[" + ps%left_cap = "┃" + end if + + if (present(right_cap)) then + ps%right_cap = right_cap + else + ps%right_cap = "]" + ps%right_cap = "┃" + end if + + ps%last_draw_ms = -huge(0_int64) + + ! ── TTY detection & non-TTY state ──────────────────────────── + ps%tty = (c_isatty(1_c_int) /= 0) + ps%barrier = 0.0_wp + ps%increment = 10.0_wp + if (.not.ps%tty .and. present(total)) then + if (total > 20000) ps%increment = 1.0_wp + if (total > 10000 .and. total <= 20000) ps%increment = 2.5_wp + if (total > 5000 .and. total <= 10000) ps%increment = 5.0_wp + if (total > 1000 .and. total <= 5000) ps%increment = 7.5_wp + end if + end subroutine progress_init + + subroutine progress_update(ps,curr,tot,unit,force) + !*********************************************************** + !* Update the progress display. + !* TTY: renders the in-place ANSI bar. + !* non-TTY: prints sequential |>x% markers. + !*********************************************************** + implicit none + type(progress_state),intent(inout) :: ps + integer,intent(in) :: curr,tot + integer,intent(in),optional :: unit + logical,intent(in),optional :: force + + integer :: u + integer(int64) :: current,total + logical :: do_force + integer(int64) :: ms,elapsed_ms + real(wp) :: frac,elapsed_s,rate,eta_s + integer :: filled,nfilled + character(:),allocatable :: line,pct,time_str,eta_str + character(:),allocatable :: bar + real(wp) :: pct_r + character(len=5) :: atmp + + if (.not.ps%started) call progress_init(ps) + + current = int(curr,int64) + total = int(tot,int64) + u = stdout; if (present(unit)) u = unit + do_force = .false.; if (present(force)) do_force = force + + if (ps%tty) then + ! ── Fancy in-place ANSI bar ─────────────────────────────────── + if (current == total) do_force = .true. + + ms = now_millis_i64() + if (.not.do_force) then + if (ps%last_draw_ms >= 0_int64) then + if (ms-ps%last_draw_ms < ps%min_interval_ms) return + end if + end if + ps%last_draw_ms = ms + + if (total <= 0_int64) then + frac = 0.0_wp + else + frac = real(max(0_int64,min(current,total)),wp)/real(total,wp) + end if + + filled = int(frac*real(ps%width,wp)) + filled = max(0,min(ps%width,filled)) + nfilled = max(0,(ps%width-filled)) + + pct = fmt_percent(frac) + + elapsed_s = now_seconds()-ps%t0 + elapsed_ms = int(elapsed_s*1000.0_wp,int64) + + if (ps%show_time) then + time_str = " "//dim_text("("//fmt_hms(elapsed_ms)//")") + else + time_str = "" + end if + + if (ps%show_eta.and.current > 0_int64.and.total > 0_int64.and.current < total) then + rate = real(current,wp)/max(1.0e-12_wp,elapsed_s) + eta_s = real(total-current,wp)/max(1.0e-12_wp,rate) + eta_str = " "//dim_text("ETA "//fmt_hms(int(eta_s*1000.0_wp,int64))) + else if (ps%show_eta.and.total > 0_int64.and.current >= total) then + eta_str = " "//dim_text("ETA 00:00") + else + eta_str = "" + end if + + if (ansi_enabled) then + if (frac >= 0.999_wp) then + bar = ps%left_cap// & + & fg(GREEN,bright=.true.)//repeat(ps%fill_char,filled)//reset()// & + & repeat(ps%empty_char,nfilled)//ps%right_cap + else + bar = ps%left_cap// & + & fg(GREEN,bright=.false.)//repeat(ps%fill_char,filled)//reset()// & + & fg(YELLOW,bright=.false.)//repeat(ps%empty_char,nfilled)//reset()// & + & ps%right_cap + end if + else + bar = ps%left_cap//repeat(ps%fill_char,filled)// & + & repeat(ps%empty_char,nfilled)//ps%right_cap + end if + + line = clear_line()//ps%prefix//bar//" "//pct//ps%suffix//eta_str//time_str + + call printc(line,unit=u,advance=.false.) + call flush_unit(u) + + else + ! ── non-TTY: sequential |>x% printout ──────────────────────── + if (current <= 0_int64) return + if (total <= 0_int64) return + pct_r = real(current,wp)/real(total,wp)*100.0_wp + if (pct_r >= ps%barrier) then + write (atmp,'(f5.1)') pct_r + write (u,'(1x,a)',advance='no') '|>'//trim(adjustl(atmp))//'%' + ps%barrier = min(ps%barrier+ps%increment,100.0_wp) + call flush_unit(u) + end if + + end if + end subroutine progress_update + + subroutine progress_finish(ps,unit,newline) + !*********************************************************** + !* Finalise the progress display. + !* TTY: prints a terminating newline. + !* non-TTY: prints a newline followed by " done." to close the + !* inline |>x% sequence. + !*********************************************************** + implicit none + type(progress_state),intent(inout) :: ps + integer,intent(in),optional :: unit + logical,intent(in),optional :: newline + integer :: u + logical :: nl + + u = stdout; if (present(unit)) u = unit + nl = .true.; if (present(newline)) nl = newline + + if (ps%tty) then + if (nl) call printc("",unit=u,advance=.true.) + else + if (nl) write (u,'(/,1x,a)') 'done.' + end if + call flush_unit(u) + ps%started = .false. + end subroutine progress_finish + + !> ============================================================ + !> Utilities: formatting, flushing, ANSI stripping + !> ============================================================ + subroutine flush_unit(u) + integer,intent(in) :: u + flush (u) + end subroutine flush_unit + + pure function itoa(i) result(s) + integer,intent(in) :: i + character(:),allocatable :: s + character(len=64) :: buf + write (buf,'(i0)') i + s = trim(buf) + end function itoa + + pure function itoa_i64(i) result(s) + integer(int64),intent(in) :: i + character(:),allocatable :: s + character(len=64) :: buf + write (buf,'(i0)') i + s = trim(buf) + end function itoa_i64 + + pure function fmt_percent(frac) result(s) + real(wp),intent(in) :: frac + character(:),allocatable :: s + integer :: p + real(wp) :: rp + character(8) :: buf + rp = min(100.0_wp*max(0.0_wp,min(1.0_wp,frac))+0.5_wp,100.0_wp) + write (buf,'(f5.1,a)') rp,"%" + s = adjustl(buf) + end function fmt_percent + + pure function fmt_hms(ms) result(s) + integer(int64),intent(in) :: ms + character(:),allocatable :: s + integer(int64) :: t,hh,mm,ss + character(32) :: buf + + t = max(0_int64,ms/1000_int64) + hh = t/3600_int64 + mm = (t-hh*3600_int64)/60_int64 + ss = t-hh*3600_int64-mm*60_int64 + + if (hh > 0_int64) then + write (buf,'(i0,":",i2.2,":",i2.2)') hh,int(mm),int(ss) + else + write (buf,'(i2.2,":",i2.2)') int(mm),int(ss) + end if + s = trim(buf) + end function fmt_hms + + pure function dim_text(t) result(s) + character(len=*),intent(in) :: t + character(:),allocatable :: s + if (.not.ansi_enabled) then + s = t + else + s = style(S_DIM)//t//reset() + end if + end function dim_text + + pure function strip_ansi(s_in) result(s_out) + character(len=*),intent(in) :: s_in + character(:),allocatable :: s_out + integer :: i,n + character :: c + logical :: in_esc + in_esc = .false. + s_out = "" + n = len_trim(s_in) + + i = 1 + do while (i <= n) + c = s_in(i:i) + if (.not.in_esc) then + if (c == achar(27)) then + in_esc = .true. + else + s_out = s_out//c + end if + else + !> We are inside ESC[ ... m ; consume until 'm' or end + if (c == "m") then + in_esc = .false. + end if + end if + i = i+1 + end do + end function strip_ansi + +!=============================================================================! +!#############################################################################! +!=============================================================================! +end module term_ui + diff --git a/src/axis_module.f90 b/src/axis_module.f90 index f6e7379a..e03952dd 100644 --- a/src/axis_module.f90 +++ b/src/axis_module.f90 @@ -75,6 +75,8 @@ module axis_module end interface cma public :: CMAtrf + public :: uniqueax + !========================================================================================! !========================================================================================! contains !> MODULE PROCEDURES START HERE @@ -95,15 +97,15 @@ module axis_module !========================================================================================! subroutine axis_0(nat,at,coord,rot,avmom,evec) implicit none - integer :: nat - integer :: at(nat) - real(wp) :: coord(3,nat) - real(wp) :: rot(3),avmom,evec(3,3) + integer,intent(in) :: nat + integer,intent(in) :: at(nat) + real(wp),intent(in) :: coord(3,nat) + real(wp),intent(out) :: rot(3),avmom,evec(3,3) real(wp) :: a(3,3) real(wp) :: t(6),xyzmom(3),eig(3) !real(wp) :: x(nat),y(nat),z(nat) - real(wp),allocatable :: x(:),y(:),z(:) - real(wp) :: atmass + !real(wp),allocatable :: x(:),y(:),z(:) + real(wp) :: atmass,shift(3) integer :: i,j !************************************************************************ !* const1 = 10**40/(n*a*a) @@ -116,8 +118,9 @@ subroutine axis_0(nat,at,coord,rot,avmom,evec) !> first we move the molecule to the CMA !> this depends on the isotopic masses, and the cartesian geometry. !> - allocate (x(nat),y(nat),z(nat),source=0.0_wp) - call CMA(nat,at,coord,x,y,z) +! allocate (x(nat),y(nat),z(nat),source=0.0_wp) +! call CMA(nat,at,coord,x,y,z) + call CMAshift(nat,at,coord,shift) !************************************************************************ !* matrix for moments of inertia is of form @@ -133,12 +136,18 @@ subroutine axis_0(nat,at,coord,rot,avmom,evec) end do do i = 1,nat atmass = ams(at(i)) - t(1) = t(1) + atmass * (y(i)**2 + z(i)**2) - t(2) = t(2) - atmass * x(i) * y(i) - t(3) = t(3) + atmass * (z(i)**2 + x(i)**2) - t(4) = t(4) - atmass * z(i) * x(i) - t(5) = t(5) - atmass * y(i) * z(i) - t(6) = t(6) + atmass * (x(i)**2 + y(i)**2) +! t(1) = t(1) + atmass * (y(i)**2 + z(i)**2) +! t(2) = t(2) - atmass * x(i) * y(i) +! t(3) = t(3) + atmass * (z(i)**2 + x(i)**2) +! t(4) = t(4) - atmass * z(i) * x(i) +! t(5) = t(5) - atmass * y(i) * z(i) +! t(6) = t(6) + atmass * (x(i)**2 + y(i)**2) + t(1) = t(1) + atmass * ((coord(2,i)-shift(2))**2 + (coord(3,i)-shift(3))**2) + t(2) = t(2) - atmass * (coord(1,i)-shift(1)) * (coord(2,i)-shift(2)) + t(3) = t(3) + atmass * ((coord(3,i)-shift(3))**2 + (coord(1,i)-shift(1))**2) + t(4) = t(4) - atmass * (coord(3,i)-shift(3)) * (coord(1,i)-shift(1)) + t(5) = t(5) - atmass * (coord(2,i)-shift(2)) * (coord(3,i)-shift(3)) + t(6) = t(6) + atmass * ((coord(1,i)-shift(1))**2 + (coord(2,i)-shift(2))**2) a(1,1) = t(1) a(2,1) = t(2) a(1,2) = t(2) @@ -149,7 +158,7 @@ subroutine axis_0(nat,at,coord,rot,avmom,evec) a(2,3) = t(5) a(3,3) = t(6) end do - deallocate (z,y,x) +! deallocate (z,y,x) evec = 0.0_wp eig = 0.0_wp @@ -241,12 +250,14 @@ subroutine axis_2(pr,nat,at,coord,eax) end subroutine axis_2 !========================================================================================! -!> subroutine axis_3 -!> axis routine that orients the molecule along the -!> calculated principle axes and shifts it to CMA. -!> new geometry is written to coordout. -!>--------------------------------------------- + subroutine axis_3(nat,at,coord,coordout,rot) +!**************************************************** +!* subroutine axis_3 +!* axis routine that orients the molecule along the +!* calculated principle axes and shifts it to CMA. +!* new geometry is written to coordout. +!**************************************************** implicit none integer,intent(in) :: nat integer,intent(in) :: at(nat) @@ -293,24 +304,50 @@ subroutine axis_3(nat,at,coord,coordout,rot) end subroutine axis_3 !========================================================================================! -!> subroutine axis_4 -!> axis routine that orients the molecule along the -!> calculated principle axes and shifts it to CMA. -!> new geometry OVERWRITES input. -!>-------------------------------- - subroutine axis_4(nat,at,coord) + + subroutine axis_4(nat,at,coord,rotconst) +!**************************************************** +!* subroutine axis_4 +!* axis routine that orients the molecule along the +!* calculated principle axes and shifts it to CMA. +!* new geometry OVERWRITES input. +!* Optimized for minimal allocation overhead. +!**************************************************** implicit none integer,intent(in) :: nat integer,intent(in) :: at(nat) real(wp),intent(inout) :: coord(3,nat) - real(wp) :: rot(3) - real(wp),allocatable :: coordtmp(:,:) + real(wp),intent(out),optional :: rotconst(3) + real(wp) :: coordtmp(3),shift(3) + real(wp) :: rot(3),avmom,evec(3,3) + integer :: i,j,k + real(wp) :: xsum + call axis_0(nat,at,coord,rot,avmom,evec) + call CMAshift(nat,at,coord,shift) + do i=1,nat + coord(:,i) = coord(:,i) - shift(:) + enddo + !> do the trafo (chirality is preserved) + xsum = calcxsum(evec) + if (xsum .lt. 0.0_wp) then + do j = 1,3 + evec(j,1) = -evec(j,1) + end do + end if - allocate (coordtmp(3,nat)) - !> call axis routine - call axis_3(nat,at,coord,coordtmp,rot) - coord = coordtmp - deallocate (coordtmp) + do i = 1,nat + coordtmp(:) = coord(:,i) + do j = 1,3 + xsum = 0.0_wp + do k = 1,3 + xsum = xsum + coordtmp(k) * evec(k,j) + end do + coord(j,i) = xsum + end do + end do + if(present(rotconst))then + rotconst(:) = rot(:) + endif return end subroutine axis_4 @@ -378,10 +415,43 @@ real(wp) function calcxsum(evec) end function calcxsum !========================================================================================! -!> subroutine CMA -!> calculate CMA-shifted coordinates x y z -!>-------------------------------------- + + subroutine uniqueax(rot,unique,thr) +!************************************************** +!* check if a given rotational constant is unique +!************************************************** + implicit none + real(wp),intent(in) :: rot(3) + logical,intent(out) :: unique(3) + real(wp),intent(in),optional :: thr + real(wp) :: thrtmp + real(wp) :: diff(3) + + unique(:) = .false. + + if(present(thr))then + thrtmp = thr + else + thrtmp = 0.01_wp + endif + + diff(1) = abs(rot(2)/rot(1) - 1.0_wp) + diff(2) = abs(rot(3)/rot(1) - 1.0_wp) + diff(3) = abs(rot(3)/rot(2) - 1.0_wp) + + if(diff(1) .gt. thrtmp .and. diff(2) .gt. thrtmp) unique(1) = .true. + if(diff(1) .gt. thrtmp .and. diff(3) .gt. thrtmp) unique(2) = .true. + if(diff(2) .gt. thrtmp .and. diff(3) .gt. thrtmp) unique(3) = .true. + + end subroutine uniqueax + +!========================================================================================! + subroutine CMAxyz(nat,at,coord,x,y,z) +!******************************************** +!* subroutine CMA +!* calculate CMA-shifted coordinates x y z +!******************************************** implicit none integer,intent(in) :: nat integer,intent(in) :: at(nat) @@ -411,6 +481,40 @@ subroutine CMAxyz(nat,at,coord,x,y,z) return end subroutine CMAxyz +!========================================================================================! + + subroutine CMAshift(nat,at,coord,shift) +!********************************************************* +!* subroutine CMAshift +!* calculate the shift vector to shift a molecule to CMA +!********************************************************* + implicit none + integer,intent(in) :: nat + integer,intent(in) :: at(nat) + real(wp),intent(in) :: coord(3,nat) + real(wp),intent(out) :: shift(3) + integer :: i + real(wp) :: sumw,sumwx,sumwy,sumwz,atmass + sumw = 1.d-20 + sumwx = 0.d0 + sumwy = 0.d0 + sumwz = 0.d0 + do i = 1,nat + atmass = ams(at(i)) + sumw = sumw + atmass + sumwx = sumwx + atmass * coord(1,i) + sumwy = sumwy + atmass * coord(2,i) + sumwz = sumwz + atmass * coord(3,i) + end do + sumwx = sumwx / sumw + sumwy = sumwy / sumw + sumwz = sumwz / sumw + shift(1) = sumwx + shift(2) = sumwy + shift(3) = sumwz + return + end subroutine CMAshift + !========================================================================================! !> subroutine CMAtrf !> calculate a shift to the first nat0 atoms' CMA diff --git a/src/basinhopping/CMakeLists.txt b/src/basinhopping/CMakeLists.txt new file mode 100644 index 00000000..6a29ef85 --- /dev/null +++ b/src/basinhopping/CMakeLists.txt @@ -0,0 +1,35 @@ +# This file is part of crest. +# SPDX-Identifier: LGPL-3.0-or-later +# +# crest is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# crest is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with crest. If not, see . + +set(dir "${CMAKE_CURRENT_SOURCE_DIR}") + +list(APPEND srcs + "${dir}/algo.f90" + "${dir}/basinhopping.f90" + "${dir}/class.f90" + "${dir}/mc.f90" + "${dir}/takestep.f90" +) + +set(srcs ${srcs} PARENT_SCOPE) + + + + + + + + diff --git a/src/basinhopping/algo.f90 b/src/basinhopping/algo.f90 new file mode 100644 index 00000000..c800fc67 --- /dev/null +++ b/src/basinhopping/algo.f90 @@ -0,0 +1,335 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2024 Philipp Pracht, David Wales +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module bh_algo_interface + implicit none + interface + subroutine single_basinhopping_core(env,mol,calc,structuredump) + use crest_data + use crest_calculator + use strucrd + implicit none + type(systemdata),intent(inout) :: env + type(coord),intent(inout) :: mol + type(coord),allocatable,intent(inout) :: structuredump(:) + type(calcdata),intent(inout) :: calc + end subroutine single_basinhopping_core + subroutine parallel_basinhopping_core(env,mol,calc,structuredump) + use crest_data + use crest_calculator + use strucrd + implicit none + !> INPUT/OUTPUT + type(systemdata),intent(inout) :: env + type(coord),intent(inout) :: mol + type(coord),allocatable,intent(inout) :: structuredump(:) + type(calcdata),intent(in) :: calc + end subroutine parallel_basinhopping_core + end interface +end module bh_algo_interface + +!================================================================================! +!================================================================================! +!================================================================================! + +subroutine crest_basinhopping(env,tim) + use crest_parameters + use crest_data + use crest_calculator + use strucrd + use cregen_interface,only:unionizeEnsembles,cregen_irmsd_sort + use optimize_module + use bh_module + use bh_algo_interface + implicit none + type(systemdata),intent(inout) :: env + type(timer),intent(inout) :: tim + type(coord) :: mol,molnew + integer :: i,j,k,l,io,ich,T,Tn,mciter + logical :: pr,wr +!========================================================================================! + type(calcdata) :: calc + + real(wp) :: energy,gnorm + real(wp),allocatable :: grad(:,:) + integer :: nall + type(coord),allocatable :: structuredump(:) + integer,allocatable :: groups(:) + logical :: parallel + character(len=80) :: atmp + character(len=*),parameter :: trjf = 'crest_quenched.xyz' +!========================================================================================! + write (stdout,*) + write (stdout,*) " ____ _ _ _ _ " + write (stdout,*) "| __ ) __ _ ___(_)_ __ | | | | ___ _ __ _ __ (_)_ __ __ _ " + write (stdout,*) "| _ \ / _` / __| | '_ \| |_| |/ _ \| '_ \| '_ \| | '_ \ / _` |" + write (stdout,*) "| |_) | (_| \__ \ | | | | _ | (_) | |_) | |_) | | | | | (_| |" + write (stdout,*) "|____/ \__,_|___/_|_| |_|_| |_|\___/| .__/| .__/|_|_| |_|\__, |" + write (stdout,*) " |_| |_| |___/ " + write (stdout,*) "" + call new_ompautoset(env,'max',0,T,Tn) + call ompprint_intern() + + calc = env%calc + call env%ref%to(mol) + write (stdout,*) + write (stdout,*) 'Input structure:' + call mol%append(stdout) + write (stdout,*) +!========================================================================================! +!>--- print calculation info + call calc%info(stdout) + write (stdout,'(a)') '> Geometry optimization settings:' + call print_opt_data(calc,stdout,natoms=mol%nat,tag=' : ') + write (stdout,*) + +!>--- singlepoint of input structure + allocate (grad(3,mol%nat),source=0.0_wp) + call engrad(mol,calc,energy,grad,io) + mol%energy = energy !> we need this to start the Markov-chain + +!==========================================================================================! + parallel = .false. + if (allocated(env%bh_ref)) then + parallel = env%bh_ref%parallel + end if + +!=========================================================================================! + call tim%start(14,'Basin-Hopping (BH)') + + if (parallel) then + call parallel_basinhopping_core(env,mol,calc,structuredump) + else + call single_basinhopping_core(env,mol,calc,structuredump) + end if +!>--- dump saved minima + nall = size(structuredump,1) + open (newunit=ich,file=trjf) + call wrensemble(ich,nall,structuredump) + close (ich) + + if (io == 0) then + write (stdout,*) + write (stdout,*) 'BH run completed successfully' + write (stdout,*) 'Successfull quenches written to ',trjf + else + write (stdout,*) 'WARNING: BH run terminated ABNORMALLY' + env%iostatus_meta = status_failed + end if + call tim%stop(14) + + write (stdout,*) + call smallhead('Final Ensemble Sorting (iRMSD)') + allocate (groups(nall),source=0) + env%confgo = .true. + call cregen_irmsd_sort(env,nall,structuredump,groups,allcanon=.false.,printlvl=2) + + if (allocated(groups)) deallocate (groups) + if (allocated(structuredump)) deallocate (structuredump) + return +end subroutine crest_basinhopping + +subroutine single_basinhopping_core(env,mol,calc,structuredump) + use crest_parameters + use crest_data + use crest_calculator + use strucrd + use cregen_interface,only:unionizeEnsembles + use optimize_module + use molbuilder_classify + use bh_module + implicit none + type(systemdata),intent(inout) :: env + type(coord),intent(inout) :: mol + type(coord),allocatable,intent(inout) :: structuredump(:) + type(calcdata),intent(inout) :: calc +!========================================================================================! + integer :: i,j,k,l,io,ich,T,Tn,mciter + logical :: pr,wr + type(bh_class) :: bh + integer :: nall +!========================================================================================! + call new_ompautoset(env,'max',0,T,Tn) +!========================================================================================! +!>--- actual basin hopping + if (allocated(env%bh_ref)) then + bh = env%bh_ref + call bh%init() + else + call bh%init(300.0_wp,200,20) + bh%stepsize(1) = 1.0_wp + end if + select case (bh%steptype) + case (1,2) !> internals, dihedral only + write (stdout,'(a)') '> Setting up internal coordinates for input molecule:' + call setup_classify(mol,bh%molc) + call functional_group_classify(bh%molc) + call bh%molc%get_zmat(.true.) + call bh%molc%print_zmat(stdout) + write (stdout,*) + call bh%molc%check_dihedrals() + end select + bh%id = 0 + if (allocated(env%refine_queue)) then + bh%refine_queue = env%refine_queue + end if + + nall = 0 + do mciter = 1,bh%maxiter + if (bh%maxiter > 1) call printiter3('Basin-Hopping Epoch',mciter) + call bh%newiter() + call mc(calc,mol,bh,io,verbosity=2) + + if (io .eq. 0) then + write (stdout,'(a)') 'New structures will be appended to memory ...' + call unionizeEnsembles(nall,structuredump,bh%saved,bh%structures, & + & ethr=bh%ethr,rthr=bh%rthr) + else + write (stdout,'(a)') 'Skipping run with failed initial quench ...' + end if + write (stdout,'(a,i0,a)') 'Currently ',nall,' structures saved!' + end do + return +end subroutine single_basinhopping_core + +subroutine parallel_basinhopping_core(env,mol,calc,structuredump) +!************************************************************************** +!* subroutine parallel_basinhopping_core +!* Perform multiple independent BH runs from a single given starting point +!* Ensembles are unified at the end and returned via structuedump +!************************************************************************** + use crest_parameters + use crest_data + use crest_calculator + use strucrd + use cregen_interface,only:unionizeEnsembles + use optimize_module + use bh_module + use iomod,only:is_terminal + use molbuilder_classify + implicit none + !> INPUT/OUTPUT + type(systemdata),intent(inout) :: env + type(coord),intent(inout) :: mol + type(coord),allocatable,intent(inout) :: structuredump(:) + type(calcdata),intent(in) :: calc +!========================================================================================! + !> LOCAL + integer :: i,j,k,l,io,ich,T,Tn,mciter + logical :: pr,wr + type(calcdata),allocatable :: calcp(:) + type(bh_class),allocatable :: bhp(:) + type(coord),allocatable :: mols(:) + real(wp) :: energy + integer :: nall,verbose,iostatus + character(len=128) :: tag + type(mollist),allocatable :: dumplist(:) + + call new_ompautoset(env,'auto',0,T,Tn) + !======================================================================================! + !> THIS IS THE PARALLEL IMPORTANT BIT + !======================================================================================! +!>--- allocate temporary spaces for parallel usage + allocate (mols(T),source=mol) + allocate (bhp(T)) + allocate (calcp(T),source=calc) + allocate (dumplist(T)) + if (allocated(env%bh_ref)) then + do K = 1,T + bhp(K) = env%bh_ref + call bhp(K)%init() + end do + else + do K = 1,T + call bhp(K)%init(300.0_wp,200,20) + bhp(K)%stepsize(1) = 1.0_wp + end do + end if + do K = 1,T + call calcp(K)%copy(calc) + bhp(K)%id = K-1 + !$omp critical + select case (bhp(K)%steptype) + case (1,2) !> internals, dihedral only + if (K == 1) write (stdout,'(a)') '> Setting up internal coordinates for input molecule:' + call setup_classify(mol,bhp(K)%molc) + call functional_group_classify(bhp(K)%molc) + call bhp(K)%molc%get_zmat(.true.) + if (K == 1) call bhp(K)%molc%print_zmat(stdout) + if (K == 1) write (stdout,*) + call bhp(K)%molc%check_dihedrals() + end select + if (allocated(env%refine_queue)) then + bhp(K)%refine_queue = env%refine_queue + end if + !$omp end critical + end do + +!>--- pre-start server-based calculators before forking OMP threads + call preinit_mlip_parallel(calcp,T) + + write (stdout,'(a)') '> Starting parallel Basin-Hopping execution' + write (stdout,*) + + do mciter = 1,bhp(1)%maxiter + !$omp critical + write (tag,'(a,i0,a)') 'Basin-Hopping Epoch' + if (bhp(1)%maxiter > 1) call printiter3(trim(tag),mciter) + !$omp end critical + !$omp parallel do default(shared) private(K, mciter, iostatus) schedule(dynamic) + do K = 1,T + call bhp(K)%newiter() + call mc(calcp(K),mols(K),bhp(K),iostatus,verbosity=1) + if (iostatus .eq. 0) then + !$omp critical + write (stdout,'(a)') 'New structures will be appended to memory ...' + !$omp end critical + call unionizeEnsembles(dumplist(K)%nall,dumplist(K)%structure, & + & bhp(K)%saved,bhp(K)%structures, & + & ethr=bhp(K)%ethr,rthr=bhp(K)%rthr) + else + !$omp critical + write (stdout,'(a)') 'Skipping run with failed initial quench ...' + !$omp end critical + end if + !$omp critical + write (stdout,'(a,i0,a,i0,a)') 'Currently ',dumplist(K)%nall, & + & ' structures saved (BH[',bhp(K)%id,'])!' + !$omp end critical + end do + !$omp end parallel do + + !> Do things here (?) + end do + + write (stdout,*) + write (stdout,'(a)') 'Parallel BH runs done!' + write (stdout,'(a)') 'Collecting structures in one ensemble ...' + nall = 0 + do K = 1,T + call unionizeEnsembles(nall,structuredump, & + & dumplist(K)%nall,dumplist(K)%structure, & + & ethr=bhp(K)%ethr,rthr=bhp(K)%rthr) + end do + write (stdout,'(a,i0,a)') 'Total of ',nall,' structures remain.' +!=======================================================================================! +!> PARALLEL BIT END +!=======================================================================================! + return +end subroutine parallel_basinhopping_core diff --git a/src/basinhopping/basinhopping.f90 b/src/basinhopping/basinhopping.f90 new file mode 100644 index 00000000..ddbc62e3 --- /dev/null +++ b/src/basinhopping/basinhopping.f90 @@ -0,0 +1,47 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2024 Philipp Pracht, David Wales +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module bh_module + use crest_parameters + use strucrd,only:coord + use crest_calculator + use optimize_module + use bh_class_module + use bh_step_module + use bh_mc_module + implicit none + private + + logical,parameter :: debug = .true. +! logical,parameter :: debug = .false. + +!>-- RE-EXPORTS + public :: mc + public :: bh_class + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + +!=========================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<. +!================================================================================! + +module bh_class_module + use crest_parameters + use strucrd,only:coord + use canonical_mod + use irmsd_module + use molbuilder_classify + implicit none + +!=========================================================================================! + + public :: bh_class + type :: bh_class +!************************************************************************ +!* data object that contains the data for a *SINGLE* basin-hopping chain +!************************************************************************ + integer :: id = 0 !> Run/Thread ID + integer,allocatable :: seed !> RNG seed, only used when allocated +!>--- settings + logical :: parallel = .false. !> runtype definition + integer :: quenchmode = 0 !> selection of how to quench structures + integer :: duplicatemode = 0 !> selection of how to prune duplicates + +!>--- counters + integer :: iteration = 0 !> current iteration + integer :: saved = 0 !> number of saved quenches + +!>--- paramters + integer :: maxiter = 1 !> maximum repetitions of the whole BH run + integer :: maxsteps = 100 !> maximum steps to take + real(wp) :: temp = 300.0_wp !> MC acceptance temperature + real(wp) :: scalefac = 1.0_wp !> temperature increase factor + real(wp) :: rthr = 0.125_wp !> RMSD threshold (\AA) + real(wp) :: ethr = 0.05_wp !> minima/conformer energy distinction (kcal/mol) + integer :: steptype = 0 !> step type selection + real(wp) :: stepsize(3) = & !> step sizes e.g. for lengths, angles, dihedrals + & (/0.2_wp,0.2_wp,0.2_wp/) + integer :: maxsave = 100 !> maximum number of quenches saved + real(wp),allocatable :: etarget !> target energy to be hit (useful in benchmarks) + + integer,allocatable :: refine_queue(:) + +!>--- results/properties + real(wp) :: emin = 0.0_wp !> current ref energy of markov chain + integer :: whichmin = 0 !> mapping to which structure emin refers + real(wp) :: emax = 0.0_wp !> highest energy structure among saved quenches + integer :: whichmax = 0 !> mapping of highest energy structure + type(coord),allocatable :: structures(:) !> list of structures from succesfull quenches + +!>--- temporary storage + integer,allocatable :: amat(:,:) !> adjacency matrix + type(rmsd_cache),allocatable :: rcache !> similarity check cache (iRMSD) + logical :: stereocheck = .false. !> check for false-rotamers? + type(canonical_sorter),allocatable :: sorters(:) !> canonical atom ID storage + logical :: topocheck = .true. !> check for correct connectivity + type(canonical_sorter),allocatable :: refsort !> use same reference connectivity for all + +!> internal coordinates (stored via coord_classify) + type(coord_classify) :: molc + + +!>--- Type procedures + contains + procedure :: init => bh_class_allocate + procedure :: deallocate => bh_class_deallocate + procedure :: add => bh_class_add + procedure :: newiter => bh_class_newiter + end type bh_class + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + + subroutine bh_class_allocate(self,temp,maxsteps,maxsave) + implicit none + class(bh_class) :: self + real(wp),intent(in),optional :: temp + integer,intent(in),optional :: maxsteps + integer,intent(in),optional :: maxsave + real(wp) :: rand + + call self%deallocate() + if (present(temp)) then + self%temp = temp + end if + if (present(maxsteps)) then + self%maxsteps = maxsteps + end if + if (present(maxsave)) then + self%maxsave = maxsave + end if + self%maxsave = min(self%maxsave,self%maxsteps) + + self%iteration = 0 + self%saved = 0 + allocate (self%structures(self%maxsave)) + allocate (self%sorters(self%maxsave)) + +!>--- generate a random seed, if the object doesn't have one already + if (.not.allocated(self%seed)) then + !> Generate a real in [0,1) + call random_number(rand) + !> Scale and shift to produce an integer in [1,10mil] + allocate (self%seed) + self%seed = (int(rand*100000000.0)+1) + end if + end subroutine bh_class_allocate + +!=========================================================================================! + + subroutine bh_class_deallocate(self) + implicit none + class(bh_class) :: self + if (allocated(self%structures)) deallocate (self%structures) + if (allocated(self%amat)) deallocate (self%amat) + if (allocated(self%sorters)) deallocate (self%sorters) + if (allocated(self%rcache)) deallocate (self%rcache) + if (allocated(self%refsort)) deallocate (self%refsort) + end subroutine bh_class_deallocate + +!========================================================================================! + + subroutine bh_class_newiter(self) + implicit none + class(bh_class) :: self + integer :: i + self%iteration = self%iteration + 1 + !$omp critical + do i = 1,self%saved + call self%sorters(i)%deallocate() + enddo + !$omp end critical + self%saved=0 + end subroutine bh_class_newiter + +!=========================================================================================! + + subroutine bh_class_add(self,mol) + implicit none + class(bh_class) :: self + type(coord) :: mol + integer :: i,j + real(wp) :: mintmp,maxtmp + if (self%saved < self%maxsave) then + self%saved = self%saved+1 + i = self%saved + !$omp critical + self%structures(i) = mol + call self%sorters(i)%init(mol,invtype='apsp+',heavy=.false.) + if (i == 1) then + self%stereocheck = .not. (self%sorters(i)%hasstereo(mol)) + end if + !$omp end critical + else + i = self%whichmax + !$omp critical + self%structures(i) = mol + call self%sorters(i)%deallocate() + call self%sorters(i)%init(mol,invtype='apsp+',heavy=.false.) + call self%sorters(i)%shrink() + !$omp end critical + end if + + mintmp = huge(mintmp) + maxtmp = -huge(maxtmp) + do i = 1,self%saved + if (self%structures(i)%energy < mintmp) then + mintmp = self%structures(i)%energy + self%whichmin = i + end if + if (self%structures(i)%energy > maxtmp) then + maxtmp = self%structures(i)%energy + self%whichmax = i + end if + end do + self%emin = mintmp + self%emax = maxtmp + end subroutine bh_class_add + +!=========================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<. +!================================================================================! + +module bh_mc_module + use crest_parameters + use iomod + use strucrd,only:coord + use crest_calculator + use optimize_module + use axis_module + use irmsd_module + use canonical_mod + use quicksort_interface,only:ensemble_qsort + use bh_class_module + use bh_step_module + implicit none + private + +! logical,parameter :: debug = .true. + logical,parameter :: debug = .false. + + public :: mc + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + + subroutine mc(calc,mol,bh,iostat,verbosity) +!******************************************************************** +!* A thread-safe single basin-hopping MC run +!* Parameters and quenched structures are saved within the bh object +!******************************************************************** + implicit none + !> IN/OUTPUT + type(calcdata),intent(inout) :: calc !> potential settings + type(coord),intent(inout) :: mol !> molecular system + type(bh_class),intent(inout) :: bh !> BH settings + integer,intent(out) :: iostat + integer,intent(in),optional :: verbosity !> printout parameter + !> LOCAL + type(coord) :: tmpmol !> copy to take steps + type(coord) :: optmol !> quenched structure + integer :: iter,iostatus,accepted,discarded,broke + real(wp) :: etot,ratio + real(wp),allocatable :: grd(:,:) + logical :: accept,dupe,broken + integer :: printlvl,first,last,dynamicseed + character(len=20) :: tag + + !$omp critical + write (tag,'("BH[Runner ",i3,"]>")') bh%id + !$omp end critical + + if (present(verbosity)) then + printlvl = verbosity + else + printlvl = 0 + end if + + iostat = 0 + +!>--- Add input energy to Markov chain after an initial quench + !$omp critical + allocate (grd(3,mol%nat),source=0.0_wp) + !$omp end critical + + if (printlvl > 0) then + !$omp critical + write (stdout,'(a,1x,a)') trim(tag),'Performing '//colorify('initial quench','gold')//"." + !$omp end critical + end if + + tmpmol = mol + call mcquench(calc,bh,tmpmol,optmol,etot,grd,iostatus) + if (iostatus .ne. 0) then + !$omp critical + write (stdout,'(a,1x,a)') trim(tag),colorify('** WARNING **','red')// & + & ' initial quench failed. Returning.' + !$omp end critical + iostat = iostatus + return + end if + !$omp critical + mol = optmol + bh%emin = mol%energy + !$omp end critical + call bh%add(mol) + +!>--- print information about the run? + if (printlvl > 0) then + !$omp critical + call mcheader(bh) + !$omp end critical + end if + +!>--- seed the RNG? + if (allocated(bh%seed)) then + dynamicseed = bh%seed+(bh%iteration-1)+bh%id*1000 + if (printlvl > 1) then + !$omp critical + write (stdout,'(a,1x,2(a,i0),a)') trim(tag), & + & 'Seeding current RNG instance with: ',bh%seed,' (',dynamicseed,')' + !$omp end critical + end if + call RNG_seed(bh%seed) + end if + +!=======================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Take the step (mol --> tmpmol) + call takestep(mol,calc,bh,tmpmol) + +!>--- Quench it (tmpmol --> optmol) + call mcquench(calc,bh,tmpmol,optmol,etot,grd,iostatus) + +!>--- Accept/reject + if (iostatus == 0) then !> successfull optimization + + if (printlvl > 1) then + !$omp critical + write (stdout,'(a,1x,a,es17.8,a,es17.8,a)') trim(tag),'Quench E=',etot, & + & ' Eh, Markov E=',bh%emin,' Eh' + !$omp end critical + end if + + accept = mcaccept(optmol,bh) + if (accept) then + accepted = accepted+1 + + call axis(optmol%nat,optmol%at,optmol%xyz) + + !> check duplicates here + call mcduplicate(mol,bh,dupe,broken) + + !$omp critical + if (printlvl > 1) then + write (stdout,'(a)',advance='no') repeat(' ',len_trim(tag)+1)// & + & "Quench "//colorify('ACCEPTED','green') + end if + + if (broken) then + broke = broke+1 + if (printlvl > 1) write (stdout,'(a)',advance='no') & + & ', but REJECTED due to topology mismatch!' + else if (dupe) then + discarded = discarded+1 + if (printlvl > 1) write (stdout,'(a)',advance='no') & + & ', but '//colorify('NOT SAVED','yellow')//' due to duplicate detection!' + else if (printlvl == 1) then + write (stdout,'(a,1x,a,a,es17.8,a)') trim(tag),"Quench "//colorify('ACCEPTED','green'), & + & ', NEW Markov E=',bh%emin,' Eh' + end if + + if (printlvl > 1) write (stdout,'(/)') + !$omp end critical + else + if (printlvl > 1) then + !$omp critical + write (stdout,'(a,a,/)') repeat(' ',len_trim(tag)+1), & + & 'Quench '//colorify('REJECTED','red')//', does not fulfill MC criterion' + !$omp end critical + end if + cycle MonteCarlo + end if + else + if (printlvl > 1)then + !$omp critical + write (stdout,'(a,1x,a,/)') trim(tag),"Quench "//colorify("FAILED","red") + !$omp end critical + endif + cycle MonteCarlo + end if + +!>--- Update structures + if (.not.broken) then + !> continue Markov chain + !$omp critical + mol = optmol + !$omp end critical + + if (.not.dupe) then + !> Save new unique structures + call bh%add(mol) + end if + end if + + end do MonteCarlo +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<--- Post-processing + first = 1 + last = bh%saved + call ensemble_qsort(bh%maxsave,bh%structures,first,last) + +!>--- Stats + if (printlvl > 0) then + !$omp critical + call mcstats(bh,accepted,discarded,broke) + !$omp end critical + end if + + deallocate (grd) + end subroutine mc + +!=========================================================================================! + + subroutine mcheader(bh) + implicit none + type(bh_class),intent(in) :: bh + character(len=80) :: atmp + integer :: n + + write (stdout,'(t8,a)') '┌'//repeat('─',63)//'┐' + write (stdout,'(t8,a,1x)',advance='no') '│' + write (stdout,'(a,3x)',advance='no') 'Starting Basin-Hopping Global Optimization' + write (stdout,'(a,i3,a)',advance='no') '[Thread/ID ',bh%id,']' + write (stdout,'(2x,"│")') + write (stdout,'(t8,a)') '╞'//repeat('═',63)//'╡' + + write (stdout,'(t8,a,1x)',advance='no') '│' + write (stdout,'(a,f20.10,a)',advance='no') 'Initial energy:',bh%emin,' Eh' + write (stdout,'(24x,"│")') + + write (stdout,'(t8,a,1x)',advance='no') '│' + write (stdout,'(a,es10.3,2x)',advance='no') 'T/K: ',bh%temp + write (stdout,'(a,i5,3x)',advance='no') 'steps: ',bh%maxsteps + write (stdout,'(a,i5,3x)',advance='no') 'max save: ',bh%maxsave + write (stdout,'(12x,"│")') + + if (allocated(bh%seed)) then + write (stdout,'(t8,a,1x)',advance='no') '│' + write (atmp,'(a,i0)') 'Random number generation (reference) seed: ',bh%seed + write (stdout,'(a,1x)',advance='no') trim(atmp) + n = 61-len_trim(atmp) + write (stdout,'(a)',advance='no') repeat(' ',n) + write (stdout,'("│")') + end if + + write (stdout,'(t8,a,1x)',advance='no') '│' + write (stdout,'(a,a,2x)',advance='no') 'step type: ',steptypestr(bh%steptype) + write (stdout,'(a,3f9.5)',advance='no') 'step size:',bh%stepsize(1:3) + write (stdout,'(3x,"│")') + + write (stdout,'(t8,a,1x)',advance='no') '│' + write (stdout,'(a,f9.5,a)',advance='no') 'Thresholds ΔRMSD:',bh%rthr,' Å, ' + write (stdout,'(a,es11.4,a)',advance='no') 'ΔE: ',bh%ethr,' kcal/mol' + write (stdout,'(5x,"│")') + + write (stdout,'(t8,a)') '└'//repeat('─',63)//'┘' + end subroutine mcheader + + subroutine mcstats(bh,accepted,discarded,broke) + implicit none + type(bh_class),intent(in) :: bh + integer,intent(in) :: accepted,discarded,broke + real(wp) :: ratio + + write (stdout,'(t8,a)') colorify('┌'//repeat('─',63)//'┐','green') + write (stdout,'(t8,a,1x)',advance='no') colorify("│","green") + write (stdout,'(a,21x)',advance='no') 'Basin-Hopping Statistics' + write (stdout,'(a,i3,a)',advance='no') '[Thread/ID ',bh%id,']' + write (stdout,'(2x,a)') colorify("│","green") + + write (stdout,'(t8,a,1x)',advance='no') colorify("│","green") + write (stdout,'(a,12x,f17.8,a)',advance='no') 'Latest Markov chain energy: ',bh%emin,' Eh' + write (stdout,'(2x,a)') colorify("│","green") + + write (stdout,'(t8,a,1x)',advance='no') colorify("│","green") + ratio = real(accepted,wp)/real(bh%maxsteps,wp) + write (stdout,'(a,f6.2,a)',advance='no') 'MC acceptance ratio ',ratio*100.0_wp,' %, ' + ratio = real(discarded,wp)/real(accepted,wp) + write (stdout,'(a,f6.2,a)',advance='no') 'similarity rejection ',ratio*100.0_wp,' %' + write (stdout,'(2x,a)') colorify("│","green") + + write (stdout,'(t8,a,1x)',advance='no') colorify("│","green") + ratio = real(broke,wp)/real(accepted,wp) + write (stdout,'(a,f6.2,a)',advance='no') 'topology rejection ',ratio*100.0_wp,' %, ' + ratio = real(accepted-discarded-broke,wp)/real(bh%maxsteps,wp) + write (stdout,'(a,f6.2,a)',advance='no') 'TOTAL ACCEPT ratio ',ratio*100.0_wp,' %' + write (stdout,'(2x,a)') colorify("│","green") + write (stdout,'(t8,a)') colorify('└'//repeat('─',63)//'┘',"green") + end subroutine mcstats + +!=========================================================================================! + + subroutine RNG_seed(iseed) +!************************************* +!* seed the RNG to get a reproducible +!* sequence of random numbers +!************************************* + integer,intent(in) :: iseed + integer :: n + integer,allocatable :: seedArray(:) + !> 1) Query how many integers are needed to set the seed (compiler dependent!) + call random_seed(size=n) + !> 2) Allocate and assign a known pattern + allocate (seedArray(n)) + seedArray(:) = iseed + !> 3) Set the seed explicitly + call random_seed(put=seedArray) + deallocate (seedArray) + end subroutine RNG_seed + +!=========================================================================================! + + function mcaccept(mol,bh) result(accept) +!************************************** +!* The regular MC acceptance condition +!************************************** + implicit none + logical :: accept + type(coord),intent(in) :: mol + type(bh_class),intent(in) :: bh + real(wp) :: eold,enew,temp + real(wp) :: random,fact + accept = .false. + eold = bh%emin + enew = mol%energy + temp = bh%temp*kB !> Kelvin to a.u. + + if (enew .lt. eold) then + accept = .true. + else + call random_number(random) + fact = exp(-(enew-eold)/temp) + if (fact .gt. random) accept = .true. + end if + + end function mcaccept + +!=========================================================================================! + + subroutine mcduplicate(mol,bh,dupe,broken) +!***************************************************** +!* Check if a new structure (mol) is already in the +!* list of saved structures (bh%structures) +!***************************************************** + implicit none + type(coord),intent(in) :: mol + type(bh_class),intent(inout) :: bh + real(wp) :: rthr,ethr + logical,intent(out) :: dupe,broken + !> LOCAL + integer :: i,j,k,l,nat + type(canonical_sorter) :: newsort + real(wp) :: rmsdval,deltaE + logical :: topocheck + + dupe = .false. + broken = .false. + ethr = bh%ethr + rthr = bh%rthr + nat = mol%nat + topocheck = .true. + + if (debug) write (*,*) + + if (.not.allocated(bh%rcache)) then + if (debug) write (*,*) "allocating RCACHE" + !$omp critical + allocate (bh%rcache) + call bh%rcache%allocate(nat) + !$omp end critical + end if + + !$omp critical + call newsort%init(mol,invtype='apsp+',heavy=.false.) + !$omp end critical + + COMPARE: do i = 1,bh%saved + + !> Energy difference + deltaE = (mol%energy-bh%structures(i)%energy)*autokcal + + !> Geometry difference (permutation-invariant RMSD) + if (topocheck) then + bh%rcache%rank(1:nat,1) = newsort%rank(1:nat) + bh%rcache%rank(1:nat,2) = bh%sorters(i)%rank(1:nat) + end if + call min_rmsd(mol,bh%structures(i), & + & rcache=bh%rcache,rmsdout=rmsdval) + + if (debug) write (*,'(a,es15.4,a,es15.4,a)') 'RMSD=',rmsdval*autoaa, & + & ' Å, delta E=',deltaE,' kcal/mol' + + !> Check + if (abs(deltaE) .lt. ethr.and.rmsdval*autoaa .lt. rthr) then + dupe = .true. + if (deltaE < 0.0_wp) then + !> if the energy is lower, we replace the molecule (better conformation) + bh%structures(i) = mol + end if + exit COMPARE + end if + end do COMPARE + + !$omp critical + call newsort%deallocate() + !$omp end critical + end subroutine mcduplicate + + !========================================================================================! + + subroutine mcquench(calc,bh,tmpmol,optmol,etot,grd,iostat) + implicit none + !> Input + type(calcdata),intent(inout) :: calc !> potential settings + type(bh_class),intent(inout) :: bh !> BH settings + type(coord),intent(inout) :: tmpmol !> molecular system + real(wp),intent(inout) :: etot !> quenechd energy + real(wp),intent(inout) :: grd(:,:) !> gradient (temp storage) + !> Output + type(coord),intent(out) :: optmol !> molecular system output + integer,intent(out) :: iostat !> return status + + integer :: nrefine,ii + real(wp) :: etmp + iostat = 1 + + !> initial proper quench (refine_lvl = 0) + call optimize_geometry(tmpmol,optmol,calc,etot,grd, & + & .false.,.false.,iostat) + + !> Special Post-processing via refinement queue + if (allocated(bh%refine_queue).and.iostat == 0) then + + nrefine = size(bh%refine_queue,1) + + do ii = 1,nrefine + if (iostat .ne. 0) exit + calc%refine_stage = bh%refine_queue(ii) + select case (calc%refine_stage) + case (1) !> singlepoint (rerank) + call engrad(optmol,calc,etot,grd,iostat) + + case (2) !> singlepoint (add) + call engrad(optmol,calc,etmp,grd,iostat) + etot = etot+etmp + + case (3) !> geometry opt (requench) + tmpmol = optmol + call optimize_geometry(tmpmol,optmol,calc,etot,grd, & + & .false.,.false.,iostat) + + case default + continue + end select + end do + + !> RESET refine level for next quench + calc%refine_stage = 0 + + !> Important: last energy must be stored in the optmol + optmol%energy = etot + end if + + end subroutine mcquench + +!=========================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<. +!================================================================================! + +module bh_step_module + use crest_parameters + use strucrd,only:coord + use crest_calculator + use bh_class_module + implicit none + private + + logical,parameter :: debug = .true. +! logical,parameter :: debug = .false. + + public :: takestep,steptypestr,takestep_cart,take_fixed_stepsize_cart + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + + function steptypestr(steptype) result(str) + implicit none + integer,intent(in) :: steptype + character(len=:),allocatable :: str + select case (steptype) + case default !> Cartesian + str = 'Cartesian' + case (1) !> natural internals + str = 'internal ' + case (2) !> dihedral only + str = 'dihedral ' + case (3) !> intermolecular (CMA,tilt) only + str = 'intermol ' + end select + end function steptypestr + +!=========================================================================================! +!=========================================================================================! + + subroutine takestep(mol,calc,bh,newmol) + implicit none + !> IN/OUTPUT + type(coord),intent(in) :: mol !> molecular system + type(calcdata),intent(inout) :: calc + type(bh_class),intent(inout) :: bh !> BH settings + type(coord),intent(out) :: newmol + !> LOCAL + + select case (bh%steptype) + case (0) !> Cartesian + newmol = mol + call takestep_cart(newmol,bh%stepsize(1),calc) + case (2) !> dihedral only + newmol = mol + call takestep_dihedral(newmol,bh%molc,bh%stepsize(3),calc) + case default + error stop 'Steptype not implemented yet' + end select + + end subroutine takestep + +!=========================================================================================! +!=========================================================================================! + + subroutine takestep_cart(newmol,stepsize,calc) + implicit none + type(coord),intent(inout) :: newmol + real(wp),intent(in) :: stepsize + type(calcdata),intent(inout) :: calc + real(wp) :: r(3) + integer :: i + do i = 1,newmol%nat + if (calc%nfreeze > 0) then + if (calc%freezelist(i)) cycle + end if + call random_number(r) + r(:) = (r(:)-0.5_wp)*2.0_wp + newmol%xyz(:,i) = newmol%xyz(:,i)+r(:)*stepsize + end do + end subroutine takestep_cart + + subroutine take_fixed_stepsize_cart(newmol,stepsize,calc) + implicit none + type(coord),intent(inout) :: newmol + real(wp),intent(in) :: stepsize + type(calcdata),intent(inout) :: calc + real(wp) :: r(3),length + integer :: i + do i = 1,newmol%nat + if (calc%nfreeze > 0) then + if (calc%freezelist(i)) cycle + end if + call random_number(r) + r(:) = (r(:)-0.5_wp)*2.0_wp + length = norm2(r) + newmol%xyz(:,i) = newmol%xyz(:,i)+r(:)*stepsize/length + end do + end subroutine take_fixed_stepsize_cart + +!=========================================================================================! + + subroutine takestep_dihedral(newmol,molc,stepsize,calc) + use molbuilder_classify_type, only: dtypes + implicit none + type(coord),intent(inout) :: newmol + type(coord_classify),intent(inout) :: molc + real(wp),intent(in) :: stepsize + type(calcdata),intent(inout) :: calc + real(wp) :: r(1) + integer :: i,k + logical :: smartstep + call molc%update_zmat(newmol) + smartstep = allocated(molc%dtype) + if (smartstep) then + !> fallback: we need at least one valid dihdral if smartstep is true + !> otherwise we should turn it off again + k = count(molc%dtype(:) == dtypes%single) + if (k == 0) smartstep = .false. + end if + + do i = 1,newmol%nat + if (molc%zmap(i,3) .ne. 0) then + if (smartstep) then + if (molc%dtype(i) .ne. dtypes%single) cycle + end if + call random_number(r) + r(:) = (r(:)-0.5_wp)*2.0_wp + + molc%zmat(3,i) = molc%zmat(3,i)+r(1)*stepsize + end if + end do + !call molc%print_zmat(stdout) + call molc%from_zmat(newmol) + end subroutine takestep_dihedral + +!=========================================================================================! +!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< real64,stdout => output_unit use strucrd use calc_type - use iomod,only:makedir,directory_exist,remove + use iomod,only:makedir,directory_exist,remove,dump_array_to_tmp + use omp_lib !> API modules use api_helpers use tblite_api use gfn0_api use gfnff_api + use crest_solvation,only:solvation_setup,solvation_core + use crest_electrostatic,only:electrostatic_core use libpvol_api use lj + use approxg_module + use penalty_module + use mlip_sc implicit none !>--- private module variables and parameters private @@ -43,7 +49,13 @@ module api_engrad public :: gfn0_engrad,gfn0occ_engrad public :: gfnff_engrad public :: libpvol_engrad - public :: lj_engrad !> RE-EXPORT + public :: lj_engrad !> RE-EXPORT + public :: modelhessian_engrad + public :: rmsd_engrad + public :: mlip_engrad + public :: preinit_mlip_parallel + public :: solvation_engrad + public :: electrostatic_engrad !=========================================================================================! !=========================================================================================! @@ -63,7 +75,6 @@ subroutine tblite_engrad(mol,calc,energy,grad,iostatus) real(wp),intent(inout) :: grad(3,mol%nat) integer,intent(out) :: iostatus - character(len=:),allocatable :: cpath logical :: loadnew,pr integer :: i,j,k,l,ich,och,io @@ -76,25 +87,27 @@ subroutine tblite_engrad(mol,calc,energy,grad,iostatus) call tblite_init(calc,loadnew) !>--- tblite printout handling call api_handle_output(calc,'tblite.out',mol,pr) - if (pr .or. calc%prstdout) then + if (pr.or.calc%prstdout) then !> tblite uses its context (ctx) type, rather than calc%prch calc%tblite%ctx%unit = calc%prch calc%tblite%ctx%verbosity = 1 - if(calc%prstdout)then + if (calc%prstdout) then !> special case, fwd to stdout (be carefule with this!) calc%tblite%ctx%unit = stdout calc%tblite%ctx%verbosity = 2 - endif + end if else calc%tblite%ctx%verbosity = 0 end if !>-- populate parameters and wavefunction if (loadnew) then - call tblite_setup(mol,calc%chrg,calc%uhf,calc%tblitelvl,calc%etemp,calc%tblite) + call tblite_setup(mol,calc%chrg,calc%uhf,calc%tblitelvl,calc%etemp,calc%tblite,calc%ceh_guess) call tblite_addsettings(calc%tblite,calc%maxscc,calc%rdwbo,calc%saveint,calc%accuracy) + call tblite_add_efield(calc%tblite,calc%efield) + call tblite_add_solv(mol,calc%chrg,calc%uhf,calc%tblite, & & calc%solvmodel,calc%solvent) end if @@ -105,7 +118,7 @@ subroutine tblite_engrad(mol,calc,energy,grad,iostatus) call tblite_singlepoint(mol,calc%chrg,calc%uhf,calc%tblite, & & energy,grad,iostatus) if (iostatus /= 0) return - if(.not.calc%prstdout) & + if (.not.calc%prstdout) & & call api_print_e_grd(pr,calc%prch,mol,energy,grad) !>--- postprocessing, getting other data @@ -116,6 +129,67 @@ subroutine tblite_engrad(mol,calc,energy,grad,iostatus) return end subroutine tblite_engrad +!========================================================================================! + + subroutine solvation_engrad(mol,calc,energy,grad,iostatus) +!********************************************************************* +!* Interface singlepoint call for the composite solvation calculator +!********************************************************************* + implicit none + type(coord) :: mol + type(calculation_settings) :: calc + real(wp),intent(inout) :: energy + real(wp),intent(inout) :: grad(3,mol%nat) + integer,intent(out) :: iostatus + + iostatus = 0 + if (.not.allocated(calc%solv)) then + iostatus = 1 + return + end if + +!>--- lazily resolve dielectric constant + GFN2/ALPB parameters + call solvation_setup(mol,calc%solv,iostatus) + if (iostatus /= 0) return + +!>--- stitched energy + gradient + call solvation_core(mol,calc%chrg,calc%solv,energy,grad,iostatus) + if (.not.calc%prstdout) call api_print_e_grd(.false.,calc%prch,mol,energy,grad) + + return + end subroutine solvation_engrad + +!========================================================================================! + + subroutine electrostatic_engrad(mol,calc,energy,grad,iostatus) +!********************************************************************* +!* Interface singlepoint call for the charge-equilibration calculator +!********************************************************************* + implicit none + type(coord) :: mol + type(calculation_settings) :: calc + real(wp),intent(inout) :: energy + real(wp),intent(inout) :: grad(3,mol%nat) + integer,intent(out) :: iostatus + character(len=:),allocatable :: model + + iostatus = 0 + if (.not.allocated(calc%eeq)) then + iostatus = 1 + return + end if + model = 'eeqbc' + if (allocated(calc%eeq%charge_model)) model = calc%eeq%charge_model + +!>--- energy, gradient and atomic charges (kept on the settings) + if (.not.allocated(calc%qat)) allocate (calc%qat(mol%nat)) + call electrostatic_core(mol,calc%chrg,model,energy,grad,calc%qat,iostatus) + if (iostatus /= 0) return + if (.not.calc%prstdout) call api_print_e_grd(.false.,calc%prch,mol,energy,grad) + + return + end subroutine electrostatic_engrad + !========================================================================================! subroutine gfn0_engrad(mol,calc,g0calc,energy,grad,iostatus) @@ -134,7 +208,6 @@ subroutine gfn0_engrad(mol,calc,g0calc,energy,grad,iostatus) integer,intent(out) :: iostatus !> LOCAL type(gfn0_results) :: res - character(len=:),allocatable :: cpath logical :: loadnew logical :: pr @@ -161,7 +234,7 @@ subroutine gfn0_engrad(mol,calc,g0calc,energy,grad,iostatus) if (iostatus /= 0) return if (pr) then call gfn0_print(calc%prch,g0calc,res) - if(.not.calc%prstdout) & + if (.not.calc%prstdout) & & call api_print_e_grd(pr,calc%prch,mol,energy,grad) end if @@ -191,7 +264,6 @@ subroutine gfn0occ_engrad(mol,calc,g0calc,energy,grad,iostatus) integer,intent(out) :: iostatus !> LOCAL type(gfn0_results) :: res - character(len=:),allocatable :: cpath logical :: loadnew,pr integer :: i,j,k,l,ich,och,io logical :: ex @@ -217,7 +289,7 @@ subroutine gfn0occ_engrad(mol,calc,g0calc,energy,grad,iostatus) if (iostatus /= 0) return if (pr) then call gfn0_print(calc%prch,g0calc,res) - if(.not.calc%prstdout) & + if (.not.calc%prstdout) & & call api_print_e_grd(pr,calc%prch,mol,energy,grad) end if @@ -243,13 +315,14 @@ subroutine gfnff_engrad(mol,calc,energy,grad,iostatus) real(wp),intent(inout) :: grad(3,mol%nat) integer,intent(out) :: iostatus - character(len=:),allocatable :: cpath logical :: loadnew,pr integer :: i,j,k,l,ich,och,io logical :: ex + character(len=:),allocatable :: tmpchrgs + real(wp),allocatable :: q(:) iostatus = 0 pr = .false. -!>--- setup system call information +!>--- setup calculation data !$omp critical call gfnff_init(calc,loadnew) !>--- printout handling @@ -257,7 +330,22 @@ subroutine gfnff_engrad(mol,calc,energy,grad,iostatus) !>--- populate parameters and neighbourlists if (loadnew) then + if (calc%ceh_guess) then + if (pr) then + write (calc%prch,'(/,a)') 'Initializing (fragement) charges from CEH model' + end if + !> A bit hacky and additional I/O, but would need adjusting submodule code otherwise + call tblite_quick_ceh_q(mol,q,calc%chrg,pr=pr,prch=calc%prch) + tmpchrgs = dump_array_to_tmp(q) + calc%ff_dat%refcharges = tmpchrgs + end if + call gfnff_api_setup(mol,calc%chrg,calc%ff_dat,iostatus,pr,calc%prch) + + if (calc%ceh_guess) then + call remove(tmpchrgs) + deallocate (q) + end if end if !$omp end critical if (iostatus /= 0) return @@ -270,7 +358,7 @@ subroutine gfnff_engrad(mol,calc,energy,grad,iostatus) !>--- printout if (pr) then call gfnff_printout(calc%prch,calc%ff_dat) - if(.not.calc%prstdout) & + if (.not.calc%prstdout) & & call api_print_e_grd(pr,calc%prch,mol,energy,grad) end if @@ -296,7 +384,6 @@ subroutine libpvol_engrad(mol,calc,energy,grad,iostatus) real(wp),intent(inout) :: grad(3,mol%nat) integer,intent(out) :: iostatus - character(len=:),allocatable :: cpath logical :: loadnew,pr integer :: i,j,k,l,ich,och,io logical :: ex @@ -324,7 +411,7 @@ subroutine libpvol_engrad(mol,calc,energy,grad,iostatus) !>--- printout if (pr) then !> the libpvol_sp call includes the printout within libpvol-lib - if(.not.calc%prstdout) & + if (.not.calc%prstdout) & & call api_print_e_grd(pr,calc%prch,mol,energy,grad) end if @@ -334,5 +421,203 @@ subroutine libpvol_engrad(mol,calc,energy,grad,iostatus) end subroutine libpvol_engrad !========================================================================================! + + subroutine modelhessian_engrad(mol,calc,energy,grad,iostatus) +!*************************************************************** +!* Interface singlepoint call between CREST and XHC force field +!*************************************************************** + implicit none + type(coord) :: mol + type(calculation_settings) :: calc + + real(wp),intent(inout) :: energy + real(wp),intent(inout) :: grad(3,mol%nat) + integer,intent(out) :: iostatus + + logical :: loadnew,pr + integer :: i,j,k,l,ich,och,io,n3 + logical :: ex + iostatus = 0 + pr = .false. +!>--- setup system call information + !$omp critical +!>--- printout handling + call api_handle_output(calc,'modh.out',mol,pr) +!>--- populate parameters + n3 = mol%nat*3 + if (calc%ag%dim .ne. mol%nat) then + calc%ag%pr = calc%prstdout.and..not.calc%numgrad + + if (allocated(calc%ag%hess)) deallocate (calc%ag%hess) + allocate (calc%ag%hess(n3,n3),source=0.0_wp) + + if (allocated(calc%ag%h)) deallocate (calc%ag%h) + allocate (calc%ag%h(n3*(n3+1)/2),source=0.0_wp) + + if (allocated(calc%ag%freq)) deallocate (calc%ag%freq) + allocate (calc%ag%freq(n3),source=0.0_wp) + + if (allocated(calc%ag%xyz)) deallocate (calc%ag%xyz) + allocate (calc%ag%xyz(3,mol%nat),source=0.0_wp) + + calc%ag%dim = mol%nat + else + calc%ag%hess(:,:) = 0.0_wp + calc%ag%h(:) = 0.0_wp + end if + !$omp end critical + if (iostatus /= 0) return + +!>--- do the engrad call + call initsignal() + call modh_engrad(mol,calc%ag,energy,grad,iostatus) + if (iostatus /= 0) return + +!>--- printout + if (pr) then + !> the libpvol_sp call includes the printout within libpvol-lib + if (.not.calc%prstdout) & + & call api_print_e_grd(pr,calc%prch,mol,energy,grad) + end if + +!>--- postprocessing, getting other data + + return + end subroutine modelhessian_engrad + +!========================================================================================! + + subroutine rmsd_engrad(mol,calc,energy,grad,iostatus) +!************************************************************************** +!* Interface singlepoint to add RMSD penalty function (as in metadynamics) +!************************************************************************** + implicit none + type(coord) :: mol + type(calculation_settings),target :: calc + + real(wp),intent(inout) :: energy + real(wp),intent(inout) :: grad(3,mol%nat) + integer,intent(out) :: iostatus + + logical :: loadnew,pr + integer :: i,j,k,l,ich,och,io,nall + logical :: ex + iostatus = 0 + pr = .false. +!>--- setup system call information + + if (.not.associated(calc%penalty%biaslist)) then + if (allocated(calc%penalty%biasfile)) then + !$omp critical + call rdensemble(calc%penalty%biasfile,nall,calc%penalty%biastmp) + calc%penalty%biaslist => calc%penalty%biastmp + !$omp end critical + else + return + end if + end if + !$omp critical +!>--- printout handling + call api_handle_output(calc,'rmsd_penalty.out',mol,pr) +!>--- populate parameters + if (.not.allocated(calc%penalty%gradtmp)) then + allocate (calc%penalty%gradtmp(3,mol%nat),source=0.0_wp) + call calc%penalty%ccache%allocate(mol%nat) + else + calc%penalty%gradtmp(:,:) = 0.0_wp + end if + !$omp end critical + if (iostatus /= 0) return + +!>--- do the engrad call + call initsignal() + call rmsd_penalty_engrad(mol,calc%penalty,energy,grad,iostatus) + if (iostatus /= 0) return + +!>--- printout + if (pr) then + !> the libpvol_sp call includes the printout within libpvol-lib + if (.not.calc%prstdout) & + & call api_print_e_grd(pr,calc%prch,mol,energy,grad) + end if + +!>--- postprocessing, getting other data + + return + end subroutine rmsd_engrad + +!========================================================================================! + + subroutine preinit_mlip_parallel(calculations,T) +!*********************************************************************** +!* Serially start one fmlip-relay server instance per OMP thread. +!* Must be called before the OMP parallel region to avoid fork() inside +!* a live thread team (triggers OMP Warning #191). +!* Input: calculations - per-thread calcdata array (size >= T) +!* T - number of OMP threads / instances to start +!*********************************************************************** + implicit none + type(calcdata),intent(inout) :: calculations(:) + integer,intent(in) :: T + integer :: i,j + do i = 1,T + do j = 1,calculations(i)%ncalculations + if (calculations(i)%calcs(j)%id == jobtype%mlip) then + call fmlip_relay_init(calculations(i)%calcs(j)%MPAR,i) + end if + end do + end do + end subroutine preinit_mlip_parallel + +!========================================================================================! + + subroutine mlip_engrad(mol,calc,energy,grad,iostatus) +!************************************************************************** +!* MLIP singlepoint through persistent python socket +!************************************************************************** + implicit none + type(coord) :: mol + type(calculation_settings),target :: calc + + real(wp),intent(inout) :: energy + real(wp),intent(inout) :: grad(3,mol%nat) + integer,intent(out) :: iostatus + + logical :: loadnew,pr + integer :: i,j,k,l,ich,och,io,iid + logical :: ex + iostatus = 0 + pr = .false. +!>--- each OpenMP thread owns one server instance; init is a no-op if running + iid = OMP_GET_THREAD_NUM()+1 + !$omp critical + call fmlip_relay_init(calc%MPAR,iid) +!>--- printout handling + call api_handle_output(calc,'mlip.out',mol,pr) + !$omp end critical + if (iostatus /= 0) return + +!>--- do the engrad call + call initsignal() + !> fmlip-relay expects the spin multiplicity (2S+1), not uhf = Nα-Nβ + call calc%sync_multiplicity() + call mlip_engrad_core(mol,calc%MPAR,energy,grad,iostatus, & + & charge=calc%chrg,spin=calc%multiplicity,iid=iid) + if (iostatus /= 0) return + +!>--- printout + if (pr) then + !> the libpvol_sp call includes the printout within libpvol-lib + if (.not.calc%prstdout) & + & call api_print_e_grd(pr,calc%prch,mol,energy,grad) + end if + +!>--- postprocessing, getting other data + + return + end subroutine mlip_engrad + +!========================================================================================! +!########################################################################################! !========================================================================================! end module api_engrad diff --git a/src/calculator/api_helpers.F90 b/src/calculator/api_helpers.F90 index 81ee45e0..b268b938 100644 --- a/src/calculator/api_helpers.F90 +++ b/src/calculator/api_helpers.F90 @@ -21,7 +21,7 @@ module api_helpers use iso_fortran_env,only:wp => real64,stdout => output_unit use strucrd use calc_type - use iomod,only:makedir,directory_exist,remove + use iomod,only:makedir,directory_exist,remove,random_tmp_name !> APIs use tblite_api use gfn0_api @@ -155,6 +155,7 @@ subroutine tblite_init(calc,loadnew) if(allocated(calc%tbliteparam))then calc%tblite%paramfile = calc%tbliteparam endif + calc%tblite%spin_polarized = calc%spin_polarized loadnew = .true. end if if (calc%apiclean) loadnew = .true. @@ -390,6 +391,8 @@ subroutine libpvol_initcheck(calc,loadnew) #endif end subroutine libpvol_initcheck + + !========================================================================================! !========================================================================================! end module api_helpers diff --git a/src/calculator/approxg.f90 b/src/calculator/approxg.f90 new file mode 100644 index 00000000..61342048 --- /dev/null +++ b/src/calculator/approxg.f90 @@ -0,0 +1,93 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +!> a small module for getting a free energy as engrad call + +module approxg_module + use crest_parameters + use modelhessian_core + use thermochem_module + use strucrd + use optimize_maths,only:dhtosq + implicit none + private + + public :: approxg_params + type :: approxg_params + integer :: dim = 0 + logical :: pr = .false. + real(wp) :: T = 298.15_wp + real(wp),allocatable :: hess(:,:) + real(wp),allocatable :: h(:) + real(wp),allocatable :: freq(:) + real(wp),allocatable :: xyz(:,:) + real(wp) :: fscal = 1.0_wp + real(wp) :: ithr = -50.0_wp + real(wp) :: sthr = 50.0_wp + end type approxg_params + + public :: modh_engrad + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + + subroutine modh_engrad(mol,ag,dg,dggrad,iostatus) + type(coord),intent(in) :: mol + type(approxg_params),intent(inout) :: ag + real(wp),intent(out) :: dg + real(wp),intent(out) :: dggrad(:,:) + integer,intent(out) :: iostatus + integer :: n3,io + integer,parameter :: nt = 1 + real(wp) :: temps(nt),et(nt),ht(nt),gt(nt),stot(nt) + + type(mhparam) :: mhset + + iostatus = 0 + dg = 0.0_wp + dggrad(:,:) = 0.0_wp + temps(1) = ag%T + + !> setup + n3 = mol%nat*3 + call ddvopt(mol%xyz,mol%nat,ag%h,mol%at,mhset) + + call dhtosq(n3,ag%hess,ag%h) + ag%h(:) = 0.0_wp + + call prj_mw_hess(mol%nat,mol%at,n3,mol%xyz, & + & ag%hess,ag%h) + + call frequencies(mol%nat,mol%at,mol%xyz,n3,ag%hess,ag%freq,io) + iostatus = io + if (iostatus .ne. 0) return + + ag%xyz(:,:) = mol%xyz(:,:) + call calcthermo(mol%nat,mol%at,ag%xyz,ag%freq,ag%pr, & + ag%ithr,ag%fscal,ag%sthr,nt,temps,et,ht,gt,stot) + + dg = gt(1) + end subroutine modh_engrad + +!========================================================================================! +!========================================================================================! +end module approxg_module diff --git a/src/calculator/calc_type.f90 b/src/calculator/calc_type.f90 index 3b61bc49..4ff5106f 100644 --- a/src/calculator/calc_type.f90 +++ b/src/calculator/calc_type.f90 @@ -24,11 +24,17 @@ module calc_type !>--- api types use tblite_api use gfn0_api + use crest_solvation,only:solvation_data + use crest_electrostatic,only:electrostatic_data use gfnff_api,only:gfnff_data use libpvol_api,only:libpvol_calculator !>--- other types use orca_type use lwoniom_module + use hessian_reconstruct + use approxg_module, only: approxg_params + use penalty_module, only: penalty_params + use mlip_sc, only: mlip_params implicit none character(len=*),public,parameter :: sep = '/' @@ -49,10 +55,15 @@ module calc_type integer :: gfnff = 9 integer :: libpvol = 10 integer :: lj = 11 + integer :: approxg = 12 + integer :: penalty = 13 + integer :: mlip = 14 + integer :: solvation = 15 + integer :: electrostatic = 16 end type enum_jobtype type(enum_jobtype), parameter,public :: jobtype = enum_jobtype() - character(len=45),parameter,private :: jobdescription(12) = [ & + character(len=45),parameter,private :: jobdescription(17) = [ & & 'Unknown calculation type ', & & 'xTB calculation via external binary ', & & 'Generic script execution ', & @@ -64,7 +75,12 @@ module calc_type & 'GFN0*-xTB calculation via GFN0 lib ', & & 'GFN-FF calculation via GFNFF lib ', & & 'external pressure calculation via libpvol ', & - & 'Lennard-Jones potential calculation ' ] + & 'Lennard-Jones potential calculation ', & + & 'Approximate free energy computation ', & + & 'Empirical penalty function ', & + & 'MLIP via persistent python socket ', & + & 'Standalone implicit solvation contribution ', & + & 'Charge-equilibration electrostatics '] !&> !=========================================================================================! @@ -82,7 +98,9 @@ module calc_type integer :: refine_lvl = 0 !> to allow defining different refinement levels integer :: chrg = 0 !> molecular charge - integer :: uhf = 0 !> uhf parameter (xtb) or multiplicity (other) + integer :: uhf = 0 !> uhf = Nα-Nβ = 2S (xtb/tblite/gfn0/gfnff/turbomole) + integer :: multiplicity = 1 !> spin multiplicity = 2S+1 = uhf+1 (ORCA, fmlip-relay) + !> kept aligned with uhf via sync_multiplicity/set_multiplicity logical :: active = .true. !> active setting to disable the calculation (this is different from weight=0) real(wp) :: weight = 1.0_wp !> calculation weight (when adding them up) @@ -126,14 +144,16 @@ module calc_type real(wp),allocatable :: dipgrad(:,:) !> other properties - logical,allocatable :: getsasa(:) + logical,allocatable :: getsasa(:) logical :: getlmocent = .false. integer :: nprot = 0 - real(wp),allocatable :: protxyz(:,:) + real(wp),allocatable :: protxyz(:,:) + real(wp),allocatable :: efield(:) !> in V/Å !>--- API constructs integer :: tblitelvl = 2 real(wp) :: etemp = 300.0_wp + logical :: etemp_user_set = .false. real(wp) :: accuracy = 1.0_wp logical :: apiclean = .true. integer :: maxscc = 500 @@ -150,6 +170,8 @@ module calc_type !>--- tblite data type(tblite_data),allocatable :: tblite character(len=:),allocatable :: tbliteparam + logical :: ceh_guess = .false. + logical :: spin_polarized = .false. !>--- GFN0-xTB data type(gfn0_data),allocatable :: g0calc @@ -169,13 +191,28 @@ module calc_type real(wp) :: pvradscal = 1.0_wp !> Scaling factor for SAS radii type(libpvol_calculator),allocatable :: libpvol +!>--- implicit solvation composite data + type(solvation_data),allocatable :: solv + +!>--- charge-equilibration electrostatics data + type(electrostatic_data),allocatable :: eeq + +!>--- approxg data + type(approxg_params) :: ag + +!>--- penalty params + type(penalty_params) :: penalty + !> ONIOM fragment IDs integer :: ONIOM_highlowroot = 0 integer :: ONIOM_id = 0 - !> ORCA job template +!>--- ORCA job template type(orca_input) :: ORCA +!>--- MLIP settings + type(mlip_params) :: MPAR + !>--- Type procedures contains procedure :: deallocate => calculation_settings_deallocate @@ -186,6 +223,9 @@ module calc_type procedure :: create => create_calclevel_shortcut procedure :: norestarts => calculation_settings_norestarts procedure :: dumpdipgrad => calculation_dump_dipgrad + procedure :: copy => calculation_settings_copy + procedure :: sync_multiplicity => calc_sync_multiplicity + procedure :: set_multiplicity => calc_set_multiplicity end type calculation_settings !=========================================================================================! @@ -251,6 +291,9 @@ module calc_type logical :: tsopt = .false. integer :: iupdat = 0 !> 0=BFGS, 1=Powell, 2=SR1, 3=Bofill, 4=Schlegel integer :: opt_engine = 0 !> default: ANCOPT + integer :: lbfgs_histsize = 20 !> L-BFGS history size + integer :: hess_init = 5 !> Initialization of the hessian, standard modhess lindh95 + logical :: logextxyz = .true. !> write extended xyz files from optimization trajectories !>--- GFN0* data, needed for special MECP application type(gfn0_data),allocatable :: g0calc @@ -266,10 +309,36 @@ module calc_type integer,allocatable :: ONIOMmap(:) !> map ONIOM fragments to calculation_settings integer,allocatable :: ONIOMrevmap(:) !> map calculation settings to ONIOM frags (or zero) +!>--- Hessian Reconstructor and Thermo data + type(cashed_hessian),allocatable :: chess + logical :: do_HR = .false. + logical :: full_HR = .false. !> Keyword for HR with all opt steps + integer :: hu_steps = 10 !> default number of update steps + integer :: nt !> following all required for thermochemistry + real(wp),allocatable :: temperatures(:) + real(wp),allocatable :: et(:),ht(:),gt(:),stot(:) + real(wp) :: ithr,fscal,sthr + character(len=:),allocatable :: emodel + integer :: initialize_hr_type !> case defining initialization + integer :: mh_type = 0 + integer :: hr_hu_type = 0 + logical :: deform_opt_hess = .false. + real(wp) :: doh_stepsize = 0.10_wp !>stepsize for the deformation/reoptimization hessian generation + real(wp) :: chess_id_guess = 0.1_wp + logical :: g_sampling = .false. !>Do sampling on free energy surface as approximated using the lindh95 hessian + integer :: gs_hess_type = 5 + +!>--- Parameters for smooth function within optimizer + real(wp) :: L = 1.50_wp + real(wp) :: k = 5000.0_wp + real(wp) :: shift = 0.0006_wp + real(wp) :: scaling = 0.1_wp + !>--- Type procedures contains procedure :: reset => calculation_reset procedure :: init => calculation_init + procedure :: create => calcdata_create_shortcut generic,public :: add => calculation_add_constraint,calculation_add_settings, & & calculation_add_scan,calculation_add_constraintlist procedure,private :: calculation_add_constraint,calculation_add_settings, & @@ -281,7 +350,10 @@ module calc_type procedure :: ONIOMexpand => calculation_ONIOMexpand procedure :: active => calc_set_active procedure :: active_restore => calc_set_active_restore + generic,public :: set_freeze => calculation_set_freeze_range,calculation_set_freeze_bools + procedure,private :: calculation_set_freeze_range,calculation_set_freeze_bools procedure :: freezegrad => calculation_freezegrad + procedure :: set_charge => calculation_set_charge procedure :: increase_charge => calculation_increase_charge procedure :: decrease_charge => calculation_decrease_charge procedure :: dealloc_params => calculation_deallocate_params @@ -353,10 +425,10 @@ subroutine calculation_deallocate_params(self) integer :: i,j,k if (self%ncalculations > 0) then do i = 1,self%ncalculations - if(allocated(self%calcs(i)%tblite)) deallocate(self%calcs(i)%tblite) - if(allocated(self%calcs(i)%g0calc)) deallocate(self%calcs(i)%g0calc) - if(allocated(self%calcs(i)%ff_dat)) deallocate(self%calcs(i)%ff_dat) - if(allocated(self%calcs(i)%libpvol)) deallocate(self%calcs(i)%libpvol) + if (allocated(self%calcs(i)%tblite)) deallocate (self%calcs(i)%tblite) + if (allocated(self%calcs(i)%g0calc)) deallocate (self%calcs(i)%g0calc) + if (allocated(self%calcs(i)%ff_dat)) deallocate (self%calcs(i)%ff_dat) + if (allocated(self%calcs(i)%libpvol)) deallocate (self%calcs(i)%libpvol) end do end if end subroutine calculation_deallocate_params @@ -387,6 +459,52 @@ subroutine calculation_add_settings(self,cal) return end subroutine calculation_add_settings +!=========================================================================================! + + subroutine calcdata_create_shortcut(self,levelstring, & + & chrg,uhf,solvmodel,solvent) +!********************************************************************* +!* subroutine calcdata_create_shortcut called with %create(...) +!* Quick clean setup of a *fresh* calcdata object holding exactly +!* one calculation level. The calcdata is reset first, then a single +!* calculation_settings object is built (via its own %create) for the +!* requested level of theory and registered in self%calcs(1). +!* +!* This is the calcdata-level counterpart to the %create shortcut of +!* the calculation_settings type and exists as an internal code +!* shortcut only (it does not sanity-check the chosen settings). +!* +!* Required argument: +!* - levelstring : level of theory, passed on to settings%create +!* Optional arguments (forwarded to settings%create): +!* - chrg : molecular charge (integer) +!* - uhf : uhf parameter (integer) +!* - solvmodel : solvation model (only together with solvent) +!* - solvent : implicit solvent (only together with solvmodel) +!********************************************************************* + implicit none + class(calcdata),intent(inout) :: self + character(len=*),intent(in) :: levelstring + integer,intent(in),optional :: chrg + integer,intent(in),optional :: uhf + character(len=*),intent(in),optional :: solvmodel + character(len=*),intent(in),optional :: solvent + type(calculation_settings) :: job + + ! ── start from a clean calcdata object ────────────────────────── + call self%reset() + + ! ── build the single calculation level (absent optionals are ──── + ! ── forwarded as absent to settings%create) ───────────────────── + call job%create(levelstring,chrg=chrg,uhf=uhf, & + & solvmodel=solvmodel,solvent=solvent) + + ! ── register it as the only level in this calcdata ────────────── + call self%add(job) + + return + end subroutine calcdata_create_shortcut + !=========================================================================================! subroutine calculation_add_constraint(self,constr) @@ -532,47 +650,159 @@ end subroutine calculation_init !=========================================================================================! !> copy a calcdata object from src to self - subroutine calculation_copy(self,src) + subroutine calculation_copy(self,src,ignore_constraints) class(calcdata) :: self - type(calcdata) :: src + type(calcdata),intent(in) :: src + logical,intent(in),optional :: ignore_constraints + type(calculation_settings) :: newset + type(constraint) :: newcons integer :: i + logical :: igno - self%id = src%id + call self%reset() - self%ncalculations = src%ncalculations - if (allocated(self%calcs)) deallocate (self%calcs) - !self%calcs = src%calcs - do i = 1,self%ncalculations - call self%add(src%calcs(i)) - end do +! ── identity ───────────────────────────────────────────────────────────────── + self%id = src%id + self%refine_stage = src%refine_stage - self%nconstraints = src%nconstraints - if (allocated(self%cons)) deallocate (self%cons) - !self%cons = src%cons - do i = 1,self%nconstraints - call self%add(src%cons(i)) +! ── calculation levels ─────────────────────────────────────────────────────── + if (allocated(self%calcs)) deallocate(self%calcs) + self%ncalculations = 0 + do i = 1,src%ncalculations + call newset%copy(src%calcs(i)) + call self%add(newset) end do - self%optlev = src%optlev - self%micro_opt = src%micro_opt - self%maxcycle = src%maxcycle - self%maxdispl_opt = src%maxdispl_opt - self%hlow_opt = src%hlow_opt - self%hmax_opt = src%hmax_opt - self%acc_opt = src%acc_opt - self%exact_rf = src%exact_rf - self%average_conv = src%average_conv - self%tsopt = src%tsopt - self%iupdat = src%iupdat +! ── constraints ────────────────────────────────────────────────────────────── + igno = .false. + if (present(ignore_constraints)) igno = ignore_constraints + if (allocated(self%cons)) deallocate(self%cons) + self%nconstraints = 0 + if (.not.igno) then + do i = 1,src%nconstraints + call newcons%copy(src%cons(i)) + call self%add(newcons) + end do + end if - self%pr_energies = src%pr_energies - self%eout_unit = src%eout_unit - self%elog = src%elog +! ── scans ──────────────────────────────────────────────────────────────────── + self%nscans = src%nscans + self%relaxscan = src%relaxscan + self%scansforce = src%scansforce + if (allocated(src%scans)) then + allocate(self%scans(src%nscans)) + do i = 1,src%nscans + self%scans(i)%type = src%scans(i)%type + self%scans(i)%n = src%scans(i)%n + self%scans(i)%steps = src%scans(i)%steps + self%scans(i)%minval = src%scans(i)%minval + self%scans(i)%maxval = src%scans(i)%maxval + self%scans(i)%constrnmbr = src%scans(i)%constrnmbr + self%scans(i)%restore = src%scans(i)%restore + self%scans(i)%currentstep = src%scans(i)%currentstep + if (allocated(src%scans(i)%atms)) self%scans(i)%atms = src%scans(i)%atms + if (allocated(src%scans(i)%points)) self%scans(i)%points = src%scans(i)%points + end do + end if +! ── frozen atoms ───────────────────────────────────────────────────────────── + self%nfreeze = src%nfreeze + if (allocated(src%freezelist)) self%freezelist = src%freezelist + +! ── optimization settings ──────────────────────────────────────────────────── + self%optnewinit = src%optnewinit + self%anopt = src%anopt + self%optlev = src%optlev + self%micro_opt = src%micro_opt + self%maxcycle = src%maxcycle + self%maxdispl_opt = src%maxdispl_opt + self%ethr_opt = src%ethr_opt + self%gthr_opt = src%gthr_opt + self%hlow_opt = src%hlow_opt + self%hmax_opt = src%hmax_opt + self%acc_opt = src%acc_opt + self%maxerise = src%maxerise + self%hguess = src%hguess + self%exact_rf = src%exact_rf + self%average_conv = src%average_conv + self%tsopt = src%tsopt + self%iupdat = src%iupdat + self%opt_engine = src%opt_engine + self%lbfgs_histsize = src%lbfgs_histsize + self%hess_init = src%hess_init + self%logextxyz = src%logextxyz + +! ── smooth-function parameters ─────────────────────────────────────────────── + self%L = src%L + self%k = src%k + self%shift = src%shift + self%scaling = src%scaling + +! ── printout and I/O ───────────────────────────────────────────────────────── + self%pr_energies = src%pr_energies + self%eout_unit = src%eout_unit + if (allocated(src%elog)) self%elog = src%elog + +! ── ONIOM integer maps ─────────────────────────────────────────────────────── + if (allocated(src%ONIOMmap)) self%ONIOMmap = src%ONIOMmap + if (allocated(src%ONIOMrevmap)) self%ONIOMrevmap = src%ONIOMrevmap + +! ── thermochemistry ────────────────────────────────────────────────────────── + self%do_HR = src%do_HR + self%full_HR = src%full_HR + self%hu_steps = src%hu_steps + self%nt = src%nt + self%ithr = src%ithr + self%fscal = src%fscal + self%sthr = src%sthr + self%initialize_hr_type = src%initialize_hr_type + self%mh_type = src%mh_type + self%hr_hu_type = src%hr_hu_type + self%deform_opt_hess = src%deform_opt_hess + self%doh_stepsize = src%doh_stepsize + self%chess_id_guess = src%chess_id_guess + self%g_sampling = src%g_sampling + self%gs_hess_type = src%gs_hess_type + if (allocated(src%emodel)) self%emodel = src%emodel + if (allocated(src%temperatures)) self%temperatures = src%temperatures + if (allocated(src%et)) self%et = src%et + if (allocated(src%ht)) self%ht = src%ht + if (allocated(src%gt)) self%gt = src%gt + if (allocated(src%stot)) self%stot = src%stot + +!> NOTE: API handle objects (g0calc, ONIOM, ONIOMmols, chess) are NOT copied; +!> they hold C-level or heavy reconstructed state and are re-initialized. return end subroutine calculation_copy !=========================================================================================! + subroutine calculation_set_freeze_range(self,nat,start,finish) + class(calcdata) :: self + integer,intent(in) :: nat,start,finish + integer :: i,k + if (allocated(self%freezelist)) deallocate (self%freezelist) + allocate (self%freezelist(nat),source=.false.) + k = 0 + do i = 1,nat + + if (i >= start.and.i <= finish) then + k = k+1 + self%freezelist(i) = .true. + end if + end do + self%nfreeze = k + end subroutine calculation_set_freeze_range + + subroutine calculation_set_freeze_bools(self,freezetmp) + class(calcdata) :: self + logical,intent(in) :: freezetmp(:) + integer :: nat + if (allocated(self%freezelist)) deallocate (self%freezelist) + nat = size(freezetmp,1) + allocate (self%freezelist(nat),source=.false.) + self%nfreeze = count(freezetmp) + self%freezelist(:) = freezetmp(:) + end subroutine calculation_set_freeze_bools subroutine calculation_freezegrad(self,grad) class(calcdata) :: self @@ -591,25 +821,43 @@ end subroutine calculation_freezegrad !=========================================================================================! + subroutine calculation_set_charge(self,dchrg) +!*********************************************************** +!* set the charge of all calculation_settings objects to +!* the specified dchrg +!*********************************************************** + implicit none + class(calcdata) :: self + integer,intent(in) :: dchrg + integer :: i,j + if (self%ncalculations > 0) then + j = dchrg + do i = 1,self%ncalculations + self%calcs(i)%chrg = j + end do + end if + return + end subroutine calculation_set_charge + subroutine calculation_increase_charge(self,dchrg) !****************************************************************** !* increase the charge of all calculation_settings objects by one !* or the specified dchrg -!****************************************************************** +!****************************************************************** implicit none class(calcdata) :: self integer,intent(in),optional :: dchrg integer :: i,j - if(self%ncalculations > 0)then - if(present(dchrg))then - j = dchrg - else - j = 1 - endif - do i=1,self%ncalculations - self%calcs(i)%chrg = self%calcs(i)%chrg + j - enddo - endif + if (self%ncalculations > 0) then + if (present(dchrg)) then + j = dchrg + else + j = 1 + end if + do i = 1,self%ncalculations + self%calcs(i)%chrg = self%calcs(i)%chrg+j + end do + end if return end subroutine calculation_increase_charge @@ -619,21 +867,21 @@ subroutine calculation_decrease_charge(self,dchrg) !****************************************************************** !* decrease the charge of all calculation_settings objects by one !* or the specified dchrg -!****************************************************************** +!****************************************************************** implicit none class(calcdata) :: self integer,intent(in),optional :: dchrg integer :: i,j - if(self%ncalculations > 0)then - if(present(dchrg))then - j = dchrg - else - j = 1 - endif - do i=1,self%ncalculations - self%calcs(i)%chrg = self%calcs(i)%chrg - j - enddo - endif + if (self%ncalculations > 0) then + if (present(dchrg)) then + j = dchrg + else + j = 1 + end if + do i = 1,self%ncalculations + self%calcs(i)%chrg = self%calcs(i)%chrg-j + end do + end if return end subroutine calculation_decrease_charge @@ -659,9 +907,9 @@ subroutine calc_set_active(self,ids) self%calcs(i)%active = .false. else !>--- and all other to active - if(self%calcs(i)%weight == 0.0_wp)then - self%calcs(i)%weight = 1.0_wp - endif + if (self%calcs(i)%weight == 0.0_wp) then + self%calcs(i)%weight = 1.0_wp + end if self%calcs(i)%active = .true. end if end do @@ -806,19 +1054,24 @@ end subroutine calculation_ONIOMexpand !========================================================================================! - subroutine calculation_info(self,iunit) + subroutine calculation_info(self,iunit,printhdr) implicit none class(calcdata) :: self integer,intent(in) :: iunit + logical,intent(in),optional :: printhdr integer :: i,j character(len=*),parameter :: fmt1 = '(1x,a20," : ",i5)' character(len=*),parameter :: fmt2 = '(1x,a20," : ",f12.5)' character(len=20) :: atmp integer :: constraintype(8) + logical :: prhdr - write (iunit,'(1x,a)') '----------------' - write (iunit,'(1x,a)') 'Calculation info' - write (iunit,'(1x,a)') '----------------' + prhdr=.true.; if(present(printhdr)) prhdr = printhdr + if(prhdr)then + write (iunit,'(1x,a)') '----------------' + write (iunit,'(1x,a)') 'Calculation info' + write (iunit,'(1x,a)') '----------------' + endif if (self%ncalculations <= 0) then write (iunit,'("> ",a)') 'No calculation levels set up!' else if (self%ncalculations > 1) then @@ -840,11 +1093,13 @@ subroutine calculation_info(self,iunit) write (iunit,'("> ",a)') 'User-defined constraints:' if (self%nconstraints <= 20) then do i = 1,self%nconstraints + if (.not.self%cons(i)%active) cycle call self%cons(i)%print(iunit) end do else constraintype(:) = 0 do i = 1,self%nconstraints + if (.not.self%cons(i)%active) cycle j = self%cons(i)%type if (j > 0.and.j < 9) then constraintype(j) = constraintype(j)+1 @@ -888,8 +1143,6 @@ subroutine calculation_info(self,iunit) return end subroutine calculation_info - - !=========================================================================================! !>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< CALCULATION_SETTINGS associated routines @@ -916,6 +1169,8 @@ subroutine calculation_settings_deallocate(self) if (allocated(self%solvent)) deallocate (self%solvent) if (allocated(self%tblite)) deallocate (self%tblite) if (allocated(self%g0calc)) deallocate (self%g0calc) + if (allocated(self%solv)) deallocate (self%solv) + if (allocated(self%eeq)) deallocate (self%eeq) if (allocated(self%ff_dat)) deallocate (self%ff_dat) if (allocated(self%libpvol)) deallocate (self%libpvol) @@ -936,6 +1191,7 @@ subroutine calculation_settings_deallocate(self) self%tblitelvl = 2 self%etemp = 300.0_wp + self%etemp_user_set = .false. self%accuracy = 1.0_wp self%apiclean = .false. self%maxscc = 500 @@ -947,9 +1203,120 @@ subroutine calculation_settings_deallocate(self) self%ONIOM_highlowroot = 0 self%ONIOM_id = 0 + return end subroutine calculation_settings_deallocate + subroutine calculation_settings_copy(self,src) + implicit none + class(calculation_settings),intent(out) :: self + type(calculation_settings) :: src + +! ── identity and printout ──────────────────────────────────────────────────── + self%id = src%id + self%prch = src%prch + self%pr = src%pr + self%prappend = src%prappend + self%prstdout = src%prstdout + self%refine_lvl = src%refine_lvl + +! ── system ─────────────────────────────────────────────────────────────────── + self%chrg = src%chrg + self%uhf = src%uhf + self%multiplicity = src%multiplicity + self%active = src%active + self%weight = src%weight + +! ── allocatable strings ────────────────────────────────────────────────────── + if (allocated(src%calcspace)) self%calcspace = src%calcspace + if (allocated(src%calcfile)) self%calcfile = src%calcfile + if (allocated(src%gradfile)) self%gradfile = src%gradfile + if (allocated(src%path)) self%path = src%path + if (allocated(src%other)) self%other = src%other + if (allocated(src%binary)) self%binary = src%binary + if (allocated(src%systemcall)) self%systemcall = src%systemcall + if (allocated(src%description)) self%description = src%description + if (allocated(src%shortflag)) self%shortflag = src%shortflag + if (allocated(src%gradkey)) self%gradkey = src%gradkey + if (allocated(src%efile)) self%efile = src%efile + if (allocated(src%solvmodel)) self%solvmodel = src%solvmodel + if (allocated(src%solvent)) self%solvent = src%solvent + if (allocated(src%parametrisation))self%parametrisation= src%parametrisation + if (allocated(src%restartfile)) self%restartfile = src%restartfile + if (allocated(src%refgeo)) self%refgeo = src%refgeo + if (allocated(src%refcharges)) self%refcharges = src%refcharges + if (allocated(src%tbliteparam)) self%tbliteparam = src%tbliteparam + if (allocated(src%solv)) self%solv = src%solv + if (allocated(src%eeq)) self%eeq = src%eeq + +! ── gradient settings ──────────────────────────────────────────────────────── + self%numgrad = src%numgrad + self%gradstep = src%gradstep + self%rdgrad = src%rdgrad + self%gradtype = src%gradtype + self%gradfmt = src%gradfmt + +! ── property requests ──────────────────────────────────────────────────────── + self%rdwbo = src%rdwbo + self%rdqat = src%rdqat + self%dumpq = src%dumpq + self%rddip = src%rddip + self%dipole = src%dipole + self%rddipgrad = src%rddipgrad + self%getlmocent = src%getlmocent + self%nprot = src%nprot + if (allocated(src%getsasa)) self%getsasa = src%getsasa + if (allocated(src%efield)) self%efield = src%efield + if (allocated(src%wbo)) self%wbo = src%wbo + if (allocated(src%qat)) self%qat = src%qat + if (allocated(src%dipgrad)) self%dipgrad = src%dipgrad + if (allocated(src%protxyz)) self%protxyz = src%protxyz + +! ── API / backend settings ─────────────────────────────────────────────────── + self%tblitelvl = src%tblitelvl + self%etemp = src%etemp + self%etemp_user_set = src%etemp_user_set + self%accuracy = src%accuracy + self%apiclean = src%apiclean + self%maxscc = src%maxscc + self%saveint = src%saveint + self%ceh_guess = src%ceh_guess + self%restart = src%restart + + self%ngrid = src%ngrid + self%extpressure = src%extpressure + self%proberad = src%proberad + self%pvmodel = src%pvmodel + self%vdwset = src%vdwset + self%pvradscal = src%pvradscal + + self%nconfig = src%nconfig + if (allocated(src%config)) self%config = src%config + if (allocated(src%occ)) self%occ = src%occ + +! ── ONIOM identifiers ──────────────────────────────────────────────────────── + self%ONIOM_highlowroot = src%ONIOM_highlowroot + self%ONIOM_id = src%ONIOM_id + +! ── ORCA input block ───────────────────────────────────────────────────────── + self%ORCA%mpi = src%ORCA%mpi + self%ORCA%nlines = src%ORCA%nlines + if (allocated(src%ORCA%cmd)) self%ORCA%cmd = src%ORCA%cmd + if (allocated(src%ORCA%input)) self%ORCA%input = src%ORCA%input + +! ── inline potentials ──────────────────────────────────────────────────────── + self%ag = src%ag + self%penalty = src%penalty + +! ── MLIP settings ──────────────────────────────────────────────────────────── + self%MPAR = src%MPAR + self%MPAR%iid = 0 !> reset instance ID for parallelization + +!> NOTE: API handle objects (tblite, g0calc, ff_dat, libpvol) are NOT copied; +!> they hold C-level state and are re-initialized on first use. + return + end subroutine calculation_settings_copy + !=========================================================================================! subroutine calculation_settings_addconfig(self,config) @@ -993,6 +1360,9 @@ subroutine calculation_settings_autocomplete(self,id) self%description = trim(jobdescription(self%id+1)) call calculation_settings_shortflag(self) + !> ── keep the spin multiplicity aligned with the uhf carrier ───────────────── + call self%sync_multiplicity() + if (.not.allocated(self%calcspace)) then !> I've decided to perform all calculations in a separate directory to !> avoid accumulation of files in the main workspace @@ -1000,29 +1370,58 @@ subroutine calculation_settings_autocomplete(self,id) self%calcspace = 'calculation.level.'//trim(nmbr) end if - if (self%pr .and. self%prch.ne.stdout) then + if (self%pr.and.self%prch .ne. stdout) then self%prch = self%prch+id end if end subroutine calculation_settings_autocomplete +!========================================================================================! + + subroutine calc_sync_multiplicity(self) + !*************************************************************** + !* Align the spin multiplicity with the uhf carrier: * + !* multiplicity = uhf + 1 = (Nα-Nβ) + 1 = 2S+1. * + !* uhf is the single source of truth that survives copies and * + !* direct assignments; call this before any interface that * + !* consumes a multiplicity (ORCA, fmlip-relay). * + !*************************************************************** + implicit none + class(calculation_settings) :: self + self%multiplicity = self%uhf+1 + end subroutine calc_sync_multiplicity + +!========================================================================================! + + subroutine calc_set_multiplicity(self,mult) + !*************************************************************** + !* Set the spin state from a multiplicity (2S+1) value while * + !* keeping the uhf carrier (=2S=Nα-Nβ) aligned: uhf = mult-1. * + !*************************************************************** + implicit none + class(calculation_settings) :: self + integer,intent(in) :: mult + self%multiplicity = mult + self%uhf = mult-1 + end subroutine calc_set_multiplicity + !>--- create a short calculation info flag - subroutine calculation_settings_shortflag(self) + subroutine calculation_settings_shortflag(self) implicit none class(calculation_settings) :: self integer :: i,j - select case( self%id ) - case( jobtype%xtbsys ) + select case (self%id) + case (jobtype%xtbsys) self%shortflag = 'xtb subprocess' - case( jobtype%generic ) - self%shortflag = 'generic subprocess' - case( jobtype%turbomole ) + case (jobtype%generic) + self%shortflag = 'generic subprocess' + case (jobtype%turbomole) self%shortflag = 'TURBOMOLE subprocess' - case( jobtype%orca ) + case (jobtype%orca) self%shortflag = 'ORCA subprocess' - case( jobtype%terachem ) + case (jobtype%terachem) self%shortflag = 'TeraChem subprocess' - case( jobtype%tblite ) + case (jobtype%tblite) select case (self%tblitelvl) case (xtblvl%gfn2) self%shortflag = 'GFN2-xTB' @@ -1037,23 +1436,43 @@ subroutine calculation_settings_shortflag(self) case (xtblvl%param) self%shortflag = 'parameter file: '//trim(self%tbliteparam) end select - case( jobtype%gfn0 ) - self%shortflag = 'GFN0-xTB' - case( jobtype%gfn0occ ) - self%shortflag = 'GFN0-xTB*' - case( jobtype%gfnff ) - self%shortflag = 'GFN-FF' - case( jobtype%libpvol ) - self%shortflag = 'LIVPVOL' - case( jobtype%lj ) - self%shortflag = 'LJ' + case (jobtype%gfn0) + self%shortflag = 'GFN0-xTB' + case (jobtype%gfn0occ) + self%shortflag = 'GFN0-xTB*' + case (jobtype%gfnff) + self%shortflag = 'GFN-FF' + case (jobtype%libpvol) + self%shortflag = 'libpvol' + case (jobtype%lj) + self%shortflag = 'LJ' + case (jobtype%approxg) + self%shortflag = 'model Hessian gradient' + case (jobtype%penalty) + self%shortflag = 'penalty potential' + case (jobtype%mlip) + self%shortflag = 'MLIP' + case (jobtype%solvation) + self%shortflag = 'solvation' + if (allocated(self%solv)) then + if (allocated(self%solv%smodel)) & + & self%shortflag = self%shortflag//'/'//trim(self%solv%smodel) + if (allocated(self%solv%solvent)) & + & self%shortflag = self%shortflag//'('//trim(self%solv%solvent)//')' + end if + case (jobtype%electrostatic) + self%shortflag = 'EEQ' + if (allocated(self%eeq)) then + if (allocated(self%eeq%charge_model)) & + & self%shortflag = trim(self%eeq%charge_model) + end if case default self%shortflag = 'undefined' end select - if(allocated(self%solvmodel).and.allocated(self%solvent))then + if (allocated(self%solvmodel).and.allocated(self%solvent)) then self%shortflag = self%shortflag//'/'//trim(self%solvmodel) - self%shortflag = self%shortflag//'('//trim(self%solvent)//')' - endif + self%shortflag = self%shortflag//'('//trim(self%solvent)//')' + end if end subroutine calculation_settings_shortflag !>-- generate a unique print id for the calculation @@ -1095,7 +1514,7 @@ subroutine calculation_settings_info(self,iunit) character(len=20) :: atmp logical :: gxtbwarn - gxtbwarn=.false. + gxtbwarn = .false. if (allocated(self%description)) then write (iunit,'(" :",1x,a)') trim(self%description) @@ -1114,26 +1533,177 @@ subroutine calculation_settings_info(self,iunit) write (iunit,fmt4) 'Charge Extended Hückel (CEH) model' end select end if + + !> MLIP (fmlip-relay) backend details — print only what is meaningful + !> for the selected backend + if (self%id == jobtype%mlip) then + block + character(len=:),allocatable :: bk + bk = 'unknown' + if (allocated(self%MPAR%backend)) bk = trim(self%MPAR%backend) + ! ── friendly backend headline ─────────────────────────────────────── + select case (bk) + case ('uma') + write (iunit,fmt4) 'FairChem UMA foundation model (fairchem)' + case ('mace_off') + write (iunit,fmt4) 'MACE-OFF23 organic force field' + case ('mace_mp') + write (iunit,fmt4) 'MACE-MP foundation model (Materials Project)' + case ('mace') + write (iunit,fmt4) 'Custom MACE model' + case ('lj') + write (iunit,fmt4) 'Lennard-Jones potential' + case ('dummy') + write (iunit,fmt4) 'Dummy (zero) potential' + case default + write (iunit,fmt4) 'fmlip-relay backend: '//bk + end select + write (atmp,*) 'MLIP backend' + write (iunit,fmt3) atmp,bk + ! ── backend-specific model / task selection ───────────────────────── + select case (bk) + case ('uma') + write (atmp,*) 'UMA model' + if (allocated(self%MPAR%umamodel)) then + write (iunit,fmt3) atmp,trim(self%MPAR%umamodel) + else + write (iunit,fmt3) atmp,'uma-s-1p2 (default)' + end if + write (atmp,*) 'UMA task' + if (allocated(self%MPAR%umatask)) then + write (iunit,fmt3) atmp,trim(self%MPAR%umatask) + else + write (iunit,fmt3) atmp,'omol (default)' + end if + case ('mace_off','mace_mp') + if (allocated(self%MPAR%modelpath)) then + write (atmp,*) 'MACE model file' + write (iunit,fmt3) atmp,trim(self%MPAR%modelpath) + else + write (atmp,*) 'MACE model size' + if (allocated(self%MPAR%modelsize)) then + write (iunit,fmt3) atmp,trim(self%MPAR%modelsize) + else + write (iunit,fmt3) atmp,'medium (default)' + end if + end if + case default + if (allocated(self%MPAR%modelpath)) then + write (atmp,*) 'Model file' + write (iunit,fmt3) atmp,trim(self%MPAR%modelpath) + end if + end select + ! ── compute device: always shown for the torch-based backends ─────── + select case (bk) + case ('uma','mace_off','mace_mp','mace') + write (atmp,*) 'Compute device' + if (allocated(self%MPAR%device)) then + write (iunit,fmt3) atmp,trim(self%MPAR%device) + else + write (iunit,fmt3) atmp,'cpu (default)' + end if + case default + !> lj / dummy etc. have no torch device; show only if explicitly set + if (allocated(self%MPAR%device)) then + write (atmp,*) 'Compute device' + write (iunit,fmt3) atmp,trim(self%MPAR%device) + end if + end select + if (self%MPAR%BASE_PORT /= 54320) then + write (atmp,*) 'Socket base port' + write (iunit,fmt1) atmp,self%MPAR%BASE_PORT + end if + if (self%MPAR%TIMEOUT_SEC /= 120) then + write (atmp,*) 'Server timeout [s]' + write (iunit,fmt1) atmp,self%MPAR%TIMEOUT_SEC + end if + end block + end if + + !> composite implicit-solvation calculator details + if (self%id == jobtype%solvation .and. allocated(self%solv)) then + write (iunit,fmt4) 'Composite implicit solvation (ddX-based)' + if (allocated(self%solv%solvent)) then + write (atmp,*) 'solvent' + write (iunit,fmt3) atmp,trim(self%solv%solvent) + end if + if (allocated(self%solv%smodel)) then + write (atmp,*) 'continuum model' + write (iunit,fmt3) atmp,trim(self%solv%smodel) + end if + if (allocated(self%solv%charge_model)) then + write (atmp,*) 'charge model' + write (iunit,fmt3) atmp,trim(self%solv%charge_model) + end if + write (atmp,*) 'nonpolar params' + write (iunit,fmt3) atmp,'GFN2/ALPB' + write (atmp,*) 'H-bond term' + if (self%solv%do_hbond) then + write (iunit,fmt3) atmp,'on' + else + write (iunit,fmt3) atmp,'off' + end if + end if + + !> charge-equilibration electrostatics details + if (self%id == jobtype%electrostatic .and. allocated(self%eeq)) then + write (iunit,fmt4) 'Charge-equilibration electrostatics (multicharge)' + if (allocated(self%eeq%charge_model)) then + write (atmp,*) 'charge model' + write (iunit,fmt3) atmp,trim(self%eeq%charge_model) + end if + end if + if (any((/jobtype%orca,jobtype%xtbsys,jobtype%turbomole, & & jobtype%generic,jobtype%terachem/) == self%id)) then - if(index(self%binary,'gxtb').ne.0)then - write(iunit,fmt4) 'g-xTB (development version)' + if (index(self%binary,'gxtb') .ne. 0) then + write (iunit,fmt4) 'g-xTB (development version)' gxtbwarn = .true. - else + else write (iunit,'(" :",3x,a,a)') 'selected binary : ',trim(self%binary) - endif + end if end if if (self%refine_lvl > 0) then write (atmp,*) 'refinement stage' - write (iunit,fmt1) atmp,self%refine_lvl + block + character(len=20) :: rtmp + select case (self%refine_lvl) + case (1); rtmp = 'singlepoint' + case (2); rtmp = 'correction' + case (3); rtmp = 'geoopt' + case (5); rtmp = 'deltaG' + case (10); rtmp = 'post_opt' + case (11); rtmp = 'post_sp' + case (12); rtmp = 'post_reopt' + case default + write (rtmp,'(i0)') self%refine_lvl + end select + write (iunit,fmt3) atmp,trim(rtmp) + end block end if !> system data write (atmp,*) 'Molecular charge' write (iunit,fmt1) atmp,self%chrg - if (self%uhf /= 0) then - write (atmp,*) 'UHF parameter' - write (iunit,fmt1) atmp,self%uhf + !> spin: ORCA and the UMA (omol) backend consume a multiplicity (2S+1) + !> from the multiplicity store, the xtb-type methods consume uhf = Nα-Nβ + block + logical :: use_mult + use_mult = (self%id == jobtype%orca) + if (self%id == jobtype%mlip .and. allocated(self%MPAR%backend)) then + if (trim(self%MPAR%backend) == 'uma') use_mult = .true. + end if + if (use_mult) then + write (atmp,*) 'Multiplicity' + write (iunit,fmt1) atmp,self%multiplicity + else if (self%uhf /= 0) then + write (atmp,*) 'UHF parameter' + write (iunit,fmt1) atmp,self%uhf + end if + end block + if (self%id == jobtype%tblite .and. self%spin_polarized) then + write (atmp,*) 'Spin-polarization' + write (iunit,fmt3) atmp,'yes' end if if (allocated(self%solvmodel)) then @@ -1141,7 +1711,7 @@ subroutine calculation_settings_info(self,iunit) write (iunit,fmt3) atmp,trim(self%solvmodel) end if if (allocated(self%solvent)) then - write (atmp,*) 'Solvent' + write (atmp,*) 'Solvent' write (iunit,fmt3) atmp,trim(self%solvent) end if @@ -1173,31 +1743,45 @@ subroutine calculation_settings_info(self,iunit) end select write (iunit,fmt1) trim(atmp),self%ONIOM_id else - if(self%weight .ne. 1.0_wp)then + if (self%weight .ne. 1.0_wp) then write (atmp,*) 'Weight' write (iunit,fmt2) atmp,self%weight - endif + end if end if - if(gxtbwarn)then - write(iunit,fmt4) 'WARNING: This currently is the development version of g-xTB.' - write(iunit,fmt4) 'WARNING: Gradients are NUMERICAL (i.e., expensive and noisy!)' - endif + if (gxtbwarn) then + write (iunit,fmt4) 'WARNING: This currently is the development version of g-xTB.' + write (iunit,fmt4) 'WARNING: Gradients are NUMERICAL (i.e., expensive and noisy!)' + end if end subroutine calculation_settings_info !=========================================================================================! - subroutine create_calclevel_shortcut(self,levelstring) + subroutine create_calclevel_shortcut(self,levelstring, & + & chrg,uhf,solvmodel,solvent) !********************************************************************* !* subroutine create_calclevel_shortcut called with %create(...) !* Set up a calculation_settings object for a given level of theory !* More shortcuts can be added as required. +!* +!* Optional settings are for: +!* - molecular charge (integer) +!* - uhf parameter (integer) +!* - solvent/solventmodel (either none or BOTH must be present to work) +!* !* Be careful about the intent(out) setting! +!* Also, the routine is "dumb" and does not check if the user-provided +!* settings actually make sense for a create_calclevel_shortcutation. It very much +!* exists as an internal code shortcut only. !********************************************************************* implicit none class(calculation_settings),intent(out) :: self - character(len=*) :: levelstring + character(len=*),intent(in) :: levelstring + integer,intent(in),optional :: chrg + integer,intent(in),optional :: uhf + character(len=*),intent(in),optional :: solvmodel + character(len=*),intent(in),optional :: solvent call self%deallocate() select case (trim(levelstring)) case ('gfnff','--gff','--gfnff') @@ -1214,22 +1798,57 @@ subroutine create_calclevel_shortcut(self,levelstring) self%id = jobtype%turbomole self%rdgrad = .false. self%binary = 'gp3' - case ('gxtb','gxtb_dev') - self%id = jobtype%turbomole - self%rdgrad = .false. - self%binary = 'gxtb' - self%rdwbo = .false. - if(index(levelstring,'_dev').ne.0)then - self%other = '-grad' - self%rdgrad=.true. - endif - case ('orca') + case ('gxtb','--gxtb') + if (have_gxtb) then + self%id = jobtype%tblite + self%tblitelvl = xtblvl%gxtb + self%etemp = 0.0_wp ! g-xTB uses integer occupations (T=0) + else + self%id = jobtype%xtbsys + self%other = '--gxtb' + end if + case ('orca','--orca') self%id = jobtype%orca + case ('uma','--uma') + !> FairChem UMA via fmlip-relay; default to the molecular (omol) task head + self%id = jobtype%mlip + self%MPAR%backend = 'uma' + self%MPAR%umatask = 'omol' + + case ('maceoff','mace-off','mace_off','--maceoff') + !> MACE-OFF23 organic force field via fmlip-relay; default to medium size + self%id = jobtype%mlip + self%MPAR%backend = 'mace_off' + self%MPAR%modelsize = 'medium' + case ('generic') self%id = jobtype%generic end select + + if (present(chrg)) then + self%chrg = chrg + end if + + if (present(uhf)) then + self%uhf = uhf + end if + + !> both must be present to work + if (present(solvmodel).and.present(solvent)) then + !> the first two if-cases exist to convert cli args + !> into sensible keywords (required for legacy compatibility) + if (index(solvmodel,'gbsa') .ne. 0) then + self%solvmodel = 'gbsa' + else if (index(solvmodel,'alpb') .ne. 0) then + self%solvmodel = 'alpb' + else + self%solvmodel = trim(solvmodel) + end if + self%solvent = trim(solvent) + end if + call self%autocomplete(self%id) end subroutine create_calclevel_shortcut diff --git a/src/calculator/calculator.F90 b/src/calculator/calculator.F90 index 85e02162..4d0cd887 100644 --- a/src/calculator/calculator.F90 +++ b/src/calculator/calculator.F90 @@ -51,11 +51,12 @@ module crest_calculator !=========================================================================================! !>--- global engrad call counter - integer(int64),public :: engrad_total = 0 + real(wp),public :: engrad_total = 0.0_wp !>--- public module routines public :: potential_core public :: engrad + public :: preinit_mlip_parallel interface engrad module procedure :: engrad_mol end interface engrad @@ -126,7 +127,7 @@ subroutine engrad_mol(mol,calc,energy,gradient,iostatus) end do end if !>--- count the engrad call - engrad_total = engrad_total+1 + engrad_total = engrad_total+1.0_wp end if !>--- update ONIOM geometries @@ -297,6 +298,27 @@ subroutine engrad_mol(mol,calc,energy,gradient,iostatus) !********************************************** call calc%freezegrad(gradient) +!********************************************** +!>--- Hessian Reconstruct +!********************************************** + + if ((calc%do_HR.or.calc%deform_opt_hess).and.allocated(calc%chess)) then + if (calc%chess%track_step) then + call calc%chess%update(gradient,energy,mol%xyz) + !write(stdout,*) "HESSIAN CASH UPDATED" + end if + end if + +!********************************************* +!>--- store some outptut data to mol itself? +!********************************************* + mol%energy = energy + if (allocated(mol%gradient).or.mol%wrextxyz) then + !$omp critical + mol%gradient = gradient + !$omp end critical + end if + return end subroutine engrad_mol @@ -378,6 +400,20 @@ subroutine potential_core(molptr,calc,id,iostatus) & calc%etmp(id),calc%grdtmp(:,1:pnat,id)) calc%grdtmp(:,:,id) = calc%grdtmp(:,:,id)*autoaa + case (jobtype%approxg) + call modelhessian_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus) + + case (jobtype%penalty) + call rmsd_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus) + + case (jobtype%mlip) + call mlip_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus) + + case (jobtype%solvation) + call solvation_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus) + + case (jobtype%electrostatic) + call electrostatic_engrad(molptr,calc%calcs(id),calc%etmp(id),calc%grdtmp(:,1:pnat,id),iostatus) case default calc%etmp(id) = 0.0_wp calc%grdtmp(:,:,id) = 0.0_wp @@ -398,7 +434,7 @@ subroutine numgrad_core(molptr,calc,id,iostatus) integer :: i,j,k,l,ich,och,io,pnat type(coord),allocatable :: moltmp - real(wp) :: energy,el,er, step,step2 + real(wp) :: energy,el,er,step,step2 real(wp),allocatable :: ngrd(:,:) !real(wp),parameter :: step = 0.0005_wp !real(wp),parameter :: step2 = 0.5_wp/step @@ -410,13 +446,13 @@ subroutine numgrad_core(molptr,calc,id,iostatus) step = calc%calcs(id)%gradstep step2 = 0.5_wp/step - !> back up energy + !> back up energy energy = calc%etmp(id) !> allocate temprorary gradient space !$omp critical - allocate(ngrd(3,pnat), source=0.0_wp) - allocate(moltmp, source=molptr) + allocate (ngrd(3,pnat),source=0.0_wp) + allocate (moltmp,source=molptr) !$omp end critical do i = 1,molptr%nat @@ -437,8 +473,8 @@ subroutine numgrad_core(molptr,calc,id,iostatus) !> transfer tmp gradient to the calc object calc%grdtmp(:,1:pnat,id) = ngrd(:,1:pnat) !$omp critical - deallocate(moltmp) - deallocate(ngrd) + deallocate (moltmp) + deallocate (ngrd) !$omp end critical !> restore the energy @@ -543,6 +579,8 @@ subroutine numhess1(nat,at,xyz,calc,hess,io) allocate (gradr(3,mol%nat),source=0.0_wp) !dummy allocate (gradl(3,mol%nat),source=0.0_wp) !dummy + if (allocated(calc%chess)) calc%chess%track_step = .false. + do i = 1,mol%nat do j = 1,3 ii = (i-1)*3+j @@ -577,6 +615,8 @@ subroutine numhess1(nat,at,xyz,calc,hess,io) call engrad(mol,calc,el,gradl,io) !>- to get the gradient of the non-displaced structure + if (allocated(calc%chess)) calc%chess%track_step = .true. + deallocate (gradl,gradr) call mol%deallocate() return diff --git a/src/calculator/components/ddx_pc.F90 b/src/calculator/components/ddx_pc.F90 new file mode 100644 index 00000000..621ef943 --- /dev/null +++ b/src/calculator/components/ddx_pc.F90 @@ -0,0 +1,185 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module crest_ddx_pc +!******************************************************************************** +!* Standalone ddX point-charge solvation engine. +!* +!* Drives the low-level ddX API (COSMO/CPCM/PCM) with an *externally* supplied +!* set of atomic point charges, returning the electrostatic solvation free +!* energy and its Cartesian gradient. This is the method-independent analogue +!* of tblite's self-consistent reaction field: instead of variational xTB +!* charges it accepts fixed charges (e.g. EEQ/EEQ-BC) plus their geometry +!* derivative dq/dR, which adds the chain-rule term the SCRF case does not need. +!******************************************************************************** + use crest_parameters + use strucrd,only:coord +#ifdef WITH_DDX + use ddx,only:ddx_type,ddx_state_type,ddx_error_type,ddinit,allocate_state, & + & setup,fill_guess,fill_guess_adjoint,solve,solve_adjoint, & + & solvation_force_terms,check_error + use ddx_core,only:ddx_electrostatics_type + use ddx_multipolar_solutes,only:multipole_electrostatics,multipole_psi, & + & multipole_force_terms + use tblite_solvation_data,only:get_vdw_rad_cosmo + use tblite_mesh_lebedev,only:grid_size +#endif + implicit none + private + + public :: ddx_pc_engrad + +contains +!========================================================================================! + + subroutine ddx_pc_engrad(mol,q,smodel,eps,energy,gradient,iostatus,dqdr) + !*********************************************************************** + !* Electrostatic solvation energy + gradient for fixed point charges. + !* + !* mol : molecular structure (Bohr) + !* q(nat) : atomic point charges + !* smodel : 'cosmo' | 'cpcm' | 'pcm' + !* eps : solvent dielectric constant + !* energy : solvation free energy (out) + !* gradient : 3,nat Cartesian gradient (out, overwritten) + !* dqdr : optional dq_i/dR_a (3,nat,nat); adds the charge-response + !* term. If absent the charges are treated as geometry fixed. + !*********************************************************************** + type(coord),intent(in) :: mol + real(wp),intent(in) :: q(:) + character(len=*),intent(in) :: smodel + real(wp),intent(in) :: eps + real(wp),intent(out) :: energy + real(wp),intent(out) :: gradient(:,:) + integer,intent(out) :: iostatus + real(wp),intent(in),optional :: dqdr(:,:,:) +#ifdef WITH_DDX + type(ddx_type) :: ddx + type(ddx_state_type) :: state + type(ddx_error_type) :: error + type(ddx_electrostatics_type) :: elec + real(wp),allocatable :: rvdw(:),multipoles(:,:),force(:,:),jmat(:,:),dedq(:) + real(wp) :: feps,shift,sqrt4pi + integer :: model,nat,iat,jat,izp + real(wp),parameter :: conv = 1.0e-9_wp + + iostatus = 0 + nat = mol%nat + energy = 0.0_wp + gradient(:,:) = 0.0_wp + sqrt4pi = sqrt(4.0_wp*pi) + + ! ── model mapping + dielectric scaling factor (cf. tblite) ─────────────── + select case (trim(smodel)) + case ('cosmo') + model = 1; shift = -1.0_wp; feps = (eps-1.0_wp)/(eps+0.5_wp) + case ('cpcm') + model = 1; shift = -1.0_wp; feps = (eps-1.0_wp)/eps + case ('pcm') + model = 2; shift = 0.0_wp; feps = 1.0_wp + case default + iostatus = 1; return + end select + + ! ── van-der-Waals (COSMO) cavity radii ─────────────────────────────────── + allocate (rvdw(nat)) + do iat = 1,nat + izp = mol%at(iat) + rvdw(iat) = get_vdw_rad_cosmo(izp) + end do + + ! ── ddX model + state setup ────────────────────────────────────────────── + call ddinit(model,nat,mol%xyz,rvdw,eps,ddx,error,force=1,ngrid=grid_size(8), & + & lmax=1,eta=0.1_wp,shift=shift) + if (error%flag /= 0) then; iostatus = 1; return; end if + call allocate_state(ddx%params,ddx%constants,state,error) + if (error%flag /= 0) then; iostatus = 1; return; end if + + ! ── build RHS from the (normalized) monopole distribution ──────────────── + allocate (multipoles(1,nat)) + multipoles(1,:) = q(:)/sqrt4pi + elec%do_phi = .true.; elec%do_e = .true.; elec%do_g = .true. + call multipole_electrostatics(ddx%params,ddx%constants,ddx%workspace, & + & multipoles,0,elec,error) + call multipole_psi(ddx%params,multipoles,0,state%psi) + call setup(ddx%params,ddx%constants,ddx%workspace,state,elec,state%psi,error) + if (error%flag /= 0) then; iostatus = 1; return; end if + + ! ── solve primal + adjoint linear systems ──────────────────────────────── + call fill_guess(ddx%params,ddx%constants,ddx%workspace,state,conv,error) + call fill_guess_adjoint(ddx%params,ddx%constants,ddx%workspace,state,conv,error) + call solve(ddx%params,ddx%constants,ddx%workspace,state,conv,error) + call solve_adjoint(ddx%params,ddx%constants,ddx%workspace,state,conv,error) + if (error%flag /= 0) then; iostatus = 1; return; end if + + ! ── energy: feps * 1/2 ────────────────────────────────────────── + energy = feps*0.5_wp*sum(state%xs*state%psi) + + ! ── explicit (fixed-charge) gradient ───────────────────────────────────── + allocate (force(3,nat),source=0.0_wp) + call solvation_force_terms(ddx%params,ddx%constants,ddx%workspace,state, & + & elec,force,error) + call multipole_force_terms(ddx%params,ddx%constants,ddx%workspace,state, & + & 0,multipoles,force,error) + if (error%flag /= 0) then; iostatus = 1; return; end if + gradient(:,:) = feps*force(:,:) + + ! ── charge-response term: sum_i (dE/dq_i) * dq_i/dR ────────────────────── + if (present(dqdr)) then + allocate (jmat(ddx%constants%ncav,nat),source=0.0_wp) + call get_coulomb_matrix(mol%xyz,ddx%constants%ccav,jmat) + ! dE/dq_i = 1/2 feps ( -[J^T zeta]_i + sqrt(4pi) xs_0i ) + allocate (dedq(nat)) + dedq = 0.5_wp*feps*(-matmul(transpose(jmat),state%zeta)+sqrt4pi*state%xs(1,:)) + do iat = 1,nat + do jat = 1,nat + gradient(:,iat) = gradient(:,iat)+dedq(jat)*dqdr(:,iat,jat) + end do + end do + end if +#else + iostatus = 1 + energy = 0.0_wp + gradient(:,:) = 0.0_wp +#endif + end subroutine ddx_pc_engrad + +#ifdef WITH_DDX +!========================================================================================! + + subroutine get_coulomb_matrix(xyz,ccav,jmat) + !********************************************************* + !* 1/r Coulomb matrix between atoms and cavity points. + !********************************************************* + real(wp),intent(in) :: xyz(:,:) + real(wp),intent(in) :: ccav(:,:) + real(wp),intent(inout) :: jmat(:,:) + integer :: ic,jat + real(wp) :: vec(3) + do ic = 1,size(ccav,2) + do jat = 1,size(xyz,2) + vec(:) = ccav(:,ic)-xyz(:,jat) + jmat(ic,jat) = 1.0_wp/sqrt(sum(vec**2)) + end do + end do + end subroutine get_coulomb_matrix +#endif + +!========================================================================================! +end module crest_ddx_pc diff --git a/src/calculator/components/electrostatic.F90 b/src/calculator/components/electrostatic.F90 new file mode 100644 index 00000000..2be714e6 --- /dev/null +++ b/src/calculator/components/electrostatic.F90 @@ -0,0 +1,133 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module crest_electrostatic +!******************************************************************************** +!* Charge-equilibration electrostatic component. +!* +!* Thin wrapper around the multicharge library exposing the electrostatic +!* energy, its Cartesian gradient, the atomic partial charges and their +!* geometry derivative dq/dR for the EEQ (2019) and EEQ-BC (2025) models. +!* Used standalone and as the charge source for the ddX solvation engine. +!******************************************************************************** + use crest_parameters + use strucrd,only:coord +#ifdef WITH_TBLITE + use mctc_env,only:error_type + use mctc_io,only:structure_type,new + use mctc_cutoff,only:get_lattice_points + use multicharge_model,only:mchrg_model_type + use multicharge_param,only:new_eeq2019_model,new_eeqbc2025_model +#endif + implicit none + private + + public :: electrostatic_data + public :: electrostatic_core + +!> Bundled electrostatic settings (stored on calculation_settings) + type :: electrostatic_data + character(len=:),allocatable :: charge_model !> 'eeq' | 'eeqbc' + end type electrostatic_data + +contains +!========================================================================================! + + subroutine electrostatic_core(mol,chrg,model,energy,gradient,qat,iostatus,dqdr) + !*********************************************************************** + !* Charge-equilibration energy, gradient and atomic charges. + !* + !* mol : molecular structure (Bohr) + !* chrg : total molecular charge + !* model : 'eeq' (2019) | 'eeqbc' (2025) + !* energy : electrostatic energy (out) + !* gradient : 3,nat Cartesian gradient (out, overwritten) + !* qat : atomic partial charges (out) + !* dqdr : optional dq_i/dR_a (3,nat,nat) charge derivative (out) + !*********************************************************************** + type(coord),intent(in) :: mol + integer,intent(in) :: chrg + character(len=*),intent(in) :: model + real(wp),intent(out) :: energy + real(wp),intent(out) :: gradient(:,:) + real(wp),intent(out) :: qat(:) + integer,intent(out) :: iostatus + real(wp),intent(out),optional :: dqdr(:,:,:) +#ifdef WITH_TBLITE + type(structure_type) :: struc + class(mchrg_model_type),allocatable :: mchrg + type(error_type),allocatable :: error + real(wp),allocatable :: cn(:),dcndr(:,:,:),dcndL(:,:,:) + real(wp),allocatable :: qloc(:),dqlocdr(:,:,:),dqlocdL(:,:,:) + real(wp),allocatable :: trans(:,:),ener(:),sigma(:,:),dqdL(:,:,:) + integer :: nat + + iostatus = 0 + nat = mol%nat + energy = 0.0_wp + gradient(:,:) = 0.0_wp + + call new(struc,mol%at,mol%xyz,charge=real(chrg,wp)) + + select case (trim(model)) + case ('eeq') + call new_eeq2019_model(struc,mchrg,error) + case ('eeqbc') + call new_eeqbc2025_model(struc,mchrg,error) + case default + iostatus = 1; return + end select + if (allocated(error)) then; iostatus = 1; return; end if + + ! ── coordination numbers + local charges (and their derivatives) ───────── + allocate (cn(nat),qloc(nat)) + allocate (dcndr(3,nat,nat),dcndL(3,3,nat)) + allocate (dqlocdr(3,nat,nat),dqlocdL(3,3,nat)) + call get_lattice_points(struc%periodic,struc%lattice,mchrg%ncoord%cutoff,trans) + call mchrg%ncoord%get_coordination_number(struc,trans,cn,dcndr,dcndL) + call mchrg%local_charge(struc,trans,qloc,dqlocdr,dqlocdL) + + ! ── solve the equilibration: energy + gradient + charges (+ dq/dR) ─────── + allocate (ener(nat),source=0.0_wp) + allocate (sigma(3,3),source=0.0_wp) + !> branch on dqdr presence: forwarding a non-present optional to solve's + !> contiguous dummy segfaults under ifx. dqdr is only evaluated by solve + !> when dqdL is present as well, so it has to be passed too. + if (present(dqdr)) then + allocate (dqdL(3,3,nat),source=0.0_wp) + call mchrg%solve(struc,error,cn,qloc,dcndr,dcndL,dqlocdr,dqlocdL, & + & energy=ener,gradient=gradient,sigma=sigma,qvec=qat, & + & dqdr=dqdr,dqdL=dqdL) + else + call mchrg%solve(struc,error,cn,qloc,dcndr,dcndL,dqlocdr,dqlocdL, & + & energy=ener,gradient=gradient,sigma=sigma,qvec=qat) + end if + if (allocated(error)) then; iostatus = 1; return; end if + energy = sum(ener) +#else + iostatus = 1 + energy = 0.0_wp + gradient(:,:) = 0.0_wp + qat(:) = 0.0_wp + if (present(dqdr)) dqdr(:,:,:) = 0.0_wp +#endif + end subroutine electrostatic_core + +!========================================================================================! +end module crest_electrostatic diff --git a/src/calculator/libpvol.F90 b/src/calculator/components/libpvol.F90 similarity index 100% rename from src/calculator/libpvol.F90 rename to src/calculator/components/libpvol.F90 diff --git a/src/calculator/components/rmsdpot.f90 b/src/calculator/components/rmsdpot.f90 new file mode 100644 index 00000000..e6996ef9 --- /dev/null +++ b/src/calculator/components/rmsdpot.f90 @@ -0,0 +1,75 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2025 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module rmsdpot + use strucrd + use iso_fortran_env,only:wp => real64 + + implicit none + private + + type :: rmsdbias + integer :: nbias = 0 + real(wp),allocatable :: alpha(:) + real(wp),allocatable :: kpush(:) + integer,allocatable :: mult(:) + type(coord),pointer :: ptr_structures(:) + end type rmsdbias + + public :: rmsdbias + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + + subroutine rmsd_push_engrad(mol,rbias,energy,grad) +!************************************************************************* +!* Compute a repulsive energy and corresponding forces for +!* the similarity match between the currnt mol and a list of references +!************************************************************************* + implicit none + type(coord),intent(in) :: mol + type(rmsdbias) :: rbias + + real(wp),intent(inout) :: energy + real(wp),intent(inout) :: grad(3,mol%nat) + integer :: i,j,k,l + + real(wp) :: tmpe,ktot,rmssq + real(wp),allocatable :: tmpgrad(:,:) + + energy = 0.0_wp + grad = 0.0_wp + + do i=1,rbias%nbias + rmssq = 0.0_wp ** 2 + ktot = real(rbias%mult(i))*rbias%kpush(i)*real(mol%nat) + tmpe = ktot*exp(-rbias%alpha(i)*rmssq ) + + + enddo + + return + end subroutine rmsd_push_engrad + +!========================================================================================! +!========================================================================================! +end module rmsdpot diff --git a/src/calculator/components/solvation.F90 b/src/calculator/components/solvation.F90 new file mode 100644 index 00000000..da5c61b9 --- /dev/null +++ b/src/calculator/components/solvation.F90 @@ -0,0 +1,236 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module crest_solvation +!******************************************************************************** +!* Composite implicit-solvation contribution. +!* +!* Stitches the building-block components into a single, method-independent +!* solvation free energy and gradient that can be added on top of any parent +!* potential: +!* - charges (+ dq/dR) from the EEQ/EEQ-BC electrostatic component +!* - polar (electrostatic) free energy from the ddX continuum engine +!* - nonpolar surface-tension term from the SASA component +!* - optional charge-dependent hydrogen-bond term h_i S_i q_i^2 +!* +!* All settings and the cached (GFN2/ALPB) parameters are bundled in the +!* solvation_data object, which is stored on the calculation_settings and +!* passed together with the geometry to solvation_core, mirroring the +!* tblite/gfn0/gfnff calculators. +!******************************************************************************** + use crest_parameters + use strucrd,only:coord +#ifdef WITH_DDX + use crest_electrostatic,only:electrostatic_core + use crest_ddx_pc,only:ddx_pc_engrad + use crest_surface,only:surface_engrad + use mctc_env,only:error_type + use mctc_io,only:structure_type,new + use tblite_solvation_cds,only:cds_input + use tblite_solvation_data_cds,only:get_cds_param + use tblite_solvation,only:get_solvent_data,solvent_data +#endif + implicit none + private + + public :: solvation_data + public :: solvation_setup + public :: solvation_core + +!> Bundled solvation settings + cached parameters (stored on calculation_settings) + type :: solvation_data + !> user configuration + character(len=:),allocatable :: charge_model !> 'eeq' | 'eeqbc' + character(len=:),allocatable :: smodel !> 'cosmo' | 'cpcm' | 'pcm' + character(len=:),allocatable :: solvent !> e.g. 'water' + logical :: do_hbond = .true. + !> derived/cached by solvation_setup + logical :: loaded = .false. + real(wp) :: eps = 0.0_wp + real(wp) :: probe = 0.0_wp + real(wp),allocatable :: tension(:) !> per atom + real(wp),allocatable :: hbond(:) !> per atom (scaled) + real(wp),allocatable :: rad(:) !> per species (D3) + end type solvation_data + +contains +!========================================================================================! + + subroutine solvation_setup(mol,solv,iostatus) + !*********************************************************************** + !* Resolve the solvent dielectric constant and load the GFN2/ALPB CDS + !* nonpolar parameters for the given molecule into the solvation_data + !* object. Idempotent: skips once loaded. + !*********************************************************************** + type(coord),intent(in) :: mol + type(solvation_data),intent(inout) :: solv + integer,intent(out) :: iostatus +#ifdef WITH_DDX + type(solvent_data) :: sdat + real(wp),allocatable :: tension(:),hbond(:),rad(:) + real(wp) :: probe + character(len=:),allocatable :: solv_name + + iostatus = 0 + if (solv%loaded) return + if (.not.allocated(solv%charge_model)) solv%charge_model = 'eeqbc' + if (.not.allocated(solv%smodel)) solv%smodel = 'cpcm' + if (.not.allocated(solv%solvent)) then + iostatus = 1; return + end if + + !> dielectric constant from the solvent database (tblite calls water 'water') + solv_name = solv%solvent + if (solv_name == 'h2o') solv_name = 'water' + sdat = get_solvent_data(solv_name) + if (sdat%eps <= 0.0_wp) then + iostatus = 1; return + end if + solv%eps = sdat%eps + + !> GFN2/ALPB CDS nonpolar parameters + allocate (tension(mol%nat),hbond(mol%nat)) + call solvation_cds_params(mol,solv_name,tension,hbond,rad,probe,iostatus) + if (iostatus /= 0) return + call move_alloc(tension,solv%tension) + call move_alloc(hbond,solv%hbond) + call move_alloc(rad,solv%rad) + solv%probe = probe + solv%loaded = .true. +#else + iostatus = 1 +#endif + end subroutine solvation_setup + +!========================================================================================! + + subroutine solvation_core(mol,chrg,solv,energy,gradient,iostatus) + !*********************************************************************** + !* Total implicit-solvation energy and gradient for a prepared + !* solvation_data object (call solvation_setup first). + !* + !* mol : molecular structure (Bohr) + !* chrg : total molecular charge + !* solv : bundled settings + cached parameters + !* energy : total solvation free energy (out) + !* gradient : 3,nat Cartesian gradient (out, overwritten) + !*********************************************************************** + type(coord),intent(in) :: mol + integer,intent(in) :: chrg + type(solvation_data),intent(in) :: solv + real(wp),intent(out) :: energy + real(wp),intent(out) :: gradient(:,:) + integer,intent(out) :: iostatus +#ifdef WITH_DDX + real(wp),allocatable :: qat(:),dqdr(:,:,:),gtmp(:,:),sasa(:),dsdr(:,:,:) + real(wp) :: edum,etmp + integer :: nat,iat,jat + + iostatus = 0 + nat = mol%nat + energy = 0.0_wp + gradient(:,:) = 0.0_wp + allocate (qat(nat),dqdr(3,nat,nat),gtmp(3,nat),sasa(nat),dsdr(3,nat,nat)) + + ! ── charges + dq/dR from the electrostatic component ───────────────────── + call electrostatic_core(mol,chrg,solv%charge_model,edum,gtmp,qat,iostatus,dqdr=dqdr) + if (iostatus /= 0) return + + ! ── polar (ddX) free energy + gradient (incl. dq/dR chain term) ────────── + call ddx_pc_engrad(mol,qat,solv%smodel,solv%eps,etmp,gradient,iostatus,dqdr=dqdr) + if (iostatus /= 0) return + energy = etmp + + ! ── nonpolar surface-tension term ──────────────────────────────────────── + call surface_engrad(mol,solv%tension,etmp,gtmp,iostatus,sasa=sasa,dsdr=dsdr, & + & rad=solv%rad,probe=solv%probe) + if (iostatus /= 0) return + energy = energy+etmp + gradient(:,:) = gradient+gtmp + + ! ── charge-dependent hydrogen-bond term: E = sum_i h_i S_i q_i^2 ───────── + if (solv%do_hbond) then + energy = energy+sum(solv%hbond*sasa*qat**2) + do iat = 1,nat + do jat = 1,nat + gradient(:,iat) = gradient(:,iat) & + & +solv%hbond(jat)*qat(jat)**2*dsdr(:,iat,jat) & !> dS/dR part + & +solv%hbond(jat)*sasa(jat)*2.0_wp*qat(jat)*dqdr(:,iat,jat) !> dq/dR part + end do + end do + end if +#else + iostatus = 1 + energy = 0.0_wp + gradient(:,:) = 0.0_wp +#endif + end subroutine solvation_core + +!========================================================================================! + + subroutine solvation_cds_params(mol,solvent,tension,hbond,rad,probe,iostatus) + !*********************************************************************** + !* Fetch the GFN2/ALPB CDS nonpolar parameters for a given solvent from + !* tblite (no hard-coded numbers): per-atom surface tensions, per-atom + !* scaled hydrogen-bond strengths, per-species (D3) radii and the probe. + !* + !* We might want to switch this parameter getter out for something else + !* that is not hardcoded to the GFN2 calculator + !*********************************************************************** + type(coord),intent(in) :: mol + character(len=*),intent(in) :: solvent + real(wp),intent(out) :: tension(:) + real(wp),intent(out) :: hbond(:) + real(wp),allocatable,intent(out) :: rad(:) + real(wp),intent(out) :: probe + integer,intent(out) :: iostatus +#ifdef WITH_DDX + type(structure_type) :: struc + type(cds_input) :: inp + type(error_type),allocatable :: error + real(wp),allocatable :: hbspec(:) + integer :: iat + + iostatus = 0 + call new(struc,mol%at,mol%xyz) + inp%alpb = .true. + inp%solvent = solvent + call get_cds_param(inp,struc,'gfn2',error) + if (allocated(error).or..not.allocated(inp%tension)) then + iostatus = 1; return + end if + probe = inp%probe + rad = inp%rad + !> hydrogen-bond strength is scaled by the atomic surface (cf. tblite new_cds) + allocate (hbspec(size(inp%hbond))) + hbspec = inp%hbond/(4.0_wp*pi*(inp%rad+inp%probe)**2) + do iat = 1,mol%nat + tension(iat) = inp%tension(struc%id(iat)) + hbond(iat) = hbspec(struc%id(iat)) + end do +#else + iostatus = 1 + tension(:) = 0.0_wp + hbond(:) = 0.0_wp + probe = 0.0_wp +#endif + end subroutine solvation_cds_params + +!========================================================================================! +end module crest_solvation diff --git a/src/calculator/components/surface.F90 b/src/calculator/components/surface.F90 new file mode 100644 index 00000000..4a087456 --- /dev/null +++ b/src/calculator/components/surface.F90 @@ -0,0 +1,120 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +module crest_surface +!******************************************************************************** +!* Solvent-accessible-surface-area (SASA) component. +!* +!* Thin wrapper around tblite's surface integrator providing per-atom SASA and +!* the geometry-only nonpolar (cavity/dispersion) energy E = sum_i gamma_i S_i +!* together with its Cartesian gradient. This is the surface-tension part of +!* the xTB ALPB/GBSA CDS term; the charge-dependent hydrogen-bond contribution +!* is left to the solvation composite, where atomic charges are available. +!******************************************************************************** + use crest_parameters + use strucrd,only:coord +#ifdef WITH_TBLITE + use mctc_io,only:structure_type,new + use tblite_solvation_surface,only:surface_integrator,new_surface_integrator + use tblite_solvation_data,only:get_vdw_rad_cosmo +#endif + implicit none + private + + public :: surface_engrad + +#ifdef WITH_TBLITE + real(wp),parameter :: probe_default = 1.0_wp*aatoau !> probe radius (Bohr) + integer,parameter :: nang_default = 230 !> Lebedev grid points +#endif + +contains +!========================================================================================! + + subroutine surface_engrad(mol,tension,energy,gradient,iostatus,sasa,dsdr,rad,probe) + !*********************************************************************** + !* Nonpolar SASA energy and gradient. + !* + !* mol : molecular structure (Bohr) + !* tension : per-atom surface tensions gamma_i + !* energy : nonpolar energy sum_i gamma_i S_i (out) + !* gradient : 3,nat Cartesian gradient (out, overwritten) + !* sasa : optional per-atom solvent-accessible surface area (out) + !* dsdr : optional dS_i/dR_a (3,nat,nat); for charge-dependent terms + !* (e.g. hydrogen bonding) assembled by the caller + !* rad : optional per-species vdW radii (default: COSMO radii) + !* probe : optional solvent probe radius (default: 1.0 Angstrom) + !*********************************************************************** + type(coord),intent(in) :: mol + real(wp),intent(in) :: tension(:) + real(wp),intent(out) :: energy + real(wp),intent(out) :: gradient(:,:) + integer,intent(out) :: iostatus + real(wp),intent(out),optional :: sasa(:) + real(wp),intent(out),optional :: dsdr(:,:,:) + real(wp),intent(in),optional :: rad(:) + real(wp),intent(in),optional :: probe +#ifdef WITH_TBLITE + type(structure_type) :: struc + type(surface_integrator) :: integ + real(wp),allocatable :: vdw(:),surface(:),ds(:,:,:) + real(wp) :: prb + integer :: nat,iat,jat + + iostatus = 0 + nat = mol%nat + energy = 0.0_wp + gradient(:,:) = 0.0_wp + + call new(struc,mol%at,mol%xyz) + + ! ── per-species radii + probe + surface integrator ─────────────────────── + allocate (vdw(size(struc%num))) + if (present(rad)) then + vdw(:) = rad(:) + else + vdw(:) = get_vdw_rad_cosmo(struc%num) + end if + prb = probe_default + if (present(probe)) prb = probe + call new_surface_integrator(integ,struc%id,vdw,prb,nang_default) + + ! ── SASA and its Cartesian derivative ──────────────────────────────────── + allocate (surface(nat),ds(3,nat,nat)) + call integ%get_surface(struc,surface,ds) + + ! ── energy + gradient: E = sum_i gamma_i S_i ───────────────────────────── + energy = sum(surface*tension) + do iat = 1,nat + do jat = 1,nat + gradient(:,iat) = gradient(:,iat)+tension(jat)*ds(:,iat,jat) + end do + end do + if (present(sasa)) sasa(:) = surface(:) + if (present(dsdr)) dsdr(:,:,:) = ds(:,:,:) +#else + iostatus = 1 + energy = 0.0_wp + gradient(:,:) = 0.0_wp + if (present(sasa)) sasa(:) = 0.0_wp +#endif + end subroutine surface_engrad + +!========================================================================================! +end module crest_surface diff --git a/src/calculator/constraints.f90 b/src/calculator/constraints.f90 index c4629213..a36f3160 100644 --- a/src/calculator/constraints.f90 +++ b/src/calculator/constraints.f90 @@ -82,6 +82,7 @@ module constraints contains procedure :: print => print_constraint procedure :: deallocate => constraint_deallocate + procedure :: copy => constraint_copy procedure :: bondconstraint => create_bond_constraint generic,public :: sphereconstraint => create_sphere_constraint,create_sphere_constraint_all procedure,private :: create_sphere_constraint,create_sphere_constraint_all @@ -154,7 +155,7 @@ subroutine complete_defaults(self,mol) if (self%n .ne. 2) error stop '*** ERROR *** wrong number of atoms for bondrange constraint' if (.not.allocated(self%fc)) then allocate (self%fc(2)) - self%fc(1) = fcdefault/kB !> bondrange doesn't use 300K default! + self%fc(1) = fcdefault/kB !> bondrange doesn't use 300K default! self%fc(2) = betadefault else if (size(self%fc) < 2) error stop '*** ERROR *** wrong number of parameters for bondrange constraint' @@ -165,7 +166,7 @@ subroutine complete_defaults(self,mol) self%ref(2) = self%ref(1)-1.0_wp else dummy = minval(self%ref(:)) - self%ref(1) = maxval(self%ref(:)) + self%ref(1) = maxval(self%ref(:)) self%ref(2) = dummy end if @@ -288,7 +289,7 @@ subroutine calc_constraint(n,xyz,constr,energy,grd) energy = 0.0_wp grd = 0.0_wp - if(.not.constr%active) return + if (.not.constr%active) return select case (constr%type) case (bond) @@ -324,7 +325,7 @@ subroutine print_constraint(self,chnl) character(len=10) :: atm integer :: chnl logical :: pr - character(len=*),parameter :: headfmt ='("> constraint: ",a,a)' + character(len=*),parameter :: headfmt = '("> constraint: ",a,a)' if (self%type == 0) return pr = .true. select case (self%type) @@ -469,6 +470,27 @@ subroutine constraint_deallocate(self) return end subroutine constraint_deallocate + subroutine constraint_copy(self,src) + implicit none + class(constraint) :: self + type(constraint) :: src +!&> + if (allocated(src%atms )) self%atms = src%atms + if (allocated(src%ref )) self%ref = src%ref + if (allocated(src%fc )) self%fc = src%fc + self%active = src%active + self%type = src%type + self%n = src%n + self%frozenatms = src%frozenatms + if(src%frozenatms)then + call self%addfreeze(src%freezeptr) + endif + self%wscal = src%wscal + self%subtype = src%subtype +!&< + return + end subroutine constraint_copy + !========================================================================================! !> subroutine constraint_freezeassoc !> associate the freezeptr @@ -1082,10 +1104,10 @@ subroutine create_sphere_constraint(self,n,atms,r,k,alpha,logfermi) allocate (self%ref(3),source=r) ii = 0 do i = 1,n - if (atms(i))then - ii = ii +1 + if (atms(i)) then + ii = ii+1 self%atms(ii) = i - endif + end if end do self%ref(:) = r self%fc(1) = k @@ -1116,10 +1138,10 @@ subroutine create_ellips_constraint(self,n,atms,r,k,alpha,logfermi) allocate (self%ref(3),source=r) ii = 0 do i = 1,n - if (atms(i))then - ii = ii +1 + if (atms(i)) then + ii = ii+1 self%atms(ii) = i - endif + end if end do self%ref(:) = r(:) self%fc(1) = k diff --git a/src/calculator/gfnff_api.F90 b/src/calculator/gfnff_api.F90 index c9ec8ec4..d5ce3c0e 100644 --- a/src/calculator/gfnff_api.F90 +++ b/src/calculator/gfnff_api.F90 @@ -1,7 +1,7 @@ !================================================================================! ! This file is part of crest. ! -! Copyright (C) 2023 Philipp Pracht +! Copyright (C) 2023-2026 Philipp Pracht ! ! crest is free software: you can redistribute it and/or modify it under ! the terms of the GNU Lesser General Public License as published by @@ -58,6 +58,20 @@ module gfnff_api !========================================================================================! subroutine gfnff_api_setup(mol,chrg,ff_dat,io,pr,iunit) +!************************************************************* +!* Set up (initialize) a GFN-FF calculator from a coord mol. * +!* Lattice vectors are read from mol%lat when present, so * +!* PBC calculations are automatically activated. * +!* * +!* INPUT: * +!* mol - molecule (coords + optional lattice) * +!* chrg - total molecular charge * +!* pr - optional verbosity flag (logical) * +!* iunit - optional output unit * +!* OUTPUT: * +!* ff_dat - initialized GFN-FF data object * +!* io - error status (0 = success) * +!************************************************************* implicit none type(coord),intent(in) :: mol integer,intent(in) :: chrg @@ -66,18 +80,44 @@ subroutine gfnff_api_setup(mol,chrg,ff_dat,io,pr,iunit) integer,intent(in),optional :: iunit type(gfnff_data),allocatable,intent(inout) :: ff_dat type(coord) :: refmol + !> LOCAL + integer :: mylevel,myunit io = 0 + + ! ── map legacy pr/iunit to integer printlevel/printunit ────────────────── + mylevel = 0 + if (present(pr)) then + if (pr) mylevel = 2 + end if + if (present(iunit)) then + myunit = iunit + else + myunit = stdout + end if + #ifdef WITH_GFNFF if (allocated(ff_dat%refgeo)) then - !> initialize GFN-FF from a separate reference structure + ! ── initialize from a separate reference structure ────────────────────── call refmol%open(ff_dat%refgeo) - call gfnff_initialize(refmol%nat,refmol%at,refmol%xyz,ff_dat, & - & ichrg=chrg,print=pr,iostat=io,iunit=iunit) + if (allocated(refmol%lat)) then + call gfnff_initialize(refmol%nat,refmol%at,refmol%xyz,ff_dat, & + & ichrg=chrg,printlevel=mylevel,printunit=myunit,iostat=io, & + & lattice=refmol%lat,npbc=3) + else + call gfnff_initialize(refmol%nat,refmol%at,refmol%xyz,ff_dat, & + & ichrg=chrg,printlevel=mylevel,printunit=myunit,iostat=io) + end if call refmol%deallocate() else - !> initialize parametrization and topology of GFN-FF - call gfnff_initialize(mol%nat,mol%at,mol%xyz,ff_dat, & - & ichrg=chrg,print=pr,iostat=io,iunit=iunit) + ! ── initialize from mol directly ──────────────────────────────────────── + if (allocated(mol%lat)) then + call gfnff_initialize(mol%nat,mol%at,mol%xyz,ff_dat, & + & ichrg=chrg,printlevel=mylevel,printunit=myunit,iostat=io, & + & lattice=mol%lat,npbc=3) + else + call gfnff_initialize(mol%nat,mol%at,mol%xyz,ff_dat, & + & ichrg=chrg,printlevel=mylevel,printunit=myunit,iostat=io) + end if end if #else /* WITH_GFNFF */ @@ -89,7 +129,21 @@ end subroutine gfnff_api_setup !========================================================================================! - subroutine gfnff_sp(mol,ff_dat,energy,gradient,iostatus) + subroutine gfnff_sp(mol,ff_dat,energy,gradient,iostatus,sigma) +!************************************************************* +!* GFN-FF single-point energy + gradient for mol. * +!* When mol%lat is allocated the periodic singlepoint call * +!* is used automatically (lattice passed to gfnff). * +!* * +!* INPUT: * +!* mol - molecule (coords + optional lattice) * +!* ff_dat - initialized GFN-FF data object * +!* OUTPUT: * +!* energy - total energy (Hartree) * +!* gradient - gradient (Eh/Bohr) * +!* iostatus - error status (0 = success) * +!* sigma - optional stress tensor (Eh); zero non-PBC * +!************************************************************* implicit none !> INPUT type(coord),intent(in) :: mol @@ -97,16 +151,23 @@ subroutine gfnff_sp(mol,ff_dat,energy,gradient,iostatus) !> OUTPUT real(wp),intent(out) :: energy real(wp),intent(out) :: gradient(3,mol%nat) - integer,intent(out) :: iostatus + integer,intent(out) :: iostatus + real(wp),intent(out),optional :: sigma(3,3) !> LOCAL - logical :: fail + real(wp) :: sigma_loc(3,3) energy = 0.0_wp gradient = 0.0_wp iostatus = 0 - fail = .false. + sigma_loc = 0.0_wp #ifdef WITH_GFNFF - call gfnff_singlepoint(mol%nat,mol%at,mol%xyz,ff_dat, & - & energy,gradient,iostat=iostatus) + if (allocated(mol%lat)) then + call gfnff_singlepoint(mol%nat,mol%at,mol%xyz,ff_dat, & + & energy,gradient,lattice=mol%lat,sigma=sigma_loc,iostat=iostatus) + else + call gfnff_singlepoint(mol%nat,mol%at,mol%xyz,ff_dat, & + & energy,gradient,iostat=iostatus) + end if + if (present(sigma)) sigma = sigma_loc #else write (stdout,*) 'Error: Compiled without GFN-FF support!' write (stdout,*) 'Use -DWITH_GFNFF=true in the setup to enable this function' @@ -121,10 +182,8 @@ subroutine gfnff_printout(iunit,ff_dat) !> INPUT integer,intent(in) :: iunit type(gfnff_data),allocatable,intent(inout) :: ff_dat - !> LOCAL - logical :: fail #ifdef WITH_GFNFF - call print_gfnff_results(iunit,ff_dat%res,allocated(ff_dat%solvation)) + call ff_dat%resultprint(printunit=iunit) #else write (stdout,*) 'Error: Compiled without GFN-FF support!' write (stdout,*) 'Use -DWITH_GFNFF=true in the setup to enable this function' @@ -162,17 +221,16 @@ subroutine gfnff_dump_sasa(ff_dat,nat,atlist) integer,intent(in) :: nat integer :: i real(wp) :: sumsasa - if(allocated(ff_dat%solvation))then - if(allocated(ff_dat%solvation%sasa))then + if (allocated(ff_dat%solvation)) then + if (allocated(ff_dat%solvation%sasa)) then sumsasa = 0.0_wp - do i=1,nat - if(atlist(i)) sumsasa = sumsasa + ff_dat%solvation%sasa(i) - enddo - write(5454,*) sumsasa - endif - endif + do i = 1,nat + if (atlist(i)) sumsasa = sumsasa+ff_dat%solvation%sasa(i) + end do + write (5454,*) sumsasa + end if + end if end subroutine gfnff_dump_sasa !========================================================================================! !========================================================================================! end module gfnff_api - diff --git a/src/calculator/hessian_reconstruct.f90 b/src/calculator/hessian_reconstruct.f90 new file mode 100644 index 00000000..20db7311 --- /dev/null +++ b/src/calculator/hessian_reconstruct.f90 @@ -0,0 +1,168 @@ +module hessian_reconstruct + use iso_fortran_env,only:wp => real64 + use hessupdate_module + use optimize_maths + use crest_parameters + implicit none + private + + public cashed_hessian + + type :: cashed_hessian + + integer :: steps = 10 + real(wp),allocatable :: gradient(:,:,:) + real(wp),allocatable :: coords(:,:,:) + real(wp),allocatable :: energy(:) + real(wp),allocatable :: H(:,:) + integer,allocatable :: order(:),natm + integer :: stepcount = 0 + real(wp) :: hguess = 0.02_wp + real(wp),allocatable ::hess(:) + logical :: track_step = .true. + integer :: initialize_type != 0 + integer :: hu_type != 0 + + contains + + procedure :: alloc => cashed_hessian_allocate + procedure :: dealloc => cashed_hessian_deallocate + procedure :: update => update_cashed_hessian + procedure :: construct_hessian + + end type cashed_hessian + +contains + + subroutine cashed_hessian_allocate(self,N,steps,hguess,initialize_type, hu_type) !> maybe make keywords optional later + integer,intent(in) :: N,steps, initialize_type, hu_type + class(cashed_hessian),intent(inout) :: self + real(wp),intent(in) :: hguess + + + self%steps = steps + self%hguess = hguess + self%natm = N + self%initialize_type = initialize_type + self%hu_type = hu_type + allocate (self%gradient(steps,3,N)) + allocate (self%coords(steps,3,N)) + allocate (self%energy(steps)) + allocate (self%order(steps)) + allocate (self%hess((3*N*(3*N+1))/2)) + allocate (self%H(3*N,3*N)) + + self%order(:) = 0 + + end subroutine cashed_hessian_allocate + + subroutine cashed_hessian_deallocate(self) + class(cashed_hessian),intent(inout) :: self + + if (allocated(self%gradient)) deallocate (self%gradient) + if (allocated(self%coords)) deallocate (self%coords) + if (allocated(self%energy)) deallocate (self%energy) + if (allocated(self%order)) deallocate (self%order) + + end subroutine cashed_hessian_deallocate + + subroutine update_cashed_hessian(self,gradient,energy,coords) + class(cashed_hessian),intent(inout) :: self + real(wp),intent(in) :: gradient(:,:),energy,coords(:,:) + integer :: idx,i + + self%stepcount = self%stepcount+1 + idx = minloc(self%order,1) + self%order(idx) = self%stepcount + self%gradient(idx,:,:) = gradient + self%energy(idx) = energy + self%coords(idx,:,:) = coords + + end subroutine update_cashed_hessian + + subroutine construct_hessian(self) + class(cashed_hessian),intent(inout) :: self + integer :: i,j,k,nat3 + real(wp),allocatable :: tmp(:),tmp_coords(:,:),tmp_grads(:,:),dx(:) + real(wp) :: gnorm + integer :: unit,iter,made_iters + + nat3 = 3*self%natm + + allocate (tmp_coords(self%steps,nat3)) + allocate (tmp_grads(self%steps,nat3)) + allocate (tmp(self%steps)) + allocate (dx(nat3)) + + tmp = self%order + + tmp_coords = reshape(self%coords, [self%steps,nat3]) + tmp_grads = reshape(self%gradient, [self%steps,nat3]) + + made_iters = self%steps + + !>Hessian guess is installed previously in optimize routine but could also be read in explicitly for better readability? + + if (minval(tmp) == 0) then !> Implement keyword like exact HU that kills the process + made_iters = maxval(tmp) !> if made_iters This only happens if made_iters>steps + if (j == 1) then !> => Not affected if too many steps requested + dx = tmp_coords(j,:)-tmp_coords(self%steps,:) + call update_hessian(nat3,gnorm,tmp_grads(j,:),tmp_grads(self%steps,:),dx,self%hess(:),self%hu_type) + else + dx = tmp_coords(j,:)-tmp_coords(j-1,:) + call update_hessian(nat3,gnorm,tmp_grads(j,:),tmp_grads(j-1,:),dx,self%hess(:),self%hu_type) + end if + tmp(j) = HUGE(tmp(j)) + end if + end do + + call dhtosq(nat3,self%H(:,:),self%hess(:)) !>B needs to be renamed eventually! + + end subroutine construct_hessian + + subroutine update_hessian(nat3,gnorm,grd1,gold,dx,hess,hu_type) + !============================================== + !Wrapper for hessian update scheme selection + !============================================== + !class(cashed_hessian),intent(inout) :: self + integer,intent(in) :: nat3 + real(wp),intent(in) :: dx(:), grd1(:),gold(:) + real(wp),intent(in) :: gnorm + real(wp),intent(inout) :: hess(:) + integer,intent(in) :: hu_type + + select case (hu_type) + case (0) + call bfgs(nat3,gnorm,grd1,gold,dx,hess) + case (1) + call powell(nat3,gnorm,grd1,gold,dx,hess) + case (2) + call sr1(nat3,gnorm,grd1,gold,dx,hess) + case (3) + call bofill(nat3,gnorm,grd1,gold,dx,hess) + case (4) + call schlegel(nat3,gnorm,grd1,gold,dx,hess) + case default + write (*,*) 'invalid update selection for hessian reconstruction' + stop + end select + + end subroutine update_hessian + +end module hessian_reconstruct diff --git a/src/calculator/hr_utils.f90 b/src/calculator/hr_utils.f90 new file mode 100644 index 00000000..039553df --- /dev/null +++ b/src/calculator/hr_utils.f90 @@ -0,0 +1,152 @@ +module hr_utils + use iso_fortran_env,only:wp => real64 + use crest_calculator + use crest_parameters + use optimize_maths + use modelhessian_module + use axis_module + use strucrd + implicit none + private + + public initialize_hessian + +contains + + subroutine initialize_hessian(calc,type,xyz,nat,at,hess,hguess,pr) !>Matrix is forced to be positive definite + type(calcdata),intent(inout) :: calc + integer,intent(in) :: type + real(wp),intent(in) :: xyz(3,nat) + integer,intent(in) :: at(nat),nat + real(wp),intent(inout) :: hess(:) + real(wp),optional,intent(in) :: hguess + logical,intent(in) :: pr + type(calcdata),allocatable :: newcalc + type(mhparam) :: mhset + integer :: k,i,j,idx,io,nat3 + + real(wp),allocatable :: hess_full(:,:) + + real(wp),allocatable :: pmode(:,:),grad(:,:) + real(wp) :: rot(3),dumi + logical :: linear + type(coord) :: mol + + nat3 = 3*nat + + !!$omp critical + !allocate (pmode(nat3,1)) ! dummy allocated + !!$omp end critical + + !$omp critical + allocate (newcalc) + allocate (hess_full(nat3,nat3),source=0.0_wp) + !$omp end critical + + select case (type) + case (0) !>Initialize as a scaled identity + if (present(hguess)) then + k = 0 + do i = 1,nat3 + do j = 1,i + k = k+1 + if (i /= j) then + hess(k) = 0.0_wp + else + hess(k) = hguess + end if + end do + end do + else + write (stdout,*) "No hguess provided" + end if + case (1) + !$omp critical + !write(stdout,*) calc%calcs(1)%chrg + call newcalc%create('gfnff',chrg=calc%calcs(1)%chrg,uhf=calc%calcs(1)%uhf) !> Different levels?? and what happens to solvent?? + !$omp end critical + call numhess1(nat,at,xyz,newcalc,hess_full(:,:),io) + call dsqtoh(nat3,hess_full(:,:),hess(:)) !>Pack Hessian + case (2) + !$omp critical + call newcalc%create('gfn0',chrg=calc%calcs(1)%chrg,uhf=calc%calcs(1)%uhf) !> Different levels?? and what happens to solvent?? + !$omp end critical + call numhess1(nat,at,xyz,newcalc,hess_full(:,:),io) + call dsqtoh(nat3,hess_full(:,:),hess(:)) + case (3) + !$omp critical + call newcalc%create('gfn1',chrg=calc%calcs(1)%chrg,uhf=calc%calcs(1)%uhf) !> Different levels?? and what happens to solvent?? + !$omp end critical + call numhess1(nat,at,xyz,newcalc,hess_full(:,:),io) + call dsqtoh(nat3,hess_full(:,:),hess(:)) + case (4) + !$omp critical + call newcalc%create('gfn2',chrg=calc%calcs(1)%chrg,uhf=calc%calcs(1)%uhf) !> Different levels?? and what happens to solvent?? + !$omp end critical + call numhess1(nat,at,xyz,newcalc,hess_full(:,:),io) + call dsqtoh(nat3,hess_full(:,:),hess(:)) + case (5) + !$omp critical + mhset%model = calc%mh_type + call modhes(calc,mhset,nat,xyz,at,hess(:),pr) + !$omp end critical + end select + + !call axis(nat,at,xyz,rot,dumi) + !linear = (rot(3) .lt. 1.d-10).or.(nat == 2) + + !if (.not.linear) then + ! if (calc%nfreeze == 0) then + ! call trproj(nat,nat3,xyz,hess,.false.,0,pmode,1) !> normal + ! else + ! call trproj(nat,nat3,xyz,hess,.false.,calc%freezelist) !> fozen atoms + ! end if + !end if + + call force_positive_definiteness(hess,nat3) + + end subroutine initialize_hessian + + subroutine force_positive_definiteness(hess,nat3) + real(wp),intent(inout) :: hess(:) + integer,intent(in) :: nat3 + real(wp),allocatable :: eigvec(:,:),eigval(:) + real(wp),allocatable :: work(:) + integer,allocatable :: iwork(:) + integer :: lwork,liwork,info,i,j,k,l + real(wp) :: elow,damp + + allocate (eigvec(nat3,nat3),eigval(nat3)) + lwork = 1+6*nat3+2*nat3*nat3 + liwork = 8*nat3 + allocate (work(lwork),iwork(liwork)) + + call dspevd('V','U',nat3,hess(:),eigval,eigvec,nat3, & + work,lwork,iwork,liwork,info) + + if (info /= 0) then + write (*,*) "dspevd failed, info = ",info + stop + end if + + elow = minval(eigval) + damp = max(1.0e-4_wp-elow,0.0_wp) + eigval = eigval+damp + + hess(:) = 0.0_wp + k = 0 + do j = 1,nat3 + do i = 1,j + k = k+1 + hess(k) = 0.0_wp + do l = 1,nat3 + hess(k) = hess(k)+eigval(l)*eigvec(i,l)*eigvec(j,l) + end do + end do + end do + + deallocate (eigvec,eigval,work,iwork) + + end subroutine force_positive_definiteness + +end module hr_utils diff --git a/src/calculator/meson.build b/src/calculator/meson.build index da3d2598..57f15256 100644 --- a/src/calculator/meson.build +++ b/src/calculator/meson.build @@ -29,11 +29,22 @@ srcs += files( 'api_helpers.F90', 'api_engrad.f90', 'gradreader.f90', - 'libpvol.F90', + 'components/libpvol.F90', 'xtb_sc.f90', 'subprocess_types.f90', 'orca_sc.f90', 'generic_sc.f90', 'turbom_sc.f90', 'subprocess_engrad.f90', + 'hr_utils.f90', + 'modelhessians.f90', + 'approxg.f90', + 'penalty.f90', + 'mlip_sc.F90', + 'hessian_reconstruct.f90', + 'components/rmsdpot.f90', + 'components/ddx_pc.F90', + 'components/electrostatic.F90', + 'components/surface.F90', + 'components/solvation.F90', ) diff --git a/src/calculator/mlip_sc.F90 b/src/calculator/mlip_sc.F90 new file mode 100644 index 00000000..97682706 --- /dev/null +++ b/src/calculator/mlip_sc.F90 @@ -0,0 +1,234 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2026 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +!================================================================================! + +!> module mlip_sc +!> A module containing routines for calling MLIPs though persistent python instances +!> enabled through the fmlip_relay submodule + +!=========================================================================================! +module mlip_sc + use crest_parameters + use strucrd + use iomod +#ifdef WITH_FMLIP_RELAY + use fmlip_relay_client +#endif + implicit none + !>--- private module variables and parameters + private + + character(len=*),parameter :: basebin = 'fmlip-relay-server' + + public :: mlip_params + type :: mlip_params + integer :: BASE_PORT = 54320 + integer :: TIMEOUT_SEC = 120 + character(len=:),allocatable :: backend + character(len=:),allocatable :: modelpath + character(len=:),allocatable :: modelsize + !> shared neural-network options (mace* / uma backends) + character(len=:),allocatable :: device !> torch device: cpu | cuda | cuda:0 + !> FairChem UMA backend options (--backend uma) + character(len=:),allocatable :: umamodel !> checkpoint, e.g. uma-s-1p2 (default), uma-m-1 + character(len=:),allocatable :: umatask !> task head: omol | omat | omc | oc20 | odac + integer :: iid = 0 + end type mlip_params + + public :: mlip_engrad_core,fmlip_relay_init,mlips_shutdown + + integer,parameter :: nopbc(3) = (/0,0,0/) + integer,parameter :: allpbc(3) = (/1,1,1/) + real(wp),parameter :: bigcell(3,3) = reshape( & + & (/10000.0_wp,0.0_wp,0.0_wp, & + & 0.0_wp,10000.0_wp,0.0_wp, & + & 0.0_wp,0.0_wp,10000.0_wp/), [3,3]) + + external creststop +!========================================================================================! +!========================================================================================! +contains !>--- Module routines start here +!========================================================================================! +!========================================================================================! + + subroutine fmlip_relay_init(MPAR,iid) + type(mlip_params),intent(inout) :: MPAR + integer,intent(in) :: iid + integer :: io,tmpport + character(len=256) :: cmd,cmd_0,cmd_1 +#ifdef WITH_FMLIP_RELAY + if (.not.allocated(MPAR%backend)) then + write (stdout,*) + write (stdout,*) '** ERROR ** No model backend selected for MLIP' + write (stdout,*) + call creststop(20) + end if + + ! ── fast path: instance already running, nothing to do ─────────────────── + call mlip_ping(iid,io) + if (io == MLIP_OK) then + MPAR%iid = iid + return + end if + + call checkprog_silent(basebin,verbose=.false.,iostat=io) + if (io .ne. 0) then + write (stdout,*) + write (stdout,*) '** ERROR ** can not find socket server for MLIPs '//basebin + write (stdout,*) ' Make sure you install it from the fmlip_relay subproject via pip' + write (stdout,*) + call creststop(20) + end if + + !> check if we have limitations for parallelity + if (iid > MLIP_MAX_INSTANCES) then + write (stdout,*) + write (stdout,*) '** ERROR ** exeeding the max number of parallel socket servers for MLIPs ' + write (stdout,*) ' Please request fewer than '//to_str(MLIP_MAX_INSTANCES) + write (stdout,*) + call creststop(20) + end if + + !> options prepping + tmpport = MPAR%BASE_PORT+iid + write (cmd_1,'("--dtype float64")') + + select case (MPAR%backend) + case ('mace_off','mace_mp') + + if (allocated(MPAR%modelpath)) then + if (.not.file_exists(MPAR%modelpath)) then + write (stdout,*) + write (stdout,*) '** ERROR ** model path allocated but can not find '//trim(MPAR%modelpath) + write (stdout,*) + call creststop(20) + end if + !> a user-provided checkpoint is served through the generic 'mace' backend + cmd_0 = '--backend mace --model '//trim(MPAR%modelpath) + else + cmd_0 = '--backend '//trim(MPAR%backend) + if (allocated(MPAR%modelsize)) cmd_0 = trim(cmd_0)//' --mace-model '//trim(MPAR%modelsize) + end if + if (allocated(MPAR%device)) cmd_0 = trim(cmd_0)//' --device '//trim(MPAR%device) + write (cmd,'(a,1x,a,1x,i0,1x,a,1x,a)') basebin,'--port',tmpport,trim(adjustl(cmd_0)),trim(cmd_1) + + case ('uma') + !> FairChem UMA foundation model (fairchem-core v2). Charge and spin + !> multiplicity are forwarded per-call via the relay protocol; only the + !> checkpoint, task head and torch device are fixed at server startup. + cmd_0 = '' + if (allocated(MPAR%umamodel)) cmd_0 = trim(cmd_0)//' --uma-model '//trim(MPAR%umamodel) + if (allocated(MPAR%umatask)) cmd_0 = trim(cmd_0)//' --uma-task '//trim(MPAR%umatask) + if (allocated(MPAR%device)) cmd_0 = trim(cmd_0)//' --device '//trim(MPAR%device) + write (cmd,'(a,1x,a,1x,i0,1x,a,1x,a,1x,a)') basebin,'--port',tmpport, & + & '--backend uma',trim(adjustl(cmd_0)),trim(cmd_1) + + case default + + if (allocated(MPAR%modelpath)) then + if (.not.file_exists(MPAR%modelpath)) then + write (stdout,*) + write (stdout,*) '** ERROR ** model path allocated but can not find '//trim(MPAR%modelpath) + write (stdout,*) + call creststop(20) + end if + write (cmd,'(a,1x,a,1x,i0,2(1x,a,1x,a),1x,a)') basebin,'--port',tmpport,'--backend', & + & trim(MPAR%backend),'--model',trim(MPAR%modelpath),trim(cmd_1) + else + !> no model path (e.g. lj, dummy backends that need no model file) + write (cmd,'(a,1x,a,1x,i0,1x,a,1x,a,1x,a)') basebin,'--port',tmpport, & + & '--backend',trim(MPAR%backend),trim(cmd_1) + end if + end select + + !> spawn the server and verify + call mlip_init(iid,tmpport,trim(cmd)//' 2>/dev/null',MPAR%TIMEOUT_SEC,io) + if (io /= MLIP_OK) then + write (stdout,*) + write (stdout,*) '** ERROR ** failed to initialize MLIP server' + write (stdout,*) + call creststop(1) + end if + call mlip_ping(iid,io) + if (io /= MLIP_OK) then + write (stdout,*) + write (stdout,*) '** ERROR ** failed to ping MLIP server' + call creststop(1) + end if + + MPAR%iid = iid + +#else /* WITH_FMLIP_RELAY */ + write (stdout,*) 'Error: Compiled without fmlip-relay support!' + write (stdout,*) 'Use -DWITH_FMLIP_RELAY=true in the setup to enable this function' + write (stdout,*) + call creststop(20) +#endif + end subroutine fmlip_relay_init + + subroutine mlip_engrad_core(mol,MPAR,energy,gradient,iostatus, & + & charge,spin,iid) + type(coord),intent(in) :: mol + type(mlip_params),intent(in) :: MPAR + integer,intent(in),optional :: charge + integer,intent(in),optional :: spin + integer,intent(in),optional :: iid + real(wp),intent(out) :: energy + real(wp),intent(out) :: gradient(3,mol%nat) + integer,intent(out) :: iostatus + + integer :: chrg,spn,instance_id + real(wp) :: stress(3,3) + + energy = 0.0_wp + gradient(:,:) = 0.0_wp + iostatus = 1 + + chrg = 0 + spn = 1 + if (present(charge)) chrg = charge + if (present(spin)) spn = spin + instance_id = MPAR%iid + if (present(iid)) instance_id = iid + +#ifdef WITH_FMLIP_RELAY + if (allocated(mol%lat)) then + call mlip_compute(instance_id,mol%nat,mol%at,mol%xyz*autoaa,mol%lat,allpbc,0,chrg,spn, & + & energy,gradient,stress,iostatus) + else + call mlip_compute(instance_id,mol%nat,mol%at,mol%xyz*autoaa,bigcell,nopbc,0,chrg,spn, & + & energy,gradient,stress,iostatus) + end if + + !> CREST always works with atomic units, convert from eV and Angstroem: + energy = energy/autoev + gradient(:,:) = -gradient(:,:)*(1.0_wp/(autoev*aatoau)) +#endif + end subroutine mlip_engrad_core + +!========================================================================================! + + subroutine mlips_shutdown() + integer :: io +#ifdef WITH_FMLIP_RELAY + call mlip_finalize_all(io) +#endif + end subroutine mlips_shutdown + +!========================================================================================! +end module mlip_sc diff --git a/src/calculator/modelhessians.f90 b/src/calculator/modelhessians.f90 new file mode 100644 index 00000000..cd15ecb4 --- /dev/null +++ b/src/calculator/modelhessians.f90 @@ -0,0 +1,3145 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2021 - 2022 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . +! +! Routines were adapted from the xtb code (github.com/grimme-lab/xtb) +! under the Open-source software LGPL-3.0 Licencse. +!================================================================================! +module modelhessian_core + use iso_fortran_env,only:wp => real64,stdout => output_unit + implicit none + +!> a modelhessian type to save settings + type :: mhparam + integer :: model = 0 !> model hessian selection + real(wp) :: s6 = 20.0_wp !> dispersion scaling + real(wp) :: rcut = 70.0_wp !> cutoff parameter + !> force constants + real(wp) :: kr = 0.4000_wp + real(wp) :: kf = 0.1300_wp + real(wp) :: kt = 0.0075_wp + real(wp) :: ko = 0.0000_wp + real(wp) :: kd = 0.0000_wp + real(wp) :: kq = 0.0000_wp + end type mhparam + +!> Parameters & constants + real(wp),parameter :: bohr = 0.52917726_wp + real(wp),parameter :: aatoau = 1.0/bohr + real(wp),parameter :: pi = 3.141592653589793_wp + real(wp),parameter :: Zero = 0.0_wp + real(wp),parameter :: One = 1.0_wp + real(wp),parameter :: Two = 2.0_wp + real(wp),parameter :: Three = 3.0_wp + real(wp),parameter :: Four = 4.0_wp + real(wp),parameter :: Five = 5.0_wp + real(wp),parameter :: Six = 6.0_wp + real(wp),parameter :: Seven = 7.0_wp + real(wp),parameter :: Eight = 8.0_wp + real(wp),parameter :: RNine = 9.0_wp + real(wp),parameter :: Ten = 10.0_wp + real(wp),parameter :: Half = 0.5_wp + real(wp),parameter :: SqrtP2 = 0.8862269254527579_wp + real(wp),parameter :: TwoP34 = 0.2519794355383808_wp + real(wp),parameter :: TwoP54 = 5.914967172795612_wp + real(wp),parameter :: One2C2 = 0.2662567690426443D-04 + + !> van-der-Waals radii used in the D2 model (NOTE: here not in a.u.) + real(wp),parameter :: vander(86) = (/ & + & 0.91_wp,0.92_wp, & ! H, He + & 0.75_wp,1.28_wp,1.35_wp,1.32_wp,1.27_wp,1.22_wp,1.17_wp,1.13_wp, & ! Li-Ne + & 1.04_wp,1.24_wp,1.49_wp,1.56_wp,1.55_wp,1.53_wp,1.49_wp,1.45_wp, & ! Na-Ar + & 1.35_wp,1.34_wp, & ! K, Ca + & 1.42_wp,1.42_wp,1.42_wp,1.42_wp,1.42_wp, & ! Sc-Zn + & 1.42_wp,1.42_wp,1.42_wp,1.42_wp,1.42_wp, & + & 1.50_wp,1.57_wp,1.60_wp,1.61_wp,1.59_wp,1.57_wp, & ! Ga-Kr + & 1.48_wp,1.46_wp, & ! Rb, Sr + & 1.49_wp,1.49_wp,1.49_wp,1.49_wp,1.49_wp, & ! Y-Cd + & 1.49_wp,1.49_wp,1.49_wp,1.49_wp,1.49_wp, & + & 1.52_wp,1.64_wp,1.71_wp,1.72_wp,1.72_wp,1.71_wp, & ! In-Xe + & 2.00_wp,2.00_wp, & + & 2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp, & ! La-Yb + & 2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp, & + & 2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp, & ! Lu-Hg + & 2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp, & + & 2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp,2.00_wp/) ! Tl-Rn + !> C6 coefficients used in the D2 model + real(wp),parameter :: c6(86) = (/ & + & 0.14_wp,0.08_wp, & ! H,He + & 1.61_wp,1.61_wp,3.13_wp,1.75_wp,1.23_wp,0.70_wp,0.75_wp,0.63_wp, & + & 5.71_wp,5.71_wp,10.79_wp,9.23_wp,7.84_wp,5.57_wp,5.07_wp,4.61_wp, & + & 10.80_wp,10.80_wp, & ! K,Ca + & 10.80_wp,10.80_wp,10.80_wp,10.80_wp,10.80_wp, & ! Sc-Zn + & 10.80_wp,10.80_wp,10.80_wp,10.80_wp,10.80_wp, & + & 16.99_wp,17.10_wp,16.37_wp,12.64_wp,12.47_wp,12.01_wp, & ! Ga-Kr + & 24.67_wp,24.67_wp, & ! Rb,Sr + & 24.67_wp,24.67_wp,24.67_wp,24.67_wp,24.67_wp, & ! Y-Cd + & 24.67_wp,24.67_wp,24.67_wp,24.67_wp,24.67_wp, & + & 37.32_wp,38.71_wp,38.44_wp,31.74_wp,31.50_wp,29.99_wp, & ! In-Xe + & 50.00_wp,50.00_wp, & ! Cs,Ba + & 50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp, & ! La-Yb + & 50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp, & + & 50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp, & ! Lu-Hg + & 50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp, & + & 50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp,50.00_wp/) ! Tl-Rn + +!&< + integer, private, parameter :: max_elem = 118 + !> covalent radii (taken from Pyykko and Atsumi, Chem. Eur. J. 15, 2009, + ! 188-197), values for metals decreased by 10 % + real(wp),parameter :: covrad_2009(max_elem) = aatoau * [ & + & 0.32_wp,0.46_wp, & ! H,He + & 1.20_wp,0.94_wp,0.77_wp,0.75_wp,0.71_wp,0.63_wp,0.64_wp,0.67_wp, & ! Li-Ne + & 1.40_wp,1.25_wp,1.13_wp,1.04_wp,1.10_wp,1.02_wp,0.99_wp,0.96_wp, & ! Na-Ar + & 1.76_wp,1.54_wp, & ! K,Ca + & 1.33_wp,1.22_wp,1.21_wp,1.10_wp,1.07_wp, & ! Sc- + & 1.04_wp,1.00_wp,0.99_wp,1.01_wp,1.09_wp, & ! -Zn + & 1.12_wp,1.09_wp,1.15_wp,1.10_wp,1.14_wp,1.17_wp, & ! Ga-Kr + & 1.89_wp,1.67_wp, & ! Rb,Sr + & 1.47_wp,1.39_wp,1.32_wp,1.24_wp,1.15_wp, & ! Y- + & 1.13_wp,1.13_wp,1.08_wp,1.15_wp,1.23_wp, & ! -Cd + & 1.28_wp,1.26_wp,1.26_wp,1.23_wp,1.32_wp,1.31_wp, & ! In-Xe + & 2.09_wp,1.76_wp, & ! Cs,Ba + & 1.62_wp,1.47_wp,1.58_wp,1.57_wp,1.56_wp,1.55_wp,1.51_wp, & ! La-Eu + & 1.52_wp,1.51_wp,1.50_wp,1.49_wp,1.49_wp,1.48_wp,1.53_wp, & ! Gd-Yb + & 1.46_wp,1.37_wp,1.31_wp,1.23_wp,1.18_wp, & ! Lu- + & 1.16_wp,1.11_wp,1.12_wp,1.13_wp,1.32_wp, & ! -Hg + & 1.30_wp,1.30_wp,1.36_wp,1.31_wp,1.38_wp,1.42_wp, & ! Tl-Rn + & 2.01_wp,1.81_wp, & ! Fr,Ra + & 1.67_wp,1.58_wp,1.52_wp,1.53_wp,1.54_wp,1.55_wp,1.49_wp, & ! Ac-Am + & 1.49_wp,1.51_wp,1.51_wp,1.48_wp,1.50_wp,1.56_wp,1.58_wp, & ! Cm-No + & 1.45_wp,1.41_wp,1.34_wp,1.29_wp,1.27_wp, & ! Lr- + & 1.21_wp,1.16_wp,1.15_wp,1.09_wp,1.22_wp, & ! -Cn + & 1.36_wp,1.43_wp,1.46_wp,1.58_wp,1.48_wp,1.57_wp ] ! Nh-Og +!&> + + public :: ddvopt,mh_lindh_d2,mh_lindh,mh_swart + +!==============================================================================! +contains !> MODULE PROCEDURES START HERE +!==============================================================================! + + subroutine ddvopt(Cart,nAtoms,Hess,iANr,mhset) +!*********************************************************** +!* subroutine ddvopt +!* generates a Lindh Model Hessian +!* Chem. Phys. Let. 241(1995) 423-428 +!* +!* Input: +!* Cart - cartesian coordinates +!* nAtoms - number of atoms +!* iANr - atom types as integers +!* mhset - model Hessian parameters +!* +!* Output: +!* Hess - the (packed) model Hessian +!********************************************************** + Implicit Integer(i-n) + Implicit Real(wp) (a-h,o-z) + type(mhparam) :: mhset + + real(wp) :: s6 + real(wp) :: rcut + + Real(wp) :: Cart(3,nAtoms),rij(3),rjk(3),rkl(3), & + & Hess((3*nAtoms)*(3*nAtoms+1)/2),si(3),sj(3),sk(3), & + & sl(3),sm(3),x(2),y(2),z(2), & + & xyz(3,4),C(3,4),Dum(3,4,3,4) + Integer iANr(nAtoms) + +! include "common/ddvdt.inc" (molpro 2002.6) + Real(wp) :: rAV(3,3),aAV(3,3), & + & B_Str(6),A_Bend(2),A_Trsn(2),A_StrH(2), & + & rkr,rkf,A_Str,RF_Const, & + & wthr + + Data rAv/1.3500d+00,2.1000d+00,2.5300d+00, & + & 2.1000d+00,2.8700d+00,3.4000d+00, & + & 2.5300d+00,3.4000d+00,3.4000d+00/ + Data aAv/1.0000d+00,0.3949d+00,0.3949d+00, & + & 0.3949d+00,0.2800d+00,0.2800d+00, & + & 0.3949d+00,0.2800d+00,0.2800d+00/ +!org Data rkr,rkf,rkt/0.4500D+00,0.1500D+00,0.5000D-02/ + Data rkr,rkf,rkt/0.4000D+00,0.1300D+00,0.7500D-02/ + Data A_Str/1.734d0/ + Data B_Str/-.244d0,0.352d0,1.085d0,0.660d0,1.522d0,2.068d0/ + Data A_Bend/0.160d0,0.250d0/ + Data A_Trsn/0.0023d0,0.07d0/ + Data A_StrH/0.3601d0,1.944d0/ + Data RF_Const/1.0D-2/ + Data wthr/0.2/ + +!cc VDWx-Parameters (Grimme) used for vdw-correction of model hessian + real(wp) :: alphavdw,damp,c6k,c6l,c66,vdw(3,3),dr(3) + integer :: kxyz,lxyz +!cc End: VDWx ccccccccccccccccc + + !> BLAS + external :: dcopy + + s6 = mhset%s6 + rcut = mhset%rcut + +! +!------- Statement functions +! +! ixyz(i,iAtom) = (iAtom-1)*3 + i +! Jnd(i,j) = i*(i-1)/2 +j +! Ind(i,iAtom,j,jAtom)=Jnd(Max(ixyz(i,iAtom),ixyz(j,jAtom)), & +! & Min(ixyz(i,iAtom),ixyz(j,jAtom))) +!end + + Fact = One +!hjw threshold reduced + rZero = 1.0d-10 + n3 = 3*nAtoms + Hess = 0.0d0 + +! +! Hessian for tension +! + Do kAtom = 1,nAtoms + kr = iTabRow(iANr(kAtom)) +! If (kr.eq.0) Go To 5 + + Do lAtom = 1,kAtom-1 + lr = iTabRow(iANr(lAtom)) +! If (lr.eq.0) Go To 10 + xkl = Cart(1,kAtom)-Cart(1,lAtom) + ykl = Cart(2,kAtom)-Cart(2,lAtom) + zkl = Cart(3,kAtom)-Cart(3,lAtom) + rkl2 = xkl**2+ykl**2+zkl**2 + r0 = rAv(kr,lr) + alpha = aAv(kr,lr) + +!cccccc VDWx ccccccccccccccccccccccccccccccccc + c6k = c6(iANr(katom)) + c6l = c6(iANr(latom)) + c66 = sqrt(c6k*c6l) + Rv = (vander(iANr(katom))+vander(iANr(latom)))/bohr + + call getvdwxx(xkl,ykl,zkl,c66,s6,Rv,vdw(1,1)) + call getvdwxy(xkl,ykl,zkl,c66,s6,Rv,vdw(1,2)) + call getvdwxy(xkl,zkl,ykl,c66,s6,Rv,vdw(1,3)) + call getvdwxx(ykl,xkl,zkl,c66,s6,Rv,vdw(2,2)) + call getvdwxy(ykl,zkl,xkl,c66,s6,Rv,vdw(2,3)) + call getvdwxx(zkl,xkl,ykl,c66,s6,Rv,vdw(3,3)) +!cccccc Ende VDWx ccccccccccccccccccccccccccccccc + + gamma = rkr*Exp(alpha*r0**2) +! not better: *sqrt(abs(wb(kAtom,lAtom))) + gmm = gamma*Exp(-alpha*rkl2) + Hxx = gmm*xkl*xkl/rkl2-vdw(1,1) + Hxy = gmm*xkl*ykl/rkl2-vdw(1,2) + Hxz = gmm*xkl*zkl/rkl2-vdw(1,3) + Hyy = gmm*ykl*ykl/rkl2-vdw(2,2) + Hyz = gmm*ykl*zkl/rkl2-vdw(2,3) + Hzz = gmm*zkl*zkl/rkl2-vdw(3,3) + +! + Hess(Ind(1,kAtom,1,kAtom)) = Hess(Ind(1,kAtom,1,kAtom))+Hxx + Hess(Ind(2,kAtom,1,kAtom)) = Hess(Ind(2,kAtom,1,kAtom))+Hxy + Hess(Ind(2,kAtom,2,kAtom)) = Hess(Ind(2,kAtom,2,kAtom))+Hyy + Hess(Ind(3,kAtom,1,kAtom)) = Hess(Ind(3,kAtom,1,kAtom))+Hxz + Hess(Ind(3,kAtom,2,kAtom)) = Hess(Ind(3,kAtom,2,kAtom))+Hyz + Hess(Ind(3,kAtom,3,kAtom)) = Hess(Ind(3,kAtom,3,kAtom))+Hzz +! + Hess(Ind(1,kAtom,1,lAtom)) = Hess(Ind(1,kAtom,1,lAtom))-Hxx + Hess(Ind(1,kAtom,2,lAtom)) = Hess(Ind(1,kAtom,2,lAtom))-Hxy + Hess(Ind(1,kAtom,3,lAtom)) = Hess(Ind(1,kAtom,3,lAtom))-Hxz + Hess(Ind(2,kAtom,1,lAtom)) = Hess(Ind(2,kAtom,1,lAtom))-Hxy + Hess(Ind(2,kAtom,2,lAtom)) = Hess(Ind(2,kAtom,2,lAtom))-Hyy + Hess(Ind(2,kAtom,3,lAtom)) = Hess(Ind(2,kAtom,3,lAtom))-Hyz + Hess(Ind(3,kAtom,1,lAtom)) = Hess(Ind(3,kAtom,1,lAtom))-Hxz + Hess(Ind(3,kAtom,2,lAtom)) = Hess(Ind(3,kAtom,2,lAtom))-Hyz + Hess(Ind(3,kAtom,3,lAtom)) = Hess(Ind(3,kAtom,3,lAtom))-Hzz +! + Hess(Ind(1,lAtom,1,lAtom)) = Hess(Ind(1,lAtom,1,lAtom))+Hxx + Hess(Ind(2,lAtom,1,lAtom)) = Hess(Ind(2,lAtom,1,lAtom))+Hxy + Hess(Ind(2,lAtom,2,lAtom)) = Hess(Ind(2,lAtom,2,lAtom))+Hyy + Hess(Ind(3,lAtom,1,lAtom)) = Hess(Ind(3,lAtom,1,lAtom))+Hxz + Hess(Ind(3,lAtom,2,lAtom)) = Hess(Ind(3,lAtom,2,lAtom))+Hyz + Hess(Ind(3,lAtom,3,lAtom)) = Hess(Ind(3,lAtom,3,lAtom))+Hzz +! +10 Continue + End Do + +5 Continue + End Do + +! +! Hessian for bending +! + Do mAtom = 1,nAtoms + mr = iTabRow(iANr(mAtom)) +! If (mr.eq.0) Go To 20 + Do iAtom = 1,nAtoms + If (iAtom .eq. mAtom) Go To 30 + ir = iTabRow(iANr(iAtom)) +! If (ir.eq.0) Go To 30 + if (rcutoff(cart,iatom,matom,rcut)) cycle +! if(wb(iatom,matom).lt.wthr) cycle + Do jAtom = 1,iAtom-1 + If (jAtom .eq. mAtom) Go To 40 + jr = iTabRow(iANr(jAtom)) +! If (jr.eq.0) Go To 40 + if (rcutoff(cart,jatom,iatom,rcut)) cycle + if (rcutoff(cart,jatom,matom,rcut)) cycle +! if(wb(jatom,iatom).lt.wthr) cycle +! if(wb(jatom,matom).lt.wthr) cycle + + xmi = (Cart(1,iAtom)-Cart(1,mAtom)) + ymi = (Cart(2,iAtom)-Cart(2,mAtom)) + zmi = (Cart(3,iAtom)-Cart(3,mAtom)) + rmi2 = xmi**2+ymi**2+zmi**2 + rmi = sqrt(rmi2) + r0mi = rAv(mr,ir) + ami = aAv(mr,ir) +! + xmj = (Cart(1,jAtom)-Cart(1,mAtom)) + ymj = (Cart(2,jAtom)-Cart(2,mAtom)) + zmj = (Cart(3,jAtom)-Cart(3,mAtom)) + rmj2 = xmj**2+ymj**2+zmj**2 + rmj = sqrt(rmj2) + r0mj = rAv(mr,jr) + amj = aAv(mr,jr) +! +!---------- Test if zero angle +! + Test = xmi*xmj+ymi*ymj+zmi*zmj + Test = Test/(rmi*rmj) + If (Test .eq. One) Go To 40 +! + xij = (Cart(1,jAtom)-Cart(1,iAtom)) + yij = (Cart(2,jAtom)-Cart(2,iAtom)) + zij = (Cart(3,jAtom)-Cart(3,iAtom)) + rij2 = xij**2+yij**2+zij**2 + rrij = sqrt(rij2) +! + alpha = rkf*exp((ami*r0mi**2+amj*r0mj**2)) +! + r = sqrt(rmj2+rmi2) + gij = alpha*exp(-(ami*rmi2+amj*rmj2)) +! Write (*,*) ' gij=',gij + rL2 = (ymi*zmj-zmi*ymj)**2+(zmi*xmj-xmi*zmj)**2+ & + & (xmi*ymj-ymi*xmj)**2 +!hjw modified + if (rL2 .lt. 1.d-14) then + rL = 0 + else + rL = sqrt(rL2) + end if +! + if ((rmj .gt. rZero).and.(rmi .gt. rZero).and. & + & (rrij .gt. rZero)) Then + SinPhi = rL/(rmj*rmi) + rmidotrmj = xmi*xmj+ymi*ymj+zmi*zmj + CosPhi = rmidotrmj/(rmj*rmi) +! +!-------------None linear case +! + If (SinPhi .gt. rZero) Then +! Write (*,*) ' None linear case' + si(1) = (xmi/rmi*cosphi-xmj/rmj)/(rmi*sinphi) + si(2) = (ymi/rmi*cosphi-ymj/rmj)/(rmi*sinphi) + si(3) = (zmi/rmi*cosphi-zmj/rmj)/(rmi*sinphi) + sj(1) = (cosphi*xmj/rmj-xmi/rmi)/(rmj*sinphi) + sj(2) = (cosphi*ymj/rmj-ymi/rmi)/(rmj*sinphi) + sj(3) = (cosphi*zmj/rmj-zmi/rmi)/(rmj*sinphi) + sm(1) = -si(1)-sj(1) + sm(2) = -si(2)-sj(2) + sm(3) = -si(3)-sj(3) + Do icoor = 1,3 + Do jCoor = 1,3 + If (mAtom .gt. iAtom) Then + Hess(Ind(icoor,mAtom,jcoor,iAtom)) = & + & Hess(Ind(icoor,mAtom,jcoor,iAtom)) & + & +gij*sm(icoor)*si(jcoor) + else + Hess(Ind(icoor,iAtom,jcoor,mAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,mAtom)) & + & +gij*si(icoor)*sm(jcoor) + End If + If (mAtom .gt. jAtom) Then + Hess(Ind(icoor,mAtom,jcoor,jAtom)) = & + & Hess(Ind(icoor,mAtom,jcoor,jAtom)) & + & +gij*sm(icoor)*sj(jcoor) + else + Hess(Ind(icoor,jAtom,jcoor,mAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,mAtom)) & + & +gij*sj(icoor)*sm(jcoor) + End If + If (iAtom .gt. jAtom) Then + Hess(Ind(icoor,iAtom,jcoor,jAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,jAtom)) & + & +gij*si(icoor)*sj(jcoor) + else + Hess(Ind(icoor,jAtom,jcoor,iAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,iAtom)) & + & +gij*sj(icoor)*si(jcoor) + End If + End Do + End Do + Do icoor = 1,3 + Do jCoor = 1,icoor + Hess(Ind(icoor,iAtom,jcoor,iAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,iAtom)) & + & +gij*si(icoor)*si(jcoor) + Hess(Ind(icoor,mAtom,jcoor,mAtom)) = & + & Hess(Ind(icoor,mAtom,jcoor,mAtom)) & + & +gij*sm(icoor)*sm(jcoor) + Hess(Ind(icoor,jAtom,jcoor,jAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,jAtom)) & + & +gij*sj(icoor)*sj(jcoor) + +! + End Do + End Do + Else +! +!----------------Linear case +! + if ((abs(ymi) .gt. rZero).or. & +& (abs(xmi) .gt. rZero)) Then + x(1) = -ymi + y(1) = xmi + z(1) = Zero + x(2) = -xmi*zmi + y(2) = -ymi*zmi + z(2) = xmi*xmi+ymi*ymi + Else + x(1) = One + y(1) = Zero + z(1) = Zero + x(2) = Zero + y(2) = One + z(2) = Zero + End If + Do i = 1,2 + r1 = sqrt(x(i)**2+y(i)**2+z(i)**2) + cosThetax = x(i)/r1 + cosThetay = y(i)/r1 + cosThetaz = z(i)/r1 + si(1) = -cosThetax/rmi + si(2) = -cosThetay/rmi + si(3) = -cosThetaz/rmi + sj(1) = -cosThetax/rmj + sj(2) = -cosThetay/rmj + sj(3) = -cosThetaz/rmj + sm(1) = -(si(1)+sj(1)) + sm(2) = -(si(2)+sj(2)) + sm(3) = -(si(3)+sj(3)) +! + Do icoor = 1,3 + Do jCoor = 1,3 + If (mAtom .gt. iAtom) Then + Hess(Ind(icoor,mAtom,jcoor,iAtom)) = & + & Hess(Ind(icoor,mAtom,jcoor,iAtom)) & + & +gij*sm(icoor)*si(jcoor) + else + Hess(Ind(icoor,iAtom,jcoor,mAtom)) = & +& Hess(Ind(icoor,iAtom,jcoor,mAtom)) & +& +gij*si(icoor)*sm(jcoor) + End If + If (mAtom .gt. jAtom) Then + Hess(Ind(icoor,mAtom,jcoor,jAtom)) = & + & Hess(Ind(icoor,mAtom,jcoor,jAtom)) & + & +gij*sm(icoor)*sj(jcoor) + else + Hess(Ind(icoor,jAtom,jcoor,mAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,mAtom)) & + & +gij*sj(icoor)*sm(jcoor) + End If + If (iAtom .gt. jAtom) Then + Hess(Ind(icoor,iAtom,jcoor,jAtom)) = & +& Hess(Ind(icoor,iAtom,jcoor,jAtom)) & +& +gij*si(icoor)*sj(jcoor) + else + Hess(Ind(icoor,jAtom,jcoor,iAtom)) = & +& Hess(Ind(icoor,jAtom,jcoor,iAtom)) & +& +gij*sj(icoor)*si(jcoor) + End If + End Do + End Do + Do icoor = 1,3 + Do jCoor = 1,icoor + Hess(Ind(icoor,iAtom,jcoor,iAtom)) = & +& Hess(Ind(icoor,iAtom,jcoor,iAtom)) & +& +gij*si(icoor)*si(jcoor) + Hess(Ind(icoor,mAtom,jcoor,mAtom)) = & +& Hess(Ind(icoor,mAtom,jcoor,mAtom)) & +& +gij*sm(icoor)*sm(jcoor) + Hess(Ind(icoor,jAtom,jcoor,jAtom)) = & +& Hess(Ind(icoor,jAtom,jcoor,jAtom)) & +& +gij*sj(icoor)*sj(jcoor) + End Do + End Do + End Do + End If + End If +! +40 Continue + End Do +30 Continue + End Do +20 Continue + End Do +! +! Hessian for torsion +! + Do jAtom = 1,nAtoms + jr = iTabRow(iANr(jAtom)) +! If (jr.eq.0) Go To 444 +! + Call DCopy(3,Cart(1,jAtom),1,xyz(1,2),1) +! + Do kAtom = 1,nAtoms + If (kAtom .eq. jAtom) Go To 111 + kr = iTabRow(iANr(kAtom)) +! If (kr.eq.0) Go To 111 + + if (rcutoff(cart,katom,jatom,rcut)) cycle +! if(wb(katom,jatom).lt.wthr) cycle +! + Call DCopy(3,Cart(1,kAtom),1,xyz(1,3),1) +! + Do iAtom = 1,nAtoms + ij_ = nAtoms*(jAtom-1)+iAtom + If (iAtom .eq. jAtom) Go To 333 + If (iAtom .eq. kAtom) Go To 333 + ir = iTabRow(iANr(iAtom)) +! If (ir.eq.0) Go To 333 +! + if (rcutoff(cart,iatom,katom,rcut)) cycle + if (rcutoff(cart,iatom,jatom,rcut)) cycle +! if(wb(iatom,katom).lt.wthr) cycle +! if(wb(iatom,jatom).lt.wthr) cycle + + Call DCopy(3,Cart(1,iAtom),1,xyz(1,1),1) +! + Do lAtom = 1,nAtoms + lk_ = nAtoms*(kAtom-1)+lAtom + If (ij_ .le. lk_) Go To 222 + If (lAtom .eq. iAtom) Go To 222 + If (lAtom .eq. jAtom) Go To 222 + If (lAtom .eq. kAtom) Go To 222 + lr = iTabRow(iANr(lAtom)) +! If (lr.eq.0) Go To 222 +! + if (rcutoff(cart,latom,iatom,rcut)) cycle + if (rcutoff(cart,latom,katom,rcut)) cycle + if (rcutoff(cart,latom,jatom,rcut)) cycle +! if(wb(latom,iatom).lt.wthr) cycle +! if(wb(latom,katom).lt.wthr) cycle +! if(wb(latom,jatom).lt.wthr) cycle + + Call DCopy(3,Cart(1,lAtom),1,xyz(1,4),1) +! + rij(1) = Cart(1,iAtom)-Cart(1,jAtom) + rij(2) = Cart(2,iAtom)-Cart(2,jAtom) + rij(3) = Cart(3,iAtom)-Cart(3,jAtom) + rij0 = rAv(ir,jr)**2 + aij = aAv(ir,jr) +! + rjk(1) = Cart(1,jAtom)-Cart(1,kAtom) + rjk(2) = Cart(2,jAtom)-Cart(2,kAtom) + rjk(3) = Cart(3,jAtom)-Cart(3,kAtom) + rjk0 = rAv(jr,kr)**2 + ajk = aAv(jr,kr) +! + rkl(1) = Cart(1,kAtom)-Cart(1,lAtom) + rkl(2) = Cart(2,kAtom)-Cart(2,lAtom) + rkl(3) = Cart(3,kAtom)-Cart(3,lAtom) + rkl0 = rAv(kr,lr)**2 + akl = aAv(kr,lr) +! + rij2 = rij(1)**2+rij(2)**2+rij(3)**2 + rjk2 = rjk(1)**2+rjk(2)**2+rjk(3)**2 + rkl2 = rkl(1)**2+rkl(2)**2+rkl(3)**2 +! Allow only angles in the range of 35-145 + A35 = (35.0D0/180.D0)*Pi + CosFi_Max = Cos(A35) + CosFi2 = (rij(1)*rjk(1)+rij(2)*rjk(2)+rij(3)*rjk(3)) & + & /Sqrt(rij2*rjk2) + If (Abs(CosFi2) .gt. CosFi_Max) Go To 222 + CosFi3 = (rkl(1)*rjk(1)+rkl(2)*rjk(2)+rkl(3)*rjk(3)) & + & /Sqrt(rkl2*rjk2) + If (Abs(CosFi3) .gt. CosFi_Max) Go To 222 + + beta = rkt* & + & exp((aij*rij0+ajk*rjk0+akl*rkl0)) + tij = beta*exp(-(aij*rij2+ajk*rjk2+akl*rkl2)) + + Call Trsn(xyz,4,Tau,C,.False.,.False.,' ', & + & Dum,.False.) + Call DCopy(3,C(1,1),1,si,1) + Call DCopy(3,C(1,2),1,sj,1) + Call DCopy(3,C(1,3),1,sk,1) + Call DCopy(3,C(1,4),1,sl,1) +! +!-------------Off diagonal block +! + Do icoor = 1,3 + Do jCoor = 1,3 + Hess(Ind(icoor,iAtom,jcoor,jAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,jAtom)) & + & +tij*si(icoor)*sj(jcoor) + Hess(Ind(icoor,iAtom,jcoor,kAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,kAtom)) & + & +tij*si(icoor)*sk(jcoor) + Hess(Ind(icoor,iAtom,jcoor,lAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,lAtom)) & + & +tij*si(icoor)*sl(jcoor) + Hess(Ind(icoor,jAtom,jcoor,kAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,kAtom)) & + & +tij*sj(icoor)*sk(jcoor) + Hess(Ind(icoor,jAtom,jcoor,lAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,lAtom)) & + & +tij*sj(icoor)*sl(jcoor) + Hess(Ind(icoor,kAtom,jcoor,lAtom)) = & + & Hess(Ind(icoor,kAtom,jcoor,lAtom)) & + & +tij*sk(icoor)*sl(jcoor) + + End Do + End Do +! +!-------------Diagonal block +! + Do icoor = 1,3 + Do jCoor = 1,icoor + Hess(Ind(icoor,iAtom,jcoor,iAtom)) = & + & Hess(Ind(icoor,iAtom,jcoor,iAtom)) & + & +tij*si(icoor)*si(jcoor) + Hess(Ind(icoor,jAtom,jcoor,jAtom)) = & + & Hess(Ind(icoor,jAtom,jcoor,jAtom)) & + & +tij*sj(icoor)*sj(jcoor) + Hess(Ind(icoor,kAtom,jcoor,kAtom)) = & + & Hess(Ind(icoor,kAtom,jcoor,kAtom)) & + & +tij*sk(icoor)*sk(jcoor) + Hess(Ind(icoor,lAtom,jcoor,lAtom)) = & + & Hess(Ind(icoor,lAtom,jcoor,lAtom)) & + & +tij*sl(icoor)*sl(jcoor) + +! + End Do + End Do +222 Continue + End Do ! lAtom +333 Continue + End Do ! iAtom +111 Continue + End Do ! kAtom +444 Continue + End Do ! jAtom + Return + + contains + function ixyz(i,iatom) + integer :: ixyz + integer,intent(in) :: i,iatom + ixyz = (iatom-1)*3+i + end function ixyz + function jnd(i,j) + integer :: jnd + integer,intent(in) :: i,j + jnd = i*(i-1)/2+j + end function jnd + function ind(i,iatom,j,jatom) + integer :: ind + integer,intent(in) :: i,iatom,j,jatom + ind = jnd(max(ixyz(i,iatom),ixyz(j,jatom)),min(ixyz(i,iatom),ixyz(j,jatom))) + end function ind + end subroutine ddvopt + +!========================================================================================! +!########################################################################################! +!========================================================================================! + + subroutine mh_swart(xyz,n,hess,at,modh) +!**************************************************************************** +!* Swart's Model Hessian augmented with D2 +!* ------------------------------------------------------------------------ +!* Implemented after: +!* M. Swart, F. M. Bickelhaupt, Int. J. Quantum Chem., 2006, 106, 2536–2544. +!* DOI:10.1002/qua.21049 +!* +!* gij = exp[-(Rij/Cij-1)] +!* kij = rkr·gij +!* kijk = rkf·gij·gjk +!* kijkl = rkt·gij·gjk·gkl +!* +!* The proposed force constants by Swart are: +!* rkr = 0.35, rkf = 0.15, rkt = 0.005 +!* +!* This Hessian is additionally augmented with D2, please note that D2 +!* is not implemented in atomic units and requires some magical conversion +!* factor somewhere hidden in the implementation below. +!**************************************************************************** + implicit none + + integer,intent(in) :: n + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(out) :: hess((3*n)*(3*n+1)/2) + integer,intent(in) :: at(n) + type(mhparam),intent(in) :: modh + + integer :: n3 + real(wp),parameter :: rzero = 1.0e-10_wp + logical,allocatable :: lcutoff(:,:) + real(wp) :: kd + + allocate (lcutoff(n,n),source=.false.) + + n3 = 3*n + hess = 0.0d0 + +! the dispersion force constant is used relative to the stretch force constant + kd = modh%kd/modh%kr + + associate (rad => covrad_2009) + + call mh_swart_stretch(n,at,xyz,hess,modh%kr,kd,modh%s6,rad,rad,lcutoff,modh%rcut) + if (modh%kf .ne. 0.0_wp) & + call mh_swart_bend(n,at,xyz,hess,modh%kf,kd,rad,rad,lcutoff) + if (modh%kt .ne. 0.0_wp) & + call mh_swart_torsion(n,at,xyz,hess,modh%kt,kd,rad,rad,lcutoff) + if (modh%ko .ne. 0.0_wp) & + call mh_swart_outofp(n,at,xyz,hess,modh%ko,kd,rad,rad,lcutoff) + if (modh%kq .ne. 0.0_wp) then +! call new_charge_model_2019(chrgeq,n,at) + call mh_eeq(n,at,xyz,0.0_wp,modh%kq,hess) + end if + + end associate + + end subroutine mh_swart + + pure subroutine mh_swart_stretch(n,at,xyz,hess,kr,kd,s6,rcov,rvdw,lcutoff,rcut) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: kr + real(wp),intent(in) :: kd + real(wp),intent(in) :: s6 + real(wp),intent(in) :: rcov(:) + real(wp),intent(in) :: rvdw(:) + logical,intent(out) :: lcutoff(n,n) + real(wp),intent(in) :: rcut + + integer :: i,j + real(wp) :: xij,yij,zij,rij2,r0,d0 + real(wp) :: gmm + real(wp) :: c6i,c6j,c6ij,rv + real(wp) :: hxx,hxy,hxz,hyy,hyz,hzz + real(wp) :: vdw(3,3) + +!! ------------------------------------------------------------------------ +! Hessian for stretch +!! ------------------------------------------------------------------------ + stretch_iAt: do i = 1,n + + stretch_jAt: do j = 1,i-1 + + ! save for later + lcutoff(i,j) = rcutoff(xyz,i,j,rcut) + lcutoff(j,i) = lcutoff(i,j) + + xij = xyz(1,i)-xyz(1,j) + yij = xyz(2,i)-xyz(2,j) + zij = xyz(3,i)-xyz(3,j) + rij2 = xij**2+yij**2+zij**2 + r0 = rcov(at(i))+rcov(at(j)) + d0 = rvdw(at(i))+rvdw(at(j)) + + !cccccc vdwx ccccccccccccccccccccccccccccccccc + c6i = c6(at(i)) + c6j = c6(at(j)) + c6ij = sqrt(c6i*c6j) + rv = (vander(at(i))+vander(at(j)))*aatoau + + call getvdwxx(xij,yij,zij,c6ij,s6,rv,vdw(1,1)) + call getvdwxy(xij,yij,zij,c6ij,s6,rv,vdw(1,2)) + call getvdwxy(xij,zij,yij,c6ij,s6,rv,vdw(1,3)) + call getvdwxx(yij,xij,zij,c6ij,s6,rv,vdw(2,2)) + call getvdwxy(yij,zij,xij,c6ij,s6,rv,vdw(2,3)) + call getvdwxx(zij,xij,yij,c6ij,s6,rv,vdw(3,3)) + !cccccc ende vdwx ccccccccccccccccccccccccccccccc + + gmm = kr*fk_swart(1.0_wp,r0,rij2) & + +kr*kd*fk_vdw(5.0_wp,d0,rij2) + + !gmm = max(gmm,min_fk) + + hxx = gmm*xij*xij/rij2-vdw(1,1) + hxy = gmm*xij*yij/rij2-vdw(1,2) + hxz = gmm*xij*zij/rij2-vdw(1,3) + hyy = gmm*yij*yij/rij2-vdw(2,2) + hyz = gmm*yij*zij/rij2-vdw(2,3) + hzz = gmm*zij*zij/rij2-vdw(3,3) + + ! save diagonal elements for atom i + hess(ind(1,i,1,i)) = hess(ind(1,i,1,i))+hxx + hess(ind(2,i,1,i)) = hess(ind(2,i,1,i))+hxy + hess(ind(2,i,2,i)) = hess(ind(2,i,2,i))+hyy + hess(ind(3,i,1,i)) = hess(ind(3,i,1,i))+hxz + hess(ind(3,i,2,i)) = hess(ind(3,i,2,i))+hyz + hess(ind(3,i,3,i)) = hess(ind(3,i,3,i))+hzz + ! save elements between atom i and atom j + hess(ind(1,i,1,j)) = hess(ind(1,i,1,j))-hxx + hess(ind(1,i,2,j)) = hess(ind(1,i,2,j))-hxy + hess(ind(1,i,3,j)) = hess(ind(1,i,3,j))-hxz + hess(ind(2,i,1,j)) = hess(ind(2,i,1,j))-hxy + hess(ind(2,i,2,j)) = hess(ind(2,i,2,j))-hyy + hess(ind(2,i,3,j)) = hess(ind(2,i,3,j))-hyz + hess(ind(3,i,1,j)) = hess(ind(3,i,1,j))-hxz + hess(ind(3,i,2,j)) = hess(ind(3,i,2,j))-hyz + hess(ind(3,i,3,j)) = hess(ind(3,i,3,j))-hzz + ! save diagonal elements for atom j + hess(ind(1,j,1,j)) = hess(ind(1,j,1,j))+hxx + hess(ind(2,j,1,j)) = hess(ind(2,j,1,j))+hxy + hess(ind(2,j,2,j)) = hess(ind(2,j,2,j))+hyy + hess(ind(3,j,1,j)) = hess(ind(3,j,1,j))+hxz + hess(ind(3,j,2,j)) = hess(ind(3,j,2,j))+hyz + hess(ind(3,j,3,j)) = hess(ind(3,j,3,j))+hzz + + end do stretch_jAt + end do stretch_iAt + + end subroutine mh_swart_stretch + + pure subroutine mh_swart_bend(n,at,xyz,hess,kf,kd,rcov,rvdw,lcutoff) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: kf + real(wp),intent(in) :: kd + real(wp),intent(in) :: rcov(:) + real(wp),intent(in) :: rvdw(:) + logical,intent(in) :: lcutoff(n,n) + + integer :: i,j,m,ic,jc,ii + real(wp),parameter :: rzero = 1.0e-10_wp + real(wp) :: xij,yij,zij,rij2,rrij,r1 + real(wp) :: xmi,ymi,zmi,rmi2,rmi,r0mi,d0mj,gmi + real(wp) :: xmj,ymj,zmj,rmj2,rmj,r0mj,d0mi,gmj + real(wp) :: test,gij,rl2,rl,rmidotrmj + real(wp) :: sinphi,cosphi,costhetax,costhetay,costhetaz + real(wp) :: alpha + real(wp) :: si(3),sj(3),sm(3),x(2),y(2),z(2) + +!! ------------------------------------------------------------------------ +! Hessian for bending +!! ------------------------------------------------------------------------ + bend_mAt: do m = 1,n + bend_iAt: do i = 1,n + if (i .eq. m) cycle bend_iAt + if (lcutoff(i,m)) cycle bend_iAt + + xmi = (xyz(1,i)-xyz(1,m)) + ymi = (xyz(2,i)-xyz(2,m)) + zmi = (xyz(3,i)-xyz(3,m)) + rmi2 = xmi**2+ymi**2+zmi**2 + rmi = sqrt(rmi2) + r0mi = rcov(at(m))+rcov(at(i)) + d0mi = rvdw(at(m))+rvdw(at(i)) + + bend_jAt: do j = 1,i-1 + if (j .eq. m) cycle bend_jAt + if (lcutoff(j,i)) cycle bend_jAt + if (lcutoff(j,m)) cycle bend_jAt + + xmj = (xyz(1,j)-xyz(1,m)) + ymj = (xyz(2,j)-xyz(2,m)) + zmj = (xyz(3,j)-xyz(3,m)) + rmj2 = xmj**2+ymj**2+zmj**2 + rmj = sqrt(rmj2) + r0mj = rcov(at(m))+rcov(at(j)) + d0mj = rvdw(at(m))+rvdw(at(j)) + + ! test if zero angle + test = xmi*xmj+ymi*ymj+zmi*zmj + test = test/(rmi*rmj) + if (abs(test-1.0_wp) .lt. 1.0e-12_wp) cycle bend_jAt + + xij = (xyz(1,j)-xyz(1,i)) + yij = (xyz(2,j)-xyz(2,i)) + zij = (xyz(3,j)-xyz(3,i)) + rij2 = xij**2+yij**2+zij**2 + rrij = sqrt(rij2) + + gmi = fk_swart(1.0_wp,r0mi,rmi2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0mi,rmi2) + gmj = fk_swart(1.0_wp,r0mj,rmj2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0mj,rmj2) + + gij = kf*gmi*gmj + + rl2 = (ymi*zmj-zmi*ymj)**2+(zmi*xmj-xmi*zmj)**2+(xmi*ymj-ymi*xmj)**2 + + if (rl2 .lt. 1.e-14_wp) then + rl = 0.0_wp + else + rl = sqrt(rl2) + end if + + !gij = max(gij,min_fk) + + if ((rmj .gt. rzero).and.(rmi .gt. rzero).and.(rrij .gt. rzero)) then + sinphi = rl/(rmj*rmi) + rmidotrmj = xmi*xmj+ymi*ymj+zmi*zmj + cosphi = rmidotrmj/(rmj*rmi) + ! none linear case + if (sinphi .gt. rzero) then + si(1) = (xmi/rmi*cosphi-xmj/rmj)/(rmi*sinphi) + si(2) = (ymi/rmi*cosphi-ymj/rmj)/(rmi*sinphi) + si(3) = (zmi/rmi*cosphi-zmj/rmj)/(rmi*sinphi) + sj(1) = (cosphi*xmj/rmj-xmi/rmi)/(rmj*sinphi) + sj(2) = (cosphi*ymj/rmj-ymi/rmi)/(rmj*sinphi) + sj(3) = (cosphi*zmj/rmj-zmi/rmi)/(rmj*sinphi) + sm(1) = -si(1)-sj(1) + sm(2) = -si(2)-sj(2) + sm(3) = -si(3)-sj(3) + do ic = 1,3 + do jc = 1,3 + if (m .gt. i) then + hess(ind(ic,m,jc,i)) = hess(ind(ic,m,jc,i)) & + +gij*sm(ic)*si(jc) + else + hess(ind(ic,i,jc,m)) = hess(ind(ic,i,jc,m)) & + +gij*si(ic)*sm(jc) + end if + if (m .gt. j) then + hess(ind(ic,m,jc,j)) = hess(ind(ic,m,jc,j)) & + +gij*sm(ic)*sj(jc) + else + hess(ind(ic,j,jc,m)) = hess(ind(ic,j,jc,m)) & + +gij*sj(ic)*sm(jc) + end if + if (i .gt. j) then + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j)) & + +gij*si(ic)*sj(jc) + else + hess(ind(ic,j,jc,i)) = hess(ind(ic,j,jc,i)) & + +gij*sj(ic)*si(jc) + end if + end do + end do + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i))+gij*si(ic)*si(jc) + hess(ind(ic,m,jc,m)) = hess(ind(ic,m,jc,m))+gij*sm(ic)*sm(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j))+gij*sj(ic)*sj(jc) + end do + end do + else + ! linear case + if ((abs(ymi) .gt. rzero).or.(abs(xmi) .gt. rzero)) then + x(1) = -ymi + y(1) = xmi + z(1) = 0.0_wp + x(2) = -xmi*zmi + y(2) = -ymi*zmi + z(2) = xmi*xmi+ymi*ymi + else + x(1) = 1.0_wp + y(1) = 0.0_wp + z(1) = 0.0_wp + x(2) = 0.0_wp + y(2) = 1.0_wp + z(2) = 0.0_wp + end if + do ii = 1,2 + r1 = sqrt(x(ii)**2+y(ii)**2+z(ii)**2) + costhetax = x(ii)/r1 + costhetay = y(ii)/r1 + costhetaz = z(ii)/r1 + si(1) = -costhetax/rmi + si(2) = -costhetay/rmi + si(3) = -costhetaz/rmi + sj(1) = -costhetax/rmj + sj(2) = -costhetay/rmj + sj(3) = -costhetaz/rmj + sm(1) = -(si(1)+sj(1)) + sm(2) = -(si(2)+sj(2)) + sm(3) = -(si(3)+sj(3)) + ! + do ic = 1,3 + do jc = 1,3 + if (m .gt. i) then + hess(ind(ic,m,jc,i)) = hess(ind(ic,m,jc,i)) & + +gij*sm(ic)*si(jc) + else + hess(ind(ic,i,jc,m)) = hess(ind(ic,i,jc,m)) & + +gij*si(ic)*sm(jc) + end if + if (m .gt. j) then + hess(ind(ic,m,jc,j)) = hess(ind(ic,m,jc,j)) & + +gij*sm(ic)*sj(jc) + else + hess(ind(ic,j,jc,m)) = hess(ind(ic,j,jc,m)) & + +gij*sj(ic)*sm(jc) + end if + if (i .gt. j) then + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j)) & + +gij*si(ic)*sj(jc) + else + hess(ind(ic,j,jc,i)) = hess(ind(ic,j,jc,i)) & + +gij*sj(ic)*si(jc) + end if + end do + end do + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i)) & + +gij*si(ic)*si(jc) + hess(ind(ic,m,jc,m)) = hess(ind(ic,m,jc,m)) & + +gij*sm(ic)*sm(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j)) & + +gij*sj(ic)*sj(jc) + end do + end do + end do + + end if + end if + + end do bend_jAt + end do bend_iAt + end do bend_mAt + + end subroutine mh_swart_bend + + pure subroutine mh_swart_torsion(n,at,xyz,hess,kt,kd,rcov,rvdw,lcutoff) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: kt + real(wp),intent(in) :: kd + real(wp),intent(in) :: rcov(:) + real(wp),intent(in) :: rvdw(:) + logical,intent(in) :: lcutoff(n,n) + + integer :: i,j,k,l,ic,jc,ij,kl +! allow only angles in the range of 35-145 + real(wp),parameter :: a35 = (35.0d0/180.d0)*pi + real(wp),parameter :: cosfi_max = cos(a35) + real(wp) :: txyz(3,4),c(3,4) + real(wp) :: rij(3),rij0,aij,rij2,d0ij,gij + real(wp) :: rjk(3),rjk0,ajk,rjk2,d0jk,gjk + real(wp) :: rkl(3),rkl0,akl,rkl2,d0kl,gkl + real(wp) :: cosfi2,cosfi3,cosfi4 + real(wp) :: beta,tij,tau + real(wp) :: si(3),sj(3),sk(3),sl(3) + +!! ------------------------------------------------------------------------ +! Hessian for torsion +!! ------------------------------------------------------------------------ + torsion_jAt: do j = 1,n + txyz(:,2) = xyz(:,j) + torsion_kAt: do k = 1,n + if (k .eq. j) cycle torsion_kAt + if (lcutoff(k,j)) cycle torsion_kAt + txyz(:,3) = xyz(:,k) + torsion_iAt: do i = 1,n + ij = n*(j-1)+i + if (i .eq. j) cycle torsion_iAt + if (i .eq. k) cycle torsion_iAt + if (lcutoff(i,k)) cycle torsion_iAt + if (lcutoff(i,j)) cycle torsion_iAt + + txyz(:,1) = xyz(:,i) + torsion_lAt: do l = 1,n + kl = n*(l-1)+k + if (ij .le. kl) cycle torsion_lAt + if (l .eq. i) cycle torsion_lAt + if (l .eq. j) cycle torsion_lAt + if (l .eq. k) cycle torsion_lAt +! + if (lcutoff(l,i)) cycle torsion_lAt + if (lcutoff(l,k)) cycle torsion_lAt + if (lcutoff(l,j)) cycle torsion_lAt + + txyz(:,4) = xyz(:,l) + + rij = xyz(:,i)-xyz(:,j) + d0ij = rvdw(at(i))+rvdw(at(j)) + rij0 = rcov(at(i))+rcov(at(j)) + + rjk = xyz(:,j)-xyz(:,k) + d0jk = rvdw(at(j))+rvdw(at(k)) + rjk0 = rcov(at(j))+rcov(at(k)) + + rkl = xyz(:,k)-xyz(:,l) + d0kl = rvdw(at(k))+rvdw(at(l)) + rkl0 = rcov(at(k))+rcov(at(l)) + + rij2 = sum(rij**2) + rjk2 = sum(rjk**2) + rkl2 = sum(rjk**2) + + cosfi2 = dot_product(rij,rjk)/sqrt(rij2*rjk2) + if (abs(cosfi2) .gt. cosfi_max) cycle + cosfi3 = dot_product(rkl,rjk)/sqrt(rkl2*rjk2) + if (abs(cosfi3) .gt. cosfi_max) cycle + + gij = fk_swart(1.0_wp,rij0,rij2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0ij,rij2) + gjk = fk_swart(1.0_wp,rjk0,rjk2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0jk,rjk2) + gkl = fk_swart(1.0_wp,rkl0,rkl2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0kl,rkl2) + + tij = kt*gij*gjk*gkl + + !tij = max(tij,10*min_fk) + + call trsn2(txyz,tau,c) + si = c(:,1) + sj = c(:,2) + sk = c(:,3) + sl = c(:,4) + + ! off diagonal block + do ic = 1,3 + do jc = 1,3 + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j))+tij*si(ic)*sj(jc) + hess(ind(ic,i,jc,k)) = hess(ind(ic,i,jc,k))+tij*si(ic)*sk(jc) + hess(ind(ic,i,jc,l)) = hess(ind(ic,i,jc,l))+tij*si(ic)*sl(jc) + hess(ind(ic,j,jc,k)) = hess(ind(ic,j,jc,k))+tij*sj(ic)*sk(jc) + hess(ind(ic,j,jc,l)) = hess(ind(ic,j,jc,l))+tij*sj(ic)*sl(jc) + hess(ind(ic,k,jc,l)) = hess(ind(ic,k,jc,l))+tij*sk(ic)*sl(jc) + end do + end do + + ! diagonal block + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i))+tij*si(ic)*si(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j))+tij*sj(ic)*sj(jc) + hess(ind(ic,k,jc,k)) = hess(ind(ic,k,jc,k))+tij*sk(ic)*sk(jc) + hess(ind(ic,l,jc,l)) = hess(ind(ic,l,jc,l))+tij*sl(ic)*sl(jc) + end do + end do + + end do torsion_lAt + end do torsion_iAt + end do torsion_kAt + end do torsion_jAt + + end subroutine mh_swart_torsion + + pure subroutine mh_swart_outofp(n,at,xyz,hess,ko,kd,rcov,rvdw,lcutoff) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: ko + real(wp),intent(in) :: kd + real(wp),intent(in) :: rcov(:) + real(wp),intent(in) :: rvdw(:) + logical,intent(in) :: lcutoff(n,n) + + integer :: i,ir,j,jr,k,kr,l,lr,ic,jc + real(wp) :: txyz(3,4),c(3,4) + real(wp) :: rij(3),rij0,d0ij,rij2,gij + real(wp) :: rik(3),rik0,d0ik,rik2,gik + real(wp) :: ril(3),ril0,d0il,ril2,gil + real(wp) :: cosfi2,cosfi3,cosfi4 + real(wp) :: beta,tij,tau + real(wp) :: si(3),sj(3),sk(3),sl(3) + +!! ------------------------------------------------------------------------ +! Hessian for out-of-plane +!! ------------------------------------------------------------------------ + outofplane_iAt: do i = 1,n + txyz(:,4) = xyz(:,i) + outofplane_jAt: do j = 1,n + if (j .eq. i) cycle outofplane_jAt + if (lcutoff(j,i)) cycle outofplane_jAt + txyz(:,1) = xyz(:,j) + outofplane_kAt: do k = 1,n + if (i .eq. k) cycle outofplane_kAt + if (j .eq. k) cycle outofplane_kat + if (lcutoff(k,i)) cycle outofplane_kAt + if (lcutoff(k,j)) cycle outofplane_kAt + txyz(:,2) = xyz(:,k) + outofplane_lAt: do l = 1,n + txyz(:,3) = xyz(:,l) + if (l .eq. i) cycle outofplane_lAt + if (l .eq. j) cycle outofplane_lAt + if (l .eq. k) cycle outofplane_lAt + if (lcutoff(l,i)) cycle outofplane_lAt + if (lcutoff(l,k)) cycle outofplane_lAt + if (lcutoff(l,j)) cycle outofplane_lAt + + rij = xyz(:,i)-xyz(:,j) + rij0 = rcov(at(i))+rcov(at(j)) + d0ij = rvdw(at(i))+rvdw(at(j)) + + rik = xyz(:,i)-xyz(:,k) + rik0 = rcov(at(i))+rcov(at(k)) + d0ik = rvdw(at(i))+rvdw(at(k)) + + ril = xyz(:,i)-xyz(:,l) + ril0 = rcov(at(i))+rcov(at(l)) + d0il = rvdw(at(i))+rvdw(at(l)) + + rij2 = sum(rij**2) + rik2 = sum(rik**2) + ril2 = sum(ril**2) + + cosfi2 = dot_product(rij,rik)/sqrt(rij2*rik2) + if (abs(abs(cosfi2)-1.0_wp) .lt. 1.0e-1_wp) cycle + cosfi3 = dot_product(rij,ril)/sqrt(rij2*ril2) + if (abs(abs(cosfi3)-1.0_wp) .lt. 1.0e-1_wp) cycle + cosfi4 = dot_product(rik,ril)/sqrt(rik2*ril2) + if (abs(abs(cosfi4)-1.0_wp) .lt. 1.0e-1_wp) cycle + + gij = fk_swart(1.0_wp,rij0,rij2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0ij,rij2) + gik = fk_swart(1.0_wp,rik0,rik2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0ik,rik2) + gil = fk_swart(1.0_wp,ril0,ril2) & + +0.5_wp*kd*fk_vdw(5.0_wp,d0il,ril2) + + tij = ko*gij*gik*gil + + !tij = max(tij,10*min_fk) + + call outofp2(xyz,tau,c) + If (abs(tau) .gt. 45.0d0*(pi/180.d0)) cycle + + si = c(:,4) + sj = c(:,1) + sk = c(:,2) + sl = c(:,3) + + ! off diagonal block + do ic = 1,3 + do jc = 1,3 + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j))+tij*si(ic)*sj(jc) + hess(ind(ic,i,jc,k)) = hess(ind(ic,i,jc,k))+tij*si(ic)*sk(jc) + hess(ind(ic,i,jc,l)) = hess(ind(ic,i,jc,l))+tij*si(ic)*sl(jc) + hess(ind(ic,j,jc,k)) = hess(ind(ic,j,jc,k))+tij*sj(ic)*sk(jc) + hess(ind(ic,j,jc,l)) = hess(ind(ic,j,jc,l))+tij*sj(ic)*sl(jc) + hess(ind(ic,k,jc,l)) = hess(ind(ic,k,jc,l))+tij*sk(ic)*sl(jc) + end do + end do + + ! diagonal block + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i))+tij*si(ic)*si(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j))+tij*sj(ic)*sj(jc) + hess(ind(ic,k,jc,k)) = hess(ind(ic,k,jc,k))+tij*sk(ic)*sk(jc) + hess(ind(ic,l,jc,l)) = hess(ind(ic,l,jc,l))+tij*sl(ic)*sl(jc) + end do + end do + + end do outofplane_lAt + end do outofplane_kAt + end do outofplane_jAt + end do outofplane_iAt + + end subroutine mh_swart_outofp + +!========================================================================================! +!########################################################################################! +!========================================================================================! + + subroutine mh_lindh(xyz,n,hess,at,modh) +!************************************************************************** +!* Lindh's Model Hessian updated around 2007 +!* ------------------------------------------------------------------------ +!* R. Lindh, personal communication. +!* +!* gij = exp[αij(R²ref - R²ij)] +!* dij = exp[-4·(Rvdw - Rij)²] +!* kij = rkr·gij + rkd·dij +!* kijk = rkf·(gij+½·rkd/rkr·dij)·(gjk+½·rkd/rkr·djk) +!* kijkl = rkt·(gij+½·rkd/rkr·dij)·(gjk+½·rkd/rkr·djk)·(gkl+½·rkd/rkr·dkl) +!* +!* parameters tweaked by R. Lindh in 2007: +!* rkr = 0.45, rkf = 0.10, rkt = 0.0025, rko = 0.16, rkd = 0.05 +!* +!* the reference distances are divided by rows in the PSE: +!* rAv: 1 2 3 aAv: 1 2 3 +!* 1 1.3500 2.1000 2.5300 1 1.0000 0.3949 0.3949 +!* 2 2.1000 2.8700 3.8000 2 0.3949 0.2800 0.1200 +!* 3 2.5300 3.8000 4.5000 3 0.3949 0.1200 0.0600 +!* +!* dAv: 1 2 3 +!* 1 0.0000 3.6000 3.6000 +!* 2 3.6000 5.3000 5.3000 +!* 3 3.6000 5.3000 5.3000 +!* +!************************************************************************** + implicit none + + integer,intent(in) :: n + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(out) :: hess((3*n)*(3*n+1)/2) + integer,intent(in) :: at(n) + type(mhparam),intent(in) :: modh + + real(wp),parameter :: rAv(3,3) = reshape( & + (/1.3500_wp,2.1000_wp,2.5300_wp, & + 2.1000_wp,2.8700_wp,3.8000_wp, & + 2.5300_wp,3.8000_wp,4.5000_wp/),shape(rAv)) + real(wp),parameter :: aAv(3,3) = reshape( & + (/1.0000_wp,0.3949_wp,0.3949_wp, & + 0.3949_wp,0.2800_wp,0.1200_wp, & + 0.3949_wp,0.1200_wp,0.0600_wp/),shape(aAv)) + real(wp),parameter :: dAv(3,3) = reshape( & + (/0.0000_wp,3.6000_wp,3.6000_wp, & + 3.6000_wp,5.3000_wp,5.3000_wp, & + 3.6000_wp,5.3000_wp,5.3000_wp/),shape(aAv)) + + integer :: n3 + real(wp) :: kd + logical,allocatable :: lcutoff(:,:) + !type(chrg_parameter) :: chrgeq + + allocate (lcutoff(n,n),source=.false.) + + n3 = 3*n + hess = 0.0d0 + +! the dispersion force constant is used relative to the stretch force constant + kd = modh%kd/modh%kr + + call mh_lindh_stretch(n,at,xyz,hess,modh%kr,kd,modh%s6,aav,rav,dav,lcutoff,modh%rcut) + if (modh%kf .ne. 0.0_wp) & + call mh_lindh_bend(n,at,xyz,hess,modh%kf,kd,aav,rav,dav,lcutoff) + if (modh%kt .ne. 0.0_wp) & + call mh_lindh_torsion(n,at,xyz,hess,modh%kt,kd,aav,rav,dav,lcutoff) + if (modh%ko .ne. 0.0_wp) & + call mh_lindh_outofp(n,at,xyz,hess,modh%ko,0.0_wp,aav,rav,dav,lcutoff) + if (modh%kq .ne. 0.0_wp) then + !call new_charge_model_2019(chrgeq,n,at) + call mh_eeq(n,at,xyz,0.0_wp,modh%kq,hess) + end if + + end subroutine mh_lindh + + subroutine mh_lindh_d2(xyz,n,hess,at,modh) +!************************************************************************** +!* Lindh's Model Hessian augmented with D2 +!* ------------------------------------------------------------------------ +!* Implemented after: +!* Lindh, R., Bernhardsson, A., Karlström, G., & Malmqvist, P.-Å. (1995). +!* On the use of a Hessian model function in molecular geometry optimizations. +!* Chem. Phys. Lett., 241(4), 423–428. doi:10.1016/0009-2614(95)00646-l +!* +!* gij = exp[αij(R²ref - R²ij)] +!* kij = rkr·gij +!* kijk = rkf·gij·gjk +!* kijkl = rkt·gij·gjk·gkl +!* +!* Originally Lindh proposed (we tweaked those a little bit): +!* rkr = 0.45, rkf = 0.15, rkt = 0.005 +!* +!* the reference distances are divided by rows in the PSE: +!* rAv: 1 2 3 aAv: 1 2 3 +!* 1 1.3500 2.1000 2.5300 1 1.0000 0.3949 0.3949 +!* 2 2.1000 2.8700 3.4000 2 0.3949 0.2800 0.2800 +!* 3 2.5300 3.4000 3.4000 3 0.3949 0.2800 0.2800 +!* +!* This Hessian is additionally augmented with D2, please note that D2 +!* is not implemented in atomic units and requires some magical conversion +!* factor somewhere hidden in the implementation below. +!************************************************************************* + implicit none + + integer,intent(in) :: n + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(out) :: hess((3*n)*(3*n+1)/2) + integer,intent(in) :: at(n) + type(mhparam),intent(in) :: modh + + real(wp),parameter :: rAv(3,3) = reshape( & + (/1.3500_wp,2.1000_wp,2.5300_wp, & + 2.1000_wp,2.8700_wp,3.4000_wp, & + 2.5300_wp,3.4000_wp,3.4000_wp/),shape(rAv)) + real(wp),parameter :: aAv(3,3) = reshape( & + (/1.0000_wp,0.3949_wp,0.3949_wp, & + 0.3949_wp,0.2800_wp,0.2800_wp, & + 0.3949_wp,0.2800_wp,0.2800_wp/),shape(aAv)) + real(wp),parameter :: dAv(3,3) = reshape( & + (/0.0000_wp,0.0000_wp,0.0000_wp, & + 0.0000_wp,0.0000_wp,0.0000_wp, & + 0.0000_wp,0.0000_wp,0.0000_wp/),shape(aAv)) + + integer :: n3 + real(wp) :: kd + logical,allocatable :: lcutoff(:,:) + + allocate (lcutoff(n,n),source=.false.) + + n3 = 3*n + hess = 0.0d0 + +! the dispersion force constant is used relative to the stretch force constant + kd = modh%kd/modh%kr + + call mh_lindh_stretch(n,at,xyz,hess,modh%kr,kd,modh%s6,aav,rav,dav,lcutoff,modh%rcut) + if (modh%kf .ne. 0.0_wp) & + call mh_lindh_bend(n,at,xyz,hess,modh%kf,kd,aav,rav,dav,lcutoff) + if (modh%kt .ne. 0.0_wp) & + call mh_lindh_torsion(n,at,xyz,hess,modh%kt,kd,aav,rav,dav,lcutoff) + if (modh%ko .ne. 0.0_wp) & + call mh_lindh_outofp(n,at,xyz,hess,modh%ko,kd,aav,rav,dav,lcutoff) + if (modh%kq .ne. 0.0_wp) then + call mh_eeq(n,at,xyz,0.0_wp,modh%kq,hess) + end if + end subroutine mh_lindh_d2 + + pure subroutine mh_lindh_stretch(n,at,xyz,hess,kr,kd,s6,aav,rav,dav,lcutoff,rcut) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: kr + real(wp),intent(in) :: kd + real(wp),intent(in) :: s6 + real(wp),intent(in) :: aav(3,3) + real(wp),intent(in) :: rav(3,3) + real(wp),intent(in) :: dav(3,3) + logical,intent(out) :: lcutoff(n,n) + real(wp),intent(in) :: rcut + + integer :: i,ir,j,jr + real(wp) :: xij,yij,zij,rij2,r0,d0 + real(wp) :: alpha,gmm + real(wp) :: c6i,c6j,c6ij,rv + real(wp) :: hxx,hxy,hxz,hyy,hyz,hzz + real(wp) :: vdw(3,3) + +!! ------------------------------------------------------------------------ +! Hessian for stretch +!! ------------------------------------------------------------------------ + stretch_iAt: do i = 1,n + ir = itabrow(at(i)) + + stretch_jAt: do j = 1,i-1 + jr = itabrow(at(j)) + + ! save for later + lcutoff(i,j) = rcutoff(xyz,i,j,rcut) + lcutoff(j,i) = lcutoff(i,j) + + xij = xyz(1,i)-xyz(1,j) + yij = xyz(2,i)-xyz(2,j) + zij = xyz(3,i)-xyz(3,j) + rij2 = xij**2+yij**2+zij**2 + r0 = rav(ir,jr) + d0 = dav(ir,jr) + alpha = aav(ir,jr) + + !cccccc vdwx ccccccccccccccccccccccccccccccccc + c6i = c6(at(i)) + c6j = c6(at(j)) + c6ij = sqrt(c6i*c6j) + rv = (vander(at(i))+vander(at(j)))*aatoau + + call getvdwxx(xij,yij,zij,c6ij,s6,rv,vdw(1,1)) + call getvdwxy(xij,yij,zij,c6ij,s6,rv,vdw(1,2)) + call getvdwxy(xij,zij,yij,c6ij,s6,rv,vdw(1,3)) + call getvdwxx(yij,xij,zij,c6ij,s6,rv,vdw(2,2)) + call getvdwxy(yij,zij,xij,c6ij,s6,rv,vdw(2,3)) + call getvdwxx(zij,xij,yij,c6ij,s6,rv,vdw(3,3)) + !cccccc ende vdwx ccccccccccccccccccccccccccccccc + + gmm = kr*fk_lindh(alpha,r0,rij2) & + +kr*kd*fk_vdw(4.0_wp,d0,rij2) + + !gmm = max(gmm,min_fk) + + hxx = gmm*xij*xij/rij2-vdw(1,1) + hxy = gmm*xij*yij/rij2-vdw(1,2) + hxz = gmm*xij*zij/rij2-vdw(1,3) + hyy = gmm*yij*yij/rij2-vdw(2,2) + hyz = gmm*yij*zij/rij2-vdw(2,3) + hzz = gmm*zij*zij/rij2-vdw(3,3) + + ! save diagonal elements for atom i + hess(ind(1,i,1,i)) = hess(ind(1,i,1,i))+hxx + hess(ind(2,i,1,i)) = hess(ind(2,i,1,i))+hxy + hess(ind(2,i,2,i)) = hess(ind(2,i,2,i))+hyy + hess(ind(3,i,1,i)) = hess(ind(3,i,1,i))+hxz + hess(ind(3,i,2,i)) = hess(ind(3,i,2,i))+hyz + hess(ind(3,i,3,i)) = hess(ind(3,i,3,i))+hzz + ! save elements between atom i and atom j + hess(ind(1,i,1,j)) = hess(ind(1,i,1,j))-hxx + hess(ind(1,i,2,j)) = hess(ind(1,i,2,j))-hxy + hess(ind(1,i,3,j)) = hess(ind(1,i,3,j))-hxz + hess(ind(2,i,1,j)) = hess(ind(2,i,1,j))-hxy + hess(ind(2,i,2,j)) = hess(ind(2,i,2,j))-hyy + hess(ind(2,i,3,j)) = hess(ind(2,i,3,j))-hyz + hess(ind(3,i,1,j)) = hess(ind(3,i,1,j))-hxz + hess(ind(3,i,2,j)) = hess(ind(3,i,2,j))-hyz + hess(ind(3,i,3,j)) = hess(ind(3,i,3,j))-hzz + ! save diagonal elements for atom j + hess(ind(1,j,1,j)) = hess(ind(1,j,1,j))+hxx + hess(ind(2,j,1,j)) = hess(ind(2,j,1,j))+hxy + hess(ind(2,j,2,j)) = hess(ind(2,j,2,j))+hyy + hess(ind(3,j,1,j)) = hess(ind(3,j,1,j))+hxz + hess(ind(3,j,2,j)) = hess(ind(3,j,2,j))+hyz + hess(ind(3,j,3,j)) = hess(ind(3,j,3,j))+hzz + + end do stretch_jAt + end do stretch_iAt + + end subroutine mh_lindh_stretch + + pure subroutine mh_lindh_bend(n,at,xyz,hess,kf,kd,aav,rav,dav,lcutoff) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: kf + real(wp),intent(in) :: kd + real(wp),intent(in) :: aav(3,3) + real(wp),intent(in) :: rav(3,3) + real(wp),intent(in) :: dav(3,3) + logical,intent(in) :: lcutoff(n,n) + + integer :: i,ir,j,jr,m,mr,ic,jc,ii + real(wp),parameter :: rzero = 1.0e-10_wp + real(wp) :: xij,yij,zij,rij2,rrij,r1 + real(wp) :: xmi,ymi,zmi,rmi2,rmi,r0mi,ami,d0mj,gmi + real(wp) :: xmj,ymj,zmj,rmj2,rmj,r0mj,amj,d0mi,gmj + real(wp) :: test,gij,rl2,rl,rmidotrmj + real(wp) :: sinphi,cosphi,costhetax,costhetay,costhetaz + real(wp) :: alpha + real(wp) :: si(3),sj(3),sm(3),x(2),y(2),z(2) + +!! ------------------------------------------------------------------------ +! Hessian for bending +!! ------------------------------------------------------------------------ + bend_mAt: do m = 1,n + mr = itabrow(at(m)) + bend_iAt: do i = 1,n + if (i .eq. m) cycle bend_iAt + ir = itabrow(at(i)) + if (lcutoff(i,m)) cycle bend_iAt + + xmi = (xyz(1,i)-xyz(1,m)) + ymi = (xyz(2,i)-xyz(2,m)) + zmi = (xyz(3,i)-xyz(3,m)) + rmi2 = xmi**2+ymi**2+zmi**2 + rmi = sqrt(rmi2) + r0mi = rav(mr,ir) + d0mi = dav(mr,ir) + ami = aav(mr,ir) + + bend_jAt: do j = 1,i-1 + if (j .eq. m) cycle bend_jAt + jr = itabrow(at(j)) + if (lcutoff(j,i)) cycle bend_jAt + if (lcutoff(j,m)) cycle bend_jAt + + xmj = (xyz(1,j)-xyz(1,m)) + ymj = (xyz(2,j)-xyz(2,m)) + zmj = (xyz(3,j)-xyz(3,m)) + rmj2 = xmj**2+ymj**2+zmj**2 + rmj = sqrt(rmj2) + r0mj = rav(mr,jr) + d0mj = dav(mr,jr) + amj = aav(mr,jr) + + ! test if zero angle + test = xmi*xmj+ymi*ymj+zmi*zmj + test = test/(rmi*rmj) + if (abs(test-1.0_wp) .lt. 1.0e-12_wp) cycle bend_jAt + + xij = (xyz(1,j)-xyz(1,i)) + yij = (xyz(2,j)-xyz(2,i)) + zij = (xyz(3,j)-xyz(3,i)) + rij2 = xij**2+yij**2+zij**2 + rrij = sqrt(rij2) + + gmi = fk_lindh(ami,r0mi,rmi2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0mi,rmi2) + gmj = fk_lindh(amj,r0mj,rmj2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0mj,rmj2) + + gij = kf*gmi*gmj + + rl2 = (ymi*zmj-zmi*ymj)**2+(zmi*xmj-xmi*zmj)**2+(xmi*ymj-ymi*xmj)**2 + + if (rl2 .lt. 1.e-14_wp) then + rl = 0.0_wp + else + rl = sqrt(rl2) + end if + + !gij = max(gij,min_fk) + + if ((rmj .gt. rzero).and.(rmi .gt. rzero).and.(rrij .gt. rzero)) then + sinphi = rl/(rmj*rmi) + rmidotrmj = xmi*xmj+ymi*ymj+zmi*zmj + cosphi = rmidotrmj/(rmj*rmi) + ! none linear case + if (sinphi .gt. rzero) then + si(1) = (xmi/rmi*cosphi-xmj/rmj)/(rmi*sinphi) + si(2) = (ymi/rmi*cosphi-ymj/rmj)/(rmi*sinphi) + si(3) = (zmi/rmi*cosphi-zmj/rmj)/(rmi*sinphi) + sj(1) = (cosphi*xmj/rmj-xmi/rmi)/(rmj*sinphi) + sj(2) = (cosphi*ymj/rmj-ymi/rmi)/(rmj*sinphi) + sj(3) = (cosphi*zmj/rmj-zmi/rmi)/(rmj*sinphi) + sm(1) = -si(1)-sj(1) + sm(2) = -si(2)-sj(2) + sm(3) = -si(3)-sj(3) + do ic = 1,3 + do jc = 1,3 + if (m .gt. i) then + hess(ind(ic,m,jc,i)) = hess(ind(ic,m,jc,i)) & + +gij*sm(ic)*si(jc) + else + hess(ind(ic,i,jc,m)) = hess(ind(ic,i,jc,m)) & + +gij*si(ic)*sm(jc) + end if + if (m .gt. j) then + hess(ind(ic,m,jc,j)) = hess(ind(ic,m,jc,j)) & + +gij*sm(ic)*sj(jc) + else + hess(ind(ic,j,jc,m)) = hess(ind(ic,j,jc,m)) & + +gij*sj(ic)*sm(jc) + end if + if (i .gt. j) then + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j)) & + +gij*si(ic)*sj(jc) + else + hess(ind(ic,j,jc,i)) = hess(ind(ic,j,jc,i)) & + +gij*sj(ic)*si(jc) + end if + end do + end do + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i))+gij*si(ic)*si(jc) + hess(ind(ic,m,jc,m)) = hess(ind(ic,m,jc,m))+gij*sm(ic)*sm(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j))+gij*sj(ic)*sj(jc) + end do + end do + else + ! linear case + if ((abs(ymi) .gt. rzero).or.(abs(xmi) .gt. rzero)) then + x(1) = -ymi + y(1) = xmi + z(1) = 0.0_wp + x(2) = -xmi*zmi + y(2) = -ymi*zmi + z(2) = xmi*xmi+ymi*ymi + else + x(1) = 1.0_wp + y(1) = 0.0_wp + z(1) = 0.0_wp + x(2) = 0.0_wp + y(2) = 1.0_wp + z(2) = 0.0_wp + end if + do ii = 1,2 + r1 = sqrt(x(ii)**2+y(ii)**2+z(ii)**2) + costhetax = x(ii)/r1 + costhetay = y(ii)/r1 + costhetaz = z(ii)/r1 + si(1) = -costhetax/rmi + si(2) = -costhetay/rmi + si(3) = -costhetaz/rmi + sj(1) = -costhetax/rmj + sj(2) = -costhetay/rmj + sj(3) = -costhetaz/rmj + sm(1) = -(si(1)+sj(1)) + sm(2) = -(si(2)+sj(2)) + sm(3) = -(si(3)+sj(3)) + ! + do ic = 1,3 + do jc = 1,3 + if (m .gt. i) then + hess(ind(ic,m,jc,i)) = hess(ind(ic,m,jc,i)) & + +gij*sm(ic)*si(jc) + else + hess(ind(ic,i,jc,m)) = hess(ind(ic,i,jc,m)) & + +gij*si(ic)*sm(jc) + end if + if (m .gt. j) then + hess(ind(ic,m,jc,j)) = hess(ind(ic,m,jc,j)) & + +gij*sm(ic)*sj(jc) + else + hess(ind(ic,j,jc,m)) = hess(ind(ic,j,jc,m)) & + +gij*sj(ic)*sm(jc) + end if + if (i .gt. j) then + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j)) & + +gij*si(ic)*sj(jc) + else + hess(ind(ic,j,jc,i)) = hess(ind(ic,j,jc,i)) & + +gij*sj(ic)*si(jc) + end if + end do + end do + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i)) & + +gij*si(ic)*si(jc) + hess(ind(ic,m,jc,m)) = hess(ind(ic,m,jc,m)) & + +gij*sm(ic)*sm(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j)) & + +gij*sj(ic)*sj(jc) + end do + end do + end do + + end if + end if + + end do bend_jAt + end do bend_iAt + end do bend_mAt + + end subroutine mh_lindh_bend + + subroutine mh_lindh_torsion(n,at,xyz,hess,kt,kd,aav,rav,dav,lcutoff) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: kt + real(wp),intent(in) :: kd + real(wp),intent(in) :: aav(3,3) + real(wp),intent(in) :: rav(3,3) + real(wp),intent(in) :: dav(3,3) + logical,intent(in) :: lcutoff(n,n) + + integer :: i,ir,j,jr,k,kr,l,lr,ic,jc,ij,kl +! allow only angles in the range of 35-145 + real(wp),parameter :: a35 = (35.0d0/180.d0)*pi + real(wp),parameter :: cosfi_max = cos(a35) + real(wp) :: txyz(3,4),c(3,4) + real(wp) :: rij(3),rij0,aij,rij2,d0ij,gij + real(wp) :: rjk(3),rjk0,ajk,rjk2,d0jk,gjk + real(wp) :: rkl(3),rkl0,akl,rkl2,d0kl,gkl + real(wp) :: cosfi2,cosfi3,cosfi4 + real(wp) :: beta,tij,tau,dum(3,4,3,4) + real(wp) :: si(3),sj(3),sk(3),sl(3) + +!! ------------------------------------------------------------------------ +! Hessian for torsion +!! ------------------------------------------------------------------------ + torsion_jAt: do j = 1,n + jr = itabrow(at(j)) + txyz(:,2) = xyz(:,j) + torsion_kAt: do k = 1,n + if (k .eq. j) cycle torsion_kAt + kr = itabrow(at(k)) + if (lcutoff(k,j)) cycle torsion_kAt + txyz(:,3) = xyz(:,k) + torsion_iAt: do i = 1,n + ij = n*(j-1)+i + if (i .eq. j) cycle torsion_iAt + if (i .eq. k) cycle torsion_iAt + ir = itabrow(at(i)) + if (lcutoff(i,k)) cycle torsion_iAt + if (lcutoff(i,j)) cycle torsion_iAt + + txyz(:,1) = xyz(:,i) + torsion_lAt: do l = 1,n + kl = n*(k-1)+l + if (ij .le. kl) cycle torsion_lAt + if (l .eq. i) cycle torsion_lAt + if (l .eq. j) cycle torsion_lAt + if (l .eq. k) cycle torsion_lAt + lr = itabrow(at(l)) +! + if (lcutoff(l,i)) cycle torsion_lAt + if (lcutoff(l,k)) cycle torsion_lAt + if (lcutoff(l,j)) cycle torsion_lAt + + txyz(:,4) = xyz(:,l) + + rij = xyz(:,i)-xyz(:,j) + d0ij = dav(ir,jr) + rij0 = rav(ir,jr) + aij = aav(ir,jr) + + rjk = xyz(:,j)-xyz(:,k) + d0jk = dav(jr,kr) + rjk0 = rav(jr,kr) + ajk = aav(jr,kr) + + rkl = xyz(:,k)-xyz(:,l) + d0kl = dav(kr,lr) + rkl0 = rav(kr,lr) + akl = aav(kr,lr) + + rij2 = sum(rij**2) + rjk2 = sum(rjk**2) + rkl2 = sum(rjk**2) + + cosfi2 = dot_product(rij,rjk)/sqrt(rij2*rjk2) + if (abs(cosfi2) .gt. cosfi_max) cycle + cosfi3 = dot_product(rkl,rjk)/sqrt(rkl2*rjk2) + if (abs(cosfi3) .gt. cosfi_max) cycle + + gij = fk_lindh(aij,rij0,rij2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0ij,rij2) + gjk = fk_lindh(ajk,rjk0,rjk2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0jk,rjk2) + gkl = fk_lindh(akl,rkl0,rkl2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0kl,rkl2) + + tij = kt*gij*gjk*gkl + + !tij = max(tij,10*min_fk) + + !call trsn2(txyz,tau,c) + Call Trsn(txyz,4,Tau,C,.False.,.False.,' ', & + & Dum,.False.) + si = c(:,1) + sj = c(:,2) + sk = c(:,3) + sl = c(:,4) + + ! off diagonal block + do ic = 1,3 + do jc = 1,3 + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j))+tij*si(ic)*sj(jc) + hess(ind(ic,i,jc,k)) = hess(ind(ic,i,jc,k))+tij*si(ic)*sk(jc) + hess(ind(ic,i,jc,l)) = hess(ind(ic,i,jc,l))+tij*si(ic)*sl(jc) + hess(ind(ic,j,jc,k)) = hess(ind(ic,j,jc,k))+tij*sj(ic)*sk(jc) + hess(ind(ic,j,jc,l)) = hess(ind(ic,j,jc,l))+tij*sj(ic)*sl(jc) + hess(ind(ic,k,jc,l)) = hess(ind(ic,k,jc,l))+tij*sk(ic)*sl(jc) + end do + end do + + ! diagonal block + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i))+tij*si(ic)*si(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j))+tij*sj(ic)*sj(jc) + hess(ind(ic,k,jc,k)) = hess(ind(ic,k,jc,k))+tij*sk(ic)*sk(jc) + hess(ind(ic,l,jc,l)) = hess(ind(ic,l,jc,l))+tij*sl(ic)*sl(jc) + end do + end do + + end do torsion_lAt + end do torsion_iAt + end do torsion_kAt + end do torsion_jAt + + end subroutine mh_lindh_torsion + + pure subroutine mh_lindh_outofp(n,at,xyz,hess,ko,kd,aav,rav,dav,lcutoff) + implicit none + + integer,intent(in) :: n + integer,intent(in) :: at(n) + real(wp),intent(in) :: xyz(3,n) + real(wp),intent(inout) :: hess((3*n)*(3*n+1)/2) + real(wp),intent(in) :: ko + real(wp),intent(in) :: kd + real(wp),intent(in) :: aav(3,3) + real(wp),intent(in) :: rav(3,3) + real(wp),intent(in) :: dav(3,3) + logical,intent(in) :: lcutoff(n,n) + + integer :: i,ir,j,jr,k,kr,l,lr,ic,jc + real(wp) :: txyz(3,4),c(3,4) + real(wp) :: rij(3),rij0,aij,rij2,gij,d0ij + real(wp) :: rik(3),rik0,aik,rik2,gik,d0ik + real(wp) :: ril(3),ril0,ail,ril2,gil,d0il + real(wp) :: cosfi2,cosfi3,cosfi4 + real(wp) :: beta,tij,tau + real(wp) :: si(3),sj(3),sk(3),sl(3) + +!! ------------------------------------------------------------------------ +! Hessian for out-of-plane +!! ------------------------------------------------------------------------ + outofplane_iAt: do i = 1,n + ir = itabrow(at(i)) + txyz(:,4) = xyz(:,i) + outofplane_jAt: do j = 1,n + if (j .eq. i) cycle outofplane_jAt + if (lcutoff(j,i)) cycle outofplane_jAt + jr = itabrow(at(j)) + txyz(:,1) = xyz(:,j) + outofplane_kAt: do k = 1,n + if (i .eq. k) cycle outofplane_kAt + if (j .eq. k) cycle outofplane_kat + if (lcutoff(k,i)) cycle outofplane_kAt + if (lcutoff(k,j)) cycle outofplane_kAt + kr = itabrow(at(k)) + txyz(:,2) = xyz(:,k) + outofplane_lAt: do l = 1,n + lr = itabrow(at(l)) + txyz(:,3) = xyz(:,l) + if (l .eq. i) cycle outofplane_lAt + if (l .eq. j) cycle outofplane_lAt + if (l .eq. k) cycle outofplane_lAt + if (lcutoff(l,i)) cycle outofplane_lAt + if (lcutoff(l,k)) cycle outofplane_lAt + if (lcutoff(l,j)) cycle outofplane_lAt + + rij = xyz(:,i)-xyz(:,j) + d0ij = dav(ir,jr) + rij0 = rav(ir,jr) + aij = aav(ir,jr) + + rik = xyz(:,i)-xyz(:,k) + d0ik = dav(ir,kr) + rik0 = rav(ir,kr) + aik = aav(ir,kr) + + ril = xyz(:,i)-xyz(:,l) + d0il = dav(ir,lr) + ril0 = rav(ir,lr) + ail = aav(ir,lr) + + rij2 = sum(rij**2) + rik2 = sum(rik**2) + ril2 = sum(ril**2) + + cosfi2 = dot_product(rij,rik)/sqrt(rij2*rik2) + if (abs(abs(cosfi2)-1.0_wp) .lt. 1.0e-1_wp) cycle + cosfi3 = dot_product(rij,ril)/sqrt(rij2*ril2) + if (abs(abs(cosfi3)-1.0_wp) .lt. 1.0e-1_wp) cycle + cosfi4 = dot_product(rik,ril)/sqrt(rik2*ril2) + if (abs(abs(cosfi4)-1.0_wp) .lt. 1.0e-1_wp) cycle + + gij = fk_lindh(aij,rij0,rij2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0ij,rij2) + gik = fk_lindh(aik,rik0,rik2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0ik,rik2) + gil = fk_lindh(ail,ril0,ril2) & + +0.5_wp*kd*fk_vdw(4.0_wp,d0il,ril2) + + tij = ko*gij*gik*gil + + !tij = max(tij,10*min_fk) + + call outofp2(xyz,tau,c) + If (abs(tau) .gt. 45.0d0*(pi/180.d0)) cycle + + si = c(:,4) + sj = c(:,1) + sk = c(:,2) + sl = c(:,3) + + ! off diagonal block + do ic = 1,3 + do jc = 1,3 + hess(ind(ic,i,jc,j)) = hess(ind(ic,i,jc,j))+tij*si(ic)*sj(jc) + hess(ind(ic,i,jc,k)) = hess(ind(ic,i,jc,k))+tij*si(ic)*sk(jc) + hess(ind(ic,i,jc,l)) = hess(ind(ic,i,jc,l))+tij*si(ic)*sl(jc) + hess(ind(ic,j,jc,k)) = hess(ind(ic,j,jc,k))+tij*sj(ic)*sk(jc) + hess(ind(ic,j,jc,l)) = hess(ind(ic,j,jc,l))+tij*sj(ic)*sl(jc) + hess(ind(ic,k,jc,l)) = hess(ind(ic,k,jc,l))+tij*sk(ic)*sl(jc) + end do + end do + + ! diagonal block + do ic = 1,3 + do jc = 1,ic + hess(ind(ic,i,jc,i)) = hess(ind(ic,i,jc,i))+tij*si(ic)*si(jc) + hess(ind(ic,j,jc,j)) = hess(ind(ic,j,jc,j))+tij*sj(ic)*sj(jc) + hess(ind(ic,k,jc,k)) = hess(ind(ic,k,jc,k))+tij*sk(ic)*sk(jc) + hess(ind(ic,l,jc,l)) = hess(ind(ic,l,jc,l))+tij*sl(ic)*sl(jc) + end do + end do + + end do outofplane_lAt + end do outofplane_kAt + end do outofplane_jAt + end do outofplane_iAt + + end subroutine mh_lindh_outofp + +!========================================================================================! +!########################################################################################! +!========================================================================================! + + pure function rcutoff(xyz,katom,latom,rcut) + implicit none + logical :: rcutoff + real(wp),intent(in) :: xyz(3,*) + real(wp),intent(in) :: rcut + real(wp) :: rkl(3),rkl2 + integer,intent(in) :: katom,latom + rcutoff = .false. + rkl = xyz(:,kAtom)-xyz(:,lAtom) + rkl2 = sum(rkl**2) + if (rkl2 .gt. rcut) rcutoff = .true. + end function rcutoff + + pure elemental function itabrow(i) + integer :: itabrow + integer,intent(in) :: i + + itabrow = 0 + if (i .gt. 0.and.i .le. 2) then + itabrow = 1 + else if (i .gt. 2.and.i .le. 10) then + itabrow = 2 + else if (i .gt. 10.and.i .le. 18) then + itabrow = 3 + else if (i .gt. 18.and.i .le. 36) then + itabrow = 3 + else if (i .gt. 36.and.i .le. 54) then + itabrow = 3 + else if (i .gt. 54.and.i .le. 86) then + itabrow = 3 + else if (i .gt. 86) then + itabrow = 3 + end if + + return + end function itabrow + + pure subroutine getvdwxy(rx,ry,rz,c66,s6,r0,vdw) + !cc Ableitung nach rx und ry + implicit none + real(wp),intent(in) :: rx,ry,rz,c66,s6,r0 + real(wp),intent(out) :: vdw + real(wp) :: t1,t2,t3,t4,t5,t6,t7,t11,t12,t16,t17,t25,t26,t35 + real(wp) :: t40,t41,t43,t44,t56,avdw + + ! write(*,*) 's6:', s6 + avdw = 20.0 + t1 = s6*C66 + t2 = rx**2 + t3 = ry**2 + t4 = rz**2 + t5 = t2+t3+t4 + t6 = t5**2 + t7 = t6**2 + t11 = sqrt(t5) + t12 = 0.1D1/r0 + t16 = exp(-avdw*(t11*t12-0.1D1)) + t17 = 0.1D1+t16 + t25 = t17**2 + t26 = 0.1D1/t25 + t35 = 0.1D1/t7 + t40 = avdw**2 + t41 = r0**2 + t43 = t40/t41 + t44 = t16**2 + t56 = -0.48D2*t1/t7/t5/t17*rx*ry+0.13D2*t1/t11/& + & t7*t26*rx*avdw*t12*ry*t16-0.2D1*t1*t35/t25/& + &t17*t43*rx*t44*ry+t1*t35*t26*t43*rx*ry*t16 + vdw = t56 + return + end subroutine getvdwxy + + pure subroutine getvdwxx(rx,ry,rz,c66,s6,r0,vdw) + !cc Ableitung nach rx und rx + Implicit none + real(wp),intent(in) :: rx,ry,rz,c66,s6,r0 + real(wp),intent(out) :: vdw + real(wp) :: t1,t2,t3,t4,t5,t6,t7,t10,t11,t15,t16,t17,t24,t25,t29 + real(wp) :: t33,t41,t42,t44,t45,t62,avdw + avdw = 20.0 + ! write(*,*) 's6:', s6 + t1 = s6*C66 + t2 = rx**2 + t3 = ry**2 + t4 = rz**2 + t5 = t2+t3+t4 + t6 = t5**2 + t7 = t6**2 + t10 = sqrt(t5) + t11 = 0.1D1/r0 + t15 = exp(-avdw*(t10*t11-0.1D1)) + t16 = 0.1D1+t15 + t17 = 0.1D1/t16 + t24 = t16**2 + t25 = 0.1D1/t24 + t29 = t11*t15 + t33 = 0.1D1/t7 + t41 = avdw**2 + t42 = r0**2 + t44 = t41/t42 + t45 = t15**2 + t62 = -0.48D2*t1/t7/t5*t17*t2+0.13D2*t1/t10/t7*& + & t25*t2*avdw*t29+0.6D1*t1*t33*t17-0.2D1*t1*t33& + & /t24/t16*t44*t2*t45-t1/t10/t6/t5*t25*avdw*& + &t29+t1*t33*t25*t44*t2*t15 + vdw = t62 + end subroutine getvdwxx + + pure subroutine trsn2(xyz,tau,bt) + implicit none + real(wp),intent(out) :: bt(3,4) + real(wp),intent(out) :: tau + real(wp),intent(in) :: xyz(3,4) + real(wp) :: rij(3),rij1,brij(3,2) + real(wp) :: rjk(3),rjk1,brjk(3,2) + real(wp) :: rkl(3),rkl1,brkl(3,2) + real(wp) :: bf2(3,3),fi2,sinfi2,cosfi2 + real(wp) :: bf3(3,3),fi3,sinfi3,cosfi3 + real(wp) :: costau,sintau + integer :: ix,iy,iz + call strtch2(xyz(1,1),rij1,brij) + call strtch2(xyz(1,2),rjk1,brjk) + call strtch2(xyz(1,3),rkl1,brkl) + call bend2(xyz(1,1),fi2,bf2) + sinfi2 = sin(fi2) + cosfi2 = cos(fi2) + call bend2(xyz(1,2),fi3,bf3) + sinfi3 = sin(fi3) + cosfi3 = cos(fi3) + costau = ((brij(2,1)*brjk(3,2)-brij(3,1)*brjk(2,2))* & + (brjk(2,1)*brkl(3,2)-brjk(3,1)*brkl(2,2))+ & + (brij(3,1)*brjk(1,2)-brij(1,1)*brjk(3,2))* & + (brjk(3,1)*brkl(1,2)-brjk(1,1)*brkl(3,2))+ & + (brij(1,1)*brjk(2,2)-brij(2,1)*brjk(1,2))* & + (brjk(1,1)*brkl(2,2)-brjk(2,1)*brkl(1,2))) & + /(sinfi2*sinfi3) + sintau = (brij(1,2)*(brjk(2,1)*brkl(3,2)-brjk(3,1)*brkl(2,2)) & + +brij(2,2)*(brjk(3,1)*brkl(1,2)-brjk(1,1)*brkl(3,2)) & + +brij(3,2)*(brjk(1,1)*brkl(2,2)-brjk(2,1)*brkl(1,2))) & + /(sinfi2*sinfi3) + tau = atan2(sintau,costau) + if (abs(tau) .eq. pi) tau = pi + do ix = 1,3 + iy = ix+1 + if (iy .gt. 3) iy = iy-3 + iz = iy+1 + if (iz .gt. 3) iz = iz-3 + bt(ix,1) = (brij(iy,2)*brjk(iz,2)-brij(iz,2)*brjk(iy,2)) & + & /(rij1*sinfi2**2) + bt(ix,4) = (brkl(iy,1)*brjk(iz,1)-brkl(iz,1)*brjk(iy,1)) & + & /(rkl1*sinfi3**2) + bt(ix,2) = -((rjk1-rij1*cosfi2)*bt(ix,1) & + & +rkl1*cosfi3*bt(ix,4))/rjk1 + bt(ix,3) = -(bt(ix,1)+bt(ix,2)+bt(ix,4)) + end do + end subroutine trsn2 + pure subroutine strtch2(xyz,avst,b) + implicit none + real(wp),intent(out) :: b(3,2) + real(wp),intent(in) :: xyz(3,2) + real(wp) :: r(3) + real(wp) :: rr + real(wp),intent(out) :: avst + r = xyz(:,2)-xyz(:,1) + rr = norm2(r) + avst = rr + b(:,1) = -r/rr + b(:,2) = -b(:,1) + end subroutine strtch2 + pure subroutine bend2(xyz,fir,bf) + implicit none + real(wp),intent(out) :: bf(3,3) + real(wp),intent(in) :: xyz(3,3) + real(wp) :: brij(3,2) + real(wp) :: brjk(3,2) + real(wp) :: co,crap + real(wp),intent(out) :: fir + real(wp) :: si + real(wp) :: rij1,rjk1 + integer :: i + call strtch2(xyz(1,1),rij1,brij) + call strtch2(xyz(1,2),rjk1,brjk) + co = 0.0_wp + crap = 0.0_wp + do i = 1,3 + co = co+brij(i,1)*brjk(i,2) + crap = crap+(brjk(i,2)+brij(i,1))**2 + end do + if (sqrt(crap) .lt. 1.0d-6) then + fir = pi-asin(sqrt(crap)) + si = sqrt(crap) + else + fir = acos(co) + si = sqrt(1.0_wp-co**2) + end if + if (abs(fir-pi) .lt. 1.0d-13) then + fir = pi + return + end if + do i = 1,3 + bf(i,1) = (co*brij(i,1)-brjk(i,2))/(si*rij1) + bf(i,3) = (co*brjk(i,2)-brij(i,1))/(si*rjk1) + bf(i,2) = -(bf(i,1)+bf(i,3)) + end do + end subroutine bend2 + + pure subroutine outofp2(xyz,teta,bt) + implicit none + real(wp),intent(out) :: teta + real(wp),intent(out) :: bt(3,4) + real(wp),intent(in) :: xyz(3,4) + real(wp) :: r1(3),r2(3),r3(3) + real(wp) :: q41,q42,q43,e41(3),e42(3),e43(3) + real(wp) :: cosfi1,fi1,dfi1,cosfi2,fi2,dfi2,cosfi3,fi3,dfi3 + real(wp) :: c14(3,3),br14(3,3) + real(wp) :: r42(3),r43(3) + integer :: ix,iy,iz +! 4 -> 1 (bond) + r1 = xyz(:,1)-xyz(:,4) + q41 = norm2(r1) + e41 = r1/q41 +! 4 -> 2 (bond in plane) + r2 = xyz(:,2)-xyz(:,4) + q42 = norm2(r2) + e42 = r2/q42 +! 4 -> 3 (bond in plane) + r3 = xyz(:,3)-xyz(:,4) + q43 = norm2(r3) + e43 = r3/q43 +! +! get the angle between e43 and e42 +! + cosfi1 = dot_product(e43,e42) + + fi1 = acos(cosfi1) + dfi1 = 180.d0*fi1/pi +! +! dirty exit! this happens when an earlier structure is ill defined. +! + if (abs(fi1-pi) .lt. 1.0d-13) then + teta = 0.0_wp + bt = 0.0_wp + return + end if +! +! get the angle between e41 and e43 +! + cosfi2 = dot_product(e41,e43) + + fi2 = acos(cosfi2) + dfi2 = 180.d0*fi2/pi +! +! get the angle between e41 and e42 +! + cosfi3 = dot_product(e41,e42) + + fi3 = acos(cosfi3) + dfi3 = 180.d0*fi3/pi +! +! the first two centers are trivially +! + c14(:,1) = xyz(:,1) + c14(:,2) = xyz(:,4) +! +! the 3rd is +! + r42 = xyz(:,2)-xyz(:,4) + r43 = xyz(:,3)-xyz(:,4) + c14(1,3) = r42(2)*r43(3)-r42(3)*r43(2) + c14(2,3) = r42(3)*r43(1)-r42(1)*r43(3) + c14(3,3) = r42(1)*r43(2)-r42(2)*r43(1) +! +! exit if 2-3-4 are collinear +! (equivalent to the above check, but this is more concrete) +! + if ((c14(1,3)**2+c14(2,3)**2+c14(3,3)**2) .lt. 1.0d-10) then + teta = 0.0d0 + bt = 0.0_wp + return + end if + c14(1,3) = c14(1,3)+xyz(1,4) + c14(2,3) = c14(2,3)+xyz(2,4) + c14(3,3) = c14(3,3)+xyz(3,4) + + call bend2(c14,teta,br14) + + teta = teta-0.5_wp*pi +! +!--compute the wdc matrix +! + do ix = 1,3 + iy = mod(ix+1,4)+(ix+1)/4 + iz = mod(iy+1,4)+(iy+1)/4 + + bt(ix,1) = -br14(ix,1) + bt(ix,2) = r43(iz)*br14(iy,3)-r43(iy)*br14(iz,3) + bt(ix,3) = -r42(iz)*br14(iy,3)+r42(iy)*br14(iz,3) + + bt(ix,4) = -(bt(ix,1)+bt(ix,2)+bt(ix,3)) + + end do + + bt = -bt + end subroutine outofp2 + + Subroutine Trsn(xyz,nCent,Tau,Bt,lWrite,lWarn,Label,dBt,ldB) +!************************************************************************ +!* * +!* Reference: Molecular Vibrations, E. Bright Wilson, Jr, J. C. Decicius* +!* nd Paul C. Cross, Sec. 4-1, Eq. 20-24 * +!* * +!* R.Lindh May-June '96 * +!************************************************************************ + Implicit Real(wp) (a-h,o-z) + + integer :: nCent,mCent,i,j,ix,iy,iz,jx,jy,jz + Real(wp) Bt(3,nCent),xyz(3,nCent),Rij(3),Eij(3),Rjk(3),Ejk(3),& + & Rkl(3),Ekl(3),Rijk(3),Eijk(3),dBt(3,nCent,3,nCent),& + & BRij(3,2),dBRij(3,2,3,2),BRjk(3,2),dBRjk(3,2,3,2),& + & BRkl(3,2),dBRkl(3,2,3,2),Bf2(3,3),dum(3,4,3,4),& + & Bf3(3,3) + Logical :: lWrite,lWarn,ldB + Character(len=8) :: Label + ! + ! Call qEnter('Trsn') + mCent = 2 + Call Strtch(xyz(1,1),mCent,Rij1,BRij,.False.,Label,dBRij,ldB) + Call Strtch(xyz(1,2),mCent,Rjk1,BRjk,.False.,Label,dBRjk,ldB) + Call Strtch(xyz(1,3),mCent,Rkl1,BRkl,.False.,Label,dBRkl,ldB) + mCent = 3 + Call Bend(xyz(1,1),mCent,Fi2,Bf2,.False.,.False.,Label,Dum,& + & .False.) + SinFi2 = Sin(Fi2) + CosFi2 = Cos(Fi2) + Call Bend(xyz(1,2),mCent,Fi3,Bf3,.False.,.False.,Label,Dum,& + & .False.) + SinFi3 = Sin(Fi3) + CosFi3 = Cos(Fi3) + ! + ! Get the angle between the two planes, i.e. the + ! angle between the normal vectors. + ! + ! r123 * r234 = CosTau + ! + CosTau = ((BRij(2,1)*BRjk(3,2)-BRij(3,1)*BRjk(2,2))*& + & (BRjk(2,1)*BRkl(3,2)-BRjk(3,1)*BRkl(2,2))+& + & (BRij(3,1)*BRjk(1,2)-BRij(1,1)*BRjk(3,2))*& + & (BRjk(3,1)*BRkl(1,2)-BRjk(1,1)*BRkl(3,2))+& + & (BRij(1,1)*BRjk(2,2)-BRij(2,1)*BRjk(1,2))*& + & (BRjk(1,1)*BRkl(2,2)-BRjk(2,1)*BRkl(1,2)))& + & /(SinFi2*SinFi3) + ! + ! For the vector product of the two vectors. This + ! will give a vector parallell to e23. The direction + ! relative to e23 defines the sign. + ! + ! e123 X e234 = SinTau * e23 + ! + SinTau = (BRij(1,2)*(BRjk(2,1)*BRkl(3,2)-BRjk(3,1)*BRkl(2,2))& + & +BRij(2,2)*(BRjk(3,1)*BRkl(1,2)-BRjk(1,1)*BRkl(3,2))& + & +BRij(3,2)*(BRjk(1,1)*BRkl(2,2)-BRjk(2,1)*BRkl(1,2)))& + & /(SinFi2*SinFi3) + ! + ! (-Pi < Tau <= Pi) + ! + Tau = ATan2(SinTau,CosTau) + If (Abs(Tau) .eq. Pi) Tau = Pi + ! + dTau = 180.0D+00*Tau/Pi + dFi2 = 180.0D+00*Fi2/Pi + dFi3 = 180.0D+00*Fi3/Pi + If (lWarn) Then + If (dTau .gt. 177.5.or.dTau .lt. -177.5) Then + Write (*,*) ' Warning: dihedral angle close to'& + & //' end of range' + End If + If (dFi2 .gt. 177.5.or.dFi2 .lt. 2.5) Then + Write (*,*) ' Warning: bond angle close to'& + & //' end of range' + End If + If (dFi3 .gt. 177.5.or.dFi3 .lt. 2.5) Then + Write (*,*) ' Warning: bond angle close to'& + & //' end of range' + End If + End If + If (LWRITE) Write (*,1) Label,dTau,Tau +1 FORMAT(1X,A,' : Dihedral Angle=',F10.4,& + & '/degree,',F10.4,'/rad') + ! + !---- Compute the WDC matrix. + ! + Do ix = 1,3 + iy = ix+1 + If (iy .gt. 3) iy = iy-3 + iz = iy+1 + If (iz .gt. 3) iz = iz-3 + Bt(ix,1) = (BRij(iy,2)*BRjk(iz,2)-BRij(iz,2)*BRjk(iy,2))& + & /(Rij1*SinFi2**2) + Bt(ix,4) = (BRkl(iy,1)*BRjk(iz,1)-BRkl(iz,1)*BRjk(iy,1))& + & /(Rkl1*SinFi3**2) + Bt(ix,2) = -((Rjk1-Rij1*CosFi2)*Bt(ix,1)& + & +Rkl1*CosFi3*Bt(ix,4))/Rjk1 + Bt(ix,3) = -(Bt(ix,1)+Bt(ix,2)+Bt(ix,4)) + End Do + ! + If (ldB) Then + ! + !------- Compute the derivative of the WDC matrix. + ! + Do ix = 1,3 + iy = ix+1 + If (iy .gt. 3) iy = iy-3 + iz = iy+1 + If (iz .gt. 3) iz = iz-3 + Do jx = 1,ix + jy = jx+1 + If (jy .gt. 3) jy = jy-3 + jz = jy+1 + If (jz .gt. 3) jz = jz-3 + ! + dBt(ix,1,jx,1) = (dBRij(ix,1,jy,2)*BRjk(jz,2)& + & -dBRij(ix,1,jz,2)*BRjk(jy,2)& + & -Bt(jx,1)*(BRij(ix,1)*SinFi2**2& + & +Rij1*Two*SinFi2*CosFi2*Bf2(ix,1)))& + & /(Rij1*SinFi2**2) + dBt(ix,1,jx,2) = -((-BRij(ix,1)*CosFi2& + & +Rij1*SinFi2*Bf2(ix,1))*Bt(jx,1)& + & +(Rjk1-Rij1*CosFi2)*dBt(ix,1,jx,1))& + & /Rjk1 + dBt(jx,2,ix,1) = dBt(ix,1,jx,2) + dBt(ix,1,jx,4) = Zero + dBt(jx,4,ix,1) = dBt(ix,1,jx,4) + dBt(ix,1,jx,3) = -(dBt(ix,1,jx,1)+dBt(ix,1,jx,2)) + dBt(jx,3,ix,1) = dBt(ix,1,jx,3) + dBt(ix,4,jx,4) = (dBRkl(ix,2,jy,1)*BRjk(jz,1)& + & -dBRkl(ix,2,jz,1)*BRjk(jy,1)& + & -Bt(jx,4)*(BRkl(ix,2)*SinFi3**2& + & +Rkl1*Two*SinFi3*CosFi3*Bf3(ix,3)))& + & /(Rkl1*SinFi3**2) + dBt(ix,4,jx,3) = -((-BRkl(ix,2)*CosFi3& + & +Rkl1*SinFi3*Bf3(ix,3))*Bt(jx,4)& + & +(Rjk1-Rkl1*CosFi3)*dBt(ix,4,jx,4))& + & /Rjk1 + dBt(jx,3,ix,4) = dBt(ix,4,jx,3) + dBt(ix,4,jx,2) = -(dBt(ix,4,jx,4)+dBt(ix,4,jx,3)) + dBt(jx,2,ix,4) = dBt(ix,4,jx,2) + If (ix .ne. jx) Then + dBt(jx,1,ix,1) = dBt(ix,1,jx,1) + dBt(ix,4,jx,1) = Zero + dBt(jx,4,ix,4) = dBt(ix,4,jx,4) + dBt(jx,1,ix,4) = dBt(ix,4,jx,1) + dBt(jx,1,ix,2) = -((-BRij(jx,1)*CosFi2& + & +Rij1*SinFi2*Bf2(jx,1))*Bt(ix,1)& + & +(Rjk1-Rij1*CosFi2)*dBt(jx,1,ix,1))& + & /Rjk1 + dBt(ix,2,jx,1) = dBt(jx,1,ix,2) + dBt(ix,3,jx,1) = -(dBt(ix,1,jx,1)+dBt(ix,2,jx,1)& + & +dBt(ix,4,jx,1)) + dBt(jx,1,ix,3) = dBt(ix,3,jx,1) + dBt(jx,4,ix,3) = -((-BRkl(jx,2)*CosFi3& + & +Rkl1*SinFi3*Bf3(jx,3))*Bt(ix,4)& + & +(Rjk1-Rkl1*CosFi3)*dBt(jx,4,ix,4))& + & /Rjk1 + dBt(ix,3,jx,4) = dBt(jx,4,ix,3) + dBt(ix,2,jx,4) = -(dBt(ix,4,jx,4)+dBt(ix,3,jx,4)) + dBt(jx,4,ix,2) = dBt(ix,2,jx,4) + End If + dBt(ix,2,jx,3) = -((BRjk(ix,1)& + & +Rkl1*SinFi3*Bf3(ix,1))*Bt(jx,4)& + & +(Rjk1-Rkl1*CosFi3)*dBt(ix,2,jx,4)& + & +(BRij(ix,2)*CosFi2& + & -Rij1*SinFi2*Bf2(ix,2))*Bt(jx,1)& + & +Rij1*CosFi2*dBt(ix,2,jx,1)& + & +Bt(jx,3)*BRjk(ix,1))/Rjk1 + dBt(jx,3,ix,2) = dBt(ix,2,jx,3) + dBt(ix,2,jx,2) = -(dBt(ix,2,jx,1)+dBt(ix,2,jx,4)& + & +dBt(ix,2,jx,3)) + dBt(ix,3,jx,3) = -(dBt(ix,2,jx,3)+dBt(ix,1,jx,3)& + & +dBt(ix,4,jx,3)) + If (ix .ne. jx) Then + dBt(ix,3,jx,2) = -(dBt(ix,2,jx,2)+dBt(ix,1,jx,2)& + & +dBt(ix,4,jx,2)) + dBt(jx,2,ix,3) = dBt(ix,3,jx,2) + dBt(jx,2,ix,2) = dBt(ix,2,jx,2) + dBt(jx,3,ix,3) = dBt(ix,3,jx,3) + End If + ! + End Do + End Do + ! + End If + ! Call qExit('Trsn') + Return + contains + Subroutine Strtch(xyz,nCent,Avst,B,lWrite,Label,dB,ldB) + Implicit Real(wp) (a-h,o-z) + ! include "common/real.inc" + !comdeck real.inc $Revision: 2002.3 $ + Real(wp) :: Zero,One,Two,Three,Four,Five,Six,Seven,& + & Eight,RNine,Ten,Half,Pi,SqrtP2,TwoP34,& + & TwoP54,One2C2 + Parameter(Zero=0.0D0,One=1.0D0,Two=2.0D0,Three=3.0D0,& + & Four=4.0D0,Five=5.0D0,Six=6.0D0,Seven=7.0D0,& + & Eight=8.0D0,rNine=9.0D0,Ten=1.0D1,Half=0.5D0,& + & Pi=3.141592653589793D0,& + & SqrtP2=0.8862269254527579D0,& + & TwoP34=0.2519794355383808D0,& + & TwoP54=5.914967172795612D0,& + & One2C2=0.2662567690426443D-04) + + integer :: nCent + Real(wp) :: B(3,nCent),xyz(3,nCent),dB(3,nCent,3,nCent),R(3) + Logical :: lWrite,ldB + Character(len=8) :: Label + ! include "common/angstr.inc" + !comdeck angstr.inc $Revision: 2002.3 $ + ! + ! Conversion factor angstrom to bohr from the IUPAC + ! publication + ! .529177249(24) angstrom / bohr + ! "Quantities, Units and Symbols in Physical Chemistry" + ! I. Mills, T. Cvitas, K. Homann, N. Kallay and + ! K. Kuchitsu, Blackwell Scientific Publications, + ! Oxford, 1988. + ! + Data Angstr/0.529177249D+00/ + ! + R(1) = xyz(1,2)-xyz(1,1) + R(2) = xyz(2,2)-xyz(2,1) + R(3) = xyz(3,2)-xyz(3,1) + R2 = R(1)**2+R(2)**2+R(3)**2 + RR = Sqrt(R2) + Avst = RR + ! + aRR = RR*Angstr + If (lWrite) Write (*,'(1X,A,A,2(F10.6,A))') Label,& + & ' : Bond Length=',aRR,' / Angstrom',RR,' / bohr' + ! + !---- Compute the WDC B-matrix. + ! + B(1,1) = -R(1)/RR + B(2,1) = -R(2)/RR + B(3,1) = -R(3)/RR + !.... Utilize translational invariance. + B(1,2) = -B(1,1) + B(2,2) = -B(2,1) + B(3,2) = -B(3,1) + ! + !---- Compute the cartesian derivative of the B-matrix. + ! + If (ldB) Then + ! + Do i = 1,3 + Do j = 1,i + If (i .eq. j) Then + dB(i,1,j,1) = (One-B(j,1)*B(i,1))/RR + Else + dB(i,1,j,1) = (-B(j,1)*B(i,1))/RR + End If + dB(j,1,i,1) = dB(i,1,j,1) + ! + dB(i,2,j,1) = -dB(i,1,j,1) + dB(j,1,i,2) = dB(i,2,j,1) + ! + dB(i,1,j,2) = -dB(i,1,j,1) + dB(j,2,i,1) = dB(i,1,j,2) + ! + dB(i,2,j,2) = -dB(i,2,j,1) + dB(j,2,i,2) = dB(i,2,j,2) + End Do + End Do + ! + End If + ! Call qExit('Strtch') + ! Call GetMem('Exit Strtch','Chec','Real',ipMass,2*msAtom) + Return + End subroutine strtch + Subroutine Bend(xyz,nCent,Fir,Bf,lWrite,lWarn,Label,dBf,ldB) + Implicit Real(wp) (a-h,o-z) + + integer :: nCent + !Real(wp) :: Bf(3,nCent),xyz(3,nCent),dBf(3,nCent,3,nCent),& + Real(wp) :: Bf(3,3),xyz(3,nCent),dBf(3,nCent,3,nCent),& + & BRij(3,2),dBRij(3,2,3,2),& + & BRjk(3,2),dBRjk(3,2,3,2) + Logical lWrite,ldB,lWarn + Character(len=8) :: Label + ! + ! Call QEnter('Bend') + ! + mCent = 2 + Call Strtch(xyz(1,1),mCent,Rij1,BRij,.False.,Label,dBRij,ldB) + Call Strtch(xyz(1,2),mCent,Rjk1,BRjk,.False.,Label,dBRjk,ldB) + Co = Zero + Crap = Zero + Do i = 1,3 + Co = Co+BRij(i,1)*BRjk(i,2) + Crap = Crap+(BRjk(i,2)+BRij(i,1))**2 + End Do + ! + !.... Special care for cases close to linearity + ! + If (Sqrt(Crap) .lt. 1.0D-6) Then + Fir = Pi-ArSin(Sqrt(Crap)) + Si = Sqrt(Crap) + Else + Fir = ArCos(Co) + Si = Sqrt(One-Co**2) + End If + ! + If (Abs(Fir-Pi) .lt. 1.0d-13) Then + Fir = Pi + Return + End If + dFir = 180.0D0*Fir/Pi + If ((Abs(dFir) .gt. 177.5.or.Abs(dFir) .lt. 2.5).and.lWarn)& + & Write (*,*) ' Valence angle close to end in '//& + & 'range of definition' + If (lWrite) Write (*,'(1X,A,A,F10.4,A,F10.6,A)') Label,& + & ' : Angle=',dFir,'/degree, ',Fir,'/rad' + ! + !---- Compute the WDC B-matrix + ! + ! Bf=-11.1111 + Do i = 1,3 + Bf(i,1) = (Co*BRij(i,1)-BRjk(i,2))/(Si*Rij1) + Bf(i,3) = (Co*BRjk(i,2)-BRij(i,1))/(Si*Rjk1) + !....... Utilize translational invariance. + Bf(i,2) = -(Bf(i,1)+Bf(i,3)) + End Do + ! Call RecPrt('Bf',' ',Bf,9,1) + ! + !---- Compute the cartesian derivative of the B-Matrix. + ! + If (ldB) Then + ! + ! dBf=-11.11111 + Do i = 1,3 + Do j = 1,i + dBf(i,1,j,1) = (-Si*Bf(i,1)*BRij(j,1)& + & +Co*dBRij(i,1,j,1)& + & -Bf(j,1)*(Co*Bf(i,1)*Rij1& + & +Si*BRij(i,1)))/(Si*Rij1) + dBf(i,1,j,3) = (-Si*Bf(i,1)*BRjk(j,2)& + & +dBRij(i,1,j,2)& + & -Bf(j,3)*Co*Bf(i,1)*Rjk1)& + & /(Si*Rjk1) + ! Write (*,*) '13',dBf(i,1,j,3), i, j + dBf(i,3,j,1) = (-Si*Bf(i,3)*BRij(j,1)& + & +dBRjk(i,2,j,1)& + & -Bf(j,1)*Co*Bf(i,3)*Rij1)& + & /(Si*Rij1) + dBf(i,3,j,3) = (-Si*Bf(i,3)*BRjk(j,2)& + & +Co*dBRjk(i,2,j,2)& + & -Bf(j,3)*(Co*Bf(i,3)*Rjk1& + & +Si*BRjk(i,2)))/(Si*Rjk1) + ! + dBf(j,1,i,1) = dBf(i,1,j,1) + dBf(j,3,i,1) = dBf(i,1,j,3) + dBf(j,1,i,3) = dBf(i,3,j,1) + dBf(j,3,i,3) = dBf(i,3,j,3) + ! + dBf(i,1,j,2) = -(dBf(i,1,j,1)+dBf(i,1,j,3)) + dBf(j,2,i,1) = dBf(i,1,j,2) + dBf(j,1,i,2) = -(dBf(j,1,i,1)+dBf(j,1,i,3)) + dBf(i,2,j,1) = dBf(j,1,i,2) + dBf(i,3,j,2) = -(dBf(i,3,j,1)+dBf(i,3,j,3)) + dBf(j,2,i,3) = dBf(i,3,j,2) + dBf(j,3,i,2) = -(dBf(j,3,i,1)+dBf(j,3,i,3)) + dBf(i,2,j,3) = dBf(j,3,i,2) + ! + dBf(i,2,j,2) = -(dBf(i,2,j,1)+dBf(i,2,j,3)) + dBf(j,2,i,2) = dBf(i,2,j,2) + ! + End Do + End Do + ! Call RecPrt('dBf','(9F9.1)',dBf,9,9) + ! + End If + ! + ! Call QExit('Bend') + Return + End subroutine bend + Function arSin(Arg) + Implicit Real*8(a-h,o-z) + Real*8 ArSin + + A = Arg + IF (ABS(A) .GT. One) Then + PRINT 3,A +3 FORMAT(1X,'Warning argument of aSin= ',1F21.18) + A = Sign(One,A) + End If + ! + ArSin = ASin(A) + Return + End function arSin + Function arCos(Arg) + Implicit Real(wp) (a-h,o-z) + Real(wp) :: ArCos + A = Arg + IF (ABS(A) .GT. One) Then + A = Sign(One,A) + End If + ArCos = ACos(A) + Return + End function arCos + End subroutine trsn + + pure elemental function ixyz(i,iatom) + integer :: ixyz + integer,intent(in) :: i,iatom + ixyz = (iatom-1)*3+i + end function ixyz + pure elemental function jnd(i,j) + integer :: jnd + integer,intent(in) :: i,j + jnd = i*(i-1)/2+j + end function jnd + pure elemental function ind(i,iatom,j,jatom) + integer :: ind + integer,intent(in) :: i,iatom,j,jatom + ind = jnd(max(ixyz(i,iatom),ixyz(j,jatom)),min(ixyz(i,iatom),ixyz(j,jatom))) + end function ind + + pure elemental function fk_lindh(alpha,r0,r2) result(gmm) + implicit none + real(wp),intent(in) :: alpha,r0,r2 + real(wp) :: gmm + gmm = exp(alpha*(r0**2-r2)) + end function fk_lindh + + pure elemental function fk_swart(alpha,r0,r2) result(gmm) + implicit none + real(wp),intent(in) :: alpha,r0,r2 + real(wp) :: gmm + gmm = exp(-alpha*(sqrt(r2)/r0-1.0_wp)) + end function fk_swart + + pure elemental function fk_vdw(alpha,r0,r2) result(gmm) + implicit none + real(wp),intent(in) :: alpha,r0,r2 + real(wp) :: gmm + gmm = exp(-alpha*(r0-sqrt(r2))**2) + end function fk_vdw + +!========================================================================================! +!########################################################################################! +!========================================================================================! + + subroutine mh_eeq(n,at,xyz,chrg,kq,hess) + implicit none + +!! ------------------------------------------------------------------------ +! Input +!! ------------------------------------------------------------------------ + integer,intent(in) :: n ! number of atoms + integer,intent(in) :: at(n) ! ordinal numbers + real(wp),intent(in) :: xyz(3,n) ! geometry + real(wp),intent(in) :: chrg ! total charge + real(wp),intent(in) :: kq ! scaling parameter +! type(chrg_parameter),intent(in) :: chrgeq ! charge model +!! ------------------------------------------------------------------------ +! Output +!! ------------------------------------------------------------------------ + real(wp),intent(out) :: hess((3*n)*(3*n+1)/2) + real(wp),allocatable :: hessian(:,:,:,:) ! molecular hessian of IES + +! π itself + real(wp),parameter :: pi = 3.1415926535897932384626433832795029_wp +! √π + real(wp),parameter :: sqrtpi = sqrt(pi) +! √(2/π) + real(wp),parameter :: sqrt2pi = sqrt(2.0_wp/pi) +! +!! ------------------------------------------------------------------------ +! charge model +!! ------------------------------------------------------------------------ + integer :: m ! dimension of the Lagrangian + real(wp),allocatable :: Amat(:,:) + real(wp),allocatable :: Xvec(:) + real(wp),allocatable :: Ainv(:,:) + real(wp),allocatable :: dAmat(:,:,:) + real(wp),allocatable :: dqdr(:,:,:) + +!! ------------------------------------------------------------------------ +! local variables +!! ------------------------------------------------------------------------ + integer :: i,j,k,l + real(wp) :: r,rij(3),r2 + real(wp) :: gamij,gamij2 + real(wp) :: arg,arg2,tmp,dtmp + real(wp) :: lambda + real(wp) :: es,expterm,erfterm + real(wp) :: htmp,rxr(3,3) + real(wp) :: rcovij,rr + +!! ------------------------------------------------------------------------ +! scratch variables +!! ------------------------------------------------------------------------ + real(wp),allocatable :: alpha(:) + real(wp),allocatable :: xtmp(:) + real(wp),allocatable :: atmp(:,:) + +!! ------------------------------------------------------------------------ +! Lapack work variables +!! ------------------------------------------------------------------------ + integer,allocatable :: ipiv(:) + real(wp),allocatable :: temp(:) + real(wp),allocatable :: work(:) + integer :: lwork + integer :: info + real(wp) :: test(1) + +!! ------------------------------------------------------------------------ +! EEQ parameters +! PARAMETRISATION BY S. SPICHER (Fri, 14 Dec 2018 16:13:08 +0100) +!! ------------------------------------------------------------------------ + integer,parameter :: max_elem = 86 +!&< + real(wp),parameter :: enparam(max_elem) = (/ & + 1.23695041_wp, 1.26590957_wp, 0.54341808_wp, 0.99666991_wp, 1.26691604_wp, & + 1.40028282_wp, 1.55819364_wp, 1.56866440_wp, 1.57540015_wp, 1.15056627_wp, & + 0.55936220_wp, 0.72373742_wp, 1.12910844_wp, 1.12306840_wp, 1.52672442_wp, & + 1.40768172_wp, 1.48154584_wp, 1.31062963_wp, 0.40374140_wp, 0.75442607_wp, & + 0.76482096_wp, 0.98457281_wp, 0.96702598_wp, 1.05266584_wp, 0.93274875_wp, & + 1.04025281_wp, 0.92738624_wp, 1.07419210_wp, 1.07900668_wp, 1.04712861_wp, & + 1.15018618_wp, 1.15388455_wp, 1.36313743_wp, 1.36485106_wp, 1.39801837_wp, & + 1.18695346_wp, 0.36273870_wp, 0.58797255_wp, 0.71961946_wp, 0.96158233_wp, & + 0.89585296_wp, 0.81360499_wp, 1.00794665_wp, 0.92613682_wp, 1.09152285_wp, & + 1.14907070_wp, 1.13508911_wp, 1.08853785_wp, 1.11005982_wp, 1.12452195_wp, & + 1.21642129_wp, 1.36507125_wp, 1.40340000_wp, 1.16653482_wp, 0.34125098_wp, & + 0.58884173_wp, 0.68441115_wp, 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, & + 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, & + 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, 0.56999999_wp, & + 0.56999999_wp, 0.87936784_wp, 1.02761808_wp, 0.93297476_wp, 1.10172128_wp, & + 0.97350071_wp, 1.16695666_wp, 1.23997927_wp, 1.18464453_wp, 1.14191734_wp, & + 1.12334192_wp, 1.01485321_wp, 1.12950808_wp, 1.30804834_wp, 1.33689961_wp, & + 1.27465977_wp /) + real(wp),parameter :: gamparam(max_elem) = (/ & + -0.35015861_wp, 1.04121227_wp, 0.09281243_wp, 0.09412380_wp, 0.26629137_wp, & + 0.19408787_wp, 0.05317918_wp, 0.03151644_wp, 0.32275132_wp, 1.30996037_wp, & + 0.24206510_wp, 0.04147733_wp, 0.11634126_wp, 0.13155266_wp, 0.15350650_wp, & + 0.15250997_wp, 0.17523529_wp, 0.28774450_wp, 0.42937314_wp, 0.01896455_wp, & + 0.07179178_wp,-0.01121381_wp,-0.03093370_wp, 0.02716319_wp,-0.01843812_wp, & + -0.15270393_wp,-0.09192645_wp,-0.13418723_wp,-0.09861139_wp, 0.18338109_wp, & + 0.08299615_wp, 0.11370033_wp, 0.19005278_wp, 0.10980677_wp, 0.12327841_wp, & + 0.25345554_wp, 0.58615231_wp, 0.16093861_wp, 0.04548530_wp,-0.02478645_wp, & + 0.01909943_wp, 0.01402541_wp,-0.03595279_wp, 0.01137752_wp,-0.03697213_wp, & + 0.08009416_wp, 0.02274892_wp, 0.12801822_wp,-0.02078702_wp, 0.05284319_wp, & + 0.07581190_wp, 0.09663758_wp, 0.09547417_wp, 0.07803344_wp, 0.64913257_wp, & + 0.15348654_wp, 0.05054344_wp, 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, & + 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, & + 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, 0.11000000_wp, & + 0.11000000_wp,-0.02786741_wp, 0.01057858_wp,-0.03892226_wp,-0.04574364_wp, & + -0.03874080_wp,-0.03782372_wp,-0.07046855_wp, 0.09546597_wp, 0.21953269_wp, & + 0.02522348_wp, 0.15263050_wp, 0.08042611_wp, 0.01878626_wp, 0.08715453_wp, & + 0.10500484_wp /) + real(wp),parameter :: kappa(max_elem) = (/ & + 0.04916110_wp, 0.10937243_wp,-0.12349591_wp,-0.02665108_wp,-0.02631658_wp, & + 0.06005196_wp, 0.09279548_wp, 0.11689703_wp, 0.15704746_wp, 0.07987901_wp, & + -0.10002962_wp,-0.07712863_wp,-0.02170561_wp,-0.04964052_wp, 0.14250599_wp, & + 0.07126660_wp, 0.13682750_wp, 0.14877121_wp,-0.10219289_wp,-0.08979338_wp, & + -0.08273597_wp,-0.01754829_wp,-0.02765460_wp,-0.02558926_wp,-0.08010286_wp, & + -0.04163215_wp,-0.09369631_wp,-0.03774117_wp,-0.05759708_wp, 0.02431998_wp, & + -0.01056270_wp,-0.02692862_wp, 0.07657769_wp, 0.06561608_wp, 0.08006749_wp, & + 0.14139200_wp,-0.05351029_wp,-0.06701705_wp,-0.07377246_wp,-0.02927768_wp, & + -0.03867291_wp,-0.06929825_wp,-0.04485293_wp,-0.04800824_wp,-0.01484022_wp, & + 0.07917502_wp, 0.06619243_wp, 0.02434095_wp,-0.01505548_wp,-0.03030768_wp, & + 0.01418235_wp, 0.08953411_wp, 0.08967527_wp, 0.07277771_wp,-0.02129476_wp, & + -0.06188828_wp,-0.06568203_wp,-0.11000000_wp,-0.11000000_wp,-0.11000000_wp, & + -0.11000000_wp,-0.11000000_wp,-0.11000000_wp,-0.11000000_wp,-0.11000000_wp, & + -0.11000000_wp,-0.11000000_wp,-0.11000000_wp,-0.11000000_wp,-0.11000000_wp, & + -0.11000000_wp,-0.03585873_wp,-0.03132400_wp,-0.05902379_wp,-0.02827592_wp, & + -0.07606260_wp,-0.02123839_wp, 0.03814822_wp, 0.02146834_wp, 0.01580538_wp, & + -0.00894298_wp,-0.05864876_wp,-0.01817842_wp, 0.07721851_wp, 0.07936083_wp, & + 0.05849285_wp /) + real(wp),parameter :: alphaparam(max_elem) = (/ & + 0.55159092_wp, 0.66205886_wp, 0.90529132_wp, 1.51710827_wp, 2.86070364_wp, & + 1.88862966_wp, 1.32250290_wp, 1.23166285_wp, 1.77503721_wp, 1.11955204_wp, & + 1.28263182_wp, 1.22344336_wp, 1.70936266_wp, 1.54075036_wp, 1.38200579_wp, & + 2.18849322_wp, 1.36779065_wp, 1.27039703_wp, 1.64466502_wp, 1.58859404_wp, & + 1.65357953_wp, 1.50021521_wp, 1.30104175_wp, 1.46301827_wp, 1.32928147_wp, & + 1.02766713_wp, 1.02291377_wp, 0.94343886_wp, 1.14881311_wp, 1.47080755_wp, & + 1.76901636_wp, 1.98724061_wp, 2.41244711_wp, 2.26739524_wp, 2.95378999_wp, & + 1.20807752_wp, 1.65941046_wp, 1.62733880_wp, 1.61344972_wp, 1.63220728_wp, & + 1.60899928_wp, 1.43501286_wp, 1.54559205_wp, 1.32663678_wp, 1.37644152_wp, & + 1.36051851_wp, 1.23395526_wp, 1.65734544_wp, 1.53895240_wp, 1.97542736_wp, & + 1.97636542_wp, 2.05432381_wp, 3.80138135_wp, 1.43893803_wp, 1.75505957_wp, & + 1.59815118_wp, 1.76401732_wp, 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, & + 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, & + 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, 1.63999999_wp, & + 1.63999999_wp, 1.47055223_wp, 1.81127084_wp, 1.40189963_wp, 1.54015481_wp, & + 1.33721475_wp, 1.57165422_wp, 1.04815857_wp, 1.78342098_wp, 2.79106396_wp, & + 1.78160840_wp, 2.47588882_wp, 2.37670734_wp, 1.76613217_wp, 2.66172302_wp, & + 2.82773085_wp /) +!&> + +!! ------------------------------------------------------------------------ +! initizialization +!! ------------------------------------------------------------------------ + m = n+1 + allocate (ipiv(m),source=0) + allocate (Amat(m,m),Xvec(m),alpha(n),dqdr(3,n,m),source=0.0_wp) + +!! ------------------------------------------------------------------------ +! set up the A matrix and X vector +!! ------------------------------------------------------------------------ +! αi -> alpha(i), ENi -> xi(i), κi -> kappa(i), Jii -> gam(i) +! γij = 1/√(αi+αj) +! Xi = -ENi + κi·√CNi +! Aii = Jii + 2/√π·γii +! Aij = erf(γij·Rij)/Rij = 2/√π·F0(γ²ij·R²ij) +!! ------------------------------------------------------------------------ +! prepare some arrays +!$omp parallel default(none) & +!!$omp shared(n,at,chrgeq) & +!$omp shared(n,at) & +!$omp private(i) & +!$omp shared(Xvec,alpha) +!$omp do schedule(dynamic) + do i = 1,n +! Xvec(i) = -chrgeq%en(i) +! alpha(i) = chrgeq%alpha(i)**2 + Xvec(i) = -enparam(at(i)) + alpha(i) = alphaparam(at(i))**2 + end do +!$omp enddo +!$omp endparallel + +!$omp parallel default(none) & +!!$omp shared(n,at,xyz,chrgeq,alpha) & +!$omp shared(n,at,xyz,alpha) & +!$omp private(i,j,r,gamij) & +!$omp shared(Amat) +!$omp do schedule(dynamic) + ! prepare A matrix + do i = 1,n + ! EN of atom i + do j = 1,i-1 + r = sqrt(sum((xyz(:,j)-xyz(:,i))**2)) + gamij = 1.0_wp/sqrt(alpha(i)+alpha(j)) + Amat(j,i) = erf(gamij*r)/r + Amat(i,j) = Amat(j,i) + end do +! Amat(i,i) = chrgeq%gam(i)+sqrt2pi/sqrt(alpha(i)) + Amat(i,i) = gamparam(at(i))+sqrt2pi/sqrt(alpha(i)) + end do +!$omp enddo +!$omp endparallel + +!! ------------------------------------------------------------------------ +! solve the linear equations to obtain partial charges +!! ------------------------------------------------------------------------ + Amat(m,1:m) = 1.0_wp + Amat(1:m,m) = 1.0_wp + Amat(m,m) = 0.0_wp + Xvec(m) = chrg + ! generate temporary copy + allocate (Atmp(m,m),source=Amat) + allocate (Xtmp(m),source=Xvec) + + ! assume work space query, set best value to test after first dsysv call + call dsysv('u',m,1,Atmp,m,ipiv,Xtmp,m,test,-1,info) + lwork = int(test(1)) + allocate (work(lwork),source=0.0_wp) + + call dsysv('u',m,1,Atmp,m,ipiv,Xtmp,m,work,lwork,info) + if (info > 0) error stop '** ERROR ** (goedecker_solve) DSYSV failed' + + if (abs(sum(Xtmp(:n))-chrg) > 1.e-6_wp) & + error stop '** ERROR ** (goedecker_solve) charge constrain error' + !print'(3f20.14)',Xtmp + +!! ------------------------------------------------------------------------ +! calculate isotropic electrostatic (IES) energy +!! ------------------------------------------------------------------------ +! E = ∑i (ENi - κi·√CNi)·qi + ∑i (Jii + 2/√π·γii)·q²i +! + ½ ∑i ∑j,j≠i qi·qj·2/√π·F0(γ²ij·R²ij) +! = q·(½A·q - X) +!! ------------------------------------------------------------------------ +! work(:m) = Xvec +! call dsymv('u',m,0.5_wp,Amat,m,Xtmp,1,-1.0_wp,work,1) +! es = dot_product(Xtmp,work(:m)) +! energy = es + energy + +!! ------------------------------------------------------------------------ +! calculate molecular gradient of the IES energy +!! ------------------------------------------------------------------------ +! dE/dRj -> g(:,j), ∂Xi/∂Rj -> -dcn(:,i,j), ½∂Aij/∂Rj -> dAmat(:,j,i) +! dE/dR = (½∂A/∂R·q - ∂X/∂R)·q +! ∂Aij/∂Rj = ∂Aij/∂Ri +!! ------------------------------------------------------------------------ + allocate (dAmat(3,n,m),source=0.0_wp) +!$omp parallel default(none) & +!$omp shared(n,xyz,alpha,Amat,Xtmp) & +!$omp private(i,j,rij,r2,gamij,arg,dtmp) & +!$omp reduction(+:dAmat) +!$omp do schedule(dynamic) + do i = 1,n + do j = 1,i-1 + rij = xyz(:,i)-xyz(:,j) + r2 = sum(rij**2) + gamij = 1.0_wp/sqrt(alpha(i)+alpha(j)) + arg = gamij**2*r2 + dtmp = 2.0_wp*gamij*exp(-arg)/(sqrtpi*r2)-Amat(j,i)/r2 + dAmat(:,i,i) = +dtmp*rij*Xtmp(j)+dAmat(:,i,i) + dAmat(:,j,j) = -dtmp*rij*Xtmp(i)+dAmat(:,j,j) + dAmat(:,i,j) = +dtmp*rij*Xtmp(i) + dAmat(:,j,i) = -dtmp*rij*Xtmp(j) + end do + end do +!$omp enddo +!$omp endparallel + +!! ------------------------------------------------------------------------ +! invert the A matrix using a Bunch-Kaufman factorization +! A⁻¹ = (L·D·L^T)⁻¹ = L^T·D⁻¹·L +!! ------------------------------------------------------------------------ + allocate (Ainv(m,m),source=Amat) + + ! assume work space query, set best value to test after first dsytrf call + call dsytrf('L',m,Ainv,m,ipiv,test,-1,info) + if (int(test(1)) > lwork) then + deallocate (work) + lwork = int(test(1)) + allocate (work(lwork),source=0.0_wp) + end if + + ! Bunch-Kaufman factorization A = L*D*L**T + call dsytrf('L',m,Ainv,m,ipiv,work,lwork,info) + if (info > 0) then + error stop '** ERROR ** (goedecker_inversion) DSYTRF failed' + + end if + + ! A⁻¹ from factorized L matrix, save lower part of A⁻¹ in Ainv matrix + ! Ainv matrix is overwritten with lower triangular part of A⁻¹ + call dsytri('L',m,Ainv,m,ipiv,work,info) + if (info > 0) then + error stop '** ERROR ** (goedecker_inversion) DSYTRI failed' + end if + + ! symmetrizes A⁻¹ matrix from lower triangular part of inverse matrix + do i = 1,m + do j = i+1,m + Ainv(i,j) = Ainv(j,i) + end do + end do + +!! ------------------------------------------------------------------------ +! calculate gradient of the partial charge w.r.t. the nuclear coordinates +!! ------------------------------------------------------------------------ + !call dsymm('r','l',3*n,m,-1.0_wp,Ainv,m,dAmat,3*n,1.0_wp,dqdr,3*n) + call dgemm('n','n',3*n,m,m,-1.0_wp,dAmat,3*n,Ainv,m,1.0_wp,dqdr,3*n) + !print'(/,"analytical gradient")' + !print'(3f20.14)',dqdr(:,:,:n) + +!! ------------------------------------------------------------------------ +! molecular Hessian calculation +!! ------------------------------------------------------------------------ + do i = 1,n + do j = 1,i-1 + rij = xyz(:,j)-xyz(:,i) + r2 = sum(rij**2) + r = sqrt(r2) + gamij = 1.0_wp/sqrt(alpha(i)+alpha(j)) + gamij2 = gamij**2 + arg2 = gamij2*r2 + arg = sqrt(arg2) + erfterm = Xtmp(i)*Xtmp(j)*erf(arg)/r + expterm = Xtmp(i)*Xtmp(j)*2*gamij*exp(-arg2)/sqrtpi + ! ∂²(qAq)/(∂Ri∂Rj): + ! ∂²(qAq)/(∂Xi∂Xi) = (1-3X²ij/R²ij-2γ²ijX²ij) 2γij/√π exp[-γ²ij·R²ij]/R²ij + ! - (R²ij-3X²ij) erf[γij·Rij]/R⁵ij + ! ∂²(qAq)/(∂Xi∂Xj) = (R²ij-3X²ij) erf[γij·Rij]/R⁵ij + ! - (1-3X²ij/R²ij-2γ²ijX²ij) 2γij/√π exp[-γ²ij·R²ij]/R²ij + ! ∂²(qAq)/(∂Xi∂Yi) = 3X²ij erf[γij·Rij]/R⁵ij + ! - (3X²ij/R²ij+2γ²ijX²ij) 2γij/√π exp[-γ²ij·R²ij]/R²ij + ! ∂²(qAq)/(∂Xi∂Yj) = (3X²ij/R²ij+2γ²ijX²ij) 2γij/√π exp[-γ²ij·R²ij]/R²ij + ! - 3X²ij erf[γij·Rij]/R⁵ij + rxr(1,1) = erfterm*(3*rij(1)**2/r2**2-1.0_wp/r2) & + -expterm*(3*rij(1)**2/r2**2+2*gamij2*rij(1)**2/r2-1/r2) + rxr(2,2) = erfterm*(3*rij(2)**2/r2**2-1.0_wp/r2) & + -expterm*(3*rij(2)**2/r2**2+2*gamij2*rij(2)**2/r2-1/r2) + rxr(3,3) = erfterm*(3*rij(3)**2/r2**2-1.0_wp/r2) & + -expterm*(3*rij(3)**2/r2**2+2*gamij2*rij(3)**2/r2-1/r2) + rxr(2,1) = erfterm*3*rij(2)*rij(1)/r2**2 & + -expterm*(3*rij(2)*rij(1)/r2**2+2*gamij2*rij(2)*rij(1)/r2) + rxr(3,1) = erfterm*3*rij(3)*rij(1)/r2**2 & + -expterm*(3*rij(3)*rij(1)/r2**2+2*gamij2*rij(3)*rij(1)/r2) + rxr(3,2) = erfterm*3*rij(3)*rij(2)/r2**2 & + -expterm*(3*rij(3)*rij(2)/r2**2+2*gamij2*rij(3)*rij(2)/r2) + + do k = 1,m + rxr(1,1) = rxr(1,1)+0.5_wp*dqdr(1,i,k)*dAmat(1,j,k) & + +0.5_wp*dqdr(1,j,k)*dAmat(1,i,k) + rxr(2,1) = rxr(2,1)+0.5_wp*dqdr(2,i,k)*dAmat(1,j,k) & + +0.5_wp*dqdr(2,j,k)*dAmat(1,i,k) + rxr(3,1) = rxr(3,1)+0.5_wp*dqdr(3,i,k)*dAmat(1,j,k) & + +0.5_wp*dqdr(3,j,k)*dAmat(1,i,k) + rxr(2,2) = rxr(2,2)+0.5_wp*dqdr(2,i,k)*dAmat(2,j,k) & + +0.5_wp*dqdr(2,j,k)*dAmat(2,i,k) + rxr(3,2) = rxr(3,2)+0.5_wp*dqdr(3,i,k)*dAmat(2,j,k) & + +0.5_wp*dqdr(3,j,k)*dAmat(2,i,k) + rxr(3,3) = rxr(3,3)+0.5_wp*dqdr(3,i,k)*dAmat(3,j,k) & + +0.5_wp*dqdr(3,j,k)*dAmat(3,i,k) + end do + ! symmetrize + rxr(1,2) = rxr(2,1) + rxr(1,3) = rxr(3,1) + rxr(2,3) = rxr(3,2) + + ! save diagonal elements for atom i + hess(ind(1,i,1,i)) = hess(ind(1,i,1,i))+kq*rxr(1,1) + hess(ind(2,i,1,i)) = hess(ind(2,i,1,i))+kq*rxr(2,1) + hess(ind(2,i,2,i)) = hess(ind(2,i,2,i))+kq*rxr(2,2) + hess(ind(3,i,1,i)) = hess(ind(3,i,1,i))+kq*rxr(3,1) + hess(ind(3,i,2,i)) = hess(ind(3,i,2,i))+kq*rxr(3,2) + hess(ind(3,i,3,i)) = hess(ind(3,i,3,i))+kq*rxr(3,3) + ! save elements between atom i and atom j + hess(ind(1,i,1,j)) = hess(ind(1,i,1,j))-kq*rxr(1,1) + hess(ind(1,i,2,j)) = hess(ind(1,i,2,j))-kq*rxr(2,1) + hess(ind(1,i,3,j)) = hess(ind(1,i,3,j))-kq*rxr(3,1) + hess(ind(2,i,1,j)) = hess(ind(2,i,1,j))-kq*rxr(2,1) + hess(ind(2,i,2,j)) = hess(ind(2,i,2,j))-kq*rxr(2,2) + hess(ind(2,i,3,j)) = hess(ind(2,i,3,j))-kq*rxr(3,2) + hess(ind(3,i,1,j)) = hess(ind(3,i,1,j))-kq*rxr(3,1) + hess(ind(3,i,2,j)) = hess(ind(3,i,2,j))-kq*rxr(3,2) + hess(ind(3,i,3,j)) = hess(ind(3,i,3,j))-kq*rxr(3,3) + ! save diagonal elements for atom j + hess(ind(1,j,1,j)) = hess(ind(1,j,1,j))+kq*rxr(1,1) + hess(ind(2,j,1,j)) = hess(ind(2,j,1,j))+kq*rxr(2,1) + hess(ind(2,j,2,j)) = hess(ind(2,j,2,j))+kq*rxr(2,2) + hess(ind(3,j,1,j)) = hess(ind(3,j,1,j))+kq*rxr(3,1) + hess(ind(3,j,2,j)) = hess(ind(3,j,2,j))+kq*rxr(3,2) + hess(ind(3,j,3,j)) = hess(ind(3,j,3,j))+kq*rxr(3,3) + end do + end do + + ! ∂²(qA)/(∂Ri∂q)·∂q/∂Rj + ! hessian = hessian + reshape(matmul(reshape(dqdr,(/3*n,m/)),& + ! transpose(reshape(dAmat,(/3*n,m/)))),(/3,n,3,n/)) + !call dgemm('n','t',3*n,m,3*n,+1.0_wp,dqdr,3*n,dAmat,3*n,1.0_wp,hessian,3*n) + !call dgemm('n','t',3*n,m,3*n,+1.0_wp,dAmat,3*n,dqdr,3*n,1.0_wp,hessian,3*n) + + end subroutine mh_eeq + +!========================================================================================! +!########################################################################################! +!========================================================================================! +end module modelhessian_core diff --git a/src/calculator/orca_sc.f90 b/src/calculator/orca_sc.f90 index 90ffd9e5..09e206d4 100644 --- a/src/calculator/orca_sc.f90 +++ b/src/calculator/orca_sc.f90 @@ -155,8 +155,9 @@ subroutine ORCA_setup(mol,calc) else fname = calc%calcfile end if - if(calc%uhf < 1) calc%uhf = 1 !> ORCA uses multiplicity, not n_alpha - n_beta! - call calc%ORCA%write(fname,mol,calc%chrg,calc%uhf) + !> ORCA uses the spin multiplicity (2S+1), not uhf = Nα-Nβ + call calc%sync_multiplicity() + call calc%ORCA%write(fname,mol,calc%chrg,calc%multiplicity) deallocate (fname) !>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<. +!================================================================================! + +!> a small module for getting a penalty contribution, e.g. from the RMSD potential used in metadynamics + +module penalty_module + use crest_parameters + use strucrd + use irmsd_module + implicit none + private + + public :: penalty_params + type :: penalty_params + type(coord),pointer :: biaslist(:) + + real(wp) :: alpha = 1.0_wp + real(wp) :: kpush = 0.002_wp + real(wp),allocatable :: ramp(:) + real(wp),allocatable :: gradtmp(:,:) + type(rmsd_core_cache) :: ccache + + character(len=:),allocatable :: biasfile + type(coord),allocatable :: biastmp(:) + end type penalty_params + + public :: rmsd_penalty_engrad + +!========================================================================================! +!========================================================================================! +contains !> MODULE PROCEDURES START HERE +!========================================================================================! +!========================================================================================! + + subroutine rmsd_penalty_engrad(mol,ppars,energy,grad,iostatus) + type(coord),intent(in) :: mol + type(penalty_params),intent(inout) :: ppars + real(wp),intent(out) :: energy + real(wp),intent(out) :: grad(:,:) + integer,intent(out) :: iostatus + integer :: nall,io,ii + real(wp) :: etmp,rmsdval,dEdr,knat + real(wp),parameter :: thr = sqrt(epsilon(thr)) + + iostatus = 0 + energy = 0.0_wp + grad(:,:) = 0.0_wp + rmsdval = 0.0_wp + nall = size(ppars%biaslist,1) + knat = ppars%kpush*mol%nat + + do ii = 1,nall + + rmsdval = rmsd(mol,ppars%biaslist(ii),gradient=ppars%gradtmp,ccache=ppars%ccache) + + !> energy contribution + call penalty_potential_gauss(knat,ppars%alpha,rmsdval,etmp,dEdr) + energy = energy+etmp + !> fallback: exactly matching structures will produce NaN gradients! + if (rmsdval < thr) cycle + !> gradient contribution + grad(:,:) = grad(:,:)+dEdr*ppars%gradtmp(:,:) + end do + + end subroutine rmsd_penalty_engrad + +!========================================================================================! + + subroutine penalty_potential_gauss(k,a,r,etmp,dEdr) + real(wp),intent(in) :: k,a,r + real(wp),intent(out) :: etmp,dEdr + etmp = k*exp(-a*r**2) + dEdr = -2.0_wp*a*etmp*r + end subroutine penalty_potential_gauss + +!========================================================================================! +!========================================================================================! +end module penalty_module diff --git a/src/calculator/tblite_api.F90 b/src/calculator/tblite_api.F90 index 34e72df4..6eddc612 100644 --- a/src/calculator/tblite_api.F90 +++ b/src/calculator/tblite_api.F90 @@ -23,21 +23,26 @@ !====================================================! module tblite_api - use iso_fortran_env,only:wp => real64,stdout => output_unit + use crest_parameters use strucrd #ifdef WITH_TBLITE use mctc_env,only:error_type use mctc_io,only:structure_type,new use tblite_context_type,only:tblite_ctx => context_type use tblite_wavefunction_type,only:wavefunction_type,new_wavefunction - use tblite_wavefunction,only:sad_guess,eeq_guess + use tblite_wavefunction,only:sad_guess,eeq_guess,shell_partition use tblite_xtb,xtb_calculator => xtb_calculator use tblite_xtb_calculator,only:new_xtb_calculator +#ifdef WITH_GXTB + use tblite_xtb,only:new_gxtb_calculator +#endif use tblite_param,only:param_record use tblite_results,only:tblite_resultstype => results_type use tblite_wavefunction_mulliken,only:get_molecular_dipole_moment use tblite_ceh_singlepoint,only:ceh_singlepoint use tblite_ceh_ceh,only:new_ceh_calculator + use tblite_spin,only:spin_polarization,new_spin_polarization + use tblite_container,only:container_type #endif use wiberg_mayer implicit none @@ -68,10 +73,12 @@ module tblite_api integer :: lvl = 0 real(wp) :: accuracy = 1.0_wp character(len=:),allocatable :: paramfile - type(wavefunction_type) :: wfn + type(wavefunction_type) :: wfn + type(wavefunction_type),allocatable :: wfn_aux type(xtb_calculator) :: calc type(tblite_ctx) :: ctx type(tblite_resultstype) :: res + logical :: spin_polarized = .false. end type tblite_data public :: tblite_data @@ -85,9 +92,16 @@ module tblite_api integer :: eeq = 4 integer :: ceh = 5 integer :: param = 6 + integer :: gxtb = 7 end type enum_tblite_method type(enum_tblite_method),parameter,public :: xtblvl = enum_tblite_method() +#ifdef WITH_GXTB + logical,parameter,public :: have_gxtb = .true. +#else + logical,parameter,public :: have_gxtb = .false. +#endif + !> Conversion factor from Kelvin to Hartree real(wp),parameter :: ktoau = 3.166808578545117e-06_wp @@ -96,8 +110,10 @@ module tblite_api public :: tblite_setup,tblite_singlepoint,tblite_addsettings public :: tblite_getwbos public :: tblite_add_solv + public :: tblite_add_efield public :: tblite_getcharges public :: tblite_getdipole + public :: tblite_quick_ceh_q !========================================================================================! !========================================================================================! @@ -105,11 +121,16 @@ module tblite_api !========================================================================================! !========================================================================================! - subroutine tblite_setup(mol,chrg,uhf,lvl,etemp,tblite) + subroutine tblite_setup(mol,chrg,uhf,lvl,etemp,tblite,ceh_guess) !***************************************************************** !* subroutine tblite_setup initializes the tblite object which is !* passed between the CREST calculators and this module !***************************************************************** +#ifdef WITH_TBLITE +#ifdef WITH_GXTB + use multicharge,only:get_charges +#endif +#endif implicit none type(coord),intent(in) :: mol integer,intent(in) :: chrg @@ -117,6 +138,7 @@ subroutine tblite_setup(mol,chrg,uhf,lvl,etemp,tblite) type(tblite_data),intent(inout) :: tblite integer,intent(in) :: lvl real(wp),intent(in) :: etemp + logical,intent(in),optional :: ceh_guess #ifdef WITH_TBLITE type(structure_type) :: mctcmol type(error_type),allocatable :: error @@ -125,7 +147,7 @@ subroutine tblite_setup(mol,chrg,uhf,lvl,etemp,tblite) real(wp) :: etemp_au,energy real(wp),allocatable :: grad(:,:) logical :: pr - integer :: io + integer :: io,nspin pr = (tblite%ctx%verbosity > 0) @@ -136,22 +158,22 @@ subroutine tblite_setup(mol,chrg,uhf,lvl,etemp,tblite) tblite%lvl = lvl select case (tblite%lvl) case (xtblvl%gfn1) - if (pr) call tblite%ctx%message("tblite> setting up GFN1-xTB calculation") + if (pr) call tblite%ctx%message("tblite> Setting up GFN1-xTB calculation") call new_gfn1_calculator(tblite%calc,mctcmol,error) case (xtblvl%gfn2) - if (pr) call tblite%ctx%message("tblite> setting up GFN2-xTB calculation") + if (pr) call tblite%ctx%message("tblite> Setting up GFN2-xTB calculation") call new_gfn2_calculator(tblite%calc,mctcmol,error) case (xtblvl%ipea1) - if (pr) call tblite%ctx%message("tblite> setting up IPEA1-xTB calculation") + if (pr) call tblite%ctx%message("tblite> Setting up IPEA1-xTB calculation") call new_ipea1_calculator(tblite%calc,mctcmol,error) case (xtblvl%ceh) - if (pr) call tblite%ctx%message("tblite> setting up CEH calculation") + if (pr) call tblite%ctx%message("tblite> Setting up CEH calculation") call new_ceh_calculator(tblite%calc,mctcmol,error) case (xtblvl%eeq) - if (pr) call tblite%ctx%message("tblite> setting up D4 EEQ charges calculation") + if (pr) call tblite%ctx%message("tblite> Setting up D4 EEQ charges calculation") call new_ceh_calculator(tblite%calc,mctcmol,error) !> doesn't matter but needs initialization case (xtblvl%param) - if (pr) call tblite%ctx%message("tblite> setting up xtb calculator from parameter file") + if (pr) call tblite%ctx%message("tblite> Setting up xtb calculator from parameter file") if (allocated(tblite%paramfile)) then call tblite_read_param_record(tblite%paramfile,param,io) call new_xtb_calculator(tblite%calc,mctcmol,param,error) @@ -163,15 +185,59 @@ subroutine tblite_setup(mol,chrg,uhf,lvl,etemp,tblite) if (pr) call tblite%ctx%message("tblite> parameter file does not exist, defaulting to GFN2-xTB") call new_gfn2_calculator(tblite%calc,mctcmol,error) end if +#ifdef WITH_GXTB + case (xtblvl%gxtb) + if (pr) call tblite%ctx%message("tblite> Setting up g-xTB calculation") + call new_gxtb_calculator(tblite%calc,mctcmol,error) +#else + case (xtblvl%gxtb) + write (stdout,'(a)') 'Error: g-xTB via tblite not available (compiled without WITH_GXTB).' + write (stdout,'(a)') 'This code path should not be reached — use the xtb binary fallback.' + error stop +#endif case default call tblite%ctx%message("Error: Unknown method in tblite!") error stop end select + if (pr) call tblite%ctx%message('') !>-- setup wavefunction object etemp_au = etemp*ktoau + nspin = merge(2,1,tblite%spin_polarized) call new_wavefunction(tblite%wfn,mol%nat,tblite%calc%bas%nsh, & - & tblite%calc%bas%nao,1,etemp_au) + & tblite%calc%bas%nao,nspin,etemp_au) +#ifdef WITH_GXTB + if (tblite%lvl == xtblvl%gxtb) then + call sad_guess(mctcmol,tblite%calc,tblite%wfn) + end if +#endif + if (ceh_guess) then + call tblite_internal_ceh_guess(mctcmol,tblite) + end if + +!>--- spin-polarization setup (spGFN2-xTB etc.) + if (tblite%spin_polarized) then + block + class(container_type),allocatable :: cont + type(spin_polarization),allocatable :: spin + real(wp),allocatable :: wll(:,:,:) + allocate (spin) + call get_spin_constants(wll,mctcmol,tblite%calc%bas) + call new_spin_polarization(spin,mctcmol,wll,tblite%calc%bas%nsh_id) + call move_alloc(spin,cont) + call tblite%calc%push_back(cont) + end block + end if + +!>--- for methods with an auxiliary charge model (e.g., gxTB), pre-allocate wfn_aux. +!>--- Charges are updated at each singlepoint call (geometry-dependent). +#ifdef WITH_GXTB + if (allocated(tblite%calc%charge_model)) then + if (allocated(tblite%wfn_aux)) deallocate (tblite%wfn_aux) + allocate (tblite%wfn_aux) + call new_wavefunction(tblite%wfn_aux,mctcmol%nat,tblite%calc%bas%nsh,0,1,0.0_wp,.true.) + end if +#endif #else /* WITH_TBLITE */ write (stdout,*) 'Error: Compiled without tblite support!' @@ -190,8 +256,9 @@ subroutine tblite_add_solv(mol,chrg,uhf,tblite,smodel,solvent) use tblite_container,only:container_type use tblite_solvation,only:new_solvation,tblite_solvation_type => solvation_type, & & solvent_data,get_solvent_data,solvation_input, & - & cpcm_input,alpb_input,alpb_solvation, & + & ddx_input,ddx_solvation_model,alpb_input,alpb_solvation, & & cds_input,new_solvation_cds,shift_input,new_solvation_shift + use tblite_features,only:get_tblite_feature #endif implicit none type(coord),intent(in) :: mol @@ -226,7 +293,7 @@ subroutine tblite_add_solv(mol,chrg,uhf,tblite,smodel,solvent) end if select case (tblite%lvl) case (xtblvl%gfn1) - method ='gfn1' + method = 'gfn1' case (xtblvl%gfn2) method = 'gfn2' end select @@ -243,61 +310,71 @@ subroutine tblite_add_solv(mol,chrg,uhf,tblite,smodel,solvent) end if solv_data = get_solvent_data(solvdum) if (solv_data%eps <= 0.0_wp) then - if (pr) call tblite%ctx%message("tblite> Unknown solvent!") - return + write (stdout,'(a)') 'Error: unknown solvent "'//solvdum//'" for tblite implicit solvation!' + error stop end if allocate (solv_inp) select case (trim(smodel)) case ('gbsa') if (pr) call tblite%ctx%message("tblite> using GBSA/"//solvdum) alpb_tmp%dielectric_const = solv_data%eps - alpb_tmp%alpb=.false. + alpb_tmp%alpb = .false. !alpb_tmp%method=method - alpb_tmp%solvent=solv_data%solvent + alpb_tmp%solvent = solv_data%solvent !alpb_tmp%xtb=.true. - allocate (solv_inp%alpb, source=alpb_tmp) - cds_tmp%alpb=.false. - cds_tmp%solvent=solv_data%solvent - !cds_tmp%method=method - allocate (solv_inp%cds, source=cds_tmp) - shift_tmp%alpb=.false. - shift_tmp%solvent=solv_data%solvent + allocate (solv_inp%alpb,source=alpb_tmp) + cds_tmp%alpb = .false. + cds_tmp%solvent = solv_data%solvent + !cds_tmp%method=method + allocate (solv_inp%cds,source=cds_tmp) + shift_tmp%alpb = .false. + shift_tmp%solvent = solv_data%solvent !shift_tmp%method=method - allocate (solv_inp%shift, source=shift_tmp) - case ('cpcm') - if (pr) call tblite%ctx%message("tblite> using CPCM/"//solvdum) - allocate (solv_inp%cpcm) - solv_inp%cpcm = cpcm_input(solv_data%eps) + allocate (solv_inp%shift,source=shift_tmp) + case ('cpcm','cosmo','pcm') + !> Continuum solvation models are provided via the ddX library since + !> tblite 0.6.x and require tblite compiled with ddX support (ddx=true) + if (.not.get_tblite_feature('ddx')) then + write (stdout,'(a)') 'Error: "'//trim(smodel)//'" solvation requires '// & + & 'tblite compiled with ddX support!' + error stop + end if + if (pr) call tblite%ctx%message("tblite> using "//trim(smodel)//"/"//solvdum) + allocate (solv_inp%ddx) + select case (trim(smodel)) + case ('cosmo') + solv_inp%ddx = ddx_input(ddx_solvation_model%cosmo,solv_data%eps) + case ('pcm') + solv_inp%ddx = ddx_input(ddx_solvation_model%pcm,solv_data%eps) + case default !> cpcm + solv_inp%ddx = ddx_input(ddx_solvation_model%cpcm,solv_data%eps) + end select case ('alpb') if (pr) call tblite%ctx%message("tblite> using ALPB/"//solvdum) alpb_tmp%dielectric_const = solv_data%eps - alpb_tmp%alpb=.true. + alpb_tmp%alpb = .true. !alpb_tmp%method=method - alpb_tmp%solvent=solv_data%solvent + alpb_tmp%solvent = solv_data%solvent !alpb_tmp%xtb=.true. - allocate (solv_inp%alpb, source=alpb_tmp) - cds_tmp%alpb=.true. - cds_tmp%solvent=solv_data%solvent - !cds_tmp%method=method - allocate (solv_inp%cds, source=cds_tmp) - shift_tmp%alpb=.true. - shift_tmp%solvent=solv_data%solvent + allocate (solv_inp%alpb,source=alpb_tmp) + cds_tmp%alpb = .true. + cds_tmp%solvent = solv_data%solvent + !cds_tmp%method=method + allocate (solv_inp%cds,source=cds_tmp) + shift_tmp%alpb = .true. + shift_tmp%solvent = solv_data%solvent !shift_tmp%method=method - allocate (solv_inp%shift, source=shift_tmp) + allocate (solv_inp%shift,source=shift_tmp) case default - if (pr) call tblite%ctx%message("tblite> Unknown tblite implicit solvation model!") - return + write (stdout,'(a)') 'Error: unknown tblite implicit solvation model "'//trim(smodel)//'"!' + error stop end select - str = 'tblite> WARNING: implicit solvation energies are not entirely '// & - &'consistent with the xtb implementation.' - if (pr) call tblite%ctx%message(str) - !>--- add electrostatic (Born part) to calculator call new_solvation(solv,mctcmol,solv_inp,error,method) if (allocated(error)) then - if (pr) call tblite%ctx%message("tblite> failed to set up tblite implicit solvation!") - return + write (stdout,'(a)') 'Error: failed to set up tblite implicit solvation: '//error%message + error stop end if call move_alloc(solv,cont) call tblite%calc%push_back(cont) @@ -338,6 +415,11 @@ subroutine tblite_singlepoint(mol,chrg,uhf,tblite,energy,gradient,iostatus) !* The actual calculator call. !* The tblite object must be set up at this point !************************************************** +#ifdef WITH_TBLITE +#ifdef WITH_GXTB + use multicharge,only:get_charges +#endif +#endif implicit none type(coord),intent(in) :: mol integer,intent(in) :: chrg @@ -366,17 +448,39 @@ subroutine tblite_singlepoint(mol,chrg,uhf,tblite,energy,gradient,iostatus) !>--- make an mctcmol object from mol call tblite_mol2mol(mol,chrg,uhf,mctcmol) +!>--- update geometry-dependent EEQ-BC charges in wfn_aux (allocated once in tblite_setup) +#ifdef WITH_GXTB + if (allocated(tblite%wfn_aux)) then + call get_charges(tblite%calc%charge_model,mctcmol,error,tblite%wfn_aux%qat(:,1), & + & dqdr=tblite%wfn_aux%dqatdr(:,:,:,1),dqdL=tblite%wfn_aux%dqatdL(:,:,:,1)) + if (allocated(error)) then + if (pr) call tblite%ctx%message("tblite> auxiliary charge model failed: "//error%message) + iostatus = 1 + return + end if + end if +#endif + !>--- call the singlepoint routine select case (tblite%lvl) case default +#ifdef WITH_GXTB + if (allocated(tblite%wfn_aux)) then + call xtb_singlepoint(tblite%ctx,mctcmol,tblite%calc,tblite%wfn,tblite%accuracy, & + & energy,gradient,sigma,verbosity,results=tblite%res,wfn_aux=tblite%wfn_aux) + else + call xtb_singlepoint(tblite%ctx,mctcmol,tblite%calc,tblite%wfn,tblite%accuracy, & + & energy,gradient,sigma,verbosity,results=tblite%res) + end if +#else call xtb_singlepoint(tblite%ctx,mctcmol,tblite%calc,tblite%wfn,tblite%accuracy, & - & energy,gradient, & - & sigma,verbosity,results=tblite%res) + & energy,gradient,sigma,verbosity,results=tblite%res) +#endif case (xtblvl%ceh) call ceh_singlepoint(tblite%ctx,tblite%calc,mctcmol,tblite%wfn, & & tblite%accuracy,verbosity) case (xtblvl%eeq) - call eeq_guess(mctcmol,tblite%calc,tblite%wfn) + call eeq_guess(mctcmol,tblite%calc,tblite%wfn,error) end select if (tblite%ctx%failed()) then @@ -444,6 +548,37 @@ subroutine tblite_addsettings(tblite,maxscc,rdwbo,saveint,accuracy) #endif end subroutine tblite_addsettings + subroutine tblite_add_efield(tblite,efield) +!********************************************************** +!* tblite_add_efield +!* if efield is allocated, add it to the tblite calculator +!********************************************************** +#ifdef WITH_TBLITE + use tblite_container,only:container_type + use tblite_external_field,only:electric_field +#endif + implicit none + type(tblite_data),intent(inout) :: tblite + real(wp),intent(in),allocatable :: efield(:) + class(container_type),allocatable :: cont + logical :: pr + character(len=90) :: str +#ifdef WITH_TBLITE + pr = (tblite%ctx%verbosity > 0) + if (allocated(efield)) then + if (pr) then + write (str,'(a,3(es10.3),a)') "tblite> Calculation includes the following electric field:" + call tblite%ctx%message(trim(str)) + write (str,'(8x, a,3(es15.5,1x),a)') "[",efield,"] V/Å" + call tblite%ctx%message(trim(str)) + call tblite%ctx%message('') + end if + cont = electric_field(efield*vatoau) + call tblite%calc%push_back(cont) + end if +#endif + end subroutine tblite_add_efield + !========================================================================================! subroutine tblite_getwbos(tblite,nat,wbo) @@ -565,6 +700,161 @@ subroutine tblite_read_param_record(paramfile,param,io) end subroutine tblite_read_param_record #endif +!========================================================================================! + +#ifdef WITH_TBLITE + subroutine tblite_internal_ceh_guess(mctcmol,tblite) + !********************************************************* + !* Init the tblite calculator with a set of CEH charges + !********************************************************* + implicit none + type(tblite_data),intent(inout) :: tblite + type(structure_type),intent(in) :: mctcmol + !> LOCAL + type(wavefunction_type) :: wfn_ceh + type(xtb_calculator) :: calc_ceh + type(error_type),allocatable :: error + integer :: verbosity + logical :: pr + real(wp),parameter :: etemp_guess_au = 4000.0_wp*ktoau + + !> if we only do a eeq or ceh calc, we don't need this, so return + select case (tblite%lvl) + case default + continue + case (xtblvl%ceh,xtblvl%eeq) + return + end select + + pr = (tblite%ctx%verbosity > 0) + if (tblite%ctx%verbosity > 1) then + verbosity = tblite%ctx%verbosity + else + verbosity = 0 + end if + + !> ceh guess calculator and wavefunction + call new_ceh_calculator(calc_ceh,mctcmol,error) + if (allocated(error)) return + call new_wavefunction(wfn_ceh,mctcmol%nat,calc_ceh%bas%nsh, & + & calc_ceh%bas%nao,1,etemp_guess_au) + + !> TODO ceh guess efield + + call ceh_singlepoint(tblite%ctx,calc_ceh,mctcmol,wfn_ceh, & + & tblite%accuracy,verbosity) + + if (tblite%ctx%failed()) then + if (pr) then + call tblite%ctx%get_error(error) + call tblite%ctx%message("CEH singlepoint calculation failed") + call tblite%ctx%message("-> "//error%message) + end if + return + end if + + !> pass on to actual calculator + tblite%wfn%qat(:,1) = wfn_ceh%qat(:,1) + call shell_partition(mctcmol,tblite%calc,tblite%wfn) + + end subroutine tblite_internal_ceh_guess +#endif + +!========================================================================================! + + subroutine tblite_quick_ceh_q(mol,q,chrg,uhf,pr,prch) + !********************************************************* + !* Calculate CEH charges + !********************************************************* + implicit none + type(coord),intent(in) :: mol + integer,intent(in) :: chrg + real(wp),intent(out),allocatable :: q(:) + integer,intent(in),optional :: uhf + logical,intent(in),optional :: pr + integer,intent(in),optional :: prch +#ifdef WITH_TBLITE + type(structure_type) :: mctcmol + !> LOCAL + type(wavefunction_type) :: wfn_ceh + type(xtb_calculator) :: calc_ceh + type(tblite_ctx) :: ctx + type(error_type),allocatable :: error +#endif + integer :: verbosity,uhf_loc + logical :: pr_loc + real(wp),parameter :: etemp_guess_au = 4000.0_wp*ktoau + real(wp),parameter :: accuracy = 1.0_wp + + pr_loc = .false. + if (present(pr)) pr_loc = pr + verbosity = 0 + if (pr_loc) verbosity = 2 + + allocate (q(mol%nat),source=0.0_wp) + +#ifdef WITH_TBLITE + uhf_loc = 0 + if (present(uhf)) uhf_loc = uhf + if (present(prch)) ctx%unit = prch + + !>--- make an mctcmol object from mol + call tblite_mol2mol(mol,chrg,uhf_loc,mctcmol) + + !> ceh guess calculator and wavefunction + call new_ceh_calculator(calc_ceh,mctcmol,error) + if (allocated(error)) return + call new_wavefunction(wfn_ceh,mctcmol%nat,calc_ceh%bas%nsh, & + & calc_ceh%bas%nao,1,etemp_guess_au) + + !> TODO ceh guess efield + + call ceh_singlepoint(ctx,calc_ceh,mctcmol,wfn_ceh, & + & accuracy,verbosity) + + if (ctx%failed()) then + if (pr_loc) then + call ctx%get_error(error) + call ctx%message("CEH singlepoint calculation failed") + call ctx%message("-> "//error%message) + end if + return + end if + + !> pass on the charges + q(:) = wfn_ceh%qat(:,1) +#else /* WITH_TBLITE */ + write (stdout,*) 'Error: Compiled without tblite support!' + write (stdout,*) 'Use -DWITH_TBLITE=true in the setup to enable this function' + error stop +#endif + end subroutine tblite_quick_ceh_q + +! ══════════════════════════════════════════════════════════════════════════════ +#ifdef WITH_TBLITE + subroutine get_spin_constants(wll,mol,bas) + use tblite_basis_type,only:basis_type + use tblite_data_spin,only:get_spin_constant + real(wp),allocatable,intent(out) :: wll(:,:,:) + type(structure_type),intent(in) :: mol + type(basis_type),intent(in) :: bas + + integer :: izp,ish,jsh,il,jl + + allocate (wll(bas%nsh,bas%nsh,mol%nid),source=0.0_wp) + + do izp = 1,mol%nid + do ish = 1,bas%nsh_id(izp) + il = bas%cgto(ish,izp)%ang + do jsh = 1,bas%nsh_id(izp) + jl = bas%cgto(jsh,izp)%ang + wll(jsh,ish,izp) = get_spin_constant(jl,il,mol%num(izp)) + end do + end do + end do + end subroutine get_spin_constants +#endif + !========================================================================================! !========================================================================================! end module tblite_api diff --git a/src/ccegen.f90 b/src/ccegen.f90 deleted file mode 100644 index bd991425..00000000 --- a/src/ccegen.f90 +++ /dev/null @@ -1,1373 +0,0 @@ -!================================================================================! -! This file is part of crest. -! -! Copyright (C) 2020 Philipp Pracht, Stefan Grimme -! -! crest is free software: you can redistribute it and/or modify it under -! the terms of the GNU Lesser General Public License as published by -! the Free Software Foundation, either version 3 of the License, or -! (at your option) any later version. -! -! crest is distributed in the hope that it will be useful, -! but WITHOUT ANY WARRANTY; without even the implied warranty of -! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -! GNU Lesser General Public License for more details. -! -! You should have received a copy of the GNU Lesser General Public License -! along with crest. If not, see . -!================================================================================! - -!=========================================================================================! -!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC! -!=========================================================================================! -!===============================================================================! -! -! This is the CCEGEN routine, used for clustering a conformational ensemble -! and determine representative structures for the molecule. -! -! A principal component analysis (PCA) is performed, and the generated data -! is clustered. -! -! -! On Input: pr - printout boolean -! fname - name of the ensemble file -! -!==============================================================================! -subroutine CCEGEN(env,pr,fname) - use crest_parameters,idp => dp - use crest_data - use zdata - use strucrd - use utilities - implicit none - type(systemdata) :: env ! MAIN STORAGE OS SYSTEM DATA - type(timer) :: ctimer - logical,intent(in) :: pr - character(len=*),intent(in) :: fname - type(zmolecule) :: zmol - type(zequal) :: groups - type(zequal) :: subgroups - integer,allocatable :: inc(:) - logical :: heavyonly - integer :: i,j,k,l,ich,c - real(wp) :: dum,dum2 - type(ensemble) :: zens - - character(len=:),allocatable :: measuretype - - !>--- SVD params - integer :: ntaken - integer :: nallnew - real(wp),allocatable :: xyznew(:,:,:) - !integer,allocatable :: atnew(:) - real(wp),allocatable :: measure(:,:) !this is what is passed to the SVD - integer :: mn,mm !--> measure(mn,mm) - real(wp),allocatable :: pc(:) !the principal components - real(wp),allocatable :: pcvec(:,:) !the principal component eigenvectors - real(wp),allocatable :: pcdum(:,:) - integer :: nbnd,ndied - integer,allocatable :: diedat(:,:) !atoms spanning relevant dihedral angles - real(wp),allocatable :: diedr(:) - real(wp) :: pcsum - real(wp) :: pcthr - real(wp) :: pcmin - integer :: pccap - integer :: npc - real(wp),allocatable :: geo(:,:) - integer,allocatable :: na(:),nb(:),nc(:) - - !>--- CLUSTERING params - character(len=:),allocatable :: clusteralgo - integer :: nclust !number of clusters - integer :: nclustiter !iterator for nclust - integer :: nclustmin,nclustmax - integer,allocatable :: member(:) !track cluster correspondence - real(ap),allocatable :: p(:),q(:) - real(sp),allocatable :: dist(:) - real(ap),allocatable :: centroid(:,:) - integer(idp) :: ndist,klong - real(ap) :: eucdist !this is a function - real(wp) :: DBI,pSF,SSRSST,SSRSSTthr - real(wp) :: csthr - integer :: ncb,ancb - real(wp),allocatable :: eclust(:) - integer,allocatable :: clustbest(:),ind(:) - real(wp),allocatable :: statistics(:,:) - logical,allocatable :: extrema(:,:) - logical :: autolimit - real(wp) :: fraclimit - - !>--- printout and params - real(wp) :: emin,erel - real(wp),parameter :: kcal = 627.5095d0 - !real(wp),parameter :: pi = 3.14159265358979D0 - real(wp),parameter :: rad = 180.0d0/pi - - call ctimer%init(20) - if (pr) then - call largehead('Principal Component Analysis (PCA) and Clustering') - write (stdout,'(1x,a,a)') 'Input file: ',trim(fname) - end if - -!=========================================================! -! set threads -!=========================================================! - call cregen_setthreads(stdout,env,pr) - -!=========================================================! -! Prepare a coordinate ensemble for the clustering -!=========================================================! - !>--- 0. Set defaults, read ensemble - call zens%open(fname) !read in the ensemble - if (zens%nall < 1) then - error stop "Ensemble is empty! must stop" - else if (zens%nall == 1) then - if (pr) then - write (stdout,*) 'Only one structure in ensemble!' - write (stdout,*) 'Write structure to ',clusterfile,' and skip PCA parts' - end if - open (newunit=ich,file=clusterfile) - dum = zens%er(1) - call wrxyz(ich,zens%nat,zens%at,zens%xyz(:,:,1),dum) - close (ich) - call zens%deallocate() - return - end if - - heavyonly = .true. - !measuretype = 'dihedral' - measuretype = env%pcmeasure - clusteralgo = 'kmeans' - !pcthr = 0.85d0 !PCs must add up to this amount of "representability" - pcthr = env%pcthr - pcmin = env%pcmin - !csthr = 0.80d0 !threshold for SSR/SST to select a suitable cluster count - csthr = env%csthr - !pccap = 100 !a cap for the number of principal components used in the clustering - pccap = env%pccap - autolimit = .true. !if the ensemble is very large, take only a fraction to speed up things - !(only for predefined clustersizes with "-cluster N" ) - fraclimit = 0.25d0 !if autolimit=true, 1/4 of the ensemble is taken - - !>--- 1. topology for reference strucuture - if (env%wbotopo) then - env%wbofile = 'wbo' - else - env%wbofile = 'none given' - end if - zens%xyz = zens%xyz/bohr !ANG to Bohr for topo - call simpletopo(zens%nat,zens%at,zens%xyz,zmol,pr,.false.,env%wbofile) - zens%xyz = zens%xyz*bohr !Bohr to ANG - allocate (inc(zmol%nat),source=0) - -!===========================================================! - if (measuretype .ne. 'dihedral') then -!===========================================================! - !>--- 2. read nuclear equivalencies - if (pr) then - write (stdout,*) - call smallhead('READING NUCLEAR EQUIVALENCIES') - end if - call readequals('anmr_nucinfo',zmol,groups) - if (pr) then - call groups%prsum(6) !--- print summary to screen - write (stdout,'(1x,a)') 'Unlisted nuclei (groups) are unique.' - end if - - !>--- 3. distribute groups into subgroups basedon topology - if (pr) then - write (stdout,*) - call smallhead('ANALYZING EQUIVALENCIES') - end if - call distsubgr(zmol,groups,subgroups,inc,pr) - - !>--- 4. Equivalent atoms must be excluded in clustering to reduce noise - if (pr) then - write (stdout,*) - call smallhead('DETERMINE ATOMS TO INCLUDE IN PCA') - end if - call excludeFromRMSD(zmol,inc) - if (sum(inc) == 0) then - if (pr) then - write (stdout,*) 'WARNING: No atoms included in PCA' - write (stdout,*) 'Including more atoms ...' - end if - inc = 1 - !-- for this exclude the equivalent atoms from anmr_nucinfo directly - do i = 1,groups%ng - if (groups%grp(i)%nm > 1) then - write (stdout,*) groups%grp(i)%mem - do j = 1,groups%grp(i)%nm - k = groups%grp(i)%mem(j) - inc(k) = 0 - end do - end if - end do - end if - !>-- exclude user set atoms - if (env%pcaexclude) then - call excludeSelected(zmol,inc,env%atlist) - end if - !>-- exclude H atoms - if (heavyonly) then - call excludeLight(zmol,inc) - end if - if (pr) then - do i = 1,zmol%nat - if (inc(i) == 1) then - write (stdout,'(1x,a,a,i0,a,5x,a)') zmol%zat(i)%el,'(',i,')','taken' - end if - end do - end if - ntaken = sum(inc) - !>--- if we have too few, include the heav atoms at least - if (ntaken <= 3) then - do i = 1,zmol%nat - if (zmol%at(i) /= 1) then - inc(i) = 1 - if (pr) write (stdout,'(1x,a,a,i0,a,5x,a)') zmol%zat(i)%el,'(',i,')','taken' - end if - end do - end if - ntaken = sum(inc) - - call zmol%deallocate - - !>-- for very large ensemble files limit the clustering - if (autolimit) then - if ((env%nclust /= 0).and.(env%nclust*100 < zens%nall)) then - dum = float(zens%nall)*fraclimit - dum2 = float(env%nclust) - nallnew = nint(max(dum,dum2)) - else - nallnew = zens%nall - end if - else - nallnew = zens%nall - end if - - !>--- 5. Transfer the relevant atoms to a new array - allocate (xyznew(3,ntaken,nallnew))!,atnew(ntaken)) - do i = 1,nallnew - k = 0 - do j = 1,zens%nat - if (inc(j) == 1) then - k = k+1 - xyznew(:,k,i) = zens%xyz(:,j,i) - !atnew(k) = zens%at(j) - end if - end do - end do - -!===================================================! - else !measuretype=='dihedral' -!===================================================! - - !-- for very large ensemble files limit the clustering - if (autolimit) then - if ((env%nclust /= 0).and.(env%nclust*100 < zens%nall)) then - dum = float(zens%nall)*fraclimit - dum2 = float(env%nclust) - nallnew = nint(max(dum,dum2)) - else - nallnew = zens%nall - end if - else - nallnew = zens%nall - end if - - inc = 1 - ntaken = sum(inc) - - call zmol%countbonds() - nbnd = zmol%nb - allocate (diedat(4,zmol%nb),source=0) - call getdiederatoms(zmol,zmol%nat,inc,nbnd,diedat,ndied) - ntaken = ndied -!==================================================! - end if -!==================================================! - -!===================================================================================================! -! do the SVD to get the principal components -!===================================================================================================! - if (ntaken > 3) then !> all of this only makes sense if we have something to compare - call ctimer%start(1,'PCA') - if (pr) then - write (stdout,*) - call smallhead('PRINCIPAL COMPONENT ANALYSIS') - end if - !mm = zens%nall - mm = nallnew - select case (measuretype) - !==========================================================================! - case ('cma','CMA','cmadist') - if (pr) then - write (stdout,'(1x,a)') 'Using CMA DISTANCES as descriptors:' - end if - !>-- all structures should have been shifted to the CMA by CREGEN - !> therefore assume the CMA is at (0,0,0). - !> somewhat robust measure, but provides less information. - mn = min(ntaken,mm) - allocate (measure(mn,mm),pc(mn),pcvec(mm,mn)) - do i = 1,mm - do j = 1,mn - measure(j,i) = xyznew(1,j,i)**2+ & - & xyznew(2,j,i)**2+ & - & xyznew(3,j,i)**2 - measure(j,i) = sqrt(measure(j,i)) - end do - end do - !==========================================================================! - case ('cartesian','coords') - if (pr) then - write (stdout,'(1x,a)') 'Using CARTESIAN COORDINATES as descriptors:' - end if - !>-- all Cartesian components of the selected atoms - !> REQUIRES PERFECT ALIGNMENT(!), hence not very robust - mn = min(ntaken*3,mm) - allocate (measure(mn,mm),pc(mn),pcvec(mm,mn)) - do i = 1,mm - l = 0 - do j = 1,ntaken - do k = 1,3 - l = l+1 - measure(j,l) = xyznew(k,j,i) - end do - end do - end do - !==========================================================================! - case default !case( 'zmat','zmatrix' ) - if (pr) then - write (stdout,'(1x,a)') 'Using ZMATRIX as descriptors:' - end if - !>-- dihedral angles - mn = ntaken-3 !>--- first three dihedral angles are zero - mn = min(mm,mn) !>--- no more descriptors than structures for SVD! - if (mn < 1) then !> we need at least 2 dihedral angles, and therefore 5 descriptors - if (pr) then - write (stdout,*) "Not enough descriptors for PCA!" - return - end if - end if - allocate (measure(mn,mm),pc(mn),pcvec(mm,mn)) - allocate (geo(3,ntaken),source=0.0d0) - allocate (na(ntaken),nb(ntaken),nc(ntaken)) - do i = 1,mm - na = 0; nb = 0; nc = 0 - geo = 0.0d0 - call xyzint(xyznew(1:3,1:ntaken,i),ntaken,na,nb,nc,rad,geo) - do j = 1,mn - k = j+3 - measure(j,i) = geo(3,k) - end do - end do - deallocate (nc,nb,na,geo) - !=========================================================================! - case ('dihedral') - mn = min(mm,ntaken) !>--- no more descriptors than structures for SVD! - allocate (measure(mn,mm),diedr(ndied)) - if (pr) then - write (stdout,'(1x,a)') 'Using DIHEDRAL ANGLES as descriptors:' - do i = 1,mn - write (stdout,'(1x,a,4i6)') 'Atoms: ',diedat(1:4,i) - end do - write (stdout,*) - end if - do i = 1,mm - call calc_dieders(zens%nat,zens%xyz(:,:,i),ndied,diedat,diedr) - do j = 1,mn - ! if(i<5 .and. pr)then - ! write(*,'(1x,4i6,1x,f8.2)') diedat(1:4,j),diedr(j) - ! endif - measure(j,i) = diedr(j) - end do - end do - if (allocated(diedat)) deallocate (diedat) - if (allocated(diedr)) deallocate (diedr) - allocate (pc(mn),pcvec(mm,mn)) - !=====================================================! - end select - !=====================================================! - if (pr) then - write (stdout,*) - write (stdout,'(1x,a,i0,a,i0,a)') 'Performing SVD for ', & -& mm,' structures and ',mn,' props' - end if - !>--- do the SVD ! MM must not be smaller than MN ! - call SVD_to_PC(measure,mm,mn,pc,pcvec,.false.) -!=========================================================================================! - call ctimer%stop(1) - else - write (stdout,*) 'There are not enough descriptors for a PCA!' - write (stdout,*) 'Taking all structures as representative and writing ',clusterfile - open (newunit=ich,file=clusterfile) - do i = 1,zens%nall - dum = zens%er(i) - call wrxyz(ich,zens%nat,zens%at,zens%xyz(:,:,i),dum) - end do - close (ich) - return - end if -!========================================================================================! - if (allocated(measure)) deallocate (measure) - if (allocated(xyznew)) deallocate (xyznew) - if (allocated(inc)) deallocate (inc) - - !>--- normalize PC eigenvalues - pcsum = sum(pc) - pc = pc/pcsum - !>--- get the contributing principal components - pcsum = 0.0d0 - npc = 0 - do i = 1,mn - if (pc(i) < pcmin) exit - pcsum = pcsum+pc(i) - npc = npc+1 - if (pcsum .ge. pcthr) exit - end do - !npc = max(npc,2) !>-- at least 2 principal components should be used - npc = min(npc,pccap) - pcsum = 0.0d0 - do i = 1,npc - pcsum = pcsum+pc(i) - end do - - if (pr) then - i = min(100,MM) - k = min(npc,6) - write (stdout,*) - call smallhead('EIGENVECTORS AND NORMALIZED EIGENVALUES OF SVD ANALYIS') - call PRMAT(6,pcvec,i,k,'Eigenvectors of principal components') - write (stdout,'(1x,a,i0,a)') 'NOTE: eigenvectors are only shown for the first ',i,' structures' - write (stdout,'(1x,a,i0,a)') ' and the first ',k,' contributing principal components.' - write (stdout,*) - - write (stdout,*) mn,'principal component eigenvalues (normalized)' - write (stdout,*) pc - !call PRMAT(6,pc,mn,1,'Principal components (eigenvalues)') - write (stdout,*) - write (stdout,'(1x,a,i0,a,f6.2,a)') 'The first ',npc,' components account for a total of ',100.d0*pcsum,'% of the' - write (stdout,'(1x,a)') 'ensembles unique structural features and are used for the clustering' - end if - - !>--- use some less memory and rearrange the eigenvectors (COLUMNS AND ROWS ARE SWAPPED) - !> untaken PCs are not considered further - allocate (pcdum(npc,mm)) - do i = 1,mm - pcdum(1:npc,i) = pcvec(i,1:npc) - end do - call move_alloc(pcdum,pcvec) !>THIS CHANGES THE SHAPE OF pcvec (COLUMNS AND ROWS ARE SWAPPED) - -!=========================================================! -! do the Clustering -!=========================================================! - - if (pr) then - write (stdout,*) - call smallhead('CLUSTERING ANALYSIS OF PRINCIPAL COMPONENTS') - end if - - allocate (member(mm),source=0) - - !>--- get Euclidean distances (packed matrix) between all structures - !> ndist = (mm*(mm+1))/2 ! overflows for large ensembles - ndist = mm - ndist = ndist*(mm+1) - ndist = ndist/2 - allocate (dist(ndist),source=0.0_sp) - allocate (p(npc),q(npc),source=0.0_ap) - - do i = 1,mm - p(1:npc) = pcvec(1:npc,i) -!$OMP PARALLEL PRIVATE ( j, klong, q, dum ) & -!$OMP SHARED ( i, dist, npc, p, pcvec ) -!$OMP DO - do j = 1,i - q(1:npc) = pcvec(1:npc,j) - dum = eucdist(npc,p,q) - klong = lina(i,j) - dist(klong) = real(dum,sp) - end do -!$OMP END DO -!$OMP END PARALLEL - end do - - !>-- NOTE - !>-- different clustering algorithms exist, but what is common - !>-- among them is, that no optimal number of clusters is known - !>-- at the beginning. The lower bound for the number of - !>-- clusters is the number of investigated PCs, the upper - !>-- bound is the number of structures - if (pr) then - select case (clusteralgo) - case ('means','kmeans') - write (stdout,'(1x,a)') 'Using a MEANS cluster algorithm.' - end select - write (stdout,'(1x,a)') 'For a good review of cluster algorithms see' - write (stdout,'(1x,a)') 'JCTC, 2007, 3, 2312 (doi.org/10.1021/ct700119m)' - write (stdout,*) - write (stdout,'(1x,a)') 'DBI = Davies-Bouldin index' - write (stdout,'(1x,a)') 'pSF = pseudo F-statistic' - write (stdout,'(1x,a)') 'SSR/SST = ratio of explained and unexplained variation' - write (stdout,*) - write (stdout,'(1x,a8,4x,a14,4x,a14,4x,a14)') 'Nclust','DBI','pSF','SSR/SST' - write (stdout,'(1x,a8,4x,a14,4x,a14,4x,a14)') '------','-------------','-------------','-------------' - end if - -!-----------------------------------------------------------------------! -! Cluster evaluation settings -!-----------------------------------------------------------------------! - if (env%maxcluster == 0) then - !nclustmax=100 !some random default value - call clustleveval(env,nclustmax,csthr,SSRSSTthr) ! defaults - nclustmax = min(mm,nclustmax) - !SSRSSTthr=0.90 !exit if this value is reached for SSR/SST - else - nclustmax = max(2,env%maxcluster) !no less than 2 clusters - nclustmax = min(mm,env%maxcluster) !there cannot be more clustes than structures. - end if - if (env%nclust == 0) then - nclustmin = 1 - else - !>-- predefined number of clusters - nclust = min(mm,env%nclust) - nclustmin = nclust - nclustmax = nclust - end if - - allocate (statistics(3,nclustmax),source=0.0d0) - CLUSTERSIZES: do nclustiter = nclustmin,nclustmax - - !>-- regular case: test continuous cluster sizes - nclust = nclustiter - !>-- special case: test cluster sizes incrementally (good for large ensembles) - if (env%clustlev >= 10) then - dum = float(mm)/float(nclustmax) - dum2 = dum*float(nclustiter) - nclust = nint(dum2) - end if - - allocate (centroid(npc,nclust),source=0.0_ap) - centroid = 0.0_ap - - select case (clusteralgo) - case ('means','kmeans') - call ctimer%start(2,'k-Means clustering') - call kmeans(nclust,npc,mm,centroid,pcvec,ndist,dist,member) - call ctimer%stop(2) - end select - - call ctimer%start(3,'statistics') - call cluststat(nclust,npc,mm,centroid,pcvec,member,DBI,pSF,SSRSST) - if (pr) then - write (stdout,'(1x,i8,4x,f14.6,4x,f14.6,4x,f14.6)') nclust,DBI,pSF,SSRSST - end if - call ctimer%stop(3) - deallocate (centroid) - - statistics(1,nclust) = DBI - statistics(2,nclust) = pSF - statistics(3,nclust) = SSRSST - - if (nclust == env%nclust) exit - if (SSRSST > SSRSSTthr) exit - end do CLUSTERSIZES - if (allocated(centroid)) deallocate (centroid) - - write (stdout,*) - if (env%nclust == 0) then - if (pr) then - write (stdout,'(1x,a,i0,a)') 'Ensemble checked up to a partitioning into ',nclust,' clusters.' - write (stdout,'(1x,a)') 'Local MINIMA of the DBI indicate adequate cluster counts.' - write (stdout,'(1x,a)') 'Local MAXIMA of the pSF indicate adequate cluster counts.' - write (stdout,'(1x,a)') 'Higher SSR/SST vaules indicate more distinct clusters.' - write (stdout,'(1x,a)') 'Analyzing statistical values ...' - end if - k = nclust - allocate (extrema(2,k)) - call ctimer%start(3,'statistics') - call statanal(k,nclustmax,statistics,extrema,pr) - if (pr) call statwarning(fname) - !>-- determine a suggested cluster size (smallest suggested cluster with good SSR/SST) - do i = 2,k - if ((extrema(1,i).or.extrema(2,i)).and.(statistics(3,i) > csthr)) then - nclust = i - exit - end if - end do - call ctimer%stop(3) - deallocate (extrema) - if (pr) then - write (stdout,*) - write (stdout,'(1x,a,f4.2,a,i0)') 'Suggested (SSR/SST >',csthr,') cluster count: ',nclust - end if - !>-- calculate the determined partition into clusters again for final file - allocate (centroid(npc,nclust),source=0.0_ap) - select case (clusteralgo) - case ('means','kmeans') - call ctimer%start(2,'k-Means clustering') - call kmeans(nclust,npc,mm,centroid,pcvec,ndist,dist,member) - call ctimer%stop(2) - end select - deallocate (centroid) - else - if (pr) then - write (stdout,'(1x,a,i0,a)') 'Ensemble partitioning into ',nclust,' clsuters.' - end if - end if - deallocate (statistics) - - deallocate (q,p,dist) - !>-- finally, assign a representative structure to each group (based on lowest energy) - !>-- and write the new ensemble file - call PCA_grpwrite(nclust,npc,mm,pcvec,member) - - !ncb=maxval(member,1) !--total number of cluster - ncb = nclust - ancb = ncb !>--actual number of clusters - - if (ancb .le. 1) return - - if (pr) then - write (stdout,*) - write (stdout,'(1x,a)') 'Representative structures' - write (stdout,'(1x,a6,1x,a6,3x,a6,1x,a16,1x,a16)') 'Nr.','conf.','clust.','Etot/Eh','Erel/ kcal/mol' - write (stdout,'(1x,a6,1x,a6,3x,a6,1x,a16,1x,a16)') '---','-----','------','-------','--------------' - end if - allocate (eclust(ncb),source=0.0d0) - allocate (clustbest(ncb),ind(ncb),source=0) - !>--- initialize eclust and clustbest - iiincb: do i = 1,ncb - do j = 1,mm - if (member(j) == i) then - eclust(i) = zens%er(j) - clustbest(i) = j - cycle iiincb - end if - end do - end do iiincb - !>--- then, look for the lowest energy - do i = 1,ncb - ind(i) = i - c = 0 - do j = 1,mm - if (member(j) == i) then - c = c+1 - if (zens%er(j) < eclust(i)) then - eclust(i) = zens%er(j) - clustbest(i) = j - end if - end if - end do - !>-- if there are clusters without structures - if (c == 0) then - clustbest(i) = -1 - ancb = ancb-1 - end if - end do - call qsort(eclust,1,ncb,ind) - emin = minval(eclust,1) - open (newunit=ich,file=clusterfile) - do i = 1,ncb - k = clustbest(ind(i)) - if (k > 0) then - dum = zens%er(k) - call wrxyz(ich,zens%nat,zens%at,zens%xyz(:,:,k),dum) - if (pr) then - erel = (dum-emin)*kcal - write (stdout,'(1x,i6,1x,i6,3x,i6,1x,f16.8,1x,f16.4)') i,k,member(k),dum,erel - end if - end if - end do - close (ich) - if (pr) then - write (stdout,'(/,1x,a)') '(The "clust." column refers to the cluster "ID")' - write (stdout,*) - write (stdout,'(1x,a,a,a,i0,a)') 'File ',clusterfile,' written with ',ancb,' representative structures.' - if (ancb < ncb) then - write (stdout,'(1x,a,i0,a)') '(',ncb-ancb,' clusters discarded due to cluster merge)' - end if - end if - call zens%deallocate() - - if (pr) then - write (stdout,*) - !write(*,'(1x,a)') 'Timings:' - !call eval_sub_timer(ctimer) - call ctimer%write(stdout,'PCA/k-Means clustering') - end if - call ctimer%clear() - return -end subroutine CCEGEN - -!=======================================================================================! -! set clustering level defaults -!=======================================================================================! -subroutine clustleveval(env,maxclust,csthr,SSRSSTthr) - use crest_parameters,idp => dp - use crest_data - implicit none - type(systemdata) :: env ! MAIN STORAGE OS SYSTEM DATA - integer :: clev - integer :: maxclust - real(wp) :: csthr - real(wp) :: SSRSSTthr - - SSRSSTthr = 0.90 !exit if this value is reached for SSR/SST - - clev = env%clustlev - if (env%clustlev >= 10) then !for incremental modes - clev = env%clustlev-10 - end if - - select case (clev) - case (-1) !-- loose - maxclust = 25 - csthr = 0.80d0 - case (1) !-- tight - maxclust = 400 - if (env%clustlev >= 10) maxclust = 50 - csthr = 0.85d0 - case (2) !-- vtight - maxclust = 400 - if (env%clustlev >= 10) maxclust = 100 - csthr = 0.9d0 - SSRSSTthr = 0.92d0 - case default !-- normal - maxclust = 100 - if (env%clustlev >= 10) maxclust = 25 - csthr = 0.80d0 - end select - - return -end subroutine clustleveval - -!=======================================================================================! -! write a file with the 2 most contributing principal components of each structure -! and the cluster to which the structure belongs -!=======================================================================================! -subroutine PCA_grpwrite(nclust,npc,mm,pcvec,member) - use crest_parameters,idp => dp - implicit none - integer,intent(in) :: nclust ! number of required centroids - integer,intent(in) :: npc,mm - real(wp),intent(in) :: pcvec(npc,mm) - integer,intent(in) :: member(mm) ! membership for each structure - integer :: ich,i - open (newunit=ich,file='cluster.order') - write (ich,'(4x,i0,4x,i0,4x,i0)') mm,nclust,npc - if (npc > 1) then - do i = 1,mm - write (ich,'(i8,1x,f16.8,1x,f16.8,1x,i8)') i,pcvec(1,i),pcvec(2,i),member(i) - end do - else - do i = 1,mm - write (ich,'(i8,1x,f16.8,1x,i8)') i,pcvec(1,i),member(i) - end do - end if - close (ich) - return -end subroutine PCA_grpwrite - -!=======================================================================================! -! Exclude light - exclude H atoms in the inc array -!=======================================================================================! -subroutine excludeLight(zmol,inc) - use crest_parameters,idp => dp - use zdata - implicit none - type(zmolecule) :: zmol - integer :: inc(zmol%nat) - integer :: i - do i = 1,zmol%nat - if (zmol%at(i) == 1) then - inc(i) = 0 - end if - end do - return -end subroutine excludeLight - -!=======================================================================================! -! Exclude Specified Atoms -!=======================================================================================! -subroutine excludeSelected(zmol,inc,atlist) - use crest_parameters,idp => dp - use zdata - implicit none - type(zmolecule) :: zmol - integer :: inc(zmol%nat) - character(len=*) :: atlist !a string containing atom numbers, needs parsing - integer :: i,ncon - integer,allocatable :: inc2(:) - allocate (inc2(zmol%nat),source=0) - call parse_atlist_new(atlist,ncon,zmol%nat,zmol%at,inc2) - do i = 1,zmol%nat - if (inc2(i) == 1) inc(i) = 0 - end do - deallocate (inc2) - return -end subroutine excludeSelected - -!=======================================================================================! -! Perform a single value decomposition (SVD) and get the principal components -! -! X = U*sig*V^(T) -! -! The eigenvalues saved in sig are the principal components -! The SVD only works if M >= N -! -!=======================================================================================! -subroutine svd_to_pc(measure,m,n,sig,U,pr) - use crest_parameters,idp => dp - implicit none - integer :: n,m - real(wp) :: measure(n,m) - real(wp) :: sig(n) - real(wp) :: U(m,n) - integer :: i,j,info,lwork - real(wp),allocatable :: mean(:),tmp(:) - integer,allocatable :: ind(:) - real(wp),allocatable :: X(:,:),V(:,:),work(:) - integer,allocatable :: iwork(:) - logical :: pr - if (pr) then - write (stdout,*) m,' mesaurements' - write (stdout,*) n,' props' - end if - allocate (mean(n),ind(m),tmp(m)) - lwork = max(2*M+N,6*N+2*N*N) - allocate (X(m,n),V(n,n),iwork(m+3*n),work(lwork)) - mean = 0.0d0 - do i = 1,m - do j = 1,n - mean(j) = mean(j)+measure(j,i) - end do - end do - mean = mean/float(m) - if (pr) write (stdout,*) mean - do i = 1,m - do j = 1,n - X(i,j) = (mean(j)-measure(j,i)) - end do - end do - if (pr) then - call PRMAT(6,X,m,n,'X') - end if -!>--- LAPACKs' DGEJSV - call DGEJSV('C','U','V','N','N','N', & - & m,n,X,m,sig,U,m,V,n, & - & WORK,LWORK,IWORK,INFO) - if (pr) then - write (stdout,*) info - write (stdout,*) sig - call PRMAT(6,U**2,M,N,'U') - call PRMAT(6,V,N,N,'V') - end if - deallocate (work,iwork,V,X,tmp,ind,mean) - return -end subroutine svd_to_pc - -!======================================================================! -! calculate the Euclidian distance between two points p and q -!======================================================================! -function eucdist(ndim,p,q) result(dist) - use crest_parameters,idp => dp - implicit none - real(ap) :: dist - integer :: ndim - real(ap) :: p(ndim) - real(ap) :: q(ndim) - integer :: i - dist = 0.0d0 - do i = 1,ndim - dist = dist+(q(i)-p(i))**2 - end do - dist = sqrt(dist) - return -end function eucdist - -!======================================================================! -! K-means clustering algorithm -! -! determine a position of cluster centroids iteratively for a given -! number of centroids. -! -!======================================================================! -subroutine kmeans(nclust,npc,mm,centroid,pcvec,ndist,dist,member) - use crest_parameters,idp => dp - implicit none - integer,intent(in) :: nclust ! number of required centroids - integer,intent(in) :: npc,mm - real(wp),intent(in) :: pcvec(npc,mm) - integer(idp),intent(in) :: ndist - real(sp),intent(in) :: dist(ndist) - integer,intent(inout) :: member(mm) ! membership for each structure - real(ap),intent(inout):: centroid(npc,nclust) - integer,allocatable :: refmember(:) - - if (nclust .le. 1) return !no singular clusters! - - allocate (refmember(mm),source=0) - - !>-- determine seeds for the centroids (i.e., initial positions) - call kmeans_seeds(nclust,npc,mm,centroid,pcvec,ndist,dist) - - do - !>-- determine cluster membership for all structures - !> (by shortest Euc. distance to the respective centroid) - member = 0 !reset - call kmeans_assign(nclust,npc,mm,centroid,pcvec,member) - - !>-- check if memberships changed w.r.t. previous memberships - if (all(member == refmember)) then - exit - else - refmember = member - end if - !>-- update centroids if necessary - call kmeans_recenter(nclust,npc,mm,centroid,pcvec,member) - end do - - deallocate (refmember) - return -end subroutine kmeans -!===================================================================! -! determine cluster seeds for the K-means algo -!===================================================================! -subroutine kmeans_seeds(nclust,npc,mm,centroid,pcvec,ndist,dist) - use crest_parameters,idp => dp - use utilities - implicit none - integer :: nclust,npc,mm - real(ap) :: centroid(npc,nclust) - real(wp) :: pcvec(npc,mm) - integer(idp) :: ndist - real(sp) :: dist(ndist) - real(sp) :: ddum - integer(idp) :: k,kiter - integer :: i,j,l,c - real(wp) :: distsum,maxdistsum - real(ap) :: eucdist !this is a function - real(ap),allocatable :: p(:),q(:) - integer,allocatable :: taken(:) - - !>--- first two centroids are located at the most apart points - !> in the PC space - ddum = 0.0_sp - do kiter = 1,ndist - if (dist(kiter) > ddum) then - ddum=dist(kiter) - k = kiter - end if - end do - call revlin(k,j,i) !> reverse of the lin function, get indices i and j - - centroid(1:npc,1) = pcvec(1:npc,i) - centroid(1:npc,2) = pcvec(1:npc,j) - - if (nclust .le. 2) return !>-- if we only need two centroids, return - - !>-- If more centroids are needed, search for one point which has the maximal sum of - !>-- the distances between the already determined centroids and itself. - allocate (p(npc),q(npc),taken(nclust)) - taken = 0 - taken(1) = i - taken(2) = j - do i = 3,nclust - maxdistsum = 0.0d0 - c = 0 -!$OMP PARALLEL PRIVATE ( l, q, p, j, distsum ) & -!$OMP SHARED ( i, centroid, npc, mm, maxdistsum, pcvec, c, taken ) -!$OMP DO - do j = 1,mm - distsum = 0.0d0 - p(1:npc) = pcvec(1:npc,j) - do l = 1,i-1 - q(1:npc) = centroid(1:npc,l) - distsum = distsum+eucdist(npc,p,q) - end do - !$OMP CRITICAL - if (.not.any(taken == j)) then - if (distsum .gt. maxdistsum) then - maxdistsum = distsum - c = j - taken(i) = c - end if - end if - !$OMP END CRITICAL - end do -!$OMP END DO -!$OMP END PARALLEL - if (c == 0) then - exit - else - centroid(1:npc,i) = pcvec(1:npc,c) - end if - end do - - deallocate (taken,q,p) - - return -end subroutine kmeans_seeds -!===================================================================! -! assign structures as members to a centroid -!===================================================================! -subroutine kmeans_assign(nclust,npc,mm,centroid,pcvec,member) - use crest_parameters,idp => dp - implicit none - integer :: nclust,npc,mm - real(ap) :: centroid(npc,nclust) - real(wp) :: pcvec(npc,mm) - integer :: member(mm) - real(ap) :: eucdist !this is a function - integer :: i,j,c - real(ap),allocatable :: centdist(:) - real(ap),allocatable :: p(:),q(:) - - allocate (centdist(nclust),source=0.0_ap) - allocate (p(npc),q(npc)) -!$OMP PARALLEL PRIVATE ( i, j, p, q, c, centdist ) & -!$OMP SHARED ( mm, nclust, member, centroid, npc, pcvec ) -!$OMP DO - do i = 1,mm - p(1:npc) = pcvec(1:npc,i) - do j = 1,nclust - q(1:npc) = centroid(1:npc,j) - centdist(j) = eucdist(npc,p,q) - end do - c = minloc(centdist,1) - member(i) = c - end do -!$OMP END DO -!$OMP END PARALLEL - deallocate (q,p) - deallocate (centdist) - return -end subroutine kmeans_assign - -!===================================================================! -! re-center centroids for given (sorted) structures -!===================================================================! -subroutine kmeans_recenter(nclust,npc,mm,centroid,pcvec,member) - use crest_parameters,idp => dp - implicit none - integer :: nclust,npc,mm - real(ap) :: centroid(npc,nclust) - real(wp) :: pcvec(npc,mm) - integer :: member(mm) - integer :: i,j,c - real(ap),allocatable :: p(:),q(:) - - allocate (p(npc),q(npc)) - do i = 1,nclust - c = 0 - p = 0.0d0 - do j = 1,mm - if (member(j) == i) then - c = c+1 - p(1:npc) = p(1:npc)+pcvec(1:npc,j) - end if - end do - if (c > 0) then - p = p/float(c) - centroid(1:npc,i) = p(1:npc) - else - p = 999.9d0 - end if - end do - deallocate (q,p) - - return -end subroutine kmeans_recenter - -!======================================================================! -! calculate statistical values for the given cluster size -! Values to compute: -! DBI - the Davies-Bouldin index -! pSF - the "pseudo-F statistic" -! SSR/SST ratio -!======================================================================! -subroutine cluststat(nclust,npc,mm,centroid,pcvec,member,DBI,pSF,SSRSST) - use crest_parameters,idp => dp - implicit none - integer,intent(in) :: nclust ! number of required centroids - integer,intent(in) :: npc,mm - real(wp),intent(in) :: pcvec(npc,mm) - integer,intent(in) :: member(mm) ! membership for each structure - real(ap),intent(in):: centroid(npc,nclust) - real(wp),intent(out) :: DBI,pSF,SSRSST - real(wp) :: SSE,SSR,SST - real(ap),allocatable :: p(:),q(:) - real(wp),allocatable :: compact(:) - real(wp),allocatable :: DBmat(:,:) - real(ap) :: eucdist !this is a function - real(wp) :: d,Rij,maxDB,weight - integer :: i,c,k,c2 - - DBI = 0.0d0 - pSF = 0.0d0 - SSRSST = 0.0d0 - - if (nclust < 2) return - - allocate (p(npc),q(npc)) - - !>-- Sum of squares error - SSE = 0.0d0 - do c = 1,nclust - p(1:npc) = centroid(1:npc,c) - do i = 1,mm - if (member(i) == c) then - q(1:npc) = pcvec(1:npc,i) - d = eucdist(npc,p,q) - SSE = SSE+d**2 - end if - end do - end do - SSE = SSE - - !>-- Total sum of squares - SST = 0.0d0 - p = 0.0d0 - do c = 1,nclust - weight = real(count(member(:)==c,1),wp)/real(mm,wp) - p(1:npc) = p(1:npc)+centroid(1:npc,c)*weight - end do - !p = p/float(nclust) - do i = 1,mm - q(1:npc) = pcvec(1:npc,i) - d = eucdist(npc,p,q) - SST = SST+d**2 - end do - SST = SST - - !>-- Sum of squares regression - SSR = SST-SSE - - !>-- SSR/SST ratio - SSRSST = SSR/SST - - !>-- pseudo-F statistic - if (nclust > 1) then - pSF = (SSR/(float(nclust)-1.0d0)) - if (mm == nclust) then - pSF = 0.0d0 - else - pSF = pSF/(SSE/(float(mm)-float(nclust))) - end if - else - pSF = 0.0d0 - end if - - !>-- Davies-Bouldin index (DBI) - allocate (compact(nclust),source=0.0d0) !cluster compactness - do c = 1,nclust - p(1:npc) = centroid(1:npc,c) - k = 0 - do i = 1,mm - if (member(i) == c) then - k = k+1 - q(1:npc) = pcvec(1:npc,i) - d = eucdist(npc,p,q) - compact(c) = compact(c)+d - end if - end do - if (k > 0) then - compact(c) = compact(c)/float(k) - else - compact(c) = 0 - end if - end do - allocate (DBmat(nclust,nclust),source=0.0d0) - do c = 1,nclust - p(1:npc) = centroid(1:npc,c) - do c2 = 1,nclust - if (c2 == c) cycle - q(1:npc) = centroid(1:npc,c2) - d = eucdist(npc,p,q) - Rij = (compact(c)+compact(c2))/d - DBmat(c,c2) = Rij - end do - end do - do c = 1,nclust - maxDB = maxval(DBmat(:,c),1) - DBI = DBI+maxDB - end do - DBI = DBI/float(nclust) - deallocate (DBmat) - deallocate (compact) - - deallocate (q,p) - return -end subroutine cluststat - -!==============================================================! -! analyze the statistical values DBI and pSF to get the -! respective extrema -!==============================================================! -subroutine statanal(n,nmax,statistics,extrema,pr) - use crest_parameters - implicit none - integer :: n,nmax - real(wp) :: statistics(3,nmax) - logical,intent(inout) :: extrema(2,n) - logical :: pr - real(wp) :: last,next,current - integer :: i - - extrema = .false. -!>--- identify local extrema of the DBI and pSF - do i = 2,n-1 - !>-- DBI - last = statistics(1,i-1) - next = statistics(1,i+1) - current = statistics(1,i) - if ((current < last).and.(current < next)) then - extrema(1,i) = .true. - end if - !>-- pSF - last = statistics(2,i-1) - next = statistics(2,i+1) - current = statistics(2,i) - if ((current > last).and.(current > next)) then - extrema(2,i) = .true. - end if - end do - - if (pr) then - write (stdout,*) - write (stdout,'(1x,a,/)') 'Suggestions for cluster sizes:' - do i = 1,n - if (extrema(1,i).or.extrema(2,i)) then - if (extrema(1,i).and.extrema(2,i)) then - write (stdout,'(1x,i8,''*'',3x,a,f8.4)') i,'SSR/SST',statistics(3,i) - else - write (stdout,'(1x,i8,4x,a,f8.4)') i,'SSR/SST',statistics(3,i) - end if - end if - end do - write (stdout,'(/,1x,a)') 'Cluster counts marked with a star (*) are reasonable' - write (stdout,'(1x,a)') 'suggestions according to BOTH the DBI and pSF.' - end if - - return -end subroutine statanal - -!==============================================================! -! print a warning regarding the nature of the cluster partitioning -!==============================================================! -subroutine statwarning(fname) - use crest_parameters - implicit none - character(len=*) :: fname - write (stdout,*) - write (stdout,'(1x,a)') '!---------------------------- NOTE ----------------------------!' - write (stdout,'(2x,a)') 'The partitioning of data (the ensemble) into clusters' - write (stdout,'(2x,a)') 'of similar characteristics (structures) is ARBITRARY' - write (stdout,'(2x,a)') 'and depends on many criteria (e.g. choice of PCs).' - write (stdout,'(2x,a)') 'The selected cluster count is the smallest reasonable' - write (stdout,'(2x,a)') 'number of clusters that can be formed according to' - write (stdout,'(2x,a)') 'the DBI and pSF values for the given data.' - write (stdout,*) - write (stdout,'(2x,a)') 'If other cluster sizes are desired, rerun CREST with' - write (stdout,'(2x,3a)') '"crest --for ',trim(fname),' --cluster "' - write (stdout,*) - write (stdout,'(2x,a)') 'Other default evaluation settings can be chosen with the' - write (stdout,'(2x,a)') 'keywords "loose","normal", and "tight" as via' - write (stdout,'(2x,3a)') '"crest --for ',trim(fname),' --cluster "' - write (stdout,'(1x,a)') '!--------------------------------------------------------------!' -end subroutine statwarning - -!====================================================================! -subroutine getdiederatoms(zmol,nat,inc,nb,diedat,ndied) - use crest_parameters,idp => dp - use zdata - use strucrd - implicit none - type(zmolecule) :: zmol - integer :: nat - integer :: inc(nat) !contains 1 (=include) or 0 (=ignore) - integer :: nb - integer :: diedat(4,nb) - integer,intent(out) :: ndied - integer :: a,b,c,d - integer :: i,j,k - - ndied = 0 - do i = 1,nb - a = zmol%bondpairs(1,i) - b = zmol%bondpairs(2,i) - if (inc(a) == 0) cycle !ignored by user? - if (inc(b) == 0) cycle !ignored by user? - if (zmol%zat(a)%nei == 1) cycle !terminal atom? - if (zmol%zat(b)%nei == 1) cycle !terminal atom? - if (zmol%methyl(a)) cycle !methyl C? - if (zmol%methyl(b)) cycle !methyl C? - !>-- passed all checks, so let's get atoms - !>-- a neighbour for a - do j = 1,zmol%zat(a)%nei - c = zmol%zat(a)%ngh(j) - if (c == b) then - cycle - else - exit - end if - end do - !>-- a neighbour for b - do k = 1,zmol%zat(b)%nei - d = zmol%zat(b)%ngh(k) - if (d == a) then - cycle - else - exit - end if - end do - ndied = ndied+1 - !>the bond is between a and b - !>c is a neighbour of a, d is a neighbour of b - diedat(2,ndied) = a - diedat(3,ndied) = b - diedat(1,ndied) = c - diedat(4,ndied) = d - end do - - return -end subroutine getdiederatoms - -subroutine calc_dieders(nat,xyz,ndied,diedat,diedr) - use crest_parameters,idp => dp - use crest_data - use zdata - use strucrd - implicit none - integer :: nat,ndied - real(wp) :: xyz(3,nat) - integer :: diedat(4,ndied) - real(wp),intent(out) :: diedr(ndied) - integer :: i - integer :: a,b,c,d - real(wp) :: coords(3,4) - real(wp) :: angle - real(wp),parameter :: rad2degree = 57.29578_wp - real(wp),parameter :: tol = 5.0_wp !tolerance for almost 360 degree - - diedr = 0.0_wp - do i = 1,ndied - a = diedat(2,i) - b = diedat(3,i) - c = diedat(1,i) - d = diedat(4,i) - coords(1:3,1) = xyz(1:3,c) - coords(1:3,2) = xyz(1:3,a) - coords(1:3,3) = xyz(1:3,b) - coords(1:3,4) = xyz(1:3,d) - call DIHED(coords,1,2,3,4,angle) - angle = abs(angle)*rad2degree - if (abs(angle-360.0_wp) < tol) angle = 0.0_wp - diedr(i) = angle - end do - - return -end subroutine calc_dieders - diff --git a/src/rigidconf/CMakeLists.txt b/src/chelpers/CMakeLists.txt similarity index 88% rename from src/rigidconf/CMakeLists.txt rename to src/chelpers/CMakeLists.txt index c70e846d..7e2992ab 100644 --- a/src/rigidconf/CMakeLists.txt +++ b/src/chelpers/CMakeLists.txt @@ -17,16 +17,8 @@ set(dir "${CMAKE_CURRENT_SOURCE_DIR}") list(APPEND srcs - "${dir}/rigidconf.f90" - "${dir}/tree.f90" - "${dir}/analyze.f90" - "${dir}/reconstruct.f90" + "${dir}/mempeak.c" + "${dir}/signal.c" ) set(srcs ${srcs} PARENT_SCOPE) - - - - - - diff --git a/src/chelpers/mempeak.c b/src/chelpers/mempeak.c new file mode 100644 index 00000000..b3d98555 --- /dev/null +++ b/src/chelpers/mempeak.c @@ -0,0 +1,48 @@ +/* +! This file is part of crest. +! +! Copyright (C) 2025 Philipp Pracht +! +! crest is free software: you can redistribute it and/or modify it under +! the terms of the GNU Lesser General Public License as published by +! the Free Software Foundation, either version 3 of the License, or +! (at your option) any later version. +! +! crest is distributed in the hope that it will be useful, +! but WITHOUT ANY WARRANTY; without even the implied warranty of +! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +! GNU Lesser General Public License for more details. +! +! You should have received a copy of the GNU Lesser General Public License +! along with crest. If not, see . + +*/ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +__declspec(dllexport) long long get_peak_rss_kb(void) { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + // bytes -> kilobytes + return (long long)(pmc.PeakWorkingSetSize / 1024); + } + return -1; +} +#else +#include +#include +long long get_peak_rss_kb(void) { + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) == 0) { +// On Linux: ru_maxrss is in kilobytes +// On macOS/BSD: ru_maxrss is in bytes — convert to KB +#ifdef __APPLE__ + return (long long)(ru.ru_maxrss / 1024); +#else + return (long long)ru.ru_maxrss; +#endif + } + return -1; +} +#endif diff --git a/src/rigidconf/meson.build b/src/chelpers/meson.build similarity index 91% rename from src/rigidconf/meson.build rename to src/chelpers/meson.build index 22aebeda..e700225e 100644 --- a/src/rigidconf/meson.build +++ b/src/chelpers/meson.build @@ -15,8 +15,6 @@ # along with crest. If not, see . srcs += files( - 'rigidconf.f90', - 'tree.f90', - 'analyze.f90', - 'reconstruct.f90', + 'mempeak.c', + 'signal.c', ) diff --git a/src/signal.c b/src/chelpers/signal.c similarity index 58% rename from src/signal.c rename to src/chelpers/signal.c index d6a4007e..9954719d 100644 --- a/src/signal.c +++ b/src/chelpers/signal.c @@ -14,3 +14,11 @@ void signal_( int* signum, sighandler_t handler) { signal(*signum, handler); } + +/* Called from Fortran via ISO_C_BINDING for ifx builds. + * handler is a BIND(C) Fortran subroutine with no dummy arguments; + * the int signum passed by the OS is silently ignored. */ +void crest_install_signal(int signum, void (*handler)(void)) +{ + signal(signum, (sighandler_t)handler); +} diff --git a/src/choose_settings.f90 b/src/choose_settings.f90 index 901d5d7d..13c38b29 100644 --- a/src/choose_settings.f90 +++ b/src/choose_settings.f90 @@ -30,17 +30,17 @@ subroutine md_length_setup(env) use crest_parameters use crest_data use strucrd - use zdata, only:readwbo + use zdata,only:readwbo implicit none !> IN/OUTPUT type(systemdata) :: env !> MAIN STORAGE OS SYSTEM DATA !> LOCAL real(wp) :: total,minimum,lenthr real(wp) :: flex,av1,rfac,nciflex - type(coord) :: mol + type(coord) :: mol logical :: ex -!> get reference geometry - call env%ref%to( mol ) +!> get reference geometry + call env%ref%to(mol) !> at least 5ps per MTD minimum = 5.0d0 @@ -50,19 +50,19 @@ subroutine md_length_setup(env) call smallhead('Generating MTD length from a flexibility measure') if ((env%crestver .ne. crest_solv).and..not.env%NCI) then - write(stdout,'(1x,a)',advance='no') 'Calculating GFN0-xTB WBOs ...' + write (stdout,'(1x,a)',advance='no') 'Calculating GFN0-xTB WBOs ...' !>-- xtb singlepoint to get WBOs (always GFN0) call xtbsp(env,0) write (stdout,'(1x,a)') 'done.' !>-- save those WBOs to the reference - inquire(file='wbo',exist = ex) - if(ex)then - if(.not.allocated(env%ref%wbo)) allocate(env%ref%wbo( mol%nat, mol%nat), source=0.0_wp) - call readwbo('wbo',mol%nat, env%ref%wbo) - endif + inquire (file='wbo',exist=ex) + if (ex) then + if (.not.allocated(env%ref%wbo)) allocate (env%ref%wbo(mol%nat,mol%nat),source=0.0_wp) + call readwbo('wbo',mol%nat,env%ref%wbo) + end if !>-- covalent flexibility measure based on WBO and structure only - call flexi( mol, env%rednat, env%includeRMSD, flex) + call flexi(mol,env%rednat,env%includeRMSD,flex) !>-- NCI flexi based on E(HB)/Nat and E(disp)/Nat call nciflexi(env,nciflex) write (stdout,'(1x,'' covalent flexibility measure :'',f8.3)') flex @@ -118,9 +118,9 @@ subroutine md_length_setup(env) !>-- ONLY use generated MD length if not already set by the user if (env%mdtime .le. 0.0d0) then - if(env%mddat%length_ps > 0.0_wp)then + if (env%mddat%length_ps > 0.0_wp) then total = env%mddat%length_ps - write(stdout,'(1x,"t(MTD) / ps set via calculator :", f8.1)') total + write (stdout,'(1x,"t(MTD) / ps set via calculator :", f8.1)') total else if (total .gt. lenthr) then total = lenthr call mtdwarning(lenthr*rfac) @@ -135,9 +135,9 @@ subroutine md_length_setup(env) & env%mdtime*float(env%nmetadyn),env%nmetadyn !> A MTD Vbias snapshot is taken every 1 ps - if(allocated(env%metadlist))then + if (allocated(env%metadlist)) then env%metadlist(:) = ceiling(env%mdtime) - endif + end if return end subroutine md_length_setup @@ -149,7 +149,7 @@ subroutine defaultGF(env) !* Setmetadynamics default Guiding Force Parameter !* There are different combinations depending on the runtype !************************************************************ - use crest_parameters + use crest_parameters use crest_data use filemod implicit none @@ -200,7 +200,7 @@ subroutine defaultGF(env) !+++++++++++++++++++++++++++++++++++++++++++++++++++++++! select case (env%runver) !---------- "-quick","-squick" - case (2,5) + case (2,5) na = 3 nk = 2 nmtdyn = na*nk @@ -209,7 +209,7 @@ subroutine defaultGF(env) alpinc = 2.0 ! increment kinc = 2.0 ! increment !---------- "-mquick" - case (6) + case (6) na = 3 nk = 2 nmtdyn = na*nk @@ -218,7 +218,7 @@ subroutine defaultGF(env) alpinc = 2.0 ! increment kinc = 2.0 ! increment !---------- "-qcg" - case (3) + case (3) na = 4 nk = 3 nmtdyn = na*nk @@ -227,7 +227,7 @@ subroutine defaultGF(env) alpinc = (3./2.) ! increment kinc = (3./2.) ! increment !---------- "-nci" - case (4) + case (4) na = 3 nk = 2 nmtdyn = na*nk @@ -236,7 +236,7 @@ subroutine defaultGF(env) alpinc = 2.0 ! increment kinc = 2.0 ! increment !---------- "-singlerun" - case (45) + case (45) na = 1 nk = 1 nmtdyn = na*nk @@ -254,7 +254,7 @@ subroutine defaultGF(env) alpinc = 2.0 ! increment kinc = 2.0 ! increment !---------- "-compress" - case (77) + case (77) na = 3 nk = 3 nmtdyn = na*nk @@ -263,7 +263,7 @@ subroutine defaultGF(env) alpinc = 1.61803 ! increment kinc = 2.0 ! increment !--------- "search_1" - case (crest_s1,crest_mecp) + case (crest_s1,crest_mecp) na = 3 nk = 3 nmtdyn = (na*nk) @@ -280,7 +280,7 @@ subroutine defaultGF(env) alpinc = (5./3.) ! increment kinc = 1.5d0 ! increment !---------- "-entropy" - case (111) + case (111) na = 6 nk = 4 nmtdyn = (na*nk) @@ -395,13 +395,13 @@ subroutine adjustnormmd(env) !>--- first the number of normMDs on low conformers if (env%nrotammds .le. 0) then !> if no user input was set !> multiple short MDs, which has a better parallel efficiency - !> default is 4 - env%nrotammds = max(1,nint(float(env%nmetadyn)/4.0d0)) + !> default is 4 + env%nrotammds = max(1,nint(float(env%nmetadyn)/4.0d0)) end if !>--- then the temperature range if (env%temps .le. 0) then - !> at how many different temperatures? + !> at how many different temperatures? !> starting at 400k and increasing 100K for each (200 K for -entropy mode) env%temps = 2 if (env%entropic) then @@ -414,7 +414,7 @@ subroutine adjustnormmd(env) !==============================================! !>--- settings for static MTDS in entropy mode !==============================================! - if (env%entropymd) then + if (env%entropymd) then env%emtd%iter = 20 !> max number of iterations env%emtd%nbias = min(150,nint(env%tmtd/4)) !> max number of bias structures env%emtd%nbiasgrow = min(1.4d0,1.2d0+env%tmtd*1.d-3) !> increase of nBias in each cycle @@ -476,55 +476,71 @@ subroutine env_to_mddat(env) implicit none type(systemdata) :: env real(wp) :: dum + integer :: i,j,nat !!>--- dont override user-defined settings ! if(env%mddat%requested) return !> we will check if any default settings were already set individually, instead !> the if-statements in the following take care of that !>--- necessary transfer global settings into mddat object - if(env%mddat%length_ps <= 0.0_wp)then - !> total runtime in ps - env%mddat%length_ps = env%mdtime - else - env%mdtime = env%mddat%length_ps - endif - if(env%mddat%tstep <= 0.0_wp)then - !> time step in fs - env%mddat%tstep = env%mdstep - endif - !> simulation steps (would be recovered automatically later, but just to make sure) - env%mddat%length_steps = nint(env%mddat%length_ps*1000.0_wp / env%mddat%tstep) - if(env%mddat%tsoll <= 0.0_wp)then - !> target temperature - env%mddat%tsoll = env%mdtemp - endif - - if( env%mddat%dumpstep <= 0.0_wp ) then - !> dump frequency in fs - env%mddat%dumpstep = float(env%mddumpxyz) - endif - if(env%mddat%sdump <= 0)then - !> trajectory structure dump every x steps - dum = max(1.0_wp, (env%mddat%dumpstep / env%mddat%tstep)) - env%mddat%sdump = nint(dum) - endif - - !> The SHAKE setup (special condition referring to the default) - env%mddat%shake = env%mddat%shake .and.(env%shake > 0) !> SHAKE algorithm? - if( env%mddat%shake .and. env%mddat%shk%shake_mode == 0)then - env%mddat%shk%shake_mode = env%shake !> H-only shake =1, all atom =2 - endif - - if(env%mddat%md_hmass <= 0.0_wp)then - !> hydrogen mass (to enable longer timesteps) - env%mddat%md_hmass = env%hmass - endif - - ! TODO: WBO reader if shake is applied and wbo file is present + if (env%mddat%length_ps <= 0.0_wp) then + !> total runtime in ps + env%mddat%length_ps = env%mdtime + else + env%mdtime = env%mddat%length_ps + end if + if (env%mddat%tstep <= 0.0_wp) then + !> time step in fs + env%mddat%tstep = env%mdstep + end if + !> simulation steps (would be recovered automatically later, but just to make sure) + env%mddat%length_steps = nint(env%mddat%length_ps*1000.0_wp/env%mddat%tstep) + if (env%mddat%tsoll <= 0.0_wp) then + !> target temperature + env%mddat%tsoll = env%mdtemp + end if + + if (env%mddat%dumpstep <= 0.0_wp) then + !> dump frequency in fs + env%mddat%dumpstep = real(env%mddumpxyz,wp) + end if + if (env%mddat%sdump <= 0) then + !> trajectory structure dump every x steps + dum = max(1.0_wp, (env%mddat%dumpstep/env%mddat%tstep)) + env%mddat%sdump = nint(dum) + end if + + !> The SHAKE setup (special condition referring to the default) + env%mddat%shake = env%mddat%shake.and.(env%shake > 0) !> SHAKE algorithm? + if (env%mddat%shake.and.env%mddat%shk%shake_mode == 0) then + env%mddat%shk%shake_mode = env%shake !> H-only shake =1, all atom =2 + end if + + if (env%mddat%md_hmass <= 0.0_wp) then + !> hydrogen mass (to enable longer timesteps) + env%mddat%md_hmass = env%hmass + end if + + if (allocated(env%mddat%mtd)) then + nat = env%ref%nat + if (sum(env%includeRMSD) < nat) then + do i = 1,env%mddat%npot + if (.not.allocated(env%mddat%mtd(i)%atinclude)) then + allocate (env%mddat%mtd(i)%atinclude(nat),source=.false.) + else + env%mddat%mtd(i)%atinclude = .false. + end if + do j = 1,nat + if (env%includeRMSD(j) == 1) env%mddat%mtd(i)%atinclude(j) = .true. + end do + end do + end if + end if + + ! TODO: WBO reader if shake is applied and wbo file is present !>--- set flag to signal present settings env%mddat%requested = .true. end subroutine env_to_mddat - diff --git a/src/classes.f90 b/src/classes.f90 index f24dbd9d..b3e4afbe 100644 --- a/src/classes.f90 +++ b/src/classes.f90 @@ -25,15 +25,18 @@ module crest_data use iso_fortran_env,wp => real64,dp => int64 use crest_calculator,only:calcdata use dynamics_module,only:mddata + use bh_module,only:bh_class use strucrd,only:coord use crest_type_timer,only:timer - use lwoniom_module, only: lwoniom_input + use lwoniom_module,only:lwoniom_input + use molbuilder_construct_list !> from molbuilder dir + use term_ui,only:progress_state implicit none public :: systemdata public :: timer !> RE-EXPORT from crest_type_timer public :: protobj - public :: constra + public :: legacy_constraints public :: optlevflag,optlevnum,optlevmap_alt public :: optlev_to_multilev @@ -74,6 +77,7 @@ module crest_data integer,parameter,public :: crest_protonate = 16 integer,parameter,public :: crest_deprotonate = 17 integer,parameter,public :: crest_tautomerize = 18 + integer,parameter,public :: crest_sorting = 19 !>> runtypes with IDs between use non-legacy routines <> <---- propcalc sub-modes (imode argument of propcalc / values in env%pqueue) + integer,parameter,public :: p_prop_hess = 1 !> Hessian for all conformers + integer,parameter,public :: p_prop_autoir = 2 !> IR spectrum averaging + integer,parameter,public :: p_prop_ohess = 10 !> Optimization + Hessian + integer,parameter,public :: p_prop_finalhess = 11 !> Hessian+thermo for final conformer ensemble + integer,parameter,public :: p_prop_gsolv = 13 !> Free energy in solvation (2-step) + integer,parameter,public :: p_prop_reopt = 20 !> Vtight reoptimization + integer,parameter,public :: p_prop_multilevel = 50 !> Multilevel/hybrid reopt base (range 50:59) + integer,parameter,public :: p_prop_dipole = 998 !> Singlepoint + dipole extraction + integer,parameter,public :: p_prop_rerank = 999 !> Singlepoint + reranking !>--- exit status integer,parameter,public :: status_normal = 0 !> success integer,parameter,public :: status_error = 1 !> general error integer,parameter,public :: status_ioerr = 2 !> general I/O error - integer,parameter,public :: status_args = 4 !> invalid subroutine arguments + integer,parameter,public :: status_args = 4 !> invalid subroutine arguments integer,parameter,public :: status_input = 10 !> Input file read error integer,parameter,public :: status_config = 20 !> invalid configuration integer,parameter,public :: status_failed = 155 !> general calculation failure @@ -127,7 +145,14 @@ module crest_data integer :: singlepoint = 1 integer :: correction = 2 integer :: geoopt = 3 - integer :: ConfSolv = 4 + integer :: deltaG = 5 + !> post_opt (= 10): post-search re-optimization via pqueue job 51 (e.g. A@B) + !> post_sp (= 11): post-search SP reranking via pqueue job 52 (--rerank) + !> post_reopt (= 12): post-search geo-opt standalone via pqueue job 53 (--reopt) + !> Values 6-9 reserved for future inline stages. + integer :: post_opt = 10 + integer :: post_sp = 11 + integer :: post_reopt = 12 end type refine_type type(refine_type), parameter,public :: refine = refine_type() @@ -139,7 +164,7 @@ module crest_data !========================================================================================! !========================================================================================! - type :: constra + type :: legacy_constraints !**************************************************** !* separate settings for LEGACY constraint handling !**************************************************** @@ -169,9 +194,10 @@ module crest_data logical :: usermsdpot = .false. logical :: gesc_heavy = .false. contains - procedure :: allocate => allocate_constraints - procedure :: deallocate => deallocate_constraints - end type constra + procedure :: allocate => allocate_legacy_constraints + procedure :: deallocate => deallocate_legacy_constraints + procedure :: info => legacy_constraints_info + end type legacy_constraints !========================================================================================! @@ -179,7 +205,7 @@ module crest_data !************************************************************ !* separate settings for protonation and related procedures !************************************************************ - integer :: nfrag = 0 + integer :: nfrag = 0 integer :: newchrg = 0 integer :: iter = 1 real(wp) :: ewin = 30.0_wp !> separate EWIN threshold @@ -285,10 +311,12 @@ module crest_data integer :: pcap = 50000 !> limit number of structures logical :: avbhess = .false. !> use bhess in the msRRHO average calc. for all structures (expensive!) logical :: constrhess = .false. !> apply constraints in rrhoav? - logical :: printpop = .false. !> print a file with populations at different T + logical :: printpop = .false. !> print a file with populations at different T + character(len=:),allocatable :: emodel contains procedure :: get_temps => thermo_get_temps procedure :: read_temps => thermo_read_temps + procedure :: get_close_rt => thermo_get_close_rt end type thermodata !========================================================================================! @@ -307,7 +335,9 @@ module crest_data integer,allocatable :: topo(:) real(wp),allocatable :: charges(:) real(wp),allocatable :: wbo(:,:) + real(wp),allocatable :: efield(:) contains + procedure :: init => ref_init procedure :: rdcharges => read_charges procedure :: to => ref_to_mol procedure :: load => ref_load_mol @@ -343,6 +373,7 @@ module crest_data real(wp) :: pthrsum real(wp) :: tboltz logical :: cgf(6) !> collection of CREGEN options + integer :: iinversion = 0 !> 0=auto,1=on, 2=off real(wp) :: mdtemps(10) !> different temperatures for the QMDFF-MDs in V1 real(wp) :: mdtime !> MD length (V1&2) @@ -378,24 +409,29 @@ module crest_data logical :: omp_allow_nested = .true. !> allow nested OpenMP threadding !>--- various names and flags - character(len=128) :: ensemblename !> ensemble input name for SCREEN,MDOPT and CREGEN - character(len=128) :: ensemblename2 !> another ensemble input name - character(len=128) :: fixfile - character(len=512) :: constraints !> name of the constraint file - character(len=20) :: solvent !> the solvent + character(len=128) :: ensemblename = '' !> ensemble input name for SCREEN,MDOPT and CREGEN + character(len=128) :: ensemblename2 = '' !> another ensemble input name + character(len=128) :: fixfile = '' + character(len=512) :: constraints = '' !> name of the constraint file + character(len=20) :: solvent = '' !> the solvent character(len=:),allocatable :: solv !> the entrie gbsa flag including solvent - character(len=20) :: gfnver !> GFN version - character(len=20) :: gfnver2 !> GFN version (multilevel) - character(len=20) :: lmover !> GFN version for LMO computation in xtb_lmo subroutine - character(len=512) :: ProgName !> name of the xtb executable (+ path) - character(len=512) :: ProgIFF !> name of xtbiff for QCG-mode - character(len=512) :: homedir !> original directory from which calculation was started - character(len=512) :: scratchdir !> path to the scratch directory + character(len=20) :: gfnver = '' !> GFN version + character(len=20) :: gfnver2 = '' !> GFN version (multilevel) + character(len=:),allocatable :: orca_template !> ORCA input template (--orca) + character(len=:),allocatable :: orca_cmd !> ORCA executable path (--orca) + character(len=40) :: rerank_lvl = '' !> method for post-search SP reranking (--rerank) + character(len=40) :: reopt_lvl = '' !> method for post-search geo-opt standalone (--reopt) + character(len=20) :: lmover = '' !> GFN version for LMO computation in xtb_lmo subroutine + character(len=512) :: ProgName = '' !> name of the xtb executable (+ path) + character(len=512) :: ProgIFF = '' !> name of xtbiff for QCG-mode + character(len=512) :: homedir = '' !> original directory from which calculation was started + character(len=512) :: scratchdir = '' !> path to the scratch directory character(len=:),allocatable :: cmd character(len=:),allocatable :: inputcoords character(len=:),allocatable :: wbofile character(len=:),allocatable :: atlist character(len=:),allocatable :: chargesfilename + character(len=:),allocatable :: sortmode !>--- METADYN data real(wp) :: hmass @@ -430,11 +466,11 @@ module crest_data type(protobj) :: protb !>--- saved constraints - type(constra) :: cts + type(legacy_constraints) :: cts !>--- NCI mode data real(wp) :: potscal = 1.0_wp - real(wp) :: potpad = 0.0_wp + real(wp) :: potpad = 0.0_wp character(len=:),allocatable :: potatlist !>--- Nanoreactor data @@ -451,6 +487,14 @@ module crest_data !>--- reference structure data (the input structure) type(refdata) :: ref + !>--- the reference mol may be partitioned into subfragments + !> the corresponding bookkeeping data is saved here + logical :: substructure_queue = .false. + type(split_atms),allocatable :: splitqueue(:) + type(construct_heap) :: splitheap + integer :: queue_iter = 0 + integer :: queue_maxreconstruct = 7500 + real(wp) :: queue_depthfac = 0.85_wp !>--- QCG data integer :: qcg_runtype = 0 !> Default is grow, 1= ensemble & opt, 2= e_solv, 3= g_solv @@ -458,11 +502,11 @@ module crest_data integer :: nqcgclust = 0 !> Number of cluster to be taken integer :: max_solv = 0 !> Maximal number of solvents added, if none is given integer :: ensemble_method = -1 !> Default -1 for qcgmtd, 0= crest, 1= standard MD, 2= MTD - character(len=:), allocatable :: directed_file !name of the directed list - character(len=64), allocatable :: directed_list(:,:) !How many solvents at which atom to add - integer, allocatable :: directed_number(:) !Numbers of solvents added per defined atom - character(len=20) :: ensemble_opt !> Method for ensemble optimization in qcg mode - character(len=20) :: freqver !> Method for frequency computation in qcg mode + character(len=:),allocatable :: directed_file !name of the directed list + character(len=64),allocatable :: directed_list(:,:) !How many solvents at which atom to add + integer,allocatable :: directed_number(:) !Numbers of solvents added per defined atom + character(len=20) :: ensemble_opt = '' !> Method for ensemble optimization in qcg mode + character(len=20) :: freqver = '' !> Method for frequency computation in qcg mode real(wp) :: freq_scal !> Frequency scaling factor character(len=:),allocatable :: solu_file,solv_file !> solute and solvent input file character(len=5) :: docking_qcg_flag = '--qcg' @@ -496,8 +540,9 @@ module crest_data !================================================! !>--- Calculation settings for newer implementations (version >= 3.0) - type(calcdata) :: calc + type(calcdata),pointer :: calc type(mddata) :: mddat + type(bh_class),allocatable :: bh_ref !>--- rigidconf data integer :: rigidconf_algo = 0 integer :: rigidconf_toposource = 0 @@ -510,14 +555,14 @@ module crest_data !================================================! !>--- msreact mode settings - logical :: msei =.true. ! use the ei mode as default - logical :: mscid =.false. ! use the cid mode - logical :: msnoiso =.false. ! print only dissociated structures in msreact - logical :: msiso =.false. ! only print non-dissociated structures in msreact - logical :: msmolbar =.false. ! sort out duplicates by molbar - logical :: msinchi =.false. ! sort out duplicates by inchi - logical :: mslargeprint=.false. ! dont remove temporary files - logical :: msattrh=.true. ! add attractive potential for H-atoms + logical :: msei = .true. ! use the ei mode as default + logical :: mscid = .false. ! use the cid mode + logical :: msnoiso = .false. ! print only dissociated structures in msreact + logical :: msiso = .false. ! only print non-dissociated structures in msreact + logical :: msmolbar = .false. ! sort out duplicates by molbar + logical :: msinchi = .false. ! sort out duplicates by inchi + logical :: mslargeprint = .false. ! dont remove temporary files + logical :: msattrh = .true. ! add attractive potential for H-atoms integer :: msnbonds = 3 ! distance of bonds up to nonds are stretched integer :: msnshifts = 0 ! number of random shifts applied to whole mol integer :: msnshifts2 = 0 ! number of random shifts applied to whole mol @@ -526,11 +571,15 @@ module crest_data !>--- general logical data logical :: allrot = .true. !> use all rotational constants for check instead of mean? + logical :: alkylize = .false. !> alkylization setting + logical :: alkylizeskip = .true. !> alkylization sampling skip logical :: altopt = .false. - logical :: autothreads !> automatically determine threads - logical :: autozsort !> do the ZSORT in the beginning ? + logical :: autothreads = .true. !> automatically determine threads + logical :: autozsort = .false. !> do the ZSORT in the beginning ? logical :: allowrestart = .true. !> allow restart in crest algos? - logical :: better !> found a better conformer and restart in V1 + logical :: better = .false. !> found a better conformer and restart in V1 + logical :: ceh_guess = .false. !> use CEH guess in tblite or gfnff, if available + logical :: spin_polarized = .false. !> enable spin-polarized calculations logical :: cff !> CFF used in QCG-energy calculation logical :: cluster = .false. !> perform a clustering analysis logical :: checktopo = .true. !> perform topolgy check in CREGEN @@ -540,7 +589,7 @@ module crest_data logical :: confgo !> perform only the CREGEN routine ? logical :: constrain_solu !> constrain the solute logical :: crest_ohess = .false. !> append numerical Hessian after optimization - logical :: doNMR !> determine NMR equivalencies in CREGEN ? + logical :: doNMR = .false. !> determine NMR equivalencies in CREGEN ? logical :: dryrun = .false. !> dryrun to print settings logical :: ENSO !> some options for usage of CREST within ENSO logical :: ens_const = .false. !> constrain solute also in Ensemble generation @@ -551,24 +600,24 @@ module crest_data logical :: extLFER = .false. !> read in external LFER parameters logical :: FINAL_GFN2_OPT = .false. logical :: fullcre = .false. !> calculate exact rotamer degeneracies - logical :: gbsa !> use gbsa + logical :: gbsa = .false. !> use gbsa logical :: gcmultiopt !> 2 level optimization for GC in V2 logical :: gradsp = .true. !> turn on/off gradient calculation in singlepoint logical :: heavyrmsd = .false. !> use only heavy atoms for RMSD in CREGEN? logical :: inplaceMode = .true. !> in-place mode: optimization dirs are created "on-the-fly" - logical :: iterativeV2 !> iterative version of V2 (= V3) + logical :: iterativeV2 = .true. !> iterative version of V2 (= V3) logical :: iru !> re-use previously found conformers as bias in iterative approach logical :: keepModef !> keep MODEF* dirs in V1 ? logical :: keepScratch = .false. !> keep scratch directory or delete it? logical :: legacy = .false. !> switch between the original system call routines of crest and newer, e.g. tblite implementations logical :: metadynset !> is the number of MTDs already set (V2) ? logical :: methautocorr !> try to automatically include Methyl equivalencies in CREGEN ? - logical :: multilevelopt =.true. !> perform the multileveloptimization + logical :: multilevelopt = .true. !> perform the multileveloptimization logical :: newcregen = .false. !> use the CREGEN rewrite - logical :: NCI !> NCI special usage - logical :: niceprint !> make a nice progress-bar printout + logical :: NCI = .false. !> NCI special usage + logical :: niceprint = .false. !> make a nice progress-bar printout logical :: noconst = .false. !> no constrain of solute during QCG Growth - logical :: onlyZsort !> do only the ZSORT routine ? + logical :: onlyZsort = .false. !> do only the ZSORT routine ? logical :: optpurge = .false. !> MDOPT purge application logical :: outputsdf = .false. !> write output ensemble as sdf? logical :: pcaexclude = .false. !> exclude user set atoms from PCA? @@ -595,10 +644,10 @@ module crest_data logical :: riso = .false. !> take only isomers in reactor mode logical :: rotamermds !> do additional MDs after second multilevel OPT step in V2 ? logical :: refine_presort = .false. !> run CREGEN at the beginning of crest_refine? - logical :: refine_esort = .false. !> if CREGEN is run after crest_refine, only sort energy? + logical :: refine_esort = .false. !> if CREGEN is run after crest_refine, only sort energy? logical :: sameRandomNumber = .false. !> QCG related, choose same random number for iff logical :: scallen !> scale the automatically determined MD length by some factor? - logical :: scratch !> use scratch directory + logical :: scratch = .false. !> use scratch directory logical :: setgcmax = .false. !> adjust the maxmimum number of structures taken into account for GC? logical :: sdfformat = .false. !> was the SDF format used as input file? logical :: slow !> slowmode (counterpart to quick mode) @@ -617,10 +666,11 @@ module crest_data logical :: user_nclust = .false. !> true if number of cluster is set by user (only QCG) logical :: user_dumxyz = .false. !> true if dumpxyz is set by user logical :: user_wscal = .false. !> true if wscal is set by user - logical :: useqmdff !> use QMDFF in V2? + logical :: useqmdff = .false. !> use QMDFF in V2? logical :: water = .false. !> true if water is used as solvent (only QCG) logical :: wallsetup = .false. !> set up a wall potential? logical :: wbotopo = .false. !> set up topo with WBOs + type(progress_state) :: ps !> terminal progress bar state contains procedure :: allocate => allocate_metadyn procedure :: deallocate => deallocate_metadyn @@ -629,6 +679,8 @@ module crest_data procedure :: rmhy => pqueue_removehybrid procedure :: addrefine => add_to_refinequeue procedure :: wrtCHRG => wrtCHRG + procedure :: addsplitqueue => env_addsplitqueue + procedure :: copy => systemdata_copy end type systemdata !========================================================================================! @@ -653,7 +705,6 @@ subroutine allocate_metadyn(self,n) end if return end subroutine allocate_metadyn -!========================================================================================! subroutine deallocate_metadyn(self) implicit none class(systemdata) :: self @@ -662,23 +713,56 @@ subroutine deallocate_metadyn(self) if (allocated(self%metadlist)) deallocate (self%metadlist) end subroutine deallocate_metadyn !========================================================================================! - subroutine allocate_constraints(self,n) + subroutine allocate_legacy_constraints(self,n) implicit none - class(constra) :: self + class(legacy_constraints) :: self integer,intent(in) :: n self%ndim = n allocate (self%sett(n)) allocate (self%buff(n)) self%sett = '' self%buff = '' - end subroutine allocate_constraints -!========================================================================================! - subroutine deallocate_constraints(self) + end subroutine allocate_legacy_constraints + + subroutine deallocate_legacy_constraints(self) implicit none - class(constra) :: self + class(legacy_constraints) :: self if (allocated(self%sett)) deallocate (self%sett) if (allocated(self%buff)) deallocate (self%buff) - end subroutine deallocate_constraints + end subroutine deallocate_legacy_constraints + + subroutine legacy_constraints_info(self) + implicit none + class(legacy_constraints) :: self + integer :: i + write (*,*) "legacy constraints set?",self%used + if (self%used) then + do i = 1,self%ndim + if (trim(self%sett(i)) .ne. '') then + write (*,'(a)') trim(self%sett(i)) + end if + end do + end if + + write (*,*) 'legacy constraints NCI?',self%NCI + if (self%NCI.and.allocated(self%pots)) then + do i = 1,10 + if (trim(self%pots(i)) .ne. '') then + write (*,'(a)') trim(self%pots(i)) + end if + end do + end if + + write (*,*) 'legacy constraints CBONDS?',allocated(self%cbonds) + if (allocated(self%cbonds)) then + do i = 1,min(10,self%n_cbonds) + if (trim(self%cbonds(i)) .ne. '') then + write (*,'(a)') trim(self%cbonds(i)) + end if + end do + if (self%n_cbonds > 10) write (*,*) '... and some more' + end if + end subroutine legacy_constraints_info !========================================================================================! !========================================================================================! @@ -752,14 +836,13 @@ subroutine pqueue_removehybrid(self) return end subroutine pqueue_removehybrid - subroutine add_to_refinequeue(self,refinetype) implicit none class(systemdata) :: self integer :: refinetype integer :: idum integer,allocatable :: qdum(:) - if( refinetype <= 0 ) return + if (refinetype <= 0) return if (.not.allocated(self%refine_queue)) then allocate (self%refine_queue(1)) self%refine_queue(1) = refinetype @@ -768,11 +851,18 @@ subroutine add_to_refinequeue(self,refinetype) allocate (qdum(idum+1)) qdum(1:idum) = self%refine_queue(1:idum) qdum(idum+1) = refinetype - call move_alloc(qdum,self%pqueue) + call move_alloc(qdum,self%refine_queue) end if return end subroutine add_to_refinequeue + subroutine env_addsplitqueue(self,raw_split) + implicit none + class(systemdata) :: self + integer,intent(in) :: raw_split(:) + call add_to_splitqueue(self%splitqueue,raw_split) + end subroutine env_addsplitqueue + !========================================================================================! !========================================================================================! !> write a .CHRG (and .UHF) file in the specified dir, but only if it is needed @@ -827,6 +917,15 @@ subroutine wrtCHRG(self,dir) end subroutine wrtCHRG !========================================================================================! + subroutine ref_init(self,nat) + class(refdata) :: self + integer,intent(in) :: nat + if (allocated(self%at)) deallocate (self%at) + if (allocated(self%xyz)) deallocate (self%xyz) + allocate (self%at(nat),source=0) + allocate (self%xyz(3,nat),source=0.0_wp) + end subroutine ref_init + !> read atomic charges from a file (one line per atom) subroutine read_charges(self,chargesfilename,totchrg) implicit none @@ -858,10 +957,10 @@ end subroutine read_charges subroutine ref_to_mol(self,mol) implicit none class(refdata) :: self - type(coord) :: mol + class(coord) :: mol mol%nat = self%nat - if(allocated(self%at)) mol%at = self%at - if(allocated(self%xyz)) mol%xyz = self%xyz + if (allocated(self%at)) mol%at = self%at + if (allocated(self%xyz)) mol%xyz = self%xyz mol%chrg = self%ichrg mol%uhf = self%uhf return @@ -870,12 +969,13 @@ end subroutine ref_to_mol subroutine ref_load_mol(self,mol) implicit none class(refdata) :: self - type(coord) :: mol - self%nat = mol%nat - self%at = mol%at - self%xyz = mol%xyz - self%ichrg = mol%chrg - self%uhf = mol%uhf + class(coord) :: mol + call self%init(mol%nat) + self%nat = mol%nat + self%at = mol%at + self%xyz = mol%xyz + self%ichrg = mol%chrg + self%uhf = mol%uhf return end subroutine ref_load_mol @@ -898,27 +998,31 @@ function optlevflag(optlev) result(flag) return end function optlevflag - function optlevnum(flag) result(optlev) + function optlevnum(flag,iostat) result(optlev) implicit none real(wp) :: optlev - character(len=*):: flag + character(len=*),intent(in) :: flag + integer,intent(out),optional :: iostat + if (present(iostat)) iostat = 0 optlev = 0.0_wp - if (index(flag,'crude') .ne. 0) optlev = -3.0d0 - if (index(flag,'loose') .ne. 0) optlev = -1.0d0 - if (index(flag,'vloose') .ne. 0) optlev = -2.0d0 - if (index(flag,'sloppy') .ne. 0) optlev = -2.0d0 - if (index(flag,'normal') .ne. 0) optlev = 0.0d0 - if (index(flag,'tight') .ne. 0) optlev = 1.0d0 - if (index(flag,'verytight') .ne. 0) optlev = 2.0d0 - if (index(flag,'vtight') .ne. 0) optlev = 2.0d0 - if (index(flag,'extreme') .ne. 0) optlev = 3.0d0 - if (index(flag,'3') .ne. 0) optlev = 3.0d0 - if (index(flag,'2') .ne. 0) optlev = 2.0d0 - if (index(flag,'1') .ne. 0) optlev = 1.0d0 - if (index(flag,'0') .ne. 0) optlev = 0.0d0 - if (index(flag,'-3') .ne. 0) optlev = -3.0d0 - if (index(flag,'-2') .ne. 0) optlev = -2.0d0 - if (index(flag,'-1') .ne. 0) optlev = -1.0d0 + select case (trim(adjustl(flag))) + case ('crude','-3') + optlev = -3.0_wp + case ('vloose','sloppy','-2') + optlev = -2.0_wp + case ('loose','-1') + optlev = -1.0_wp + case ('normal','0') + optlev = 0.0_wp + case ('tight','1') + optlev = 1.0_wp + case ('verytight','vtight','2') + optlev = 2.0_wp + case ('extreme','3') + optlev = 3.0_wp + case default + if (present(iostat)) iostat = 1 + end select return end function optlevnum @@ -939,23 +1043,23 @@ subroutine optlev_to_multilev(optlev,multilev) real(wp),intent(in) :: optlev logical,intent(out) :: multilev(6) integer :: j - if (optlev <= 3.0d0)then !> "extreme" thresholds + if (optlev <= 3.0d0) then !> "extreme" thresholds multilev(:) = .false. multilev(6) = .true. multilev(4) = .true. multilev(1) = .true. - endif + end if j = optlevmap_alt(optlev) - j = max(j-1, 1) !> j is reduced by one - if (optlev <= 2.0d0)then !> "normal" to "vtight" - multilev(:) = .false. - multilev(1) = .true. - multilev(j) = .true. - endif - if (optlev <= -1.0d0)then !> "loose" to "crude" - multilev(:) = .false. - multilev(j) = .true. - endif + j = max(j-1,1) !> j is reduced by one + if (optlev <= 2.0d0) then !> "normal" to "vtight" + multilev(:) = .false. + multilev(1) = .true. + multilev(j) = .true. + end if + if (optlev <= -1.0d0) then !> "loose" to "crude" + multilev(:) = .false. + multilev(j) = .true. + end if end subroutine optlev_to_multilev !========================================================================================! @@ -1023,6 +1127,369 @@ subroutine thermo_read_temps(self,fname) return end subroutine thermo_read_temps + function thermo_get_close_rt(self,nrt) result(temp) + implicit none + class(thermodata) :: self + integer,intent(out) :: nrt + real(wp) :: temp + integer :: i,nt,io,ich + real(wp),allocatable :: tmptemps(:) + nrt = 0 + nt = self%ntemps + allocate (tmptemps(nt),source=0.0_wp) + tmptemps(:) = abs(self%temps(:)-298.15_wp) + nrt = minloc(tmptemps,1) + temp = self%temps(nrt) + end function thermo_get_close_rt +!========================================================================================! +!========================================================================================! + + subroutine systemdata_copy(self,src) +!************************************************************* +!* Deep copy of a systemdata object from src to self. * +!* * +!* On Input: src - source systemdata object * +!* On Output: self - destination, populated with src data * +!* * +!* NOTE: Some embedded derived types (mddat, splitqueue, * +!* splitheap, bh_ref, ONIOM_input) are assigned via * +!* Fortran intrinsic assignment, which may only be a * +!* shallow copy if those types contain pointer * +!* components. See individual placeholder comments. * +!************************************************************* + implicit none + class(systemdata),intent(out) :: self + type(systemdata),intent(in) :: src + +! ── run-control integers ────────────────────────────────────────────────────── + self%iostatus_meta = src%iostatus_meta + self%crestver = src%crestver + self%runver = src%runver + self%properties = src%properties + self%properties2 = src%properties2 + self%npq = src%npq + if (allocated(src%pqueue)) self%pqueue = src%pqueue + +! ── CREGEN thresholds ───────────────────────────────────────────────────────── + self%level = src%level + self%thresholds = src%thresholds + self%ewin = src%ewin + self%ethr = src%ethr + self%ethrpurge = src%ethrpurge + self%couthr = src%couthr + self%rthr = src%rthr + self%bthr = src%bthr + self%bthr2 = src%bthr2 + self%bthrmax = src%bthrmax + self%bthrshift = src%bthrshift + self%athr = src%athr + self%pthr = src%pthr + self%pthrsum = src%pthrsum + self%tboltz = src%tboltz + self%cgf = src%cgf + self%iinversion = src%iinversion + +! ── MD / algo control ───────────────────────────────────────────────────────── + self%mdtemps = src%mdtemps + self%mdtime = src%mdtime + self%elowest = src%elowest + self%eprivious = src%eprivious + self%gcmax = src%gcmax + self%gcmaxparent = src%gcmaxparent + self%icount = src%icount + self%mdmode = src%mdmode + self%nmodes = src%nmodes + self%temps = src%temps + self%snapshots = src%snapshots + self%Maxrestart = src%Maxrestart + self%nreset = src%nreset + self%nrotammds = src%nrotammds + self%maxcompare = src%maxcompare + self%tsplit = src%tsplit + +! ── molecular data ──────────────────────────────────────────────────────────── + self%nat = src%nat + self%chrg = src%chrg + self%uhf = src%uhf + self%rednat = src%rednat + self%optlev = src%optlev + self%forceconst = src%forceconst + self%dummypercent = src%dummypercent + +! ── parallelization ─────────────────────────────────────────────────────────── + self%MAXRUN = src%MAXRUN + self%omp = src%omp + self%Threads = src%Threads + self%omp_allow_nested = src%omp_allow_nested + +! ── fixed-length names and flags ────────────────────────────────────────────── + self%ensemblename = src%ensemblename + self%ensemblename2 = src%ensemblename2 + self%fixfile = src%fixfile + self%constraints = src%constraints + self%solvent = src%solvent + self%gfnver = src%gfnver + self%gfnver2 = src%gfnver2 + self%rerank_lvl = src%rerank_lvl + self%reopt_lvl = src%reopt_lvl + self%lmover = src%lmover + self%ProgName = src%ProgName + self%ProgIFF = src%ProgIFF + self%homedir = src%homedir + self%scratchdir = src%scratchdir + +! ── allocatable character fields ────────────────────────────────────────────── + if (allocated(src%solv)) self%solv = src%solv + if (allocated(src%cmd)) self%cmd = src%cmd + if (allocated(src%inputcoords)) self%inputcoords = src%inputcoords + if (allocated(src%wbofile)) self%wbofile = src%wbofile + if (allocated(src%atlist)) self%atlist = src%atlist + if (allocated(src%chargesfilename)) self%chargesfilename = src%chargesfilename + if (allocated(src%sortmode)) self%sortmode = src%sortmode + +! ── METADYN scalar settings ─────────────────────────────────────────────────── + self%hmass = src%hmass + self%mdtemp = src%mdtemp + self%nmdtemp = src%nmdtemp + self%mdstep = src%mdstep + self%mdlenfac = src%mdlenfac + self%tmtd = src%tmtd + self%flexi = src%flexi + self%shake = src%shake + self%mddumpxyz = src%mddumpxyz + self%mdskip = src%mdskip + self%mddump = src%mddump + self%maxopt = src%maxopt + self%hlowopt = src%hlowopt + self%microopt = src%microopt + self%s6opt = src%s6opt + self%mtd_kscal = src%mtd_kscal + self%nstatic = src%nstatic + +! ── METADYN allocatable arrays ──────────────────────────────────────────────── + self%nmetadyn = src%nmetadyn + if (allocated(src%metadfac)) self%metadfac = src%metadfac + if (allocated(src%metadexp)) self%metadexp = src%metadexp + if (allocated(src%metadlist)) self%metadlist = src%metadlist + if (allocated(src%mtdstaticfile)) self%mtdstaticfile = src%mtdstaticfile + if (allocated(src%includeRMSD)) self%includeRMSD = src%includeRMSD + if (allocated(src%excludeTOPO)) self%excludeTOPO = src%excludeTOPO + +! ── NCI / reactor settings ──────────────────────────────────────────────────── + self%potscal = src%potscal + self%potpad = src%potpad + self%rdens = src%rdens + self%tempfermi = src%tempfermi + self%XH3 = src%XH3 + self%kappa = src%kappa + if (allocated(src%potatlist)) self%potatlist = src%potatlist + +! ── embedded derived types (allocatable-only internals; intrinsic =) ────────── + self%protb = src%protb !> protobj: allocatables only, intrinsic = is deep + self%cts = src%cts !> legacy_constraints: allocatables only, intrinsic = is deep + self%eMTD = src%eMTD !> entropyMTD: allocatables only, intrinsic = is deep + self%thermo = src%thermo !> thermodata: allocatables only, intrinsic = is deep + self%ref = src%ref !> refdata: allocatables only, intrinsic = is deep + +! ── calc pointer: allocate new target and deep-copy ─────────────────────────── + if (associated(src%calc)) then + if (.not. associated(self%calc)) allocate (self%calc) + call self%calc%copy(src%calc) + end if + +! ── placeholder: mddat (mddata) ─────────────────────────────────────────────── +! mddata may contain pointer components; use intrinsic = as placeholder. + self%mddat = src%mddat + +! ── placeholder: bh_ref (bh_class) ─────────────────────────────────────────── +! bh_class may contain pointer components; use intrinsic = as placeholder. + if (allocated(src%bh_ref)) then + if (.not. allocated(self%bh_ref)) allocate (self%bh_ref,source=src%bh_ref) + end if + +! ── rigidconf settings ──────────────────────────────────────────────────────── + self%rigidconf_algo = src%rigidconf_algo + self%rigidconf_toposource = src%rigidconf_toposource + if (allocated(src%rigidconf_userfile)) self%rigidconf_userfile = src%rigidconf_userfile + if (allocated(src%refine_queue)) self%refine_queue = src%refine_queue + if (allocated(src%ONIOM_toml)) self%ONIOM_toml = src%ONIOM_toml + +! ── placeholder: ONIOM_input (lwoniom_input) ────────────────────────────────── +! lwoniom_input may contain pointer components; use intrinsic = as placeholder. + if (allocated(src%ONIOM_input)) then + if (.not. allocated(self%ONIOM_input)) allocate (self%ONIOM_input,source=src%ONIOM_input) + end if + +! ── substructure queue ──────────────────────────────────────────────────────── + self%substructure_queue = src%substructure_queue + self%queue_iter = src%queue_iter + self%queue_maxreconstruct = src%queue_maxreconstruct + self%queue_depthfac = src%queue_depthfac +! splitqueue (split_atms) and splitheap (construct_heap): placeholder + if (allocated(src%splitqueue)) self%splitqueue = src%splitqueue + self%splitheap = src%splitheap + +! ── QCG settings ───────────────────────────────────────────────────────────── + self%qcg_runtype = src%qcg_runtype + self%nsolv = src%nsolv + self%nqcgclust = src%nqcgclust + self%max_solv = src%max_solv + self%ensemble_method = src%ensemble_method + self%ensemble_opt = src%ensemble_opt + self%freqver = src%freqver + self%freq_scal = src%freq_scal + self%docking_qcg_flag = src%docking_qcg_flag + if (allocated(src%directed_file)) self%directed_file = src%directed_file + if (allocated(src%directed_list)) self%directed_list = src%directed_list + if (allocated(src%directed_number)) self%directed_number = src%directed_number + if (allocated(src%solu_file)) self%solu_file = src%solu_file + if (allocated(src%solv_file)) self%solv_file = src%solv_file + +! ── clustering settings ─────────────────────────────────────────────────────── + self%maxcluster = src%maxcluster + self%nclust = src%nclust + self%pccap = src%pccap + self%pcthr = src%pcthr + self%pcmin = src%pcmin + self%csthr = src%csthr + self%clustlev = src%clustlev + if (allocated(src%pcmeasure)) self%pcmeasure = src%pcmeasure + +! ── structure generation / bias settings ────────────────────────────────────── + self%doOHflip = src%doOHflip + self%maxflip = src%maxflip + self%rthr2 = src%rthr2 + self%kshift = src%kshift + self%kshiftnum = src%kshiftnum + self%gescoptlev = src%gescoptlev + if (allocated(src%biasfile)) self%biasfile = src%biasfile + +! ── DFT driver (deprecated) ─────────────────────────────────────────────────── + self%hardcutDFT = src%hardcutDFT + self%harcutpthr = src%harcutpthr + self%hardcutnst = src%hardcutnst + if (allocated(src%dftrcfile)) self%dftrcfile = src%dftrcfile + +! ── msreact settings ────────────────────────────────────────────────────────── + self%msei = src%msei + self%mscid = src%mscid + self%msnoiso = src%msnoiso + self%msiso = src%msiso + self%msmolbar = src%msmolbar + self%msinchi = src%msinchi + self%mslargeprint = src%mslargeprint + self%msattrh = src%msattrh + self%msnbonds = src%msnbonds + self%msnshifts = src%msnshifts + self%msnshifts2 = src%msnshifts2 + self%msnfrag = src%msnfrag + self%msinput = src%msinput + +! ── general logical flags ───────────────────────────────────────────────────── + self%allrot = src%allrot + self%alkylize = src%alkylize + self%alkylizeskip = src%alkylizeskip + self%altopt = src%altopt + self%autothreads = src%autothreads + self%autozsort = src%autozsort + self%allowrestart = src%allowrestart + self%better = src%better + self%ceh_guess = src%ceh_guess + self%spin_polarized = src%spin_polarized + self%cff = src%cff + self%cluster = src%cluster + self%checktopo = src%checktopo + self%checkiso = src%checkiso + self%chargesfile = src%chargesfile + self%compareens = src%compareens + self%confgo = src%confgo + self%constrain_solu = src%constrain_solu + self%crest_ohess = src%crest_ohess + self%doNMR = src%doNMR + self%dryrun = src%dryrun + self%ENSO = src%ENSO + self%ens_const = src%ens_const + self%entropic = src%entropic + self%entropymd = src%entropymd + self%esort = src%esort + self%ext = src%ext + self%extLFER = src%extLFER + self%FINAL_GFN2_OPT = src%FINAL_GFN2_OPT + self%fullcre = src%fullcre + self%gbsa = src%gbsa + self%gcmultiopt = src%gcmultiopt + self%gradsp = src%gradsp + self%heavyrmsd = src%heavyrmsd + self%inplaceMode = src%inplaceMode + self%iterativeV2 = src%iterativeV2 + self%iru = src%iru + self%keepModef = src%keepModef + self%keepScratch = src%keepScratch + self%legacy = src%legacy + self%metadynset = src%metadynset + self%methautocorr = src%methautocorr + self%multilevelopt = src%multilevelopt + self%newcregen = src%newcregen + self%NCI = src%NCI + self%niceprint = src%niceprint + self%noconst = src%noconst + self%onlyZsort = src%onlyZsort + self%optpurge = src%optpurge + self%outputsdf = src%outputsdf + self%pcaexclude = src%pcaexclude + self%pclean = src%pclean + self%performCross = src%performCross + self%performMD = src%performMD + self%performModef = src%performModef + self%performMTD = src%performMTD + self%preactormtd = src%preactormtd + self%preactorpot = src%preactorpot + self%preopt = src%preopt + self%presp = src%presp + self%printscoords = src%printscoords + self%QCG = src%QCG + self%qcg_flag = src%qcg_flag + self%qcg_restart = src%qcg_restart + self%nopreopt = src%nopreopt + self%quick = src%quick + self%readbias = src%readbias + self%reftopo = src%reftopo + self%relax = src%relax + self%restartopt = src%restartopt + self%reweight = src%reweight + self%riso = src%riso + self%rotamermds = src%rotamermds + self%refine_presort = src%refine_presort + self%refine_esort = src%refine_esort + self%sameRandomNumber = src%sameRandomNumber + self%scallen = src%scallen + self%scratch = src%scratch + self%setgcmax = src%setgcmax + self%sdfformat = src%sdfformat + self%slow = src%slow + self%solv_md = src%solv_md + self%staticmtd = src%staticmtd + self%subRMSD = src%subRMSD + self%superquick = src%superquick + self%threadssetmanual = src%threadssetmanual + self%trackorigin = src%trackorigin + self%testnumgrad = src%testnumgrad + self%use_xtbiff = src%use_xtbiff + self%user_enslvl = src%user_enslvl + self%user_temp = src%user_temp + self%user_mdtime = src%user_mdtime + self%user_mdstep = src%user_mdstep + self%user_nclust = src%user_nclust + self%user_dumxyz = src%user_dumxyz + self%user_wscal = src%user_wscal + self%useqmdff = src%useqmdff + self%water = src%water + self%wallsetup = src%wallsetup + self%wbotopo = src%wbotopo + + return + end subroutine systemdata_copy + !========================================================================================! !========================================================================================! end module crest_data diff --git a/src/cleanup.f90 b/src/cleanup.f90 index c3f43f06..8a6a0d0b 100644 --- a/src/cleanup.f90 +++ b/src/cleanup.f90 @@ -72,9 +72,9 @@ subroutine custom_cleanup(env) call rmrf('cregen_*.tmp') call rmrf('MDFILES') if(allocated(env%calc%calcs))then - if(.not.any(env%calc%calcs(:)%pr))then + !if(.not.any(env%calc%calcs(:)%pr))then call rmrfw('calculation.level.') - endif + !endif endif endif call rmrf('.CHRG .UHF') diff --git a/src/confparse.f90 b/src/confparse.f90 index 203eec7e..014ebd91 100644 --- a/src/confparse.f90 +++ b/src/confparse.f90 @@ -46,7 +46,8 @@ subroutine parseflags(env,arg,nra) use optimize_module use parse_inputfile use crest_restartlog - use lwoniom_module + use parse_hybrid + implicit none type(systemdata),intent(inout) :: env integer,intent(in) :: nra @@ -61,10 +62,17 @@ subroutine parseflags(env,arg,nra) integer :: ctype logical :: ex,bondconst character(len=:),allocatable :: argument + logical,allocatable :: processedarg(:) + logical,allocatable :: atlist(:) + character(len=:),allocatable :: arg1,arg2,arg3 + character(len=:),allocatable :: hybrid_quality,hybrid_workhorse + character(len=4) :: hybrid_mode allocate (xx(10),floats(3),strings(3)) ctmp = '' dtmp = '' + + allocate (processedarg(nra),source=.false.) !========================================================================================! !>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! !> Set the defaults @@ -85,14 +93,15 @@ subroutine parseflags(env,arg,nra) if (.not.gui) then call confscript_head(.false.) - write (*,'(/,1x,a)') 'Command line input:' - write (*,'(1x,a,a,/)') '$ ',trim(cmd) + write (stdout,'(/,1x,a)') 'Command line input:' + write (stdout,'(1x,a,a,/)') '$ ',trim(cmd) end if env%cmd = trim(cmd) !=========================================================================================! !>--- check if help is requested or citations shall be diplayed do i = 1,nra + if (processedarg(i)) cycle if (any((/character(6)::'-h','-H','--h','--H','--help'/) == trim(arg(i)))) then if (nra > i) then ctmp = trim(arg(i+1)) @@ -107,12 +116,15 @@ subroutine parseflags(env,arg,nra) end if if (index(arg(i),'-newversion') .ne. 0) then !> as in CREST version >= 3.0 env%legacy = .false. + !processedarg(i) = .true. end if if (index(arg(i),'-legacy') .ne. 0) then !> as in CREST version <3.0 env%legacy = .true. + !processedarg(i) = .true. end if if (index(arg(i),'-dry') .ne. 0) then !> "dry" run to print settings env%dryrun = .true. + processedarg(i) = .true. end if end do @@ -194,7 +206,7 @@ subroutine parseflags(env,arg,nra) env%multilevelopt = .true. !> perform multilevel optimization env%trackorigin = .true. !> for v2 track generation step by default env%compareens = .false. !> compare two given ensembles - env%maxcompare = 10 !> maximum number of (lowest) conformers to compare when using "-compare" + env%maxcompare = 100 !> maximum number of (lowest) structures to compare when using "-compare" env%QCG = .false. !> special QCG usage !>--- The following settings are mainly for v.1 (MF-MD-GC) @@ -256,6 +268,7 @@ subroutine parseflags(env,arg,nra) end if inquire (file='.UHF',exist=ex) if (any(index(arg,'-uhf') .ne. 0)) ex = .false. + if (any(index(arg,'-mult') .ne. 0)) ex = .false. if (ex) then write(stdout,*) '**ERROR** CREST will not read .UHF files following version 3.0.2' write(stdout,*) 'Please use --uhf or the input file specifications.' @@ -282,14 +295,15 @@ subroutine parseflags(env,arg,nra) !>--- options for principal component analysis (PCA) and clustering env%pcmeasure = 'dihedral' -!>--- thermo options +!>--- Standard THERMO options env%thermo%trange(1) = 278.15d0 !> T start env%thermo%trange(2) = 380.0d0 !> T stop (approx.) env%thermo%trange(3) = 10.0d0 !> T step - env%thermo%ptot = 0.9d0 !> for hessians take x% conformers - env%thermo%pcap = 50000 !> limit number of structures - env%thermo%sthr = 25.0d0 !> rotor cutoff - env%thermo%fscal = 1.0d0 !> frequency scaling factor + env%thermo%ptot = 0.9d0 !> for hessians take x% conformers + env%thermo%pcap = 50000 !> limit number of structures + env%thermo%sthr = 25.0d0 !> rotor cutoff + env%thermo%fscal = 1.0d0 !> frequency scaling factor + env%thermo%emodel = 'grimme' !> Svib treatment !>--- other things env%crest_ohess = .false. @@ -324,7 +338,7 @@ subroutine parseflags(env,arg,nra) !=========================================================================================! !=========================================================================================! !>--- get the CREST version/runtype - env%crestver = crest_imtd !> confscript version (v.1 = MF-MD-GC, v.2 = MTD) + env%crestver = crest_none !> no runtype selected — must be set explicitly env%runver = 1 !> default env%properties = p_none !> additional calculations/options before or after confsearch env%properties2 = p_none !> backup for env%properties @@ -332,13 +346,21 @@ subroutine parseflags(env,arg,nra) env%preopt = .true. !>--- check for (TOML) input file call find_input_file(arg,nra,idum) - if(idum.ne.0)then + if (idum .ne. 0) then call parseinputfile(env,trim(arg(idum))) - endif + processedarg(idum) = .true. + end if !>--- first arg loop do i = 1,nra + if (processedarg(i)) cycle argument = trim(arg(i)) + arg1 = '' + if (i+1 .le. nra) arg1 = trim(arg(i+1)) + arg2 = '' + if (i+2 .le. nra) arg2 = trim(arg(i+2)) + arg3 = '' + if (i+3 .le. nra) arg3 = trim(arg(i+3)) if (argument(1:2) == '--') then argument = argument(2:) end if @@ -346,77 +368,112 @@ subroutine parseflags(env,arg,nra) select case (argument) !> RUNTYPES case ('-v1') !> confscript version 1 (MF-MD-GC) + processedarg(i) = .true. env%crestver = crest_mfmdgc - write (*,'(2x,a,'' : MF-MD-GC'')') trim(arg(i)) + write (stdout,'(2x,a,'' : MF-MD-GC'')') trim(arg(i)) env%mdtime = 40.0d0 !> simulation length of the MD, 40ps total (2*20ps)(default for QMDFF would be 500) env%temps = 1 !> number of default MD cycles env%Maxrestart = 15 env%performModef = .true. !> do the MF in V1 env%trackorigin = .false. !> for v1 there is not much insight from this + call parseflags_deprecated(argument) + call creststop(status_safety) exit case ('-v2') !> confscript version 2 (MTD-GC) + processedarg(i) = .true. env%crestver = crest_imtd - write (*,'(2x,a,'' : MTD-GC'')') trim(arg(i)) + write (stdout,'(2x,a,'' : MTD-GC'')') trim(arg(i)) env%iterativeV2 = .false. !> iterative crest V2 version env%Maxrestart = 1 !> for non-iterative MTD-GC only exit - case ('-v3','-v2i') !> confscript version 2 but iterativ (iMTD-GC) + case ('-v3','-v2i','-imtdgc') !> confscript version 2 but iterativ (iMTD-GC) + processedarg(i) = .true. env%crestver = crest_imtd env%iterativeV2 = .true. - write (*,'(2x,a,'' : iMTD-GC'')') trim(arg(i)) + write (stdout,'(2x,a,'' : iMTD-GC'')') trim(arg(i)) + exit + + case ('-entropy') !> sMTD-iMTD+entropy extrapol + processedarg(i) = .false. !> THIS IS IMPORTANT, WE READ FURTHER ENTROPY SETTINGS BELOW + env%crestver = crest_imtd + env%iterativeV2 = .true. + write (stdout,'(2x,a,'' : iMTD-sMTD + entropy extrapolation'')') trim(arg(i)) exit case ('-v4') !> sMTD-iMTD (same as entropy mode) + processedarg(i) = .true. env%crestver = crest_imtd2 env%iterativeV2 = .true. env%entropymd = .true. env%rotamermds = .false. env%performCross = .false. env%emtd%maxfallback = 1 - write (*,'(2x,a,'' : iMTD-sMTD'')') trim(arg(i)) + write (stdout,'(2x,a,'' : iMTD-sMTD'')') trim(arg(i)) exit case ('-mdopt','-purge') !> MDOPT + processedarg(i) = .true. env%crestver = crest_mdopt atmp = '' env%preopt = .false. env%ensemblename = 'none selected' if (nra .ge. (i+1)) atmp = adjustl(arg(i+1)) if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1)) then + processedarg(i+1) = .true. env%ensemblename = trim(atmp) + call xyz2coord(env%ensemblename,'coord') !> write coord from lowest structure + env%inputcoords = env%ensemblename !> just for a printout end if - call xyz2coord(env%ensemblename,'coord') !> write coord from lowest structure - env%inputcoords = env%ensemblename !> just for a printout exit case ('-screen') !> SCREEN + processedarg(i) = .true. env%crestver = crest_screen atmp = '' env%ensemblename = 'none selected' if (nra .ge. (i+1)) atmp = adjustl(arg(i+1)) if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1)) then + processedarg(i+1) = .true. env%ensemblename = trim(atmp) + call xyz2coord(env%ensemblename,'coord') !write coord from lowest structure + env%inputcoords = env%ensemblename !just for a printout end if - call xyz2coord(env%ensemblename,'coord') !write coord from lowest structure - env%inputcoords = env%ensemblename !just for a printout exit case ('-mdsp','-ensemblesp') !> Singlepoints along ensemble + processedarg(i) = .true. env%crestver = crest_ensemblesp atmp = '' env%preopt = .false. env%ensemblename = 'none selected' if (nra .ge. (i+1)) atmp = adjustl(arg(i+1)) if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1)) then + processedarg(i+1) = .true. env%ensemblename = trim(atmp) + call xyz2coord(env%ensemblename,'coord') !> write coord from lowest structure + env%inputcoords = env%ensemblename !> just for a printout + end if + exit + + case ('-mdhess','-ensemblehess') !> Hessians + thermochemistry along ensemble + processedarg(i) = .true. + env%crestver = crest_ensemblehess + atmp = '' + env%preopt = .false. + env%ensemblename = 'none selected' + if (nra .ge. (i+1)) atmp = adjustl(arg(i+1)) + if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1)) then + processedarg(i+1) = .true. + env%ensemblename = trim(atmp) + call xyz2coord(env%ensemblename,'coord') !> write coord from lowest structure + env%inputcoords = env%ensemblename !> just for a printout end if - call xyz2coord(env%ensemblename,'coord') !> write coord from lowest structure - env%inputcoords = env%ensemblename !> just for a printout exit case ('-pka','-pKa') !> pKa calculation script + processedarg(i) = .true. env%crestver = crest_pka env%runver = 33 !env%relax=.true. @@ -428,103 +485,111 @@ subroutine parseflags(env,arg,nra) env%solv = '--alpb h2o' env%protb%h_acidic = 0 call pka_argparse(arg(i+1),env%protb%h_acidic) - if (env%protb%h_acidic == -2) env%protb%pka_baseinp = trim(arg(i+1)) + if (env%protb%h_acidic == -2) env%protb%pka_baseinp = arg1 case ('-compare') !> flag for comparing two ensembles, analysis tool + processedarg(i) = .true. env%compareens = .true. - env%crestver = 5 - env%properties = p_compare + env%crestver = crest_sorting + env%sortmode = 'compare' + env%preopt = .false. env%ensemblename = 'none selected' env%ensemblename2 = 'none selected' if (nra .ge. (i+2)) then atmp = adjustl(arg(i+1)) btmp = adjustl(arg(i+2)) else - write (*,'(a,a)') trim(arg(i)),' requires two arguments:' - write (*,'(2x,a,a)') trim(arg(i)),' [ensemble1] [ensemble2]' + write (stdout,'(a,a)') trim(arg(i)),' requires two arguments:' + write (stdout,'(2x,a,a)') trim(arg(i)),' [ensemble1] [ensemble2]' error stop end if if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1).and. & & (btmp(1:1) /= '-').and.(len_trim(btmp) .ge. 1)) then env%ensemblename = trim(atmp) env%ensemblename2 = trim(btmp) + processedarg(i+1) = .true. + processedarg(i+2) = .true. end if - write (*,'(1x,a,1x,a,1x,a)') trim(arg(i)),trim(env%ensemblename),trim(env%ensemblename2) + write (stdout,'(1x,a,1x,a,1x,a)') trim(arg(i)),trim(env%ensemblename),trim(env%ensemblename2) exit case ('-protonate') !> protonation tool + processedarg(i) = .true. env%properties = p_protonate env%crestver = crest_protonate - env%legacy = .true. !> TODO, set active at later version - write (*,'(2x,a,'' : automated protonation script'')') trim(arg(i)) + env%legacy = .false. + write (stdout,'(2x,a,'' : automated protonation script'')') trim(arg(i)) exit case ('-deprotonate') !> deprotonation tool + processedarg(i) = .true. env%properties = p_deprotonate env%crestver = crest_deprotonate - env%legacy = .true. !> TODO, set active at later version - write (*,'(2x,a,'' : automated deprotonation script'')') trim(arg(i)) + env%legacy = .false. + write (stdout,'(2x,a,'' : automated deprotonation script'')') trim(arg(i)) exit case ('-tautomerize') !> tautomerization tool + processedarg(i) = .true. env%properties = p_tautomerize env%crestver = crest_tautomerize - env%legacy = .true. !> TODO, set active at later version - write (*,'(2x,a,'' : automated tautomerization script'')') trim(arg(i)) + env%legacy = .false. + write (stdout,'(2x,a,'' : automated tautomerization script'')') trim(arg(i)) exit case ('-isomerize','-stereomers') !> isomerization tool + processedarg(i) = .true. env%properties = p_isomerize - write (*,'(2x,a,'' : automated stereoisomerization script'')') trim(arg(i)) - write (*,'(2x,''Note: Use of GFN-FF required for stereoisomer generation.'')') + write (stdout,'(2x,a,'' : automated stereoisomerization script'')') trim(arg(i)) + write (stdout,'(2x,''Note: Use of GFN-FF required for stereoisomer generation.'')') exit - case ('-forall','-for') !> property mode with ensemble as input - env%properties = p_propcalc - atmp = '' - env%ensemblename = 'none selected' - if (nra .ge. (i+1)) atmp = adjustl(arg(i+1)) - if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1)) then - env%ensemblename = trim(atmp) - end if - inquire (file=env%ensemblename,exist=ex) - if (.not.ex) then - write (*,'(1x,a,a,a)') 'invalid ensemble file <',trim(env%ensemblename),'>. exit.' - error stop - end if - call xyz2coord(env%ensemblename,'coord') !> write coord from lowest structure - env%inputcoords = env%ensemblename !> just for a printout - if (argument == '-forall') then - env%protb%alldivers = .true. - end if + case ('-forall','-for') !> property mode with ensemble as input (deprecated) + call parseflags_deprecated(argument) exit case ('-rrhoav') !> Hessians along given ensemble and average + processedarg(i) = .true. env%properties = p_rrhoaverage atmp = '' env%ensemblename = 'none selected' if (nra .ge. (i+1)) atmp = adjustl(arg(i+1)) if ((atmp(1:1) /= '-').and.(len_trim(atmp) .ge. 1)) then env%ensemblename = trim(atmp) + processedarg(i+1) = .true. end if inquire (file=env%ensemblename,exist=ex) if (.not.ex) then - write (*,'(1x,a,a,a)') 'invalid ensemble file <',trim(env%ensemblename),'>. exit.' + write (stdout,'(1x,a,a,a)') 'invalid ensemble file <',trim(env%ensemblename),'>. exit.' error stop end if exit case ('-reactor') !> xtb nanoreactor workarounds + processedarg(i) = .true. env%preopt = .false. env%crestver = crest_nano exit case ('-solvtool','-qcg') + processedarg(i) = .true. !> Set solute file if present - if (i == 2) env%solu_file = trim(arg(i-1)) + if (i == 2) then + env%solu_file = trim(arg(i-1)) + inquire (file=env%solu_file,exist=ex) + if (ex) then + processedarg(i-1) = .true. + end if + end if !> Set solvent file if prensent !> If it is another argument, it doesent matter as solvent file is checke in solvtool - if (nra >= i+1) env%solv_file = trim(arg(i+1)) + if (nra >= i+1) then + env%solv_file = arg1 + inquire (file=env%solv_file,exist=ex) + if (ex) then + processedarg(i+1) = .true. + end if + end if !> Set QCG defaults env%preopt = .false. env%crestver = crest_solv @@ -537,9 +602,10 @@ subroutine parseflags(env,arg,nra) env%doOHflip = .false. !> Switch off OH-flip if (env%iterativeV2) env%iterativeV2 = .false. exit - env%legacy = .true. !> force legacy routines for now + !env%legacy = .true. !> force legacy routines for now case ('-compress') + processedarg(i) = .true. env%crestver = crest_compr env%runver = 77 env%mdstep = 2.5d0 @@ -548,149 +614,239 @@ subroutine parseflags(env,arg,nra) exit case ('-msreact') + processedarg(i) = .true. env%crestver = crest_msreac env%preopt = .false. env%presp = .true. env%ewin = 200.0d0 !> 200 kcal for msreact case ('-splitfile') - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 + processedarg(i+1) = .true. k = huge(j) l = 1 if (nra >= i+2) then read (arg(i+2),*,iostat=io) j if (io == 0) then k = j + processedarg(i+2) = .true. end if end if if (nra >= i+3) then read (arg(i+3),*,iostat=io) j if (io == 0) then l = j + processedarg(i+3) = .true. end if end if call splitfile(ctmp,k,l) - stop + call creststop(status_normal) case ('-printaniso') - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 inquire (file=ctmp,exist=ex) if (ex) then + processedarg(i+1) = .true. call printaniso(ctmp,0.01_wp,0.025_wp,0.5_wp) end if stop + case ('-rotalign') + processedarg(i) = .true. + ctmp = arg1 + inquire (file=ctmp,exist=ex) + if (ex) then + processedarg(i+1) = .true. + call rotalign_tool(ctmp) + end if + stop + case ('-printboltz') + processedarg(i) = .true. if (nra >= i+2) then - ctmp = trim(arg(i+1)) - dtmp = trim(arg(i+2)) + ctmp = arg1 + dtmp = arg2 call prbweight(ctmp,dtmp) + processedarg(i+1) = .true. + processedarg(i+2) = .true. else - ctmp = trim(arg(i+1)) + ctmp = arg1 call prbweight(ctmp,'') + processedarg(i+1) = .true. end if case ('-wbotopo','-usewbo') !> try to use a WBO file in topology analysis - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 if (ctmp(1:1) .ne. '-'.and.(nra >= i+1)) then env%wbofile = trim(ctmp) + processedarg(i+1) = .true. else env%wbofile = 'wbo' end if env%wbotopo = .true. case ('-testtopo') - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 inquire (file=ctmp,exist=ex) if (i+2 .le. nra) then - dtmp = trim(arg(i+2)) + dtmp = arg2 if (dtmp(1:1) == '-') then dtmp = 'default' + else + processedarg(i+2) = .true. end if end if if (ex) then + processedarg(i+1) = .true. call testtopo(ctmp,env,dtmp) end if case ('-resortensemble') - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 inquire (file=ctmp,exist=ex) if (ex) then + processedarg(i+1) = .true. call resort_ensemble(ctmp) end if stop case ('-thermo','-thermotool') + processedarg(i) = .true. env%properties = p_thermo ctmp = trim(arg(1)) ! first argument to read the structure if (ctmp(1:1) .ne. '-') then env%inputcoords = trim(ctmp) env%thermo%coords = trim(ctmp) end if - ctmp = trim(arg(i+1)) ! second argument to read the vibspectrum + ctmp = arg1 ! second argument to read the vibspectrum if (ctmp(1:1) .ne. '-') then + processedarg(i+1) = .true. env%thermo%vibfile = trim(ctmp) end if case ('-rmsd','-rmsdheavy','-hrmsd') - ctmp = trim(arg(i+1)) - dtmp = trim(arg(i+2)) + processedarg(i) = .true. if ((argument == '-rmsdheavy').or.(argument == '-hrmsd')) then - call quick_rmsd_tool(ctmp,dtmp,.true.) + env%sortmode = 'hrmsd' + else + env%sortmode = 'rmsd' + end if + ctmp = arg1 + dtmp = arg2 + env%preopt = .false. + env%crestver = crest_sorting + inquire (file=ctmp,exist=ex) + if (ex) then + env%inputcoords = ctmp + env%ensemblename = ctmp + processedarg(i+1) = .true. + end if + inquire (file=dtmp,exist=ex) + if (ex) then + env%ensemblename2 = dtmp + processedarg(i+2) = .true. + end if + + case ('-irmsd','-irmsd_noinv') + processedarg(i) = .true. + ctmp = arg1 + dtmp = arg2 + env%preopt = .false. + env%crestver = crest_sorting + env%sortmode = 'irmsd' + inquire (file=ctmp,exist=ex) + if (ex) then + env%inputcoords = ctmp + env%ensemblename = ctmp + processedarg(i+1) = .true. + end if + inquire (file=dtmp,exist=ex) + if (ex) then + env%ensemblename2 = dtmp + processedarg(i+2) = .true. + end if + if (index(argument,'_noinv') .ne. 0) then + env%iinversion = 2 + end if + + case ('-hungarian','-hungarianheavy','-hhungarian','-lsap','-hlsap','-lsapheavy') + processedarg(i:i+2) = .true. + ctmp = arg1 + dtmp = arg2 + if ((argument == '-hungarianheavy').or.(argument == '-hhungarian').or. & + &(argument == '-lsapheavy').or.(argument == '-hlsap')) then + call quick_hungarian_match(ctmp,dtmp,.true.) else - call quick_rmsd_tool(ctmp,dtmp,.false.) + call quick_hungarian_match(ctmp,dtmp,.false.) end if stop case ('-symmetries') - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 inquire (file=ctmp,exist=ex) if (ex) then + processedarg(i+1) = .true. call ensemble_analsym(trim(ctmp),.true.) end if - stop + call exit(0) case ('-exlig','-exligand','-exchligand') + processedarg(i) = .true. env%properties = p_ligand env%protb%infile = trim(arg(1)) - ctmp = trim(arg(i+1)) + ctmp = arg1 + processedarg(i+1) = .true. env%protb%newligand = trim(ctmp) read (arg(i+2),*,iostat=io) j if (io == 0) then env%protb%centeratom = j + processedarg(i+2) = .true. end if read (arg(i+3),*,iostat=io) j if (io == 0) then env%protb%ligand = j + processedarg(i+3) = .true. end if exit case ("-acidbase","-ab",'-abprep','-pkaprep','-gdissprep') !-- acid base correction + processedarg(i) = .true. !> crest --ab --chrg env%properties = p_acidbase if (index(arg(i),'prep') .ne. 0) then call pka_argparse2(env,arg(i+1),arg(i+2),env%protb%pka_mode) else - ctmp = trim(arg(i+1)) + ctmp = arg1 inquire (file=ctmp,exist=ex) if (ex) then + processedarg(i+1) = .true. env%protb%pka_acidensemble = trim(ctmp) - write (*,'(1x,a,a)') 'File used for the acid: ',trim(ctmp) + write (stdout,'(1x,a,a)') 'File used for the acid: ',trim(ctmp) end if - ctmp = trim(arg(i+2)) + ctmp = arg2 inquire (file=ctmp,exist=ex) if (ex) then + processedarg(i+2) = .true. env%protb%pka_baseensemble = trim(ctmp) - write (*,'(1x,a,a)') 'File used for the base: ',trim(ctmp) + write (stdout,'(1x,a,a)') 'File used for the base: ',trim(ctmp) end if end if env%solv = '--alpb h2o' env%gfnver = '--gfn2' case ('-redoextrapol') - ctmp = trim(arg(i+1)) + processedarg(i) = .true. + ctmp = arg1 + processedarg(i+1) = .true. read (arg(i+2),*,iostat=io) j if (io == 0) then + processedarg(i+2) = .true. call redo_extrapol(ctmp,j) else call redo_extrapol(ctmp,0) @@ -698,40 +854,91 @@ subroutine parseflags(env,arg,nra) stop case ('-sp') !> singlepoint calculation (uses new calculator routines) + processedarg(i) = .true. env%crestver = crest_sp env%preopt = .false. env%legacy = .false. + write (stdout,'(2x,a,t15,a)') argument//':','Singlepoint energy calculation runtype' exit case ('-opt','-optimize','-ancopt','-ohess') !> ANCOPT structure optimization (uses new calculator routines) + processedarg(i) = .true. env%preopt = .false. env%crestver = crest_optimize env%legacy = .false. - if (argument .eq. '-ohess') env%crest_ohess = .true. + if (argument .eq. '-ohess') then + env%crest_ohess = .true. + write (stdout,'(2x,a,t15,a)') argument//':','Geometry optimization + frequency calculation runtype' + else + write (stdout,'(2x,a,t15,a)') argument//':','Geometry optimization runtype' + end if + !if (i+1 .le. nra) then + env%optlev = optlevnum(arg(i+1),iostat=io) + if (io == 0) processedarg(i+1) = .true. + !end if + exit case ('-hess','-numhess') !> Numerical hessian + processedarg(i) = .true. env%preopt = .false. env%crestver = crest_numhessian env%legacy = .false. + write (stdout,'(2x,a,t15,a)') argument//':','Frequency calculation runtype' exit case ('-trialopt') !> test optimization with topocheck + processedarg(i) = .true. env%preopt = .false. env%crestver = crest_trialopt + write (stdout,'(2x,a,t15,a)') argument//':','Trial geometry optimization' exit case ('-dynamics','-dyn') !> molecular dynamics (uses new calculator routines) + processedarg(i) = .true. env%preopt = .false. env%crestver = crest_moldyn env%legacy = .false. + write (stdout,'(2x,a,t15,a)') argument//':','Molecular dynamics simulation' + exit + + case ('-sort','-cregen') + processedarg(i) = .true. + env%preopt = .false. + env%crestver = crest_sorting + env%autozsort = .false. + if (argument == '-cregen') then + env%sortmode = 'cregen' + env%confgo = .true. + end if + ctmp = arg1 + inquire (file=ctmp,exist=ex) + if (ex) then + processedarg(i+1) = .true. + env%inputcoords = ctmp + env%ensemblename = ctmp + end if + if (argument == '-sort'.and.nra >= i+2) then + ctmp = arg2 + if (ctmp(1:1) .ne. '-') then + processedarg(i+2) = .true. + env%sortmode = trim(ctmp) + end if + end if + + case ('-bh','-GMIN') + processedarg(i) = .true. + env%crestver = crest_bh + write (stdout,'(2x,a,t15,a)') argument//':','Basin-hopping global optimization' exit case ('-SANDBOX') - !>--- IMPLEMENT HERE WHATEVER YOU LIKE, FOR TESTING + processedarg(i) = .true. + !>--- readl vs readl_old test suite !>----- stop case ('-PLAYGROUND','-TEST') + processedarg(i) = .true. env%preopt = .false. env%crestver = crest_test exit @@ -780,12 +987,12 @@ subroutine parseflags(env,arg,nra) call inputcoords(env,env%inputcoords) else call inputcoords(env,trim(arg(1))) + processedarg(1) = .true. + end if +!> For sorting runtypes, fall back to the input file if no ensemble was set explicitly + if (env%crestver == crest_sorting.and.len_trim(env%ensemblename) == 0) then + env%ensemblename = env%inputcoords end if -!========================================================================================! -!> after this point there should always be a "coord" file present -!========================================================================================! - allocate (env%includeRMSD(env%nat)) - env%includeRMSD = 1 !========================================================================================! !>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! @@ -793,18 +1000,28 @@ subroutine parseflags(env,arg,nra) !>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>! !========================================================================================! do i = 1,nra + if (processedarg(i)) cycle argument = trim(arg(i)) + arg1 = '' + if (i+1 .le. nra) arg1 = trim(arg(i+1)) + arg2 = '' + if (i+2 .le. nra) arg2 = trim(arg(i+2)) + arg3 = '' + if (i+3 .le. nra) arg3 = trim(arg(i+3)) if (argument(1:2) == '--') then argument = argument(2:) end if if (argument .ne. '') then + !========================================================================================! !-------- switch between legacy (systemcall) and new code (API) implementations !========================================================================================! select case (argument) case ('-legacy') !> switch to old xtb-call version where possible + processedarg(i) = .true. env%legacy = .true. case ('-newversion') !> switch to newer implementations (CREST >3.0) + processedarg(i) = .true. env%legacy = .false. end select !========================================================================================! @@ -812,61 +1029,70 @@ subroutine parseflags(env,arg,nra) !========================================================================================! if (any((/crest_imtd,crest_imtd2,11/) == env%crestver)) then select case (argument) !> V2 - case ('-mdtemp') !> set MTD temperature (V2 version) - call readl(arg(i+1),xx,j) - env%mdtemp = xx(1) - env%user_temp = .true. + case ('-quick') !> performing quick conformational search + processedarg(i) = .true. env%quick = .true. env%runver = 2 env%ewin = 5.0d0 if (env%optlev > 1.0d0) env%optlev = 1.0d0 !> optlev tight for quick run - case ('-shake') !> set shake - call readl(arg(i+1),xx,j) - env%shake = nint(xx(1)) - case ('-tstep') !> set MD timestep in fs - call readl(arg(i+1),xx,j) - env%mdstep = xx(1) - env%user_mdstep = .true. - case ('-vbdump') !> Vbias dump in ps - call readl(arg(i+1),xx,j) - xx(2) = xx(1)*1000 - env%mddump = nint(xx(2)) + case ('-mdskip') !> set skipping structures in -mdopt - call readl(arg(i+1),xx,j) - env%mdskip = nint(xx(1)) - case ('-mddump') !> set dumpstep for writing structures from MD - call readl(arg(i+1),xx,j) - env%mddumpxyz = nint(xx(1)) + processedarg(i) = .true. + call readl(arg1,xx,j) + if (j > 0) then + env%mdskip = nint(xx(1)) + processedarg(i+1) = .true. + else + call parseflags_missing(argument) + end if + case ('-nomtd') !> Don't do the MTD in V2 + processedarg(i) = .true. env%performMTD = .false. + case ('-restartopt') !> go to step 2 of multilevel optimization immideatly + processedarg(i) = .true. env%restartopt = .true. env%autozsort = .false. + case ('-norotmd') !> don't do the regular mds after step 2 in multilevel optimization of V2 + processedarg(i) = .true. env%rotamermds = .false. + case ('-rotmd') + processedarg(i) = .true. env%rotamermds = .true. - case ('-tnmd') !> temperature for additional normal MDs - call readl(arg(i+1),xx,j) - env%nmdtemp = xx(1) + case ('-gcmopt') !> GC multilevel optimization activate in V2 + processedarg(i) = .true. env%gcmultiopt = .true. + case ('-gcsopt') !> GC single level optimization in V2 + processedarg(i) = .true. env%gcmultiopt = .false. + case ('-nogcmopt') !> GC single level optimization in V2 + processedarg(i) = .true. env%gcmultiopt = .false. + case ('-qmdff') !> use QMDFF for the MDs in V2? + processedarg(i) = .true. env%useqmdff = .true. + call parseflags_deprecated(argument) + case ('-nci') !> NCI special mode - write (*,'(2x,a,1x,a)') trim(arg(i)),' : Special NCI mode for non-covalently bound complexes or clusters.' + processedarg(i) = .true. + write (stdout,'(2x,a,1x,a)') trim(arg(i)),' : Special NCI mode for non-covalently bound complexes or clusters.' env%NCI = .true. env%runver = 4 env%autozsort = .false. env%performCross = .false. env%rotamermds = .false. + case ('-squick','-superquick') !> extremely crude quick mode - write (*,'(2x,a,1x,a)') trim(arg(i)),' : very crude quick-mode (no NORMMD, no GC, crude opt.)' + processedarg(i) = .true. + write (stdout,'(2x,a,1x,a)') trim(arg(i)),' : very crude quick-mode (no NORMMD, no GC, crude opt.)' env%rotamermds = .false. !> no NORMMD env%performCross = .false. !> no GC env%quick = .true. !> MTD settings from the quick-mode @@ -876,7 +1102,8 @@ subroutine parseflags(env,arg,nra) env%ewin = 5.0d0 !> smaller energy window case ('-mquick','-megaquick') !> extremely crude quick mode pt.2 - write (*,'(2x,a,1x,a)') trim(arg(i)),' : very crude quick-mode (no NORMMD, no GC, crude opt.)' + processedarg(i) = .true. + write (stdout,'(2x,a,1x,a)') trim(arg(i)),' : very crude quick-mode (no NORMMD, no GC, crude opt.)' env%rotamermds = .false. !> no NORMMD env%performCross = .false. !> no GC env%quick = .true. !> MTD settings from the quick-mode @@ -887,14 +1114,18 @@ subroutine parseflags(env,arg,nra) env%ewin = 2.5d0 !> smaller energy window case ('-extensive') !> counterpart to quick mode + processedarg(i) = .true. env%slow = .true. env%quick = .false. env%superquick = .false. env%optlev = 0.0d0 env%ewin = 8.0d0 env%runver = 8 + case ('-static','-staticmtd') + processedarg(i) = .true. env%staticmtd = .true. + case default continue end select !> V2 @@ -902,17 +1133,26 @@ subroutine parseflags(env,arg,nra) if (env%iterativeV2) then select case (argument) !> V2i case ('-mrest') !> set max number of restarts - call readl(arg(i+1),xx,j) - env%Maxrestart = nint(xx(1)) + processedarg(i) = .true. + call readl(arg1,xx,j) + if (j > 0) then + env%Maxrestart = nint(xx(1)) + processedarg(i+1) = .true. + else + call parseflags_missing(argument) + end if + case ('-iru') !> re-use previously found conformers as bias in iterative approach + processedarg(i) = .true. env%iru = .true. - case ('-keepdir','-keeptmp') !> Do not delete METADYN and NORMMD directories - env%keepModef = .true. + case ('-singlerun') !> QCG special mode - write (*,'(2x,a,1x,a)') trim(arg(i)),' : run mode with only a single MTD and no iterations (for testing)' + processedarg(i) = .true. + write (stdout,'(2x,a,1x,a)') trim(arg(i)),' : run mode with only a single MTD and no iterations (for testing)' env%runver = 45 env%Maxrestart = 1 env%rotamermds = .false. + case default continue end select !> V2i @@ -925,10 +1165,17 @@ subroutine parseflags(env,arg,nra) if (env%crestver == crest_mdopt.or.env%crestver == crest_screen) then select case (argument) !> SCREEN case ('-purge') !> Purge special application + processedarg(i) = .true. env%optpurge = .true. + case ('-ethrpurge','-ethrp') + processedarg(i) = .true. read (arg(i+1),*,iostat=io) rdum - if (io == 0) env%ethrpurge = rdum + if (io == 0) then + env%ethrpurge = rdum + processedarg(i+1) = .true. + end if + case default continue end select !> SCREEN @@ -939,23 +1186,28 @@ subroutine parseflags(env,arg,nra) if (env%crestver == crest_nano) then select case (argument) !> RCTR case ('-genpot') + processedarg(i) = .true. if (i+1 .le. nra) then - atmp = trim(arg(i+1)) + atmp = arg1 if (atmp(1:1) .ne. '-') then - call readl(arg(i+1),xx,j) + call readl(arg1,xx,j) env%rdens = xx(1) + processedarg(i+1) = .true. end if end if env%properties = p_reactorset env%preactorpot = .true. + case ('-genmtd') + processedarg(i) = .true. env%properties = p_reactorset env%mdtime = 20.0d0 if (i+1 .le. nra) then - atmp = trim(arg(i+1)) + atmp = arg1 if (atmp(1:1) .ne. '-') then - call readl(arg(i+1),xx,j) + call readl(arg1,xx,j) env%mdtime = xx(1) + processedarg(i+1) = .true. end if end if env%nmetadyn = 1 @@ -968,10 +1220,15 @@ subroutine parseflags(env,arg,nra) env%metadexp(1) = 1.00_wp env%metadfac(1) = 0.04_wp env%preactormtd = .true. + case ('-fragopt') + processedarg(i) = .true. env%restartopt = .true. + case ('-iso') + processedarg(i) = .true. env%riso = .true. + case default continue end select !> RCTR @@ -981,48 +1238,31 @@ subroutine parseflags(env,arg,nra) !========================================================================================! if (env%QCG) then select case (argument) !> QCG - case ('-keepdir','-keeptmp') + case ('-keeptmp') + processedarg(i) = .true. env%keepModef = .true. - case ('-tstep') !> set MD timestep in fs - call readl(arg(i+1),xx,j) - env%mdstep = xx(1) - env%user_mdstep = .true. - case ('-vbdump') !> Vbias dump in ps - call readl(arg(i+1),xx,j) - xx(2) = xx(1)*1000 - env%mddump = nint(xx(2)) - case ('-mdskip') !> set skipping structures in -mdopt - call readl(arg(i+1),xx,j) - env%mdskip = nint(xx(1)) - case ('-mddump') !> set dumpstep for writing structures out of the md - env%user_dumxyz = .true. - call readl(arg(i+1),xx,j) - env%mddumpxyz = nint(xx(1)) + case ('-nomtd') !> Don't do the MTD in V2 + processedarg(i) = .true. env%performMTD = .false. - case ('-wscal') !> scale size of wall potential - call readl(arg(i+1),xx,j) - env%potscal = xx(1) - env%user_wscal = .true. + case ('-fixsolute') !> Fix the solute after CMA trafo + processedarg(i) = .true. env%constrain_solu = .true. + case ('-nofix') !> No fixing of the solute after CMA trafo + processedarg(i) = .true. env%noconst = .true. + case ('-restartopt') !> go to step 2 of multilevel optimization immideatly + processedarg(i) = .true. env%restartopt = .true. env%autozsort = .false. + case ('-norotmd') !> don't do the regular mds after step 2 in multilevel optimization of V2 + processedarg(i) = .true. env%rotamermds = .false. - case ('-mdtemp') !> set MTD temperature (V2 version) - call readl(arg(i+1),xx,j) - env%mdtemp = xx(1) - env%user_temp = .true. - case ('-tnmd') !> temperature for additional normal MDs - call readl(arg(i+1),xx,j) - env%nmdtemp = xx(1) - case ('-shake') !> set shake - call readl(arg(i+1),xx,j) - env%shake = nint(xx(1)) + end select !> QCG end if @@ -1032,38 +1272,86 @@ subroutine parseflags(env,arg,nra) if (env%crestver == crest_msreac) then select case (argument) !> msreact case ('-msei') + processedarg(i) = .true. env%msei = .true. + case ('-mscid') + processedarg(i) = .true. env%mscid = .true. env%msei = .false. + case ('-msnoiso') !> filter out non fragmentated structures in msreact + processedarg(i) = .true. env%msnoiso = .true. + case ('-msiso') !> filter out fragmentated structures in msreact + processedarg(i) = .true. env%msiso = .true. + case ('-msnbonds') ! give number of bonds up to which bias potential is added between atoms default 3 - call readl(arg(i+1),xx,j) - env%msnbonds = xx(1) + processedarg(i) = .true. + call readl(arg1,xx,j) + if (j > 0) then + env%msnbonds = xx(1) + processedarg(i+1) = .true. + else + call parseflags_missing(argument) + end if + case ('-msnshifts') ! give number of times atoms are randomly shifted before optimization - call readl(arg(i+1),xx,j) - env%msnshifts = xx(1) + processedarg(i) = .true. + call readl(arg1,xx,j) + if (j > 0) then + env%msnshifts = xx(1) + processedarg(i+1) = .true. + else + call parseflags_missing(argument) + end if + case ('-msnshifts2') ! give number of times atoms are randomly shifted before applying the constrained optimization default 0 - call readl(arg(i+1),xx,j) - env%msnshifts2 = xx(1) + processedarg(i) = .true. + call readl(arg1,xx,j) + if (j > 0) then + env%msnshifts2 = xx(1) + processedarg(i+1) = .true. + else + call parseflags_missing(argument) + end if + case ('-msnfrag') ! give number of structures that should be generated - call readl(arg(i+1),xx,j) - env%msnfrag = xx(1) + processedarg(i) = .true. + call readl(arg1,xx,j) + if (j > 0) then + env%msnfrag = xx(1) + processedarg(i+1) = .true. + else + call parseflags_missing(argument) + end if + case ('-msmolbar') !> filter out structures with same molbar code in msreact + processedarg(i) = .true. env%msmolbar = .true. + case ('-msinchi') !> filter out structures with same inchi code in msreact + processedarg(i) = .true. env%msinchi = .true. + case ('-msnoattrh') !> add attractive potential for H-atoms + processedarg(i) = .true. env%msattrh = .false. + case ('-mslargeprint') !> additional printouts and keep MSDIR + processedarg(i) = .true. env%mslargeprint = .true. - case ('-msinput') ! give number of times atoms are randomly shifted before applying the constrained optimization default 0 - ctmp = trim(arg(i+1)) + + case ('-msinput') ! msreact input file + processedarg(i) = .true. + ctmp = arg1 if (ctmp(1:1) .ne. '-') then env%msinput = trim(ctmp) + processedarg(i+1) = .true. + else + end if end select !> msreact end if @@ -1072,26 +1360,45 @@ subroutine parseflags(env,arg,nra) !========================================================================================! select case (argument) !> ARGPARSER1 case ('-dry') !> "dry" run to print settings + processedarg(i) = .true. env%dryrun = .true. + case ('-nozs') + processedarg(i) = .true. env%autozsort = .false. !> turn off automatic zsort (default) + case ('-zs') + processedarg(i) = .true. env%autozsort = .true. !> turn on automatic zsort + case ('-nocross') + processedarg(i) = .true. env%performCross = .false. !> skip the genetic crossing - write (*,'(2x,a,1x,a)') trim(arg(i)),' : skipping GC part.' + write (stdout,'(2x,a,1x,a)') trim(arg(i)),' : skipping GC part.' + case ('-cross') + processedarg(i) = .true. env%performCross = .true. !> do the genetic crossing env%autozsort = .true. + case ('-keepdir','-keeptmp') !> Do not delete temporary directories at the end + processedarg(i) = .true. env%keepModef = .true. + case ('-opt','-optlev') !> settings for optimization level of GFN-xTB - env%optlev = optlevnum(arg(i+1)) - write (*,'(2x,a,1x,a)') trim(arg(i)),optlevflag(env%optlev) + processedarg(i) = .true. + if (i+1 .le. nra) then + env%optlev = optlevnum(arg(i+1),iostat=io) + if (io == 0) processedarg(i+1) = .true. + end if + write (stdout,'(2x,a,1x,a)') trim(arg(i)),optlevflag(env%optlev) + case ('-gfn','-gfn1','-gfn2','-gfn0','-gff','-gfnff') + processedarg(i) = .true. ctmp = argument if (argument == '-gfn') then - dtmp = trim(arg(i+1)) + processedarg(i+1) = .true. + dtmp = arg1 ctmp = ctmp//dtmp end if if (env%properties == p_isomerize) then @@ -1100,16 +1407,16 @@ subroutine parseflags(env,arg,nra) select case (ctmp) !> GFN case ('-gfn1') env%gfnver = '--gfn1' - write (*,'(2x,a,'' : Use of GFN1-xTB requested.'')') env%gfnver + write (stdout,'(2x,a,'' : Use of GFN1-xTB requested.'')') env%gfnver case ('-gfn2') env%gfnver = '--gfn2' - write (*,'(2x,a,'' : Use of GFN2-xTB requested.'')') env%gfnver + write (stdout,'(2x,a,'' : Use of GFN2-xTB requested.'')') env%gfnver case ('-gfn0') env%gfnver = '--gfn0' - write (*,'(2x,a,'' : Use of GFN0-xTB requested.'')') env%gfnver + write (stdout,'(2x,a,'' : Use of GFN0-xTB requested.'')') env%gfnver case ('-gff','-gfnff') env%gfnver = '--gff' - write (*,'(2x,a,'' : Use of GFN-FF requested.'')') '--gfnff' + write (stdout,'(2x,a,'' : Use of GFN-FF requested.'')') '--gfnff' env%mdstep = 1.5d0 env%hmass = 5.0d0 ctype = 5 !> bond constraint activated @@ -1125,95 +1432,201 @@ subroutine parseflags(env,arg,nra) end select !> GFN case ('-gxtb') - call gxtb_dev_warning() + processedarg(i) = .true. + env%gfnver = '--gxtb' + write (stdout,'(2x,a,'' : Use of g-xTB requested.'')') env%gfnver + call gxtb_syscall_warning() + case ('-gxtb_dev') - env%gfnver = 'gxtb_dev' + processedarg(i) = .true. + env%gfnver = '--gxtb' + write (stdout,'(2x,a)') 'Note: --gxtb_dev is deprecated, redirecting to --gxtb.' - case ('-gfn2@gfn0','-gfn2@gfn1','-gfn2@gff','-gfn2@ff','-gfn2@gfnff') - if (.not.env%legacy) then !TODO - write (*,'("> ",a,1x,a)') argument,'option not yet available with new calculator' - error stop - end if - select case (argument) !> GFN2ON - case ('-gfn2@gfn0') - env%gfnver = '--gfn0' - case ('-gfn2@gfn1') - env%gfnver = '--gfn1' - case ('-gfn2@gff','-gfn2@ff','-gfn2@gfnff') - env%gfnver = '--gff' - env%mdstep = 2.0d0 + case ('-mlip') !> quick setup of an fmlip-relay ML potential: --mlip + processedarg(i) = .true. + env%legacy = .false. !> new calculators only! + select case (trim(arg1)) + case ('uma') + env%gfnver = 'uma' + processedarg(i+1) = .true. + write (stdout,'(2x,a)') '--mlip uma : FairChem UMA (omol task) via fmlip-relay requested.' + case ('maceoff','mace-off','mace_off') + env%gfnver = 'maceoff' + processedarg(i+1) = .true. + write (stdout,'(2x,a)') '--mlip maceoff : MACE-OFF23 (medium) via fmlip-relay requested.' case default - env%gfnver = '--gfn2' - end select !> GFN2ON - env%gfnver2 = '--gfn2' - call env%addjob(51) - call env%checkhy() - env%reweight = .false. - case ('-gfn2//gfnff') - if (.not.env%legacy) then !TODO - write (*,'("> ",a,1x,a)') argument,'option not yet available with new calculator' - error stop - end if - env%gfnver = '--gff' - env%mdstep = 2.0d0 - env%gfnver2 = '--gfn2' - env%reweight = .true. - env%mdstep = 2.0d0 - env%hmass = 4.0d0 - ctype = 1 !> bond constraint - bondconst = .true. - env%cts%cbonds_md = .true. - env%checkiso = .true. - if (index(arg(i+1),'opt') .ne. 0) then - env%altopt = .true. - write (*,'(2x,a,a)') argument,' : GFN-FF MDs + GFN2 opt.' - else - write (*,'(2x,a,a)') argument,' : energy reweighting' - end if + write (stdout,'(/,2x,a)') '┌─[ --mlip: missing or unknown backend ]' + write (stdout,'(2x,a)') '│ The --mlip flag selects an fmlip-relay ML potential' + write (stdout,'(2x,a)') '│ and expects one backend keyword:' + write (stdout,'(2x,a)') '│ uma FairChem UMA foundation model (omol task)' + write (stdout,'(2x,a)') '│ maceoff MACE-OFF23 organic force field (medium)' + write (stdout,'(2x,a)') '│ For finer control (model size, task, device, custom' + write (stdout,'(2x,a)') '│ checkpoints) use the TOML [[calculation.level]] block.' + write (stdout,'(2x,a,/)') '└────────────────────────────────────────────────' + call creststop(status_config) + end select + + case ('-orca') !> set up a single ORCA driver level: --orca