diff --git a/.gitignore b/.gitignore index 95327c32..631e65ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.o *.mod *.tgz +*.i90 +*__genmod.f90 github_bin/ build_majestix build_commands diff --git a/CMakeLists.txt b/CMakeLists.txt index 37fc48df..d8da2cae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,131 +10,260 @@ # 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 . + +cmake_minimum_required(VERSION 3.17) +option(WITH_OBJECT "To build using object library" TRUE) +option(INSTALL_MODULES "Install Fortran module files to include directory." FALSE) -cmake_minimum_required(VERSION 3.14) +# Buggy CMake versions +if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.27.0 AND CMAKE_VERSION VERSION_LESS 3.28.0) + set(WITH_OBJECT FALSE) +endif() +# Setup the crest Project project( - "crest" - LANGUAGES "Fortran" "C" - VERSION "3.0" + crest + LANGUAGES "C" "Fortran" + VERSION 3.0 + DESCRIPTION "A tool for the exploration of low-energy chemical space" ) set(SOVERSION "pre") +#enable_testing() + # Follow GNU conventions for installing directories include(GNUInstallDirs) -# General configuration information -set(libs) +# Include further configurations add_subdirectory("config") -# -# Libraries -# +############################################################################### +####################### SUBPROJECTS & DEPENDENCIES ############################ +############################################################################### +# Check a specific CMake targets and execute the +# corresponding Find scripts (located in config/modules/) -# OpenMP dependency -if(NOT TARGET OpenMP::OpenMP_Fortran AND WITH_OpenMP) - find_package(OpenMP REQUIRED) - list( - APPEND libs - OpenMP::OpenMP_Fortran - ) +# LAPACK, BLAS, OpenMP (usually via shared dependencies) +if(STATICBUILD) + set(BLA_STATIC ON) endif() - -# BLAS and LAPACK -if(NOT TARGET BLAS::BLAS) - find_package(BLAS REQUIRED) - if(NOT TARGET BLAS::BLAS AND BLAS_FOUND) - add_library(BLAS::BLAS INTERFACE IMPORTED) - target_link_libraries(BLAS::BLAS INTERFACE "${BLAS_LIBRARIES}") - target_link_options(BLAS::BLAS INTERFACE "${BLAS_LINKER_FLAGS}") - endif() +find_package(LAPACK REQUIRED) +find_package(BLAS REQUIRED) +if(NOT TARGET "OpenMP::OpenMP_Fortran" AND WITH_OpenMP) + find_package("OpenMP" REQUIRED) endif() -if(NOT TARGET LAPACK::LAPACK) - find_package(LAPACK REQUIRED) - if(NOT TARGET LAPACK::LAPACK AND LAPACK_FOUND) - add_library(LAPACK::LAPACK INTERFACE IMPORTED) - target_link_libraries(LAPACK::LAPACK INTERFACE "${LAPACK_LIBRARIES}") - target_link_options(LAPACK::LAPACK INTERFACE "${LAPACK_LINKER_FLAGS}") - endif() + +# TOML-F (needs to be imported before tblite) +if(NOT TARGET "toml-f::toml-f" AND WITH_TOMLF) + find_package("toml-f" REQUIRED) + add_compile_definitions(WITH_TOMLF) endif() -list( - APPEND libs - LAPACK::LAPACK - BLAS::BLAS -) # tblite -if(WITH_TBLITE) - find_package("tblite" REQUIRED) - add_definitions(-DWITH_TBLITE) - list( - APPEND libs - tblite::tblite - ) +if(NOT TARGET "tblite::tblite" AND WITH_TBLITE) + find_package("tblite" REQUIRED) + add_compile_definitions(WITH_TBLITE) endif() +# GFN-FF +if(NOT TARGET "gfnff::gfnff" AND WITH_GFNFF) + find_package("gfnff" REQUIRED) + add_compile_definitions(WITH_GFNFF) +endif() -# toml-f -if(WITH_TOMLF) - find_package("toml-f" REQUIRED) - add_definitions(-DWITH_TOMLF) - list( - APPEND libs - toml-f::toml-f - ) +# GFN0-xTB +if(NOT TARGET "gfn0::gfn0" AND WITH_GFN0) + find_package("gfn0" REQUIRED) + add_compile_definitions(WITH_GFN0) endif() +# XHCFF +if(NOT TARGET "xhcff::xhcff" AND WITH_XHCFF) + find_package("xhcff" REQUIRED) + add_compile_definitions(WITH_XHCFF) +endif() -# GFN0-xTB -if(WITH_GFN0) - find_package("gfn0" REQUIRED) - add_definitions(-DWITH_GFN0) - list( - APPEND libs - gfn0::gfn0 - ) +# lwONIOM +if(NOT TARGET "lwoniom::lwoniom" AND WITH_LWONIOM) + find_package("lwoniom" REQUIRED) + add_compile_definitions(WITH_LWONIOM) endif() +# Sources: initialize program sources (prog) and library sources (srcs) empty +set(prog) +set(srcs) +add_subdirectory("src") -# GFN-FF -if(WITH_GFNFF) - find_package("gfnff" REQUIRED) - add_definitions(-DWITH_GFNFF) - list( - APPEND libs - gfnff::gfnff - ) + +if(NOT EXISTS "${PROJECT_BINARY_DIR}/include") + file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/include") endif() -# XHCFF -if(WITH_XHCFF) - find_package("xhcff" REQUIRED) - add_definitions(-DWITH_XHCFF) - list( - APPEND libs - xhcff::xhcff - ) +############################################################################### +########################## OBJECT LIBRARY ##################################### +############################################################################### +if(WITH_OBJECT AND NOT STATICBUILD) + + add_library( + "${PROJECT_NAME}-object" + OBJECT + ${srcs} + ) + + # customize object library + set_target_properties( + "${PROJECT_NAME}-object" + PROPERTIES + Fortran_MODULE_DIRECTORY "${PROJECT_BINARY_DIR}/include" + POSITION_INDEPENDENT_CODE ON + ) + + # link object library conditionally against required + target_link_libraries( + "${PROJECT_NAME}-object" + PUBLIC + $<$:tblite::tblite> + $<$:gfn0::gfn0> + $<$:gfnff::gfnff> + $<$:xhcff::xhcff> + $<$:toml-f::toml-f> + $<$:lwoniom::lwoniom> + $<$:OpenMP::OpenMP_Fortran> + ) + + # include directories + target_include_directories( + "${PROJECT_NAME}-object" + PUBLIC + ${crest-config-dir} + ${PROJECT_BINARY_DIR} + $ + $ + $/${CMAKE_INSTALL_INCLUDEDIR}> + ) + endif() +############################################################################### +############################### Static Library ################################ +############################################################################### +if(WITH_OBJECT AND NOT STATICBUILD) + add_library( + "lib-${PROJECT_NAME}-static" + STATIC + $ + ) +else() + add_library( + "lib-${PROJECT_NAME}-static" + STATIC + ${srcs} + ) +endif() +# workaround attemp. doesn't work yet +set(LINK_OpenMP FALSE) +if(WITH_OpenMP AND NOT STATICBUILD) + set(LINK_OpenMP TRUE) +endif() +target_link_libraries( + "lib-${PROJECT_NAME}-static" + PUBLIC + ${BLAS_LIBRARIES} + ${LAPACK_LIBRARIES} + $<$:OpenMP::OpenMP_Fortran> + $<$:tblite::tblite> + $<$:gfn0::gfn0> + $<$:gfnff::gfnff> + $<$:xhcff::xhcff> + $<$:toml-f::toml-f> + $<$:lwoniom::lwoniom> + $<$:-static> +) -# -# CREST sources -# -set(prog) -set(srcs) -add_subdirectory("src") +set_target_properties( + "lib-${PROJECT_NAME}-static" + PROPERTIES + Fortran_MODULE_DIRECTORY "${PROJECT_BINARY_DIR}/include" + ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}" + POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME "${PROJECT_NAME}" + ) -# -# Executables -# + +target_include_directories( + "lib-${PROJECT_NAME}-static" + PUBLIC + $ + $ + $/${CMAKE_INSTALL_INCLUDEDIR}> +) + + +#################################################################################### +############################# Shared Library ####################################### +#################################################################################### +if (WITH_OBJECT AND NOT STATICBUILD) + add_library( + "lib-${PROJECT_NAME}-shared" + SHARED + $ + ) + + target_link_libraries( + "lib-${PROJECT_NAME}-shared" + PUBLIC + ${BLAS_LIBRARIES} + ${LAPACK_LIBRARIES} + $<$:OpenMP::OpenMP_Fortran> + $<$:tblite::tblite> + $<$:gfn0::gfn0> + $<$:gfnff::gfnff> + $<$:xhcff::xhcff> + $<$:toml-f::toml-f> + $<$:lwoniom::lwoniom> + ) + + set_target_properties( + "lib-${PROJECT_NAME}-shared" + PROPERTIES + Fortran_MODULE_DIRECTORY "${PROJECT_BINARY_DIR}/include" + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}" + OUTPUT_NAME "${PROJECT_NAME}" + VERSION "${PROJECT_VERSION}" + SOVERSION "${PROJECT_VERSION_MAJOR}" + ) + + target_include_directories( + "lib-${PROJECT_NAME}-shared" + PUBLIC + $ + $ + $/${CMAKE_INSTALL_INCLUDEDIR}> + ) +endif() + +############################################################################### +############################### Executables ################################### +############################################################################### add_executable( ${PROJECT_NAME}-exe - "${srcs}" "${prog}" + ${prog} ) + +target_link_libraries( + ${PROJECT_NAME}-exe + PRIVATE + "lib-${PROJECT_NAME}-static" + $<$:-static> +) + set_target_properties( ${PROJECT_NAME}-exe PROPERTIES @@ -142,24 +271,11 @@ set_target_properties( RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR} OUTPUT_NAME "${PROJECT_NAME}" ) -target_link_libraries( - ${PROJECT_NAME}-exe - PRIVATE - "${libs}" -) - -get_target_property(OUT ${PROJECT_NAME}-exe LINK_LIBRARIES) -message(STATUS ${OUT}) -# -# Binary installing option -# -install( - TARGETS - "${PROJECT_NAME}-exe" - EXPORT - "${PROJECT_NAME}-targets" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" +target_include_directories( + ${PROJECT_NAME}-exe + PRIVATE + ${PROJECT_SOURCE_DIR}/include + $ ) + diff --git a/README.md b/README.md index 10e009f7..07ad9188 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ Working and tested builds of CREST (mostly on Ubuntu 20.04 LTS): |--------------|----------|------------------------|:--------------:|:----------:| | CMake | GNU (gcc 10.3.0) | [OpenBLAS](https://github.com/xianyi/OpenBLAS) (with OpenMP) | dynamic | ✅ | | CMake | GNU (gcc 10.3.0) | [MKL shared (oneAPI 2023.1)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | dynamic | ✅ | +| CMake | GNU (gcc 9.5.0) | [MKL shared (oneAPI 2023.1)](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onemkl.html) | dynamic | ✅ | +| CMake | [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 | ✅ | | Meson | [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) | static | ✅ | | Meson | [Intel (`ifort` 2021.9.0/`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 | ✅ | @@ -77,7 +79,26 @@ For more information about builds including subprojects see [here](./subprojects Some basic build instructions can be found in the following dropdown tabs: + +
+

CMake build

+ + +Building CREST with CMake works with the following chain of commands (in this example with `gfortran/gcc` compilers): +```bash +export FC=gfortran CC=gcc +cmake -B _build -DCMAKE_BUILD_TYPE=Release +``` +and then to build the CREST binary +```bash +make -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. +
+ +

meson build

@@ -97,25 +118,6 @@ When attempting to build with `gfortran` and `gcc`, add `-Dla_backend=mkl` to th By default the `meson` build will create a **statically** linked binary.
-
-

CMake build

- - -For the setup of CMake see also the [CMake setup](https://github.com/grimme-lab/xtb/blob/master/cmake/README.adoc) page hosted at the `xtb` repository. -Building CREST with CMake works with the following chain of commands: -```bash -export FC=gfortran CC=gcc -cmake -B _build -DCMAKE_BUILD_TYPE=Release -``` -and then to build the CREST binary -```bash -make -C _build -``` - -The CMake build of CREST is focused on and tested with the GNU `gfortran`/`gcc` compilers. The Intel compilers could technically be used as well, but in our experience the respective build is more fragile than its static `meson` counterpart. - -By default the `CMake` build will create a **dynamically** linked binary. -

Conda build

diff --git a/assets/template/metadata.f90 b/assets/template/metadata.f90 index b2e4da29..38a4e74b 100644 --- a/assets/template/metadata.f90 +++ b/assets/template/metadata.f90 @@ -10,4 +10,4 @@ character(len=*),parameter :: gfnffvar = "@gfnffvar@" character(len=*),parameter :: tblitevar = "@tblitevar@" character(len=*),parameter :: xhcffvar = "@xhcffvar@" - +character(len=*),parameter :: lwoniomvar = "@lwoniomvar@" diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 06497a99..e6a7e09d 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -13,21 +13,34 @@ # # You should have received a copy of the GNU Lesser General Public License # along with crest. If not, see . +# Set the module path for CMake includes +######################################################################################### +######################################################################################### +# Add modules to the CMake build list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/modules") set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) + +# specify module installation directory install( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/modules/" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" ) -set( - module-dir - "${PROJECT_NAME}/${CMAKE_Fortran_COMPILER_ID}-${CMAKE_Fortran_COMPILER_VERSION}" -) -set(module-dir "${module-dir}" PARENT_SCOPE) +# Options for enabling or disabling features +option(WITH_OpenMP "Enable OpenMP support" TRUE) +option(WITH_TBLITE "Enable support for tblite" TRUE) +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_XHCFF "Enable support for XHCFF" FALSE) +option(WITH_LWONIOM "Enable support for lwONIOM" TRUE) +option(STATICBUILD "Attempt to link everything statically" FALSE) # doesn't work yet + +######################################################################################### +######################################################################################### # Set build type as CMake does not provide defaults if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) @@ -42,57 +55,28 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) ) endif() +######################################################################################### +######################################################################################### -### Options and defaults - -include("${CMAKE_CURRENT_SOURCE_DIR}/modules/crest-utils.cmake") -set(fortran_minimal_versions "GNU;7.5" "Intel;19.0") -check_minimal_compiler_version("Fortran" "${fortran_minimal_versions}") - -option(WITH_OpenMP "Enable support for shared memory parallelisation with OpenMP" TRUE) - -option(WITH_TBLITE "Enable build with the lightweight tight-binding library" TRUE) - -option(WITH_TOMLF "Enable build with toml-f support" TRUE) - -option(WITH_GFN0 "Enable build with GFN0-xTB support" TRUE) - -option(WITH_GFNFF "Enable build with GFN-FF support" TRUE) - -option(WITH_XHCFF "Enable build with XHCFF support" FALSE) - -option(WITH_LWONIOM "Enable build with lwONIOM support" TRUE) - -if(NOT DEFINED "${PROJECT_NAME}-dependency-method") - set( - "${PROJECT_NAME}-dependency-method" - "subproject" "cmake" "pkgconf" "fetch" - ) -endif() - -# -# Compiler settings -# +# Compiler settings for GNU and Intel Fortran compilers if(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") -# set(dialect "-fdefault-real-8 -fdefault-double-8 -ffree-line-length-none -fbacktrace") set(dialect "-g -O0 -fbacktrace -ffree-line-length-none -fbacktrace") - set(bounds "-fbounds-check") -endif() -if(CMAKE_Fortran_COMPILER_ID MATCHES "Intel") -# set(dialect "-axAVX2 -r8 -traceback") - set(dialect "-g -O2 -r8 -align array64byte -traceback") - set(bounds "-check bounds") -endif() -if(CMAKE_Fortran_COMPILER_ID MATCHES "PGI") - set(dialect "-Mbackslash -Mallocatable=03 -r8 -traceback") + set(bounds "-fbounds-check -ffpe-trap=invalid,zero,overflow") +elseif(CMAKE_Fortran_COMPILER_ID MATCHES "Intel") + set(dialect "-g -O2 -r8 -align array64byte -traceback") + set(bounds "-check all -fpe0") +else() + message(FATAL_ERROR "Please use an Intel or GNU compiler!") endif() + +# Apply the compiler flags set(CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} ${bounds}" PARENT_SCOPE) set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${dialect}" PARENT_SCOPE) +######################################################################################### +######################################################################################### -# # Populate crest_metadata.fh -# set(version ${PROJECT_VERSION}${SOVERSION}) execute_process(COMMAND git show -s --format=%h RESULT_VARIABLE git_return @@ -111,7 +95,7 @@ set(fcver ${CMAKE_Fortran_COMPILER_VERSION}) set(ccid ${CMAKE_C_COMPILER_ID}) set(ccver ${CMAKE_C_COMPILER_VERSION}) set(bsystem "cmake ${CMAKE_VERSION}") -set(tomlfvar "${WITH_TOMLF}")#string(TOLOWER "${MY_BOOL}" ${WITH_TOMLF})) +set(tomlfvar "${WITH_TOMLF}") set(gfn0var "${WITH_GFN0}") set(gfnffvar "${WITH_GFNFF}") set(tblitevar "${WITH_TBLITE}") @@ -123,12 +107,6 @@ configure_file( "${PROJECT_BINARY_DIR}/crest_metadata.fh" @ONLY ) -# gfortran needs the file at another place... -configure_file( - "${PROJECT_SOURCE_DIR}/assets/template/metadata.f90" - "${PROJECT_BINARY_DIR}/include/crest_metadata.fh" - @ONLY -) - - +######################################################################################### +######################################################################################### diff --git a/config/modules/Findgfn0.cmake b/config/modules/Findgfn0.cmake index f5699b7a..a44f8c8d 100644 --- a/config/modules/Findgfn0.cmake +++ b/config/modules/Findgfn0.cmake @@ -19,29 +19,19 @@ set(_pkg "GFN0") set(_url "https://github.com/pprcht/gfn0") 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() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +set (found FALSE) if(TARGET "gfn0::gfn0") set (found TRUE) -else() - set (found FALSE) endif() -message("-- Found GFN0: ${found}") +message(STATUS "Found GFN0-xTB: ${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/Findgfnff.cmake b/config/modules/Findgfnff.cmake index 9308b613..faf16024 100644 --- a/config/modules/Findgfnff.cmake +++ b/config/modules/Findgfnff.cmake @@ -19,29 +19,19 @@ set(_pkg "GFNFF") set(_url "https://github.com/pprcht/gfnff") 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() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +set(found FALSE) if(TARGET "gfnff::gfnff") set (found TRUE) -else() - set (found FALSE) endif() -message("-- Found GFN-FF: ${found}") +message(STATUS "Found GFN-FF: ${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/Findlwoniom.cmake b/config/modules/Findlwoniom.cmake index 8c131dbe..1f825b13 100644 --- a/config/modules/Findlwoniom.cmake +++ b/config/modules/Findlwoniom.cmake @@ -16,32 +16,22 @@ set(_lib "lwoniom") set(_pkg "LWONIOM") -set(_url "https://github.com/pprcht/lwoniom") +set(_url "https://github.com/crest-lab/lwoniom") 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() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +set(found FALSE) if(TARGET "lwoniom::lwoniom") set (found TRUE) -else() - set (found FALSE) endif() -message("-- Found lwONIOM: ${found}") +message(STATUS "Found lwONIOM: ${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/Findtblite.cmake b/config/modules/Findtblite.cmake index 51464b5d..9ca3cfc3 100644 --- a/config/modules/Findtblite.cmake +++ b/config/modules/Findtblite.cmake @@ -19,29 +19,19 @@ set(_pkg "TBLITE") set(_url "https://github.com/tblite/tblite") 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() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +set(found FALSE) if(TARGET "tblite::tblite") set (found TRUE) -else() - set (found FALSE) endif() -message("-- Found tblite: ${found}") +message(STATUS "Found tblite: ${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/Findtoml-f.cmake b/config/modules/Findtoml-f.cmake index 81eeb159..ca1da4cb 100644 --- a/config/modules/Findtoml-f.cmake +++ b/config/modules/Findtoml-f.cmake @@ -19,12 +19,7 @@ set(_pkg "TOML-F") set(_url "https://github.com/toml-f/toml-f") 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() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") @@ -36,12 +31,8 @@ if(TARGET "toml-f::toml-f") else() set (found FALSE) endif() -message("-- Found toml-f: ${found}") +message(STATUS "Found toml-f: ${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/Findxhcff.cmake b/config/modules/Findxhcff.cmake index bcfaae4e..fff680e1 100644 --- a/config/modules/Findxhcff.cmake +++ b/config/modules/Findxhcff.cmake @@ -19,29 +19,19 @@ set(_pkg "XHCFF") set(_url "https://github.com/zellerf/xhcff-lib") 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() include("${CMAKE_CURRENT_LIST_DIR}/crest-utils.cmake") crest_find_package("${_lib}" "${${_pkg}_FIND_METHOD}" "${_url}") +set(found FALSE) if(TARGET "xhcff::xhcff") set (found TRUE) -else() - set (found FALSE) endif() -message("-- Found xhcff: ${found}") +message(STATUS "Found xhcff: ${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/crest-utils.cmake b/config/modules/crest-utils.cmake index dfc38046..375714a2 100644 --- a/config/modules/crest-utils.cmake +++ b/config/modules/crest-utils.cmake @@ -14,6 +14,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with crest. If not, see . +######################################################################################### +######################################################################################### + # Handling of subproject dependencies macro( "crest_find_package" @@ -24,12 +27,17 @@ macro( string(TOLOWER "${package}" _pkg_lc) string(TOUPPER "${package}" _pkg_uc) + + # iterate through lookup types in order foreach(method ${methods}) if(TARGET "${package}::${package}") break() endif() +######################################################################################### + + # look for a -config.cmake on the system if("${method}" STREQUAL "cmake") if(DEFINED "${_pkg_uc}_DIR") set("_${_pkg_uc}_DIR") @@ -42,6 +50,9 @@ macro( endif() endif() +######################################################################################### + + # look for dependency via pkgconf if("${method}" STREQUAL "pkgconf") find_package(PkgConfig QUIET) pkg_check_modules("${_pkg_uc}" QUIET "${package}") @@ -63,6 +74,9 @@ macro( endif() endif() +######################################################################################### + + # look for SOURCE in the subprojects directory (we usually prefer this one) if("${method}" STREQUAL "subproject") if(NOT DEFINED "${_pkg_uc}_SUBPROJECT") set("_${_pkg_uc}_SUBPROJECT") @@ -89,6 +103,9 @@ macro( endif() endif() +######################################################################################### + + # finally, we can try to download sources if("${method}" STREQUAL "fetch") message(STATUS "Retrieving ${package} from ${url}") include(FetchContent) @@ -112,6 +129,8 @@ macro( break() endif() +######################################################################################### + endforeach() if(TARGET "${package}::${package}") @@ -138,9 +157,10 @@ macro( endif() endmacro() -# +######################################################################################### +######################################################################################### + # Check current compiler version requirements. -# function (check_minimal_compiler_version lang compiler_versions) while(compiler_versions) list(POP_FRONT compiler_versions compiler version) diff --git a/docs/man/crest.adoc b/docs/man/crest.adoc index 7710aecb..b59a4d1b 100644 --- a/docs/man/crest.adoc +++ b/docs/man/crest.adoc @@ -275,6 +275,47 @@ The user is able to include additional constraints to **ALL** xtb**(1)** calcula Sum of population for structures considered in msRRHO average. [_default_: **0.9** (= 90%)] +=== options for MSREACT automated mass spectra fragment generator + + *-msreact*:: + start the msreact mode + + *-msnoattrh*:: + deactivate attractive potential between hydrogen and LMO centers + + *-msnshifts* _int_:: + perform n optimizations with randomly shifted atom postions (default 0) + + *-msnshifts* _int_:: + perform n optimizations with randomly shifted atom postions and repulsive potential applied to bonds (default 0) + + *-msnbonds* _int_:: + maximum number of bonds between atoms pairs for applying repulsive potential (default 3) + + *-msmolbar*:: + sort out topological duplicates by molbar codes (requires sourced "molbar") + + *-msinchi*:: + sort out topological duplicates by inchi codes (requires sourced "obabel") + + *-msnfrag* _int_:: + number of fragments that are printed by msreact (random selection) + + *-msiso*:: + print only non-dissociated structures (isomers) + + *-msnoiso*:: + print only dissociated structures + + *-mslargeprint*:: + do not remove temporary files and MSDIR with constrained optimizations + + *-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 *-zsort*:: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e407cfb5..51c70c1a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,7 @@ add_subdirectory("rigidconf") add_subdirectory("discretize") add_subdirectory("entropy") add_subdirectory("legacy_algos") +add_subdirectory("msreact") set(dir "${CMAKE_CURRENT_SOURCE_DIR}") @@ -59,8 +60,6 @@ list(APPEND srcs "${dir}/marqfit.f90" "${dir}/minitools.f90" "${dir}/miscdata.f90" - "${dir}/msmod.f90" - "${dir}/msreact.f90" "${dir}/ncigeo.f90" "${dir}/ompmklset.F90" "${dir}/printouts.f90" diff --git a/src/classes.f90 b/src/classes.f90 index 800bc966..b6661674 100644 --- a/src/classes.f90 +++ b/src/classes.f90 @@ -471,6 +471,19 @@ module crest_data type(lwoniom_input),allocatable :: ONIOM_input !================================================! + !>--- msreact mode settings + 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 + integer :: msnfrag = 0 !number of fragments that are printed in msreact mode + character(len=80) :: msinput = '' !name of input file for msreact + !>--- general logical data logical :: allrot = .true. !> use all rotational constants for check instead of mean? logical :: altopt = .false. @@ -567,7 +580,6 @@ module crest_data 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 - contains procedure :: allocate => allocate_metadyn procedure :: deallocate => deallocate_metadyn diff --git a/src/confparse.f90 b/src/confparse.f90 index 7baa89b9..3538154c 100644 --- a/src/confparse.f90 +++ b/src/confparse.f90 @@ -305,6 +305,17 @@ subroutine parseflags(env,arg,nra) env%solv_file = '' env%solu_file = '' +!>--- options for msreact + env%msiso = .false. ! msiso and msnoiso are mutually exclusive !!! + env%msnoiso = .false. + env%msmolbar = .false. + env%mslargeprint=.false. ! dont remove temporary files + env%msattrh=.true. ! add attractive potential for H-atoms + env%msnbonds = 3 ! distance of bonds up to nonds are stretched + env%msnshifts = 0 ! number of random shifts applied to whole mol + env%msnshifts2 = 0 ! number of random shifts applied to whole mol + env%msnfrag = 0 !number of fragments that are printed in msreact mode + !&> !=========================================================================================! @@ -536,10 +547,11 @@ subroutine parseflags(env,arg,nra) env%autozsort = .false. exit - case ('-msreact') + case ('-msreact') 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)) @@ -1007,6 +1019,43 @@ subroutine parseflags(env,arg,nra) env%shake = nint(xx(1)) end select !> QCG end if + +!========================================================================================! +!------- Flags for msreact +!========================================================================================! + if (env%crestver == crest_msreac) then + select case (argument) !> msreact + case('-msnoiso') !> filter out non fragmentated structures in msreact + env%msnoiso=.true. + case('-msiso') !> filter out fragmentated structures in msreact + 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) + case('-msnshifts') ! give number of times atoms are randomly shifted before optimization + call readl(arg(i + 1),xx,j) + env%msnshifts = xx(1) + 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) + case('-msnfrag') ! give number of structures that should be generated + call readl(arg(i + 1),xx,j) + env%msnfrag = xx(1) + case('-msmolbar') !> filter out structures with same molbar code in msreact + env%msmolbar=.true. + case('-msinchi') !> filter out structures with same inchi code in msreact + env%msinchi=.true. + case('-msnoattrh') !> add attractive potential for H-atoms + env%msattrh=.false. + case('-mslargeprint') !> additional printouts and keep MSDIR + 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)) + if (ctmp(1:1) .ne. '-') then + env%msinput = trim(ctmp) + end if + end select !> msreact + end if !========================================================================================! !------- other general flags !========================================================================================! diff --git a/src/cregen.f90 b/src/cregen.f90 index 60683d4c..b53ec795 100644 --- a/src/cregen.f90 +++ b/src/cregen.f90 @@ -28,6 +28,7 @@ !> 6 - energy sorting only !> 9 - no sorting, only check groups !> 12 - no topology check, turn ewin to infty +!> 13 - no topology check, ewin and rmsd checking (msreact settings) !=========================================================================================! !=========================================================================================! @@ -364,6 +365,11 @@ subroutine cregen_files(env,fname,oname,cname,simpleset,userinput,iounit) oname = "crest_mecp_search.xyz.sorted" cname = "crest_ensemble.xyz" end if + if (simpleset == 13) then !> MSREACT files + fname = "crest_unique_products.xyz" + oname = "crest_unique_products.sorted" + cname = "crest_msreact_products.xyz" + end if if (simpleset == 15) then !> crossing files call checkname_xyz('confcross',fname,oname) cname = trim(fname)//'.unique' @@ -420,6 +426,13 @@ subroutine cregen_prout(env,simpleset,pr1,pr2,pr3,pr4) pr4 = .true. end if + if (simpleset == 13) then + pr1 = .false. + pr2 = .false. + pr3 = .false. + pr4 = .false. + end if + return end subroutine cregen_prout @@ -515,6 +528,21 @@ subroutine cregen_director(env,simpleset,checkbroken,sortE,sortRMSD,sortRMSD2, & anal = .false. end if + if(simpleset == 13)then !msreact mode + checkbroken = .false. + sorte = .true. + sortRMSD = .true. + sortRMSD2 = .false. + repairord = .false. + newfile = .true. + conffile = .true. + topocheck = .false. + checkez = .false. + bonusfiles= .false. + anal = .false. + + endif + return end subroutine cregen_director diff --git a/src/legacy_algos/cregen_old.f90 b/src/legacy_algos/cregen_old.f90 index 28208ff2..053f2ca4 100644 --- a/src/legacy_algos/cregen_old.f90 +++ b/src/legacy_algos/cregen_old.f90 @@ -32,7 +32,7 @@ subroutine cregen2(env) use axis_module use miscdata, only: rcov use utilities - !$ use omp_lib + use omp_lib implicit none type(systemdata) :: env ! MAIN STORAGE OS SYSTEM DATA diff --git a/src/legacy_algos/protonate.f90 b/src/legacy_algos/protonate.f90 index 1956ea9f..aec27abd 100644 --- a/src/legacy_algos/protonate.f90 +++ b/src/legacy_algos/protonate.f90 @@ -63,6 +63,14 @@ subroutine protonate(env,tim) logical :: ex + interface + subroutine xtblmo(env, print) + import :: systemdata + type(systemdata) :: env + logical, optional :: print + end subroutine xtblmo + end interface + !--- printout & clean directory call protclean call prothead @@ -87,7 +95,7 @@ subroutine protonate(env,tim) !--- do the xTB calculation for the LMOs call tim%start(1,'LMO calc.') - call xtblmo(env) + call xtblmo(env,.true.) call tim%stop(1) inquire (file='coordprot.0',exist=ex) if (.not.ex) then @@ -197,7 +205,7 @@ end subroutine protonate !-------------------------------------------------------------------------------------------- ! A quick single point xtb calculation and calculate LMOs !-------------------------------------------------------------------------------------------- -subroutine xtblmo(env) +subroutine xtblmo(env,print) use crest_parameters use iomod use crest_data @@ -207,6 +215,7 @@ subroutine xtblmo(env) character(len=:),allocatable :: jobcall integer :: io character(len=*),parameter :: pipe = ' > xtb.out 2>/dev/null' + logical, optional :: print ! leave the xtb.out file (e.g. for msreact mode) !---- setting threads if (env%autothreads) then @@ -228,7 +237,7 @@ subroutine xtblmo(env) !---- cleanup call remove(fname) - call remove('xtb.out') + if (.not. print) call remove('xtb.out') call remove('energy') call remove('charges') call remove('xtbrestart') diff --git a/src/meson.build b/src/meson.build index 09b3c439..e58da187 100644 --- a/src/meson.build +++ b/src/meson.build @@ -26,6 +26,7 @@ subdir('rigidconf') subdir('discretize') subdir('entropy') subdir('legacy_algos') +subdir('msreact') srcs += files( @@ -56,8 +57,6 @@ srcs += files( 'marqfit.f90', 'minitools.f90', 'miscdata.f90', - 'msmod.f90', - 'msreact.f90', 'ncigeo.f90', 'ompmklset.F90', 'printouts.f90', diff --git a/src/miscdata.f90 b/src/miscdata.f90 index 2b7ca31d..2c1a1866 100644 --- a/src/miscdata.f90 +++ b/src/miscdata.f90 @@ -69,6 +69,37 @@ module miscdata & * aatoau * 4.0_wp / 3.0_wp !&> +!&< + ! Radius used in QCxMS (in au) + real(wp), parameter :: QCxMS_Rad(118) = aatoau * [ & + & 0.32_wp,0.37_wp, & ! H,He + & 1.30_wp,0.99_wp,0.84_wp,0.75_wp,0.71_wp,0.64_wp,0.60_wp,0.62_wp, & ! Li-Ne + & 1.60_wp,1.40_wp,1.24_wp,1.14_wp,1.09_wp,1.04_wp,1.00_wp,1.01_wp, & ! Na-Ar + & 2.00_wp,1.74_wp, & ! K,Ca + & 1.59_wp,1.48_wp,1.44_wp,1.30_wp,1.29_wp, & ! Sc- + & 1.24_wp,1.18_wp,1.17_wp,1.22_wp,1.20_wp, & ! -Zn + & 1.23_wp,1.20_wp,1.20_wp,1.18_wp,1.17_wp,1.16_wp, & ! Ga-Kr + & 2.15_wp,1.90_wp, & ! Rb,Sr + & 1.76_wp,1.64_wp,1.56_wp,1.46_wp,1.38_wp, & ! Y- + & 1.36_wp,1.34_wp,1.30_wp,1.36_wp,1.40_wp, & ! -Cd + & 1.42_wp,1.40_wp,1.40_wp,1.37_wp,1.36_wp,1.36_wp, & ! In-Xe + & 2.38_wp,2.06_wp, & ! Cs,Ba + & 1.94_wp,1.84_wp,1.90_wp,1.88_wp,1.86_wp,1.85_wp,1.83_wp, & ! La-Eu + & 1.82_wp,1.81_wp,1.80_wp,1.79_wp,1.77_wp,1.77_wp,1.78_wp, & ! Gd-Yb + & 1.74_wp,1.64_wp,1.58_wp,1.50_wp,1.41_wp, & ! Lu- + & 1.36_wp,1.32_wp,1.30_wp,1.30_wp,1.32_wp, & ! -Hg + & 1.44_wp,1.45_wp,1.50_wp,1.42_wp,1.48_wp,1.46_wp, & ! Tl-Rn + & 2.42_wp,2.11_wp, & ! Fr,Ra + & 2.01_wp,1.90_wp,1.84_wp,1.83_wp,1.80_wp,1.80_wp,& ! Ac-Pu + ! from covalent 2009 covalent radii, such that it is complete up to 118 + & 1.49_wp, & ! 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 +!&> + + !&< !> D3 pairwise van-der-Waals radii (only homoatomic pairs present here) real(wp), parameter :: vdw_D3(1:94) = aatoau * [& diff --git a/src/msmod.f90 b/src/msmod.f90 deleted file mode 100644 index 0a741de9..00000000 --- a/src/msmod.f90 +++ /dev/null @@ -1,173 +0,0 @@ -!================================================================================! -! This file is part of crest. -! -! Copyright (C) 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 . -!================================================================================! - -module msmod - - use iso_fortran_env, wp => real64 - implicit none - - - !-- storage for a single mol - type msmol - integer :: nat - integer,allocatable :: at(:) - real(wp),allocatable :: xyz(:,:) - real(wp) :: etot - integer :: chrg - integer :: nfrag - integer,allocatable :: frag(:) - integer :: gen - contains - procedure :: newmol => msmod_newmol - procedure :: dist => msmoldist - end type msmol - - public :: msmol - - !-- isomer list - type ilist - integer :: nmol - type(msmol),allocatable :: mol(:) - logical,allocatable :: new(:) - contains - procedure :: dealloc => deallocate_ilist - procedure :: append => ilist_append - end type ilist - - !-- fragmentized structure list - type flist - integer :: nmol - type(msmol),allocatable :: mol(:) - logical,allocatable :: new(:) - contains - procedure :: dealloc => deallocate_flist - end type flist - - - !-- global data for msreact - type msobj - - real(wp) :: ethr = 1.0_wp !cov. struc. chekc. thr. - real(wp) :: wbormsd = 0.5_wp !wbo rmsd comp. thr. - - real(wp) :: T = 3000.0_wp !start temperature - real(wp) :: fc = 0.05_wp !start fc - real(wp) :: cdist = 1.5_wp !constraing distance scaling factor of rcov - integer :: maxc = 30 !max optimization cycle - - type(ilist) :: il - type(flist) :: fl - - end type msobj - - public :: msobj - - private -contains - -subroutine deallocate_ilist(self) - class(ilist) :: self - if(allocated(self%mol)) deallocate(self%mol) - if(allocated(self%new)) deallocate(self%new) - return -end subroutine deallocate_ilist - -subroutine deallocate_flist(self) - class(flist) :: self - if(allocated(self%mol)) deallocate(self%mol) - if(allocated(self%new)) deallocate(self%new) - return -end subroutine deallocate_flist - -subroutine msmod_newmol(self,nat,xyz,at,etot,chrg,gen) - implicit none - class(msmol) :: self - integer :: nat - real(wp) :: xyz(3,nat) - integer :: at(nat) - real(wp) :: etot - integer :: chrg - integer :: gen - self%nat = nat - allocate(self%xyz(3,nat)) - self%xyz = xyz - allocate(self%at(nat)) - self%at = at - self%etot = etot - self%chrg = chrg - self%gen = gen - return -end subroutine msmod_newmol - -!calculate the distance between two atoms i and j -function msmoldist(self,i,j) result(dist) - implicit none - class(msmol) :: self - integer :: i,j - real(wp) :: dist - dist = 0.0_wp - if(allocated(self%xyz))then - dist=(self%xyz(1,i)-self%xyz(1,j))**2 + & - & (self%xyz(2,i)-self%xyz(2,j))**2 + & - & (self%xyz(3,i)-self%xyz(3,j))**2 - dist = sqrt(dist) - endif - return -end function msmoldist - - - -subroutine ilist_append(self,nat,at,xyz,etot,chrg,gen) - implicit none - class(ilist) :: self - type(msmol) :: mol - integer :: nat - real(wp) :: xyz(3,nat) - integer :: at(nat) - real(wp) :: etot - integer :: chrg - integer :: gen - - type(msmol),allocatable :: dummy(:) - logical,allocatable :: btmp(:) - integer :: n - call mol%newmol(nat,xyz,at,etot,chrg,gen) - if(.not.allocated(self%mol))then - self%nmol = 1 - allocate(self%mol(1)) - allocate(self%new(1)) - self%mol(1) = mol - self%new(1) = .true. - else - n = self%nmol + 1 - allocate(dummy(n)) - allocate(btmp(n)) - dummy(1:self%nmol) = self%mol(1:self%nmol) - dummy(n) = mol - btmp(1:self%nmol) = self%new(1:self%nmol) - btmp(n) = .true. - self%nmol = n - call move_alloc(btmp, self%new) - call move_alloc(dummy, self%mol) - endif - return -end subroutine ilist_append - - -end module msmod diff --git a/src/msreact.f90 b/src/msreact.f90 deleted file mode 100644 index dc775369..00000000 --- a/src/msreact.f90 +++ /dev/null @@ -1,346 +0,0 @@ -!================================================================================! -! This file is part of crest. -! -! Copyright (C) 2020 Stefan Grimme, 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 . -!================================================================================! - -!==============================================================! -! the handler function is the sub-program that is called -! by crest, i.e., within this routine the seperate input file is -! read and the calculation is started. -!==============================================================! -subroutine msreact_handler(env,tim) - use crest_parameters - use crest_data - use msmod - use strucrd - implicit none - - type(systemdata) :: env - type(timer) :: tim - - type(msobj) :: mso !a new msreact object - type(coord) :: struc - - interface - subroutine msreact_topowrap(mol,pair,paths,wboname) - import :: msmol - implicit none - type(msmol) :: mol - integer :: pair(mol%nat*(mol%nat+1)/2) - integer :: paths(mol%nat*(mol%nat+1)/2,mol%nat) - character(len=*),optional :: wboname - end subroutine msreact_topowrap - end interface - integer :: nat - integer,allocatable :: pair(:) - integer,allocatable :: paths(:,:) - integer :: k - - call tim%start(1,'MSREACT') - - !-- read the input coord and put it into the - ! iso-list as Gen 0 structure - call struc%open('coord') - struc%xyz=struc%xyz*bohr !to Angström, from this point on by convention! - call mso%il%append(struc%nat,struc%at,struc%xyz,0.0_wp,env%chrg,0) - call struc%deallocate() - - !-- additional input file could be read here - !call msinputreader(mso) - - - nat = mso%il%mol(1)%nat - k=nat*(nat+1)/2 - allocate(pair(k),paths(k,nat)) - call msreact_topowrap(mso%il%mol(1),pair,paths,'wbo') - - !-- setting the threads for correct parallelization - if(env%autothreads)then - call ompautoset(env%threads,6,env%omp,env%MAXRUN,0) !set global OMP/MKL variable for xtb jobs - endif - - !-- do the directory setup and optimizations - call msreact(mso,mso%il%mol(1),nat,pair,3) - - - deallocate(paths,pair) - - call tim%stop(1) - return -end subroutine msreact_handler - -!==============================================================! -! the main implementation of the msreact algo should go here -!==============================================================! -subroutine msreact(mso,mol,nat,pair,nbonds) - use crest_parameters - use msmod - use iomod - use miscdata, only: rcov - use utilities, only: lin - implicit none - - type(msobj) :: mso !main storage object - type(msmol) :: mol ! xyz etc - - integer :: nat - integer :: pair(nat*(nat+1)/2) - !integer :: paths(nat*(nat+1)/2,nat) - integer :: nbonds - integer :: i,j,k - integer :: p - integer :: np - integer :: io - - character(len=:),allocatable :: subdir - character(len=40) :: pdir - character(len=512) :: thisdir - real(wp) :: constr_dist - - !-- main subdirectory handling - call getcwd(thisdir) - subdir='MSDIR' - io = makedir(subdir) - call chdir(subdir) - - - !-- get specific pairs - np=0 - do p=1,nbonds ! bonds in between -! write(*,'(1x,a,i0,a)') '1,',p+1,' pairs' - do i=1,nat - do j=i,nat - k=lin(i,j) - if(p.eq.1.and.pair(k).eq.1.and.(mol%at(i).eq.1.or.mol%at(j).eq.1)) cycle - if(pair(k)==p)then - np = np+1 - write(pdir,'(a,i0)')'Pair_',np - constr_dist = mso%cdist*(rcov(mol%at(i))+rcov(mol%at(j)))*bohr + float(p) -! write(*,*) mol%at(i),mol%at(j),constr_dist - call isodir(mso,trim(pdir),mol,i,j,constr_dist) - endif - enddo - enddo - enddo - - write(*,*) '# of distortions',np - call msreact_jobber(np,'Pair_',.false.) - - call msreact_collect(mol%nat,np,'products.xyz') - call rename(subdir//'/'//'products.xyz','products.xyz') - call chdir(thisdir) - return -end subroutine msreact - - -!============================================================! -! make a dir for a structure without fragments, -! a controlfile with constraints on atoms A and B (at dist D) -! will be written into the directory -!============================================================! -subroutine isodir(mso,dirname,mol,A,B,D) - use crest_parameters - use msmod - use iomod - use strucrd, only : wrxyz - implicit none - type(msobj) :: mso - character(len=*) :: dirname - type(msmol) :: mol - integer :: A,B - real(wp) :: D - - character(len=:),allocatable :: fname - character(len=20) :: dumm - integer :: io,ich - - io = makedir(dirname) !create the directory - - fname = trim(dirname)//'/'//'struc.xyz' - open(newunit=ich,file=fname) - call wrxyz(ich,mol%nat,mol%at,mol%xyz) - close(ich) - - fname = trim(dirname)//'/'//'.CHRG' - open(newunit=ich,file=fname) - write(ich,'(i0)') mol%chrg + 1 ! EI +1, DEA -1, CID 0 - close(ich) - - fname = trim(dirname)//'/'//'.xc1' - open(newunit=ich, file=fname) - write(ich,'(a)') '$scc' - write(dumm,'(f16.2)') mso%T - write(ich,'(1x,a,a)')'temp=',adjustl(trim(dumm)) - write(ich,'(a)')'$constrain' - write(dumm,'(f16.4)') mso%fc - write(ich,'(3x,a,a)')'force constant=',adjustl(trim(dumm)) - write(ich,'(3x,a,1x,i0,a,1x,i0,a,1x,f8.5)') 'distance:',A,',',B,',',D - close(ich) - - fname = trim(dirname)//'/'//'.xc2' - open(newunit=ich, file=fname) - write(ich,'(a)') '$scc' - write(dumm,'(f16.2)') mso%T - write(ich,'(1x,a,a)')'temp=',adjustl(trim(dumm)) - write(ich,'(a)') '$opt' - write(ich,'(1x,a)') 'maxcycle=5' - write(ich,'(a)') '$write' - write(ich,'(1x,a)') 'wiberg=true' - close(ich) - - return -end subroutine isodir - -!=====================================================================! -! The job construction routine for MSREACT -! (will have to be modified later, for now it is for testing) -!=====================================================================! -subroutine msreact_jobber(ndirs,base,niceprint) - use crest_parameters - use msmod - use iomod - implicit none - integer :: ndirs - character(len=*) :: base - logical :: niceprint - - character(len=1024) :: jobcall - character(len=1024) :: jobcall2 - - jobcall = '' - jobcall2 = '' - - write(jobcall,'(a)') 'xtb struc.xyz --opt loose --input .xc1 > split.out 2>/dev/null' - write(jobcall2,'(a)') 'xtb xtbopt.xyz --opt crude --input .xc2 > xtb.out 2>/dev/null' - jobcall = trim(jobcall)//' ; '//trim(jobcall2) - - !-- directories must be numbered consecutively - call opt_OMP_loop(ndirs,base,jobcall,niceprint) - write(*,*) - write(*,*) 'done.' - return -end subroutine msreact_jobber - - - -!=====================================================================! -! A wrapper to generate the topology for a molecule within the -! MSREACT subprogram -!=====================================================================! -subroutine msreact_topowrap(mol,pair,paths,wboname) - use crest_parameters - use msmod - use zdata - use adjacency - use utilities, only: lin - implicit none - type(msmol) :: mol - integer :: pair(mol%nat*(mol%nat+1)/2) - !integer :: pair(mol%nat,mol%nat) - integer :: paths(mol%nat*(mol%nat+1)/2,mol%nat) - character(len=*),optional :: wboname - type(zmolecule) :: zmol - - integer,allocatable :: A(:,:) - integer,allocatable :: prev(:,:) - real(wp),allocatable :: E(:,:) - real(wp),allocatable :: dist(:,:) - - integer :: lpath,i,j,k - integer,allocatable :: path(:) - logical :: ex - - - ex=.false. - if(present(wboname))then - inquire(file=wboname,exist=ex) - endif - if(ex)then - call simpletopo(mol%nat,mol%at,mol%xyz,zmol,.false.,.false.,wboname) - else - mol%xyz = mol%xyz / bohr !CN based topo requires Bohrs - call simpletopo(mol%nat,mol%at,mol%xyz,zmol,.false.,.false.,'') - mol%xyz = mol%xyz * bohr - endif - - allocate(A(mol%nat,mol%nat),E(mol%nat,mol%nat)) - call zmol%adjacency(A,E) - - allocate(prev(mol%nat,mol%nat),dist(mol%nat,mol%nat)) - - call FloydWarshall(mol%nat,A,E,dist,prev) - allocate(path(mol%nat), source = 0) - do i=1,mol%nat - do j=i,mol%nat - path = 0 - call getPathFW(mol%nat,prev,i,j,path,lpath) - !write(*,*) path(1:lpath) - k=lin(i,j) - pair(k) = lpath - 1 ! number of bonds - paths(k,:) = path(:) - enddo - enddo - - deallocate(dist,prev) - deallocate(E,A) - - call zmol%deallocate() !clear the zmol memory - return -end subroutine msreact_topowrap - -!========================================================================! -! collect structures of optimized molecules -! xyz files should still have the same number and order of atoms -!========================================================================! -subroutine msreact_collect(nat,np,outfile) - use crest_parameters - use strucrd - implicit none - integer :: nat - integer :: np - character(len=*) :: outfile - integer :: ich - character(len=40) :: pdir - character(len=:),allocatable :: optfile - character(len=128) :: newcomment - integer :: p,p2 - logical :: ex - integer,allocatable :: at(:) - real(wp),allocatable :: xyz(:,:) - real(wp) :: etot - - - allocate(at(nat),xyz(3,nat)) - open(newunit=ich,file=outfile) - p=0 - do p2=1,np - write(pdir,'(i0,i0,a,i0)')1,p+1,'Pair_',p2 - write(pdir,'(a,i0)')'Pair_',p2 - optfile=trim(pdir)//'/'//'xtbopt.xyz' - inquire(file=optfile,exist=ex) - if(ex)then - call rdcoord(optfile,nat,at,xyz,etot) - xyz = xyz*bohr - write(newcomment,'(1x,f18.8,5x,a)')etot,trim(pdir) - call wrxyz(ich,nat,at,xyz,newcomment) - endif - enddo - close(ich) - - deallocate(xyz,at) - return -end subroutine msreact_collect diff --git a/src/msreact/CMakeLists.txt b/src/msreact/CMakeLists.txt new file mode 100644 index 00000000..b65ec325 --- /dev/null +++ b/src/msreact/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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}/msreact.f90" + "${dir}/msmod.f90" +) + +set(srcs ${srcs} PARENT_SCOPE) diff --git a/src/msreact/meson.build b/src/msreact/meson.build new file mode 100644 index 00000000..4f3cafe6 --- /dev/null +++ b/src/msreact/meson.build @@ -0,0 +1,20 @@ +# 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 . + +srcs += files( + 'msreact.f90', + 'msmod.f90', +) diff --git a/src/msreact/msmod.f90 b/src/msreact/msmod.f90 new file mode 100644 index 00000000..ba25c1e0 --- /dev/null +++ b/src/msreact/msmod.f90 @@ -0,0 +1,1154 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2020-2024 Philipp Pracht, Johannes Gorges +! +! 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 msmod + use crest_parameters + use crest_data + use strucrd + use iomod + implicit none + + !>-- storage for a single mol + type msmol + integer :: nat + integer,allocatable :: at(:) + real(wp),allocatable :: xyz(:,:) + real(wp) :: etot + integer :: chrg + integer :: nfrag + !> index of fragatoms 1 first fragment 2 second fragment + integer,allocatable :: fragi(:) + !> number of fragments in product + !> 1: isomer 1< : fragmentation; 0, not determined yet, -1 means we sort this out + integer :: fragcount + real(wp) :: molmass + contains + procedure :: newmol => msmod_newmol + procedure :: dist => msmoldist + end type msmol + + public :: msmol + + !-- product list + type plist + integer :: nmol + type(msmol),allocatable :: mol(:) + logical,allocatable :: new(:) + contains + procedure :: dealloc => deallocate_plist + procedure :: append => plist_append + procedure :: remove => plist_remove + end type plist + + !>-- global data for msreact + type msobj + real(wp) :: rcut = 1.3_wp !> cutoff for factor of sum of covalent radii for detection of fragmentation of fragment_structures + real(wp) :: T = 5000.0_wp !> electronic temperature! favours open-shell fragment generation + real(wp) :: fc = 0.05_wp !> repulsive force constant in constrained optimizations + real(wp) :: fc_attr = -0.05_wp !> attractive force constant in constrained optimizations + real(wp) :: cdist = 1.5_wp !> constraing distance scaling factor of rcov + real(wp) :: cdist_att = 0.5_wp !> constraing distance scaling factor of rcov for attractive part + real(wp) :: distthr_att = 4.0_wp !> distance threshold in Angstrom for attractive part of constraint + real(wp) :: atomshift = 0.75_wp !> shift of atoms in random atom displacement + real(wp) :: fragdist = 0.0_wp !> increase distance by to separate fragments (useful for subsequent transition state search) + integer :: maxc = 15 !>max optimization cycle in constrained optimizations + + type(plist) :: pl + end type msobj + + public :: msobj + +!=======================================================================================! +!=======================================================================================! +contains !> MODULE PROCEDURES START HERE +!=======================================================================================! +!=======================================================================================! + + subroutine deallocate_plist(self) + class(plist) :: self + if (allocated(self%mol)) deallocate (self%mol) + if (allocated(self%new)) deallocate (self%new) + return + end subroutine deallocate_plist + +!=======================================================================================! + + subroutine msmod_newmol(self,nat,xyz,at,etot,chrg,fragcount,molmass) + implicit none + class(msmol) :: self + integer :: nat + real(wp) :: xyz(3,nat) + integer :: at(nat) + real(wp) :: etot + integer :: chrg + integer :: fragcount + real(wp) :: molmass + self%nat = nat + allocate (self%xyz(3,nat)) + self%xyz = xyz + allocate (self%at(nat)) + self%at = at + self%etot = etot + self%chrg = chrg + self%fragcount = fragcount + self%molmass = molmass + return + end subroutine msmod_newmol + +!=======================================================================================! + + function msmoldist(self,i,j) result(dist) +!*************************************************** +!* calculate the distance between two atoms i and j +!*************************************************** + implicit none + class(msmol) :: self + integer :: i,j + real(wp) :: dist + dist = 0.0_wp + if (allocated(self%xyz)) then + dist = (self%xyz(1,i)-self%xyz(1,j))**2+ & + & (self%xyz(2,i)-self%xyz(2,j))**2+ & + & (self%xyz(3,i)-self%xyz(3,j))**2 + dist = sqrt(dist) + end if + return + end function msmoldist + +!=======================================================================================! + + subroutine plist_append(self,nat,at,xyz,etot,chrg,fragcount,molmass) + implicit none + class(plist) :: self + type(msmol) :: mol + integer :: nat + real(wp) :: xyz(3,nat) + integer :: at(nat) + real(wp) :: etot + integer :: chrg + integer :: fragcount + real(wp) :: molmass + + type(msmol),allocatable :: dummy(:) + logical,allocatable :: btmp(:) + integer :: n + call mol%newmol(nat,xyz,at,etot,chrg,fragcount,molmass) + if (.not.allocated(self%mol)) then + self%nmol = 1 + allocate (self%mol(1)) + allocate (self%new(1)) + self%mol(1) = mol + self%new(1) = .true. + else + n = self%nmol+1 + allocate (dummy(n)) + allocate (btmp(n)) + dummy(1:self%nmol) = self%mol(1:self%nmol) + dummy(n) = mol + btmp(1:self%nmol) = self%new(1:self%nmol) + btmp(n) = .true. + self%nmol = n + call move_alloc(btmp,self%new) + call move_alloc(dummy,self%mol) + end if + return + end subroutine plist_append + +!=======================================================================================! + + subroutine plist_remove(self,index) +!**************************************************** +!* remove entry from product with index i from list +!**************************************************** + implicit none + class(plist) :: self + type(msmol) :: mol + + integer :: n,i + integer,intent(in) :: index + + if (index < 1.or.index > self%nmol) then + write (*,*) "Invalid index. Cannot remove molecule." + return + end if + + do i = index,self%nmol-1 + self%mol(i) = self%mol(i+1) ! replace the entry with the next one + end do + + self%nmol = self%nmol-1 ! ignore last entry + + end subroutine plist_remove + +!=======================================================================================! + + subroutine get_input_energy(env,etemp,etot) +!****************************************************************************** +!* A quick xtb geometry optimization in xyz coordinates to get starting energy +!****************************************************************************** + implicit none + type(systemdata) :: env + character(len=80) :: fname,pipe + character(len=:),allocatable :: jobcall + logical :: fin + character(len=256) :: atmp + integer :: ich,iost,io,i + type(coord) :: mol + integer :: ntopo + integer,allocatable :: topo(:) + real(wp),intent(out) :: etot + real(wp) :: etemp ! electronic temperature in K + logical :: tchange = .false. + logical :: ldum + +!---- setting threads + if (env%autothreads) then + call ompautoset(env%threads,7,env%omp,env%MAXRUN,1) !set the global OMP/MKL variables for the xtb jobs + end if + +!---- small header + write (*,*) + call smallhead('xTB Geometry Optimization') +!---- some options + pipe = ' > xtb.out 2>/dev/null' + call remove('gfnff_topo') + if (.not.env%chargesfile) call remove('charges') + call remove('grad') + call remove('mos') + call remove('xtbopt.log') + call remove('xtbrestart') + + fname = '.CHRG' + open (newunit=ich,file=fname) + write (ich,'(i0)') env%chrg ! EI +1, DEA -1, CID 0, give in crest call + close (ich) + +!---- input xyz file + fname = env%inputcoords + +! write (jobcall,'(a,1x,a,f10.4,1x,a,1x,a)') & +! & trim(env%ProgName),trim(fname)//" --sp --etemp ",etemp,trim(env%gfnver),trim(pipe) + jobcall = trim(env%ProgName) + jobcall = trim(jobcall)//' '//trim(fname) + write(atmp,'(f10.4)') etemp + jobcall = trim(jobcall)//' --sp --etemp '//trim(atmp) + jobcall = trim(jobcall)//trim(env%gfnver)//trim(pipe) + + call execute_command_line(trim(jobcall),exitstat=io) + + call minigrep('xtb.out','finished run',fin) + if (.not.fin) then + write (*,*) + write (*,*) ' Initial singlepoint calculation failed!' + write (*,*) ' Please check your input.' + error stop + end if + write (*,*) 'Input energy successfully calculated.' + + call grepval('xtb.out',"| TOTAL ENERGY",ldum,etot) +!---- cleanup + call remove('xtb.out') + call remove('energy') + if (.not.env%chargesfile) call remove('charges') + call remove('grad') + call remove('mos') + call remove('xtbopt.log') + call remove('xtbtopo.mol') + call remove('.xtbopttok') + call remove('xtbrestart') + call remove('gfnff_topo') + end subroutine get_input_energy + +!=======================================================================================! + + subroutine readbasicpos(env,nbaseat,basicatlist) +!******************************************************************** +!* this subroutine reads the lmo.out file of an xtb -lmo calculation +!* and identifies the atoms which have a LP or pi-Orbital +!******************************************************************** + implicit none + type(systemdata) :: env + integer :: i,io,ich,at1,at2,j + integer,intent(out) :: nbaseat + integer,intent(out),allocatable :: basicatlist(:) + integer,allocatable :: dumlist(:) + character(len=512) :: tmp + character(len=64) :: fname + character(len=64) :: type,dumc + real(wp) :: dumr + logical :: ex + fname = 'xtb.out' + inquire (file=fname,exist=ex) + if (.not.ex) then + write (*,*) "xtb.out not found" + stop + end if + nbaseat = 0 + open (newunit=ich,file=fname) + do + read (ich,'(a)',iostat=io) tmp + if (index(tmp,'files:') .ne. 0) exit + if (index(tmp,'pi ') .ne. 0) nbaseat = nbaseat+2 ! count first two atoms of pi or delpi bond with highest participation and ignore the rest + if (index(tmp,'LP ') .ne. 0) nbaseat = nbaseat+1 ! count LP + end do + allocate (dumlist(nbaseat)) + dumlist = 0 + j = 0 + rewind (ich) + do + read (ich,'(a)',iostat=io) tmp + if (index(tmp,'starting deloc pi') .ne. 0) exit + if (index(tmp,'files:') .ne. 0) exit + if (index(tmp,'pi ') .ne. 0) then + backspace (ich) + if (tmp(64:64) == ' ') then ! if element symbol has one character, there is a space and we use this routine + if (tmp(80:80) == ' ') then + read (ich,*) dumr,type,dumr,dumr,dumr,dumr,dumr,at1,dumc,dumc,dumr,at2,dumc,dumc,dumr ! first element has one and second has one character + else + read (ich,*) dumr,type,dumr,dumr,dumr,dumr,dumr,at1,dumc,dumc,dumr,at2,dumc,dumr ! first element has one and second has two character + end if + else + if (tmp(80:80) == ' ') then + read (ich,*) dumr,type,dumr,dumr,dumr,dumr,dumr,at1,dumc,dumr,at2,dumc,dumc,dumr ! first element has two and second has pm character + else + read (ich,*) dumr,type,dumr,dumr,dumr,dumr,dumr,at1,dumc,dumr,at2,dumc,dumr ! first element has two and second has two character + end if + end if + if (findloc(dumlist,at1,1) .eq. 0) then + j = j+1 + dumlist(j) = at1 + end if + if (findloc(dumlist,at2,1) .eq. 0) then + j = j+1 + dumlist(j) = at2 + end if + end if + + if (index(tmp,'LP ') .ne. 0) then + backspace (ich) + if (tmp(64:64) == ' ') then ! if element symbol has one character, there is a space and we use this routine + read (ich,*) dumr,type,dumr,dumr,dumr,dumr,dumr,at1,dumc,dumc,dumr + else ! if element symbol has two characters, there is no space and we use this routine + read (ich,*) dumr,type,dumr,dumr,dumr,dumr,dumr,at1,dumc,dumr + end if + if (findloc(dumlist,at1,1) .eq. 0) then + j = j+1 + dumlist(j) = at1 + end if + end if + end do + close (ich) + basicatlist = pack(dumlist,dumlist .ne. 0) ! sort out zeroes + nbaseat = size(basicatlist) + write (*,"('Protonation sites at atom positions:', *(i5))") (basicatlist(i),i=1,nbaseat) + call remove('xtb.out') + end subroutine readbasicpos + +!=======================================================================================! + + subroutine fragment_structure(nat,oz,xyz,rcut,at1,at2,frag,fragcount) +!***************************************************************************************** +!* Taken and slightly modified from QCxMS mass spectra code https://github.com/qcxms/QCxMS +!* Subroutine for definition of two or more fragments +!* if at1 = 0 : global separation in (nonbonded parts), beginning with atom at2 +!* if at1 and at2 <> 0 : define fragments only if a at1-at2 bond (and no other) exists +!* if at1 and at2 = 0 : delete all fragment assignments +!* no bond if rcut times the cov.dist. +!* works better than mrec +!***************************************************************************************** + use miscdata,only:Rad => QCxMS_Rad + implicit none + integer :: at1,at2,nat + integer :: i,j + integer :: attotal,currentfrag + integer :: oz(nat),frag(nat) + integer :: fragcount + real(wp),intent(in) :: xyz(3,nat) + real(wp) :: rcov,r + real(wp) :: rcut + + logical :: finish + logical,allocatable :: connect(:,:) + + allocate (connect(nat,nat)) + connect(1:nat,1:nat) = .false. + + do i = 1,nat-1 + do j = i+1,nat + r = sqrt((xyz(1,i)-xyz(1,j))**2+(xyz(2,i)-xyz(2,j))**2 & + & +(xyz(3,i)-xyz(3,j))**2) + rcov = rcut*0.5_wp*(Rad(oz(i))+Rad(oz(j))) + if (r .lt. rcov) then + connect(i,j) = .true. + connect(j,i) = .true. + end if + end do + end do + if ((at1 .eq. 0).and.(at2 .eq. 0)) then + do i = 1,nat + frag(i) = 1 + end do + return + else + + do i = 1,nat + frag(i) = 0 + end do + + frag(at1) = 1 + attotal = 1 + + if (at2 .ne. 0) then + connect(at1,at2) = .false. + connect(at2,at1) = .false. + end if + + finish = .false. + currentfrag = 0 + + do while (attotal .ne. nat) + + currentfrag = currentfrag+1 + + ! cycle through atoms and find connected ones + + do while (.not. (finish)) + finish = .true. + do i = 1,nat + if (frag(i) .eq. currentfrag) then + do j = 1,nat + if (connect(i,j)) then + if (frag(j) .eq. 0) then + frag(j) = currentfrag + attotal = attotal+1 + finish = .false. + elseif (frag(j) .eq. currentfrag) then + cycle + end if + end if + end do + end if + end do + end do + + ! find the first atom in the next fragment + + do i = 1,nat + if (frag(i) .eq. 0) then + frag(i) = currentfrag+1 + attotal = attotal+1 + exit + end if + end do + finish = .false. + end do + + end if + + do i = 1,3 ! is enough, we only need to know we have 1, 2 or more than 2 fragments + if (count(frag == i) .gt. 0) then + fragcount = i + end if + end do + + deallocate (connect) + return + end subroutine fragment_structure + +!=======================================================================================! + + subroutine sortoutduplicates(env,mso) +!****************************************************************************** +!* sort out duplicates with molbar or inchi by openbabel (needs to be sourced) +!****************************************************************************** + implicit none + type(msobj) :: mso + integer :: np + integer :: i,j + integer :: rm !removecounter + character(len=1024),allocatable :: barcodes(:) + character(len=2048) :: duplicates + logical,allocatable :: double(:) + type(systemdata) :: env ! MAIN STORAGE OS SYSTEM DATA + logical :: lprint + logical :: stat + + lprint = env%mslargeprint + np = mso%pl%nmol + allocate (double(np),barcodes(np)) + + ! compute barcodes + if (env%msmolbar) then + call calcmolbar(env,mso,np,barcodes,stat) + if (.not.stat) return + elseif (env%msinchi) then + call calcinchi(env,mso,np,barcodes,stat) + if (.not.stat) return + else + return ! no topology check for duplicated structures selected + end if + + ! identify duplicates + double = .false. + do i = 1,np + if (double(i)) cycle + duplicates = "" + do j = i+1,np + if (.not.double(j)) then + if (trim(barcodes(i)) == trim(barcodes(j))) then + double(j) = .true. + if (lprint) write (duplicates,'(a,i0)') trim(duplicates)//" ",j + else + cycle + end if + end if + end do + if (lprint.and.len(trim(duplicates)) .gt. 0) then + write (*,'(a,i0,a)') "removed duplicates of ",i,": "//trim(duplicates) + end if + end do + + ! remove duplicates + rm = 0 + do i = 1,np + rm = rm+1 + if (double(i)) then + call mso%pl%remove(rm) + rm = rm-1 + end if + end do + + write (*,*) "sorted out ",np-mso%pl%nmol," fragments accoding to topology check" + write (*,*) "number of products is now:",mso%pl%nmol-1 + + end subroutine sortoutduplicates + +!=======================================================================================! + + subroutine calcmolbar(env,mso,np,barcodes,stat) +!****************************************************** +!* compute molbar codes for all structures with molbar +!****************************************************** + implicit none + type(systemdata) :: env ! MAIN STORAGE OS SYSTEM DATA + type(msobj) :: mso + integer :: np + integer :: i,ich,iocheck,io + character(len=:),allocatable :: prog + character(len=:),allocatable :: subdir + character(len=1024) :: barcodes(np) + character(len=512) :: jobcall + character(len=512) :: thisdir + character(len=80) :: fname + logical :: sourced,stat + + stat = .true. + + prog = 'molbar' + call checkprog_silent(prog,.true.,io) + if (io .ne. 0) then + write (*,*) trim(prog)," not found, no topology check for duplicated structures possible" + stat = .false. + return + end if + + call getcwd(thisdir) + subdir = 'topodir' + io = makedir(subdir) + call chdir(subdir) + + do i = 1,np + write (fname,'(i0,a)') i,"temp.xyz" + open (newunit=ich,file=fname) + call wrxyz(ich,mso%pl%mol(i)%nat,mso%pl%mol(i)%at,mso%pl%mol(i)%xyz(:,:),mso%pl%mol(i)%etot) + close (ich) + end do + + ! molbar on multiple core is faster but was unstable in some cases, single core more stable + !write(jobcall,'(a)') 'molbar *temp.xyz -s > molbar.out' + + write (jobcall,'(a,i0,a)') 'molbar *temp.xyz -s -T ',env%threads,' > molbar.out' + write (*,*) "Calling molbar for sorting out duplicates by molbar" + + call execute_command_line(trim(jobcall)) + + do i = 1,np + write (fname,'(i0,a)') i,'temp.mb' + open (newunit=ich,file=fname,status="old",action="read") + read (ich,'(a)',iostat=iocheck) barcodes(i) + close (ich) + end do + + call chdir(thisdir) + call rmrf('topodir') + end subroutine calcmolbar + +!=======================================================================================! + + subroutine calcinchi(env,mso,np,barcodes,stat) +!******************************************************** +!* compute inchi codes for all structures with openbabel +!******************************************************** + implicit none + type(systemdata) :: env ! MAIN STORAGE OS SYSTEM DATA + type(msobj) :: mso + integer :: np + integer :: i,ich,iocheck,io + character(len=:),allocatable :: prog + character(len=:),allocatable :: subdir + character(len=1024) :: barcodes(np) + character(len=512) :: jobcall + character(len=512) :: thisdir + character(len=80) :: fname + logical :: sourced,stat + + stat = .true. + + prog = 'obabel' + call checkprog_silent(prog,.true.,io) + if (io .ne. 0) then + write (*,*) trim(prog)," not found, no topology check for duplicated structures possible" + stat = .false. + return + end if + + call getcwd(thisdir) + subdir = 'topodir' + io = makedir(subdir) + call chdir(subdir) + + open (newunit=ich,file='temp.xyz') + do i = 1,np + call wrxyz(ich,mso%pl%mol(i)%nat,mso%pl%mol(i)%at,mso%pl%mol(i)%xyz(:,:),mso%pl%mol(i)%etot) + end do + close (ich) + + write (jobcall,'(a)') 'obabel -i xyz temp.xyz -o inchi > inchicodes 2>obabelout' + write (*,*) "Calling obabel for sorting out duplicates by InChi codes" + call execute_command_line(trim(jobcall)) + + open (newunit=ich,file='inchicodes',status="old",action="read") + do i = 1,np + read (ich,"(a)") barcodes(i) + end do + close (ich) + + call chdir(thisdir) + call rmrf('topodir') + end subroutine calcinchi + +!=======================================================================================! + + subroutine write_fragments(env,mso,estart,nisomers,nfragpairs,fname,lprint) +!********************************************************* +!* write fragments to files and optionally to directories +!********************************************************* + use atmasses + implicit none + character(len=*) :: fname + real(wp),intent(in) :: estart + integer,intent(in) :: nisomers,nfragpairs + type(systemdata) :: env + type(msobj) :: mso + integer :: prstruc ! number of structures to print + integer :: incr + integer :: np0,np,np2 + integer :: i,j,k,ich1,ich2,ich,r + integer :: nat + integer :: natf(2) ! number of atoms of fragments 1 and 2 + integer,allocatable :: atf(:,:) ! atomtypes of fragment + character(len=1024) :: thisdir,isodir,fragdir + character(len=40) :: strucname + character(len=80) :: comment + logical :: ex + logical :: lprint + character(len=40) :: sumform,sumformula + real(wp) :: mass,erel + + nat = mso%pl%mol(1)%nat + call getcwd(thisdir) + np = mso%pl%nmol + np0 = np + ! first write to isomers.xyz and fragmentpairs.xyz and filter out + write (*,*) "writing isomers to and fragmentpairs to " + if (lprint) write (*,*) "writing product structures and molecular masses to directories" + + prstruc = env%msnfrag + if (prstruc .gt. 0) then + write (*,*) "Printing only ",prstruc," selected structures to products.xyz" + incr = np/prstruc + else + incr = 1 + prstruc = np + end if + + write (*,'(a)') '========================================================' + write (*,'(a,i0)') " number of printed structures: ",prstruc ! todo changes depending on print settings + write (*,*) " directory | fragment type | rel. energy [kcal/mol]" + if (.not.env%msiso) write (*,*) " fragmentpair: | sumformula | molecular mass" + + np2 = 0 ! number of printed sturctures + open (newunit=ich1,file=fname,status='replace') + do i = 1,np,incr + if (np2 .ge. prstruc) exit + erel = (mso%pl%mol(i)%etot-estart)*autokcal + if (mso%pl%mol(i)%fragcount .lt. 1) write (*,*) "no fragment count for ",i + if (mso%pl%mol(i)%fragcount .eq. 1) then + np2 = np2+1 + write (isodir,'(a,i0)') "p",np2 + write (*,'(a4,a20,f18.8)') trim(isodir),"isomer",erel + strucname = 'isomer.xyz' + elseif (mso%pl%mol(i)%fragcount .gt. 1) then + np2 = np2+1 + write (isodir,'(a,i0)') "p",np2 + write (*,'(a4,a20,f18.8)') trim(isodir),"fragmentpair",erel + strucname = 'pair.xyz' + end if + write (comment,'(f9.5,1x,a)') mso%pl%mol(i)%etot,trim(isodir) + call wrxyz(ich1,mso%pl%mol(i)%nat,mso%pl%mol(i)%at,mso%pl%mol(i)%xyz(:,:),trim(comment)) + + ! write to directories + if (lprint) then + r = makedir(trim(isodir)) + call chdir(trim(isodir)) + open (newunit=ich,file=strucname,status='replace') + call wrxyz(ich,mso%pl%mol(i)%nat,mso%pl%mol(i)%at,mso%pl%mol(i)%xyz(:,:),mso%pl%mol(i)%etot) + close (ich) + ! just write mass for pairs too + mass = 0 + do k = 1,nat + mass = mass+ams(mso%pl%mol(i)%at(k)) + end do + call wrshort_real("molmass",mass) + call chdir(trim(thisdir)) + end if + ! no write separated fragment structures + if (mso%pl%mol(i)%fragcount .gt. 1) then + do j = 1,2 ! loop over fragment 1 and 2 + natf(j) = count(mso%pl%mol(i)%fragi == j) + mass = 0 + atf(j,:) = 0 + do k = 1,nat + if (mso%pl%mol(i)%fragi(k) == j) then + mass = mass+ams(mso%pl%mol(i)%at(k)) + atf(j,k) = mso%pl%mol(i)%at(k) + end if + end do + sumformula = sumform(nat,atf(j,:)) + ! write to directories + if (lprint) then + write (fragdir,'(a,i0)') trim(isodir)//"f",j + r = makedir(trim(fragdir)) + call chdir(fragdir) + strucname = 'fragment.xyz' + open (newunit=ich,file=strucname,status='replace') + write (ich,*) natf(j) + write (ich,*) + do k = 1,nat + if (mso%pl%mol(i)%fragi(k) == j) then !only write xyz if fragment really exists + write (ich,'(a2,5x,3F18.8)') i2e(mso%pl%mol(i)%at(k)),mso%pl%mol(i)%xyz(1,k),mso%pl%mol(i)%xyz(2,k),mso%pl%mol(i)%xyz(3,k) + end if + end do + call wrshort_real("mass",mass) + call chdir(trim(thisdir)) + end if + !write(*,*) " directory | sumformula | mass" + write (*,'(a6,i0,5x,a20,9x,f9.5)') trim(isodir)//"f",j,trim(sumformula),mass + end do + end if + end do + + write (*,'(a)') '========================================================' + write (*,*) + call wrshort_int('npairs',np2) + if (env%msnoiso) then + write (*,*) "sorted out ",np0-np2,"non-dissociated structures" + elseif (env%msiso) then + write (*,*) "sorted out ",np0-np2,"dissociated structure pairs" + end if + write (*,*) "Number of generated isomers: ",nisomers + write (*,*) "Number of generated fragmentpairs: ",nfragpairs + end subroutine write_fragments + +!=======================================================================================! + + subroutine msinputreader(mso,msinput) +!********************************************************** +!* a simple input reader to read in additional input file +!* to set more advanced options +!********************************************************** + implicit none + type(msobj) :: mso + integer :: ich,io,iocheck + character(len=40) :: line + character(len=80) :: msinput + real(wp) :: xx(10) + integer :: j + logical :: ex + + write (*,*) "msinput is:,",trim(msinput) + !---- read input file + inquire (file=trim(msinput),exist=ex) + if (.not.ex) then + return + else + write (*,*) 'Reading <'//trim(msinput)//'> file' + end if + + mso%rcut = 1.3_wp !> cutoff for factor of sum of covalent radii for detection of fragmentation of fragment_structures + mso%T = 5000.0_wp !> electronic temperature! favours open-shell fragment generation + mso%fc = 0.05_wp !> repulsive force constant in constrained optimizations + mso%fc_attr = -0.05_wp !> attractive force constant in constrained optimizations + mso%cdist = 1.5_wp !> constraing distance scaling factor of rcov + mso%cdist_att = 0.5_wp !> constraing distance scaling factor of rcov for attractive part + mso%distthr_att = 4.0_wp !> distance threshold in Angstrom for attractive part of constraint + mso%atomshift = 0.75_wp !> shift of atoms in random atom displacement + mso%fragdist = 0.0_wp !> increase distance (in Angstrom) to separate fragments (useful for subsequent transition state search) + mso%maxc = 15 + + open (newunit=ich,file=trim(msinput),status='old') + + ! Read line-by-line + do + read (ich,'(a)',iostat=iocheck) line + + ! Check for errors in the input + if (iocheck > 0) then !Fail + write (*,*) 'Something is wrong in the input. Exiting...' + stop + + ! End-of-file + elseif (iocheck < 0) then !EOF + exit + + ! Keywords + else + write (*,'(''>'',a)') line + ! capatilize and trimthe line + line = uppercase(line) + line = trim(line) + + ! read keywords + if (index(line,'FRAGDIST') /= 0) then + call readl(trim(line),xx,j) + mso%fragdist = xx(1) + end if + if (index(line,'ATOMSHIFT') /= 0) then + call readl(trim(line),xx,j) + mso%atomshift = xx(1) + end if + if (index(line,'DISTTHR_ATTR') /= 0) then + call readl(trim(line),xx,j) + mso%distthr_att = xx(1) + end if + if (index(line,'FC_REP') /= 0) then + call readl(trim(line),xx,j) + mso%fc = xx(1) + end if + if (index(line,'FC_ATTR') /= 0) then + call readl(trim(line),xx,j) + mso%fc_attr = xx(1) + end if + if (index(line,'ETEMP') /= 0) then + call readl(trim(line),xx,j) + mso%T = xx(1) + end if + end if + + end do + + end subroutine msinputreader + +!=======================================================================================! + + subroutine detect_fragments(mso,lprint,nisomers,nfragpairs) +!******************************************************************* +! detection of fragments and removal of multiple fragmented species +!******************************************************************* + implicit none + type(msobj) :: mso + integer :: nat + integer :: i + integer :: nfragpairs,nisomers + integer :: npoly ! number of multiple fragmented species + integer :: np ! number of products + character(len=2048) :: multfrags + logical,intent(in) :: lprint + + write (*,*) "Detect fragments" + + nat = mso%pl%mol(1)%nat + np = mso%pl%nmol + + nisomers = 0 + nfragpairs = 0 + npoly = 0 + + ! determine fragments for every structure + do i = 1,np + allocate (mso%pl%mol(i)%fragi(nat)) + call fragment_structure(mso%pl%mol(i)%nat,mso%pl%mol(i)%at,mso%pl%mol(i)%xyz(:,:),mso%rcut,1,0,mso%pl%mol(i)%fragi,mso%pl%mol(i)%fragcount) ! works better than mrec + end do + ! remove multiple fragmented species + multfrags = '' + i = 0 + do + i = i+1 + if (i .gt. np) exit ! np changes within the loop + if (mso%pl%mol(i)%fragcount .gt. 2) then + npoly = npoly+1 + if (lprint) write (multfrags,'(a,i0)') trim(multfrags)//" ",i + call mso%pl%remove(i) + i = i-1 + np = np-1 + cycle + elseif (mso%pl%mol(i)%fragcount .eq. 2) then + nfragpairs = nfragpairs+1 + else ! for isomers do nothing + nisomers = nisomers+1 + cycle + end if + end do + if (lprint.and.len(trim(multfrags)) .gt. 0) then + write (*,'(a,i0,a)') "removed multiple fragmented structures: "//trim(multfrags) + end if + return + end subroutine detect_fragments + +!=======================================================================================! + + subroutine increase_fragdist(mso,lprint) +!**************************************************************************** +!* increase distance between fragments, better for transistion state search +!* we remove here also multiple fragmented species, +!* and unreasonable fragments (too much distance between fragments) +!**************************************************************************** + use axis_module + implicit none + type(msobj) :: mso + integer :: nat + integer,allocatable :: at1(:),at2(:) + integer,allocatable :: fragi(:) !fragment index + integer :: frag1,frag2,i,j + integer :: npoly ! number of multiple fragmented species + integer :: np ! number of products + real(wp) :: norm + real(wp),allocatable :: xyz1(:,:),xyz2(:,:) ! xyz of fragment 1 and 2 + real(wp) :: cmass1(3),cmass2(3) ! center of mass of fragment 1 and 2 + character(len=2048) :: distfrags + logical,intent(in) :: lprint + + frag1 = 0 + frag2 = 0 + if (mso%fragdist .gt. 0.0_wp) then + write (*,'(a,f10.8,a)') " Increase distance of fragments by ",mso%fragdist," Angstrom" + else + return + end if + + nat = mso%pl%mol(1)%nat + np = mso%pl%nmol + + ! increase distance between fragments (based on center of mass) and remove unreasonable fragments + ! that are too far away from each other, which cause problems in transition state search + + distfrags = '' + i = 0 + do + i = i+1 + if (i .gt. np) exit ! np changes within the loop + ! increase distance between fragments + if (mso%pl%mol(i)%fragcount .eq. 2) then + do j = 1,nat + if (mso%pl%mol(i)%fragi(j) == 1) then + frag1 = frag1+1 + end if + if (mso%pl%mol(i)%fragi(j) == 2) then + frag2 = frag2+1 + end if + end do + allocate (at1(frag1),at2(frag2)) + allocate (xyz1(3,frag1),xyz2(3,frag2)) + frag1 = 0 + frag2 = 0 + do j = 1,nat + if (mso%pl%mol(i)%fragi(j) == 1) then + frag1 = frag1+1 + xyz1(:,frag1) = mso%pl%mol(i)%xyz(:,j) + at1(frag1) = mso%pl%mol(i)%at(j) + end if + if (mso%pl%mol(i)%fragi(j) == 2) then + frag2 = frag2+1 + xyz2(:,frag2) = mso%pl%mol(i)%xyz(:,j) + at2(frag2) = mso%pl%mol(i)%at(j) + end if + end do + call CMAv(frag1,at1,xyz1,cmass1) + call CMAv(frag2,at2,xyz2,cmass2) + norm = sqrt((cmass2(1)-cmass1(1))**2+(cmass2(2)-cmass1(2))**2+(cmass2(3)-cmass1(3))**2) + ! remove structures with unreasonable distance between fragments + if (norm .gt. 15) then !15 angstrom worked well in tests + if (lprint) write (distfrags,'(a,i0)') trim(distfrags)//" ",i + call mso%pl%remove(i) + i = i-1 + np = np-1 + cycle + end if + do j = 1,nat + ! move fragment 2 away from fragment 1 + if (mso%pl%mol(i)%fragi(j) == 2) then + mso%pl%mol(i)%xyz(:,j) = mso%pl%mol(i)%xyz(:,j)+(cmass2-cmass1)/norm*mso%fragdist + end if + end do + deallocate (at1,at2,xyz1,xyz2) + else ! for isomers do nothing + cycle + end if + end do + + if (lprint.and.len(trim(distfrags)) .gt. 0) then + write (*,'(a,i0,a)') "removed unreasonable structures:"//trim(distfrags) + end if + + return + end subroutine increase_fragdist + +!=======================================================================================! + + subroutine rdplist(mso,estart,fname) +!******************************************************* +!* collect structures of ensemble fil into product list +!******************************************************* + use atmasses + implicit none + integer :: nat + integer :: nall + integer :: chrg ! charge of the molecule + type(msobj) :: mso + character(len=*) :: fname + integer,allocatable :: at(:) + real(wp),allocatable :: xyz(:,:) + real(wp) :: eread + real(wp) :: molmass + real(wp) :: estart + character(len=6) :: sym + character(len=512) :: line + integer :: i,j,k,dum,io,ich + logical :: ex,found + + found = .false. + write (*,*) "reading product structures file ",fname + + call rdensembleparam(fname,nat,nall) + allocate (at(nat),xyz(3,nat)) + call rdshort('.CHRG',chrg) + eread = 0.0_wp + xyz = 0.0_wp + open (newunit=ich,file=fname) + do i = 1,nall + read (ich,*,iostat=io) dum + if (io < 0) exit + if (io > 0) cycle + read (ich,'(a)',iostat=io) line + if (io < 0) exit + eread = grepenergy(line) + if (abs(eread-estart) .lt. 0.00001_wp) then ! skip input structure, detect by energy + found = .true. + do k = 1,dum + read (ich,'(a)',iostat=io) line + end do + cycle + if (io < 0) exit + end if + do j = 1,dum + read (ich,'(a)',iostat=io) line + if (io < 0) exit + call coordline(line,sym,xyz(1:3,j),io) + if (io .ne. 0) then + backspace (ich) + exit + end if + at(j) = e2i(sym) + end do + molmass = molweight(nat,at) + call mso%pl%append(nat,at,xyz,eread,chrg,0,molmass) + end do + close (ich) + + if (io < 0) then + error stop 'error while reading product file.' + end if + + deallocate (xyz,at) + if (.not.found) then + write (*,*) "Warning, input structure not found in ensemble and could not be sorted out" + end if + return + end subroutine rdplist + +!=======================================================================================! + + subroutine wrplist(mso,fname) +!************************* +!* write products to file +!************************* + implicit none + integer :: i,ich,io + character(len=*) :: fname + type(msobj) :: mso + open (newunit=ich,file=fname) + do i = 1,mso%pl%nmol + call wrxyz(ich,mso%pl%mol(i)%nat,mso%pl%mol(i)%at,mso%pl%mol(i)%xyz(:,:),mso%pl%mol(i)%etot) + end do + close (ich) + return + end subroutine wrplist + +!=======================================================================================! +!=======================================================================================! +end module msmod !> END OF MODULE +!=======================================================================================! +!=======================================================================================! diff --git a/src/msreact/msreact.f90 b/src/msreact/msreact.f90 new file mode 100644 index 00000000..c3b3e857 --- /dev/null +++ b/src/msreact/msreact.f90 @@ -0,0 +1,556 @@ +!================================================================================! +! This file is part of crest. +! +! Copyright (C) 2020 Stefan Grimme, 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 . +!================================================================================! + +!==============================================================! +! the handler function is the sub-program that is called +! by crest, i.e., within this routine the seperate input file is +! read and the calculation is started. +!==============================================================! +subroutine msreact_handler(env,tim) + use crest_parameters + use crest_data + use msmod + use strucrd + use atmasses + use iomod + use zdata + implicit none + + type(systemdata) :: env + type(timer) :: tim + + type(msobj) :: mso !a new msreact object + type(coord) :: struc + + interface + subroutine msreact_topowrap(mol,pair,paths,wboname) + import :: msmol + implicit none + type(msmol) :: mol + integer :: pair(mol%nat*(mol%nat+1)/2) + integer :: paths(mol%nat*(mol%nat+1)/2,mol%nat) + character(len=*),optional :: wboname + end subroutine msreact_topowrap + end interface + integer :: nat + integer,allocatable :: pair(:) + integer,allocatable :: paths(:,:) + integer :: k + integer :: nisomers,nfragpairs + real(wp) :: molmass + real(wp) :: estart ! energy of input structure + + call tim%start(1,'MSREACT') + + call msreact_head(env,mso) + + ! get starting energy of lowest fragment + call get_input_energy(env,mso%T,estart) + !-- read the input coord and put it into the + ! product-list as first structure + call struc%open('coord') + struc%xyz = struc%xyz*bohr !to Angstrom, from this point on by convention! + molmass = molweight(struc%nat,struc%at) + call mso%pl%append(struc%nat,struc%at,struc%xyz,estart,env%chrg,1,molmass) + if (env%mslargeprint) call wrshort_real("mass",mso%pl%mol(1)%molmass) ! write mass + call struc%deallocate() + + !-- additional input file can be read here + call msinputreader(mso,env%msinput) + + nat = mso%pl%mol(1)%nat + k = nat*(nat+1)/2 + allocate (pair(k),paths(k,nat)) + call msreact_topowrap(mso%pl%mol(1),pair,paths,'wbo') + + !-- setting the threads for correct parallelization + if (env%autothreads) then + call ompautoset(env%threads,6,env%omp,env%MAXRUN,0) !set global OMP/MKL variable for xtb jobs + end if + + !-- do the directory setup and optimizations + call msreact(env,mso,nat,pair) + deallocate (paths,pair) + + if (env%mslargeprint) then + write (*,*) "All structures written to file " + call wrplist(mso,'crest_allproducts.xyz') + end if + ! detect fragmented structures + call detect_fragments(mso,env%mslargeprint,nisomers,nfragpairs) + call increase_fragdist(mso,env%mslargeprint) + write (*,*) "Remaining structures:",mso%pl%nmol-1 + + ! call write_products(env,mso) print out structures if lprint is chosen? + + ! sort out topologically identical structures + call sortoutduplicates(env,mso) + + write (*,'(a,f10.4,a)') "Calling CREGEN routine and sort out structures above energy window of",env%ewin," kcal/mol" + ! write plist to file to use cregen routine + call wrplist(mso,'crest_unique_products.xyz') + if (env%mslargeprint) then + write (*,*) "All unique structures written to file " + end if + ! sort according to energies and RMSD, remove highe energy structures + call newcregen(env,13,'unique_products.xyz') + call mso%pl%dealloc() + ! read in again plist after cregen and remove starting structure + call rdplist(mso,estart,'crest_msreact_products.xyz') + write (*,*) "Remaining structures after cregen:",mso%pl%nmol + call detect_fragments(mso,env%mslargeprint,nisomers,nfragpairs) + + ! write final structures to file and directories + call write_fragments(env,mso,estart,nisomers,nfragpairs,'crest_msreact_products.xyz',env%mslargeprint) + + !cleanup files + if (.not.env%mslargeprint) then + call rmrf('MSDIR') + call rmrf('crest_allproducts.xyz') + call rmrf('crest_unique_products.xyz') + end if + call rmrf('crest_unique_products.sorted') + call rmrf('scoord.1') + call rmrf('xtbscreen.xyz xtblmoinfo') + call tim%stop(1) + return +end subroutine msreact_handler + +!==============================================================! +! the main implementation of the msreact algo +!==============================================================! +subroutine msreact(env,mso,nat,pair) + use crest_parameters + use msmod + use iomod + use miscdata,only:rcov + use utilities,only:lin + implicit none + + type(msobj) :: mso !main storage object + type(msmol) :: inmol ! xyz of molecule + type(msmol) :: inmoldisp ! xyz of molecule with displacement + type(systemdata) :: env ! system data + + integer :: nat + integer :: pair(nat*(nat+1)/2) ! gives number of bonds between two atoms lin(i,j) + integer :: nbonds + integer :: i,j,k,l + integer :: p + integer :: np + integer :: io + integer :: nbaseat ! number of lewis basic atoms according to a xTB LMO analysis + integer,allocatable :: basicatlist(:) ! list of lewis basic atoms according to a xTB LMO analysis + + character(len=:),allocatable :: subdir + character(len=40) :: pdir + character(len=30) :: base + character(len=512) :: thisdir + real(wp) :: constr_dist + + inmol = mso%pl%mol(1) + + !-- main subdirectory handling + call getcwd(thisdir) + subdir = 'MSDIR' + io = makedir(subdir) + call chdir(subdir) + + !-- get specific pairs + base = 'Pair_' + np = 0 + do i = 1,nat + do j = i,nat + k = lin(i,j) + p = pair(k) + if (p .eq. 1.and.(inmol%at(i) .eq. 1.or.inmol%at(j) .eq. 1)) cycle ! do not distort all X-H bonds + if (p .gt. 0.and.p .le. env%msnbonds) then + !write(*,*) " distort bond ",i,"-",j," with ",p," bonds" + np = np+1 + write (pdir,'(a,i0)') trim(base),np + constr_dist = mso%cdist*(rcov(inmol%at(i))+rcov(inmol%at(j)))*bohr+float(p) + ! write(*,*) inmol%at(i),inmol%at(j),constr_dist + call isodir(mso,trim(pdir),inmol,i,j,constr_dist,mso%fc) + end if + end do + end do + + !attractive potential for H-shifts + !for H-shifts: xtb -lmo , read xtb.out -> get list of atoms with pi or lone-pair -> H-allowed to shift there + if (env%msattrh) then + call chdir(thisdir) + call xtblmo(env,.true.) + call readbasicpos(env,nbaseat,basicatlist) + call chdir(subdir) + write (*,*) "Add attractive potential for H-shifts:" + !-- setting the threads for correct parallelization (were changed in xtblmo call) + if (env%autothreads) then + call ompautoset(env%threads,6,env%omp,env%MAXRUN,0) !set global OMP/MKL variable for xtb jobs + end if + do i = 1,nat + do j = i,nat + k = lin(i,j) + p = pair(k) + if (p .ge. 2) then ! only add attractive potential for pairs with at least 2 bonds distance + if ((inmol%at(i) .eq. 1.and.(findloc(basicatlist,j,1) /= 0)).or. & ! pair of H and basic atom? + & (inmol%at(j) .eq. 1.and.(findloc(basicatlist,i,1) /= 0))) then + if (msmoldist(inmol,i,j) .lt. mso%distthr_att) then + np = np+1 + !write(*,*) "add attractive potential for pair ", np," between ",i," and ",j + write (pdir,'(a,i0)') 'Pair_',np + constr_dist = mso%cdist_att*(rcov(inmol%at(i))+rcov(inmol%at(j)))*bohr+float(p) + call isodir(mso,trim(pdir),inmol,i,j,constr_dist,mso%fc_attr) + end if + end if + end if + end do + end do + end if + + write (*,*) '# of distortions',np + call msreact_jobber(env,np,base,.true.,.false.) + call msreact_collect(nat,np,base,mso) + + !-- do additional shifting of atoms and subsequent optimization + if (env%msnshifts .gt. 0) then + write (*,'(a,i0,a)') "Shift atoms ",env%msnshifts," times and reoptimize" + base = 'Distortion_' + np = 0 + do l = 1,env%msnshifts + call shiftatoms(mso,inmol,inmoldisp) + np = np+1 + write (pdir,'(a,i0)') trim(base),np + call isodiropt(mso,trim(pdir),inmoldisp) + end do + call msreact_jobber(env,np,base,.false.,.false.) + ! collect everything in isomer list + call msreact_collect(nat,np,base,mso) + end if + + ! do additional shifting of atoms and subsequent optimization with constrained bonds + if (env%msnshifts2 .gt. 2) then + write (*,'(a,i0,a)') "Shift atoms",env%msnshifts2," times and apply repulsive potential on bonds of distorted structure" + base = 'Distortedpair_' + np = 0 + do l = 1,env%msnshifts2 + call shiftatoms(mso,inmol,inmoldisp) + do i = 1,nat + do j = i,nat + k = lin(i,j) + p = pair(k) + if (p .eq. 1.and.(inmol%at(i) .eq. 1.or.inmol%at(j) .eq. 1)) cycle + if (pair(k) .le. env%msnbonds) then + np = np+1 + write (pdir,'(a,i0)') trim(base),np + constr_dist = mso%cdist*(rcov(inmoldisp%at(i))+rcov(inmoldisp%at(j)))*bohr+float(p) + call isodir(mso,trim(pdir),inmoldisp,i,j,constr_dist,mso%fc) + end if + end do + end do + end do + call msreact_jobber(env,np,base,.true.,.false.) + ! collect everything in isomer list + call msreact_collect(nat,np,base,mso) + end if + + call chdir(thisdir) + return +end subroutine msreact + +!============================================================! +! make a dir for a constrained optimization of a structure, +! a controlfile with constraints on atoms A and B (at dist D) +! will be written into the directory +!============================================================! +subroutine isodir(mso,dirname,mol,A,B,D,fc) + use crest_parameters + use msmod + use iomod + use strucrd,only:wrxyz + implicit none + type(msobj) :: mso + character(len=*) :: dirname + type(msmol),intent(in) :: mol + integer :: A,B + real(wp) :: D + real(wp),intent(in) :: fc ! force constant for bias potential + + character(len=:),allocatable :: fname + character(len=20) :: dumm + integer :: io,ich + + io = makedir(dirname) !create the directory + + fname = trim(dirname)//'/'//'struc.xyz' + open (newunit=ich,file=fname) + call wrxyz(ich,mol%nat,mol%at,mol%xyz) + close (ich) + + fname = trim(dirname)//'/'//'.CHRG' + open (newunit=ich,file=fname) + write (ich,'(i0)') mol%chrg ! EI +1, DEA -1, CID 0 + close (ich) + + ! write control file for bond length constraint + + fname = trim(dirname)//'/'//'.xc1' + open (newunit=ich,file=fname) + write (ich,'(a)') '$scc' + write (dumm,'(f16.2)') mso%T + write (ich,'(1x,a,a)') 'temp=',adjustl(trim(dumm)) + write (ich,'(a)') '$constrain' + write (dumm,'(f16.4)') fc + write (ich,'(3x,a,a)') 'force constant=',adjustl(trim(dumm)) + write (ich,'(3x,a,1x,i0,a,1x,i0,a,1x,f8.5)') 'distance:',A,',',B,',',D + close (ich) + + fname = trim(dirname)//'/'//'.xc2' + open (newunit=ich,file=fname) + write (ich,'(a)') '$scc' + write (dumm,'(f16.2)') mso%T + write (ich,'(1x,a,a)') 'temp=',adjustl(trim(dumm)) + write (ich,'(a)') '$opt' + write (ich,'(1x,a)') 'maxcycle=',mso%maxc ! only allow 15 cycles to avoid recombination of fragments + write (ich,'(a)') '$write' + write (ich,'(1x,a)') 'wiberg=true' + close (ich) + + return +end subroutine isodir + +!============================================================! +! make a dir for a optimization of a displaced structure, +! a controlfile for an optimization after the atom shifting +! will be written into the directory +!============================================================! +subroutine isodiropt(mso,dirname,mol) + use iso_fortran_env,only:wp => real64 + use msmod + use iomod + use strucrd,only:wrxyz + implicit none + type(msobj) :: mso + character(len=*) :: dirname + type(msmol),intent(in) :: mol + integer :: A,B + real(wp) :: D + + character(len=:),allocatable :: fname + character(len=20) :: dumm + integer :: io,ich + + io = makedir(dirname) !create the directory + + fname = trim(dirname)//'/'//'struc.xyz' + open (newunit=ich,file=fname) + call wrxyz(ich,mol%nat,mol%at,mol%xyz) + close (ich) + + fname = trim(dirname)//'/'//'.CHRG' + open (newunit=ich,file=fname) + write (ich,'(i0)') mol%chrg ! EI +1, DEA -1, CID 0, give in crest call + close (ich) + + fname = trim(dirname)//'/'//'.xc1' + open (newunit=ich,file=fname) + write (ich,'(a)') '$scc' + write (dumm,'(f16.2)') mso%T + write (ich,'(1x,a,a)') 'temp=',adjustl(trim(dumm)) + close (ich) + return +end subroutine isodiropt + +!=====================================================================! +! The job construction routine for MSREACT +! (will have to be modified later, for now it is for testing) +!=====================================================================! +subroutine msreact_jobber(env,ndirs,base,constr,niceprint) + use crest_parameters + use msmod + use iomod + implicit none + integer :: ndirs + character(len=*) :: base + logical :: niceprint,constr + character(len=1024) :: jobcall,jobcall2 + type(systemdata) :: env ! system data + + jobcall = '' + jobcall2 = '' + + if (constr) then + write (jobcall,'(a)') 'xtb struc.xyz --opt loose --input .xc1 '//trim(env%gfnver)//' > split.out 2>/dev/null' + write (jobcall2,'(a)') 'xtb xtbopt.xyz --opt crude --input .xc2 '//trim(env%gfnver)//' > xtb.out 2>/dev/null' + jobcall = trim(jobcall)//' ; '//trim(jobcall2) + else + write (jobcall,'(a)') 'xtb struc.xyz --opt crude --input .xc1 '//trim(env%gfnver)//' > xtb.out 2>/dev/null' + end if + + !-- directories must be numbered consecutively + call opt_OMP_loop(ndirs,base,jobcall,niceprint) + write (*,*) + write (*,*) 'done.' + return +end subroutine msreact_jobber + +!=====================================================================! +! A wrapper to generate the topology for a molecule within the +! MSREACT subprogram +!=====================================================================! +subroutine msreact_topowrap(mol,pair,paths,wboname) + use crest_parameters + use msmod + use zdata + use adjacency + use utilities,only:lin + implicit none + type(msmol) :: mol + integer :: pair(mol%nat*(mol%nat+1)/2) + !integer :: pair(mol%nat,mol%nat) + integer :: paths(mol%nat*(mol%nat+1)/2,mol%nat) + character(len=*),optional :: wboname + type(zmolecule) :: zmol + + integer,allocatable :: A(:,:) + integer,allocatable :: prev(:,:) + real(wp),allocatable :: E(:,:) + real(wp),allocatable :: dist(:,:) + + integer :: lpath,i,j,k + integer,allocatable :: path(:) + logical :: ex + + ex = .false. + if (present(wboname)) then + inquire (file=wboname,exist=ex) + end if + if (ex) then + call simpletopo(mol%nat,mol%at,mol%xyz,zmol,.false.,.false.,wboname) + else + mol%xyz = mol%xyz/bohr !CN based topo requires Bohrs + call simpletopo(mol%nat,mol%at,mol%xyz,zmol,.false.,.false.,'') + mol%xyz = mol%xyz*bohr + end if + + allocate (A(mol%nat,mol%nat),E(mol%nat,mol%nat)) + call zmol%adjacency(A,E) + + allocate (prev(mol%nat,mol%nat),dist(mol%nat,mol%nat)) + + call FloydWarshall(mol%nat,A,E,dist,prev) + allocate (path(mol%nat),source=0) + do i = 1,mol%nat + do j = i,mol%nat + path = 0 + call getPathFW(mol%nat,prev,i,j,path,lpath) + !write(*,*) path(1:lpath) + k = lin(i,j) + pair(k) = lpath-1 ! number of bonds + paths(k,:) = path(:) + end do + end do + + deallocate (dist,prev) + deallocate (E,A) + + call zmol%deallocate() !clear the zmol memory + return +end subroutine msreact_topowrap + +!========================================================================! +! collect structures of optimized molecules +! xyz files should still have the same number and order of atoms +!========================================================================! +subroutine msreact_collect(nat,np,base,mso) + use crest_parameters + use strucrd + use msmod + use atmasses + implicit none + integer :: nat + integer :: np + integer :: ich + integer :: chrg ! charge of the molecule + type(msobj) :: mso + character(len=40) :: pdir + character(len=*) :: base + character(len=:),allocatable :: optfile + character(len=128) :: newcomment + integer :: p + logical :: ex + integer,allocatable :: at(:) + real(wp),allocatable :: xyz(:,:) + real(wp) :: etot + real(wp) :: molmass + + allocate (at(nat),xyz(3,nat)) + + do p = 1,np + write (pdir,'(a,i0)') trim(base),p + optfile = trim(pdir)//'/'//'xtbopt.xyz' + inquire (file=optfile,exist=ex) + if (ex) then + call rdcoord(optfile,nat,at,xyz,etot) + xyz = xyz*bohr + call rdshort(trim(pdir)//'/'//'.CHRG',chrg) + molmass = molweight(nat,at) + call mso%pl%append(nat,at,xyz,etot,chrg,0,molmass) + end if + end do + close (ich) + + deallocate (xyz,at) + return +end subroutine msreact_collect + +!============================================================! +! shift atoms of input structure randomly +! to generate more structures +! for planar molecules important +!============================================================! +subroutine shiftatoms(mso,molin,molout) + use iso_fortran_env,only:wp => real64 + use msmod + use iomod + use strucrd,only:wrxyz + implicit none + type(msobj) :: mso + type(msmol) :: molin + type(msmol) :: molout + real(wp) :: D + real(wp) :: x,shift + real(wp) :: dist ! distance to shift atoms + character(len=80) :: fname + character(len=20) :: dumm + integer :: io,ich,i,j,count,incr + + dist = mso%atomshift ! distance of displacement + molout = molin + do i = 1,molin%nat + do j = 1,3 + call Random_Number(x) + ! positive and negative direction possible + x = 2.0_wp*x-1.0_wp + shift = x*dist + molout%xyz(j,i) = molin%xyz(j,i)+shift + end do + end do + return +end subroutine shiftatoms + diff --git a/src/printouts.f90 b/src/printouts.f90 index bd124fb7..7d738c20 100644 --- a/src/printouts.f90 +++ b/src/printouts.f90 @@ -299,7 +299,34 @@ subroutine confscript_morehelp(flag) write (*,'(5x,''-freqscal : defines frequency scale factor. Only for outprint'')') write (*,'(5x,''-freqlvl [method] : define a method for frequency computation. All gfn versions are supported'')') write (*,*) - + + case('msreact') + write (*,'(1x,'' mass spectral fragment generator (msreact)'')') + write (*,'(1x,''General usage :'')') + write (*,'(5x,'' -msreact [options]'')') + write (*,'(1x,''options:'')') + write(*,'(5x,''-msnoattrh : deactivate attractive potential between hydrogen and LMO centers)'')') + write(*,'(5x,''-msnshifts [int] : perform n optimizations with randomly shifted atom postions (default 0) '')') + write(*,'(5x,''-msnshifts2 [int] : perform n optimizations with randomly shifted atom postions and repulsive potential applied to bonds (default 0) '')') + write(*,'(5x ''-msnbonds [int] : maximum number of bonds between atoms pairs for applying repulsive potential (default 3)'')') + write(*,'(5x,''-msmolbar : sort out topological duplicates by molbar codes (requires sourced "molbar")'')') + write(*,'(5x,''-msinchi : sort out topological duplicates by inchi codes (requires sourced "obabel")'')') + write(*,'(5x ''-msnfrag [int] : number of fragments that are printed by msreact (random selection)'')') + write(*,'(5x,''-msiso : print only non-dissociated structures (isomers)'')') + write(*,'(5x,''-msnoiso : print only dissociated structures'')') + write(*,'(5x,''-mslargeprint : do not remove temporary files and MSDIR do not remove temporary files and MSDIR with constrained optimizations'')') + write (*,'(5x,''-chrg : set the molecules´ charge'')') + write (*,'(5x,''-ewin : set energy window in for sorting out fragments kcal/mol,'')') + write (*,'(5x,'' [default: 200.0 kcal/mol] '')') + write (*,'(5x,''-msinput : read in an input file with special settings for msreact'')') + write (*,'(5x,''keywords for inputfile:'')') + write (*,'(5x,'' fragdist : increase distance between fragments xyz structures (default 0 Angstrom) '')') + write (*,'(5x,'' atomshift : shift of atoms in random atom displacement (default 0.75 Angstrom)'')') + write (*,'(5x,'' distthr_attr : distance threshold in Angstrom for H-LMO attraction (default 4.0 Angstrom) '')') + write (*,'(5x,'' fc_rep : force constant for repulsive potential between atom pairs (default 0.5) '')') + write (*,'(5x,'' fc_attr : force constant for attractive potential between hydrogen and LMO centers (default -0.5) '')') + write (*,'(5x,'' etemp : electronic temperature in xTB optimizations'')') + case ('other') write (*,'(1x,''Other tools for standalone use:'')') write (*,'(5x,''-zsort : use only the zsort subroutine'')') @@ -504,6 +531,27 @@ end subroutine qcg_head !========================================================================================! +subroutine msreact_head() + implicit none + write (*,*) + write (*,'(2x,''========================================'')') + write (*,'(2x,''| |'')') + write (*,'(2x,''| MSREACT |'')') + write (*,'(2x,''| automated MS fragment generator |'')') + write (*,'(2x,''| |'')') + write (*,'(2x,''| University of Bonn, MCTC |'')') + write (*,'(2x,''========================================'')') + write (*,'(2x,'' S. Grimme, P. Pracht, J. Gorges.'')') + write (*,*) + write (*,'(3x,''Cite work conducted with this code as'')') + write (*,'(/,3x,''Philipp Pracht, Stefan Grimme, Christoph Bannwarth, Fabian Bohle, Sebastian Ehlert, Gereon Feldmann,'')') + write (*,'(3x,''Johannes Gorges, Marcel Müller, Tim Neudecker, Christoph Plett, Sebastian Spicher, Pit Steinbach,'')') + write (*,'(3x,''Patryk A. Wesolowski, and Felix Zeller J. Chem. Phys., 2024, submitted.'')') + write (*,*) + end subroutine msreact_head + +!========================================================================================! + subroutine smallhead(str) !********************************************** !> convert a string in a small header printout @@ -671,7 +719,7 @@ subroutine print_crest_metadata() write (*,'(2x,a,1x,a)') '-DWITH_GFNFF :',gfnffvar write (*,'(2x,a,1x,a)') '-DWITH_TBLITE :',tblitevar write (*,'(2x,a,1x,a)') '-DWITH_XHCFF :',xhcffvar - + write (*,'(2x,a,1x,a)') '-DWITH_LWONIOM :',lwoniomvar end subroutine print_crest_metadata !========================================================================================!