From f4d4df6d4b594800ceaf8aa0594e907196f4d7df Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Wed, 8 Nov 2023 04:03:36 -0500 Subject: [PATCH] Add Colvars module version 2023-10-23 --- CMakeLists.txt | 4 + cmake/gmxManageColvars.cmake | 57 + cmake/gmxManageLepton.cmake | 22 + cmake/gmxVersionInfo.cmake | 2 +- src/external/colvars/colvar.cpp | 2992 ++++++++++++++++ src/external/colvars/colvar.h | 779 ++++ src/external/colvars/colvar_UIestimator.h | 749 ++++ src/external/colvars/colvar_arithmeticpath.h | 137 + src/external/colvars/colvar_geometricpath.h | 263 ++ .../colvars/colvar_neuralnetworkcompute.cpp | 310 ++ .../colvars/colvar_neuralnetworkcompute.h | 146 + .../colvars/colvar_rotation_derivative.h | 627 ++++ src/external/colvars/colvaratoms.cpp | 1475 ++++++++ src/external/colvars/colvaratoms.h | 547 +++ src/external/colvars/colvarbias.cpp | 1052 ++++++ src/external/colvars/colvarbias.h | 367 ++ src/external/colvars/colvarbias_abf.cpp | 924 +++++ src/external/colvars/colvarbias_abf.h | 186 + src/external/colvars/colvarbias_alb.cpp | 433 +++ src/external/colvars/colvarbias_alb.h | 88 + src/external/colvars/colvarbias_histogram.cpp | 235 ++ src/external/colvars/colvarbias_histogram.h | 50 + .../colvarbias_histogram_reweight_amd.cpp | 416 +++ .../colvarbias_histogram_reweight_amd.h | 99 + src/external/colvars/colvarbias_meta.cpp | 2132 +++++++++++ src/external/colvars/colvarbias_meta.h | 438 +++ src/external/colvars/colvarbias_restraint.cpp | 1561 ++++++++ src/external/colvars/colvarbias_restraint.h | 368 ++ src/external/colvars/colvarcomp.cpp | 689 ++++ src/external/colvars/colvarcomp.h | 1870 ++++++++++ .../colvars/colvarcomp_alchlambda.cpp | 106 + src/external/colvars/colvarcomp_angles.cpp | 641 ++++ src/external/colvars/colvarcomp_apath.cpp | 422 +++ .../colvars/colvarcomp_combination.cpp | 329 ++ src/external/colvars/colvarcomp_coordnums.cpp | 655 ++++ src/external/colvars/colvarcomp_distances.cpp | 1572 ++++++++ src/external/colvars/colvarcomp_gpath.cpp | 830 +++++ .../colvars/colvarcomp_neuralnetwork.cpp | 193 + src/external/colvars/colvarcomp_protein.cpp | 496 +++ src/external/colvars/colvarcomp_rotations.cpp | 813 +++++ src/external/colvars/colvarcomp_volmaps.cpp | 143 + src/external/colvars/colvardeps.cpp | 521 +++ src/external/colvars/colvardeps.h | 442 +++ src/external/colvars/colvargrid.cpp | 1219 +++++++ src/external/colvars/colvargrid.h | 1643 +++++++++ src/external/colvars/colvargrid_def.h | 488 +++ src/external/colvars/colvarmodule.cpp | 2551 +++++++++++++ src/external/colvars/colvarmodule.h | 888 +++++ src/external/colvars/colvarmodule_refs.h | 589 +++ src/external/colvars/colvarmodule_utils.h | 80 + src/external/colvars/colvarparams.cpp | 111 + src/external/colvars/colvarparams.h | 66 + src/external/colvars/colvarparse.cpp | 1021 ++++++ src/external/colvars/colvarparse.h | 395 ++ src/external/colvars/colvarproxy.cpp | 747 ++++ src/external/colvars/colvarproxy.h | 725 ++++ src/external/colvars/colvarproxy_io.cpp | 440 +++ src/external/colvars/colvarproxy_io.h | 187 + src/external/colvars/colvarproxy_replicas.cpp | 56 + src/external/colvars/colvarproxy_system.cpp | 204 ++ src/external/colvars/colvarproxy_system.h | 186 + src/external/colvars/colvarproxy_tcl.cpp | 225 ++ src/external/colvars/colvarproxy_tcl.h | 89 + src/external/colvars/colvarproxy_volmaps.cpp | 134 + src/external/colvars/colvarproxy_volmaps.h | 128 + src/external/colvars/colvars_memstream.cpp | 102 + src/external/colvars/colvars_memstream.h | 289 ++ src/external/colvars/colvars_version.h | 3 + src/external/colvars/colvarscript.cpp | 923 +++++ src/external/colvars/colvarscript.h | 455 +++ .../colvars/colvarscript_commands.cpp | 118 + src/external/colvars/colvarscript_commands.h | 674 ++++ .../colvars/colvarscript_commands_bias.cpp | 53 + .../colvars/colvarscript_commands_bias.h | 192 + .../colvars/colvarscript_commands_colvar.cpp | 53 + .../colvars/colvarscript_commands_colvar.h | 260 ++ src/external/colvars/colvartypes.cpp | 456 +++ src/external/colvars/colvartypes.h | 1509 ++++++++ src/external/colvars/colvarvalue.cpp | 1029 ++++++ src/external/colvars/colvarvalue.h | 755 ++++ src/external/colvars/nr_jacobi.cpp | 139 + src/external/colvars/nr_jacobi.h | 18 + src/external/lepton/include/Lepton.h | 43 + .../include/lepton/CompiledExpression.h | 124 + .../include/lepton/CompiledVectorExpression.h | 145 + .../lepton/include/lepton/CustomFunction.h | 109 + .../lepton/include/lepton/Exception.h | 59 + .../lepton/include/lepton/ExpressionProgram.h | 103 + .../include/lepton/ExpressionTreeNode.h | 111 + .../lepton/include/lepton/Operation.h | 1193 +++++++ .../lepton/include/lepton/ParsedExpression.h | 142 + src/external/lepton/include/lepton/Parser.h | 77 + .../lepton/include/lepton/windowsIncludes.h | 41 + .../lepton/src/CompiledExpression.cpp | 812 +++++ .../lepton/src/CompiledVectorExpression.cpp | 933 +++++ src/external/lepton/src/ExpressionProgram.cpp | 110 + .../lepton/src/ExpressionTreeNode.cpp | 153 + src/external/lepton/src/MSVC_erfc.h | 91 + src/external/lepton/src/Operation.cpp | 425 +++ src/external/lepton/src/ParsedExpression.cpp | 430 +++ src/external/lepton/src/Parser.cpp | 409 +++ src/gromacs/CMakeLists.txt | 7 + src/gromacs/CMakeLists.txt.orig | 540 +++ src/gromacs/applied_forces/CMakeLists.txt | 1 + .../applied_forces/colvars/CMakeLists.txt | 35 + .../colvars/colvarproxy_gromacs.cpp | 582 +++ .../colvars/colvarproxy_gromacs.h | 144 + .../colvars/colvarproxy_gromacs_version.h | 3 + src/gromacs/fileio/checkpoint.cpp | 64 + src/gromacs/fileio/checkpoint.cpp.orig | 3167 +++++++++++++++++ src/gromacs/fileio/checkpoint.h | 4 + src/gromacs/mdlib/energyoutput.cpp | 2 +- src/gromacs/mdlib/mdoutf.cpp | 6 + src/gromacs/mdlib/sim_util.cpp | 12 + src/gromacs/mdrun/legacymdrunoptions.h | 4 +- src/gromacs/mdrun/replicaexchange.cpp | 1 + src/gromacs/mdrun/runner.cpp | 59 + src/gromacs/mdtypes/colvarshistory.h | 20 + src/gromacs/mdtypes/inputrec.h | 6 + src/gromacs/mdtypes/observableshistory.cpp | 1 + src/gromacs/mdtypes/observableshistory.h | 4 + src/gromacs/mdtypes/state.cpp | 4 +- src/gromacs/mdtypes/state.h | 3 + .../tests/refdata/MdrunTest_WritesHelp.xml | 7 +- 124 files changed, 55562 insertions(+), 5 deletions(-) create mode 100644 cmake/gmxManageColvars.cmake create mode 100644 cmake/gmxManageLepton.cmake create mode 100644 src/external/colvars/colvar.cpp create mode 100644 src/external/colvars/colvar.h create mode 100644 src/external/colvars/colvar_UIestimator.h create mode 100644 src/external/colvars/colvar_arithmeticpath.h create mode 100644 src/external/colvars/colvar_geometricpath.h create mode 100644 src/external/colvars/colvar_neuralnetworkcompute.cpp create mode 100644 src/external/colvars/colvar_neuralnetworkcompute.h create mode 100644 src/external/colvars/colvar_rotation_derivative.h create mode 100644 src/external/colvars/colvaratoms.cpp create mode 100644 src/external/colvars/colvaratoms.h create mode 100644 src/external/colvars/colvarbias.cpp create mode 100644 src/external/colvars/colvarbias.h create mode 100644 src/external/colvars/colvarbias_abf.cpp create mode 100644 src/external/colvars/colvarbias_abf.h create mode 100644 src/external/colvars/colvarbias_alb.cpp create mode 100644 src/external/colvars/colvarbias_alb.h create mode 100644 src/external/colvars/colvarbias_histogram.cpp create mode 100644 src/external/colvars/colvarbias_histogram.h create mode 100644 src/external/colvars/colvarbias_histogram_reweight_amd.cpp create mode 100644 src/external/colvars/colvarbias_histogram_reweight_amd.h create mode 100644 src/external/colvars/colvarbias_meta.cpp create mode 100644 src/external/colvars/colvarbias_meta.h create mode 100644 src/external/colvars/colvarbias_restraint.cpp create mode 100644 src/external/colvars/colvarbias_restraint.h create mode 100644 src/external/colvars/colvarcomp.cpp create mode 100644 src/external/colvars/colvarcomp.h create mode 100644 src/external/colvars/colvarcomp_alchlambda.cpp create mode 100644 src/external/colvars/colvarcomp_angles.cpp create mode 100644 src/external/colvars/colvarcomp_apath.cpp create mode 100644 src/external/colvars/colvarcomp_combination.cpp create mode 100644 src/external/colvars/colvarcomp_coordnums.cpp create mode 100644 src/external/colvars/colvarcomp_distances.cpp create mode 100644 src/external/colvars/colvarcomp_gpath.cpp create mode 100644 src/external/colvars/colvarcomp_neuralnetwork.cpp create mode 100644 src/external/colvars/colvarcomp_protein.cpp create mode 100644 src/external/colvars/colvarcomp_rotations.cpp create mode 100644 src/external/colvars/colvarcomp_volmaps.cpp create mode 100644 src/external/colvars/colvardeps.cpp create mode 100644 src/external/colvars/colvardeps.h create mode 100644 src/external/colvars/colvargrid.cpp create mode 100644 src/external/colvars/colvargrid.h create mode 100644 src/external/colvars/colvargrid_def.h create mode 100644 src/external/colvars/colvarmodule.cpp create mode 100644 src/external/colvars/colvarmodule.h create mode 100644 src/external/colvars/colvarmodule_refs.h create mode 100644 src/external/colvars/colvarmodule_utils.h create mode 100644 src/external/colvars/colvarparams.cpp create mode 100644 src/external/colvars/colvarparams.h create mode 100644 src/external/colvars/colvarparse.cpp create mode 100644 src/external/colvars/colvarparse.h create mode 100644 src/external/colvars/colvarproxy.cpp create mode 100644 src/external/colvars/colvarproxy.h create mode 100644 src/external/colvars/colvarproxy_io.cpp create mode 100644 src/external/colvars/colvarproxy_io.h create mode 100644 src/external/colvars/colvarproxy_replicas.cpp create mode 100644 src/external/colvars/colvarproxy_system.cpp create mode 100644 src/external/colvars/colvarproxy_system.h create mode 100644 src/external/colvars/colvarproxy_tcl.cpp create mode 100644 src/external/colvars/colvarproxy_tcl.h create mode 100644 src/external/colvars/colvarproxy_volmaps.cpp create mode 100644 src/external/colvars/colvarproxy_volmaps.h create mode 100644 src/external/colvars/colvars_memstream.cpp create mode 100644 src/external/colvars/colvars_memstream.h create mode 100644 src/external/colvars/colvars_version.h create mode 100644 src/external/colvars/colvarscript.cpp create mode 100644 src/external/colvars/colvarscript.h create mode 100644 src/external/colvars/colvarscript_commands.cpp create mode 100644 src/external/colvars/colvarscript_commands.h create mode 100644 src/external/colvars/colvarscript_commands_bias.cpp create mode 100644 src/external/colvars/colvarscript_commands_bias.h create mode 100644 src/external/colvars/colvarscript_commands_colvar.cpp create mode 100644 src/external/colvars/colvarscript_commands_colvar.h create mode 100644 src/external/colvars/colvartypes.cpp create mode 100644 src/external/colvars/colvartypes.h create mode 100644 src/external/colvars/colvarvalue.cpp create mode 100644 src/external/colvars/colvarvalue.h create mode 100644 src/external/colvars/nr_jacobi.cpp create mode 100644 src/external/colvars/nr_jacobi.h create mode 100644 src/external/lepton/include/Lepton.h create mode 100644 src/external/lepton/include/lepton/CompiledExpression.h create mode 100644 src/external/lepton/include/lepton/CompiledVectorExpression.h create mode 100644 src/external/lepton/include/lepton/CustomFunction.h create mode 100644 src/external/lepton/include/lepton/Exception.h create mode 100644 src/external/lepton/include/lepton/ExpressionProgram.h create mode 100644 src/external/lepton/include/lepton/ExpressionTreeNode.h create mode 100644 src/external/lepton/include/lepton/Operation.h create mode 100644 src/external/lepton/include/lepton/ParsedExpression.h create mode 100644 src/external/lepton/include/lepton/Parser.h create mode 100644 src/external/lepton/include/lepton/windowsIncludes.h create mode 100644 src/external/lepton/src/CompiledExpression.cpp create mode 100644 src/external/lepton/src/CompiledVectorExpression.cpp create mode 100644 src/external/lepton/src/ExpressionProgram.cpp create mode 100644 src/external/lepton/src/ExpressionTreeNode.cpp create mode 100644 src/external/lepton/src/MSVC_erfc.h create mode 100644 src/external/lepton/src/Operation.cpp create mode 100644 src/external/lepton/src/ParsedExpression.cpp create mode 100644 src/external/lepton/src/Parser.cpp create mode 100644 src/gromacs/CMakeLists.txt.orig create mode 100644 src/gromacs/applied_forces/colvars/CMakeLists.txt create mode 100644 src/gromacs/applied_forces/colvars/colvarproxy_gromacs.cpp create mode 100644 src/gromacs/applied_forces/colvars/colvarproxy_gromacs.h create mode 100644 src/gromacs/applied_forces/colvars/colvarproxy_gromacs_version.h create mode 100644 src/gromacs/fileio/checkpoint.cpp.orig create mode 100644 src/gromacs/mdtypes/colvarshistory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 641c14d63c9..bbf644f4c43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -588,6 +588,10 @@ include(gmxManageLmfit) include(gmxManageMuparser) +include(gmxManageColvars) + +include(gmxManageLepton) + ################################################## # Process SIMD instruction settings ################################################## diff --git a/cmake/gmxManageColvars.cmake b/cmake/gmxManageColvars.cmake new file mode 100644 index 00000000000..d548d60f2c7 --- /dev/null +++ b/cmake/gmxManageColvars.cmake @@ -0,0 +1,57 @@ +# +# This file is part of the GROMACS molecular simulation package. +# +# Copyright 2023- The GROMACS Authors +# and the project initiators Erik Lindahl, Berk Hess and David van der Spoel. +# Consult the AUTHORS/COPYING files and https://www.gromacs.org for details. +# +# GROMACS 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 2.1 +# of the License, or (at your option) any later version. +# +# GROMACS 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 GROMACS; if not, see +# https://www.gnu.org/licenses, or write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# If you want to redistribute modifications to GROMACS, please +# consider that scientific software is very special. Version +# control is crucial - bugs must be traceable. We will be happy to +# consider code for inclusion in the official distribution, but +# derived work must not be called official GROMACS. Details are found +# in the README & COPYING files - if they are missing, get the +# official version at https://www.gromacs.org. +# +# To help us fund GROMACS development, we humbly ask that you cite +# the research papers on the package. Check out https://www.gromacs.org. + +# Build Colvars library as bundled in a GROMACS worktree; not supporting external linkage yet +gmx_option_multichoice(GMX_USE_COLVARS + "Build the collective variables (Colvars) library interfaced with GROMACS" + INTERNAL + INTERNAL NONE) +mark_as_advanced(GMX_USE_COLVARS) + +function(gmx_manage_colvars) + if(GMX_USE_COLVARS STREQUAL "INTERNAL") + file(GLOB COLVARS_SOURCES ${PROJECT_SOURCE_DIR}/src/external/colvars/*.cpp) + add_library(colvars OBJECT ${COLVARS_SOURCES}) + # Set correctly the value of __cplusplus, which MSVC doesn't do by default + target_compile_options(colvars PRIVATE $<$:/Zc:__cplusplus>) + set(HAVE_COLVARS 1 CACHE INTERNAL "Is Colvars available?") + else() + set(HAVE_COLVARS 0 CACHE INTERNAL "Is Colvars available?") + endif() +endfunction() + +function(gmx_include_colvars_headers) + if(GMX_USE_COLVARS STREQUAL "INTERNAL") + target_include_directories(libgromacs PRIVATE ${PROJECT_SOURCE_DIR}/src/external/colvars) + endif() +endfunction() diff --git a/cmake/gmxManageLepton.cmake b/cmake/gmxManageLepton.cmake new file mode 100644 index 00000000000..898acfae612 --- /dev/null +++ b/cmake/gmxManageLepton.cmake @@ -0,0 +1,22 @@ +# This file is part of the Collective Variables module (Colvars). +# The original version of Colvars and its updates are located at: +# https://github.com/Colvars/colvars +# Please update all Colvars source files before making any changes. +# If you wish to distribute your changes, please submit them to the +# Colvars repository at GitHub. + +function(gmx_manage_lepton) + + # Add Lepton library, which is developed and distributed as part of OpenMM: + # https://github.com/openmm/openmm + + file(GLOB LEPTON_SOURCES ${PROJECT_SOURCE_DIR}/src/external/lepton/src/*.cpp) + add_library(lepton OBJECT ${LEPTON_SOURCES}) + + target_include_directories(lepton PRIVATE ${PROJECT_SOURCE_DIR}/src/external/lepton/include) + target_compile_options(lepton PRIVATE -DLEPTON_BUILDING_STATIC_LIBRARY) + + # Set flags so that Colvars can leverage Lepton functionality + target_include_directories(colvars PRIVATE ${PROJECT_SOURCE_DIR}/src/external/lepton/include) + target_compile_options(colvars PRIVATE -DLEPTON -DLEPTON_USE_STATIC_LIBRARIES) +endfunction() diff --git a/cmake/gmxVersionInfo.cmake b/cmake/gmxVersionInfo.cmake index 6d31e026a7f..35af23bcbb2 100644 --- a/cmake/gmxVersionInfo.cmake +++ b/cmake/gmxVersionInfo.cmake @@ -254,7 +254,7 @@ set(REGRESSIONTEST_MD5SUM "5b8f458ca79f285abb4de34869afdfe6" CACHE INTERNAL "MD5 # If you are distributing a patch to GROMACS, then this change would # be great as part of your patch. Otherwise for personal use, you can # also just set a CMake cache variable. -set(GMX_VERSION_STRING_OF_FORK "" CACHE INTERNAL +set(GMX_VERSION_STRING_OF_FORK "Colvars-2023-10-23" CACHE INTERNAL "Version string for forks of GROMACS to set to describe themselves") mark_as_advanced(GMX_VERSION_STRING_OF_FORK) if (GMX_VERSION_STRING_OF_FORK) diff --git a/src/external/colvars/colvar.cpp b/src/external/colvars/colvar.cpp new file mode 100644 index 00000000000..de5917430c8 --- /dev/null +++ b/src/external/colvars/colvar.cpp @@ -0,0 +1,2992 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvarcomp.h" +#include "colvar.h" +#include "colvarscript.h" +#include "colvars_memstream.h" + + +std::map> + colvar::global_cvc_map = + std::map>(); + + +colvar::colvar() +{ + prev_timestep = -1L; + after_restart = false; + kinetic_energy = 0.0; + potential_energy = 0.0; + +#ifdef LEPTON + dev_null = 0.0; +#endif + + matching_state = false; + + expand_boundaries = false; + + description = "uninitialized colvar"; + colvar::init_dependencies(); +} + + +/// Compare two cvcs using their names +/// Used to sort CVC array in scripted coordinates +bool colvar::compare_cvc(const colvar::cvc* const i, const colvar::cvc* const j) +{ + return i->name < j->name; +} + + +int colvar::init(std::string const &conf) +{ + cvm::log("Initializing a new collective variable.\n"); + colvarparse::set_string(conf); + + int error_code = COLVARS_OK; + + colvarmodule *cv = cvm::main(); + + get_keyval(conf, "name", this->name, + (std::string("colvar")+cvm::to_str(cv->variables()->size()))); + + if ((cvm::colvar_by_name(this->name) != NULL) && + (cvm::colvar_by_name(this->name) != this)) { + cvm::error("Error: this colvar cannot have the same name, \""+this->name+ + "\", as another colvar.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + // Initialize dependency members + // Could be a function defined in a different source file, for space? + + this->description = "colvar " + this->name; + + error_code |= init_components(conf); + if (error_code != COLVARS_OK) { + return cvm::get_error(); + } + + size_t i; + +#ifdef LEPTON + error_code |= init_custom_function(conf); + if (error_code != COLVARS_OK) { + return cvm::get_error(); + } +#endif + + // Setup colvar as scripted function of components + if (get_keyval(conf, "scriptedFunction", scripted_function, + "", colvarparse::parse_silent)) { + + enable(f_cv_scripted); + cvm::log("This colvar uses scripted function \"" + scripted_function + "\".\n"); + cvm::main()->cite_feature("Scripted functions (Tcl)"); + + std::string type_str; + get_keyval(conf, "scriptedFunctionType", type_str, "scalar"); + + x.type(colvarvalue::type_notset); + int t; + for (t = 0; t < colvarvalue::type_all; t++) { + if (type_str == colvarvalue::type_keyword(colvarvalue::Type(t))) { + x.type(colvarvalue::Type(t)); + break; + } + } + if (x.type() == colvarvalue::type_notset) { + cvm::error("Could not parse scripted colvar type.", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + cvm::log(std::string("Expecting colvar value of type ") + + colvarvalue::type_desc(x.type())); + + if (x.type() == colvarvalue::type_vector) { + int size; + if (!get_keyval(conf, "scriptedFunctionVectorSize", size)) { + cvm::error("Error: no size specified for vector scripted function.", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + x.vector1d_value.resize(size); + } + + x_reported.type(x); + + // Sort array of cvcs based on their names + // Note: default CVC names are in input order for same type of CVC + std::sort(cvcs.begin(), cvcs.end(), colvar::compare_cvc); + + if(cvcs.size() > 1) { + cvm::log("Sorted list of components for this scripted colvar:\n"); + for (i = 0; i < cvcs.size(); i++) { + cvm::log(cvm::to_str(i+1) + " " + cvcs[i]->name); + } + } + + // Build ordered list of component values that will be + // passed to the script + for (i = 0; i < cvcs.size(); i++) { + sorted_cvc_values.push_back(&(cvcs[i]->value())); + } + } + + if (!(is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function))) { + colvarvalue const &cvc_value = (cvcs[0])->value(); + if (cvm::debug()) + cvm::log ("This collective variable is a "+ + colvarvalue::type_desc(cvc_value.type())+ + ((cvc_value.size() > 1) ? " with "+ + cvm::to_str(cvc_value.size())+" individual components.\n" : + ".\n")); + x.type(cvc_value); + x_reported.type(cvc_value); + } + + set_enabled(f_cv_scalar, (value().type() == colvarvalue::type_scalar)); + + // If using scripted biases, any colvar may receive bias forces + // and will need its gradient + if (cvm::scripted_forces()) { + enable(f_cv_gradient); + } + + // check for linear combinations + { + bool lin = !(is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function)); + for (i = 0; i < cvcs.size(); i++) { + + // FIXME this is a reverse dependency, ie. cv feature depends on cvc flag + // need to clarify this case + // if ((cvcs[i])->b_debug_gradients) + // enable(task_gradients); + + if ((cvcs[i])->sup_np != 1) { + if (cvm::debug() && lin) + cvm::log("Warning: You are using a non-linear polynomial " + "combination to define this collective variable, " + "some biasing methods may be unavailable.\n"); + lin = false; + + if ((cvcs[i])->sup_np < 0) { + cvm::log("Warning: you chose a negative exponent in the combination; " + "if you apply forces, the simulation may become unstable " + "when the component \""+ + (cvcs[i])->function_type+"\" approaches zero.\n"); + } + } + } + set_enabled(f_cv_linear, lin); + } + + // Colvar is homogeneous if: + // - it is linear (hence not scripted) + // - all cvcs have coefficient 1 or -1 + // i.e. sum or difference of cvcs + { + bool homogeneous = is_enabled(f_cv_linear); + for (i = 0; i < cvcs.size(); i++) { + if (cvm::fabs(cvm::fabs(cvcs[i]->sup_coeff) - 1.0) > 1.0e-10) { + homogeneous = false; + } + } + set_enabled(f_cv_homogeneous, homogeneous); + } + + // A single-component variable almost concides with its CVC object + if ((cvcs.size() == 1) && is_enabled(f_cv_homogeneous)) { + if ( !is_enabled(f_cv_scripted) && !is_enabled(f_cv_custom_function) && + (cvm::fabs(cvcs[0]->sup_coeff - 1.0) < 1.0e-10) && + (cvcs[0]->sup_np == 1) ) { + enable(f_cv_single_cvc); + } + } + + // Colvar is deemed periodic if: + // - it is homogeneous + // - all cvcs are periodic + // - all cvcs have the same period + if (is_enabled(f_cv_homogeneous) && cvcs[0]->is_enabled(f_cvc_periodic)) { + bool b_periodic = true; + period = cvcs[0]->period; + wrap_center = cvcs[0]->wrap_center; + for (i = 1; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled(f_cvc_periodic) || cvcs[i]->period != period) { + b_periodic = false; + period = 0.0; + cvm::log("Warning: although one component is periodic, this colvar will " + "not be treated as periodic, either because the exponent is not " + "1, or because components of different periodicity are defined. " + "Make sure that you know what you are doing!"); + } + } + set_enabled(f_cv_periodic, b_periodic); + } + + // Allow scripted/custom functions to be defined as periodic + if ( (is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function)) && is_enabled(f_cv_scalar) ) { + if (get_keyval(conf, "period", period, 0.)) { + enable(f_cv_periodic); + get_keyval(conf, "wrapAround", wrap_center, 0.); + } + } + + // check that cvcs are compatible + + for (i = 0; i < cvcs.size(); i++) { + + // components may have different types only for scripted functions + if (!(is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function)) && (colvarvalue::check_types(cvcs[i]->value(), + cvcs[0]->value())) ) { + cvm::error("ERROR: you are defining this collective variable " + "by using components of different types. " + "You must use the same type in order to " + "sum them together.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + + active_cvc_square_norm = 0.; + for (i = 0; i < cvcs.size(); i++) { + active_cvc_square_norm += cvcs[i]->sup_coeff * cvcs[i]->sup_coeff; + } + + // at this point, the colvar's type is defined + f.type(value()); + + x_old.type(value()); + v_fdiff.type(value()); + v_reported.type(value()); + fj.type(value()); + ft.type(value()); + ft_reported.type(value()); + f_old.type(value()); + f_old.reset(); + + x_restart.type(value()); + + reset_bias_force(); + + get_keyval(conf, "timeStepFactor", time_step_factor, 1); + if (time_step_factor < 0) { + cvm::error("Error: timeStepFactor must be positive.\n"); + return COLVARS_ERROR; + } + if (time_step_factor != 1) { + enable(f_cv_multiple_ts); + } + + error_code |= init_grid_parameters(conf); + + // Detect if we have a single component that is an alchemical lambda + if (is_enabled(f_cv_single_cvc) && cvcs[0]->function_type == "alchLambda") { + enable(f_cv_external); + } + + error_code |= init_extended_Lagrangian(conf); + error_code |= init_output_flags(conf); + + // Now that the children are defined we can solve dependencies + enable(f_cv_active); + + error_code |= parse_analysis(conf); + + if (cvm::debug()) + cvm::log("Done initializing collective variable \""+this->name+"\".\n"); + + return error_code; +} + + +#ifdef LEPTON +int colvar::init_custom_function(std::string const &conf) +{ + std::string expr, expr_in; // expr_in is a buffer to remember expr after unsuccessful parsing + std::vector pexprs; + Lepton::ParsedExpression pexpr; + size_t pos = 0; // current position in config string + double *ref; + + if (!key_lookup(conf, "customFunction", &expr_in, &pos)) { + return COLVARS_OK; + } + + cvm::main()->cite_feature("Custom functions (Lepton)"); + + enable(f_cv_custom_function); + cvm::log("This colvar uses a custom function.\n"); + + do { + expr = expr_in; + if (cvm::debug()) + cvm::log("Parsing expression \"" + expr + "\".\n"); + try { + pexpr = Lepton::Parser::parse(expr); + pexprs.push_back(pexpr); + } + catch (...) { + cvm::error("Error parsing expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + try { + value_evaluators.push_back( + new Lepton::CompiledExpression(pexpr.createCompiledExpression())); + // Define variables for cvc values + // Stored in order: expr1, cvc1, cvc2, expr2, cvc1... + for (size_t i = 0; i < cvcs.size(); i++) { + for (size_t j = 0; j < cvcs[i]->value().size(); j++) { + std::string vn = cvcs[i]->name + + (cvcs[i]->value().size() > 1 ? cvm::to_str(j+1) : ""); + try { + ref =&value_evaluators.back()->getVariableReference(vn); + } + catch (...) { // Variable is absent from expression + // To keep the same workflow, we use a pointer to a double here + // that will receive CVC values - even though none was allocated by Lepton + ref = &dev_null; + cvm::log("Warning: Variable " + vn + " is absent from expression \"" + expr + "\".\n"); + } + value_eval_var_refs.push_back(ref); + } + } + } + catch (...) { + cvm::error("Error compiling expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } while (key_lookup(conf, "customFunction", &expr_in, &pos)); + + + // Now define derivative with respect to each scalar sub-component + for (size_t i = 0; i < cvcs.size(); i++) { + for (size_t j = 0; j < cvcs[i]->value().size(); j++) { + std::string vn = cvcs[i]->name + + (cvcs[i]->value().size() > 1 ? cvm::to_str(j+1) : ""); + // Element ordering: we want the + // gradient vector of derivatives of all elements of the colvar + // wrt to a given element of a cvc ([i][j]) + for (size_t c = 0; c < pexprs.size(); c++) { + gradient_evaluators.push_back( + new Lepton::CompiledExpression(pexprs[c].differentiate(vn).createCompiledExpression())); + // and record the refs to each variable in those expressions + for (size_t k = 0; k < cvcs.size(); k++) { + for (size_t l = 0; l < cvcs[k]->value().size(); l++) { + std::string vvn = cvcs[k]->name + + (cvcs[k]->value().size() > 1 ? cvm::to_str(l+1) : ""); + try { + ref = &gradient_evaluators.back()->getVariableReference(vvn); + } + catch (...) { // Variable is absent from derivative + // To keep the same workflow, we use a pointer to a double here + // that will receive CVC values - even though none was allocated by Lepton + if (cvm::debug()) { + cvm::log("Warning: Variable " + vvn + " is absent from derivative of \"" + expr + "\" wrt " + vn + ".\n"); + } + ref = &dev_null; + } + grad_eval_var_refs.push_back(ref); + } + } + } + } + } + + + if (value_evaluators.size() == 0) { + cvm::error("Error: no custom function defined.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + std::string type_str; + bool b_type_specified = get_keyval(conf, "customFunctionType", + type_str, "scalar", parse_silent); + x.type(colvarvalue::type_notset); + int t; + for (t = 0; t < colvarvalue::type_all; t++) { + if (type_str == colvarvalue::type_keyword(colvarvalue::Type(t))) { + x.type(colvarvalue::Type(t)); + break; + } + } + if (x.type() == colvarvalue::type_notset) { + cvm::error("Could not parse custom colvar type.", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + // Guess type based on number of expressions + if (!b_type_specified) { + if (value_evaluators.size() == 1) { + x.type(colvarvalue::type_scalar); + } else { + x.type(colvarvalue::type_vector); + } + } + + if (x.type() == colvarvalue::type_vector) { + x.vector1d_value.resize(value_evaluators.size()); + } + + x_reported.type(x); + cvm::log(std::string("Expecting colvar value of type ") + + colvarvalue::type_desc(x.type()) + + (x.type()==colvarvalue::type_vector ? " of size " + cvm::to_str(x.size()) : "") + + ".\n"); + + if (x.size() != value_evaluators.size()) { + cvm::error("Error: based on custom function type, expected " + + cvm::to_str(x.size()) + " scalar expressions, but " + + cvm::to_str(value_evaluators.size()) + " were found.\n"); + return COLVARS_INPUT_ERROR; + } + + return COLVARS_OK; +} + +#else + +int colvar::init_custom_function(std::string const &conf) +{ + + std::string expr; + size_t pos = 0; + if (key_lookup(conf, "customFunction", &expr, &pos)) { + std::string msg("Error: customFunction requires the Lepton library."); + return cvm::error(msg, COLVARS_NOT_IMPLEMENTED); + } + + return COLVARS_OK; +} + +#endif // #ifdef LEPTON + + +int colvar::init_grid_parameters(std::string const &conf) +{ + int error_code = COLVARS_OK; + + colvarmodule *cv = cvm::main(); + + cvm::real default_width = width; + + if (!key_already_set("width")) { + // The first time, check if the CVC has a width to provide + default_width = 1.0; + if (is_enabled(f_cv_single_cvc) && cvcs[0]->is_enabled(f_cvc_width)) { + cvm::real const cvc_width = cvcs[0]->get_param("width"); + default_width = cvc_width; + } + } + + get_keyval(conf, "width", width, default_width); + + if (width <= 0.0) { + cvm::error("Error: \"width\" must be positive.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + lower_boundary.type(value()); + upper_boundary.type(value()); + lower_boundary.real_value = 0.0; + upper_boundary.real_value = width; // Default to 1-wide grids + + if (is_enabled(f_cv_scalar)) { + + if (is_enabled(f_cv_single_cvc)) { + // Get the default boundaries from the component + if (cvcs[0]->is_enabled(f_cvc_lower_boundary)) { + enable(f_cv_lower_boundary); + enable(f_cv_hard_lower_boundary); + lower_boundary = + *(reinterpret_cast(cvcs[0]->get_param_ptr("lowerBoundary"))); + } + if (cvcs[0]->is_enabled(f_cvc_upper_boundary)) { + enable(f_cv_upper_boundary); + enable(f_cv_hard_upper_boundary); + upper_boundary = + *(reinterpret_cast(cvcs[0]->get_param_ptr("upperBoundary"))); + } + } + + if (get_keyval(conf, "lowerBoundary", lower_boundary, lower_boundary)) { + enable(f_cv_lower_boundary); + // Because this is the user's choice, we cannot assume it is a true + // physical boundary + disable(f_cv_hard_lower_boundary); + } + + if (get_keyval(conf, "upperBoundary", upper_boundary, upper_boundary)) { + enable(f_cv_upper_boundary); + disable(f_cv_hard_upper_boundary); + } + + // Parse legacy wall options and set up a harmonicWalls bias if needed + cvm::real lower_wall_k = 0.0, upper_wall_k = 0.0; + cvm::real lower_wall = 0.0, upper_wall = 0.0; + std::string lw_conf, uw_conf; + + if (get_keyval(conf, "lowerWallConstant", lower_wall_k, 0.0, + parse_silent)) { + cvm::log("Reading legacy options lowerWall and lowerWallConstant: " + "consider using a harmonicWalls restraint (caution: force constant would then be scaled by width^2).\n"); + if (!get_keyval(conf, "lowerWall", lower_wall)) { + error_code |= cvm::error("Error: the value of lowerWall must be set " + "explicitly.\n", COLVARS_INPUT_ERROR); + } + lw_conf = std::string("\n\ + lowerWallConstant "+cvm::to_str(lower_wall_k*width*width)+"\n\ + lowerWalls "+cvm::to_str(lower_wall)+"\n"); + } + + if (get_keyval(conf, "upperWallConstant", upper_wall_k, 0.0, + parse_silent)) { + cvm::log("Reading legacy options upperWall and upperWallConstant: " + "consider using a harmonicWalls restraint (caution: force constant would then be scaled by width^2).\n"); + if (!get_keyval(conf, "upperWall", upper_wall)) { + error_code |= cvm::error("Error: the value of upperWall must be set " + "explicitly.\n", COLVARS_INPUT_ERROR); + } + uw_conf = std::string("\n\ + upperWallConstant "+cvm::to_str(upper_wall_k*width*width)+"\n\ + upperWalls "+cvm::to_str(upper_wall)+"\n"); + } + + if (lw_conf.size() && uw_conf.size()) { + if (lower_wall >= upper_wall) { + error_code |= cvm::error("Error: the upper wall, "+ + cvm::to_str(upper_wall)+ + ", is not higher than the lower wall, "+ + cvm::to_str(lower_wall)+".\n", + COLVARS_INPUT_ERROR); + } + } + + if (lw_conf.size() || uw_conf.size()) { + cvm::log("Generating a new harmonicWalls bias for compatibility purposes.\n"); + std::string const walls_conf("\n\ +harmonicWalls {\n\ + name "+this->name+"w\n\ + colvars "+this->name+"\n"+lw_conf+uw_conf+"\ + timeStepFactor "+cvm::to_str(time_step_factor)+"\n"+ + "}\n"); + error_code |= cv->append_new_config(walls_conf); + } + } + + get_keyval_feature(this, conf, "hardLowerBoundary", f_cv_hard_lower_boundary, + is_enabled(f_cv_hard_lower_boundary)); + + get_keyval_feature(this, conf, "hardUpperBoundary", f_cv_hard_upper_boundary, + is_enabled(f_cv_hard_upper_boundary)); + + // consistency checks for boundaries and walls + if (is_enabled(f_cv_lower_boundary) && is_enabled(f_cv_upper_boundary)) { + if (lower_boundary >= upper_boundary) { + error_code |= cvm::error("Error: the upper boundary, "+ + cvm::to_str(upper_boundary)+ + ", is not higher than the lower boundary, "+ + cvm::to_str(lower_boundary)+".\n", + COLVARS_INPUT_ERROR); + } + } + + get_keyval(conf, "expandBoundaries", expand_boundaries, expand_boundaries); + if (expand_boundaries && periodic_boundaries()) { + error_code |= cvm::error("Error: trying to expand boundaries that already " + "cover a whole period of a periodic colvar.\n", + COLVARS_INPUT_ERROR); + } + + if (expand_boundaries && is_enabled(f_cv_hard_lower_boundary) && + is_enabled(f_cv_hard_upper_boundary)) { + error_code |= cvm::error("Error: inconsistent configuration " + "(trying to expand boundaries, but both " + "hardLowerBoundary and hardUpperBoundary " + "are enabled).\n", COLVARS_INPUT_ERROR); + } + + return error_code; +} + + +int colvar::init_extended_Lagrangian(std::string const &conf) +{ + colvarproxy *proxy = cvm::main()->proxy; + get_keyval_feature(this, conf, "extendedLagrangian", f_cv_extended_Lagrangian, false); + + if (is_enabled(f_cv_extended_Lagrangian)) { + cvm::real temp, tolerance, extended_period; + + cvm::log("Enabling the extended Lagrangian term for colvar \""+ + this->name+"\".\n"); + + // Mark x_ext as uninitialized so we can initialize it to the colvar value when updating + x_ext.type(colvarvalue::type_notset); + v_ext.type(value()); + fr.type(value()); + const bool temp_provided = get_keyval(conf, "extendedTemp", temp, + proxy->target_temperature()); + if (is_enabled(f_cv_external)) { + // In the case of an "external" coordinate, there is no coupling potential: + // only the fictitious mass is meaningful + get_keyval(conf, "extendedMass", ext_mass); + // Ensure that the computed restraint energy term is zero + ext_force_k = 0.0; + } else { + // Standard case of coupling to a geometric colvar + if (temp <= 0.0) { // Then a finite temperature is required + if (temp_provided) + cvm::error("Error: \"extendedTemp\" must be positive.\n", COLVARS_INPUT_ERROR); + else + cvm::error("Error: a positive temperature must be provided, either " + "by enabling a thermostat, or through \"extendedTemp\".\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + get_keyval(conf, "extendedFluctuation", tolerance); + if (tolerance <= 0.0) { + cvm::error("Error: \"extendedFluctuation\" must be positive.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + ext_force_k = proxy->boltzmann() * temp / (tolerance * tolerance); + cvm::log("Computed extended system force constant: " + cvm::to_str(ext_force_k) + " [E]/U^2\n"); + + get_keyval(conf, "extendedTimeConstant", extended_period, 200.0); + if (extended_period <= 0.0) { + cvm::error("Error: \"extendedTimeConstant\" must be positive.\n", COLVARS_INPUT_ERROR); + } + ext_mass = (proxy->boltzmann() * temp * extended_period * extended_period) + / (4.0 * PI * PI * tolerance * tolerance); + cvm::log("Computed fictitious mass: " + cvm::to_str(ext_mass) + " [E]/(U/fs)^2 (U: colvar unit)\n"); + } + { + bool b_output_energy; + get_keyval(conf, "outputEnergy", b_output_energy, false); + if (b_output_energy) { + enable(f_cv_output_energy); + } + } + + get_keyval(conf, "extendedLangevinDamping", ext_gamma, 1.0); + if (ext_gamma < 0.0) { + cvm::error("Error: \"extendedLangevinDamping\" may not be negative.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + if (ext_gamma != 0.0) { + enable(f_cv_Langevin); + cvm::main()->cite_feature("BAOA integrator"); + ext_gamma *= 1.0e-3; // correct as long as input is required in ps-1 and cvm::dt() is in fs + // Adjust Langevin sigma for slow time step if time_step_factor != 1 + // Eq. (6a) in https://doi.org/10.1021/acs.jctc.2c00585 + ext_sigma = cvm::sqrt((1.0 - cvm::exp(-2.0 * ext_gamma * cvm::dt() * cvm::real(time_step_factor))) + * ext_mass * proxy->boltzmann() * temp); + } else { + ext_sigma = 0.0; + } + + get_keyval_feature(this, conf, "reflectingLowerBoundary", f_cv_reflecting_lower_boundary, false); + get_keyval_feature(this, conf, "reflectingUpperBoundary", f_cv_reflecting_upper_boundary, false); + } + + return COLVARS_OK; +} + + +int colvar::init_output_flags(std::string const &conf) +{ + { + bool b_output_value; + get_keyval(conf, "outputValue", b_output_value, true); + if (b_output_value) { + enable(f_cv_output_value); + } + } + + { + bool b_output_velocity; + get_keyval(conf, "outputVelocity", b_output_velocity, false); + if (b_output_velocity) { + enable(f_cv_output_velocity); + } + } + + { + bool temp; + if (get_keyval(conf, "outputSystemForce", temp, false, colvarparse::parse_silent)) { + cvm::error("Option outputSystemForce is deprecated: only outputTotalForce is supported instead.\n" + "The two are NOT identical: see https://colvars.github.io/totalforce.html.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + + get_keyval_feature(this, conf, "outputTotalForce", f_cv_output_total_force, false); + get_keyval_feature(this, conf, "outputAppliedForce", f_cv_output_applied_force, false); + get_keyval_feature(this, conf, "subtractAppliedForce", f_cv_subtract_applied_force, false); + + return COLVARS_OK; +} + + +template +int colvar::init_components_type(std::string const &, char const * /* def_desc */, + char const *def_config_key) +{ + global_cvc_map[def_config_key] = [](const std::string &cvc_conf) { + return new def_class_name(cvc_conf); + }; + // TODO: maybe it is better to do more check to avoid duplication in the map? + return COLVARS_OK; +} + + +int colvar::init_components_type_from_global_map(const std::string& conf, + const char* def_config_key) { + size_t def_count = 0; + std::string def_conf = ""; + size_t pos = 0; + while ( this->key_lookup(conf, + def_config_key, + &def_conf, + &pos) ) { + if (!def_conf.size()) continue; + cvm::log("Initializing " + "a new \""+std::string(def_config_key)+"\" component"+ + (cvm::debug() ? ", with configuration:\n"+def_conf + : ".\n")); + cvc *cvcp = global_cvc_map[def_config_key](def_conf); + cvm::increase_depth(); + if (cvcp) { + int error_code = cvcp->init_code; + cvcs.push_back(cvcp); + error_code |= cvcp->set_function_type(def_config_key); + if (error_code == COLVARS_OK) { + error_code |= cvcp->check_keywords(def_conf, def_config_key); + } + if (error_code != COLVARS_OK) { + cvm::decrease_depth(); + return cvm::error("Error: in setting up component \"" + std::string(def_config_key) + + "\".\n", + COLVARS_INPUT_ERROR); + } + } else { + cvm::decrease_depth(); + return cvm::error("Error: in allocating component \"" + std::string(def_config_key) + "\".\n", + COLVARS_MEMORY_ERROR); + } + + if ((cvcp->period != 0.0) || (cvcp->wrap_center != 0.0)) { + if (!cvcp->is_enabled(f_cvc_periodic)) { + cvm::decrease_depth(); + return cvm::error("Error: invalid use of period and/or " + "wrapAround in a \"" + + std::string(def_config_key) + "\" component.\n" + + "Period: " + cvm::to_str(cvcp->period) + + " wrapAround: " + cvm::to_str(cvcp->wrap_center), + COLVARS_INPUT_ERROR); + } + } + + if ( ! cvcs.back()->name.size()) { + std::ostringstream s; + s << def_config_key << std::setfill('0') << std::setw(4) << ++def_count; + cvcs.back()->name = s.str(); + /* pad cvc number for correct ordering when sorting by name */ + } + + cvcs.back()->setup(); + if (cvm::debug()) { + cvm::log("Done initializing a \"" + std::string(def_config_key) + "\" component" + + (cvm::debug() ? ", named \"" + cvcs.back()->name + "\"" : "") + ".\n"); + } + + cvm::decrease_depth(); + + def_conf = ""; + if (cvm::debug()) { + cvm::log("Parsed " + cvm::to_str(cvcs.size()) + " components at this time.\n"); + } + } + + return COLVARS_OK; +} + + +int colvar::init_components(std::string const &conf) +{ + int error_code = COLVARS_OK; + size_t i = 0, j = 0; + + // in the non-C++11 case, the components are initialized directly by init_components_type; + // in the C++11 case, the components are stored in the global_cvc_map at first + // by init_components_type, and then the map is iterated to initialize all components. + error_code |= init_components_type(conf, "distance", "distance"); + error_code |= init_components_type(conf, "distance vector", "distanceVec"); + error_code |= init_components_type(conf, "Cartesian coordinates", "cartesian"); + error_code |= init_components_type(conf, "distance vector " + "direction", "distanceDir"); + error_code |= init_components_type(conf, "distance projection " + "on an axis", "distanceZ"); + error_code |= init_components_type(conf, "distance projection " + "on a plane", "distanceXY"); + error_code |= init_components_type(conf, "spherical polar angle theta", + "polarTheta"); + error_code |= init_components_type(conf, "spherical azimuthal angle phi", + "polarPhi"); + error_code |= init_components_type(conf, "average distance " + "weighted by inverse power", "distanceInv"); + error_code |= init_components_type(conf, "N1xN2-long vector " + "of pairwise distances", "distancePairs"); + error_code |= init_components_type(conf, "dipole magnitude", + "dipoleMagnitude"); + error_code |= init_components_type(conf, "coordination " + "number", "coordNum"); + error_code |= init_components_type(conf, "self-coordination " + "number", "selfCoordNum"); + error_code |= init_components_type(conf, "group-coordination " + "number", "groupCoord"); + error_code |= init_components_type(conf, "angle", "angle"); + error_code |= init_components_type(conf, "dipole angle", "dipoleAngle"); + error_code |= init_components_type(conf, "dihedral", "dihedral"); + error_code |= init_components_type(conf, "hydrogen bond", "hBond"); + error_code |= init_components_type(conf, "alpha helix", "alpha"); + error_code |= init_components_type(conf, "dihedral " + "principal component", "dihedralPC"); + error_code |= init_components_type(conf, "orientation", "orientation"); + error_code |= init_components_type(conf, "orientation " + "angle", "orientationAngle"); + error_code |= init_components_type(conf, "orientation " + "projection", "orientationProj"); + error_code |= init_components_type(conf, "tilt", "tilt"); + error_code |= init_components_type(conf, "spin angle", "spinAngle"); + error_code |= init_components_type(conf, "RMSD", "rmsd"); + error_code |= init_components_type(conf, "radius of " + "gyration", "gyration"); + error_code |= init_components_type(conf, "moment of " + "inertia", "inertia"); + error_code |= init_components_type(conf, "moment of inertia around an axis", "inertiaZ"); + error_code |= init_components_type(conf, "eigenvector", "eigenvector"); + error_code |= init_components_type(conf, "alchemical coupling parameter", "alchLambda"); + error_code |= init_components_type(conf, "force on alchemical coupling parameter", "alchFLambda"); + error_code |= init_components_type(conf, "arithmetic path collective variables (s)", "aspath"); + error_code |= init_components_type(conf, "arithmetic path collective variables (z)", "azpath"); + error_code |= init_components_type(conf, "geometrical path collective variables (s)", "gspath"); + error_code |= init_components_type(conf, "geometrical path collective variables (z)", "gzpath"); + error_code |= init_components_type(conf, "linear combination of other collective variables", "linearCombination"); + error_code |= init_components_type(conf, "geometrical path collective variables (s) for other CVs", "gspathCV"); + error_code |= init_components_type(conf, "geometrical path collective variables (z) for other CVs", "gzpathCV"); + error_code |= init_components_type(conf, "arithmetic path collective variables (s) for other CVs", "aspathCV"); + error_code |= init_components_type(conf, "arithmetic path collective variables (s) for other CVs", "azpathCV"); + error_code |= init_components_type(conf, "euler phi angle of the optimal orientation", "eulerPhi"); + error_code |= init_components_type(conf, "euler psi angle of the optimal orientation", "eulerPsi"); + error_code |= init_components_type(conf, "euler theta angle of the optimal orientation", "eulerTheta"); +#ifdef LEPTON + error_code |= init_components_type(conf, "CV with support of the lepton custom function", "customColvar"); +#endif + error_code |= init_components_type(conf, "neural network CV for other CVs", "NeuralNetwork"); + + error_code |= init_components_type(conf, "total value of atomic map", "mapTotal"); + + // iterate over all available CVC in the map + for (auto it = global_cvc_map.begin(); it != global_cvc_map.end(); ++it) { + error_code |= init_components_type_from_global_map(conf, it->first.c_str()); + // TODO: is it better to check the error code here? + if (error_code != COLVARS_OK) { + cvm::log("Failed to initialize " + it->first + " with the following configuration:\n"); + cvm::log(conf); + // TODO: should it stop here? + break; + } + } + + if (!cvcs.size() || (error_code != COLVARS_OK)) { + cvm::error("Error: no valid components were provided " + "for this collective variable.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + // Check for uniqueness of CVC names (esp. if user-provided) + for (i = 0; i < cvcs.size(); i++) { + for (j = i+1; j < cvcs.size(); j++) { + if (cvcs[i]->name == cvcs[j]->name) { + cvm::error("Components " + cvm::to_str(i) + " and " + cvm::to_str(j) +\ + " cannot have the same name \"" + cvcs[i]->name+ "\".\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + } + + n_active_cvcs = cvcs.size(); + + // Store list of children cvcs for dependency checking purposes + for (i = 0; i < cvcs.size(); i++) { + add_child(cvcs[i]); + } + + cvm::log("All components initialized.\n"); + + return COLVARS_OK; +} + + +void colvar::do_feature_side_effects(int id) +{ + switch (id) { + case f_cv_total_force_calc: + cvm::request_total_force(); + break; + case f_cv_collect_atom_ids: + // Needed for getting gradients vias collect_gradients + // or via atomic forces e.g. in Colvars Dashboard in VMD + if (atom_ids.size() == 0) { + build_atom_list(); + } + break; + } +} + + +void colvar::build_atom_list(void) +{ + // If atomic gradients are requested, build full list of atom ids from all cvcs + std::list temp_id_list; + + for (size_t i = 0; i < cvcs.size(); i++) { + for (size_t j = 0; j < cvcs[i]->atom_groups.size(); j++) { + cvm::atom_group const &ag = *(cvcs[i]->atom_groups[j]); + for (size_t k = 0; k < ag.size(); k++) { + temp_id_list.push_back(ag[k].id); + } + if (ag.is_enabled(f_ag_fitting_group) && ag.is_enabled(f_ag_fit_gradients)) { + cvm::atom_group const &fg = *(ag.fitting_group); + for (size_t k = 0; k < fg.size(); k++) { + temp_id_list.push_back(fg[k].id); + } + } + } + } + + temp_id_list.sort(); + temp_id_list.unique(); + + std::list::iterator li; + for (li = temp_id_list.begin(); li != temp_id_list.end(); ++li) { + atom_ids.push_back(*li); + } + + temp_id_list.clear(); + + atomic_gradients.resize(atom_ids.size()); + if (atom_ids.size()) { + if (cvm::debug()) + cvm::log("Colvar: created atom list with " + cvm::to_str(atom_ids.size()) + " atoms.\n"); + } else { + cvm::log("Warning: colvar components communicated no atom IDs.\n"); + } +} + + +int colvar::parse_analysis(std::string const &conf) +{ + + // if (cvm::debug()) + // cvm::log ("Parsing analysis flags for collective variable \""+ + // this->name+"\".\n"); + + runave_length = 0; + bool b_runave = false; + if (get_keyval(conf, "runAve", b_runave) && b_runave) { + + enable(f_cv_runave); + + get_keyval(conf, "runAveLength", runave_length, 1000); + get_keyval(conf, "runAveStride", runave_stride, 1); + + if ((cvm::restart_out_freq % runave_stride) != 0) { + cvm::error("Error: runAveStride must be commensurate with the restart frequency.\n", COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "runAveOutputFile", runave_outfile, runave_outfile); + } + + acf_length = 0; + bool b_acf = false; + if (get_keyval(conf, "corrFunc", b_acf) && b_acf) { + + enable(f_cv_corrfunc); + + get_keyval(conf, "corrFuncWithColvar", acf_colvar_name, this->name); + if (acf_colvar_name == this->name) { + cvm::log("Calculating auto-correlation function.\n"); + } else { + cvm::log("Calculating correlation function with \""+ + this->name+"\".\n"); + } + + std::string acf_type_str; + get_keyval(conf, "corrFuncType", acf_type_str, to_lower_cppstr(std::string("velocity"))); + if (acf_type_str == to_lower_cppstr(std::string("coordinate"))) { + acf_type = acf_coor; + } else if (acf_type_str == to_lower_cppstr(std::string("velocity"))) { + acf_type = acf_vel; + enable(f_cv_fdiff_velocity); + colvar *cv2 = cvm::colvar_by_name(acf_colvar_name); + if (cv2 == NULL) { + return cvm::error("Error: collective variable \""+acf_colvar_name+ + "\" is not defined at this time.\n", COLVARS_INPUT_ERROR); + } + cv2->enable(f_cv_fdiff_velocity); // Manual dependency to object of same type + } else if (acf_type_str == to_lower_cppstr(std::string("coordinate_p2"))) { + acf_type = acf_p2coor; + } else { + cvm::log("Unknown type of correlation function, \""+ + acf_type_str+"\".\n"); + cvm::set_error_bits(COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "corrFuncOffset", acf_offset, 0); + get_keyval(conf, "corrFuncLength", acf_length, 1000); + get_keyval(conf, "corrFuncStride", acf_stride, 1); + + if ((cvm::restart_out_freq % acf_stride) != 0) { + cvm::error("Error: corrFuncStride must be commensurate with the restart frequency.\n", COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "corrFuncNormalize", acf_normalize, true); + get_keyval(conf, "corrFuncOutputFile", acf_outfile, acf_outfile); + } + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +int colvar::init_dependencies() { + size_t i; + if (features().size() == 0) { + for (i = 0; i < f_cv_ntot; i++) { + modify_features().push_back(new feature); + } + + init_feature(f_cv_active, "active", f_type_dynamic); + // Do not require f_cvc_active in children, as some components may be disabled + // Colvars must be either a linear combination, or scalar (and polynomial) or scripted/custom + require_feature_alt(f_cv_active, f_cv_scalar, f_cv_linear, f_cv_scripted, f_cv_custom_function); + + init_feature(f_cv_awake, "awake", f_type_static); + require_feature_self(f_cv_awake, f_cv_active); + + init_feature(f_cv_gradient, "gradient", f_type_dynamic); + require_feature_children(f_cv_gradient, f_cvc_gradient); + + init_feature(f_cv_collect_gradient, "collect_gradient", f_type_dynamic); + require_feature_self(f_cv_collect_gradient, f_cv_gradient); + require_feature_self(f_cv_collect_gradient, f_cv_scalar); + require_feature_self(f_cv_collect_gradient, f_cv_collect_atom_ids); + // The following exclusions could be lifted by implementing the feature + exclude_feature_self(f_cv_collect_gradient, f_cv_scripted); + exclude_feature_self(f_cv_collect_gradient, f_cv_custom_function); + require_feature_children(f_cv_collect_gradient, f_cvc_explicit_gradient); + + init_feature(f_cv_collect_atom_ids, "collect_atom_ids", f_type_dynamic); + require_feature_children(f_cv_collect_atom_ids, f_cvc_collect_atom_ids); + + init_feature(f_cv_fdiff_velocity, "velocity_from_finite_differences", f_type_dynamic); + + // System force: either trivial (spring force); through extended Lagrangian, or calculated explicitly + init_feature(f_cv_total_force, "total_force", f_type_dynamic); + require_feature_alt(f_cv_total_force, f_cv_extended_Lagrangian, f_cv_total_force_calc); + + // Deps for explicit total force calculation + init_feature(f_cv_total_force_calc, "total_force_calculation", f_type_dynamic); + require_feature_self(f_cv_total_force_calc, f_cv_scalar); + require_feature_self(f_cv_total_force_calc, f_cv_linear); + require_feature_children(f_cv_total_force_calc, f_cvc_inv_gradient); + require_feature_self(f_cv_total_force_calc, f_cv_Jacobian); + + init_feature(f_cv_Jacobian, "Jacobian_derivative", f_type_dynamic); + require_feature_self(f_cv_Jacobian, f_cv_scalar); + require_feature_self(f_cv_Jacobian, f_cv_linear); + require_feature_children(f_cv_Jacobian, f_cvc_Jacobian); + + init_feature(f_cv_hide_Jacobian, "hide_Jacobian_force", f_type_user); + require_feature_self(f_cv_hide_Jacobian, f_cv_Jacobian); // can only hide if calculated + exclude_feature_self(f_cv_hide_Jacobian, f_cv_extended_Lagrangian); + + init_feature(f_cv_extended_Lagrangian, "extended_Lagrangian", f_type_user); + require_feature_self(f_cv_extended_Lagrangian, f_cv_scalar); + require_feature_self(f_cv_extended_Lagrangian, f_cv_gradient); + + init_feature(f_cv_Langevin, "Langevin_dynamics", f_type_user); + require_feature_self(f_cv_Langevin, f_cv_extended_Lagrangian); + + init_feature(f_cv_external, "external", f_type_user); + require_feature_self(f_cv_external, f_cv_single_cvc); + + init_feature(f_cv_single_cvc, "single_component", f_type_static); + + init_feature(f_cv_linear, "linear", f_type_static); + + init_feature(f_cv_scalar, "scalar", f_type_static); + + init_feature(f_cv_output_energy, "output_energy", f_type_user); + + init_feature(f_cv_output_value, "output_value", f_type_user); + + init_feature(f_cv_output_velocity, "output_velocity", f_type_user); + require_feature_self(f_cv_output_velocity, f_cv_fdiff_velocity); + + init_feature(f_cv_output_applied_force, "output_applied_force", f_type_user); + + init_feature(f_cv_output_total_force, "output_total_force", f_type_user); + require_feature_self(f_cv_output_total_force, f_cv_total_force); + + init_feature(f_cv_subtract_applied_force, "subtract_applied_force_from_total_force", f_type_user); + require_feature_self(f_cv_subtract_applied_force, f_cv_total_force); + + init_feature(f_cv_lower_boundary, "lower_boundary", f_type_user); + require_feature_self(f_cv_lower_boundary, f_cv_scalar); + + init_feature(f_cv_upper_boundary, "upper_boundary", f_type_user); + require_feature_self(f_cv_upper_boundary, f_cv_scalar); + + init_feature(f_cv_hard_lower_boundary, "hard_lower_boundary", f_type_user); + require_feature_self(f_cv_hard_lower_boundary, f_cv_lower_boundary); + + init_feature(f_cv_hard_upper_boundary, "hard_upper_boundary", f_type_user); + require_feature_self(f_cv_hard_upper_boundary, f_cv_upper_boundary); + + init_feature(f_cv_reflecting_lower_boundary, "reflecting_lower_boundary", f_type_user); + require_feature_self(f_cv_reflecting_lower_boundary, f_cv_lower_boundary); + require_feature_self(f_cv_reflecting_lower_boundary, f_cv_extended_Lagrangian); + + init_feature(f_cv_reflecting_upper_boundary, "reflecting_upper_boundary", f_type_user); + require_feature_self(f_cv_reflecting_upper_boundary, f_cv_upper_boundary); + require_feature_self(f_cv_reflecting_upper_boundary, f_cv_extended_Lagrangian); + + init_feature(f_cv_grid, "grid", f_type_dynamic); + require_feature_self(f_cv_grid, f_cv_scalar); + + init_feature(f_cv_runave, "running_average", f_type_user); + + init_feature(f_cv_corrfunc, "correlation_function", f_type_user); + + init_feature(f_cv_scripted, "scripted", f_type_user); + + init_feature(f_cv_custom_function, "custom_function", f_type_user); + exclude_feature_self(f_cv_custom_function, f_cv_scripted); + + init_feature(f_cv_periodic, "periodic", f_type_static); + require_feature_self(f_cv_periodic, f_cv_scalar); + init_feature(f_cv_scalar, "scalar", f_type_static); + init_feature(f_cv_linear, "linear", f_type_static); + init_feature(f_cv_homogeneous, "homogeneous", f_type_static); + + // because total forces are obtained from the previous time step, + // we cannot (currently) have colvar values and total forces for the same timestep + init_feature(f_cv_multiple_ts, "multiple_timestep", f_type_static); + exclude_feature_self(f_cv_multiple_ts, f_cv_total_force_calc); + + // check that everything is initialized + for (i = 0; i < colvardeps::f_cv_ntot; i++) { + if (is_not_set(i)) { + cvm::error("Uninitialized feature " + cvm::to_str(i) + " in " + description); + } + } + } + + // Initialize feature_states for each instance + feature_states.reserve(f_cv_ntot); + for (i = 0; i < f_cv_ntot; i++) { + feature_states.push_back(feature_state(true, false)); + // Most features are available, so we set them so + // and list exceptions below + } + + feature_states[f_cv_fdiff_velocity].available = + cvm::main()->proxy->simulation_running(); + + return COLVARS_OK; +} + + +void colvar::setup() +{ + // loop over all components to update masses and charges of all groups + for (size_t i = 0; i < cvcs.size(); i++) { + for (size_t ig = 0; ig < cvcs[i]->atom_groups.size(); ig++) { + cvm::atom_group *atoms = cvcs[i]->atom_groups[ig]; + atoms->setup(); + atoms->print_properties(name, i, ig); + atoms->read_positions(); + } + } +} + + +std::vector > colvar::get_atom_lists() +{ + std::vector > lists; + for (size_t i = 0; i < cvcs.size(); i++) { + std::vector > li = cvcs[i]->get_atom_lists(); + lists.insert(lists.end(), li.begin(), li.end()); + } + return lists; +} + + +std::vector const &colvar::get_volmap_ids() +{ + volmap_ids_.resize(cvcs.size()); + for (size_t i = 0; i < cvcs.size(); i++) { + if (cvcs[i]->param_exists("mapID") == COLVARS_OK) { + volmap_ids_[i] = + *(reinterpret_cast(cvcs[i]->get_param_ptr("mapID"))); + } else { + volmap_ids_[i] = -1; + } + } + return volmap_ids_; +} + + +colvar::~colvar() +{ + // There is no need to call free_children_deps() here + // because the children are cvcs and will be deleted + // just below + + // Clear references to this colvar's cvcs as children + // for dependency purposes + remove_all_children(); + + for (std::vector::reverse_iterator ci = cvcs.rbegin(); + ci != cvcs.rend(); + ++ci) { + // clear all children of this cvc (i.e. its atom groups) + // because the cvc base class destructor can't do it early enough + // and we don't want to have each cvc derived class do it separately + (*ci)->remove_all_children(); + delete *ci; + } + cvcs.clear(); + + while (biases.size() > 0) { + size_t const i = biases.size()-1; + cvm::log("Warning: before deleting colvar " + name + + ", deleting related bias " + biases[i]->name); + delete biases[i]; + } + biases.clear(); + + // remove reference to this colvar from the module + colvarmodule *cv = cvm::main(); + for (std::vector::iterator cvi = cv->variables()->begin(); + cvi != cv->variables()->end(); + ++cvi) { + if ( *cvi == this) { + cv->variables()->erase(cvi); + break; + } + } + + cv->config_changed(); + +#ifdef LEPTON + for (std::vector::iterator cei = value_evaluators.begin(); + cei != value_evaluators.end(); + ++cei) { + if (*cei != NULL) delete (*cei); + } + value_evaluators.clear(); + + for (std::vector::iterator gei = gradient_evaluators.begin(); + gei != gradient_evaluators.end(); + ++gei) { + if (*gei != NULL) delete (*gei); + } + gradient_evaluators.clear(); +#endif +} + + + +// ******************** CALC FUNCTIONS ******************** + + +// Default schedule (everything is serialized) +int colvar::calc() +{ + // Note: if anything is added here, it should be added also in the SMP block of calc_colvars() + int error_code = COLVARS_OK; + if (is_enabled(f_cv_active)) { + error_code |= update_cvc_flags(); + if (error_code != COLVARS_OK) return error_code; + error_code |= calc_cvcs(); + if (error_code != COLVARS_OK) return error_code; + error_code |= collect_cvc_data(); + } + return error_code; +} + + +int colvar::calc_cvcs(int first_cvc, size_t num_cvcs) +{ + if (cvm::debug()) + cvm::log("Calculating colvar \""+this->name+"\", components "+ + cvm::to_str(first_cvc)+" through "+cvm::to_str(first_cvc+num_cvcs)+".\n"); + + colvarproxy *proxy = cvm::main()->proxy; + int error_code = COLVARS_OK; + + error_code |= check_cvc_range(first_cvc, num_cvcs); + if (error_code != COLVARS_OK) { + return error_code; + } + + if ((cvm::step_relative() > 0) && (!proxy->total_forces_same_step())){ + // Use Jacobian derivative from previous timestep + error_code |= calc_cvc_total_force(first_cvc, num_cvcs); + } + // atom coordinates are updated by the next line + error_code |= calc_cvc_values(first_cvc, num_cvcs); + error_code |= calc_cvc_gradients(first_cvc, num_cvcs); + error_code |= calc_cvc_Jacobians(first_cvc, num_cvcs); + if (proxy->total_forces_same_step()){ + // Use Jacobian derivative from this timestep + error_code |= calc_cvc_total_force(first_cvc, num_cvcs); + } + + if (cvm::debug()) + cvm::log("Done calculating colvar \""+this->name+"\".\n"); + + return error_code; +} + + +int colvar::collect_cvc_data() +{ + if (cvm::debug()) + cvm::log("Calculating colvar \""+this->name+"\"'s properties.\n"); + + colvarproxy *proxy = cvm::main()->proxy; + int error_code = COLVARS_OK; + + if ((cvm::step_relative() > 0) && (!proxy->total_forces_same_step())){ + // Total force depends on Jacobian derivative from previous timestep + // collect_cvc_total_forces() uses the previous value of jd + error_code |= collect_cvc_total_forces(); + } + error_code |= collect_cvc_values(); + error_code |= collect_cvc_gradients(); + error_code |= collect_cvc_Jacobians(); + if (proxy->total_forces_same_step()){ + // Use Jacobian derivative from this timestep + error_code |= collect_cvc_total_forces(); + } + error_code |= calc_colvar_properties(); + + if (cvm::debug()) + cvm::log("Done calculating colvar \""+this->name+"\"'s properties.\n"); + + return error_code; +} + + +int colvar::check_cvc_range(int first_cvc, size_t /* num_cvcs */) +{ + if ((first_cvc < 0) || (first_cvc >= ((int) cvcs.size()))) { + cvm::error("Error: trying to address a component outside the " + "range defined for colvar \""+name+"\".\n", COLVARS_BUG_ERROR); + return COLVARS_BUG_ERROR; + } + return COLVARS_OK; +} + + +int colvar::calc_cvc_values(int first_cvc, size_t num_cvcs) +{ + size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); + size_t i, cvc_count; + + // calculate the value of the colvar + + if (cvm::debug()) + cvm::log("Calculating colvar components.\n"); + + // First, calculate component values + cvm::increase_depth(); + for (i = first_cvc, cvc_count = 0; + (i < cvcs.size()) && (cvc_count < cvc_max_count); + i++) { + if (!cvcs[i]->is_enabled()) continue; + cvc_count++; + (cvcs[i])->read_data(); + (cvcs[i])->calc_value(); + if (cvm::debug()) + cvm::log("Colvar component no. "+cvm::to_str(i+1)+ + " within colvar \""+this->name+"\" has value "+ + cvm::to_str((cvcs[i])->value(), + cvm::cv_width, cvm::cv_prec)+".\n"); + } + cvm::decrease_depth(); + + return COLVARS_OK; +} + + +int colvar::collect_cvc_values() +{ + x.reset(); + + // combine them appropriately, using either a scripted function or a polynomial + if (is_enabled(f_cv_scripted)) { + // cvcs combined by user script + int res = cvm::proxy->run_colvar_callback(scripted_function, sorted_cvc_values, x); + if (res == COLVARS_NOT_IMPLEMENTED) { + cvm::error("Scripted colvars are not implemented."); + return COLVARS_NOT_IMPLEMENTED; + } + if (res != COLVARS_OK) { + cvm::error("Error running scripted colvar"); + return COLVARS_OK; + } + +#ifdef LEPTON + } else if (is_enabled(f_cv_custom_function)) { + + size_t l = 0; // index in the vector of variable references + + for (size_t i = 0; i < x.size(); i++) { + // Fill Lepton evaluator variables with CVC values, serialized into scalars + for (size_t j = 0; j < cvcs.size(); j++) { + for (size_t k = 0; k < cvcs[j]->value().size(); k++) { + *(value_eval_var_refs[l++]) = cvcs[j]->value()[k]; + } + } + x[i] = value_evaluators[i]->evaluate(); + } +#endif + + } else if (x.type() == colvarvalue::type_scalar) { + // polynomial combination allowed + for (size_t i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + x += (cvcs[i])->sup_coeff * + ( ((cvcs[i])->sup_np != 1) ? + cvm::integer_power((cvcs[i])->value().real_value, (cvcs[i])->sup_np) : + (cvcs[i])->value().real_value ); + } + } else { + for (size_t i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + x += (cvcs[i])->sup_coeff * (cvcs[i])->value(); + } + } + + if (cvm::debug()) + cvm::log("Colvar \""+this->name+"\" has value "+ + cvm::to_str(x, cvm::cv_width, cvm::cv_prec)+".\n"); + + if (after_restart) { + if (cvm::proxy->simulation_running()) { + cvm::real const jump2 = dist2(x, x_restart) / (width*width); + if (jump2 > 0.25) { + cvm::error("Error: the calculated value of colvar \""+name+ + "\":\n"+cvm::to_str(x)+"\n differs greatly from the value " + "last read from the state file:\n"+cvm::to_str(x_restart)+ + "\nPossible causes are changes in configuration, " + "wrong state file, or how PBC wrapping is handled.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + } + + return COLVARS_OK; +} + + +int colvar::calc_cvc_gradients(int first_cvc, size_t num_cvcs) +{ + size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); + size_t i, cvc_count; + + if (cvm::debug()) + cvm::log("Calculating gradients of colvar \""+this->name+"\".\n"); + + // calculate the gradients of each component + cvm::increase_depth(); + for (i = first_cvc, cvc_count = 0; + (i < cvcs.size()) && (cvc_count < cvc_max_count); + i++) { + if (!cvcs[i]->is_enabled()) continue; + cvc_count++; + + if ((cvcs[i])->is_enabled(f_cvc_gradient)) { + (cvcs[i])->calc_gradients(); + // if requested, propagate (via chain rule) the gradients above + // to the atoms used to define the roto-translation + (cvcs[i])->calc_fit_gradients(); + if ((cvcs[i])->is_enabled(f_cvc_debug_gradient)) + (cvcs[i])->debug_gradients(); + } + + if (cvm::debug()) + cvm::log("Done calculating gradients of colvar \""+this->name+"\".\n"); + } + + cvm::decrease_depth(); + + return COLVARS_OK; +} + + +int colvar::collect_cvc_gradients() +{ + size_t i; + if (is_enabled(f_cv_collect_gradient)) { + // Collect the atomic gradients inside colvar object + for (unsigned int a = 0; a < atomic_gradients.size(); a++) { + atomic_gradients[a].reset(); + } + for (i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + cvcs[i]->collect_gradients(atom_ids, atomic_gradients); + } + } + return COLVARS_OK; +} + + +int colvar::calc_cvc_total_force(int first_cvc, size_t num_cvcs) +{ + size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); + size_t i, cvc_count; + + if (is_enabled(f_cv_total_force_calc)) { + if (cvm::debug()) + cvm::log("Calculating total force of colvar \""+this->name+"\".\n"); + + cvm::increase_depth(); + + for (i = first_cvc, cvc_count = 0; + (i < cvcs.size()) && (cvc_count < cvc_max_count); + i++) { + if (!cvcs[i]->is_enabled()) continue; + cvc_count++; + (cvcs[i])->calc_force_invgrads(); + } + cvm::decrease_depth(); + + + if (cvm::debug()) + cvm::log("Done calculating total force of colvar \""+this->name+"\".\n"); + } + + return COLVARS_OK; +} + + +int colvar::collect_cvc_total_forces() +{ + if (is_enabled(f_cv_total_force_calc)) { + ft.reset(); + + if (cvm::step_relative() > 0) { + // get from the cvcs the total forces from the PREVIOUS step + for (size_t i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + if (cvm::debug()) + cvm::log("Colvar component no. "+cvm::to_str(i+1)+ + " within colvar \""+this->name+"\" has total force "+ + cvm::to_str((cvcs[i])->total_force(), + cvm::cv_width, cvm::cv_prec)+".\n"); + // linear combination is assumed + ft += (cvcs[i])->total_force() * (cvcs[i])->sup_coeff / active_cvc_square_norm; + } + } + + if (!(is_enabled(f_cv_hide_Jacobian) && is_enabled(f_cv_subtract_applied_force))) { + // add the Jacobian force to the total force, and don't apply any silent + // correction internally: biases such as colvarbias_abf will handle it + // If f_cv_hide_Jacobian is enabled, a force of -fj is present in ft due to the + // Jacobian-compensating force + ft += fj; + } + } + + return COLVARS_OK; +} + + +int colvar::calc_cvc_Jacobians(int first_cvc, size_t num_cvcs) +{ + size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); + + if (is_enabled(f_cv_Jacobian)) { + cvm::increase_depth(); + size_t i, cvc_count; + for (i = first_cvc, cvc_count = 0; + (i < cvcs.size()) && (cvc_count < cvc_max_count); + i++) { + if (!cvcs[i]->is_enabled()) continue; + cvc_count++; + (cvcs[i])->calc_Jacobian_derivative(); + } + cvm::decrease_depth(); + } + + return COLVARS_OK; +} + + +int colvar::collect_cvc_Jacobians() +{ + colvarproxy *proxy = cvm::main()->proxy; + if (is_enabled(f_cv_Jacobian)) { + fj.reset(); + for (size_t i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + if (cvm::debug()) + cvm::log("Colvar component no. "+cvm::to_str(i+1)+ + " within colvar \""+this->name+"\" has Jacobian derivative"+ + cvm::to_str((cvcs[i])->Jacobian_derivative(), + cvm::cv_width, cvm::cv_prec)+".\n"); + // linear combination is assumed + fj += (cvcs[i])->Jacobian_derivative() * (cvcs[i])->sup_coeff / active_cvc_square_norm; + } + fj *= proxy->boltzmann() * proxy->target_temperature(); + } + + return COLVARS_OK; +} + + +int colvar::calc_colvar_properties() +{ + if (is_enabled(f_cv_fdiff_velocity)) { + // calculate the velocity by finite differences + if (cvm::step_relative() == 0) { + x_old = x; + v_fdiff.reset(); // Do not pretend we know anything about the actual velocity + // eg. upon restarting. That would require saving v_fdiff or x_old to the state file + } else { + v_fdiff = fdiff_velocity(x_old, x); + v_reported = v_fdiff; + } + } + + if (is_enabled(f_cv_extended_Lagrangian)) { + // initialize the restraint center in the first step to the value + // just calculated from the cvcs + // Do the same if no simulation is running (eg. VMD postprocessing) + if ((cvm::step_relative() == 0 && !after_restart) || x_ext.type() == colvarvalue::type_notset || !cvm::proxy->simulation_running()) { + x_ext = x; + if (is_enabled(f_cv_reflecting_lower_boundary) && x_ext < lower_boundary) { + cvm::log("Warning: initializing extended coordinate to reflective lower boundary, as colvar value is below."); + x_ext = lower_boundary; + } + if (is_enabled(f_cv_reflecting_upper_boundary) && x_ext > upper_boundary) { + cvm::log("Warning: initializing extended coordinate to reflective upper boundary, as colvar value is above."); + x_ext = upper_boundary; + } + + v_ext.reset(); // (already 0; added for clarity) + } + + // Special case of a repeated timestep (eg. multiple NAMD "run" statements) + // revert values of the extended coordinate and velocity prior to latest integration + if (cvm::proxy->simulation_running() && cvm::step_relative() == prev_timestep) { + x_ext = prev_x_ext; + v_ext = prev_v_ext; + } + // report the restraint center as "value" + // These position and velocities come from integration at the _previous timestep_ in update_forces_energy() + // But we report values at the beginning of the timestep (value at t=0 on the first timestep) + x_reported = x_ext; + v_reported = v_ext; + // the "total force" with the extended Lagrangian is + // calculated in update_forces_energy() below + + } else { + + if (is_enabled(f_cv_subtract_applied_force)) { + // correct the total force only if it has been measured + // TODO add a specific test instead of relying on sq norm + if (ft.norm2() > 0.0) { + ft -= f_old; + } + } + + x_reported = x; + ft_reported = ft; + } + + // At the end of the first update after a restart, we can reset the flag + after_restart = false; + return COLVARS_OK; +} + + +cvm::real colvar::update_forces_energy() +{ + if (cvm::debug()) + cvm::log("Updating colvar \""+this->name+"\".\n"); + + // set to zero the applied force + f.type(value()); + f.reset(); + fr.reset(); + + // If we are not active at this timestep, that's all we have to do + // return with energy == zero + if (!is_enabled(f_cv_active)) return 0.; + + // add the biases' force, which at this point should already have + // been summed over each bias using this colvar + // fb is already multiplied by the relevant time step factor for each bias + f += fb; + + if (is_enabled(f_cv_Jacobian)) { + // the instantaneous Jacobian force was not included in the reported total force; + // instead, it is subtracted from the applied force (silent Jacobian correction) + // This requires the Jacobian term for the *current* timestep + // Need to scale it for impulse MTS + if (is_enabled(f_cv_hide_Jacobian)) + f -= fj * cvm::real(time_step_factor); + } + + // At this point f is the force f from external biases that will be applied to the + // extended variable if there is one + if (is_enabled(f_cv_extended_Lagrangian) && cvm::proxy->simulation_running()) { + update_extended_Lagrangian(); + } + + if (!is_enabled(f_cv_external)) { + // Now adding the force on the actual colvar (for those biases that + // bypass the extended Lagrangian mass) + f += fb_actual; + } + + if (cvm::debug()) + cvm::log("Done updating colvar \""+this->name+"\".\n"); + return (potential_energy + kinetic_energy); +} + + +void colvar::update_extended_Lagrangian() +{ + if (cvm::debug()) { + cvm::log("Updating extended-Lagrangian degree of freedom.\n"); + } + + if (prev_timestep > -1L) { + // Keep track of slow timestep to integrate MTS colvars + // the colvar checks the interval after waking up twice + cvm::step_number n_timesteps = cvm::step_relative() - prev_timestep; + if (n_timesteps != 0 && n_timesteps != time_step_factor) { + cvm::error("Error: extended-Lagrangian " + description + " has timeStepFactor " + + cvm::to_str(time_step_factor) + ", but was activated after " + cvm::to_str(n_timesteps) + + " steps at timestep " + cvm::to_str(cvm::step_absolute()) + " (relative step: " + + cvm::to_str(cvm::step_relative()) + ").\n" + + "Make sure that this colvar is requested by biases at multiples of timeStepFactor.\n"); + return; + } + } + + // Integrate with slow timestep (if time_step_factor != 1) + cvm::real dt = cvm::dt() * cvm::real(time_step_factor); + + colvarvalue f_ext(fr.type()); // force acting on the extended variable + f_ext.reset(); + + if (is_enabled(f_cv_external)) { + // There are no forces on the "actual colvar" bc there is no gradient wrt atomic coordinates + // So we apply this to the extended DOF + f += fb_actual; + } + + // fr: bias force on extended variable (without harmonic spring), for output in trajectory + fr = f; + + // External force has been scaled for an inner-timestep impulse (for the back-end integrator) + // here we scale it back because this integrator uses only the outer (long) timestep + f_ext = f / cvm::real(time_step_factor); + + colvarvalue f_system(fr.type()); // force exterted by the system on the extended DOF + + if (is_enabled(f_cv_external)) { + // Add "alchemical" force from external variable + f_system = cvcs[0]->total_force(); + // f is now irrelevant because we are not applying atomic forces in the simulation + // just driving the external variable lambda + } else { + // the total force is applied to the fictitious mass, while the + // atoms only feel the harmonic force + wall force + // f_ext: total force on extended variable (including harmonic spring) + // f: - initially, external biasing force + // - after this code block, colvar force to be applied to atomic coordinates + // ie. spring force (fb_actual will be added just below) + f_system = (-0.5 * ext_force_k) * this->dist2_lgrad(x_ext, x); + f = -1.0 * f_system; + // Coupling force will be applied to atomic coords impulse-style + // over an inner timestep of the back-end integrator + f *= cvm::real(time_step_factor); + } + f_ext += f_system; + + if (is_enabled(f_cv_subtract_applied_force)) { + // Report a "system" force without the biases on this colvar + // that is, just the spring force (or alchemical force) + ft_reported = f_system; + } else { + // The total force acting on the extended variable is f_ext + // This will be used in the next timestep + ft_reported = f_ext; + } + + // backup in case we need to revert this integration timestep + // if the same MD timestep is re-run + prev_x_ext = x_ext; + prev_v_ext = v_ext; + + // BAOA (GSD) integrator as formulated in https://doi.org/10.1021/acs.jctc.2c00585 + // starting from x_t, f_t, v_(t-1/2) + // Variation: the velocity step is split in two to estimate the kinetic energy at time t + // so this is more of a "BBAOA" scheme: a rearranged BAOAB where the second B is deferred + // to the next time step for implementation reasons (waiting for the force calculation) + + // [B] Eq. (10a) split into two half-steps + // would reduce to leapfrog when gamma = 0 if this was the reported velocity + v_ext += 0.5 * dt * f_ext / ext_mass; + + // Kinetic energy at t + kinetic_energy = 0.5 * ext_mass * v_ext * v_ext; + + // Potential energy at t + potential_energy = 0.5 * ext_force_k * this->dist2(x_ext, x); + + // Total energy will lag behind position by one timestep + // (current kinetic energy is not accessible before the next force calculation) + + v_ext += 0.5 * dt * f_ext / ext_mass; + // Final v_ext lags behind x_ext by half a timestep + + // [A] Half step in position (10b) + x_ext += dt * v_ext / 2.0; + + // [O] leap to v_(i+1/2) (10c) + if (is_enabled(f_cv_Langevin)) { + colvarvalue rnd(x); + rnd.set_random(); + // ext_sigma has been computed at init time according to (10c) + v_ext = cvm::exp(- 1.0 * dt * ext_gamma) * v_ext + ext_sigma * rnd / ext_mass; + } + // [A] Second half step in position (10d) + x_ext += dt * v_ext / 2.0; + + cvm::real delta = 0; // Length of overshoot past either reflecting boundary + if ((is_enabled(f_cv_reflecting_lower_boundary) && (delta = x_ext - lower_boundary) < 0) || + (is_enabled(f_cv_reflecting_upper_boundary) && (delta = x_ext - upper_boundary) > 0)) { + // Reflect arrival position + x_ext -= 2.0 * delta; + // Bounce happened on average at t+1/2 -> reflect velocity at t+1/2 + v_ext = -0.5 * (prev_v_ext + v_ext); + if ((is_enabled(f_cv_reflecting_lower_boundary) && (x_ext - lower_boundary) < 0.0) || + (is_enabled(f_cv_reflecting_upper_boundary) && (x_ext - upper_boundary) > 0.0)) { + cvm::error("Error: extended coordinate value " + cvm::to_str(x_ext) + " is still outside boundaries after reflection.\n"); + } + } + + x_ext.apply_constraints(); + this->wrap(x_ext); + + if (is_enabled(f_cv_external)) { + // Colvar value is constrained to the extended value + x = x_ext; + cvcs[0]->set_value(x_ext); + } +} + + +int colvar::end_of_step() +{ + if (cvm::debug()) + cvm::log("End of step for colvar \""+this->name+"\".\n"); + + if (is_enabled(f_cv_fdiff_velocity)) { + x_old = x; + } + + if (is_enabled(f_cv_subtract_applied_force)) { + f_old = f; + } + + prev_timestep = cvm::step_relative(); + + return COLVARS_OK; +} + + +void colvar::communicate_forces() +{ + size_t i; + if (cvm::debug()) { + cvm::log("Communicating forces from colvar \""+this->name+"\".\n"); + cvm::log("Force to be applied: " + cvm::to_str(f) + "\n"); + } + + if (is_enabled(f_cv_scripted)) { + std::vector > func_grads; + func_grads.reserve(cvcs.size()); + for (i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + func_grads.push_back(cvm::matrix2d (x.size(), + cvcs[i]->value().size())); + } + int res = cvm::proxy->run_colvar_gradient_callback(scripted_function, sorted_cvc_values, func_grads); + + if (res != COLVARS_OK) { + if (res == COLVARS_NOT_IMPLEMENTED) { + cvm::error("Colvar gradient scripts are not implemented.", COLVARS_NOT_IMPLEMENTED); + } else { + cvm::error("Error running colvar gradient script"); + } + return; + } + + int grad_index = 0; // index in the scripted gradients, to account for some components being disabled + for (i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + // cvc force is colvar force times colvar/cvc Jacobian + // (vector-matrix product) + (cvcs[i])->apply_force(colvarvalue(f.as_vector() * func_grads[grad_index++], + cvcs[i]->value().type())); + } + +#ifdef LEPTON + } else if (is_enabled(f_cv_custom_function)) { + + size_t r = 0; // index in the vector of variable references + size_t e = 0; // index of the gradient evaluator + + for (i = 0; i < cvcs.size(); i++) { // gradient with respect to cvc i + cvm::matrix2d jacobian (x.size(), cvcs[i]->value().size()); + for (size_t j = 0; j < cvcs[i]->value().size(); j++) { // j-th element + for (size_t c = 0; c < x.size(); c++) { // derivative of scalar element c of the colvarvalue + + // Feed cvc values to the evaluator + for (size_t k = 0; k < cvcs.size(); k++) { // + for (size_t l = 0; l < cvcs[k]->value().size(); l++) { + *(grad_eval_var_refs[r++]) = cvcs[k]->value()[l]; + } + } + jacobian[c][j] = gradient_evaluators[e++]->evaluate(); + } + } + // cvc force is colvar force times colvar/cvc Jacobian + // (vector-matrix product) + (cvcs[i])->apply_force(colvarvalue(f.as_vector() * jacobian, + cvcs[i]->value().type())); + } +#endif + + } else if (x.type() == colvarvalue::type_scalar) { + + for (i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + (cvcs[i])->apply_force(f * (cvcs[i])->sup_coeff * + cvm::real((cvcs[i])->sup_np) * + (cvm::integer_power((cvcs[i])->value().real_value, + (cvcs[i])->sup_np-1)) ); + } + + } else { + + for (i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + (cvcs[i])->apply_force(f * (cvcs[i])->sup_coeff); + } + } + + if (cvm::debug()) + cvm::log("Done communicating forces from colvar \""+this->name+"\".\n"); +} + + +int colvar::set_cvc_flags(std::vector const &flags) +{ + if (flags.size() != cvcs.size()) { + cvm::error("ERROR: Wrong number of CVC flags provided."); + return COLVARS_ERROR; + } + // We cannot enable or disable cvcs in the middle of a timestep or colvar evaluation sequence + // so we store the flags that will be enforced at the next call to calc() + cvc_flags = flags; + return COLVARS_OK; +} + + +void colvar::update_active_cvc_square_norm() +{ + active_cvc_square_norm = 0.0; + for (size_t i = 0; i < cvcs.size(); i++) { + if (cvcs[i]->is_enabled()) { + active_cvc_square_norm += cvcs[i]->sup_coeff * cvcs[i]->sup_coeff; + } + } +} + + +int colvar::update_cvc_flags() +{ + // Update the enabled/disabled status of cvcs if necessary + if (cvc_flags.size()) { + n_active_cvcs = 0; + for (size_t i = 0; i < cvcs.size(); i++) { + cvcs[i]->set_enabled(f_cvc_active, cvc_flags[i]); + if (cvcs[i]->is_enabled()) { + n_active_cvcs++; + } + } + if (!n_active_cvcs) { + cvm::error("ERROR: All CVCs are disabled for colvar " + this->name +"\n"); + return COLVARS_ERROR; + } + cvc_flags.clear(); + + update_active_cvc_square_norm(); + } + + return COLVARS_OK; +} + + +int colvar::update_cvc_config(std::vector const &confs) +{ + cvm::log("Updating configuration for colvar \""+name+"\"\n"); + + if (confs.size() != cvcs.size()) { + return cvm::error("Error: Wrong number of CVC config strings. " + "For those CVCs that are not being changed, try passing " + "an empty string.", COLVARS_INPUT_ERROR); + } + + int error_code = COLVARS_OK; + int num_changes = 0; + for (size_t i = 0; i < cvcs.size(); i++) { + if (confs[i].size()) { + std::string conf(confs[i]); + cvm::increase_depth(); + error_code |= cvcs[i]->colvar::cvc::init(conf); + error_code |= cvcs[i]->check_keywords(conf, + cvcs[i]->config_key.c_str()); + cvm::decrease_depth(); + num_changes++; + } + } + + if (num_changes == 0) { + cvm::log("Warning: no changes were applied through modifycvcs; " + "please check that its argument is a list of strings.\n"); + } + + update_active_cvc_square_norm(); + + return error_code; +} + + +int colvar::cvc_param_exists(std::string const ¶m_name) +{ + if (is_enabled(f_cv_single_cvc)) { + return cvcs[0]->param_exists(param_name); + } + return cvm::error("Error: calling colvar::cvc_param_exists() for a variable " + "with more than one component.\n", COLVARS_NOT_IMPLEMENTED); +} + + +cvm::real colvar::get_cvc_param(std::string const ¶m_name) +{ + if (is_enabled(f_cv_single_cvc)) { + return cvcs[0]->get_param(param_name); + } + cvm::error("Error: calling colvar::get_cvc_param() for a variable " + "with more than one component.\n", COLVARS_NOT_IMPLEMENTED); + return 0.0; +} + + +void const *colvar::get_cvc_param_ptr(std::string const ¶m_name) +{ + if (is_enabled(f_cv_single_cvc)) { + return cvcs[0]->get_param_ptr(param_name); + } + cvm::error("Error: calling colvar::get_cvc_param() for a variable " + "with more than one component.\n", COLVARS_NOT_IMPLEMENTED); + return NULL; +} + + +colvarvalue const *colvar::get_cvc_param_grad(std::string const ¶m_name) +{ + if (is_enabled(f_cv_single_cvc)) { + return cvcs[0]->get_param_grad(param_name); + } + cvm::error("Error: calling colvar::get_cvc_param_grad() for a variable " + "with more than one component.\n", COLVARS_NOT_IMPLEMENTED); + return NULL; +} + + +int colvar::set_cvc_param(std::string const ¶m_name, void const *new_value) +{ + if (is_enabled(f_cv_single_cvc)) { + return cvcs[0]->set_param(param_name, new_value); + } + return cvm::error("Error: calling colvar::set_cvc_param() for a variable " + "with more than one component.\n", COLVARS_NOT_IMPLEMENTED); +} + + +// ******************** METRIC FUNCTIONS ******************** +// Use the metrics defined by \link colvar::cvc \endlink objects + + +bool colvar::periodic_boundaries(colvarvalue const &lb, colvarvalue const &ub) const +{ + if (period > 0.0) { + if ( ((cvm::sqrt(this->dist2(lb, ub))) / this->width) + < 1.0E-10 ) { + return true; + } + } + + return false; +} + +bool colvar::periodic_boundaries() const +{ + if ( (!is_enabled(f_cv_lower_boundary)) || (!is_enabled(f_cv_upper_boundary)) ) { + // Return false if answer is unknown at this time + return false; + } + + return periodic_boundaries(lower_boundary, upper_boundary); +} + + +cvm::real colvar::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + if ( is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function) ) { + if (is_enabled(f_cv_periodic) && is_enabled(f_cv_scalar)) { + cvm::real diff = x1.real_value - x2.real_value; + const cvm::real period_lower_boundary = wrap_center - period / 2.0; + const cvm::real period_upper_boundary = wrap_center + period / 2.0; + diff = (diff < period_lower_boundary ? diff + period : ( diff > period_upper_boundary ? diff - period : diff)); + return diff * diff; + } + } + if (is_enabled(f_cv_homogeneous)) { + return (cvcs[0])->dist2(x1, x2); + } else { + return x1.dist2(x2); + } +} + +colvarvalue colvar::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + if ( is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function) ) { + if (is_enabled(f_cv_periodic) && is_enabled(f_cv_scalar)) { + cvm::real diff = x1.real_value - x2.real_value; + const cvm::real period_lower_boundary = wrap_center - period / 2.0; + const cvm::real period_upper_boundary = wrap_center + period / 2.0; + diff = (diff < period_lower_boundary ? diff + period : ( diff > period_upper_boundary ? diff - period : diff)); + return 2.0 * diff; + } + } + if (is_enabled(f_cv_homogeneous)) { + return (cvcs[0])->dist2_lgrad(x1, x2); + } else { + return x1.dist2_grad(x2); + } +} + +colvarvalue colvar::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + if ( is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function) ) { + if (is_enabled(f_cv_periodic) && is_enabled(f_cv_scalar)) { + cvm::real diff = x1.real_value - x2.real_value; + const cvm::real period_lower_boundary = wrap_center - period / 2.0; + const cvm::real period_upper_boundary = wrap_center + period / 2.0; + diff = (diff < period_lower_boundary ? diff + period : ( diff > period_upper_boundary ? diff - period : diff)); + return (-2.0) * diff; + } + } + if (is_enabled(f_cv_homogeneous)) { + return (cvcs[0])->dist2_rgrad(x1, x2); + } else { + return x2.dist2_grad(x1); + } +} + + +void colvar::wrap(colvarvalue &x_unwrapped) const +{ + if (!is_enabled(f_cv_periodic)) { + return; + } + + if ( is_enabled(f_cv_scripted) || is_enabled(f_cv_custom_function) ) { + // Scripted functions do their own wrapping, as cvcs might not be periodic + cvm::real shift = cvm::floor((x_unwrapped.real_value - wrap_center) / + period + 0.5); + x_unwrapped.real_value -= shift * period; + } else { + cvcs[0]->wrap(x_unwrapped); + } +} + + +// ******************** INPUT FUNCTIONS ******************** + +std::istream & colvar::read_state(std::istream &is) +{ + auto const start_pos = is.tellg(); + + std::string conf; + if ( !(is >> colvarparse::read_block("colvar", &conf)) || + (check_matching_state(conf) != COLVARS_OK) ) { + // this is not a colvar block + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + if (!matching_state) { + // No errors reading, but this state is not for this colvar; rewind + is.seekg(start_pos); + return is; + } + + if (set_state_params(conf) != COLVARS_OK) { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + + return is; +} + + +int colvar::check_matching_state(std::string const &conf) +{ + std::string check_name = ""; + get_keyval(conf, "name", check_name, std::string(""), colvarparse::parse_silent); + + if (check_name.size() == 0) { + return cvm::error("Error: Collective variable in the " + "state file without any identifier.\n", COLVARS_INPUT_ERROR); + } + + if (check_name != name) { + if (cvm::debug()) { + cvm::log("Ignoring state of colvar \""+check_name+ + "\": this colvar is named \""+name+"\".\n"); + } + matching_state = false; + } else { + matching_state = true; + } + + return COLVARS_OK; +} + + +int colvar::set_state_params(std::string const &conf) +{ + int error_code = COLVARS_OK; + if ( !(get_keyval(conf, "x", x, x, colvarparse::parse_silent)) ) { + error_code |= cvm::error("Error: restart file does not contain " + "the value of the colvar \""+ + name+"\" .\n", COLVARS_INPUT_ERROR); + } else { + cvm::log("Restarting collective variable \""+name+"\" from value: "+ + cvm::to_str(x)+"\n"); + x_restart = x; + after_restart = true; + } + + if (is_enabled(f_cv_extended_Lagrangian)) { + if ( !(get_keyval(conf, "extended_x", x_ext, + colvarvalue(x.type()), colvarparse::parse_silent)) || + !(get_keyval(conf, "extended_v", v_ext, + colvarvalue(x.type()), colvarparse::parse_silent)) ) { + error_code |= cvm::error("Error: restart file does not contain " + "\"extended_x\" or \"extended_v\" for the colvar \""+ + name+"\", but you requested \"extendedLagrangian\".\n", + COLVARS_INPUT_ERROR); + } + x_reported = x_ext; + } else { + x_reported = x; + } + + if (is_enabled(f_cv_output_velocity)) { + + if ( !(get_keyval(conf, "v", v_fdiff, + colvarvalue(x.type()), colvarparse::parse_silent)) ) { + error_code |= cvm::error("Error: restart file does not contain " + "the velocity for the colvar \""+ + name+"\", but you requested \"outputVelocity\".\n", + COLVARS_INPUT_ERROR); + } + + if (is_enabled(f_cv_extended_Lagrangian)) { + v_reported = v_ext; + } else { + v_reported = v_fdiff; + } + } + + return error_code; +} + + +cvm::memory_stream &colvar::read_state(cvm::memory_stream &is) +{ + auto const start_pos = is.tellg(); + std::string key, data; + if (is >> key) { + if (key == "colvar") { + // Read a formatted config string, then read the state parameters from it + if (is >> data) { + if (set_state_params(data) == COLVARS_OK) { + return is; + } + } + } + } + + auto const error_pos = is.tellg(); + + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + + std::string error_msg("Error: in reading state data for colvar \"" + name + " at position " + + cvm::to_str(error_pos) + " in unformatted stream.\n"); + if (key.size() && key != "colvar") { + error_msg += "; the keyword read was \"" + key + "\", but \"colvar\" was expected"; + } + if (data.size()) { + error_msg += "; the configuration string read was not recognized"; + } + error_msg += ".\n"; + cvm::error(error_msg, COLVARS_INPUT_ERROR); + return is; +} + + +std::istream & colvar::read_traj(std::istream &is) +{ + std::streampos const start_pos = is.tellg(); + + if (is_enabled(f_cv_output_value)) { + + if (!(is >> x)) { + cvm::log("Error: in reading the value of colvar \""+ + this->name+"\" from trajectory.\n"); + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + if (is_enabled(f_cv_extended_Lagrangian)) { + is >> x_ext; + x_reported = x_ext; + } else { + x_reported = x; + } + } + + if (is_enabled(f_cv_output_velocity)) { + + is >> v_fdiff; + + if (is_enabled(f_cv_extended_Lagrangian)) { + is >> v_ext; + v_reported = v_ext; + } else { + v_reported = v_fdiff; + } + } + + if (is_enabled(f_cv_output_total_force)) { + is >> ft; + ft_reported = ft; + } + + if (is_enabled(f_cv_output_applied_force)) { + is >> f; + } + + return is; +} + + +// ******************** OUTPUT FUNCTIONS ******************** + +std::ostream & colvar::write_state(std::ostream &os) const +{ + os << "colvar {\n" << get_state_params() << "}\n\n"; + + if (runave_outfile.size() > 0) { + cvm::main()->proxy->flush_output_stream(runave_outfile); + } + + return os; +} + + +std::string const colvar::get_state_params() const +{ + std::ostringstream os; + + os << " name " << name << "\n" + << " x " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << x << "\n"; + + if (is_enabled(f_cv_output_velocity)) { + os << " v " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << v_reported << "\n"; + } + + if (is_enabled(f_cv_extended_Lagrangian)) { + os << " extended_x " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << x_reported << "\n" + << " extended_v " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << v_reported << "\n"; + } + + return os.str(); +} + + +cvm::memory_stream & colvar::write_state(cvm::memory_stream &os) const +{ + os << std::string("colvar") << get_state_params(); + + if (runave_outfile.size() > 0) { + cvm::main()->proxy->flush_output_stream(runave_outfile); + } + + return os; +} + + +std::ostream & colvar::write_traj_label(std::ostream & os) +{ + size_t const this_cv_width = x.output_width(cvm::cv_width); + + os << " "; + + if (is_enabled(f_cv_output_value)) { + + os << " " + << cvm::wrap_string(this->name, this_cv_width); + + if (is_enabled(f_cv_extended_Lagrangian) && !is_enabled(f_cv_external)) { + // extended DOF + os << " r_" + << cvm::wrap_string(this->name, this_cv_width-2); + } + } + + if (is_enabled(f_cv_output_velocity)) { + + os << " v_" + << cvm::wrap_string(this->name, this_cv_width-2); + + if (is_enabled(f_cv_extended_Lagrangian) && !is_enabled(f_cv_external)) { + // extended DOF + os << " vr_" + << cvm::wrap_string(this->name, this_cv_width-3); + } + } + + if (is_enabled(f_cv_output_energy)) { + os << " Ep_" + << cvm::wrap_string(this->name, this_cv_width-3) + << " Ek_" + << cvm::wrap_string(this->name, this_cv_width-3); + } + + if (is_enabled(f_cv_output_total_force)) { + os << " ft_" + << cvm::wrap_string(this->name, this_cv_width-3); + } + + if (is_enabled(f_cv_output_applied_force)) { + os << " fa_" + << cvm::wrap_string(this->name, this_cv_width-3); + } + + return os; +} + + +std::ostream & colvar::write_traj(std::ostream &os) +{ + os << " "; + if (is_enabled(f_cv_output_value)) { + + if (is_enabled(f_cv_extended_Lagrangian) && !is_enabled(f_cv_external)) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << x; + } + + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << x_reported; + } + + if (is_enabled(f_cv_output_velocity)) { + + if (is_enabled(f_cv_extended_Lagrangian) && !is_enabled(f_cv_external)) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << v_fdiff; + } + + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << v_reported; + } + + if (is_enabled(f_cv_output_energy)) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << potential_energy + << " " + << kinetic_energy; + } + + + if (is_enabled(f_cv_output_total_force)) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << ft_reported; + } + + if (is_enabled(f_cv_output_applied_force)) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << applied_force(); + } + + return os; +} + + +int colvar::write_output_files() +{ + int error_code = COLVARS_OK; + + if (is_enabled(f_cv_corrfunc)) { + if (acf.size()) { + if (acf_outfile.size() == 0) { + acf_outfile = std::string(cvm::output_prefix()+"."+this->name+ + ".corrfunc.dat"); + } + cvm::log("Writing correlation function to file \""+acf_outfile+"\".\n"); + cvm::backup_file(acf_outfile.c_str()); + std::ostream &acf_os = cvm::proxy->output_stream(acf_outfile, + "colvar ACF file"); + if (!acf_os) { + error_code |= COLVARS_FILE_ERROR; + } else { + error_code |= write_acf(acf_os); + } + cvm::proxy->close_output_stream(acf_outfile); + } + } + + return error_code; +} + + + +// ******************** ANALYSIS FUNCTIONS ******************** + +int colvar::analyze() +{ + int error_code = COLVARS_OK; + + if (is_enabled(f_cv_runave)) { + error_code |= calc_runave(); + } + + if (is_enabled(f_cv_corrfunc)) { + error_code |= calc_acf(); + } + + return error_code; +} + + +inline void history_add_value(size_t const &history_length, + std::list &history, + colvarvalue const &new_value) +{ + history.push_front(new_value); + if (history.size() > history_length) + history.pop_back(); +} + + +inline void history_incr(std::list< std::list > &history, + std::list< std::list >::iterator &history_p) +{ + if ((++history_p) == history.end()) + history_p = history.begin(); +} + + +int colvar::calc_acf() +{ + // using here an acf_stride-long list of vectors for either + // coordinates (acf_x_history) or velocities (acf_v_history); each vector can + // contain up to acf_length values, which are contiguous in memory + // representation but separated by acf_stride in the time series; + // the pointer to each vector is changed at every step + + colvar const *cfcv = cvm::colvar_by_name(acf_colvar_name); + if (cfcv == NULL) { + return cvm::error("Error: collective variable \""+acf_colvar_name+ + "\" is not defined at this time.\n", COLVARS_INPUT_ERROR); + } + + if (acf_x_history.empty() && acf_v_history.empty()) { + + // first-step operations + + if (colvarvalue::check_types(cfcv->value(), value())) { + cvm::error("Error: correlation function between \""+cfcv->name+ + "\" and \""+this->name+"\" cannot be calculated, " + "because their value types are different.\n", + COLVARS_INPUT_ERROR); + } + acf_nframes = 0; + + cvm::log("Colvar \""+this->name+"\": initializing correlation function " + "calculation.\n"); + + if (acf.size() < acf_length+1) + acf.resize(acf_length+1, 0.0); + + size_t i; + switch (acf_type) { + + case acf_vel: + // allocate space for the velocities history + for (i = 0; i < acf_stride; i++) { + acf_v_history.push_back(std::list()); + } + acf_v_history_p = acf_v_history.begin(); + break; + + case acf_coor: + case acf_p2coor: + // allocate space for the coordinates history + for (i = 0; i < acf_stride; i++) { + acf_x_history.push_back(std::list()); + } + acf_x_history_p = acf_x_history.begin(); + break; + + case acf_notset: + default: + break; + } + + } else if (cvm::step_relative() > prev_timestep) { + + switch (acf_type) { + + case acf_vel: + + calc_vel_acf((*acf_v_history_p), cfcv->velocity()); + history_add_value(acf_length+acf_offset, *acf_v_history_p, + cfcv->velocity()); + history_incr(acf_v_history, acf_v_history_p); + break; + + case acf_coor: + + calc_coor_acf((*acf_x_history_p), cfcv->value()); + history_add_value(acf_length+acf_offset, *acf_x_history_p, + cfcv->value()); + history_incr(acf_x_history, acf_x_history_p); + break; + + case acf_p2coor: + + calc_p2coor_acf((*acf_x_history_p), cfcv->value()); + history_add_value(acf_length+acf_offset, *acf_x_history_p, + cfcv->value()); + history_incr(acf_x_history, acf_x_history_p); + break; + + case acf_notset: + default: + break; + } + } + + return COLVARS_OK; +} + + +void colvar::calc_vel_acf(std::list &v_list, + colvarvalue const &v) +{ + // loop over stored velocities and add to the ACF, but only if the + // length is sufficient to hold an entire row of ACF values + if (v_list.size() >= acf_length+acf_offset) { + std::list::iterator vs_i = v_list.begin(); + std::vector::iterator acf_i = acf.begin(); + + for (size_t i = 0; i < acf_offset; i++) + ++vs_i; + + // current vel with itself + *(acf_i) += v.norm2(); + ++acf_i; + + // inner products of previous velocities with current (acf_i and + // vs_i are updated) + colvarvalue::inner_opt(v, vs_i, v_list.end(), acf_i); + + acf_nframes++; + } +} + + +void colvar::calc_coor_acf(std::list &x_list, + colvarvalue const &x_now) +{ + // same as above but for coordinates + if (x_list.size() >= acf_length+acf_offset) { + std::list::iterator xs_i = x_list.begin(); + std::vector::iterator acf_i = acf.begin(); + + for (size_t i = 0; i < acf_offset; i++) + ++xs_i; + + *(acf_i++) += x.norm2(); + + colvarvalue::inner_opt(x_now, xs_i, x_list.end(), acf_i); + + acf_nframes++; + } +} + + +void colvar::calc_p2coor_acf(std::list &x_list, + colvarvalue const &x_now) +{ + // same as above but with second order Legendre polynomial instead + // of just the scalar product + if (x_list.size() >= acf_length+acf_offset) { + std::list::iterator xs_i = x_list.begin(); + std::vector::iterator acf_i = acf.begin(); + + for (size_t i = 0; i < acf_offset; i++) + ++xs_i; + + // value of P2(0) = 1 + *(acf_i++) += 1.0; + + colvarvalue::p2leg_opt(x_now, xs_i, x_list.end(), acf_i); + + acf_nframes++; + } +} + + +int colvar::write_acf(std::ostream &os) +{ + if (!acf_nframes) { + return COLVARS_OK; + } + + os.setf(std::ios::scientific, std::ios::floatfield); + os << "# "; + switch (acf_type) { + case acf_vel: + os << "Velocity"; + break; + case acf_coor: + os << "Coordinate"; + break; + case acf_p2coor: + os << "Coordinate (2nd Legendre poly)"; + break; + case acf_notset: + default: + break; + } + + if (acf_colvar_name == name) { + os << " autocorrelation function for variable \"" + << this->name << "\"\n"; + } else { + os << " correlation function between variables \"" // + << this->name << "\" and \"" << acf_colvar_name << "\"\n"; + } + + os << "# Number of samples = "; + if (acf_normalize) { + os << (acf_nframes-1) << " (one DoF is used for normalization)\n"; + } else { + os << acf_nframes << "\n"; + } + + os << "# " << cvm::wrap_string("step", cvm::it_width-2) << " " + << cvm::wrap_string("corrfunc(step)", cvm::cv_width) << "\n"; + + cvm::real const acf_norm = acf.front() / cvm::real(acf_nframes); + + std::vector::iterator acf_i; + size_t it = acf_offset; + for (acf_i = acf.begin(); acf_i != acf.end(); ++acf_i) { + os << std::setw(cvm::it_width) << acf_stride * (it++) << " " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << ( acf_normalize ? + (*acf_i)/(acf_norm * cvm::real(acf_nframes)) : + (*acf_i)/(cvm::real(acf_nframes)) ) << "\n"; + } + + return os.good() ? COLVARS_OK : COLVARS_FILE_ERROR; +} + + +int colvar::calc_runave() +{ + int error_code = COLVARS_OK; + colvarproxy *proxy = cvm::main()->proxy; + + if (x_history.empty()) { + + runave.type(value().type()); + runave.reset(); + + // first-step operationsf + + if (cvm::debug()) + cvm::log("Colvar \""+this->name+ + "\": initializing running average calculation.\n"); + + acf_nframes = 0; + + x_history.push_back(std::list()); + x_history_p = x_history.begin(); + + } else { + + if ( (cvm::step_relative() % runave_stride) == 0 && + (cvm::step_relative() > prev_timestep) ) { + + if ((*x_history_p).size() >= runave_length-1) { + + if (runave_outfile.size() == 0) { + runave_outfile = std::string(cvm::output_prefix()+"."+ + this->name+".runave.traj"); + } + + if (! proxy->output_stream_exists(runave_outfile)) { + size_t const this_cv_width = x.output_width(cvm::cv_width); + std::ostream &runave_os = proxy->output_stream(runave_outfile, + "colvar running average"); + runave_os.setf(std::ios::scientific, std::ios::floatfield); + runave_os << "# " << cvm::wrap_string("step", cvm::it_width-2) + << " " + << cvm::wrap_string("running average", this_cv_width) + << " " + << cvm::wrap_string("running stddev", this_cv_width) + << "\n"; + } + + runave = x; + std::list::iterator xs_i; + for (xs_i = (*x_history_p).begin(); + xs_i != (*x_history_p).end(); ++xs_i) { + runave += (*xs_i); + } + runave *= 1.0 / cvm::real(runave_length); + runave.apply_constraints(); + + runave_variance = 0.0; + runave_variance += this->dist2(x, runave); + for (xs_i = (*x_history_p).begin(); + xs_i != (*x_history_p).end(); ++xs_i) { + runave_variance += this->dist2(x, (*xs_i)); + } + runave_variance *= 1.0 / cvm::real(runave_length-1); + + if (runave_outfile.size() > 0) { + std::ostream &runave_os = + proxy->output_stream(runave_outfile, "running average output file"); + runave_os << std::setw(cvm::it_width) << cvm::step_relative() << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) << runave << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << cvm::sqrt(runave_variance) << "\n"; + } + } + + history_add_value(runave_length, *x_history_p, x); + } + } + + return error_code; +} + +// Static members + +std::vector colvar::cv_features; diff --git a/src/external/colvars/colvar.h b/src/external/colvars/colvar.h new file mode 100644 index 00000000000..4dee696ce9f --- /dev/null +++ b/src/external/colvars/colvar.h @@ -0,0 +1,779 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVAR_H +#define COLVAR_H + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvardeps.h" + +#ifdef LEPTON +#include "Lepton.h" // for runtime custom expressions +#endif + +/// \brief A collective variable (main class); to be defined, it needs +/// at least one object of a derived class of colvar::cvc; it +/// calculates and returns a \link colvarvalue \endlink object +/// +/// This class parses the configuration, defines the behaviour and +/// stores the value (\link colvar::x \endlink) and all related data +/// of a collective variable. How the value is calculated is defined +/// in \link colvar::cvc \endlink and its derived classes. The +/// \link colvar \endlink object contains pointers to multiple \link +/// colvar::cvc \endlink derived objects, which can be combined +/// together into one collective variable. This makes possible to +/// implement new collective variables at runtime based on the +/// existing ones. Currently, this possibility is limited to a +/// polynomial, using the coefficients cvc::sup_coeff and the +/// exponents cvc::sup_np. In case of non-scalar variables, +/// only exponents equal to 1 are accepted. +/// +/// Please note that most of its members are \link colvarvalue +/// \endlink objects, i.e. they can handle different data types +/// together, and must all be set to the same type of colvar::value() +/// before using them together in assignments or other operations; this is usually done +/// automatically in the constructor. If you add a new member of +/// \link colvarvalue \endlink type, you should also add its +/// initialization line in the \link colvar \endlink constructor. + +class colvar : public colvarparse, public colvardeps { + +public: + + /// Name + std::string name; + + /// \brief Current value (previously set by calc() or by read_traj()) + colvarvalue const & value() const; + + /// \brief Current actual value (not extended DOF) + colvarvalue const & actual_value() const; + + /// \brief Current running average (if calculated as set by analysis flag) + colvarvalue const & run_ave() const; + + /// \brief Force constant of the spring + cvm::real const & force_constant() const; + + /// \brief Current velocity (previously set by calc() or by read_traj()) + colvarvalue const & velocity() const; + + /// \brief Current total force (previously obtained from calc() or + /// read_traj()). Note: this is calculated using the atomic forces + /// from the last simulation step. + /// + /// Total atom forces are read from the MD program, the total force + /// acting on the collective variable is calculated summing those + /// from all colvar components, the bias and walls forces are + /// subtracted. + colvarvalue const & total_force() const; + + /// \brief Typical fluctuation amplitude for this collective + /// variable (e.g. local width of a free energy basin) + /// + /// In metadynamics calculations, \link colvarbias_meta \endlink, + /// this value is used to calculate the width of a gaussian. In ABF + /// calculations, \link colvarbias_abf \endlink, it is used to + /// calculate the grid spacing in the direction of this collective + /// variable. + cvm::real width = 1.0; + + /// \brief Implementation of the feature list for colvar + static std::vector cv_features; + + /// \brief Implementation of the feature list accessor for colvar + virtual const std::vector &features() const + { + return cv_features; + } + virtual std::vector &modify_features() + { + return cv_features; + } + static void delete_features() { + for (size_t i=0; i < cv_features.size(); i++) { + delete cv_features[i]; + } + cv_features.clear(); + } + + /// Implements possible actions to be carried out + /// when a given feature is enabled + /// This overloads the base function in colvardeps + void do_feature_side_effects(int id); + + /// List of biases that depend on this colvar + std::vector biases; + +protected: + + + /* + extended: + H = H_{0} + \sum_{i} 1/2*\lambda*(S_i(x(t))-s_i(t))^2 \\ + + \sum_{i} 1/2*m_i*(ds_i(t)/dt)^2 \\ + + \sum_{t' 0.0) ? (1.0/dt) : 1.0 ) * + 0.5 * dist2_lgrad(xnew, xold) ); + } + + /// Cached reported velocity + colvarvalue v_reported; + + // Options for extended_lagrangian + /// Restraint center + colvarvalue x_ext; + /// Previous value of the restraint center; + colvarvalue prev_x_ext; + /// Velocity of the restraint center + colvarvalue v_ext; + /// Previous velocity of the restraint center + colvarvalue prev_v_ext; + /// Mass of the restraint center + cvm::real ext_mass = 0.0; + /// Restraint force constant + cvm::real ext_force_k = 0.0; + /// Friction coefficient for Langevin extended dynamics + cvm::real ext_gamma = 0.0; + /// Amplitude of Gaussian white noise for Langevin extended dynamics + cvm::real ext_sigma = 0.0; + + /// \brief Applied force on extended DOF, for output (unscaled if using MTS) + colvarvalue fr; + + /// \brief Jacobian force, when Jacobian_force is enabled + colvarvalue fj; + + /// Cached reported total force + colvarvalue ft_reported; + +public: + + + /// \brief Bias force; reset_bias_force() should be called before + /// the biases are updated + colvarvalue fb; + + /// \brief Bias force to the actual value (only useful with extended Lagrangian) + colvarvalue fb_actual; + + /// \brief Total \em applied force; fr (if extended_lagrangian + /// is defined), fb (if biases are applied) and the walls' forces + /// (if defined) contribute to it + colvarvalue f; + + /// Applied force at the previous step (to be subtracted from total force if needed) + colvarvalue f_old; + + /// \brief Total force, as derived from the atomic trajectory; + /// should equal the system force plus \link f \endlink + colvarvalue ft; + + /// Period, if this variable is periodic + cvm::real period = 0.0; + + /// Center of wrapping, if this variable is periodic + cvm::real wrap_center = 0.0; + + /// \brief Expand the boundaries of multiples of width, to keep the + /// value always within range + bool expand_boundaries = false; + + /// \brief Location of the lower boundary + colvarvalue lower_boundary; + + /// \brief Location of the upper boundary + colvarvalue upper_boundary; + + /// \brief Is the interval defined by the two boundaries periodic? + bool periodic_boundaries() const; + + /// \brief Is the interval defined by the two boundaries periodic? + bool periodic_boundaries(colvarvalue const &lb, colvarvalue const &ub) const; + + + /// Constructor + colvar(); + + /// Main init function + int init(std::string const &conf); + + /// Parse the CVC configuration and allocate their data + int init_components(std::string const &conf); + + /// Parse parameters for custom function with Lepton + int init_custom_function(std::string const &conf); + + /// Init defaults for grid options + int init_grid_parameters(std::string const &conf); + + /// Init extended Lagrangian parameters + int init_extended_Lagrangian(std::string const &conf); + + /// Init output flags + int init_output_flags(std::string const &conf); + + /// \brief Initialize dependency tree + virtual int init_dependencies(); + +private: + + /// Parse the CVC configuration for all components of a certain type + template int init_components_type(std::string const & conf, + char const *def_desc, + char const *def_config_key); + + /// The names of all available components are registered in the global map + /// at first, and then the CVC configuration is parsed by this function + int init_components_type_from_global_map(const std::string& conf, + const char* def_config_key); + +public: + + /// Get ready for a run and re-initialize internal data if needed + void setup(); + + /// Destructor + ~colvar(); + + + /// \brief Calculate the colvar's value and related quantities + int calc(); + + /// Carry out operations needed before next step is run + int end_of_step(); + + /// \brief Calculate a subset of the colvar components (CVCs) currently active + /// (default: all active CVCs) + /// Note: both arguments refer to the sect of *active* CVCs, not all CVCs + int calc_cvcs(int first = 0, size_t num_cvcs = 0); + + /// Ensure that the selected range of CVCs is consistent + int check_cvc_range(int first_cvc, size_t num_cvcs); + + /// \brief Calculate the values of the given subset of CVCs + int calc_cvc_values(int first, size_t num_cvcs); + /// \brief Same as \link colvar::calc_cvc_values \endlink but for gradients + int calc_cvc_gradients(int first, size_t num_cvcs); + /// \brief Same as \link colvar::calc_cvc_values \endlink but for total forces + int calc_cvc_total_force(int first, size_t num_cvcs); + /// \brief Same as \link colvar::calc_cvc_values \endlink but for Jacobian derivatives/forces + int calc_cvc_Jacobians(int first, size_t num_cvcs); + + /// \brief Collect quantities from CVCs and update aggregated data for the colvar + int collect_cvc_data(); + + /// \brief Collect the values of the CVCs + int collect_cvc_values(); + /// \brief Same as \link colvar::collect_cvc_values \endlink but for gradients + int collect_cvc_gradients(); + /// \brief Same as \link colvar::collect_cvc_values \endlink but for total forces + int collect_cvc_total_forces(); + /// \brief Same as \link colvar::collect_cvc_values \endlink but for Jacobian derivatives/forces + int collect_cvc_Jacobians(); + /// \brief Calculate the quantities associated to the colvar (but not to the CVCs) + int calc_colvar_properties(); + + /// Get the current applied force + inline colvarvalue const applied_force() const + { + if (is_enabled(f_cv_extended_Lagrangian)) { + return fr; + } + return f; + } + + /// Set the total biasing force to zero + void reset_bias_force(); + + /// Add to the total force from biases + void add_bias_force(colvarvalue const &force); + + /// Apply a force to the actual value (only meaningful with extended Lagrangian) + void add_bias_force_actual_value(colvarvalue const &force); + + /// \brief Collect all forces on this colvar, integrate internal + /// equations of motion of internal degrees of freedom; see also + /// colvar::communicate_forces() + /// return colvar energy if extended Lagrandian active + cvm::real update_forces_energy(); + + /// \brief Integrate equations of motion of extended Lagrangian coordinate if needed + void update_extended_Lagrangian(); + + /// \brief Communicate forces (previously calculated in + /// colvar::update()) to the external degrees of freedom + void communicate_forces(); + + /// \brief Enables and disables individual CVCs based on the given array + int set_cvc_flags(std::vector const &flags); + + /// \brief Updates the flags in the CVC objects, and their number + int update_cvc_flags(); + + /// \brief Modify the configuration of CVCs (currently, only base class data) + int update_cvc_config(std::vector const &confs); + + /// Whether this named parameter exists (in the first and only component) + int cvc_param_exists(std::string const ¶m_name); + + /// Get the value of the named parameter (from the first and only component) + cvm::real get_cvc_param(std::string const ¶m_name); + + /// Get a pointer to the named parameter (from the first and only component) + void const *get_cvc_param_ptr(std::string const ¶m_name); + + /// Pointer to the derivative of the variable with respect to param_name + colvarvalue const *get_cvc_param_grad(std::string const ¶m_name); + + /// Set the named parameter in the first and only component to the given value + int set_cvc_param(std::string const ¶m_name, void const *new_value); + +protected: + + /// \brief Number of CVC objects with an active flag + size_t n_active_cvcs = 0; + + /// Sum of square coefficients for active cvcs + cvm::real active_cvc_square_norm = 0.0; + + /// Update the sum of square coefficients for active cvcs + void update_active_cvc_square_norm(); + + /// \brief Absolute timestep number when this colvar was last updated + cvm::step_number prev_timestep; + +public: + + /// \brief Number of dimensions of the value of this colvar + inline size_t num_dimensions() const + { + return value().size(); + } + + /// \brief Number of CVC objects defined + inline size_t num_cvcs() const + { + return cvcs.size(); + } + + /// \brief number of CVC objects with an active flag (as set by + /// update_cvc_flags) + inline size_t num_active_cvcs() const + { + return n_active_cvcs; + } + + /// \brief Use the internal metrics (as from \link colvar::cvc + /// \endlink objects) to calculate square distances and gradients + /// + /// Handles correctly symmetries and periodic boundary conditions + cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Use the internal metrics (as from \link colvar::cvc + /// \endlink objects) to calculate square distances and gradients + /// + /// Handles correctly symmetries and periodic boundary conditions + colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Use the internal metrics (as from \link colvar::cvc + /// \endlink objects) to calculate square distances and gradients + /// + /// Handles correctly symmetries and periodic boundary conditions + colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Use the internal metrics (as from \link colvar::cvc + /// \endlink objects) to wrap a value into a standard interval + /// + /// Handles correctly symmetries and periodic boundary conditions + void wrap(colvarvalue &x_unwrapped) const; + + /// Read the analysis tasks + int parse_analysis(std::string const &conf); + + /// Perform analysis tasks + int analyze(); + + /// Read the value from a collective variable trajectory file + std::istream & read_traj(std::istream &is); + + /// Output formatted values to the trajectory file + std::ostream & write_traj(std::ostream &os); + /// Write a label to the trajectory file (comment line) + std::ostream & write_traj_label(std::ostream &os); + + /// Read the colvar's state from a formatted input stream + std::istream & read_state(std::istream &is); + + /// Read the colvar's state from an unformatted input stream + cvm::memory_stream & read_state(cvm::memory_stream &is); + + /// Check the name of the bias vs. the given string, set the matching_state flag accordingly + int check_matching_state(std::string const &state_conf); + + /// Read the values of colvar mutable data from a string (used by both versions of read_state()) + int set_state_params(std::string const &state_conf); + + /// Write the state information of this colvar in a block of text, suitable for later parsing + std::string const get_state_params() const; + + /// Write the colvar's state to a formatted output stream + std::ostream & write_state(std::ostream &os) const; + + /// Write the colvar's state to an unformatted output stream + cvm::memory_stream & write_state(cvm::memory_stream &os) const; + + /// Write output files (if defined, e.g. in analysis mode) + int write_output_files(); + +protected: + + /// Flag used to tell if the state string being read is for this colvar + bool matching_state; + + /// Previous value (to calculate velocities during analysis) + colvarvalue x_old; + + /// Value read from the most recent state file (if any) + colvarvalue x_restart; + + /// True if a state file was just read + bool after_restart; + + /// Time series of values and velocities used in correlation + /// functions + std::list< std::list > acf_x_history, acf_v_history; + /// Time series of values and velocities used in correlation + /// functions (pointers)x + std::list< std::list >::iterator acf_x_history_p, acf_v_history_p; + + /// Time series of values and velocities used in running averages + std::list< std::list > x_history; + /// Time series of values and velocities used in correlation + /// functions (pointers)x + std::list< std::list >::iterator x_history_p; + + /// \brief Collective variable with which the correlation is + /// calculated (default: itself) + std::string acf_colvar_name; + /// Length of autocorrelation function (ACF) + size_t acf_length; + /// After how many steps the ACF starts + size_t acf_offset; + /// How many timesteps separate two ACF values + size_t acf_stride; + /// Number of frames for each ACF point + size_t acf_nframes; + /// Normalize the ACF to a maximum value of 1? + bool acf_normalize; + /// ACF values + std::vector acf; + /// Name of the file to write the ACF + std::string acf_outfile; + + /// Type of autocorrelation function (ACF) + enum acf_type_e { + /// Unset type + acf_notset, + /// Velocity ACF, scalar product between v(0) and v(t) + acf_vel, + /// Coordinate ACF, scalar product between x(0) and x(t) + acf_coor, + /// \brief Coordinate ACF, second order Legendre polynomial + /// between x(0) and x(t) (does not work with scalar numbers) + acf_p2coor + }; + + /// Type of autocorrelation function (ACF) + acf_type_e acf_type; + + /// \brief Velocity ACF, scalar product between v(0) and v(t) + void calc_vel_acf(std::list &v_history, + colvarvalue const &v); + + /// \brief Coordinate ACF, scalar product between x(0) and x(t) + /// (does not work with scalar numbers) + void calc_coor_acf(std::list &x_history, + colvarvalue const &x); + + /// \brief Coordinate ACF, second order Legendre polynomial between + /// x(0) and x(t) (does not work with scalar numbers) + void calc_p2coor_acf(std::list &x_history, + colvarvalue const &x); + + /// Calculate the auto-correlation function (ACF) + int calc_acf(); + /// Save the ACF to a file + int write_acf(std::ostream &os); + + /// Length of running average series + size_t runave_length; + /// Timesteps to skip between two values in the running average series + size_t runave_stride; + /// Name of the file to write the running average + std::string runave_outfile; + /// Current value of the running average + colvarvalue runave; + /// Current value of the square deviation from the running average + cvm::real runave_variance = 0.0; + + /// Calculate the running average and its standard deviation + int calc_runave(); + + /// If extended Lagrangian active: colvar kinetic energy + cvm::real kinetic_energy = 0.0; + /// If extended Lagrangian active: colvar harmonic potential + cvm::real potential_energy = 0.0; + +public: + + // collective variable component base class + class cvc; + + // list of available collective variable components + + // scalar colvar components + class distance; + class distance_z; + class distance_xy; + class polar_theta; + class polar_phi; + class distance_inv; + class distance_pairs; + class dipole_magnitude; + class angle; + class dipole_angle; + class dihedral; + class coordnum; + class selfcoordnum; + class groupcoordnum; + class h_bond; + class rmsd; + class orientation_angle; + class orientation_proj; + class tilt; + class spin_angle; + class gyration; + class inertia; + class inertia_z; + class eigenvector; + class alpha_dihedrals; + class alpha_angles; + class dihedPC; + class alch_lambda; + class alch_Flambda; + class componentDisabled; + class CartesianBasedPath; + class aspath; + class azpath; + class gspath; + class gzpath; + class linearCombination; + class CVBasedPath; + class gspathCV; + class gzpathCV; + class aspathCV; + class azpathCV; + class euler_phi; + class euler_psi; + class euler_theta; + class neuralNetwork; + class customColvar; + + // non-scalar components + class distance_vec; + class distance_dir; + class cartesian; + class orientation; + + // components that do not handle any atoms directly + class map_total; + + /// A global mapping of cvc names to the cvc constructors + static const std::map> & + get_global_cvc_map() + { + return global_cvc_map; + } + + /// \brief function for sorting cvcs by their names + static bool compare_cvc(const colvar::cvc* const i, const colvar::cvc* const j); + +protected: + + /// \brief Array of \link colvar::cvc \endlink objects + std::vector cvcs; + + /// \brief Flags to enable or disable cvcs at next colvar evaluation + std::vector cvc_flags; + + /// \brief Initialize the sorted list of atom IDs for atoms involved + /// in all cvcs (called when enabling f_cv_collect_gradients) + void build_atom_list(void); + + /// Name of scripted function to be used + std::string scripted_function; + + /// Current cvc values in the order requested by script + /// when using scriptedFunction + std::vector sorted_cvc_values; + +#ifdef LEPTON + /// Vector of evaluators for custom functions using Lepton + std::vector value_evaluators; + + /// Vector of evaluators for gradients of custom functions + std::vector gradient_evaluators; + + /// Vector of references to cvc values to be passed to Lepton evaluators + std::vector value_eval_var_refs; + std::vector grad_eval_var_refs; + + /// Unused value that is written to when a variable simplifies out of a Lepton expression + double dev_null; +#endif + + /// A global mapping of cvc names to the cvc constructors + static std::map> + global_cvc_map; + + /// Volmap numeric IDs, one for each CVC (-1 if not available) + std::vector volmap_ids_; + +public: + + /// \brief Sorted array of (zero-based) IDs for all atoms involved + std::vector atom_ids; + + /// \brief Array of atomic gradients collected from all cvcs + /// with appropriate components, rotations etc. + /// For scalar variables only! + std::vector atomic_gradients; + + /// \brief Get vector of vectors of atom IDs for all atom groups + virtual std::vector > get_atom_lists(); + + /// Volmap numeric IDs, one for each CVC (-1 if not available) + std::vector const &get_volmap_ids(); + +}; + + +inline cvm::real const & colvar::force_constant() const +{ + return ext_force_k; +} + + +inline colvarvalue const & colvar::value() const +{ + return x_reported; +} + + +inline colvarvalue const & colvar::actual_value() const +{ + return x; +} + + +inline colvarvalue const & colvar::run_ave() const +{ + return runave; +} + + +inline colvarvalue const & colvar::velocity() const +{ + return v_reported; +} + + +inline colvarvalue const & colvar::total_force() const +{ + return ft_reported; +} + + +inline void colvar::add_bias_force(colvarvalue const &force) +{ + check_enabled(f_cv_gradient, + std::string("applying a force to the variable \""+name+"\"")); + if (cvm::debug()) { + cvm::log("Adding biasing force "+cvm::to_str(force)+" to colvar \""+name+"\".\n"); + } + fb += force; +} + + +inline void colvar::add_bias_force_actual_value(colvarvalue const &force) +{ + if (cvm::debug()) { + cvm::log("Adding biasing force "+cvm::to_str(force)+" to colvar \""+name+"\".\n"); + } + fb_actual += force; +} + + +inline void colvar::reset_bias_force() { + fb.type(value()); + fb.reset(); + fb_actual.type(value()); + fb_actual.reset(); +} + +#endif diff --git a/src/external/colvars/colvar_UIestimator.h b/src/external/colvars/colvar_UIestimator.h new file mode 100644 index 00000000000..30a90a57991 --- /dev/null +++ b/src/external/colvars/colvar_UIestimator.h @@ -0,0 +1,749 @@ +// -*- Mode:c++; c-basic-offset: 4; -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVAR_UIESTIMATOR_H +#define COLVAR_UIESTIMATOR_H + +#include +#include +#include +#include +#include + +#include + +// only for colvar module! +// when integrated into other code, just remove this line and "...cvm::backup_file(...)" +#include "colvarmodule.h" +#include "colvarproxy.h" + +namespace UIestimator { + const int Y_SIZE = 21; // defines the range of extended CV with respect to a given CV + // For example, CV=10, width=1, Y_SIZE=21, then eCV=[0-20], having a size of 21 + const int HALF_Y_SIZE = 10; + const int EXTENDED_X_SIZE = HALF_Y_SIZE; + const double EPSILON = 0.000001; // for comparison of float numbers + + class n_matrix { // Stores the distribution matrix of n(x,y) + + public: + n_matrix() {} + n_matrix(const std::vector & lowerboundary_input, // lowerboundary of x + const std::vector & upperboundary_input, // upperboundary of + const std::vector & width_input, // width of x + const int y_size_input) { // size of y, for example, ysize=7, then when x=1, the distribution of y in [-2,4] is considered + + int i; + + this->lowerboundary = lowerboundary_input; + this->upperboundary = upperboundary_input; + this->width = width_input; + this->dimension = lowerboundary_input.size(); + this->y_size = y_size_input; // keep in mind the internal (spare) matrix is stored in diagonal form + this->y_total_size = int(cvm::pow(double(y_size_input), double(dimension)) + EPSILON); + + // the range of the matrix is [lowerboundary, upperboundary] + x_total_size = 1; + for (i = 0; i < dimension; i++) { + x_size.push_back(int((upperboundary_input[i] - lowerboundary_input[i]) / width_input[i] + EPSILON)); + x_total_size *= x_size[i]; + } + + // initialize the internal matrix + matrix.reserve(x_total_size); + for (i = 0; i < x_total_size; i++) { + matrix.push_back(std::vector(y_total_size, 0)); + } + + temp.resize(dimension); + } + + int get_value(const std::vector & x, const std::vector & y) { + return matrix[convert_x(x)][convert_y(x, y)]; + } + + void set_value(const std::vector & x, const std::vector & y, const int value) { + matrix[convert_x(x)][convert_y(x,y)] = value; + } + + void increase_value(const std::vector & x, const std::vector & y, const int value) { + matrix[convert_x(x)][convert_y(x,y)] += value; + } + + private: + std::vector lowerboundary; + std::vector upperboundary; + std::vector width; + int dimension; + std::vector x_size; // the size of x in each dimension + int x_total_size; // the size of x of the internal matrix + int y_size; // the size of y in each dimension + int y_total_size; // the size of y of the internal matrix + + std::vector > matrix; // the internal matrix + + std::vector temp; // this vector is used in convert_x and convert_y to save computational resource + + int convert_x(const std::vector & x) { // convert real x value to its interal index + + int i, j; + + for (i = 0; i < dimension; i++) { + temp[i] = int((x[i] - lowerboundary[i]) / width[i] + EPSILON); + } + + int index = 0; + for (i = 0; i < dimension; i++) { + if (i + 1 < dimension) { + int x_temp = 1; + for (j = i + 1; j < dimension; j++) + x_temp *= x_size[j]; + index += temp[i] * x_temp; + } + else + index += temp[i]; + } + return index; + } + + int convert_y(const std::vector & x, const std::vector & y) { // convert real y value to its interal index + + int i; + + for (i = 0; i < dimension; i++) { + temp[i] = int(round((round(y[i] / width[i] + EPSILON) - round(x[i] / width[i] + EPSILON)) + (y_size - 1) / 2 + EPSILON)); + } + + int index = 0; + for (i = 0; i < dimension; i++) { + if (i + 1 < dimension) + index += temp[i] * int(cvm::pow(double(y_size), double(dimension - i - 1)) + EPSILON); + else + index += temp[i]; + } + return index; + } + + double round(double r) { + return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5); + } + }; + + // vector, store the sum_x, sum_x_square, count_y + template + class n_vector { + + public: + n_vector() {} + n_vector(const std::vector & lowerboundary_input, // lowerboundary of x + const std::vector & upperboundary_input, // upperboundary of + const std::vector & width_input, // width of x + const int y_size_input, // size of y, for example, ysize=7, then when x=1, the distribution of y in [-2,4] is considered + const T & default_value) { // the default value of T + + this->width = width_input; + this->dimension = lowerboundary_input.size(); + + x_total_size = 1; + for (int i = 0; i < dimension; i++) { + this->lowerboundary.push_back(lowerboundary_input[i] - (y_size_input - 1) / 2 * width_input[i] - EPSILON); + this->upperboundary.push_back(upperboundary_input[i] + (y_size_input - 1) / 2 * width_input[i] + EPSILON); + + x_size.push_back(int((this->upperboundary[i] - this->lowerboundary[i]) / this->width[i] + EPSILON)); + x_total_size *= x_size[i]; + } + + // initialize the internal vector + vector.resize(x_total_size, default_value); + + temp.resize(dimension); + } + + T & get_value(const std::vector & x) { + return vector[convert_x(x)]; + } + + void set_value(const std::vector & x, const T value) { + vector[convert_x(x)] = value; + } + + void increase_value(const std::vector & x, const T value) { + vector[convert_x(x)] += value; + } + + private: + std::vector lowerboundary; + std::vector upperboundary; + std::vector width; + int dimension; + std::vector x_size; // the size of x in each dimension + int x_total_size; // the size of x of the internal matrix + + std::vector vector; // the internal vector + + std::vector temp; // this vector is used in convert_x and convert_y to save computational resource + + int convert_x(const std::vector & x) { // convert real x value to its interal index + + int i, j; + + for (i = 0; i < dimension; i++) { + temp[i] = int((x[i] - lowerboundary[i]) / width[i] + EPSILON); + } + + int index = 0; + for (i = 0; i < dimension; i++) { + if (i + 1 < dimension) { + int x_temp = 1; + for (j = i + 1; j < dimension; j++) + x_temp *= x_size[j]; + index += temp[i] * x_temp; + } + else + index += temp[i]; + } + return index; + } + }; + + class UIestimator { // the implemension of UI estimator + + public: + UIestimator() {} + + //called when (re)start an eabf simulation + UIestimator(const std::vector & lowerboundary_input, + const std::vector & upperboundary_input, + const std::vector & width_input, + const std::vector & krestr_input, // force constant in eABF + const std::string & output_filename_input, // the prefix of output files + const int output_freq_input, + const bool restart_input, // whether restart from a .count and a .grad file + const std::vector & input_filename_input, // the prefixes of input files + const double temperature_input) { + + // initialize variables + this->lowerboundary = lowerboundary_input; + this->upperboundary = upperboundary_input; + this->width = width_input; + this->krestr = krestr_input; + this->output_filename = output_filename_input; + this->output_freq = output_freq_input; + this->restart = restart_input; + this->input_filename = input_filename_input; + this->temperature = temperature_input; + + int i, j; + + dimension = lowerboundary.size(); + + for (i = 0; i < dimension; i++) { + sum_x.push_back(n_vector(lowerboundary, upperboundary, width, Y_SIZE, 0.0)); + sum_x_square.push_back(n_vector(lowerboundary, upperboundary, width, Y_SIZE, 0.0)); + + x_av.push_back(n_vector(lowerboundary, upperboundary, width, Y_SIZE, 0.0)); + sigma_square.push_back(n_vector(lowerboundary, upperboundary, width, Y_SIZE, 0.0)); + } + + count_y = n_vector(lowerboundary, upperboundary, width, Y_SIZE, 0); + distribution_x_y = n_matrix(lowerboundary, upperboundary, width, Y_SIZE); + + grad = n_vector >(lowerboundary, upperboundary, width, 1, std::vector(dimension, 0.0)); + count = n_vector(lowerboundary, upperboundary, width, 1, 0); + + written = false; + written_1D = false; + + if (dimension == 1) { + std::vector upperboundary_temp = upperboundary; + upperboundary_temp[0] = upperboundary[0] + width[0]; + oneD_pmf = n_vector(lowerboundary, upperboundary_temp, width, 1, 0.0); + } + + if (restart == true) { + input_grad = n_vector >(lowerboundary, upperboundary, width, 1, std::vector(dimension, 0.0)); + input_count = n_vector(lowerboundary, upperboundary, width, 1, 0); + + // initialize input_Grad and input_count + // the loop_flag is a n-dimensional vector, increae from lowerboundary to upperboundary when looping + std::vector loop_flag(dimension, 0); + for (i = 0; i < dimension; i++) { + loop_flag[i] = lowerboundary[i]; + } + + i = 0; + while (i >= 0) { + for (j = 0; j < dimension; j++) { + input_grad.set_value(loop_flag, std::vector(dimension,0)); + } + input_count.set_value(loop_flag, 0); + + // iterate over any dimensions + i = dimension - 1; + while (i >= 0) { + loop_flag[i] += width[i]; + if (loop_flag[i] > upperboundary[i] - width[i] + EPSILON) { + loop_flag[i] = lowerboundary[i]; + i--; + } + else + break; + } + } + read_inputfiles(input_filename); + } + } + + ~UIestimator() {} + + // called from MD engine every step + bool update(cvm::step_number /* step */, + std::vector x, std::vector y) { + + int i; + + for (i = 0; i < dimension; i++) { + // for dihedral RC, it is possible that x = 179 and y = -179, should correct it + // may have problem, need to fix + if (x[i] > 150 && y[i] < -150) { + y[i] += 360; + } + if (x[i] < -150 && y[i] > 150) { + y[i] -= 360; + } + + if (x[i] < lowerboundary[i] - EXTENDED_X_SIZE * width[i] + EPSILON || x[i] > upperboundary[i] + EXTENDED_X_SIZE * width[i] - EPSILON \ + || y[i] - x[i] < -HALF_Y_SIZE * width[i] + EPSILON || y[i] - x[i] > HALF_Y_SIZE * width[i] - EPSILON \ + || y[i] - lowerboundary[i] < -HALF_Y_SIZE * width[i] + EPSILON || y[i] - upperboundary[i] > HALF_Y_SIZE * width[i] - EPSILON) + return false; + } + + for (i = 0; i < dimension; i++) { + sum_x[i].increase_value(y, x[i]); + sum_x_square[i].increase_value(y, x[i] * x[i]); + } + count_y.increase_value(y, 1); + + for (i = 0; i < dimension; i++) { + // adapt colvars precision + if (x[i] < lowerboundary[i] + EPSILON || x[i] > upperboundary[i] - EPSILON) + return false; + } + distribution_x_y.increase_value(x, y, 1); + + return true; + } + + // update the output_filename + void update_output_filename(const std::string& filename) { + output_filename = filename; + } + + private: + std::vector > sum_x; // the sum of x in each y bin + std::vector > sum_x_square; // the sum of x in each y bin + n_vector count_y; // the distribution of y + n_matrix distribution_x_y; // the distribution of pair + + int dimension; + + std::vector lowerboundary; + std::vector upperboundary; + std::vector width; + std::vector krestr; + std::string output_filename; + int output_freq; + bool restart; + std::vector input_filename; + double temperature; + + n_vector > grad; + n_vector count; + + n_vector oneD_pmf; + + n_vector > input_grad; + n_vector input_count; + + // used in double integration + std::vector > x_av; + std::vector > sigma_square; + + bool written; + bool written_1D; + + public: + // calculate gradients from the internal variables + void calc_pmf() { + colvarproxy *proxy = cvm::main()->proxy; + + int norm; + int i, j, k; + + std::vector loop_flag(dimension, 0); + for (i = 0; i < dimension; i++) { + loop_flag[i] = lowerboundary[i] - HALF_Y_SIZE * width[i]; + } + + i = 0; + while (i >= 0) { + norm = count_y.get_value(loop_flag) > 0 ? count_y.get_value(loop_flag) : 1; + for (j = 0; j < dimension; j++) { + x_av[j].set_value(loop_flag, sum_x[j].get_value(loop_flag) / norm); + sigma_square[j].set_value(loop_flag, sum_x_square[j].get_value(loop_flag) / norm - x_av[j].get_value(loop_flag) * x_av[j].get_value(loop_flag)); + } + + // iterate over any dimensions + i = dimension - 1; + while (i >= 0) { + loop_flag[i] += width[i]; + if (loop_flag[i] > upperboundary[i] + HALF_Y_SIZE * width[i] - width[i] + EPSILON) { + loop_flag[i] = lowerboundary[i] - HALF_Y_SIZE * width[i]; + i--; + } + else + break; + } + } + + // double integration + std::vector av(dimension, 0); + std::vector diff_av(dimension, 0); + + std::vector loop_flag_x(dimension, 0); + std::vector loop_flag_y(dimension, 0); + for (i = 0; i < dimension; i++) { + loop_flag_x[i] = lowerboundary[i]; + loop_flag_y[i] = loop_flag_x[i] - HALF_Y_SIZE * width[i]; + } + + i = 0; + while (i >= 0) { + norm = 0; + for (k = 0; k < dimension; k++) { + av[k] = 0; + diff_av[k] = 0; + loop_flag_y[k] = loop_flag_x[k] - HALF_Y_SIZE * width[k]; + } + + j = 0; + while (j >= 0) { + norm += distribution_x_y.get_value(loop_flag_x, loop_flag_y); + for (k = 0; k < dimension; k++) { + if (sigma_square[k].get_value(loop_flag_y) > EPSILON || sigma_square[k].get_value(loop_flag_y) < -EPSILON) + av[k] += distribution_x_y.get_value(loop_flag_x, loop_flag_y) * ( (loop_flag_x[k] + 0.5 * width[k]) - x_av[k].get_value(loop_flag_y)) / sigma_square[k].get_value(loop_flag_y); + + diff_av[k] += distribution_x_y.get_value(loop_flag_x, loop_flag_y) * (loop_flag_x[k] - loop_flag_y[k]); + } + + // iterate over any dimensions + j = dimension - 1; + while (j >= 0) { + loop_flag_y[j] += width[j]; + if (loop_flag_y[j] > loop_flag_x[j] + HALF_Y_SIZE * width[j] - width[j] + EPSILON) { + loop_flag_y[j] = loop_flag_x[j] - HALF_Y_SIZE * width[j]; + j--; + } + else + break; + } + } + + std::vector grad_temp(dimension, 0); + for (k = 0; k < dimension; k++) { + diff_av[k] /= (norm > 0 ? norm : 1); + av[k] = proxy->boltzmann() * temperature * av[k] / (norm > 0 ? norm : 1); + grad_temp[k] = av[k] - krestr[k] * diff_av[k]; + } + grad.set_value(loop_flag_x, grad_temp); + count.set_value(loop_flag_x, norm); + + // iterate over any dimensions + i = dimension - 1; + while (i >= 0) { + loop_flag_x[i] += width[i]; + if (loop_flag_x[i] > upperboundary[i] - width[i] + EPSILON) { + loop_flag_x[i] = lowerboundary[i]; + i--; + } + else + break; + } + } + } + + + // calculate 1D pmf + void calc_1D_pmf() + { + std::vector last_position(1, 0); + std::vector position(1, 0); + + double min = 0; + double dG = 0; + double i; + + oneD_pmf.set_value(lowerboundary, 0); + last_position = lowerboundary; + for (i = lowerboundary[0] + width[0]; i < upperboundary[0] + EPSILON; i += width[0]) { + position[0] = i + EPSILON; + if (restart == false || input_count.get_value(last_position) == 0) { + dG = oneD_pmf.get_value(last_position) + grad.get_value(last_position)[0] * width[0]; + } + else { + dG = oneD_pmf.get_value(last_position) + ((grad.get_value(last_position)[0] * count.get_value(last_position) + input_grad.get_value(last_position)[0] * input_count.get_value(last_position)) / (count.get_value(last_position) + input_count.get_value(last_position))) * width[0]; + } + if (dG < min) + min = dG; + oneD_pmf.set_value(position, dG); + last_position[0] = i + EPSILON; + } + + for (i = lowerboundary[0]; i < upperboundary[0] + EPSILON; i += width[0]) { + position[0] = i + EPSILON; + oneD_pmf.set_value(position, oneD_pmf.get_value(position) - min); + } + } + + // write 1D pmf + void write_1D_pmf() { + std::string pmf_filename = output_filename + ".UI.pmf"; + + // only for colvars module! + if (written_1D) cvm::backup_file(pmf_filename.c_str()); + + std::ostream &ofile_pmf = cvm::proxy->output_stream(pmf_filename, + "PMF file"); + + std::vector position(1, 0); + for (double i = lowerboundary[0]; i < upperboundary[0] + EPSILON; i += width[0]) { + ofile_pmf << i << " "; + position[0] = i + EPSILON; + ofile_pmf << oneD_pmf.get_value(position) << std::endl; + } + cvm::proxy->close_output_stream(pmf_filename); + + written_1D = true; + } + + // write heads of the output files + void writehead(std::ostream& os) const { + os << "# " << dimension << std::endl; + for (int i = 0; i < dimension; i++) { + os << "# " << lowerboundary[i] << " " << width[i] << " " << int((upperboundary[i] - lowerboundary[i]) / width[i] + EPSILON) << " " << 0 << std::endl; + } + os << std::endl; + } + + // write interal data, used for testing + void write_interal_data() { + std::string internal_filename = output_filename + ".UI.internal"; + + std::ostream &ofile_internal = cvm::proxy->output_stream(internal_filename, + "UI internal file"); + + std::vector loop_flag(dimension, 0); + for (int i = 0; i < dimension; i++) { + loop_flag[i] = lowerboundary[i]; + } + + int n = 0; + while (n >= 0) { + for (int j = 0; j < dimension; j++) { + ofile_internal << loop_flag[j] + 0.5 * width[j] << " "; + } + + for (int k = 0; k < dimension; k++) { + ofile_internal << grad.get_value(loop_flag)[k] << " "; + } + + std::vector ii(dimension,0); + for (double i = loop_flag[0] - 10; i < loop_flag[0] + 10 + EPSILON; i+= width[0]) { + for (double j = loop_flag[1] - 10; j< loop_flag[1] + 10 + EPSILON; j+=width[1]) { + ii[0] = i; + ii[1] = j; + ofile_internal << i <<" "<= 0) { + loop_flag[n] += width[n]; + if (loop_flag[n] > upperboundary[n] - width[n] + EPSILON) { + loop_flag[n] = lowerboundary[n]; + n--; + } + else + break; + } + } + cvm::proxy->close_output_stream(internal_filename.c_str()); + } + + // write output files + void write_files() { + std::string grad_filename = output_filename + ".UI.grad"; + std::string hist_filename = output_filename + ".UI.hist.grad"; + std::string count_filename = output_filename + ".UI.count"; + + int i, j; +// + // only for colvars module! + if (written) cvm::backup_file(grad_filename.c_str()); + //if (written) cvm::backup_file(hist_filename.c_str()); + if (written) cvm::backup_file(count_filename.c_str()); + + std::ostream &ofile = cvm::proxy->output_stream(grad_filename, + "gradient file"); + std::ostream &ofile_hist = cvm::proxy->output_stream(hist_filename, + "gradient history file"); + std::ostream &ofile_count = cvm::proxy->output_stream(count_filename, + "count file"); + + writehead(ofile); + writehead(ofile_hist); + writehead(ofile_count); + + if (dimension == 1) { + calc_1D_pmf(); + write_1D_pmf(); + } + + std::vector loop_flag(dimension, 0); + for (i = 0; i < dimension; i++) { + loop_flag[i] = lowerboundary[i]; + } + + i = 0; + while (i >= 0) { + for (j = 0; j < dimension; j++) { + ofile << loop_flag[j] + 0.5 * width[j] << " "; + ofile_hist << loop_flag[j] + 0.5 * width[j] << " "; + ofile_count << loop_flag[j] + 0.5 * width[j] << " "; + } + + if (restart == false) { + for (j = 0; j < dimension; j++) { + ofile << grad.get_value(loop_flag)[j] << " "; + ofile_hist << grad.get_value(loop_flag)[j] << " "; + } + ofile << std::endl; + ofile_hist << std::endl; + ofile_count << count.get_value(loop_flag) << " " <= 0) { + loop_flag[i] += width[i]; + if (loop_flag[i] > upperboundary[i] - width[i] + EPSILON) { + loop_flag[i] = lowerboundary[i]; + i--; + ofile << std::endl; + ofile_hist << std::endl; + ofile_count << std::endl; + } + else + break; + } + } + cvm::proxy->close_output_stream(grad_filename.c_str()); + // cvm::proxy->close_output_stream(hist_filename.c_str()); + cvm::proxy->close_output_stream(count_filename.c_str()); + + written = true; + } + + // read input files + void read_inputfiles(const std::vector filename) + { + char sharp; + double nothing; + int dimension_temp; + int i, j, k, l, m; + + colvarproxy *proxy = cvm::main()->proxy; + std::vector loop_bin_size(dimension, 0); + std::vector position_temp(dimension, 0); + std::vector grad_temp(dimension, 0); + int count_temp = 0; + for (i = 0; i < int(filename.size()); i++) { + int size = 1 , size_temp = 0; + + std::string count_filename = filename[i] + ".UI.count"; + std::string grad_filename = filename[i] + ".UI.grad"; + + std::istream &count_file = + proxy->input_stream(count_filename, "count filename"); + std::istream &grad_file = + proxy->input_stream(grad_filename, "gradient filename"); + + if (!count_file || !grad_file) { + return; + } + + count_file >> sharp >> dimension_temp; + grad_file >> sharp >> dimension_temp; + + for (j = 0; j < dimension; j++) { + count_file >> sharp >> nothing >> nothing >> size_temp >> nothing; + grad_file >> sharp >> nothing >> nothing >> nothing >> nothing; + size *= size_temp; + } + + for (j = 0; j < size; j++) { + do { + for (k = 0; k < dimension; k++) { + count_file >> position_temp[k]; + grad_file >> nothing; + } + + for (l = 0; l < dimension; l++) { + grad_file >> grad_temp[l]; + } + count_file >> count_temp; + } + while (position_temp[i] < lowerboundary[i] - EPSILON || position_temp[i] > upperboundary[i] + EPSILON); + + if (count_temp == 0) { + continue; + } + + for (m = 0; m < dimension; m++) { + grad_temp[m] = (grad_temp[m] * count_temp + input_grad.get_value(position_temp)[m] * input_count.get_value(position_temp)) / (count_temp + input_count.get_value(position_temp)); + } + input_grad.set_value(position_temp, grad_temp); + input_count.increase_value(position_temp, count_temp); + } + + proxy->close_input_stream(count_filename); + proxy->close_input_stream(grad_filename); + } + } + }; +} + +#endif diff --git a/src/external/colvars/colvar_arithmeticpath.h b/src/external/colvars/colvar_arithmeticpath.h new file mode 100644 index 00000000000..ee97390dfc4 --- /dev/null +++ b/src/external/colvars/colvar_arithmeticpath.h @@ -0,0 +1,137 @@ +#ifndef ARITHMETICPATHCV_H +#define ARITHMETICPATHCV_H + +#include "colvarmodule.h" + +#include +#include +#include +#include +#include + +namespace ArithmeticPathCV { + +using std::vector; + +template +class ArithmeticPathBase { +public: + ArithmeticPathBase() {} + ~ArithmeticPathBase() {} + void initialize(size_t p_num_elements, size_t p_total_frames, scalar_type p_lambda, const vector& p_weights); + void reComputeLambda(const vector& rmsd_between_refs); + template + void computeValue(const vector>& frame_element_distances, scalar_type *s = nullptr, scalar_type *z = nullptr); + // can only be called after computeValue() for element-wise derivatives and store derivatives of i-th frame to dsdx and dzdx + template + void computeDerivatives(const vector>& frame_element_distances, vector> *dsdx = nullptr, vector> *dzdx = nullptr); +protected: + scalar_type lambda; + vector squared_weights; + size_t num_elements; + size_t total_frames; + vector exponents; + scalar_type max_exponent; + scalar_type saved_exponent_sum; + scalar_type normalization_factor; + scalar_type saved_s; +}; + +template +void ArithmeticPathBase::initialize(size_t p_num_elements, size_t p_total_frames, scalar_type p_lambda, const vector& p_weights) { + lambda = p_lambda; + for (size_t i = 0; i < p_weights.size(); ++i) squared_weights.push_back(p_weights[i] * p_weights[i]); + num_elements = p_num_elements; + total_frames = p_total_frames; + exponents.resize(total_frames); + normalization_factor = 1.0 / static_cast(total_frames - 1); + saved_s = scalar_type(); + saved_exponent_sum = scalar_type(); + max_exponent = scalar_type(); +} + +template +template +void ArithmeticPathBase::computeValue( + const vector>& frame_element_distances, + scalar_type *s, scalar_type *z) +{ + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + scalar_type exponent_tmp = scalar_type(); + for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { + exponent_tmp += squared_weights[j_elem] * frame_element_distances[i_frame][j_elem] * frame_element_distances[i_frame][j_elem]; + } + exponents[i_frame] = exponent_tmp * -1.0 * lambda; + if (i_frame == 0 || exponents[i_frame] > max_exponent) max_exponent = exponents[i_frame]; + } + scalar_type log_sum_exp_0 = scalar_type(); + scalar_type log_sum_exp_1 = scalar_type(); + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + exponents[i_frame] = cvm::exp(exponents[i_frame] - max_exponent); + log_sum_exp_0 += exponents[i_frame]; + log_sum_exp_1 += i_frame * exponents[i_frame]; + } + saved_exponent_sum = log_sum_exp_0; + log_sum_exp_0 = max_exponent + cvm::logn(log_sum_exp_0); + log_sum_exp_1 = max_exponent + cvm::logn(log_sum_exp_1); + saved_s = normalization_factor * cvm::exp(log_sum_exp_1 - log_sum_exp_0); + if (s != nullptr) { + *s = saved_s; + } + if (z != nullptr) { + *z = -1.0 / lambda * log_sum_exp_0; + } +} + +template +void ArithmeticPathBase::reComputeLambda(const vector& rmsd_between_refs) { + scalar_type mean_square_displacements = 0.0; + for (size_t i_frame = 1; i_frame < total_frames; ++i_frame) { + cvm::log(std::string("Distance between frame ") + cvm::to_str(i_frame) + " and " + cvm::to_str(i_frame + 1) + " is " + cvm::to_str(rmsd_between_refs[i_frame - 1]) + std::string("\n")); + mean_square_displacements += rmsd_between_refs[i_frame - 1] * rmsd_between_refs[i_frame - 1]; + } + mean_square_displacements /= scalar_type(total_frames - 1); + lambda = 1.0 / mean_square_displacements; +} + +// frame-wise derivatives for frames using optimal rotation +template +template +void ArithmeticPathBase::computeDerivatives( + const vector>& frame_element_distances, + vector> *dsdx, + vector> *dzdx) +{ + vector softmax_out, tmps; + softmax_out.reserve(total_frames); + tmps.reserve(total_frames); + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + softmax_out.push_back(exponents[i_frame] / saved_exponent_sum); + tmps.push_back( + (static_cast(i_frame) - + static_cast(total_frames - 1) * saved_s) * + normalization_factor); + } + if (dsdx != nullptr) { + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { + (*dsdx)[i_frame][j_elem] = + -2.0 * squared_weights[j_elem] * lambda * + frame_element_distances[i_frame][j_elem] * + softmax_out[i_frame] * tmps[i_frame]; + } + } + } + if (dzdx != nullptr) { + for (size_t i_frame = 0; i_frame < total_frames; ++i_frame) { + for (size_t j_elem = 0; j_elem < num_elements; ++j_elem) { + (*dzdx)[i_frame][j_elem] = + 2.0 * squared_weights[j_elem] * softmax_out[i_frame] * + frame_element_distances[i_frame][j_elem]; + } + } + } +} +} + +#endif // ARITHMETICPATHCV_H diff --git a/src/external/colvars/colvar_geometricpath.h b/src/external/colvars/colvar_geometricpath.h new file mode 100644 index 00000000000..51f97bb675a --- /dev/null +++ b/src/external/colvars/colvar_geometricpath.h @@ -0,0 +1,263 @@ +#ifndef GEOMETRICPATHCV_H +#define GEOMETRICPATHCV_H +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include +#include +#include +#include + +#include "colvarmodule.h" + + +namespace GeometricPathCV { + +enum path_sz {S, Z}; + +template +class GeometricPathBase { +private: + struct doCompareFrameDistance { + doCompareFrameDistance(const GeometricPathBase& obj): m_obj(obj) {} + const GeometricPathBase& m_obj; + bool operator()(const size_t& i1, const size_t& i2) { + return m_obj.frame_distances[i1] < m_obj.frame_distances[i2]; + } + }; +protected: + scalar_type v1v1; + scalar_type v2v2; + scalar_type v3v3; + scalar_type v4v4; + scalar_type v1v3; + scalar_type v1v4; + scalar_type f; + scalar_type dx; + scalar_type s; + scalar_type z; + scalar_type zz; + std::vector v1; + std::vector v2; + std::vector v3; + std::vector v4; + std::vector dfdv1; + std::vector dfdv2; + std::vector dzdv1; + std::vector dzdv2; + std::vector frame_distances; + std::vector frame_index; + bool use_second_closest_frame; + bool use_third_closest_frame; + bool use_z_square; + long min_frame_index_1; + long min_frame_index_2; + long min_frame_index_3; + long sign; + double M; + double m; +public: + GeometricPathBase(size_t vector_size, const element_type& element = element_type(), size_t total_frames = 1, bool p_use_second_closest_frame = true, bool p_use_third_closest_frame = false, bool p_use_z_square = false); + GeometricPathBase(size_t vector_size, const std::vector& elements, size_t total_frames = 1, bool p_use_second_closest_frame = true, bool p_use_third_closest_frame = false, bool p_use_z_square = false); + GeometricPathBase() {} + virtual ~GeometricPathBase() {} + virtual void initialize(size_t vector_size, const element_type& element = element_type(), size_t total_frames = 1, bool p_use_second_closest_frame = true, bool p_use_third_closest_frame = false, bool p_use_z_square = false); + virtual void initialize(size_t vector_size, const std::vector& elements, size_t total_frames = 1, bool p_use_second_closest_frame = true, bool p_use_third_closest_frame = false, bool p_use_z_square = false); + virtual void prepareVectors() = 0; + virtual void updateDistanceToReferenceFrames() = 0; + virtual void compute(); + virtual void determineClosestFrames(); + virtual void computeValue(); + virtual void computeDerivatives(); +}; + +template +GeometricPathBase::GeometricPathBase(size_t vector_size, const element_type& element, size_t total_frames, bool p_use_second_closest_frame, bool p_use_third_closest_frame, bool p_use_z_square) { + initialize(vector_size, element, total_frames, p_use_second_closest_frame, p_use_third_closest_frame, p_use_z_square); +} + +template +GeometricPathBase::GeometricPathBase(size_t vector_size, const std::vector& elements, size_t total_frames, bool p_use_second_closest_frame, bool p_use_third_closest_frame, bool p_use_z_square) { + initialize(vector_size, elements, total_frames, p_use_second_closest_frame, p_use_third_closest_frame, p_use_z_square); +} + +template +void GeometricPathBase::initialize(size_t vector_size, const element_type& element, size_t total_frames, bool p_use_second_closest_frame, bool p_use_third_closest_frame, bool p_use_z_square) { + v1v1 = scalar_type(); + v2v2 = scalar_type(); + v3v3 = scalar_type(); + v4v4 = scalar_type(); + v1v3 = scalar_type(); + v1v4 = scalar_type(); + f = scalar_type(); + dx = scalar_type(); + z = scalar_type(); + zz = scalar_type(); + sign = 0; + v1.resize(vector_size, element); + v2.resize(vector_size, element); + v3.resize(vector_size, element); + v4.resize(vector_size, element); + dfdv1.resize(vector_size, element); + dfdv2.resize(vector_size, element); + dzdv1.resize(vector_size, element); + dzdv2.resize(vector_size, element); + frame_distances.resize(total_frames); + frame_index.resize(total_frames); + for (size_t i_frame = 0; i_frame < frame_index.size(); ++i_frame) { + frame_index[i_frame] = i_frame; + } + use_second_closest_frame = p_use_second_closest_frame; + use_third_closest_frame = p_use_third_closest_frame; + use_z_square = p_use_z_square; + M = static_cast(total_frames - 1); + m = static_cast(1.0); +} + +template +void GeometricPathBase::initialize(size_t /* vector_size */, const std::vector& elements, size_t total_frames, bool p_use_second_closest_frame, bool p_use_third_closest_frame, bool p_use_z_square) { + v1v1 = scalar_type(); + v2v2 = scalar_type(); + v3v3 = scalar_type(); + v4v4 = scalar_type(); + v1v3 = scalar_type(); + v1v4 = scalar_type(); + f = scalar_type(); + dx = scalar_type(); + z = scalar_type(); + zz = scalar_type(); + sign = 0; + v1 = elements; + v2 = elements; + v3 = elements; + v4 = elements; + dfdv1 = elements; + dfdv2 = elements; + dzdv1 = elements; + dzdv2 = elements; + frame_distances.resize(total_frames); + frame_index.resize(total_frames); + for (size_t i_frame = 0; i_frame < frame_index.size(); ++i_frame) { + frame_index[i_frame] = i_frame; + } + use_second_closest_frame = p_use_second_closest_frame; + use_third_closest_frame = p_use_third_closest_frame; + use_z_square = p_use_z_square; + M = static_cast(total_frames - 1); + m = static_cast(1.0); +} + +template +void GeometricPathBase::compute() { + computeValue(); + computeDerivatives(); +} + +template +void GeometricPathBase::determineClosestFrames() { + // Find the closest and the second closest frames + std::sort(frame_index.begin(), frame_index.end(), doCompareFrameDistance(*this)); + // Determine the sign + sign = static_cast(frame_index[0]) - static_cast(frame_index[1]); + if (sign > 1) { + // sigma(z) is on the left side of the closest frame + sign = 1; + } else if (sign < -1) { + // sigma(z) is on the right side of the closest frame + sign = -1; + } + if (cvm::fabs(static_cast(frame_index[0]) - static_cast(frame_index[1])) > 1) { + std::string message( + "Warning: Geometrical pathCV relies on the assumption that the second closest frame is " + "the neighbouring frame\n" + " Please check your configuration or increase restraint on z(σ)\n"); + for (size_t i_frame = 0; i_frame < frame_index.size(); ++i_frame) { + message += "Frame index: " + cvm::to_str(frame_index[i_frame]) + + " ; optimal RMSD = " + cvm::to_str(frame_distances[frame_index[i_frame]]) + + "\n"; + } + } + min_frame_index_1 = frame_index[0]; // s_m + min_frame_index_2 = use_second_closest_frame ? frame_index[1] : min_frame_index_1 - sign; // s_(m-1) + min_frame_index_3 = use_third_closest_frame ? frame_index[2] : min_frame_index_1 + sign; // s_(m+1) + m = static_cast(frame_index[0]); +} + +template +void GeometricPathBase::computeValue() { + updateDistanceToReferenceFrames(); + determineClosestFrames(); + prepareVectors(); + v1v1 = scalar_type(); + v2v2 = scalar_type(); + v3v3 = scalar_type(); + v1v3 = scalar_type(); + if (path_type == Z) { + v1v4 = scalar_type(); + v4v4 = scalar_type(); + } + for (size_t i_elem = 0; i_elem < v1.size(); ++i_elem) { + v1v1 += v1[i_elem] * v1[i_elem]; + v2v2 += v2[i_elem] * v2[i_elem]; + v3v3 += v3[i_elem] * v3[i_elem]; + v1v3 += v1[i_elem] * v3[i_elem]; + if (path_type == Z) { + v1v4 += v1[i_elem] * v4[i_elem]; + v4v4 += v4[i_elem] * v4[i_elem]; + } + } + f = (cvm::sqrt(v1v3 * v1v3 - v3v3 * (v1v1 - v2v2)) - v1v3) / v3v3; + if (path_type == Z) { + dx = 0.5 * (f - 1); + zz = v1v1 + 2 * dx * v1v4 + dx * dx * v4v4; + if (use_z_square) { + z = zz; + } else { + z = cvm::sqrt(cvm::fabs(zz)); + } + } + if (path_type == S) { + s = m/M + static_cast(sign) * ((f - 1) / (2 * M)); + } +} + +template +void GeometricPathBase::computeDerivatives() { + const scalar_type factor1 = 1.0 / (2.0 * v3v3 * cvm::sqrt(v1v3 * v1v3 - v3v3 * (v1v1 - v2v2))); + const scalar_type factor2 = 1.0 / v3v3; + for (size_t i_elem = 0; i_elem < v1.size(); ++i_elem) { + // Compute the derivative of f with vector v1 + dfdv1[i_elem] = factor1 * (2.0 * v1v3 * v3[i_elem] - 2.0 * v3v3 * v1[i_elem]) - factor2 * v3[i_elem]; + // Compute the derivative of f with respect to vector v2 + dfdv2[i_elem] = factor1 * (2.0 * v3v3 * v2[i_elem]); + // dZ(v1(r), v2(r), v3) / dr = ∂Z/∂v1 * dv1/dr + ∂Z/∂v2 * dv2/dr + // dv1/dr = [fitting matrix 1][-1, ..., -1] + // dv2/dr = [fitting matrix 2][1, ..., 1] + // ∂Z/∂v1 = 1/(2*z) * (2v1 + (f-1)v4 + (v1â‹…v4)∂f/∂v1 + v4^2 * 1/4 * 2(f-1) * ∂f/∂v1) + // ∂Z/∂v2 = 1/(2*z) * ((v1â‹…v4)∂f/∂v2 + v4^2 * 1/4 * 2(f-1) * ∂f/∂v2) + if (path_type == Z) { + if (use_z_square) { + dzdv1[i_elem] = 2.0 * v1[i_elem] + (f-1) * v4[i_elem] + v1v4 * dfdv1[i_elem] + v4v4 * 0.25 * 2.0 * (f-1) * dfdv1[i_elem]; + dzdv2[i_elem] = v1v4 * dfdv2[i_elem] + v4v4 * 0.25 * 2.0 * (f-1) * dfdv2[i_elem]; + } else { + if (z > static_cast(0)) { + dzdv1[i_elem] = (1.0 / (2.0 * z)) * (2.0 * v1[i_elem] + (f-1) * v4[i_elem] + v1v4 * dfdv1[i_elem] + v4v4 * 0.25 * 2.0 * (f-1) * dfdv1[i_elem]); + dzdv2[i_elem] = (1.0 / (2.0 * z)) * (v1v4 * dfdv2[i_elem] + v4v4 * 0.25 * 2.0 * (f-1) * dfdv2[i_elem]); + } else { + // workaround at z = 0 + dzdv1[i_elem] = 0; + dzdv2[i_elem] = 0; + } + } + } + } +} + +} + +#endif // GEOMETRICPATHCV_H diff --git a/src/external/colvars/colvar_neuralnetworkcompute.cpp b/src/external/colvars/colvar_neuralnetworkcompute.cpp new file mode 100644 index 00000000000..b77db0cfa3e --- /dev/null +++ b/src/external/colvars/colvar_neuralnetworkcompute.cpp @@ -0,0 +1,310 @@ +// -*- Mode:c++; c-basic-offset: 4; -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include + +#include "colvar_neuralnetworkcompute.h" +#include "colvarparse.h" +#include "colvarproxy.h" + +namespace neuralnetworkCV { +std::map, std::function>> activation_function_map +{ + {"tanh", {[](double x){return std::tanh(x);}, + [](double x){return 1.0 - std::tanh(x) * std::tanh(x);}}}, + {"sigmoid", {[](double x){return 1.0 / (1.0 + std::exp(-x));}, + [](double x){return std::exp(-x) / ((1.0 + std::exp(-x)) * (1.0 + std::exp(-x)));}}}, + {"linear", {[](double x){return x;}, + [](double /*x*/){return 1.0;}}}, + {"relu", {[](double x){return x < 0. ? 0. : x;}, + [](double x){return x < 0. ? 0. : 1.;}}}, + {"lrelu100", {[](double x){return x < 0. ? 0.01 * x : x;}, + [](double x){return x < 0. ? 0.01 : 1.;}}}, + {"elu", {[](double x){return x < 0. ? std::exp(x)-1. : x;}, + [](double x){return x < 0. ? std::exp(x) : 1.;}}} +}; + +#ifdef LEPTON +customActivationFunction::customActivationFunction(): +expression(), value_evaluator(nullptr), gradient_evaluator(nullptr), +input_reference(nullptr), derivative_reference(nullptr) {} + +customActivationFunction::customActivationFunction(const std::string& expression_string): +expression(), value_evaluator(nullptr), gradient_evaluator(nullptr), +input_reference(nullptr), derivative_reference(nullptr) { + setExpression(expression_string); +} + +customActivationFunction::customActivationFunction(const customActivationFunction& source): +expression(), value_evaluator(nullptr), gradient_evaluator(nullptr), +input_reference(nullptr), derivative_reference(nullptr) { + // check if the source object is initialized + if (source.value_evaluator != nullptr) { + this->setExpression(source.expression); + } +} + +customActivationFunction& customActivationFunction::operator=(const customActivationFunction& source) { + if (source.value_evaluator != nullptr) { + this->setExpression(source.expression); + } else { + expression = std::string(); + value_evaluator = nullptr; + gradient_evaluator = nullptr; + input_reference = nullptr; + derivative_reference = nullptr; + } + return *this; +} + +void customActivationFunction::setExpression(const std::string& expression_string) { + expression = expression_string; + Lepton::ParsedExpression parsed_expression; + // the variable must be "x" for the input of an activation function + const std::string activation_input_variable{"x"}; + // parse the expression + try { + parsed_expression = Lepton::Parser::parse(expression); + } catch (...) { + cvm::error("Error parsing or compiling expression \"" + expression + "\".\n", COLVARS_INPUT_ERROR); + } + // compile the expression + try { + value_evaluator = std::unique_ptr(new Lepton::CompiledExpression(parsed_expression.createCompiledExpression())); + } catch (...) { + cvm::error("Error compiling expression \"" + expression + "\".\n", COLVARS_INPUT_ERROR); + } + // create a compiled expression for the derivative + try { + gradient_evaluator = std::unique_ptr(new Lepton::CompiledExpression(parsed_expression.differentiate(activation_input_variable).createCompiledExpression())); + } catch (...) { + cvm::error("Error creating compiled expression for variable \"" + activation_input_variable + "\".\n", COLVARS_INPUT_ERROR); + } + // get the reference to the input variable in the compiled expression + try { + input_reference = &(value_evaluator->getVariableReference(activation_input_variable)); + } catch (...) { + cvm::error("Error on getting the reference to variable \"" + activation_input_variable + "\" in the compiled expression.\n", COLVARS_INPUT_ERROR); + } + // get the reference to the input variable in the compiled derivative expression + try { + derivative_reference = &(gradient_evaluator->getVariableReference(activation_input_variable)); + } catch (...) { + cvm::error("Error on getting the reference to variable \"" + activation_input_variable + "\" in the compiled derivative exprssion.\n", COLVARS_INPUT_ERROR); + } +} + +std::string customActivationFunction::getExpression() const { + return expression; +} + +double customActivationFunction::evaluate(double x) const { + *input_reference = x; + return value_evaluator->evaluate(); +} + +double customActivationFunction::derivative(double x) const { + *derivative_reference = x; + return gradient_evaluator->evaluate(); +} +#endif + +denseLayer::denseLayer(const std::string& weights_file, const std::string& biases_file, const std::function& f, const std::function& df): m_activation_function(f), m_activation_function_derivative(df) { +#ifdef LEPTON + m_use_custom_activation = false; +#endif + readFromFile(weights_file, biases_file); +} + +#ifdef LEPTON +denseLayer::denseLayer(const std::string& weights_file, const std::string& biases_file, const std::string& custom_activation_expression) { + m_use_custom_activation = true; + m_custom_activation_function = customActivationFunction(custom_activation_expression); + readFromFile(weights_file, biases_file); +} +#endif + +void denseLayer::readFromFile(const std::string& weights_file, const std::string& biases_file) { + // parse weights file + m_weights.clear(); + m_biases.clear(); + std::string line; + colvarproxy *proxy = cvm::main()->proxy; + auto &ifs_weights = proxy->input_stream(weights_file, "weights file"); + while (std::getline(ifs_weights, line)) { + if (!ifs_weights) { + throw std::runtime_error("I/O error while reading " + weights_file); + } + std::vector splitted_data; + colvarparse::split_string(line, std::string{" "}, splitted_data); + if (splitted_data.size() > 0) { + std::vector weights_tmp(splitted_data.size()); + for (size_t i = 0; i < splitted_data.size(); ++i) { + try { + weights_tmp[i] = std::stod(splitted_data[i]); + } catch (...) { + throw std::runtime_error("Cannot convert " + splitted_data[i] + " to a number while reading file " + weights_file); + } + } + m_weights.push_back(weights_tmp); + } + } + proxy->close_input_stream(weights_file); + + // parse biases file + auto &ifs_biases = proxy->input_stream(biases_file, "biases file"); + while (std::getline(ifs_biases, line)) { + if (!ifs_biases) { + throw std::runtime_error("I/O error while reading " + biases_file); + } + std::vector splitted_data; + colvarparse::split_string(line, std::string{" "}, splitted_data); + if (splitted_data.size() > 0) { + double bias = 0; + try { + bias = std::stod(splitted_data[0]); + } catch (...) { + throw std::runtime_error("Cannot convert " + splitted_data[0] + " to a number while reading file " + biases_file); + } + m_biases.push_back(bias); + } + } + proxy->close_input_stream(biases_file); + + m_input_size = m_weights[0].size(); + m_output_size = m_weights.size(); +} + +void denseLayer::setActivationFunction(const std::function& f, const std::function& df) { + m_activation_function = f; + m_activation_function_derivative = df; +} + +void denseLayer::compute(const std::vector& input, std::vector& output) const { + for (size_t i = 0; i < m_output_size; ++i) { + output[i] = 0; + for (size_t j = 0; j < m_input_size; ++j) { + output[i] += input[j] * m_weights[i][j]; + } + output[i] += m_biases[i]; +#ifdef LEPTON + if (m_use_custom_activation) { + output[i] = m_custom_activation_function.evaluate(output[i]); + } else { +#endif + output[i] = m_activation_function(output[i]); +#ifdef LEPTON + } +#endif + } +} + +double denseLayer::computeGradientElement(const std::vector& input, const size_t i, const size_t j) const { + double sum_with_bias = 0; + for (size_t j_in = 0; j_in < m_input_size; ++j_in) { + sum_with_bias += input[j_in] * m_weights[i][j_in]; + } + sum_with_bias += m_biases[i]; +#ifdef LEPTON + if (m_use_custom_activation) { + const double grad_ij = m_custom_activation_function.derivative(sum_with_bias) * m_weights[i][j]; + return grad_ij; + } else { +#endif + const double grad_ij = m_activation_function_derivative(sum_with_bias) * m_weights[i][j]; + return grad_ij; +#ifdef LEPTON + } +#endif +} + +void denseLayer::computeGradient(const std::vector& input, std::vector>& output_grad) const { + for (size_t j = 0; j < m_input_size; ++j) { + for (size_t i = 0; i < m_output_size; ++i) { + output_grad[i][j] = computeGradientElement(input, i, j); + } + } +} + +neuralNetworkCompute::neuralNetworkCompute(const std::vector& dense_layers): m_dense_layers(dense_layers) { + m_layers_output.resize(m_dense_layers.size()); + m_grads_tmp.resize(m_dense_layers.size()); + for (size_t i_layer = 0; i_layer < m_layers_output.size(); ++i_layer) { + m_layers_output[i_layer].assign(m_dense_layers[i_layer].getOutputSize(), 0); + m_grads_tmp[i_layer].assign(m_dense_layers[i_layer].getOutputSize(), std::vector(m_dense_layers[i_layer].getInputSize(), 0)); + } +} + +bool neuralNetworkCompute::addDenseLayer(const denseLayer& layer) { + if (m_dense_layers.empty()) { + // add layer to this ann directly if m_dense_layers is empty + m_dense_layers.push_back(layer); + m_layers_output.push_back(std::vector(layer.getOutputSize())); + m_grads_tmp.push_back(std::vector>(layer.getOutputSize(), std::vector(layer.getInputSize(), 0))); + return true; + } else { + // otherwise, we need to check if the output of last layer in m_dense_layers matches the input of layer to be added + if (m_dense_layers.back().getOutputSize() == layer.getInputSize()) { + m_dense_layers.push_back(layer); + m_layers_output.push_back(std::vector(layer.getOutputSize())); + m_grads_tmp.push_back(std::vector>(layer.getOutputSize(), std::vector(layer.getInputSize(), 0))); + return true; + } else { + return false; + } + } +} + +std::vector> neuralNetworkCompute::multiply_matrix(const std::vector>& A, const std::vector>& B) { + const size_t m = A.size(); + const size_t n = B.size(); + if (A[0].size() != n) { + std::cerr << "Error on multiplying matrices!\n"; + } + const size_t t = B[0].size(); + std::vector> C(m, std::vector(t, 0.0)); + for (size_t i = 0; i < m; ++i) { + for (size_t k = 0; k < n; ++k) { + const auto tmp = A[i][k]; + auto& C_i = C[i]; + auto& B_k = B[k]; + for (size_t j = 0; j < t; ++j) { + C_i[j] += tmp * B_k[j]; + } + } + } + return C; +} + +void neuralNetworkCompute::compute() { + if (m_dense_layers.empty()) { + return; + } + size_t i_layer; + m_dense_layers[0].compute(m_input, m_layers_output[0]); + for (i_layer = 1; i_layer < m_dense_layers.size(); ++i_layer) { + m_dense_layers[i_layer].compute(m_layers_output[i_layer - 1], m_layers_output[i_layer]); + } + // gradients of each layer + m_dense_layers[0].computeGradient(m_input, m_grads_tmp[0]); + for (i_layer = 1; i_layer < m_dense_layers.size(); ++i_layer) { + m_dense_layers[i_layer].computeGradient(m_layers_output[i_layer - 1], m_grads_tmp[i_layer]); + } + // chain rule + if (m_dense_layers.size() > 1) { + m_chained_grad = multiply_matrix(m_grads_tmp[1], m_grads_tmp[0]); + for (i_layer = 2; i_layer < m_dense_layers.size(); ++i_layer) { + m_chained_grad = multiply_matrix(m_grads_tmp[i_layer], m_chained_grad); + } + } else { + m_chained_grad = m_grads_tmp[0]; + } +} +} diff --git a/src/external/colvars/colvar_neuralnetworkcompute.h b/src/external/colvars/colvar_neuralnetworkcompute.h new file mode 100644 index 00000000000..575ce3b40b4 --- /dev/null +++ b/src/external/colvars/colvar_neuralnetworkcompute.h @@ -0,0 +1,146 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef NEURALNETWORKCOMPUTE_H +#define NEURALNETWORKCOMPUTE_H + +#include +#include +#include +#include +#include +#include + +#ifdef LEPTON +#include "Lepton.h" +#endif + +namespace neuralnetworkCV { +/// mapping from a string to the activation function and its derivative +extern std::map, std::function>> activation_function_map; + +#ifdef LEPTON +// allow to define a custom activation function +class customActivationFunction { +public: + /// empty constructor + customActivationFunction(); + /// construct by an mathematical expression + customActivationFunction(const std::string& expression_string); + /// copy constructor + customActivationFunction(const customActivationFunction& source); + /// overload assignment operator + customActivationFunction& operator=(const customActivationFunction& source); + /// setter for the custom expression + void setExpression(const std::string& expression_string); + /// getter for the custom expression + std::string getExpression() const; + /// evaluate the value of an expression + double evaluate(double x) const; + /// evaluate the gradient of an expression + double derivative(double x) const; +private: + std::string expression; + std::unique_ptr value_evaluator; + std::unique_ptr gradient_evaluator; + double* input_reference; + double* derivative_reference; +}; +#endif + +class denseLayer { +private: + size_t m_input_size; + size_t m_output_size; + std::function m_activation_function; + std::function m_activation_function_derivative; +#ifdef LEPTON + bool m_use_custom_activation; + customActivationFunction m_custom_activation_function; +#else + static const bool m_use_custom_activation = false; +#endif + /// weights[i][j] is the weight of the i-th output and the j-th input + std::vector> m_weights; + /// bias of each node + std::vector m_biases; +public: + /// empty constructor + denseLayer() {} + /*! @param[in] weights_file filename of the weights file + * @param[in] biases_file filename of the biases file + * @param[in] f activation function + * @param[in] df derivative of the activation function + */ + denseLayer(const std::string& weights_file, const std::string& biases_file, const std::function& f, const std::function& df); +#ifdef LEPTON + /*! @param[in] weights_file filename of the weights file + * @param[in] biases_file filename of the biases file + * @param[in] custom_activation_expression the expression of the custom activation function + */ + denseLayer(const std::string& weights_file, const std::string& biases_file, const std::string& custom_activation_expression); +#endif + /// read data from file + void readFromFile(const std::string& weights_file, const std::string& biases_file); + /// setup activation function + void setActivationFunction(const std::function& f, const std::function& df); + /// compute the value of this layer + void compute(const std::vector& input, std::vector& output) const; + /// compute the gradient of i-th output wrt j-th input + double computeGradientElement(const std::vector& input, const size_t i, const size_t j) const; + /// output[i][j] is the gradient of i-th output wrt j-th input + void computeGradient(const std::vector& input, std::vector>& output_grad) const; + /// get the input size + size_t getInputSize() const { + return m_input_size; + } + /// get the output size + size_t getOutputSize() const { + return m_output_size; + } + /// getter for weights and biases + double getWeight(size_t i, size_t j) const { + return m_weights[i][j]; + } + double getBias(size_t i) const { + return m_biases[i]; + } + ~denseLayer() {} +}; + +class neuralNetworkCompute { +private: + std::vector m_dense_layers; + std::vector m_input; + /// temporary output for each layer, useful to speedup the gradients' calculation + std::vector> m_layers_output; + std::vector>> m_grads_tmp; + std::vector> m_chained_grad; +private: + /// helper function: multiply two matrix constructed from 2D vector + static std::vector> multiply_matrix(const std::vector>& A, const std::vector>& B); +public: + neuralNetworkCompute(): m_dense_layers(0), m_layers_output(0) {} + neuralNetworkCompute(const std::vector& dense_layers); + bool addDenseLayer(const denseLayer& layer); + // for faster computation + const std::vector& input() const {return m_input;} + std::vector& input() {return m_input;} + /// compute the values and the gradients of all output nodes + void compute(); + double getOutput(const size_t i) const {return m_layers_output.back()[i];} + double getGradient(const size_t i, const size_t j) const {return m_chained_grad[i][j];} + /// get a specified layer + const denseLayer& getLayer(const size_t i) const {return m_dense_layers[i];} + /// get the number of layers + size_t getNumberOfLayers() const {return m_dense_layers.size();} +}; + +} +#endif diff --git a/src/external/colvars/colvar_rotation_derivative.h b/src/external/colvars/colvar_rotation_derivative.h new file mode 100644 index 00000000000..50f4f1aa97f --- /dev/null +++ b/src/external/colvars/colvar_rotation_derivative.h @@ -0,0 +1,627 @@ +#ifndef COLVAR_ROTATION_DERIVATIVE +#define COLVAR_ROTATION_DERIVATIVE + +#include "colvartypes.h" +#include +#include + +/// \brief Helper function for loading the ia-th atom in the vector pos to x, y and z (C++11 SFINAE is used) +template ::value, bool>::type = true> +inline void read_atom_coord( + size_t ia, const std::vector& pos, + cvm::real* x, cvm::real* y, cvm::real* z) { + *x = pos[ia].x; + *y = pos[ia].y; + *z = pos[ia].z; +} + +template ::value, bool>::type = true> +inline void read_atom_coord( + size_t ia, const std::vector& pos, + cvm::real* x, cvm::real* y, cvm::real* z) { + *x = pos[ia].pos.x; + *y = pos[ia].pos.y; + *z = pos[ia].pos.z; +} + +/// \brief Helper enum class for specifying options in rotation_derivative::prepare_derivative +enum class rotation_derivative_dldq { + /// Require the derivative of the leading eigenvalue with respect to the atom coordinats + use_dl = 1 << 0, + /// Require the derivative of the leading eigenvector with respect to the atom coordinats + use_dq = 1 << 1 +}; + +inline constexpr rotation_derivative_dldq operator|(rotation_derivative_dldq Lhs, rotation_derivative_dldq Rhs) { + return static_cast( + static_cast::type>(Lhs) | + static_cast::type>(Rhs)); +} + +inline constexpr bool operator&(rotation_derivative_dldq Lhs, rotation_derivative_dldq Rhs) +{ + return (static_cast::type>(Lhs) & + static_cast::type>(Rhs)); +} + +/// \brief Helper class for calculating the derivative of rotation +template +struct rotation_derivative { + static_assert(std::is_same::value || std::is_same::value, + "class template rotation_derivative only supports cvm::atom_pos or cvm::atom types."); + static_assert(std::is_same::value || std::is_same::value, + "class template rotation_derivative only supports cvm::atom_pos or cvm::atom types."); + /// \brief Reference to the rotation + const cvm::rotation &m_rot; + /// \brief Reference to the atom positions of group 1 + const std::vector &m_pos1; + /// \brief Reference to the atom positions of group 2 + const std::vector &m_pos2; + /// \brief Temporary variable that will be updated if prepare_derivative called + cvm::real tmp_Q0Q0[4][4]; + cvm::real tmp_Q0Q0_L[4][4][4]; + /*! @brief Constructor of the cvm::rotation::derivative class + * @param[in] rot The cvm::rotation object (must have called + * `calc_optimal_rotation` before calling + * `calc_derivative_wrt_group1` and + * `calc_derivative_wrt_group2`) + * @param[in] pos1 The atom positions of group 1 + * @param[in] pos2 The atom positions of group 2 + */ + rotation_derivative( + const cvm::rotation &rot, + const std::vector &pos1, + const std::vector &pos2): + m_rot(rot), m_pos1(pos1), m_pos2(pos2) {}; + /*! @brief This function must be called before `calc_derivative_wrt_group1` + * and `calc_derivative_wrt_group2` in order to prepare the tmp_Q0Q0 + * and tmp_Q0Q0_L. + * @param[in] require_dl_dq Require the calculation of the derivatives of L or/and Q + * with respect to atoms. + */ + void prepare_derivative(rotation_derivative_dldq require_dl_dq) { + if (require_dl_dq & rotation_derivative_dldq::use_dl) { + const auto &Q0 = m_rot.S_eigvec[0]; + tmp_Q0Q0[0][0] = Q0[0] * Q0[0]; + tmp_Q0Q0[0][1] = Q0[0] * Q0[1]; + tmp_Q0Q0[0][2] = Q0[0] * Q0[2]; + tmp_Q0Q0[0][3] = Q0[0] * Q0[3]; + tmp_Q0Q0[1][0] = Q0[1] * Q0[0]; + tmp_Q0Q0[1][1] = Q0[1] * Q0[1]; + tmp_Q0Q0[1][2] = Q0[1] * Q0[2]; + tmp_Q0Q0[1][3] = Q0[1] * Q0[3]; + tmp_Q0Q0[2][0] = Q0[2] * Q0[0]; + tmp_Q0Q0[2][1] = Q0[2] * Q0[1]; + tmp_Q0Q0[2][2] = Q0[2] * Q0[2]; + tmp_Q0Q0[2][3] = Q0[2] * Q0[3]; + tmp_Q0Q0[3][0] = Q0[3] * Q0[0]; + tmp_Q0Q0[3][1] = Q0[3] * Q0[1]; + tmp_Q0Q0[3][2] = Q0[3] * Q0[2]; + tmp_Q0Q0[3][3] = Q0[3] * Q0[3]; + } + if (require_dl_dq & rotation_derivative_dldq::use_dq) { + const auto &Q0 = m_rot.S_eigvec[0]; + const auto &Q1 = m_rot.S_eigvec[1]; + const auto &Q2 = m_rot.S_eigvec[2]; + const auto &Q3 = m_rot.S_eigvec[3]; + cvm::real const L0 = m_rot.S_eigval[0]; + cvm::real const L1 = m_rot.S_eigval[1]; + cvm::real const L2 = m_rot.S_eigval[2]; + cvm::real const L3 = m_rot.S_eigval[3]; + + tmp_Q0Q0_L[0][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][0] = (Q1[0] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][1] = (Q1[0] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[1]) / (L0-L3) * Q3[3]; + + + tmp_Q0Q0_L[0][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][2] = (Q1[0] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][0][3] = (Q1[0] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[0] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[0] * Q0[3]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][0] = (Q1[1] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][1] = (Q1[1] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[1]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][2] = (Q1[1] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][1][3] = (Q1[1] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[1] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[1] * Q0[3]) / (L0-L3) * Q3[3]; + + + tmp_Q0Q0_L[0][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][0] = (Q1[2] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][1] = (Q1[2] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[1]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][2] = (Q1[2] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][2][3] = (Q1[2] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[2] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[2] * Q0[3]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][0] = (Q1[3] * Q0[0]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[0]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[0]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][1] = (Q1[3] * Q0[1]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[1]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[1]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][2] = (Q1[3] * Q0[2]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[2]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[2]) / (L0-L3) * Q3[3]; + + tmp_Q0Q0_L[0][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[0] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[0] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[0]; + tmp_Q0Q0_L[1][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[1] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[1] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[1]; + tmp_Q0Q0_L[2][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[2] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[2] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[2]; + tmp_Q0Q0_L[3][3][3] = (Q1[3] * Q0[3]) / (L0-L1) * Q1[3] + + (Q2[3] * Q0[3]) / (L0-L2) * Q2[3] + + (Q3[3] * Q0[3]) / (L0-L3) * Q3[3]; + } + } + /*! @brief Actual implementation of the derivative calculation + * @param[in] ds The derivative of matrix S with respect to an atom of + * either group 1 or group 2 + * @param[out] dl0_out The output of derivative of L + * @param[out] dq0_out The output of derivative of Q + * @param[out] ds_out The output of derivative of overlap matrix S + */ + void calc_derivative_impl( + const cvm::rvector (&ds)[4][4], + cvm::rvector* const dl0_out, + cvm::vector1d* const dq0_out, + cvm::matrix2d* const ds_out) const { + if (ds_out != nullptr) { + // this code path is for debug_gradients, so not necessary to unroll the loop + *ds_out = cvm::matrix2d(4, 4); + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + (*ds_out)[i][j] = ds[i][j]; + } + } + } + if (dl0_out != nullptr) { + /* manually loop unrolling of the following loop: + dl0_1.reset(); + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + dl0_1 += Q0[i] * ds_1[i][j] * Q0[j]; + } + } + */ + *dl0_out = tmp_Q0Q0[0][0] * ds[0][0] + + tmp_Q0Q0[0][1] * ds[0][1] + + tmp_Q0Q0[0][2] * ds[0][2] + + tmp_Q0Q0[0][3] * ds[0][3] + + tmp_Q0Q0[1][0] * ds[1][0] + + tmp_Q0Q0[1][1] * ds[1][1] + + tmp_Q0Q0[1][2] * ds[1][2] + + tmp_Q0Q0[1][3] * ds[1][3] + + tmp_Q0Q0[2][0] * ds[2][0] + + tmp_Q0Q0[2][1] * ds[2][1] + + tmp_Q0Q0[2][2] * ds[2][2] + + tmp_Q0Q0[2][3] * ds[2][3] + + tmp_Q0Q0[3][0] * ds[3][0] + + tmp_Q0Q0[3][1] * ds[3][1] + + tmp_Q0Q0[3][2] * ds[3][2] + + tmp_Q0Q0[3][3] * ds[3][3]; + } + if (dq0_out != nullptr) { + // we can skip this check if a fixed-size array is used + if (dq0_out->size() != 4) dq0_out->resize(4); + /* manually loop unrolling of the following loop: + dq0_1.reset(); + for (size_t p = 0; p < 4; p++) { + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + dq0_1[p] += + (Q1[i] * ds_1[i][j] * Q0[j]) / (L0-L1) * Q1[p] + + (Q2[i] * ds_1[i][j] * Q0[j]) / (L0-L2) * Q2[p] + + (Q3[i] * ds_1[i][j] * Q0[j]) / (L0-L3) * Q3[p]; + } + } + } + */ + (*dq0_out)[0] = tmp_Q0Q0_L[0][0][0] * ds[0][0] + + tmp_Q0Q0_L[0][0][1] * ds[0][1] + + tmp_Q0Q0_L[0][0][2] * ds[0][2] + + tmp_Q0Q0_L[0][0][3] * ds[0][3] + + tmp_Q0Q0_L[0][1][0] * ds[1][0] + + tmp_Q0Q0_L[0][1][1] * ds[1][1] + + tmp_Q0Q0_L[0][1][2] * ds[1][2] + + tmp_Q0Q0_L[0][1][3] * ds[1][3] + + tmp_Q0Q0_L[0][2][0] * ds[2][0] + + tmp_Q0Q0_L[0][2][1] * ds[2][1] + + tmp_Q0Q0_L[0][2][2] * ds[2][2] + + tmp_Q0Q0_L[0][2][3] * ds[2][3] + + tmp_Q0Q0_L[0][3][0] * ds[3][0] + + tmp_Q0Q0_L[0][3][1] * ds[3][1] + + tmp_Q0Q0_L[0][3][2] * ds[3][2] + + tmp_Q0Q0_L[0][3][3] * ds[3][3]; + + (*dq0_out)[1] = tmp_Q0Q0_L[1][0][0] * ds[0][0] + + tmp_Q0Q0_L[1][0][1] * ds[0][1] + + tmp_Q0Q0_L[1][0][2] * ds[0][2] + + tmp_Q0Q0_L[1][0][3] * ds[0][3] + + tmp_Q0Q0_L[1][1][0] * ds[1][0] + + tmp_Q0Q0_L[1][1][1] * ds[1][1] + + tmp_Q0Q0_L[1][1][2] * ds[1][2] + + tmp_Q0Q0_L[1][1][3] * ds[1][3] + + tmp_Q0Q0_L[1][2][0] * ds[2][0] + + tmp_Q0Q0_L[1][2][1] * ds[2][1] + + tmp_Q0Q0_L[1][2][2] * ds[2][2] + + tmp_Q0Q0_L[1][2][3] * ds[2][3] + + tmp_Q0Q0_L[1][3][0] * ds[3][0] + + tmp_Q0Q0_L[1][3][1] * ds[3][1] + + tmp_Q0Q0_L[1][3][2] * ds[3][2] + + tmp_Q0Q0_L[1][3][3] * ds[3][3]; + + (*dq0_out)[2] = tmp_Q0Q0_L[2][0][0] * ds[0][0] + + tmp_Q0Q0_L[2][0][1] * ds[0][1] + + tmp_Q0Q0_L[2][0][2] * ds[0][2] + + tmp_Q0Q0_L[2][0][3] * ds[0][3] + + tmp_Q0Q0_L[2][1][0] * ds[1][0] + + tmp_Q0Q0_L[2][1][1] * ds[1][1] + + tmp_Q0Q0_L[2][1][2] * ds[1][2] + + tmp_Q0Q0_L[2][1][3] * ds[1][3] + + tmp_Q0Q0_L[2][2][0] * ds[2][0] + + tmp_Q0Q0_L[2][2][1] * ds[2][1] + + tmp_Q0Q0_L[2][2][2] * ds[2][2] + + tmp_Q0Q0_L[2][2][3] * ds[2][3] + + tmp_Q0Q0_L[2][3][0] * ds[3][0] + + tmp_Q0Q0_L[2][3][1] * ds[3][1] + + tmp_Q0Q0_L[2][3][2] * ds[3][2] + + tmp_Q0Q0_L[2][3][3] * ds[3][3]; + + (*dq0_out)[3] = tmp_Q0Q0_L[3][0][0] * ds[0][0] + + tmp_Q0Q0_L[3][0][1] * ds[0][1] + + tmp_Q0Q0_L[3][0][2] * ds[0][2] + + tmp_Q0Q0_L[3][0][3] * ds[0][3] + + tmp_Q0Q0_L[3][1][0] * ds[1][0] + + tmp_Q0Q0_L[3][1][1] * ds[1][1] + + tmp_Q0Q0_L[3][1][2] * ds[1][2] + + tmp_Q0Q0_L[3][1][3] * ds[1][3] + + tmp_Q0Q0_L[3][2][0] * ds[2][0] + + tmp_Q0Q0_L[3][2][1] * ds[2][1] + + tmp_Q0Q0_L[3][2][2] * ds[2][2] + + tmp_Q0Q0_L[3][2][3] * ds[2][3] + + tmp_Q0Q0_L[3][3][0] * ds[3][0] + + tmp_Q0Q0_L[3][3][1] * ds[3][1] + + tmp_Q0Q0_L[3][3][2] * ds[3][2] + + tmp_Q0Q0_L[3][3][3] * ds[3][3]; + } + } + /*! @brief Calculate the derivatives of S, the leading eigenvalue L and + * the leading eigenvector Q with respect to `m_pos1` + * @param[in] ia The index the of atom + * @param[out] dl0_1_out The output of derivative of L with respect to + * ia-th atom of group 1 + * @param[out] dq0_1_out The output of derivative of Q with respect to + * ia-th atom of group 1 + * @param[out] ds_1_out The output of derivative of overlap matrix S with + * respect to ia-th atom of group 1 + */ + void calc_derivative_wrt_group1( + size_t ia, cvm::rvector* const dl0_1_out = nullptr, + cvm::vector1d* const dq0_1_out = nullptr, + cvm::matrix2d* const ds_1_out = nullptr) const { + if (dl0_1_out == nullptr && dq0_1_out == nullptr) return; + cvm::real a2x, a2y, a2z; + // we can get rid of the helper function read_atom_coord if C++17 (constexpr) is available + read_atom_coord(ia, m_pos2, &a2x, &a2y, &a2z); + cvm::rvector ds_1[4][4]; + ds_1[0][0].set( a2x, a2y, a2z); + ds_1[1][0].set( 0.0, a2z, -a2y); + ds_1[0][1] = ds_1[1][0]; + ds_1[2][0].set(-a2z, 0.0, a2x); + ds_1[0][2] = ds_1[2][0]; + ds_1[3][0].set( a2y, -a2x, 0.0); + ds_1[0][3] = ds_1[3][0]; + ds_1[1][1].set( a2x, -a2y, -a2z); + ds_1[2][1].set( a2y, a2x, 0.0); + ds_1[1][2] = ds_1[2][1]; + ds_1[3][1].set( a2z, 0.0, a2x); + ds_1[1][3] = ds_1[3][1]; + ds_1[2][2].set(-a2x, a2y, -a2z); + ds_1[3][2].set( 0.0, a2z, a2y); + ds_1[2][3] = ds_1[3][2]; + ds_1[3][3].set(-a2x, -a2y, a2z); + calc_derivative_impl(ds_1, dl0_1_out, dq0_1_out, ds_1_out); + } + /*! @brief Calculate the derivatives of S, the leading eigenvalue L and + * the leading eigenvector Q with respect to `m_pos2` + * @param[in] ia The index the of atom + * @param[out] dl0_2_out The output of derivative of L with respect to + * ia-th atom of group 2 + * @param[out] dq0_2_out The output of derivative of Q with respect to + * ia-th atom of group 2 + * @param[out] ds_2_out The output of derivative of overlap matrix S with + * respect to ia-th atom of group 2 + */ + void calc_derivative_wrt_group2( + size_t ia, cvm::rvector* const dl0_2_out = nullptr, + cvm::vector1d* const dq0_2_out = nullptr, + cvm::matrix2d* const ds_2_out = nullptr) const { + if (dl0_2_out == nullptr && dq0_2_out == nullptr) return; + cvm::real a1x, a1y, a1z; + // we can get rid of the helper function read_atom_coord if C++17 (constexpr) is available + read_atom_coord(ia, m_pos1, &a1x, &a1y, &a1z); + cvm::rvector ds_2[4][4]; + ds_2[0][0].set( a1x, a1y, a1z); + ds_2[1][0].set( 0.0, -a1z, a1y); + ds_2[0][1] = ds_2[1][0]; + ds_2[2][0].set( a1z, 0.0, -a1x); + ds_2[0][2] = ds_2[2][0]; + ds_2[3][0].set(-a1y, a1x, 0.0); + ds_2[0][3] = ds_2[3][0]; + ds_2[1][1].set( a1x, -a1y, -a1z); + ds_2[2][1].set( a1y, a1x, 0.0); + ds_2[1][2] = ds_2[2][1]; + ds_2[3][1].set( a1z, 0.0, a1x); + ds_2[1][3] = ds_2[3][1]; + ds_2[2][2].set(-a1x, a1y, -a1z); + ds_2[3][2].set( 0.0, a1z, a1y); + ds_2[2][3] = ds_2[3][2]; + ds_2[3][3].set(-a1x, -a1y, a1z); + calc_derivative_impl(ds_2, dl0_2_out, dq0_2_out, ds_2_out); + } +}; + +/*! @brief Function for debugging gradients (allow using either + * std::vector or std::vector for + * pos1 and pos2) + * @param[in] pos1 Atom positions of group 1 + * @param[in] pos2 Atom positions of group 2 + */ +template +void debug_gradients( + cvm::rotation &rot, + const std::vector &pos1, + const std::vector &pos2) { + static_assert(std::is_same::value || std::is_same::value, ""); + static_assert(std::is_same::value || std::is_same::value, ""); + // eigenvalues and eigenvectors + cvm::real const L0 = rot.S_eigval[0]; + cvm::real const L1 = rot.S_eigval[1]; + cvm::real const L2 = rot.S_eigval[2]; + cvm::real const L3 = rot.S_eigval[3]; + cvm::quaternion const Q0(rot.S_eigvec[0]); + cvm::quaternion const Q1(rot.S_eigvec[1]); + cvm::quaternion const Q2(rot.S_eigvec[2]); + cvm::quaternion const Q3(rot.S_eigvec[3]); + + cvm::log("L0 = "+cvm::to_str(L0, cvm::cv_width, cvm::cv_prec)+ + ", Q0 = "+cvm::to_str(Q0, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q0 = "+cvm::to_str(Q0.inner(Q0), cvm::cv_width, cvm::cv_prec)+ + "\n"); + cvm::log("L1 = "+cvm::to_str(L1, cvm::cv_width, cvm::cv_prec)+ + ", Q1 = "+cvm::to_str(Q1, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q1 = "+cvm::to_str(Q0.inner(Q1), cvm::cv_width, cvm::cv_prec)+ + "\n"); + cvm::log("L2 = "+cvm::to_str(L2, cvm::cv_width, cvm::cv_prec)+ + ", Q2 = "+cvm::to_str(Q2, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q2 = "+cvm::to_str(Q0.inner(Q2), cvm::cv_width, cvm::cv_prec)+ + "\n"); + cvm::log("L3 = "+cvm::to_str(L3, cvm::cv_width, cvm::cv_prec)+ + ", Q3 = "+cvm::to_str(Q3, cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q3 = "+cvm::to_str(Q0.inner(Q3), cvm::cv_width, cvm::cv_prec)+ + "\n"); + + rotation_derivative deriv(rot, pos1, pos2); + cvm::rvector dl0_2; + cvm::vector1d dq0_2(4); + cvm::matrix2d ds_2; +#ifdef COLVARS_LAMMPS + MathEigen::Jacobi *ecalc = + reinterpret_cast *>(rot.jacobi); +#endif + deriv.prepare_derivative(rotation_derivative_dldq::use_dl | rotation_derivative_dldq::use_dq); + cvm::real S_new[4][4]; + cvm::real S_new_eigval[4]; + cvm::real S_new_eigvec[4][4]; + for (size_t ia = 0; ia < pos2.size(); ++ia) { + // cvm::real const &a1x = pos1[ia].x; + // cvm::real const &a1y = pos1[ia].y; + // cvm::real const &a1z = pos1[ia].z; + deriv.calc_derivative_wrt_group2(ia, &dl0_2, &dq0_2, &ds_2); + // make an infitesimal move along each cartesian coordinate of + // this atom, and solve again the eigenvector problem + for (size_t comp = 0; comp < 3; comp++) { + std::memcpy(S_new, rot.S_backup, sizeof(cvm::real) * 4 * 4); + std::memset(S_new_eigval, 0, sizeof(cvm::real) * 4); + std::memset(S_new_eigvec, 0, sizeof(cvm::real) * 4 * 4); + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + S_new[i][j] += + colvarmodule::debug_gradients_step_size * ds_2[i][j][comp]; + } + } +#ifdef COLVARS_LAMMPS + ecalc->Diagonalize(S_new, S_new_eigval, S_new_eigvec); +#else + NR::diagonalize_matrix(S_new, S_new_eigval, S_new_eigvec); +#endif + cvm::real const &L0_new = S_new_eigval[0]; + cvm::quaternion const Q0_new(S_new_eigvec[0]); + + cvm::real const DL0 = (dl0_2[comp]) * colvarmodule::debug_gradients_step_size; + cvm::quaternion const DQ0(dq0_2[0][comp] * colvarmodule::debug_gradients_step_size, + dq0_2[1][comp] * colvarmodule::debug_gradients_step_size, + dq0_2[2][comp] * colvarmodule::debug_gradients_step_size, + dq0_2[3][comp] * colvarmodule::debug_gradients_step_size); + + cvm::log( "|(l_0+dl_0) - l_0^new|/l_0 = "+ + cvm::to_str(cvm::fabs(L0+DL0 - L0_new)/L0, cvm::cv_width, cvm::cv_prec)+ + ", |(q_0+dq_0) - q_0^new| = "+ + cvm::to_str((Q0+DQ0 - Q0_new).norm(), cvm::cv_width, cvm::cv_prec)+ + "\n"); + } + } +} + +#endif // COLVAR_ROTATION_DERIVATIVE diff --git a/src/external/colvars/colvaratoms.cpp b/src/external/colvars/colvaratoms.cpp new file mode 100644 index 00000000000..534ca967b98 --- /dev/null +++ b/src/external/colvars/colvaratoms.cpp @@ -0,0 +1,1475 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include +#include +#include + +#include "colvardeps.h" +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarparse.h" +#include "colvaratoms.h" +#include "colvar_rotation_derivative.h" + + +cvm::atom::atom() +{ + index = -1; + id = -1; + mass = 1.0; + charge = 0.0; + reset_data(); +} + + +cvm::atom::atom(int atom_number) +{ + colvarproxy *p = cvm::main()->proxy; + index = p->init_atom(atom_number); + id = p->get_atom_id(index); + update_mass(); + update_charge(); + reset_data(); +} + + +cvm::atom::atom(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id) +{ + colvarproxy *p = cvm::main()->proxy; + index = p->init_atom(residue, atom_name, segment_id); + id = p->get_atom_id(index); + update_mass(); + update_charge(); + reset_data(); +} + + +cvm::atom::atom(atom const &a) + : index(a.index) +{ + colvarproxy *p = cvm::main()->proxy; + id = p->get_atom_id(index); + p->increase_refcount(index); + update_mass(); + update_charge(); + reset_data(); +} + + +cvm::atom::~atom() +{ + if (index >= 0) { + (cvm::main()->proxy)->clear_atom(index); + } +} + + +cvm::atom & cvm::atom::operator = (cvm::atom const &a) +{ + index = a.index; + id = (cvm::main()->proxy)->get_atom_id(index); + update_mass(); + update_charge(); + reset_data(); + return *this; +} + + + +cvm::atom_group::atom_group() +{ + init(); +} + + +cvm::atom_group::atom_group(char const *key_in) +{ + key = key_in; + init(); +} + + +cvm::atom_group::atom_group(std::vector const &atoms_in) +{ + init(); + atoms = atoms_in; + setup(); +} + + +cvm::atom_group::~atom_group() +{ + if (is_enabled(f_ag_scalable) && !b_dummy) { + (cvm::main()->proxy)->clear_atom_group(index); + index = -1; + } + + if (fitting_group) { + delete fitting_group; + fitting_group = NULL; + } + + if (rot_deriv != nullptr) { + delete rot_deriv; + rot_deriv = nullptr; + } + + cvm::main()->unregister_named_atom_group(this); +} + + +int cvm::atom_group::add_atom(cvm::atom const &a) +{ + if (a.id < 0) { + return COLVARS_ERROR; + } + + for (size_t i = 0; i < atoms_ids.size(); i++) { + if (atoms_ids[i] == a.id) { + if (cvm::debug()) + cvm::log("Discarding doubly counted atom with number "+ + cvm::to_str(a.id+1)+".\n"); + return COLVARS_OK; + } + } + + // for consistency with add_atom_id(), we update the list as well + atoms_ids.push_back(a.id); + atoms.push_back(a); + total_mass += a.mass; + total_charge += a.charge; + + return COLVARS_OK; +} + + +int cvm::atom_group::add_atom_id(int aid) +{ + if (aid < 0) { + return COLVARS_ERROR; + } + + for (size_t i = 0; i < atoms_ids.size(); i++) { + if (atoms_ids[i] == aid) { + if (cvm::debug()) + cvm::log("Discarding doubly counted atom with number "+ + cvm::to_str(aid+1)+".\n"); + return COLVARS_OK; + } + } + + atoms_ids.push_back(aid); + return COLVARS_OK; +} + + +int cvm::atom_group::remove_atom(cvm::atom_iter ai) +{ + if (is_enabled(f_ag_scalable)) { + cvm::error("Error: cannot remove atoms from a scalable group.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + if (!this->size()) { + cvm::error("Error: trying to remove an atom from an empty group.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } else { + total_mass -= ai->mass; + total_charge -= ai->charge; + atoms_ids.erase(atoms_ids.begin() + (ai - atoms.begin())); + atoms.erase(ai); + } + + return COLVARS_OK; +} + + +int cvm::atom_group::set_dummy() +{ + if (atoms_ids.size() > 0) { + return cvm::error("Error: setting group with keyword \""+key+ + "\" and name \""+name+"\" as dummy, but it already " + "contains atoms.\n", COLVARS_INPUT_ERROR); + } + b_dummy = true; + return COLVARS_OK; +} + + +int cvm::atom_group::set_dummy_pos(cvm::atom_pos const &pos) +{ + if (b_dummy) { + dummy_atom_pos = pos; + } else { + return cvm::error("Error: setting dummy position for group with keyword \""+ + key+"\" and name \""+name+ + "\", but it is not dummy.\n", COLVARS_INPUT_ERROR); + } + return COLVARS_OK; +} + + +int cvm::atom_group::init() +{ + if (!key.size()) key = "unnamed"; + description = "atom group " + key; + // These may be overwritten by parse(), if a name is provided + + atoms.clear(); + atom_group::init_dependencies(); + index = -1; + + b_dummy = false; + b_user_defined_fit = false; + fitting_group = NULL; + rot_deriv = nullptr; + + noforce = false; + + total_mass = 0.0; + total_charge = 0.0; + + cog.reset(); + com.reset(); + + return COLVARS_OK; +} + + +int cvm::atom_group::init_dependencies() { + size_t i; + // Initialize static array once and for all + if (features().size() == 0) { + for (i = 0; i < f_ag_ntot; i++) { + modify_features().push_back(new feature); + } + + init_feature(f_ag_active, "active", f_type_dynamic); + init_feature(f_ag_center, "center_to_reference", f_type_user); + init_feature(f_ag_center_origin, "center_to_origin", f_type_user); + init_feature(f_ag_rotate, "rotate_to_origin", f_type_user); + init_feature(f_ag_fitting_group, "fitting_group", f_type_static); + init_feature(f_ag_explicit_gradient, "explicit_atom_gradient", f_type_dynamic); + init_feature(f_ag_fit_gradients, "fit_gradients", f_type_user); + require_feature_self(f_ag_fit_gradients, f_ag_explicit_gradient); + + init_feature(f_ag_atom_forces, "atomic_forces", f_type_dynamic); + + // parallel calculation implies that we have at least a scalable center of mass, + // but f_ag_scalable is kept as a separate feature to deal with future dependencies + init_feature(f_ag_scalable, "scalable_group", f_type_dynamic); + init_feature(f_ag_scalable_com, "scalable_group_center_of_mass", f_type_static); + require_feature_self(f_ag_scalable_com, f_ag_scalable); + + init_feature(f_ag_collect_atom_ids, "collect_atom_ids", f_type_dynamic); + exclude_feature_self(f_ag_collect_atom_ids, f_ag_scalable); + + // check that everything is initialized + for (i = 0; i < colvardeps::f_ag_ntot; i++) { + if (is_not_set(i)) { + cvm::error("Uninitialized feature " + cvm::to_str(i) + " in " + description); + } + } + } + + // Initialize feature_states for each instance + // default as unavailable, not enabled + feature_states.reserve(f_ag_ntot); + for (i = 0; i < colvardeps::f_ag_ntot; i++) { + feature_states.push_back(feature_state(false, false)); + } + + // Features that are implemented (or not) by all atom groups + feature_states[f_ag_active].available = true; + feature_states[f_ag_center].available = true; + feature_states[f_ag_center_origin].available = true; + feature_states[f_ag_rotate].available = true; + + // f_ag_scalable_com is provided by the CVC iff it is COM-based + feature_states[f_ag_scalable_com].available = false; + feature_states[f_ag_scalable].available = true; + feature_states[f_ag_fit_gradients].available = true; + feature_states[f_ag_fitting_group].available = true; + feature_states[f_ag_explicit_gradient].available = true; + feature_states[f_ag_collect_atom_ids].available = true; + + return COLVARS_OK; +} + + +int cvm::atom_group::setup() +{ + if (atoms_ids.size() == 0) { + atoms_ids.reserve(atoms.size()); + for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { + atoms_ids.push_back(ai->id); + } + } + for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { + ai->update_mass(); + ai->update_charge(); + } + update_total_mass(); + update_total_charge(); + return COLVARS_OK; +} + +void cvm::atom_group::setup_rotation_derivative() { + if (rot_deriv != nullptr) delete rot_deriv; + rot_deriv = new rotation_derivative( + rot, fitting_group ? fitting_group->atoms : this->atoms, ref_pos + ); +} + + +void cvm::atom_group::update_total_mass() +{ + if (b_dummy) { + total_mass = 1.0; + return; + } + + if (is_enabled(f_ag_scalable)) { + total_mass = (cvm::main()->proxy)->get_atom_group_mass(index); + } else { + total_mass = 0.0; + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + total_mass += ai->mass; + } + } + if (total_mass < 1e-15) { + cvm::error("ERROR: " + description + " has zero total mass.\n"); + } +} + + +void cvm::atom_group::update_total_charge() +{ + if (b_dummy) { + total_charge = 0.0; + return; + } + + if (is_enabled(f_ag_scalable)) { + total_charge = (cvm::main()->proxy)->get_atom_group_charge(index); + } else { + total_charge = 0.0; + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + total_charge += ai->charge; + } + } +} + + +void cvm::atom_group::print_properties(std::string const &colvar_name, + int i, int j) +{ + if (cvm::proxy->updated_masses() && cvm::proxy->updated_charges()) { + cvm::log("Re-initialized atom group for variable \""+colvar_name+"\":"+ + cvm::to_str(i)+"/"+ + cvm::to_str(j)+". "+ cvm::to_str(atoms_ids.size())+ + " atoms: total mass = "+cvm::to_str(total_mass)+ + ", total charge = "+cvm::to_str(total_charge)+".\n"); + } +} + + +int cvm::atom_group::parse(std::string const &group_conf) +{ + cvm::log("Initializing atom group \""+key+"\".\n"); + + // whether or not to include messages in the log + // colvarparse::Parse_Mode mode = parse_silent; + // { + // bool b_verbose; + // get_keyval (group_conf, "verboseOutput", b_verbose, false, parse_silent); + // if (b_verbose) mode = parse_normal; + // } + // colvarparse::Parse_Mode mode = parse_normal; + + int error_code = COLVARS_OK; + + // Optional group name will let other groups reuse atom definition + if (get_keyval(group_conf, "name", name)) { + if ((cvm::atom_group_by_name(this->name) != NULL) && + (cvm::atom_group_by_name(this->name) != this)) { + cvm::error("Error: this atom group cannot have the same name, \""+this->name+ + "\", as another atom group.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + cvm::main()->register_named_atom_group(this); + description = "atom group " + name; + } + + // We need to know about fitting to decide whether the group is scalable + // and we need to know about scalability before adding atoms + bool b_defined_center = get_keyval_feature(this, group_conf, "centerToOrigin", f_ag_center_origin, false); + // Legacy alias + b_defined_center |= get_keyval_feature(this, group_conf, "centerReference", f_ag_center, is_enabled(f_ag_center_origin), parse_deprecated); + b_defined_center |= get_keyval_feature(this, group_conf, "centerToReference", f_ag_center, is_enabled(f_ag_center)); + + if (is_enabled(f_ag_center_origin) && ! is_enabled(f_ag_center)) { + return cvm::error("centerToReference may not be disabled if centerToOrigin" + "is enabled.\n", COLVARS_INPUT_ERROR); + } + // Legacy alias + bool b_defined_rotate = get_keyval_feature(this, group_conf, "rotateReference", f_ag_rotate, false, parse_deprecated); + b_defined_rotate |= get_keyval_feature(this, group_conf, "rotateToReference", f_ag_rotate, is_enabled(f_ag_rotate)); + + if (is_enabled(f_ag_rotate) || is_enabled(f_ag_center) || + is_enabled(f_ag_center_origin)) { + cvm::main()->cite_feature("Moving frame of reference"); + } + + // is the user setting explicit options? + b_user_defined_fit = b_defined_center || b_defined_rotate; + + if (is_available(f_ag_scalable_com) && !is_enabled(f_ag_rotate) && !is_enabled(f_ag_center)) { + enable(f_ag_scalable_com); + } + + { + std::string atoms_of = ""; + if (get_keyval(group_conf, "atomsOfGroup", atoms_of)) { + atom_group * ag = atom_group_by_name(atoms_of); + if (ag == NULL) { + cvm::error("Error: cannot find atom group with name " + atoms_of + ".\n"); + return COLVARS_ERROR; + } + error_code |= add_atoms_of_group(ag); + } + } + +// if (get_keyval(group_conf, "copyOfGroup", source)) { +// // Goal: Initialize this as a full copy +// // for this we'll need an atom_group copy constructor +// return COLVARS_OK; +// } + + { + std::string numbers_conf = ""; + size_t pos = 0; + while (key_lookup(group_conf, "atomNumbers", &numbers_conf, &pos)) { + error_code |= add_atom_numbers(numbers_conf); + numbers_conf = ""; + } + } + + { + std::string index_group_name; + if (get_keyval(group_conf, "indexGroup", index_group_name)) { + // use an index group from the index file read globally + error_code |= add_index_group(index_group_name); + } + } + + { + std::string range_conf = ""; + size_t pos = 0; + while (key_lookup(group_conf, "atomNumbersRange", + &range_conf, &pos)) { + error_code |= add_atom_numbers_range(range_conf); + range_conf = ""; + } + } + + { + std::vector psf_segids; + get_keyval(group_conf, "psfSegID", psf_segids, std::vector()); + std::vector::iterator psii; + for (psii = psf_segids.begin(); psii < psf_segids.end(); ++psii) { + if ( (psii->size() == 0) || (psii->size() > 4) ) { + cvm::error("Error: invalid PSF segment identifier provided, \""+ + (*psii)+"\".\n", COLVARS_INPUT_ERROR); + } + } + + std::string range_conf = ""; + size_t pos = 0; + size_t range_count = 0; + psii = psf_segids.begin(); + while (key_lookup(group_conf, "atomNameResidueRange", + &range_conf, &pos)) { + range_count++; + if (psf_segids.size() && (range_count > psf_segids.size())) { + cvm::error("Error: more instances of \"atomNameResidueRange\" than " + "values of \"psfSegID\".\n", COLVARS_INPUT_ERROR); + } else { + error_code |= add_atom_name_residue_range(psf_segids.size() ? + *psii : std::string(""), range_conf); + if (psf_segids.size()) psii++; + } + range_conf = ""; + } + } + + { + // read the atoms from a file + std::string atoms_file_name; + if (get_keyval(group_conf, "atomsFile", atoms_file_name, std::string(""))) { + + std::string atoms_col; + if (!get_keyval(group_conf, "atomsCol", atoms_col, std::string(""))) { + cvm::error("Error: parameter atomsCol is required if atomsFile is set.\n", + COLVARS_INPUT_ERROR); + } + + double atoms_col_value; + bool const atoms_col_value_defined = get_keyval(group_conf, "atomsColValue", atoms_col_value, 0.0); + if (atoms_col_value_defined && (!atoms_col_value)) { + cvm::error("Error: atomsColValue, if provided, must be non-zero.\n", COLVARS_INPUT_ERROR); + } + + // NOTE: calls to add_atom() and/or add_atom_id() are in the proxy-implemented function + error_code |= cvm::load_atoms(atoms_file_name.c_str(), *this, atoms_col, atoms_col_value); + } + } + + // Catch any errors from all the initialization steps above + if (error_code || cvm::get_error()) return (error_code || cvm::get_error()); + + // checks of doubly-counted atoms have been handled by add_atom() already + + if (get_keyval(group_conf, "dummyAtom", dummy_atom_pos, cvm::atom_pos())) { + + error_code |= set_dummy(); + error_code |= set_dummy_pos(dummy_atom_pos); + + } else { + + if (!(atoms_ids.size())) { + error_code |= cvm::error("Error: no atoms defined for atom group \"" + key + "\".\n", + COLVARS_INPUT_ERROR); + } + + // whether these atoms will ever receive forces or not + bool enable_forces = true; + get_keyval(group_conf, "enableForces", enable_forces, true, colvarparse::parse_silent); + noforce = !enable_forces; + } + + // Now that atoms are defined we can parse the detailed fitting options + error_code |= parse_fitting_options(group_conf); + + if (is_enabled(f_ag_scalable) && !b_dummy) { + cvm::log("Enabling scalable calculation for group \""+this->key+"\".\n"); + index = (cvm::proxy)->init_atom_group(atoms_ids); + } + + bool b_print_atom_ids = false; + get_keyval(group_conf, "printAtomIDs", b_print_atom_ids, false); + + // Calculate all required properties (such as total mass) + setup(); + + if (cvm::debug()) + cvm::log("Done initializing atom group \""+key+"\".\n"); + + { + std::string init_msg; + init_msg.append("Atom group \""+key+"\" defined with "+ + cvm::to_str(atoms_ids.size())+" atoms requested"); + if ((cvm::proxy)->updated_masses()) { + init_msg.append(": total mass = "+ + cvm::to_str(total_mass)); + if ((cvm::proxy)->updated_charges()) { + init_msg.append(", total charge = "+ + cvm::to_str(total_charge)); + } + } + init_msg.append(".\n"); + cvm::log(init_msg); + } + + if (b_print_atom_ids) { + cvm::log("Internal definition of the atom group:\n"); + cvm::log(print_atom_ids()); + } + + if (is_enabled(f_ag_rotate)) setup_rotation_derivative(); + + return error_code; +} + + +int cvm::atom_group::add_atoms_of_group(atom_group const *ag) +{ + std::vector const &source_ids = ag->atoms_ids; + + if (source_ids.size()) { + atoms_ids.reserve(atoms_ids.size()+source_ids.size()); + + if (is_enabled(f_ag_scalable)) { + for (size_t i = 0; i < source_ids.size(); i++) { + add_atom_id(source_ids[i]); + } + } else { + atoms.reserve(atoms.size()+source_ids.size()); + for (size_t i = 0; i < source_ids.size(); i++) { + // We could use the atom copy constructor, but only if the source + // group is not scalable - whereas this works in both cases + // atom constructor expects 1-based atom number + add_atom(cvm::atom(source_ids[i] + 1)); + } + } + + if (cvm::get_error()) return COLVARS_ERROR; + } else { + cvm::error("Error: source atom group contains no atoms\".\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + return COLVARS_OK; +} + + +int cvm::atom_group::add_atom_numbers(std::string const &numbers_conf) +{ + std::vector atom_indexes; + + if (numbers_conf.size()) { + std::istringstream is(numbers_conf); + int ia; + while (is >> ia) { + atom_indexes.push_back(ia); + } + } + + if (atom_indexes.size()) { + atoms_ids.reserve(atoms_ids.size()+atom_indexes.size()); + + if (is_enabled(f_ag_scalable)) { + for (size_t i = 0; i < atom_indexes.size(); i++) { + add_atom_id((cvm::proxy)->check_atom_id(atom_indexes[i])); + } + } else { + // if we are handling the group on rank 0, better allocate the vector in one shot + atoms.reserve(atoms.size()+atom_indexes.size()); + for (size_t i = 0; i < atom_indexes.size(); i++) { + add_atom(cvm::atom(atom_indexes[i])); + } + } + + if (cvm::get_error()) return COLVARS_ERROR; + } else { + cvm::error("Error: no numbers provided for \"" + "atomNumbers\".\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + return COLVARS_OK; +} + + +int cvm::atom_group::add_index_group(std::string const &index_group_name) +{ + std::vector const &index_group_names = + cvm::main()->index_group_names; + std::vector *> const &index_groups = + cvm::main()->index_groups; + + size_t i_group = 0; + for ( ; i_group < index_groups.size(); i_group++) { + if (index_group_names[i_group] == index_group_name) + break; + } + + if (i_group >= index_group_names.size()) { + return cvm::error("Error: could not find index group "+ + index_group_name+" among those already provided.\n", + COLVARS_INPUT_ERROR); + } + + int error_code = COLVARS_OK; + + std::vector const &index_group = *(index_groups[i_group]); + + atoms_ids.reserve(atoms_ids.size()+index_group.size()); + + if (is_enabled(f_ag_scalable)) { + for (size_t i = 0; i < index_group.size(); i++) { + error_code |= + add_atom_id((cvm::proxy)->check_atom_id(index_group[i])); + } + } else { + atoms.reserve(atoms.size()+index_group.size()); + for (size_t i = 0; i < index_group.size(); i++) { + error_code |= add_atom(cvm::atom(index_group[i])); + } + } + + return error_code; +} + + +int cvm::atom_group::add_atom_numbers_range(std::string const &range_conf) +{ + if (range_conf.size()) { + std::istringstream is(range_conf); + int initial, final; + char dash; + if ( (is >> initial) && (initial > 0) && + (is >> dash) && (dash == '-') && + (is >> final) && (final > 0) ) { + + atoms_ids.reserve(atoms_ids.size() + (final - initial + 1)); + + if (is_enabled(f_ag_scalable)) { + for (int anum = initial; anum <= final; anum++) { + add_atom_id((cvm::proxy)->check_atom_id(anum)); + } + } else { + atoms.reserve(atoms.size() + (final - initial + 1)); + for (int anum = initial; anum <= final; anum++) { + add_atom(cvm::atom(anum)); + } + } + + } + if (cvm::get_error()) return COLVARS_ERROR; + } else { + cvm::error("Error: no valid definition for \"atomNumbersRange\", \""+ + range_conf+"\".\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + return COLVARS_OK; +} + + +int cvm::atom_group::add_atom_name_residue_range(std::string const &psf_segid, + std::string const &range_conf) +{ + if (range_conf.size()) { + std::istringstream is(range_conf); + std::string atom_name; + int initial, final; + char dash; + if ( (is >> atom_name) && (atom_name.size()) && + (is >> initial) && (initial > 0) && + (is >> dash) && (dash == '-') && + (is >> final) && (final > 0) ) { + + atoms_ids.reserve(atoms_ids.size() + (final - initial + 1)); + + if (is_enabled(f_ag_scalable)) { + for (int resid = initial; resid <= final; resid++) { + add_atom_id((cvm::proxy)->check_atom_id(resid, atom_name, psf_segid)); + } + } else { + atoms.reserve(atoms.size() + (final - initial + 1)); + for (int resid = initial; resid <= final; resid++) { + add_atom(cvm::atom(resid, atom_name, psf_segid)); + } + } + + if (cvm::get_error()) return COLVARS_ERROR; + } else { + cvm::error("Error: cannot parse definition for \"" + "atomNameResidueRange\", \""+ + range_conf+"\".\n"); + return COLVARS_ERROR; + } + } else { + cvm::error("Error: atomNameResidueRange with empty definition.\n"); + return COLVARS_ERROR; + } + return COLVARS_OK; +} + + +std::string const cvm::atom_group::print_atom_ids() const +{ + size_t line_count = 0; + std::ostringstream os(""); + for (size_t i = 0; i < atoms_ids.size(); i++) { + os << " " << std::setw(9) << atoms_ids[i]; + if (++line_count == 7) { + os << "\n"; + line_count = 0; + } + } + return os.str(); +} + + +int cvm::atom_group::parse_fitting_options(std::string const &group_conf) +{ + if (is_enabled(f_ag_center) || is_enabled(f_ag_rotate)) { + + if (b_dummy) + cvm::error("Error: centerToReference or rotateToReference " + "cannot be defined for a dummy atom.\n"); + + bool b_ref_pos_group = false; + std::string fitting_group_conf; + if (key_lookup(group_conf, "refPositionsGroup", &fitting_group_conf)) { + b_ref_pos_group = true; + cvm::log("Warning: keyword \"refPositionsGroup\" is deprecated: please use \"fittingGroup\" instead.\n"); + } + + if (b_ref_pos_group || key_lookup(group_conf, "fittingGroup", &fitting_group_conf)) { + // instead of this group, define another group to compute the fit + if (fitting_group) { + cvm::error("Error: the atom group \""+ + key+"\" has already a reference group " + "for the rototranslational fit, which was communicated by the " + "colvar component. You should not use fittingGroup " + "in this case.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + cvm::log("Within atom group \""+key+"\":\n"); + fitting_group = new atom_group("fittingGroup"); + if (fitting_group->parse(fitting_group_conf) == COLVARS_OK) { + fitting_group->check_keywords(fitting_group_conf, "fittingGroup"); + if (cvm::get_error()) { + cvm::error("Error setting up atom group \"fittingGroup\".", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + enable(f_ag_fitting_group); + } + + atom_group *group_for_fit = fitting_group ? fitting_group : this; + + get_keyval(group_conf, "refPositions", ref_pos, ref_pos); + + std::string ref_pos_file; + if (get_keyval(group_conf, "refPositionsFile", ref_pos_file, std::string(""))) { + + if (ref_pos.size()) { + cvm::error("Error: cannot specify \"refPositionsFile\" and " + "\"refPositions\" at the same time.\n"); + } + + std::string ref_pos_col; + double ref_pos_col_value=0.0; + + if (get_keyval(group_conf, "refPositionsCol", ref_pos_col, std::string(""))) { + // if provided, use PDB column to select coordinates + bool found = get_keyval(group_conf, "refPositionsColValue", ref_pos_col_value, 0.0); + if (found && ref_pos_col_value == 0.0) { + cvm::error("Error: refPositionsColValue, " + "if provided, must be non-zero.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + } + + ref_pos.resize(group_for_fit->size()); + cvm::load_coords(ref_pos_file.c_str(), &ref_pos, group_for_fit, + ref_pos_col, ref_pos_col_value); + } + + if (ref_pos.size()) { + + if (is_enabled(f_ag_rotate)) { + if (ref_pos.size() != group_for_fit->size()) + cvm::error("Error: the number of reference positions provided("+ + cvm::to_str(ref_pos.size())+ + ") does not match the number of atoms within \""+ + key+ + "\" ("+cvm::to_str(group_for_fit->size())+ + "): to perform a rotational fit, "+ + "these numbers should be equal.\n", COLVARS_INPUT_ERROR); + } + + // save the center of geometry of ref_pos and subtract it + center_ref_pos(); + + } else { + cvm::error("Error: no reference positions provided.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + if (is_enabled(f_ag_rotate) && !noforce) { + cvm::log("Warning: atom group \""+key+ + "\" will be aligned to a fixed orientation given by the reference positions provided. " + "If the internal structure of the group changes too much (i.e. its RMSD is comparable " + "to its radius of gyration), the optimal rotation and its gradients may become discontinuous. " + "If that happens, use fittingGroup (or a different definition for it if already defined) " + "to align the coordinates.\n"); + } + } + + // Enable fit gradient calculation only if necessary, and not disabled by the user + // This must happen after fitting group is defined so that side-effects are performed + // properly (ie. allocating fitting group gradients) + { + bool b_fit_gradients; + get_keyval(group_conf, "enableFitGradients", b_fit_gradients, true); + + if (b_fit_gradients && (is_enabled(f_ag_center) || is_enabled(f_ag_rotate))) { + enable(f_ag_fit_gradients); + } + } + + return COLVARS_OK; +} + + +void cvm::atom_group::do_feature_side_effects(int id) +{ + // If enabled features are changed upstream, the features below should be refreshed + switch (id) { + case f_ag_fit_gradients: + if (is_enabled(f_ag_center) || is_enabled(f_ag_rotate)) { + atom_group *group_for_fit = fitting_group ? fitting_group : this; + group_for_fit->fit_gradients.assign(group_for_fit->size(), cvm::atom_pos(0.0, 0.0, 0.0)); + } + break; + } +} + + +int cvm::atom_group::create_sorted_ids() +{ + // Only do the work if the vector is not yet populated + if (sorted_atoms_ids.size()) + return COLVARS_OK; + + // Sort the internal IDs + std::list sorted_atoms_ids_list; + for (size_t i = 0; i < atoms_ids.size(); i++) { + sorted_atoms_ids_list.push_back(atoms_ids[i]); + } + sorted_atoms_ids_list.sort(); + sorted_atoms_ids_list.unique(); + if (sorted_atoms_ids_list.size() != atoms_ids.size()) { + return cvm::error("Error: duplicate atom IDs in atom group? (found " + + cvm::to_str(sorted_atoms_ids_list.size()) + + " unique atom IDs instead of " + + cvm::to_str(atoms_ids.size()) + ").\n", COLVARS_BUG_ERROR); + } + + // Compute map between sorted and unsorted elements + sorted_atoms_ids.resize(atoms_ids.size()); + sorted_atoms_ids_map.resize(atoms_ids.size()); + std::list::iterator lsii = sorted_atoms_ids_list.begin(); + size_t ii = 0; + for ( ; ii < atoms_ids.size(); lsii++, ii++) { + sorted_atoms_ids[ii] = *lsii; + size_t const pos = std::find(atoms_ids.begin(), atoms_ids.end(), *lsii) - + atoms_ids.begin(); + sorted_atoms_ids_map[ii] = pos; + } + + return COLVARS_OK; +} + + +int cvm::atom_group::overlap(const atom_group &g1, const atom_group &g2){ + for (cvm::atom_const_iter ai1 = g1.begin(); ai1 != g1.end(); ai1++) { + for (cvm::atom_const_iter ai2 = g2.begin(); ai2 != g2.end(); ai2++) { + if (ai1->id == ai2->id) { + return (ai1->id + 1); // 1-based index to allow boolean usage + } + } + } + return 0; +} + + +void cvm::atom_group::center_ref_pos() +{ + ref_pos_cog = cvm::atom_pos(0.0, 0.0, 0.0); + std::vector::iterator pi; + for (pi = ref_pos.begin(); pi != ref_pos.end(); ++pi) { + ref_pos_cog += *pi; + } + ref_pos_cog /= (cvm::real) ref_pos.size(); + for (pi = ref_pos.begin(); pi != ref_pos.end(); ++pi) { + (*pi) -= ref_pos_cog; + } +} + + +void cvm::atom_group::read_positions() +{ + if (b_dummy) return; + + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->read_position(); + } + + if (fitting_group) + fitting_group->read_positions(); +} + + +int cvm::atom_group::calc_required_properties() +{ + // TODO check if the com is needed? + calc_center_of_mass(); + calc_center_of_geometry(); + + if (!is_enabled(f_ag_scalable)) { + if (is_enabled(f_ag_center) || is_enabled(f_ag_rotate)) { + if (fitting_group) { + fitting_group->calc_center_of_geometry(); + } + + calc_apply_roto_translation(); + + // update COM and COG after fitting + calc_center_of_geometry(); + calc_center_of_mass(); + if (fitting_group) { + fitting_group->calc_center_of_geometry(); + } + } + } + + // TODO calculate elements of scalable cvc's here before reduction + + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +void cvm::atom_group::calc_apply_roto_translation() +{ + // store the laborarory-frame COGs for when they are needed later + cog_orig = this->center_of_geometry(); + if (fitting_group) { + fitting_group->cog_orig = fitting_group->center_of_geometry(); + } + + if (is_enabled(f_ag_center)) { + // center on the origin first + cvm::atom_pos const rpg_cog = fitting_group ? + fitting_group->center_of_geometry() : this->center_of_geometry(); + apply_translation(-1.0 * rpg_cog); + if (fitting_group) { + fitting_group->apply_translation(-1.0 * rpg_cog); + } + } + + if (is_enabled(f_ag_rotate)) { + // rotate the group (around the center of geometry if f_ag_center is + // enabled, around the origin otherwise) + rot.calc_optimal_rotation(fitting_group ? + fitting_group->atoms: + this->atoms, + ref_pos); + const auto rot_mat = rot.matrix(); + + cvm::atom_iter ai; + for (ai = this->begin(); ai != this->end(); ai++) { + ai->pos = rot_mat * ai->pos; + } + if (fitting_group) { + for (ai = fitting_group->begin(); ai != fitting_group->end(); ai++) { + ai->pos = rot_mat * ai->pos; + } + } + } + + if (is_enabled(f_ag_center) && !is_enabled(f_ag_center_origin)) { + // align with the center of geometry of ref_pos + apply_translation(ref_pos_cog); + if (fitting_group) { + fitting_group->apply_translation(ref_pos_cog); + } + } + // update of COM and COG is done from the calling routine +} + + +void cvm::atom_group::apply_translation(cvm::rvector const &t) +{ + if (b_dummy) { + cvm::error("Error: cannot translate the coordinates of a dummy atom group.\n", COLVARS_INPUT_ERROR); + return; + } + + if (is_enabled(f_ag_scalable)) { + cvm::error("Error: cannot translate the coordinates of a scalable atom group.\n", COLVARS_INPUT_ERROR); + return; + } + + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->pos += t; + } +} + + +void cvm::atom_group::read_velocities() +{ + if (b_dummy) return; + + if (is_enabled(f_ag_rotate)) { + + const auto rot_mat = rot.matrix(); + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->read_velocity(); + ai->vel = rot_mat * ai->vel; + } + + } else { + + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->read_velocity(); + } + } +} + + +// TODO make this a calc function +void cvm::atom_group::read_total_forces() +{ + if (b_dummy) return; + + if (is_enabled(f_ag_rotate)) { + + const auto rot_mat = rot.matrix(); + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->read_total_force(); + ai->total_force = rot_mat * ai->total_force; + } + + } else { + + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->read_total_force(); + } + } +} + + +int cvm::atom_group::calc_center_of_geometry() +{ + if (b_dummy) { + cog = dummy_atom_pos; + } else { + cog.reset(); + for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { + cog += ai->pos; + } + cog /= cvm::real(this->size()); + } + return COLVARS_OK; +} + + +int cvm::atom_group::calc_center_of_mass() +{ + if (b_dummy) { + com = dummy_atom_pos; + if (cvm::debug()) { + cvm::log("Dummy atom center of mass = "+cvm::to_str(com)+"\n"); + } + } else if (is_enabled(f_ag_scalable)) { + com = (cvm::proxy)->get_atom_group_com(index); + } else { + com.reset(); + for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { + com += ai->mass * ai->pos; + } + com /= total_mass; + } + return COLVARS_OK; +} + + +int cvm::atom_group::calc_dipole(cvm::atom_pos const &dipole_center) +{ + if (b_dummy) { + return cvm::error("Error: trying to compute the dipole " + "of a dummy group.\n", COLVARS_INPUT_ERROR); + } + dip.reset(); + for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { + dip += ai->charge * (ai->pos - dipole_center); + } + return COLVARS_OK; +} + + +void cvm::atom_group::set_weighted_gradient(cvm::rvector const &grad) +{ + if (b_dummy) return; + + scalar_com_gradient = grad; + + if (!is_enabled(f_ag_scalable)) { + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->grad = (ai->mass/total_mass) * grad; + } + } +} + + +void cvm::atom_group::calc_fit_gradients() +{ + if (b_dummy || ! is_enabled(f_ag_fit_gradients)) return; + + if (cvm::debug()) + cvm::log("Calculating fit gradients.\n"); + + if (is_enabled(f_ag_center) && is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + if (is_enabled(f_ag_center) && !is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + if (!is_enabled(f_ag_center) && is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + if (!is_enabled(f_ag_center) && !is_enabled(f_ag_rotate)) + calc_fit_gradients_impl(); + + if (cvm::debug()) + cvm::log("Done calculating fit gradients.\n"); +} + + +template +void cvm::atom_group::calc_fit_gradients_impl() { + cvm::atom_group *group_for_fit = fitting_group ? fitting_group : this; + // the center of geometry contribution to the gradients + cvm::rvector atom_grad; + // the rotation matrix contribution to the gradients + const auto rot_inv = rot.inverse().matrix(); + // temporary variables for computing and summing derivatives + cvm::real sum_dxdq[4] = {0, 0, 0, 0}; + cvm::vector1d dq0_1(4); + // loop 1: iterate over the current atom group + for (size_t i = 0; i < size(); i++) { + cvm::atom_pos pos_orig; + if (B_ag_center) { + atom_grad += atoms[i].grad; + if (B_ag_rotate) pos_orig = rot_inv * (atoms[i].pos - ref_pos_cog); + } else { + if (B_ag_rotate) pos_orig = atoms[i].pos; + } + if (B_ag_rotate) { + // calculate \partial(R(q) \vec{x}_i)/\partial q) \cdot \partial\xi/\partial\vec{x}_i + cvm::quaternion const dxdq = + rot.q.position_derivative_inner(pos_orig, atoms[i].grad); + sum_dxdq[0] += dxdq[0]; + sum_dxdq[1] += dxdq[1]; + sum_dxdq[2] += dxdq[2]; + sum_dxdq[3] += dxdq[3]; + } + } + if (B_ag_center) { + if (B_ag_rotate) atom_grad = rot.inverse().matrix() * atom_grad; + atom_grad *= (-1.0)/(cvm::real(group_for_fit->size())); + } + // loop 2: iterate over the fitting group + if (B_ag_rotate) rot_deriv->prepare_derivative(rotation_derivative_dldq::use_dq); + for (size_t j = 0; j < group_for_fit->size(); j++) { + if (B_ag_center) { + group_for_fit->fit_gradients[j] = atom_grad; + } + if (B_ag_rotate) { + rot_deriv->calc_derivative_wrt_group1(j, nullptr, &dq0_1); + // multiply by {\partial q}/\partial\vec{x}_j and add it to the fit gradients + group_for_fit->fit_gradients[j] += sum_dxdq[0] * dq0_1[0] + + sum_dxdq[1] * dq0_1[1] + + sum_dxdq[2] * dq0_1[2] + + sum_dxdq[3] * dq0_1[3]; + } + } +} + + +std::vector cvm::atom_group::positions() const +{ + if (b_dummy) { + cvm::error("Error: positions are not available " + "from a dummy atom group.\n", COLVARS_INPUT_ERROR); + } + + if (is_enabled(f_ag_scalable)) { + cvm::error("Error: atomic positions are not available " + "from a scalable atom group.\n", COLVARS_INPUT_ERROR); + } + + std::vector x(this->size(), 0.0); + cvm::atom_const_iter ai = this->begin(); + std::vector::iterator xi = x.begin(); + for ( ; ai != this->end(); ++xi, ++ai) { + *xi = ai->pos; + } + return x; +} + +std::vector cvm::atom_group::positions_shifted(cvm::rvector const &shift) const +{ + if (b_dummy) { + cvm::error("Error: positions are not available " + "from a dummy atom group.\n", COLVARS_INPUT_ERROR); + } + + if (is_enabled(f_ag_scalable)) { + cvm::error("Error: atomic positions are not available " + "from a scalable atom group.\n", COLVARS_INPUT_ERROR); + } + + std::vector x(this->size(), 0.0); + cvm::atom_const_iter ai = this->begin(); + std::vector::iterator xi = x.begin(); + for ( ; ai != this->end(); ++xi, ++ai) { + *xi = (ai->pos + shift); + } + return x; +} + +std::vector cvm::atom_group::velocities() const +{ + if (b_dummy) { + cvm::error("Error: velocities are not available " + "from a dummy atom group.\n", COLVARS_INPUT_ERROR); + } + + if (is_enabled(f_ag_scalable)) { + cvm::error("Error: atomic velocities are not available " + "from a scalable atom group.\n", COLVARS_INPUT_ERROR); + } + + std::vector v(this->size(), 0.0); + cvm::atom_const_iter ai = this->begin(); + std::vector::iterator vi = v.begin(); + for ( ; ai != this->end(); vi++, ai++) { + *vi = ai->vel; + } + return v; +} + +std::vector cvm::atom_group::total_forces() const +{ + if (b_dummy) { + cvm::error("Error: total forces are not available " + "from a dummy atom group.\n", COLVARS_INPUT_ERROR); + } + + if (is_enabled(f_ag_scalable)) { + cvm::error("Error: atomic total forces are not available " + "from a scalable atom group.\n", COLVARS_INPUT_ERROR); + } + + std::vector f(this->size(), 0.0); + cvm::atom_const_iter ai = this->begin(); + std::vector::iterator fi = f.begin(); + for ( ; ai != this->end(); ++fi, ++ai) { + *fi = ai->total_force; + } + return f; +} + + +// TODO make this an accessor +cvm::rvector cvm::atom_group::total_force() const +{ + if (b_dummy) { + cvm::error("Error: total total forces are not available " + "from a dummy atom group.\n", COLVARS_INPUT_ERROR); + } + + if (is_enabled(f_ag_scalable)) { + return (cvm::proxy)->get_atom_group_total_force(index); + } + + cvm::rvector f(0.0); + for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { + f += ai->total_force; + } + return f; +} + + +void cvm::atom_group::apply_colvar_force(cvm::real const &force) +{ + if (cvm::debug()) { + log("Communicating a colvar force from atom group to the MD engine.\n"); + } + + if (b_dummy) return; + + if (noforce) { + cvm::error("Error: sending a force to a group that has " + "\"enableForces\" set to off.\n"); + return; + } + + if (is_enabled(f_ag_scalable)) { + (cvm::proxy)->apply_atom_group_force(index, force * scalar_com_gradient); + return; + } + + if (is_enabled(f_ag_rotate)) { + + // rotate forces back to the original frame + const auto rot_inv = rot.inverse().matrix(); + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->apply_force(rot_inv * (force * ai->grad)); + } + + } else { + + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->apply_force(force * ai->grad); + } + } + + if ((is_enabled(f_ag_center) || is_enabled(f_ag_rotate)) && is_enabled(f_ag_fit_gradients)) { + + atom_group *group_for_fit = fitting_group ? fitting_group : this; + + // Fit gradients are already calculated in "laboratory" frame + for (size_t j = 0; j < group_for_fit->size(); j++) { + (*group_for_fit)[j].apply_force(force * group_for_fit->fit_gradients[j]); + } + } +} + + +void cvm::atom_group::apply_force(cvm::rvector const &force) +{ + if (cvm::debug()) { + log("Communicating a colvar force from atom group to the MD engine.\n"); + } + + if (b_dummy) return; + + if (noforce) { + cvm::error("Error: sending a force to a group that has " + "\"enableForces\" set to off.\n"); + return; + } + + if (is_enabled(f_ag_scalable)) { + (cvm::proxy)->apply_atom_group_force(index, force); + return; + } + + if (is_enabled(f_ag_rotate)) { + + const auto rot_inv = rot.inverse().matrix(); + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->apply_force(rot_inv * ((ai->mass/total_mass) * force)); + } + + } else { + + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + ai->apply_force((ai->mass/total_mass) * force); + } + } +} + + +// Static members + +std::vector cvm::atom_group::ag_features; + + diff --git a/src/external/colvars/colvaratoms.h b/src/external/colvars/colvaratoms.h new file mode 100644 index 00000000000..d16ca7bd567 --- /dev/null +++ b/src/external/colvars/colvaratoms.h @@ -0,0 +1,547 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARATOMS_H +#define COLVARATOMS_H + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarparse.h" +#include "colvardeps.h" + +template +struct rotation_derivative; + + +/// \brief Stores numeric id, mass and all mutable data for an atom, +/// mostly used by a \link colvar::cvc \endlink +/// +/// This class may be used to keep atomic data such as id, mass, +/// position and collective variable derivatives) altogether. +/// There may be multiple instances with identical +/// numeric id, all acting independently: forces communicated through +/// these instances will be summed together. + +class colvarmodule::atom { + +protected: + + /// Index in the colvarproxy arrays (\b NOT in the global topology!) + int index; + +public: + + /// Identifier for the MD program (0-based) + int id; + + /// Mass + cvm::real mass; + + /// Charge + cvm::real charge; + + /// \brief Current position (copied from the program, can be + /// modified if necessary) + cvm::atom_pos pos; + + /// \brief Current velocity (copied from the program, can be + /// modified if necessary) + cvm::rvector vel; + + /// \brief System force at the previous step (copied from the + /// program, can be modified if necessary) + cvm::rvector total_force; + + /// \brief Gradient of a scalar collective variable with respect + /// to this atom + /// + /// This can only handle a scalar collective variable (i.e. when + /// the \link colvarvalue::real_value \endlink member is used + /// from the \link colvarvalue \endlink class), which is also the + /// most frequent case. For more complex types of \link + /// colvarvalue \endlink objects, atomic gradients should be + /// defined within the specific \link colvar::cvc \endlink + /// implementation + cvm::rvector grad; + + /// \brief Default constructor (sets index and id both to -1) + atom(); + + /// \brief Initialize an atom for collective variable calculation + /// and get its internal identifier \param atom_number Atom index in + /// the system topology (1-based) + atom(int atom_number); + + /// \brief Initialize an atom for collective variable calculation + /// and get its internal identifier \param residue Residue number + /// \param atom_name Name of the atom in the residue \param + /// segment_id For PSF topologies, the segment identifier; for other + /// type of topologies, may not be required + atom(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id); + + /// Copy constructor + atom(atom const &a); + + /// Destructor + ~atom(); + + /// Assignment operator (added to appease LGTM) + atom & operator = (atom const &a); + + /// Set mutable data (everything except id and mass) to zero + inline void reset_data() + { + pos = cvm::atom_pos(0.0); + vel = grad = total_force = cvm::rvector(0.0); + } + + /// Get the latest value of the mass + inline void update_mass() + { + colvarproxy *p = cvm::proxy; + mass = p->get_atom_mass(index); + } + + /// Get the latest value of the charge + inline void update_charge() + { + colvarproxy *p = cvm::proxy; + charge = p->get_atom_charge(index); + } + + /// Get the current position + inline void read_position() + { + pos = (cvm::proxy)->get_atom_position(index); + } + + /// Get the current velocity + inline void read_velocity() + { + vel = (cvm::proxy)->get_atom_velocity(index); + } + + /// Get the total force + inline void read_total_force() + { + total_force = (cvm::proxy)->get_atom_total_force(index); + } + + /// \brief Apply a force to the atom + /// + /// Note: the force is not applied instantly, but will be used later + /// by the MD integrator (the colvars module does not integrate + /// equations of motion. + /// + /// Multiple calls to this function by either the same + /// \link atom \endlink object or different objects with identical + /// \link id \endlink will all be added together. + inline void apply_force(cvm::rvector const &new_force) const + { + (cvm::proxy)->apply_atom_force(index, new_force); + } +}; + + + +/// \brief Group of \link atom \endlink objects, mostly used by a +/// \link colvar::cvc \endlink object to gather all atomic data +class colvarmodule::atom_group + : public colvarparse, public colvardeps +{ +public: + + + /// \brief Default constructor + atom_group(); + + /// \brief Create a group object, assign a name to it + atom_group(char const *key); + + /// \brief Initialize the group after a (temporary) vector of atoms + atom_group(std::vector const &atoms_in); + + /// \brief Destructor + ~atom_group() override; + + /// \brief Optional name to reuse properties of this in other groups + std::string name; + + /// \brief Keyword used to define the group + // TODO Make this field part of the data structures that link a group to a CVC + std::string key; + + /// \brief Set default values for common flags + int init(); + + /// \brief Initialize dependency tree + int init_dependencies() override; + + /// \brief Update data required to calculate cvc's + int setup(); + + /// \brief Initialize the group by looking up its configuration + /// string in conf and parsing it + int parse(std::string const &conf); + + int add_atom_numbers(std::string const &numbers_conf); + int add_atoms_of_group(atom_group const * ag); + int add_index_group(std::string const &index_group_name); + int add_atom_numbers_range(std::string const &range_conf); + int add_atom_name_residue_range(std::string const &psf_segid, + std::string const &range_conf); + int parse_fitting_options(std::string const &group_conf); + + /// \brief Add an atom object to this group + int add_atom(cvm::atom const &a); + + /// \brief Add an atom ID to this group (the actual atomicdata will be not be handled by the group) + int add_atom_id(int aid); + + /// \brief Remove an atom object from this group + int remove_atom(cvm::atom_iter ai); + + /// Set this group as a dummy group (no actual atoms) + int set_dummy(); + + /// If this group is dummy, set the corresponding position + int set_dummy_pos(cvm::atom_pos const &pos); + + /// \brief Print the updated the total mass and charge of a group. + /// This is needed in case the hosting MD code has an option to + /// change atom masses after their initialization. + void print_properties(std::string const &colvar_name, int i, int j); + + /// \brief Implementation of the feature list for atom group + static std::vector ag_features; + + /// \brief Implementation of the feature list accessor for atom group + const std::vector &features() const override { return ag_features; } + + std::vector &modify_features() override { return ag_features; } + + static void delete_features() + { + for (size_t i = 0; i < ag_features.size(); i++) { + delete ag_features[i]; + } + ag_features.clear(); + } + +protected: + + /// \brief Array of atom objects + std::vector atoms; + + /// \brief Internal atom IDs for host code + std::vector atoms_ids; + + /// Sorted list of internal atom IDs (populated on-demand by + /// create_sorted_ids); used to read coordinate files + std::vector sorted_atoms_ids; + + /// Map entries of sorted_atoms_ids onto the original positions in the group + std::vector sorted_atoms_ids_map; + + /// \brief Dummy atom position + cvm::atom_pos dummy_atom_pos; + + /// \brief Index in the colvarproxy arrays (if the group is scalable) + int index; + +public: + + inline cvm::atom & operator [] (size_t const i) + { + return atoms[i]; + } + + inline cvm::atom const & operator [] (size_t const i) const + { + return atoms[i]; + } + + inline cvm::atom_iter begin() + { + return atoms.begin(); + } + + inline cvm::atom_const_iter begin() const + { + return atoms.begin(); + } + + inline cvm::atom_iter end() + { + return atoms.end(); + } + + inline cvm::atom_const_iter end() const + { + return atoms.end(); + } + + inline size_t size() const + { + return atoms.size(); + } + + /// \brief If this option is on, this group merely acts as a wrapper + /// for a fixed position; any calls to atoms within or to + /// functions that return disaggregated data will fail + bool b_dummy; + + /// Internal atom IDs (populated during initialization) + inline std::vector const &ids() const + { + return atoms_ids; + } + + std::string const print_atom_ids() const; + + /// Allocates and populates sorted_ids and sorted_ids_map + int create_sorted_ids(); + + /// Sorted internal atom IDs (populated on-demand by create_sorted_ids); + /// used to read coordinate files + inline std::vector const &sorted_ids() const + { + return sorted_atoms_ids; + } + + /// Map entries of sorted_atoms_ids onto the original positions in the group + inline std::vector const &sorted_ids_map() const + { + return sorted_atoms_ids_map; + } + + /// Detect whether two groups share atoms + /// If yes, returns 1-based number of a common atom; else, returns 0 + static int overlap(const atom_group &g1, const atom_group &g2); + + /// The rotation calculated automatically if f_ag_rotate is defined + cvm::rotation rot; + + /// Rotation derivative; + rotation_derivative* rot_deriv; + + /// \brief Indicates that the user has explicitly set centerToReference or + /// rotateReference, and the corresponding reference: + /// cvc's (eg rmsd, eigenvector) will not override the user's choice + bool b_user_defined_fit; + + /// \brief use reference coordinates for f_ag_center or f_ag_rotate + std::vector ref_pos; + + /// \brief Center of geometry of the reference coordinates; regardless + /// of whether f_ag_center is true, ref_pos is centered to zero at + /// initialization, and ref_pos_cog serves to center the positions + cvm::atom_pos ref_pos_cog; + + /// \brief If f_ag_center or f_ag_rotate is true, use this group to + /// define the transformation (default: this group itself) + atom_group *fitting_group; + + /// Total mass of the atom group + cvm::real total_mass; + + /// Update the total mass of the atom group + void update_total_mass(); + + /// Total charge of the atom group + cvm::real total_charge; + + /// Update the total mass of the group + void update_total_charge(); + + /// \brief Don't apply any force on this group (use its coordinates + /// only to calculate a colvar) + bool noforce; + + /// \brief Get the current positions + void read_positions(); + + /// \brief (Re)calculate the optimal roto-translation + void calc_apply_roto_translation(); + + void setup_rotation_derivative(); + + /// \brief Save aside the center of geometry of the reference positions, + /// then subtract it from them + /// + /// In this way it will be possible to use ref_pos also for the + /// rotational fit. + /// This is called either by atom_group::parse or by CVCs that assign + /// reference positions (eg. RMSD, eigenvector). + void center_ref_pos(); + + /// \brief Move all positions + void apply_translation(cvm::rvector const &t); + + /// \brief Get the current velocities; this must be called always + /// *after* read_positions(); if f_ag_rotate is defined, the same + /// rotation applied to the coordinates will be used + void read_velocities(); + + /// \brief Get the current total_forces; this must be called always + /// *after* read_positions(); if f_ag_rotate is defined, the same + /// rotation applied to the coordinates will be used + void read_total_forces(); + + /// Call reset_data() for each atom + inline void reset_atoms_data() + { + for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) + ai->reset_data(); + if (fitting_group) + fitting_group->reset_atoms_data(); + } + + /// \brief Recompute all mutable quantities that are required to compute CVCs + int calc_required_properties(); + + /// \brief Return a copy of the current atom positions + std::vector positions() const; + + /// \brief Calculate the center of geometry of the atomic positions, assuming + /// that they are already pbc-wrapped + int calc_center_of_geometry(); + +private: + + /// \brief Center of geometry + cvm::atom_pos cog; + + /// \brief Center of geometry before any fitting + cvm::atom_pos cog_orig; + +public: + + /// \brief Return the center of geometry of the atomic positions + inline cvm::atom_pos center_of_geometry() const + { + return cog; + } + + /// \brief Calculate the center of mass of the atomic positions, assuming that + /// they are already pbc-wrapped + int calc_center_of_mass(); + +private: + + /// \brief Center of mass + cvm::atom_pos com; + + /// \brief The derivative of a scalar variable with respect to the COM + // TODO for scalable calculations of more complex variables (e.g. rotation), + // use a colvarvalue of vectors to hold the entire derivative + cvm::rvector scalar_com_gradient; + +public: + + /// \brief Return the center of mass (COM) of the atomic positions + inline cvm::atom_pos center_of_mass() const + { + return com; + } + + /// \brief Return previously gradient of scalar variable with respect to the + /// COM + inline cvm::rvector center_of_mass_scalar_gradient() const + { + return scalar_com_gradient; + } + + /// \brief Return a copy of the current atom positions, shifted by a constant vector + std::vector positions_shifted(cvm::rvector const &shift) const; + + /// \brief Return a copy of the current atom velocities + std::vector velocities() const; + + ///\brief Calculate the dipole of the atom group around the specified center + int calc_dipole(cvm::atom_pos const &dipole_center); + +private: + + /// Dipole moment of the atom group + cvm::rvector dip; + +public: + + ///\brief Return the (previously calculated) dipole of the atom group + inline cvm::rvector dipole() const + { + return dip; + } + + /// \brief Return a copy of the total forces + std::vector total_forces() const; + + /// \brief Return a copy of the aggregated total force on the group + cvm::rvector total_force() const; + + + /// \brief Shorthand: save the specified gradient on each atom, + /// weighting with the atom mass (mostly used in combination with + /// \link center_of_mass() \endlink) + void set_weighted_gradient(cvm::rvector const &grad); + + /// \brief Calculate the derivatives of the fitting transformation + void calc_fit_gradients(); + +/*! @brief Actual implementation of `calc_fit_gradients`. The template is + * used to avoid branching inside the loops in case that the CPU + * branch prediction is broken (or further migration to GPU code). + * @tparam B_ag_center Centered the reference to origin? This should follow + * the value of `is_enabled(f_ag_center)`. + * @tparam B_ag_rotate Calculate the optimal rotation? This should follow + * the value of `is_enabled(f_ag_rotate)`. + */ + template void calc_fit_gradients_impl(); + + /// \brief Derivatives of the fitting transformation + std::vector fit_gradients; + + /// \brief Used by a (scalar) colvar to apply its force on its \link + /// atom_group \endlink members + /// + /// The (scalar) force is multiplied by the colvar gradient for each + /// atom; this should be used when a colvar with scalar \link + /// colvarvalue \endlink type is used (this is the most frequent + /// case: for colvars with a non-scalar type, the most convenient + /// solution is to sum together the Cartesian forces from all the + /// colvar components, and use apply_force() or apply_forces()). If + /// the group is being rotated to a reference frame (e.g. to express + /// the colvar independently from the solute rotation), the + /// gradients are temporarily rotated to the original frame. + void apply_colvar_force(cvm::real const &force); + + /// \brief Apply a force "to the center of mass", i.e. the force is + /// distributed on each atom according to its mass + /// + /// If the group is being rotated to a reference frame (e.g. to + /// express the colvar independently from the solute rotation), the + /// force is rotated back to the original frame. Colvar gradients + /// are not used, either because they were not defined (e.g because + /// the colvar has not a scalar value) or the biases require to + /// micromanage the force. + /// This function will be phased out eventually, in favor of + /// apply_colvar_force() once that is implemented for non-scalar values + void apply_force(cvm::rvector const &force); + + /// Implements possible actions to be carried out + /// when a given feature is enabled + /// This overloads the base function in colvardeps + void do_feature_side_effects(int id) override; +}; + + +#endif diff --git a/src/external/colvars/colvarbias.cpp b/src/external/colvars/colvarbias.cpp new file mode 100644 index 00000000000..169fc073b9c --- /dev/null +++ b/src/external/colvars/colvarbias.cpp @@ -0,0 +1,1052 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarvalue.h" +#include "colvarbias.h" +#include "colvargrid.h" +#include "colvars_memstream.h" + + +colvarbias::colvarbias(char const *key) +{ + bias_type = colvarparse::to_lower_cppstr(key); + state_keyword = bias_type; + + rank = -1; + description = "uninitialized " + bias_type + " bias"; + + colvarbias::init_dependencies(); + + time_step_factor = 1; + + has_data = false; + b_output_energy = false; + output_freq = cvm::restart_out_freq; + + colvarbias::reset(); + state_file_step = 0L; + matching_state = false; + biasing_force_scaling_factors = NULL; +} + + +int colvarbias::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + + name = bias_type + cvm::to_str(rank); + colvarparse::set_string(conf); + + size_t i = 0; + + if (num_variables() == 0) { + // First initialization + + cvm::log("Initializing a new \""+bias_type+"\" instance.\n"); + + // Only allow setting a non-default name on first init + get_keyval(conf, "name", name, name); + + colvarbias *bias_with_name = cvm::bias_by_name(this->name); + if (bias_with_name != NULL) { + if ((bias_with_name->rank != this->rank) || + (bias_with_name->bias_type != this->bias_type)) { + error_code |= cvm::error("Error: this bias cannot have the same name, \""+ + this->name+"\", as another bias.\n", + COLVARS_INPUT_ERROR); + } + } + description = "bias " + name; + + { + // lookup the associated colvars + std::vector colvar_names; + if (get_keyval(conf, "colvars", colvar_names)) { + if (num_variables()) { + error_code |= cvm::error("Error: cannot redefine the colvars that " + "a bias was already defined on.\n", + COLVARS_INPUT_ERROR); + } + for (i = 0; i < colvar_names.size(); i++) { + add_colvar(colvar_names[i]); + } + } + } + + if (!num_variables()) { + error_code |= cvm::error("Error: no collective variables specified.\n", + COLVARS_INPUT_ERROR); + } + + } else { + cvm::log("Reinitializing bias \""+name+"\".\n"); + } + + colvar_values.resize(num_variables()); + for (i = 0; i < num_variables(); i++) { + colvar_values[i].type(colvars[i]->value().type()); + colvar_forces[i].type(colvar_values[i].type()); + previous_colvar_forces[i].type(colvar_values[i].type()); + } + + output_prefix = cvm::output_prefix(); + + get_keyval_feature(this, conf, "stepZeroData", f_cvb_step_zero_data, is_enabled(f_cvb_step_zero_data)); + + // Write energy to traj file? + get_keyval(conf, "outputEnergy", b_output_energy, b_output_energy); + + // How often to write full output files? + get_keyval(conf, "outputFreq", output_freq, output_freq); + + // Disabled by default in base class; default value can be overridden by derived class constructor + get_keyval_feature(this, conf, "bypassExtendedLagrangian", f_cvb_bypass_ext_lagrangian, is_enabled(f_cvb_bypass_ext_lagrangian), parse_echo); + + get_keyval(conf, "timeStepFactor", time_step_factor, time_step_factor); + if (time_step_factor < 1) { + error_code |= cvm::error("Error: timeStepFactor must be 1 or greater.\n", + COLVARS_INPUT_ERROR); + } + + // Use the scaling factors from a grid? + get_keyval_feature(this, conf, "scaledBiasingForce", + f_cvb_scale_biasing_force, + is_enabled(f_cvb_scale_biasing_force), parse_echo); + if (is_enabled(f_cvb_scale_biasing_force)) { + std::string biasing_force_scaling_factors_in_filename; + get_keyval(conf, "scaledBiasingForceFactorsGrid", + biasing_force_scaling_factors_in_filename, std::string()); + biasing_force_scaling_factors = new colvar_grid_scalar(colvars); + error_code |= biasing_force_scaling_factors->read_multicol(biasing_force_scaling_factors_in_filename, + "grid file"); + biasing_force_scaling_factors_bin.assign(num_variables(), 0); + } + + // Now that children are defined, we can solve dependencies + enable(f_cvb_active); + if (cvm::debug()) print_state(); + + return error_code; +} + + +int colvarbias::init_dependencies() { + int i; + if (features().size() == 0) { + for (i = 0; i < f_cvb_ntot; i++) { + modify_features().push_back(new feature); + } + + init_feature(f_cvb_active, "active", f_type_dynamic); + require_feature_children(f_cvb_active, f_cv_active); + + init_feature(f_cvb_awake, "awake", f_type_static); + require_feature_self(f_cvb_awake, f_cvb_active); + + init_feature(f_cvb_step_zero_data, "step_zero_data", f_type_user); + + init_feature(f_cvb_apply_force, "apply_force", f_type_user); + require_feature_children(f_cvb_apply_force, f_cv_gradient); + + init_feature(f_cvb_bypass_ext_lagrangian, "bypass_extended_Lagrangian_coordinates", f_type_user); + + // The exclusion below prevents the inconsistency where biasing forces are applied onto + // the actual colvar, while total forces are measured on the extended coordinate + exclude_feature_self(f_cvb_bypass_ext_lagrangian, f_cvb_get_total_force); + + init_feature(f_cvb_get_total_force, "obtain_total_force", f_type_dynamic); + require_feature_children(f_cvb_get_total_force, f_cv_total_force); + + init_feature(f_cvb_output_acc_work, "output_accumulated_work", f_type_user); + require_feature_self(f_cvb_output_acc_work, f_cvb_apply_force); + + init_feature(f_cvb_history_dependent, "history_dependent", f_type_static); + + init_feature(f_cvb_time_dependent, "time_dependent", f_type_static); + + init_feature(f_cvb_scalar_variables, "require_scalar_variables", f_type_static); + require_feature_children(f_cvb_scalar_variables, f_cv_scalar); + + init_feature(f_cvb_calc_pmf, "calculate_a_PMF", f_type_static); + + init_feature(f_cvb_calc_ti_samples, "calculate_TI_samples", f_type_dynamic); + require_feature_self(f_cvb_calc_ti_samples, f_cvb_get_total_force); + require_feature_children(f_cvb_calc_ti_samples, f_cv_grid); + + init_feature(f_cvb_write_ti_samples, "write_TI_samples_", f_type_user); + require_feature_self(f_cvb_write_ti_samples, f_cvb_calc_ti_samples); + + init_feature(f_cvb_write_ti_pmf, "write_TI_PMF", f_type_user); + require_feature_self(f_cvb_write_ti_pmf, f_cvb_calc_ti_samples); + + init_feature(f_cvb_scale_biasing_force, "scale_biasing_force", f_type_user); + require_feature_children(f_cvb_scale_biasing_force, f_cv_grid); + + // check that everything is initialized + for (i = 0; i < colvardeps::f_cvb_ntot; i++) { + if (is_not_set(i)) { + cvm::error("Uninitialized feature " + cvm::to_str(i) + " in " + description); + } + } + } + + // Initialize feature_states for each instance + feature_states.reserve(f_cvb_ntot); + for (i = 0; i < f_cvb_ntot; i++) { + feature_states.push_back(feature_state(true, false)); + // Most features are available, so we set them so + // and list exceptions below + } + + // only compute TI samples when deriving from colvarbias_ti + feature_states[f_cvb_calc_ti_samples].available = false; + + // The feature f_cvb_bypass_ext_lagrangian is only implemented by some derived classes + // (initially, harmonicWalls) + feature_states[f_cvb_bypass_ext_lagrangian].available = false; + // disabled by default; can be changed by derived classes that implement it + feature_states[f_cvb_bypass_ext_lagrangian].enabled = false; + + return COLVARS_OK; +} + + +int colvarbias::reset() +{ + bias_energy = 0.0; + for (size_t i = 0; i < num_variables(); i++) { + colvar_forces[i].reset(); + } + return COLVARS_OK; +} + + +colvarbias::colvarbias() + : colvarparse(), has_data(false) +{} + + +colvarbias::~colvarbias() +{ + colvarbias::clear(); +} + + +int colvarbias::clear() +{ + free_children_deps(); + + // Remove references to this bias from colvars + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + ++cvi) { + for (std::vector::iterator bi = (*cvi)->biases.begin(); + bi != (*cvi)->biases.end(); + ++bi) { + if ( *bi == this) { + (*cvi)->biases.erase(bi); + break; + } + } + } + + colvarmodule *cv = cvm::main(); + // ...and from the colvars module + for (std::vector::iterator bi = cv->biases.begin(); + bi != cv->biases.end(); + ++bi) { + if ( *bi == this) { + cv->biases.erase(bi); + break; + } + } + + if (biasing_force_scaling_factors != NULL) { + delete biasing_force_scaling_factors; + biasing_force_scaling_factors = NULL; + biasing_force_scaling_factors_bin.clear(); + } + + cv->config_changed(); + + return COLVARS_OK; +} + + +int colvarbias::clear_state_data() +{ + // no mutable content to delete for base class + return COLVARS_OK; +} + + +int colvarbias::add_colvar(std::string const &cv_name) +{ + if (colvar *cv = cvm::colvar_by_name(cv_name)) { + + if (cvm::debug()) { + cvm::log("Applying this bias to collective variable \""+ + cv->name+"\".\n"); + } + + colvars.push_back(cv); + cv->biases.push_back(this); // add back-reference to this bias to colvar + + // Add dependency link. All biases need at least the value of each colvar + // although possibly not at all timesteps + add_child(cv); + + colvar_forces.push_back(colvarvalue()); + colvar_forces.back().type(cv->value()); // make sure each force is initialized to zero + colvar_forces.back().is_derivative(); // colvar constraints are not applied to the force + colvar_forces.back().reset(); + previous_colvar_forces.push_back(colvar_forces.back()); + + } else { + cvm::error("Error: cannot find a colvar named \""+ + cv_name+"\".\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + return COLVARS_OK; +} + + +int colvarbias::update() +{ + if (cvm::debug()) { + cvm::log("Updating the "+bias_type+" bias \""+this->name+"\".\n"); + } + + int error_code = COLVARS_OK; + + has_data = true; + + // Update the cached colvar values + for (size_t i = 0; i < num_variables(); i++) { + colvar_values[i] = colvars[i]->value(); + } + + error_code |= calc_energy(NULL); + error_code |= calc_forces(NULL); + + return error_code; +} + + +bool colvarbias::can_accumulate_data() +{ + colvarproxy *proxy = cvm::main()->proxy; + if (((cvm::step_relative() > 0) && !proxy->simulation_continuing()) || + is_enabled(f_cvb_step_zero_data)) { + return true; + } + return false; +} + + +int colvarbias::calc_energy(std::vector const *) +{ + bias_energy = 0.0; + return COLVARS_OK; +} + + +int colvarbias::calc_forces(std::vector const *) +{ + for (size_t ir = 0; ir < num_variables(); ir++) { + colvar_forces[ir].reset(); + } + return COLVARS_OK; +} + + +int colvarbias::communicate_forces() +{ + int error_code = COLVARS_OK; + if (! is_enabled(f_cvb_apply_force)) { + return error_code; + } + cvm::real biasing_force_factor = 1.0; + size_t i = 0; + if (is_enabled(f_cvb_scale_biasing_force)) { + for (i = 0; i < num_variables(); i++) { + biasing_force_scaling_factors_bin[i] = biasing_force_scaling_factors->current_bin_scalar(i); + } + if (biasing_force_scaling_factors->index_ok(biasing_force_scaling_factors_bin)) { + biasing_force_factor *= biasing_force_scaling_factors->value(biasing_force_scaling_factors_bin); + } + } + for (i = 0; i < num_variables(); i++) { + if (cvm::debug()) { + cvm::log("Communicating a force to colvar \""+ + variables(i)->name+"\".\n"); + } + // Impulse-style multiple timestep + // Note that biases with different values of time_step_factor + // may send forces to the same colvar + // which is why rescaling has to happen now: the colvar is not + // aware of this bias' time_step_factor + if (is_enabled(f_cvb_bypass_ext_lagrangian)) { + variables(i)->add_bias_force_actual_value(cvm::real(time_step_factor) * colvar_forces[i] * biasing_force_factor); + } else { + variables(i)->add_bias_force(cvm::real(time_step_factor) * colvar_forces[i] * biasing_force_factor); + } + previous_colvar_forces[i] = colvar_forces[i]; + } + return error_code; +} + + +int colvarbias::end_of_step() +{ + return COLVARS_OK; +} + + +int colvarbias::change_configuration(std::string const & /* conf */) +{ + cvm::error("Error: change_configuration() not implemented.\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; +} + + +cvm::real colvarbias::energy_difference(std::string const & /* conf */) +{ + cvm::error("Error: energy_difference() not implemented.\n", + COLVARS_NOT_IMPLEMENTED); + return 0.0; +} + + +// So far, these are only implemented in colvarbias_abf +int colvarbias::bin_num() +{ + cvm::error("Error: bin_num() not implemented.\n"); + return COLVARS_NOT_IMPLEMENTED; +} +int colvarbias::current_bin() +{ + cvm::error("Error: current_bin() not implemented.\n"); + return COLVARS_NOT_IMPLEMENTED; +} +int colvarbias::bin_count(int /* bin_index */) +{ + cvm::error("Error: bin_count() not implemented.\n"); + return COLVARS_NOT_IMPLEMENTED; +} +int colvarbias::replica_share() +{ + cvm::error("Error: replica_share() not implemented.\n"); + return COLVARS_NOT_IMPLEMENTED; +} + + +std::string const colvarbias::get_state_params() const +{ + std::ostringstream os; + os << "step " << cvm::step_absolute() << "\n" + << "name " << this->name << "\n"; + return os.str(); +} + + +int colvarbias::check_matching_state(std::string const &conf) +{ + std::string check_name = ""; + colvarparse::get_keyval(conf, "name", check_name, + std::string(""), colvarparse::parse_silent); + + if (check_name.size() == 0) { + return cvm::error("Error: \""+bias_type+"\" block within the state file " + "has no identifiers.\n", COLVARS_INPUT_ERROR); + } + + if (check_name != this->name) { + if (cvm::debug()) { + cvm::log("Ignoring state of bias \""+check_name+ + "\": this bias is named \""+name+"\".\n"); + } + matching_state = false; + } else { + matching_state = true; + } + + return COLVARS_OK; +} + + +int colvarbias::set_state_params(std::string const &conf) +{ + colvarparse::get_keyval(conf, "step", state_file_step, + cvm::step_absolute(), colvarparse::parse_silent); + + return COLVARS_OK; +} + + +std::ostream & colvarbias::write_state(std::ostream &os) +{ + if (cvm::debug()) { + cvm::log("Writing formatted state for bias \""+name+"\"\n"); + } + os.setf(std::ios::scientific, std::ios::floatfield); + os.precision(cvm::cv_prec); + os << state_keyword << " {\n" + << " configuration {\n" + << get_state_params() + << " }\n"; + write_state_data(os); + os << "}\n\n"; + return os; +} + + +cvm::memory_stream & colvarbias::write_state(cvm::memory_stream &os) +{ + if (cvm::debug()) { + cvm::log("Writing unformatted state for bias \""+name+"\"\n"); + } + os << state_keyword << std::string("configuration") << get_state_params(); + write_state_data(os); + return os; +} + + +template +void raise_error_rewind(IST &is, SPT start_pos, std::string const &bias_type, + std::string const &bias_name, std::string const added_msg = "") +{ + auto state = is.rdstate(); + is.clear(); + is.seekg(start_pos); + is.setstate(state | std::ios::failbit); + cvm::error("Error: in reading state for \"" + bias_type + "\" bias \"" + bias_name + + "\" at position " + cvm::to_str(static_cast(is.tellg())) + " in stream." + + added_msg + "\n", + COLVARS_INPUT_ERROR); +} + + +template IST & colvarbias::read_state_template_(IST &is) +{ + auto const start_pos = is.tellg(); + + std::string key, brace, conf; + if (is >> key) { + if (key == state_keyword || key == bias_type) { + + if (! std::is_same::value) { + // Formatted input only + if (!(is >> brace) || !(brace == "{") ) { + raise_error_rewind(is, start_pos, bias_type, name); + return is; + } + } + + if (!(is >> colvarparse::read_block("configuration", &conf)) || + (check_matching_state(conf) != COLVARS_OK)) { + raise_error_rewind(is, start_pos, bias_type, name); + return is; + } + + } else { + // Not a match for this bias type, rewind without error + is.seekg(start_pos); + return is; + } + + } else { + raise_error_rewind(is, start_pos, bias_type, name); + return is; + } + + if (!matching_state) { + // No errors, but not a match for this bias instance; rewind + is.seekg(start_pos); + return is; + } + + if ((set_state_params(conf) != COLVARS_OK) || !read_state_data(is)) { + raise_error_rewind(is, start_pos, bias_type, name); + } + + if (! std::is_same::value) { + is >> brace; + if (brace != "}") { + cvm::error("Error: corrupt restart information for \""+bias_type+"\" bias \""+ + this->name+"\": no matching brace at position "+ + cvm::to_str(static_cast(is.tellg()))+ + " in stream.\n"); + raise_error_rewind(is, start_pos, bias_type, name); + } + } + + cvm::log("Restarted " + bias_type + " bias \"" + name + "\" with step number " + + cvm::to_str(state_file_step) + ".\n"); + + return is; +} + + +std::istream &colvarbias::read_state(std::istream &is) +{ + return read_state_template_(is); +} + + +cvm::memory_stream &colvarbias::read_state(cvm::memory_stream &is) +{ + return read_state_template_(is); +} + + +int colvarbias::write_state_prefix(std::string const &prefix) +{ + std::string const filename = + cvm::state_file_prefix(prefix.c_str())+".colvars.state"; + std::ostream &os = cvm::proxy->output_stream(filename.c_str(), "bias state file"); + int error_code = COLVARS_OK; + if (os) { + os.setf(std::ios::scientific, std::ios::floatfield); + error_code = write_state(os) ? COLVARS_OK : COLVARS_FILE_ERROR; + } else { + error_code = COLVARS_FILE_ERROR; + } + cvm::proxy->close_output_stream(filename.c_str()); + return error_code; +} + + +int colvarbias::write_state_string(std::string &output) +{ + std::ostringstream os; + if (!write_state(os)) { + return cvm::error("Error: in writing state of bias \""+name+ + "\" to buffer.\n", COLVARS_FILE_ERROR); + } + output = os.str(); + return COLVARS_OK; +} + + +int colvarbias::read_state_prefix(std::string const &prefix) +{ + std::string filename(prefix+std::string(".colvars.state")); + std::istream *is = &(cvm::main()->proxy->input_stream(filename, + "bias state file", + false)); + if (!*is) { + filename = prefix; + is = &(cvm::main()->proxy->input_stream(filename, "bias state file")); + } + + if (read_state(*is)) { + return cvm::main()->proxy->close_input_stream(filename); + } + return COLVARS_FILE_ERROR; +} + + +int colvarbias::read_state_string(char const *buffer) +{ + if (buffer != NULL) { + size_t const buffer_size = strlen(buffer); + if (cvm::debug()) { + cvm::log("colvarbias::read_state_string() with argument:\n"); + cvm::log(buffer); + } + + if (buffer_size > 0) { + std::istringstream is; + is.rdbuf()->pubsetbuf(const_cast(buffer), buffer_size); + return read_state(is).good() ? COLVARS_OK : + cvm::error("Error: in reading state for \""+name+"\" from buffer.\n", + COLVARS_FILE_ERROR); + } + return COLVARS_OK; + } + return cvm::error("Error: NULL pointer for colvarbias::read_state_string()", + COLVARS_BUG_ERROR); +} + + +std::ostream &colvarbias::write_state_data_key(std::ostream &os, std::string const &key, + bool header) +{ + os << (header ? "\n" : "") << key << (header ? "\n" : " "); + return os; +} + + +cvm::memory_stream &colvarbias::write_state_data_key(cvm::memory_stream &os, std::string const &key, + bool /* header */) +{ + os << std::string(key); + return os; +} + + +template +IST &colvarbias::read_state_data_key_template_(IST &is, std::string const &key) +{ + auto const start_pos = is.tellg(); + std::string key_in; + if (is >> key_in) { + if (key_in != key) { + raise_error_rewind(is, start_pos, bias_type, name, + " Expected keyword \"" + std::string(key) + "\", found \"" + key_in + + "\"."); + } + } else { + raise_error_rewind(is, start_pos, bias_type, name); + } + return is; +} + + +std::istream & colvarbias::read_state_data_key(std::istream &is, std::string const &key) +{ + return read_state_data_key_template_(is, key); +} + + +cvm::memory_stream & colvarbias::read_state_data_key(cvm::memory_stream &is, std::string const &key) +{ + return read_state_data_key_template_(is, key); +} + + +std::ostream & colvarbias::write_traj_label(std::ostream &os) +{ + os << " "; + if (b_output_energy) + os << " E_" + << cvm::wrap_string(this->name, cvm::en_width-2); + return os; +} + + +std::ostream & colvarbias::write_traj(std::ostream &os) +{ + os << " "; + if (b_output_energy) + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << bias_energy; + return os; +} + + + +colvarbias_ti::colvarbias_ti(char const *key) + : colvarbias(key) +{ + colvarproxy *proxy = cvm::main()->proxy; + provide(f_cvb_calc_ti_samples); + if (!proxy->total_forces_same_step()) { + // Samples at step zero can not be collected + feature_states[f_cvb_step_zero_data].available = false; + } + ti_avg_forces = NULL; + ti_count = NULL; +} + + +colvarbias_ti::~colvarbias_ti() +{ + colvarbias_ti::clear_state_data(); +} + + +int colvarbias_ti::clear_state_data() +{ + if (ti_avg_forces != NULL) { + delete ti_avg_forces; + ti_avg_forces = NULL; + } + if (ti_count != NULL) { + delete ti_count; + ti_count = NULL; + } + return COLVARS_OK; +} + + +int colvarbias_ti::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + + get_keyval_feature(this, conf, "writeTISamples", + f_cvb_write_ti_samples, + is_enabled(f_cvb_write_ti_samples)); + + get_keyval_feature(this, conf, "writeTIPMF", + f_cvb_write_ti_pmf, + is_enabled(f_cvb_write_ti_pmf)); + + if ((num_variables() > 1) && is_enabled(f_cvb_write_ti_pmf)) { + return cvm::error("Error: only 1-dimensional PMFs can be written " + "on the fly.\n" + "Consider using writeTISamples instead and " + "post-processing the sampled free-energy gradients.\n", + COLVARS_NOT_IMPLEMENTED); + } else { + error_code |= init_grids(); + } + + if (is_enabled(f_cvb_write_ti_pmf)) { + enable(f_cvb_write_ti_samples); + } + + if (is_enabled(f_cvb_calc_ti_samples)) { + std::vector const time_biases = + cvm::main()->time_dependent_biases(); + if (time_biases.size() > 0) { + if ((time_biases.size() > 1) || (time_biases[0] != this->name)) { + for (size_t i = 0; i < num_variables(); i++) { + if (! variables(i)->is_enabled(f_cv_subtract_applied_force)) { + return cvm::error("Error: cannot collect TI samples while other " + "time-dependent biases are active and not all " + "variables have subtractAppliedForces on.\n", + COLVARS_INPUT_ERROR); + } + } + } + } + } + + if (is_enabled(f_cvb_write_ti_pmf) || is_enabled(f_cvb_write_ti_samples)) { + cvm::main()->cite_feature("Internal-forces free energy estimator"); + } + + return error_code; +} + + +int colvarbias_ti::init_grids() +{ + if (is_enabled(f_cvb_calc_ti_samples)) { + if (ti_avg_forces == NULL) { + ti_bin.resize(num_variables()); + ti_system_forces.resize(num_variables()); + for (size_t icv = 0; icv < num_variables(); icv++) { + ti_system_forces[icv].type(variables(icv)->value()); + ti_system_forces[icv].is_derivative(); + ti_system_forces[icv].reset(); + } + ti_avg_forces = new colvar_grid_gradient(colvars); + ti_count = new colvar_grid_count(colvars); + ti_avg_forces->samples = ti_count; + ti_count->has_parent_data = true; + } + } + + return COLVARS_OK; +} + + +int colvarbias_ti::update() +{ + return update_system_forces(NULL); +} + + +int colvarbias_ti::update_system_forces(std::vector const + *subtract_forces) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return COLVARS_OK; + } + + has_data = true; + + if (cvm::debug()) { + cvm::log("Updating system forces for bias "+this->name+"\n"); + } + + colvarproxy *proxy = cvm::main()->proxy; + + size_t i; + + if (proxy->total_forces_same_step()) { + for (i = 0; i < num_variables(); i++) { + ti_bin[i] = ti_avg_forces->current_bin_scalar(i); + } + } + + // Collect total colvar forces + if ((cvm::step_relative() > 0) || proxy->total_forces_same_step()) { + if (ti_avg_forces->index_ok(ti_bin)) { + for (i = 0; i < num_variables(); i++) { + if (variables(i)->is_enabled(f_cv_subtract_applied_force)) { + // this colvar is already subtracting all applied forces + ti_system_forces[i] = variables(i)->total_force(); + } else { + ti_system_forces[i] = variables(i)->total_force() - + ((subtract_forces != NULL) ? + (*subtract_forces)[i] : previous_colvar_forces[i]); + } + } + if (cvm::step_relative() > 0 || is_enabled(f_cvb_step_zero_data)) { + ti_avg_forces->acc_value(ti_bin, ti_system_forces); + } + } + } + + if (!proxy->total_forces_same_step()) { + // Set the index for use in the next iteration, when total forces come in + for (i = 0; i < num_variables(); i++) { + ti_bin[i] = ti_avg_forces->current_bin_scalar(i); + } + } + + return COLVARS_OK; +} + + +std::string const colvarbias_ti::get_state_params() const +{ + return std::string(""); +} + + +int colvarbias_ti::set_state_params(std::string const & /* state_conf */) +{ + return COLVARS_OK; +} + + +std::ostream & colvarbias_ti::write_state_data(std::ostream &os) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return os; + } + write_state_data_key(os, "histogram"); + ti_count->write_raw(os); + write_state_data_key(os, "system_forces"); + ti_avg_forces->write_raw(os); + return os; +} + + +cvm::memory_stream & colvarbias_ti::write_state_data(cvm::memory_stream &os) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return os; + } + write_state_data_key(os, "histogram"); + ti_count->write_raw(os); + write_state_data_key(os, "system_forces"); + ti_avg_forces->write_raw(os); + return os; +} + + +std::istream & colvarbias_ti::read_state_data(std::istream &is) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return is; + } + if (cvm::debug()) { + cvm::log("Reading state data for the TI estimator.\n"); + } + if (! read_state_data_key(is, "histogram")) { + return is; + } + if (! ti_count->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "system_forces")) { + return is; + } + if (! ti_avg_forces->read_raw(is)) { + return is; + } + if (cvm::debug()) { + cvm::log("Done reading state data for the TI estimator.\n"); + } + return is; +} + + +cvm::memory_stream & colvarbias_ti::read_state_data(cvm::memory_stream &is) +{ + if (! is_enabled(f_cvb_calc_ti_samples)) { + return is; + } + if (cvm::debug()) { + cvm::log("Reading state data for the TI estimator.\n"); + } + if (! read_state_data_key(is, "histogram")) { + return is; + } + if (! ti_count->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "system_forces")) { + return is; + } + if (! ti_avg_forces->read_raw(is)) { + return is; + } + if (cvm::debug()) { + cvm::log("Done reading state data for the TI estimator.\n"); + } + return is; +} + + +int colvarbias_ti::write_output_files() +{ + int error_code = COLVARS_OK; + + if (!has_data) { + // nothing to write + return COLVARS_OK; + } + + std::string const ti_output_prefix = cvm::output_prefix()+"."+this->name; + + if (is_enabled(f_cvb_write_ti_samples)) { + std::string const ti_count_file_name(ti_output_prefix+".ti.count"); + error_code |= ti_count->write_multicol(ti_count_file_name, "TI count file"); + + std::string const ti_grad_file_name(ti_output_prefix+".ti.force"); + error_code |= ti_avg_forces->write_multicol(ti_grad_file_name, "TI gradient file"); + } + + if (is_enabled(f_cvb_write_ti_pmf)) { + std::string const pmf_file_name(ti_output_prefix+".ti.pmf"); + cvm::log("Writing TI PMF to file \""+pmf_file_name+"\".\n"); + std::ostream &os = cvm::proxy->output_stream(pmf_file_name, "TI PMF"); + if (os) { + // get the FE gradient + ti_avg_forces->multiply_constant(-1.0); + ti_avg_forces->write_1D_integral(os); + ti_avg_forces->multiply_constant(-1.0); + cvm::proxy->close_output_stream(pmf_file_name); + } else { + error_code |= COLVARS_FILE_ERROR; + } + } + + return error_code; +} + + +// Static members + +std::vector colvarbias::cvb_features; diff --git a/src/external/colvars/colvarbias.h b/src/external/colvars/colvarbias.h new file mode 100644 index 00000000000..9a51f9d513b --- /dev/null +++ b/src/external/colvars/colvarbias.h @@ -0,0 +1,367 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_H +#define COLVARBIAS_H + +#include "colvar.h" +#include "colvarparse.h" +#include "colvardeps.h" + +class colvar_grid_scalar; + +/// \brief Collective variable bias, base class +class colvarbias + : public virtual colvarparse, public virtual colvardeps { +public: + + /// Name of this bias + std::string name; + + /// Keyword indicating the type of this bias + std::string bias_type; + + /// Keyword used in state files (== bias_type most of the time) + std::string state_keyword; + + /// Track how many times a bias of this type was defined + int rank; + + /// Add a new collective variable to this bias + int add_colvar(std::string const &cv_name); + + /// How many variables are defined for this bias + inline size_t num_variables() const + { + return colvars.size(); + } + + /// Access the variables vector + inline std::vector *variables() + { + return &colvars; + } + + /// Access the i-th variable + inline colvar * variables(int i) const + { + return colvars[i]; + } + + /// Retrieve colvar values and calculate their biasing forces + /// Some implementations may use calc_energy() and calc_forces() + virtual int update(); + + /// Returns true if the current step represent a valid increment, whose data + /// can be recorded (as opposed to e.g. a continuation step from a restart) + virtual bool can_accumulate_data(); + + /// Compute the energy of the bias + /// Uses the vector of colvar values provided if not NULL, and the values + /// currently cached in the bias instance otherwise + virtual int calc_energy(std::vector const *values); + + /// Compute the forces due to the bias + /// Uses the vector of colvar values provided if not NULL, and the values + /// currently cached in the bias instance otherwise + virtual int calc_forces(std::vector const *values); + + /// Send forces to the collective variables + int communicate_forces(); + + /// Carry out operations needed before next step is run + virtual int end_of_step(); + + /// Load new configuration - force constant and/or centers only + virtual int change_configuration(std::string const &conf); + + /// Calculate change in energy from using alternate configuration + virtual cvm::real energy_difference(std::string const &conf); + + /// Give the total number of bins for a given bias. + // FIXME this is currently 1D only + virtual int bin_num(); + /// Calculate the bin index for a given bias. + // FIXME this is currently 1D only + virtual int current_bin(); + //// Give the count at a given bin index. + // FIXME this is currently 1D only + virtual int bin_count(int bin_index); + //// Share information between replicas, whatever it may be. + virtual int replica_share(); + + /// Perform analysis tasks + virtual void analyze() {} + + /// \brief Constructor + colvarbias(char const *key); + + /// \brief Parse config string and (re)initialize + virtual int init(std::string const &conf); + + /// \brief Initialize dependency tree + virtual int init_dependencies(); + + /// \brief Set to zero all mutable data + virtual int reset(); + +private: + + /// Default constructor + colvarbias(); + + /// Copy constructor + colvarbias(colvarbias &); + +public: + + /// \brief Delete everything + virtual int clear(); + + /// \brief Delete only the allocatable data (save memory) + virtual int clear_state_data(); + + /// Destructor + virtual ~colvarbias(); + + /// Write the values of specific mutable properties to a string + virtual std::string const get_state_params() const; + + /// Check the name of the bias vs. the given string, set the matching_state flag accordingly + int check_matching_state(std::string const &conf); + + /// Read the values of specific mutable properties from a string + virtual int set_state_params(std::string const &state_conf); + + /// Write all mutable data not already written by get_state_params() to a formatted stream + virtual std::ostream & write_state_data(std::ostream &os) + { + return os; + } + + /// Write all mutable data not already written by get_state_params() to an unformatted stream + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os) + { + return os; + } + + /// Read all mutable data not already set by set_state_params() from a formatted stream + virtual std::istream & read_state_data(std::istream &is) + { + return is; + } + + /// Read all mutable data not already set by set_state_params() from an unformatted stream + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is) + { + return is; + } + + /// Write a keyword header for a data sequence to a formatted stream + /// \param[in,out] os Output stream + /// \param[in] key Keyword labeling the header block + /// \param[in] header Whether this is the header of a multi-line segment vs a single line + std::ostream &write_state_data_key(std::ostream &os, std::string const &key, bool header = true); + + /// Write a keyword header for a data sequence to an unformatted stream + /// \param[in,out] os Output stream + /// \param[in] key Keyword labeling the header block + /// \param[in] header Ignored + cvm::memory_stream &write_state_data_key(cvm::memory_stream &os, std::string const &key, + bool header = true); + +private: + + /// Read a keyword header for a data sequence from a stream + /// \param[in,out] Input stream + /// \param[in] Keyword labeling the header block; an error will be raised if not matching + template IST &read_state_data_key_template_(IST &is, std::string const &key); + +public: + + /// Read a keyword header for a data sequence from a formatted stream + /// \param[in,out] Input stream + /// \param[in] Keyword labeling the header block; an error will be raised if not matching + std::istream & read_state_data_key(std::istream &is, std::string const &key); + + /// Read a keyword header for a data sequence from an unformatted stream + /// \param[in,out] Input stream + /// \param[in] Keyword labeling the header block; an error will be raised if not matching + cvm::memory_stream & read_state_data_key(cvm::memory_stream &is, std::string const &key); + +private: + + /// Generic stream reading function (formatted and not) + template IST & read_state_template_(IST &is); + +public: + + /// Write the bias configuration to a formatted stream + std::ostream &write_state(std::ostream &os); + + /// Write the bias configuration to an unformatted stream + cvm::memory_stream & write_state(cvm::memory_stream &os); + + /// Read the bias configuration from a formatted stream + std::istream & read_state(std::istream &is); + + /// Read the bias configuration from an unformatted stream + cvm::memory_stream & read_state(cvm::memory_stream &is); + + /// Write the bias state to a file with the given prefix + int write_state_prefix(std::string const &prefix); + + /// Write the bias state to a string + int write_state_string(std::string &output); + + /// Read the bias state from a file with this name or prefix + int read_state_prefix(std::string const &prefix); + + /// Read the bias state from this string buffer + int read_state_string(char const *buffer); + + /// Write a label to the trajectory file (comment line) + virtual std::ostream & write_traj_label(std::ostream &os); + + /// Output quantities such as the bias energy to the trajectory file + virtual std::ostream & write_traj(std::ostream &os); + + /// (Re)initialize the output files (does not write them yet) + virtual int setup_output() + { + return COLVARS_OK; + } + + /// Frequency for writing output files + size_t output_freq; + + /// Write any output files that this bias may have (e.g. PMF files) + virtual int write_output_files() + { + return COLVARS_OK; + } + + /// Use this prefix for all output files + std::string output_prefix; + + /// If this bias is communicating with other replicas through files, send it to them + virtual int write_state_to_replicas() + { + return COLVARS_OK; + } + + inline cvm::real get_energy() + { + return bias_energy; + } + + /// \brief Implementation of the feature list for colvarbias + static std::vector cvb_features; + + /// \brief Implementation of the feature list accessor for colvarbias + virtual const std::vector &features() const + { + return cvb_features; + } + virtual std::vector &modify_features() + { + return cvb_features; + } + static void delete_features() { + for (size_t i=0; i < cvb_features.size(); i++) { + delete cvb_features[i]; + } + cvb_features.clear(); + } + +protected: + + /// \brief Pointers to collective variables to which the bias is + /// applied; current values and metric functions will be obtained + /// through each colvar object + std::vector colvars; + + /// \brief Up to date value of each colvar + std::vector colvar_values; + + /// \brief Current forces from this bias to the variables + std::vector colvar_forces; + + /// \brief Forces last applied by this bias to the variables + std::vector previous_colvar_forces; + + /// \brief Current energy of this bias (colvar_forces should be obtained by deriving this) + cvm::real bias_energy; + + /// Whether to write the current bias energy from this bias to the trajectory file + bool b_output_energy; + + /// \brief Whether this bias has already accumulated information + /// (for history-dependent biases) + bool has_data; + + /// \brief Step number read from the last state file + cvm::step_number state_file_step; + + /// Flag used to tell if the state string being read is for this bias + bool matching_state; + + /// \brief The biasing forces will be scaled by the factor in this grid + /// if b_bias_force_scaled is true + colvar_grid_scalar* biasing_force_scaling_factors; + std::vector biasing_force_scaling_factors_bin; +}; + + +class colvar_grid_gradient; +class colvar_grid_count; + +/// \brief Base class for unconstrained thermodynamic-integration FE estimator +class colvarbias_ti : public virtual colvarbias { +public: + + colvarbias_ti(char const *key); + virtual ~colvarbias_ti(); + + virtual int clear_state_data(); + + virtual int init(std::string const &conf); + virtual int init_grids(); + virtual int update(); + + /// Subtract applied forces (either last forces or argument) from the total + /// forces + virtual int update_system_forces(std::vector const + *subtract_forces); + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &state_conf); + virtual std::ostream & write_state_data(std::ostream &os); + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os); + virtual std::istream & read_state_data(std::istream &is); + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is); + virtual int write_output_files(); + +protected: + + /// \brief Forces exerted from the system to the associated variables + std::vector ti_system_forces; + + /// Averaged system forces + colvar_grid_gradient *ti_avg_forces; + + /// Histogram of sampled data + colvar_grid_count *ti_count; + + /// Because total forces may be from the last simulation step, + /// store the index of the variables then + std::vector ti_bin; +}; + +#endif diff --git a/src/external/colvars/colvarbias_abf.cpp b/src/external/colvars/colvarbias_abf.cpp new file mode 100644 index 00000000000..dcf58a31969 --- /dev/null +++ b/src/external/colvars/colvarbias_abf.cpp @@ -0,0 +1,924 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include + +#include "colvarmodule.h" +#include "colvar.h" +#include "colvarbias_abf.h" +#include "colvars_memstream.h" + + +colvarbias_abf::colvarbias_abf(char const *key) + : colvarbias(key), + b_UI_estimator(false), + b_CZAR_estimator(false), + pabf_freq(0), + system_force(NULL), + gradients(NULL), + samples(NULL), + pmf(NULL), + z_gradients(NULL), + z_samples(NULL), + czar_gradients(NULL), + czar_pmf(NULL), + last_gradients(NULL), + last_samples(NULL) +{ + colvarproxy *proxy = cvm::main()->proxy; + if (!proxy->total_forces_same_step()) { + // Samples at step zero can not be collected + feature_states[f_cvb_step_zero_data].available = false; + } +} + + +int colvarbias_abf::init(std::string const &conf) +{ + colvarproxy *proxy = cvm::main()->proxy; + + colvarbias::init(conf); + cvm::main()->cite_feature("ABF colvar bias implementation"); + + enable(f_cvb_scalar_variables); + enable(f_cvb_calc_pmf); + + if ((proxy->target_temperature() == 0.0) && proxy->simulation_running()) { + cvm::log("WARNING: ABF should not be run without a thermostat or at 0 Kelvin!\n"); + } + + // ************* parsing general ABF options *********************** + + get_keyval_feature((colvarparse *)this, conf, "applyBias", f_cvb_apply_force, true); + if (!is_enabled(f_cvb_apply_force)){ + cvm::log("WARNING: ABF biases will *not* be applied!\n"); + } + + get_keyval(conf, "updateBias", update_bias, true); + if (update_bias) { + enable(f_cvb_history_dependent); + } else { + cvm::log("WARNING: ABF biases will *not* be updated!\n"); + } + + get_keyval(conf, "hideJacobian", hide_Jacobian, false); + if (hide_Jacobian) { + cvm::log("Jacobian (geometric) forces will be handled internally.\n"); + } else { + cvm::log("Jacobian (geometric) forces will be included in reported free energy gradients.\n"); + } + + get_keyval(conf, "fullSamples", full_samples, 200); + if ( full_samples <= 1 ) full_samples = 1; + min_samples = full_samples / 2; + // full_samples - min_samples >= 1 is guaranteed + + get_keyval(conf, "inputPrefix", input_prefix, std::vector()); + + get_keyval(conf, "historyFreq", history_freq, 0); + if (history_freq != 0) { + if (output_freq == 0) { + cvm::error("Error: historyFreq must be a multiple of outputFreq.\n", + COLVARS_INPUT_ERROR); + } else { + if ((history_freq % output_freq) != 0) { + cvm::error("Error: historyFreq must be a multiple of outputFreq.\n", + COLVARS_INPUT_ERROR); + } + } + } + b_history_files = (history_freq > 0); + + // shared ABF + get_keyval(conf, "shared", shared_on, false); + if (shared_on) { + cvm::main()->cite_feature("Multiple-walker ABF implementation"); + if ((proxy->replica_enabled() != COLVARS_OK) || + (proxy->num_replicas() <= 1)) { + return cvm::error("Error: shared ABF requires more than one replica.", + COLVARS_INPUT_ERROR); + } + cvm::log("shared ABF will be applied among "+ + cvm::to_str(proxy->num_replicas()) + " replicas.\n"); + if (cvm::proxy->smp_enabled() == COLVARS_OK) { + cvm::error("Error: shared ABF is currently not available with SMP parallelism; " + "please set \"SMP off\" at the top of the Colvars configuration file.\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; + } + + // If shared_freq is not set, we default to output_freq + get_keyval(conf, "sharedFreq", shared_freq, output_freq); + } + + // ************* checking the associated colvars ******************* + + if (num_variables() == 0) { + cvm::error("Error: no collective variables specified for the ABF bias.\n"); + return COLVARS_ERROR; + } + + if (update_bias) { + // Request calculation of total force + if(enable(f_cvb_get_total_force)) return cvm::get_error(); + } + + bool b_extended = false; + size_t i; + for (i = 0; i < num_variables(); i++) { + + if (colvars[i]->value().type() != colvarvalue::type_scalar) { + cvm::error("Error: ABF bias can only use scalar-type variables.\n"); + } + colvars[i]->enable(f_cv_grid); // Could be a child dependency of a f_cvb_use_grids feature + if (hide_Jacobian) { + colvars[i]->enable(f_cv_hide_Jacobian); + } + + // If any colvar is extended-system (restrained style, not external with constraint), we are running eABF + if (colvars[i]->is_enabled(f_cv_extended_Lagrangian) + && !colvars[i]->is_enabled(f_cv_external)) { + b_extended = true; + } + + // Cannot mix and match coarse time steps with ABF because it gives + // wrong total force averages - total force needs to be averaged over + // every time step + if (colvars[i]->get_time_step_factor() != time_step_factor) { + cvm::error("Error: " + colvars[i]->description + " has a value of timeStepFactor (" + + cvm::to_str(colvars[i]->get_time_step_factor()) + ") different from that of " + + description + " (" + cvm::to_str(time_step_factor) + ").\n"); + return COLVARS_ERROR; + } + + // Here we could check for orthogonality of the Cartesian coordinates + // and make it just a warning if some parameter is set? + } + + if (b_extended) { + cvm::main()->cite_feature("eABF implementation"); + } else { + cvm::main()->cite_feature("Internal-forces free energy estimator"); + } + + if (get_keyval(conf, "maxForce", max_force)) { + if (max_force.size() != num_variables()) { + cvm::error("Error: Number of parameters to maxForce does not match number of colvars."); + } + for (i = 0; i < num_variables(); i++) { + if (max_force[i] < 0.0) { + cvm::error("Error: maxForce should be non-negative."); + return COLVARS_ERROR; + } + } + cap_force = true; + } else { + cap_force = false; + } + + bin.assign(num_variables(), 0); + force_bin.assign(num_variables(), 0); + system_force = new cvm::real [num_variables()]; + + // Construct empty grids based on the colvars + if (cvm::debug()) { + cvm::log("Allocating count and free energy gradient grids.\n"); + } + + samples = new colvar_grid_count(colvars); + gradients = new colvar_grid_gradient(colvars); + gradients->samples = samples; + samples->has_parent_data = true; + + // Data for eAB F z-based estimator + if ( b_extended ) { + get_keyval(conf, "CZARestimator", b_CZAR_estimator, true); + if ( b_CZAR_estimator ) { + cvm::main()->cite_feature("CZAR eABF estimator"); + } + // CZAR output files for stratified eABF + get_keyval(conf, "writeCZARwindowFile", b_czar_window_file, false, + colvarparse::parse_silent); + + z_bin.assign(num_variables(), 0); + z_samples = new colvar_grid_count(colvars); + z_samples->request_actual_value(); + z_gradients = new colvar_grid_gradient(colvars); + z_gradients->request_actual_value(); + z_gradients->samples = z_samples; + z_samples->has_parent_data = true; + czar_gradients = new colvar_grid_gradient(colvars); + } + + get_keyval(conf, "integrate", b_integrate, num_variables() <= 3); // Integrate for output if d<=3 + if (b_integrate) { + // For now, we integrate on-the-fly iff the grid is < 3D + if ( num_variables() > 3 ) { + cvm::error("Error: cannot integrate free energy in dimension > 3.\n"); + return COLVARS_ERROR; + } + pmf = new integrate_potential(colvars, gradients); + if ( b_CZAR_estimator ) { + czar_pmf = new integrate_potential(colvars, czar_gradients); + } + // Parameters for integrating initial (and final) gradient data + get_keyval(conf, "integrateMaxIterations", integrate_iterations, 10000, colvarparse::parse_silent); + get_keyval(conf, "integrateTol", integrate_tol, 1e-6, colvarparse::parse_silent); + // Projected ABF, updating the integrated PMF on the fly + get_keyval(conf, "pABFintegrateFreq", pabf_freq, 0, colvarparse::parse_silent); + get_keyval(conf, "pABFintegrateMaxIterations", pabf_integrate_iterations, 100, colvarparse::parse_silent); + get_keyval(conf, "pABFintegrateTol", pabf_integrate_tol, 1e-4, colvarparse::parse_silent); + } + + // For shared ABF, we store a second set of grids. + // This used to be only if "shared" was defined, + // but now we allow calling share externally (e.g. from Tcl). + last_samples = new colvar_grid_count(colvars); + last_gradients = new colvar_grid_gradient(colvars); + last_gradients->samples = last_samples; + last_samples->has_parent_data = true; + shared_last_step = -1; + + // If custom grids are provided, read them + if ( input_prefix.size() > 0 ) { + read_gradients_samples(); + // Update divergence to account for input data + pmf->set_div(); + } + + // if extendedLangrangian is on, then call UI estimator + if (b_extended) { + get_keyval(conf, "UIestimator", b_UI_estimator, false); + + if (b_UI_estimator) { + + cvm::main()->cite_feature("Umbrella-integration eABF estimator"); + std::vector UI_lowerboundary; + std::vector UI_upperboundary; + std::vector UI_width; + std::vector UI_krestr; + + bool UI_restart = (input_prefix.size() > 0); + + for (i = 0; i < num_variables(); i++) { + UI_lowerboundary.push_back(colvars[i]->lower_boundary); + UI_upperboundary.push_back(colvars[i]->upper_boundary); + UI_width.push_back(colvars[i]->width); + UI_krestr.push_back(colvars[i]->force_constant()); + } + eabf_UI = UIestimator::UIestimator(UI_lowerboundary, + UI_upperboundary, + UI_width, + UI_krestr, // force constant in eABF + output_prefix, // the prefix of output files + cvm::restart_out_freq, + UI_restart, // whether restart from a .count and a .grad file + input_prefix, // the prefixes of input files + proxy->target_temperature()); + } + } + + cvm::log("Finished ABF setup.\n"); + return COLVARS_OK; +} + +/// Destructor +colvarbias_abf::~colvarbias_abf() +{ + if (samples) { + delete samples; + samples = NULL; + } + + if (gradients) { + delete gradients; + gradients = NULL; + } + + if (pmf) { + delete pmf; + pmf = NULL; + } + + if (z_samples) { + delete z_samples; + z_samples = NULL; + } + + if (z_gradients) { + delete z_gradients; + z_gradients = NULL; + } + + if (czar_gradients) { + delete czar_gradients; + czar_gradients = NULL; + } + + if (czar_pmf) { + delete czar_pmf; + czar_pmf = NULL; + } + + // shared ABF + // We used to only do this if "shared" was defined, + // but now we can call shared externally + if (last_samples) { + delete last_samples; + last_samples = NULL; + } + + if (last_gradients) { + delete last_gradients; + last_gradients = NULL; + } + + if (system_force) { + delete [] system_force; + system_force = NULL; + } +} + + +/// Update the FE gradient, compute and apply biasing force +/// also output data to disk if needed + +int colvarbias_abf::update() +{ + if (cvm::debug()) cvm::log("Updating ABF bias " + this->name); + + size_t i; + for (i = 0; i < num_variables(); i++) { + bin[i] = samples->current_bin_scalar(i); + } + if (cvm::proxy->total_forces_same_step()) { + // e.g. in LAMMPS, total forces are current + force_bin = bin; + } + + if (cvm::step_relative() > 0 || is_enabled(f_cvb_step_zero_data)) { + + if (update_bias) { +// if (b_adiabatic_reweighting) { +// // Update gradients non-locally based on conditional distribution of +// // fictitious variable TODO +// +// } else + if (samples->index_ok(force_bin)) { + // Only if requested and within bounds of the grid... + + for (i = 0; i < num_variables(); i++) { + // get total forces (lagging by 1 timestep) from colvars + // and subtract previous ABF force if necessary + update_system_force(i); + } + gradients->acc_force(force_bin, system_force); + if ( b_integrate ) { + pmf->update_div_neighbors(force_bin); + } + } + } + + if ( z_gradients && update_bias ) { + for (i = 0; i < num_variables(); i++) { + z_bin[i] = z_samples->current_bin_scalar(i); + } + if ( z_samples->index_ok(z_bin) ) { + for (i = 0; i < num_variables(); i++) { + // If we are outside the range of xi, the force has not been obtained above + // the function is just an accessor, so cheap to call again anyway + update_system_force(i); + } + z_gradients->acc_force(z_bin, system_force); + } + } + + if ( b_integrate ) { + if ( pabf_freq && cvm::step_relative() % pabf_freq == 0 ) { + cvm::real err; + int iter = pmf->integrate(pabf_integrate_iterations, pabf_integrate_tol, err); + if ( iter == pabf_integrate_iterations ) { + cvm::log("Warning: PMF integration did not converge to " + cvm::to_str(pabf_integrate_tol) + + " in " + cvm::to_str(pabf_integrate_iterations) + + " steps. Residual error: " + cvm::to_str(err)); + } + pmf->set_zero_minimum(); // TODO: do this only when necessary + } + } + } + + if (!cvm::proxy->total_forces_same_step()) { + // e.g. in NAMD, total forces will be available for next timestep + // hence we store the current colvar bin + force_bin = bin; + } + + // Reset biasing forces from previous timestep + for (i = 0; i < num_variables(); i++) { + colvar_forces[i].reset(); + } + + // Compute and apply the new bias, if applicable + if (is_enabled(f_cvb_apply_force) && samples->index_ok(bin)) { + + cvm::real count = cvm::real(samples->value(bin)); + cvm::real fact = 1.0; + + // Factor that ensures smooth introduction of the force + if ( count < full_samples ) { + fact = (count < min_samples) ? 0.0 : + (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); + } + + std::vector grad(num_variables()); + + if ( pabf_freq ) { + // In projected ABF, the force is the PMF gradient estimate + pmf->vector_gradient_finite_diff(bin, grad); + } else { + // Normal ABF + gradients->vector_value(bin, grad); + } + +// if ( b_adiabatic_reweighting) { +// // Average of force according to conditional distribution of fictitious variable +// // need freshly integrated PMF, gradient TODO +// } else + if ( fact != 0.0 ) { + if ( (num_variables() == 1) && colvars[0]->periodic_boundaries() ) { + // Enforce a zero-mean bias on periodic, 1D coordinates + // in other words: boundary condition is that the biasing potential is periodic + // This is enforced naturally if using integrated PMF + colvar_forces[0].real_value = fact * (grad[0] - gradients->average ()); + } else { + for (i = 0; i < num_variables(); i++) { + // subtracting the mean force (opposite of the FE gradient) means adding the gradient + colvar_forces[i].real_value = fact * grad[i]; + } + } + if (cap_force) { + for (i = 0; i < num_variables(); i++) { + if ( colvar_forces[i].real_value * colvar_forces[i].real_value > max_force[i] * max_force[i] ) { + colvar_forces[i].real_value = (colvar_forces[i].real_value > 0 ? max_force[i] : -1.0 * max_force[i]); + } + } + } + } + } + + // update the output prefix; TODO: move later to setup_output() function + if (cvm::main()->num_biases_feature(colvardeps::f_cvb_calc_pmf) == 1) { + // This is the only bias computing PMFs + output_prefix = cvm::output_prefix(); + } else { + output_prefix = cvm::output_prefix() + "." + this->name; + } + + if (shared_on && shared_last_step >= 0 && cvm::step_absolute() % shared_freq == 0) { + // Share gradients and samples for shared ABF. + replica_share(); + } + + // Prepare for the first sharing. + if (shared_last_step < 0) { + // Copy the current gradient and count values into last. + last_gradients->copy_grid(*gradients); + last_samples->copy_grid(*samples); + shared_last_step = cvm::step_absolute(); + cvm::log("Prepared sample and gradient buffers at step "+cvm::to_str(cvm::step_absolute())+".\n"); + } + + // update UI estimator every step + if (b_UI_estimator) + { + std::vector x(num_variables(),0); + std::vector y(num_variables(),0); + for (i = 0; i < num_variables(); i++) + { + x[i] = colvars[i]->actual_value(); + y[i] = colvars[i]->value(); + } + eabf_UI.update_output_filename(output_prefix); + eabf_UI.update(cvm::step_absolute(), x, y); + } + + /// Compute the bias energy + int error_code = calc_energy(NULL); + + return error_code; +} + + +int colvarbias_abf::replica_share() { + + colvarproxy *proxy = cvm::main()->proxy; + + if (proxy->replica_enabled() != COLVARS_OK) { + cvm::error("Error: shared ABF: No replicas.\n"); + return COLVARS_ERROR; + } + // We must have stored the last_gradients and last_samples. + if (shared_last_step < 0 ) { + cvm::error("Error: shared ABF: Tried to apply shared ABF before any sampling had occurred.\n"); + return COLVARS_ERROR; + } + + // Share gradients for shared ABF. + cvm::log("shared ABF: Sharing gradient and samples among replicas at step "+cvm::to_str(cvm::step_absolute()) ); + + // Count of data items. + size_t data_n = gradients->raw_data_num(); + size_t samp_start = data_n*sizeof(cvm::real); + size_t msg_total = data_n*sizeof(size_t) + samp_start; + char* msg_data = new char[msg_total]; + + if (proxy->replica_index() == 0) { + int p; + // Replica 0 collects the delta gradient and count from the others. + for (p = 1; p < proxy->num_replicas(); p++) { + // Receive the deltas. + proxy->replica_comm_recv(msg_data, msg_total, p); + + // Map the deltas from the others into the grids. + last_gradients->raw_data_in((cvm::real*)(&msg_data[0])); + last_samples->raw_data_in((size_t*)(&msg_data[samp_start])); + + // Combine the delta gradient and count of the other replicas + // with Replica 0's current state (including its delta). + gradients->add_grid( *last_gradients ); + samples->add_grid( *last_samples ); + } + + // Now we must send the combined gradient to the other replicas. + gradients->raw_data_out((cvm::real*)(&msg_data[0])); + samples->raw_data_out((size_t*)(&msg_data[samp_start])); + for (p = 1; p < proxy->num_replicas(); p++) { + proxy->replica_comm_send(msg_data, msg_total, p); + } + + } else { + // All other replicas send their delta gradient and count. + // Calculate the delta gradient and count. + last_gradients->delta_grid(*gradients); + last_samples->delta_grid(*samples); + + // Cast the raw char data to the gradient and samples. + last_gradients->raw_data_out((cvm::real*)(&msg_data[0])); + last_samples->raw_data_out((size_t*)(&msg_data[samp_start])); + proxy->replica_comm_send(msg_data, msg_total, 0); + + // We now receive the combined gradient from Replica 0. + proxy->replica_comm_recv(msg_data, msg_total, 0); + // We sync to the combined gradient computed by Replica 0. + gradients->raw_data_in((cvm::real*)(&msg_data[0])); + samples->raw_data_in((size_t*)(&msg_data[samp_start])); + } + + // Without a barrier it's possible that one replica starts + // share 2 when other replicas haven't finished share 1. + proxy->replica_comm_barrier(); + // Done syncing the replicas. + delete[] msg_data; + + // Copy the current gradient and count values into last. + last_gradients->copy_grid(*gradients); + last_samples->copy_grid(*samples); + shared_last_step = cvm::step_absolute(); + + if (b_integrate) { + // Update divergence to account for newly shared gradients + pmf->set_div(); + } + return COLVARS_OK; +} + + +template int colvarbias_abf::write_grid_to_file(T const *grid, + std::string const &filename, + bool close) { + std::ostream &os = cvm::proxy->output_stream(filename, "multicolumn grid file"); + if (!os) { + return cvm::error("Error opening file " + filename + " for writing.\n", COLVARS_ERROR | COLVARS_FILE_ERROR); + } + grid->write_multicol(os); + if (close) { + cvm::proxy->close_output_stream(filename); + } else { + // Insert empty line between frames in history files + os << std::endl; + cvm::proxy->flush_output_stream(filename); + } + + // In dimension higher than 2, dx is easier to handle and visualize + // but we cannot write multiple frames in a dx file now + // (could be implemented as multiple dx files) + if (num_variables() > 2 && close) { + std::string dx = filename + ".dx"; + std::ostream &dx_os = cvm::proxy->output_stream(dx, "OpenDX grid file"); + if (!dx_os) { + return cvm::error("Error opening file " + dx + " for writing.\n", COLVARS_ERROR | COLVARS_FILE_ERROR); + } + grid->write_opendx(dx_os); + // if (close) { + cvm::proxy->close_output_stream(dx); + // } + // else { + // // TODO, decide convention for multiple datasets in dx file + // *dx_os << std::endl; + // dx_os->flush(); + // } + } + return COLVARS_OK; +} + + +void colvarbias_abf::write_gradients_samples(const std::string &prefix, bool close) +{ + colvarproxy *proxy = cvm::main()->proxy; + + write_grid_to_file(samples, prefix + ".count", close); + write_grid_to_file(gradients, prefix + ".grad", close); + + if (b_integrate) { + // Do numerical integration (to high precision) and output a PMF + cvm::real err; + pmf->integrate(integrate_iterations, integrate_tol, err); + pmf->set_zero_minimum(); + write_grid_to_file(pmf, prefix + ".pmf", close); + } + + if (b_CZAR_estimator) { + // Write eABF CZAR-related quantities + write_grid_to_file(z_samples, prefix + ".zcount", close); + if (b_czar_window_file) { + write_grid_to_file(z_gradients, prefix + ".zgrad", close); + } + + // Calculate CZAR estimator of gradients + for (std::vector ix = czar_gradients->new_index(); + czar_gradients->index_ok(ix); czar_gradients->incr(ix)) { + for (size_t n = 0; n < czar_gradients->multiplicity(); n++) { + czar_gradients->set_value(ix, z_gradients->value_output(ix, n) + - proxy->target_temperature() * proxy->boltzmann() * z_samples->log_gradient_finite_diff(ix, n), n); + } + } + write_grid_to_file(czar_gradients, prefix + ".czar.grad", close); + + if (b_integrate) { + // Do numerical integration (to high precision) and output a PMF + cvm::real err; + czar_pmf->set_div(); + czar_pmf->integrate(integrate_iterations, integrate_tol, err); + czar_pmf->set_zero_minimum(); + write_grid_to_file(czar_pmf, prefix + ".czar.pmf", close); + } + } + return; +} + + +// For Tcl implementation of selection rules. +/// Give the total number of bins for a given bias. +int colvarbias_abf::bin_num() { + return samples->number_of_points(0); +} +/// Calculate the bin index for a given bias. +int colvarbias_abf::current_bin() { + return samples->current_bin_scalar(0); +} +/// Give the count at a given bin index. +int colvarbias_abf::bin_count(int bin_index) { + if (bin_index < 0 || bin_index >= bin_num()) { + cvm::error("Error: Tried to get bin count from invalid bin index "+cvm::to_str(bin_index)); + return -1; + } + std::vector ix(1,(int)bin_index); + return samples->value(ix); +} + + +int colvarbias_abf::read_gradients_samples() +{ + int error_code = COLVARS_OK; + + std::string samples_in_name, gradients_in_name, z_samples_in_name, z_gradients_in_name; + + for ( size_t i = 0; i < input_prefix.size(); i++ ) { + samples_in_name = input_prefix[i] + ".count"; + gradients_in_name = input_prefix[i] + ".grad"; + z_samples_in_name = input_prefix[i] + ".zcount"; + z_gradients_in_name = input_prefix[i] + ".zgrad"; + // For user-provided files, the per-bias naming scheme may not apply + cvm::log("Reading sample count from " + samples_in_name + + " and gradient from " + gradients_in_name); + + error_code |= samples->read_multicol(samples_in_name, + "ABF samples file", + true); + + error_code |= gradients->read_multicol(gradients_in_name, + "ABF gradient file", + true); + + if (b_CZAR_estimator) { + // Read eABF z-averaged data for CZAR + cvm::log("Reading z-histogram from " + z_samples_in_name + " and z-gradient from " + z_gradients_in_name); + error_code |= z_samples->read_multicol(z_samples_in_name, + "eABF z-histogram file", + true); + error_code |= z_gradients->read_multicol(z_gradients_in_name, + "eABF z-gradient file", + true); + } + } + + return error_code; +} + + +template OST & colvarbias_abf::write_state_data_template_(OST &os) +{ + auto flags = os.flags(); + + os.setf(std::ios::fmtflags(0), std::ios::floatfield); // default floating-point format + write_state_data_key(os, "samples"); + samples->write_raw(os, 8); + os.flags(flags); + + write_state_data_key(os, "gradient"); + gradients->write_raw(os, 8); + + if (b_CZAR_estimator) { + os.setf(std::ios::fmtflags(0), std::ios::floatfield); // default floating-point format + write_state_data_key(os, "z_samples"); + z_samples->write_raw(os, 8); + os.flags(flags); + write_state_data_key(os, "z_gradient"); + z_gradients->write_raw(os, 8); + } + + os.flags(flags); + return os; +} + + +std::ostream & colvarbias_abf::write_state_data(std::ostream& os) +{ + return write_state_data_template_(os); +} + + +cvm::memory_stream & colvarbias_abf::write_state_data(cvm::memory_stream& os) +{ + return write_state_data_template_(os); +} + + +template IST &colvarbias_abf::read_state_data_template_(IST &is) +{ + if ( input_prefix.size() > 0 ) { + cvm::error("ERROR: cannot provide both inputPrefix and a colvars state file.\n", COLVARS_INPUT_ERROR); + } + + if (! read_state_data_key(is, "samples")) { + return is; + } + if (! samples->read_raw(is)) { + return is; + } + + if (! read_state_data_key(is, "gradient")) { + return is; + } + if (! gradients->read_raw(is)) { + return is; + } + if (b_integrate) { + // Update divergence to account for restart data + pmf->set_div(); + } + + if (b_CZAR_estimator) { + + if (! read_state_data_key(is, "z_samples")) { + return is; + } + if (! z_samples->read_raw(is)) { + return is; + } + + if (! read_state_data_key(is, "z_gradient")) { + return is; + } + if (! z_gradients->read_raw(is)) { + return is; + } + } + + return is; +} + + +std::istream & colvarbias_abf::read_state_data(std::istream& is) +{ + return read_state_data_template_(is); +} + + +cvm::memory_stream & colvarbias_abf::read_state_data(cvm::memory_stream& is) +{ + return read_state_data_template_(is); +} + + +int colvarbias_abf::write_output_files() +{ + if (cvm::debug()) { + cvm::log("ABF bias trying to write gradients and samples to disk"); + } + + if (shared_on && cvm::main()->proxy->replica_index() > 0 + && ! (b_CZAR_estimator || b_UI_estimator) ) { + // No need to report the same data as replica 0, let it do the I/O job + // except if using an eABF FE estimator + return COLVARS_OK; + } + + write_gradients_samples(output_prefix); + if (b_history_files) { + if ((cvm::step_absolute() % history_freq) == 0) { + write_gradients_samples(output_prefix + ".hist", false); + } + } + + if (b_UI_estimator) { + eabf_UI.calc_pmf(); + eabf_UI.write_files(); + } + + return COLVARS_OK; +} + + +int colvarbias_abf::calc_energy(std::vector const *values) +{ + bias_energy = 0.0; // default value, overridden if a value can be calculated + + if (num_variables() > 1 || values != NULL) { + // Use simple estimate: neglect effect of fullSamples, + // return value at center of bin + if (pmf != NULL) { + std::vector const curr_bin = values ? + pmf->get_colvars_index(*values) : + pmf->get_colvars_index(); + + if (pmf->index_ok(curr_bin)) { + bias_energy = pmf->value(curr_bin); + } + } + return COLVARS_OK; + } + + // Get the home bin. + int home0 = gradients->current_bin_scalar(0); + if (home0 < 0) return COLVARS_OK; + int gradient_len = (int)(gradients->number_of_points(0)); + int home = (home0 < gradient_len) ? home0 : (gradient_len-1); + + // Integrate the gradient up to the home bin. + cvm::real sum = 0.0; + for (int i = 0; i < home; i++) { + std::vector ix(1,i); + + // Include the full_samples factor if necessary. + unsigned int count = samples->value(ix); + cvm::real fact = 1.0; + if ( count < full_samples ) { + fact = (count < min_samples) ? 0.0 : + (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); + } + if (count > 0) sum += fact*gradients->value(ix)/count*gradients->widths[0]; + } + + // Integrate the gradient up to the current position in the home interval, a fractional portion of a bin. + std::vector ix(1,home); + cvm::real frac = gradients->current_bin_scalar_fraction(0); + unsigned int count = samples->value(ix); + cvm::real fact = 1.0; + if ( count < full_samples ) { + fact = (count < min_samples) ? 0.0 : + (cvm::real(count - min_samples)) / (cvm::real(full_samples - min_samples)); + } + if (count > 0) + sum += fact*gradients->value(ix)/count*gradients->widths[0]*frac; + + // The applied potential is the negative integral of force samples. + bias_energy = -sum; + return COLVARS_OK; +} diff --git a/src/external/colvars/colvarbias_abf.h b/src/external/colvars/colvarbias_abf.h new file mode 100644 index 00000000000..635e0fbfd34 --- /dev/null +++ b/src/external/colvars/colvarbias_abf.h @@ -0,0 +1,186 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_ABF_H +#define COLVARBIAS_ABF_H + +#include +#include +#include +#include + +#include "colvarproxy.h" +#include "colvarbias.h" +#include "colvargrid.h" +#include "colvar_UIestimator.h" + +typedef cvm::real* gradient_t; + + +/// ABF bias +class colvarbias_abf : public colvarbias { + +public: + + /// Constructor for ABF bias + colvarbias_abf(char const *key); + /// Initializer for ABF bias + virtual int init(std::string const &conf); + /// Default destructor for ABF bias + virtual ~colvarbias_abf(); + /// Per-timestep update of ABF bias + virtual int update(); + +private: + + /// Base filename(s) for reading previous gradient data (replaces data from restart file) + std::vector input_prefix; + + /// Adapt the bias at each time step (as opposed to keeping it constant)? + bool update_bias; + /// Use normalized definition of PMF for distance functions? (flat at long distances) + /// by including the Jacobian term separately of the recorded PMF + bool hide_Jacobian; + /// Integrate gradients into a PMF on output + bool b_integrate; + + /// Number of samples per bin before applying the full biasing force + size_t full_samples; + /// Number of samples per bin before applying a scaled-down biasing force + size_t min_samples; + /// Write combined files with a history of all output data? + bool b_history_files; + /// Write CZAR output file for stratified eABF (.zgrad) + bool b_czar_window_file; + /// Number of timesteps between recording data in history files (if non-zero) + size_t history_freq; + /// Umbrella Integration estimator of free energy from eABF + UIestimator::UIestimator eabf_UI; + /// Run UI estimator? + bool b_UI_estimator; + /// Run CZAR estimator? + bool b_CZAR_estimator; + + /// Frequency for updating pABF PMF (if zero, pABF is not used) + int pabf_freq; + /// Max number of CG iterations for integrating PMF at startup and for file output + int integrate_iterations; + /// Tolerance for integrating PMF at startup and for file output + cvm::real integrate_tol; + /// Max number of CG iterations for integrating PMF at on-the-fly pABF updates + int pabf_integrate_iterations; + /// Tolerance for integrating PMF at on-the-fly pABF updates + cvm::real pabf_integrate_tol; + + /// Cap the biasing force to be applied? (option maxForce) + bool cap_force; + /// Maximum force to be applied + std::vector max_force; + + // Internal data and methods + + /// Current bin in sample grid + std::vector bin; + /// Current bin in force grid + std::vector force_bin; + /// Cuurent bin in "actual" coordinate, when running extended Lagrangian dynamics + std::vector z_bin; + + /// Measured instantaneous system force + gradient_t system_force; + + /// n-dim grid of free energy gradients + colvar_grid_gradient *gradients; + /// n-dim grid of number of samples + colvar_grid_count *samples; + /// n-dim grid of pmf (dimension 1 to 3) + integrate_potential *pmf; + /// n-dim grid: average force on "real" coordinate for eABF z-based estimator + colvar_grid_gradient *z_gradients; + /// n-dim grid of number of samples on "real" coordinate for eABF z-based estimator + colvar_grid_count *z_samples; + /// n-dim grid containing CZAR estimator of "real" free energy gradients + colvar_grid_gradient *czar_gradients; + /// n-dim grid of CZAR pmf (dimension 1 to 3) + integrate_potential *czar_pmf; + + inline int update_system_force(size_t i) + { + if (colvars[i]->is_enabled(f_cv_subtract_applied_force)) { + // this colvar is already subtracting the ABF force + system_force[i] = colvars[i]->total_force().real_value; + } else { + system_force[i] = colvars[i]->total_force().real_value + - colvar_forces[i].real_value; + // If hideJacobian is active then total_force has an extra term of -fj + // which is the Jacobian-compensating force at the colvar level + } + if (cvm::debug()) + cvm::log("ABF System force calc: cv " + cvm::to_str(i) + + " fs " + cvm::to_str(system_force[i]) + + " = ft " + cvm::to_str(colvars[i]->total_force().real_value) + + " - fa " + cvm::to_str(colvar_forces[i].real_value)); + return COLVARS_OK; + } + + // shared ABF + bool shared_on; + size_t shared_freq; + cvm::step_number shared_last_step; + // Share between replicas -- may be called independently of update + virtual int replica_share(); + + // Store the last set for shared ABF + colvar_grid_gradient *last_gradients; + colvar_grid_count *last_samples; + + // For Tcl implementation of selection rules. + /// Give the total number of bins for a given bias. + virtual int bin_num(); + /// Calculate the bin index for a given bias. + virtual int current_bin(); + //// Give the count at a given bin index. + virtual int bin_count(int bin_index); + + /// Write human-readable FE gradients and sample count, and DX file in dim > 2 + void write_gradients_samples(const std::string &prefix, bool close = true); + + /// Read human-readable FE gradients and sample count (if not using restart) + int read_gradients_samples(); + + /// Template used in write_gradient_samples() + template int write_grid_to_file(T const *grid, + std::string const &name, + bool close); + +private: + + /// Generic stream writing function (formatted and not) + template OST &write_state_data_template_(OST &os); + + /// Generic stream readingx function (formatted and not) + template IST &read_state_data_template_(IST &is); + +public: + + virtual std::ostream &write_state_data(std::ostream &os); + + virtual cvm::memory_stream &write_state_data(cvm::memory_stream &os); + + virtual std::istream &read_state_data(std::istream &is); + + virtual cvm::memory_stream &read_state_data(cvm::memory_stream &is); + + virtual int write_output_files(); + + /// Calculate the bias energy for 1D ABF + virtual int calc_energy(std::vector const *values); +}; + +#endif diff --git a/src/external/colvars/colvarbias_alb.cpp b/src/external/colvars/colvarbias_alb.cpp new file mode 100644 index 00000000000..b432659bf4e --- /dev/null +++ b/src/external/colvars/colvarbias_alb.cpp @@ -0,0 +1,433 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarbias.h" +#include "colvarbias_alb.h" + +#ifdef _MSC_VER +#if _MSC_VER <= 1700 +#define copysign(A,B) _copysign(A,B) +double fmax(double A, double B) { return ( A > B ? A : B ); } +double fmin(double A, double B) { return ( A < B ? A : B ); } +#endif +#endif + +/* Note about nomenclature. Force constant is called a coupling + * constant here to emphasize its changing in the code. Outwards, + * everything is called a force constant to keep it consistent with + * the rest of colvars. + * + */ + +colvarbias_alb::colvarbias_alb(char const *key) + : colvarbias(key), update_calls(0), b_equilibration(true) +{ +} + + +int colvarbias_alb::init(std::string const &conf) +{ + colvarproxy *proxy = cvm::main()->proxy; + colvarbias::init(conf); + cvm::main()->cite_feature("ALB colvar bias implementation"); + + enable(f_cvb_scalar_variables); + + size_t i; + + // get the initial restraint centers + colvar_centers.resize(num_variables()); + + means.resize(num_variables()); + ssd.resize(num_variables()); //sum of squares of differences from mean + + //setup force vectors + max_coupling_range.resize(num_variables()); + max_coupling_rate.resize(num_variables()); + coupling_accum.resize(num_variables()); + set_coupling.resize(num_variables()); + current_coupling.resize(num_variables()); + coupling_rate.resize(num_variables()); + + enable(f_cvb_apply_force); + + for (i = 0; i < num_variables(); i++) { + colvar_centers[i].type(colvars[i]->value()); + //zero moments + means[i] = ssd[i] = 0; + + //zero force some of the force vectors that aren't initialized + coupling_accum[i] = current_coupling[i] = 0; + + } + if (get_keyval(conf, "centers", colvar_centers, colvar_centers)) { + for (i = 0; i < num_variables(); i++) { + colvar_centers[i].apply_constraints(); + } + } else { + colvar_centers.clear(); + cvm::error("Error: must define the initial centers of adaptive linear bias .\n"); + } + + if (colvar_centers.size() != num_variables()) + cvm::error("Error: number of centers does not match " + "that of collective variables.\n"); + + if (!get_keyval(conf, "UpdateFrequency", update_freq, 0)) + cvm::error("Error: must set updateFrequency for adaptive linear bias.\n"); + + //we split the time between updating and equilibrating + update_freq /= 2; + + if (update_freq <= 1) + cvm::error("Error: must set updateFrequency to greater than 2.\n"); + + enable(f_cvb_history_dependent); + + get_keyval(conf, "outputCenters", b_output_centers, false); + get_keyval(conf, "outputGradient", b_output_grad, false); + get_keyval(conf, "outputCoupling", b_output_coupling, true); + get_keyval(conf, "hardForceRange", b_hard_coupling_range, true); + + //initial guess + if (!get_keyval(conf, "forceConstant", set_coupling, set_coupling)) + for (i =0 ; i < num_variables(); i++) + set_coupling[i] = 0.; + + //how we're going to increase to that point + for (i = 0; i < num_variables(); i++) + coupling_rate[i] = (set_coupling[i] - current_coupling[i]) / update_freq; + + + if (!get_keyval(conf, "forceRange", max_coupling_range, max_coupling_range)) { + //set to default + for (i = 0; i < num_variables(); i++) { + if (proxy->target_temperature() > 0.0) { + max_coupling_range[i] = 3 * proxy->target_temperature() * + proxy->boltzmann(); + } else { + max_coupling_range[i] = 3 * proxy->boltzmann(); + } + } + } + + if (!get_keyval(conf, "rateMax", max_coupling_rate, max_coupling_rate)) { + //set to default + for (i = 0; i < num_variables(); i++) { + max_coupling_rate[i] = max_coupling_range[i] / (10 * update_freq); + } + } + + + if (cvm::debug()) + cvm::log(" bias.\n"); + + return COLVARS_OK; +} + + +colvarbias_alb::~colvarbias_alb() +{ +} + + +int colvarbias_alb::update() +{ + colvarproxy *proxy = cvm::main()->proxy; + + bias_energy = 0.0; + update_calls++; + + if (cvm::debug()) + cvm::log("Updating the adaptive linear bias \""+this->name+"\".\n"); + + //log the moments of the CVs + // Force and energy calculation + bool finished_equil_flag = 1; + cvm::real delta; + for (size_t i = 0; i < num_variables(); i++) { + colvar_forces[i] = -1.0 * restraint_force(restraint_convert_k(current_coupling[i], colvars[i]->width), + colvars[i], + colvar_centers[i]); + bias_energy += restraint_potential(restraint_convert_k(current_coupling[i], colvars[i]->width), + colvars[i], + colvar_centers[i]); + + if (!b_equilibration) { + //Welford, West, and Hanso online variance method + + delta = static_cast(colvars[i]->value()) - means[i]; + means[i] += delta / update_calls; + ssd[i] += delta * (static_cast(colvars[i]->value()) - means[i]); + + } else { + //check if we've reached the setpoint + cvm::real const coupling_diff = current_coupling[i] - set_coupling[i]; + if ((coupling_rate[i] == 0) || + ((coupling_diff*coupling_diff) < (coupling_rate[i]*coupling_rate[i]))) { + finished_equil_flag &= 1; //we continue equilibrating as long as we haven't reached all the set points + } + else { + current_coupling[i] += coupling_rate[i]; + finished_equil_flag = 0; + } + + + //update max_coupling_range + if (!b_hard_coupling_range && fabs(current_coupling[i]) > max_coupling_range[i]) { + std::ostringstream logStream; + logStream << "Coupling constant for " + << colvars[i]->name + << " has exceeded coupling range of " + << max_coupling_range[i] + << ".\n"; + + max_coupling_range[i] *= 1.25; + logStream << "Expanding coupling range to " << max_coupling_range[i] << ".\n"; + cvm::log(logStream.str()); + } + + + } + } + + if (b_equilibration && finished_equil_flag) { + b_equilibration = false; + update_calls = 0; + } + + + //now we update coupling constant, if necessary + if (!b_equilibration && update_calls == update_freq) { + + //use estimated variance to take a step + cvm::real step_size = 0; + cvm::real temp; + + //reset means and sum of squares of differences + for (size_t i = 0; i < num_variables(); i++) { + + temp = 2. * (means[i] / (static_cast (colvar_centers[i])) - 1) * ssd[i] / (update_calls - 1); + + if (proxy->target_temperature() > 0.0) { + step_size = temp / (proxy->target_temperature() * proxy->boltzmann()); + } else { + step_size = temp / proxy->boltzmann(); + } + + means[i] = 0; + ssd[i] = 0; + + //stochastic if we do that update or not + if (num_variables() == 1 || rand() < RAND_MAX / ((int) num_variables())) { + coupling_accum[i] += step_size * step_size; + current_coupling[i] = set_coupling[i]; + set_coupling[i] += max_coupling_range[i] / sqrt(coupling_accum[i]) * step_size; + coupling_rate[i] = (set_coupling[i] - current_coupling[i]) / update_freq; + //set to the minimum rate and then put the sign back on it + coupling_rate[i] = copysign(fmin(fabs(coupling_rate[i]), max_coupling_rate[i]), coupling_rate[i]); + } else { + coupling_rate[i] = 0; + } + + } + + update_calls = 0; + b_equilibration = true; + + } + + return COLVARS_OK; +} + + +int colvarbias_alb::set_state_params(std::string const &conf) +{ + int error_code = colvarbias::set_state_params(conf); + + if (error_code != COLVARS_OK) { + return error_code; + } + + if (!get_keyval(conf, "setCoupling", set_coupling)) + cvm::error("Error: current setCoupling is missing from the restart.\n"); + + if (!get_keyval(conf, "currentCoupling", current_coupling)) + cvm::error("Error: current setCoupling is missing from the restart.\n"); + + if (!get_keyval(conf, "maxCouplingRange", max_coupling_range)) + cvm::error("Error: maxCouplingRange is missing from the restart.\n"); + + if (!get_keyval(conf, "couplingRate", coupling_rate)) + cvm::error("Error: current setCoupling is missing from the restart.\n"); + + if (!get_keyval(conf, "couplingAccum", coupling_accum)) + cvm::error("Error: couplingAccum is missing from the restart.\n"); + + if (!get_keyval(conf, "mean", means)) + cvm::error("Error: current mean is missing from the restart.\n"); + + if (!get_keyval(conf, "ssd", ssd)) + cvm::error("Error: current ssd is missing from the restart.\n"); + + if (!get_keyval(conf, "updateCalls", update_calls)) + cvm::error("Error: current updateCalls is missing from the restart.\n"); + + if (!get_keyval(conf, "b_equilibration", b_equilibration)) + cvm::error("Error: current updateCalls is missing from the restart.\n"); + + return COLVARS_OK; +} + + +std::string const colvarbias_alb::get_state_params() const +{ + std::ostringstream os; + os << " setCoupling "; + size_t i; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << set_coupling[i] << "\n"; + } + os << " currentCoupling "; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << current_coupling[i] << "\n"; + } + os << " maxCouplingRange "; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << max_coupling_range[i] << "\n"; + } + os << " couplingRate "; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << coupling_rate[i] << "\n"; + } + os << " couplingAccum "; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << coupling_accum[i] << "\n"; + } + os << " mean "; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << means[i] << "\n"; + } + os << " ssd "; + for (i = 0; i < num_variables(); i++) { + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << ssd[i] << "\n"; + } + os << " updateCalls " << update_calls << "\n"; + if (b_equilibration) + os << " b_equilibration yes\n"; + else + os << " b_equilibration no\n"; + + return os.str(); +} + + +std::ostream & colvarbias_alb::write_traj_label(std::ostream &os) +{ + os << " "; + + if (b_output_energy) + os << " E_" + << cvm::wrap_string(this->name, cvm::en_width-2); + + if (b_output_coupling) + for (size_t i = 0; i < current_coupling.size(); i++) { + os << " ForceConst_" << i + <name, cvm::cv_width - 4); + } + + if (b_output_centers) + for (size_t i = 0; i < num_variables(); i++) { + size_t const this_cv_width = (colvars[i]->value()).output_width(cvm::cv_width); + os << " x0_" + << cvm::wrap_string(colvars[i]->name, this_cv_width-3); + } + + return os; +} + + +std::ostream & colvarbias_alb::write_traj(std::ostream &os) +{ + os << " "; + + if (b_output_energy) + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << bias_energy; + + if (b_output_coupling) + for (size_t i = 0; i < current_coupling.size(); i++) { + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << current_coupling[i]; + } + + + if (b_output_centers) + for (size_t i = 0; i < num_variables(); i++) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << colvar_centers[i]; + } + + if (b_output_grad) + for (size_t i = 0; i < means.size(); i++) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << -2.0 * (means[i] / (static_cast(colvar_centers[i])) - 1) * ssd[i] / (fmax(update_calls, 2.0) - 1); + + } + + return os; +} + + +cvm::real colvarbias_alb::restraint_potential(cvm::real k, + colvar const *x, + colvarvalue const &xcenter) const +{ + return k * (x->value() - xcenter); +} + + +colvarvalue colvarbias_alb::restraint_force(cvm::real k, + colvar const * /* x */, + colvarvalue const & /* xcenter */) const +{ + return k; +} + + +cvm::real colvarbias_alb::restraint_convert_k(cvm::real k, + cvm::real dist_measure) const +{ + return k / dist_measure; +} + diff --git a/src/external/colvars/colvarbias_alb.h b/src/external/colvars/colvarbias_alb.h new file mode 100644 index 00000000000..4d16a4e7e23 --- /dev/null +++ b/src/external/colvars/colvarbias_alb.h @@ -0,0 +1,88 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_ALB_H +#define COLVARBIAS_ALB_H + +#include "colvar.h" +#include "colvarbias.h" + + +class colvarbias_alb : public colvarbias { + +public: + + colvarbias_alb(char const *key); + virtual ~colvarbias_alb(); + virtual int init(std::string const &conf); + virtual int update(); + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + /// \brief Restraint centers + std::vector colvar_centers; + + /// \brief colvar parameters, used for calculating the gradient/variance + std::vector means; + std::vector ssd; // SSD = sum of squares of differences from mean + int update_calls; + + ///\brief how often to update coupling constant + int update_freq; + + ///\brief Estimated range of coupling constant values in kT + std::vector max_coupling_range; + + //\brief Estimated max for how quickly the rate can change in kT / time + std::vector max_coupling_rate; + + /// \brief accumated couping force; used in stochastic online gradient descent algorithm + std::vector coupling_accum; + + /// \brief coupling constant + std::vector set_coupling; + + /// \brief current coupling constant, which is ramped up during equilibration to coupling + std::vector current_coupling; + + /// \brief how quickly to change the coupling constant + std::vector coupling_rate; + + // \brief if we're equilibrating our estimates or collecting data + bool b_equilibration; + + // \brief If the coupling range should be increased + bool b_hard_coupling_range; + + + /// \brief flag for outputting colvar centers + bool b_output_centers; + + /// \brief flag for outputting current gradient + bool b_output_grad; + + /// \brief flag for outputting coupling constant + bool b_output_coupling; + + cvm::real restraint_potential(cvm::real k, const colvar* x, const colvarvalue& xcenter) const; + + /// \brief Force function + colvarvalue restraint_force(cvm::real k, const colvar* x, const colvarvalue& xcenter) const; + + ///\brief Unit scaling + cvm::real restraint_convert_k(cvm::real k, cvm::real dist_measure) const; + +}; + +#endif diff --git a/src/external/colvars/colvarbias_histogram.cpp b/src/external/colvars/colvarbias_histogram.cpp new file mode 100644 index 00000000000..640bec35369 --- /dev/null +++ b/src/external/colvars/colvarbias_histogram.cpp @@ -0,0 +1,235 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvar.h" +#include "colvarbias_histogram.h" +#include "colvars_memstream.h" + + +colvarbias_histogram::colvarbias_histogram(char const *key) + : colvarbias(key), + grid(NULL), out_name("") +{ + provide(f_cvb_bypass_ext_lagrangian); // Allow histograms of actual cv for extended-Lagrangian +} + + +int colvarbias_histogram::init(std::string const &conf) +{ + colvarbias::init(conf); + cvm::main()->cite_feature("Histogram colvar bias implementation"); + + enable(f_cvb_scalar_variables); + enable(f_cvb_history_dependent); + + size_t i; + + get_keyval(conf, "outputFile", out_name, ""); + // Write DX file by default only in dimension >= 3 + std::string default_name_dx = this->num_variables() > 2 ? "" : "none"; + get_keyval(conf, "outputFileDX", out_name_dx, default_name_dx); + + /// with VMD, this may not be an error + // if ( output_freq == 0 ) { + // cvm::error("User required histogram with zero output frequency"); + // } + + colvar_array_size = 0; + { + bool colvar_array = false; + get_keyval(conf, "gatherVectorColvars", colvar_array, colvar_array); + + if (colvar_array) { + for (i = 0; i < num_variables(); i++) { // should be all vector + if (colvars[i]->value().type() != colvarvalue::type_vector) { + cvm::error("Error: used gatherVectorColvars with non-vector colvar.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + if (i == 0) { + colvar_array_size = colvars[i]->value().size(); + if (colvar_array_size < 1) { + cvm::error("Error: vector variable has dimension less than one.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } else { + if (colvar_array_size != colvars[i]->value().size()) { + cvm::error("Error: trying to combine vector colvars of different lengths.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + } + } else { + for (i = 0; i < num_variables(); i++) { // should be all scalar + if (colvars[i]->value().type() != colvarvalue::type_scalar) { + cvm::error("Error: only scalar colvars are supported when gatherVectorColvars is off.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + } + } + + if (colvar_array_size > 0) { + weights.assign(colvar_array_size, 1.0); + get_keyval(conf, "weights", weights, weights); + } + + for (i = 0; i < num_variables(); i++) { + colvars[i]->enable(f_cv_grid); // Could be a child dependency of a f_cvb_use_grids feature + } + + grid = new colvar_grid_scalar(); + grid->init_from_colvars(colvars); + + if (is_enabled(f_cvb_bypass_ext_lagrangian)) { + grid->request_actual_value(); + } + + { + std::string grid_conf; + if (key_lookup(conf, "histogramGrid", &grid_conf)) { + grid->parse_params(grid_conf); + grid->check_keywords(grid_conf, "histogramGrid"); + } + } + + return COLVARS_OK; +} + + +colvarbias_histogram::~colvarbias_histogram() +{ + if (grid) { + delete grid; + grid = NULL; + } +} + + +int colvarbias_histogram::update() +{ + int error_code = COLVARS_OK; + // update base class + error_code |= colvarbias::update(); + + if (cvm::debug()) { + cvm::log("Updating histogram bias " + this->name); + } + + // assign a valid bin size + bin.assign(num_variables(), 0); + + if (out_name.size() == 0) { + // At the first timestep, we need to assign out_name since + // output_prefix is unset during the constructor + if (cvm::step_relative() == 0) { + out_name = cvm::output_prefix() + "." + this->name + ".dat"; + cvm::log("Histogram " + this->name + " will be written to file \"" + out_name + "\"\n"); + } + } + + if (out_name_dx.size() == 0) { + if (cvm::step_relative() == 0) { + out_name_dx = cvm::output_prefix() + "." + this->name + ".dx"; + cvm::log("Histogram " + this->name + " will be written to file \"" + out_name_dx + "\"\n"); + } + } + + if (colvar_array_size == 0) { + // update indices for scalar values + size_t i; + for (i = 0; i < num_variables(); i++) { + bin[i] = grid->current_bin_scalar(i); + } + + if (can_accumulate_data()) { + if (grid->index_ok(bin)) { + grid->acc_value(bin, 1.0); + } + } + } else { + // update indices for vector/array values + size_t iv, i; + for (iv = 0; iv < colvar_array_size; iv++) { + for (i = 0; i < num_variables(); i++) { + bin[i] = grid->current_bin_scalar(i, iv); + } + + if (grid->index_ok(bin)) { + grid->acc_value(bin, weights[iv]); + } + } + } + + error_code |= cvm::get_error(); + return error_code; +} + + +int colvarbias_histogram::write_output_files() +{ + if (!has_data) { + // nothing to write + return COLVARS_OK; + } + + int error_code = COLVARS_OK; + + if (out_name.size() && out_name != "none") { + cvm::log("Writing the histogram file \""+out_name+"\".\n"); + error_code |= grid->write_multicol(out_name, "histogram output file"); + } + + if (out_name_dx.size() && out_name_dx != "none") { + cvm::log("Writing the histogram file \""+out_name_dx+"\".\n"); + error_code |= grid->write_opendx(out_name_dx, "histogram DX output file"); + } + + return error_code; +} + + +std::istream & colvarbias_histogram::read_state_data(std::istream& is) +{ + if (read_state_data_key(is, "grid")) { + grid->read_raw(is); + } + return is; +} + + +cvm::memory_stream & colvarbias_histogram::read_state_data(cvm::memory_stream& is) +{ + if (read_state_data_key(is, "grid")) { + grid->read_raw(is); + } + return is; +} + + +std::ostream & colvarbias_histogram::write_state_data(std::ostream& os) +{ + std::ios::fmtflags flags(os.flags()); + os.setf(std::ios::fmtflags(0), std::ios::floatfield); + write_state_data_key(os, "grid"); + grid->write_raw(os, 8); + os.flags(flags); + return os; +} + + +cvm::memory_stream & colvarbias_histogram::write_state_data(cvm::memory_stream& os) +{ + write_state_data_key(os, "grid"); + grid->write_raw(os); + return os; +} diff --git a/src/external/colvars/colvarbias_histogram.h b/src/external/colvars/colvarbias_histogram.h new file mode 100644 index 00000000000..2c6ee84d1ff --- /dev/null +++ b/src/external/colvars/colvarbias_histogram.h @@ -0,0 +1,50 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_HISTOGRAM_H +#define COLVARBIAS_HISTOGRAM_H + +#include +#include +#include +#include + +#include "colvarbias.h" +#include "colvargrid.h" + +/// Histogram "bias" (does as the name says) +class colvarbias_histogram : public colvarbias { + +public: + + colvarbias_histogram(char const *key); + virtual ~colvarbias_histogram(); + virtual int init(std::string const &conf); + virtual int update(); + virtual int write_output_files(); + + virtual std::ostream & write_state_data(std::ostream &os); + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os); + virtual std::istream & read_state_data(std::istream &is); + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is); + +protected: + + /// n-dim histogram + colvar_grid_scalar *grid; + std::vector bin; + std::string out_name, out_name_dx; + + /// If one or more of the variables are \link colvarvalue::type_vector \endlink, treat them as arrays of this length + size_t colvar_array_size; + /// If colvar_array_size is larger than 1, weigh each one by this number before accumulating the histogram + std::vector weights; +}; + +#endif diff --git a/src/external/colvars/colvarbias_histogram_reweight_amd.cpp b/src/external/colvars/colvarbias_histogram_reweight_amd.cpp new file mode 100644 index 00000000000..de2f6d9b8a8 --- /dev/null +++ b/src/external/colvars/colvarbias_histogram_reweight_amd.cpp @@ -0,0 +1,416 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarbias_histogram_reweight_amd.h" +#include "colvarproxy.h" +#include "colvars_memstream.h" + +colvarbias_reweightaMD::colvarbias_reweightaMD(char const *key) + : colvarbias_histogram(key), grid_count(NULL), grid_dV(NULL), + grid_dV_square(NULL), pmf_grid_exp_avg(NULL), pmf_grid_cumulant(NULL), + grad_grid_exp_avg(NULL), grad_grid_cumulant(NULL) +{ +} + +colvarbias_reweightaMD::~colvarbias_reweightaMD() { + if (grid_dV) { + delete grid_dV; + grid_dV = NULL; + } + if (grid_dV_square) { + delete grid_dV_square; + grid_dV_square = NULL; + } + if (grid_count) { + delete grid_count; + grid_count = NULL; + } + if (pmf_grid_exp_avg) { + delete pmf_grid_exp_avg; + pmf_grid_exp_avg = NULL; + } + if (pmf_grid_cumulant) { + delete pmf_grid_cumulant; + pmf_grid_cumulant = NULL; + } + if (grad_grid_exp_avg) { + delete grad_grid_exp_avg; + grad_grid_exp_avg = NULL; + } + if (grad_grid_cumulant) { + delete grad_grid_cumulant; + grad_grid_cumulant = NULL; + } +} + +int colvarbias_reweightaMD::init(std::string const &conf) { + if (cvm::proxy->accelMD_enabled() == false) { + cvm::error("Error: accelerated MD in your MD engine is not enabled.\n", COLVARS_INPUT_ERROR); + } + cvm::main()->cite_feature("reweightaMD colvar bias implementation (NAMD)"); + int baseclass_init_code = colvarbias_histogram::init(conf); + get_keyval(conf, "CollectAfterSteps", start_after_steps, 0); + get_keyval(conf, "CumulantExpansion", b_use_cumulant_expansion, true); + get_keyval(conf, "WritePMFGradients", b_write_gradients, true); + get_keyval(conf, "historyFreq", history_freq, 0); + b_history_files = (history_freq > 0); + grid_count = new colvar_grid_scalar(colvars); + grid_count->request_actual_value(); + grid->request_actual_value(); + pmf_grid_exp_avg = new colvar_grid_scalar(colvars); + if (b_write_gradients) { + grad_grid_exp_avg = new colvar_grid_gradient(colvars); + } + if (b_use_cumulant_expansion) { + grid_dV = new colvar_grid_scalar(colvars); + grid_dV_square = new colvar_grid_scalar(colvars); + pmf_grid_cumulant = new colvar_grid_scalar(colvars); + grid_dV->request_actual_value(); + grid_dV_square->request_actual_value(); + if (b_write_gradients) { + grad_grid_cumulant = new colvar_grid_gradient(colvars); + } + } + previous_bin.assign(num_variables(), -1); + return baseclass_init_code; +} + +int colvarbias_reweightaMD::update() { + colvarproxy *proxy = cvm::main()->proxy; + int error_code = COLVARS_OK; + if (cvm::step_relative() >= start_after_steps) { + // update base class + error_code |= colvarbias::update(); + + if (cvm::debug()) { + cvm::log("Updating histogram bias " + this->name); + } + + if (cvm::step_relative() > 0) { + previous_bin = bin; + } + + // assign a valid bin size + bin.assign(num_variables(), 0); + + if (colvar_array_size == 0) { + // update indices for scalar values + size_t i; + for (i = 0; i < num_variables(); i++) { + bin[i] = grid->current_bin_scalar(i); + } + + if (grid->index_ok(previous_bin) && cvm::step_relative() > 0) { + const cvm::real reweighting_factor = cvm::proxy->get_accelMD_factor(); + grid_count->acc_value(previous_bin, 1.0); + grid->acc_value(previous_bin, reweighting_factor); + if (b_use_cumulant_expansion) { + const cvm::real dV = cvm::logn(reweighting_factor) * + proxy->target_temperature() * proxy->boltzmann(); + grid_dV->acc_value(previous_bin, dV); + grid_dV_square->acc_value(previous_bin, dV * dV); + } + } + } else { + // update indices for vector/array values + size_t iv, i; + for (iv = 0; iv < colvar_array_size; iv++) { + for (i = 0; i < num_variables(); i++) { + bin[i] = grid->current_bin_scalar(i, iv); + } + + if (grid->index_ok(previous_bin) && cvm::step_relative() > 0) { + const cvm::real reweighting_factor = cvm::proxy->get_accelMD_factor(); + grid_count->acc_value(previous_bin, 1.0); + grid->acc_value(previous_bin, reweighting_factor); + if (b_use_cumulant_expansion) { + const cvm::real dV = cvm::logn(reweighting_factor) * + proxy->target_temperature() * proxy->boltzmann(); + grid_dV->acc_value(previous_bin, dV); + grid_dV_square->acc_value(previous_bin, dV * dV); + } + } + } + } + previous_bin.assign(num_variables(), 0); + + error_code |= cvm::get_error(); + } + return error_code; +} + +int colvarbias_reweightaMD::write_output_files() { + int error_code = COLVARS_OK; + // error_code |= colvarbias_histogram::write_output_files(); + const std::string out_name_pmf = cvm::output_prefix() + "." + + this->name + ".reweight"; + error_code |= write_exponential_reweighted_pmf(out_name_pmf); + const std::string out_count_prefix = cvm::output_prefix() + "." + + this->name; + error_code |= write_count(out_count_prefix); + const bool write_history = b_history_files && + (cvm::step_absolute() % history_freq) == 0; + if (write_history) { + error_code |= write_exponential_reweighted_pmf( + out_name_pmf + ".hist", true); + error_code |= write_count(out_count_prefix + ".hist", + (cvm::step_relative() > 0)); + } + if (b_use_cumulant_expansion) { + const std::string out_name_cumulant_pmf = cvm::output_prefix() + "." + + this->name + ".cumulant"; + error_code |= write_cumulant_expansion_pmf(out_name_cumulant_pmf); + if (write_history) { + error_code |= write_cumulant_expansion_pmf( + out_name_cumulant_pmf + ".hist", true); + } + } + error_code |= cvm::get_error(); + return error_code; +} + +int colvarbias_reweightaMD::write_exponential_reweighted_pmf( + const std::string& p_output_prefix, bool keep_open) { + const std::string output_pmf = p_output_prefix + ".pmf"; + + cvm::log("Writing the accelerated MD PMF file \"" + output_pmf + "\".\n"); + std::ostream &pmf_grid_os = cvm::proxy->output_stream(output_pmf, "PMF file"); + if (!pmf_grid_os) { + return COLVARS_FILE_ERROR; + } + pmf_grid_exp_avg->copy_grid(*grid); + // compute the average + for (size_t i = 0; i < pmf_grid_exp_avg->raw_data_num(); ++i) { + const double count = grid_count->value(i); + if (count > 0) { + const double tmp = pmf_grid_exp_avg->value(i); + pmf_grid_exp_avg->set_value(i, tmp / count); + } + } + hist_to_pmf(pmf_grid_exp_avg, grid_count); + pmf_grid_exp_avg->write_multicol(pmf_grid_os); + if (!keep_open) { + cvm::proxy->close_output_stream(output_pmf); + } + + if (b_write_gradients) { + const std::string output_grad = p_output_prefix + ".grad"; + cvm::log("Writing the accelerated MD gradients file \"" + output_grad + + "\".\n"); + std::ostream &grad_grid_os = cvm::proxy->output_stream(output_grad, "gradient file"); + if (!grad_grid_os) { + return COLVARS_FILE_ERROR; + } + for (std::vector ix = grad_grid_exp_avg->new_index(); + grad_grid_exp_avg->index_ok(ix); grad_grid_exp_avg->incr(ix)) { + for (size_t n = 0; n < grad_grid_exp_avg->multiplicity(); n++) { + grad_grid_exp_avg->set_value( + ix, pmf_grid_exp_avg->gradient_finite_diff(ix, n), n); + } + } + grad_grid_exp_avg->write_multicol(grad_grid_os); + if (!keep_open) { + cvm::proxy->close_output_stream(output_grad); + } + } + + return COLVARS_OK; +} + +int colvarbias_reweightaMD::write_cumulant_expansion_pmf( + const std::string& p_output_prefix, bool keep_open) { + const std::string output_pmf = p_output_prefix + ".pmf"; + cvm::log("Writing the accelerated MD PMF file using cumulant expansion: \"" + output_pmf + "\".\n"); + std::ostream &pmf_grid_cumulant_os = cvm::proxy->output_stream(output_pmf, "PMF file"); + if (!pmf_grid_cumulant_os) { + return COLVARS_FILE_ERROR; + } + compute_cumulant_expansion_factor(grid_dV, grid_dV_square, + grid_count, pmf_grid_cumulant); + hist_to_pmf(pmf_grid_cumulant, grid_count); + pmf_grid_cumulant->write_multicol(pmf_grid_cumulant_os); + if (!keep_open) { + cvm::proxy->close_output_stream(output_pmf); + } + + if (b_write_gradients) { + const std::string output_grad = p_output_prefix + ".grad"; + cvm::log("Writing the accelerated MD gradients file \"" + output_grad + "\".\n"); + std::ostream &grad_grid_os = cvm::proxy->output_stream(output_grad, "grad file"); + if (!grad_grid_os) { + return COLVARS_FILE_ERROR; + } + for (std::vector ix = grad_grid_cumulant->new_index(); + grad_grid_cumulant->index_ok(ix); grad_grid_cumulant->incr(ix)) { + for (size_t n = 0; n < grad_grid_cumulant->multiplicity(); n++) { + grad_grid_cumulant->set_value( + ix, pmf_grid_cumulant->gradient_finite_diff(ix, n), n); + } + } + grad_grid_cumulant->write_multicol(grad_grid_os); + cvm::proxy->close_output_stream(output_grad); + } + return COLVARS_OK; +} + +int colvarbias_reweightaMD::write_count(const std::string& p_output_prefix, bool keep_open) { + const std::string output_name = p_output_prefix + ".count"; + cvm::log("Writing the accelerated MD count file \""+output_name+"\".\n"); + std::ostream &grid_count_os = cvm::proxy->output_stream(output_name, "count file"); + if (!grid_count_os) { + return COLVARS_FILE_ERROR; + } + grid_count->write_multicol(grid_count_os); + if (!keep_open) { + cvm::proxy->close_output_stream(output_name); + } + return COLVARS_OK; +} + +void colvarbias_reweightaMD::hist_to_pmf( + colvar_grid_scalar* hist, + const colvar_grid_scalar* hist_count) const +{ + colvarproxy *proxy = cvm::main()->proxy; + if (hist->raw_data_num() == 0) return; + const cvm::real kbt = proxy->boltzmann() * proxy->target_temperature(); + bool first_min_element = true; + bool first_max_element = true; + cvm::real min_element = 0; + cvm::real max_element = 0; + size_t i = 0; + // the first loop: using logarithm to compute PMF + for (i = 0; i < hist->raw_data_num(); ++i) { + const cvm::real count = hist_count->value(i); + if (count > 0) { + const cvm::real x = hist->value(i); + const cvm::real pmf_value = -1.0 * kbt * cvm::logn(x); + hist->set_value(i, pmf_value); + // find the minimum PMF value + if (first_min_element) { + // assign current PMF value to min_element at the first time + min_element = pmf_value; + first_min_element = false; + } else { + // if this is not the first time, then + min_element = (pmf_value < min_element) ? pmf_value : min_element; + } + // do the same to the maximum + if (first_max_element) { + max_element = pmf_value; + first_max_element = false; + } else { + max_element = (pmf_value > max_element) ? pmf_value : max_element; + } + } + } + // the second loop: bringing the minimum PMF value to zero + for (i = 0; i < hist->raw_data_num(); ++i) { + const cvm::real count = hist_count->value(i); + if (count > 0) { + // bins that have samples + const cvm::real x = hist->value(i); + hist->set_value(i, x - min_element); + } else { + hist->set_value(i, max_element - min_element); + } + } +} + + +void colvarbias_reweightaMD::compute_cumulant_expansion_factor( + const colvar_grid_scalar* hist_dV, + const colvar_grid_scalar* hist_dV_square, + const colvar_grid_scalar* hist_count, + colvar_grid_scalar* cumulant_expansion_factor) const +{ + colvarproxy *proxy = cvm::main()->proxy; + const cvm::real beta = 1.0 / (proxy->boltzmann() * proxy->target_temperature()); + size_t i = 0; + for (i = 0; i < hist_dV->raw_data_num(); ++i) { + const cvm::real count = hist_count->value(i); + if (count > 0) { + const cvm::real dV_avg = hist_dV->value(i) / count; + const cvm::real dV_square_avg = hist_dV_square->value(i) / count; + const cvm::real factor = cvm::exp(beta * dV_avg + 0.5 * beta * beta * (dV_square_avg - dV_avg * dV_avg)); + cumulant_expansion_factor->set_value(i, factor); + } + } +} + + +template OST & colvarbias_reweightaMD::write_state_data_template_(OST& os) +{ + std::ios::fmtflags flags(os.flags()); + os.setf(std::ios::fmtflags(0), std::ios::floatfield); + write_state_data_key(os, "grid"); + grid->write_raw(os, 8); + write_state_data_key(os, "grid_count"); + grid_count->write_raw(os, 8); + write_state_data_key(os, "grid_dV"); + grid_dV->write_raw(os, 8); + write_state_data_key(os, "grid_dV_square"); + grid_dV_square->write_raw(os, 8); + os.flags(flags); + return os; +} + + +std::ostream & colvarbias_reweightaMD::write_state_data(std::ostream& os) +{ + return write_state_data_template_(os); +} + + +cvm::memory_stream & colvarbias_reweightaMD::write_state_data(cvm::memory_stream& os) +{ + return write_state_data_template_(os); +} + + +template IST & colvarbias_reweightaMD::read_state_data_template_(IST& is) +{ + if (! read_state_data_key(is, "grid")) { + return is; + } + if (! grid->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "grid_count")) { + return is; + } + if (! grid_count->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "grid_dV")) { + return is; + } + if (! grid_dV->read_raw(is)) { + return is; + } + if (! read_state_data_key(is, "grid_dV_square")) { + return is; + } + if (! grid_dV_square->read_raw(is)) { + return is; + } + return is; +} + + +std::istream & colvarbias_reweightaMD::read_state_data(std::istream& is) +{ + return read_state_data_template_(is); +} + + +cvm::memory_stream & colvarbias_reweightaMD::read_state_data(cvm::memory_stream& is) +{ + return read_state_data_template_(is); +} diff --git a/src/external/colvars/colvarbias_histogram_reweight_amd.h b/src/external/colvars/colvarbias_histogram_reweight_amd.h new file mode 100644 index 00000000000..43759b3bde1 --- /dev/null +++ b/src/external/colvars/colvarbias_histogram_reweight_amd.h @@ -0,0 +1,99 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_HISTOGRAM_REWEIGHT_AMD +#define COLVARBIAS_HISTOGRAM_REWEIGHT_AMD + +#include "colvarbias_histogram.h" + +/// Reweighted histogram for accelerated molecular dynamics (aMD) or +/// Gaussian aMD (GaMD) +class colvarbias_reweightaMD : public colvarbias_histogram { +public: + colvarbias_reweightaMD(char const *key); + virtual ~colvarbias_reweightaMD(); + virtual int init(std::string const &conf) override; + virtual int update() override; + virtual int write_output_files() override; + + /// @brief convert histogram to PMF by taking logarithm and multiplying + /// it with -1/beta + /// @param[in,out] hist the origin histogram and also the output PMF + /// @param[in] hist_count the sampling or biased histogram + void hist_to_pmf( + colvar_grid_scalar* hist, + const colvar_grid_scalar* hist_count) const; + + /// @brief calculate the cumulant expansion to second order + /// @param[in] hist_dV the histogram of the boosting potential, ΔV(ξ) + /// @param[in] hist_dV_square the histogram of the square of boosting + /// potential + /// @param[in] hist_count the sampling or biased histogram + /// @param[out] cumulant_expansion_factor the factor of the cumulant + /// expansion to second order + void compute_cumulant_expansion_factor( + const colvar_grid_scalar* hist_dV, + const colvar_grid_scalar* hist_dV_square, + const colvar_grid_scalar* hist_count, + colvar_grid_scalar* cumulant_expansion_factor) const; + + /// @brief output the PMF by the exponential average estimator + /// @param[in] p_output_prefix the prefix of the output file + /// @param[in] keep_open Allow writing the history of the PMF + virtual int write_exponential_reweighted_pmf( + const std::string& p_output_prefix, bool keep_open = false); + + /// @brief output the PMF by the cumulant expansion estimator + /// @param[in] p_output_prefix the prefix of the output file + /// @param[in] keep_open Allow writing the history of the expansion + virtual int write_cumulant_expansion_pmf( + const std::string& p_output_prefix, bool keep_open = false); + + /// @brief output the biased sampling + /// @param[in] p_output_prefix the prefix of the output file + /// @param[in] keep_open Allow writing the history of the samples + virtual int write_count( + const std::string& p_output_prefix, bool keep_open = false); +protected: + /// Current accelMD factor is the from previous frame + std::vector previous_bin; + /// Start collecting samples after N steps + colvarmodule::step_number start_after_steps; + + /// Use cumulant expansion to second order? + bool b_use_cumulant_expansion; + colvar_grid_scalar* grid_count; + colvar_grid_scalar* grid_dV; + colvar_grid_scalar* grid_dV_square; + + /// Number of timesteps between recording data in history files (if non-zero) + size_t history_freq; + bool b_history_files; + + /// Write gradients of the PMF? + bool b_write_gradients; + + template OST & write_state_data_template_(OST& os); + template IST & read_state_data_template_(IST& is); + + /// save and restore + virtual std::istream & read_state_data(std::istream &is) override; + virtual cvm::memory_stream & read_state_data(cvm::memory_stream &is) override; + virtual std::ostream & write_state_data(std::ostream &os) override; + virtual cvm::memory_stream & write_state_data(cvm::memory_stream &os) override; + +private: + /// temporary grids for evaluating PMFs + colvar_grid_scalar *pmf_grid_exp_avg; + colvar_grid_scalar *pmf_grid_cumulant; + colvar_grid_gradient *grad_grid_exp_avg; + colvar_grid_gradient *grad_grid_cumulant; +}; + +#endif // COLVARBIAS_HISTOGRAM_REWEIGHT_AMD diff --git a/src/external/colvars/colvarbias_meta.cpp b/src/external/colvars/colvarbias_meta.cpp new file mode 100644 index 00000000000..3a2bf6b59de --- /dev/null +++ b/src/external/colvars/colvarbias_meta.cpp @@ -0,0 +1,2132 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include +#include +#include + +// Define function to get the absolute path of a replica file +#if defined(_WIN32) && !defined(__CYGWIN__) +#include +#define GETCWD(BUF, SIZE) ::_getcwd(BUF, SIZE) +#define PATHSEP "\\" +#else +#include +#define GETCWD(BUF, SIZE) ::getcwd(BUF, SIZE) +#define PATHSEP "/" +#endif + +#ifdef __cpp_lib_filesystem +// When std::filesystem is available, use it +#include +#undef GETCWD +#define GETCWD(BUF, SIZE) (std::filesystem::current_path().string().c_str()) +#endif + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvar.h" +#include "colvarbias_meta.h" +#include "colvars_memstream.h" + + +colvarbias_meta::colvarbias_meta(char const *key) + : colvarbias(key), colvarbias_ti(key) +{ + new_hills_begin = hills.end(); + + hill_weight = 0.0; + hill_width = 0.0; + + new_hill_freq = 1000; + + use_grids = true; + grids_freq = 0; + rebin_grids = false; + hills_energy = NULL; + hills_energy_gradients = NULL; + + dump_fes = true; + keep_hills = false; + restart_keep_hills = false; + dump_fes_save = false; + dump_replica_fes = false; + + b_hills_traj = false; + + ebmeta_equil_steps = 0L; + + replica_update_freq = 0; + replica_id.clear(); +} + + +int colvarbias_meta::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + size_t i = 0; + + error_code |= colvarbias::init(conf); + error_code |= colvarbias_ti::init(conf); + + cvm::main()->cite_feature("Metadynamics colvar bias implementation"); + + enable(f_cvb_calc_pmf); + + get_keyval(conf, "hillWeight", hill_weight, hill_weight); + if (hill_weight > 0.0) { + enable(f_cvb_apply_force); + } else { + cvm::error("Error: hillWeight must be provided, and a positive number.\n", COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "newHillFrequency", new_hill_freq, new_hill_freq); + if (new_hill_freq > 0) { + enable(f_cvb_history_dependent); + if (grids_freq == 0) { + grids_freq = new_hill_freq; + } + } + + get_keyval(conf, "gaussianSigmas", colvar_sigmas, colvar_sigmas); + + get_keyval(conf, "hillWidth", hill_width, hill_width); + + if ((colvar_sigmas.size() > 0) && (hill_width > 0.0)) { + error_code |= cvm::error("Error: hillWidth and gaussianSigmas are " + "mutually exclusive.", COLVARS_INPUT_ERROR); + } + + if (hill_width > 0.0) { + colvar_sigmas.resize(num_variables()); + // Print the calculated sigma parameters + cvm::log("Half-widths of the Gaussian hills (sigma's):\n"); + for (i = 0; i < num_variables(); i++) { + colvar_sigmas[i] = variables(i)->width * hill_width / 2.0; + cvm::log(variables(i)->name+std::string(": ")+ + cvm::to_str(colvar_sigmas[i])); + } + } + + if (colvar_sigmas.size() == 0) { + error_code |= cvm::error("Error: positive values are required for " + "either hillWidth or gaussianSigmas.", + COLVARS_INPUT_ERROR); + } + + { + bool b_replicas = false; + get_keyval(conf, "multipleReplicas", b_replicas, false); + if (b_replicas) { + cvm::main()->cite_feature("Multiple-walker metadynamics colvar bias implementation"); + comm = multiple_replicas; + } else { + comm = single_replica; + } + } + + get_keyval(conf, "useGrids", use_grids, use_grids); + + if (use_grids) { + + for (i = 0; i < num_variables(); i++) { + if (2.0*colvar_sigmas[i] < variables(i)->width) { + cvm::log("Warning: gaussianSigmas is too narrow for the grid " + "spacing along "+variables(i)->name+"."); + } + } + + get_keyval(conf, "gridsUpdateFrequency", grids_freq, grids_freq); + get_keyval(conf, "rebinGrids", rebin_grids, rebin_grids); + + expand_grids = false; + for (i = 0; i < num_variables(); i++) { + variables(i)->enable(f_cv_grid); // Could be a child dependency of a f_cvb_use_grids feature + if (variables(i)->expand_boundaries) { + expand_grids = true; + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": Will expand grids when the colvar \""+ + variables(i)->name+"\" approaches its boundaries.\n"); + } + } + + get_keyval(conf, "writeFreeEnergyFile", dump_fes, dump_fes); + + get_keyval(conf, "keepHills", keep_hills, keep_hills); + get_keyval(conf, "keepFreeEnergyFiles", dump_fes_save, dump_fes_save); + + if (hills_energy == NULL) { + hills_energy = new colvar_grid_scalar(colvars); + hills_energy_gradients = new colvar_grid_gradient(colvars); + } + + } else { + + dump_fes = false; + } + + get_keyval(conf, "writeHillsTrajectory", b_hills_traj, b_hills_traj); + + error_code |= init_replicas_params(conf); + error_code |= init_well_tempered_params(conf); + error_code |= init_ebmeta_params(conf); + + if (cvm::debug()) + cvm::log("Done initializing the metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+".\n"); + + return error_code; +} + + +int colvarbias_meta::init_replicas_params(std::string const &conf) +{ + colvarproxy *proxy = cvm::main()->proxy; + + // in all cases, the first replica is this bias itself + if (replicas.size() == 0) { + replicas.push_back(this); + } + + if (comm != single_replica) { + + if (!get_keyval(conf, "writePartialFreeEnergyFile", + dump_replica_fes, dump_replica_fes)) { + get_keyval(conf, "dumpPartialFreeEnergyFile", dump_replica_fes, + dump_replica_fes, colvarparse::parse_silent); + } + + if (dump_replica_fes && (! dump_fes)) { + dump_fes = true; + cvm::log("Enabling \"writeFreeEnergyFile\".\n"); + } + + get_keyval(conf, "replicaID", replica_id, replica_id); + if (!replica_id.size()) { + if (proxy->replica_enabled() == COLVARS_OK) { + // Obtain replicaID from the communicator + replica_id = cvm::to_str(proxy->replica_index()); + cvm::log("Setting replicaID from communication layer: replicaID = "+ + replica_id+".\n"); + } else { + return cvm::error("Error: using more than one replica, but replicaID " + "could not be obtained.\n", COLVARS_INPUT_ERROR); + } + } + + get_keyval(conf, "replicasRegistry", replicas_registry_file, + replicas_registry_file); + if (!replicas_registry_file.size()) { + return cvm::error("Error: the name of the \"replicasRegistry\" file " + "must be provided.\n", COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "replicaUpdateFrequency", + replica_update_freq, replica_update_freq); + if (replica_update_freq == 0) { + return cvm::error("Error: replicaUpdateFrequency must be positive.\n", + COLVARS_INPUT_ERROR); + } + + if (expand_grids) { + return cvm::error("Error: expandBoundaries is not supported when " + "using more than one replicas; please allocate " + "wide enough boundaries for each colvar" + "ahead of time.\n", COLVARS_INPUT_ERROR); + } + + if (keep_hills) { + return cvm::error("Error: multipleReplicas and keepHills are not " + "supported together.\n", COLVARS_INPUT_ERROR); + } + } + + return COLVARS_OK; +} + + +int colvarbias_meta::init_well_tempered_params(std::string const &conf) +{ + // for well-tempered metadynamics + get_keyval(conf, "wellTempered", well_tempered, false); + get_keyval(conf, "biasTemperature", bias_temperature, -1.0); + if ((bias_temperature == -1.0) && well_tempered) { + cvm::error("Error: biasTemperature must be set to a positive value.\n", + COLVARS_INPUT_ERROR); + } + if (well_tempered) { + cvm::log("Well-tempered metadynamics is used.\n"); + cvm::log("The bias temperature is "+cvm::to_str(bias_temperature)+".\n"); + } + return COLVARS_OK; +} + + +int colvarbias_meta::init_ebmeta_params(std::string const &conf) +{ + int error_code = COLVARS_OK; + // for ebmeta + target_dist = NULL; + get_keyval(conf, "ebMeta", ebmeta, false); + if(ebmeta){ + cvm::main()->cite_feature("Ensemble-biased metadynamics (ebMetaD)"); + if (use_grids && expand_grids) { + error_code |= cvm::error("Error: expandBoundaries is not supported with " + "ebMeta; please allocate wide enough boundaries " + "for each colvar ahead of time and set " + "targetDistFile accordingly.\n", + COLVARS_INPUT_ERROR); + } + target_dist = new colvar_grid_scalar(); + error_code |= target_dist->init_from_colvars(colvars); + std::string target_dist_file; + get_keyval(conf, "targetDistFile", target_dist_file); + error_code |= target_dist->read_multicol(target_dist_file, + "ebMeta target histogram"); + cvm::real min_val = target_dist->minimum_value(); + cvm::real max_val = target_dist->maximum_value(); + if (min_val < 0.0) { + error_code |= cvm::error("Error: Target distribution of EBMetaD " + "has negative values!.\n", + COLVARS_INPUT_ERROR); + } + cvm::real target_dist_min_val; + get_keyval(conf, "targetDistMinVal", target_dist_min_val, 1/1000000.0); + if(target_dist_min_val>0 && target_dist_min_val<1){ + target_dist_min_val=max_val*target_dist_min_val; + target_dist->remove_small_values(target_dist_min_val); + } else { + if (target_dist_min_val==0) { + cvm::log("NOTE: targetDistMinVal is set to zero, the minimum value of the target \n"); + cvm::log(" distribution will be set as the minimum positive value.\n"); + cvm::real min_pos_val = target_dist->minimum_pos_value(); + if (min_pos_val <= 0.0){ + error_code |= cvm::error("Error: Target distribution of EBMetaD has " + "negative or zero minimum positive value.\n", + COLVARS_INPUT_ERROR); + } + if (min_val == 0.0){ + cvm::log("WARNING: Target distribution has zero values.\n"); + cvm::log("Zeros will be converted to the minimum positive value.\n"); + target_dist->remove_small_values(min_pos_val); + } + } else { + error_code |= cvm::error("Error: targetDistMinVal must be a value " + "between 0 and 1.\n", COLVARS_INPUT_ERROR); + } + } + // normalize target distribution and multiply by effective volume = exp(differential entropy) + target_dist->multiply_constant(1.0/target_dist->integral()); + cvm::real volume = cvm::exp(target_dist->entropy()); + target_dist->multiply_constant(volume); + get_keyval(conf, "ebMetaEquilSteps", ebmeta_equil_steps, ebmeta_equil_steps); + } + + return error_code; +} + + +colvarbias_meta::~colvarbias_meta() +{ + colvarbias_meta::clear_state_data(); + colvarproxy *proxy = cvm::main()->proxy; + + proxy->close_output_stream(replica_hills_file); + + proxy->close_output_stream(hills_traj_file_name()); + + if (target_dist) { + delete target_dist; + target_dist = NULL; + } +} + + +int colvarbias_meta::clear_state_data() +{ + if (hills_energy) { + delete hills_energy; + hills_energy = NULL; + } + + if (hills_energy_gradients) { + delete hills_energy_gradients; + hills_energy_gradients = NULL; + } + + hills.clear(); + hills_off_grid.clear(); + + return COLVARS_OK; +} + + +// ********************************************************************** +// Hill management member functions +// ********************************************************************** + +std::list::const_iterator +colvarbias_meta::add_hill(colvarbias_meta::hill const &h) +{ + hill_iter const hills_end = hills.end(); + hills.push_back(h); + if (new_hills_begin == hills_end) { + // if new_hills_begin is unset, set it for the first time + new_hills_begin = hills.end(); + new_hills_begin--; + } + + if (use_grids) { + + // also add it to the list of hills that are off-grid, which may + // need to be computed analytically when the colvar returns + // off-grid + cvm::real const min_dist = hills_energy->bin_distance_from_boundaries(h.centers, true); + if (min_dist < (3.0 * cvm::floor(hill_width)) + 1.0) { + hills_off_grid.push_back(h); + } + } + + // output to trajectory (if specified) + if (b_hills_traj) { + // Save the current hill to a buffer for further traj output + hills_traj_os_buf << (hills.back()).output_traj(); + } + + has_data = true; + return hills.end(); +} + + +std::list::const_iterator +colvarbias_meta::delete_hill(hill_iter &h) +{ + if (cvm::debug()) { + cvm::log("Deleting hill from the metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ", with step number "+ + cvm::to_str(h->it)+(h->replica.size() ? + ", replica id \""+h->replica : + "")+".\n"); + } + + if (use_grids && !hills_off_grid.empty()) { + for (hill_iter hoff = hills_off_grid.begin(); + hoff != hills_off_grid.end(); hoff++) { + if (*h == *hoff) { + hills_off_grid.erase(hoff); + break; + } + } + } + + if (b_hills_traj) { + // Save the current hill to a buffer for further traj output + hills_traj_os_buf << "# DELETED this hill: " + << (hills.back()).output_traj() + << "\n"; + } + + return hills.erase(h); +} + + +int colvarbias_meta::update() +{ + int error_code = COLVARS_OK; + + // update base class + error_code |= colvarbias::update(); + + // update the TI estimator (if defined) + error_code |= colvarbias_ti::update(); + + // update grid definition, if needed + error_code |= update_grid_params(); + // add new biasing energy/forces + error_code |= update_bias(); + // update grid content to reflect new bias + error_code |= update_grid_data(); + + if (comm != single_replica && + (cvm::step_absolute() % replica_update_freq) == 0) { + // sync with the other replicas (if needed) + error_code |= replica_share(); + } + + error_code |= calc_energy(NULL); + error_code |= calc_forces(NULL); + + return error_code; +} + + +int colvarbias_meta::update_grid_params() +{ + if (use_grids) { + + std::vector curr_bin = hills_energy->get_colvars_index(); + if (cvm::debug()) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": current coordinates on the grid: "+ + cvm::to_str(curr_bin)+".\n"); + } + + if (expand_grids) { + // first of all, expand the grids, if specified + bool changed_grids = false; + int const min_buffer = + (3 * (size_t) cvm::floor(hill_width)) + 1; + + std::vector new_sizes(hills_energy->sizes()); + std::vector new_lower_boundaries(hills_energy->lower_boundaries); + std::vector new_upper_boundaries(hills_energy->upper_boundaries); + + for (size_t i = 0; i < num_variables(); i++) { + + if (! variables(i)->expand_boundaries) + continue; + + cvm::real &new_lb = new_lower_boundaries[i].real_value; + cvm::real &new_ub = new_upper_boundaries[i].real_value; + int &new_size = new_sizes[i]; + bool changed_lb = false, changed_ub = false; + + if (!variables(i)->is_enabled(f_cv_hard_lower_boundary)) + if (curr_bin[i] < min_buffer) { + int const extra_points = (min_buffer - curr_bin[i]); + new_lb -= extra_points * variables(i)->width; + new_size += extra_points; + // changed offset in this direction => the pointer needs to + // be changed, too + curr_bin[i] += extra_points; + + changed_lb = true; + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": new lower boundary for colvar \""+ + variables(i)->name+"\", at "+ + cvm::to_str(new_lower_boundaries[i])+".\n"); + } + + if (!variables(i)->is_enabled(f_cv_hard_upper_boundary)) + if (curr_bin[i] > new_size - min_buffer - 1) { + int const extra_points = (curr_bin[i] - (new_size - 1) + min_buffer); + new_ub += extra_points * variables(i)->width; + new_size += extra_points; + + changed_ub = true; + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": new upper boundary for colvar \""+ + variables(i)->name+"\", at "+ + cvm::to_str(new_upper_boundaries[i])+".\n"); + } + + if (changed_lb || changed_ub) + changed_grids = true; + } + + if (changed_grids) { + + // map everything into new grids + + colvar_grid_scalar *new_hills_energy = + new colvar_grid_scalar(*hills_energy); + colvar_grid_gradient *new_hills_energy_gradients = + new colvar_grid_gradient(*hills_energy_gradients); + + // supply new boundaries to the new grids + + new_hills_energy->lower_boundaries = new_lower_boundaries; + new_hills_energy->upper_boundaries = new_upper_boundaries; + new_hills_energy->setup(new_sizes, 0.0, 1); + + new_hills_energy_gradients->lower_boundaries = new_lower_boundaries; + new_hills_energy_gradients->upper_boundaries = new_upper_boundaries; + new_hills_energy_gradients->setup(new_sizes, 0.0, num_variables()); + + new_hills_energy->map_grid(*hills_energy); + new_hills_energy_gradients->map_grid(*hills_energy_gradients); + + delete hills_energy; + delete hills_energy_gradients; + hills_energy = new_hills_energy; + hills_energy_gradients = new_hills_energy_gradients; + + curr_bin = hills_energy->get_colvars_index(); + if (cvm::debug()) + cvm::log("Coordinates on the new grid: "+ + cvm::to_str(curr_bin)+".\n"); + } + } + } + return COLVARS_OK; +} + + +int colvarbias_meta::update_bias() +{ + colvarproxy *proxy = cvm::main()->proxy; + // add a new hill if the required time interval has passed + if (((cvm::step_absolute() % new_hill_freq) == 0) && + can_accumulate_data() && is_enabled(f_cvb_history_dependent)) { + + if (cvm::debug()) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": adding a new hill at step "+cvm::to_str(cvm::step_absolute())+".\n"); + } + + cvm::real hills_scale=1.0; + + if (ebmeta) { + hills_scale *= 1.0/target_dist->value(target_dist->get_colvars_index()); + if(cvm::step_absolute() <= ebmeta_equil_steps) { + cvm::real const hills_lambda = + (cvm::real(ebmeta_equil_steps - cvm::step_absolute())) / + (cvm::real(ebmeta_equil_steps)); + hills_scale = hills_lambda + (1-hills_lambda)*hills_scale; + } + } + + if (well_tempered) { + cvm::real hills_energy_sum_here = 0.0; + if (use_grids) { + std::vector curr_bin = hills_energy->get_colvars_index(); + hills_energy_sum_here = hills_energy->value(curr_bin); + } else { + calc_hills(new_hills_begin, hills.end(), hills_energy_sum_here, NULL); + } + hills_scale *= cvm::exp(-1.0*hills_energy_sum_here/(bias_temperature*proxy->boltzmann())); + } + + switch (comm) { + + case single_replica: + + add_hill(hill(cvm::step_absolute(), hill_weight*hills_scale, + colvar_values, colvar_sigmas)); + + break; + + case multiple_replicas: + add_hill(hill(cvm::step_absolute(), hill_weight*hills_scale, + colvar_values, colvar_sigmas, replica_id)); + std::ostream &replica_hills_os = + cvm::proxy->output_stream(replica_hills_file, "replica hills file"); + if (replica_hills_os) { + write_hill(replica_hills_os, hills.back()); + } else { + return cvm::error("Error: in metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + " while writing hills for the other replicas.\n", COLVARS_FILE_ERROR); + } + break; + } + } + + return COLVARS_OK; +} + + +int colvarbias_meta::update_grid_data() +{ + if ((cvm::step_absolute() % grids_freq) == 0) { + // map the most recent gaussians to the grids + project_hills(new_hills_begin, hills.end(), + hills_energy, hills_energy_gradients); + new_hills_begin = hills.end(); + + // TODO: we may want to condense all into one replicas array, + // including "this" as the first element + if (comm == multiple_replicas) { + for (size_t ir = 0; ir < replicas.size(); ir++) { + replicas[ir]->project_hills(replicas[ir]->new_hills_begin, + replicas[ir]->hills.end(), + replicas[ir]->hills_energy, + replicas[ir]->hills_energy_gradients); + replicas[ir]->new_hills_begin = replicas[ir]->hills.end(); + } + } + } + + return COLVARS_OK; +} + + +int colvarbias_meta::calc_energy(std::vector const *values) +{ + size_t ir = 0; + + for (ir = 0; ir < replicas.size(); ir++) { + replicas[ir]->bias_energy = 0.0; + } + + std::vector const curr_bin = values ? + hills_energy->get_colvars_index(*values) : + hills_energy->get_colvars_index(); + + if (hills_energy->index_ok(curr_bin)) { + // index is within the grid: get the energy from there + for (ir = 0; ir < replicas.size(); ir++) { + + bias_energy += replicas[ir]->hills_energy->value(curr_bin); + if (cvm::debug()) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": current coordinates on the grid: "+ + cvm::to_str(curr_bin)+".\n"); + cvm::log("Grid energy = "+cvm::to_str(bias_energy)+".\n"); + } + } + } else { + // off the grid: compute analytically only the hills at the grid's edges + for (ir = 0; ir < replicas.size(); ir++) { + calc_hills(replicas[ir]->hills_off_grid.begin(), + replicas[ir]->hills_off_grid.end(), + bias_energy, + values); + } + } + + // now include the hills that have not been binned yet (starting + // from new_hills_begin) + + for (ir = 0; ir < replicas.size(); ir++) { + calc_hills(replicas[ir]->new_hills_begin, + replicas[ir]->hills.end(), + bias_energy, + values); + if (cvm::debug()) { + cvm::log("Hills energy = "+cvm::to_str(bias_energy)+".\n"); + } + } + + return COLVARS_OK; +} + + +int colvarbias_meta::calc_forces(std::vector const *values) +{ + size_t ir = 0, ic = 0; + for (ir = 0; ir < replicas.size(); ir++) { + for (ic = 0; ic < num_variables(); ic++) { + replicas[ir]->colvar_forces[ic].reset(); + } + } + + std::vector const curr_bin = values ? + hills_energy->get_colvars_index(*values) : + hills_energy->get_colvars_index(); + + if (hills_energy->index_ok(curr_bin)) { + for (ir = 0; ir < replicas.size(); ir++) { + cvm::real const *f = &(replicas[ir]->hills_energy_gradients->value(curr_bin)); + for (ic = 0; ic < num_variables(); ic++) { + // the gradients are stored, not the forces + colvar_forces[ic].real_value += -1.0 * f[ic]; + } + } + } else { + // off the grid: compute analytically only the hills at the grid's edges + for (ir = 0; ir < replicas.size(); ir++) { + for (ic = 0; ic < num_variables(); ic++) { + calc_hills_force(ic, + replicas[ir]->hills_off_grid.begin(), + replicas[ir]->hills_off_grid.end(), + colvar_forces, + values); + } + } + } + + // now include the hills that have not been binned yet (starting + // from new_hills_begin) + + if (cvm::debug()) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": adding the forces from the other replicas.\n"); + } + + for (ir = 0; ir < replicas.size(); ir++) { + for (ic = 0; ic < num_variables(); ic++) { + calc_hills_force(ic, + replicas[ir]->new_hills_begin, + replicas[ir]->hills.end(), + colvar_forces, + values); + if (cvm::debug()) { + cvm::log("Hills forces = "+cvm::to_str(colvar_forces)+".\n"); + } + } + } + + return COLVARS_OK; +} + + + +void colvarbias_meta::calc_hills(colvarbias_meta::hill_iter h_first, + colvarbias_meta::hill_iter h_last, + cvm::real &energy, + std::vector const *values) +{ + size_t i = 0; + + for (hill_iter h = h_first; h != h_last; h++) { + + // compute the gaussian exponent + cvm::real cv_sqdev = 0.0; + for (i = 0; i < num_variables(); i++) { + colvarvalue const &x = values ? (*values)[i] : colvar_values[i]; + colvarvalue const ¢er = h->centers[i]; + cvm::real const sigma = h->sigmas[i]; + cv_sqdev += (variables(i)->dist2(x, center)) / (sigma*sigma); + } + + // compute the gaussian + if (cv_sqdev > 23.0) { + // set it to zero if the exponent is more negative than log(1.0E-06) + h->value(0.0); + } else { + h->value(cvm::exp(-0.5*cv_sqdev)); + } + energy += h->energy(); + } +} + + +void colvarbias_meta::calc_hills_force(size_t const &i, + colvarbias_meta::hill_iter h_first, + colvarbias_meta::hill_iter h_last, + std::vector &forces, + std::vector const *values) +{ + // Retrieve the value of the colvar + colvarvalue const x(values ? (*values)[i] : colvar_values[i]); + + // do the type check only once (all colvarvalues in the hills series + // were already saved with their types matching those in the + // colvars) + + hill_iter h; + switch (x.type()) { + + case colvarvalue::type_scalar: + for (h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const sigma = h->sigmas[i]; + forces[i].real_value += + ( h->weight() * h->value() * (0.5 / (sigma*sigma)) * + (variables(i)->dist2_lgrad(x, center)).real_value ); + } + break; + + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + for (h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const sigma = h->sigmas[i]; + forces[i].rvector_value += + ( h->weight() * h->value() * (0.5 / (sigma*sigma)) * + (variables(i)->dist2_lgrad(x, center)).rvector_value ); + } + break; + + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + for (h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const sigma = h->sigmas[i]; + forces[i].quaternion_value += + ( h->weight() * h->value() * (0.5 / (sigma*sigma)) * + (variables(i)->dist2_lgrad(x, center)).quaternion_value ); + } + break; + + case colvarvalue::type_vector: + for (h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const sigma = h->sigmas[i]; + forces[i].vector1d_value += + ( h->weight() * h->value() * (0.5 / (sigma*sigma)) * + (variables(i)->dist2_lgrad(x, center)).vector1d_value ); + } + break; + + case colvarvalue::type_notset: + case colvarvalue::type_all: + default: + break; + } +} + + +// ********************************************************************** +// grid management functions +// ********************************************************************** + +void colvarbias_meta::project_hills(colvarbias_meta::hill_iter h_first, + colvarbias_meta::hill_iter h_last, + colvar_grid_scalar *he, + colvar_grid_gradient *hg, + bool print_progress) +{ + if (cvm::debug()) + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": projecting hills.\n"); + + // TODO: improve it by looping over a small subgrid instead of the whole grid + + std::vector new_colvar_values(num_variables()); + std::vector colvar_forces_scalar(num_variables()); + + std::vector he_ix = he->new_index(); + std::vector hg_ix = (hg != NULL) ? hg->new_index() : std::vector (0); + cvm::real hills_energy_here = 0.0; + std::vector hills_forces_here(num_variables(), 0.0); + + size_t count = 0; + size_t const print_frequency = ((hills.size() >= 1000000) ? 1 : (1000000/(hills.size()+1))); + + if (hg != NULL) { + + // loop over the points of the grid + for ( ; + (he->index_ok(he_ix)) && (hg->index_ok(hg_ix)); + count++) { + size_t i; + for (i = 0; i < num_variables(); i++) { + new_colvar_values[i] = he->bin_to_value_scalar(he_ix[i], i); + } + + // loop over the hills and increment the energy grid locally + hills_energy_here = 0.0; + calc_hills(h_first, h_last, hills_energy_here, &new_colvar_values); + he->acc_value(he_ix, hills_energy_here); + + for (i = 0; i < num_variables(); i++) { + hills_forces_here[i].reset(); + calc_hills_force(i, h_first, h_last, hills_forces_here, &new_colvar_values); + colvar_forces_scalar[i] = hills_forces_here[i].real_value; + } + hg->acc_force(hg_ix, &(colvar_forces_scalar.front())); + + he->incr(he_ix); + hg->incr(hg_ix); + + if ((count % print_frequency) == 0) { + if (print_progress) { + cvm::real const progress = cvm::real(count) / cvm::real(hg->number_of_points()); + std::ostringstream os; + os.setf(std::ios::fixed, std::ios::floatfield); + os << std::setw(6) << std::setprecision(2) + << 100.0 * progress + << "% done."; + cvm::log(os.str()); + } + } + } + + } else { + cvm::error("No grid object provided in metadynamics::project_hills()\n", + COLVARS_BUG_ERROR); + } + + if (print_progress) { + cvm::log("100.00% done.\n"); + } + + if (! keep_hills) { + hills.erase(hills.begin(), hills.end()); + } +} + + +void colvarbias_meta::recount_hills_off_grid(colvarbias_meta::hill_iter h_first, + colvarbias_meta::hill_iter h_last, + colvar_grid_scalar * /* he */) +{ + hills_off_grid.clear(); + + for (hill_iter h = h_first; h != h_last; h++) { + cvm::real const min_dist = hills_energy->bin_distance_from_boundaries(h->centers, true); + if (min_dist < (3.0 * cvm::floor(hill_width)) + 1.0) { + hills_off_grid.push_back(*h); + } + } +} + + + +// ********************************************************************** +// multiple replicas functions +// ********************************************************************** + + +int colvarbias_meta::replica_share() +{ + int error_code = COLVARS_OK; + colvarproxy *proxy = cvm::proxy; + // sync with the other replicas (if needed) + if (comm == multiple_replicas) { + // reread the replicas registry + error_code |= update_replicas_registry(); + // empty the output buffer + error_code |= proxy->flush_output_stream(replica_hills_file); + error_code |= read_replica_files(); + } + return error_code; +} + + +int colvarbias_meta::update_replicas_registry() +{ + int error_code = COLVARS_OK; + + if (cvm::debug()) + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": updating the list of replicas, currently containing "+ + cvm::to_str(replicas.size())+" elements.\n"); + + { + // copy the whole file into a string for convenience + std::string line(""); + std::ifstream reg_file(replicas_registry_file.c_str()); + if (reg_file.is_open()) { + replicas_registry.clear(); + while (colvarparse::getline_nocomments(reg_file, line)) + replicas_registry.append(line+"\n"); + } else { + error_code |= cvm::error("Error: failed to open file \""+ + replicas_registry_file+"\" for reading.\n", + COLVARS_FILE_ERROR); + } + } + + // now parse it + std::istringstream reg_is(replicas_registry); + if (reg_is.good()) { + + std::string new_replica(""); + std::string new_replica_file(""); + while ((reg_is >> new_replica) && new_replica.size() && + (reg_is >> new_replica_file) && new_replica_file.size()) { + + if (new_replica == this->replica_id) { + // this is the record for this same replica, skip it + new_replica_file.clear(); + new_replica.clear(); + continue; + } + + bool already_loaded = false; + for (size_t ir = 0; ir < replicas.size(); ir++) { + if (new_replica == (replicas[ir])->replica_id) { + // this replica was already added + if (cvm::debug()) + cvm::log("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": skipping a replica already loaded, \""+ + (replicas[ir])->replica_id+"\".\n"); + already_loaded = true; + break; + } + } + + if (!already_loaded) { + // add this replica to the registry + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": accessing replica \""+new_replica+"\".\n"); + replicas.push_back(new colvarbias_meta("metadynamics")); + (replicas.back())->replica_id = new_replica; + (replicas.back())->replica_list_file = new_replica_file; + (replicas.back())->replica_state_file = ""; + (replicas.back())->replica_state_file_in_sync = false; + + // Note: the following could become a copy constructor? + (replicas.back())->name = this->name; + (replicas.back())->colvars = colvars; + (replicas.back())->use_grids = use_grids; + (replicas.back())->dump_fes = false; + (replicas.back())->expand_grids = false; + (replicas.back())->rebin_grids = false; + (replicas.back())->keep_hills = false; + (replicas.back())->colvar_forces = colvar_forces; + + (replicas.back())->comm = multiple_replicas; + + if (use_grids) { + (replicas.back())->hills_energy = new colvar_grid_scalar(colvars); + (replicas.back())->hills_energy_gradients = new colvar_grid_gradient(colvars); + } + if (is_enabled(f_cvb_calc_ti_samples)) { + (replicas.back())->enable(f_cvb_calc_ti_samples); + (replicas.back())->colvarbias_ti::init_grids(); + } + (replicas.back())->update_status = 1; + } + } + } else { + error_code |= cvm::error("Error: cannot read the replicas registry file \""+ + replicas_registry+"\".\n", COLVARS_FILE_ERROR); + } + + // now (re)read the list file of each replica + for (size_t ir = 0; ir < replicas.size(); ir++) { + if (cvm::debug()) + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": reading the list file for replica \""+ + (replicas[ir])->replica_id+"\".\n"); + + std::ifstream list_is((replicas[ir])->replica_list_file.c_str()); + std::string key; + std::string new_state_file, new_hills_file; + if (!(list_is >> key) || + !(key == std::string("stateFile")) || + !(list_is >> new_state_file) || + !(list_is >> key) || + !(key == std::string("hillsFile")) || + !(list_is >> new_hills_file)) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": failed to read the file \""+ + (replicas[ir])->replica_list_file+"\": will try again after "+ + cvm::to_str(replica_update_freq)+" steps.\n"); + (replicas[ir])->update_status++; + } else { + if (new_state_file != (replicas[ir])->replica_state_file) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": replica \""+(replicas[ir])->replica_id+ + "\" has supplied a new state file, \""+new_state_file+ + "\".\n"); + (replicas[ir])->replica_state_file_in_sync = false; + (replicas[ir])->replica_state_file = new_state_file; + (replicas[ir])->replica_hills_file = new_hills_file; + } + } + } + + if (cvm::debug()) + cvm::log("Metadynamics bias \""+this->name+"\": the list of replicas contains "+ + cvm::to_str(replicas.size())+" elements.\n"); + + return error_code; +} + + +int colvarbias_meta::read_replica_files() +{ + // Note: we start from the 2nd replica. + for (size_t ir = 1; ir < replicas.size(); ir++) { + + // (re)read the state file if necessary + if ( (! (replicas[ir])->has_data) || + (! (replicas[ir])->replica_state_file_in_sync) ) { + if ((replicas[ir])->replica_state_file.size()) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": reading the state of replica \""+ + (replicas[ir])->replica_id+"\" from file \""+ + (replicas[ir])->replica_state_file+"\".\n"); + std::ifstream is((replicas[ir])->replica_state_file.c_str()); + if ((replicas[ir])->read_state(is)) { + // state file has been read successfully + (replicas[ir])->replica_state_file_in_sync = true; + (replicas[ir])->update_status = 0; + } else { + cvm::log("Failed to read the file \""+ + (replicas[ir])->replica_state_file+ + "\": will try again in "+ + cvm::to_str(replica_update_freq)+" steps.\n"); + (replicas[ir])->replica_state_file_in_sync = false; + (replicas[ir])->update_status++; + } + is.close(); + } else { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": the state file of replica \""+ + (replicas[ir])->replica_id+"\" is currently undefined: " + "will try again after "+ + cvm::to_str(replica_update_freq)+" steps.\n"); + (replicas[ir])->update_status++; + } + } + + if (! (replicas[ir])->replica_state_file_in_sync) { + // if a new state file is being read, the hills file is also new + (replicas[ir])->replica_hills_file_pos = 0; + } + + // now read the hills added after writing the state file + if ((replicas[ir])->replica_hills_file.size()) { + + if (cvm::debug()) + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": checking for new hills from replica \""+ + (replicas[ir])->replica_id+"\" in the file \""+ + (replicas[ir])->replica_hills_file+"\".\n"); + + // read hills from the other replicas' files + + std::ifstream is((replicas[ir])->replica_hills_file.c_str()); + if (is.is_open()) { + + // try to resume the previous position (if not the beginning) + if ((replicas[ir])->replica_hills_file_pos > 0) { + is.seekg((replicas[ir])->replica_hills_file_pos, std::ios::beg); + } + + if (!is.is_open()){ + // if fail (the file may have been overwritten), reset this + // position + is.clear(); + is.seekg(0, std::ios::beg); + // reset the counter + (replicas[ir])->replica_hills_file_pos = 0; + // schedule to reread the state file + (replicas[ir])->replica_state_file_in_sync = false; + // and record the failure + (replicas[ir])->update_status++; + cvm::log("Failed to read the file \""+(replicas[ir])->replica_hills_file+ + "\" at the previous position: will try again in "+ + cvm::to_str(replica_update_freq)+" steps.\n"); + } else { + + while ((replicas[ir])->read_hill(is)) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": received a hill from replica \""+ + (replicas[ir])->replica_id+ + "\" at step "+ + cvm::to_str(((replicas[ir])->hills.back()).it)+".\n"); + } + is.clear(); + // store the position for the next read + (replicas[ir])->replica_hills_file_pos = is.tellg(); + if (cvm::debug()) { + cvm::log("Metadynamics bias \""+this->name+"\""+ + ": stopped reading file \""+ + (replicas[ir])->replica_hills_file+ + "\" at position "+ + cvm::to_str((replicas[ir])->replica_hills_file_pos)+".\n"); + } + + // test whether this is the end of the file + is.seekg(0, std::ios::end); + if (is.tellg() > (replicas[ir])->replica_hills_file_pos + ((std::streampos) 1)) { + (replicas[ir])->update_status++; + } else { + (replicas[ir])->update_status = 0; + } + } + + } else { + cvm::log("Failed to read the file \""+ + (replicas[ir])->replica_hills_file+ + "\": will try again in "+ + cvm::to_str(replica_update_freq)+" steps.\n"); + (replicas[ir])->update_status++; + } + is.close(); + } + + size_t const n_flush = (replica_update_freq/new_hill_freq + 1); + if ((replicas[ir])->update_status > 3*n_flush) { + // TODO: suspend the calculation? + cvm::log("WARNING: metadynamics bias \""+this->name+"\""+ + " could not read information from replica \""+ + (replicas[ir])->replica_id+ + "\" after more than "+ + cvm::to_str((replicas[ir])->update_status * replica_update_freq)+ + " steps. Ensure that it is still running.\n"); + } + } + return COLVARS_OK; +} + + +int colvarbias_meta::set_state_params(std::string const &state_conf) +{ + int error_code = colvarbias::set_state_params(state_conf); + + if (error_code != COLVARS_OK) { + return error_code; + } + + colvarparse::get_keyval(state_conf, "keepHills", restart_keep_hills, false, + colvarparse::parse_restart); + + if ((!restart_keep_hills) && (cvm::main()->restart_version_number() < 20210604)) { + if (keep_hills) { + cvm::log("Warning: could not ensure that keepHills was enabled when " + "this state file was written; because it is enabled now, " + "it is assumed that it was also then, but please verify.\n"); + restart_keep_hills = true; + } + } else { + if (restart_keep_hills) { + cvm::log("This state file/stream contains explicit hills.\n"); + } + } + + std::string check_replica = ""; + if (colvarparse::get_keyval(state_conf, "replicaID", check_replica, + std::string(""), colvarparse::parse_restart) && + (check_replica != this->replica_id)) { + return cvm::error("Error: in the state file , the " + "\"metadynamics\" block has a different replicaID ("+ + check_replica+" instead of "+replica_id+").\n", + COLVARS_INPUT_ERROR); + } + + return COLVARS_OK; +} + + +template +IST & colvarbias_meta::read_grid_data_template_(IST& is, std::string const &key, + GT *grid, GT *backup_grid) +{ + auto const start_pos = is.tellg(); + std::string key_in; + if (is >> key_in) { + if ((key != key_in) || !(grid->read_restart(is))) { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + if (!rebin_grids) { + if ((backup_grid == nullptr) || (comm == single_replica)) { + cvm::error("Error: couldn't read grid data for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + "; if useGrids was off when the state file was written, " + "try enabling rebinGrids now to regenerate the grids.\n", COLVARS_INPUT_ERROR); + } + } + } + } else { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + return is; +} + + +template IST &colvarbias_meta::read_state_data_template_(IST &is) +{ + if (use_grids) { + + colvar_grid_scalar *hills_energy_backup = NULL; + colvar_grid_gradient *hills_energy_gradients_backup = NULL; + + if (has_data) { + if (cvm::debug()) + cvm::log("Backupping grids for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+".\n"); + hills_energy_backup = hills_energy; + hills_energy_gradients_backup = hills_energy_gradients; + hills_energy = new colvar_grid_scalar(colvars); + hills_energy_gradients = new colvar_grid_gradient(colvars); + } + + read_grid_data_template_(is, "hills_energy", hills_energy, + hills_energy_backup); + + read_grid_data_template_( + is, "hills_energy_gradients", hills_energy_gradients, hills_energy_gradients_backup); + + if (is) { + cvm::log(" successfully read the biasing potential and its gradients from grids.\n"); + if (hills_energy_backup != nullptr) { + // Now that we have successfully updated the grids, delete the backup copies + delete hills_energy_backup; + delete hills_energy_gradients_backup; + } + } else { + return is; + } + } + + // Save references to the end of the list of existing hills, so that they can + // be cleared if hills are read successfully from the stream + bool const existing_hills = !hills.empty(); + size_t const old_hills_size = hills.size(); + hill_iter old_hills_end = hills.end(); + hill_iter old_hills_off_grid_end = hills_off_grid.end(); + if (cvm::debug()) { + cvm::log("Before reading hills from the state file, there are "+ + cvm::to_str(hills.size())+" hills in memory.\n"); + } + + // Read any hills following the grid data (if any) + while (read_hill(is)) { + if (cvm::debug()) { + cvm::log("Read a previously saved hill under the " + "metadynamics bias \"" + + this->name + "\", created at step " + cvm::to_str((hills.back()).it) + + "; position in stream is " + cvm::to_str(is.tellg()) + ".\n"); + } + } + + is.clear(); + + new_hills_begin = hills.end(); + cvm::log(" successfully read "+cvm::to_str(hills.size() - old_hills_size)+ + " explicit hills from state.\n"); + + if (existing_hills) { + // Prune any hills that pre-existed those just read + hills.erase(hills.begin(), old_hills_end); + hills_off_grid.erase(hills_off_grid.begin(), old_hills_off_grid_end); + if (cvm::debug()) { + cvm::log("After pruning the old hills, there are now "+ + cvm::to_str(hills.size())+" hills in memory.\n"); + } + } + + // If rebinGrids is set, rebin the grids based on the current information + rebin_grids_after_restart(); + + if (use_grids) { + if (!hills_off_grid.empty()) { + cvm::log(cvm::to_str(hills_off_grid.size())+" hills are near the " + "grid boundaries: they will be computed analytically " + "and saved to the state files.\n"); + } + } + + colvarbias_ti::read_state_data(is); + + if (cvm::debug()) + cvm::log("colvarbias_meta::read_restart() done\n"); + + has_data = true; + + if (comm == multiple_replicas) { + read_replica_files(); + } + + return is; +} + + +std::istream & colvarbias_meta::read_state_data(std::istream& is) +{ + return read_state_data_template_(is); +} + + +cvm::memory_stream &colvarbias_meta::read_state_data(cvm::memory_stream &is) +{ + return read_state_data_template_(is); +} + + +void colvarbias_meta::rebin_grids_after_restart() +{ + if (rebin_grids) { + + // allocate new grids (based on the new boundaries and widths just + // read from the configuration file), and project onto them the + // grids just read from the restart file + + colvar_grid_scalar *new_hills_energy = + new colvar_grid_scalar(colvars); + colvar_grid_gradient *new_hills_energy_gradients = + new colvar_grid_gradient(colvars); + + if (cvm::debug()) { + std::ostringstream tmp_os; + tmp_os << "hills_energy parameters:\n"; + tmp_os << hills_energy->get_state_params(); + tmp_os << "new_hills_energy parameters:\n"; + tmp_os << new_hills_energy->get_state_params(); + cvm::log(tmp_os.str()); + } + + if (restart_keep_hills && !hills.empty()) { + // if there are hills, recompute the new grids from them + cvm::log("Rebinning the energy and forces grids from "+ + cvm::to_str(hills.size())+" hills (this may take a while)...\n"); + project_hills(hills.begin(), hills.end(), + new_hills_energy, new_hills_energy_gradients, true); + cvm::log("rebinning done.\n"); + + } else { + // otherwise, use the grids in the restart file + cvm::log("Rebinning the energy and forces grids " + "from the grids in the restart file.\n"); + new_hills_energy->map_grid(*hills_energy); + new_hills_energy_gradients->map_grid(*hills_energy_gradients); + } + + delete hills_energy; + delete hills_energy_gradients; + hills_energy = new_hills_energy; + hills_energy_gradients = new_hills_energy_gradients; + + // assuming that some boundaries have expanded, eliminate those + // off-grid hills that aren't necessary any more + if (!hills.empty()) + recount_hills_off_grid(hills.begin(), hills.end(), hills_energy); + } +} + + +template +OST &colvarbias_meta::write_hill_template_(OST &os, colvarbias_meta::hill const &h) +{ + bool const formatted = !std::is_same::value; + + if (formatted) { + os.setf(std::ios::scientific, std::ios::floatfield); + } + + write_state_data_key(os, "hill", false); + + if (formatted) + os << "{\n"; + + write_state_data_key(os, "step", false); + if (formatted) + os << std::setw(cvm::it_width); + os << h.it; + if (formatted) + os << "\n"; + + write_state_data_key(os, "weight", false); + if (formatted) + os << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width); + os << h.W; + if (formatted) + os << "\n"; + + size_t i; + write_state_data_key(os, "centers", false); + for (i = 0; i < (h.centers).size(); i++) { + if (formatted) + os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width); + os << h.centers[i]; + } + if (formatted) + os << "\n"; + + // For backward compatibility, write the widths instead of the sigmas + write_state_data_key(os, "widths", false); + for (i = 0; i < (h.sigmas).size(); i++) { + if (formatted) + os << " " << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width); + os << 2.0 * h.sigmas[i]; + } + if (formatted) + os << "\n"; + + if (h.replica.size()) { + write_state_data_key(os, "replicaID", false); + os << h.replica; + if (formatted) + os << "\n"; + } + + if (formatted) + os << "}\n"; + + return os; +} + + +std::ostream &colvarbias_meta::write_hill(std::ostream &os, colvarbias_meta::hill const &h) +{ + return write_hill_template_(os, h); +} + + +cvm::memory_stream &colvarbias_meta::write_hill(cvm::memory_stream &os, + colvarbias_meta::hill const &h) +{ + return write_hill_template_(os, h); +} + + +template IST &hill_stream_error(IST &is, size_t start_pos, std::string const &key) +{ + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + cvm::error("Error: in reading data for keyword \"" + key + "\" from stream.\n", + COLVARS_INPUT_ERROR); + return is; +} + + +template IST &colvarbias_meta::read_hill_template_(IST &is) +{ + if (!is) + return is; // do nothing if failbit is set + + bool const formatted = !std::is_same::value; + + auto const start_pos = is.tellg(); + + std::string key; + if (!(is >> key) || (key != "hill")) { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + if (formatted) { + std::string brace; + if (!(is >> brace) || (brace != "{")) { + return hill_stream_error(is, start_pos, "hill"); + } + } + + cvm::step_number h_it = 0L; + cvm::real h_weight = 0.0; + std::vector h_centers(num_variables()); + for (size_t i = 0; i < num_variables(); i++) { + h_centers[i].type(variables(i)->value()); + } + std::vector h_sigmas(num_variables()); + std::string h_replica; + + if (!read_state_data_key(is, "step") || !(is >> h_it)) { + return hill_stream_error(is, start_pos, "step"); + } + + if (read_state_data_key(is, "weight")) { + if (!(is >> h_weight)) { + return hill_stream_error(is, start_pos, "weight"); + } + } + + if (read_state_data_key(is, "centers")) { + for (size_t i = 0; i < num_variables(); i++) { + if (!(is >> h_centers[i])) { + return hill_stream_error(is, start_pos, "centers"); + } + } + } + + if (read_state_data_key(is, "widths")) { + for (size_t i = 0; i < num_variables(); i++) { + if (!(is >> h_sigmas[i])) { + return hill_stream_error(is, start_pos, "widths"); + } + // For backward compatibility, read the widths instead of the sigmas + h_sigmas[i] /= 2.0; + } + } + + if (comm != single_replica) { + if (read_state_data_key(is, "replicaID")) { + if (!(is >> h_replica)) { + return hill_stream_error(is, start_pos, "replicaID"); + } + if (h_replica != replica_id) { + cvm::error("Error: trying to read a hill created by replica \"" + h_replica + + "\" for replica \"" + replica_id + "\"; did you swap output files?\n", + COLVARS_INPUT_ERROR); + return hill_stream_error(is, start_pos, "replicaID"); + } + } + } + + if (formatted) { + std::string brace; + if (!(is >> brace) || (brace != "}")) { + return hill_stream_error(is, start_pos, "hill"); + } + } + + if ((h_it <= state_file_step) && !restart_keep_hills) { + if (cvm::debug()) + cvm::log("Skipping a hill older than the state file for metadynamics bias \"" + this->name + + "\"" + ((comm != single_replica) ? ", replica \"" + replica_id + "\"" : "") + "\n"); + return is; + } + + hill_iter const hills_end = hills.end(); + hills.push_back(hill(h_it, h_weight, h_centers, h_sigmas, h_replica)); + if (new_hills_begin == hills_end) { + // if new_hills_begin is unset, set it for the first time + new_hills_begin = hills.end(); + new_hills_begin--; + } + + if (use_grids) { + // add this also to the list of hills that are off-grid, which will + // be computed analytically + cvm::real const min_dist = + hills_energy->bin_distance_from_boundaries((hills.back()).centers, true); + if (min_dist < (3.0 * cvm::floor(hill_width)) + 1.0) { + hills_off_grid.push_back(hills.back()); + } + } + + has_data = true; + return is; +} + + +std::istream &colvarbias_meta::read_hill(std::istream &is) +{ + return read_hill_template_(is); +} + + +cvm::memory_stream &colvarbias_meta::read_hill(cvm::memory_stream &is) +{ + return read_hill_template_(is); +} + + +int colvarbias_meta::setup_output() +{ + int error_code = COLVARS_OK; + + output_prefix = cvm::output_prefix(); + if (cvm::main()->num_biases_feature(colvardeps::f_cvb_calc_pmf) > 1) { + // if this is not the only free energy integrator, append + // this bias's name, to distinguish it from the output of the other + // biases producing a .pmf file + output_prefix += ("."+this->name); + } + + if (comm == multiple_replicas) { + + // TODO: one may want to specify the path manually for intricated filesystems? + char *pwd = new char[3001]; + if (GETCWD(pwd, 3000) == nullptr) { + if (pwd != nullptr) { // + delete[] pwd; + } + return cvm::error("Error: cannot get the path of the current working directory.\n", + COLVARS_BUG_ERROR); + } + + replica_list_file = + (std::string(pwd)+std::string(PATHSEP)+ + this->name+"."+replica_id+".files.txt"); + // replica_hills_file and replica_state_file are those written + // by the current replica; within the mirror biases, they are + // those by another replica + replica_hills_file = + (std::string(pwd)+std::string(PATHSEP)+ + cvm::output_prefix()+".colvars."+this->name+"."+replica_id+".hills"); + replica_state_file = + (std::string(pwd)+std::string(PATHSEP)+ + cvm::output_prefix()+".colvars."+this->name+"."+replica_id+".state"); + delete[] pwd; + + // now register this replica + + // first check that it isn't already there + bool registered_replica = false; + std::ifstream reg_is(replicas_registry_file.c_str()); + if (reg_is.is_open()) { // the file may not be there yet + std::string existing_replica(""); + std::string existing_replica_file(""); + while ((reg_is >> existing_replica) && existing_replica.size() && + (reg_is >> existing_replica_file) && existing_replica_file.size()) { + if (existing_replica == replica_id) { + // this replica was already registered + replica_list_file = existing_replica_file; + reg_is.close(); + registered_replica = true; + break; + } + } + reg_is.close(); + } + + // if this replica was not included yet, we should generate a + // new record for it: but first, we write this replica's files, + // for the others to read + + // open the "hills" buffer file + reopen_replica_buffer_file(); + + // write the state file (so that there is always one available) + write_replica_state_file(); + + // schedule to read the state files of the other replicas + for (size_t ir = 0; ir < replicas.size(); ir++) { + (replicas[ir])->replica_state_file_in_sync = false; + } + + // if we're running without grids, use a growing list of "hills" files + // otherwise, just one state file and one "hills" file as buffer + std::ostream &list_os = cvm::proxy->output_stream(replica_list_file, "replica list file"); + if (list_os) { + list_os << "stateFile " << replica_state_file << "\n"; + list_os << "hillsFile " << replica_hills_file << "\n"; + cvm::proxy->close_output_stream(replica_list_file); + } else { + error_code |= COLVARS_FILE_ERROR; + } + + // finally, add a new record for this replica to the registry + if (! registered_replica) { + std::ofstream reg_os(replicas_registry_file.c_str(), std::ios::app); + if (!reg_os) { + return cvm::get_error(); + } + reg_os << replica_id << " " << replica_list_file << "\n"; + cvm::proxy->close_output_stream(replicas_registry_file); + } + } + + if (b_hills_traj) { + std::ostream &hills_traj_os = + cvm::proxy->output_stream(hills_traj_file_name(), "hills trajectory file"); + if (!hills_traj_os) { + error_code |= COLVARS_FILE_ERROR; + } + } + + return error_code; +} + + +std::string const colvarbias_meta::hills_traj_file_name() const +{ + return std::string(cvm::output_prefix()+ + ".colvars."+this->name+ + ( (comm != single_replica) ? + ("."+replica_id) : + ("") )+ + ".hills.traj"); +} + + +std::string const colvarbias_meta::get_state_params() const +{ + std::ostringstream os; + if (keep_hills) { + os << "keepHills on" << "\n"; + } + if (this->comm != single_replica) { + os << "replicaID " << this->replica_id << "\n"; + } + return (colvarbias::get_state_params() + os.str()); +} + + +template OST &colvarbias_meta::write_state_data_template_(OST &os) +{ + if (use_grids) { + + // this is a very good time to project hills, if you haven't done + // it already! + project_hills(new_hills_begin, hills.end(), hills_energy, hills_energy_gradients); + new_hills_begin = hills.end(); + + // write down the grids to the restart file + write_state_data_key(os, "hills_energy"); + hills_energy->write_restart(os); + write_state_data_key(os, "hills_energy_gradients"); + hills_energy_gradients->write_restart(os); + } + + if ((!use_grids) || keep_hills) { + // write all hills currently in memory + for (std::list::const_iterator h = this->hills.begin(); h != this->hills.end(); h++) { + write_hill(os, *h); + } + } else { + // write just those that are near the grid boundaries + for (std::list::const_iterator h = this->hills_off_grid.begin(); + h != this->hills_off_grid.end(); h++) { + write_hill(os, *h); + } + } + + colvarbias_ti::write_state_data(os); + return os; +} + + +std::ostream & colvarbias_meta::write_state_data(std::ostream& os) +{ + return write_state_data_template_(os); +} + + +cvm::memory_stream &colvarbias_meta::write_state_data(cvm::memory_stream &os) +{ + return write_state_data_template_(os); +} + + +int colvarbias_meta::write_state_to_replicas() +{ + int error_code = COLVARS_OK; + if (comm != single_replica) { + error_code |= write_replica_state_file(); + error_code |= reopen_replica_buffer_file(); + // schedule to reread the state files of the other replicas + for (size_t ir = 0; ir < replicas.size(); ir++) { + (replicas[ir])->replica_state_file_in_sync = false; + } + } + return error_code; +} + + +int colvarbias_meta::write_output_files() +{ + colvarbias_ti::write_output_files(); + if (dump_fes) { + write_pmf(); + } + if (b_hills_traj) { + std::ostream &hills_traj_os = + cvm::proxy->output_stream(hills_traj_file_name(), "hills trajectory file"); + hills_traj_os << hills_traj_os_buf.str(); + cvm::proxy->flush_output_stream(hills_traj_file_name()); + // clear the buffer + hills_traj_os_buf.str(""); + hills_traj_os_buf.clear(); + } + return COLVARS_OK; +} + + +void colvarbias_meta::write_pmf() +{ + colvarproxy *proxy = cvm::main()->proxy; + // allocate a new grid to store the pmf + colvar_grid_scalar *pmf = new colvar_grid_scalar(*hills_energy); + pmf->setup(); + + if ((comm == single_replica) || (dump_replica_fes)) { + // output the PMF from this instance or replica + pmf->reset(); + pmf->add_grid(*hills_energy); + + if (ebmeta) { + int nt_points=pmf->number_of_points(); + for (int i = 0; i < nt_points; i++) { + cvm::real pmf_val=0.0; + cvm::real target_val=target_dist->value(i); + if (target_val>0) { + pmf_val=pmf->value(i); + pmf_val=pmf_val + proxy->target_temperature() * proxy->boltzmann() * cvm::logn(target_val); + } + pmf->set_value(i,pmf_val); + } + } + + cvm::real const max = pmf->maximum_value(); + pmf->add_constant(-1.0 * max); + pmf->multiply_constant(-1.0); + if (well_tempered) { + cvm::real const well_temper_scale = (bias_temperature + proxy->target_temperature()) / bias_temperature; + pmf->multiply_constant(well_temper_scale); + } + { + std::string const fes_file_name(this->output_prefix + + ((comm != single_replica) ? ".partial" : "") + + (dump_fes_save ? + "."+cvm::to_str(cvm::step_absolute()) : "") + + ".pmf"); + pmf->write_multicol(fes_file_name, "PMF file"); + } + } + + if (comm != single_replica) { + // output the combined PMF from all replicas + pmf->reset(); + // current replica already included in the pools of replicas + for (size_t ir = 0; ir < replicas.size(); ir++) { + pmf->add_grid(*(replicas[ir]->hills_energy)); + } + + if (ebmeta) { + int nt_points=pmf->number_of_points(); + for (int i = 0; i < nt_points; i++) { + cvm::real pmf_val=0.0; + cvm::real target_val=target_dist->value(i); + if (target_val>0) { + pmf_val=pmf->value(i); + pmf_val=pmf_val + proxy->target_temperature() * proxy->boltzmann() * cvm::logn(target_val); + } + pmf->set_value(i,pmf_val); + } + } + + cvm::real const max = pmf->maximum_value(); + pmf->add_constant(-1.0 * max); + pmf->multiply_constant(-1.0); + if (well_tempered) { + cvm::real const well_temper_scale = (bias_temperature + proxy->target_temperature()) / bias_temperature; + pmf->multiply_constant(well_temper_scale); + } + std::string const fes_file_name(this->output_prefix + + (dump_fes_save ? + "."+cvm::to_str(cvm::step_absolute()) : "") + + ".pmf"); + pmf->write_multicol(fes_file_name, "partial PMF file"); + } + + delete pmf; +} + + + +int colvarbias_meta::write_replica_state_file() +{ + colvarproxy *proxy = cvm::proxy; + + if (cvm::debug()) { + cvm::log("Writing replica state file for bias \""+name+"\"\n"); + } + + int error_code = COLVARS_OK; + + // Write to temporary state file + std::string const tmp_state_file(replica_state_file+".tmp"); + error_code |= proxy->remove_file(tmp_state_file); + std::ostream &rep_state_os = cvm::proxy->output_stream(tmp_state_file, "temporary state file"); + if (rep_state_os) { + if (!write_state(rep_state_os)) { + error_code |= cvm::error("Error: in writing to temporary file \""+ + tmp_state_file+"\".\n", COLVARS_FILE_ERROR); + } + } + error_code |= proxy->close_output_stream(tmp_state_file); + + error_code |= proxy->rename_file(tmp_state_file, replica_state_file); + + return error_code; +} + + +int colvarbias_meta::reopen_replica_buffer_file() +{ + int error_code = COLVARS_OK; + colvarproxy *proxy = cvm::proxy; + if (proxy->output_stream(replica_hills_file, "replica hills file")) { + error_code |= proxy->close_output_stream(replica_hills_file); + } + error_code |= proxy->remove_file(replica_hills_file); + std::ostream &replica_hills_os = proxy->output_stream(replica_hills_file, "replica hills file"); + if (replica_hills_os) { + replica_hills_os.setf(std::ios::scientific, std::ios::floatfield); + } else { + error_code |= COLVARS_FILE_ERROR; + } + return error_code; +} + + +std::string colvarbias_meta::hill::output_traj() +{ + std::ostringstream os; + os.setf(std::ios::fixed, std::ios::floatfield); + os << std::setw(cvm::it_width) << it << " "; + + os.setf(std::ios::scientific, std::ios::floatfield); + + size_t i; + os << " "; + for (i = 0; i < centers.size(); i++) { + os << " "; + os << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) << centers[i]; + } + + os << " "; + for (i = 0; i < sigmas.size(); i++) { + os << " "; + os << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) << sigmas[i]; + } + + os << " "; + os << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << W << "\n"; + + return os.str(); +} + + +colvarbias_meta::hill::hill(cvm::step_number it_in, + cvm::real W_in, + std::vector const &cv_values, + std::vector const &cv_sigmas, + std::string const &replica_in) + : it(it_in), + sW(1.0), + W(W_in), + centers(cv_values.size()), + sigmas(cv_values.size()), + replica(replica_in) +{ + hill_value = 0.0; + for (size_t i = 0; i < cv_values.size(); i++) { + centers[i].type(cv_values[i]); + centers[i] = cv_values[i]; + sigmas[i] = cv_sigmas[i]; + } + if (cvm::debug()) { + cvm::log("New hill, applied to "+cvm::to_str(cv_values.size())+ + " collective variables, with centers "+ + cvm::to_str(centers)+", sigmas "+ + cvm::to_str(sigmas)+" and weight "+ + cvm::to_str(W)+".\n"); + } +} + + +colvarbias_meta::hill::hill(colvarbias_meta::hill const &h) + : it(h.it), + hill_value(0.0), + sW(1.0), + W(h.W), + centers(h.centers), + sigmas(h.sigmas), + replica(h.replica) +{ + hill_value = 0.0; +} + + +colvarbias_meta::hill & +colvarbias_meta::hill::operator = (colvarbias_meta::hill const &h) +{ + it = h.it; + hill_value = 0.0; + sW = 1.0; + W = h.W; + centers = h.centers; + sigmas = h.sigmas; + replica = h.replica; + hill_value = h.hill_value; + return *this; +} + + +colvarbias_meta::hill::~hill() +{} diff --git a/src/external/colvars/colvarbias_meta.h b/src/external/colvars/colvarbias_meta.h new file mode 100644 index 00000000000..f85bb0fdc29 --- /dev/null +++ b/src/external/colvars/colvarbias_meta.h @@ -0,0 +1,438 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_META_H +#define COLVARBIAS_META_H + +#include +#include +#include + +#include "colvarbias.h" +#include "colvargrid.h" + + +/// Metadynamics bias (implementation of \link colvarbias \endlink) +class colvarbias_meta + : public virtual colvarbias, + public virtual colvarbias_ti +{ + +public: + + /// Communication between different replicas + enum Communication { + /// One replica (default) + single_replica, + /// Hills added concurrently by several replicas + multiple_replicas + }; + + /// Communication between different replicas + Communication comm; + + colvarbias_meta(char const *key); + virtual ~colvarbias_meta(); + + virtual int init(std::string const &conf); + virtual int init_replicas_params(std::string const &conf); + virtual int init_well_tempered_params(std::string const &conf); + virtual int init_ebmeta_params(std::string const &conf); + + virtual int clear_state_data(); + + virtual int update(); + virtual int update_grid_params(); + virtual int update_bias(); + virtual int update_grid_data(); + virtual int replica_share(); + + virtual int calc_energy(std::vector const *values); + virtual int calc_forces(std::vector const *values); + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &state_conf); + + virtual std::ostream &write_state_data(std::ostream &os); + virtual cvm::memory_stream &write_state_data(cvm::memory_stream &os); + virtual std::istream &read_state_data(std::istream &is); + virtual cvm::memory_stream &read_state_data(cvm::memory_stream &is); + +private: + + template + IST &read_grid_data_template_(IST &is, std::string const &key, GT *grid, GT *backup_grid); + + template IST &read_state_data_template_(IST &is); + + template OST &write_state_data_template_(OST &os); + +public: + + /// Function called by read_state_data() to execute rebinning (if requested) + void rebin_grids_after_restart(); + + virtual int setup_output(); + virtual int write_output_files(); + virtual void write_pmf(); + virtual int write_state_to_replicas(); + + class hill; + typedef std::list::iterator hill_iter; + +protected: + + /// Width of a hill in number of grid points + /// + /// The local width of each collective variable, multiplied by this + /// number, provides the hill width along that direction + cvm::real hill_width; + + /// The sigma parameters of the Gaussian hills + std::vector colvar_sigmas; + + /// \brief Number of simulation steps between two hills + size_t new_hill_freq; + + /// Write the hill logfile + bool b_hills_traj; + + /// Name of the hill logfile + std::string const hills_traj_file_name() const; + + /// \brief List of hills used on this bias (total); if a grid is + /// employed, these don't need to be updated at every time step + std::list hills; + + /// \brief Iterator to the first of the "newest" hills (when using + /// grids, those who haven't been mapped yet) + hill_iter new_hills_begin; + + /// \brief List of hills used on this bias that are on the boundary + /// edges; these are updated regardless of whether hills are used + std::list hills_off_grid; + + /// \brief Same as new_hills_begin, but for the off-grid ones + hill_iter new_hills_off_grid_begin; + + /// Regenerate the hills_off_grid list + void recount_hills_off_grid(hill_iter h_first, hill_iter h_last, + colvar_grid_scalar *ge); + + template OST &write_hill_template_(OST &os, colvarbias_meta::hill const &h); + + /// Write a hill to a formatted stream + std::ostream &write_hill(std::ostream &os, hill const &h); + + /// Write a hill to an unformatted stream + cvm::memory_stream &write_hill(cvm::memory_stream &os, hill const &h); + + template IST &read_hill_template_(IST &is); + + /// Read a new hill from a formatted stream + std::istream & read_hill(std::istream &is); + + /// Read a new hill from an unformatted stream + cvm::memory_stream & read_hill(cvm::memory_stream &is); + + /// \brief Add a new hill; if a .hills trajectory is written, + /// write it there; if there is more than one replica, communicate + /// it to the others + std::list::const_iterator add_hill(hill const &h); + + /// \brief Remove a previously saved hill (returns an iterator for + /// the next hill in the list) + std::list::const_iterator delete_hill(hill_iter &h); + + /// \brief Calculate the values of the hills, incrementing + /// bias_energy + virtual void calc_hills(hill_iter h_first, + hill_iter h_last, + cvm::real &energy, + std::vector const *values); + + /// \brief Calculate the forces acting on the i-th colvar, + /// incrementing colvar_forces[i]; must be called after calc_hills + /// each time the values of the colvars are changed + virtual void calc_hills_force(size_t const &i, + hill_iter h_first, + hill_iter h_last, + std::vector &forces, + std::vector const *values); + + + /// Height of new hills + cvm::real hill_weight; + + /// \brief Bin the hills on grids of energy and forces, and use them + /// to force the colvars (as opposed to deriving the hills analytically) + bool use_grids; + + /// \brief Rebin the hills upon restarting + bool rebin_grids; + + /// \brief Should the grids be expanded if necessary? + bool expand_grids; + + /// \brief How often the hills should be projected onto the grids + size_t grids_freq; + + /// Keep hills in the restart file (e.g. to accurately rebin later) + bool keep_hills; + + /// value of keepHills saved in the most recent restart file + bool restart_keep_hills; + + /// \brief Dump the free energy surface (.pmf file) every restartFrequency + bool dump_fes; + + /// \brief Dump the free energy surface (.pmf file) every restartFrequency + /// using only the hills from this replica (only applicable to more than one replica) + bool dump_replica_fes; + + /// \brief Dump the free energy surface files at different + /// time steps, appending the step number to each file + bool dump_fes_save; + + /// \brief Whether to use well-tempered metadynamics + bool well_tempered; + + /// \brief Biasing temperature in well-tempered metadynamics + cvm::real bias_temperature; + + /// Ensemble-biased metadynamics (EBmeta) flag + bool ebmeta; + + /// Target distribution for EBmeta + colvar_grid_scalar* target_dist; + + /// Number of equilibration steps for EBmeta + cvm::step_number ebmeta_equil_steps; + + + /// \brief Try to read the restart information by allocating new + /// grids before replacing the current ones (used e.g. in + /// multiple_replicas) + bool safely_read_restart; + + /// Hill energy, cached on a grid + colvar_grid_scalar *hills_energy; + + /// Hill forces, cached on a grid + colvar_grid_gradient *hills_energy_gradients; + + /// \brief Project the selected hills onto grids + void project_hills(hill_iter h_first, hill_iter h_last, + colvar_grid_scalar *ge, colvar_grid_gradient *gf, + bool print_progress = false); + + + // Multiple Replicas variables and functions + + /// \brief Identifier for this replica + std::string replica_id; + + /// \brief File containing the paths to the output files from this replica + std::string replica_file_name; + + /// \brief Read the existing replicas on registry + virtual int update_replicas_registry(); + + /// \brief Read new data from replicas' files + virtual int read_replica_files(); + + /// Write full state information to be read by other replicas + virtual int write_replica_state_file(); + + /// Call this after write_replica_state_file() + virtual int reopen_replica_buffer_file(); + + /// \brief Additional, "mirror" metadynamics biases, to collect info + /// from the other replicas + /// + /// These are supposed to be synchronized by reading data from the + /// other replicas, and not be modified by the "local" replica + std::vector replicas; + + /// \brief Frequency at which data the "mirror" biases are updated + size_t replica_update_freq; + + /// List of replicas (and their output list files): contents are + /// copied into replicas_registry for convenience + std::string replicas_registry_file; + /// List of replicas (and their output list files) + std::string replicas_registry; + /// List of files written by this replica + std::string replica_list_file; + + /// Hills energy and gradients written specifically for other + /// replica (in addition to its own restart file) + std::string replica_state_file; + /// Whether a mirror bias has read the latest version of its state file + bool replica_state_file_in_sync; + + /// If there was a failure reading one of the files (because they + /// are not complete), this counter is incremented + size_t update_status; + + /// Explicit hills communicated between replicas + /// + /// This file becomes empty after replica_state_file is rewritten + std::string replica_hills_file; + + /// Position within replica_hills_file (when reading it) + std::streampos replica_hills_file_pos; + + /// Cache of the hills trajectory + std::ostringstream hills_traj_os_buf; +}; + + + + +/// \brief A hill for the metadynamics bias +class colvarbias_meta::hill { + +protected: + + /// Time step at which this hill was added + cvm::step_number it; + + /// Value of the hill function (ranges between 0 and 1) + cvm::real hill_value; + + /// Scale factor, which could be modified at runtime (default: 1) + cvm::real sW; + + /// Maximum height in energy of the hill + cvm::real W; + + /// Centers of the hill in the collective variable space + std::vector centers; + + /// Half-widths of the hill in the collective variable space + std::vector sigmas; + + /// Identity of the replica who added this hill + std::string replica; + +public: + + friend class colvarbias_meta; + + /// Constructor of a hill object + /// \param it Step number at which the hill was added + /// \param W Weight of the hill (energy units) + /// \param cv_values Array of collective variable values + /// \param cv_sigmas Array of collective variable values + /// \param replica ID of the replica that creates the hill (optional) + hill(cvm::step_number it, cvm::real W, + std::vector const &cv_values, + std::vector const &cv_sigmas, + std::string const &replica = ""); + + /// Copy constructor + hill(colvarbias_meta::hill const &h); + + /// Destructor + ~hill(); + + /// Assignment operator + hill & operator = (colvarbias_meta::hill const &h); + + /// Get the energy + inline cvm::real energy() + { + return W * sW * hill_value; + } + + /// Get the energy using another hill weight + inline cvm::real energy(cvm::real const &new_weight) + { + return new_weight * sW * hill_value; + } + + /// Get the current hill value + inline cvm::real const &value() + { + return hill_value; + } + + /// Set the hill value as specified + inline void value(cvm::real const &new_value) + { + hill_value = new_value; + } + + /// Get the weight + inline cvm::real weight() + { + return W * sW; + } + + /// Scale the weight with this factor (by default 1.0 is used) + inline void scale(cvm::real const &new_scale_fac) + { + sW = new_scale_fac; + } + + /// Get the center of the hill + inline std::vector & center() + { + return centers; + } + + /// Get the i-th component of the center + inline colvarvalue & center(size_t const &i) + { + return centers[i]; + } + + /// Comparison operator + inline friend bool operator < (hill const &h1, hill const &h2) + { + if (h1.it < h2.it) return true; + else return false; + } + + /// Comparison operator + inline friend bool operator <= (hill const &h1, hill const &h2) + { + if (h1.it <= h2.it) return true; + else return false; + } + + /// Comparison operator + inline friend bool operator > (hill const &h1, hill const &h2) + { + if (h1.it > h2.it) return true; + else return false; + } + + /// Comparison operator + inline friend bool operator >= (hill const &h1, hill const &h2) + { + if (h1.it >= h2.it) return true; + else return false; + } + + /// Comparison operator + inline friend bool operator == (hill const &h1, hill const &h2) + { + if ( (h1.it >= h2.it) && (h1.replica == h2.replica) ) return true; + else return false; + } + + /// Represent the hill ina string suitable for a trajectory file + std::string output_traj(); + +}; + + +#endif diff --git a/src/external/colvars/colvarbias_restraint.cpp b/src/external/colvars/colvarbias_restraint.cpp new file mode 100644 index 00000000000..56200198d5f --- /dev/null +++ b/src/external/colvars/colvarbias_restraint.cpp @@ -0,0 +1,1561 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarvalue.h" +#include "colvarbias_restraint.h" + + + +colvarbias_restraint::colvarbias_restraint(char const *key) + : colvarbias(key), colvarbias_ti(key) +{ + state_keyword = "restraint"; +} + + +int colvarbias_restraint::init(std::string const &conf) +{ + colvarbias::init(conf); + enable(f_cvb_apply_force); + + colvarbias_ti::init(conf); + + if (cvm::debug()) + cvm::log("Initializing a new restraint bias.\n"); + + return COLVARS_OK; +} + + +int colvarbias_restraint::update() +{ + // Update base class (bias_energy and colvar_forces are zeroed there) + colvarbias::update(); + + // Force and energy calculation + for (size_t i = 0; i < num_variables(); i++) { + bias_energy += restraint_potential(i); + colvar_forces[i].type(variables(i)->value()); + colvar_forces[i].is_derivative(); + colvar_forces[i] = restraint_force(i); + } + + if (cvm::debug()) + cvm::log("Done updating the restraint bias \""+this->name+"\".\n"); + + if (cvm::debug()) + cvm::log("Current forces for the restraint bias \""+ + this->name+"\": "+cvm::to_str(colvar_forces)+".\n"); + + return COLVARS_OK; +} + + +colvarbias_restraint::~colvarbias_restraint() +{ +} + + +std::string const colvarbias_restraint::get_state_params() const +{ + return colvarbias::get_state_params(); +} + + +int colvarbias_restraint::set_state_params(std::string const &conf) +{ + return colvarbias::set_state_params(conf); +} + + +std::ostream & colvarbias_restraint::write_traj_label(std::ostream &os) +{ + return colvarbias::write_traj_label(os); +} + + +std::ostream & colvarbias_restraint::write_traj(std::ostream &os) +{ + return colvarbias::write_traj(os); +} + + + +colvarbias_restraint_centers::colvarbias_restraint_centers(char const *key) + : colvarbias(key), colvarbias_ti(key), colvarbias_restraint(key) +{ +} + + +int colvarbias_restraint_centers::init(std::string const &conf) +{ + size_t i; + + bool null_centers = (colvar_centers.size() == 0); + if (null_centers) { + // try to initialize the restraint centers for the first time + colvar_centers.resize(num_variables()); + for (i = 0; i < num_variables(); i++) { + colvar_centers[i].type(variables(i)->value()); + colvar_centers[i].reset(); + } + } + + if (get_keyval(conf, "centers", colvar_centers, colvar_centers)) { + for (i = 0; i < num_variables(); i++) { + if (cvm::debug()) { + cvm::log("colvarbias_restraint: parsing initial centers, i = "+cvm::to_str(i)+".\n"); + } + colvar_centers[i].apply_constraints(); + } + null_centers = false; + } + + if (null_centers) { + colvar_centers.clear(); + cvm::error("Error: must define the initial centers of the restraints.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + if (colvar_centers.size() != num_variables()) { + cvm::error("Error: number of centers does not match " + "that of collective variables.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_centers::change_configuration(std::string const &conf) +{ + if (get_keyval(conf, "centers", colvar_centers, colvar_centers)) { + for (size_t i = 0; i < num_variables(); i++) { + colvar_centers[i].type(variables(i)->value()); + colvar_centers[i].apply_constraints(); + } + } + return COLVARS_OK; +} + + + +colvarbias_restraint_k::colvarbias_restraint_k(char const *key) + : colvarbias(key), colvarbias_ti(key), colvarbias_restraint(key) +{ + force_k = -1.0; + check_positive_k = true; +} + + +int colvarbias_restraint_k::init(std::string const &conf) +{ + get_keyval(conf, "forceConstant", force_k, (force_k > 0.0 ? force_k : 1.0)); + if (check_positive_k && (force_k < 0.0)) { + cvm::error("Error: undefined or invalid force constant.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + return COLVARS_OK; +} + + +int colvarbias_restraint_k::change_configuration(std::string const &conf) +{ + get_keyval(conf, "forceConstant", force_k, force_k); + return COLVARS_OK; +} + + + +colvarbias_restraint_moving::colvarbias_restraint_moving(char const * /* key */) +{ + target_nstages = 0; + target_nsteps = 0L; + stage = 0; + acc_work = 0.0; + b_chg_centers = false; + b_chg_force_k = false; +} + + +int colvarbias_restraint_moving::init(std::string const &conf) +{ + if (b_chg_centers && b_chg_force_k) { + cvm::error("Error: cannot specify both targetCenters and targetForceConstant.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + if (b_chg_centers || b_chg_force_k) { + + first_step = cvm::step_absolute(); + + cvm::log("Initial step for restraint change: " + cvm::to_str(first_step) + "\n"); + + get_keyval(conf, "targetNumSteps", target_nsteps, target_nsteps); + if (!target_nsteps) { + cvm::error("Error: targetNumSteps must be non-zero.\n", COLVARS_INPUT_ERROR); + return cvm::get_error(); + } + + if (get_keyval(conf, "targetNumStages", target_nstages, target_nstages) && + lambda_schedule.size()) { + cvm::error("Error: targetNumStages and lambdaSchedule are incompatible.\n", COLVARS_INPUT_ERROR); + return cvm::get_error(); + } + + get_keyval_feature(this, conf, "outputAccumulatedWork", + f_cvb_output_acc_work, + is_enabled(f_cvb_output_acc_work)); + if (is_enabled(f_cvb_output_acc_work) && (target_nstages > 0)) { + return cvm::error("Error: outputAccumulatedWork and targetNumStages " + "are incompatible.\n", COLVARS_INPUT_ERROR); + } + } + + return COLVARS_OK; +} + + +std::string const colvarbias_restraint_moving::get_state_params() const +{ + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + if (b_chg_centers || b_chg_force_k) { + os << "firstStep " << std::setw(cvm::it_width) << first_step << "\n"; + if (target_nstages) { + os << "stage " << std::setw(cvm::it_width) << stage << "\n"; + } + } + return os.str(); +} + + +int colvarbias_restraint_moving::set_state_params(std::string const &conf) +{ + if (b_chg_centers || b_chg_force_k) { + auto first_step_flags = colvarparse::parse_restart; + if (cvm::main()->restart_version_number() > 20230906) { + // Only require the first step when the code could produce it + first_step_flags = colvarparse::parse_restart | colvarparse::parse_required; + } + get_keyval(conf, "firstStep", first_step, first_step, first_step_flags); + if (target_nstages) { + get_keyval(conf, "stage", stage, stage, + colvarparse::parse_restart | colvarparse::parse_required); + } + } + return COLVARS_OK; +} + + + +colvarbias_restraint_centers_moving::colvarbias_restraint_centers_moving(char const *key) + : colvarbias(key), + colvarbias_ti(key), + colvarbias_restraint(key), + colvarbias_restraint_centers(key), + colvarbias_restraint_moving(key) +{ + b_chg_centers = false; + b_output_centers = false; +} + + +int colvarbias_restraint_centers_moving::init(std::string const &conf) +{ + colvarbias_restraint_centers::init(conf); + + if (cvm::debug()) { + cvm::log("colvarbias_restraint: parsing target centers.\n"); + } + + size_t i; + if (get_keyval(conf, "targetCenters", target_centers, colvar_centers)) { + if (target_centers.size() != num_variables()) { + cvm::error("Error: number of target centers does not match " + "that of collective variables.\n", COLVARS_INPUT_ERROR); + } + b_chg_centers = true; + for (i = 0; i < target_centers.size(); i++) { + target_centers[i].apply_constraints(); + centers_incr.push_back(colvar_centers[i]); + centers_incr[i].reset(); + } + } + + if (b_chg_centers) { + // parse moving schedule options + colvarbias_restraint_moving::init(conf); + if (initial_centers.size() == 0) { + // One-time init + initial_centers = colvar_centers; + } + // Call to check that the definition is correct + for (i = 0; i < num_variables(); i++) { + colvarvalue const midpoint = + colvarvalue::interpolate(initial_centers[i], + target_centers[i], + 0.5); + } + + } else { + target_centers.clear(); + } + + // Output restraint centers even when they do not change; some NAMD REUS + // scripts expect this behavior + get_keyval(conf, "outputCenters", b_output_centers, b_output_centers); + + return COLVARS_OK; +} + + +int colvarbias_restraint_centers_moving::update_centers(cvm::real lambda) +{ + if (cvm::debug()) { + cvm::log("Updating centers for the restraint bias \""+ + this->name+"\": "+cvm::to_str(colvar_centers)+".\n"); + } + size_t i; + for (i = 0; i < num_variables(); i++) { + colvarvalue const c_new = colvarvalue::interpolate(initial_centers[i], + target_centers[i], + lambda); + centers_incr[i] = 0.5 * c_new.dist2_grad(colvar_centers[i]); + colvar_centers[i] = c_new; + variables(i)->wrap(colvar_centers[i]); + } + if (cvm::debug()) { + cvm::log("New centers for the restraint bias \""+ + this->name+"\": "+cvm::to_str(colvar_centers)+".\n"); + } + return cvm::get_error(); +} + + +int colvarbias_restraint_centers_moving::update() +{ + if (!cvm::main()->proxy->simulation_running()) { + return COLVARS_OK; + } + + if (b_chg_centers) { + + if (target_nstages) { + // Staged update + if (stage <= target_nstages) { + if ((cvm::step_relative() > 0) && + (((cvm::step_absolute() - first_step) % target_nsteps) == 1)) { + cvm::real const lambda = + cvm::real(stage)/cvm::real(target_nstages); + update_centers(lambda); + stage++; + cvm::log("Moving restraint \"" + this->name + + "\" stage " + cvm::to_str(stage) + + " : setting centers to " + cvm::to_str(colvar_centers) + + " at step " + cvm::to_str(cvm::step_absolute())); + } else { + for (size_t i = 0; i < num_variables(); i++) { + centers_incr[i].reset(); + } + } + } + } else { + // Continuous update + if (cvm::step_absolute() - first_step <= target_nsteps) { + cvm::real const lambda = + cvm::real(cvm::step_absolute() - first_step)/cvm::real(target_nsteps); + update_centers(lambda); + } else { + for (size_t i = 0; i < num_variables(); i++) { + centers_incr[i].reset(); + } + } + } + + if (cvm::step_relative() == 0) { + for (size_t i = 0; i < num_variables(); i++) { + // finite differences are undefined when restarting + centers_incr[i].reset(); + } + } + + if (cvm::debug()) { + cvm::log("Center increment for the restraint bias \""+ + this->name+"\": "+cvm::to_str(centers_incr)+ + " at stage "+cvm::to_str(stage)+ ".\n"); + } + } + + return cvm::get_error(); +} + + +int colvarbias_restraint_centers_moving::update_acc_work() +{ + if (!cvm::main()->proxy->simulation_running()) { + return COLVARS_OK; + } + if (b_chg_centers) { + if (is_enabled(f_cvb_output_acc_work)) { + if ((cvm::step_relative() > 0) && + (cvm::step_absolute() - first_step <= target_nsteps)) { + for (size_t i = 0; i < num_variables(); i++) { + // project forces on the calculated increments at this step + acc_work += colvar_forces[i] * centers_incr[i]; + } + } + } + } + return COLVARS_OK; +} + + +std::string const colvarbias_restraint_centers_moving::get_state_params() const +{ + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + + if (b_chg_centers) { + size_t i; + os << "centers "; + for (i = 0; i < num_variables(); i++) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << colvar_centers[i]; + } + os << "\n"; + + if (is_enabled(f_cvb_output_acc_work)) { + os << "accumulatedWork " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << acc_work << "\n"; + } + } + + return os.str(); +} + + +int colvarbias_restraint_centers_moving::set_state_params(std::string const &conf) +{ + colvarbias_restraint::set_state_params(conf); + + if (b_chg_centers) { + get_keyval(conf, "centers", colvar_centers, colvar_centers, + colvarparse::parse_restart | colvarparse::parse_required); + } + + if (is_enabled(f_cvb_output_acc_work)) { + get_keyval(conf, "accumulatedWork", acc_work, acc_work, + colvarparse::parse_restart | colvarparse::parse_required); + } + + return COLVARS_OK; +} + + +std::ostream & colvarbias_restraint_centers_moving::write_traj_label(std::ostream &os) +{ + if (b_output_centers) { + for (size_t i = 0; i < num_variables(); i++) { + size_t const this_cv_width = (variables(i)->value()).output_width(cvm::cv_width); + os << " x0_" + << cvm::wrap_string(variables(i)->name, this_cv_width-3); + } + } + + if (b_chg_centers && is_enabled(f_cvb_output_acc_work)) { + os << " W_" + << cvm::wrap_string(this->name, cvm::en_width-2); + } + + return os; +} + + +std::ostream & colvarbias_restraint_centers_moving::write_traj(std::ostream &os) +{ + if (b_output_centers) { + for (size_t i = 0; i < num_variables(); i++) { + os << " " + << std::setprecision(cvm::cv_prec) << std::setw(cvm::cv_width) + << colvar_centers[i]; + } + } + + if (b_chg_centers && is_enabled(f_cvb_output_acc_work)) { + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << acc_work; + } + + return os; +} + + + +colvarbias_restraint_k_moving::colvarbias_restraint_k_moving(char const *key) + : colvarbias(key), + colvarbias_ti(key), + colvarbias_restraint(key), + colvarbias_restraint_k(key), + colvarbias_restraint_moving(key) +{ + b_chg_force_k = false; + b_decoupling = false; + target_equil_steps = 0; + target_force_k = -1.0; + starting_force_k = -1.0; + lambda_exp = 1.0; + restraint_FE = 0.0; + force_k_incr = 0.0; +} + + +int colvarbias_restraint_k_moving::init(std::string const &conf) +{ + colvarbias_restraint_k::init(conf); + + get_keyval(conf, "decoupling", b_decoupling, b_decoupling); + if (b_decoupling) { + starting_force_k = 0.0; + target_force_k = force_k; + b_chg_force_k = true; + } + + if (get_keyval(conf, "targetForceConstant", target_force_k, target_force_k)) { + if (b_decoupling) { + cvm::error("Error: targetForceConstant may not be specified together with decoupling.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + starting_force_k = force_k; + b_chg_force_k = true; + } + + if (!b_chg_force_k) { + return COLVARS_OK; + } + + // parse moving restraint options + colvarbias_restraint_moving::init(conf); + + get_keyval(conf, "targetEquilSteps", target_equil_steps, target_equil_steps); + + if (get_keyval(conf, "lambdaSchedule", lambda_schedule, lambda_schedule) && + target_nstages > 0) { + cvm::error("Error: targetNumStages and lambdaSchedule are incompatible.\n", COLVARS_INPUT_ERROR); + return cvm::get_error(); + } + + if (lambda_schedule.size()) { + // There is one more lambda-point than stages + target_nstages = lambda_schedule.size() - 1; + } + + if ((get_keyval(conf, "targetForceExponent", lambda_exp, lambda_exp, parse_deprecated) + || get_keyval(conf, "lambdaExponent", lambda_exp, lambda_exp)) + && !b_chg_force_k) { + cvm::error("Error: cannot set lambdaExponent unless a changing force constant is active.\n", COLVARS_INPUT_ERROR); + } + if (lambda_exp < 1.0) { + cvm::log("Warning: for all practical purposes, lambdaExponent should be 1.0 or greater.\n"); + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_k_moving::update() +{ + if (!cvm::main()->proxy->simulation_running()) { + return COLVARS_OK; + } + if (b_chg_force_k) { + + cvm::real lambda; + + if (target_nstages) { + + if (cvm::step_absolute() == first_step) { + // Setup first stage of staged variable force constant calculation + if (lambda_schedule.size()) { + lambda = lambda_schedule[0]; + } else { + lambda = (b_decoupling ? 1.0 : 0.0); + } + force_k = starting_force_k + (target_force_k - starting_force_k) + * cvm::pow(lambda, lambda_exp); + cvm::log("Restraint " + this->name + ", stage " + cvm::to_str(stage) + + " : lambda = " + cvm::to_str(lambda) + + ", k = " + cvm::to_str(force_k)+"\n"); + } + + // TI calculation: estimate free energy derivative + // need current lambda + if (lambda_schedule.size()) { + lambda = lambda_schedule[stage]; + } else { + lambda = cvm::real(stage) / cvm::real(target_nstages); + if (b_decoupling) lambda = 1.0 - lambda; + } + + if (target_equil_steps == 0 || (cvm::step_absolute() - first_step) % target_nsteps >= target_equil_steps) { + // Start averaging after equilibration period, if requested + + // Derivative of energy with respect to force_k + cvm::real dU_dk = 0.0; + for (size_t i = 0; i < num_variables(); i++) { + dU_dk += d_restraint_potential_dk(i); + } + restraint_FE += lambda_exp * cvm::pow(lambda, lambda_exp - 1.0) + * (target_force_k - starting_force_k) * dU_dk; + } + + // Finish current stage... + if ((cvm::step_absolute() - first_step) % target_nsteps == 0 && + cvm::step_absolute() > first_step) { + + cvm::log("Restraint " + this->name + " Lambda= " + + cvm::to_str(lambda) + " dA/dLambda= " + + cvm::to_str(restraint_FE / cvm::real(target_nsteps - target_equil_steps))+"\n"); + + // ...and move on to the next one + if (stage < target_nstages) { + + restraint_FE = 0.0; + stage++; + if (lambda_schedule.size()) { + lambda = lambda_schedule[stage]; + } else { + lambda = cvm::real(stage) / cvm::real(target_nstages); + if (b_decoupling) lambda = 1.0 - lambda; + } + force_k = starting_force_k + (target_force_k - starting_force_k) + * cvm::pow(lambda, lambda_exp); + cvm::log("Restraint " + this->name + ", stage " + cvm::to_str(stage) + + " : lambda = " + cvm::to_str(lambda) + + ", k = " + cvm::to_str(force_k)+"\n"); + } + } + + } else if (cvm::step_absolute() - first_step <= target_nsteps) { + + // update force constant (slow growth) + lambda = cvm::real(cvm::step_absolute() - first_step) / cvm::real(target_nsteps); + if (b_decoupling) lambda = 1.0 - lambda; + cvm::real const force_k_old = force_k; + force_k = starting_force_k + (target_force_k - starting_force_k) + * cvm::pow(lambda, lambda_exp); + force_k_incr = force_k - force_k_old; + } + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_k_moving::update_acc_work() +{ + if (!cvm::main()->proxy->simulation_running()) { + return COLVARS_OK; + } + if (b_chg_force_k) { + if (is_enabled(f_cvb_output_acc_work)) { + if (cvm::step_relative() > 0) { + cvm::real dU_dk = 0.0; + for (size_t i = 0; i < num_variables(); i++) { + dU_dk += d_restraint_potential_dk(i); + } + acc_work += dU_dk * force_k_incr; + } + } + } + return COLVARS_OK; +} + + +std::string const colvarbias_restraint_k_moving::get_state_params() const +{ + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + if (b_chg_force_k) { + os << "forceConstant " + << std::setprecision(cvm::en_prec) + << std::setw(cvm::en_width) << force_k << "\n"; + + if (is_enabled(f_cvb_output_acc_work)) { + os << "accumulatedWork " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << acc_work << "\n"; + } + } + return os.str(); +} + + +int colvarbias_restraint_k_moving::set_state_params(std::string const &conf) +{ + colvarbias_restraint::set_state_params(conf); + + if (b_chg_force_k) { + get_keyval(conf, "forceConstant", force_k, force_k, + colvarparse::parse_restart | colvarparse::parse_required); + } + + if (is_enabled(f_cvb_output_acc_work)) { + get_keyval(conf, "accumulatedWork", acc_work, acc_work, + colvarparse::parse_restart | colvarparse::parse_required); + } + + return COLVARS_OK; +} + + +std::ostream & colvarbias_restraint_k_moving::write_traj_label(std::ostream &os) +{ + if (b_chg_force_k && is_enabled(f_cvb_output_acc_work)) { + os << " W_" + << cvm::wrap_string(this->name, cvm::en_width-2); + } + return os; +} + + +std::ostream & colvarbias_restraint_k_moving::write_traj(std::ostream &os) +{ + if (b_chg_force_k && is_enabled(f_cvb_output_acc_work)) { + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << acc_work; + } + return os; +} + + + +colvarbias_restraint_harmonic::colvarbias_restraint_harmonic(char const *key) + : colvarbias(key), + colvarbias_ti(key), + colvarbias_restraint(key), + colvarbias_restraint_centers(key), + colvarbias_restraint_moving(key), + colvarbias_restraint_k(key), + colvarbias_restraint_centers_moving(key), + colvarbias_restraint_k_moving(key) +{ +} + + +int colvarbias_restraint_harmonic::init(std::string const &conf) +{ + colvarbias_restraint::init(conf); + colvarbias_restraint_moving::init(conf); + colvarbias_restraint_centers_moving::init(conf); + colvarbias_restraint_k_moving::init(conf); + + cvm::main()->cite_feature("Harmonic colvar bias implementation"); + + for (size_t i = 0; i < num_variables(); i++) { + cvm::real const w = variables(i)->width; + cvm::log("The force constant for colvar \""+variables(i)->name+ + "\" will be rescaled to "+ + cvm::to_str(force_k/(w*w))+ + " according to the specified width ("+cvm::to_str(w)+").\n"); + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_harmonic::update() +{ + int error_code = COLVARS_OK; + + // update the TI estimator (if defined) + error_code |= colvarbias_ti::update(); + + // update parameters (centers or force constant) + error_code |= colvarbias_restraint_centers_moving::update(); + error_code |= colvarbias_restraint_k_moving::update(); + + // update restraint energy and forces + error_code |= colvarbias_restraint::update(); + + // update accumulated work using the current forces + error_code |= colvarbias_restraint_centers_moving::update_acc_work(); + error_code |= colvarbias_restraint_k_moving::update_acc_work(); + + return error_code; +} + + +cvm::real colvarbias_restraint_harmonic::restraint_potential(size_t i) const +{ + return 0.5 * force_k / (variables(i)->width * variables(i)->width) * + variables(i)->dist2(variables(i)->value(), colvar_centers[i]); +} + + +colvarvalue const colvarbias_restraint_harmonic::restraint_force(size_t i) const +{ + return -0.5 * force_k / (variables(i)->width * variables(i)->width) * + variables(i)->dist2_lgrad(variables(i)->value(), colvar_centers[i]); +} + + +cvm::real colvarbias_restraint_harmonic::d_restraint_potential_dk(size_t i) const +{ + return 0.5 / (variables(i)->width * variables(i)->width) * + variables(i)->dist2(variables(i)->value(), colvar_centers[i]); +} + + +std::string const colvarbias_restraint_harmonic::get_state_params() const +{ + return colvarbias_restraint::get_state_params() + + colvarbias_restraint_moving::get_state_params() + + colvarbias_restraint_centers_moving::get_state_params() + + colvarbias_restraint_k_moving::get_state_params(); +} + + +int colvarbias_restraint_harmonic::set_state_params(std::string const &conf) +{ + int error_code = COLVARS_OK; + error_code |= colvarbias_restraint::set_state_params(conf); + error_code |= colvarbias_restraint_moving::set_state_params(conf); + error_code |= colvarbias_restraint_centers_moving::set_state_params(conf); + error_code |= colvarbias_restraint_k_moving::set_state_params(conf); + return error_code; +} + + +std::ostream & colvarbias_restraint_harmonic::write_traj_label(std::ostream &os) +{ + colvarbias_restraint::write_traj_label(os); + colvarbias_restraint_centers_moving::write_traj_label(os); + colvarbias_restraint_k_moving::write_traj_label(os); + return os; +} + + +std::ostream & colvarbias_restraint_harmonic::write_traj(std::ostream &os) +{ + colvarbias_restraint::write_traj(os); + colvarbias_restraint_centers_moving::write_traj(os); + colvarbias_restraint_k_moving::write_traj(os); + return os; +} + + +int colvarbias_restraint_harmonic::change_configuration(std::string const &conf) +{ + return colvarbias_restraint_centers::change_configuration(conf) | + colvarbias_restraint_k::change_configuration(conf); +} + + +cvm::real colvarbias_restraint_harmonic::energy_difference(std::string const &conf) +{ + cvm::real const old_bias_energy = bias_energy; + cvm::real const old_force_k = force_k; + std::vector const old_centers = colvar_centers; + + change_configuration(conf); + update(); + + cvm::real const result = (bias_energy - old_bias_energy); + + bias_energy = old_bias_energy; + force_k = old_force_k; + colvar_centers = old_centers; + + return result; +} + + + +colvarbias_restraint_harmonic_walls::colvarbias_restraint_harmonic_walls(char const *key) + : colvarbias(key), + colvarbias_ti(key), + colvarbias_restraint(key), + colvarbias_restraint_k(key), + colvarbias_restraint_moving(key), + colvarbias_restraint_k_moving(key) +{ + lower_wall_k = -1.0; + upper_wall_k = -1.0; + // This bias implements the bias_actual_colvars feature (most others do not) + provide(f_cvb_bypass_ext_lagrangian); + set_enabled(f_cvb_bypass_ext_lagrangian); // Defaults to enabled +} + + +int colvarbias_restraint_harmonic_walls::init(std::string const &conf) +{ + colvarbias_restraint::init(conf); + colvarbias_restraint_moving::init(conf); + colvarbias_restraint_k_moving::init(conf); + + cvm::main()->cite_feature("harmonicWalls colvar bias implementation"); + + enable(f_cvb_scalar_variables); + + size_t i; + + bool b_null_lower_walls = false; + if (lower_walls.size() == 0) { + b_null_lower_walls = true; + lower_walls.resize(num_variables()); + for (i = 0; i < num_variables(); i++) { + lower_walls[i].type(variables(i)->value()); + lower_walls[i].reset(); + } + } + if (!get_keyval(conf, "lowerWalls", lower_walls, lower_walls) && + b_null_lower_walls) { + cvm::log("Lower walls were not provided.\n"); + lower_walls.clear(); + } + + bool b_null_upper_walls = false; + if (upper_walls.size() == 0) { + b_null_upper_walls = true; + upper_walls.resize(num_variables()); + for (i = 0; i < num_variables(); i++) { + upper_walls[i].type(variables(i)->value()); + upper_walls[i].reset(); + } + } + if (!get_keyval(conf, "upperWalls", upper_walls, upper_walls) && + b_null_upper_walls) { + cvm::log("Upper walls were not provided.\n"); + upper_walls.clear(); + } + + if ((lower_walls.size() == 0) && (upper_walls.size() == 0)) { + return cvm::error("Error: no walls provided.\n", COLVARS_INPUT_ERROR); + } + + if (lower_walls.size() > 0) { + get_keyval(conf, "lowerWallConstant", lower_wall_k, + (lower_wall_k > 0.0) ? lower_wall_k : force_k); + } + if (upper_walls.size() > 0) { + get_keyval(conf, "upperWallConstant", upper_wall_k, + (upper_wall_k > 0.0) ? upper_wall_k : force_k); + } + + if ((lower_walls.size() == 0) || (upper_walls.size() == 0)) { + for (i = 0; i < num_variables(); i++) { + if (variables(i)->is_enabled(f_cv_periodic)) { + return cvm::error("Error: at least one variable is periodic, " + "both walls must be provided.\n", COLVARS_INPUT_ERROR); + } + } + } + + if ((lower_walls.size() > 0) && (upper_walls.size() > 0)) { + for (i = 0; i < num_variables(); i++) { + if (lower_walls[i] >= upper_walls[i]) { + return cvm::error("Error: one upper wall, "+ + cvm::to_str(upper_walls[i])+ + ", is not higher than the lower wall, "+ + cvm::to_str(lower_walls[i])+".\n", + COLVARS_INPUT_ERROR); + } + if (variables(i)->dist2(lower_walls[i], upper_walls[i]) < 1.0e-12) { + return cvm::error("Error: lower wall and upper wall are equal " + "in the domain of the variable \""+ + variables(i)->name+"\".\n", COLVARS_INPUT_ERROR); + } + } + if (lower_wall_k * upper_wall_k == 0.0) { + cvm::error("Error: lowerWallConstant and upperWallConstant, " + "when defined, must both be positive.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + force_k = cvm::sqrt(lower_wall_k * upper_wall_k); + // transform the two constants to relative values using gemetric mean as ref + // to preserve force_k if provided as single parameter + // (allow changing both via force_k) + lower_wall_k /= force_k; + upper_wall_k /= force_k; + } else { + // If only one wall is defined, need to rescale as well + if (lower_walls.size() > 0) { + force_k = lower_wall_k; + lower_wall_k = 1.0; + } + if (upper_walls.size() > 0) { + force_k = upper_wall_k; + upper_wall_k = 1.0; + } + } + + // Initialize starting value of the force constant (in case it's changing) + starting_force_k = (b_decoupling ? 0.0 : force_k); + + if (lower_walls.size() > 0) { + for (i = 0; i < num_variables(); i++) { + cvm::real const w = variables(i)->width; + cvm::log("The lower wall force constant for colvar \""+ + variables(i)->name+"\" will be rescaled to "+ + cvm::to_str(lower_wall_k * force_k / (w*w))+ + " according to the specified width ("+cvm::to_str(w)+").\n"); + } + } + + if (upper_walls.size() > 0) { + for (i = 0; i < num_variables(); i++) { + cvm::real const w = variables(i)->width; + cvm::log("The upper wall force constant for colvar \""+ + variables(i)->name+"\" will be rescaled to "+ + cvm::to_str(upper_wall_k * force_k / (w*w))+ + " according to the specified width ("+cvm::to_str(w)+").\n"); + } + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_harmonic_walls::update() +{ + int error_code = COLVARS_OK; + + error_code |= colvarbias_ti::update(); + + error_code |= colvarbias_restraint_k_moving::update(); + + error_code |= colvarbias_restraint::update(); + + error_code |= colvarbias_restraint_k_moving::update_acc_work(); + + return error_code; +} + + +cvm::real colvarbias_restraint_harmonic_walls::colvar_distance(size_t i) const +{ + colvar *cv = variables(i); + + colvarvalue const &cvv = is_enabled(f_cvb_bypass_ext_lagrangian) ? + variables(i)->actual_value() : + variables(i)->value(); + + // For a periodic colvar, both walls may be applicable at the same time + // in which case we pick the closer one + + if (cv->is_enabled(f_cv_periodic)) { + cvm::real const lower_wall_dist2 = cv->dist2(cvv, lower_walls[i]); + cvm::real const upper_wall_dist2 = cv->dist2(cvv, upper_walls[i]); + if (lower_wall_dist2 < upper_wall_dist2) { + cvm::real const grad = cv->dist2_lgrad(cvv, lower_walls[i]); + if (grad < 0.0) { return 0.5 * grad; } + } else { + cvm::real const grad = cv->dist2_lgrad(cvv, upper_walls[i]); + if (grad > 0.0) { return 0.5 * grad; } + } + return 0.0; + } + + if (lower_walls.size() > 0) { + cvm::real const grad = cv->dist2_lgrad(cvv, lower_walls[i]); + if (grad < 0.0) { return 0.5 * grad; } + } + if (upper_walls.size() > 0) { + cvm::real const grad = cv->dist2_lgrad(cvv, upper_walls[i]); + if (grad > 0.0) { return 0.5 * grad; } + } + return 0.0; +} + + +cvm::real colvarbias_restraint_harmonic_walls::restraint_potential(size_t i) const +{ + cvm::real const dist = colvar_distance(i); + cvm::real const scale = dist > 0.0 ? upper_wall_k : lower_wall_k; + return 0.5 * force_k * scale / (variables(i)->width * variables(i)->width) * + dist * dist; +} + + +colvarvalue const colvarbias_restraint_harmonic_walls::restraint_force(size_t i) const +{ + cvm::real const dist = colvar_distance(i); + cvm::real const scale = dist > 0.0 ? upper_wall_k : lower_wall_k; + return - force_k * scale / (variables(i)->width * variables(i)->width) * dist; +} + + +cvm::real colvarbias_restraint_harmonic_walls::d_restraint_potential_dk(size_t i) const +{ + cvm::real const dist = colvar_distance(i); + cvm::real const scale = dist > 0.0 ? upper_wall_k : lower_wall_k; + return 0.5 * scale / (variables(i)->width * variables(i)->width) * + dist * dist; +} + + +std::string const colvarbias_restraint_harmonic_walls::get_state_params() const +{ + return colvarbias_restraint::get_state_params() + + colvarbias_restraint_moving::get_state_params() + + colvarbias_restraint_k_moving::get_state_params(); +} + + +int colvarbias_restraint_harmonic_walls::set_state_params(std::string const &conf) +{ + int error_code = COLVARS_OK; + error_code |= colvarbias_restraint::set_state_params(conf); + error_code |= colvarbias_restraint_moving::set_state_params(conf); + error_code |= colvarbias_restraint_k_moving::set_state_params(conf); + return error_code; +} + + +std::ostream & colvarbias_restraint_harmonic_walls::write_traj_label(std::ostream &os) +{ + colvarbias_restraint::write_traj_label(os); + colvarbias_restraint_k_moving::write_traj_label(os); + return os; +} + + +std::ostream & colvarbias_restraint_harmonic_walls::write_traj(std::ostream &os) +{ + colvarbias_restraint::write_traj(os); + colvarbias_restraint_k_moving::write_traj(os); + return os; +} + + + +colvarbias_restraint_linear::colvarbias_restraint_linear(char const *key) + : colvarbias(key), + colvarbias_ti(key), + colvarbias_restraint(key), + colvarbias_restraint_centers(key), + colvarbias_restraint_moving(key), + colvarbias_restraint_k(key), + colvarbias_restraint_centers_moving(key), + colvarbias_restraint_k_moving(key) +{ + check_positive_k = false; +} + + +int colvarbias_restraint_linear::init(std::string const &conf) +{ + colvarbias_restraint::init(conf); + colvarbias_restraint_moving::init(conf); + colvarbias_restraint_centers_moving::init(conf); + colvarbias_restraint_k_moving::init(conf); + + cvm::main()->cite_feature("harmonicWalls colvar bias implementation"); + + for (size_t i = 0; i < num_variables(); i++) { + if (variables(i)->is_enabled(f_cv_periodic)) { + cvm::error("Error: linear biases cannot be applied to periodic variables.\n", + COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + cvm::real const w = variables(i)->width; + cvm::log("The force constant for colvar \""+variables(i)->name+ + "\" will be rescaled to "+ + cvm::to_str(force_k / w)+ + " according to the specified width ("+cvm::to_str(w)+").\n"); + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_linear::update() +{ + int error_code = COLVARS_OK; + + // update the TI estimator (if defined) + error_code |= colvarbias_ti::update(); + + // update parameters (centers or force constant) + error_code |= colvarbias_restraint_centers_moving::update(); + error_code |= colvarbias_restraint_k_moving::update(); + + // update restraint energy and forces + error_code |= colvarbias_restraint::update(); + + // update accumulated work using the current forces + error_code |= colvarbias_restraint_centers_moving::update_acc_work(); + error_code |= colvarbias_restraint_k_moving::update_acc_work(); + + return error_code; +} + + +int colvarbias_restraint_linear::change_configuration(std::string const &conf) +{ + // Only makes sense to change the force constant + return colvarbias_restraint_k::change_configuration(conf); +} + + +cvm::real colvarbias_restraint_linear::energy_difference(std::string const &conf) +{ + cvm::real const old_bias_energy = bias_energy; + cvm::real const old_force_k = force_k; + + change_configuration(conf); + update(); + + cvm::real const result = (bias_energy - old_bias_energy); + + bias_energy = old_bias_energy; + force_k = old_force_k; + + return result; +} + + +cvm::real colvarbias_restraint_linear::restraint_potential(size_t i) const +{ + return force_k / variables(i)->width * (variables(i)->value() - + colvar_centers[i]).sum(); +} + + +colvarvalue const colvarbias_restraint_linear::restraint_force(size_t i) const +{ + colvarvalue dummy(variables(i)->value()); + dummy.set_ones(); + return -1.0 * force_k / variables(i)->width * dummy; +} + + +cvm::real colvarbias_restraint_linear::d_restraint_potential_dk(size_t i) const +{ + return 1.0 / variables(i)->width * (variables(i)->value() - + colvar_centers[i]).sum(); +} + + +std::string const colvarbias_restraint_linear::get_state_params() const +{ + return colvarbias_restraint::get_state_params() + + colvarbias_restraint_moving::get_state_params() + + colvarbias_restraint_centers_moving::get_state_params() + + colvarbias_restraint_k_moving::get_state_params(); +} + + +int colvarbias_restraint_linear::set_state_params(std::string const &conf) +{ + int error_code = COLVARS_OK; + error_code |= colvarbias_restraint::set_state_params(conf); + error_code |= colvarbias_restraint_moving::set_state_params(conf); + error_code |= colvarbias_restraint_centers_moving::set_state_params(conf); + error_code |= colvarbias_restraint_k_moving::set_state_params(conf); + return error_code; +} + + +std::ostream & colvarbias_restraint_linear::write_traj_label(std::ostream &os) +{ + colvarbias_restraint::write_traj_label(os); + colvarbias_restraint_centers_moving::write_traj_label(os); + colvarbias_restraint_k_moving::write_traj_label(os); + return os; +} + + +std::ostream & colvarbias_restraint_linear::write_traj(std::ostream &os) +{ + colvarbias_restraint::write_traj(os); + colvarbias_restraint_centers_moving::write_traj(os); + colvarbias_restraint_k_moving::write_traj(os); + return os; +} + + + +colvarbias_restraint_histogram::colvarbias_restraint_histogram(char const *key) + : colvarbias(key) +{ + lower_boundary = 0.0; + upper_boundary = 0.0; + width = 0.0; + gaussian_width = 0.0; +} + + +int colvarbias_restraint_histogram::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + + colvarbias::init(conf); + enable(f_cvb_apply_force); + + cvm::main()->cite_feature("histogramRestraint colvar bias implementation"); + + get_keyval(conf, "lowerBoundary", lower_boundary, lower_boundary); + get_keyval(conf, "upperBoundary", upper_boundary, upper_boundary); + get_keyval(conf, "width", width, width); + + if (width <= 0.0) { + error_code |= cvm::error("Error: \"width\" must be positive.\n", + COLVARS_INPUT_ERROR); + } + + get_keyval(conf, "gaussianWidth", gaussian_width, 2.0 * width, colvarparse::parse_silent); + get_keyval(conf, "gaussianSigma", gaussian_width, 2.0 * width); + + if (lower_boundary >= upper_boundary) { + error_code |= cvm::error("Error: the upper boundary, "+ + cvm::to_str(upper_boundary)+ + ", is not higher than the lower boundary, "+ + cvm::to_str(lower_boundary)+".\n", + COLVARS_INPUT_ERROR); + } + + cvm::real const nbins = (upper_boundary - lower_boundary) / width; + int const nbins_round = (int)(nbins); + + if (cvm::fabs(nbins - cvm::real(nbins_round)) > 1.0E-10) { + cvm::log("Warning: grid interval ("+ + cvm::to_str(lower_boundary, cvm::cv_width, cvm::cv_prec)+" - "+ + cvm::to_str(upper_boundary, cvm::cv_width, cvm::cv_prec)+ + ") is not commensurate to its bin width ("+ + cvm::to_str(width, cvm::cv_width, cvm::cv_prec)+").\n"); + } + + p.resize(nbins_round); + ref_p.resize(nbins_round); + p_diff.resize(nbins_round); + + bool const inline_ref_p = + get_keyval(conf, "refHistogram", ref_p.data_array(), ref_p.data_array()); + std::string ref_p_file; + get_keyval(conf, "refHistogramFile", ref_p_file, std::string("")); + if (ref_p_file.size()) { + if (inline_ref_p) { + error_code |= cvm::error("Error: cannot specify both refHistogram and refHistogramFile at the same time.\n", + COLVARS_INPUT_ERROR); + } else { + + std::istream &is = + cvm::main()->proxy->input_stream(ref_p_file, + "reference histogram file"); + + std::string data_s = ""; + std::string line; + while (getline_nocomments(is, line)) { + data_s.append(line+"\n"); + } + if (data_s.size() == 0) { + error_code |= cvm::error("Error: file \""+ref_p_file+ + "\" empty or unreadable.\n", + COLVARS_FILE_ERROR); + } + error_code |= cvm::main()->proxy->close_input_stream(ref_p_file); + + cvm::vector1d data; + if (data.from_simple_string(data_s) != 0) { + error_code |= cvm::error("Error: could not read histogram from file \""+ + ref_p_file+"\".\n"); + } + if (data.size() == 2*ref_p.size()) { + // file contains both x and p(x) + size_t i; + for (i = 0; i < ref_p.size(); i++) { + ref_p[i] = data[2*i+1]; + } + } else if (data.size() == ref_p.size()) { + ref_p = data; + } else { + error_code |= cvm::error("Error: file \""+ref_p_file+ + "\" contains a histogram of different length.\n", + COLVARS_INPUT_ERROR); + } + } + } + + cvm::real const ref_integral = ref_p.sum() * width; + if (cvm::fabs(ref_integral - 1.0) > 1.0e-03) { + cvm::log("Reference distribution not normalized, normalizing to unity.\n"); + ref_p /= ref_integral; + } + + get_keyval(conf, "writeHistogram", b_write_histogram, false); + get_keyval(conf, "forceConstant", force_k, 1.0); + + return error_code; +} + + +colvarbias_restraint_histogram::~colvarbias_restraint_histogram() +{ + p.clear(); + ref_p.clear(); + p_diff.clear(); +} + + +int colvarbias_restraint_histogram::update() +{ + if (cvm::debug()) + cvm::log("Updating the histogram restraint bias \""+this->name+"\".\n"); + + size_t vector_size = 0; + size_t icv; + for (icv = 0; icv < num_variables(); icv++) { + vector_size += variables(icv)->value().size(); + } + + cvm::real const norm = 1.0/(cvm::sqrt(2.0*PI)*gaussian_width*vector_size); + + // calculate the histogram + p.reset(); + for (icv = 0; icv < num_variables(); icv++) { + colvarvalue const &cv = variables(icv)->value(); + if (cv.type() == colvarvalue::type_scalar) { + cvm::real const cv_value = cv.real_value; + size_t igrid; + for (igrid = 0; igrid < p.size(); igrid++) { + cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); + p[igrid] += norm * cvm::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / + (2.0 * gaussian_width * gaussian_width)); + } + } else if (cv.type() == colvarvalue::type_vector) { + size_t idim; + for (idim = 0; idim < cv.vector1d_value.size(); idim++) { + cvm::real const cv_value = cv.vector1d_value[idim]; + size_t igrid; + for (igrid = 0; igrid < p.size(); igrid++) { + cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); + p[igrid] += norm * cvm::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / + (2.0 * gaussian_width * gaussian_width)); + } + } + } else { + cvm::error("Error: unsupported type for variable "+variables(icv)->name+".\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; + } + } + + cvm::real const force_k_cv = force_k * vector_size; + + // calculate the difference between current and reference + p_diff = p - ref_p; + bias_energy = 0.5 * force_k_cv * p_diff * p_diff; + + // calculate the forces + for (icv = 0; icv < num_variables(); icv++) { + colvarvalue const &cv = variables(icv)->value(); + colvarvalue &cv_force = colvar_forces[icv]; + cv_force.type(cv); + cv_force.reset(); + + if (cv.type() == colvarvalue::type_scalar) { + cvm::real const cv_value = cv.real_value; + cvm::real &force = cv_force.real_value; + size_t igrid; + for (igrid = 0; igrid < p.size(); igrid++) { + cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); + force += force_k_cv * p_diff[igrid] * + norm * cvm::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / + (2.0 * gaussian_width * gaussian_width)) * + (-1.0 * (x_grid - cv_value) / (gaussian_width * gaussian_width)); + } + } else if (cv.type() == colvarvalue::type_vector) { + size_t idim; + for (idim = 0; idim < cv.vector1d_value.size(); idim++) { + cvm::real const cv_value = cv.vector1d_value[idim]; + cvm::real &force = cv_force.vector1d_value[idim]; + size_t igrid; + for (igrid = 0; igrid < p.size(); igrid++) { + cvm::real const x_grid = (lower_boundary + (igrid+0.5)*width); + force += force_k_cv * p_diff[igrid] * + norm * cvm::exp(-1.0 * (x_grid - cv_value) * (x_grid - cv_value) / + (2.0 * gaussian_width * gaussian_width)) * + (-1.0 * (x_grid - cv_value) / (gaussian_width * gaussian_width)); + } + } + } else { + // TODO + } + } + + return COLVARS_OK; +} + + +int colvarbias_restraint_histogram::write_output_files() +{ + if (b_write_histogram) { + colvarproxy *proxy = cvm::main()->proxy; + std::string file_name(cvm::output_prefix()+"."+this->name+".hist.dat"); + std::ostream &os = proxy->output_stream(file_name, + "histogram output file"); + os << "# " << cvm::wrap_string(variables(0)->name, cvm::cv_width) + << " " << "p(" << cvm::wrap_string(variables(0)->name, cvm::cv_width-3) + << ")\n"; + + os.setf(std::ios::fixed, std::ios::floatfield); + + size_t igrid; + for (igrid = 0; igrid < p.size(); igrid++) { + cvm::real const x_grid = (lower_boundary + (igrid+1)*width); + os << " " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << x_grid + << " " + << std::setprecision(cvm::cv_prec) + << std::setw(cvm::cv_width) + << p[igrid] << "\n"; + } + proxy->close_output_stream(file_name); + } + return COLVARS_OK; +} + + +std::ostream & colvarbias_restraint_histogram::write_traj_label(std::ostream &os) +{ + os << " "; + if (b_output_energy) { + os << " E_" + << cvm::wrap_string(this->name, cvm::en_width-2); + } + return os; +} + + +std::ostream & colvarbias_restraint_histogram::write_traj(std::ostream &os) +{ + os << " "; + if (b_output_energy) { + os << " " + << std::setprecision(cvm::en_prec) << std::setw(cvm::en_width) + << bias_energy; + } + return os; +} diff --git a/src/external/colvars/colvarbias_restraint.h b/src/external/colvars/colvarbias_restraint.h new file mode 100644 index 00000000000..f030a5cad72 --- /dev/null +++ b/src/external/colvars/colvarbias_restraint.h @@ -0,0 +1,368 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARBIAS_RESTRAINT_H +#define COLVARBIAS_RESTRAINT_H + +#include "colvarbias.h" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4250) // Silence diamond inheritance warning +#endif + +/// \brief Most general definition of a colvar restraint: +/// see derived classes for specific types +/// (implementation of \link colvarbias \endlink) +class colvarbias_restraint + : public virtual colvarbias, + public virtual colvarbias_ti +{ + +public: + + /// Retrieve colvar values and calculate their biasing forces + virtual int update(); + + /// Load new configuration - force constant and/or centers only + virtual int change_configuration(std::string const & /* conf */) { return COLVARS_NOT_IMPLEMENTED; } + + /// Calculate change in energy from using alternate configuration + virtual cvm::real energy_difference(std::string const & /* conf */) { return 0.0; } + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + + /// \brief Constructor + colvarbias_restraint(char const *key); + + virtual int init(std::string const &conf); + virtual ~colvarbias_restraint(); + + +protected: + + /// \brief Potential function for the i-th colvar + virtual cvm::real restraint_potential(size_t i) const = 0; + + /// \brief Force function for the i-th colvar + virtual colvarvalue const restraint_force(size_t i) const = 0; + + /// \brief Derivative of the potential function with respect to the force constant + virtual cvm::real d_restraint_potential_dk(size_t i) const = 0; +}; + + +/// Definition and parsing of the restraint centers +class colvarbias_restraint_centers + : public virtual colvarbias_restraint +{ +public: + + colvarbias_restraint_centers(char const *key); + virtual int init(std::string const &conf); + virtual int change_configuration(std::string const &conf); + +protected: + + /// \brief Restraint centers + std::vector colvar_centers; +}; + + +/// Definition and parsing of the force constant +class colvarbias_restraint_k + : public virtual colvarbias_restraint +{ +public: + + colvarbias_restraint_k(char const *key); + virtual int init(std::string const &conf); + virtual int change_configuration(std::string const &conf); + +protected: + + /// \brief Restraint force constant + cvm::real force_k; + + /// \brief Whether the force constant should be positive + bool check_positive_k; +}; + + +/// Options to change the restraint configuration over time (shared between centers and k moving) +class colvarbias_restraint_moving + : public virtual colvarparse, public virtual colvardeps { +public: + + colvarbias_restraint_moving(char const *key); + // Note: despite the diamond inheritance, most of this function gets only executed once + virtual int init(std::string const &conf); + virtual int update() { return COLVARS_OK; } + virtual int change_configuration(std::string const & /* conf */) { return COLVARS_NOT_IMPLEMENTED; } + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + +protected: + + /// \brief Moving target? + bool b_chg_centers; + + /// \brief Changing force constant? + bool b_chg_force_k; + + /// \brief Perform decoupling of the restraint? + bool b_decoupling; + + /// \brief Number of stages over which to perform the change + /// If zero, perform a continuous change + int target_nstages; + + /// \brief Number of current stage of the perturbation + int stage; + + /// \brief Lambda-schedule for custom varying force constant + std::vector lambda_schedule; + + /// \brief Number of steps required to reach the target force constant + /// or restraint centers + cvm::step_number target_nsteps; + + /// \brief Timestep at which the restraint starts moving + cvm::step_number first_step; + + /// \brief Accumulated work (computed when outputAccumulatedWork == true) + cvm::real acc_work; +}; + + +/// Options to change the restraint centers over time +class colvarbias_restraint_centers_moving + : public virtual colvarbias_restraint_centers, + public virtual colvarbias_restraint_moving +{ +public: + + colvarbias_restraint_centers_moving(char const *key); + virtual int init(std::string const &conf); + virtual int update(); + virtual int change_configuration(std::string const & /* conf */) { return COLVARS_NOT_IMPLEMENTED; } + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + /// \brief New restraint centers + std::vector target_centers; + + /// \brief Initial value of the restraint centers + std::vector initial_centers; + + /// \brief Increment of the restraint centers at each step + std::vector centers_incr; + + /// \brief Update the centers by interpolating between initial and target + virtual int update_centers(cvm::real lambda); + + /// Whether to write the current restraint centers to the trajectory file + bool b_output_centers; + + /// Update the accumulated work + int update_acc_work(); +}; + + +/// Options to change the restraint force constant over time +class colvarbias_restraint_k_moving + : public virtual colvarbias_restraint_k, + public virtual colvarbias_restraint_moving +{ +public: + + colvarbias_restraint_k_moving(char const *key); + virtual int init(std::string const &conf); + virtual int update(); + virtual int change_configuration(std::string const & /* conf */) { return COLVARS_NOT_IMPLEMENTED; } + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + /// \brief Restraint force constant (target value) + cvm::real target_force_k; + + /// \brief Restraint force constant (starting value) + cvm::real starting_force_k; + + /// \brief Exponent for varying the force constant + cvm::real lambda_exp; + + /// \brief Intermediate quantity to compute the restraint free energy + /// (in TI, would be the accumulating FE derivative) + cvm::real restraint_FE; + + /// \brief Equilibration steps for restraint FE calculation through TI + cvm::real target_equil_steps; + + /// \brief Increment of the force constant at each step + cvm::real force_k_incr; + + /// Update the accumulated work + int update_acc_work(); +}; + + +/// \brief Harmonic bias restraint +/// (implementation of \link colvarbias_restraint \endlink) +class colvarbias_restraint_harmonic + : public colvarbias_restraint_centers_moving, + public colvarbias_restraint_k_moving +{ +public: + colvarbias_restraint_harmonic(char const *key); + virtual int init(std::string const &conf); + virtual int update(); + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + virtual int change_configuration(std::string const &conf); + virtual cvm::real energy_difference(std::string const &conf); + +protected: + + virtual cvm::real restraint_potential(size_t i) const; + virtual colvarvalue const restraint_force(size_t i) const; + virtual cvm::real d_restraint_potential_dk(size_t i) const; +}; + + +/// \brief Wall restraint +/// (implementation of \link colvarbias_restraint \endlink) +class colvarbias_restraint_harmonic_walls + : public colvarbias_restraint_k_moving +{ +public: + + colvarbias_restraint_harmonic_walls(char const *key); + virtual int init(std::string const &conf); + virtual int update(); + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + /// \brief Location of the lower walls + std::vector lower_walls; + + /// \brief Location of the upper walls + std::vector upper_walls; + + /// \brief If both walls are defined, use this k for the lower + cvm::real lower_wall_k; + + /// \brief If both walls are defined, use this k for the upper + cvm::real upper_wall_k; + + virtual cvm::real colvar_distance(size_t i) const; + virtual cvm::real restraint_potential(size_t i) const; + virtual colvarvalue const restraint_force(size_t i) const; + virtual cvm::real d_restraint_potential_dk(size_t i) const; +}; + + +/// \brief Linear bias restraint +/// (implementation of \link colvarbias_restraint \endlink) +class colvarbias_restraint_linear + : public colvarbias_restraint_centers_moving, + public colvarbias_restraint_k_moving +{ + +public: + colvarbias_restraint_linear(char const *key); + virtual int init(std::string const &conf); + virtual int update(); + virtual int change_configuration(std::string const &conf); + virtual cvm::real energy_difference(std::string const &conf); + + virtual std::string const get_state_params() const; + virtual int set_state_params(std::string const &conf); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + virtual cvm::real restraint_potential(size_t i) const; + virtual colvarvalue const restraint_force(size_t i) const; + virtual cvm::real d_restraint_potential_dk(size_t i) const; +}; + + +/// Restrain the 1D histogram of a set of variables (or of a multidimensional one) +// TODO this could be reimplemented more cleanly as a derived class of both restraint and histogram +class colvarbias_restraint_histogram : public colvarbias { + +public: + + colvarbias_restraint_histogram(char const *key); + int init(std::string const &conf); + ~colvarbias_restraint_histogram(); + + virtual int update(); + + virtual int write_output_files(); + virtual std::ostream & write_traj_label(std::ostream &os); + virtual std::ostream & write_traj(std::ostream &os); + +protected: + + /// Probability density + cvm::vector1d p; + + /// Reference probability density + cvm::vector1d ref_p; + + /// Difference between probability density and reference + cvm::vector1d p_diff; + + /// Lower boundary of the grid + cvm::real lower_boundary; + + /// Upper boundary of the grid + cvm::real upper_boundary; + + /// Resolution of the grid + cvm::real width; + + /// Width of the Gaussians + cvm::real gaussian_width; + + /// Restraint force constant + cvm::real force_k; + + /// Write the histogram to a file + bool b_write_histogram; +}; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/src/external/colvars/colvarcomp.cpp b/src/external/colvars/colvarcomp.cpp new file mode 100644 index 00000000000..0d05e176a3d --- /dev/null +++ b/src/external/colvars/colvarcomp.cpp @@ -0,0 +1,689 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvar.h" +#include "colvarcomp.h" + + + +colvar::cvc::cvc() +{ + description = "uninitialized colvar component"; + b_try_scalable = true; + sup_coeff = 1.0; + sup_np = 1; + period = 0.0; + wrap_center = 0.0; + width = 0.0; + cvc::init_dependencies(); +} + + +colvar::cvc::cvc(std::string const &conf) +{ + description = "uninitialized colvar component"; + b_try_scalable = true; + sup_coeff = 1.0; + sup_np = 1; + period = 0.0; + wrap_center = 0.0; + width = 0.0; + init_dependencies(); + colvar::cvc::init(conf); +} + + +int colvar::cvc::update_description() +{ + if (name.size() > 0) { + description = "cvc " + name; + } else { + description = "unnamed cvc"; + } + if (function_type.size() > 0) { + description += " of type \"" + function_type + "\""; + } else { + description += " of unset type"; + } + return COLVARS_OK; +} + + +int colvar::cvc::set_function_type(std::string const &type) +{ + function_type = type; + if (function_types.size() == 0) { + function_types.push_back(function_type); + } else { + if (function_types.back() != function_type) { + function_types.push_back(function_type); + } + } + update_description(); + + for (size_t i = function_types.size()-1; i > 0; i--) { + cvm::main()->cite_feature(function_types[i]+" colvar component"+ + " (derived from "+function_types[i-1]+")"); + } + cvm::main()->cite_feature(function_types[0]+" colvar component"); + return COLVARS_OK; +} + + +int colvar::cvc::init(std::string const &conf) +{ + if (cvm::debug()) + cvm::log("Initializing cvc base object.\n"); + + std::string const old_name(name); + + if (name.size() > 0) { + cvm::log("Updating configuration for component \""+name+"\"\n"); + } + + if (get_keyval(conf, "name", name, name)) { + if ((name != old_name) && (old_name.size() > 0)) { + cvm::error("Error: cannot rename component \""+old_name+ + "\" after initialization (new name = \""+name+"\")", + COLVARS_INPUT_ERROR); + name = old_name; + } + } + update_description(); + + get_keyval(conf, "componentCoeff", sup_coeff, sup_coeff); + get_keyval(conf, "componentExp", sup_np, sup_np); + if (sup_coeff != 1.0 || sup_np != 1) { + cvm::main()->cite_feature("Linear and polynomial combination of colvar components"); + } + // TODO these could be condensed into get_keyval() + register_param("componentCoeff", reinterpret_cast(&sup_coeff)); + register_param("componentExp", reinterpret_cast(&sup_np)); + + get_keyval(conf, "period", period, period); + get_keyval(conf, "wrapAround", wrap_center, wrap_center); + // TODO when init() is called after all constructors, check periodic flag + register_param("period", reinterpret_cast(&period)); + register_param("wrapAround", reinterpret_cast(&wrap_center)); + + get_keyval_feature(this, conf, "debugGradients", + f_cvc_debug_gradient, false, parse_silent); + + bool b_no_PBC = !is_enabled(f_cvc_pbc_minimum_image); // Enabled by default + get_keyval(conf, "forceNoPBC", b_no_PBC, b_no_PBC); + if (b_no_PBC) { + disable(f_cvc_pbc_minimum_image); + } else { + enable(f_cvc_pbc_minimum_image); + } + + // Attempt scalable calculations when in parallel? (By default yes, if available) + get_keyval(conf, "scalable", b_try_scalable, b_try_scalable); + + if (cvm::debug()) + cvm::log("Done initializing cvc base object.\n"); + + return cvm::get_error(); +} + + +int colvar::cvc::init_total_force_params(std::string const &conf) +{ + if (cvm::get_error()) return COLVARS_ERROR; + + if (get_keyval_feature(this, conf, "oneSiteSystemForce", + f_cvc_one_site_total_force, is_enabled(f_cvc_one_site_total_force))) { + cvm::log("Warning: keyword \"oneSiteSystemForce\" is deprecated: " + "please use \"oneSiteTotalForce\" instead.\n"); + } + if (get_keyval_feature(this, conf, "oneSiteTotalForce", + f_cvc_one_site_total_force, is_enabled(f_cvc_one_site_total_force))) { + cvm::log("Computing total force on group 1 only\n"); + } + + if (! is_enabled(f_cvc_one_site_total_force)) { + // check whether any of the other atom groups is dummy + std::vector::iterator agi = atom_groups.begin(); + agi++; + for ( ; agi != atom_groups.end(); agi++) { + if ((*agi)->b_dummy) { + provide(f_cvc_inv_gradient, false); + provide(f_cvc_Jacobian, false); + } + } + } + + return COLVARS_OK; +} + + +cvm::atom_group *colvar::cvc::parse_group(std::string const &conf, + char const *group_key, + bool optional) +{ + int &error_code = init_code; + + cvm::atom_group *group = nullptr; + std::string group_conf; + + if (key_lookup(conf, group_key, &group_conf)) { + group = new cvm::atom_group(group_key); + + if (b_try_scalable) { + if (is_available(f_cvc_scalable_com) + && is_enabled(f_cvc_com_based) + && !is_enabled(f_cvc_debug_gradient)) { + disable(f_cvc_explicit_gradient); + enable(f_cvc_scalable_com); + // The CVC makes the feature available; + // the atom group will enable it unless it needs to compute a rotational fit + group->provide(f_ag_scalable_com); + } + + // TODO check for other types of parallelism here + } + + if (group_conf.empty()) { + error_code |= cvm::error("Error: atom group \"" + group->key + "\" has no definition.\n", + COLVARS_INPUT_ERROR); + delete group; + group = nullptr; + return group; + } + + cvm::increase_depth(); + error_code |= group->parse(group_conf); + if (error_code != COLVARS_OK) { + error_code |= + cvm::error("Error: in definition of atom group \"" + std::string(group_key) + "\".", + COLVARS_INPUT_ERROR); + delete group; + group = nullptr; + } else { + register_atom_group(group); + error_code |= group->check_keywords(group_conf, group_key); + } + cvm::decrease_depth(); + + } else { + + if (!optional) { + error_code |= + cvm::error("Error: atom group \"" + std::string(group_key) + "\" is required.\n", + COLVARS_INPUT_ERROR); + } + } + + return group; +} + + +int colvar::cvc::init_dependencies() { + size_t i; + // Initialize static array once and for all + if (features().size() == 0) { + for (i = 0; i < colvardeps::f_cvc_ntot; i++) { + modify_features().push_back(new feature); + } + + init_feature(f_cvc_active, "active", f_type_dynamic); +// The dependency below may become useful if we use dynamic atom groups +// require_feature_children(f_cvc_active, f_ag_active); + + init_feature(f_cvc_scalar, "scalar", f_type_static); + + init_feature(f_cvc_periodic, "periodic", f_type_static); + + init_feature(f_cvc_width, "defined_width", f_type_static); + + init_feature(f_cvc_lower_boundary, "defined_lower_boundary", f_type_static); + + init_feature(f_cvc_upper_boundary, "defined_upper_boundary", f_type_static); + + init_feature(f_cvc_gradient, "gradient", f_type_dynamic); + + init_feature(f_cvc_explicit_gradient, "explicit_gradient", f_type_static); + require_feature_children(f_cvc_explicit_gradient, f_ag_explicit_gradient); + + init_feature(f_cvc_inv_gradient, "inverse_gradient", f_type_dynamic); + require_feature_self(f_cvc_inv_gradient, f_cvc_gradient); + + init_feature(f_cvc_debug_gradient, "debug_gradient", f_type_user); + require_feature_self(f_cvc_debug_gradient, f_cvc_gradient); + require_feature_self(f_cvc_debug_gradient, f_cvc_explicit_gradient); + + init_feature(f_cvc_Jacobian, "Jacobian_derivative", f_type_dynamic); + require_feature_self(f_cvc_Jacobian, f_cvc_inv_gradient); + + // Compute total force on first site only to avoid unwanted + // coupling to other colvars (see e.g. Ciccotti et al., 2005) + init_feature(f_cvc_one_site_total_force, "total_force_from_one_group", f_type_user); + require_feature_self(f_cvc_one_site_total_force, f_cvc_com_based); + + init_feature(f_cvc_com_based, "function_of_centers_of_mass", f_type_static); + + init_feature(f_cvc_pbc_minimum_image, "use_minimum-image_with_PBCs", f_type_user); + + init_feature(f_cvc_scalable, "scalable_calculation", f_type_dynamic); + require_feature_self(f_cvc_scalable_com, f_cvc_scalable); + // CVC cannot compute atom-level gradients on rank 0 if colvar computation is distributed + exclude_feature_self(f_cvc_scalable, f_cvc_explicit_gradient); + + init_feature(f_cvc_scalable_com, "scalable_calculation_of_centers_of_mass", f_type_static); + require_feature_self(f_cvc_scalable_com, f_cvc_com_based); + // CVC cannot compute atom-level gradients if computed on atom group COM + exclude_feature_self(f_cvc_scalable_com, f_cvc_explicit_gradient); + + init_feature(f_cvc_collect_atom_ids, "collect_atom_ids", f_type_dynamic); + require_feature_children(f_cvc_collect_atom_ids, f_ag_collect_atom_ids); + + // TODO only enable this when f_ag_scalable can be turned on for a pre-initialized group + // require_feature_children(f_cvc_scalable, f_ag_scalable); + // require_feature_children(f_cvc_scalable_com, f_ag_scalable_com); + + // check that everything is initialized + for (i = 0; i < colvardeps::f_cvc_ntot; i++) { + if (is_not_set(i)) { + cvm::error("Uninitialized feature " + cvm::to_str(i) + " in " + description); + } + } + } + + // Initialize feature_states for each instance + // default as available, not enabled + // except dynamic features which default as unavailable + feature_states.reserve(f_cvc_ntot); + for (i = 0; i < colvardeps::f_cvc_ntot; i++) { + bool avail = is_dynamic(i) ? false : true; + feature_states.push_back(feature_state(avail, false)); + } + + // Features that are implemented by all cvcs by default + // Each cvc specifies what other features are available + feature_states[f_cvc_active].available = true; + feature_states[f_cvc_gradient].available = true; + feature_states[f_cvc_collect_atom_ids].available = true; + + // CVCs are enabled from the start - get disabled based on flags + enable(f_cvc_active); + + // Explicit gradients are implemented in most CVCs. Exceptions must be specified explicitly. + enable(f_cvc_explicit_gradient); + + // Use minimum-image distances by default + enable(f_cvc_pbc_minimum_image); + + // Features that are implemented by default if their requirements are + feature_states[f_cvc_one_site_total_force].available = true; + + // Features That are implemented only for certain simulation engine configurations + feature_states[f_cvc_scalable_com].available = (cvm::proxy->scalable_group_coms() == COLVARS_OK); + feature_states[f_cvc_scalable].available = feature_states[f_cvc_scalable_com].available; + + return COLVARS_OK; +} + + +int colvar::cvc::setup() +{ + update_description(); + return COLVARS_OK; +} + + +colvar::cvc::~cvc() +{ + free_children_deps(); + remove_all_children(); + for (size_t i = 0; i < atom_groups.size(); i++) { + if (atom_groups[i] != NULL) delete atom_groups[i]; + } +} + + +void colvar::cvc::init_as_distance() +{ + x.type(colvarvalue::type_scalar); + enable(f_cvc_lower_boundary); + lower_boundary.type(colvarvalue::type_scalar); + lower_boundary.real_value = 0.0; + register_param("lowerBoundary", reinterpret_cast(&lower_boundary)); +} + + +void colvar::cvc::init_as_angle() +{ + x.type(colvarvalue::type_scalar); + init_scalar_boundaries(0.0, 180.0); +} + + +void colvar::cvc::init_as_periodic_angle() +{ + x.type(colvarvalue::type_scalar); + enable(f_cvc_periodic); + period = 360.0; + init_scalar_boundaries(-180.0, 180.0); +} + + +void colvar::cvc::init_scalar_boundaries(cvm::real lb, cvm::real ub) +{ + enable(f_cvc_lower_boundary); + lower_boundary.type(colvarvalue::type_scalar); + lower_boundary.real_value = lb; + enable(f_cvc_upper_boundary); + upper_boundary.type(colvarvalue::type_scalar); + upper_boundary.real_value = ub; + register_param("lowerBoundary", reinterpret_cast(&lower_boundary)); + register_param("upperBoundary", reinterpret_cast(&upper_boundary)); +} + + +void colvar::cvc::register_atom_group(cvm::atom_group *ag) +{ + atom_groups.push_back(ag); + add_child(ag); +} + + +colvarvalue const *colvar::cvc::get_param_grad(std::string const ¶m_name) +{ + colvarvalue const *ptr = + reinterpret_cast(get_param_grad_ptr(param_name)); + return ptr != NULL ? ptr : NULL; +} + + +int colvar::cvc::set_param(std::string const ¶m_name, + void const *new_value) +{ + if (param_map.count(param_name) > 0) { + + // TODO When we can use C++11, make this a proper function map + if (param_name.compare("componentCoeff") == 0) { + sup_coeff = *(reinterpret_cast(new_value)); + } + if (param_name.compare("componentExp") == 0) { + sup_np = *(reinterpret_cast(new_value)); + } + if (is_enabled(f_cvc_periodic)) { + if (param_name.compare("period") == 0) { + period = *(reinterpret_cast(new_value)); + } + if (param_name.compare("wrapAround") == 0) { + wrap_center = *(reinterpret_cast(new_value)); + } + } + } + + return colvarparams::set_param(param_name, new_value); +} + + +void colvar::cvc::read_data() +{ + size_t ig; + for (ig = 0; ig < atom_groups.size(); ig++) { + cvm::atom_group &atoms = *(atom_groups[ig]); + atoms.reset_atoms_data(); + atoms.read_positions(); + atoms.calc_required_properties(); + // each atom group will take care of its own fitting_group, if defined + } + +//// Don't try to get atom velocities, as no back-end currently implements it +// if (tasks[task_output_velocity] && !tasks[task_fdiff_velocity]) { +// for (i = 0; i < cvcs.size(); i++) { +// for (ig = 0; ig < cvcs[i]->atom_groups.size(); ig++) { +// cvcs[i]->atom_groups[ig]->read_velocities(); +// } +// } +// } +} + + +std::vector > colvar::cvc::get_atom_lists() +{ + std::vector > lists; + + std::vector::iterator agi = atom_groups.begin(); + for ( ; agi != atom_groups.end(); ++agi) { + (*agi)->create_sorted_ids(); + lists.push_back((*agi)->sorted_ids()); + if ((*agi)->is_enabled(f_ag_fitting_group) && (*agi)->is_enabled(f_ag_fit_gradients)) { + cvm::atom_group &fg = *((*agi)->fitting_group); + fg.create_sorted_ids(); + lists.push_back(fg.sorted_ids()); + } + } + return lists; +} + + +void colvar::cvc::collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients) +{ + // Coefficient: d(a * x^n) = a * n * x^(n-1) * dx + cvm::real coeff = sup_coeff * cvm::real(sup_np) * + cvm::integer_power(value().real_value, sup_np-1); + + for (size_t j = 0; j < atom_groups.size(); j++) { + + cvm::atom_group &ag = *(atom_groups[j]); + + // If necessary, apply inverse rotation to get atomic + // gradient in the laboratory frame + if (ag.is_enabled(f_ag_rotate)) { + const auto rot_inv = ag.rot.inverse().matrix(); + + for (size_t k = 0; k < ag.size(); k++) { + size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), + ag[k].id) - atom_ids.begin(); + atomic_gradients[a] += coeff * (rot_inv * ag[k].grad); + } + + } else { + + for (size_t k = 0; k < ag.size(); k++) { + size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), + ag[k].id) - atom_ids.begin(); + atomic_gradients[a] += coeff * ag[k].grad; + } + } + if (ag.is_enabled(f_ag_fitting_group) && ag.is_enabled(f_ag_fit_gradients)) { + cvm::atom_group const &fg = *(ag.fitting_group); + for (size_t k = 0; k < fg.size(); k++) { + size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), + fg[k].id) - atom_ids.begin(); + // fit gradients are in the unrotated (simulation) frame + atomic_gradients[a] += coeff * fg.fit_gradients[k]; + } + } + } +} + + +void colvar::cvc::calc_force_invgrads() +{ + cvm::error("Error: calculation of inverse gradients is not implemented " + "for colvar components of type \""+function_type+"\".\n", + COLVARS_NOT_IMPLEMENTED); +} + + +void colvar::cvc::calc_Jacobian_derivative() +{ + cvm::error("Error: calculation of inverse gradients is not implemented " + "for colvar components of type \""+function_type+"\".\n", + COLVARS_NOT_IMPLEMENTED); +} + + +void colvar::cvc::calc_fit_gradients() +{ + for (size_t ig = 0; ig < atom_groups.size(); ig++) { + atom_groups[ig]->calc_fit_gradients(); + } +} + + +void colvar::cvc::debug_gradients() +{ + // this function should work for any scalar cvc: + // the only difference will be the name of the atom group (here, "group") + // NOTE: this assumes that groups for this cvc are non-overlapping, + // since atom coordinates are modified only within the current group + + cvm::log("Debugging gradients for " + description); + + for (size_t ig = 0; ig < atom_groups.size(); ig++) { + cvm::atom_group *group = atom_groups[ig]; + if (group->b_dummy) continue; + + const auto rot_0 = group->rot.matrix(); + const auto rot_inv = group->rot.inverse().matrix(); + + cvm::real x_0 = x.real_value; + if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_0 = x[0]; + + // cvm::log("gradients = "+cvm::to_str (gradients)+"\n"); + + cvm::atom_group *group_for_fit = group->fitting_group ? group->fitting_group : group; + cvm::atom_pos fit_gradient_sum, gradient_sum; + + // print the values of the fit gradients + if (group->is_enabled(f_ag_center) || group->is_enabled(f_ag_rotate)) { + if (group->is_enabled(f_ag_fit_gradients)) { + size_t j; + + // fit_gradients are in the simulation frame: we should print them in the rotated frame + cvm::log("Fit gradients:\n"); + for (j = 0; j < group_for_fit->fit_gradients.size(); j++) { + cvm::log((group->fitting_group ? std::string("refPosGroup") : group->key) + + "[" + cvm::to_str(j) + "] = " + + (group->is_enabled(f_ag_rotate) ? + cvm::to_str(rot_0 * (group_for_fit->fit_gradients[j])) : + cvm::to_str(group_for_fit->fit_gradients[j]))); + } + } + } + + // debug the gradients + for (size_t ia = 0; ia < group->size(); ia++) { + + // tests are best conducted in the unrotated (simulation) frame + cvm::rvector const atom_grad = (group->is_enabled(f_ag_rotate) ? + rot_inv * ((*group)[ia].grad) : + (*group)[ia].grad); + gradient_sum += atom_grad; + + for (size_t id = 0; id < 3; id++) { + // (re)read original positions + group->read_positions(); + // change one coordinate + (*group)[ia].pos[id] += cvm::debug_gradients_step_size; + group->calc_required_properties(); + calc_value(); + cvm::real x_1 = x.real_value; + if ((x.type() == colvarvalue::type_vector) && (x.size() == 1)) x_1 = x[0]; + cvm::log("Atom "+cvm::to_str(ia)+", component "+cvm::to_str(id)+":\n"); + cvm::log("dx(actual) = "+cvm::to_str(x_1 - x_0, + 21, 14)+"\n"); + cvm::real const dx_pred = (group->fit_gradients.size()) ? + (cvm::debug_gradients_step_size * (atom_grad[id] + group->fit_gradients[ia][id])) : + (cvm::debug_gradients_step_size * atom_grad[id]); + cvm::log("dx(interp) = "+cvm::to_str(dx_pred, + 21, 14)+"\n"); + cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str(cvm::fabs(x_1 - x_0 - dx_pred) / + cvm::fabs(x_1 - x_0), 12, 5)+"\n"); + } + } + + if ((group->is_enabled(f_ag_fit_gradients)) && (group->fitting_group != NULL)) { + cvm::atom_group *ref_group = group->fitting_group; + group->read_positions(); + group->calc_required_properties(); + + for (size_t ia = 0; ia < ref_group->size(); ia++) { + + // fit gradients are in the unrotated (simulation) frame + cvm::rvector const atom_grad = ref_group->fit_gradients[ia]; + fit_gradient_sum += atom_grad; + + for (size_t id = 0; id < 3; id++) { + // (re)read original positions + group->read_positions(); + ref_group->read_positions(); + // change one coordinate + (*ref_group)[ia].pos[id] += cvm::debug_gradients_step_size; + group->calc_required_properties(); + calc_value(); + + cvm::real const x_1 = x.real_value; + cvm::log("refPosGroup atom "+cvm::to_str(ia)+", component "+cvm::to_str (id)+":\n"); + cvm::log("dx(actual) = "+cvm::to_str (x_1 - x_0, + 21, 14)+"\n"); + + cvm::real const dx_pred = cvm::debug_gradients_step_size * atom_grad[id]; + + cvm::log("dx(interp) = "+cvm::to_str (dx_pred, + 21, 14)+"\n"); + cvm::log ("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str(cvm::fabs (x_1 - x_0 - dx_pred) / + cvm::fabs (x_1 - x_0), + 12, 5)+ + ".\n"); + } + } + } + + cvm::log("Gradient sum: " + cvm::to_str(gradient_sum) + + " Fit gradient sum: " + cvm::to_str(fit_gradient_sum) + + " Total " + cvm::to_str(gradient_sum + fit_gradient_sum)); + } + return; +} + + +cvm::real colvar::cvc::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.dist2(x2); +} + + +colvarvalue colvar::cvc::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.dist2_grad(x2); +} + + +colvarvalue colvar::cvc::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x2.dist2_grad(x1); +} + + +void colvar::cvc::wrap(colvarvalue & /* x_unwrapped */) const +{ + return; +} + + + +// Static members + +std::vector colvar::cvc::cvc_features; diff --git a/src/external/colvars/colvarcomp.h b/src/external/colvars/colvarcomp.h new file mode 100644 index 00000000000..8d7b6961887 --- /dev/null +++ b/src/external/colvars/colvarcomp.h @@ -0,0 +1,1870 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARCOMP_H +#define COLVARCOMP_H + +// Declaration of colvar::cvc base class and derived ones. +// +// Future cvc's could be declared on additional header files. +// After the declaration of a new derived class, its metric +// functions must be reimplemented as well. +// If the new cvc has no symmetry or periodicity, +// this can be done straightforwardly by using the macro: +// simple_scalar_dist_functions (derived_class) + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvaratoms.h" +#include "colvar.h" +#include "colvar_geometricpath.h" + + +/// \brief Colvar component (base class for collective variables) +/// +/// A \link colvar::cvc \endlink object (or an object of a +/// cvc-derived class) implements the calculation of a collective +/// variable, its gradients and any other related physical quantities +/// that depend on microscopic degrees of freedom. +/// +/// No restriction is set to what kind of calculation a \link colvar::cvc \endlink +/// object performs (usually an analytical function of atomic coordinates). +/// The only constraints are that: \par +/// +/// - The value is calculated by the \link calc_value() \endlink +/// method, and is an object of \link colvarvalue \endlink class. This +/// provides a transparent way to treat scalar and non-scalar variables +/// alike, and allows an automatic selection of the applicable algorithms. +/// +/// - The object provides an implementation \link apply_force() \endlink to +/// apply forces to atoms. Typically, one or more \link colvarmodule::atom_group +/// \endlink objects are used, but this is not a requirement for as long as +/// the \link colvar::cvc \endlink object communicates with the simulation program. +/// +/// If you wish to implement a new collective variable component, you +/// should write your own class by inheriting directly from \link +/// colvar::cvc \endlink, or one of its derived classes (for instance, +/// \link colvar::distance \endlink is frequently used, because it provides +/// useful data and function members for any colvar based on two +/// atom groups). +/// +/// The steps are: \par +/// 1. Declare the new class as a derivative of \link colvar::cvc \endlink +/// in the file \link colvarcomp.h \endlink +/// 2. Implement the new class in a file named colvarcomp_.cpp +/// 3. Declare the name of the new class inside the \link colvar \endlink class +/// in \link colvar.h \endlink (see "list of available components") +/// 4. Add a call for the new class in colvar::init_components() +//// (file: colvar.cpp) +/// + +class colvar::cvc + : public colvarparse, public colvardeps +{ +public: + + /// \brief The name of the object (helps to identify this + /// cvc instance when debugging) + std::string name; + + /// \brief Description of the type of collective variable + /// + /// Normally this string is set by the parent \link colvar \endlink + /// object within its constructor, when all \link colvar::cvc \endlink + /// objects are initialized; therefore the main "config string" + /// constructor does not need to define it. If a \link colvar::cvc + /// \endlink is initialized and/or a different constructor is used, + /// this variable definition should be set within the constructor. + std::string function_type; + + /// Keyword used in the input to denote this CVC + std::string config_key; + + /// \brief Coefficient in the polynomial combination (default: 1.0) + cvm::real sup_coeff; + /// \brief Exponent in the polynomial combination (default: 1) + int sup_np; + + /// \brief Period of the values of this CVC (default: 0.0, non periodic) + cvm::real period; + + /// \brief If the component is periodic, wrap around this value (default: 0.0) + cvm::real wrap_center; + + /// \brief Constructor + /// + /// Calls the init() function of the class + cvc(std::string const &conf); + + /// Current initialization state; TODO remove this when using init() after default constructor + int init_code = COLVARS_OK; + + /// Set the value of \link function_type \endlink and its dependencies + int set_function_type(std::string const &type); + + /// An init function should be defined for every class inheriting from cvc + /// \param conf Contents of the configuration file pertaining to this \link + /// cvc \endlink + virtual int init(std::string const &conf); + + /// \brief Initialize dependency tree + virtual int init_dependencies(); + + /// \brief Within the constructor, make a group parse its own + /// options from the provided configuration string + /// Returns reference to new group + cvm::atom_group *parse_group(std::string const &conf, + char const *group_key, + bool optional = false); + + /// \brief Parse options pertaining to total force calculation + virtual int init_total_force_params(std::string const &conf); + + /// \brief After construction, set data related to dependency handling + int setup(); + + /// \brief Default constructor (used when \link colvar::cvc \endlink + /// objects are declared within other ones) + cvc(); + + /// Destructor + virtual ~cvc(); + + /// \brief Implementation of the feature list for colvar + static std::vector cvc_features; + + /// \brief Implementation of the feature list accessor for colvar + virtual const std::vector &features() const + { + return cvc_features; + } + virtual std::vector &modify_features() + { + return cvc_features; + } + static void delete_features() { + for (size_t i=0; i < cvc_features.size(); i++) { + delete cvc_features[i]; + } + cvc_features.clear(); + } + + /// \brief Get vector of vectors of atom IDs for all atom groups + virtual std::vector > get_atom_lists(); + + /// \brief Obtain data needed for the calculation for the backend + virtual void read_data(); + + /// \brief Calculate the variable + virtual void calc_value() = 0; + + /// \brief Calculate the atomic gradients, to be reused later in + /// order to apply forces + virtual void calc_gradients() {} + + /// \brief Calculate the atomic fit gradients + void calc_fit_gradients(); + + /// \brief Calculate finite-difference gradients alongside the analytical ones, for each Cartesian component + virtual void debug_gradients(); + + /// \brief Calculate atomic gradients and add them to the corresponding item in gradient vector + /// May be overridden by CVCs that do not store their gradients in the classic way, see dihedPC + virtual void collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients); + + /// \brief Calculate the total force from the system using the + /// inverse atomic gradients + virtual void calc_force_invgrads(); + + /// \brief Calculate the divergence of the inverse atomic gradients + virtual void calc_Jacobian_derivative(); + + + /// \brief Return the previously calculated value + colvarvalue const & value() const; + + /// \brief Return the previously calculated total force + colvarvalue const & total_force() const; + + /// \brief Return the previously calculated divergence of the + /// inverse atomic gradients + colvarvalue const & Jacobian_derivative() const; + + /// \brief Apply the collective variable force, by communicating the + /// atomic forces to the simulation program (\b Note: the \link ft + /// \endlink member is not altered by this function) + /// + /// Note: multiple calls to this function within the same simulation + /// step will add the forces altogether \param cvforce The + /// collective variable force, usually coming from the biases and + /// eventually manipulated by the parent \link colvar \endlink + /// object + virtual void apply_force(colvarvalue const &cvforce) = 0; + + /// \brief Square distance between x1 and x2 (can be redefined to + /// transparently implement constraints, symmetries and + /// periodicities) + /// + /// colvar::cvc::dist2() and the related functions are + /// declared as "const" functions, but not "static", because + /// additional parameters defining the metrics (e.g. the + /// periodicity) may be specific to each colvar::cvc object. + /// + /// If symmetries or periodicities are present, the + /// colvar::cvc::dist2() should be redefined to return the + /// "closest distance" value and colvar::cvc::dist2_lgrad(), + /// colvar::cvc::dist2_rgrad() to return its gradients. + /// + /// If constraints are present (and not already implemented by any + /// of the \link colvarvalue \endlink types), the + /// colvar::cvc::dist2_lgrad() and + /// colvar::cvc::dist2_rgrad() functions should be redefined + /// to provide a gradient which is compatible with the constraint, + /// i.e. already deprived of its component normal to the constraint + /// hypersurface. + /// + /// Finally, another useful application, if you are performing very + /// many operations with these functions, could be to override the + /// \link colvarvalue \endlink member functions and access directly + /// its member data. For instance: to define dist2(x1,x2) as + /// (x2.real_value-x1.real_value)*(x2.real_value-x1.real_value) in + /// case of a scalar \link colvarvalue \endlink type. + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Gradient(with respect to x1) of the square distance (can + /// be redefined to transparently implement constraints, symmetries + /// and periodicities) + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Gradient(with respect to x2) of the square distance (can + /// be redefined to transparently implement constraints, symmetries + /// and periodicities) + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Wrap value (for periodic/symmetric cvcs) + virtual void wrap(colvarvalue &x_unwrapped) const; + + /// \brief Pointers to all atom groups, to let colvars collect info + /// e.g. atomic gradients + std::vector atom_groups; + + /// \brief Store a pointer to new atom group, and list as child for dependencies + void register_atom_group(cvm::atom_group *ag); + + /// Pointer to the gradient of parameter param_name + virtual colvarvalue const *get_param_grad(std::string const ¶m_name); + + /// Set the named parameter to the given value + virtual int set_param(std::string const ¶m_name, void const *new_value); + + /// \brief Whether or not this CVC will be computed in parallel whenever possible + bool b_try_scalable; + + /// Forcibly set value of CVC - useful for driving an external coordinate, + /// eg. lambda dynamics + inline void set_value(colvarvalue const &new_value) { + x = new_value; + } + +protected: + + /// Update the description string based on name and type + int update_description(); + + /// Record the type of this class as well as those it is derived from + std::vector function_types; + + /// \brief Cached value + colvarvalue x; + + /// \brief Value at the previous step + colvarvalue x_old; + + /// \brief Calculated total force (\b Note: this is calculated from + /// the total atomic forces read from the program, subtracting fromt + /// the "internal" forces of the system the "external" forces from + /// the colvar biases) + colvarvalue ft; + + /// \brief Calculated Jacobian derivative (divergence of the inverse + /// gradients): serves to calculate the phase space correction + colvarvalue jd; + + /// \brief Set data types for a scalar distance (convenience function) + void init_as_distance(); + + /// \brief Set data types for a bounded angle (0° to 180°) + void init_as_angle(); + + /// \brief Set data types for a periodic angle (-180° to 180°) + void init_as_periodic_angle(); + + /// \brief Set two scalar boundaries (convenience function) + void init_scalar_boundaries(cvm::real lb, cvm::real ub); + + /// \brief Location of the lower boundary (not defined by user choice) + colvarvalue lower_boundary; + + /// \brief Location of the upper boundary (not defined by user choice) + colvarvalue upper_boundary; + + /// \brief CVC-specific default colvar width + cvm::real width; +}; + + +inline colvarvalue const & colvar::cvc::value() const +{ + return x; +} + + +inline colvarvalue const & colvar::cvc::total_force() const +{ + return ft; +} + + +inline colvarvalue const & colvar::cvc::Jacobian_derivative() const +{ + return jd; +} + + + +/// \brief Colvar component: distance between the centers of mass of +/// two groups (colvarvalue::type_scalar type, range [0:*)) + +class colvar::distance + : public colvar::cvc +{ +protected: + /// First atom group + cvm::atom_group *group1; + /// Second atom group + cvm::atom_group *group2; + /// Vector distance, cached to be recycled + cvm::rvector dist_v; +public: + distance(std::string const &conf); + distance(); + virtual ~distance() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +// \brief Colvar component: distance vector between centers of mass +// of two groups (\link colvarvalue::type_3vector \endlink type, +// range (-*:*)x(-*:*)x(-*:*)) +class colvar::distance_vec + : public colvar::distance +{ +public: + distance_vec(std::string const &conf); + distance_vec(); + virtual ~distance_vec() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + /// Redefined to handle the box periodicity + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the box periodicity + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the box periodicity + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: distance unit vector (direction) between +/// centers of mass of two groups (colvarvalue::type_unit3vector type, +/// range [-1:1]x[-1:1]x[-1:1]) +class colvar::distance_dir + : public colvar::distance +{ +public: + distance_dir(std::string const &conf); + distance_dir(); + virtual ~distance_dir() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + /// Redefined to override the distance ones + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to override the distance ones + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to override the distance ones + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: projection of the distance vector along +/// an axis(colvarvalue::type_scalar type, range (-*:*)) +class colvar::distance_z + : public colvar::cvc +{ +protected: + /// Main atom group + cvm::atom_group *main; + /// Reference atom group + cvm::atom_group *ref1; + /// Optional, second ref atom group + cvm::atom_group *ref2; + /// Vector on which the distance vector is projected + cvm::rvector axis; + /// Norm of the axis + cvm::real axis_norm; + /// Vector distance, cached to be recycled + cvm::rvector dist_v; + /// Flag: using a fixed axis vector? + bool fixed_axis; +public: + distance_z(std::string const &conf); + distance_z(); + virtual ~distance_z() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// \brief Redefined to make use of the user-provided period + virtual void wrap(colvarvalue &x_unwrapped) const; +}; + + + +/// \brief Colvar component: projection of the distance vector on a +/// plane (colvarvalue::type_scalar type, range [0:*)) +class colvar::distance_xy + : public colvar::distance_z +{ +protected: + /// Components of the distance vector orthogonal to the axis + cvm::rvector dist_v_ortho; + /// Vector distances + cvm::rvector v12, v13; +public: + distance_xy(std::string const &conf); + distance_xy(); + virtual ~distance_xy() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +/// \brief Colvar component: polar coordinate phi of a group +/// (colvarvalue::type_scalar type, range [-180:180]) +class colvar::polar_phi + : public colvar::cvc +{ +public: + polar_phi(std::string const &conf); + polar_phi(); + virtual ~polar_phi() {} +protected: + cvm::atom_group *atoms; + cvm::real r, theta, phi; +public: + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + /// Redefined to handle the 2*PI periodicity + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap(colvarvalue &x_unwrapped) const; +}; + + +/// \brief Colvar component: polar coordinate theta of a group +/// (colvarvalue::type_scalar type, range [0:180]) +class colvar::polar_theta + : public colvar::cvc +{ +public: + polar_theta(std::string const &conf); + polar_theta(); + virtual ~polar_theta() {} +protected: + cvm::atom_group *atoms; + cvm::real r, theta, phi; +public: + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + /// Redefined to override the distance ones + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to override the distance ones + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to override the distance ones + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + +/// \brief Colvar component: average distance between two groups of atoms, weighted as the sixth power, +/// as in NMR refinements(colvarvalue::type_scalar type, range (0:*)) +class colvar::distance_inv + : public colvar::cvc +{ +protected: + /// First atom group + cvm::atom_group *group1; + /// Second atom group + cvm::atom_group *group2; + /// Components of the distance vector orthogonal to the axis + int exponent; +public: + distance_inv(std::string const &conf); + virtual ~distance_inv() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: N1xN2 vector of pairwise distances +/// (colvarvalue::type_vector type, range (0:*) for each component) +class colvar::distance_pairs + : public colvar::cvc +{ +protected: + /// First atom group + cvm::atom_group *group1; + /// Second atom group + cvm::atom_group *group2; +public: + distance_pairs(std::string const &conf); + distance_pairs(); + virtual ~distance_pairs() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + + +/// \brief Colvar component: dipole magnitude of a molecule +class colvar::dipole_magnitude + : public colvar::cvc +{ +protected: + /// Dipole atom group + cvm::atom_group *atoms; + cvm::atom_pos dipoleV; +public: + /// Initialize by parsing the configuration + dipole_magnitude (std::string const &conf); + dipole_magnitude (cvm::atom const &a1); + dipole_magnitude(); + virtual ~dipole_magnitude() {} + virtual void calc_value(); + virtual void calc_gradients(); + //virtual void calc_force_invgrads(); + //virtual void calc_Jacobian_derivative(); + virtual void apply_force (colvarvalue const &force); + virtual cvm::real dist2 (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: Radius of gyration of an atom group +/// (colvarvalue::type_scalar type, range [0:*)) +class colvar::gyration + : public colvar::cvc +{ +protected: + /// Atoms involved + cvm::atom_group *atoms; +public: + gyration(std::string const &conf); + virtual ~gyration() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: moment of inertia of an atom group +/// (colvarvalue::type_scalar type, range [0:*)) +class colvar::inertia + : public colvar::gyration +{ +public: + /// Constructor + inertia(std::string const &conf); + inertia(); + virtual ~inertia() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: moment of inertia of an atom group +/// around a user-defined axis (colvarvalue::type_scalar type, range [0:*)) +class colvar::inertia_z + : public colvar::inertia +{ +protected: + /// Vector on which the inertia tensor is projected + cvm::rvector axis; +public: + /// Constructor + inertia_z(std::string const &conf); + inertia_z(); + virtual ~inertia_z() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: projection of 3N coordinates onto an +/// eigenvector(colvarvalue::type_scalar type, range (-*:*)) +class colvar::eigenvector + : public colvar::cvc +{ +protected: + + /// Atom group + cvm::atom_group * atoms; + + /// Reference coordinates + std::vector ref_pos; + + /// Eigenvector (of a normal or essential mode): will always have zero center + std::vector eigenvec; + + /// Inverse square norm of the eigenvector + cvm::real eigenvec_invnorm2; + +public: + + /// Constructor + eigenvector(std::string const &conf); + virtual ~eigenvector() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: angle between the centers of mass of +/// three groups (colvarvalue::type_scalar type, range [0:PI]) +class colvar::angle + : public colvar::cvc +{ +protected: + + /// Atom group + cvm::atom_group *group1; + /// Atom group + cvm::atom_group *group2; + /// Atom group + cvm::atom_group *group3; + + /// Inter site vectors + cvm::rvector r21, r23; + /// Inter site vector norms + cvm::real r21l, r23l; + /// Derivatives wrt group centers of mass + cvm::rvector dxdr1, dxdr3; + + /// Compute total force on first site only to avoid unwanted + /// coupling to other colvars (see e.g. Ciccotti et al., 2005) + /// (or to allow dummy atoms) + bool b_1site_force; +public: + + /// Initialize by parsing the configuration + angle(std::string const &conf); + /// \brief Initialize the three groups after three atoms + angle(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3); + virtual ~angle() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: angle between the dipole of a molecule and an axis +/// formed by two groups of atoms(colvarvalue::type_scalar type, range [0:PI]) +class colvar::dipole_angle + : public colvar::cvc +{ +protected: + + /// Dipole atom group + cvm::atom_group *group1; + /// Atom group + cvm::atom_group *group2; + /// Atom group + cvm::atom_group *group3; + + /// Inter site vectors + cvm::rvector r21, r23; + /// Inter site vector norms + cvm::real r21l, r23l; + /// Derivatives wrt group centers of mass + cvm::rvector dxdr1, dxdr3; + + /// Compute total force on first site only to avoid unwanted + /// coupling to other colvars (see e.g. Ciccotti et al., 2005) + /// (or to allow dummy atoms) + bool b_1site_force; +public: + + /// Initialize by parsing the configuration + dipole_angle (std::string const &conf); + /// \brief Initialize the three groups after three atoms + dipole_angle (cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3); + dipole_angle(); + virtual ~dipole_angle() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force (colvarvalue const &force); + virtual cvm::real dist2 (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: dihedral between the centers of mass of +/// four groups (colvarvalue::type_scalar type, range [-PI:PI]) +class colvar::dihedral + : public colvar::cvc +{ +protected: + + /// Atom group + cvm::atom_group *group1; + /// Atom group + cvm::atom_group *group2; + /// Atom group + cvm::atom_group *group3; + /// Atom group + cvm::atom_group *group4; + /// Inter site vectors + cvm::rvector r12, r23, r34; + + /// \brief Compute total force on first site only to avoid unwanted + /// coupling to other colvars (see e.g. Ciccotti et al., 2005) + bool b_1site_force; + +public: + + /// Initialize by parsing the configuration + dihedral(std::string const &conf); + /// \brief Initialize the four groups after four atoms + dihedral(cvm::atom const &a1, cvm::atom const &a2, cvm::atom const &a3, cvm::atom const &a4); + dihedral(); + virtual ~dihedral() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + + /// Redefined to handle the 2*PI periodicity + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap(colvarvalue &x_unwrapped) const; +}; + + + +/// \brief Colvar component: coordination number between two groups +/// (colvarvalue::type_scalar type, range [0:N1*N2]) +class colvar::coordnum + : public colvar::cvc +{ +protected: + /// First atom group + cvm::atom_group *group1; + /// Second atom group + cvm::atom_group *group2; + /// \brief "Cutoff" for isotropic calculation (default) + cvm::real r0; + /// \brief "Cutoff vector" for anisotropic calculation + cvm::rvector r0_vec; + /// \brief Whether r/r0 or \vec{r}*\vec{1/r0_vec} should be used + bool b_anisotropic; + /// Integer exponent of the function numerator + int en; + /// Integer exponent of the function denominator + int ed; + + /// If true, group2 will be treated as a single atom + bool b_group2_center_only; + + /// Tolerance for the pair list + cvm::real tolerance; + + /// Frequency of update of the pair list + int pairlist_freq; + + /// Pair list + bool *pairlist; + +public: + + coordnum(std::string const &conf); + ~coordnum(); + + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + + enum { + ef_null = 0, + ef_gradients = 1, + ef_anisotropic = (1<<8), + ef_use_pairlist = (1<<9), + ef_rebuild_pairlist = (1<<10) + }; + + /// \brief Calculate a coordination number through the function + /// (1-x**n)/(1-x**m), where x = |A1-A2|/r0 \param r0, r0_vec "cutoff" for + /// the coordination number (scalar or vector depending on user choice) + /// \param en Numerator exponent \param ed Denominator exponent \param First + /// atom \param Second atom \param pairlist_elem pointer to pair flag for + /// this pair \param tolerance A pair is defined as having a larger + /// coordination than this number + template + static cvm::real switching_function(cvm::real const &r0, + cvm::rvector const &r0_vec, + int en, + int ed, + cvm::atom &A1, + cvm::atom &A2, + bool **pairlist_elem, + cvm::real tolerance); + + /// Workhorse function + template int compute_coordnum(); + + /// Workhorse function + template void main_loop(bool **pairlist_elem); + +}; + + + +/// \brief Colvar component: self-coordination number within a group +/// (colvarvalue::type_scalar type, range [0:N*(N-1)/2]) +class colvar::selfcoordnum + : public colvar::cvc +{ +protected: + + /// Selected atoms + cvm::atom_group *group1; + /// \brief "Cutoff" for isotropic calculation (default) + cvm::real r0; + /// Integer exponent of the function numerator + int en; + /// Integer exponent of the function denominator + int ed; + cvm::real tolerance; + int pairlist_freq; + bool *pairlist; + +public: + + selfcoordnum(std::string const &conf); + ~selfcoordnum(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + + /// Main workhorse function + template int compute_selfcoordnum(); +}; + + + +/// \brief Colvar component: coordination number between two groups +/// (colvarvalue::type_scalar type, range [0:N1*N2]) +class colvar::groupcoordnum + : public colvar::distance +{ +protected: + /// \brief "Cutoff" for isotropic calculation (default) + cvm::real r0; + /// \brief "Cutoff vector" for anisotropic calculation + cvm::rvector r0_vec; + /// \brief Wheter dist/r0 or \vec{dist}*\vec{1/r0_vec} should ne be + /// used + bool b_anisotropic; + /// Integer exponent of the function numerator + int en; + /// Integer exponent of the function denominator + int ed; +public: + /// Constructor + groupcoordnum(std::string const &conf); + virtual ~groupcoordnum() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: hydrogen bond, defined as the product of +/// a colvar::coordnum and 1/2*(1-cos((180-ang)/ang_tol)) +/// (colvarvalue::type_scalar type, range [0:1]) +class colvar::h_bond + : public colvar::cvc +{ +protected: + /// \brief "Cutoff" distance between acceptor and donor + cvm::real r0; + /// Integer exponent of the function numerator + int en; + /// Integer exponent of the function denominator + int ed; +public: + h_bond(std::string const &conf); + /// Constructor for atoms already allocated + h_bond(cvm::atom const &acceptor, + cvm::atom const &donor, + cvm::real r0, int en, int ed); + h_bond(); + virtual ~h_bond() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: alpha helix content of a contiguous +/// segment of 5 or more residues, implemented as a sum of phi/psi +/// dihedral angles and hydrogen bonds (colvarvalue::type_scalar type, +/// range [0:1]) +// class colvar::alpha_dihedrals +// : public colvar::cvc +// { +// protected: + +// /// Alpha-helical reference phi value +// cvm::real phi_ref; + +// /// Alpha-helical reference psi value +// cvm::real psi_ref; + +// /// List of phi dihedral angles +// std::vector phi; + +// /// List of psi dihedral angles +// std::vector psi; + +// /// List of hydrogen bonds +// std::vector hb; + +// public: + +// alpha_dihedrals (std::string const &conf); +// alpha_dihedrals(); +// virtual ~alpha_dihedrals() {} +// virtual void calc_value(); +// virtual void calc_gradients(); +// virtual void apply_force (colvarvalue const &force); +// virtual cvm::real dist2 (colvarvalue const &x1, +// colvarvalue const &x2) const; +// virtual colvarvalue dist2_lgrad (colvarvalue const &x1, +// colvarvalue const &x2) const; +// virtual colvarvalue dist2_rgrad (colvarvalue const &x1, +// colvarvalue const &x2) const; +// }; + + + +/// \brief Colvar component: alpha helix content of a contiguous +/// segment of 5 or more residues, implemented as a sum of Ca-Ca-Ca +/// angles and hydrogen bonds (colvarvalue::type_scalar type, range +/// [0:1]) +class colvar::alpha_angles + : public colvar::cvc +{ +protected: + + /// Reference Calpha-Calpha angle (default: 88 degrees) + cvm::real theta_ref; + + /// Tolerance on the Calpha-Calpha angle + cvm::real theta_tol; + + /// List of Calpha-Calpha angles + std::vector theta; + + /// List of hydrogen bonds + std::vector hb; + + /// Contribution of the hb terms + cvm::real hb_coeff; + +public: + + alpha_angles(std::string const &conf); + alpha_angles(); + virtual ~alpha_angles(); + void calc_value(); + void calc_gradients(); + /// Re-implementation of cvc::collect_gradients() to carry over atomic gradients of sub-cvcs + void collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients); + void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: dihedPC +/// Projection of the config onto a dihedral principal component +/// See e.g. Altis et al., J. Chem. Phys 126, 244111 (2007) +/// Based on a set of 'dihedral' cvcs +class colvar::dihedPC + : public colvar::cvc +{ +protected: + + std::vector theta; + std::vector coeffs; + +public: + + dihedPC(std::string const &conf); + dihedPC(); + virtual ~dihedPC(); + void calc_value(); + void calc_gradients(); + /// Re-implementation of cvc::collect_gradients() to carry over atomic gradients of sub-cvcs + void collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients); + void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: orientation in space of an atom group, +/// with respect to a set of reference coordinates +/// (colvarvalue::type_quaternion type, range +/// [-1:1]x[-1:1]x[-1:1]x[-1:1]) +class colvar::orientation + : public colvar::cvc +{ +protected: + + /// Atom group + cvm::atom_group * atoms; + /// Center of geometry of the group + cvm::atom_pos atoms_cog; + + /// Reference coordinates + std::vector ref_pos; + + /// Shifted atomic positions + std::vector shifted_pos; + + /// Rotation object + cvm::rotation rot; + + /// \brief This is used to remove jumps in the sign of the + /// quaternion, which may be annoying in the colvars trajectory + cvm::quaternion ref_quat; + + /// Rotation derivative + struct rotation_derivative_impl_; + std::unique_ptr rot_deriv_impl; + +public: + + orientation(std::string const &conf); + orientation(); + virtual int init(std::string const &conf); + virtual ~orientation(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: angle of rotation with respect to a set +/// of reference coordinates (colvarvalue::type_scalar type, range +/// [0:PI)) +class colvar::orientation_angle + : public colvar::orientation +{ +public: + + orientation_angle(std::string const &conf); + virtual int init(std::string const &conf); + virtual ~orientation_angle() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: cosine of the angle of rotation with respect to a set +/// of reference coordinates (colvarvalue::type_scalar type, range +/// [-1:1]) +class colvar::orientation_proj + : public colvar::orientation +{ +public: + + orientation_proj(std::string const &conf); + orientation_proj(); + virtual int init(std::string const &conf); + virtual ~orientation_proj() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: projection of the orientation vector onto +/// a predefined axis (colvarvalue::type_scalar type, range [-1:1]) +class colvar::tilt + : public colvar::orientation +{ +protected: + + cvm::rvector axis; + +public: + + tilt(std::string const &conf); + virtual int init(std::string const &conf); + virtual ~tilt() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +/// \brief Colvar component: angle of rotation around a predefined +/// axis (colvarvalue::type_scalar type, range [-PI:PI]) +class colvar::spin_angle + : public colvar::orientation +{ +protected: + + cvm::rvector axis; + +public: + + spin_angle(std::string const &conf); + spin_angle(); + virtual int init(std::string const &conf); + virtual ~spin_angle() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + /// Redefined to handle the 2*PI periodicity + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap(colvarvalue &x_unwrapped) const; +}; + + +class colvar::euler_phi + : public colvar::orientation +{ +public: + euler_phi(std::string const &conf); + euler_phi(); + virtual int init(std::string const &conf); + virtual ~euler_phi() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap(colvarvalue &x_unwrapped) const; +}; + + +class colvar::euler_psi + : public colvar::orientation +{ +public: + euler_psi(std::string const &conf); + euler_psi(); + virtual int init(std::string const &conf); + virtual ~euler_psi() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap(colvarvalue &x_unwrapped) const; +}; + + +class colvar::euler_theta + : public colvar::orientation +{ +public: + euler_theta(std::string const &conf); + euler_theta(); + virtual int init(std::string const &conf); + virtual ~euler_theta() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + // theta angle is a scalar variable and not periodic + // we need to override the virtual functions from orientation + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +/// \brief Colvar component: root mean square deviation (RMSD) of a +/// group with respect to a set of reference coordinates; uses \link +/// colvar::orientation \endlink to calculate the rotation matrix +/// (colvarvalue::type_scalar type, range [0:*)) +class colvar::rmsd + : public colvar::cvc +{ +protected: + + /// Atom group + cvm::atom_group *atoms; + + /// Reference coordinates (for RMSD calculation only) + /// Includes sets with symmetry permutations (n_permutations * n_atoms) + std::vector ref_pos; + + /// Number of permutations of symmetry-related atoms + size_t n_permutations; + + /// Index of the permutation yielding the smallest RMSD (0 for identity) + size_t best_perm_index; +public: + + /// Constructor + rmsd(std::string const &conf); + virtual ~rmsd() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void calc_force_invgrads(); + virtual void calc_Jacobian_derivative(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + + +// \brief Colvar component: flat vector of Cartesian coordinates +// Mostly useful to compute scripted colvar values +class colvar::cartesian + : public colvar::cvc +{ +protected: + /// Atom group + cvm::atom_group *atoms; + /// Which Cartesian coordinates to include + std::vector axes; +public: + cartesian(std::string const &conf); + cartesian(); + virtual ~cartesian() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + +// \brief Colvar component: alch_lambda +// To communicate value with back-end in lambda-dynamics +class colvar::alch_lambda + : public colvar::cvc +{ +protected: + // No atom groups needed +public: + alch_lambda(std::string const &conf); + alch_lambda(); + virtual ~alch_lambda() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +// \brief Colvar component: alch_Flambda +// To communicate force on lambda with back-end in lambda-dynamics +class colvar::alch_Flambda + : public colvar::cvc +{ +protected: + // No atom groups needed +public: + alch_Flambda(std::string const &conf); + alch_Flambda(); + virtual ~alch_Flambda() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + virtual cvm::real dist2(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +class colvar::componentDisabled + : public colvar::cvc +{ +public: + componentDisabled(std::string const & /* conf */) { + cvm::error("Error: this component is not enabled in the current build; please see https://colvars.github.io/README-c++11.html"); + } + virtual ~componentDisabled() {} + virtual void calc_value() {} + virtual void calc_gradients() {} + virtual void apply_force(colvarvalue const & /* force */) {} +}; + + + +class colvar::CartesianBasedPath + : public colvar::cvc +{ +protected: + virtual void computeDistanceBetweenReferenceFrames(std::vector& result); + virtual void computeDistanceToReferenceFrames(std::vector& result); + /// Selected atoms + cvm::atom_group *atoms; + /// Fitting options + bool has_user_defined_fitting; + /// Reference frames + std::vector> reference_frames; + std::vector> reference_fitting_frames; + /// Atom groups for RMSD calculation together with reference frames + std::vector comp_atoms; + /// Total number of reference frames + size_t total_reference_frames; +public: + CartesianBasedPath(std::string const &conf); + virtual ~CartesianBasedPath(); + virtual void calc_value() = 0; + virtual void apply_force(colvarvalue const &force) = 0; +}; + +/// \brief Colvar component: alternative path collective variable using geometry, variable s +/// For more information see https://plumed.github.io/doc-v2.5/user-doc/html/_p_a_t_h.html +/// Diaz Leines, G.; Ensing, B. Path Finding on High-Dimensional Free Energy Landscapes. Phys. Rev. Lett. 2012, 109 (2), 020601. https://doi.org/10.1103/PhysRevLett.109.020601. +class colvar::gspath + : public colvar::CartesianBasedPath, public GeometricPathCV::GeometricPathBase +{ +private: + // Optimal rotation for compute v3 + cvm::rotation rot_v3; +protected: + virtual void prepareVectors(); + virtual void updateDistanceToReferenceFrames(); +public: + gspath(std::string const &conf); + virtual ~gspath() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + + +/// \brief Colvar component: alternative path collective variable using geometry, variable z +/// This should be merged with gspath in the same class by class inheritance or something else +class colvar::gzpath + : public colvar::CartesianBasedPath, public GeometricPathCV::GeometricPathBase +{ +private: + // Optimal rotation for compute v3, v4 + cvm::rotation rot_v3; + cvm::rotation rot_v4; +protected: + virtual void prepareVectors(); + virtual void updateDistanceToReferenceFrames(); +public: + gzpath(std::string const &conf); + virtual ~gzpath() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + +/// Current only linear combination of sub-CVCs is available +class colvar::linearCombination + : public colvar::cvc +{ +protected: + /// Sub-colvar components + std::vector cv; + /// If all sub-cvs use explicit gradients then we also use it + bool use_explicit_gradients; +protected: + cvm::real getPolynomialFactorOfCVGradient(size_t i_cv) const; +public: + linearCombination(std::string const &conf); + virtual ~linearCombination(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + +/// custom expression of colvars +class colvar::customColvar + : public colvar::linearCombination +{ +protected: + bool use_custom_function; +#ifdef LEPTON + /// Vector of evaluators for custom functions using Lepton + std::vector value_evaluators; + /// Vector of evaluators for gradients of custom functions + std::vector gradient_evaluators; + /// Vector of references to cvc values to be passed to Lepton evaluators + std::vector value_eval_var_refs; + std::vector grad_eval_var_refs; + /// Unused value that is written to when a variable simplifies out of a Lepton expression + double dev_null; +#endif +public: + customColvar(std::string const &conf); + virtual ~customColvar(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + +class colvar::CVBasedPath + : public colvar::cvc +{ +protected: + /// Sub-colvar components + std::vector cv; + /// Reference colvar values from path + std::vector> ref_cv; + /// If all sub-cvs use explicit gradients then we also use it + bool use_explicit_gradients; + /// Total number of reference frames + size_t total_reference_frames; +protected: + virtual void computeDistanceToReferenceFrames(std::vector& result); + /// Helper function to determine the distance between reference frames + virtual void computeDistanceBetweenReferenceFrames(std::vector& result) const; + cvm::real getPolynomialFactorOfCVGradient(size_t i_cv) const; +public: + CVBasedPath(std::string const &conf); + virtual ~CVBasedPath(); + virtual void calc_value() = 0; + virtual void apply_force(colvarvalue const &force) = 0; +}; + + +/// \brief Colvar component: alternative path collective variable using geometry, variable s +/// Allow any combination of existing (scalar) CVs +/// For more information see https://plumed.github.io/doc-v2.5/user-doc/html/_p_a_t_h.html +/// Diaz Leines, G.; Ensing, B. Path Finding on High-Dimensional Free Energy Landscapes. Phys. Rev. Lett. 2012, 109 (2), 020601. https://doi.org/10.1103/PhysRevLett.109.020601. +class colvar::gspathCV + : public colvar::CVBasedPath, public GeometricPathCV::GeometricPathBase +{ +protected: + virtual void updateDistanceToReferenceFrames(); + virtual void prepareVectors(); +public: + gspathCV(std::string const &conf); + virtual ~gspathCV(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + + +class colvar::gzpathCV + : public colvar::CVBasedPath, public GeometricPathCV::GeometricPathBase +{ +protected: + virtual void updateDistanceToReferenceFrames(); + virtual void prepareVectors(); +public: + gzpathCV(std::string const &conf); + virtual ~gzpathCV(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + +struct ArithmeticPathImpl; + +class colvar::aspath + : public colvar::CartesianBasedPath +{ +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; +public: + aspath(std::string const &conf); + virtual ~aspath(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + +class colvar::azpath + : public colvar::CartesianBasedPath +{ +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; +public: + azpath(std::string const &conf); + virtual ~azpath(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + +class colvar::aspathCV + : public colvar::CVBasedPath +{ +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; +public: + aspathCV(std::string const &conf); + virtual ~aspathCV(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + +class colvar::azpathCV + : public colvar::CVBasedPath +{ +private: + std::unique_ptr impl_; + friend struct ArithmeticPathImpl; +public: + azpathCV(std::string const &conf); + virtual ~azpathCV(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + +// forward declaration +namespace neuralnetworkCV { + class neuralNetworkCompute; +} + + +class colvar::neuralNetwork + : public linearCombination +{ +protected: + /// actual computation happens in neuralnetworkCV::neuralNetworkCompute + std::unique_ptr nn; + /// the index of nn output components + size_t m_output_index; +public: + neuralNetwork(std::string const &conf); + virtual ~neuralNetwork(); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); +}; + + +// \brief Colvar component: total value of a scalar map +// (usually implemented as a grid by the simulation engine) +class colvar::map_total + : public colvar::cvc +{ +public: + + map_total(); + map_total(std::string const &conf); + virtual ~map_total() {} + virtual int init(std::string const &conf); + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force(colvarvalue const &force); + +protected: + + /// String identifier of the map object (as used by the simulation engine) + std::string volmap_name; + + /// Numeric identifier of the map object (as used by the simulation engine) + int volmap_id; + + /// Index of the map objet in the proxy arrays + int volmap_index; + + /// Group of atoms selected internally (optional) + cvm::atom_group *atoms; + + /// Weights assigned to each atom (default: uniform weights) + std::vector atom_weights; +}; + + + +// metrics functions for cvc implementations + +// simple definitions of the distance functions; these are useful only +// for optimization (the type check performed in the default +// colvarcomp functions is skipped) + +// definitions assuming the scalar type + +#define simple_scalar_dist_functions(TYPE) \ + \ + \ + cvm::real colvar::TYPE::dist2(colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return (x1.real_value - x2.real_value)*(x1.real_value - x2.real_value); \ + } \ + \ + \ + colvarvalue colvar::TYPE::dist2_lgrad(colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return 2.0 * (x1.real_value - x2.real_value); \ + } \ + \ + \ + colvarvalue colvar::TYPE::dist2_rgrad(colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return this->dist2_lgrad(x2, x1); \ + } \ + + +#endif diff --git a/src/external/colvars/colvarcomp_alchlambda.cpp b/src/external/colvars/colvarcomp_alchlambda.cpp new file mode 100644 index 00000000000..9f18bec7763 --- /dev/null +++ b/src/external/colvars/colvarcomp_alchlambda.cpp @@ -0,0 +1,106 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + + +colvar::alch_lambda::alch_lambda(std::string const &conf) + : cvc(conf) +{ + set_function_type("alchLambda"); + + disable(f_cvc_explicit_gradient); + disable(f_cvc_gradient); + + x.type(colvarvalue::type_scalar); + // Query initial value from back-end + cvm::proxy->get_alch_lambda(&x.real_value); +} + + +void colvar::alch_lambda::calc_value() +{ + // Special workflow: + // at the beginning of the timestep we get a force instead of calculating the value + + cvm::proxy->get_dE_dlambda(&ft.real_value); + ft.real_value *= -1.0; // Energy derivative to force + + // Include any force due to bias on Flambda + ft.real_value += cvm::proxy->indirect_lambda_biasing_force; + cvm::proxy->indirect_lambda_biasing_force = 0.0; +} + + +void colvar::alch_lambda::calc_gradients() +{ +} + + +void colvar::alch_lambda::apply_force(colvarvalue const & /* force */) +{ + // new value will be cached and sent at end of timestep + cvm::proxy->set_alch_lambda(x.real_value); +} + +simple_scalar_dist_functions(alch_lambda) + + + +colvar::alch_Flambda::alch_Flambda(std::string const &conf) + : cvc(conf) +{ + set_function_type("alch_Flambda"); + + disable(f_cvc_explicit_gradient); + disable(f_cvc_gradient); + + x.type(colvarvalue::type_scalar); +} + + +void colvar::alch_Flambda::calc_value() +{ + // Special workflow: + // at the beginning of the timestep we get a force instead of calculating the value + + // Query initial value from back-end + cvm::proxy->get_dE_dlambda(&x.real_value); + x.real_value *= -1.0; // Energy derivative to force +} + + +void colvar::alch_Flambda::calc_gradients() +{ +} + + +void colvar::alch_Flambda::apply_force(colvarvalue const &force) +{ + // Convert force on Flambda to force on dE/dlambda + cvm::real f = -1.0 * force.real_value; + // Send scalar force to back-end, which will distribute it onto atoms + cvm::proxy->apply_force_dE_dlambda(&f); + + // Propagate force on Flambda to lambda internally + cvm::real d2E_dlambda2; + cvm::proxy->get_d2E_dlambda2(&d2E_dlambda2); + + // This accumulates a force, it needs to be zeroed when taken into account by lambda + cvm::proxy->indirect_lambda_biasing_force += d2E_dlambda2 * f; +} + +simple_scalar_dist_functions(alch_Flambda) diff --git a/src/external/colvars/colvarcomp_angles.cpp b/src/external/colvars/colvarcomp_angles.cpp new file mode 100644 index 00000000000..6e02f2c9c2c --- /dev/null +++ b/src/external/colvars/colvarcomp_angles.cpp @@ -0,0 +1,641 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvar.h" +#include "colvarcomp.h" + + +colvar::angle::angle(std::string const &conf) + : cvc(conf) +{ + set_function_type("angle"); + init_as_angle(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + group3 = parse_group(conf, "group3"); + + init_total_force_params(conf); +} + + +colvar::angle::angle(cvm::atom const &a1, + cvm::atom const &a2, + cvm::atom const &a3) +{ + set_function_type("angle"); + init_as_angle(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + + group1 = new cvm::atom_group(std::vector(1, a1)); + group2 = new cvm::atom_group(std::vector(1, a2)); + group3 = new cvm::atom_group(std::vector(1, a3)); + register_atom_group(group1); + register_atom_group(group2); + register_atom_group(group3); +} + + +void colvar::angle::calc_value() +{ + cvm::atom_pos const g1_pos = group1->center_of_mass(); + cvm::atom_pos const g2_pos = group2->center_of_mass(); + cvm::atom_pos const g3_pos = group3->center_of_mass(); + + r21 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g1_pos) : + g1_pos - g2_pos; + r21l = r21.norm(); + r23 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g3_pos) : + g3_pos - g2_pos; + r23l = r23.norm(); + + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + + x.real_value = (180.0/PI) * cvm::acos(cos_theta); +} + + +void colvar::angle::calc_gradients() +{ + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + cvm::real const dxdcos = -1.0 / cvm::sqrt(1.0 - cos_theta*cos_theta); + + dxdr1 = (180.0/PI) * dxdcos * + (1.0/r21l) * ( r23/r23l + (-1.0) * cos_theta * r21/r21l ); + + dxdr3 = (180.0/PI) * dxdcos * + (1.0/r23l) * ( r21/r21l + (-1.0) * cos_theta * r23/r23l ); + + group1->set_weighted_gradient(dxdr1); + group2->set_weighted_gradient((dxdr1 + dxdr3) * (-1.0)); + group3->set_weighted_gradient(dxdr3); +} + + +void colvar::angle::calc_force_invgrads() +{ + // This uses a force measurement on groups 1 and 3 only + // to keep in line with the implicit variable change used to + // evaluate the Jacobian term (essentially polar coordinates + // centered on group2, which means group2 is kept fixed + // when propagating changes in the angle) + + if (is_enabled(f_cvc_one_site_total_force)) { + group1->read_total_forces(); + cvm::real norm_fact = 1.0 / dxdr1.norm2(); + ft.real_value = norm_fact * dxdr1 * group1->total_force(); + } else { + group1->read_total_forces(); + group3->read_total_forces(); + cvm::real norm_fact = 1.0 / (dxdr1.norm2() + dxdr3.norm2()); + ft.real_value = norm_fact * ( dxdr1 * group1->total_force() + + dxdr3 * group3->total_force()); + } + return; +} + + +void colvar::angle::calc_Jacobian_derivative() +{ + // det(J) = (2 pi) r^2 * sin(theta) + // hence Jd = cot(theta) + const cvm::real theta = x.real_value * PI / 180.0; + jd = PI / 180.0 * (theta != 0.0 ? cvm::cos(theta) / cvm::sin(theta) : 0.0); +} + + +void colvar::angle::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); + + if (!group3->noforce) + group3->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(angle) + + + +colvar::dipole_angle::dipole_angle(std::string const &conf) + : cvc(conf) +{ + set_function_type("dipoleAngle"); + init_as_angle(); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + group3 = parse_group(conf, "group3"); + + init_total_force_params(conf); +} + + +colvar::dipole_angle::dipole_angle(cvm::atom const &a1, + cvm::atom const &a2, + cvm::atom const &a3) +{ + set_function_type("dipoleAngle"); + init_as_angle(); + + group1 = new cvm::atom_group(std::vector(1, a1)); + group2 = new cvm::atom_group(std::vector(1, a2)); + group3 = new cvm::atom_group(std::vector(1, a3)); + register_atom_group(group1); + register_atom_group(group2); + register_atom_group(group3); +} + + +colvar::dipole_angle::dipole_angle() +{ + set_function_type("dipoleAngle"); + init_as_angle(); +} + + +void colvar::dipole_angle::calc_value() +{ + cvm::atom_pos const g1_pos = group1->center_of_mass(); + cvm::atom_pos const g2_pos = group2->center_of_mass(); + cvm::atom_pos const g3_pos = group3->center_of_mass(); + + group1->calc_dipole(g1_pos); + + r21 = group1->dipole(); + r21l = r21.norm(); + r23 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g3_pos) : + g3_pos - g2_pos; + r23l = r23.norm(); + + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + + x.real_value = (180.0/PI) * cvm::acos(cos_theta); +} + +//to be implemented +//void colvar::dipole_angle::calc_force_invgrads(){} +//void colvar::dipole_angle::calc_Jacobian_derivative(){} + +void colvar::dipole_angle::calc_gradients() +{ + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + cvm::real const dxdcos = -1.0 / cvm::sqrt(1.0 - cos_theta*cos_theta); + + dxdr1 = (180.0/PI) * dxdcos * + (1.0/r21l)* (r23/r23l + (-1.0) * cos_theta * r21/r21l ); + + dxdr3 = (180.0/PI) * dxdcos * + (1.0/r23l) * ( r21/r21l + (-1.0) * cos_theta * r23/r23l ); + + //this auxiliar variables are to avoid numerical errors inside "for" + double aux1 = group1->total_charge/group1->total_mass; + // double aux2 = group2->total_charge/group2->total_mass; + // double aux3 = group3->total_charge/group3->total_mass; + + size_t i; + for (i = 0; i < group1->size(); i++) { + (*group1)[i].grad =((*group1)[i].charge + (-1)* (*group1)[i].mass * aux1) * (dxdr1); + } + + for (i = 0; i < group2->size(); i++) { + (*group2)[i].grad = ((*group2)[i].mass/group2->total_mass)* dxdr3 * (-1.0); + } + + for (i = 0; i < group3->size(); i++) { + (*group3)[i].grad =((*group3)[i].mass/group3->total_mass) * (dxdr3); + } +} + + +void colvar::dipole_angle::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); + + if (!group3->noforce) + group3->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(dipole_angle) + + + +colvar::dihedral::dihedral(std::string const &conf) + : cvc(conf) +{ + set_function_type("dihedral"); + init_as_periodic_angle(); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + group3 = parse_group(conf, "group3"); + group4 = parse_group(conf, "group4"); + + init_total_force_params(conf); +} + + +colvar::dihedral::dihedral(cvm::atom const &a1, + cvm::atom const &a2, + cvm::atom const &a3, + cvm::atom const &a4) +{ + set_function_type("dihedral"); + init_as_periodic_angle(); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + + b_1site_force = false; + + group1 = new cvm::atom_group(std::vector(1, a1)); + group2 = new cvm::atom_group(std::vector(1, a2)); + group3 = new cvm::atom_group(std::vector(1, a3)); + group4 = new cvm::atom_group(std::vector(1, a4)); + register_atom_group(group1); + register_atom_group(group2); + register_atom_group(group3); + register_atom_group(group4); +} + + +colvar::dihedral::dihedral() +{ + set_function_type("dihedral"); + init_as_periodic_angle(); + enable(f_cvc_periodic); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); +} + + +void colvar::dihedral::calc_value() +{ + cvm::atom_pos const g1_pos = group1->center_of_mass(); + cvm::atom_pos const g2_pos = group2->center_of_mass(); + cvm::atom_pos const g3_pos = group3->center_of_mass(); + cvm::atom_pos const g4_pos = group4->center_of_mass(); + + // Usual sign convention: r12 = r2 - r1 + r12 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g1_pos, g2_pos) : + g2_pos - g1_pos; + r23 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g2_pos, g3_pos) : + g3_pos - g2_pos; + r34 = is_enabled(f_cvc_pbc_minimum_image) ? + cvm::position_distance(g3_pos, g4_pos) : + g4_pos - g3_pos; + + cvm::rvector const n1 = cvm::rvector::outer(r12, r23); + cvm::rvector const n2 = cvm::rvector::outer(r23, r34); + + cvm::real const cos_phi = n1 * n2; + cvm::real const sin_phi = n1 * r34 * r23.norm(); + + x.real_value = (180.0/PI) * cvm::atan2(sin_phi, cos_phi); + this->wrap(x); +} + + +void colvar::dihedral::calc_gradients() +{ + cvm::rvector A = cvm::rvector::outer(r12, r23); + cvm::real rA = A.norm(); + cvm::rvector B = cvm::rvector::outer(r23, r34); + cvm::real rB = B.norm(); + cvm::rvector C = cvm::rvector::outer(r23, A); + cvm::real rC = C.norm(); + + cvm::real const cos_phi = (A*B)/(rA*rB); + cvm::real const sin_phi = (C*B)/(rC*rB); + + cvm::rvector f1, f2, f3; + + rB = 1.0/rB; + B *= rB; + + if (cvm::fabs(sin_phi) > 0.1) { + rA = 1.0/rA; + A *= rA; + cvm::rvector const dcosdA = rA*(cos_phi*A-B); + cvm::rvector const dcosdB = rB*(cos_phi*B-A); + // rA = 1.0; + + cvm::real const K = (1.0/sin_phi) * (180.0/PI); + + f1 = K * cvm::rvector::outer(r23, dcosdA); + f3 = K * cvm::rvector::outer(dcosdB, r23); + f2 = K * (cvm::rvector::outer(dcosdA, r12) + + cvm::rvector::outer(r34, dcosdB)); + } + else { + rC = 1.0/rC; + C *= rC; + cvm::rvector const dsindC = rC*(sin_phi*C-B); + cvm::rvector const dsindB = rB*(sin_phi*B-C); + // rC = 1.0; + + cvm::real const K = (-1.0/cos_phi) * (180.0/PI); + + f1.x = K*((r23.y*r23.y + r23.z*r23.z)*dsindC.x + - r23.x*r23.y*dsindC.y + - r23.x*r23.z*dsindC.z); + f1.y = K*((r23.z*r23.z + r23.x*r23.x)*dsindC.y + - r23.y*r23.z*dsindC.z + - r23.y*r23.x*dsindC.x); + f1.z = K*((r23.x*r23.x + r23.y*r23.y)*dsindC.z + - r23.z*r23.x*dsindC.x + - r23.z*r23.y*dsindC.y); + + f3 = cvm::rvector::outer(dsindB, r23); + f3 *= K; + + f2.x = K*(-(r23.y*r12.y + r23.z*r12.z)*dsindC.x + +(2.0*r23.x*r12.y - r12.x*r23.y)*dsindC.y + +(2.0*r23.x*r12.z - r12.x*r23.z)*dsindC.z + +dsindB.z*r34.y - dsindB.y*r34.z); + f2.y = K*(-(r23.z*r12.z + r23.x*r12.x)*dsindC.y + +(2.0*r23.y*r12.z - r12.y*r23.z)*dsindC.z + +(2.0*r23.y*r12.x - r12.y*r23.x)*dsindC.x + +dsindB.x*r34.z - dsindB.z*r34.x); + f2.z = K*(-(r23.x*r12.x + r23.y*r12.y)*dsindC.z + +(2.0*r23.z*r12.x - r12.z*r23.x)*dsindC.x + +(2.0*r23.z*r12.y - r12.z*r23.y)*dsindC.y + +dsindB.y*r34.x - dsindB.x*r34.y); + } + + group1->set_weighted_gradient(-f1); + group2->set_weighted_gradient(-f2 + f1); + group3->set_weighted_gradient(-f3 + f2); + group4->set_weighted_gradient(f3); +} + + +void colvar::dihedral::calc_force_invgrads() +{ + cvm::rvector const u12 = r12.unit(); + cvm::rvector const u23 = r23.unit(); + cvm::rvector const u34 = r34.unit(); + + cvm::real const d12 = r12.norm(); + cvm::real const d34 = r34.norm(); + + cvm::rvector const cross1 = (cvm::rvector::outer(u23, u12)).unit(); + cvm::rvector const cross4 = (cvm::rvector::outer(u23, u34)).unit(); + + cvm::real const dot1 = u23 * u12; + cvm::real const dot4 = u23 * u34; + + cvm::real const fact1 = d12 * cvm::sqrt(1.0 - dot1 * dot1); + cvm::real const fact4 = d34 * cvm::sqrt(1.0 - dot4 * dot4); + + group1->read_total_forces(); + if (is_enabled(f_cvc_one_site_total_force)) { + // This is only measuring the force on group 1 + ft.real_value = PI/180.0 * fact1 * (cross1 * group1->total_force()); + } else { + // Default case: use groups 1 and 4 + group4->read_total_forces(); + ft.real_value = PI/180.0 * 0.5 * (fact1 * (cross1 * group1->total_force()) + + fact4 * (cross4 * group4->total_force())); + } +} + + +void colvar::dihedral::calc_Jacobian_derivative() +{ + // With this choice of inverse gradient ("internal coordinates"), Jacobian correction is 0 + jd = 0.0; +} + + +void colvar::dihedral::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); + + if (!group3->noforce) + group3->apply_colvar_force(force.real_value); + + if (!group4->noforce) + group4->apply_colvar_force(force.real_value); +} + + +// metrics functions for cvc implementations with a periodicity + +cvm::real colvar::dihedral::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return diff * diff; +} + + +colvarvalue colvar::dihedral::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return 2.0 * diff; +} + + +colvarvalue colvar::dihedral::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return (-2.0) * diff; +} + + +void colvar::dihedral::wrap(colvarvalue &x_unwrapped) const +{ + if ((x_unwrapped.real_value - wrap_center) >= 180.0) { + x_unwrapped.real_value -= 360.0; + return; + } + + if ((x_unwrapped.real_value - wrap_center) < -180.0) { + x_unwrapped.real_value += 360.0; + return; + } +} + + +colvar::polar_theta::polar_theta(std::string const &conf) + : cvc(conf) +{ + set_function_type("polarTheta"); + enable(f_cvc_com_based); + + atoms = parse_group(conf, "atoms"); + init_total_force_params(conf); + x.type(colvarvalue::type_scalar); +} + + +colvar::polar_theta::polar_theta() +{ + set_function_type("polarTheta"); + x.type(colvarvalue::type_scalar); +} + + +void colvar::polar_theta::calc_value() +{ + cvm::rvector pos = atoms->center_of_mass(); + r = atoms->center_of_mass().norm(); + // Internal values of theta and phi are radians + theta = (r > 0.) ? cvm::acos(pos.z / r) : 0.; + phi = cvm::atan2(pos.y, pos.x); + x.real_value = (180.0/PI) * theta; +} + + +void colvar::polar_theta::calc_gradients() +{ + if (r == 0.) + atoms->set_weighted_gradient(cvm::rvector(0., 0., 0.)); + else + atoms->set_weighted_gradient(cvm::rvector( + (180.0/PI) * cvm::cos(theta) * cvm::cos(phi) / r, + (180.0/PI) * cvm::cos(theta) * cvm::sin(phi) / r, + (180.0/PI) * -cvm::sin(theta) / r)); +} + + +void colvar::polar_theta::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(polar_theta) + + +colvar::polar_phi::polar_phi(std::string const &conf) + : cvc(conf) +{ + set_function_type("polarPhi"); + init_as_periodic_angle(); + enable(f_cvc_com_based); + + atoms = parse_group(conf, "atoms"); + init_total_force_params(conf); +} + + +colvar::polar_phi::polar_phi() +{ + set_function_type("polarPhi"); + init_as_periodic_angle(); +} + + +void colvar::polar_phi::calc_value() +{ + cvm::rvector pos = atoms->center_of_mass(); + r = atoms->center_of_mass().norm(); + // Internal values of theta and phi are radians + theta = (r > 0.) ? cvm::acos(pos.z / r) : 0.; + phi = cvm::atan2(pos.y, pos.x); + x.real_value = (180.0/PI) * phi; +} + + +void colvar::polar_phi::calc_gradients() +{ + atoms->set_weighted_gradient(cvm::rvector( + (180.0/PI) * -cvm::sin(phi) / (r*cvm::sin(theta)), + (180.0/PI) * cvm::cos(phi) / (r*cvm::sin(theta)), + 0.)); +} + + +void colvar::polar_phi::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +// Same as dihedral, for polar_phi + +cvm::real colvar::polar_phi::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return diff * diff; +} + + +colvarvalue colvar::polar_phi::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return 2.0 * diff; +} + + +colvarvalue colvar::polar_phi::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return (-2.0) * diff; +} + + +void colvar::polar_phi::wrap(colvarvalue &x_unwrapped) const +{ + if ((x_unwrapped.real_value - wrap_center) >= 180.0) { + x_unwrapped.real_value -= 360.0; + return; + } + + if ((x_unwrapped.real_value - wrap_center) < -180.0) { + x_unwrapped.real_value += 360.0; + return; + } + + return; +} diff --git a/src/external/colvars/colvarcomp_apath.cpp b/src/external/colvars/colvarcomp_apath.cpp new file mode 100644 index 00000000000..38c570add86 --- /dev/null +++ b/src/external/colvars/colvarcomp_apath.cpp @@ -0,0 +1,422 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include +#include +#include +#include + +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include "colvar_arithmeticpath.h" + + +struct ArithmeticPathImpl: public ArithmeticPathCV::ArithmeticPathBase { + std::vector> frame_element_distances; + std::vector> dsdx; + std::vector> dzdx; + template + void updateCartesianDistanceToReferenceFrames(T* obj) { + for (size_t i_frame = 0; i_frame < obj->reference_frames.size(); ++i_frame) { + for (size_t i_atom = 0; i_atom < obj->atoms->size(); ++i_atom) { + frame_element_distances[i_frame][i_atom] = (*(obj->comp_atoms[i_frame]))[i_atom].pos - obj->reference_frames[i_frame][i_atom]; + } + } + } + template + void updateCVDistanceToReferenceFrames(T* obj) { + for (size_t i_cv = 0; i_cv < obj->cv.size(); ++i_cv) { + obj->cv[i_cv]->calc_value(); + } + for (size_t i_frame = 0; i_frame < obj->ref_cv.size(); ++i_frame) { + for (size_t i_cv = 0; i_cv < obj->cv.size(); ++i_cv) { + colvarvalue ref_cv_value(obj->ref_cv[i_frame][i_cv]); + colvarvalue current_cv_value(obj->cv[i_cv]->value()); + if (current_cv_value.type() == colvarvalue::type_scalar) { + frame_element_distances[i_frame][i_cv] = 0.5 * obj->cv[i_cv]->dist2_lgrad(obj->cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, obj->cv[i_cv]->sup_np)), ref_cv_value.real_value); + } else { + frame_element_distances[i_frame][i_cv] = 0.5 * obj->cv[i_cv]->dist2_lgrad(obj->cv[i_cv]->sup_coeff * current_cv_value, ref_cv_value); + } + } + } + } + ArithmeticPathImpl(size_t p_num_elements, size_t p_total_frames, cvm::real p_lambda, const std::vector& p_weights) { + ArithmeticPathCV::ArithmeticPathBase::initialize(p_num_elements, p_total_frames, p_lambda, p_weights); + frame_element_distances.resize(p_total_frames, std::vector(p_num_elements, colvarvalue(colvarvalue::Type::type_notset))); + dsdx.resize(p_total_frames, std::vector(p_num_elements, colvarvalue(colvarvalue::Type::type_notset))); + dzdx.resize(p_total_frames, std::vector(p_num_elements, colvarvalue(colvarvalue::Type::type_notset))); + } + cvm::real get_lambda() const {return lambda;} + cvm::real compute_s() { + cvm::real s; + computeValue(frame_element_distances, &s, nullptr); + return s; + } + cvm::real compute_z() { + cvm::real z; + computeValue(frame_element_distances, nullptr, &z); + return z; + } + void compute_s_derivatives() { + computeDerivatives(frame_element_distances, &dsdx, nullptr); + } + void compute_z_derivatives() { + computeDerivatives(frame_element_distances, nullptr, &dzdx); + } + // for debug gradients of implicit sub CVs + template + colvarvalue compute_s_analytical_derivative_ij(size_t i, size_t j, cvm::real eps, T* obj) const { + ArithmeticPathImpl tmp_left(*this), tmp_right(*this); + const size_t value_size = frame_element_distances[i][j].size(); + colvarvalue result(frame_element_distances[i][j].type()); + colvarvalue ref_cv_value(obj->ref_cv[i][j]); + for (size_t k = 0; k < value_size; ++k) { + // get the current CV value + colvarvalue current_cv_value(obj->cv[j]->value()); + // save the old values in frame element distance matrices + const auto saved_left = tmp_left.frame_element_distances[i][j][k]; + const auto saved_right = tmp_right.frame_element_distances[i][j][k]; + // update frame element distance matrices + if (current_cv_value.type() == colvarvalue::type_scalar) { + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value - eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value + eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + } else { + current_cv_value[k] -= eps; + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + current_cv_value[k] += eps + eps; + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + } + const cvm::real s_left = tmp_left.compute_s(); + const cvm::real s_right = tmp_right.compute_s(); + result[k] = (s_right - s_left) / (2.0 * eps); + tmp_left.frame_element_distances[i][j][k] = saved_left; + tmp_right.frame_element_distances[i][j][k] = saved_right; + } + return result; + } + template + colvarvalue compute_z_analytical_derivative_ij(size_t i, size_t j, cvm::real eps, T* obj) const { + ArithmeticPathImpl tmp_left(*this), tmp_right(*this); + const size_t value_size = frame_element_distances[i][j].size(); + colvarvalue result(frame_element_distances[i][j].type()); + colvarvalue ref_cv_value(obj->ref_cv[i][j]); + for (size_t k = 0; k < value_size; ++k) { + // get the current CV value + colvarvalue current_cv_value(obj->cv[j]->value()); + // save the old values in frame element distance matrices + const auto saved_left = tmp_left.frame_element_distances[i][j][k]; + const auto saved_right = tmp_right.frame_element_distances[i][j][k]; + // update frame element distance matrices + if (current_cv_value.type() == colvarvalue::type_scalar) { + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value - eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * (cvm::pow(current_cv_value.real_value + eps, obj->cv[j]->sup_np)), ref_cv_value.real_value); + } else { + current_cv_value[k] -= eps; + tmp_left.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + current_cv_value[k] += eps + eps; + tmp_right.frame_element_distances[i][j] = 0.5 * obj->cv[j]->dist2_lgrad(obj->cv[j]->sup_coeff * current_cv_value, ref_cv_value); + } + const cvm::real z_left = tmp_left.compute_z(); + const cvm::real z_right = tmp_right.compute_z(); + result[k] = (z_right - z_left) / (2.0 * eps); + tmp_left.frame_element_distances[i][j][k] = saved_left; + tmp_right.frame_element_distances[i][j][k] = saved_right; + } + return result; + } +}; + +colvar::aspath::aspath(std::string const &conf): CartesianBasedPath(conf) { + function_type = "aspath"; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + x.type(colvarvalue::type_scalar); + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + const size_t num_atoms = atoms->size(); + std::vector p_weights(num_atoms, std::sqrt(1.0 / num_atoms)); + // ArithmeticPathCV::ArithmeticPathBase::initialize(num_atoms, total_reference_frames, p_lambda, reference_frames[0], p_weights); + impl_ = std::unique_ptr(new ArithmeticPathImpl(num_atoms, total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); +} + +colvar::aspath::~aspath() {} + +void colvar::aspath::calc_value() { + if (impl_->get_lambda() < 0) { + // this implies that the user may not set a valid lambda value + // so recompute it by the suggested value in Parrinello's paper + cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); + cvm::log("This component (aspath) will recompute a value for lambda following the suggestion in the origin paper.\n"); + std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); + computeDistanceBetweenReferenceFrames(rmsd_between_refs); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); + } + impl_->updateCartesianDistanceToReferenceFrames(this); + x = impl_->compute_s(); +} + +void colvar::aspath::calc_gradients() { + impl_->compute_s_derivatives(); + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + (*(comp_atoms[i_frame]))[i_atom].grad += impl_->dsdx[i_frame][i_atom]; + } + } +} + +void colvar::aspath::apply_force(colvarvalue const &force) { + cvm::real const &F = force.real_value; + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + (*(comp_atoms[i_frame])).apply_colvar_force(F); + } +} + +colvar::azpath::azpath(std::string const &conf): CartesianBasedPath(conf) { + function_type = "azpath"; + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + x.type(colvarvalue::type_scalar); + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + const size_t num_atoms = atoms->size(); + std::vector p_weights(num_atoms, std::sqrt(1.0 / num_atoms)); + impl_ = std::unique_ptr(new ArithmeticPathImpl(num_atoms, total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); +} + +colvar::azpath::~azpath() {} + +void colvar::azpath::calc_value() { + if (impl_->get_lambda() < 0) { + // this implies that the user may not set a valid lambda value + // so recompute it by the suggested value in Parrinello's paper + cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); + cvm::log("This component (azpath) will recompute a value for lambda following the suggestion in the origin paper.\n"); + std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); + computeDistanceBetweenReferenceFrames(rmsd_between_refs); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); + } + impl_->updateCartesianDistanceToReferenceFrames(this); + x = impl_->compute_z(); +} + +void colvar::azpath::calc_gradients() { + impl_->compute_z_derivatives(); + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + (*(comp_atoms[i_frame]))[i_atom].grad += impl_->dzdx[i_frame][i_atom]; + } + } +} + +void colvar::azpath::apply_force(colvarvalue const &force) { + cvm::real const &F = force.real_value; + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + (*(comp_atoms[i_frame])).apply_colvar_force(F); + } +} + +colvar::aspathCV::aspathCV(std::string const &conf): CVBasedPath(conf) { + set_function_type("aspathCV"); + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + std::vector p_weights(cv.size(), 1.0); + get_keyval(conf, "weights", p_weights, std::vector(cv.size(), 1.0)); + x.type(colvarvalue::type_scalar); + use_explicit_gradients = true; + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + impl_ = std::unique_ptr(new ArithmeticPathImpl(cv.size(), total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + use_explicit_gradients = false; + } + cvm::log(std::string("The weight of CV ") + cvm::to_str(i_cv) + std::string(" is ") + cvm::to_str(p_weights[i_cv]) + std::string("\n")); + } +} + +colvar::aspathCV::~aspathCV() {} + +void colvar::aspathCV::calc_value() { + if (impl_->get_lambda() < 0) { + // this implies that the user may not set a valid lambda value + // so recompute it by the suggested value in Parrinello's paper + cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); + cvm::log("This component (aspathCV) will recompute a value for lambda following the suggestion in the origin paper.\n"); + std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); + computeDistanceBetweenReferenceFrames(rmsd_between_refs); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); + } + impl_->updateCVDistanceToReferenceFrames(this); + x = impl_->compute_s(); +} + +void colvar::aspathCV::calc_gradients() { + impl_->compute_s_derivatives(); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_gradients(); + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + // sum up derivatives with respect to all frames + for (size_t m_frame = 0; m_frame < impl_->dsdx.size(); ++m_frame) { + // dsdx is the derivative of s with respect to the m-th frame + grad += impl_->dsdx[m_frame][i_cv]; + } + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = grad[j_elem] * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + } + } + } + } + } +} + +void colvar::aspathCV::apply_force(colvarvalue const &force) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dsdx.size(); ++m_frame) { + // dsdx is the derivative of s with respect to the m-th frame + grad += impl_->dsdx[m_frame][i_cv]; + } + grad *= factor_polynomial; + cv[i_cv]->apply_force(force.real_value * grad); + // try my best to debug gradients even if the sub-CVs do not have explicit gradients + if (is_enabled(f_cvc_debug_gradient)) { + cvm::log("Debugging gradients for " + description + + " with respect to sub-CV " + cv[i_cv]->description + + ", which has no explicit gradient with respect to its own input(s)"); + colvarvalue analytical_grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dsdx.size(); ++m_frame) { + analytical_grad += impl_->compute_s_analytical_derivative_ij( + m_frame, i_cv, cvm::debug_gradients_step_size, this); + } + cvm::log("dx(actual) = "+cvm::to_str(analytical_grad, 21, 14)+"\n"); + cvm::log("dx(interp) = "+cvm::to_str(grad, 21, 14)+"\n"); + cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str((analytical_grad - grad).norm() / + (analytical_grad).norm(), 12, 5)+"\n"); + } + } + } +} + +colvar::azpathCV::azpathCV(std::string const &conf): CVBasedPath(conf) { + set_function_type("azpathCV"); + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + std::vector p_weights(cv.size(), 1.0); + get_keyval(conf, "weights", p_weights, std::vector(cv.size(), 1.0)); + x.type(colvarvalue::type_scalar); + use_explicit_gradients = true; + cvm::real p_lambda; + get_keyval(conf, "lambda", p_lambda, -1.0); + impl_ = std::unique_ptr(new ArithmeticPathImpl(cv.size(), total_reference_frames, p_lambda, p_weights)); + cvm::log(std::string("Lambda is ") + cvm::to_str(impl_->get_lambda()) + std::string("\n")); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + use_explicit_gradients = false; + } + cvm::log(std::string("The weight of CV ") + cvm::to_str(i_cv) + std::string(" is ") + cvm::to_str(p_weights[i_cv]) + std::string("\n")); + } +} + +void colvar::azpathCV::calc_value() { + if (impl_->get_lambda() < 0) { + // this implies that the user may not set a valid lambda value + // so recompute it by the suggested value in Parrinello's paper + cvm::log("A non-positive value of lambda is detected, which implies that it may not set in the configuration.\n"); + cvm::log("This component (azpathCV) will recompute a value for lambda following the suggestion in the origin paper.\n"); + std::vector rmsd_between_refs(total_reference_frames - 1, 0.0); + computeDistanceBetweenReferenceFrames(rmsd_between_refs); + impl_->reComputeLambda(rmsd_between_refs); + cvm::log("Ok, the value of lambda is updated to " + cvm::to_str(impl_->get_lambda())); + } + impl_->updateCVDistanceToReferenceFrames(this); + x = impl_->compute_z(); +} + +void colvar::azpathCV::calc_gradients() { + impl_->compute_z_derivatives(); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_gradients(); + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + // sum up derivatives with respect to all frames + for (size_t m_frame = 0; m_frame < impl_->dzdx.size(); ++m_frame) { + // dzdx is the derivative of z with respect to the m-th frame + grad += impl_->dzdx[m_frame][i_cv]; + } + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = grad[j_elem] * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + } + } + } + } + } +} + +void colvar::azpathCV::apply_force(colvarvalue const &force) { + // the PCV component itself is a scalar, so force should be scalar + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // compute the gradient (grad) with respect to the i-th CV + colvarvalue grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dzdx.size(); ++m_frame) { + // dzdx is the derivative of z with respect to the m-th frame + grad += impl_->dzdx[m_frame][i_cv]; + } + grad *= factor_polynomial; + cv[i_cv]->apply_force(force.real_value * grad); + // try my best to debug gradients even if the sub-CVs do not have explicit gradients + if (is_enabled(f_cvc_debug_gradient)) { + cvm::log("Debugging gradients for " + description + + " with respect to sub-CV " + cv[i_cv]->description + + ", which has no explicit gradient with respect to its own input(s)"); + colvarvalue analytical_grad(cv[i_cv]->value().type()); + for (size_t m_frame = 0; m_frame < impl_->dzdx.size(); ++m_frame) { + analytical_grad += impl_->compute_z_analytical_derivative_ij( + m_frame, i_cv, cvm::debug_gradients_step_size, this); + } + cvm::log("dx(actual) = "+cvm::to_str(analytical_grad, 21, 14)+"\n"); + cvm::log("dx(interp) = "+cvm::to_str(grad, 21, 14)+"\n"); + cvm::log("|dx(actual) - dx(interp)|/|dx(actual)| = "+ + cvm::to_str((analytical_grad - grad).norm() / + (analytical_grad).norm(), 12, 5)+"\n"); + } + } + } +} + +colvar::azpathCV::~azpathCV() {} + diff --git a/src/external/colvars/colvarcomp_combination.cpp b/src/external/colvars/colvarcomp_combination.cpp new file mode 100644 index 00000000000..974cc870c87 --- /dev/null +++ b/src/external/colvars/colvarcomp_combination.cpp @@ -0,0 +1,329 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarcomp.h" + +colvar::linearCombination::linearCombination(std::string const &conf): cvc(conf) { + // Lookup all available sub-cvcs + for (auto it_cv_map = colvar::get_global_cvc_map().begin(); it_cv_map != colvar::get_global_cvc_map().end(); ++it_cv_map) { + if (key_lookup(conf, it_cv_map->first.c_str())) { + std::vector sub_cvc_confs; + get_key_string_multi_value(conf, it_cv_map->first.c_str(), sub_cvc_confs); + for (auto it_sub_cvc_conf = sub_cvc_confs.begin(); it_sub_cvc_conf != sub_cvc_confs.end(); ++it_sub_cvc_conf) { + cv.push_back((it_cv_map->second)(*(it_sub_cvc_conf))); + } + } + } + // Sort all sub CVs by their names + std::sort(cv.begin(), cv.end(), colvar::compare_cvc); + for (auto it_sub_cv = cv.begin(); it_sub_cv != cv.end(); ++it_sub_cv) { + for (auto it_atom_group = (*it_sub_cv)->atom_groups.begin(); it_atom_group != (*it_sub_cv)->atom_groups.end(); ++it_atom_group) { + register_atom_group(*it_atom_group); + } + } + // Show useful error messages and prevent crashes if no sub CVC is found + if (cv.size() == 0) { + cvm::error("Error: the CV " + name + + " expects one or more nesting components.\n"); + return; + } else { + x.type(cv[0]->value()); + x.reset(); + } + use_explicit_gradients = true; + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + use_explicit_gradients = false; + } + } + if (!use_explicit_gradients) { + disable(f_cvc_explicit_gradient); + } +} + +cvm::real colvar::linearCombination::getPolynomialFactorOfCVGradient(size_t i_cv) const { + cvm::real factor_polynomial = 1.0; + if (cv[i_cv]->value().type() == colvarvalue::type_scalar) { + factor_polynomial = cv[i_cv]->sup_coeff * cv[i_cv]->sup_np * cvm::pow(cv[i_cv]->value().real_value, cv[i_cv]->sup_np - 1); + } else { + factor_polynomial = cv[i_cv]->sup_coeff; + } + return factor_polynomial; +} + +colvar::linearCombination::~linearCombination() { + // Recall the steps we initialize the sub-CVCs: + // 1. Lookup all sub-CVCs and then register the atom groups for sub-CVCs + // in their constructors; + // 2. Iterate over all sub-CVCs, get the pointers of their atom groups + // groups, and register again in the parent (current) CVC. + // That being said, the atom groups become children of the sub-CVCs at + // first, and then become children of the parent CVC. + // So, to destruct this class (parent CVC class), we need to remove the + // dependencies of the atom groups to the parent CVC at first. + remove_all_children(); + // Then we remove the dependencies of the atom groups to the sub-CVCs + // in their destructors. + for (auto it = cv.begin(); it != cv.end(); ++it) { + delete (*it); + } + // The last step is cleaning up the list of atom groups. + atom_groups.clear(); +} + +void colvar::linearCombination::calc_value() { + x.reset(); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_value(); + colvarvalue current_cv_value(cv[i_cv]->value()); + // polynomial combination allowed + if (current_cv_value.type() == colvarvalue::type_scalar) { + x += cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)); + } else { + x += cv[i_cv]->sup_coeff * current_cv_value; + } + } +} + +void colvar::linearCombination::calc_gradients() { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_gradients(); + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + } + } + } + } + } +} + +void colvar::linearCombination::apply_force(colvarvalue const &force) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // If this CV us explicit gradients, then atomic gradients is already calculated + // We can apply the force to atom groups directly + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + // Compute factors for polynomial combinations + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + colvarvalue cv_force = force.real_value * factor_polynomial; + cv[i_cv]->apply_force(cv_force); + } + } +} + +colvar::customColvar::customColvar(std::string const &conf): linearCombination(conf) { + use_custom_function = false; + // code swipe from colvar::init_custom_function + std::string expr_in, expr; + size_t pos = 0; // current position in config string +#ifdef LEPTON + std::vector pexprs; + Lepton::ParsedExpression pexpr; + double *ref; +#endif + if (key_lookup(conf, "customFunction", &expr_in, &pos)) { +#ifdef LEPTON + use_custom_function = true; + cvm::log("This colvar uses a custom function.\n"); + do { + expr = expr_in; + if (cvm::debug()) + cvm::log("Parsing expression \"" + expr + "\".\n"); + try { + pexpr = Lepton::Parser::parse(expr); + pexprs.push_back(pexpr); + } catch (...) { + cvm::error("Error parsing expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); + } + try { + value_evaluators.push_back(new Lepton::CompiledExpression(pexpr.createCompiledExpression())); + // Define variables for cvc values + for (size_t i = 0; i < cv.size(); ++i) { + for (size_t j = 0; j < cv[i]->value().size(); ++j) { + std::string vn = cv[i]->name + (cv[i]->value().size() > 1 ? cvm::to_str(j+1) : ""); + try { + ref = &value_evaluators.back()->getVariableReference(vn); + } catch (...) { + ref = &dev_null; + cvm::log("Warning: Variable " + vn + " is absent from expression \"" + expr + "\".\n"); + } + value_eval_var_refs.push_back(ref); + } + } + } catch (...) { + cvm::error("Error compiling expression \"" + expr + "\".\n", COLVARS_INPUT_ERROR); + } + } while (key_lookup(conf, "customFunction", &expr_in, &pos)); + // Now define derivative with respect to each scalar sub-component + for (size_t i = 0; i < cv.size(); ++i) { + for (size_t j = 0; j < cv[i]->value().size(); ++j) { + std::string vn = cv[i]->name + (cv[i]->value().size() > 1 ? cvm::to_str(j+1) : ""); + for (size_t c = 0; c < pexprs.size(); ++c) { + gradient_evaluators.push_back(new Lepton::CompiledExpression(pexprs[c].differentiate(vn).createCompiledExpression())); + for (size_t k = 0; k < cv.size(); ++k) { + for (size_t l = 0; l < cv[k]->value().size(); l++) { + std::string vvn = cv[k]->name + (cv[k]->value().size() > 1 ? cvm::to_str(l+1) : ""); + try { + ref = &gradient_evaluators.back()->getVariableReference(vvn); + } catch (...) { + cvm::log("Warning: Variable " + vvn + " is absent from derivative of \"" + expr + "\" wrt " + vn + ".\n"); + ref = &dev_null; + } + grad_eval_var_refs.push_back(ref); + } + } + } + } + } + if (value_evaluators.size() == 0) { + cvm::error("Error: no custom function defined.\n", COLVARS_INPUT_ERROR); + } + if (value_evaluators.size() != 1) { + x.type(colvarvalue::type_vector); + } else { + x.type(colvarvalue::type_scalar); + } +#else + cvm::error("customFunction requires the Lepton library, but it is not enabled during compilation.\n" + "Please refer to the Compilation Notes section of the Colvars manual for more information.\n", + COLVARS_INPUT_ERROR); +#endif + } else { + cvm::log("Warning: no customFunction specified.\n"); + cvm::log("Warning: use linear combination instead.\n"); + } +} + +colvar::customColvar::~customColvar() { +#ifdef LEPTON + for (size_t i = 0; i < value_evaluators.size(); ++i) { + if (value_evaluators[i] != nullptr) delete value_evaluators[i]; + } + for (size_t i = 0; i < gradient_evaluators.size(); ++i) { + if (gradient_evaluators[i] != nullptr) delete gradient_evaluators[i]; + } +#endif +} + +void colvar::customColvar::calc_value() { + if (!use_custom_function) { + colvar::linearCombination::calc_value(); + } else { +#ifdef LEPTON + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_value(); + } + x.reset(); + size_t l = 0; + for (size_t i = 0; i < x.size(); ++i) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + const colvarvalue& current_cv_value = cv[i_cv]->value(); + for (size_t j_elem = 0; j_elem < current_cv_value.size(); ++j_elem) { + if (current_cv_value.type() == colvarvalue::type_scalar) { + *(value_eval_var_refs[l++]) = cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)); + } else { + *(value_eval_var_refs[l++]) = cv[i_cv]->sup_coeff * current_cv_value[j_elem]; + } + } + } + x[i] = value_evaluators[i]->evaluate(); + } +#else + cvm::error("customFunction requires the Lepton library, but it is not enabled during compilation.\n" + "Please refer to the Compilation Notes section of the Colvars manual for more information.\n", + COLVARS_INPUT_ERROR); +#endif + } +} + +void colvar::customColvar::calc_gradients() { + if (!use_custom_function) { + colvar::linearCombination::calc_gradients(); + } else { +#ifdef LEPTON + size_t r = 0; // index in the vector of variable references + size_t e = 0; // index of the gradient evaluator + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { // for each CV + cv[i_cv]->calc_gradients(); + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + const colvarvalue& current_cv_value = cv[i_cv]->value(); + const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + for (size_t j_elem = 0; j_elem < current_cv_value.size(); ++j_elem) { // for each element in this CV + for (size_t c = 0; c < x.size(); ++c) { // for each custom function expression + for (size_t k = 0; k < cv.size(); ++k) { // this is required since we need to feed all CV values to this expression + const cvm::real factor_polynomial_k = getPolynomialFactorOfCVGradient(k); + for (size_t l = 0; l < cv[k]->value().size(); ++l) { + *(grad_eval_var_refs[r++]) = factor_polynomial_k * cv[k]->value()[l]; + } + } + const double expr_grad = gradient_evaluators[e++]->evaluate(); + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = expr_grad * factor_polynomial * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + } + } + } + } + } + } +#else + cvm::error("customFunction requires the Lepton library, but it is not enabled during compilation.\n" + "Please refer to the Compilation Notes section of the Colvars manual for more information.\n", + COLVARS_INPUT_ERROR); +#endif + } +} + +void colvar::customColvar::apply_force(colvarvalue const &force) { + if (!use_custom_function) { + colvar::linearCombination::apply_force(force); + } else { +#ifdef LEPTON + size_t r = 0; // index in the vector of variable references + size_t e = 0; // index of the gradient evaluator + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // If this CV us explicit gradients, then atomic gradients is already calculated + // We can apply the force to atom groups directly + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + const colvarvalue& current_cv_value = cv[i_cv]->value(); + colvarvalue cv_force(current_cv_value); + cv_force.reset(); + const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + for (size_t j_elem = 0; j_elem < current_cv_value.size(); ++j_elem) { + for (size_t c = 0; c < x.size(); ++c) { + for (size_t k = 0; k < cv.size(); ++k) { + const cvm::real factor_polynomial_k = getPolynomialFactorOfCVGradient(k); + for (size_t l = 0; l < cv[k]->value().size(); ++l) { + *(grad_eval_var_refs[r++]) = factor_polynomial_k * cv[k]->value()[l]; + } + } + cv_force[j_elem] += factor_polynomial * gradient_evaluators[e++]->evaluate() * force.real_value; + } + } + cv[i_cv]->apply_force(cv_force); + } + } +#else + cvm::error("customFunction requires the Lepton library, but it is not enabled during compilation.\n" + "Please refer to the Compilation Notes section of the Colvars manual for more information.\n", + COLVARS_INPUT_ERROR); +#endif + } +} diff --git a/src/external/colvars/colvarcomp_coordnums.cpp b/src/external/colvars/colvarcomp_coordnums.cpp new file mode 100644 index 00000000000..3d618ff8051 --- /dev/null +++ b/src/external/colvars/colvarcomp_coordnums.cpp @@ -0,0 +1,655 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvarparse.h" +#include "colvaratoms.h" +#include "colvarvalue.h" +#include "colvar.h" +#include "colvarcomp.h" + + + +template +cvm::real colvar::coordnum::switching_function(cvm::real const &r0, + cvm::rvector const &r0_vec, + int en, + int ed, + cvm::atom &A1, + cvm::atom &A2, + bool **pairlist_elem, + cvm::real pairlist_tol) +{ + if ((flags & ef_use_pairlist) && !(flags & ef_rebuild_pairlist)) { + bool const within = **pairlist_elem; + (*pairlist_elem)++; + if (!within) { + return 0.0; + } + } + + cvm::rvector const r0sq_vec(r0_vec.x*r0_vec.x, + r0_vec.y*r0_vec.y, + r0_vec.z*r0_vec.z); + + cvm::rvector const diff = cvm::position_distance(A1.pos, A2.pos); + + cvm::rvector const scal_diff(diff.x/((flags & ef_anisotropic) ? + r0_vec.x : r0), + diff.y/((flags & ef_anisotropic) ? + r0_vec.y : r0), + diff.z/((flags & ef_anisotropic) ? + r0_vec.z : r0)); + cvm::real const l2 = scal_diff.norm2(); + + // Assume en and ed are even integers, and avoid sqrt in the following + int const en2 = en/2; + int const ed2 = ed/2; + + cvm::real const xn = cvm::integer_power(l2, en2); + cvm::real const xd = cvm::integer_power(l2, ed2); + //The subtraction and division stretches the function back to the range of [0,1] from [pairlist_tol,1] + cvm::real const func = (((1.0-xn)/(1.0-xd)) - pairlist_tol) / (1.0-pairlist_tol); + + if (flags & ef_rebuild_pairlist) { + //Particles just outside of the cutoff also are considered if they come near. + **pairlist_elem = (func > (-pairlist_tol * 0.5)) ? true : false; + (*pairlist_elem)++; + } + //If the value is too small, we need to exclude it, rather than let it contribute to the sum or the gradients. + if (func < 0) + return 0; + + if (flags & ef_gradients) { + //This is the old, completely correct expression for dFdl2: + //cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - + // func*ed2*(xd/l2))*(-1.0); + //This can become: + //cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2)*(1.0-xn)/(1.0-xn) - + // func*ed2*(xd/l2))*(-1.0); + //Recognizing that func = (1.0-xn)/(1.0-xd), we can group together the "func" and get a version of dFdl2 that is 0 + //when func=0, which lets us skip this gradient calculation when func=0. + cvm::real const dFdl2 = func * ((ed2*xd/((1.0-xd)*l2)) - (en2*xn/((1.0-xn)*l2))); + cvm::rvector const dl2dx((2.0/((flags & ef_anisotropic) ? r0sq_vec.x : + r0*r0)) * diff.x, + (2.0/((flags & ef_anisotropic) ? r0sq_vec.y : + r0*r0)) * diff.y, + (2.0/((flags & ef_anisotropic) ? r0sq_vec.z : + r0*r0)) * diff.z); + A1.grad += (-1.0)*dFdl2*dl2dx; + A2.grad += dFdl2*dl2dx; + } + + return func; +} + + +colvar::coordnum::coordnum(std::string const &conf) + : cvc(conf), b_anisotropic(false), pairlist(NULL) + +{ + set_function_type("coordNum"); + x.type(colvarvalue::type_scalar); + + colvarproxy *proxy = cvm::main()->proxy; + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + + if (group1 == NULL || group2 == NULL) { + cvm::error("Error: failed to initialize atom groups.\n", + COLVARS_INPUT_ERROR); + return; + } + + if (int atom_number = cvm::atom_group::overlap(*group1, *group2)) { + cvm::error("Error: group1 and group2 share a common atom (number: " + + cvm::to_str(atom_number) + ")\n", COLVARS_INPUT_ERROR); + return; + } + + if (group1->b_dummy) { + cvm::error("Error: only group2 is allowed to be a dummy atom\n", + COLVARS_INPUT_ERROR); + return; + } + + bool const b_isotropic = get_keyval(conf, "cutoff", r0, + cvm::real(proxy->angstrom_to_internal(4.0))); + + if (get_keyval(conf, "cutoff3", r0_vec, + cvm::rvector(proxy->angstrom_to_internal(4.0), + proxy->angstrom_to_internal(4.0), + proxy->angstrom_to_internal(4.0)))) { + if (b_isotropic) { + cvm::error("Error: cannot specify \"cutoff\" and \"cutoff3\" " + "at the same time.\n", + COLVARS_INPUT_ERROR); + return; + } + + b_anisotropic = true; + // remove meaningless negative signs + if (r0_vec.x < 0.0) r0_vec.x *= -1.0; + if (r0_vec.y < 0.0) r0_vec.y *= -1.0; + if (r0_vec.z < 0.0) r0_vec.z *= -1.0; + } + + get_keyval(conf, "expNumer", en, 6); + get_keyval(conf, "expDenom", ed, 12); + + if ( (en%2) || (ed%2) ) { + cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); + } + + if ( (en <= 0) || (ed <= 0) ) { + cvm::error("Error: negative exponent(s) provided.\n", + COLVARS_INPUT_ERROR); + } + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + cvm::log("Warning: only minimum-image distances are used by this variable.\n"); + } + + get_keyval(conf, "group2CenterOnly", b_group2_center_only, group2->b_dummy); + + get_keyval(conf, "tolerance", tolerance, 0.0); + if (tolerance > 0) { + cvm::main()->cite_feature("coordNum pairlist"); + get_keyval(conf, "pairListFrequency", pairlist_freq, 100); + if ( ! (pairlist_freq > 0) ) { + cvm::error("Error: non-positive pairlistfrequency provided.\n", + COLVARS_INPUT_ERROR); + return; // and do not allocate the pairlists below + } + if (b_group2_center_only) { + pairlist = new bool[group1->size()]; + } + else { + pairlist = new bool[group1->size() * group2->size()]; + } + } + + init_scalar_boundaries(0.0, b_group2_center_only ? + static_cast(group1->size()) : + static_cast(group1->size() * + group2->size())); +} + + +colvar::coordnum::~coordnum() +{ + if (pairlist != NULL) { + delete [] pairlist; + } +} + + +template void colvar::coordnum::main_loop(bool **pairlist_elem) +{ + if (b_group2_center_only) { + cvm::atom group2_com_atom; + group2_com_atom.pos = group2->center_of_mass(); + for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { + x.real_value += switching_function(r0, r0_vec, en, ed, + *ai1, group2_com_atom, + pairlist_elem, + tolerance); + } + if (b_group2_center_only) { + group2->set_weighted_gradient(group2_com_atom.grad); + } + } else { + for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { + for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { + x.real_value += switching_function(r0, r0_vec, en, ed, + *ai1, *ai2, + pairlist_elem, + tolerance); + } + } + } +} + + +template int colvar::coordnum::compute_coordnum() +{ + bool const use_pairlist = (pairlist != NULL); + bool const rebuild_pairlist = (pairlist != NULL) && + (cvm::step_relative() % pairlist_freq == 0); + + bool *pairlist_elem = use_pairlist ? pairlist : NULL; + + if (b_anisotropic) { + + if (use_pairlist) { + if (rebuild_pairlist) { + int const flags = compute_flags | ef_anisotropic | ef_use_pairlist | + ef_rebuild_pairlist; + main_loop(&pairlist_elem); + } else { + int const flags = compute_flags | ef_anisotropic | ef_use_pairlist; + main_loop(&pairlist_elem); + } + + } else { + + int const flags = compute_flags | ef_anisotropic; + main_loop(NULL); + } + + } else { + + if (use_pairlist) { + + if (rebuild_pairlist) { + int const flags = compute_flags | ef_use_pairlist | ef_rebuild_pairlist; + main_loop(&pairlist_elem); + } else { + int const flags = compute_flags | ef_use_pairlist; + main_loop(&pairlist_elem); + } + + } else { + + int const flags = compute_flags; + main_loop(NULL); + } + } + + return COLVARS_OK; +} + + +void colvar::coordnum::calc_value() +{ + x.real_value = 0.0; + if (is_enabled(f_cvc_gradient)) { + compute_coordnum(); + } else { + compute_coordnum(); + } +} + + +void colvar::coordnum::calc_gradients() +{ + // Gradients are computed by calc_value() if f_cvc_gradients is enabled +} + + +void colvar::coordnum::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(coordnum) + + + +// h_bond member functions + +colvar::h_bond::h_bond(std::string const &conf) +: cvc(conf) +{ + if (cvm::debug()) + cvm::log("Initializing h_bond object.\n"); + + set_function_type("hBond"); + x.type(colvarvalue::type_scalar); + init_scalar_boundaries(0.0, 1.0); + + colvarproxy *proxy = cvm::main()->proxy; + + int a_num = -1, d_num = -1; + get_keyval(conf, "acceptor", a_num, a_num); + get_keyval(conf, "donor", d_num, a_num); + + if ( (a_num == -1) || (d_num == -1) ) { + cvm::error("Error: either acceptor or donor undefined.\n"); + return; + } + + cvm::atom acceptor = cvm::atom(a_num); + cvm::atom donor = cvm::atom(d_num); + register_atom_group(new cvm::atom_group); + atom_groups[0]->add_atom(acceptor); + atom_groups[0]->add_atom(donor); + + get_keyval(conf, "cutoff", r0, proxy->angstrom_to_internal(3.3)); + get_keyval(conf, "expNumer", en, 6); + get_keyval(conf, "expDenom", ed, 8); + + if ( (en%2) || (ed%2) ) { + cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); + } + + if ( (en <= 0) || (ed <= 0) ) { + cvm::error("Error: negative exponent(s) provided.\n", + COLVARS_INPUT_ERROR); + } + + if (cvm::debug()) + cvm::log("Done initializing h_bond object.\n"); +} + + +colvar::h_bond::h_bond(cvm::atom const &acceptor, + cvm::atom const &donor, + cvm::real r0_i, int en_i, int ed_i) + : r0(r0_i), en(en_i), ed(ed_i) +{ + set_function_type("hBond"); + x.type(colvarvalue::type_scalar); + init_scalar_boundaries(0.0, 1.0); + + register_atom_group(new cvm::atom_group); + atom_groups[0]->add_atom(acceptor); + atom_groups[0]->add_atom(donor); +} + + +void colvar::h_bond::calc_value() +{ + int const flags = coordnum::ef_null; + cvm::rvector const r0_vec(0.0); // TODO enable the flag? + x.real_value = + coordnum::switching_function(r0, r0_vec, en, ed, + (*atom_groups[0])[0], + (*atom_groups[0])[1], + NULL, 0.0); +} + + +void colvar::h_bond::calc_gradients() +{ + int const flags = coordnum::ef_gradients; + cvm::rvector const r0_vec(0.0); // TODO enable the flag? + coordnum::switching_function(r0, r0_vec, en, ed, + (*atom_groups[0])[0], + (*atom_groups[0])[1], + NULL, 0.0); +} + + +void colvar::h_bond::apply_force(colvarvalue const &force) +{ + (atom_groups[0])->apply_colvar_force(force); +} + + +simple_scalar_dist_functions(h_bond) + + + +colvar::selfcoordnum::selfcoordnum(std::string const &conf) + : cvc(conf), pairlist(NULL) +{ + set_function_type("selfCoordNum"); + x.type(colvarvalue::type_scalar); + + colvarproxy *proxy = cvm::main()->proxy; + + group1 = parse_group(conf, "group1"); + + get_keyval(conf, "cutoff", r0, cvm::real(proxy->angstrom_to_internal(4.0))); + get_keyval(conf, "expNumer", en, 6); + get_keyval(conf, "expDenom", ed, 12); + + + if ( (en%2) || (ed%2) ) { + cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); + } + + if ( (en <= 0) || (ed <= 0) ) { + cvm::error("Error: negative exponent(s) provided.\n", + COLVARS_INPUT_ERROR); + } + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + cvm::log("Warning: only minimum-image distances are used by this variable.\n"); + } + + get_keyval(conf, "tolerance", tolerance, 0.0); + if (tolerance > 0) { + get_keyval(conf, "pairListFrequency", pairlist_freq, 100); + if ( ! (pairlist_freq > 0) ) { + cvm::error("Error: non-positive pairlistfrequency provided.\n", + COLVARS_INPUT_ERROR); + return; + } + pairlist = new bool[(group1->size()-1) * (group1->size()-1)]; + } + + init_scalar_boundaries(0.0, static_cast((group1->size()-1) * + (group1->size()-1))); +} + + +colvar::selfcoordnum::~selfcoordnum() +{ + if (pairlist != NULL) { + delete [] pairlist; + } +} + + +template int colvar::selfcoordnum::compute_selfcoordnum() +{ + cvm::rvector const r0_vec(0.0); // TODO enable the flag? + + bool const use_pairlist = (pairlist != NULL); + bool const rebuild_pairlist = (pairlist != NULL) && + (cvm::step_relative() % pairlist_freq == 0); + + bool *pairlist_elem = use_pairlist ? pairlist : NULL; + size_t i = 0, j = 0; + size_t const n = group1->size(); + + // Always isotropic (TODO: enable the ellipsoid?) + + if (use_pairlist) { + + if (rebuild_pairlist) { + int const flags = compute_flags | coordnum::ef_use_pairlist | + coordnum::ef_rebuild_pairlist; + for (i = 0; i < n - 1; i++) { + for (j = i + 1; j < n; j++) { + x.real_value += + coordnum::switching_function(r0, r0_vec, en, ed, + (*group1)[i], + (*group1)[j], + &pairlist_elem, + tolerance); + } + } + } else { + int const flags = compute_flags | coordnum::ef_use_pairlist; + for (i = 0; i < n - 1; i++) { + for (j = i + 1; j < n; j++) { + x.real_value += + coordnum::switching_function(r0, r0_vec, en, ed, + (*group1)[i], + (*group1)[j], + &pairlist_elem, + tolerance); + } + } + } + + } else { // if (use_pairlist) { + + int const flags = compute_flags | coordnum::ef_null; + for (i = 0; i < n - 1; i++) { + for (j = i + 1; j < n; j++) { + x.real_value += + coordnum::switching_function(r0, r0_vec, en, ed, + (*group1)[i], + (*group1)[j], + &pairlist_elem, + tolerance); + } + } + } + + return COLVARS_OK; +} + + +void colvar::selfcoordnum::calc_value() +{ + x.real_value = 0.0; + if (is_enabled(f_cvc_gradient)) { + compute_selfcoordnum(); + } else { + compute_selfcoordnum(); + } +} + + +void colvar::selfcoordnum::calc_gradients() +{ + // Gradients are computed by calc_value() if f_cvc_gradients is enabled +} + + +void colvar::selfcoordnum::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) { + group1->apply_colvar_force(force.real_value); + } +} + + +simple_scalar_dist_functions(selfcoordnum) + + + +// groupcoordnum member functions +colvar::groupcoordnum::groupcoordnum(std::string const &conf) + : distance(conf), b_anisotropic(false) +{ + set_function_type("groupCoord"); + x.type(colvarvalue::type_scalar); + init_scalar_boundaries(0.0, 1.0); + + colvarproxy *proxy = cvm::main()->proxy; + + // group1 and group2 are already initialized by distance() + if (group1->b_dummy || group2->b_dummy) { + cvm::error("Error: neither group can be a dummy atom\n"); + return; + } + + bool const b_scale = get_keyval(conf, "cutoff", r0, + cvm::real(proxy->angstrom_to_internal(4.0))); + + if (get_keyval(conf, "cutoff3", r0_vec, + cvm::rvector(4.0, 4.0, 4.0), parse_silent)) { + + if (b_scale) { + cvm::error("Error: cannot specify \"scale\" and " + "\"scale3\" at the same time.\n"); + return; + } + b_anisotropic = true; + // remove meaningless negative signs + if (r0_vec.x < 0.0) r0_vec.x *= -1.0; + if (r0_vec.y < 0.0) r0_vec.y *= -1.0; + if (r0_vec.z < 0.0) r0_vec.z *= -1.0; + } + + get_keyval(conf, "expNumer", en, 6); + get_keyval(conf, "expDenom", ed, 12); + + if ( (en%2) || (ed%2) ) { + cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", + COLVARS_INPUT_ERROR); + } + + if ( (en <= 0) || (ed <= 0) ) { + cvm::error("Error: negative exponent(s) provided.\n", + COLVARS_INPUT_ERROR); + } + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + cvm::log("Warning: only minimum-image distances are used by this variable.\n"); + } + +} + + +void colvar::groupcoordnum::calc_value() +{ + // create fake atoms to hold the com coordinates + cvm::atom group1_com_atom; + cvm::atom group2_com_atom; + group1_com_atom.pos = group1->center_of_mass(); + group2_com_atom.pos = group2->center_of_mass(); + if (b_anisotropic) { + int const flags = coordnum::ef_anisotropic; + x.real_value = coordnum::switching_function(r0, r0_vec, en, ed, + group1_com_atom, + group2_com_atom, + NULL, 0.0); + } else { + int const flags = coordnum::ef_null; + x.real_value = coordnum::switching_function(r0, r0_vec, en, ed, + group1_com_atom, + group2_com_atom, + NULL, 0.0); + } +} + + +void colvar::groupcoordnum::calc_gradients() +{ + cvm::atom group1_com_atom; + cvm::atom group2_com_atom; + group1_com_atom.pos = group1->center_of_mass(); + group2_com_atom.pos = group2->center_of_mass(); + + if (b_anisotropic) { + int const flags = coordnum::ef_gradients | coordnum::ef_anisotropic; + coordnum::switching_function(r0, r0_vec, en, ed, + group1_com_atom, + group2_com_atom, + NULL, 0.0); + } else { + int const flags = coordnum::ef_gradients; + coordnum::switching_function(r0, r0_vec, en, ed, + group1_com_atom, + group2_com_atom, + NULL, 0.0); + } + + group1->set_weighted_gradient(group1_com_atom.grad); + group2->set_weighted_gradient(group2_com_atom.grad); +} + + +void colvar::groupcoordnum::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(groupcoordnum) diff --git a/src/external/colvars/colvarcomp_distances.cpp b/src/external/colvars/colvarcomp_distances.cpp new file mode 100644 index 00000000000..451bd0d3ba0 --- /dev/null +++ b/src/external/colvars/colvarcomp_distances.cpp @@ -0,0 +1,1572 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include "colvar_rotation_derivative.h" + + +colvar::distance::distance(std::string const &conf) + : cvc(conf) +{ + set_function_type("distance"); + init_as_distance(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + + init_total_force_params(conf); +} + + +colvar::distance::distance() + : cvc() +{ + set_function_type("distance"); + init_as_distance(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); +} + + +void colvar::distance::calc_value() +{ + if (!is_enabled(f_cvc_pbc_minimum_image)) { + dist_v = group2->center_of_mass() - group1->center_of_mass(); + } else { + dist_v = cvm::position_distance(group1->center_of_mass(), + group2->center_of_mass()); + } + x.real_value = dist_v.norm(); +} + + +void colvar::distance::calc_gradients() +{ + cvm::rvector const u = dist_v.unit(); + group1->set_weighted_gradient(-1.0 * u); + group2->set_weighted_gradient( u); +} + + +void colvar::distance::calc_force_invgrads() +{ + group1->read_total_forces(); + if (is_enabled(f_cvc_one_site_total_force)) { + ft.real_value = -1.0 * (group1->total_force() * dist_v.unit()); + } else { + group2->read_total_forces(); + ft.real_value = 0.5 * ((group2->total_force() - group1->total_force()) * dist_v.unit()); + } +} + + +void colvar::distance::calc_Jacobian_derivative() +{ + jd.real_value = x.real_value ? (2.0 / x.real_value) : 0.0; +} + + +void colvar::distance::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(distance) + + + +colvar::distance_vec::distance_vec(std::string const &conf) + : distance(conf) +{ + set_function_type("distanceVec"); + enable(f_cvc_com_based); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_3vector); +} + + +colvar::distance_vec::distance_vec() + : distance() +{ + set_function_type("distanceVec"); + enable(f_cvc_com_based); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_3vector); +} + + +void colvar::distance_vec::calc_value() +{ + if (!is_enabled(f_cvc_pbc_minimum_image)) { + x.rvector_value = group2->center_of_mass() - group1->center_of_mass(); + } else { + x.rvector_value = cvm::position_distance(group1->center_of_mass(), + group2->center_of_mass()); + } +} + + +void colvar::distance_vec::calc_gradients() +{ + // gradients are not stored: a 3x3 matrix for each atom would be + // needed to store just the identity matrix +} + + +void colvar::distance_vec::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_force(-1.0 * force.rvector_value); + + if (!group2->noforce) + group2->apply_force( force.rvector_value); +} + + +cvm::real colvar::distance_vec::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (cvm::position_distance(x1.rvector_value, x2.rvector_value)).norm2(); +} + + +colvarvalue colvar::distance_vec::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return 2.0 * cvm::position_distance(x2.rvector_value, x1.rvector_value); +} + + +colvarvalue colvar::distance_vec::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return 2.0 * cvm::position_distance(x2.rvector_value, x1.rvector_value); +} + + + +colvar::distance_z::distance_z(std::string const &conf) + : cvc(conf) +{ + set_function_type("distanceZ"); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + x.type(colvarvalue::type_scalar); + + // TODO detect PBC from MD engine (in simple cases) + // and then update period in real time + if (period != 0.0) { + enable(f_cvc_periodic); + } + + if ((wrap_center != 0.0) && !is_enabled(f_cvc_periodic)) { + cvm::error("Error: wrapAround was defined in a distanceZ component," + " but its period has not been set.\n"); + return; + } + + main = parse_group(conf, "main"); + ref1 = parse_group(conf, "ref"); + // this group is optional + ref2 = parse_group(conf, "ref2", true); + + if ( ref2 ) { + cvm::log("Using axis joining the centers of mass of groups \"ref\" and \"ref2\"\n"); + fixed_axis = false; + if (key_lookup(conf, "axis")) + cvm::log("Warning: explicit axis definition will be ignored!\n"); + } else { + if (get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0))) { + if (axis.norm2() == 0.0) { + cvm::error("Axis vector is zero!"); + return; + } + if (axis.norm2() != 1.0) { + axis = axis.unit(); + cvm::log("The normalized axis is: "+cvm::to_str(axis)+".\n"); + } + } + fixed_axis = true; + } + + init_total_force_params(conf); + +} + + +colvar::distance_z::distance_z() +{ + set_function_type("distanceZ"); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); + x.type(colvarvalue::type_scalar); +} + + +void colvar::distance_z::calc_value() +{ + if (fixed_axis) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { + dist_v = main->center_of_mass() - ref1->center_of_mass(); + } else { + dist_v = cvm::position_distance(ref1->center_of_mass(), + main->center_of_mass()); + } + } else { + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + dist_v = main->center_of_mass() - + (0.5 * (ref1->center_of_mass() + ref2->center_of_mass())); + axis = ref2->center_of_mass() - ref1->center_of_mass(); + } else { + dist_v = cvm::position_distance(0.5 * (ref1->center_of_mass() + + ref2->center_of_mass()), + main->center_of_mass()); + axis = cvm::position_distance(ref1->center_of_mass(), + ref2->center_of_mass()); + } + axis_norm = axis.norm(); + axis = axis.unit(); + } + x.real_value = axis * dist_v; + this->wrap(x); +} + + +void colvar::distance_z::calc_gradients() +{ + main->set_weighted_gradient( axis ); + + if (fixed_axis) { + ref1->set_weighted_gradient(-1.0 * axis); + } else { + if (!is_enabled(f_cvc_pbc_minimum_image)) { + ref1->set_weighted_gradient( 1.0 / axis_norm * + (main->center_of_mass() - ref2->center_of_mass() - + x.real_value * axis )); + ref2->set_weighted_gradient( 1.0 / axis_norm * + (ref1->center_of_mass() - main->center_of_mass() + + x.real_value * axis )); + } else { + ref1->set_weighted_gradient( 1.0 / axis_norm * ( + cvm::position_distance(ref2->center_of_mass(), + main->center_of_mass()) - x.real_value * axis )); + ref2->set_weighted_gradient( 1.0 / axis_norm * ( + cvm::position_distance(main->center_of_mass(), + ref1->center_of_mass()) + x.real_value * axis )); + } + } +} + + +void colvar::distance_z::calc_force_invgrads() +{ + main->read_total_forces(); + + if (fixed_axis && !is_enabled(f_cvc_one_site_total_force)) { + ref1->read_total_forces(); + ft.real_value = 0.5 * ((main->total_force() - ref1->total_force()) * axis); + } else { + ft.real_value = main->total_force() * axis; + } +} + + +void colvar::distance_z::calc_Jacobian_derivative() +{ + jd.real_value = 0.0; +} + + +void colvar::distance_z::apply_force(colvarvalue const &force) +{ + if (!ref1->noforce) + ref1->apply_colvar_force(force.real_value); + + if (ref2 && !ref2->noforce) + ref2->apply_colvar_force(force.real_value); + + if (!main->noforce) + main->apply_colvar_force(force.real_value); +} + + +// Differences should always be wrapped around 0 (ignoring wrap_center) +cvm::real colvar::distance_z::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + if (is_enabled(f_cvc_periodic)) { + cvm::real shift = cvm::floor(diff/period + 0.5); + diff -= shift * period; + } + return diff * diff; +} + + +colvarvalue colvar::distance_z::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + if (is_enabled(f_cvc_periodic)) { + cvm::real shift = cvm::floor(diff/period + 0.5); + diff -= shift * period; + } + return 2.0 * diff; +} + + +colvarvalue colvar::distance_z::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + if (is_enabled(f_cvc_periodic)) { + cvm::real shift = cvm::floor(diff/period + 0.5); + diff -= shift * period; + } + return (-2.0) * diff; +} + + +void colvar::distance_z::wrap(colvarvalue &x_unwrapped) const +{ + if (!is_enabled(f_cvc_periodic)) { + // don't wrap if the period has not been set + return; + } + cvm::real shift = + cvm::floor((x_unwrapped.real_value - wrap_center) / period + 0.5); + x_unwrapped.real_value -= shift * period; +} + + + +colvar::distance_xy::distance_xy(std::string const &conf) + : distance_z(conf) +{ + set_function_type("distanceXY"); + init_as_distance(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); +} + + +colvar::distance_xy::distance_xy() + : distance_z() +{ + set_function_type("distanceXY"); + init_as_distance(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + enable(f_cvc_com_based); +} + + +void colvar::distance_xy::calc_value() +{ + if (!is_enabled(f_cvc_pbc_minimum_image)) { + dist_v = main->center_of_mass() - ref1->center_of_mass(); + } else { + dist_v = cvm::position_distance(ref1->center_of_mass(), + main->center_of_mass()); + } + if (!fixed_axis) { + if (!is_enabled(f_cvc_pbc_minimum_image)) { + v12 = ref2->center_of_mass() - ref1->center_of_mass(); + } else { + v12 = cvm::position_distance(ref1->center_of_mass(), + ref2->center_of_mass()); + } + axis_norm = v12.norm(); + axis = v12.unit(); + } + + dist_v_ortho = dist_v - (dist_v * axis) * axis; + x.real_value = dist_v_ortho.norm(); +} + + +void colvar::distance_xy::calc_gradients() +{ + // Intermediate quantity (r_P3 / r_12 where P is the projection + // of 3(main) on the plane orthogonal to 12, containing 1 (ref1)) + cvm::real A; + cvm::real x_inv; + + if (x.real_value == 0.0) return; + x_inv = 1.0 / x.real_value; + + if (fixed_axis) { + ref1->set_weighted_gradient(-1.0 * x_inv * dist_v_ortho); + main->set_weighted_gradient( x_inv * dist_v_ortho); + } else { + if (!is_enabled(f_cvc_pbc_minimum_image)) { + v13 = main->center_of_mass() - ref1->center_of_mass(); + } else { + v13 = cvm::position_distance(ref1->center_of_mass(), + main->center_of_mass()); + } + A = (dist_v * axis) / axis_norm; + + ref1->set_weighted_gradient( (A - 1.0) * x_inv * dist_v_ortho); + ref2->set_weighted_gradient( -A * x_inv * dist_v_ortho); + main->set_weighted_gradient( 1.0 * x_inv * dist_v_ortho); + } +} + + +void colvar::distance_xy::calc_force_invgrads() +{ + main->read_total_forces(); + + if (fixed_axis && !is_enabled(f_cvc_one_site_total_force)) { + ref1->read_total_forces(); + ft.real_value = 0.5 / x.real_value * ((main->total_force() - ref1->total_force()) * dist_v_ortho); + } else { + ft.real_value = 1.0 / x.real_value * main->total_force() * dist_v_ortho; + } +} + + +void colvar::distance_xy::calc_Jacobian_derivative() +{ + jd.real_value = x.real_value ? (1.0 / x.real_value) : 0.0; +} + + +void colvar::distance_xy::apply_force(colvarvalue const &force) +{ + if (!ref1->noforce) + ref1->apply_colvar_force(force.real_value); + + if (ref2 && !ref2->noforce) + ref2->apply_colvar_force(force.real_value); + + if (!main->noforce) + main->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(distance_xy) + + + +colvar::distance_dir::distance_dir(std::string const &conf) + : distance(conf) +{ + set_function_type("distanceDir"); + enable(f_cvc_com_based); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_unit3vector); +} + + +colvar::distance_dir::distance_dir() + : distance() +{ + set_function_type("distanceDir"); + enable(f_cvc_com_based); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_unit3vector); +} + + +void colvar::distance_dir::calc_value() +{ + if (!is_enabled(f_cvc_pbc_minimum_image)) { + dist_v = group2->center_of_mass() - group1->center_of_mass(); + } else { + dist_v = cvm::position_distance(group1->center_of_mass(), + group2->center_of_mass()); + } + x.rvector_value = dist_v.unit(); +} + + +void colvar::distance_dir::calc_gradients() +{ + // gradients are computed on the fly within apply_force() + // Note: could be a problem if a future bias relies on gradient + // calculations... + // TODO in new deps system: remove dependency of biasing force to gradient? + // That way we could tell apart an explicit gradient dependency +} + + +void colvar::distance_dir::apply_force(colvarvalue const &force) +{ + // remove the radial force component + cvm::real const iprod = force.rvector_value * x.rvector_value; + cvm::rvector const force_tang = force.rvector_value - iprod * x.rvector_value; + + if (!group1->noforce) + group1->apply_force(-1.0 * force_tang); + + if (!group2->noforce) + group2->apply_force( force_tang); +} + + +cvm::real colvar::distance_dir::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (x1.rvector_value - x2.rvector_value).norm2(); +} + + +colvarvalue colvar::distance_dir::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return colvarvalue((x1.rvector_value - x2.rvector_value), colvarvalue::type_unit3vectorderiv); +} + + +colvarvalue colvar::distance_dir::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return colvarvalue((x2.rvector_value - x1.rvector_value), colvarvalue::type_unit3vectorderiv); +} + + + +colvar::distance_inv::distance_inv(std::string const &conf) + : cvc(conf) +{ + set_function_type("distanceInv"); + init_as_distance(); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + + get_keyval(conf, "exponent", exponent, 6); + if (exponent%2) { + cvm::error("Error: odd exponent provided, can only use even ones.\n"); + return; + } + if (exponent <= 0) { + cvm::error("Error: negative or zero exponent provided.\n"); + return; + } + + for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { + for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { + if (ai1->id == ai2->id) { + cvm::error("Error: group1 and group2 have some atoms in common: this is not allowed for distanceInv.\n"); + return; + } + } + } + + if (is_enabled(f_cvc_debug_gradient)) { + cvm::log("Warning: debugGradients will not give correct results " + "for distanceInv, because its value and gradients are computed " + "simultaneously.\n"); + } +} + + +void colvar::distance_inv::calc_value() +{ + x.real_value = 0.0; + if (!is_enabled(f_cvc_pbc_minimum_image)) { + for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { + for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { + cvm::rvector const dv = ai2->pos - ai1->pos; + cvm::real const d2 = dv.norm2(); + cvm::real const dinv = cvm::integer_power(d2, -1*(exponent/2)); + x.real_value += dinv; + cvm::rvector const dsumddv = -1.0*(exponent/2) * dinv/d2 * 2.0 * dv; + ai1->grad += -1.0 * dsumddv; + ai2->grad += dsumddv; + } + } + } else { + for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { + for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { + cvm::rvector const dv = cvm::position_distance(ai1->pos, ai2->pos); + cvm::real const d2 = dv.norm2(); + cvm::real const dinv = cvm::integer_power(d2, -1*(exponent/2)); + x.real_value += dinv; + cvm::rvector const dsumddv = -1.0*(exponent/2) * dinv/d2 * 2.0 * dv; + ai1->grad += -1.0 * dsumddv; + ai2->grad += dsumddv; + } + } + } + + x.real_value *= 1.0 / cvm::real(group1->size() * group2->size()); + x.real_value = cvm::pow(x.real_value, -1.0/cvm::real(exponent)); + + cvm::real const dxdsum = (-1.0/(cvm::real(exponent))) * + cvm::integer_power(x.real_value, exponent+1) / + cvm::real(group1->size() * group2->size()); + for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { + ai1->grad *= dxdsum; + } + for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { + ai2->grad *= dxdsum; + } +} + + +void colvar::distance_inv::calc_gradients() +{ +} + + +void colvar::distance_inv::apply_force(colvarvalue const &force) +{ + if (!group1->noforce) + group1->apply_colvar_force(force.real_value); + + if (!group2->noforce) + group2->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(distance_inv) + + + +colvar::distance_pairs::distance_pairs(std::string const &conf) + : cvc(conf) +{ + set_function_type("distancePairs"); + + group1 = parse_group(conf, "group1"); + group2 = parse_group(conf, "group2"); + + x.type(colvarvalue::type_vector); + disable(f_cvc_explicit_gradient); + x.vector1d_value.resize(group1->size() * group2->size()); +} + + +colvar::distance_pairs::distance_pairs() +{ + set_function_type("distancePairs"); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_vector); +} + + +void colvar::distance_pairs::calc_value() +{ + x.vector1d_value.resize(group1->size() * group2->size()); + + if (!is_enabled(f_cvc_pbc_minimum_image)) { + size_t i1, i2; + for (i1 = 0; i1 < group1->size(); i1++) { + for (i2 = 0; i2 < group2->size(); i2++) { + cvm::rvector const dv = (*group2)[i2].pos - (*group1)[i1].pos; + cvm::real const d = dv.norm(); + x.vector1d_value[i1*group2->size() + i2] = d; + (*group1)[i1].grad = -1.0 * dv.unit(); + (*group2)[i2].grad = dv.unit(); + } + } + } else { + size_t i1, i2; + for (i1 = 0; i1 < group1->size(); i1++) { + for (i2 = 0; i2 < group2->size(); i2++) { + cvm::rvector const dv = cvm::position_distance((*group1)[i1].pos, + (*group2)[i2].pos); + cvm::real const d = dv.norm(); + x.vector1d_value[i1*group2->size() + i2] = d; + (*group1)[i1].grad = -1.0 * dv.unit(); + (*group2)[i2].grad = dv.unit(); + } + } + } +} + + +void colvar::distance_pairs::calc_gradients() +{ + // will be calculated on the fly in apply_force() +} + + +void colvar::distance_pairs::apply_force(colvarvalue const &force) +{ + if (!is_enabled(f_cvc_pbc_minimum_image)) { + size_t i1, i2; + for (i1 = 0; i1 < group1->size(); i1++) { + for (i2 = 0; i2 < group2->size(); i2++) { + cvm::rvector const dv = (*group2)[i2].pos - (*group1)[i1].pos; + (*group1)[i1].apply_force(force[i1*group2->size() + i2] * (-1.0) * dv.unit()); + (*group2)[i2].apply_force(force[i1*group2->size() + i2] * dv.unit()); + } + } + } else { + size_t i1, i2; + for (i1 = 0; i1 < group1->size(); i1++) { + for (i2 = 0; i2 < group2->size(); i2++) { + cvm::rvector const dv = cvm::position_distance((*group1)[i1].pos, + (*group2)[i2].pos); + (*group1)[i1].apply_force(force[i1*group2->size() + i2] * (-1.0) * dv.unit()); + (*group2)[i2].apply_force(force[i1*group2->size() + i2] * dv.unit()); + } + } + } +} + + + +colvar::dipole_magnitude::dipole_magnitude(std::string const &conf) + : cvc(conf) +{ + set_function_type("dipoleMagnitude"); + atoms = parse_group(conf, "atoms"); + init_total_force_params(conf); + x.type(colvarvalue::type_scalar); +} + + +colvar::dipole_magnitude::dipole_magnitude(cvm::atom const &a1) +{ + set_function_type("dipoleMagnitude"); + atoms = new cvm::atom_group(std::vector(1, a1)); + register_atom_group(atoms); + x.type(colvarvalue::type_scalar); +} + + +colvar::dipole_magnitude::dipole_magnitude() +{ + set_function_type("dipoleMagnitude"); + x.type(colvarvalue::type_scalar); +} + + +void colvar::dipole_magnitude::calc_value() +{ + cvm::atom_pos const atomsCom = atoms->center_of_mass(); + atoms->calc_dipole(atomsCom); + dipoleV = atoms->dipole(); + x.real_value = dipoleV.norm(); +} + + +void colvar::dipole_magnitude::calc_gradients() +{ + cvm::real const aux1 = atoms->total_charge/atoms->total_mass; + cvm::atom_pos const dipVunit = dipoleV.unit(); + + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + ai->grad = (ai->charge - aux1*ai->mass) * dipVunit; + } +} + + +void colvar::dipole_magnitude::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) { + atoms->apply_colvar_force(force.real_value); + } +} + + +simple_scalar_dist_functions(dipole_magnitude) + + + +colvar::gyration::gyration(std::string const &conf) + : cvc(conf) +{ + set_function_type("gyration"); + init_as_distance(); + + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + atoms = parse_group(conf, "atoms"); + + if (atoms->b_user_defined_fit) { + cvm::log("WARNING: explicit fitting parameters were provided for atom group \"atoms\".\n"); + } else { + atoms->enable(f_ag_center); + atoms->ref_pos.assign(1, cvm::atom_pos(0.0, 0.0, 0.0)); + atoms->fit_gradients.assign(atoms->size(), cvm::rvector(0.0, 0.0, 0.0)); + } +} + + +void colvar::gyration::calc_value() +{ + x.real_value = 0.0; + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + x.real_value += (ai->pos).norm2(); + } + x.real_value = cvm::sqrt(x.real_value / cvm::real(atoms->size())); +} + + +void colvar::gyration::calc_gradients() +{ + cvm::real const drdx = 1.0/(cvm::real(atoms->size()) * x.real_value); + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + ai->grad = drdx * ai->pos; + } +} + + +void colvar::gyration::calc_force_invgrads() +{ + atoms->read_total_forces(); + + cvm::real const dxdr = 1.0/x.real_value; + ft.real_value = 0.0; + + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + ft.real_value += dxdr * ai->pos * ai->total_force; + } +} + + +void colvar::gyration::calc_Jacobian_derivative() +{ + jd = x.real_value ? (3.0 * cvm::real(atoms->size()) - 4.0) / x.real_value : 0.0; +} + + +void colvar::gyration::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(gyration) + + + +colvar::inertia::inertia(std::string const &conf) + : gyration(conf) +{ + set_function_type("inertia"); + init_as_distance(); +} + + +void colvar::inertia::calc_value() +{ + x.real_value = 0.0; + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + x.real_value += (ai->pos).norm2(); + } +} + + +void colvar::inertia::calc_gradients() +{ + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + ai->grad = 2.0 * ai->pos; + } +} + + +void colvar::inertia::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(inertia_z) + + + +colvar::inertia_z::inertia_z(std::string const &conf) + : inertia(conf) +{ + set_function_type("inertiaZ"); + init_as_distance(); + if (get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0))) { + if (axis.norm2() == 0.0) { + cvm::error("Axis vector is zero!", COLVARS_INPUT_ERROR); + return; + } + if (axis.norm2() != 1.0) { + axis = axis.unit(); + cvm::log("The normalized axis is: "+cvm::to_str(axis)+".\n"); + } + } +} + + +void colvar::inertia_z::calc_value() +{ + x.real_value = 0.0; + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + cvm::real const iprod = ai->pos * axis; + x.real_value += iprod * iprod; + } +} + + +void colvar::inertia_z::calc_gradients() +{ + for (cvm::atom_iter ai = atoms->begin(); ai != atoms->end(); ai++) { + ai->grad = 2.0 * (ai->pos * axis) * axis; + } +} + + +void colvar::inertia_z::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +simple_scalar_dist_functions(inertia) + + + + +colvar::rmsd::rmsd(std::string const &conf) + : cvc(conf) +{ + set_function_type("rmsd"); + init_as_distance(); + + provide(f_cvc_inv_gradient); + + atoms = parse_group(conf, "atoms"); + if (cvm::get_error()) return; + + if (!atoms || atoms->size() == 0) { + cvm::error("Error: \"atoms\" must contain at least 1 atom to compute RMSD."); + return; + } + + bool b_Jacobian_derivative = true; + if (atoms->fitting_group != NULL && b_Jacobian_derivative) { + cvm::log("The option \"fittingGroup\" (alternative group for fitting) was enabled: " + "Jacobian derivatives of the RMSD will not be calculated.\n"); + b_Jacobian_derivative = false; + } + if (b_Jacobian_derivative) provide(f_cvc_Jacobian); + + // the following is a simplified version of the corresponding atom group options; + // we need this because the reference coordinates defined inside the atom group + // may be used only for fitting, and even more so if fitting_group is used + if (get_keyval(conf, "refPositions", ref_pos, ref_pos)) { + cvm::log("Using reference positions from configuration file to calculate the variable.\n"); + if (ref_pos.size() != atoms->size()) { + cvm::error("Error: the number of reference positions provided ("+ + cvm::to_str(ref_pos.size())+ + ") does not match the number of atoms of group \"atoms\" ("+ + cvm::to_str(atoms->size())+").\n"); + return; + } + } else { // Only look for ref pos file if ref positions not already provided + std::string ref_pos_file; + if (get_keyval(conf, "refPositionsFile", ref_pos_file, std::string(""))) { + + if (ref_pos.size()) { + cvm::error("Error: cannot specify \"refPositionsFile\" and " + "\"refPositions\" at the same time.\n"); + return; + } + + std::string ref_pos_col; + double ref_pos_col_value=0.0; + + if (get_keyval(conf, "refPositionsCol", ref_pos_col, std::string(""))) { + // if provided, use PDB column to select coordinates + bool found = get_keyval(conf, "refPositionsColValue", ref_pos_col_value, 0.0); + if (found && ref_pos_col_value==0.0) { + cvm::error("Error: refPositionsColValue, " + "if provided, must be non-zero.\n"); + return; + } + } + + ref_pos.resize(atoms->size()); + + cvm::load_coords(ref_pos_file.c_str(), &ref_pos, atoms, + ref_pos_col, ref_pos_col_value); + } else { + cvm::error("Error: no reference positions for RMSD; use either refPositions of refPositionsFile."); + return; + } + } + + if (ref_pos.size() != atoms->size()) { + cvm::error("Error: found " + cvm::to_str(ref_pos.size()) + + " reference positions for RMSD; expected " + cvm::to_str(atoms->size())); + return; + } + + if (atoms->b_user_defined_fit) { + cvm::log("WARNING: explicit fitting parameters were provided for atom group \"atoms\".\n"); + } else { + // Default: fit everything + cvm::log("Enabling \"centerToReference\" and \"rotateToReference\", to minimize RMSD before calculating it as a variable: " + "if this is not the desired behavior, disable them explicitly within the \"atoms\" block.\n"); + atoms->enable(f_ag_center); + atoms->enable(f_ag_rotate); + // default case: reference positions for calculating the rmsd are also those used + // for fitting + atoms->ref_pos = ref_pos; + atoms->center_ref_pos(); + + cvm::log("This is a standard minimum RMSD, derivatives of the optimal rotation " + "will not be computed as they cancel out in the gradients."); + atoms->disable(f_ag_fit_gradients); + } + atoms->setup_rotation_derivative(); + + std::string perm_conf; + size_t pos = 0; // current position in config string + n_permutations = 1; + + while (key_lookup(conf, "atomPermutation", &perm_conf, &pos)) { + cvm::main()->cite_feature("Symmetry-adapted RMSD"); + std::vector perm; + if (perm_conf.size()) { + std::istringstream is(perm_conf); + size_t index; + while (is >> index) { + std::vector const &ids = atoms->ids(); + size_t const ia = std::find(ids.begin(), ids.end(), index-1) - ids.begin(); + if (ia == atoms->size()) { + cvm::error("Error: atom id " + cvm::to_str(index) + + " is not a member of group \"atoms\"."); + return; + } + if (std::find(perm.begin(), perm.end(), ia) != perm.end()) { + cvm::error("Error: atom id " + cvm::to_str(index) + + " is mentioned more than once in atomPermutation list."); + return; + } + perm.push_back(ia); + } + if (perm.size() != atoms->size()) { + cvm::error("Error: symmetry permutation in input contains " + cvm::to_str(perm.size()) + + " indices, but group \"atoms\" contains " + cvm::to_str(atoms->size()) + " atoms."); + return; + } + cvm::log("atomPermutation = " + cvm::to_str(perm)); + n_permutations++; + // Record a copy of reference positions in new order + for (size_t ia = 0; ia < atoms->size(); ia++) { + ref_pos.push_back(ref_pos[perm[ia]]); + } + } + } +} + + +void colvar::rmsd::calc_value() +{ + // rotational-translational fit is handled by the atom group + + x.real_value = 0.0; + for (size_t ia = 0; ia < atoms->size(); ia++) { + x.real_value += ((*atoms)[ia].pos - ref_pos[ia]).norm2(); + } + best_perm_index = 0; + + // Compute sum of squares for each symmetry permutation of atoms, keep the smallest + size_t ref_pos_index = atoms->size(); + for (size_t ip = 1; ip < n_permutations; ip++) { + cvm::real value = 0.0; + for (size_t ia = 0; ia < atoms->size(); ia++) { + value += ((*atoms)[ia].pos - ref_pos[ref_pos_index++]).norm2(); + } + if (value < x.real_value) { + x.real_value = value; + best_perm_index = ip; + } + } + x.real_value /= cvm::real(atoms->size()); // MSD + x.real_value = cvm::sqrt(x.real_value); +} + + +void colvar::rmsd::calc_gradients() +{ + cvm::real const drmsddx2 = (x.real_value > 0.0) ? + 0.5 / (x.real_value * cvm::real(atoms->size())) : + 0.0; + + // Use the appropriate symmetry permutation of reference positions to calculate gradients + size_t const start = atoms->size() * best_perm_index; + for (size_t ia = 0; ia < atoms->size(); ia++) { + (*atoms)[ia].grad = (drmsddx2 * 2.0 * ((*atoms)[ia].pos - ref_pos[start + ia])); + } +} + + +void colvar::rmsd::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +void colvar::rmsd::calc_force_invgrads() +{ + atoms->read_total_forces(); + ft.real_value = 0.0; + + // Note: gradient square norm is 1/N_atoms + + for (size_t ia = 0; ia < atoms->size(); ia++) { + ft.real_value += (*atoms)[ia].grad * (*atoms)[ia].total_force; + } + ft.real_value *= atoms->size(); +} + + +void colvar::rmsd::calc_Jacobian_derivative() +{ + // divergence of the rotated coordinates (including only derivatives of the rotation matrix) + cvm::real rotation_term = 0.0; + + // The rotation term only applies is coordinates are rotated + if (atoms->is_enabled(f_ag_rotate)) { + + // gradient of the rotation matrix + cvm::matrix2d grad_rot_mat(3, 3); + // gradients of products of 2 quaternion components + cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; + cvm::vector1d dq; + atoms->rot_deriv->prepare_derivative(rotation_derivative_dldq::use_dq); + for (size_t ia = 0; ia < atoms->size(); ia++) { + + // Gradient of optimal quaternion wrt current Cartesian position + atoms->rot_deriv->calc_derivative_wrt_group1(ia, nullptr, &dq); + + g11 = 2.0 * (atoms->rot.q)[1]*dq[1]; + g22 = 2.0 * (atoms->rot.q)[2]*dq[2]; + g33 = 2.0 * (atoms->rot.q)[3]*dq[3]; + g01 = (atoms->rot.q)[0]*dq[1] + (atoms->rot.q)[1]*dq[0]; + g02 = (atoms->rot.q)[0]*dq[2] + (atoms->rot.q)[2]*dq[0]; + g03 = (atoms->rot.q)[0]*dq[3] + (atoms->rot.q)[3]*dq[0]; + g12 = (atoms->rot.q)[1]*dq[2] + (atoms->rot.q)[2]*dq[1]; + g13 = (atoms->rot.q)[1]*dq[3] + (atoms->rot.q)[3]*dq[1]; + g23 = (atoms->rot.q)[2]*dq[3] + (atoms->rot.q)[3]*dq[2]; + + // Gradient of the rotation matrix wrt current Cartesian position + grad_rot_mat[0][0] = -2.0 * (g22 + g33); + grad_rot_mat[1][0] = 2.0 * (g12 + g03); + grad_rot_mat[2][0] = 2.0 * (g13 - g02); + grad_rot_mat[0][1] = 2.0 * (g12 - g03); + grad_rot_mat[1][1] = -2.0 * (g11 + g33); + grad_rot_mat[2][1] = 2.0 * (g01 + g23); + grad_rot_mat[0][2] = 2.0 * (g02 + g13); + grad_rot_mat[1][2] = 2.0 * (g23 - g01); + grad_rot_mat[2][2] = -2.0 * (g11 + g22); + + cvm::atom_pos &y = ref_pos[ia]; + + for (size_t alpha = 0; alpha < 3; alpha++) { + for (size_t beta = 0; beta < 3; beta++) { + rotation_term += grad_rot_mat[beta][alpha][alpha] * y[beta]; + // Note: equation was derived for inverse rotation (see colvars paper) + // so here the matrix is transposed + // (eq would give divergence += grad_rot_mat[alpha][beta][alpha] * y[beta];) + } + } + } + } + + // The translation term only applies is coordinates are centered + cvm::real translation_term = atoms->is_enabled(f_ag_center) ? 3.0 : 0.0; + + jd.real_value = x.real_value > 0.0 ? + (3.0 * atoms->size() - 1.0 - translation_term - rotation_term) / x.real_value : + 0.0; +} + + +simple_scalar_dist_functions(rmsd) + + + +colvar::eigenvector::eigenvector(std::string const &conf) + : cvc(conf) +{ + set_function_type("eigenvector"); + provide(f_cvc_inv_gradient); + provide(f_cvc_Jacobian); + x.type(colvarvalue::type_scalar); + + atoms = parse_group(conf, "atoms"); + if (cvm::get_error()) return; + + { + bool const b_inline = get_keyval(conf, "refPositions", ref_pos, ref_pos); + + if (b_inline) { + cvm::log("Using reference positions from input file.\n"); + if (ref_pos.size() != atoms->size()) { + cvm::error("Error: reference positions do not " + "match the number of requested atoms.\n"); + return; + } + } + + std::string file_name; + if (get_keyval(conf, "refPositionsFile", file_name)) { + + if (b_inline) { + cvm::error("Error: refPositions and refPositionsFile cannot be specified at the same time.\n"); + return; + } + + std::string file_col; + double file_col_value=0.0; + if (get_keyval(conf, "refPositionsCol", file_col, std::string(""))) { + // use PDB flags if column is provided + bool found = get_keyval(conf, "refPositionsColValue", file_col_value, 0.0); + if (found && file_col_value==0.0) { + cvm::error("Error: refPositionsColValue, " + "if provided, must be non-zero.\n"); + return; + } + } + + ref_pos.resize(atoms->size()); + cvm::load_coords(file_name.c_str(), &ref_pos, atoms, + file_col, file_col_value); + } + } + + if (ref_pos.size() == 0) { + cvm::error("Error: reference positions were not provided.\n", COLVARS_INPUT_ERROR); + return; + } + + if (ref_pos.size() != atoms->size()) { + cvm::error("Error: reference positions do not " + "match the number of requested atoms.\n", COLVARS_INPUT_ERROR); + return; + } + + // save for later the geometric center of the provided positions (may not be the origin) + cvm::rvector ref_pos_center(0.0, 0.0, 0.0); + for (size_t i = 0; i < atoms->size(); i++) { + ref_pos_center += ref_pos[i]; + } + ref_pos_center *= 1.0 / atoms->size(); + + if (atoms->b_user_defined_fit) { + cvm::log("WARNING: explicit fitting parameters were provided for atom group \"atoms\".\n"); + } else { + // default: fit everything + cvm::log("Enabling \"centerToReference\" and \"rotateToReference\", to minimize RMSD before calculating the vector projection: " + "if this is not the desired behavior, disable them explicitly within the \"atoms\" block.\n"); + atoms->enable(f_ag_center); + atoms->enable(f_ag_rotate); + atoms->ref_pos = ref_pos; + atoms->center_ref_pos(); + atoms->disable(f_ag_fit_gradients); // cancel out if group is fitted on itself + // and cvc is translationally invariant + } + atoms->setup_rotation_derivative(); + + { + bool const b_inline = get_keyval(conf, "vector", eigenvec, eigenvec); + // now load the eigenvector + if (b_inline) { + cvm::log("Using vector components from input file.\n"); + if (eigenvec.size() != atoms->size()) { + cvm::error("Error: vector components do not " + "match the number of requested atoms->\n"); + return; + } + } + + std::string file_name; + if (get_keyval(conf, "vectorFile", file_name)) { + + if (b_inline) { + cvm::error("Error: vector and vectorFile cannot be specified at the same time.\n"); + return; + } + + std::string file_col; + double file_col_value=0.0; + if (get_keyval(conf, "vectorCol", file_col, std::string(""))) { + // use PDB flags if column is provided + bool found = get_keyval(conf, "vectorColValue", file_col_value, 0.0); + if (found && file_col_value==0.0) { + cvm::error("Error: vectorColValue, if provided, must be non-zero.\n"); + return; + } + } + + eigenvec.resize(atoms->size()); + cvm::load_coords(file_name.c_str(), &eigenvec, atoms, + file_col, file_col_value); + } + } + + if (!ref_pos.size() || !eigenvec.size()) { + cvm::error("Error: both reference coordinates" + "and eigenvector must be defined.\n"); + return; + } + + cvm::atom_pos eig_center(0.0, 0.0, 0.0); + for (size_t eil = 0; eil < atoms->size(); eil++) { + eig_center += eigenvec[eil]; + } + eig_center *= 1.0 / atoms->size(); + cvm::log("Geometric center of the provided vector: "+cvm::to_str(eig_center)+"\n"); + + bool b_difference_vector = false; + get_keyval(conf, "differenceVector", b_difference_vector, false); + + if (b_difference_vector) { + + if (atoms->is_enabled(f_ag_center)) { + // both sets should be centered on the origin for fitting + for (size_t i = 0; i < atoms->size(); i++) { + eigenvec[i] -= eig_center; + ref_pos[i] -= ref_pos_center; + } + } + if (atoms->is_enabled(f_ag_rotate)) { + atoms->rot.calc_optimal_rotation(eigenvec, ref_pos); + const auto rot_mat = atoms->rot.matrix(); + for (size_t i = 0; i < atoms->size(); i++) { + eigenvec[i] = rot_mat * eigenvec[i]; + } + } + cvm::log("\"differenceVector\" is on: subtracting the reference positions from the provided vector: v = x_vec - x_ref.\n"); + for (size_t i = 0; i < atoms->size(); i++) { + eigenvec[i] -= ref_pos[i]; + } + if (atoms->is_enabled(f_ag_center)) { + // bring back the ref positions to where they were + for (size_t i = 0; i < atoms->size(); i++) { + ref_pos[i] += ref_pos_center; + } + } + + } else { + cvm::log("Centering the provided vector to zero.\n"); + for (size_t i = 0; i < atoms->size(); i++) { + eigenvec[i] -= eig_center; + } + } + + // eigenvec_invnorm2 is used when computing inverse gradients + eigenvec_invnorm2 = 0.0; + for (size_t ein = 0; ein < atoms->size(); ein++) { + eigenvec_invnorm2 += eigenvec[ein].norm2(); + } + eigenvec_invnorm2 = 1.0 / eigenvec_invnorm2; + + // Vector normalization overrides the default normalization for differenceVector + bool normalize = false; + get_keyval(conf, "normalizeVector", normalize, normalize); + + if (normalize) { + cvm::log("Normalizing the vector so that |v| = 1.\n"); + for (size_t i = 0; i < atoms->size(); i++) { + eigenvec[i] *= cvm::sqrt(eigenvec_invnorm2); + } + eigenvec_invnorm2 = 1.0; + } else if (b_difference_vector) { + cvm::log("Normalizing the vector so that the norm of the projection |v â‹… (x_vec - x_ref)| = 1.\n"); + for (size_t i = 0; i < atoms->size(); i++) { + eigenvec[i] *= eigenvec_invnorm2; + } + eigenvec_invnorm2 = 1.0/eigenvec_invnorm2; + } else { + cvm::log("The norm of the vector is |v| = "+ + cvm::to_str(1.0/cvm::sqrt(eigenvec_invnorm2))+".\n"); + } +} + + +void colvar::eigenvector::calc_value() +{ + x.real_value = 0.0; + for (size_t i = 0; i < atoms->size(); i++) { + x.real_value += ((*atoms)[i].pos - ref_pos[i]) * eigenvec[i]; + } +} + + +void colvar::eigenvector::calc_gradients() +{ + for (size_t ia = 0; ia < atoms->size(); ia++) { + (*atoms)[ia].grad = eigenvec[ia]; + } +} + + +void colvar::eigenvector::apply_force(colvarvalue const &force) +{ + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); +} + + +void colvar::eigenvector::calc_force_invgrads() +{ + atoms->read_total_forces(); + ft.real_value = 0.0; + + for (size_t ia = 0; ia < atoms->size(); ia++) { + ft.real_value += eigenvec_invnorm2 * (*atoms)[ia].grad * + (*atoms)[ia].total_force; + } +} + + +void colvar::eigenvector::calc_Jacobian_derivative() +{ + // gradient of the rotation matrix + cvm::matrix2d grad_rot_mat(3, 3); + cvm::quaternion &quat0 = atoms->rot.q; + + // gradients of products of 2 quaternion components + cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; + + cvm::real sum = 0.0; + + cvm::vector1d dq_1; + atoms->rot_deriv->prepare_derivative(rotation_derivative_dldq::use_dq); + for (size_t ia = 0; ia < atoms->size(); ia++) { + + // Gradient of optimal quaternion wrt current Cartesian position + // trick: d(R^-1)/dx = d(R^t)/dx = (dR/dx)^t + // we can just transpose the derivatives of the direct matrix + atoms->rot_deriv->calc_derivative_wrt_group1(ia, nullptr, &dq_1); + + g11 = 2.0 * quat0[1]*dq_1[1]; + g22 = 2.0 * quat0[2]*dq_1[2]; + g33 = 2.0 * quat0[3]*dq_1[3]; + g01 = quat0[0]*dq_1[1] + quat0[1]*dq_1[0]; + g02 = quat0[0]*dq_1[2] + quat0[2]*dq_1[0]; + g03 = quat0[0]*dq_1[3] + quat0[3]*dq_1[0]; + g12 = quat0[1]*dq_1[2] + quat0[2]*dq_1[1]; + g13 = quat0[1]*dq_1[3] + quat0[3]*dq_1[1]; + g23 = quat0[2]*dq_1[3] + quat0[3]*dq_1[2]; + + // Gradient of the inverse rotation matrix wrt current Cartesian position + // (transpose of the gradient of the direct rotation) + grad_rot_mat[0][0] = -2.0 * (g22 + g33); + grad_rot_mat[0][1] = 2.0 * (g12 + g03); + grad_rot_mat[0][2] = 2.0 * (g13 - g02); + grad_rot_mat[1][0] = 2.0 * (g12 - g03); + grad_rot_mat[1][1] = -2.0 * (g11 + g33); + grad_rot_mat[1][2] = 2.0 * (g01 + g23); + grad_rot_mat[2][0] = 2.0 * (g02 + g13); + grad_rot_mat[2][1] = 2.0 * (g23 - g01); + grad_rot_mat[2][2] = -2.0 * (g11 + g22); + + for (size_t i = 0; i < 3; i++) { + for (size_t j = 0; j < 3; j++) { + sum += grad_rot_mat[i][j][i] * eigenvec[ia][j]; + } + } + } + + jd.real_value = sum * cvm::sqrt(eigenvec_invnorm2); +} + + +simple_scalar_dist_functions(eigenvector) + + + +colvar::cartesian::cartesian(std::string const &conf) + : cvc(conf) +{ + set_function_type("cartesian"); + + atoms = parse_group(conf, "atoms"); + + bool use_x, use_y, use_z; + get_keyval(conf, "useX", use_x, true); + get_keyval(conf, "useY", use_y, true); + get_keyval(conf, "useZ", use_z, true); + + axes.clear(); + if (use_x) axes.push_back(0); + if (use_y) axes.push_back(1); + if (use_z) axes.push_back(2); + + if (axes.size() == 0) { + cvm::error("Error: a \"cartesian\" component was defined with all three axes disabled.\n"); + return; + } + + x.type(colvarvalue::type_vector); + disable(f_cvc_explicit_gradient); + // Don't try to access atoms if creation of the atom group failed + if (atoms != NULL) x.vector1d_value.resize(atoms->size() * axes.size()); +} + + +void colvar::cartesian::calc_value() +{ + size_t const dim = axes.size(); + size_t ia, j; + for (ia = 0; ia < atoms->size(); ia++) { + for (j = 0; j < dim; j++) { + x.vector1d_value[dim*ia + j] = (*atoms)[ia].pos[axes[j]]; + } + } +} + + +void colvar::cartesian::calc_gradients() +{ + // we're not using the "grad" member of each + // atom object, because it only can represent the gradient of a + // scalar colvar +} + + +void colvar::cartesian::apply_force(colvarvalue const &force) +{ + size_t const dim = axes.size(); + size_t ia, j; + if (!atoms->noforce) { + cvm::rvector f; + for (ia = 0; ia < atoms->size(); ia++) { + for (j = 0; j < dim; j++) { + f[axes[j]] = force.vector1d_value[dim*ia + j]; + } + (*atoms)[ia].apply_force(f); + } + } +} diff --git a/src/external/colvars/colvarcomp_gpath.cpp b/src/external/colvars/colvarcomp_gpath.cpp new file mode 100644 index 00000000000..ba7b11f92dc --- /dev/null +++ b/src/external/colvars/colvarcomp_gpath.cpp @@ -0,0 +1,830 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + +colvar::CartesianBasedPath::CartesianBasedPath(std::string const &conf): cvc(conf), atoms(nullptr), reference_frames(0) { + // Parse selected atoms + atoms = parse_group(conf, "atoms"); + has_user_defined_fitting = false; + std::string fitting_conf; + if (key_lookup(conf, "fittingAtoms", &fitting_conf)) { + has_user_defined_fitting = true; + } + // Lookup reference column of PDB + // Copied from the RMSD class + std::string reference_column; + double reference_column_value = 0.0; + if (get_keyval(conf, "refPositionsCol", reference_column, std::string(""))) { + bool found = get_keyval(conf, "refPositionsColValue", reference_column_value, reference_column_value); + if (found && reference_column_value == 0.0) { + cvm::error("Error: refPositionsColValue, " + "if provided, must be non-zero.\n"); + return; + } + } + // Lookup all reference frames + bool has_frames = true; + total_reference_frames = 0; + while (has_frames) { + std::string reference_position_file_lookup = "refPositionsFile" + cvm::to_str(total_reference_frames + 1); + if (key_lookup(conf, reference_position_file_lookup.c_str())) { + std::string reference_position_filename; + get_keyval(conf, reference_position_file_lookup.c_str(), reference_position_filename, std::string("")); + std::vector reference_position(atoms->size()); + cvm::load_coords(reference_position_filename.c_str(), &reference_position, atoms, reference_column, reference_column_value); + reference_frames.push_back(reference_position); + ++total_reference_frames; + } else { + has_frames = false; + } + } + // Setup alignment to compute RMSD with respect to reference frames + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + cvm::atom_group* tmp_atoms = parse_group(conf, "atoms"); + if (!has_user_defined_fitting) { + // Swipe from the rmsd class + tmp_atoms->enable(f_ag_center); + tmp_atoms->enable(f_ag_rotate); + tmp_atoms->ref_pos = reference_frames[i_frame]; + tmp_atoms->center_ref_pos(); + tmp_atoms->enable(f_ag_fit_gradients); + } else { + // parse a group of atoms for fitting + std::string fitting_group_name = std::string("fittingAtoms") + cvm::to_str(i_frame); + cvm::atom_group* tmp_fitting_atoms = new cvm::atom_group(fitting_group_name.c_str()); + tmp_fitting_atoms->parse(fitting_conf); + tmp_fitting_atoms->disable(f_ag_scalable); + tmp_fitting_atoms->fit_gradients.assign(tmp_fitting_atoms->size(), cvm::atom_pos(0.0, 0.0, 0.0)); + std::string reference_position_file_lookup = "refPositionsFile" + cvm::to_str(i_frame + 1); + std::string reference_position_filename; + get_keyval(conf, reference_position_file_lookup.c_str(), reference_position_filename, std::string("")); + std::vector reference_fitting_position(tmp_fitting_atoms->size()); + cvm::load_coords(reference_position_filename.c_str(), &reference_fitting_position, tmp_fitting_atoms, reference_column, reference_column_value); + // setup the atom group for calculating + tmp_atoms->enable(f_ag_center); + tmp_atoms->enable(f_ag_rotate); + tmp_atoms->b_user_defined_fit = true; + tmp_atoms->disable(f_ag_scalable); + tmp_atoms->ref_pos = reference_fitting_position; + tmp_atoms->center_ref_pos(); + tmp_atoms->enable(f_ag_fit_gradients); + tmp_atoms->enable(f_ag_fitting_group); + tmp_atoms->fitting_group = tmp_fitting_atoms; + reference_fitting_frames.push_back(reference_fitting_position); + } + tmp_atoms->setup_rotation_derivative(); + comp_atoms.push_back(tmp_atoms); + } + x.type(colvarvalue::type_scalar); + // Don't use implicit gradient + enable(f_cvc_explicit_gradient); +} + +colvar::CartesianBasedPath::~CartesianBasedPath() { + for (auto it_comp_atoms = comp_atoms.begin(); it_comp_atoms != comp_atoms.end(); ++it_comp_atoms) { + if (*it_comp_atoms != nullptr) { + delete (*it_comp_atoms); + (*it_comp_atoms) = nullptr; + } + } + // Avoid double-freeing due to CVC-in-CVC construct + atom_groups.clear(); +} + +void colvar::CartesianBasedPath::computeDistanceToReferenceFrames(std::vector& result) { + for (size_t i_frame = 0; i_frame < reference_frames.size(); ++i_frame) { + cvm::real frame_rmsd = 0.0; + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + frame_rmsd += ((*(comp_atoms[i_frame]))[i_atom].pos - reference_frames[i_frame][i_atom]).norm2(); + } + frame_rmsd /= cvm::real(atoms->size()); + frame_rmsd = cvm::sqrt(frame_rmsd); + result[i_frame] = frame_rmsd; + } +} + +// mainly used for determining the lambda value for arithmetic path +void colvar::CartesianBasedPath::computeDistanceBetweenReferenceFrames(std::vector& result) { + for (size_t i_frame = 0; i_frame < reference_frames.size() - 1; ++i_frame) { + std::vector this_frame_atom_pos(reference_frames[i_frame].size()); + std::vector next_frame_atom_pos(reference_frames[i_frame + 1].size()); + cvm::real frame_rmsd = 0.0; + const size_t this_index = i_frame; + const size_t next_index = i_frame + 1; + // compute COM of two successive images, respectively + cvm::atom_pos reference_cog_this, reference_cog_next; + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + reference_cog_this += reference_frames[this_index][i_atom]; + reference_cog_next += reference_frames[next_index][i_atom]; + } + reference_cog_this /= reference_frames[this_index].size(); + reference_cog_next /= reference_frames[next_index].size(); + // move all atoms to COM + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + this_frame_atom_pos[i_atom] = reference_frames[this_index][i_atom] - reference_cog_this; + next_frame_atom_pos[i_atom] = reference_frames[next_index][i_atom] - reference_cog_next; + } + cvm::rotation rot_this_to_next; + // compute the optimal rotation + rot_this_to_next.calc_optimal_rotation(this_frame_atom_pos, next_frame_atom_pos); + // compute rmsd between reference frames + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + frame_rmsd += (rot_this_to_next.q.rotate(this_frame_atom_pos[i_atom]) - next_frame_atom_pos[i_atom]).norm2(); + } + frame_rmsd /= cvm::real(atoms->size()); + frame_rmsd = cvm::sqrt(frame_rmsd); + result[i_frame] = frame_rmsd; + } +} + +colvar::gspath::gspath(std::string const &conf): CartesianBasedPath(conf) { + set_function_type("gspath"); + get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); + if (use_second_closest_frame == true) { + cvm::log(std::string("Geometric path s(σ) will use the second closest frame to compute s_(m-1)\n")); + } else { + cvm::log(std::string("Geometric path s(σ) will use the neighbouring frame to compute s_(m-1)\n")); + } + get_keyval(conf, "useThirdClosestFrame", use_third_closest_frame, false); + if (use_third_closest_frame == true) { + cvm::log(std::string("Geometric path s(σ) will use the third closest frame to compute s_(m+1)\n")); + } else { + cvm::log(std::string("Geometric path s(σ) will use the neighbouring frame to compute s_(m+1)\n")); + } + if (total_reference_frames < 2) { + cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gspath requires at least 2 frames.\n"); + return; + } + GeometricPathCV::GeometricPathBase::initialize(atoms->size(), cvm::atom_pos(), total_reference_frames, use_second_closest_frame, use_third_closest_frame); + cvm::log(std::string("Geometric pathCV(s) is initialized.\n")); + cvm::log(std::string("Geometric pathCV(s) loaded ") + cvm::to_str(reference_frames.size()) + std::string(" frames.\n")); +} + +void colvar::gspath::updateDistanceToReferenceFrames() { + computeDistanceToReferenceFrames(frame_distances); +} + +void colvar::gspath::prepareVectors() { + size_t i_atom; + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + // v1 = s_m - z + v1[i_atom] = reference_frames[min_frame_index_1][i_atom] - (*(comp_atoms[min_frame_index_1]))[i_atom].pos; + // v2 = z - s_(m-1) + v2[i_atom] = (*(comp_atoms[min_frame_index_2]))[i_atom].pos - reference_frames[min_frame_index_2][i_atom]; + } + if (min_frame_index_3 < 0 || min_frame_index_3 > M) { + cvm::atom_pos reference_cog_1, reference_cog_2; + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + reference_cog_1 += reference_frames[min_frame_index_1][i_atom]; + reference_cog_2 += reference_frames[min_frame_index_2][i_atom]; + } + reference_cog_1 /= cvm::real(reference_frames[min_frame_index_1].size()); + reference_cog_2 /= cvm::real(reference_frames[min_frame_index_2].size()); + std::vector tmp_reference_frame_1(reference_frames[min_frame_index_1].size()); + std::vector tmp_reference_frame_2(reference_frames[min_frame_index_2].size()); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + tmp_reference_frame_1[i_atom] = reference_frames[min_frame_index_1][i_atom] - reference_cog_1; + tmp_reference_frame_2[i_atom] = reference_frames[min_frame_index_2][i_atom] - reference_cog_2; + } + if (has_user_defined_fitting) { + cvm::atom_pos reference_fitting_cog_1, reference_fitting_cog_2; + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_1].size(); ++i_atom) { + reference_fitting_cog_1 += reference_fitting_frames[min_frame_index_1][i_atom]; + reference_fitting_cog_2 += reference_fitting_frames[min_frame_index_2][i_atom]; + } + reference_fitting_cog_1 /= cvm::real(reference_fitting_frames[min_frame_index_1].size()); + reference_fitting_cog_2 /= cvm::real(reference_fitting_frames[min_frame_index_2].size()); + std::vector tmp_reference_fitting_frame_1(reference_fitting_frames[min_frame_index_1].size()); + std::vector tmp_reference_fitting_frame_2(reference_fitting_frames[min_frame_index_2].size()); + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_1].size(); ++i_atom) { + tmp_reference_fitting_frame_1[i_atom] = reference_fitting_frames[min_frame_index_1][i_atom] - reference_fitting_cog_1; + tmp_reference_fitting_frame_2[i_atom] = reference_fitting_frames[min_frame_index_2][i_atom] - reference_fitting_cog_2; + } + rot_v3.calc_optimal_rotation(tmp_reference_fitting_frame_1, tmp_reference_fitting_frame_2); + } else { + rot_v3.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_2); + } + const auto rot_mat_v3 = rot_v3.matrix(); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + v3[i_atom] = rot_mat_v3 * tmp_reference_frame_1[i_atom] - tmp_reference_frame_2[i_atom]; + } + } else { + cvm::atom_pos reference_cog_1, reference_cog_3; + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + reference_cog_1 += reference_frames[min_frame_index_1][i_atom]; + reference_cog_3 += reference_frames[min_frame_index_3][i_atom]; + } + reference_cog_1 /= cvm::real(reference_frames[min_frame_index_1].size()); + reference_cog_3 /= cvm::real(reference_frames[min_frame_index_3].size()); + std::vector tmp_reference_frame_1(reference_frames[min_frame_index_1].size()); + std::vector tmp_reference_frame_3(reference_frames[min_frame_index_3].size()); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + tmp_reference_frame_1[i_atom] = reference_frames[min_frame_index_1][i_atom] - reference_cog_1; + tmp_reference_frame_3[i_atom] = reference_frames[min_frame_index_3][i_atom] - reference_cog_3; + } + if (has_user_defined_fitting) { + cvm::atom_pos reference_fitting_cog_1, reference_fitting_cog_3; + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_1].size(); ++i_atom) { + reference_fitting_cog_1 += reference_fitting_frames[min_frame_index_1][i_atom]; + reference_fitting_cog_3 += reference_fitting_frames[min_frame_index_3][i_atom]; + } + reference_fitting_cog_1 /= cvm::real(reference_fitting_frames[min_frame_index_1].size()); + reference_fitting_cog_3 /= cvm::real(reference_fitting_frames[min_frame_index_3].size()); + std::vector tmp_reference_fitting_frame_1(reference_fitting_frames[min_frame_index_1].size()); + std::vector tmp_reference_fitting_frame_3(reference_fitting_frames[min_frame_index_3].size()); + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_1].size(); ++i_atom) { + tmp_reference_fitting_frame_1[i_atom] = reference_fitting_frames[min_frame_index_1][i_atom] - reference_fitting_cog_1; + tmp_reference_fitting_frame_3[i_atom] = reference_fitting_frames[min_frame_index_3][i_atom] - reference_fitting_cog_3; + } + rot_v3.calc_optimal_rotation(tmp_reference_fitting_frame_1, tmp_reference_fitting_frame_3); + } else { + rot_v3.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_3); + } + const auto rot_mat_v3 = rot_v3.matrix(); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + // v3 = s_(m+1) - s_m + v3[i_atom] = tmp_reference_frame_3[i_atom] - rot_mat_v3 * tmp_reference_frame_1[i_atom]; + } + } +} + +void colvar::gspath::calc_value() { + computeValue(); + x = s; +} + +void colvar::gspath::calc_gradients() { + computeDerivatives(); + cvm::rvector tmp_atom_grad_v1, tmp_atom_grad_v2; + // dS(v1, v2(r), v3) / dr = ∂S/∂v1 * dv1/dr + ∂S/∂v2 * dv2/dr + // dv1/dr = [fitting matrix 1][-1, ..., -1] + // dv2/dr = [fitting matrix 2][1, ..., 1] + // ∂S/∂v1 = ± (∂f/∂v1) / (2M) + // ∂S/∂v2 = ± (∂f/∂v2) / (2M) + // dS(v1, v2(r), v3) / dr = -1.0 * ± (∂f/∂v1) / (2M) + ± (∂f/∂v2) / (2M) + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + tmp_atom_grad_v1[0] = -1.0 * sign * 0.5 * dfdv1[i_atom][0] / M; + tmp_atom_grad_v1[1] = -1.0 * sign * 0.5 * dfdv1[i_atom][1] / M; + tmp_atom_grad_v1[2] = -1.0 * sign * 0.5 * dfdv1[i_atom][2] / M; + tmp_atom_grad_v2[0] = sign * 0.5 * dfdv2[i_atom][0] / M; + tmp_atom_grad_v2[1] = sign * 0.5 * dfdv2[i_atom][1] / M; + tmp_atom_grad_v2[2] = sign * 0.5 * dfdv2[i_atom][2] / M; + (*(comp_atoms[min_frame_index_1]))[i_atom].grad += tmp_atom_grad_v1; + (*(comp_atoms[min_frame_index_2]))[i_atom].grad += tmp_atom_grad_v2; + } +} + +void colvar::gspath::apply_force(colvarvalue const &force) { + // The force applied to this CV is scalar type + cvm::real const &F = force.real_value; + (*(comp_atoms[min_frame_index_1])).apply_colvar_force(F); + (*(comp_atoms[min_frame_index_2])).apply_colvar_force(F); +} + +colvar::gzpath::gzpath(std::string const &conf): CartesianBasedPath(conf) { + set_function_type("gzpath"); + get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); + if (use_second_closest_frame == true) { + cvm::log(std::string("Geometric path z(σ) will use the second closest frame to compute s_(m-1)\n")); + } else { + cvm::log(std::string("Geometric path z(σ) will use the neighbouring frame to compute s_(m-1)\n")); + } + get_keyval(conf, "useThirdClosestFrame", use_third_closest_frame, false); + if (use_third_closest_frame == true) { + cvm::log(std::string("Geometric path z(σ) will use the third closest frame to compute s_(m+1)\n")); + } else { + cvm::log(std::string("Geometric path z(σ) will use the neighbouring frame to compute s_(m+1)\n")); + } + bool b_use_z_square = false; + get_keyval(conf, "useZsquare", b_use_z_square, false); + if (b_use_z_square == true) { + cvm::log(std::string("Geometric path z(σ) will use the square of distance from current frame to path compute z\n")); + } + if (total_reference_frames < 2) { + cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gzpath requires at least 2 frames.\n"); + return; + } + GeometricPathCV::GeometricPathBase::initialize(atoms->size(), cvm::atom_pos(), total_reference_frames, use_second_closest_frame, use_third_closest_frame, b_use_z_square); + // Logging + cvm::log(std::string("Geometric pathCV(z) is initialized.\n")); + cvm::log(std::string("Geometric pathCV(z) loaded ") + cvm::to_str(reference_frames.size()) + std::string(" frames.\n")); +} + +void colvar::gzpath::updateDistanceToReferenceFrames() { + computeDistanceToReferenceFrames(frame_distances); +} + +void colvar::gzpath::prepareVectors() { + cvm::atom_pos reference_cog_1, reference_cog_2; + size_t i_atom; + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + reference_cog_1 += reference_frames[min_frame_index_1][i_atom]; + reference_cog_2 += reference_frames[min_frame_index_2][i_atom]; + } + reference_cog_1 /= cvm::real(reference_frames[min_frame_index_1].size()); + reference_cog_2 /= cvm::real(reference_frames[min_frame_index_2].size()); + std::vector tmp_reference_frame_1(reference_frames[min_frame_index_1].size()); + std::vector tmp_reference_frame_2(reference_frames[min_frame_index_2].size()); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + tmp_reference_frame_1[i_atom] = reference_frames[min_frame_index_1][i_atom] - reference_cog_1; + tmp_reference_frame_2[i_atom] = reference_frames[min_frame_index_2][i_atom] - reference_cog_2; + } + std::vector tmp_reference_fitting_frame_1; + std::vector tmp_reference_fitting_frame_2; + if (has_user_defined_fitting) { + cvm::atom_pos reference_fitting_cog_1, reference_fitting_cog_2; + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_1].size(); ++i_atom) { + reference_fitting_cog_1 += reference_fitting_frames[min_frame_index_1][i_atom]; + reference_fitting_cog_2 += reference_fitting_frames[min_frame_index_2][i_atom]; + } + reference_fitting_cog_1 /= cvm::real(reference_fitting_frames[min_frame_index_1].size()); + reference_fitting_cog_2 /= cvm::real(reference_fitting_frames[min_frame_index_2].size()); + tmp_reference_fitting_frame_1.resize(reference_fitting_frames[min_frame_index_1].size()); + tmp_reference_fitting_frame_2.resize(reference_fitting_frames[min_frame_index_2].size()); + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_1].size(); ++i_atom) { + tmp_reference_fitting_frame_1[i_atom] = reference_fitting_frames[min_frame_index_1][i_atom] - reference_fitting_cog_1; + tmp_reference_fitting_frame_2[i_atom] = reference_fitting_frames[min_frame_index_2][i_atom] - reference_fitting_cog_2; + } + rot_v4.calc_optimal_rotation(tmp_reference_fitting_frame_1, tmp_reference_fitting_frame_2); + } else { + rot_v4.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_2); + } + const auto rot_mat_v4 = rot_v4.matrix(); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + v1[i_atom] = reference_frames[min_frame_index_1][i_atom] - (*(comp_atoms[min_frame_index_1]))[i_atom].pos; + v2[i_atom] = (*(comp_atoms[min_frame_index_2]))[i_atom].pos - reference_frames[min_frame_index_2][i_atom]; + // v4 only computes in gzpath + // v4 = s_m - s_(m-1) + v4[i_atom] = rot_mat_v4 * tmp_reference_frame_1[i_atom] - tmp_reference_frame_2[i_atom]; + } + if (min_frame_index_3 < 0 || min_frame_index_3 > M) { + v3 = v4; + } else { + cvm::atom_pos reference_cog_3; + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + reference_cog_3 += reference_frames[min_frame_index_3][i_atom]; + } + reference_cog_3 /= cvm::real(reference_frames[min_frame_index_3].size()); + std::vector tmp_reference_frame_3(reference_frames[min_frame_index_3].size()); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + tmp_reference_frame_3[i_atom] = reference_frames[min_frame_index_3][i_atom] - reference_cog_3; + } + if (has_user_defined_fitting) { + cvm::atom_pos reference_fitting_cog_3; + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_3].size(); ++i_atom) { + reference_fitting_cog_3 += reference_fitting_frames[min_frame_index_3][i_atom]; + } + reference_fitting_cog_3 /= cvm::real(reference_fitting_frames[min_frame_index_3].size()); + std::vector tmp_reference_fitting_frame_3(reference_fitting_frames[min_frame_index_3].size()); + for (i_atom = 0; i_atom < reference_fitting_frames[min_frame_index_3].size(); ++i_atom) { + tmp_reference_fitting_frame_3[i_atom] = reference_fitting_frames[min_frame_index_3][i_atom] - reference_fitting_cog_3; + } + rot_v3.calc_optimal_rotation(tmp_reference_fitting_frame_1, tmp_reference_fitting_frame_3); + } else { + rot_v3.calc_optimal_rotation(tmp_reference_frame_1, tmp_reference_frame_3); + } + const auto rot_mat_v3 = rot_v3.matrix(); + for (i_atom = 0; i_atom < atoms->size(); ++i_atom) { + // v3 = s_(m+1) - s_m + v3[i_atom] = tmp_reference_frame_3[i_atom] - rot_mat_v3 * tmp_reference_frame_1[i_atom]; + } + } +} + +void colvar::gzpath::calc_value() { + computeValue(); + x = z; +} + +void colvar::gzpath::calc_gradients() { + computeDerivatives(); + cvm::rvector tmp_atom_grad_v1, tmp_atom_grad_v2; + for (size_t i_atom = 0; i_atom < atoms->size(); ++i_atom) { + tmp_atom_grad_v1 = -1.0 * dzdv1[i_atom]; + tmp_atom_grad_v2 = dzdv2[i_atom]; + (*(comp_atoms[min_frame_index_1]))[i_atom].grad += tmp_atom_grad_v1; + (*(comp_atoms[min_frame_index_2]))[i_atom].grad += tmp_atom_grad_v2; + } +} + +void colvar::gzpath::apply_force(colvarvalue const &force) { + // The force applied to this CV is scalar type + cvm::real const &F = force.real_value; + (*(comp_atoms[min_frame_index_1])).apply_colvar_force(F); + (*(comp_atoms[min_frame_index_2])).apply_colvar_force(F); +} + + +colvar::CVBasedPath::CVBasedPath(std::string const &conf): cvc(conf) { + // Lookup all available sub-cvcs + for (auto it_cv_map = colvar::get_global_cvc_map().begin(); it_cv_map != colvar::get_global_cvc_map().end(); ++it_cv_map) { + if (key_lookup(conf, it_cv_map->first.c_str())) { + std::vector sub_cvc_confs; + get_key_string_multi_value(conf, it_cv_map->first.c_str(), sub_cvc_confs); + for (auto it_sub_cvc_conf = sub_cvc_confs.begin(); it_sub_cvc_conf != sub_cvc_confs.end(); ++it_sub_cvc_conf) { + cv.push_back((it_cv_map->second)(*(it_sub_cvc_conf))); + } + } + } + // Sort all sub CVs by their names + std::sort(cv.begin(), cv.end(), colvar::compare_cvc); + // Register atom groups and determine the colvar type for reference + std::vector tmp_cv; + for (auto it_sub_cv = cv.begin(); it_sub_cv != cv.end(); ++it_sub_cv) { + for (auto it_atom_group = (*it_sub_cv)->atom_groups.begin(); it_atom_group != (*it_sub_cv)->atom_groups.end(); ++it_atom_group) { + register_atom_group(*it_atom_group); + } + colvarvalue tmp_i_cv((*it_sub_cv)->value()); + tmp_i_cv.reset(); + tmp_cv.push_back(tmp_i_cv); + } + // Read path file + // Lookup all reference CV values + std::string path_filename; + get_keyval(conf, "pathFile", path_filename); + cvm::log(std::string("Reading path file: ") + path_filename + std::string("\n")); + auto &ifs_path = cvm::main()->proxy->input_stream(path_filename); + if (!ifs_path) { + return; + } + std::string line; + const std::string token(" "); + total_reference_frames = 0; + while (std::getline(ifs_path, line)) { + std::vector fields; + split_string(line, token, fields); + size_t num_value_required = 0; + cvm::log(std::string("Reading reference frame ") + cvm::to_str(total_reference_frames + 1) + std::string("\n")); + for (size_t i_cv = 0; i_cv < tmp_cv.size(); ++i_cv) { + const size_t value_size = tmp_cv[i_cv].size(); + num_value_required += value_size; + cvm::log(std::string("Reading CV ") + cv[i_cv]->name + std::string(" with ") + cvm::to_str(value_size) + std::string(" value(s)\n")); + if (num_value_required <= fields.size()) { + size_t start_index = num_value_required - value_size; + for (size_t i = start_index; i < num_value_required; ++i) { + tmp_cv[i_cv][i - start_index] = std::atof(fields[i].c_str()); + cvm::log(cvm::to_str(tmp_cv[i_cv][i - start_index])); + } + } else { + cvm::error("Error: incorrect format of path file.\n"); + return; + } + } + if (!fields.empty()) { + ref_cv.push_back(tmp_cv); + ++total_reference_frames; + } + } + cvm::main()->proxy->close_input_stream(path_filename); + if (total_reference_frames <= 1) { + cvm::error("Error: there is only 1 or 0 reference frame, which doesn't constitute a path.\n"); + return; + } + if (cv.size() == 0) { + cvm::error("Error: the CV " + name + + " expects one or more nesting components.\n"); + return; + } + x.type(colvarvalue::type_scalar); + use_explicit_gradients = true; + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + if (!cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + use_explicit_gradients = false; + } + } + if (!use_explicit_gradients) { + disable(f_cvc_explicit_gradient); + } +} + +void colvar::CVBasedPath::computeDistanceToReferenceFrames(std::vector& result) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_value(); + } + for (size_t i_frame = 0; i_frame < ref_cv.size(); ++i_frame) { + cvm::real rmsd_i = 0.0; + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + colvarvalue ref_cv_value(ref_cv[i_frame][i_cv]); + colvarvalue current_cv_value(cv[i_cv]->value()); + // polynomial combination allowed + if (current_cv_value.type() == colvarvalue::type_scalar) { + // wrapping is already in dist2 + rmsd_i += cv[i_cv]->dist2(cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)), ref_cv_value.real_value); + } else { + rmsd_i += cv[i_cv]->dist2(cv[i_cv]->sup_coeff * current_cv_value, ref_cv_value); + } + } + rmsd_i /= cvm::real(cv.size()); + rmsd_i = cvm::sqrt(rmsd_i); + result[i_frame] = rmsd_i; + } +} + +void colvar::CVBasedPath::computeDistanceBetweenReferenceFrames(std::vector& result) const { + if (ref_cv.size() < 2) return; + for (size_t i_frame = 1; i_frame < ref_cv.size(); ++i_frame) { + cvm::real dist_ij = 0.0; + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + colvarvalue ref_cv_value(ref_cv[i_frame][i_cv]); + colvarvalue prev_ref_cv_value(ref_cv[i_frame-1][i_cv]); + dist_ij += cv[i_cv]->dist2(ref_cv_value, prev_ref_cv_value); + } + dist_ij = cvm::sqrt(dist_ij); + result[i_frame-1] = dist_ij; + } +} + +cvm::real colvar::CVBasedPath::getPolynomialFactorOfCVGradient(size_t i_cv) const { + cvm::real factor_polynomial = 1.0; + if (cv[i_cv]->value().type() == colvarvalue::type_scalar) { + factor_polynomial = cv[i_cv]->sup_coeff * cv[i_cv]->sup_np * cvm::pow(cv[i_cv]->value().real_value, cv[i_cv]->sup_np - 1); + } else { + factor_polynomial = cv[i_cv]->sup_coeff; + } + return factor_polynomial; +} + +colvar::CVBasedPath::~CVBasedPath() { + // Recall the steps we initialize the sub-CVCs: + // 1. Lookup all sub-CVCs and then register the atom groups for sub-CVCs + // in their constructors; + // 2. Iterate over all sub-CVCs, get the pointers of their atom groups + // groups, and register again in the parent (current) CVC. + // That being said, the atom groups become children of the sub-CVCs at + // first, and then become children of the parent CVC. + // So, to destruct this class (parent CVC class), we need to remove the + // dependencies of the atom groups to the parent CVC at first. + remove_all_children(); + // Then we remove the dependencies of the atom groups to the sub-CVCs + // in their destructors. + for (auto it = cv.begin(); it != cv.end(); ++it) { + delete (*it); + } + // The last step is cleaning up the list of atom groups. + atom_groups.clear(); +} + +colvar::gspathCV::gspathCV(std::string const &conf): CVBasedPath(conf) { + set_function_type("gspathCV"); + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + // Initialize variables for future calculation + get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); + if (use_second_closest_frame == true) { + cvm::log(std::string("Geometric path s(σ) will use the second closest frame to compute s_(m-1)\n")); + } else { + cvm::log(std::string("Geometric path s(σ) will use the neighbouring frame to compute s_(m-1)\n")); + } + get_keyval(conf, "useThirdClosestFrame", use_third_closest_frame, false); + if (use_third_closest_frame == true) { + cvm::log(std::string("Geometric path s(σ) will use the third closest frame to compute s_(m+1)\n")); + } else { + cvm::log(std::string("Geometric path s(σ) will use the neighbouring frame to compute s_(m+1)\n")); + } + if (total_reference_frames < 2) { + cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gspathCV requires at least 2 frames.\n"); + return; + } + GeometricPathCV::GeometricPathBase::initialize(cv.size(), ref_cv[0], total_reference_frames, use_second_closest_frame, use_third_closest_frame); + x.type(colvarvalue::type_scalar); +} + +colvar::gspathCV::~gspathCV() {} + +void colvar::gspathCV::updateDistanceToReferenceFrames() { + computeDistanceToReferenceFrames(frame_distances); +} + +void colvar::gspathCV::prepareVectors() { + // Compute v1, v2 and v3 + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // values of sub-cvc are computed in update_distances + // cv[i_cv]->calc_value(); + colvarvalue f1_ref_cv_i_value(ref_cv[min_frame_index_1][i_cv]); + colvarvalue f2_ref_cv_i_value(ref_cv[min_frame_index_2][i_cv]); + colvarvalue current_cv_value(cv[i_cv]->value()); + // polynomial combination allowed + if (current_cv_value.type() == colvarvalue::type_scalar) { + v1[i_cv] = f1_ref_cv_i_value.real_value - cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)); + v2[i_cv] = cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)) - f2_ref_cv_i_value.real_value; + } else { + v1[i_cv] = f1_ref_cv_i_value - cv[i_cv]->sup_coeff * current_cv_value; + v2[i_cv] = cv[i_cv]->sup_coeff * current_cv_value - f2_ref_cv_i_value; + } + cv[i_cv]->wrap(v1[i_cv]); + cv[i_cv]->wrap(v2[i_cv]); + } + if (min_frame_index_3 < 0 || min_frame_index_3 > M) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + v3[i_cv] = ref_cv[min_frame_index_1][i_cv] - ref_cv[min_frame_index_2][i_cv]; + cv[i_cv]->wrap(v3[i_cv]); + } + } else { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + v3[i_cv] = ref_cv[min_frame_index_3][i_cv] - ref_cv[min_frame_index_1][i_cv]; + cv[i_cv]->wrap(v3[i_cv]); + } + } +} + +void colvar::gspathCV::calc_value() { + computeValue(); + x = s; +} + +void colvar::gspathCV::calc_gradients() { + computeDerivatives(); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // No matter whether the i-th cv uses implicit gradient, compute it first. + cv[i_cv]->calc_gradients(); + // If the gradient is not implicit, then add the gradients to its atom groups + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + // Temporary variables storing gradients + colvarvalue tmp_cv_grad_v1(cv[i_cv]->value()); + colvarvalue tmp_cv_grad_v2(cv[i_cv]->value()); + // Compute factors for polynomial combinations + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + // Loop over all elements of the corresponding colvar value + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + // ds/dz, z = vector of CVs + tmp_cv_grad_v1[j_elem] = -1.0 * sign * 0.5 * dfdv1[i_cv][j_elem] / M; + tmp_cv_grad_v2[j_elem] = sign * 0.5 * dfdv2[i_cv][j_elem] / M; + // Apply the gradients to the atom groups in i-th cv + // Loop over all atom groups + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + // Loop over all atoms in the k-th atom group + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + // Chain rule + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = factor_polynomial * ((*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad * tmp_cv_grad_v1[j_elem] + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad * tmp_cv_grad_v2[j_elem]); + } + } + } + } + } +} + +void colvar::gspathCV::apply_force(colvarvalue const &force) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // If this CV us explicit gradients, then atomic gradients is already calculated + // We can apply the force to atom groups directly + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + // Temporary variables storing gradients + colvarvalue tmp_cv_grad_v1(cv[i_cv]->value()); + colvarvalue tmp_cv_grad_v2(cv[i_cv]->value()); + // Compute factors for polynomial combinations + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + // ds/dz, z = vector of CVs + tmp_cv_grad_v1[j_elem] = -1.0 * sign * 0.5 * dfdv1[i_cv][j_elem] / M; + tmp_cv_grad_v2[j_elem] = sign * 0.5 * dfdv2[i_cv][j_elem] / M; + } + colvarvalue cv_force = force.real_value * factor_polynomial * (tmp_cv_grad_v1 + tmp_cv_grad_v2); + cv[i_cv]->apply_force(cv_force); + } + } +} + +colvar::gzpathCV::gzpathCV(std::string const &conf): CVBasedPath(conf) { + set_function_type("gzpathCV"); + cvm::log(std::string("Total number of frames: ") + cvm::to_str(total_reference_frames) + std::string("\n")); + // Initialize variables for future calculation + M = cvm::real(total_reference_frames - 1); + m = 1.0; + get_keyval(conf, "useSecondClosestFrame", use_second_closest_frame, true); + if (use_second_closest_frame == true) { + cvm::log(std::string("Geometric path z(σ) will use the second closest frame to compute s_(m-1)\n")); + } else { + cvm::log(std::string("Geometric path z(σ) will use the neighbouring frame to compute s_(m-1)\n")); + } + get_keyval(conf, "useThirdClosestFrame", use_third_closest_frame, false); + if (use_third_closest_frame == true) { + cvm::log(std::string("Geometric path z(σ) will use the third closest frame to compute s_(m+1)\n")); + } else { + cvm::log(std::string("Geometric path z(σ) will use the neighbouring frame to compute s_(m+1)\n")); + } + bool b_use_z_square = false; + get_keyval(conf, "useZsquare", b_use_z_square, false); + if (b_use_z_square == true) { + cvm::log(std::string("Geometric path z(σ) will use the square of distance from current frame to path compute z\n")); + } + if (total_reference_frames < 2) { + cvm::error("Error: you have specified " + cvm::to_str(total_reference_frames) + " reference frames, but gzpathCV requires at least 2 frames.\n"); + return; + } + GeometricPathCV::GeometricPathBase::initialize(cv.size(), ref_cv[0], total_reference_frames, use_second_closest_frame, use_third_closest_frame, b_use_z_square); + x.type(colvarvalue::type_scalar); +} + +colvar::gzpathCV::~gzpathCV() { +} + +void colvar::gzpathCV::updateDistanceToReferenceFrames() { + computeDistanceToReferenceFrames(frame_distances); +} + +void colvar::gzpathCV::prepareVectors() { + // Compute v1, v2 and v3 + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // values of sub-cvc are computed in update_distances + // cv[i_cv]->calc_value(); + colvarvalue f1_ref_cv_i_value(ref_cv[min_frame_index_1][i_cv]); + colvarvalue f2_ref_cv_i_value(ref_cv[min_frame_index_2][i_cv]); + colvarvalue current_cv_value(cv[i_cv]->value()); + // polynomial combination allowed + if (current_cv_value.type() == colvarvalue::type_scalar) { + v1[i_cv] = f1_ref_cv_i_value.real_value - cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)); + v2[i_cv] = cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)) - f2_ref_cv_i_value.real_value; + } else { + v1[i_cv] = f1_ref_cv_i_value - cv[i_cv]->sup_coeff * current_cv_value; + v2[i_cv] = cv[i_cv]->sup_coeff * current_cv_value - f2_ref_cv_i_value; + } + v4[i_cv] = f1_ref_cv_i_value - f2_ref_cv_i_value; + cv[i_cv]->wrap(v1[i_cv]); + cv[i_cv]->wrap(v2[i_cv]); + cv[i_cv]->wrap(v4[i_cv]); + } + if (min_frame_index_3 < 0 || min_frame_index_3 > M) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + v3[i_cv] = ref_cv[min_frame_index_1][i_cv] - ref_cv[min_frame_index_2][i_cv]; + cv[i_cv]->wrap(v3[i_cv]); + } + } else { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + v3[i_cv] = ref_cv[min_frame_index_3][i_cv] - ref_cv[min_frame_index_1][i_cv]; + cv[i_cv]->wrap(v3[i_cv]); + } + } +} + +void colvar::gzpathCV::calc_value() { + computeValue(); + x = z; +} + +void colvar::gzpathCV::calc_gradients() { + computeDerivatives(); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // No matter whether the i-th cv uses implicit gradient, compute it first. + cv[i_cv]->calc_gradients(); + // If the gradient is not implicit, then add the gradients to its atom groups + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + // Temporary variables storing gradients + colvarvalue tmp_cv_grad_v1 = -1.0 * dzdv1[i_cv]; + colvarvalue tmp_cv_grad_v2 = 1.0 * dzdv2[i_cv]; + // Compute factors for polynomial combinations + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + // Apply the gradients to the atom groups in i-th cv + // Loop over all atom groups + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + // Loop over all atoms in the k-th atom group + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + // Chain rule + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = factor_polynomial * ((*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad * tmp_cv_grad_v1[j_elem] + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad * tmp_cv_grad_v2[j_elem]); + } + } + } + } + } +} + +void colvar::gzpathCV::apply_force(colvarvalue const &force) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // If this CV us explicit gradients, then atomic gradients is already calculated + // We can apply the force to atom groups directly + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + colvarvalue tmp_cv_grad_v1 = -1.0 * dzdv1[i_cv]; + colvarvalue tmp_cv_grad_v2 = 1.0 * dzdv2[i_cv]; + // Temporary variables storing gradients + // Compute factors for polynomial combinations + cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + colvarvalue cv_force = force.real_value * factor_polynomial * (tmp_cv_grad_v1 + tmp_cv_grad_v2); + cv[i_cv]->apply_force(cv_force); + } + } +} diff --git a/src/external/colvars/colvarcomp_neuralnetwork.cpp b/src/external/colvars/colvarcomp_neuralnetwork.cpp new file mode 100644 index 00000000000..e8ad629b19e --- /dev/null +++ b/src/external/colvars/colvarcomp_neuralnetwork.cpp @@ -0,0 +1,193 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include "colvar_neuralnetworkcompute.h" + +using namespace neuralnetworkCV; + +colvar::neuralNetwork::neuralNetwork(std::string const &conf): linearCombination(conf) { + set_function_type("neuralNetwork"); + // the output of neural network consists of multiple values + // read "output_component" key to determine it + get_keyval(conf, "output_component", m_output_index); + // read weight files + bool has_weight_files = true; + size_t num_layers_weight = 0; + std::vector weight_files; + while (has_weight_files) { + std::string lookup_key = std::string{"layer"} + cvm::to_str(num_layers_weight + 1) + std::string{"_WeightsFile"}; + if (key_lookup(conf, lookup_key.c_str())) { + std::string weight_filename; + get_keyval(conf, lookup_key.c_str(), weight_filename, std::string("")); + weight_files.push_back(weight_filename); + cvm::log(std::string{"Will read layer["} + cvm::to_str(num_layers_weight + 1) + std::string{"] weights from "} + weight_filename + '\n'); + ++num_layers_weight; + } else { + has_weight_files = false; + } + } + // read bias files + bool has_bias_files = true; + size_t num_layers_bias = 0; + std::vector bias_files; + while (has_bias_files) { + std::string lookup_key = std::string{"layer"} + cvm::to_str(num_layers_bias + 1) + std::string{"_BiasesFile"}; + if (key_lookup(conf, lookup_key.c_str())) { + std::string bias_filename; + get_keyval(conf, lookup_key.c_str(), bias_filename, std::string("")); + bias_files.push_back(bias_filename); + cvm::log(std::string{"Will read layer["} + cvm::to_str(num_layers_bias + 1) + std::string{"] biases from "} + bias_filename + '\n'); + ++num_layers_bias; + } else { + has_bias_files = false; + } + } + // read activation function strings + bool has_activation_functions = true; + size_t num_activation_functions = 0; + // pair(is_custom_function, function_string) + std::vector> activation_functions; + while (has_activation_functions) { + std::string lookup_key = std::string{"layer"} + cvm::to_str(num_activation_functions + 1) + std::string{"_activation"}; + std::string lookup_key_custom = std::string{"layer"} + cvm::to_str(num_activation_functions + 1) + std::string{"_custom_activation"}; + if (key_lookup(conf, lookup_key.c_str())) { + // Ok, this is not a custom function + std::string function_name; + get_keyval(conf, lookup_key.c_str(), function_name, std::string("")); + if (activation_function_map.find(function_name) == activation_function_map.end()) { + cvm::error("Unknown activation function name: \"" + function_name + "\".\n"); + return; + } + activation_functions.push_back(std::make_pair(false, function_name)); + cvm::log(std::string{"The activation function for layer["} + cvm::to_str(num_activation_functions + 1) + std::string{"] is "} + function_name + '\n'); + ++num_activation_functions; +#ifdef LEPTON + } else if (key_lookup(conf, lookup_key_custom.c_str())) { + std::string function_expression; + get_keyval(conf, lookup_key_custom.c_str(), function_expression, std::string("")); + activation_functions.push_back(std::make_pair(true, function_expression)); + cvm::log(std::string{"The custom activation function for layer["} + cvm::to_str(num_activation_functions + 1) + std::string{"] is "} + function_expression + '\n'); + ++num_activation_functions; +#endif + } else { + has_activation_functions = false; + } + } + // expect the three numbers are equal + if ((num_layers_weight != num_layers_bias) || (num_layers_bias != num_activation_functions)) { + cvm::error("Error: the numbers of weights, biases and activation functions do not match.\n"); + return; + } +// nn = std::make_unique(); + // std::make_unique is only available in C++14 + nn = std::unique_ptr(new neuralnetworkCV::neuralNetworkCompute()); + for (size_t i_layer = 0; i_layer < num_layers_weight; ++i_layer) { + denseLayer d; +#ifdef LEPTON + if (activation_functions[i_layer].first) { + // use custom function as activation function + try { + d = denseLayer(weight_files[i_layer], bias_files[i_layer], activation_functions[i_layer].second); + } catch (std::exception &ex) { + cvm::error("Error on initializing layer " + cvm::to_str(i_layer) + " (" + ex.what() + ")\n", COLVARS_INPUT_ERROR); + return; + } + } else { +#endif + // query the map of supported activation functions + const auto& f = activation_function_map[activation_functions[i_layer].second].first; + const auto& df = activation_function_map[activation_functions[i_layer].second].second; + try { + d = denseLayer(weight_files[i_layer], bias_files[i_layer], f, df); + } catch (std::exception &ex) { + cvm::error("Error on initializing layer " + cvm::to_str(i_layer) + " (" + ex.what() + ")\n", COLVARS_INPUT_ERROR); + return; + } +#ifdef LEPTON + } +#endif + // add a new dense layer to network + if (nn->addDenseLayer(d)) { + if (cvm::debug()) { + // show information about the neural network + cvm::log("Layer " + cvm::to_str(i_layer) + " : has " + cvm::to_str(d.getInputSize()) + " input nodes and " + cvm::to_str(d.getOutputSize()) + " output nodes.\n"); + for (size_t i_output = 0; i_output < d.getOutputSize(); ++i_output) { + for (size_t j_input = 0; j_input < d.getInputSize(); ++j_input) { + cvm::log(" weights[" + cvm::to_str(i_output) + "][" + cvm::to_str(j_input) + "] = " + cvm::to_str(d.getWeight(i_output, j_input))); + } + cvm::log(" biases[" + cvm::to_str(i_output) + "] = " + cvm::to_str(d.getBias(i_output)) + "\n"); + } + } + } else { + cvm::error("Error: error on adding a new dense layer.\n"); + return; + } + } + nn->input().resize(cv.size()); +} + +colvar::neuralNetwork::~neuralNetwork() { +} + +void colvar::neuralNetwork::calc_value() { + x.reset(); + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_value(); + const colvarvalue& current_cv_value = cv[i_cv]->value(); + // for current nn implementation we have to assume taht types are always scaler + if (current_cv_value.type() == colvarvalue::type_scalar) { + nn->input()[i_cv] = cv[i_cv]->sup_coeff * (cvm::pow(current_cv_value.real_value, cv[i_cv]->sup_np)); + } else { + cvm::error("Error: using of non-scaler component.\n"); + return; + } + } + nn->compute(); + x = nn->getOutput(m_output_index); +} + +void colvar::neuralNetwork::calc_gradients() { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + cv[i_cv]->calc_gradients(); + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + const cvm::real factor = nn->getGradient(m_output_index, i_cv); + const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + for (size_t j_elem = 0; j_elem < cv[i_cv]->value().size(); ++j_elem) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + for (size_t l_atom = 0; l_atom < (cv[i_cv]->atom_groups)[k_ag]->size(); ++l_atom) { + (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad = factor_polynomial * factor * (*(cv[i_cv]->atom_groups)[k_ag])[l_atom].grad; + } + } + } + } + } +} + +void colvar::neuralNetwork::apply_force(colvarvalue const &force) { + for (size_t i_cv = 0; i_cv < cv.size(); ++i_cv) { + // If this CV us explicit gradients, then atomic gradients is already calculated + // We can apply the force to atom groups directly + if (cv[i_cv]->is_enabled(f_cvc_explicit_gradient)) { + for (size_t k_ag = 0 ; k_ag < cv[i_cv]->atom_groups.size(); ++k_ag) { + (cv[i_cv]->atom_groups)[k_ag]->apply_colvar_force(force.real_value); + } + } else { + // Compute factors for polynomial combinations + const cvm::real factor_polynomial = getPolynomialFactorOfCVGradient(i_cv); + const cvm::real factor = nn->getGradient(m_output_index, i_cv);; + colvarvalue cv_force = force.real_value * factor * factor_polynomial; + cv[i_cv]->apply_force(cv_force); + } + } +} diff --git a/src/external/colvars/colvarcomp_protein.cpp b/src/external/colvars/colvarcomp_protein.cpp new file mode 100644 index 00000000000..80ef9b855cf --- /dev/null +++ b/src/external/colvars/colvarcomp_protein.cpp @@ -0,0 +1,496 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + + +colvar::alpha_angles::alpha_angles(std::string const &conf) + : cvc(conf) +{ + set_function_type("alpha"); + enable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_scalar); + + colvarproxy *proxy = cvm::main()->proxy; + + std::string segment_id; + get_keyval(conf, "psfSegID", segment_id, std::string("MAIN")); + + std::vector residues; + { + std::string residues_conf = ""; + key_lookup(conf, "residueRange", &residues_conf); + if (residues_conf.size()) { + std::istringstream is(residues_conf); + int initial, final; + char dash; + if ( (is >> initial) && (initial > 0) && + (is >> dash) && (dash == '-') && + (is >> final) && (final > 0) ) { + for (int rnum = initial; rnum <= final; rnum++) { + residues.push_back(rnum); + } + } + } else { + cvm::error("Error: no residues defined in \"residueRange\".\n"); + return; + } + } + + if (residues.size() < 5) { + cvm::error("Error: not enough residues defined in \"residueRange\".\n"); + return; + } + + std::string const &sid = segment_id; + std::vector const &r = residues; + + + get_keyval(conf, "hBondCoeff", hb_coeff, 0.5); + if ( (hb_coeff < 0.0) || (hb_coeff > 1.0) ) { + cvm::error("Error: hBondCoeff must be defined between 0 and 1.\n"); + return; + } + + + get_keyval(conf, "angleRef", theta_ref, 88.0); + get_keyval(conf, "angleTol", theta_tol, 15.0); + + if (hb_coeff < 1.0) { + + for (size_t i = 0; i < residues.size()-2; i++) { + theta.push_back(new colvar::angle(cvm::atom(r[i ], "CA", sid), + cvm::atom(r[i+1], "CA", sid), + cvm::atom(r[i+2], "CA", sid))); + register_atom_group(theta.back()->atom_groups[0]); + register_atom_group(theta.back()->atom_groups[1]); + register_atom_group(theta.back()->atom_groups[2]); + } + + } else { + cvm::log("The hBondCoeff specified will disable the Calpha-Calpha-Calpha angle terms.\n"); + } + + { + cvm::real r0; + size_t en, ed; + get_keyval(conf, "hBondCutoff", r0, proxy->angstrom_to_internal(3.3)); + get_keyval(conf, "hBondExpNumer", en, 6); + get_keyval(conf, "hBondExpDenom", ed, 8); + + if (hb_coeff > 0.0) { + + for (size_t i = 0; i < residues.size()-4; i++) { + hb.push_back(new colvar::h_bond(cvm::atom(r[i ], "O", sid), + cvm::atom(r[i+4], "N", sid), + r0, en, ed)); + register_atom_group(hb.back()->atom_groups[0]); + } + + } else { + cvm::log("The hBondCoeff specified will disable the hydrogen bond terms.\n"); + } + } +} + + +colvar::alpha_angles::alpha_angles() + : cvc() +{ + set_function_type("alphaAngles"); + enable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_scalar); +} + + +colvar::alpha_angles::~alpha_angles() +{ + while (theta.size() != 0) { + delete theta.back(); + theta.pop_back(); + } + while (hb.size() != 0) { + delete hb.back(); + hb.pop_back(); + } + // Our references to atom groups have become invalid now that children cvcs are deleted + atom_groups.clear(); +} + + +void colvar::alpha_angles::calc_value() +{ + x.real_value = 0.0; + + if (theta.size()) { + + cvm::real const theta_norm = + (1.0-hb_coeff) / cvm::real(theta.size()); + + for (size_t i = 0; i < theta.size(); i++) { + + (theta[i])->calc_value(); + + cvm::real const t = ((theta[i])->value().real_value-theta_ref)/theta_tol; + cvm::real const f = ( (1.0 - (t*t)) / + (1.0 - (t*t*t*t)) ); + + x.real_value += theta_norm * f; + + if (cvm::debug()) + cvm::log("Calpha-Calpha angle no. "+cvm::to_str(i+1)+" in \""+ + this->name+"\" has a value of "+ + (cvm::to_str((theta[i])->value().real_value))+ + " degrees, f = "+cvm::to_str(f)+".\n"); + } + } + + if (hb.size()) { + + cvm::real const hb_norm = + hb_coeff / cvm::real(hb.size()); + + for (size_t i = 0; i < hb.size(); i++) { + (hb[i])->calc_value(); + x.real_value += hb_norm * (hb[i])->value().real_value; + if (cvm::debug()) + cvm::log("Hydrogen bond no. "+cvm::to_str(i+1)+" in \""+ + this->name+"\" has a value of "+ + (cvm::to_str((hb[i])->value().real_value))+".\n"); + } + } +} + + +void colvar::alpha_angles::calc_gradients() +{ + size_t i; + for (i = 0; i < theta.size(); i++) + (theta[i])->calc_gradients(); + + for (i = 0; i < hb.size(); i++) + (hb[i])->calc_gradients(); +} + + +void colvar::alpha_angles::collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients) +{ + cvm::real cvc_coeff = sup_coeff * cvm::real(sup_np) * cvm::integer_power(value().real_value, sup_np-1); + + if (theta.size()) { + cvm::real const theta_norm = (1.0-hb_coeff) / cvm::real(theta.size()); + + for (size_t i = 0; i < theta.size(); i++) { + cvm::real const t = ((theta[i])->value().real_value-theta_ref)/theta_tol; + cvm::real const f = ( (1.0 - (t*t)) / + (1.0 - (t*t*t*t)) ); + cvm::real const dfdt = + 1.0/(1.0 - (t*t*t*t)) * + ( (-2.0 * t) + (-1.0*f)*(-4.0 * (t*t*t)) ); + + // Coefficient of this CVC's gradient in the colvar gradient, times coefficient of this + // angle's gradient in the CVC's gradient + cvm::real const coeff = cvc_coeff * theta_norm * dfdt * (1.0/theta_tol); + + for (size_t j = 0; j < theta[i]->atom_groups.size(); j++) { + cvm::atom_group &ag = *(theta[i]->atom_groups[j]); + for (size_t k = 0; k < ag.size(); k++) { + size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), + ag[k].id) - atom_ids.begin(); + atomic_gradients[a] += coeff * ag[k].grad; + } + } + } + } + + if (hb.size()) { + + cvm::real const hb_norm = hb_coeff / cvm::real(hb.size()); + + for (size_t i = 0; i < hb.size(); i++) { + // Coefficient of this CVC's gradient in the colvar gradient, times coefficient of this + // hbond's gradient in the CVC's gradient + cvm::real const coeff = cvc_coeff * 0.5 * hb_norm; + + for (size_t j = 0; j < hb[i]->atom_groups.size(); j++) { + cvm::atom_group &ag = *(hb[i]->atom_groups[j]); + for (size_t k = 0; k < ag.size(); k++) { + size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), + ag[k].id) - atom_ids.begin(); + atomic_gradients[a] += coeff * ag[k].grad; + } + } + } + } +} + + +void colvar::alpha_angles::apply_force(colvarvalue const &force) +{ + + if (theta.size()) { + + cvm::real const theta_norm = + (1.0-hb_coeff) / cvm::real(theta.size()); + + for (size_t i = 0; i < theta.size(); i++) { + + cvm::real const t = ((theta[i])->value().real_value-theta_ref)/theta_tol; + cvm::real const f = ( (1.0 - (t*t)) / + (1.0 - (t*t*t*t)) ); + + cvm::real const dfdt = + 1.0/(1.0 - (t*t*t*t)) * + ( (-2.0 * t) + (-1.0*f)*(-4.0 * (t*t*t)) ); + + (theta[i])->apply_force(theta_norm * + dfdt * (1.0/theta_tol) * + force.real_value ); + } + } + + if (hb.size()) { + + cvm::real const hb_norm = + hb_coeff / cvm::real(hb.size()); + + for (size_t i = 0; i < hb.size(); i++) { + (hb[i])->apply_force(0.5 * hb_norm * force.real_value); + } + } +} + + +simple_scalar_dist_functions(alpha_angles) + + + +////////////////////////////////////////////////////////////////////// +// dihedral principal component +////////////////////////////////////////////////////////////////////// + +colvar::dihedPC::dihedPC(std::string const &conf) + : cvc(conf) +{ + if (cvm::debug()) + cvm::log("Initializing dihedral PC object.\n"); + + set_function_type("dihedPC"); + // Supported through references to atom groups of children cvcs + enable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_scalar); + + std::string segment_id; + get_keyval(conf, "psfSegID", segment_id, std::string("MAIN")); + + std::vector residues; + { + std::string residues_conf = ""; + key_lookup(conf, "residueRange", &residues_conf); + if (residues_conf.size()) { + std::istringstream is(residues_conf); + int initial, final; + char dash; + if ( (is >> initial) && (initial > 0) && + (is >> dash) && (dash == '-') && + (is >> final) && (final > 0) ) { + for (int rnum = initial; rnum <= final; rnum++) { + residues.push_back(rnum); + } + } + } else { + cvm::error("Error: no residues defined in \"residueRange\".\n"); + return; + } + } + + if (residues.size() < 2) { + cvm::error("Error: dihedralPC requires at least two residues.\n"); + return; + } + + std::string const &sid = segment_id; + std::vector const &r = residues; + + std::string vecFileName; + int vecNumber; + if (get_keyval(conf, "vectorFile", vecFileName, vecFileName)) { + get_keyval(conf, "vectorNumber", vecNumber, 0); + if (vecNumber < 1) { + cvm::error("A positive value of vectorNumber is required."); + return; + } + + std::istream &vecFile = + cvm::main()->proxy->input_stream(vecFileName, + "dihedral PCA vector file"); + if (!vecFile) { + return; + } + + // TODO: adapt to different formats by setting this flag + bool eigenvectors_as_columns = true; + + if (eigenvectors_as_columns) { + // Carma-style dPCA file + std::string line; + cvm::real c; + while (vecFile.good()) { + getline(vecFile, line); + if (line.length() < 2) break; + std::istringstream ls(line); + for (int i=0; i> c; + coeffs.push_back(c); + } + } +/* TODO Uncomment this when different formats are recognized + else { + // Eigenvectors as lines + // Skip to the right line + for (int i = 1; i> c; + coeffs.push_back(c); + } + } + */ + cvm::main()->proxy->close_input_stream(vecFileName); + + } else { + get_keyval(conf, "vector", coeffs, coeffs); + } + + if ( coeffs.size() != 4 * (residues.size() - 1)) { + cvm::error("Error: wrong number of coefficients: " + + cvm::to_str(coeffs.size()) + ". Expected " + + cvm::to_str(4 * (residues.size() - 1)) + + " (4 coeffs per residue, minus one residue).\n"); + return; + } + + for (size_t i = 0; i < residues.size()-1; i++) { + // Psi + theta.push_back(new colvar::dihedral(cvm::atom(r[i ], "N", sid), + cvm::atom(r[i ], "CA", sid), + cvm::atom(r[i ], "C", sid), + cvm::atom(r[i+1], "N", sid))); + register_atom_group(theta.back()->atom_groups[0]); + register_atom_group(theta.back()->atom_groups[1]); + register_atom_group(theta.back()->atom_groups[2]); + register_atom_group(theta.back()->atom_groups[3]); + // Phi (next res) + theta.push_back(new colvar::dihedral(cvm::atom(r[i ], "C", sid), + cvm::atom(r[i+1], "N", sid), + cvm::atom(r[i+1], "CA", sid), + cvm::atom(r[i+1], "C", sid))); + register_atom_group(theta.back()->atom_groups[0]); + register_atom_group(theta.back()->atom_groups[1]); + register_atom_group(theta.back()->atom_groups[2]); + register_atom_group(theta.back()->atom_groups[3]); + } + + if (cvm::debug()) + cvm::log("Done initializing dihedPC object.\n"); +} + + +colvar::dihedPC::dihedPC() + : cvc() +{ + set_function_type("dihedPC"); + // Supported through references to atom groups of children cvcs + enable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_scalar); +} + + +colvar::dihedPC::~dihedPC() +{ + while (theta.size() != 0) { + delete theta.back(); + theta.pop_back(); + } + // Our references to atom groups have become invalid now that children cvcs are deleted + atom_groups.clear(); +} + + +void colvar::dihedPC::calc_value() +{ + x.real_value = 0.0; + for (size_t i = 0; i < theta.size(); i++) { + theta[i]->calc_value(); + cvm::real const t = (PI / 180.) * theta[i]->value().real_value; + x.real_value += coeffs[2*i ] * cvm::cos(t) + + coeffs[2*i+1] * cvm::sin(t); + } +} + + +void colvar::dihedPC::calc_gradients() +{ + for (size_t i = 0; i < theta.size(); i++) { + theta[i]->calc_gradients(); + } +} + + +void colvar::dihedPC::collect_gradients(std::vector const &atom_ids, std::vector &atomic_gradients) +{ + cvm::real cvc_coeff = sup_coeff * cvm::real(sup_np) * cvm::integer_power(value().real_value, sup_np-1); + for (size_t i = 0; i < theta.size(); i++) { + cvm::real const t = (PI / 180.) * theta[i]->value().real_value; + cvm::real const dcosdt = - (PI / 180.) * cvm::sin(t); + cvm::real const dsindt = (PI / 180.) * cvm::cos(t); + // Coefficient of this dihedPC's gradient in the colvar gradient, times coefficient of this + // dihedral's gradient in the dihedPC's gradient + cvm::real const coeff = cvc_coeff * (coeffs[2*i] * dcosdt + coeffs[2*i+1] * dsindt); + + for (size_t j = 0; j < theta[i]->atom_groups.size(); j++) { + cvm::atom_group &ag = *(theta[i]->atom_groups[j]); + for (size_t k = 0; k < ag.size(); k++) { + size_t a = std::lower_bound(atom_ids.begin(), atom_ids.end(), + ag[k].id) - atom_ids.begin(); + atomic_gradients[a] += coeff * ag[k].grad; + } + } + } +} + + +void colvar::dihedPC::apply_force(colvarvalue const &force) +{ + for (size_t i = 0; i < theta.size(); i++) { + cvm::real const t = (PI / 180.) * theta[i]->value().real_value; + cvm::real const dcosdt = - (PI / 180.) * cvm::sin(t); + cvm::real const dsindt = (PI / 180.) * cvm::cos(t); + + theta[i]->apply_force((coeffs[2*i ] * dcosdt + + coeffs[2*i+1] * dsindt) * force); + } +} + + +simple_scalar_dist_functions(dihedPC) diff --git a/src/external/colvars/colvarcomp_rotations.cpp b/src/external/colvars/colvarcomp_rotations.cpp new file mode 100644 index 00000000000..3a19d5ca713 --- /dev/null +++ b/src/external/colvars/colvarcomp_rotations.cpp @@ -0,0 +1,813 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include "colvar_rotation_derivative.h" + + +struct colvar::orientation::rotation_derivative_impl_: public rotation_derivative { +public: + rotation_derivative_impl_(colvar::orientation* orientation_cvc): + rotation_derivative( + orientation_cvc->rot, orientation_cvc->ref_pos, orientation_cvc->shifted_pos) {} +}; + + +colvar::orientation::orientation(std::string const &conf) + : cvc() +{ + set_function_type("orientation"); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_quaternion); + colvar::orientation::init(conf); +} + + +colvar::orientation::~orientation() {} + + +int colvar::orientation::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + + atoms = parse_group(conf, "atoms"); + ref_pos.reserve(atoms->size()); + + if (get_keyval(conf, "refPositions", ref_pos, ref_pos)) { + cvm::log("Using reference positions from input file.\n"); + if (ref_pos.size() != atoms->size()) { + return cvm::error("Error: reference positions do not " + "match the number of requested atoms.\n", COLVARS_INPUT_ERROR); + } + } + + { + std::string file_name; + if (get_keyval(conf, "refPositionsFile", file_name)) { + + std::string file_col; + double file_col_value=0.0; + if (get_keyval(conf, "refPositionsCol", file_col, std::string(""))) { + // use PDB flags if column is provided + bool found = get_keyval(conf, "refPositionsColValue", file_col_value, 0.0); + if (found && file_col_value==0.0) { + return cvm::error("Error: refPositionsColValue, " + "if provided, must be non-zero.\n", COLVARS_INPUT_ERROR); + } + } + + ref_pos.resize(atoms->size()); + cvm::load_coords(file_name.c_str(), &ref_pos, atoms, + file_col, file_col_value); + } + } + + if (!ref_pos.size()) { + return cvm::error("Error: must define a set of " + "reference coordinates.\n", COLVARS_INPUT_ERROR); + } + + + cvm::rvector ref_cog(0.0, 0.0, 0.0); + size_t i; + for (i = 0; i < ref_pos.size(); i++) { + ref_cog += ref_pos[i]; + } + ref_cog /= cvm::real(ref_pos.size()); + cvm::log("Centering the reference coordinates on the origin by subtracting " + "the center of geometry at "+ + cvm::to_str(-1.0 * ref_cog)+"; it is " + "assumed that each atom is the closest " + "periodic image to the center of geometry.\n"); + for (i = 0; i < ref_pos.size(); i++) { + ref_pos[i] -= ref_cog; + } + + get_keyval(conf, "closestToQuaternion", ref_quat, cvm::quaternion(1.0, 0.0, 0.0, 0.0)); + + rot_deriv_impl = std::unique_ptr(new rotation_derivative_impl_(this)); + + // If the debug gradients feature is active, debug the rotation gradients + // (note that this won't be active for the orientation CVC itself, because + // colvardeps prevents the flag's activation) + rot.b_debug_gradients = is_enabled(f_cvc_debug_gradient); + + return error_code; +} + + +colvar::orientation::orientation() + : cvc() +{ + set_function_type("orientation"); + disable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_quaternion); +} + + +void colvar::orientation::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + if ((rot.q).inner(ref_quat) >= 0.0) { + x.quaternion_value = rot.q; + } else { + x.quaternion_value = -1.0 * rot.q; + } +} + + +void colvar::orientation::calc_gradients() +{ + // gradients have already been calculated and stored within the + // member object "rot"; we're not using the "grad" member of each + // atom object, because it only can represent the gradient of a + // scalar colvar +} + + +void colvar::orientation::apply_force(colvarvalue const &force) +{ + cvm::quaternion const &FQ = force.quaternion_value; + + if (!atoms->noforce) { + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + for (size_t i = 0; i < 4; i++) { + (*atoms)[ia].apply_force(FQ[i] * dq0_2[i]); + } + } + } +} + + +cvm::real colvar::orientation::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.quaternion_value.dist2(x2); +} + + +colvarvalue colvar::orientation::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.quaternion_value.dist2_grad(x2); +} + + +colvarvalue colvar::orientation::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x2.quaternion_value.dist2_grad(x1); +} + + + +colvar::orientation_angle::orientation_angle(std::string const &conf) + : orientation() +{ + set_function_type("orientationAngle"); + init_as_angle(); + enable(f_cvc_explicit_gradient); + orientation_angle::init(conf); +} + + +int colvar::orientation_angle::init(std::string const &conf) +{ + return orientation::init(conf); +} + + +void colvar::orientation_angle::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + if ((rot.q).q0 >= 0.0) { + x.real_value = (180.0/PI) * 2.0 * cvm::acos((rot.q).q0); + } else { + x.real_value = (180.0/PI) * 2.0 * cvm::acos(-1.0 * (rot.q).q0); + } +} + + +void colvar::orientation_angle::calc_gradients() +{ + cvm::real const dxdq0 = + ( ((rot.q).q0 * (rot.q).q0 < 1.0) ? + ((180.0 / PI) * (-2.0) / cvm::sqrt(1.0 - ((rot.q).q0 * (rot.q).q0))) : + 0.0 ); + + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]); + } +} + + +void colvar::orientation_angle::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +simple_scalar_dist_functions(orientation_angle) + + + +colvar::orientation_proj::orientation_proj(std::string const &conf) + : orientation() +{ + set_function_type("orientationProj"); + enable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_scalar); + init_scalar_boundaries(0.0, 1.0); + orientation_proj::init(conf); +} + + +int colvar::orientation_proj::init(std::string const &conf) +{ + return orientation::init(conf); +} + + +void colvar::orientation_proj::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + x.real_value = 2.0 * (rot.q).q0 * (rot.q).q0 - 1.0; +} + + +void colvar::orientation_proj::calc_gradients() +{ + cvm::real const dxdq0 = 2.0 * 2.0 * (rot.q).q0; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]); + } +} + + +void colvar::orientation_proj::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +simple_scalar_dist_functions(orientation_proj) + + + +colvar::tilt::tilt(std::string const &conf) + : orientation() +{ + set_function_type("tilt"); + x.type(colvarvalue::type_scalar); + enable(f_cvc_explicit_gradient); + init_scalar_boundaries(-1.0, 1.0); + tilt::init(conf); +} + + +int colvar::tilt::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + + error_code |= orientation::init(conf); + + get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0)); + if (axis.norm2() != 1.0) { + axis /= axis.norm(); + cvm::log("Normalizing rotation axis to "+cvm::to_str(axis)+".\n"); + } + + return error_code; +} + + +void colvar::tilt::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + x.real_value = rot.cos_theta(axis); +} + + +void colvar::tilt::calc_gradients() +{ + cvm::quaternion const dxdq = rot.dcos_theta_dq(axis); + + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + (*atoms)[ia].grad = cvm::rvector(0.0, 0.0, 0.0); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + for (size_t iq = 0; iq < 4; iq++) { + (*atoms)[ia].grad += (dxdq[iq] * dq0_2[iq]); + } + } +} + + +void colvar::tilt::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +simple_scalar_dist_functions(tilt) + + + +colvar::spin_angle::spin_angle(std::string const &conf) + : orientation() +{ + set_function_type("spinAngle"); + init_as_periodic_angle(); + enable(f_cvc_periodic); + enable(f_cvc_explicit_gradient); + spin_angle::init(conf); +} + + +int colvar::spin_angle::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + + error_code |= orientation::init(conf); + + get_keyval(conf, "axis", axis, cvm::rvector(0.0, 0.0, 1.0)); + if (axis.norm2() != 1.0) { + axis /= axis.norm(); + cvm::log("Normalizing rotation axis to "+cvm::to_str(axis)+".\n"); + } + + return error_code; +} + + +colvar::spin_angle::spin_angle() + : orientation() +{ + set_function_type("spinAngle"); + period = 360.0; + enable(f_cvc_periodic); + enable(f_cvc_explicit_gradient); + x.type(colvarvalue::type_scalar); +} + + +void colvar::spin_angle::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + x.real_value = rot.spin_angle(axis); + this->wrap(x); +} + + +void colvar::spin_angle::calc_gradients() +{ + cvm::quaternion const dxdq = rot.dspin_angle_dq(axis); + + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + (*atoms)[ia].grad = cvm::rvector(0.0, 0.0, 0.0); + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + for (size_t iq = 0; iq < 4; iq++) { + (*atoms)[ia].grad += (dxdq[iq] * dq0_2[iq]); + } + } +} + + +void colvar::spin_angle::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +cvm::real colvar::spin_angle::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return diff * diff; +} + + +colvarvalue colvar::spin_angle::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return 2.0 * diff; +} + + +colvarvalue colvar::spin_angle::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return (-2.0) * diff; +} + + +void colvar::spin_angle::wrap(colvarvalue &x_unwrapped) const +{ + if ((x_unwrapped.real_value - wrap_center) >= 180.0) { + x_unwrapped.real_value -= 360.0; + return; + } + + if ((x_unwrapped.real_value - wrap_center) < -180.0) { + x_unwrapped.real_value += 360.0; + return; + } + + return; +} + + +colvar::euler_phi::euler_phi(std::string const &conf) + : orientation() +{ + set_function_type("eulerPhi"); + init_as_periodic_angle(); + enable(f_cvc_explicit_gradient); + euler_phi::init(conf); +} + + +colvar::euler_phi::euler_phi() + : orientation() +{ + set_function_type("eulerPhi"); + init_as_periodic_angle(); + enable(f_cvc_explicit_gradient); +} + + +int colvar::euler_phi::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + error_code |= orientation::init(conf); + return error_code; +} + + +void colvar::euler_phi::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + const cvm::real& q0 = rot.q.q0; + const cvm::real& q1 = rot.q.q1; + const cvm::real& q2 = rot.q.q2; + const cvm::real& q3 = rot.q.q3; + const cvm::real tmp_y = 2 * (q0 * q1 + q2 * q3); + const cvm::real tmp_x = 1 - 2 * (q1 * q1 + q2 * q2); + x.real_value = cvm::atan2(tmp_y, tmp_x) * (180.0/PI); +} + + +void colvar::euler_phi::calc_gradients() +{ + const cvm::real& q0 = rot.q.q0; + const cvm::real& q1 = rot.q.q1; + const cvm::real& q2 = rot.q.q2; + const cvm::real& q3 = rot.q.q3; + const cvm::real denominator = (2 * q0 * q1 + 2 * q2 * q3) * (2 * q0 * q1 + 2 * q2 * q3) + (-2 * q1 * q1 - 2 * q2 * q2 + 1) * (-2 * q1 * q1 - 2 * q2 * q2 + 1); + const cvm::real dxdq0 = (180.0/PI) * 2 * q1 * (-2 * q1 * q1 - 2 * q2 * q2 + 1) / denominator; + const cvm::real dxdq1 = (180.0/PI) * (2 * q0 * (-2 * q1 * q1 - 2 * q2 * q2 + 1) - 4 * q1 * (-2 * q0 * q1 - 2 * q2 * q3)) / denominator; + const cvm::real dxdq2 = (180.0/PI) * (-4 * q2 * (-2 * q0 * q1 - 2 * q2 * q3) + 2 * q3 * (-2 * q1 * q1 - 2 * q2 * q2 + 1)) / denominator; + const cvm::real dxdq3 = (180.0/PI) * 2 * q2 * (-2 * q1 * q1 - 2 * q2 * q2 + 1) / denominator; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]) + + (dxdq1 * dq0_2[1]) + + (dxdq2 * dq0_2[2]) + + (dxdq3 * dq0_2[3]); + } +} + + +void colvar::euler_phi::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +cvm::real colvar::euler_phi::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return diff * diff; +} + + +colvarvalue colvar::euler_phi::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return 2.0 * diff; +} + + +colvarvalue colvar::euler_phi::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return (-2.0) * diff; +} + + +void colvar::euler_phi::wrap(colvarvalue &x_unwrapped) const +{ + if ((x_unwrapped.real_value - wrap_center) >= 180.0) { + x_unwrapped.real_value -= 360.0; + return; + } + + if ((x_unwrapped.real_value - wrap_center) < -180.0) { + x_unwrapped.real_value += 360.0; + return; + } + + return; +} + + +colvar::euler_psi::euler_psi(std::string const &conf) + : orientation() +{ + set_function_type("eulerPsi"); + init_as_periodic_angle(); + enable(f_cvc_explicit_gradient); + euler_psi::init(conf); +} + + +colvar::euler_psi::euler_psi() + : orientation() +{ + set_function_type("eulerPsi"); + init_as_periodic_angle(); + enable(f_cvc_explicit_gradient); +} + + +int colvar::euler_psi::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + error_code |= orientation::init(conf); + return error_code; +} + + +void colvar::euler_psi::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + const cvm::real& q0 = rot.q.q0; + const cvm::real& q1 = rot.q.q1; + const cvm::real& q2 = rot.q.q2; + const cvm::real& q3 = rot.q.q3; + const cvm::real tmp_y = 2 * (q0 * q3 + q1 * q2); + const cvm::real tmp_x = 1 - 2 * (q2 * q2 + q3 * q3); + x.real_value = cvm::atan2(tmp_y, tmp_x) * (180.0/PI); +} + + +void colvar::euler_psi::calc_gradients() +{ + const cvm::real& q0 = rot.q.q0; + const cvm::real& q1 = rot.q.q1; + const cvm::real& q2 = rot.q.q2; + const cvm::real& q3 = rot.q.q3; + const cvm::real denominator = (2 * q0 * q3 + 2 * q1 * q2) * (2 * q0 * q3 + 2 * q1 * q2) + (-2 * q2 * q2 - 2 * q3 * q3 + 1) * (-2 * q2 * q2 - 2 * q3 * q3 + 1); + const cvm::real dxdq0 = (180.0/PI) * 2 * q3 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) / denominator; + const cvm::real dxdq1 = (180.0/PI) * 2 * q2 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) / denominator; + const cvm::real dxdq2 = (180.0/PI) * (2 * q1 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) - 4 * q2 * (-2 * q0 * q3 - 2 * q1 * q2)) / denominator; + const cvm::real dxdq3 = (180.0/PI) * (2 * q0 * (-2 * q2 * q2 - 2 * q3 * q3 + 1) - 4 * q3 * (-2 * q0 * q3 - 2 * q1 * q2)) / denominator; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]) + + (dxdq1 * dq0_2[1]) + + (dxdq2 * dq0_2[2]) + + (dxdq3 * dq0_2[3]); + } +} + + +void colvar::euler_psi::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +cvm::real colvar::euler_psi::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return diff * diff; +} + + +colvarvalue colvar::euler_psi::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return 2.0 * diff; +} + + +colvarvalue colvar::euler_psi::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + diff = (diff < -180.0 ? diff + 360.0 : (diff > 180.0 ? diff - 360.0 : diff)); + return (-2.0) * diff; +} + + +void colvar::euler_psi::wrap(colvarvalue &x_unwrapped) const +{ + if ((x_unwrapped.real_value - wrap_center) >= 180.0) { + x_unwrapped.real_value -= 360.0; + return; + } + + if ((x_unwrapped.real_value - wrap_center) < -180.0) { + x_unwrapped.real_value += 360.0; + return; + } + + return; +} + + +colvar::euler_theta::euler_theta(std::string const &conf) + : orientation() +{ + set_function_type("eulerTheta"); + init_as_angle(); + enable(f_cvc_explicit_gradient); + euler_theta::init(conf); +} + + +colvar::euler_theta::euler_theta() + : orientation() +{ + set_function_type("eulerTheta"); + init_as_angle(); + enable(f_cvc_explicit_gradient); +} + + +int colvar::euler_theta::init(std::string const &conf) +{ + int error_code = COLVARS_OK; + error_code |= orientation::init(conf); + return error_code; +} + + +void colvar::euler_theta::calc_value() +{ + atoms_cog = atoms->center_of_geometry(); + + shifted_pos = atoms->positions_shifted(-1.0 * atoms_cog); + rot.calc_optimal_rotation(ref_pos, shifted_pos); + + const cvm::real& q0 = rot.q.q0; + const cvm::real& q1 = rot.q.q1; + const cvm::real& q2 = rot.q.q2; + const cvm::real& q3 = rot.q.q3; + x.real_value = cvm::asin(2 * (q0 * q2 - q3 * q1)) * (180.0/PI); +} + + +void colvar::euler_theta::calc_gradients() +{ + const cvm::real& q0 = rot.q.q0; + const cvm::real& q1 = rot.q.q1; + const cvm::real& q2 = rot.q.q2; + const cvm::real& q3 = rot.q.q3; + const cvm::real denominator = cvm::sqrt(1 - (2 * q0 * q2 - 2 * q1 * q3) * (2 * q0 * q2 - 2 * q1 * q3)); + const cvm::real dxdq0 = (180.0/PI) * 2 * q2 / denominator; + const cvm::real dxdq1 = (180.0/PI) * -2 * q3 / denominator; + const cvm::real dxdq2 = (180.0/PI) * 2 * q0 / denominator; + const cvm::real dxdq3 = (180.0/PI) * -2 * q1 / denominator; + rot_deriv_impl->prepare_derivative(rotation_derivative_dldq::use_dq); + cvm::vector1d dq0_2; + for (size_t ia = 0; ia < atoms->size(); ia++) { + rot_deriv_impl->calc_derivative_wrt_group2(ia, nullptr, &dq0_2); + (*atoms)[ia].grad = (dxdq0 * dq0_2[0]) + + (dxdq1 * dq0_2[1]) + + (dxdq2 * dq0_2[2]) + + (dxdq3 * dq0_2[3]); + } +} + + +void colvar::euler_theta::apply_force(colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + if (!atoms->noforce) { + atoms->apply_colvar_force(fw); + } +} + + +cvm::real colvar::euler_theta::dist2(colvarvalue const &x1, + colvarvalue const &x2) const +{ + // theta angle is not periodic + return cvc::dist2(x1, x2); +} + + +colvarvalue colvar::euler_theta::dist2_lgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + // theta angle is not periodic + return cvc::dist2_lgrad(x1, x2); +} + + +colvarvalue colvar::euler_theta::dist2_rgrad(colvarvalue const &x1, + colvarvalue const &x2) const +{ + // theta angle is not periodic + return cvc::dist2_rgrad(x1, x2); +} diff --git a/src/external/colvars/colvarcomp_volmaps.cpp b/src/external/colvars/colvarcomp_volmaps.cpp new file mode 100644 index 00000000000..00c7206bc17 --- /dev/null +++ b/src/external/colvars/colvarcomp_volmaps.cpp @@ -0,0 +1,143 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + + + +colvar::map_total::map_total() + : cvc() +{ + set_function_type("mapTotal"); + volmap_id = -1; + volmap_index = -1; + atoms = NULL; + x.type(colvarvalue::type_scalar); +} + + +colvar::map_total::map_total(std::string const &conf) + : cvc() // init() will take care of this +{ + set_function_type("mapTotal"); + volmap_id = -1; + volmap_index = -1; + atoms = NULL; + x.type(colvarvalue::type_scalar); + map_total::init(conf); +} + + +int colvar::map_total::init(std::string const &conf) +{ + int error_code = cvc::init(conf); + colvarproxy *proxy = cvm::main()->proxy; + get_keyval(conf, "mapName", volmap_name, volmap_name); + get_keyval(conf, "mapID", volmap_id, volmap_id); + register_param("mapID", reinterpret_cast(&volmap_id)); + + cvm::main()->cite_feature("Volumetric map-based collective variables"); + + if ((volmap_name.size() > 0) && (volmap_id >= 0)) { + error_code |= + cvm::error("Error: mapName and mapID are mutually exclusive.\n"); + } + + // Parse optional group + atoms = parse_group(conf, "atoms", true); + if (atoms != NULL) { + + // Using internal selection + if (volmap_name.size()) { + error_code |= proxy->check_volmap_by_name(volmap_name); + } + if (volmap_id >= 0) { + error_code |= proxy->check_volmap_by_id(volmap_id); + } + + } else { + + // Using selection from the MD engine + if (volmap_name.size()) { + volmap_index = proxy->init_volmap_by_name(volmap_name); + } + if (volmap_id >= 0) { + volmap_index = proxy->init_volmap_by_id(volmap_id); + } + error_code |= volmap_index > 0 ? COLVARS_OK : COLVARS_INPUT_ERROR; + } + + if (get_keyval(conf, "atomWeights", atom_weights, atom_weights)) { + if (atoms == NULL) { + error_code |= cvm::error("Error: weights can only be assigned when atoms " + "are selected explicitly in Colvars.\n", + COLVARS_INPUT_ERROR); + } else { + if (atoms->size() != atom_weights.size()) { + error_code |= cvm::error("Error: if defined, the number of weights ("+ + cvm::to_str(atom_weights.size())+ + ") must equal the number of atoms ("+ + cvm::to_str(atoms->size())+ + ").\n", COLVARS_INPUT_ERROR); + } + } + } + + if (volmap_name.size() > 0) { + volmap_id = proxy->get_volmap_id_from_name(volmap_name.c_str()); + } + + return error_code; +} + + +void colvar::map_total::calc_value() +{ + colvarproxy *proxy = cvm::main()->proxy; + int flags = is_enabled(f_cvc_gradient) ? colvarproxy::volmap_flag_gradients : + colvarproxy::volmap_flag_null; + + if (atoms != NULL) { + // Compute the map inside Colvars + x.real_value = 0.0; + + cvm::real *w = NULL; + if (atom_weights.size() > 0) { + flags |= colvarproxy::volmap_flag_use_atom_field; + w = &(atom_weights[0]); + } + proxy->compute_volmap(flags, volmap_id, atoms->begin(), atoms->end(), + &(x.real_value), w); + } else { + // Get the externally computed value + x.real_value = proxy->get_volmap_value(volmap_index); + } +} + + +void colvar::map_total::calc_gradients() +{ + // Computed in calc_value() or by the MD engine +} + + +void colvar::map_total::apply_force(colvarvalue const &force) +{ + colvarproxy *proxy = cvm::main()->proxy; + if (atoms) { + if (!atoms->noforce) + atoms->apply_colvar_force(force.real_value); + } else { + proxy->apply_volmap_force(volmap_index, force.real_value); + } +} diff --git a/src/external/colvars/colvardeps.cpp b/src/external/colvars/colvardeps.cpp new file mode 100644 index 00000000000..2a22b1cb57a --- /dev/null +++ b/src/external/colvars/colvardeps.cpp @@ -0,0 +1,521 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvardeps.h" + + +colvardeps::colvardeps() +{ + time_step_factor = 1; +} + + +colvardeps::~colvardeps() { + size_t i; + + // Protest if we are deleting an object while a parent object may still depend on it + if (parents.size()) { + cvm::log("Warning: destroying \"" + description + "\" before its parents objects:"); + for (i=0; idescription + "\n"); + } + } + + // Do not delete features if it's a static object + // may change in the future though +// for (i=0; irequires_children.size(); i++) { + int g = features()[fid]->requires_children[i]; + for (j=0; jfeatures()[g]->description + "\n"); + children[j]->decr_ref_count(g); + } + } + } + } + cvm::decrease_depth(); +} + + +// re-enable children features (and increase ref count accordingly) +// So free_children_deps() can be called whenever an object becomes inactive +void colvardeps::restore_children_deps() { + size_t i,j,fid; + + cvm::increase_depth(); + for (fid = 0; fid < feature_states.size(); fid++) { + if (is_enabled(fid)) { + for (i=0; irequires_children.size(); i++) { + int g = features()[fid]->requires_children[i]; + for (j=0; jfeatures()[g]->description + "\n"); + children[j]->enable(g, false, false); + } + } + } + } + cvm::decrease_depth(); +} + + +void colvardeps::provide(int feature_id, bool truefalse) { + feature_states[feature_id].available = truefalse; +} + + +void colvardeps::set_enabled(int feature_id, bool truefalse) { + if (truefalse) { + enable(feature_id); + } else { + disable(feature_id); + } +} + + +bool colvardeps::get_keyval_feature(colvarparse *cvp, + std::string const &conf, char const *key, + int feature_id, bool const &def_value, + colvarparse::Parse_Mode const parse_mode) +{ + if (!is_user(feature_id)) { + cvm::error("Cannot set feature \"" + features()[feature_id]->description + "\" from user input in \"" + description + "\".\n"); + return false; + } + bool value; + bool const found = cvp->get_keyval(conf, key, value, def_value, parse_mode); + // If the default value is on, this function should be able to disable the feature! + set_enabled(feature_id, value); + + return found; +} + + +int colvardeps::enable(int feature_id, + bool dry_run /* default: false */, + bool toplevel /* default: true */) +{ + int res; + size_t i, j; + bool ok; + feature *f = features()[feature_id]; + feature_state *fs = &feature_states[feature_id]; + + if (cvm::debug()) { + cvm::log("DEPS: " + description + + (dry_run ? " testing " : " enabling ") + + "\"" + f->description +"\"\n"); + } + + if (fs->enabled) { + if (!(dry_run || toplevel)) { + // This is a dependency: prevent disabling this feature as long + // as requirement is enabled + fs->ref_count++; + if (cvm::debug()) + cvm::log("DEPS: bumping ref_count to " + cvm::to_str(fs->ref_count) + "\n"); + } + // Do not try to further resolve deps + return COLVARS_OK; + } + + std::string feature_type_descr = is_static(feature_id) ? "Static" : + (is_dynamic(feature_id) ? "Dynamic" : "User-controlled"); + + if (!fs->available) { + if (!dry_run) { + if (toplevel) { + cvm::error("Error: " + feature_type_descr + " feature unavailable: \"" + + f->description + "\" in " + description + ".\n"); + } else { + cvm::log(feature_type_descr + " feature unavailable: \"" + + f->description + "\" in " + description + ".\n"); + } + } + return COLVARS_ERROR; + } + + if (!toplevel && !is_dynamic(feature_id)) { + if (!dry_run) { + cvm::log(feature_type_descr + " feature \"" + f->description + + "\" cannot be enabled automatically in " + description + ".\n"); + if (is_user(feature_id)) { + cvm::log("Try setting it manually.\n"); + } + } + return COLVARS_ERROR; + } + + // 1) enforce exclusions + // reminder: exclusions must be mutual for this to work + for (i=0; irequires_exclude.size(); i++) { + feature *g = features()[f->requires_exclude[i]]; + if (cvm::debug()) + cvm::log(f->description + " requires exclude " + g->description + "\n"); + if (is_enabled(f->requires_exclude[i])) { + if (!dry_run) { + cvm::log("Feature \"" + f->description + "\" is incompatible with \"" + + g->description + "\" in " + description + ".\n"); + if (toplevel) { + cvm::error("Error: Failed dependency in " + description + ".\n"); + } + } + return COLVARS_ERROR; + } + } + + // 2) solve internal deps (self) + for (i=0; irequires_self.size(); i++) { + if (cvm::debug()) + cvm::log(f->description + " requires self " + features()[f->requires_self[i]]->description + "\n"); + res = enable(f->requires_self[i], dry_run, false); + if (res != COLVARS_OK) { + if (!dry_run) { + cvm::log("...required by \"" + f->description + "\" in " + description + "\n"); + if (toplevel) { + cvm::error("Error: Failed dependency in " + description + ".\n"); + } + } + return res; + } + } + + // 3) solve internal alternate deps + for (i=0; irequires_alt.size(); i++) { + + // test if one is available; if yes, enable and exit w/ success + ok = false; + for (j=0; jrequires_alt[i].size(); j++) { + int g = f->requires_alt[i][j]; + if (cvm::debug()) + cvm::log(f->description + " requires alt " + features()[g]->description + "\n"); + res = enable(g, true, false); // see if available + if (res == COLVARS_OK) { + ok = true; + if (!dry_run) { + enable(g, false, false); // Require again, for real + fs->alternate_refs.push_back(g); // We remember we enabled this + // so we can free it if this feature gets disabled + } + break; + } + } + if (!ok) { + if (!dry_run) { + cvm::log("\"" + f->description + "\" in " + description + + " requires one of the following features, none of which can be enabled:\n"); + cvm::log("-----------------------------------------\n"); + cvm::increase_depth(); + for (j=0; jrequires_alt[i].size(); j++) { + int g = f->requires_alt[i][j]; + cvm::log(cvm::to_str(j+1) + ". " + features()[g]->description + "\n"); + enable(g, false, false); // Just for printing error output + } + cvm::decrease_depth(); + cvm::log("-----------------------------------------\n"); + if (toplevel) { + cvm::error("Error: Failed dependency in " + description + ".\n"); + } + } + return COLVARS_ERROR; + } + } + + // 4) solve deps in children + // if the object is inactive, we solve but do not enable: will be enabled + // when the object becomes active + cvm::increase_depth(); + for (i=0; irequires_children.size(); i++) { + int g = f->requires_children[i]; + for (j=0; jenable(g, dry_run || !is_enabled(), false); + if (res != COLVARS_OK) { + if (!dry_run) { + cvm::log("...required by \"" + f->description + "\" in " + description + "\n"); + if (toplevel) { + cvm::error("Error: Failed dependency in " + description + ".\n"); + } + } + return res; + } + } + } + cvm::decrease_depth(); + + // Actually enable feature only once everything checks out + if (!dry_run) { + fs->enabled = true; + // This should be the only reference + if (!toplevel) fs->ref_count = 1; + if (feature_id == 0) { + // Waking up this object, enable all deps in children + restore_children_deps(); + } + do_feature_side_effects(feature_id); + if (cvm::debug()) + cvm::log("DEPS: feature \"" + f->description + "\" in " + + description + " enabled, ref_count = 1." + "\n"); + } + return COLVARS_OK; +} + + +int colvardeps::disable(int feature_id) { + size_t i, j; + feature *f = features()[feature_id]; + feature_state *fs = &feature_states[feature_id]; + + if (cvm::debug()) cvm::log("DEPS: disabling feature \"" + + f->description + "\" in " + description + "\n"); + + if (fs->enabled == false) { + return COLVARS_OK; + } + + if (fs->ref_count > 1) { + cvm::error("Error: cannot disable feature \"" + f->description + + "\" in " + description + " because of " + cvm::to_str(fs->ref_count-1) + + " remaining references.\n" ); + return COLVARS_ERROR; + } + + // internal deps (self) + for (i=0; irequires_self.size(); i++) { + if (cvm::debug()) cvm::log("DEPS: dereferencing self " + + features()[f->requires_self[i]]->description + "\n"); + decr_ref_count(f->requires_self[i]); + } + + // alternates + for (i=0; ialternate_refs.size(); i++) { + if (cvm::debug()) cvm::log("DEPS: dereferencing alt " + + features()[fs->alternate_refs[i]]->description + "\n"); + decr_ref_count(fs->alternate_refs[i]); + } + // Forget these, now that they are dereferenced + fs->alternate_refs.clear(); + + // deps in children + // except if the object is inactive, then children dependencies + // have already been dereferenced by this function + // (or never referenced if feature was enabled while the object + // was inactive) + if (is_enabled()) { + cvm::increase_depth(); + for (i=0; irequires_children.size(); i++) { + int g = f->requires_children[i]; + for (j=0; jfeatures()[g]->description + "\n"); + children[j]->decr_ref_count(g); + } + } + cvm::decrease_depth(); + } + + fs->enabled = false; + fs->ref_count = 0; + if (feature_id == 0) { + // Putting this object to sleep + free_children_deps(); + } + return COLVARS_OK; +} + + +int colvardeps::decr_ref_count(int feature_id) { + int &rc = feature_states[feature_id].ref_count; + feature *f = features()[feature_id]; + + if (cvm::debug()) + cvm::log("DEPS: decreasing reference count of \"" + f->description + + "\" in " + description + ".\n"); + + if (rc <= 0) { + cvm::error("Error: cannot decrease reference count of feature \"" + f->description + + "\" in " + description + ", which is " + cvm::to_str(rc) + ".\n"); + return COLVARS_ERROR; + } + + rc--; + if (rc == 0 && f->is_dynamic()) { + // we can auto-disable this feature + if (cvm::debug()) + cvm::log("DEPS will now auto-disable dynamic feature \"" + f->description + + "\" in " + description + ".\n"); + disable(feature_id); + } + return COLVARS_OK; +} + + +void colvardeps::init_feature(int feature_id, const char *description_in, feature_type type) { + modify_features()[feature_id]->description = description_in; + modify_features()[feature_id]->type = type; +} + + +// Shorthand functions for describing dependencies +void colvardeps::require_feature_self(int f, int g) { + features()[f]->requires_self.push_back(g); +} + + +// Ensure that exclusions are symmetric +void colvardeps::exclude_feature_self(int f, int g) { + features()[f]->requires_exclude.push_back(g); + features()[g]->requires_exclude.push_back(f); +} + + +void colvardeps::require_feature_children(int f, int g) { + features()[f]->requires_children.push_back(g); +} + + +void colvardeps::require_feature_alt(int f, int g, int h) { + features()[f]->requires_alt.push_back(std::vector(2)); + features()[f]->requires_alt.back()[0] = g; + features()[f]->requires_alt.back()[1] = h; +} + + +void colvardeps::require_feature_alt(int f, int g, int h, int i) { + features()[f]->requires_alt.push_back(std::vector(3)); + features()[f]->requires_alt.back()[0] = g; + features()[f]->requires_alt.back()[1] = h; + features()[f]->requires_alt.back()[2] = i; +} + + +void colvardeps::require_feature_alt(int f, int g, int h, int i, int j) { + features()[f]->requires_alt.push_back(std::vector(4)); + features()[f]->requires_alt.back()[0] = g; + features()[f]->requires_alt.back()[1] = h; + features()[f]->requires_alt.back()[2] = i; + features()[f]->requires_alt.back()[3] = j; +} + + +void colvardeps::print_state() { + size_t i; + cvm::log("Features of \"" + description + "\" (refcount)\n"); + for (i = 0; i < feature_states.size(); i++) { + std::string onoff = is_enabled(i) ? "ON " : " "; + // Only display refcount if non-zero for less clutter + std::string refcount = feature_states[i].ref_count != 0 ? + " (" + cvm::to_str(feature_states[i].ref_count) + ") " : ""; + cvm::log("- " + onoff + features()[i]->description + refcount + "\n"); + } + cvm::increase_depth(); + for (i=0; iprint_state(); + } + cvm::decrease_depth(); +} + + +void colvardeps::add_child(colvardeps *child) { + + children.push_back(child); + child->parents.push_back(this); + + // Solve dependencies of already enabled parent features + // in the new child + + size_t i, fid; + cvm::increase_depth(); + for (fid = 0; fid < feature_states.size(); fid++) { + if (is_enabled(fid)) { + for (i=0; irequires_children.size(); i++) { + int g = features()[fid]->requires_children[i]; + if (cvm::debug()) cvm::log("DEPS: re-enabling children's " + + child->features()[g]->description + "\n"); + child->enable(g, false, false); + } + } + } + cvm::decrease_depth(); +} + + +void colvardeps::remove_child(colvardeps *child) { + int i; + bool found = false; + + for (i = children.size()-1; i>=0; --i) { + if (children[i] == child) { + children.erase(children.begin() + i); + found = true; + break; + } + } + if (!found) { + cvm::error("Trying to remove missing child reference from " + description + "\n"); + } + found = false; + for (i = child->parents.size()-1; i>=0; --i) { + if (child->parents[i] == this) { + child->parents.erase(child->parents.begin() + i); + found = true; + break; + } + } + if (!found) { + cvm::error("Trying to remove missing parent reference from " + child->description + "\n"); + } +} + + +void colvardeps::remove_all_children() { + size_t i; + int j; + bool found; + + for (i = 0; i < children.size(); ++i) { + found = false; + for (j = children[i]->parents.size()-1; j>=0; --j) { + if (children[i]->parents[j] == this) { + children[i]->parents.erase(children[i]->parents.begin() + j); + found = true; + break; + } + } + if (!found) { + cvm::error("Trying to remove missing parent reference from " + children[i]->description + "\n"); + } + } + children.clear(); +} diff --git a/src/external/colvars/colvardeps.h b/src/external/colvars/colvardeps.h new file mode 100644 index 00000000000..cfc93d0514e --- /dev/null +++ b/src/external/colvars/colvardeps.h @@ -0,0 +1,442 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARDEPS_H +#define COLVARDEPS_H + +#include "colvarmodule.h" +#include "colvarparse.h" + +/// \brief Parent class for a member object of a bias, cv or cvc etc. containing features and +/// their dependencies, and handling dependency resolution +/// +/// There are 3 kinds of features: +/// 1. Dynamic features are under the control of the dependency resolution +/// system. They may be enabled or disabled depending on dependencies. +/// 2. User features may be enabled based on user input (they may trigger a failure upon dependency resolution, though) +/// 3. Static features are static properties of the object, determined +/// programmatically at initialization time. +/// +/// The following diagram summarizes the dependency tree at the bias, colvar, and colvarcomp levels. +/// Isolated and atom group features are not shown to save space. +/// @image html deps_2019.svg +/// +/// In all classes, feature 0 is `active`. When an object is inactivated +/// all its children dependencies are dereferenced (free_children_deps) +/// While the object is inactive, no dependency solving is done on children +/// it is done when the object is activated back (restore_children_deps) +class colvardeps { +public: + + colvardeps(); + virtual ~colvardeps(); + + // Subclasses should initialize the following members: + + std::string description; // reference to object name (cv, cvc etc.) + + /// This contains the current state of each feature for each object + // since the feature class only contains static properties + struct feature_state { + feature_state(bool a, bool e) + : available(a), enabled(e), ref_count(0) {} + + /// Feature may be enabled, subject to possible dependencies + bool available; + /// Currently enabled - this flag is subject to change dynamically + /// TODO consider implications for dependency solving: anyone who disables + /// it should trigger a refresh of parent objects + bool enabled; // see if this should be private depending on implementation + + // bool enabledOnce; // this should trigger an update when object is evaluated + + /// Number of features requiring this one as a dependency + /// When it falls to zero: + /// - a dynamic feature is disabled automatically + /// - other features may be disabled statically + int ref_count; + /// List of features that were enabled by this one + /// as part of an alternate requirement (for ref counting purposes) + /// This is necessary because we don't know which feature in the list + /// we enabled, otherwise + std::vector alternate_refs; + }; + +protected: + /// Time step multiplier (for coarse-timestep biases & colvars) + /// Biases and colvars will only be calculated at those times + /// (f_cvb_awake and f_cv_awake); a + /// Biases use this to apply "impulse" biasing forces at the outer timestep + /// Unused by lower-level objects (cvcs and atom groups) + int time_step_factor; + + /// List of the states of all features + std::vector feature_states; + + /// Enum of possible feature types + enum feature_type { + f_type_not_set, + f_type_dynamic, + f_type_user, + f_type_static + }; + +public: + /// \brief returns time_step_factor + inline int get_time_step_factor() const {return time_step_factor;} + + /// Pair a numerical feature ID with a description and type + void init_feature(int feature_id, const char *description, feature_type type); + + /// Describes a feature and its dependencies + /// used in a static array within each subclass + class feature { + + public: + feature() : type(f_type_not_set) {} + ~feature() {} + + std::string description; // Set by derived object initializer + + // features that this feature requires in the same object + // NOTE: we have no safety mechanism against circular dependencies, however, they would have to be internal to an object (ie. requires_self or requires_alt) + std::vector requires_self; + + // Features that are incompatible, ie. required disabled + // if enabled, they will cause a dependency failure (they will not be disabled) + // To enforce these dependencies regardless of the order in which they + // are enabled, they are always set in a symmetric way, so whichever is enabled + // second will cause the dependency to fail + std::vector requires_exclude; + + // sets of features that are required in an alternate way + // when parent feature is enabled, if none are enabled, the first one listed that is available will be enabled + std::vector > requires_alt; + + // features that this feature requires in children + std::vector requires_children; + + inline bool is_dynamic() { return type == f_type_dynamic; } + inline bool is_static() { return type == f_type_static; } + inline bool is_user() { return type == f_type_user; } + /// Type of this feature, from the enum feature_type + feature_type type; + }; + + inline bool is_not_set(int id) { return features()[id]->type == f_type_not_set; } + inline bool is_dynamic(int id) { return features()[id]->type == f_type_dynamic; } + inline bool is_static(int id) { return features()[id]->type == f_type_static; } + inline bool is_user(int id) { return features()[id]->type == f_type_user; } + + // Accessor to array of all features with deps, static in most derived classes + // Subclasses with dynamic dependency trees may override this + // with a non-static array + // Intermediate classes (colvarbias and colvarcomp, which are also base classes) + // implement this as virtual to allow overriding + virtual const std::vector &features() const = 0; + virtual std::vector&modify_features() = 0; + + void add_child(colvardeps *child); + + void remove_child(colvardeps *child); + + /// Used before deleting an object, if not handled by that object's destructor + /// (useful for cvcs because their children are member objects) + void remove_all_children(); + +private: + + /// pointers to objects this object depends on + /// list should be maintained by any code that modifies the object + /// this could be secured by making lists of colvars / cvcs / atom groups private and modified through accessor functions + std::vector children; + + /// pointers to objects that depend on this object + /// the size of this array is in effect a reference counter + std::vector parents; + +public: + // Checks whether given feature is enabled + // Defaults to querying f_*_active + inline bool is_enabled(int f = f_cv_active) const { + return feature_states[f].enabled; + } + + // Checks whether given feature is available + // Defaults to querying f_*_active + inline bool is_available(int f = f_cv_active) const { + return feature_states[f].available; + } + + /// Set the feature's available flag, without checking + /// To be used for dynamic properties + /// dependencies will be checked by enable() + void provide(int feature_id, bool truefalse = true); + + /// Enable or disable, depending on flag value + void set_enabled(int feature_id, bool truefalse = true); + +protected: + + /// Parse a keyword and enable a feature accordingly + bool get_keyval_feature(colvarparse *cvp, + std::string const &conf, char const *key, + int feature_id, bool const &def_value, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + +public: + + /// Enable a feature and recursively solve its dependencies. + /// For accurate reference counting, do not add spurious calls to enable() + /// \param dry_run Recursively test whether a feature is available, without enabling it + /// \param toplevel False if this is called as part of a chain of dependency resolution. + /// This is used to diagnose failed dependencies by displaying the full stack: + /// only the toplevel dependency will throw a fatal error. + int enable(int f, bool dry_run = false, bool toplevel = true); + + /// Disable a feature, decrease the reference count of its dependencies + /// and recursively disable them as applicable + int disable(int f); + + /// disable all enabled features to free their dependencies + /// to be done when deleting the object + /// Cannot be in the base class destructor because it needs the derived class features() + void free_children_deps(); + + /// re-enable children features (to be used when object becomes active) + void restore_children_deps(); + + /// Decrement the reference count of a feature + /// disabling it if it's dynamic and count reaches zero + int decr_ref_count(int f); + + /// Implements possible actions to be carried out + /// when a given feature is enabled + /// Base function does nothing, can be overloaded + virtual void do_feature_side_effects(int /* id */) {} + + // NOTE that all feature enums should start with f_*_active + enum features_biases { + /// \brief Bias is active + f_cvb_active, + /// \brief Bias is awake (active on its own accord) this timestep + f_cvb_awake, + /// Accumulates data starting from step 0 of a simulation run + f_cvb_step_zero_data, + /// \brief will apply forces + f_cvb_apply_force, + /// \brief force this bias to act on actual value for extended-Lagrangian coordinates + f_cvb_bypass_ext_lagrangian, + /// \brief requires total forces + f_cvb_get_total_force, + /// \brief whether this bias should record the accumulated work + f_cvb_output_acc_work, + /// \brief depends on simulation history + f_cvb_history_dependent, + /// \brief depends on time + f_cvb_time_dependent, + /// \brief requires scalar colvars + f_cvb_scalar_variables, + /// \brief whether this bias will compute a PMF + f_cvb_calc_pmf, + /// \brief whether this bias will compute TI samples + f_cvb_calc_ti_samples, + /// \brief whether this bias will write TI samples + f_cvb_write_ti_samples, + /// \brief whether this bias should write the TI PMF + f_cvb_write_ti_pmf, + /// \brief whether this bias uses an external grid to scale the biasing forces + f_cvb_scale_biasing_force, + f_cvb_ntot + }; + + enum features_colvar { + /// \brief Calculate colvar + f_cv_active, + /// \brief Colvar is awake (active on its own accord) this timestep + f_cv_awake, + /// \brief Gradients are calculated and temporarily stored, so + /// that external forces can be applied + f_cv_gradient, + /// \brief Collect atomic gradient data from all cvcs into vector + /// atomic_gradient + f_cv_collect_gradient, + /// \brief Build list of atoms involved in CV calculation + f_cv_collect_atom_ids, + /// \brief Calculate the velocity with finite differences + f_cv_fdiff_velocity, + /// \brief The total force is calculated, projecting the atomic + /// forces on the inverse gradient + f_cv_total_force, + /// \brief Calculate total force from atomic forces + f_cv_total_force_calc, + /// \brief Subtract the applied force from the total force + f_cv_subtract_applied_force, + /// \brief Estimate Jacobian derivative + f_cv_Jacobian, + /// \brief Do not report the Jacobian force as part of the total force + /// instead, apply a correction internally to cancel it + f_cv_hide_Jacobian, + /// \brief The variable has a harmonic restraint around a moving + /// center with fictitious mass; bias forces will be applied to + /// the center + f_cv_extended_Lagrangian, + /// \brief An extended variable that sets an external variable in the + /// back-end (eg. an alchemical coupling parameter for lambda-dynamics) + /// Can have a single component + f_cv_external, + /// \brief The extended system coordinate undergoes Langevin dynamics + f_cv_Langevin, + /// \brief Output the potential and kinetic energies + /// (for extended Lagrangian colvars only) + f_cv_output_energy, + /// \brief Output the value to the trajectory file (on by default) + f_cv_output_value, + /// \brief Output the velocity to the trajectory file + f_cv_output_velocity, + /// \brief Output the applied force to the trajectory file + f_cv_output_applied_force, + /// \brief Output the total force to the trajectory file + f_cv_output_total_force, + /// \brief A lower boundary is defined + f_cv_lower_boundary, + /// \brief An upper boundary is defined + f_cv_upper_boundary, + /// \brief The lower boundary is not defined from user's choice + f_cv_hard_lower_boundary, + /// \brief The upper boundary is not defined from user's choice + f_cv_hard_upper_boundary, + /// \brief Reflecting lower boundary condition + f_cv_reflecting_lower_boundary, + /// \brief Reflecting upper boundary condition + f_cv_reflecting_upper_boundary, + /// \brief Provide a discretization of the values of the colvar to + /// be used by the biases or in analysis (needs lower and upper + /// boundary) + f_cv_grid, + /// \brief Compute running average + f_cv_runave, + /// \brief Compute time correlation function + f_cv_corrfunc, + /// \brief Value and gradient computed by user script + f_cv_scripted, + /// \brief Value and gradient computed by user function through Lepton + f_cv_custom_function, + /// \brief Colvar is periodic + f_cv_periodic, + /// \brief The colvar has only one component + f_cv_single_cvc, + /// \brief is scalar + f_cv_scalar, + f_cv_linear, + f_cv_homogeneous, + /// \brief multiple timestep through time_step_factor + f_cv_multiple_ts, + /// \brief Number of colvar features + f_cv_ntot + }; + + enum features_cvc { + /// Computation of this CVC is enabled + f_cvc_active, + /// This CVC computes a scalar value + f_cvc_scalar, + /// Values of this CVC lie in a periodic interval + f_cvc_periodic, + /// This CVC provides a default value for the colvar's width + f_cvc_width, + /// This CVC provides a default value for the colvar's lower boundary + f_cvc_lower_boundary, + /// This CVC provides a default value for the colvar's upper boundary + f_cvc_upper_boundary, + /// CVC calculates atom gradients + f_cvc_gradient, + /// CVC calculates and stores explicit atom gradients on rank 0 + f_cvc_explicit_gradient, + /// CVC calculates and stores inverse atom gradients (used for total force) + f_cvc_inv_gradient, + /// CVC calculates the Jacobian term of the total-force expression + f_cvc_Jacobian, + /// The total force for this CVC will be computed from one group only + f_cvc_one_site_total_force, + /// calc_gradients() will call debug_gradients() for every group needed + f_cvc_debug_gradient, + /// With PBCs, minimum-image convention will be used for distances + /// (does not affect the periodicity of CVC values, e.g. angles) + f_cvc_pbc_minimum_image, + /// This CVC is a function of centers of mass + f_cvc_com_based, + /// This CVC can be computed in parallel + f_cvc_scalable, + /// Centers-of-mass used in this CVC can be computed in parallel + f_cvc_scalable_com, + /// \brief Build list of atoms involved in CVC calculation + f_cvc_collect_atom_ids, + /// Number of CVC features + f_cvc_ntot + }; + + enum features_atomgroup { + f_ag_active, + f_ag_center, + f_ag_center_origin, + f_ag_rotate, + f_ag_fitting_group, + /// Perform a standard minimum msd fit for given atoms + /// ie. not using refpositionsgroup +// f_ag_min_msd_fit, + /// \brief Does not have explicit atom gradients from parent CVC + f_ag_explicit_gradient, + f_ag_fit_gradients, + f_ag_atom_forces, + f_ag_scalable, + f_ag_scalable_com, + /// \brief Build list of atoms involved in atom group + f_ag_collect_atom_ids, + f_ag_ntot + }; + + /// Initialize dependency tree for object of a derived class + virtual int init_dependencies() = 0; + + /// Make feature f require feature g within the same object + void require_feature_self(int f, int g); + + /// Make features f and g mutually exclusive within the same object + void exclude_feature_self(int f, int g); + + /// Make feature f require feature g within children + void require_feature_children(int f, int g); + + /// Make feature f require either g or h within the same object + void require_feature_alt(int f, int g, int h); + + /// Make feature f require any of g, h, or i within the same object + void require_feature_alt(int f, int g, int h, int i); + + /// Make feature f require any of g, h, i, or j within the same object + void require_feature_alt(int f, int g, int h, int i, int j); + + /// \brief print all enabled features and those of children, for debugging + void print_state(); + + /// \brief Check that a feature is enabled, raising COLVARS_BUG_ERROR if not + inline void check_enabled(int f, std::string const &reason) const + { + if (! is_enabled(f)) { + cvm::error("Error: "+reason+" requires that the feature \""+ + features()[f]->description+"\" is active.\n", COLVARS_BUG_ERROR); + } + } + +}; + +#endif + + diff --git a/src/external/colvars/colvargrid.cpp b/src/external/colvars/colvargrid.cpp new file mode 100644 index 00000000000..5ab10439cf3 --- /dev/null +++ b/src/external/colvars/colvargrid.cpp @@ -0,0 +1,1219 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include "colvargrid.h" +#include "colvargrid_def.h" + + + +colvar_grid_count::colvar_grid_count() + : colvar_grid() +{ + mult = 1; +} + +colvar_grid_count::colvar_grid_count(std::vector const &nx_i, + size_t const &def_count) + : colvar_grid(nx_i, def_count, 1) +{} + +colvar_grid_count::colvar_grid_count(std::vector &colvars, + size_t const &def_count, + bool margin) + : colvar_grid(colvars, def_count, 1, margin) +{} + +std::string colvar_grid_count::get_state_params() const +{ + return colvar_grid::get_state_params(); +} + +int colvar_grid_count::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + return colvar_grid::parse_params(conf, parse_mode); +} + +std::istream & colvar_grid_count::read_restart(std::istream &is) +{ + return colvar_grid::read_restart(is); +} + +cvm::memory_stream & colvar_grid_count::read_restart(cvm::memory_stream &is) +{ + return colvar_grid::read_restart(is); +} + +std::ostream & colvar_grid_count::write_restart(std::ostream &os) +{ + return colvar_grid::write_restart(os); +} + +cvm::memory_stream & colvar_grid_count::write_restart(cvm::memory_stream &os) +{ + return colvar_grid::write_restart(os); +} + +std::istream &colvar_grid_count::read_raw(std::istream &is) +{ + return colvar_grid::read_raw(is); +} + +cvm::memory_stream &colvar_grid_count::read_raw(cvm::memory_stream &is) +{ + return colvar_grid::read_raw(is); +} + +std::ostream &colvar_grid_count::write_raw(std::ostream &os, size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +cvm::memory_stream &colvar_grid_count::write_raw(cvm::memory_stream &os, + size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +std::istream & colvar_grid_count::read_multicol(std::istream &is, bool add) +{ + return colvar_grid::read_multicol(is, add); +} + +int colvar_grid_count::read_multicol(std::string const &filename, + std::string description, + bool add) +{ + return colvar_grid::read_multicol(filename, description, add); +} + +std::ostream & colvar_grid_count::write_multicol(std::ostream &os) const +{ + return colvar_grid::write_multicol(os); +} + +int colvar_grid_count::write_multicol(std::string const &filename, + std::string description) const +{ + return colvar_grid::write_multicol(filename, description); +} + +std::ostream & colvar_grid_count::write_opendx(std::ostream &os) const +{ + return colvar_grid::write_opendx(os); +} + +int colvar_grid_count::write_opendx(std::string const &filename, + std::string description) const +{ + return colvar_grid::write_opendx(filename, description); +} + + + +colvar_grid_scalar::colvar_grid_scalar() + : colvar_grid(), samples(NULL) +{} + +colvar_grid_scalar::colvar_grid_scalar(colvar_grid_scalar const &g) + : colvar_grid(g), samples(NULL) +{ +} + +colvar_grid_scalar::colvar_grid_scalar(std::vector const &nx_i) + : colvar_grid(nx_i, 0.0, 1), samples(NULL) +{ +} + +colvar_grid_scalar::colvar_grid_scalar(std::vector &colvars, bool margin) + : colvar_grid(colvars, 0.0, 1, margin), samples(NULL) +{ +} + +colvar_grid_scalar::~colvar_grid_scalar() +{ +} + +std::string colvar_grid_scalar::get_state_params() const +{ + return colvar_grid::get_state_params(); +} + +int colvar_grid_scalar::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + return colvar_grid::parse_params(conf, parse_mode); +} + +std::istream &colvar_grid_scalar::read_restart(std::istream &is) +{ + return colvar_grid::read_restart(is); +} + +cvm::memory_stream &colvar_grid_scalar::read_restart(cvm::memory_stream &is) +{ + return colvar_grid::read_restart(is); +} + +std::ostream &colvar_grid_scalar::write_restart(std::ostream &os) +{ + return colvar_grid::write_restart(os); +} + +cvm::memory_stream &colvar_grid_scalar::write_restart(cvm::memory_stream &os) +{ + return colvar_grid::write_restart(os); +} + +std::istream &colvar_grid_scalar::read_raw(std::istream &is) +{ + return colvar_grid::read_raw(is); +} + +cvm::memory_stream &colvar_grid_scalar::read_raw(cvm::memory_stream &is) +{ + return colvar_grid::read_raw(is); +} + +std::ostream &colvar_grid_scalar::write_raw(std::ostream &os, size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +cvm::memory_stream &colvar_grid_scalar::write_raw(cvm::memory_stream &os, + size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +std::istream & colvar_grid_scalar::read_multicol(std::istream &is, bool add) +{ + return colvar_grid::read_multicol(is, add); +} + +int colvar_grid_scalar::read_multicol(std::string const &filename, + std::string description, + bool add) +{ + return colvar_grid::read_multicol(filename, description, add); +} + +std::ostream & colvar_grid_scalar::write_multicol(std::ostream &os) const +{ + return colvar_grid::write_multicol(os); +} + +int colvar_grid_scalar::write_multicol(std::string const &filename, + std::string description) const +{ + return colvar_grid::write_multicol(filename, description); +} + +std::ostream & colvar_grid_scalar::write_opendx(std::ostream &os) const +{ + return colvar_grid::write_opendx(os); +} + +int colvar_grid_scalar::write_opendx(std::string const &filename, + std::string description) const +{ + return colvar_grid::write_opendx(filename, description); +} + + +cvm::real colvar_grid_scalar::maximum_value() const +{ + cvm::real max = data[0]; + for (size_t i = 0; i < nt; i++) { + if (data[i] > max) max = data[i]; + } + return max; +} + + +cvm::real colvar_grid_scalar::minimum_value() const +{ + cvm::real min = data[0]; + for (size_t i = 0; i < nt; i++) { + if (data[i] < min) min = data[i]; + } + return min; +} + +cvm::real colvar_grid_scalar::minimum_pos_value() const +{ + cvm::real minpos = data[0]; + size_t i; + for (i = 0; i < nt; i++) { + if(data[i] > 0) { + minpos = data[i]; + break; + } + } + for (i = 0; i < nt; i++) { + if (data[i] > 0 && data[i] < minpos) minpos = data[i]; + } + return minpos; +} + +cvm::real colvar_grid_scalar::integral() const +{ + cvm::real sum = 0.0; + for (size_t i = 0; i < nt; i++) { + sum += data[i]; + } + cvm::real bin_volume = 1.0; + for (size_t id = 0; id < widths.size(); id++) { + bin_volume *= widths[id]; + } + return bin_volume * sum; +} + + +cvm::real colvar_grid_scalar::entropy() const +{ + cvm::real sum = 0.0; + for (size_t i = 0; i < nt; i++) { + if (data[i] >0) { + sum += -1.0 * data[i] * cvm::logn(data[i]); + } + } + cvm::real bin_volume = 1.0; + for (size_t id = 0; id < widths.size(); id++) { + bin_volume *= widths[id]; + } + return bin_volume * sum; +} + + +colvar_grid_gradient::colvar_grid_gradient() + : colvar_grid(), + samples(NULL), + weights(NULL) +{} + +colvar_grid_gradient::colvar_grid_gradient(std::vector const &nx_i) + : colvar_grid(nx_i, 0.0, nx_i.size()), + samples(NULL), + weights(NULL) +{} + +colvar_grid_gradient::colvar_grid_gradient(std::vector &colvars) + : colvar_grid(colvars, 0.0, colvars.size()), + samples(NULL), + weights(NULL) +{} + + +colvar_grid_gradient::colvar_grid_gradient(std::string &filename) + : colvar_grid(), + samples(NULL), + weights(NULL) +{ + std::istream &is = cvm::main()->proxy->input_stream(filename, + "gradient file"); + if (!is) { + return; + } + + // Data in the header: nColvars, then for each + // xiMin, dXi, nPoints, periodic flag + + std::string hash; + size_t i; + + if ( !(is >> hash) || (hash != "#") ) { + cvm::error("Error reading grid at position "+ + cvm::to_str(static_cast(is.tellg()))+ + " in stream(read \"" + hash + "\")\n"); + return; + } + + is >> nd; + + if (nd > 50) { + cvm::error("Error: excessive number of dimensions in file \""+ + filename+"\". Please ensure that the file is not corrupt.\n", + COLVARS_INPUT_ERROR); + return; + } + + mult = nd; + std::vector lower_in(nd), widths_in(nd); + std::vector nx_in(nd); + std::vector periodic_in(nd); + + for (i = 0; i < nd; i++ ) { + if ( !(is >> hash) || (hash != "#") ) { + cvm::error("Error reading grid at position "+ + cvm::to_str(static_cast(is.tellg()))+ + " in stream(read \"" + hash + "\")\n"); + return; + } + + is >> lower_in[i] >> widths_in[i] >> nx_in[i] >> periodic_in[i]; + } + + this->setup(nx_in, 0., mult); + + widths = widths_in; + + for (i = 0; i < nd; i++ ) { + lower_boundaries.push_back(colvarvalue(lower_in[i])); + periodic.push_back(static_cast(periodic_in[i])); + } + + // Reset the istream for read_multicol, which expects the whole file + is.clear(); + is.seekg(0); + read_multicol(is); + cvm::main()->proxy->close_input_stream(filename); +} + +std::string colvar_grid_gradient::get_state_params() const +{ + return colvar_grid::get_state_params(); +} + +int colvar_grid_gradient::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + return colvar_grid::parse_params(conf, parse_mode); +} + +std::istream &colvar_grid_gradient::read_restart(std::istream &is) +{ + return colvar_grid::read_restart(is); +} + +cvm::memory_stream &colvar_grid_gradient::read_restart(cvm::memory_stream &is) +{ + return colvar_grid::read_restart(is); +} + +std::ostream &colvar_grid_gradient::write_restart(std::ostream &os) +{ + return colvar_grid::write_restart(os); +} + +cvm::memory_stream &colvar_grid_gradient::write_restart(cvm::memory_stream &os) +{ + return colvar_grid::write_restart(os); +} + +std::istream &colvar_grid_gradient::read_raw(std::istream &is) +{ + return colvar_grid::read_raw(is); +} + +cvm::memory_stream &colvar_grid_gradient::read_raw(cvm::memory_stream &is) +{ + return colvar_grid::read_raw(is); +} + +std::ostream &colvar_grid_gradient::write_raw(std::ostream &os, size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +cvm::memory_stream &colvar_grid_gradient::write_raw(cvm::memory_stream &os, + size_t const buf_size) const +{ + return colvar_grid::write_raw(os, buf_size); +} + +std::istream & colvar_grid_gradient::read_multicol(std::istream &is, bool add) +{ + return colvar_grid::read_multicol(is, add); +} + +int colvar_grid_gradient::read_multicol(std::string const &filename, + std::string description, + bool add) +{ + return colvar_grid::read_multicol(filename, description, add); +} + +std::ostream & colvar_grid_gradient::write_multicol(std::ostream &os) const +{ + return colvar_grid::write_multicol(os); +} + +int colvar_grid_gradient::write_multicol(std::string const &filename, + std::string description) const +{ + return colvar_grid::write_multicol(filename, description); +} + +std::ostream & colvar_grid_gradient::write_opendx(std::ostream &os) const +{ + return colvar_grid::write_opendx(os); +} + +int colvar_grid_gradient::write_opendx(std::string const &filename, + std::string description) const +{ + return colvar_grid::write_opendx(filename, description); +} + + +void colvar_grid_gradient::write_1D_integral(std::ostream &os) +{ + cvm::real bin, min, integral; + std::vector int_vals; + + os << "# xi A(xi)\n"; + + if (cv.size() != 1) { + cvm::error("Cannot write integral for multi-dimensional gradient grids."); + return; + } + + integral = 0.0; + int_vals.push_back(0.0); + min = 0.0; + + // correction for periodic colvars, so that the PMF is periodic + cvm::real corr; + if (periodic[0]) { + corr = average(); + } else { + corr = 0.0; + } + + for (std::vector ix = new_index(); index_ok(ix); incr(ix)) { + + if (samples) { + size_t const samples_here = samples->value(ix); + if (samples_here) + integral += (value(ix) / cvm::real(samples_here) - corr) * cv[0]->width; + } else { + integral += (value(ix) - corr) * cv[0]->width; + } + + if ( integral < min ) min = integral; + int_vals.push_back(integral); + } + + bin = 0.0; + for ( int i = 0; i < nx[0]; i++, bin += 1.0 ) { + os << std::setw(10) << cv[0]->lower_boundary.real_value + cv[0]->width * bin << " " + << std::setw(cvm::cv_width) + << std::setprecision(cvm::cv_prec) + << int_vals[i] - min << "\n"; + } + + os << std::setw(10) << cv[0]->lower_boundary.real_value + cv[0]->width * bin << " " + << std::setw(cvm::cv_width) + << std::setprecision(cvm::cv_prec) + << int_vals[nx[0]] - min << "\n"; + + return; +} + + + +integrate_potential::integrate_potential(std::vector &colvars, colvar_grid_gradient * gradients) + : colvar_grid_scalar(colvars, true), + gradients(gradients) +{ + // parent class colvar_grid_scalar is constructed with margin option set to true + // hence PMF grid is wider than gradient grid if non-PBC + + if (nd > 1) { + cvm::main()->cite_feature("Poisson integration of 2D/3D free energy surfaces"); + divergence.resize(nt); + + // Compute inverse of Laplacian diagonal for Jacobi preconditioning + // For now all code related to preconditioning is commented out + // until a method better than Jacobi is implemented +// cvm::log("Preparing inverse diagonal for preconditioning...\n"); +// inv_lap_diag.resize(nt); +// std::vector id(nt), lap_col(nt); +// for (int i = 0; i < nt; i++) { +// if (i % (nt / 100) == 0) +// cvm::log(cvm::to_str(i)); +// id[i] = 1.; +// atimes(id, lap_col); +// id[i] = 0.; +// inv_lap_diag[i] = 1. / lap_col[i]; +// } +// cvm::log("Done.\n"); + } +} + + +integrate_potential::integrate_potential(colvar_grid_gradient * gradients) + : gradients(gradients) +{ + nd = gradients->num_variables(); + nx = gradients->number_of_points_vec(); + widths = gradients->widths; + periodic = gradients->periodic; + + // Expand grid by 1 bin in non-periodic dimensions + for (size_t i = 0; i < nd; i++ ) { + if (!periodic[i]) nx[i]++; + // Shift the grid by half the bin width (values at edges instead of center of bins) + lower_boundaries.push_back(gradients->lower_boundaries[i].real_value - 0.5 * widths[i]); + } + + setup(nx); + + if (nd > 1) { + divergence.resize(nt); + } +} + + +int integrate_potential::integrate(const int itmax, const cvm::real &tol, cvm::real & err) +{ + int iter = 0; + + if (nd == 1) { + + cvm::real sum = 0.0; + cvm::real corr; + if ( periodic[0] ) { + corr = gradients->average(); // Enforce PBC by subtracting average gradient + } else { + corr = 0.0; + } + + std::vector ix; + // Iterate over valid indices in gradient grid + for (ix = new_index(); gradients->index_ok(ix); incr(ix)) { + set_value(ix, sum); + sum += (gradients->value_output(ix) - corr) * widths[0]; + } + if (index_ok(ix)) { + // This will happen if non-periodic: then PMF grid has one extra bin wrt gradient grid + set_value(ix, sum); + } + + } else if (nd <= 3) { + + nr_linbcg_sym(divergence, data, tol, itmax, iter, err); + cvm::log("Integrated in " + cvm::to_str(iter) + " steps, error: " + cvm::to_str(err) + "\n"); + + } else { + cvm::error("Cannot integrate PMF in dimension > 3\n"); + } + + return iter; +} + + +void integrate_potential::set_div() +{ + if (nd == 1) return; + for (std::vector ix = new_index(); index_ok(ix); incr(ix)) { + update_div_local(ix); + } +} + + +void integrate_potential::update_div_neighbors(const std::vector &ix0) +{ + std::vector ix(ix0); + int i, j, k; + + // If not periodic, expanded grid ensures that neighbors of ix0 are valid grid points + if (nd == 1) { + return; + + } else if (nd == 2) { + + update_div_local(ix); + ix[0]++; wrap(ix); + update_div_local(ix); + ix[1]++; wrap(ix); + update_div_local(ix); + ix[0]--; wrap(ix); + update_div_local(ix); + + } else if (nd == 3) { + + for (i = 0; i<2; i++) { + ix[1] = ix0[1]; + for (j = 0; j<2; j++) { + ix[2] = ix0[2]; + for (k = 0; k<2; k++) { + wrap(ix); + update_div_local(ix); + ix[2]++; + } + ix[1]++; + } + ix[0]++; + } + } +} + +void integrate_potential::get_grad(cvm::real * g, std::vector &ix) +{ + size_t count, i; + bool edge = gradients->wrap_edge(ix); // Detect edge if non-PBC + + if (gradients->samples) { + count = gradients->samples->value(ix); + } else { + count = 1; + } + + if (!edge && count) { + cvm::real const *grad = &(gradients->value(ix)); + cvm::real const fact = 1.0 / count; + for ( i = 0; i &ix0) +{ + const size_t linear_index = address(ix0); + int i, j, k; + std::vector ix = ix0; + + if (nd == 2) { + // gradients at grid points surrounding the current scalar grid point + cvm::real g00[2], g01[2], g10[2], g11[2]; + + get_grad(g11, ix); + ix[0] = ix0[0] - 1; + get_grad(g01, ix); + ix[1] = ix0[1] - 1; + get_grad(g00, ix); + ix[0] = ix0[0]; + get_grad(g10, ix); + + divergence[linear_index] = ((g10[0]-g00[0] + g11[0]-g01[0]) / widths[0] + + (g01[1]-g00[1] + g11[1]-g10[1]) / widths[1]) * 0.5; + } else if (nd == 3) { + cvm::real gc[24]; // stores 3d gradients in 8 contiguous bins + int index = 0; + + ix[0] = ix0[0] - 1; + for (i = 0; i<2; i++) { + ix[1] = ix0[1] - 1; + for (j = 0; j<2; j++) { + ix[2] = ix0[2] - 1; + for (k = 0; k<2; k++) { + get_grad(gc + index, ix); + index += 3; + ix[2]++; + } + ix[1]++; + } + ix[0]++; + } + + divergence[linear_index] = + ((gc[3*4]-gc[0] + gc[3*5]-gc[3*1] + gc[3*6]-gc[3*2] + gc[3*7]-gc[3*3]) + / widths[0] + + (gc[3*2+1]-gc[0+1] + gc[3*3+1]-gc[3*1+1] + gc[3*6+1]-gc[3*4+1] + gc[3*7+1]-gc[3*5+1]) + / widths[1] + + (gc[3*1+2]-gc[0+2] + gc[3*3+2]-gc[3*2+2] + gc[3*5+2]-gc[3*4+2] + gc[3*7+2]-gc[3*6+2]) + / widths[2]) * 0.25; + } +} + + +/// Multiplication by sparse matrix representing Laplacian +/// NOTE: Laplacian must be symmetric for solving with CG +void integrate_potential::atimes(const std::vector &A, std::vector &LA) +{ + if (nd == 2) { + // DIMENSION 2 + + size_t index, index2; + int i, j; + cvm::real fact; + const cvm::real ffx = 1.0 / (widths[0] * widths[0]); + const cvm::real ffy = 1.0 / (widths[1] * widths[1]); + const int h = nx[1]; + const int w = nx[0]; + // offsets for 4 reference points of the Laplacian stencil + int xm = -h; + int xp = h; + int ym = -1; + int yp = 1; + + // NOTE on performance: this version is slightly sub-optimal because + // it contains two double loops on the core of the array (for x and y terms) + // The slightly faster version is in commit 0254cb5a2958cb2e135f268371c4b45fad34866b + // yet it is much uglier, and probably horrible to extend to dimension 3 + // All terms in the matrix are assigned (=) during the x loops, then updated (+=) + // with the y (and z) contributions + + + // All x components except on x edges + index = h; // Skip first column + + // Halve the term on y edges (if any) to preserve symmetry of the Laplacian matrix + // (Long Chen, Finite Difference Methods, UCI, 2017) + fact = periodic[1] ? 1.0 : 0.5; + + for (i=1; i(w - 1); // Follows right edge + if (periodic[0]) { + xm = h * (w - 1); + xp = h; + fact = periodic[1] ? 1.0 : 0.5; + LA[index] = fact * ffx * (A[index + xm] + A[index + xp] - 2.0 * A[index]); + LA[index2] = fact * ffx * (A[index2 - xp] + A[index2 - xm] - 2.0 * A[index2]); + index++; + index2++; + for (j=1; j(h); // Skip left slab + fact = facty * factz; + for (i=1; i(d) * h * (w - 1); // Follows right slab + if (periodic[0]) { + xm = d * h * (w - 1); + xp = d * h; + fact = facty * factz; + for (j=0; j(d - 1); // Follows back slab + if (periodic[1]) { + ym = h * (d - 1); + yp = h; + fact = factx * factz; + for (i=0; i(d - 1); + index2 += h * static_cast(d - 1); + } + } else { + ym = -h; + yp = h; + fact = factx * factz; + for (i=0; i(d - 1); + index2 += h * static_cast(d - 1); + } + } + + // Now adding all z components + // All z components except on z edges + index = 1; // Skip first element (in bottom slab) + fact = factx * facty; + for (i=0; i &b, std::vector &x) +{ + for (size_t i=0; i &b, std::vector &x, const cvm::real &tol, + const int itmax, int &iter, cvm::real &err) +{ + cvm::real ak,akden,bk,bkden,bknum,bnrm; + const cvm::real EPS=1.0e-14; + int j; + std::vector p(nt), r(nt), z(nt); + + iter=0; + atimes(x,r); + for (j=0;j class colvar_grid : public colvarparse { + + //protected: +public: // TODO create accessors for these after all instantiations work + + /// Number of dimensions + size_t nd; + + /// Number of points along each dimension + std::vector nx; + + /// Cumulative number of points along each dimension + std::vector nxc; + + /// \brief Multiplicity of each datum (allow the binning of + /// non-scalar types such as atomic gradients) + size_t mult; + + /// Total number of grid points + size_t nt; + + /// Low-level array of values + std::vector data; + + /// Newly read data (used for count grids, when adding several grids read from disk) + std::vector new_data; + + /// Colvars collected in this grid + std::vector cv; + + /// Do we request actual value (for extended-system colvars)? + std::vector use_actual_value; + + /// Get the low-level index corresponding to an index + inline size_t address(std::vector const &ix) const + { + size_t addr = 0; + for (size_t i = 0; i < nd; i++) { + addr += ix[i]*static_cast(nxc[i]); + if (cvm::debug()) { + if (ix[i] >= nx[i]) { + cvm::error("Error: exceeding bounds in colvar_grid.\n", COLVARS_BUG_ERROR); + return 0; + } + } + } + return addr; + } + +public: + + /// Lower boundaries of the colvars in this grid + std::vector lower_boundaries; + + /// Upper boundaries of the colvars in this grid + std::vector upper_boundaries; + + /// Whether some colvars are periodic + std::vector periodic; + + /// Whether some colvars have hard lower boundaries + std::vector hard_lower_boundaries; + + /// Whether some colvars have hard upper boundaries + std::vector hard_upper_boundaries; + + /// Widths of the colvars in this grid + std::vector widths; + + /// True if this is a count grid related to another grid of data + bool has_parent_data; + + /// Whether this grid has been filled with data or is still empty + bool has_data; + + /// Return the number of colvar objects + inline size_t num_variables() const + { + return nd; + } + + /// Return the numbers of points in all dimensions + inline std::vector const &number_of_points_vec() const + { + return nx; + } + + /// Return the number of points in the i-th direction, if provided, or + /// the total number + inline size_t number_of_points(int const icv = -1) const + { + if (icv < 0) { + return nt; + } else { + return nx[icv]; + } + } + + /// Get the sizes in each direction + inline std::vector const & sizes() const + { + return nx; + } + + /// Set the sizes in each direction + inline void set_sizes(std::vector const &new_sizes) + { + nx = new_sizes; + } + + /// Return the multiplicity of the type used + inline size_t multiplicity() const + { + return mult; + } + + /// \brief Request grid to use actual values of extended coords + inline void request_actual_value(bool b = true) + { + size_t i; + for (i = 0; i < use_actual_value.size(); i++) { + use_actual_value[i] = b; + } + } + + /// \brief Allocate data + int setup(std::vector const &nx_i, + T const &t = T(), + size_t const &mult_i = 1) + { + if (cvm::debug()) { + cvm::log("Allocating grid: multiplicity = "+cvm::to_str(mult_i)+ + ", dimensions = "+cvm::to_str(nx_i)+".\n"); + } + + mult = mult_i; + + data.clear(); + + nx = nx_i; + nd = nx.size(); + + nxc.resize(nd); + + // setup dimensions + nt = mult; + for (int i = nd-1; i >= 0; i--) { + if (nx[i] <= 0) { + cvm::error("Error: providing an invalid number of grid points, "+ + cvm::to_str(nx[i])+".\n", COLVARS_BUG_ERROR); + return COLVARS_ERROR; + } + nxc[i] = nt; + nt *= nx[i]; + } + + if (cvm::debug()) { + cvm::log("Total number of grid elements = "+cvm::to_str(nt)+".\n"); + } + + data.reserve(nt); + data.assign(nt, t); + + return COLVARS_OK; + } + + /// \brief Allocate data (allow initialization also after construction) + int setup() + { + return setup(this->nx, T(), this->mult); + } + + /// \brief Reset data (in case the grid is being reused) + void reset(T const &t = T()) + { + data.assign(nt, t); + } + + + /// Default constructor + colvar_grid() : has_data(false) + { + nd = nt = 0; + mult = 1; + has_parent_data = false; + this->setup(); + } + + /// Destructor + virtual ~colvar_grid() + {} + + /// \brief "Almost copy-constructor": only copies configuration + /// parameters from another grid, but doesn't reallocate stuff; + /// setup() must be called after that; + colvar_grid(colvar_grid const &g) : colvarparse(), + nd(g.nd), + nx(g.nx), + mult(g.mult), + data(), + cv(g.cv), + use_actual_value(g.use_actual_value), + lower_boundaries(g.lower_boundaries), + upper_boundaries(g.upper_boundaries), + periodic(g.periodic), + hard_lower_boundaries(g.hard_lower_boundaries), + hard_upper_boundaries(g.hard_upper_boundaries), + widths(g.widths), + has_parent_data(false), + has_data(false) + {} + + /// \brief Constructor from explicit grid sizes \param nx_i Number + /// of grid points along each dimension \param t Initial value for + /// the function at each point (optional) \param mult_i Multiplicity + /// of each value + colvar_grid(std::vector const &nx_i, + T const &t = T(), + size_t mult_i = 1) + : has_parent_data(false), has_data(false) + { + this->setup(nx_i, t, mult_i); + } + + /// \brief Constructor from a vector of colvars + /// \param add_extra_bin requests that non-periodic dimensions are extended + /// by 1 bin to accommodate the integral (PMF) of another gridded quantity (gradient) + colvar_grid(std::vector const &colvars, + T const &t = T(), + size_t mult_i = 1, + bool add_extra_bin = false) + : has_parent_data(false), has_data(false) + { + (void) t; + this->init_from_colvars(colvars, mult_i, add_extra_bin); + } + + int init_from_colvars(std::vector const &colvars, + size_t mult_i = 1, + bool add_extra_bin = false) + { + if (cvm::debug()) { + cvm::log("Reading grid configuration from collective variables.\n"); + } + + cv = colvars; + nd = colvars.size(); + mult = mult_i; + + size_t i; + + if (cvm::debug()) { + cvm::log("Allocating a grid for "+cvm::to_str(colvars.size())+ + " collective variables, multiplicity = "+cvm::to_str(mult_i)+".\n"); + } + + for (i = 0; i < cv.size(); i++) { + + if (cv[i]->value().type() != colvarvalue::type_scalar) { + cvm::error("Colvar grids can only be automatically " + "constructed for scalar variables. " + "ABF and histogram can not be used; metadynamics " + "can be used with useGrids disabled.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + if (cv[i]->width <= 0.0) { + cvm::error("Tried to initialize a grid on a " + "variable with negative or zero width.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + + widths.push_back(cv[i]->width); + hard_lower_boundaries.push_back(cv[i]->is_enabled(colvardeps::f_cv_hard_lower_boundary)); + hard_upper_boundaries.push_back(cv[i]->is_enabled(colvardeps::f_cv_hard_upper_boundary)); + periodic.push_back(cv[i]->periodic_boundaries()); + + // By default, get reported colvar value (for extended Lagrangian colvars) + use_actual_value.push_back(false); + + // except if a colvar is specified twice in a row + // then the first instance is the actual value + // For histograms of extended-system coordinates + if (i > 0 && cv[i-1] == cv[i]) { + use_actual_value[i-1] = true; + } + + if (add_extra_bin) { + if (periodic[i]) { + // Shift the grid by half the bin width (values at edges instead of center of bins) + lower_boundaries.push_back(cv[i]->lower_boundary.real_value - 0.5 * widths[i]); + upper_boundaries.push_back(cv[i]->upper_boundary.real_value - 0.5 * widths[i]); + } else { + // Make this grid larger by one bin width + lower_boundaries.push_back(cv[i]->lower_boundary.real_value - 0.5 * widths[i]); + upper_boundaries.push_back(cv[i]->upper_boundary.real_value + 0.5 * widths[i]); + } + } else { + lower_boundaries.push_back(cv[i]->lower_boundary); + upper_boundaries.push_back(cv[i]->upper_boundary); + } + } + + this->init_from_boundaries(); + return this->setup(); + } + + int init_from_boundaries() + { + if (cvm::debug()) { + cvm::log("Configuring grid dimensions from colvars boundaries.\n"); + } + + // these will have to be updated + nx.clear(); + nxc.clear(); + nt = 0; + + for (size_t i = 0; i < lower_boundaries.size(); i++) { + // Re-compute periodicity using current grid boundaries + periodic[i] = cv[i]->periodic_boundaries(lower_boundaries[i].real_value, + upper_boundaries[i].real_value); + + cvm::real nbins = ( upper_boundaries[i].real_value - + lower_boundaries[i].real_value ) / widths[i]; + int nbins_round = (int)(nbins+0.5); + + if (cvm::fabs(nbins - cvm::real(nbins_round)) > 1.0E-10) { + cvm::log("Warning: grid interval("+ + cvm::to_str(lower_boundaries[i], cvm::cv_width, cvm::cv_prec)+" - "+ + cvm::to_str(upper_boundaries[i], cvm::cv_width, cvm::cv_prec)+ + ") is not commensurate to its bin width("+ + cvm::to_str(widths[i], cvm::cv_width, cvm::cv_prec)+").\n"); + upper_boundaries[i].real_value = lower_boundaries[i].real_value + + (nbins_round * widths[i]); + } + + if (cvm::debug()) + cvm::log("Number of points is "+cvm::to_str((int) nbins_round)+ + " for the colvar no. "+cvm::to_str(i+1)+".\n"); + + nx.push_back(nbins_round); + } + + return COLVARS_OK; + } + + /// Wrap an index vector around periodic boundary conditions + /// also checks validity of non-periodic indices + inline void wrap(std::vector & ix) const + { + for (size_t i = 0; i < nd; i++) { + if (periodic[i]) { + ix[i] = (ix[i] + nx[i]) % nx[i]; //to ensure non-negative result + } else { + if (ix[i] < 0 || ix[i] >= nx[i]) { + cvm::error("Trying to wrap illegal index vector (non-PBC) for a grid point: " + + cvm::to_str(ix), COLVARS_BUG_ERROR); + return; + } + } + } + } + + /// Wrap an index vector around periodic boundary conditions + /// or detects edges if non-periodic + inline bool wrap_edge(std::vector & ix) const + { + bool edge = false; + for (size_t i = 0; i < nd; i++) { + if (periodic[i]) { + ix[i] = (ix[i] + nx[i]) % nx[i]; //to ensure non-negative result + } else if (ix[i] == -1 || ix[i] == nx[i]) { + edge = true; + } + } + return edge; + } + + /// \brief Report the bin corresponding to the current value of variable i + inline int current_bin_scalar(int const i) const + { + return value_to_bin_scalar(use_actual_value[i] ? cv[i]->actual_value() : cv[i]->value(), i); + } + + /// \brief Report the bin corresponding to the current value of variable i + /// and assign first or last bin if out of boundaries + inline int current_bin_scalar_bound(int const i) const + { + return value_to_bin_scalar_bound(use_actual_value[i] ? cv[i]->actual_value() : cv[i]->value(), i); + } + + /// \brief Report the bin corresponding to the current value of item iv in variable i + inline int current_bin_scalar(int const i, int const iv) const + { + return value_to_bin_scalar(use_actual_value[i] ? + cv[i]->actual_value().vector1d_value[iv] : + cv[i]->value().vector1d_value[iv], i); + } + + /// \brief Use the lower boundary and the width to report which bin + /// the provided value is in + inline int value_to_bin_scalar(colvarvalue const &value, const int i) const + { + return (int) cvm::floor( (value.real_value - lower_boundaries[i].real_value) / widths[i] ); + } + + /// \brief Report the fraction of bin beyond current_bin_scalar() + inline cvm::real current_bin_scalar_fraction(int const i) const + { + return value_to_bin_scalar_fraction(use_actual_value[i] ? cv[i]->actual_value() : cv[i]->value(), i); + } + + /// \brief Use the lower boundary and the width to report the fraction of bin + /// beyond value_to_bin_scalar() that the provided value is in + inline cvm::real value_to_bin_scalar_fraction(colvarvalue const &value, const int i) const + { + cvm::real x = (value.real_value - lower_boundaries[i].real_value) / widths[i]; + return x - cvm::floor(x); + } + + /// \brief Use the lower boundary and the width to report which bin + /// the provided value is in and assign first or last bin if out of boundaries + inline int value_to_bin_scalar_bound(colvarvalue const &value, const int i) const + { + int bin_index = cvm::floor( (value.real_value - lower_boundaries[i].real_value) / widths[i] ); + if (bin_index < 0) bin_index=0; + if (bin_index >=int(nx[i])) bin_index=int(nx[i])-1; + return (int) bin_index; + } + + /// \brief Same as the standard version, but uses another grid definition + inline int value_to_bin_scalar(colvarvalue const &value, + colvarvalue const &new_offset, + cvm::real const &new_width) const + { + return (int) cvm::floor( (value.real_value - new_offset.real_value) / new_width ); + } + + /// \brief Use the two boundaries and the width to report the + /// central value corresponding to a bin index + inline colvarvalue bin_to_value_scalar(int const &i_bin, int const i) const + { + return lower_boundaries[i].real_value + widths[i] * (0.5 + i_bin); + } + + /// \brief Same as the standard version, but uses different parameters + inline colvarvalue bin_to_value_scalar(int const &i_bin, + colvarvalue const &new_offset, + cvm::real const &new_width) const + { + return new_offset.real_value + new_width * (0.5 + i_bin); + } + + /// Set the value at the point with index ix + inline void set_value(std::vector const &ix, + T const &t, + size_t const &imult = 0) + { + data[this->address(ix)+imult] = t; + has_data = true; + } + + /// Set the value at the point with linear address i (for speed) + inline void set_value(size_t i, T const &t) + { + data[i] = t; + } + + /// \brief Get the change from this to other_grid + /// and store the result in this. + /// this_grid := other_grid - this_grid + /// Grids must have the same dimensions. + void delta_grid(colvar_grid const &other_grid) + { + + if (other_grid.multiplicity() != this->multiplicity()) { + cvm::error("Error: trying to subtract two grids with " + "different multiplicity.\n"); + return; + } + + if (other_grid.data.size() != this->data.size()) { + cvm::error("Error: trying to subtract two grids with " + "different size.\n"); + return; + } + + for (size_t i = 0; i < data.size(); i++) { + data[i] = other_grid.data[i] - data[i]; + } + has_data = true; + } + + /// \brief Copy data from another grid of the same type, AND + /// identical definition (boundaries, widths) + /// Added for shared ABF. + void copy_grid(colvar_grid const &other_grid) + { + if (other_grid.multiplicity() != this->multiplicity()) { + cvm::error("Error: trying to copy two grids with " + "different multiplicity.\n"); + return; + } + + if (other_grid.data.size() != this->data.size()) { + cvm::error("Error: trying to copy two grids with " + "different size.\n"); + return; + } + + + for (size_t i = 0; i < data.size(); i++) { + data[i] = other_grid.data[i]; + } + has_data = true; + } + + /// \brief Extract the grid data as they are represented in memory. + /// Put the results in "out_data". + void raw_data_out(T* out_data) const + { + for (size_t i = 0; i < data.size(); i++) out_data[i] = data[i]; + } + void raw_data_out(std::vector& out_data) const + { + out_data = data; + } + /// \brief Input the data as they are represented in memory. + void raw_data_in(const T* in_data) + { + for (size_t i = 0; i < data.size(); i++) data[i] = in_data[i]; + has_data = true; + } + void raw_data_in(const std::vector& in_data) + { + data = in_data; + has_data = true; + } + /// \brief Size of the data as they are represented in memory. + size_t raw_data_num() const { return data.size(); } + + + /// \brief Get the binned value indexed by ix, or the first of them + /// if the multiplicity is larger than 1 + inline T const & value(std::vector const &ix, + size_t const &imult = 0) const + { + return data[this->address(ix) + imult]; + } + + /// \brief Get the binned value indexed by linear address i + inline T const & value(size_t i) const + { + return data[i]; + } + + /// \brief Add a constant to all elements (fast loop) + inline void add_constant(T const &t) + { + for (size_t i = 0; i < nt; i++) + data[i] += t; + has_data = true; + } + + /// \brief Multiply all elements by a scalar constant (fast loop) + inline void multiply_constant(cvm::real const &a) + { + for (size_t i = 0; i < nt; i++) + data[i] *= a; + } + + /// \brief Assign values that are smaller than scalar constant the latter value (fast loop) + inline void remove_small_values(cvm::real const &a) + { + for (size_t i = 0; i < nt; i++) + if(data[i] const get_colvars_index(std::vector const &values) const + { + std::vector index = new_index(); + for (size_t i = 0; i < nd; i++) { + index[i] = value_to_bin_scalar(values[i], i); + } + return index; + } + + /// \brief Get the bin indices corresponding to the current values + /// of the colvars + inline std::vector const get_colvars_index() const + { + std::vector index = new_index(); + for (size_t i = 0; i < nd; i++) { + index[i] = current_bin_scalar(i); + } + return index; + } + + /// \brief Get the bin indices corresponding to the provided values of + /// the colvars and assign first or last bin if out of boundaries + inline std::vector const get_colvars_index_bound() const + { + std::vector index = new_index(); + for (size_t i = 0; i < nd; i++) { + index[i] = current_bin_scalar_bound(i); + } + return index; + } + + /// \brief Get the minimal distance (in number of bins) from the + /// boundaries; a negative number is returned if the given point is + /// off-grid + inline cvm::real bin_distance_from_boundaries(std::vector const &values, + bool skip_hard_boundaries = false) + { + cvm::real minimum = 1.0E+16; + for (size_t i = 0; i < nd; i++) { + + if (periodic[i]) continue; + + cvm::real dl = cvm::sqrt(cv[i]->dist2(values[i], lower_boundaries[i])) / widths[i]; + cvm::real du = cvm::sqrt(cv[i]->dist2(values[i], upper_boundaries[i])) / widths[i]; + + if (values[i].real_value < lower_boundaries[i]) + dl *= -1.0; + if (values[i].real_value > upper_boundaries[i]) + du *= -1.0; + + if ( ((!skip_hard_boundaries) || (!hard_lower_boundaries[i])) && (dl < minimum)) + minimum = dl; + if ( ((!skip_hard_boundaries) || (!hard_upper_boundaries[i])) && (du < minimum)) + minimum = du; + } + + return minimum; + } + + + /// \brief Add data from another grid of the same type + /// + /// Note: this function maps other_grid inside this one regardless + /// of whether it fits or not. + void map_grid(colvar_grid const &other_grid) + { + if (other_grid.multiplicity() != this->multiplicity()) { + cvm::error("Error: trying to merge two grids with values of " + "different multiplicity.\n"); + return; + } + + std::vector const &gb = this->lower_boundaries; + std::vector const &gw = this->widths; + std::vector const &ogb = other_grid.lower_boundaries; + std::vector const &ogw = other_grid.widths; + + std::vector ix = this->new_index(); + std::vector oix = other_grid.new_index(); + + if (cvm::debug()) + cvm::log("Remapping grid...\n"); + for ( ; this->index_ok(ix); this->incr(ix)) { + + for (size_t i = 0; i < nd; i++) { + oix[i] = + value_to_bin_scalar(bin_to_value_scalar(ix[i], gb[i], gw[i]), + ogb[i], + ogw[i]); + } + + if (! other_grid.index_ok(oix)) { + continue; + } + + for (size_t im = 0; im < mult; im++) { + this->set_value(ix, other_grid.value(oix, im), im); + } + } + + has_data = true; + if (cvm::debug()) + cvm::log("Remapping done.\n"); + } + + /// \brief Add data from another grid of the same type, AND + /// identical definition (boundaries, widths) + void add_grid(colvar_grid const &other_grid, + cvm::real scale_factor = 1.0) + { + if (other_grid.multiplicity() != this->multiplicity()) { + cvm::error("Error: trying to sum togetehr two grids with values of " + "different multiplicity.\n"); + return; + } + if (scale_factor != 1.0) + for (size_t i = 0; i < data.size(); i++) { + data[i] += static_cast(scale_factor * other_grid.data[i]); + } + else + // skip multiplication if possible + for (size_t i = 0; i < data.size(); i++) { + data[i] += other_grid.data[i]; + } + has_data = true; + } + + /// \brief Return the value suitable for output purposes (so that it + /// may be rescaled or manipulated without changing it permanently) + virtual T value_output(std::vector const &ix, + size_t const &imult = 0) const + { + return value(ix, imult); + } + + /// \brief Get the value from a formatted output and transform it + /// into the internal representation (the two may be different, + /// e.g. when using colvar_grid_count) + virtual void value_input(std::vector const &ix, + T const &t, + size_t const &imult = 0, + bool add = false) + { + if ( add ) + data[address(ix) + imult] += t; + else + data[address(ix) + imult] = t; + has_data = true; + } + + // /// Get the pointer to the binned value indexed by ix + // inline T const *value_p (std::vector const &ix) + // { + // return &(data[address (ix)]); + // } + + /// \brief Get the index corresponding to the "first" bin, to be + /// used as the initial value for an index in looping + inline std::vector const new_index() const + { + return std::vector (nd, 0); + } + + /// \brief Check that the index is within range in each of the + /// dimensions + inline bool index_ok(std::vector const &ix) const + { + for (size_t i = 0; i < nd; i++) { + if ( (ix[i] < 0) || (ix[i] >= int(nx[i])) ) + return false; + } + return true; + } + + /// \brief Increment the index, in a way that will make it loop over + /// the whole nd-dimensional array + inline void incr(std::vector &ix) const + { + for (int i = ix.size()-1; i >= 0; i--) { + + ix[i]++; + + if (ix[i] >= nx[i]) { + + if (i > 0) { + ix[i] = 0; + continue; + } else { + // this is the last iteration, a non-valid index is being + // set for the outer index, which will be caught by + // index_ok() + ix[0] = nx[0]; + return; + } + } else { + return; + } + } + } + + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// \brief Check that the grid information inside (boundaries, + /// widths, ...) is consistent with the current setting of the + /// colvars + void check_consistency() + { + for (size_t i = 0; i < nd; i++) { + if ( (cvm::sqrt(cv[i]->dist2(cv[i]->lower_boundary, + lower_boundaries[i])) > 1.0E-10) || + (cvm::sqrt(cv[i]->dist2(cv[i]->upper_boundary, + upper_boundaries[i])) > 1.0E-10) || + (cvm::sqrt(cv[i]->dist2(cv[i]->width, + widths[i])) > 1.0E-10) ) { + cvm::error("Error: restart information for a grid is " + "inconsistent with that of its colvars.\n"); + return; + } + } + } + + + /// \brief Check that the grid information inside (boundaries, + /// widths, ...) is consistent with that of another grid + void check_consistency(colvar_grid const &other_grid) + { + for (size_t i = 0; i < nd; i++) { + // we skip dist2(), because periodicities and the like should + // matter: boundaries should be EXACTLY the same (otherwise, + // map_grid() should be used) + if ( (cvm::fabs(other_grid.lower_boundaries[i] - + lower_boundaries[i]) > 1.0E-10) || + (cvm::fabs(other_grid.upper_boundaries[i] - + upper_boundaries[i]) > 1.0E-10) || + (cvm::fabs(other_grid.widths[i] - + widths[i]) > 1.0E-10) || + (data.size() != other_grid.data.size()) ) { + cvm::error("Error: inconsistency between " + "two grids that are supposed to be equal, " + "aside from the data stored.\n"); + return; + } + } + } + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; + + /// Read a grid written by write_multicol(), incrementing if add is true + std::istream & read_multicol(std::istream &is, bool add = false); + + /// Read a grid written by write_multicol(), incrementing if add is true + int read_multicol(std::string const &filename, + std::string description = "grid file", + bool add = false); + + /// Write grid in a format which is both human-readable and gnuplot-friendly + std::ostream & write_multicol(std::ostream &os) const; + + /// Write grid in a format which is both human-readable and gnuplot-friendly + int write_multicol(std::string const &filename, + std::string description = "grid file") const; + + /// Write the grid data without labels, as they are represented in memory + std::ostream & write_opendx(std::ostream &os) const; + + /// Write the grid data without labels, as they are represented in memory + int write_opendx(std::string const &filename, + std::string description = "grid file") const; +}; + + + +/// \brief Colvar_grid derived class to hold counters in discrete +/// n-dim colvar space +class colvar_grid_count : public colvar_grid +{ +public: + + /// Default constructor + colvar_grid_count(); + + /// Destructor + virtual ~colvar_grid_count() + {} + + /// Constructor + colvar_grid_count(std::vector const &nx_i, + size_t const &def_count = 0); + + /// Constructor from a vector of colvars + colvar_grid_count(std::vector &colvars, + size_t const &def_count = 0, + bool add_extra_bin = false); + + /// Increment the counter at given position + inline void incr_count(std::vector const &ix) + { + ++(data[this->address(ix)]); + } + + /// \brief Get the binned count indexed by ix from the newly read data + inline size_t const & new_count(std::vector const &ix, + size_t const &imult = 0) + { + return new_data[address(ix) + imult]; + } + + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; + + /// Read a grid written by write_multicol(), incrementin if data is true + std::istream & read_multicol(std::istream &is, bool add = false); + + /// Read a grid written by write_multicol(), incrementin if data is true + int read_multicol(std::string const &filename, + std::string description = "grid file", + bool add = false); + + /// Write grid in a format which is both human-readable and gnuplot-friendly + std::ostream & write_multicol(std::ostream &os) const; + + /// Write grid in a format which is both human-readable and gnuplot-friendly + int write_multicol(std::string const &filename, + std::string description = "grid file") const; + + /// Write the grid data without labels, as they are represented in memory + std::ostream & write_opendx(std::ostream &os) const; + + /// Write the grid data without labels, as they are represented in memory + int write_opendx(std::string const &filename, + std::string description = "grid file") const; + + /// Enter or add a value, but also handle parent grid + virtual void value_input(std::vector const &ix, + size_t const &t, + size_t const &imult = 0, + bool add = false) + { + (void) imult; + if (add) { + data[address(ix)] += t; + if (this->has_parent_data) { + // save newly read data for inputting parent grid + new_data[address(ix)] = t; + } + } else { + data[address(ix)] = t; + } + has_data = true; + } + + /// \brief Return the log-gradient from finite differences + /// on the *same* grid for dimension n + inline cvm::real log_gradient_finite_diff(const std::vector &ix0, + int n = 0) + { + cvm::real A0, A1, A2; + std::vector ix = ix0; + + // TODO this can be rewritten more concisely with wrap_edge() + if (periodic[n]) { + ix[n]--; wrap(ix); + A0 = value(ix); + ix = ix0; + ix[n]++; wrap(ix); + A1 = value(ix); + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (cvm::logn(A1) - cvm::logn(A0)) + / (widths[n] * 2.); + } + } else if (ix[n] > 0 && ix[n] < nx[n]-1) { // not an edge + ix[n]--; + A0 = value(ix); + ix = ix0; + ix[n]++; + A1 = value(ix); + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (cvm::logn(A1) - cvm::logn(A0)) + / (widths[n] * 2.); + } + } else { + // edge: use 2nd order derivative + int increment = (ix[n] == 0 ? 1 : -1); + // move right from left edge, or the other way around + A0 = value(ix); + ix[n] += increment; A1 = value(ix); + ix[n] += increment; A2 = value(ix); + if (A0 * A1 * A2 == 0) { + return 0.; // can't handle empty bins + } else { + return (-1.5 * cvm::logn(A0) + 2. * cvm::logn(A1) + - 0.5 * cvm::logn(A2)) * increment / widths[n]; + } + } + } + + /// \brief Return the gradient of discrete count from finite differences + /// on the *same* grid for dimension n + inline cvm::real gradient_finite_diff(const std::vector &ix0, + int n = 0) + { + cvm::real A0, A1, A2; + std::vector ix = ix0; + + // FIXME this can be rewritten more concisely with wrap_edge() + if (periodic[n]) { + ix[n]--; wrap(ix); + A0 = value(ix); + ix = ix0; + ix[n]++; wrap(ix); + A1 = value(ix); + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (A1 - A0) / (widths[n] * 2.); + } + } else if (ix[n] > 0 && ix[n] < nx[n]-1) { // not an edge + ix[n]--; + A0 = value(ix); + ix = ix0; + ix[n]++; + A1 = value(ix); + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (A1 - A0) / (widths[n] * 2.); + } + } else { + // edge: use 2nd order derivative + int increment = (ix[n] == 0 ? 1 : -1); + // move right from left edge, or the other way around + A0 = value(ix); + ix[n] += increment; A1 = value(ix); + ix[n] += increment; A2 = value(ix); + return (-1.5 * A0 + 2. * A1 + - 0.5 * A2) * increment / widths[n]; + } + } +}; + + +/// Class for accumulating a scalar function on a grid +class colvar_grid_scalar : public colvar_grid +{ +public: + + /// \brief Provide the associated sample count by which each binned value + /// should be divided + colvar_grid_count *samples; + + /// Default constructor + colvar_grid_scalar(); + + /// Copy constructor (needed because of the grad pointer) + colvar_grid_scalar(colvar_grid_scalar const &g); + + /// Destructor + virtual ~colvar_grid_scalar(); + + /// Constructor from specific sizes arrays + colvar_grid_scalar(std::vector const &nx_i); + + /// Constructor from a vector of colvars + colvar_grid_scalar(std::vector &colvars, + bool add_extra_bin = false); + + /// Accumulate the value + inline void acc_value(std::vector const &ix, + cvm::real const &new_value, + size_t const &imult = 0) + { + (void) imult; + // only legal value of imult here is 0 + data[address(ix)] += new_value; + if (samples) + samples->incr_count(ix); + has_data = true; + } + + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; + + /// Read a grid written by write_multicol(), incrementin if data is true + std::istream & read_multicol(std::istream &is, bool add = false); + + /// Read a grid written by write_multicol(), incrementin if data is true + int read_multicol(std::string const &filename, + std::string description = "grid file", + bool add = false); + + /// Write grid in a format which is both human-readable and gnuplot-friendly + std::ostream & write_multicol(std::ostream &os) const; + + /// Write grid in a format which is both human-readable and gnuplot-friendly + int write_multicol(std::string const &filename, + std::string description = "grid file") const; + + /// Write the grid data without labels, as they are represented in memory + std::ostream & write_opendx(std::ostream &os) const; + + /// Write the grid data without labels, as they are represented in memory + int write_opendx(std::string const &filename, + std::string description = "grid file") const; + + /// \brief Return the gradient of the scalar field from finite differences + /// Input coordinates are those of gradient grid, shifted wrt scalar grid + /// Should not be called on edges of scalar grid, provided the latter has margins + /// wrt gradient grid + inline void vector_gradient_finite_diff( const std::vector &ix0, std::vector &grad) + { + cvm::real A0, A1; + std::vector ix; + size_t i, j, k, n; + + if (nd == 2) { + for (n = 0; n < 2; n++) { + ix = ix0; + A0 = value(ix); + ix[n]++; wrap(ix); + A1 = value(ix); + ix[1-n]++; wrap(ix); + A1 += value(ix); + ix[n]--; wrap(ix); + A0 += value(ix); + grad[n] = 0.5 * (A1 - A0) / widths[n]; + } + } else if (nd == 3) { + + cvm::real p[8]; // potential values within cube, indexed in binary (4 i + 2 j + k) + ix = ix0; + int index = 0; + for (i = 0; i<2; i++) { + ix[1] = ix0[1]; + for (j = 0; j<2; j++) { + ix[2] = ix0[2]; + for (k = 0; k<2; k++) { + wrap(ix); + p[index++] = value(ix); + ix[2]++; + } + ix[1]++; + } + ix[0]++; + } + + // The following would be easier to read using binary literals + // 100 101 110 111 000 001 010 011 + grad[0] = 0.25 * ((p[4] + p[5] + p[6] + p[7]) - (p[0] + p[1] + p[2] + p[3])) / widths[0]; + // 010 011 110 111 000 001 100 101 + grad[1] = 0.25 * ((p[2] + p[3] + p[6] + p[7]) - (p[0] + p[1] + p[4] + p[5])) / widths[1]; + // 001 011 101 111 000 010 100 110 + grad[2] = 0.25 * ((p[1] + p[3] + p[5] + p[7]) - (p[0] + p[2] + p[4] + p[6])) / widths[2]; + } else { + cvm::error("Finite differences available in dimension 2 and 3 only."); + } + } + + /// \brief Return the gradient of discrete count from finite differences + /// on the *same* grid for dimension n + inline cvm::real gradient_finite_diff(const std::vector &ix0, + int n = 0) + { + cvm::real A0, A1, A2; + std::vector ix = ix0; + + // FIXME this can be rewritten more concisely with wrap_edge() + if (periodic[n]) { + ix[n]--; wrap(ix); + A0 = value(ix); + ix = ix0; + ix[n]++; wrap(ix); + A1 = value(ix); + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (A1 - A0) / (widths[n] * 2.); + } + } else if (ix[n] > 0 && ix[n] < nx[n]-1) { // not an edge + ix[n]--; + A0 = value(ix); + ix = ix0; + ix[n]++; + A1 = value(ix); + if (A0 * A1 == 0) { + return 0.; // can't handle empty bins + } else { + return (A1 - A0) / (widths[n] * 2.); + } + } else { + // edge: use 2nd order derivative + int increment = (ix[n] == 0 ? 1 : -1); + // move right from left edge, or the other way around + A0 = value(ix); + ix[n] += increment; A1 = value(ix); + ix[n] += increment; A2 = value(ix); + return (-1.5 * A0 + 2. * A1 + - 0.5 * A2) * increment / widths[n]; + } + } + + /// \brief Return the value of the function at ix divided by its + /// number of samples (if the count grid is defined) + virtual cvm::real value_output(std::vector const &ix, + size_t const &imult = 0) const + { + if (imult > 0) { + cvm::error("Error: trying to access a component " + "larger than 1 in a scalar data grid.\n"); + return 0.; + } + if (samples) { + return (samples->value(ix) > 0) ? + (data[address(ix)] / cvm::real(samples->value(ix))) : + 0.0; + } else { + return data[address(ix)]; + } + } + + /// Enter or add value but also deal with count grid + virtual void value_input(std::vector const &ix, + cvm::real const &new_value, + size_t const &imult = 0, + bool add = false) + { + if (imult > 0) { + cvm::error("Error: trying to access a component " + "larger than 1 in a scalar data grid.\n"); + return; + } + if (add) { + if (samples) + data[address(ix)] += new_value * samples->new_count(ix); + else + data[address(ix)] += new_value; + } else { + if (samples) + data[address(ix)] = new_value * samples->value(ix); + else + data[address(ix)] = new_value; + } + has_data = true; + } + + /// \brief Return the highest value + cvm::real maximum_value() const; + + /// \brief Return the lowest value + cvm::real minimum_value() const; + + /// \brief Return the lowest positive value + cvm::real minimum_pos_value() const; + + /// \brief Calculates the integral of the map (uses widths if they are defined) + cvm::real integral() const; + + /// \brief Assuming that the map is a normalized probability density, + /// calculates the entropy (uses widths if they are defined) + cvm::real entropy() const; +}; + + + +/// Class for accumulating the gradient of a scalar function on a grid +class colvar_grid_gradient : public colvar_grid +{ +public: + + /// \brief Provide the sample count by which each binned value + /// should be divided + colvar_grid_count *samples; + + /// \brief Provide the floating point weights by which each binned value + /// should be divided (alternate to samples, only one should be non-null) + colvar_grid_scalar *weights; + + /// Default constructor + colvar_grid_gradient(); + + /// Destructor + virtual ~colvar_grid_gradient() + {} + + /// Constructor from specific sizes arrays + colvar_grid_gradient(std::vector const &nx_i); + + /// Constructor from a vector of colvars + colvar_grid_gradient(std::vector &colvars); + + /// Constructor from a multicol file + colvar_grid_gradient(std::string &filename); + + /// Write the current grid parameters to a string + std::string get_state_params() const; + + /// Read new grid parameters from a string + int parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode = colvarparse::parse_normal); + + /// Read all grid parameters and data from a formatted stream + std::istream & read_restart(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream & read_restart(cvm::memory_stream &is); + + /// Write all grid parameters and data to a formatted stream + std::ostream & write_restart(std::ostream &os); + + /// Write all grid parameters and data to an unformatted stream + cvm::memory_stream & write_restart(cvm::memory_stream &os); + + /// Read all grid parameters and data from a formatted stream + std::istream &read_raw(std::istream &is); + + /// Read all grid parameters and data from an unformatted stream + cvm::memory_stream &read_raw(cvm::memory_stream &is); + + /// Write all grid data to a formatted stream (without labels, as they are represented in memory) + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line + std::ostream &write_raw(std::ostream &os, size_t const buf_size = 3) const; + + /// Write all grid data to an unformatted stream + /// \param[in,out] os Stream object + /// \param[in] buf_size Number of values per line (note: ignored because there is no formatting) + cvm::memory_stream &write_raw(cvm::memory_stream &os, size_t const buf_size = 3) const; + + /// Read a grid written by write_multicol(), incrementin if data is true + virtual std::istream & read_multicol(std::istream &is, bool add = false); + + /// Read a grid written by write_multicol(), incrementin if data is true + virtual int read_multicol(std::string const &filename, + std::string description = "grid file", + bool add = false); + + /// Write grid in a format which is both human-readable and gnuplot-friendly + virtual std::ostream & write_multicol(std::ostream &os) const; + + /// Write grid in a format which is both human-readable and gnuplot-friendly + virtual int write_multicol(std::string const &filename, + std::string description = "grid file") const; + + /// Write the grid data without labels, as they are represented in memory + virtual std::ostream & write_opendx(std::ostream &os) const; + + /// Write the grid data without labels, as they are represented in memory + virtual int write_opendx(std::string const &filename, + std::string description = "grid file") const; + + /// \brief Get a vector with the binned value(s) indexed by ix, normalized if applicable + inline void vector_value(std::vector const &ix, std::vector &v) const + { + cvm::real const * p = &value(ix); + if (samples) { + int count = samples->value(ix); + if (count) { + cvm::real invcount = 1.0 / count; + for (size_t i = 0; i < mult; i++) { + v[i] = invcount * p[i]; + } + } else { + for (size_t i = 0; i < mult; i++) { + v[i] = 0.0; + } + } + } else { + for (size_t i = 0; i < mult; i++) { + v[i] = p[i]; + } + } + } + + /// \brief Accumulate the value + inline void acc_value(std::vector const &ix, std::vector const &values) { + for (size_t imult = 0; imult < mult; imult++) { + data[address(ix) + imult] += values[imult].real_value; + } + if (samples) + samples->incr_count(ix); + } + + /// \brief Accumulate the gradient based on the force (i.e. sums the + /// opposite of the force) + inline void acc_force(std::vector const &ix, cvm::real const *forces) { + for (size_t imult = 0; imult < mult; imult++) { + data[address(ix) + imult] -= forces[imult]; + } + if (samples) + samples->incr_count(ix); + } + + /// \brief Accumulate the gradient based on the force (i.e. sums the + /// opposite of the force) with a non-integer weight + inline void acc_force_weighted(std::vector const &ix, + cvm::real const *forces, + cvm::real weight) { + for (size_t imult = 0; imult < mult; imult++) { + data[address(ix) + imult] -= forces[imult] * weight; + } + weights->acc_value(ix, weight); + } + + /// \brief Return the value of the function at ix divided by its + /// number of samples (if the count grid is defined) + virtual cvm::real value_output(std::vector const &ix, + size_t const &imult = 0) const + { + if (samples) + return (samples->value(ix) > 0) ? + (data[address(ix) + imult] / cvm::real(samples->value(ix))) : + 0.0; + else + return data[address(ix) + imult]; + } + + /// \brief Get the value from a formatted output and transform it + /// into the internal representation (it may have been rescaled or + /// manipulated) + virtual void value_input(std::vector const &ix, + cvm::real const &new_value, + size_t const &imult = 0, + bool add = false) + { + if (add) { + if (samples) + data[address(ix) + imult] += new_value * samples->new_count(ix); + else + data[address(ix) + imult] += new_value; + } else { + if (samples) + data[address(ix) + imult] = new_value * samples->value(ix); + else + data[address(ix) + imult] = new_value; + } + has_data = true; + } + + + /// Compute and return average value for a 1D gradient grid + inline cvm::real average() + { + size_t n = 0; + + if (nd != 1 || nx[0] == 0) { + return 0.0; + } + + cvm::real sum = 0.0; + std::vector ix = new_index(); + if (samples) { + for ( ; index_ok(ix); incr(ix)) { + if ( (n = samples->value(ix)) ) + sum += value(ix) / n; + } + } else { + for ( ; index_ok(ix); incr(ix)) { + sum += value(ix); + } + } + return (sum / cvm::real(nx[0])); + } + + /// \brief If the grid is 1-dimensional, integrate it and write the + /// integral to a file + void write_1D_integral(std::ostream &os); + +}; + + + +/// Integrate (1D, 2D or 3D) gradients + +class integrate_potential : public colvar_grid_scalar +{ + public: + + integrate_potential(); + + virtual ~integrate_potential() + {} + + /// Constructor from a vector of colvars + gradient grid + integrate_potential(std::vector &colvars, colvar_grid_gradient * gradients); + + /// Constructor from a gradient grid (for processing grid files without a Colvars config) + integrate_potential(colvar_grid_gradient * gradients); + + /// \brief Calculate potential from divergence (in 2D); return number of steps + int integrate(const int itmax, const cvm::real & tol, cvm::real & err); + + /// \brief Update matrix containing divergence and boundary conditions + /// based on new gradient point value, in neighboring bins + void update_div_neighbors(const std::vector &ix); + + /// \brief Set matrix containing divergence and boundary conditions + /// based on complete gradient grid + void set_div(); + + /// \brief Add constant to potential so that its minimum value is zero + /// Useful e.g. for output + inline void set_zero_minimum() { + add_constant(-1.0 * minimum_value()); + } + + protected: + + // Reference to gradient grid + colvar_grid_gradient *gradients; + + /// Array holding divergence + boundary terms (modified Neumann) if not periodic + std::vector divergence; + +// std::vector inv_lap_diag; // Inverse of the diagonal of the Laplacian; for conditioning + + /// \brief Update matrix containing divergence and boundary conditions + /// called by update_div_neighbors + void update_div_local(const std::vector &ix); + + /// Obtain the gradient vector at given location ix, if available + /// or zero if it is on the edge of the gradient grid + /// ix gets wrapped in PBC + void get_grad(cvm::real * g, std::vector &ix); + + /// \brief Solve linear system based on CG, valid for symmetric matrices only + void nr_linbcg_sym(const std::vector &b, std::vector &x, + const cvm::real &tol, const int itmax, int &iter, cvm::real &err); + + /// l2 norm of a vector + cvm::real l2norm(const std::vector &x); + + /// Multiplication by sparse matrix representing Lagrangian (or its transpose) + void atimes(const std::vector &x, std::vector &r); + +// /// Inversion of preconditioner matrix +// void asolve(const std::vector &b, std::vector &x); +}; + +#endif + diff --git a/src/external/colvars/colvargrid_def.h b/src/external/colvars/colvargrid_def.h new file mode 100644 index 00000000000..92861f43b70 --- /dev/null +++ b/src/external/colvars/colvargrid_def.h @@ -0,0 +1,488 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +/// \file Definition of the more complex members of colvar_grid<> template + +#ifndef COLVARGRID_DEF_H +#define COLVARGRID_DEF_H + +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvar.h" +#include "colvargrid.h" +#include "colvars_memstream.h" + + +template IST &read_restart_template_(colvar_grid &g, IST &is) +{ + auto const start_pos = is.tellg(); + std::string conf; + if ((is >> colvarparse::read_block("grid_parameters", &conf)) && + (g.parse_params(conf, colvarparse::parse_restart) == COLVARS_OK) && g.read_raw(is)) { + return is; + } + auto const error_pos = is.tellg(); + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + cvm::error("Error: in reading grid state from stream at position " + cvm::to_str(error_pos) + + "\n", + COLVARS_INPUT_ERROR); + return is; +} + + +template std::istream &colvar_grid::read_restart(std::istream &is) +{ + return read_restart_template_(*this, is); +} + + +template cvm::memory_stream &colvar_grid::read_restart(cvm::memory_stream &is) +{ + return read_restart_template_(*this, is); +} + + +template std::ostream &colvar_grid::write_restart(std::ostream &os) +{ + os << "grid_parameters {\n" << get_state_params() << "}\n"; + write_raw(os); + return os; +} + + +template cvm::memory_stream &colvar_grid::write_restart(cvm::memory_stream &os) +{ + os << std::string("grid_parameters") << get_state_params(); + write_raw(os); + return os; +} + + +template IST &read_raw_template_(colvar_grid &g, IST &is) +{ + auto const start_pos = is.tellg(); + + for (std::vector ix = g.new_index(); g.index_ok(ix); g.incr(ix)) { + for (size_t imult = 0; imult < g.mult; imult++) { + T new_value; + if (is >> new_value) { + g.value_input(ix, new_value, imult); + } else { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + cvm::error( + "Error: failed to read all of the grid points from file. Possible explanations: grid " + "parameters in the configuration (lowerBoundary, upperBoundary, width) are different " + "from those in the file, or the file is corrupt/incomplete.\n", + COLVARS_INPUT_ERROR); + return is; + } + } + } + + g.has_data = true; + return is; +} + + +template std::istream &colvar_grid::read_raw(std::istream &is) +{ + return read_raw_template_(*this, is); +} + + +template cvm::memory_stream &colvar_grid::read_raw(cvm::memory_stream &is) +{ + return read_raw_template_(*this, is); +} + + +template +std::ostream &colvar_grid::write_raw(std::ostream &os, size_t const buf_size) const +{ + auto const w = os.width(); + auto const p = os.precision(); + + size_t count = 0; + for (auto ix = new_index(); index_ok(ix); incr(ix)) { + for (size_t imult = 0; imult < mult; imult++) { + os << " " << std::setw(w) << std::setprecision(p) << value_output(ix, imult); + if (((++count) % buf_size) == 0) + os << "\n"; + } + } + // write a final newline only if buffer is not empty + if ((count % buf_size) != 0) + os << "\n"; + + return os; +} + + +template +cvm::memory_stream &colvar_grid::write_raw(cvm::memory_stream &os, size_t const buf_size) const +{ + for (auto ix = new_index(); index_ok(ix); incr(ix)) { + for (size_t imult = 0; imult < mult; imult++) { + os << value_output(ix, imult); + } + } + return os; +} + + +template std::string colvar_grid::get_state_params() const +{ + std::ostringstream os; + size_t i; + os << " n_colvars " << nd << "\n"; + + os << " lower_boundaries "; + for (i = 0; i < nd; i++) + os << " " << lower_boundaries[i]; + os << "\n"; + + os << " upper_boundaries "; + for (i = 0; i < nd; i++) + os << " " << upper_boundaries[i]; + os << "\n"; + + os << " widths "; + for (i = 0; i < nd; i++) + os << " " << widths[i]; + os << "\n"; + + os << " sizes "; + for (i = 0; i < nd; i++) + os << " " << nx[i]; + os << "\n"; + + return os.str(); +} + + +template int colvar_grid::parse_params(std::string const &conf, + colvarparse::Parse_Mode const parse_mode) +{ + if (cvm::debug()) + cvm::log("Reading grid configuration from string.\n"); + + std::vector old_nx = nx; + std::vector old_lb = lower_boundaries; + std::vector old_ub = upper_boundaries; + std::vector old_w = widths; + + { + size_t nd_in = 0; + // this is only used in state files + colvarparse::get_keyval(conf, "n_colvars", nd_in, nd, colvarparse::parse_silent); + if (nd_in != nd) { + cvm::error("Error: trying to read data for a grid " + "that contains a different number of colvars ("+ + cvm::to_str(nd_in)+") than the grid defined " + "in the configuration file("+cvm::to_str(nd)+ + ").\n"); + return COLVARS_ERROR; + } + } + + // underscore keywords are used in state file + colvarparse::get_keyval(conf, "lower_boundaries", + lower_boundaries, lower_boundaries, colvarparse::parse_silent); + colvarparse::get_keyval(conf, "upper_boundaries", + upper_boundaries, upper_boundaries, colvarparse::parse_silent); + + // camel case keywords are used in config file + colvarparse::get_keyval(conf, "lowerBoundaries", + lower_boundaries, lower_boundaries, parse_mode); + colvarparse::get_keyval(conf, "upperBoundaries", + upper_boundaries, upper_boundaries, parse_mode); + + colvarparse::get_keyval(conf, "widths", widths, widths, parse_mode); + + // only used in state file + colvarparse::get_keyval(conf, "sizes", nx, nx, colvarparse::parse_silent); + + if (nd < lower_boundaries.size()) nd = lower_boundaries.size(); + + if (! use_actual_value.size()) use_actual_value.assign(nd, false); + if (! periodic.size()) periodic.assign(nd, false); + if (! widths.size()) widths.assign(nd, 1.0); + + cvm::real eps = 1.e-10; + + bool new_params = false; + if (old_nx.size()) { + for (size_t i = 0; i < nd; i++) { + if (old_nx[i] != nx[i] || + cvm::sqrt(cv[i]->dist2(old_lb[i], lower_boundaries[i])) > eps || + cvm::sqrt(cv[i]->dist2(old_ub[i], upper_boundaries[i])) > eps || + cvm::fabs(old_w[i] - widths[i]) > eps) { + new_params = true; + } + } + } else { + new_params = true; + } + + // reallocate the array in case the grid params have just changed + if (new_params) { + init_from_boundaries(); + // data.clear(); // no longer needed: setup calls clear() + return this->setup(nx, T(), mult); + } + + return COLVARS_OK; +} + + +template +std::istream & colvar_grid::read_multicol(std::istream &is, bool add) +{ + // Data in the header: nColvars, then for each + // xiMin, dXi, nPoints, periodic flag + + std::string hash; + cvm::real lower, width, x; + size_t n, periodic_flag; + bool remap; + std::vector new_value; + std::vector nx_read; + std::vector bin; + + if ( cv.size() > 0 && cv.size() != nd ) { + cvm::error("Cannot read grid file: number of variables in file differs from number referenced by grid.\n"); + return is; + } + + if ( !(is >> hash) || (hash != "#") ) { + cvm::error("Error reading grid at position "+ + cvm::to_str(static_cast(is.tellg()))+ + " in stream(read \"" + hash + "\")\n", COLVARS_INPUT_ERROR); + return is; + } + + is >> n; + if ( n != nd ) { + cvm::error("Error reading grid: wrong number of collective variables.\n"); + return is; + } + + nx_read.resize(n); + bin.resize(n); + new_value.resize(mult); + + if (this->has_parent_data && add) { + new_data.resize(data.size()); + } + + remap = false; + for (size_t i = 0; i < nd; i++ ) { + if ( !(is >> hash) || (hash != "#") ) { + cvm::error("Error reading grid at position "+ + cvm::to_str(static_cast(is.tellg()))+ + " in stream(read \"" + hash + "\")\n"); + return is; + } + + is >> lower >> width >> nx_read[i] >> periodic_flag; + + + if ( (cvm::fabs(lower - lower_boundaries[i].real_value) > 1.0e-10) || + (cvm::fabs(width - widths[i] ) > 1.0e-10) || + (nx_read[i] != nx[i]) ) { + cvm::log("Warning: reading from different grid definition (colvar " + + cvm::to_str(i+1) + "); remapping data on new grid.\n"); + remap = true; + } + } + + if ( remap ) { + // re-grid data + while (is.good()) { + bool end_of_file = false; + + for (size_t i = 0; i < nd; i++ ) { + if ( !(is >> x) ) end_of_file = true; + bin[i] = value_to_bin_scalar(x, i); + } + if (end_of_file) break; + + for (size_t imult = 0; imult < mult; imult++) { + is >> new_value[imult]; + } + + if ( index_ok(bin) ) { + for (size_t imult = 0; imult < mult; imult++) { + value_input(bin, new_value[imult], imult, add); + } + } + } + } else { + // do not re-grid the data but assume the same grid is used + for (std::vector ix = new_index(); index_ok(ix); incr(ix) ) { + for (size_t i = 0; i < nd; i++ ) { + is >> x; + } + for (size_t imult = 0; imult < mult; imult++) { + is >> new_value[imult]; + value_input(ix, new_value[imult], imult, add); + } + } + } + has_data = true; + return is; +} + + +template +int colvar_grid::read_multicol(std::string const &filename, + std::string description, + bool add) +{ + std::istream &is = cvm::main()->proxy->input_stream(filename, description); + if (!is) { + return COLVARS_FILE_ERROR; + } + if (colvar_grid::read_multicol(is, add)) { + cvm::main()->proxy->close_input_stream(filename); + return COLVARS_OK; + } + return COLVARS_FILE_ERROR; +} + + +template +std::ostream & colvar_grid::write_multicol(std::ostream &os) const +{ + // Save the output formats + std::ios_base::fmtflags prev_flags(os.flags()); + + // Data in the header: nColvars, then for each + // xiMin, dXi, nPoints, periodic + + os << std::setw(2) << "# " << nd << "\n"; + // Write the floating numbers in full precision + os.setf(std::ios::scientific, std::ios::floatfield); + for (size_t i = 0; i < nd; i++) { + os << "# " + << std::setw(cvm::cv_width) << std::setprecision(cvm::cv_prec) << lower_boundaries[i] << " " + << std::setw(cvm::cv_width) << std::setprecision(cvm::cv_prec) << widths[i] << " " + << std::setw(10) << nx[i] << " " + << periodic[i] << "\n"; + } + + for (std::vector ix = new_index(); index_ok(ix); incr(ix) ) { + + if (ix.back() == 0) { + // if the last index is 0, add a new line to mark the new record + os << "\n"; + } + + for (size_t i = 0; i < nd; i++) { + os << " " + << std::setw(cvm::cv_width) << std::setprecision(cvm::cv_prec) + << bin_to_value_scalar(ix[i], i); + } + os << " "; + for (size_t imult = 0; imult < mult; imult++) { + os << " " + << std::setw(cvm::cv_width) << std::setprecision(cvm::cv_prec) + << value_output(ix, imult); + } + os << "\n"; + } + + // Restore the output formats + os.flags(prev_flags); + + return os; +} + + +template +int colvar_grid::write_multicol(std::string const &filename, + std::string description) const +{ + int error_code = COLVARS_OK; + std::ostream &os = cvm::main()->proxy->output_stream(filename, description); + if (!os) { + return COLVARS_FILE_ERROR; + } + error_code |= colvar_grid::write_multicol(os) ? COLVARS_OK : + COLVARS_FILE_ERROR; + cvm::main()->proxy->close_output_stream(filename); + return error_code; +} + + +template +std::ostream & colvar_grid::write_opendx(std::ostream &os) const +{ + // write the header + os << "object 1 class gridpositions counts"; + size_t icv; + for (icv = 0; icv < num_variables(); icv++) { + os << " " << number_of_points(icv); + } + os << "\n"; + + os << "origin"; + for (icv = 0; icv < num_variables(); icv++) { + os << " " << (lower_boundaries[icv].real_value + 0.5 * widths[icv]); + } + os << "\n"; + + for (icv = 0; icv < num_variables(); icv++) { + os << "delta"; + for (size_t icv2 = 0; icv2 < num_variables(); icv2++) { + if (icv == icv2) os << " " << widths[icv]; + else os << " " << 0.0; + } + os << "\n"; + } + + os << "object 2 class gridconnections counts"; + for (icv = 0; icv < num_variables(); icv++) { + os << " " << number_of_points(icv); + } + os << "\n"; + + os << "object 3 class array type double rank 0 items " + << number_of_points() << " data follows\n"; + + write_raw(os); + + os << "object \"collective variables scalar field\" class field\n"; + return os; +} + + +template +int colvar_grid::write_opendx(std::string const &filename, + std::string description) const +{ + int error_code = COLVARS_OK; + std::ostream &os = cvm::main()->proxy->output_stream(filename, description); + if (!os) { + return COLVARS_FILE_ERROR; + } + error_code |= colvar_grid::write_opendx(os) ? COLVARS_OK : + COLVARS_FILE_ERROR; + cvm::main()->proxy->close_output_stream(filename); + return error_code; +} + +#endif diff --git a/src/external/colvars/colvarmodule.cpp b/src/external/colvars/colvarmodule.cpp new file mode 100644 index 00000000000..0d80edf38bd --- /dev/null +++ b/src/external/colvars/colvarmodule.cpp @@ -0,0 +1,2551 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarparse.h" +#include "colvarproxy.h" +#include "colvar.h" +#include "colvarbias.h" +#include "colvarbias_abf.h" +#include "colvarbias_alb.h" +#include "colvarbias_histogram.h" +#include "colvarbias_histogram_reweight_amd.h" +#include "colvarbias_meta.h" +#include "colvarbias_restraint.h" +#include "colvarscript.h" +#include "colvaratoms.h" +#include "colvarcomp.h" +#include "colvars_memstream.h" + + +/// Track usage of Colvars features +class colvarmodule::usage { + +public: + + /// Constructor + usage(); + + /// Increment usage count for the given feature; return error if not found + int cite_feature(std::string const &feature); + + /// Increment usage count for the given paper; return error if not found + int cite_paper(std::string const &paper); + + /// Generate a report for used features (0 = URL, 1 = BibTeX) + std::string report(int flag); + +protected: + + /// Usage count for each feature + std::map feature_count_; + + /// Usage count for each cited paper + std::map paper_count_; + + /// URL for each paper + std::map paper_url_; + + /// BibTeX entry for each paper + std::map paper_bibtex_; + + /// Map code features to the relevant papers + std::map feature_paper_map_; + +}; + + +namespace { + constexpr uint32_t colvars_magic_number = 2013813594; +} + + +colvarmodule::colvarmodule(colvarproxy *proxy_in) +{ + depth_s = 0; + log_level_ = 10; + + xyz_reader_use_count = 0; + + num_biases_types_used_ = + reinterpret_cast(new std::map()); + + restart_version_str.clear(); + restart_version_int = 0; + + usage_ = new usage(); + usage_->cite_feature("Colvars module"); + + if (proxy != NULL) { + // TODO relax this error to handle multiple molecules in VMD + // once the module is not static anymore + cvm::error("Error: trying to allocate the collective " + "variable module twice.\n", COLVARS_BUG_ERROR); + return; + } + + proxy = proxy_in; // Pointer to the proxy object + parse = new colvarparse(); // Parsing object for global options + version_int = proxy->get_version_from_string(COLVARS_VERSION); + + cvm::log(cvm::line_marker); + cvm::log("Initializing the collective variables module, version "+ + version()+".\n"); + cvm::log("Please cite Fiorin et al, Mol Phys 2013:\n" + " https://doi.org/10.1080/00268976.2013.813594\n" + "as well as all other papers listed below for individual features used.\n"); + + if (proxy->smp_enabled() == COLVARS_OK) { + cvm::log("SMP parallelism is enabled; if needed, use \"smp off\" to override this.\n"); + } + +#if (__cplusplus >= 201103L) + cvm::log("This version was built with the C++11 standard or higher.\n"); +#else + cvm::log("This version was built without the C++11 standard: some features are disabled.\n" + "Please see the following link for details:\n" + " https://colvars.github.io/README-c++11.html\n"); +#endif + + // set initial default values + + binary_restart = false; + char const *env_var = getenv("COLVARS_BINARY_RESTART"); + if (env_var && atoi(env_var)) { + binary_restart = true; + } + + // "it_restart" will be set by the input state file, if any; + // "it" should be updated by the proxy + colvarmodule::it = colvarmodule::it_restart = 0; + + use_scripted_forces = false; + scripting_after_biases = false; + + colvarmodule::debug_gradients_step_size = 1.0e-07; + + colvarmodule::rotation::monitor_crossings = false; + colvarmodule::rotation::crossing_threshold = 1.0e-02; + + cv_traj_freq = 100; + restart_out_freq = proxy->default_restart_frequency(); + + cv_traj_write_labels = true; + + // Removes the need for proxy specializations to create this + proxy->script = new colvarscript(proxy, this); +} + + +colvarmodule * colvarmodule::main() +{ + return proxy ? proxy->colvars : NULL; +} + + +std::vector *colvarmodule::variables() +{ + return &colvars; +} + + +std::vector *colvarmodule::variables_active() +{ + return &colvars_active; +} + + +std::vector *colvarmodule::variables_active_smp() +{ + return &colvars_smp; +} + + +std::vector *colvarmodule::variables_active_smp_items() +{ + return &colvars_smp_items; +} + + +std::vector *colvarmodule::biases_active() +{ + return &(biases_active_); +} + + +size_t colvarmodule::size() const +{ + return colvars.size() + biases.size(); +} + + +void colvarmodule::set_initial_step(step_number it_in) +{ + cvm::log("Setting initial step number from MD engine: " + cvm::to_str(it_in) + "\n"); + it = it_restart = it_in; +} + + +int colvarmodule::read_config_file(char const *config_filename) +{ + cvm::log(cvm::line_marker); + cvm::log("Reading new configuration from file \""+ + std::string(config_filename)+"\":\n"); + + // open the configfile + std::istream &config_s = proxy->input_stream(config_filename, + "configuration file/string"); + if (!config_s) { + return cvm::error("Error: in opening configuration file \""+ + std::string(config_filename)+"\".\n", + COLVARS_FILE_ERROR); + } + + // read the config file into a string + std::string conf = ""; + std::string line; + while (parse->read_config_line(config_s, line)) { + // Delete lines that contain only white space after removing comments + if (line.find_first_not_of(colvarparse::white_space) != std::string::npos) + conf.append(line+"\n"); + } + proxy->close_input_stream(config_filename); + + return parse_config(conf); +} + + +int colvarmodule::read_config_string(std::string const &config_str) +{ + cvm::log(cvm::line_marker); + cvm::log("Reading new configuration:\n"); + std::istringstream new_config_s(config_str); + + // strip the comments away + std::string conf = ""; + std::string line; + while (parse->read_config_line(new_config_s, line)) { + // Delete lines that contain only white space after removing comments + if (line.find_first_not_of(colvarparse::white_space) != std::string::npos) + conf.append(line+"\n"); + } + + return parse_config(conf); +} + + +std::istream & colvarmodule::getline(std::istream &is, std::string &line) +{ + std::string l; + if (std::getline(is, l)) { + size_t const sz = l.size(); + if (sz > 0) { + if (l[sz-1] == '\r' ) { + // Replace Windows newlines with Unix newlines + line = l.substr(0, sz-1); + } else { + line = l; + } + } else { + line.clear(); + } + } + return is; +} + + +int colvarmodule::parse_config(std::string &conf) +{ + // Auto-generated additional configuration + extra_conf.clear(); + + // Check that the input has matching braces + if (colvarparse::check_braces(conf, 0) != COLVARS_OK) { + return cvm::error("Error: unmatched curly braces in configuration.\n", + COLVARS_INPUT_ERROR); + } + + // Check that the input has only ASCII characters, and warn otherwise + colvarparse::check_ascii(conf); + + // Parse global options + if (catch_input_errors(parse_global_params(conf))) { + return get_error(); + } + + // Parse the options for collective variables + if (catch_input_errors(parse_colvars(conf))) { + return get_error(); + } + + // Parse the options for biases + if (catch_input_errors(parse_biases(conf))) { + return get_error(); + } + + // Done parsing known keywords, check that all keywords found were valid ones + if (catch_input_errors(parse->check_keywords(conf, "colvarmodule"))) { + return get_error(); + } + + // Parse auto-generated configuration (e.g. for back-compatibility) + if (extra_conf.size()) { + catch_input_errors(parse_global_params(extra_conf)); + catch_input_errors(parse_colvars(extra_conf)); + catch_input_errors(parse_biases(extra_conf)); + parse->check_keywords(extra_conf, "colvarmodule"); + extra_conf.clear(); + if (get_error() != COLVARS_OK) return get_error(); + } + + cvm::log(cvm::line_marker); + cvm::log("Collective variables module (re)initialized.\n"); + cvm::log(cvm::line_marker); + + if (source_Tcl_script.size() > 0) { + run_tcl_script(source_Tcl_script); + } + + return get_error(); +} + + +std::string const & colvarmodule::get_config() const +{ + return parse->get_config(); +} + + +int colvarmodule::append_new_config(std::string const &new_conf) +{ + extra_conf += new_conf; + return COLVARS_OK; +} + + +void colvarmodule::config_changed() +{ + cv_traj_write_labels = true; +} + + +int colvarmodule::parse_global_params(std::string const &conf) +{ + int error_code = COLVARS_OK; + // TODO document and then echo this keyword + parse->get_keyval(conf, "logLevel", log_level_, log_level_, + colvarparse::parse_silent); + { + std::string units; + if (parse->get_keyval(conf, "units", units)) { + units = colvarparse::to_lower_cppstr(units); + error_code |= proxy->set_unit_system(units, (colvars.size() != 0)); + } + } + + { + std::string index_file_name; + size_t pos = 0; + while (parse->key_lookup(conf, "indexFile", &index_file_name, &pos)) { + cvm::log("# indexFile = \""+index_file_name+"\"\n"); + error_code |= read_index_file(index_file_name.c_str()); + index_file_name.clear(); + } + } + + if (parse->get_keyval(conf, "smp", proxy->b_smp_active, proxy->b_smp_active)) { + if (proxy->b_smp_active == false) { + cvm::log("SMP parallelism has been disabled.\n"); + } + } + + bool b_analysis = true; + if (parse->get_keyval(conf, "analysis", b_analysis, true, colvarparse::parse_silent)) { + cvm::log("Warning: keyword \"analysis\" is deprecated: it is now always set " + "to true; individual analyses are performed only if requested."); + } + + parse->get_keyval(conf, "debugGradientsStepSize", debug_gradients_step_size, + debug_gradients_step_size, + colvarparse::parse_silent); + + parse->get_keyval(conf, "monitorEigenvalueCrossing", + colvarmodule::rotation::monitor_crossings, + colvarmodule::rotation::monitor_crossings, + colvarparse::parse_silent); + parse->get_keyval(conf, "eigenvalueCrossingThreshold", + colvarmodule::rotation::crossing_threshold, + colvarmodule::rotation::crossing_threshold, + colvarparse::parse_silent); + + parse->get_keyval(conf, "colvarsTrajFrequency", cv_traj_freq, cv_traj_freq); + parse->get_keyval(conf, "colvarsRestartFrequency", + restart_out_freq, restart_out_freq); + + parse->get_keyval(conf, "scriptedColvarForces", + use_scripted_forces, use_scripted_forces); + + parse->get_keyval(conf, "scriptingAfterBiases", + scripting_after_biases, scripting_after_biases); + +#if defined(COLVARS_TCL) + parse->get_keyval(conf, "sourceTclFile", source_Tcl_script); +#endif + + return error_code; +} + + +int colvarmodule::run_tcl_script(std::string const &filename) { + + int result = COLVARS_OK; + +#if defined(COLVARS_TCL) + result = proxy->tcl_run_file(filename); +#endif + + return result; +} + + +int colvarmodule::parse_colvars(std::string const &conf) +{ + if (cvm::debug()) + cvm::log("Initializing the collective variables.\n"); + + std::string colvar_conf = ""; + size_t pos = 0; + while (parse->key_lookup(conf, "colvar", &colvar_conf, &pos)) { + + if (colvar_conf.size()) { + cvm::log(cvm::line_marker); + cvm::increase_depth(); + colvars.push_back(new colvar()); + if (((colvars.back())->init(colvar_conf) != COLVARS_OK) || + ((colvars.back())->check_keywords(colvar_conf, "colvar") != COLVARS_OK)) { + cvm::log("Error while constructing colvar number " + + cvm::to_str(colvars.size()) + " : deleting."); + delete colvars.back(); // the colvar destructor updates the colvars array + cvm::decrease_depth(); + return COLVARS_ERROR; + } + cvm::decrease_depth(); + } else { + cvm::error("Error: \"colvar\" keyword found without any configuration.\n", COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + cvm::decrease_depth(); + colvar_conf = ""; + } + + if (pos > 0) { + // One or more new variables were added + config_changed(); + } + + if (!colvars.size()) { + cvm::log("Warning: no collective variables defined.\n"); + } + + if (colvars.size()) + cvm::log(cvm::line_marker); + cvm::log("Collective variables initialized, "+ + cvm::to_str(colvars.size())+ + " in total.\n"); + + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +bool colvarmodule::check_new_bias(std::string &conf, char const *key) +{ + if (cvm::get_error() || + (biases.back()->check_keywords(conf, key) != COLVARS_OK)) { + cvm::log("Error while constructing bias number " + + cvm::to_str(biases.size()) + " : deleting.\n"); + delete biases.back(); // the bias destructor updates the biases array + return true; + } + return false; +} + + +template +int colvarmodule::parse_biases_type(std::string const &conf, + char const *keyword) +{ + // Allow camel case when calling, but use only lower case for parsing + std::string const &type_keyword = colvarparse::to_lower_cppstr(keyword); + + // Check how many times this bias keyword was used, set default name + // accordingly + std::map *num_biases_types_used = + reinterpret_cast *>(num_biases_types_used_); + if (num_biases_types_used->count(type_keyword) == 0) { + (*num_biases_types_used)[type_keyword] = 0; + } + + std::string bias_conf = ""; + size_t conf_saved_pos = 0; + while (parse->key_lookup(conf, keyword, &bias_conf, &conf_saved_pos)) { + if (bias_conf.size()) { + cvm::log(cvm::line_marker); + cvm::increase_depth(); + int &bias_count = (*num_biases_types_used)[type_keyword]; + biases.push_back(new bias_type(type_keyword.c_str())); + bias_count += 1; + biases.back()->rank = bias_count; + biases.back()->init(bias_conf); + if (cvm::check_new_bias(bias_conf, keyword) != COLVARS_OK) { + return COLVARS_ERROR; + } + cvm::decrease_depth(); + } else { + cvm::error("Error: keyword \""+std::string(keyword)+"\" found without configuration.\n", + COLVARS_INPUT_ERROR); + return COLVARS_ERROR; + } + bias_conf = ""; + } + if (conf_saved_pos > 0) { + // One or more new biases were added + config_changed(); + } + return COLVARS_OK; +} + + +int colvarmodule::parse_biases(std::string const &conf) +{ + if (cvm::debug()) + cvm::log("Initializing the collective variables biases.\n"); + + /// initialize ABF instances + parse_biases_type(conf, "abf"); + + /// initialize adaptive linear biases + parse_biases_type(conf, "ALB"); + + /// initialize harmonic restraints + parse_biases_type(conf, "harmonic"); + + /// initialize harmonic walls restraints + parse_biases_type(conf, "harmonicWalls"); + + /// initialize histograms + parse_biases_type(conf, "histogram"); + + /// initialize histogram restraints + parse_biases_type(conf, "histogramRestraint"); + + /// initialize linear restraints + parse_biases_type(conf, "linear"); + + /// initialize metadynamics instances + parse_biases_type(conf, "metadynamics"); + + /// initialize reweightaMD instances + parse_biases_type(conf, "reweightaMD"); + + if (use_scripted_forces) { + cvm::log(cvm::line_marker); + cvm::increase_depth(); + cvm::log("User forces script will be run at each bias update.\n"); + cvm::decrease_depth(); + } + + std::vector const time_biases = time_dependent_biases(); + if (time_biases.size() > 1) { + cvm::log("WARNING: there are "+cvm::to_str(time_biases.size())+ + " time-dependent biases with non-zero force parameters:\n"+ + cvm::to_str(time_biases)+"\n"+ + "Please ensure that their forces do not counteract each other.\n"); + } + + if (num_biases() || use_scripted_forces) { + cvm::log(cvm::line_marker); + cvm::log("Collective variables biases initialized, "+ + cvm::to_str(num_biases())+" in total.\n"); + } else { + if (!use_scripted_forces) { + cvm::log("No collective variables biases were defined.\n"); + } + } + + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +size_t colvarmodule::num_variables() const +{ + return colvars.size(); +} + + +size_t colvarmodule::num_variables_feature(int feature_id) const +{ + size_t n = 0; + for (std::vector::const_iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + if ((*cvi)->is_enabled(feature_id)) { + n++; + } + } + return n; +} + + +size_t colvarmodule::num_biases() const +{ + return biases.size(); +} + + +size_t colvarmodule::num_biases_feature(int feature_id) const +{ + size_t n = 0; + for (std::vector::const_iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if ((*bi)->is_enabled(feature_id)) { + n++; + } + } + return n; +} + + +size_t colvarmodule::num_biases_type(std::string const &type) const +{ + size_t n = 0; + for (std::vector::const_iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if ((*bi)->bias_type == type) { + n++; + } + } + return n; +} + + +std::vector const colvarmodule::time_dependent_biases() const +{ + size_t i; + std::vector biases_names; + for (i = 0; i < num_biases(); i++) { + if (biases[i]->is_enabled(colvardeps::f_cvb_apply_force) && + biases[i]->is_enabled(colvardeps::f_cvb_active) && + (biases[i]->is_enabled(colvardeps::f_cvb_history_dependent) || + biases[i]->is_enabled(colvardeps::f_cvb_time_dependent))) { + biases_names.push_back(biases[i]->name); + } + } + return biases_names; +} + + +int colvarmodule::catch_input_errors(int result) +{ + if (result != COLVARS_OK || get_error()) { + set_error_bits(result); + set_error_bits(COLVARS_INPUT_ERROR); + parse->clear(); + return get_error(); + } + return COLVARS_OK; +} + + +colvarbias * colvarmodule::bias_by_name(std::string const &name) +{ + colvarmodule *cv = cvm::main(); + for (std::vector::iterator bi = cv->biases.begin(); + bi != cv->biases.end(); + bi++) { + if ((*bi)->name == name) { + return (*bi); + } + } + return NULL; +} + + +colvar *colvarmodule::colvar_by_name(std::string const &name) +{ + colvarmodule *cv = cvm::main(); + for (std::vector::iterator cvi = cv->colvars.begin(); + cvi != cv->colvars.end(); + cvi++) { + if ((*cvi)->name == name) { + return (*cvi); + } + } + return NULL; +} + + +cvm::atom_group *colvarmodule::atom_group_by_name(std::string const &name) +{ + colvarmodule *cv = cvm::main(); + for (std::vector::iterator agi = cv->named_atom_groups.begin(); + agi != cv->named_atom_groups.end(); + agi++) { + if ((*agi)->name == name) { + return (*agi); + } + } + return NULL; +} + + +void colvarmodule::register_named_atom_group(atom_group *ag) { + named_atom_groups.push_back(ag); +} + + +void colvarmodule::unregister_named_atom_group(cvm::atom_group *ag) +{ + for (std::vector::iterator agi = named_atom_groups.begin(); + agi != named_atom_groups.end(); + agi++) { + if (*agi == ag) { + named_atom_groups.erase(agi); + break; + } + } +} + + +int colvarmodule::change_configuration(std::string const &bias_name, + std::string const &conf) +{ + // This is deprecated; supported strategy is to delete the bias + // and parse the new config + cvm::increase_depth(); + colvarbias *b; + b = bias_by_name(bias_name); + if (b == NULL) { + cvm::error("Error: bias not found: " + bias_name); + return COLVARS_ERROR; + } + b->change_configuration(conf); + cvm::decrease_depth(); + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +std::string colvarmodule::read_colvar(std::string const &name) +{ + cvm::increase_depth(); + colvar *c; + std::stringstream ss; + c = colvar_by_name(name); + if (c == NULL) { + cvm::error("Error: colvar not found: " + name); + return std::string(); + } + ss << c->value(); + cvm::decrease_depth(); + return ss.str(); +} + + +cvm::real colvarmodule::energy_difference(std::string const &bias_name, + std::string const &conf) +{ + cvm::increase_depth(); + colvarbias *b; + cvm::real energy_diff = 0.; + b = bias_by_name(bias_name); + if (b == NULL) { + cvm::error("Error: bias not found: " + bias_name); + return 0.; + } + energy_diff = b->energy_difference(conf); + cvm::decrease_depth(); + return energy_diff; +} + + +int colvarmodule::calc() +{ + int error_code = COLVARS_OK; + + if (cvm::debug()) { + cvm::log(cvm::line_marker); + cvm::log("Collective variables module, step no. "+ + cvm::to_str(cvm::step_absolute())+"\n"); + } + + error_code |= calc_colvars(); + error_code |= calc_biases(); + error_code |= update_colvar_forces(); + + error_code |= analyze(); + + // write trajectory files, if needed + if (cv_traj_freq && cv_traj_name.size()) { + error_code |= write_traj_files(); + } + + // write restart files and similar data + if (restart_out_freq && (cvm::step_relative() > 0) && + ((cvm::step_absolute() % restart_out_freq) == 0)) { + + if (restart_out_name.size()) { + // Write restart file, if different from main output + error_code |= write_restart_file(restart_out_name); + } else if (output_prefix().size()) { + error_code |= write_restart_file(output_prefix() + ".colvars.state"); + } + + if (output_prefix().size()) { + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { + // TODO remove this when corrFunc becomes a bias + error_code |= (*cvi)->write_output_files(); + } + for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { + error_code |= (*bi)->write_state_to_replicas(); + } + cvm::decrease_depth(); + } + } + + // Write output files for biases, at the specified frequency for each + cvm::increase_depth(); + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if ((*bi)->output_freq > 0) { + if ((cvm::step_relative() > 0) && + ((cvm::step_absolute() % (*bi)->output_freq) == 0) ) { + error_code |= (*bi)->write_output_files(); + } + } + } + cvm::decrease_depth(); + + error_code |= end_of_step(); + + // TODO move this to a base-class proxy method that calls this function + error_code |= proxy->end_of_step(); + + return error_code; +} + + +int colvarmodule::calc_colvars() +{ + if (cvm::debug()) + cvm::log("Calculating collective variables.\n"); + // calculate collective variables and their gradients + + // First, we need to decide which biases are awake + // so they can activate colvars as needed + std::vector::iterator bi; + for (bi = biases.begin(); bi != biases.end(); bi++) { + int const tsf = (*bi)->get_time_step_factor(); + if (tsf > 1) { + if (step_absolute() % tsf == 0) { + (*bi)->enable(colvardeps::f_cvb_awake); + } else { + (*bi)->disable(colvardeps::f_cvb_awake); + } + } + } + + int error_code = COLVARS_OK; + std::vector::iterator cvi; + + // Determine which colvars are active at this iteration + variables_active()->clear(); + variables_active()->reserve(variables()->size()); + for (cvi = variables()->begin(); cvi != variables()->end(); cvi++) { + // Wake up or put to sleep variables with MTS + int tsf = (*cvi)->get_time_step_factor(); + if (tsf > 1) { + if (step_absolute() % tsf == 0) { + (*cvi)->enable(colvardeps::f_cv_awake); + } else { + (*cvi)->disable(colvardeps::f_cv_awake); + } + } + + if ((*cvi)->is_enabled()) { + variables_active()->push_back(*cvi); + } + } + + // if SMP support is available, split up the work + if (proxy->smp_enabled() == COLVARS_OK) { + + // first, calculate how much work (currently, how many active CVCs) each colvar has + + variables_active_smp()->clear(); + variables_active_smp_items()->clear(); + + variables_active_smp()->reserve(variables_active()->size()); + variables_active_smp_items()->reserve(variables_active()->size()); + + // set up a vector containing all components + cvm::increase_depth(); + for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { + + error_code |= (*cvi)->update_cvc_flags(); + + size_t num_items = (*cvi)->num_active_cvcs(); + variables_active_smp()->reserve(variables_active_smp()->size() + num_items); + variables_active_smp_items()->reserve(variables_active_smp_items()->size() + num_items); + for (size_t icvc = 0; icvc < num_items; icvc++) { + variables_active_smp()->push_back(*cvi); + variables_active_smp_items()->push_back(icvc); + } + } + cvm::decrease_depth(); + + // calculate colvar components in parallel + error_code |= proxy->smp_colvars_loop(); + + cvm::increase_depth(); + for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { + error_code |= (*cvi)->collect_cvc_data(); + } + cvm::decrease_depth(); + + } else { + + // calculate colvars one at a time + cvm::increase_depth(); + for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { + error_code |= (*cvi)->calc(); + if (cvm::get_error()) { + return COLVARS_ERROR; + } + } + cvm::decrease_depth(); + } + + error_code |= cvm::get_error(); + return error_code; +} + + +int colvarmodule::calc_biases() +{ + // update the biases and communicate their forces to the collective + // variables + if (cvm::debug() && num_biases()) + cvm::log("Updating collective variable biases.\n"); + + // set biasing forces to zero before biases are calculated and summed over + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); cvi++) { + (*cvi)->reset_bias_force(); + } + + std::vector::iterator bi; + int error_code = COLVARS_OK; + + // Total bias energy is reset before calling scripted biases + total_bias_energy = 0.0; + + // update the list of active biases + // which may have changed based on f_cvb_awake in calc_colvars() + biases_active()->clear(); + biases_active()->reserve(biases.size()); + for (bi = biases.begin(); bi != biases.end(); bi++) { + if ((*bi)->is_enabled()) { + biases_active()->push_back(*bi); + } + } + + // if SMP support is available, split up the work + if (proxy->smp_enabled() == COLVARS_OK) { + + if (use_scripted_forces && !scripting_after_biases) { + // calculate biases and scripted forces in parallel + error_code |= proxy->smp_biases_script_loop(); + } else { + // calculate biases in parallel + error_code |= proxy->smp_biases_loop(); + } + + } else { + + if (use_scripted_forces && !scripting_after_biases) { + error_code |= calc_scripted_forces(); + } + + cvm::increase_depth(); + for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { + error_code |= (*bi)->update(); + if (cvm::get_error()) { + return error_code; + } + } + cvm::decrease_depth(); + } + + for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { + total_bias_energy += (*bi)->get_energy(); + } + + return error_code; +} + + +int colvarmodule::update_colvar_forces() +{ + int error_code = COLVARS_OK; + + std::vector::iterator cvi; + std::vector::iterator bi; + + // sum the forces from all biases for each collective variable + if (cvm::debug() && num_biases()) + cvm::log("Collecting forces from all biases.\n"); + cvm::increase_depth(); + for (bi = biases_active()->begin(); bi != biases_active()->end(); bi++) { + error_code |= (*bi)->communicate_forces(); + } + cvm::decrease_depth(); + + if (use_scripted_forces && scripting_after_biases) { + error_code |= calc_scripted_forces(); + } + + // Now we have collected energies from both built-in and scripted biases + if (cvm::debug()) + cvm::log("Adding total bias energy: " + cvm::to_str(total_bias_energy) + "\n"); + proxy->add_energy(total_bias_energy); + + cvm::real total_colvar_energy = 0.0; + // sum up the forces for each colvar, including wall forces + // and integrate any internal + // equation of motion (extended system) + if (cvm::debug()) + cvm::log("Updating the internal degrees of freedom " + "of colvars (if they have any).\n"); + cvm::increase_depth(); + for (cvi = variables()->begin(); cvi != variables()->end(); cvi++) { + // Inactive colvars will only reset their forces and return 0 energy + total_colvar_energy += (*cvi)->update_forces_energy(); + } + cvm::decrease_depth(); + if (cvm::debug()) + cvm::log("Adding total colvar energy: " + cvm::to_str(total_colvar_energy) + "\n"); + proxy->add_energy(total_colvar_energy); + + // make collective variables communicate their forces to their + // coupled degrees of freedom (i.e. atoms) + if (cvm::debug()) + cvm::log("Communicating forces from the colvars to the atoms.\n"); + cvm::increase_depth(); + for (cvi = variables_active()->begin(); cvi != variables_active()->end(); cvi++) { + if ((*cvi)->is_enabled(colvardeps::f_cv_gradient)) { + (*cvi)->communicate_forces(); + if (cvm::get_error()) { + return COLVARS_ERROR; + } + } + } + cvm::decrease_depth(); + + return error_code; +} + + +int colvarmodule::calc_scripted_forces() +{ + // Run user force script, if provided, + // potentially adding scripted forces to the colvars + int res; + res = proxy->run_force_callback(); + if (res == COLVARS_NOT_IMPLEMENTED) { + cvm::error("Colvar forces scripts are not implemented."); + return COLVARS_NOT_IMPLEMENTED; + } + if (res != COLVARS_OK) { + cvm::error("Error running user colvar forces script"); + return COLVARS_ERROR; + } + return COLVARS_OK; +} + + +int colvarmodule::write_restart_file(std::string const &out_name) +{ + cvm::log("Saving collective variables state to \""+out_name+"\".\n"); + std::ostream &restart_out_os = proxy->output_stream(out_name, "state file"); + if (!restart_out_os) return COLVARS_FILE_ERROR; + + if (binary_restart) { + cvm::memory_stream mem_os; + if (!write_state(mem_os)) { + return cvm::error("Error: in writing binary state information to file.\n", COLVARS_ERROR); + } + if (!restart_out_os.write(reinterpret_cast(mem_os.output_buffer()), + mem_os.length())) { + return cvm::error("Error: in writing restart file.\n", COLVARS_FILE_ERROR); + } + } else { + if (!write_state(restart_out_os)) { + return cvm::error("Error: in writing restart file.\n", COLVARS_FILE_ERROR); + } + } + + proxy->close_output_stream(out_name); + + // Take the opportunity to flush colvars.traj + + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +int colvarmodule::write_restart_string(std::string &output) +{ + cvm::log("Saving state to output buffer.\n"); + std::ostringstream os; + if (!write_state(os)) { + return cvm::error("Error: in writing restart to buffer.\n", COLVARS_FILE_ERROR); + } + output = os.str(); + return COLVARS_OK; +} + + +int colvarmodule::write_traj_files() +{ + int error_code = COLVARS_OK; + + if (cvm::debug()) { + cvm::log("colvarmodule::write_traj_files()\n"); + } + + std::ostream &cv_traj_os = proxy->output_stream(cv_traj_name, + "colvars trajectory"); + + if (!cv_traj_os) { + return COLVARS_FILE_ERROR; + } + + // Write labels in the traj file at beginning and then every 1000 lines + if ( (cvm::step_relative() == 0) || cv_traj_write_labels || + ((cvm::step_absolute() % (cv_traj_freq * 1000)) == 0) ) { + error_code |= + write_traj_label(cv_traj_os) ? COLVARS_OK : COLVARS_FILE_ERROR; + cv_traj_write_labels = false; + } + + if (cvm::debug()) { + proxy->flush_output_stream(cv_traj_name); + } + + if ((cvm::step_absolute() % cv_traj_freq) == 0) { + error_code |= write_traj(cv_traj_os) ? COLVARS_OK : COLVARS_FILE_ERROR; + } + + if (cvm::debug()) { + proxy->flush_output_stream(cv_traj_name); + } + + if (restart_out_freq && ((cvm::step_absolute() % restart_out_freq) == 0)) { + cvm::log("Synchronizing (emptying the buffer of) trajectory file \""+ + cv_traj_name+"\".\n"); + error_code |= proxy->flush_output_stream(cv_traj_name); + } + + return error_code; +} + + +int colvarmodule::analyze() +{ + if (cvm::debug()) { + cvm::log("colvarmodule::analyze(), step = "+cvm::to_str(it)+".\n"); + } + + // perform colvar-specific analysis + for (std::vector::iterator cvi = variables_active()->begin(); + cvi != variables_active()->end(); + cvi++) { + cvm::increase_depth(); + (*cvi)->analyze(); + cvm::decrease_depth(); + } + + // perform bias-specific analysis + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + cvm::increase_depth(); + (*bi)->analyze(); + cvm::decrease_depth(); + } + + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +int colvarmodule::end_of_step() +{ + if (cvm::debug()) { + cvm::log("colvarmodule::end_of_step(), step = "+cvm::to_str(it)+".\n"); + } + + for (std::vector::iterator cvi = variables_active()->begin(); + cvi != variables_active()->end(); + cvi++) { + cvm::increase_depth(); + (*cvi)->end_of_step(); + cvm::decrease_depth(); + } + + // perform bias-specific analysis + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + cvm::increase_depth(); + (*bi)->end_of_step(); + cvm::decrease_depth(); + } + + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +int colvarmodule::update_engine_parameters() +{ + if (size() == 0) { + // No-op if no variables or biases are defined + return cvm::get_error(); + } + if (proxy->simulation_running()) { + cvm::log("Current simulation parameters: initial step = " + cvm::to_str(it) + + ", integration timestep = " + cvm::to_str(dt()) + "\n"); + } + cvm::log("Updating atomic parameters (masses, charges, etc).\n"); + for (std::vector::iterator cvi = variables()->begin(); cvi != variables()->end(); + cvi++) { + (*cvi)->setup(); + } + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +colvarmodule::~colvarmodule() +{ + if ((proxy->smp_thread_id() < 0) || // not using threads + (proxy->smp_thread_id() == 0)) { // or this is thread 0 + + reset(); + + // Delete contents of static arrays + colvarbias::delete_features(); + colvar::delete_features(); + colvar::cvc::delete_features(); + atom_group::delete_features(); + + delete + reinterpret_cast *>(num_biases_types_used_); + num_biases_types_used_ = NULL; + + delete parse; + parse = NULL; + + delete usage_; + usage_ = NULL; + + // The proxy object will be deallocated last (if at all) + proxy = NULL; + } +} + + +int colvarmodule::reset() +{ + parse->clear(); + + // Iterate backwards because we are deleting the elements as we go + for (std::vector::reverse_iterator bi = biases.rbegin(); + bi != biases.rend(); + bi++) { + delete *bi; // the bias destructor updates the biases array + } + biases.clear(); + biases_active_.clear(); + + // Reset counters tracking usage of each bias type + reinterpret_cast *>(num_biases_types_used_)->clear(); + + // Iterate backwards because we are deleting the elements as we go + for (std::vector::reverse_iterator cvi = colvars.rbegin(); + cvi != colvars.rend(); + cvi++) { + delete *cvi; // the colvar destructor updates the colvars array + } + colvars.clear(); + + reset_index_groups(); + + proxy->flush_output_streams(); + proxy->reset(); + + clear_error(); + + return COLVARS_OK; +} + + +int colvarmodule::setup_input() +{ + if (proxy->input_prefix().size()) { + + // Read state from a file + + std::string restart_in_name(proxy->input_prefix() + std::string(".colvars.state")); + std::istream *input_is = &(proxy->input_stream(restart_in_name, "restart file/channel", false)); + if (!*input_is) { + // Try without the suffix ".colvars.state" + restart_in_name = proxy->input_prefix(); + input_is = &(proxy->input_stream(restart_in_name, "restart file/channel")); + if (!*input_is) { + // Error message has already been printed, return now + return COLVARS_FILE_ERROR; + } + } + + // Now that the file has been opened, clear this field so that this block + // will not be executed twice + proxy->set_input_prefix(""); + + cvm::log(cvm::line_marker); + + input_is->seekg(0, std::ios::end); + size_t const file_size = input_is->tellg(); + input_is->seekg(0, std::ios::beg); + + bool binary_state_file = false; + + uint32_t file_magic_number = 0; + if (file_size > sizeof(uint32_t)) { + if (input_is->read(reinterpret_cast(&file_magic_number), sizeof(uint32_t))) { + if (file_magic_number == colvars_magic_number) { + binary_state_file = true; + } + input_is->seekg(0, std::ios::beg); + } + } + + if (binary_state_file) { + cvm::log("Loading state from binary file \"" + restart_in_name + "\".\n"); + // TODO integrate istream.read() into memory_stream to avoid copying + auto *buf = new unsigned char[file_size]; + if (input_is->read(reinterpret_cast(buf), file_size)) { + cvm::memory_stream mem_is(file_size, buf); + if (!read_state(mem_is)) { + input_is->setstate(std::ios::failbit); + cvm::error("Error: cannot interpret contents of binary file \"" + restart_in_name + + "\".\n", + COLVARS_INPUT_ERROR); + } + } else { + cvm::error("Error: cannot read from binary file \"" + restart_in_name + "\".\n", + COLVARS_INPUT_ERROR); + } + delete[] buf; + } else { + cvm::log("Loading state from text file \"" + restart_in_name + "\".\n"); + read_state(*input_is); + } + cvm::log(cvm::line_marker); + + // Now that the explicit input file was read, we shall ignore any unformatted buffer + input_state_buffer_.clear(); + + proxy->delete_input_stream(restart_in_name); + } + + if (proxy->input_stream_exists("input state string")) { + + cvm::log(cvm::line_marker); + cvm::log("Loading state from formatted string.\n"); + read_state(proxy->input_stream("input state string")); + cvm::log(cvm::line_marker); + + proxy->delete_input_stream("input state string"); + } + + if (input_state_buffer_.size() > 0) { + cvm::log(cvm::line_marker); + cvm::log("Loading state from unformatted memory.\n"); + cvm::memory_stream ms(input_state_buffer_.size(), input_state_buffer_.data()); + read_state(ms); + cvm::log(cvm::line_marker); + + input_state_buffer_.clear(); + } + + return cvm::get_error(); +} + + +int colvarmodule::setup_output() +{ + int error_code = COLVARS_OK; + + // output state file (restart) + restart_out_name = proxy->restart_output_prefix().size() ? + std::string(proxy->restart_output_prefix()+".colvars.state") : + std::string(""); + + std::string const state_file_format(binary_restart ? " (binary format)" : ""); + + if (restart_out_name.size()) { + cvm::log("The restart output state file" + state_file_format + " will be \""+ + restart_out_name+"\".\n"); + } + + if (output_prefix() != proxy->output_prefix()) { + output_prefix() = proxy->output_prefix(); + if (output_prefix().size()) { + cvm::log("The final output state file will be \"" + + (output_prefix().size() ? std::string(output_prefix() + ".colvars.state") + : std::string("colvars.state")) + + "\".\n"); + } + + if (proxy->output_stream_exists(cv_traj_name)) { + // Close old file + proxy->close_output_stream(cv_traj_name); + cv_traj_write_labels = true; + } + + cv_traj_name = + (output_prefix().size() ? std::string(output_prefix() + ".colvars.traj") : std::string("")); + + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + error_code |= (*bi)->setup_output(); + } + } + + return error_code; +} + + +std::string colvarmodule::state_file_prefix(char const *filename) +{ + std::string const filename_str(filename); + std::string const prefix = + filename_str.substr(0, filename_str.find(".colvars.state")); + if (prefix.size() == 0) { + cvm::error("Error: invalid filename/prefix value \""+filename_str+"\".", + COLVARS_INPUT_ERROR); + } + return prefix; +} + + +template IST & colvarmodule::read_state_template_(IST &is) +{ + bool warn_total_forces = false; + + { + // read global restart information + std::string restart_conf; + if (is >> colvarparse::read_block("configuration", &restart_conf)) { + + parse->get_keyval(restart_conf, "step", + it_restart, static_cast(0), + colvarparse::parse_restart); + it = it_restart; + + restart_version_str.clear(); + restart_version_int = 0; + parse->get_keyval(restart_conf, "version", + restart_version_str, std::string(""), + colvarparse::parse_restart); + if (restart_version_str.size()) { + // Initialize integer version number of this restart file + restart_version_int = + proxy->get_version_from_string(restart_version_str.c_str()); + } + + if (restart_version() != version()) { + cvm::log("This state file was generated with version " + restart_version() + "\n"); + if (std::is_same::value) { + cvm::log("Warning: compatibility between differetn Colvars versions is not " + "guaranteed for unformatted (binary) state files.\n"); + } + } + + if (restart_version_number() < 20160810) { + // check for total force change + if (proxy->total_forces_enabled()) { + warn_total_forces = true; + } + } + + std::string units_restart; + if (parse->get_keyval(restart_conf, "units", + units_restart, std::string(""), + colvarparse::parse_restart)) { + units_restart = colvarparse::to_lower_cppstr(units_restart); + if ((proxy->units.size() > 0) && (units_restart != proxy->units)) { + cvm::error("Error: the state file has units \""+units_restart+ + "\", but the current unit system is \""+proxy->units+ + "\".\n", COLVARS_INPUT_ERROR); + } + } + + } + is.clear(); + parse->clear_keyword_registry(); + } + + print_total_forces_errning(warn_total_forces); + + read_objects_state(is); + + return is; +} + + +std::istream & colvarmodule::read_state(std::istream &is) +{ + return read_state_template_(is); +} + + +cvm::memory_stream &colvarmodule::read_state(cvm::memory_stream &is) +{ + uint32_t file_magic_number = 0; + if (!(is >> file_magic_number)) { + return is; + } + if (file_magic_number == colvars_magic_number) { + return read_state_template_(is); + } else { + is.setstate(std::ios::failbit); + cvm::error("Error: magic number of binary file (" + + cvm::to_str(static_cast(file_magic_number)) + + ") does not match the expected magic number for a Colvars state file (" + + cvm::to_str(static_cast(colvars_magic_number)) + ").\n", + COLVARS_INPUT_ERROR); + } + return is; +} + + +int colvarmodule::set_input_state_buffer(size_t n, unsigned char *buf) +{ + input_state_buffer_.clear(); + std::copy(buf, buf + n, std::back_inserter(input_state_buffer_)); + return COLVARS_OK; +} + + +int colvarmodule::set_input_state_buffer(std::vector &buf) +{ + input_state_buffer_ = std::move(buf); + return COLVARS_OK; +} + + +std::istream & colvarmodule::read_objects_state(std::istream &is) +{ + auto pos = is.tellg(); + std::string word; + + while (is) { + pos = is.tellg(); + + if (is >> word) { + + is.seekg(pos); + + if (word == "colvar") { + + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { + if (!((*cvi)->read_state(is))) { + // Here an error signals that the variable is a match, but the + // state is corrupt; otherwise, the variable rewinds is silently + cvm::error("Error: in reading state for collective variable \"" + + (*cvi)->name + "\" at position " + cvm::to_str(is.tellg()) + + " in stream.\n", + COLVARS_INPUT_ERROR); + } + if (is.tellg() > pos) + break; // found it + } + cvm::decrease_depth(); + + } else { + + cvm::increase_depth(); + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if (((*bi)->state_keyword != word) && (*bi)->bias_type != word) { + // Skip biases with different type; state_keyword is used to + // support different versions of the state file format + continue; + } + if (!((*bi)->read_state(is))) { + // Same as above, an error means a match but the state is incorrect + cvm::error("Error: in reading state for bias \"" + (*bi)->name + "\" at position " + + cvm::to_str(is.tellg()) + " in stream.\n", + COLVARS_INPUT_ERROR); + } + if (is.tellg() > pos) + break; // found it + } + cvm::decrease_depth(); + } + } + + if (is.tellg() == pos) { + // This block has not been read by any object: discard it and move on + // to the next one + is >> colvarparse::read_block(word, NULL); + } + + if (!is) break; + } + + return is; +} + + +cvm::memory_stream &colvarmodule::read_objects_state(cvm::memory_stream &is) +{ + // An unformatted stream must match the objects' exact configuration + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); cvi != colvars.end(); cvi++) { + if (!(*cvi)->read_state(is)) { + return is; + } + } + for (std::vector::iterator bi = biases.begin(); bi != biases.end(); bi++) { + if (!(*bi)->read_state(is)) { + return is; + } + } + cvm::decrease_depth(); + return is; +} + + +int colvarmodule::print_total_forces_errning(bool warn_total_forces) +{ + if (warn_total_forces) { + cvm::log(cvm::line_marker); + cvm::log("WARNING: The definition of system forces has changed. Please see:\n"); + cvm::log(" https://colvars.github.io/README-totalforce.html\n"); + // update this ahead of time in this special case + output_prefix() = proxy->input_prefix(); + cvm::log("All output files will now be saved with the prefix \""+output_prefix()+".tmp.*\".\n"); + cvm::log("Please review the important warning above. After that, you may rename:\n\ +\""+output_prefix()+".tmp.colvars.state\"\n\ +to:\n\ +\""+proxy->input_prefix()+".colvars.state\"\n\ +and load it to continue this simulation.\n"); + output_prefix() = output_prefix()+".tmp"; + write_restart_file(output_prefix()+".colvars.state"); + return cvm::error("Exiting with error until issue is addressed.\n", + COLVARS_INPUT_ERROR); + } + + return COLVARS_OK; +} + + +int colvarmodule::backup_file(char const *filename) +{ + return proxy->backup_file(filename); +} + + +int colvarmodule::write_output_files() +{ + int error_code = COLVARS_OK; + cvm::increase_depth(); + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + // Only write output files if they have not already been written this time step + if ((*bi)->output_freq == 0 || + cvm::step_relative() == 0 || + (cvm::step_absolute() % (*bi)->output_freq) != 0) { + error_code |= (*bi)->write_output_files(); + } + error_code |= (*bi)->write_state_to_replicas(); + } + cvm::decrease_depth(); + return error_code; +} + + +int colvarmodule::read_traj(char const *traj_filename, + long traj_read_begin, + long traj_read_end) +{ + cvm::log("Opening trajectory file \""+ + std::string(traj_filename)+"\".\n"); + // NB: this function is not currently used, but when it will it should + // retain the ability for direct file-based access (in case traj files + // exceed memory) + std::ifstream traj_is(traj_filename); + + while (true) { + while (true) { + + std::string line(""); + + do { + if (!colvarparse::getline_nocomments(traj_is, line)) { + cvm::log("End of file \""+std::string(traj_filename)+ + "\" reached, or corrupted file.\n"); + traj_is.close(); + return false; + } + } while (line.find_first_not_of(colvarparse::white_space) == std::string::npos); + + std::istringstream is(line); + + if (!(is >> it)) return false; + + if ( (it < traj_read_begin) ) { + + if ((it % 1000) == 0) + std::cerr << "Skipping trajectory step " << it + << " \r"; + + continue; + + } else { + + if ((it % 1000) == 0) + std::cerr << "Reading from trajectory, step = " << it + << " \r"; + + if ( (traj_read_end > traj_read_begin) && + (it > traj_read_end) ) { + std::cerr << "\n"; + cvm::error("Reached the end of the trajectory, " + "read_end = "+cvm::to_str(traj_read_end)+"\n", + COLVARS_FILE_ERROR); + return COLVARS_ERROR; + } + + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + if (!(*cvi)->read_traj(is)) { + cvm::error("Error: in reading colvar \""+(*cvi)->name+ + "\" from trajectory file \""+ + std::string(traj_filename)+"\".\n", + COLVARS_FILE_ERROR); + return COLVARS_ERROR; + } + } + + break; + } + } + } + return (cvm::get_error() ? COLVARS_ERROR : COLVARS_OK); +} + + +template OST &colvarmodule::write_state_template_(OST &os) +{ + bool const formatted = !std::is_same::value; + + std::ostringstream oss; + oss.setf(std::ios::scientific, std::ios::floatfield); + oss << " step " << std::setw(it_width) + << it << "\n" + << " dt " << dt() << "\n" + << " version " << std::string(COLVARS_VERSION) << "\n"; + if (proxy->units.size() > 0) { + oss << " units " << proxy->units << "\n"; + } + + os << std::string("configuration"); + if (formatted) os << " {\n"; + os << oss.str(); + if (formatted) os << "}\n\n"; + + int error_code = COLVARS_OK; + + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->write_state(os); + } + + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + (*bi)->write_state(os); + } + cvm::decrease_depth(); + + if (error_code != COLVARS_OK) { + // TODO make this function return an int instead + os.setstate(std::ios::failbit); + } + + return os; +} + + +std::ostream &colvarmodule::write_state(std::ostream &os) +{ + return write_state_template_(os); +} + + +cvm::memory_stream &colvarmodule::write_state(cvm::memory_stream &os) +{ + if (os << colvars_magic_number) { + write_state_template_(os); + } + return os; +} + + +int colvarmodule::write_state_buffer(std::vector &buffer) +{ + cvm::memory_stream os(buffer); + if (os << colvars_magic_number) { + write_state_template_(os); + } + return os ? COLVARS_OK : COLVARS_ERROR; +} + + +std::ostream &colvarmodule::write_traj_label(std::ostream &os) +{ + os.setf(std::ios::scientific, std::ios::floatfield); + + os << "# " << cvm::wrap_string("step", cvm::it_width-2) + << " "; + + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->write_traj_label(os); + } + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + (*bi)->write_traj_label(os); + } + os << "\n"; + + cvm::decrease_depth(); + return os; +} + + +std::ostream & colvarmodule::write_traj(std::ostream &os) +{ + os.setf(std::ios::scientific, std::ios::floatfield); + + os << std::setw(cvm::it_width) << it + << " "; + + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->write_traj(os); + } + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + (*bi)->write_traj(os); + } + os << "\n"; + + cvm::decrease_depth(); + return os; +} + + +void colvarmodule::log(std::string const &message, int min_log_level) +{ + if (cvm::log_level() < min_log_level) return; + + std::string const trailing_newline = (message.size() > 0) ? + (message[message.size()-1] == '\n' ? "" : "\n") : ""; + // allow logging when the module is not fully initialized + size_t const d = (cvm::main() != NULL) ? depth() : 0; + if (d > 0) { + proxy->log((std::string(2*d, ' ')) + message + trailing_newline); + } else { + proxy->log(message + trailing_newline); + } +} + + +void colvarmodule::increase_depth() +{ + (depth())++; +} + + +void colvarmodule::decrease_depth() +{ + if (depth() > 0) { + (depth())--; + } +} + + +size_t & colvarmodule::depth() +{ + // NOTE: do not call log() or error() here, to avoid recursion + colvarmodule *cv = cvm::main(); + if (proxy->smp_enabled() == COLVARS_OK) { + int const nt = proxy->smp_num_threads(); + if (int(cv->depth_v.size()) != nt) { + proxy->smp_lock(); + // update array of depths + if (cv->depth_v.size() > 0) { cv->depth_s = cv->depth_v[0]; } + cv->depth_v.clear(); + cv->depth_v.assign(nt, cv->depth_s); + proxy->smp_unlock(); + } + return cv->depth_v[proxy->smp_thread_id()]; + } + return cv->depth_s; +} + + +void colvarmodule::set_error_bits(int code) +{ + if (code < 0) { + cvm::log("Error: set_error_bits() received negative error code.\n"); + return; + } + proxy->smp_lock(); + errorCode |= code | COLVARS_ERROR; + proxy->smp_unlock(); +} + + +bool colvarmodule::get_error_bit(int code) +{ + return bool(errorCode & code); +} + + +void colvarmodule::clear_error() +{ + proxy->smp_lock(); + errorCode = COLVARS_OK; + proxy->smp_unlock(); + proxy->clear_error_msgs(); +} + + +int colvarmodule::error(std::string const &message, int code) +{ + set_error_bits(code >= 0 ? code : COLVARS_ERROR); + + std::string const trailing_newline = (message.size() > 0) ? + (message[message.size()-1] == '\n' ? "" : "\n") : ""; + size_t const d = depth(); + if (d > 0) { + proxy->error((std::string(2*d, ' ')) + message + trailing_newline); + } else { + proxy->error(message + trailing_newline); + } + + return get_error(); +} + + +int cvm::read_index_file(char const *filename) +{ + std::istream &is = proxy->input_stream(filename, "index file"); + + if (!is) { + return COLVARS_FILE_ERROR; + } else { + index_file_names.push_back(std::string(filename)); + } + + while (is.good()) { + char open, close; + std::string group_name; + int index_of_group = -1; + if ( (is >> open) && (open == '[') && + (is >> group_name) && + (is >> close) && (close == ']') ) { + size_t i = 0; + for ( ; i < index_group_names.size(); i++) { + if (index_group_names[i] == group_name) { + // Found a group with the same name + index_of_group = i; + } + } + if (index_of_group < 0) { + index_group_names.push_back(group_name); + index_groups.push_back(NULL); + index_of_group = index_groups.size()-1; + } + } else { + return cvm::error("Error: in parsing index file \""+ + std::string(filename)+"\".\n", + COLVARS_INPUT_ERROR); + } + + std::vector *old_index_group = index_groups[index_of_group]; + std::vector *new_index_group = new std::vector(); + + int atom_number = 1; + std::streampos pos = is.tellg(); + while ( (is >> atom_number) && (atom_number > 0) ) { + new_index_group->push_back(atom_number); + pos = is.tellg(); + } + + if (old_index_group != NULL) { + bool equal = false; + if (new_index_group->size() == old_index_group->size()) { + if (std::equal(new_index_group->begin(), new_index_group->end(), + old_index_group->begin())) { + equal = true; + } + } + if (! equal) { + new_index_group->clear(); + delete new_index_group; + new_index_group = NULL; + return cvm::error("Error: the index group \""+group_name+ + "\" was redefined.\n", COLVARS_INPUT_ERROR); + } else { + old_index_group->clear(); + delete old_index_group; + old_index_group = NULL; + } + } + + index_groups[index_of_group] = new_index_group; + + is.clear(); + is.seekg(pos, std::ios::beg); + std::string delim; + if ( (is >> delim) && (delim == "[") ) { + // new group + is.clear(); + is.seekg(pos, std::ios::beg); + } else { + break; + } + } + + cvm::log("The following index groups are currently defined:\n"); + size_t i = 0; + for ( ; i < index_group_names.size(); i++) { + cvm::log(" "+(index_group_names[i])+" ("+ + cvm::to_str((index_groups[i])->size())+" atoms)\n"); + } + + return proxy->close_input_stream(filename); +} + + +int colvarmodule::reset_index_groups() +{ + size_t i = 0; + for ( ; i < index_groups.size(); i++) { + delete index_groups[i]; + index_groups[i] = NULL; + } + index_group_names.clear(); + index_groups.clear(); + index_file_names.clear(); + return COLVARS_OK; +} + + +int cvm::load_atoms(char const *file_name, + cvm::atom_group &atoms, + std::string const &pdb_field, + double pdb_field_value) +{ + return proxy->load_atoms(file_name, atoms, pdb_field, pdb_field_value); +} + + +int cvm::load_coords(char const *file_name, + std::vector *pos, + cvm::atom_group *atoms, + std::string const &pdb_field, + double pdb_field_value) +{ + int error_code = COLVARS_OK; + + std::string const ext(strlen(file_name) > 4 ? + (file_name + (strlen(file_name) - 4)) : + file_name); + + atoms->create_sorted_ids(); + + std::vector sorted_pos(atoms->size(), cvm::rvector(0.0)); + + // Differentiate between PDB and XYZ files + if (colvarparse::to_lower_cppstr(ext) == std::string(".xyz")) { + if (pdb_field.size() > 0) { + return cvm::error("Error: PDB column may not be specified " + "for XYZ coordinate files.\n", COLVARS_INPUT_ERROR); + } + // For XYZ files, use internal parser + error_code |= cvm::main()->load_coords_xyz(file_name, &sorted_pos, atoms); + } else { + // Otherwise, call proxy function for PDB + error_code |= proxy->load_coords(file_name, + sorted_pos, atoms->sorted_ids(), + pdb_field, pdb_field_value); + } + + std::vector const &map = atoms->sorted_ids_map(); + for (size_t i = 0; i < atoms->size(); i++) { + (*pos)[map[i]] = sorted_pos[i]; + } + + return error_code; +} + + +int cvm::load_coords_xyz(char const *filename, + std::vector *pos, + cvm::atom_group *atoms, + bool keep_open) +{ + std::istream &xyz_is = proxy->input_stream(filename, "XYZ file"); + unsigned int natoms; + char symbol[256]; + std::string line; + cvm::real x = 0.0, y = 0.0, z = 0.0; + + std::string const error_msg("Error: cannot parse XYZ file \""+ + std::string(filename)+"\".\n"); + + if ( ! (xyz_is >> natoms) ) { + // Return silent error when reaching the end of multi-frame files + return keep_open ? COLVARS_NO_SUCH_FRAME : cvm::error(error_msg, COLVARS_INPUT_ERROR); + } + + ++xyz_reader_use_count; + if (xyz_reader_use_count < 2) { + cvm::log("Warning: beginning from 2019-11-26 the XYZ file reader assumes Angstrom units.\n"); + } + + if (xyz_is.good()) { + // skip comment line + cvm::getline(xyz_is, line); + cvm::getline(xyz_is, line); + xyz_is.width(255); + } else { + return cvm::error(error_msg, COLVARS_INPUT_ERROR); + } + + std::vector::iterator pos_i = pos->begin(); + size_t xyz_natoms = 0; + if (pos->size() != natoms) { // Use specified indices + int next = 0; // indices are zero-based + if (!atoms) { + // In the other branch of this test, reading all positions from the file, + // a valid atom group pointer is not necessary + return cvm::error("Trying to read partial positions with invalid atom group pointer", + COLVARS_BUG_ERROR); + } + std::vector::const_iterator index = atoms->sorted_ids().begin(); + + for ( ; pos_i != pos->end() ; pos_i++, index++) { + while ( next < *index ) { + cvm::getline(xyz_is, line); + next++; + } + if (xyz_is.good()) { + xyz_is >> symbol; + xyz_is >> x >> y >> z; + // XYZ files are assumed to be in Angstrom (as eg. VMD will) + (*pos_i)[0] = proxy->angstrom_to_internal(x); + (*pos_i)[1] = proxy->angstrom_to_internal(y); + (*pos_i)[2] = proxy->angstrom_to_internal(z); + xyz_natoms++; + } else { + return cvm::error(error_msg, COLVARS_INPUT_ERROR); + } + } + + } else { // Use all positions + + for ( ; pos_i != pos->end() ; pos_i++) { + if (xyz_is.good()) { + xyz_is >> symbol; + xyz_is >> x >> y >> z; + (*pos_i)[0] = proxy->angstrom_to_internal(x); + (*pos_i)[1] = proxy->angstrom_to_internal(y); + (*pos_i)[2] = proxy->angstrom_to_internal(z); + xyz_natoms++; + } else { + return cvm::error(error_msg, COLVARS_INPUT_ERROR); + } + } + } + + if (xyz_natoms != pos->size()) { + return cvm::error("Error: The number of positions read from file \""+ + std::string(filename)+"\" does not match the number of "+ + "positions required: "+cvm::to_str(xyz_natoms)+" vs. "+ + cvm::to_str(pos->size())+".\n", COLVARS_INPUT_ERROR); + } + + if (keep_open) { + return COLVARS_OK; + } else { + return proxy->close_input_stream(filename); + } +} + + + +// Wrappers to proxy functions: these may go in the future + + +cvm::real cvm::dt() +{ + return proxy->dt(); +} + + +void cvm::request_total_force() +{ + proxy->request_total_force(true); +} + + +cvm::rvector cvm::position_distance(cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) +{ + return proxy->position_distance(pos1, pos2); +} + + +cvm::real cvm::rand_gaussian(void) +{ + return proxy->rand_gaussian(); +} + + +template std::string _to_str(T const &x, + size_t width, size_t prec) +{ + std::ostringstream os; + if (width) os.width(width); + if (prec) { + os.setf(std::ios::scientific, std::ios::floatfield); + os.precision(prec); + } + os << x; + return os.str(); +} + + +template std::string _to_str_vector(std::vector const &x, + size_t width, size_t prec) +{ + if (!x.size()) return std::string(""); + std::ostringstream os; + if (prec) { + os.setf(std::ios::scientific, std::ios::floatfield); + } + os << "{ "; + if (width) os.width(width); + if (prec) os.precision(prec); + os << x[0]; + for (size_t i = 1; i < x.size(); i++) { + os << ", "; + if (width) os.width(width); + if (prec) os.precision(prec); + os << x[i]; + } + os << " }"; + return os.str(); +} + + + +std::string colvarmodule::to_str(std::string const &x) +{ + return std::string("\"")+x+std::string("\""); +} + +std::string colvarmodule::to_str(char const *x) +{ + return std::string("\"")+std::string(x)+std::string("\""); +} + +std::string colvarmodule::to_str(bool x) +{ + return (x ? "on" : "off"); +} + +std::string colvarmodule::to_str(int const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(size_t const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(long int const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(step_number const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(cvm::real const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(cvm::rvector const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(cvm::quaternion const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(colvarvalue const &x, + size_t width, size_t prec) +{ + return _to_str(x, width, prec); +} + +std::string colvarmodule::to_str(cvm::vector1d const &x, + size_t width, size_t prec) +{ + return _to_str< cvm::vector1d >(x, width, prec); +} + +std::string colvarmodule::to_str(cvm::matrix2d const &x, + size_t width, size_t prec) +{ + return _to_str< cvm::matrix2d >(x, width, prec); +} + + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + +std::string colvarmodule::to_str(std::vector const &x, + size_t width, size_t prec) +{ + return _to_str_vector(x, width, prec); +} + + +std::string cvm::wrap_string(std::string const &s, size_t nchars) +{ + if (!s.size()) { + return std::string(nchars, ' '); + } else { + return ( (s.size() <= nchars) ? + (s+std::string(nchars-s.size(), ' ')) : + (std::string(s, 0, nchars)) ); + } +} + + + +int colvarmodule::cite_feature(std::string const &feature) +{ + return usage_->cite_feature(feature); +} + +std::string colvarmodule::feature_report(int flag) +{ + return usage_->report(flag); +} + + +colvarmodule::usage::usage() +{ +#include "colvarmodule_refs.h" +} + +int colvarmodule::usage::cite_feature(std::string const &feature) +{ + if (feature_count_.count(feature) > 0) { + feature_count_[feature] += 1; + return cite_paper(feature_paper_map_[feature]); + } + cvm::log("Warning: cannot cite unknown feature \""+feature+"\"\n"); + return COLVARS_OK; +} + +int colvarmodule::usage::cite_paper(std::string const &paper) +{ + if (paper_count_.count(paper) > 0) { + paper_count_[paper] += 1; + return COLVARS_OK; + } + cvm::log("Warning: cannot cite unknown paper \""+paper+"\"\n"); + return COLVARS_OK; +} + +std::string colvarmodule::usage::report(int flag) +{ + std::string result; + if (flag == 0) { + // Text + result += "SUMMARY OF COLVARS FEATURES USED SO FAR AND THEIR CITATIONS:\n"; + } + if (flag == 1) { + // LAMMPS log friendly (one-line summary, lowercase message) + result += "Colvars module (Fiorin2013, plus other works listed for specific features)\n\n"; + } + + std::map::iterator p_iter = paper_count_.begin(); + for ( ; p_iter != paper_count_.end(); p_iter++) { + std::string const paper = p_iter->first; + int const count = p_iter->second; + if (count > 0) { + result += "\n"; + std::map::iterator f_iter = + feature_paper_map_.begin(); + for ( ; f_iter != feature_paper_map_.end(); f_iter++) { + if ((f_iter->second == paper) && + (feature_count_[f_iter->first] > 0)) { + if (flag == 0) { + // URL + result += "- " + f_iter->first + ":\n"; + } + if (flag == 1) { + // BibTeX + result += "% " + f_iter->first + ":\n"; + } + } + } + if (flag == 0) { + result += " " + paper + " " + paper_url_[paper] + "\n"; + } + if (flag == 1) { + result += paper_bibtex_[paper] + "\n"; + } + } + } + + return result; +} + + +// shared pointer to the proxy object +colvarproxy *colvarmodule::proxy = NULL; + +// static runtime data +cvm::real colvarmodule::debug_gradients_step_size = 1.0e-07; +int colvarmodule::errorCode = 0; +int colvarmodule::log_level_ = 10; +cvm::step_number colvarmodule::it = 0; +cvm::step_number colvarmodule::it_restart = 0; +size_t colvarmodule::restart_out_freq = 0; +size_t colvarmodule::cv_traj_freq = 0; +bool colvarmodule::use_scripted_forces = false; +bool colvarmodule::scripting_after_biases = true; + +// i/o constants +size_t const colvarmodule::it_width = 12; +size_t const colvarmodule::cv_prec = 14; +size_t const colvarmodule::cv_width = 21; +size_t const colvarmodule::en_prec = 14; +size_t const colvarmodule::en_width = 21; +const char * const colvarmodule::line_marker = (const char *) + "----------------------------------------------------------------------\n"; diff --git a/src/external/colvars/colvarmodule.h b/src/external/colvars/colvarmodule.h new file mode 100644 index 00000000000..6c07e282808 --- /dev/null +++ b/src/external/colvars/colvarmodule.h @@ -0,0 +1,888 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARMODULE_H +#define COLVARMODULE_H + +#include + +#include "colvars_version.h" + +#ifndef COLVARS_DEBUG +#define COLVARS_DEBUG false +#endif + +/*! \mainpage Main page +This is the Developer's documentation for the Collective Variables module (Colvars). + +You can browse the class hierarchy or the list of source files. + +Please note that this documentation is only supported for the master branch, and its features may differ from those in a given release of a simulation package. + */ + +/// \file colvarmodule.h +/// \brief Collective variables main module +/// +/// This file declares the main class for defining and manipulating +/// collective variables: there should be only one instance of this +/// class, because several variables are made static (i.e. they are +/// shared between all object instances) to be accessed from other +/// objects. + +#include +#include +#include +#include + +class colvarparse; +class colvar; +class colvarbias; +class colvarproxy; +class colvarscript; +class colvarvalue; + + +/// \brief Collective variables module (main class) +/// +/// Class to control the collective variables calculation. An object +/// (usually one) of this class is spawned from the MD program, +/// containing all i/o routines and general interface. +/// +/// At initialization, the colvarmodule object creates a proxy object +/// to provide a transparent interface between the MD program and the +/// child objects +class colvarmodule { + +private: + + /// Impossible to initialize the main object without arguments + colvarmodule(); + + /// Integer representing the version string (allows comparisons) + int version_int; + +public: + + /// Get the version string (YYYY-MM-DD format) + std::string version() const + { + return std::string(COLVARS_VERSION); + } + + /// Get the version number (higher = more recent) + int version_number() const + { + return version_int; + } + + friend class colvarproxy; + // TODO colvarscript should be unaware of colvarmodule's internals + friend class colvarscript; + + /// Use a 64-bit integer to store the step number + typedef long long step_number; + + /// Defining an abstract real number allows to switch precision + typedef double real; + + + // Math functions + + /// Override the STL pow() with a product for n integer + static inline real integer_power(real const &x, int const n) + { + // Original code: math_special.h in LAMMPS + double yy, ww; + if (x == 0.0) return 0.0; + int nn = (n > 0) ? n : -n; + ww = x; + for (yy = 1.0; nn != 0; nn >>= 1, ww *=ww) { + if (nn & 1) yy *= ww; + } + return (n > 0) ? yy : 1.0/yy; + } + + /// Reimplemented to work around MS compiler issues + static inline real pow(real const &x, real const &y) + { + return ::pow(static_cast(x), static_cast(y)); + } + + /// Reimplemented to work around MS compiler issues + static inline real floor(real const &x) + { + return ::floor(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real fabs(real const &x) + { + return ::fabs(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real sqrt(real const &x) + { + return ::sqrt(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real sin(real const &x) + { + return ::sin(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real cos(real const &x) + { + return ::cos(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real asin(real const &x) + { + return ::asin(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real acos(real const &x) + { + return ::acos(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues + static inline real atan2(real const &x, real const &y) + { + return ::atan2(static_cast(x), static_cast(y)); + } + + /// Reimplemented to work around MS compiler issues + static inline real exp(real const &x) + { + return ::exp(static_cast(x)); + } + + /// Reimplemented to work around MS compiler issues. Note: log() is + /// currently defined as the text logging function, but this can be changed + /// at a later time + static inline real logn(real const &x) + { + return ::log(static_cast(x)); + } + + // Forward declarations + class rvector; + template class vector1d; + template class matrix2d; + class quaternion; + class rotation; + + class usage; + class memory_stream; + + /// Residue identifier + typedef int residue_id; + + /// \brief Atom position (different type name from rvector, to make + /// possible future PBC-transparent implementations) + typedef rvector atom_pos; + + /// \brief 3x3 matrix of real numbers + class rmatrix; + + // allow these classes to access protected data + class atom; + class atom_group; + friend class atom; + friend class atom_group; + typedef std::vector::iterator atom_iter; + typedef std::vector::const_iterator atom_const_iter; + + /// Module-wide error state + /// see constants at the top of this file +private: + + static int errorCode; + +public: + + static void set_error_bits(int code); + + static bool get_error_bit(int code); + + static inline int get_error() + { + return errorCode; + } + + static void clear_error(); + + /// Current step number + static step_number it; + /// Starting step number for this run + static step_number it_restart; + + /// Return the current step number from the beginning of this run + static inline step_number step_relative() + { + return it - it_restart; + } + + /// Return the current step number from the beginning of the whole + /// calculation + static inline step_number step_absolute() + { + return it; + } + + bool binary_restart; + + /// \brief Finite difference step size (if there is no dynamics, or + /// if gradients need to be tested independently from the size of + /// dt) + static real debug_gradients_step_size; + +private: + + /// Prefix for all output files for this run + std::string cvm_output_prefix; + +public: + /// Accessor for the above + static inline std::string &output_prefix() + { + colvarmodule *cv = colvarmodule::main(); + return cv->cvm_output_prefix; + } + +private: + + /// Array of collective variables + std::vector colvars; + + /// Array of collective variables + std::vector colvars_active; + + /// Collective variables to be calculated on different threads; + /// colvars with multple items (e.g. multiple active CVCs) are duplicated + std::vector colvars_smp; + /// Indexes of the items to calculate for each colvar + std::vector colvars_smp_items; + + /// Array of named atom groups + std::vector named_atom_groups; +public: + /// Register a named atom group into named_atom_groups + void register_named_atom_group(atom_group *ag); + + /// Remove a named atom group from named_atom_groups + void unregister_named_atom_group(atom_group *ag); + + /// Array of collective variables + std::vector *variables(); + + /* TODO: implement named CVCs + /// Array of named (reusable) collective variable components + static std::vector cvcs; + /// Named cvcs register themselves at initialization time + inline void register_cvc(cvc *p) { + cvcs.push_back(p); + } + */ + + /// Collective variables with the active flag on + std::vector *variables_active(); + + /// Collective variables to be calculated on different threads; + /// colvars with multple items (e.g. multiple active CVCs) are duplicated + std::vector *variables_active_smp(); + + /// Indexes of the items to calculate for each colvar + std::vector *variables_active_smp_items(); + + /// Array of collective variable biases + std::vector biases; + + /// Energy of built-in and scripted biases, summed per time-step + real total_bias_energy; + +private: + + /// Pointer to a map counting how many biases of each type were used + void *num_biases_types_used_; + + /// Array of active collective variable biases + std::vector biases_active_; + +public: + + /// Array of active collective variable biases + std::vector *biases_active(); + + /// \brief Whether debug output should be enabled (compile-time option) + static inline bool debug() + { + return COLVARS_DEBUG; + } + + /// How many objects (variables and biases) are configured yet? + size_t size() const; + + /// Constructor + /// \param Pointer to instance of the proxy class (communicate with engine) + colvarmodule(colvarproxy *proxy); + + /// Destructor + ~colvarmodule(); + + /// Set the initial step number (it is 0 otherwise); may be overridden when reading a state + void set_initial_step(step_number it); + + /// Actual function called by the destructor + int reset(); + + /// Open a config file, load its contents, and pass it to config_string() + /// \param config_file_name Configuration file name + int read_config_file(char const *config_file_name); + + /// \brief Parse a config string assuming it is a complete configuration + /// (i.e. calling all parse functions) + int read_config_string(std::string const &conf); + + /// \brief Parse a "clean" config string (no comments) + int parse_config(std::string &conf); + + /// Get the configuration string read so far (includes comments) + std::string const & get_config() const; + + // Parse functions (setup internal data based on a string) + + /// Allow reading from Windows text files using using std::getline + /// (which can still be used when the text is produced by Colvars itself) + static std::istream & getline(std::istream &is, std::string &line); + + /// Parse the few module's global parameters + int parse_global_params(std::string const &conf); + + /// Parse and initialize collective variables + int parse_colvars(std::string const &conf); + + /// Run provided Tcl script + int run_tcl_script(std::string const &filename); + + /// Parse and initialize collective variable biases + int parse_biases(std::string const &conf); + + /// \brief Add new configuration during parsing (e.g. to implement + /// back-compatibility); cannot be nested, i.e. conf should not contain + /// anything that triggers another call + int append_new_config(std::string const &conf); + + /// Signals to the module object that the configuration has changed + void config_changed(); + +private: + + /// Configuration string read so far by the module (includes comments) + std::string config_string; + + /// Auto-generated configuration during parsing (e.g. to implement + /// back-compatibility) + std::string extra_conf; + + /// Parse and initialize collective variable biases of a specific type + template + int parse_biases_type(std::string const &conf, char const *keyword); + + /// Test error condition and keyword parsing + /// on error, delete new bias + bool check_new_bias(std::string &conf, char const *key); + + /// Initialization Tcl script, user-provided + std::string source_Tcl_script; + +public: + + /// Return how many variables are defined + size_t num_variables() const; + + /// Return how many variables have this feature enabled + size_t num_variables_feature(int feature_id) const; + + /// Return how many biases are defined + size_t num_biases() const; + + /// Return how many biases have this feature enabled + size_t num_biases_feature(int feature_id) const; + + /// Return how many biases of this type are defined + size_t num_biases_type(std::string const &type) const; + + /// Return the names of time-dependent biases with forces enabled (ABF, + /// metadynamics, etc) + std::vector const time_dependent_biases() const; + +private: + /// Useful wrapper to interrupt parsing if any error occurs + int catch_input_errors(int result); + +public: + + // "Setup" functions (change internal data based on related data + // from the proxy that may change during program execution) + // No additional parsing is done within these functions + + /// (Re)initialize any internal data affected by changes in the engine + /// Also calls setup() member functions of colvars and biases + int update_engine_parameters(); + + /// (Re)initialize and (re)read the input state file calling read_restart() + int setup_input(); + + /// (Re)initialize the output trajectory and state file (does not write it yet) + int setup_output(); + +private: + + template IST & read_state_template_(IST &is); + + /// Internal state buffer, to be read as an unformatted stream + std::vector input_state_buffer_; + +public: + + /// Read all objects' state fron a formatted (text) stream + std::istream & read_state(std::istream &is); + + /// Read all objects' state fron an unformatted (binary) stream + memory_stream & read_state(memory_stream &is); + + /// Set an internal state buffer, to be read later as an unformatted stream when ready + int set_input_state_buffer(size_t n, unsigned char *buf); + + /// Same as set_input_state_buffer() for C array, but uses std::move + int set_input_state_buffer(std::vector &buf); + + /// Read the states of individual objects; allows for changes + std::istream & read_objects_state(std::istream &is); + + /// Read the states of individual objects; allows for changes + memory_stream & read_objects_state(memory_stream &is); + + /// If needed (old restart file), print the warning that cannot be ignored + int print_total_forces_errning(bool warn_total_forces); + +private: + template OST &write_state_template_(OST &os); + +public: + + /// Write the state of the module to a formatted (text) file + std::ostream & write_state(std::ostream &os); + + /// Write the state of the module to an unformatted (binary) file + memory_stream & write_state(memory_stream &os); + + /// Write the state of the module to an array of bytes (wrapped as a memory_stream object) + int write_state_buffer(std::vector &buffer); + + /// Strips .colvars.state from filename and checks that it is not empty + static std::string state_file_prefix(char const *filename); + + /// Open a trajectory file if requested (and leave it open) + int open_traj_file(std::string const &file_name); + /// Close it (note: currently unused) + int close_traj_file(); + /// Write in the trajectory file + std::ostream & write_traj(std::ostream &os); + /// Write explanatory labels in the trajectory file + std::ostream & write_traj_label(std::ostream &os); + + /// Write all trajectory files + int write_traj_files(); + /// Write a state file useful to resume the simulation + int write_restart_file(std::string const &out_name); + /// Write all other output files + int write_output_files(); + /// Backup a file before writing it + static int backup_file(char const *filename); + + /// Write the state into a string + int write_restart_string(std::string &output); + + /// Look up a bias by name; returns NULL if not found + static colvarbias * bias_by_name(std::string const &name); + + /// Look up a colvar by name; returns NULL if not found + static colvar * colvar_by_name(std::string const &name); + + /// Look up a named atom group by name; returns NULL if not found + static atom_group * atom_group_by_name(std::string const &name); + + /// Load new configuration for the given bias - + /// currently works for harmonic (force constant and/or centers) + int change_configuration(std::string const &bias_name, std::string const &conf); + + /// Read a colvar value + std::string read_colvar(std::string const &name); + + /// Calculate change in energy from using alt. config. for the given bias - + /// currently works for harmonic (force constant and/or centers) + real energy_difference(std::string const &bias_name, std::string const &conf); + + /// Main worker function + int calc(); + + /// Calculate collective variables + int calc_colvars(); + + /// Calculate biases + int calc_biases(); + + /// Integrate bias and restraint forces, send colvar forces to atoms + int update_colvar_forces(); + + /// Perform analysis + int analyze(); + + /// Carry out operations needed before next step is run + int end_of_step(); + + /// \brief Read a collective variable trajectory (post-processing + /// only, not called at runtime) + int read_traj(char const *traj_filename, + long traj_read_begin, + long traj_read_end); + + /// Convert to string for output purposes + static std::string to_str(char const *s); + + /// Convert to string for output purposes + static std::string to_str(std::string const &s); + + /// Convert to string for output purposes + static std::string to_str(bool x); + + /// Convert to string for output purposes + static std::string to_str(int const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(size_t const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(long int const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(step_number const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(real const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(rvector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(quaternion const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(colvarvalue const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(vector1d const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(matrix2d const &x, + size_t width = 0, size_t prec = 0); + + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + /// Convert to string for output purposes + static std::string to_str(std::vector const &x, + size_t width = 0, size_t prec = 0); + + + /// Reduce the number of characters in a string + static std::string wrap_string(std::string const &s, + size_t nchars); + + /// Number of characters to represent a time step + static size_t const it_width; + /// Number of digits to represent a collective variables value(s) + static size_t const cv_prec; + /// Number of characters to represent a collective variables value(s) + static size_t const cv_width; + /// Number of digits to represent the collective variables energy + static size_t const en_prec; + /// Number of characters to represent the collective variables energy + static size_t const en_width; + /// Line separator in the log output + static const char * const line_marker; + + + // proxy functions + + /// \brief Time step of MD integrator (fs) + static real dt(); + + /// Request calculation of total force from MD engine + static void request_total_force(); + + /// Track usage of the given Colvars feature + int cite_feature(std::string const &feature); + + /// Report usage of the Colvars features + std::string feature_report(int flag = 0); + + /// Print a message to the main log + /// \param message Message to print + /// \param min_log_level Only print if cvm::log_level() >= min_log_level + static void log(std::string const &message, int min_log_level = 10); + + /// Print a message to the main log and set global error code + static int error(std::string const &message, int code = -1); + +private: + + /// Level of logging requested by the user + static int log_level_; + +public: + + /// Level of logging requested by the user + static inline int log_level() + { + return log_level_; + } + + /// Level at which initialization messages are logged + static inline int log_init_messages() + { + return 1; + } + + /// Level at which a keyword's user-provided value is logged + static inline int log_user_params() + { + return 2; + } + + /// Level at which a keyword's default value is logged + static inline int log_default_params() + { + return 3; + } + + /// Level at which output-file operations are logged + static inline int log_output_files() + { + return 4; + } + + /// Level at which input-file operations (configuration, state) are logged + static inline int log_input_files() + { + return 5; + } + + /// \brief Get the distance between two atomic positions with pbcs handled + /// correctly + static rvector position_distance(atom_pos const &pos1, + atom_pos const &pos2); + + /// \brief Names of .ndx files that have been loaded + std::vector index_file_names; + + /// \brief Names of groups from one or more Gromacs .ndx files + std::vector index_group_names; + + /// \brief Groups from one or more Gromacs .ndx files + std::vector *> index_groups; + + /// \brief Read a Gromacs .ndx file + int read_index_file(char const *filename); + + /// Clear the index groups loaded so far + int reset_index_groups(); + + /// \brief Select atom IDs from a file (usually PDB) \param filename name of + /// the file \param atoms array into which atoms read from "filename" will be + /// appended \param pdb_field (optional) if the file is a PDB and this + /// string is non-empty, select atoms for which this field is non-zero + /// \param pdb_field_value (optional) if non-zero, select only atoms whose + /// pdb_field equals this + static int load_atoms(char const *filename, + atom_group &atoms, + std::string const &pdb_field, + double pdb_field_value = 0.0); + + /// \brief Load coordinates for a group of atoms from a file (PDB or XYZ); + /// if "pos" is already allocated, the number of its elements must match the + /// number of entries in "filename" \param filename name of the file \param + /// pos array of coordinates \param atoms group containing the atoms (used + /// to obtain internal IDs) \param pdb_field (optional) if the file is a PDB + /// and this string is non-empty, select atoms for which this field is + /// non-zero \param pdb_field_value (optional) if non-zero, select only + /// atoms whose pdb_field equals this + static int load_coords(char const *filename, + std::vector *pos, + atom_group *atoms, + std::string const &pdb_field, + double pdb_field_value = 0.0); + + /// Load coordinates into an atom group from an XYZ file (assumes Angstroms) + int load_coords_xyz(char const *filename, + std::vector *pos, + atom_group *atoms, + bool keep_open = false); + + /// Frequency for collective variables trajectory output + static size_t cv_traj_freq; + + /// Frequency for saving output restarts + static size_t restart_out_freq; + /// Output restart file name + std::string restart_out_name; + + /// Pseudo-random number with Gaussian distribution + static real rand_gaussian(); + +protected: + + /// Configuration file parser object + colvarparse *parse; + + /// Name of the trajectory file + std::string cv_traj_name; + + /// Write labels at the next iteration + bool cv_traj_write_labels; + + /// Version of the most recent state file read + std::string restart_version_str; + + /// Integer version of the most recent state file read + int restart_version_int; + + /// Counter for the current depth in the object hierarchy (useg e.g. in output) + size_t depth_s; + + /// Thread-specific depth + std::vector depth_v; + + /// Track how many times the XYZ reader has been used + int xyz_reader_use_count; + + /// Track usage of Colvars features + usage *usage_; + +public: + + /// Version of the most recent state file read + inline std::string restart_version() const + { + return restart_version_str; + } + + /// Integer version of the most recent state file read + inline int restart_version_number() const + { + return restart_version_int; + } + + /// Get the current object depth in the hierarchy + static size_t & depth(); + + /// Increase the depth (number of indentations in the output) + static void increase_depth(); + + /// Decrease the depth (number of indentations in the output) + static void decrease_depth(); + + static inline bool scripted_forces() + { + return use_scripted_forces; + } + + /// Use scripted colvars forces? + static bool use_scripted_forces; + + /// Wait for all biases before calculating scripted forces? + static bool scripting_after_biases; + + /// Calculate the energy and forces of scripted biases + int calc_scripted_forces(); + + /// \brief Pointer to the proxy object, used to retrieve atomic data + /// from the hosting program; it is static in order to be accessible + /// from static functions in the colvarmodule class + static colvarproxy *proxy; + + /// \brief Access the one instance of the Colvars module + static colvarmodule *main(); + +}; + + +/// Shorthand for the frequently used type prefix +typedef colvarmodule cvm; + + +std::ostream & operator << (std::ostream &os, cvm::rvector const &v); +std::istream & operator >> (std::istream &is, cvm::rvector &v); + + +namespace { + constexpr int32_t COLVARS_OK = 0; + constexpr int32_t COLVARS_ERROR = 1; + constexpr int32_t COLVARS_NOT_IMPLEMENTED = (1<<1); + constexpr int32_t COLVARS_INPUT_ERROR = (1<<2); // out of bounds or inconsistent input + constexpr int32_t COLVARS_BUG_ERROR = (1<<3); // Inconsistent state indicating bug + constexpr int32_t COLVARS_FILE_ERROR = (1<<4); + constexpr int32_t COLVARS_MEMORY_ERROR = (1<<5); + constexpr int32_t COLVARS_NO_SUCH_FRAME = (1<<6); // Cannot load the requested frame +} + + +#endif diff --git a/src/external/colvars/colvarmodule_refs.h b/src/external/colvars/colvarmodule_refs.h new file mode 100644 index 00000000000..b3f5842429a --- /dev/null +++ b/src/external/colvars/colvarmodule_refs.h @@ -0,0 +1,589 @@ + + paper_count_[std::string("Abraham2015")] = 0; + paper_url_[std::string("Abraham2015")] = "https://doi.org/10.1016/j.softx.2015.06.001"; + paper_bibtex_[std::string("Abraham2015")] = + "\n" + "@article{Abraham2015,\n" + " title = {{GROMACS}: High performance molecular simulations through multi-level parallelism from laptops to supercomputers},\n" + " author = {Abraham, Mark J.{} and Murtola, Teemu and Schulz, Roland and Páll, Szil\\'ard and Smith, Jeremy C.{} and Hess, Berk and Lindahl, Erik},\n" + " journal = {{SoftwareX}},\n" + " volume = {1--2},\n" + " year = {2015},\n" + " pages = {19--25},\n" + " doi = {10.1016/j.softx.2015.06.001},\n" + " url = {https://doi.org/10.1016/j.softx.2015.06.001}\n" + "}\n"; + + paper_count_[std::string("BouRabee2010")] = 0; + paper_url_[std::string("BouRabee2010")] = "https://doi.org/10.1137/090758842"; + paper_bibtex_[std::string("BouRabee2010")] = + "\n" + "@article{BouRabee2010,\n" + " doi = {10.1137/090758842},\n" + " url = {https://doi.org/10.1137/090758842},\n" + " year = {2010},\n" + " volume = {48},\n" + " number = {1},\n" + " pages = {278--297},\n" + " author = {Nawaf Bou-Rabee and Houman Owhadi},\n" + " title = {Long-Run Accuracy of Variational Integrators in the Stochastic Context},\n" + " journal = {{SIAM} Journal on Numerical Analysis}\n" + "}\n"; + + paper_count_[std::string("Chen2021")] = 0; + paper_url_[std::string("Chen2021")] = "https://doi.org/10.1021/acs.jctc.1c00103"; + paper_bibtex_[std::string("Chen2021")] = + "\n" + "@article{Chen2021,\n" + " author = {Chen, Haochuan and Fu, Haohao and Chipot, Christophe and Shao, Xueguang and Cai, Wensheng},\n" + " title = {Overcoming free-energy barriers with a seamless combination of a biasing force and a collective variable-independent boost potential},\n" + " journal = {J. Chem. Theory Comput.},\n" + " year = {2021},\n" + " volume = {17},\n" + " number = {7},\n" + " pages = {3886--3894},\n" + " doi = {10.1021/acs.jctc.1c00103},\n" + " url = {https://doi.org/10.1021/acs.jctc.1c00103}\n" + "}\n"; + + paper_count_[std::string("Chen2022")] = 0; + paper_url_[std::string("Chen2022")] = "https://doi.org/10.1021/acs.jcim.1c01010"; + paper_bibtex_[std::string("Chen2022")] = + "\n" + "@article{Chen2022,\n" + " author = {Chen, Haochuan and Liu, Han and Feng, Heying and Fu, Haohao and Cai, Wensheng and Shao, Xueguang and Chipot, Christophe},\n" + " title = {MLCV: {Bridging} {Machine-Learning-Based} {Dimensionality} {Reduction} and {Free-Energy} {Calculation}},\n" + " journal = {J. Chem. Inf. Model.},\n" + " volume = {62},\n" + " number = {1},\n" + " pages = {1-8},\n" + " year = {2022},\n" + " doi = {10.1021/acs.jcim.1c01010},\n" + " URL = {https://doi.org/10.1021/acs.jcim.1c01010}\n" + "}\n"; + + paper_count_[std::string("Comer2014c")] = 0; + paper_url_[std::string("Comer2014c")] = "https://doi.org/10.1021/ct500874p"; + paper_bibtex_[std::string("Comer2014c")] = + "\n" + "@article{Comer2014c,\n" + " author = {Comer, Jeffrey and Phillips, James C.{} and Schulten, Klaus and Chipot, Christophe},\n" + " title = {Multiple-walker strategies for free-energy calculations in {NAMD}: {Shared} adaptive biasing force and walker selection rules},\n" + " journal = {J. Chem. Theor. Comput.},\n" + " year = {2014},\n" + " volume = {10},\n" + " number = {12},\n" + " pages = {5276--5285},\n" + " doi = {10.1021/ct500874p},\n" + " pmid = {26583211},\n" + " url = {https://doi.org/10.1021/ct500874p}\n" + "}\n"; + + paper_count_[std::string("Ebrahimi2022")] = 0; + paper_url_[std::string("Ebrahimi2022")] = "https://doi.org/10.1021/acs.jctc.1c01235"; + paper_bibtex_[std::string("Ebrahimi2022")] = + "\n" + "@article {Ebrahimi2022,\n" + " author = {Ebrahimi, Mina and H\\'enin, J\\'er\\^ome},\n" + " title = {Symmetry-Adapted Restraints for Binding Free Energy Calculations},\n" + " journal = {Journal of Chemical Theory and Computation},\n" + " volume = {18},\n" + " number = {4},\n" + " pages = {2494-2502},\n" + " year = {2022},\n" + " doi = {10.1021/acs.jctc.1c01235},\n" + " url = {https://doi.org/10.1021/acs.jctc.1c01235}\n" + "}\n"; + + paper_count_[std::string("Fiorin2013")] = 0; + paper_url_[std::string("Fiorin2013")] = "https://doi.org/10.1080/00268976.2013.813594"; + paper_bibtex_[std::string("Fiorin2013")] = + "\n" + "@article{Fiorin2013,\n" + " author = {Fiorin, Giacomo and Klein, Michael L.{} and H\\'enin, J\\'er\\^ome},\n" + " title = {Using collective variables to drive molecular dynamics simulations},\n" + " journal = {Mol. Phys.},\n" + " year = {2013},\n" + " volume = {111},\n" + " number = {22-23},\n" + " pages = {3345--3362},\n" + " publisher = {Taylor & Francis},\n" + " doi = {10.1080/00268976.2013.813594},\n" + " url = {https://doi.org/10.1080/00268976.2013.813594}\n" + "}\n"; + + paper_count_[std::string("Fiorin2020")] = 0; + paper_url_[std::string("Fiorin2020")] = "https://doi.org/10.1002/jcc.26075"; + paper_bibtex_[std::string("Fiorin2020")] = + "\n" + "@article{Fiorin2020,\n" + " author = {Fiorin, Giacomo and Marinelli, Fabrizio and {Faraldo-G\\'omez}, Jos\\'e D.},\n" + " title = {Direct Derivation of Free Energies of Membrane Deformation and Other Solvent Density Variations From Enhanced Sampling Molecular Dynamics},\n" + " journal = {J. Comp. Chem.},\n" + " year = {2020},\n" + " volume = {41},\n" + " number = {5},\n" + " pages = {449--459},\n" + " doi = {10.1002/jcc.26075},\n" + " pmid = {31602694},\n" + " url = {https://doi.org/10.1002/jcc.26075}\n" + "}\n"; + + paper_count_[std::string("Fu2016")] = 0; + paper_url_[std::string("Fu2016")] = "https://doi.org/10.1021/acs.jctc.6b00447"; + paper_bibtex_[std::string("Fu2016")] = + "\n" + "@article{Fu2016,\n" + " author = {Fu, Haohao and Shao, Xueguang and Chipot, Christophe and Cai, Wensheng},\n" + " title = {Extended adaptive biasing force algorithm. {An} on--the--fly implementation for accurate free--energy calculations},\n" + " journal = {J. Chem. Theory Comput.},\n" + " year = {2016},\n" + " volume = {12},\n" + " number = {8},\n" + " pages = {3506-–3513},\n" + " pmid = {27398726},\n" + " doi = {10.1021/acs.jctc.6b00447},\n" + " pmid = {27398726},\n" + " url = {https://doi.org/10.1021/acs.jctc.6b00447}\n" + "}\n"; + + paper_count_[std::string("Fu2017")] = 0; + paper_url_[std::string("Fu2017")] = "https://doi.org/10.1021/acs.jctc.7b00791"; + paper_bibtex_[std::string("Fu2017")] = + "\n" + "@article{Fu2017,\n" + " author = {Fu, Haohao and Cai, Wensheng and H\\'enin, J\\'er\\^ome and Roux, Beno\\^it and Chipot, Christophe},\n" + " title = {New Coarse Variables for the Accurate Determination of Standard Binding Free Energies},\n" + " journal = {J. Chem. Theory. Comput.},\n" + " year = {2017},\n" + " volume = {13},\n" + " number = {11},\n" + " pages = {5173-5178},\n" + " doi = {10.1021/acs.jctc.7b00791},\n" + " pmid = {28965398},\n" + " url = {https://doi.org/10.1021/acs.jctc.7b00791}\n" + "}\n"; + + paper_count_[std::string("Garate2019")] = 0; + paper_url_[std::string("Garate2019")] = "https://doi.org/10.1021/acs.jpcb.8b09374"; + paper_bibtex_[std::string("Garate2019")] = + "\n" + "@article{Garate2019,\n" + " author = {Garate, Jos\\'e Antonio and Bernardin, Alejandro and Escalona, Yerko and Yanez, Carlos and English, Niall J.{} and {Perez-Acle}, Tomas},\n" + " title = {Orientational and Folding Thermodynamics via Electric Dipole Moment Restraining},\n" + " journal = {J. Phys. Chem. {B}},\n" + " year = {2019},\n" + " volume = {123},\n" + " number = {12},\n" + " pages = {2599--2608},\n" + " doi = {10.1021/acs.jpcb.8b09374},\n" + " pmid = {30831028},\n" + " url = {https://doi.org/10.1021/acs.jpcb.8b09374}\n" + "}\n"; + + paper_count_[std::string("Henin2010")] = 0; + paper_url_[std::string("Henin2010")] = "https://doi.org/10.1021/ct9004432"; + paper_bibtex_[std::string("Henin2010")] = + "\n" + "@article{Henin2010,\n" + " author = {H\\'enin, J\\'er\\^ome and Fiorin, Giacomo and Chipot, Christophe and Klein, Michael L.},\n" + " title = {Exploring multidimensional free energy landscapes using time-dependent biases on collective variables},\n" + " journal = {J. Chem. Theory Comput.},\n" + " year = {2010},\n" + " volume = {6},\n" + " pages = {35-47},\n" + " number = {1},\n" + " doi = {10.1021/ct9004432},\n" + " pmid = {26614317},\n" + " url = {https://doi.org/10.1021/ct9004432}\n" + "}\n"; + + paper_count_[std::string("Henin2021")] = 0; + paper_url_[std::string("Henin2021")] = "https://doi.org/10.1021/acs.jctc.1c00593"; + paper_bibtex_[std::string("Henin2021")] = + "\n" + "@Article{Henin2021,\n" + " author = {H\\'enin, J.},\n" + " journal = {J. Chem. Theory Comput.},\n" + " title = {Fast and accurate multidimensional free energy integration},\n" + " year = {2021},\n" + " doi = {10.1021/acs.jctc.1c00593},\n" + " url = {https://doi.org/10.1021/acs.jctc.1c00593},\n" + "}\n"; + + paper_count_[std::string("Humphrey1996")] = 0; + paper_url_[std::string("Humphrey1996")] = "https://doi.org/10.1016/0263-7855(96)00018-5"; + paper_bibtex_[std::string("Humphrey1996")] = + "\n" + "@article{Humphrey1996,\n" + " title = {{VMD}: visual molecular dynamics},\n" + " author = {Humphrey, William and Dalke, Andrew and Schulten, Klaus},\n" + " journal = {J. Mol. Graph.},\n" + " year = {1996},\n" + " volume = {14},\n" + " number = {1},\n" + " pages = {33--38},\n" + " doi = {10.1016/0263-7855(96)00018-5},\n" + " url = {https://doi.org/10.1016/0263-7855(96)00018-5}\n" + "}\n"; + + paper_count_[std::string("Lesage2017")] = 0; + paper_url_[std::string("Lesage2017")] = "https://doi.org/10.1021/acs.jpcb.6b10055"; + paper_bibtex_[std::string("Lesage2017")] = + "\n" + "@article{Lesage2017,\n" + " author = {Lesage, Adrien and Leli\\`evre, Tony and Stoltz, Gabriel and H\\'enin, J\\'er\\^ome},\n" + " title = {Smoothed biasing forces yield unbiased free energies with the extended-system adaptive biasing force method},\n" + " journal = {J. Phys. Chem. {B}},\n" + " year = {2017},\n" + " volume = {121},\n" + " number = {15},\n" + " pages = {3676-3685},\n" + " doi = {10.1021/acs.jpcb.6b10055},\n" + " pmid = {27959559},\n" + " url = {https://doi.org/10.1021/acs.jpcb.6b10055}\n" + "}\n"; + + paper_count_[std::string("Marinelli2015")] = 0; + paper_url_[std::string("Marinelli2015")] = "https://doi.org/10.1016/j.bpj.2015.05.024"; + paper_bibtex_[std::string("Marinelli2015")] = + "\n" + "@article{Marinelli2015,\n" + " author = {Marinelli, Fabrizio and Faraldo-G\\'omez, Jos\\'e D.},\n" + " title = {Ensemble-Biased Metadynamics: A Molecular Simulation Method to Sample Experimental Distributions},\n" + " journal = {Biophys. J.},\n" + " year = {2015},\n" + " volume = {108},\n" + " number = {12},\n" + " pages = {2779--2782},\n" + " doi = {10.1016/j.bpj.2015.05.024},\n" + " pmid = {26083917},\n" + " url = {https://doi.org/10.1016/j.bpj.2015.05.024}\n" + "}\n"; + + paper_count_[std::string("Phillips2020")] = 0; + paper_url_[std::string("Phillips2020")] = "https://doi.org/10.1063/5.0014475"; + paper_bibtex_[std::string("Phillips2020")] = + "\n" + "@article{Phillips2020,\n" + " author = {Phillips, James C.{} and Hardy, David J.{} and Maia, Julio D. C.{} and Stone, John E.{} and Ribeiro, Jo\\~ao V.{} and Bernardi, Rafael C.{} and Buch, Ronak and Fiorin, Giacomo and H\\'enin, J\\'er\\^ome and Jiang, Wei and McGreevy, Ryan and Melo, Marcelo C. R.{} and Radak, Brian K.{} and Skeel, Robert D.{} and Singharoy, Abhishek and Wang, Yi and Roux, Beno\\^it and Aksimentiev, Aleksei and Luthey-Schulten, Zaida and Kal\\'e, Laxmikant V.{} and Schulten, Klaus and Chipot, Christophe and Tajkhorshid, Emad},\n" + " title = {Scalable molecular dynamics on {CPU} and {GPU} architectures with {NAMD}},\n" + " journal = {J. Chem. Phys.},\n" + " year = {2020},\n" + " volume = {153},\n" + " number = {4},\n" + " pages = {044130},\n" + " doi = {10.1063/5.0014475},\n" + " pmid = {32752662},\n" + " url = {https://doi.org/10.1063/5.0014475}\n" + "}\n"; + + paper_count_[std::string("Thompson2022")] = 0; + paper_url_[std::string("Thompson2022")] = "https://doi.org/10.1016/j.cpc.2021.108171"; + paper_bibtex_[std::string("Thompson2022")] = + "\n" + "@article{Thompson2022,\n" + " title = {{LAMMPS} - a flexible simulation tool for particle-based materials modeling at the atomic, meso, and continuum scales},\n" + " author = {Thompson, Aidan P. and Aktulga, H. Metin and Berger, Richard and Bolintineanu, Dan S. and Brown, W. Michael and Crozier, Paul S. and {in't Veld}, Pieter J. and Kohlmeyer, Axel and Moore, Stan G. and Nguyen, Trung Dac and Shan, Ray and Stevens, Mark J. and Tranchida, Julien and Trott, Christian and Plimpton, Steven J.},\n" + " journal = {Comp. Phys. Comm.},\n" + " volume = {271},\n" + " pages = {108171},\n" + " year = {2022},\n" + " doi = {10.1016/j.cpc.2021.108171},\n" + " url = {https://doi.org/10.1016/j.cpc.2021.108171}\n" + "}\n"; + + paper_count_[std::string("Shen2015")] = 0; + paper_url_[std::string("Shen2015")] = "https://doi.org/10.1371/journal.pcbi.1004368"; + paper_bibtex_[std::string("Shen2015")] = + "\n" + "@article{Shen2015,\n" + " title = {Structural refinement of proteins by restrained molecular dynamics simulations with non-interacting molecular fragments},\n" + " author = {Shen, Rong and Han, Wei and Fiorin, Giacomo and Islam, Shahidul M and Schulten, Klaus and Roux, Beno{\\^\\i}t},\n" + " journal = {{PLoS} Comput. Biol.},\n" + " volume = {11},\n" + " year = {2015},\n" + " number = {10},\n" + " pages = {e1004368},\n" + " doi = {10.1371/journal.pcbi.1004368},\n" + " pmid = {26505197},\n" + " url = {https://doi.org/10.1371/journal.pcbi.1004368}\n" + "}\n"; + + paper_count_[std::string("Wells2007")] = 0; + paper_url_[std::string("Wells2007")] = "https://doi.org/10.1063/1.2770738"; + paper_bibtex_[std::string("Wells2007")] = + "\n" + "@article{Wells2007,\n" + " author = {Wells, David B. and Abramkina, Volha and Aksimentiev, Aleksei},\n" + " title = {Exploring transmembrane transport through $\\alpha$-hemolysin with grid-steered molecular dynamics},\n" + " journal = {J. Chem. Phys.},\n" + " year = {2007},\n" + " volume = {127},\n" + " number = {12},\n" + " pages = {125101},\n" + " doi = {10.1063/1.2770738},\n" + " pmid = {17902937},\n" + " url = {https://doi.org/10.1063/1.2770738}\n" + "}\n"; + + paper_count_[std::string("White2014")] = 0; + paper_url_[std::string("White2014")] = "https://doi.org/10.1021/ct500320c"; + paper_bibtex_[std::string("White2014")] = + "\n" + "@article{White2014,\n" + " author = {White, Andrew D.{} and Voth, Gregory A.{}},\n" + " title = {Efficient and minimal method to bias molecular simulations with experimental data},\n" + " journal = {J. Chem. Theory Comput.},\n" + " year = {2014},\n" + " volume = {10},\n" + " number = {8},\n" + " pages = {3023-–3030},\n" + " doi = {10.1021/ct500320c},\n" + " pmid = {26588273},\n" + " url = {https://doi.org/10.1021/ct500320c}\n" + "}\n"; + + paper_count_[std::string("n/a")] = 0; + paper_url_[std::string("n/a")] = ""; + paper_bibtex_[std::string("n/a")] = ""; + + feature_count_[std::string("GROMACS engine")] = 0; + feature_paper_map_[std::string("GROMACS engine")] = "Abraham2015"; + + feature_count_[std::string("BAOA integrator")] = 0; + feature_paper_map_[std::string("BAOA integrator")] = "BouRabee2010"; + + feature_count_[std::string("reweightaMD colvar bias implementation (NAMD)")] = 0; + feature_paper_map_[std::string("reweightaMD colvar bias implementation (NAMD)")] = "Chen2021"; + + feature_count_[std::string("neuralNetwork colvar component")] = 0; + feature_paper_map_[std::string("neuralNetwork colvar component")] = "Chen2022"; + + feature_count_[std::string("Multiple-walker ABF implementation")] = 0; + feature_paper_map_[std::string("Multiple-walker ABF implementation")] = "Comer2014c"; + + feature_count_[std::string("Symmetry-adapted RMSD")] = 0; + feature_paper_map_[std::string("Symmetry-adapted RMSD")] = "Ebrahimi2022"; + + feature_count_[std::string("Colvars module")] = 0; + feature_paper_map_[std::string("Colvars module")] = "Fiorin2013"; + + feature_count_[std::string("Colvars-NAMD interface")] = 0; + feature_paper_map_[std::string("Colvars-NAMD interface")] = "Fiorin2013"; + + feature_count_[std::string("Colvars-LAMMPS interface")] = 0; + feature_paper_map_[std::string("Colvars-LAMMPS interface")] = "Fiorin2013"; + + feature_count_[std::string("Colvars-VMD interface (command line)")] = 0; + feature_paper_map_[std::string("Colvars-VMD interface (command line)")] = "Fiorin2013"; + + feature_count_[std::string("distance colvar component")] = 0; + feature_paper_map_[std::string("distance colvar component")] = "Fiorin2013"; + + feature_count_[std::string("distanceXY colvar component (derived from distanceZ)")] = 0; + feature_paper_map_[std::string("distanceXY colvar component (derived from distanceZ)")] = "Fiorin2013"; + + feature_count_[std::string("distanceZ colvar component")] = 0; + feature_paper_map_[std::string("distanceZ colvar component")] = "Fiorin2013"; + + feature_count_[std::string("distanceVec colvar component (derived from distance)")] = 0; + feature_paper_map_[std::string("distanceVec colvar component (derived from distance)")] = "Fiorin2013"; + + feature_count_[std::string("distanceDir colvar component (derived from distance)")] = 0; + feature_paper_map_[std::string("distanceDir colvar component (derived from distance)")] = "Fiorin2013"; + + feature_count_[std::string("distanceInv colvar component")] = 0; + feature_paper_map_[std::string("distanceInv colvar component")] = "Fiorin2013"; + + feature_count_[std::string("angle colvar component")] = 0; + feature_paper_map_[std::string("angle colvar component")] = "Fiorin2013"; + + feature_count_[std::string("dihedral colvar component")] = 0; + feature_paper_map_[std::string("dihedral colvar component")] = "Fiorin2013"; + + feature_count_[std::string("coordNum colvar component")] = 0; + feature_paper_map_[std::string("coordNum colvar component")] = "Fiorin2013"; + + feature_count_[std::string("selfCoordNum colvar component")] = 0; + feature_paper_map_[std::string("selfCoordNum colvar component")] = "Fiorin2013"; + + feature_count_[std::string("groupCoord colvar component (derived from distance)")] = 0; + feature_paper_map_[std::string("groupCoord colvar component (derived from distance)")] = "Fiorin2013"; + + feature_count_[std::string("hBond colvar component")] = 0; + feature_paper_map_[std::string("hBond colvar component")] = "Fiorin2013"; + + feature_count_[std::string("rmsd colvar component")] = 0; + feature_paper_map_[std::string("rmsd colvar component")] = "Fiorin2013"; + + feature_count_[std::string("eigenvector colvar component")] = 0; + feature_paper_map_[std::string("eigenvector colvar component")] = "Fiorin2013"; + + feature_count_[std::string("gyration colvar component")] = 0; + feature_paper_map_[std::string("gyration colvar component")] = "Fiorin2013"; + + feature_count_[std::string("inertia colvar component (derived from gyration)")] = 0; + feature_paper_map_[std::string("inertia colvar component (derived from gyration)")] = "Fiorin2013"; + + feature_count_[std::string("inertiaZ colvar component (derived from inertia)")] = 0; + feature_paper_map_[std::string("inertiaZ colvar component (derived from inertia)")] = "Fiorin2013"; + + feature_count_[std::string("orientation colvar component")] = 0; + feature_paper_map_[std::string("orientation colvar component")] = "Fiorin2013"; + + feature_count_[std::string("Moving frame of reference")] = 0; + feature_paper_map_[std::string("Moving frame of reference")] = "Fiorin2013"; + + feature_count_[std::string("Optimal rotation via flexible fitting")] = 0; + feature_paper_map_[std::string("Optimal rotation via flexible fitting")] = "Fiorin2013"; + + feature_count_[std::string("orientationAngle colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("orientationAngle colvar component (derived from orientation)")] = "Fiorin2013"; + + feature_count_[std::string("orientationProj colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("orientationProj colvar component (derived from orientation)")] = "Fiorin2013"; + + feature_count_[std::string("spinAngle colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("spinAngle colvar component (derived from orientation)")] = "Fiorin2013"; + + feature_count_[std::string("tilt colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("tilt colvar component (derived from orientation)")] = "Fiorin2013"; + + feature_count_[std::string("alpha colvar component")] = 0; + feature_paper_map_[std::string("alpha colvar component")] = "Fiorin2013"; + + feature_count_[std::string("dihedralPC colvar component")] = 0; + feature_paper_map_[std::string("dihedralPC colvar component")] = "Fiorin2013"; + + feature_count_[std::string("cartesian colvar component")] = 0; + feature_paper_map_[std::string("cartesian colvar component")] = "Fiorin2013"; + + feature_count_[std::string("Linear and polynomial combination of colvar components")] = 0; + feature_paper_map_[std::string("Linear and polynomial combination of colvar components")] = "Fiorin2013"; + + feature_count_[std::string("Metadynamics colvar bias implementation")] = 0; + feature_paper_map_[std::string("Metadynamics colvar bias implementation")] = "Fiorin2013"; + + feature_count_[std::string("Multiple-walker metadynamics colvar bias implementation")] = 0; + feature_paper_map_[std::string("Multiple-walker metadynamics colvar bias implementation")] = "Fiorin2013"; + + feature_count_[std::string("Harmonic colvar bias implementation")] = 0; + feature_paper_map_[std::string("Harmonic colvar bias implementation")] = "Fiorin2013"; + + feature_count_[std::string("harmonicWalls colvar bias implementation")] = 0; + feature_paper_map_[std::string("harmonicWalls colvar bias implementation")] = "Fiorin2013"; + + feature_count_[std::string("Linear colvar bias implementation")] = 0; + feature_paper_map_[std::string("Linear colvar bias implementation")] = "Fiorin2013"; + + feature_count_[std::string("Histogram colvar bias implementation")] = 0; + feature_paper_map_[std::string("Histogram colvar bias implementation")] = "Fiorin2013"; + + feature_count_[std::string("mapTotal colvar component")] = 0; + feature_paper_map_[std::string("mapTotal colvar component")] = "Fiorin2020"; + + feature_count_[std::string("Volumetric map-based collective variables")] = 0; + feature_paper_map_[std::string("Volumetric map-based collective variables")] = "Fiorin2020"; + + feature_count_[std::string("Multi-Map collective variables")] = 0; + feature_paper_map_[std::string("Multi-Map collective variables")] = "Fiorin2020"; + + feature_count_[std::string("Umbrella-integration eABF estimator")] = 0; + feature_paper_map_[std::string("Umbrella-integration eABF estimator")] = "Fu2016"; + + feature_count_[std::string("polarTheta colvar component")] = 0; + feature_paper_map_[std::string("polarTheta colvar component")] = "Fu2017"; + + feature_count_[std::string("polarPhi colvar component")] = 0; + feature_paper_map_[std::string("polarPhi colvar component")] = "Fu2017"; + + feature_count_[std::string("eulerPhi colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("eulerPhi colvar component (derived from orientation)")] = "Fu2017"; + + feature_count_[std::string("eulerTheta colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("eulerTheta colvar component (derived from orientation)")] = "Fu2017"; + + feature_count_[std::string("eulerPsi colvar component (derived from orientation)")] = 0; + feature_paper_map_[std::string("eulerPsi colvar component (derived from orientation)")] = "Fu2017"; + + feature_count_[std::string("dipoleAngle colvar component")] = 0; + feature_paper_map_[std::string("dipoleAngle colvar component")] = "Garate2019"; + + feature_count_[std::string("dipoleMagnitude colvar component")] = 0; + feature_paper_map_[std::string("dipoleMagnitude colvar component")] = "Garate2019"; + + feature_count_[std::string("ABF colvar bias implementation")] = 0; + feature_paper_map_[std::string("ABF colvar bias implementation")] = "Henin2010"; + + feature_count_[std::string("Internal-forces free energy estimator")] = 0; + feature_paper_map_[std::string("Internal-forces free energy estimator")] = "Henin2010"; + + feature_count_[std::string("Poisson integration of 2D/3D free energy surfaces")] = 0; + feature_paper_map_[std::string("Poisson integration of 2D/3D free energy surfaces")] = "Henin2021"; + + feature_count_[std::string("VMD engine")] = 0; + feature_paper_map_[std::string("VMD engine")] = "Humphrey1996"; + + feature_count_[std::string("eABF implementation")] = 0; + feature_paper_map_[std::string("eABF implementation")] = "Lesage2017"; + + feature_count_[std::string("CZAR eABF estimator")] = 0; + feature_paper_map_[std::string("CZAR eABF estimator")] = "Lesage2017"; + + feature_count_[std::string("Ensemble-biased metadynamics (ebMetaD)")] = 0; + feature_paper_map_[std::string("Ensemble-biased metadynamics (ebMetaD)")] = "Marinelli2015"; + + feature_count_[std::string("NAMD engine")] = 0; + feature_paper_map_[std::string("NAMD engine")] = "Phillips2020"; + + feature_count_[std::string("Scalable center-of-mass computation (NAMD)")] = 0; + feature_paper_map_[std::string("Scalable center-of-mass computation (NAMD)")] = "Phillips2020"; + + feature_count_[std::string("LAMMPS engine")] = 0; + feature_paper_map_[std::string("LAMMPS engine")] = "Thompson2022"; + + feature_count_[std::string("distancePairs colvar component")] = 0; + feature_paper_map_[std::string("distancePairs colvar component")] = "Shen2015"; + + feature_count_[std::string("histogramRestraint colvar bias implementation")] = 0; + feature_paper_map_[std::string("histogramRestraint colvar bias implementation")] = "Shen2015"; + + feature_count_[std::string("GridForces volumetric map implementation for NAMD")] = 0; + feature_paper_map_[std::string("GridForces volumetric map implementation for NAMD")] = "Wells2007"; + + feature_count_[std::string("ALB colvar bias implementation")] = 0; + feature_paper_map_[std::string("ALB colvar bias implementation")] = "White2014"; + + feature_count_[std::string("Colvars-GROMACS interface")] = 0; + feature_paper_map_[std::string("Colvars-GROMACS interface")] = "n/a"; + + feature_count_[std::string("gspath colvar component")] = 0; + feature_paper_map_[std::string("gspath colvar component")] = "n/a"; + + feature_count_[std::string("gzpath colvar component")] = 0; + feature_paper_map_[std::string("gzpath colvar component")] = "n/a"; + + feature_count_[std::string("linearCombination colvar component")] = 0; + feature_paper_map_[std::string("linearCombination colvar component")] = "n/a"; + + feature_count_[std::string("gspathCV colvar component")] = 0; + feature_paper_map_[std::string("gspathCV colvar component")] = "n/a"; + + feature_count_[std::string("gzpathCV colvar component")] = 0; + feature_paper_map_[std::string("gzpathCV colvar component")] = "n/a"; + + feature_count_[std::string("aspathCV colvar component")] = 0; + feature_paper_map_[std::string("aspathCV colvar component")] = "n/a"; + + feature_count_[std::string("azpathCV colvar component")] = 0; + feature_paper_map_[std::string("azpathCV colvar component")] = "n/a"; + + feature_count_[std::string("coordNum pairlist")] = 0; + feature_paper_map_[std::string("coordNum pairlist")] = "n/a"; + + feature_count_[std::string("Custom functions (Lepton)")] = 0; + feature_paper_map_[std::string("Custom functions (Lepton)")] = "n/a"; + + feature_count_[std::string("Scripted functions (Tcl)")] = 0; + feature_paper_map_[std::string("Scripted functions (Tcl)")] = "n/a"; diff --git a/src/external/colvars/colvarmodule_utils.h b/src/external/colvars/colvarmodule_utils.h new file mode 100644 index 00000000000..a7004edd92b --- /dev/null +++ b/src/external/colvars/colvarmodule_utils.h @@ -0,0 +1,80 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#ifndef COLVARMODULE_UTILS_H +#define COLVARMODULE_UTILS_H + + +#include "colvarmodule.h" + + +template +cvm::real get_force_norm2(T const &x) +{ + return x.norm2(); +} + + +template <> +inline cvm::real get_force_norm2(cvm::real const &x) +{ + return x*x; +} + + +template +cvm::real compute_norm2_stats(std::vector const &v, + int *minmax_index = NULL) +{ + cvm::real result = 0.0; + if (flag == -1) { + // Initialize for minimum search, using approx. largest float32 value + result = 1.0e38; + } + + typename std::vector::const_iterator xi = v.begin(); + size_t i = 0; + + if (get_index) *minmax_index = -1; // Let's not assume minmax_index is initialized to -1 + + for ( ; xi != v.end(); xi++, i++) { + cvm::real const norm2 = get_force_norm2(*xi); + if (flag == 0) { + result += norm2; + } + if (flag == 1) { + if (norm2 > result) { + result = norm2; + if (get_index) *minmax_index = i; + } + } + if (flag == -1) { + if (norm2 < result) { + result = norm2; + if (get_index) *minmax_index = i; + } + } + } + + size_t const n = v.size(); + + if (flag == 0) { + if (n > 0) { + result /= cvm::real(n); + } + } + + result = cvm::sqrt(result); + + return result; +} + + +#endif diff --git a/src/external/colvars/colvarparams.cpp b/src/external/colvars/colvarparams.cpp new file mode 100644 index 00000000000..9c326572b34 --- /dev/null +++ b/src/external/colvars/colvarparams.cpp @@ -0,0 +1,111 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparams.h" + + + +colvarparams::colvarparams() +{} + + +colvarparams::~colvarparams() +{} + + +void colvarparams::register_param(std::string const ¶m_name, + void *param_ptr) +{ + param_map[param_name] = param_ptr; +} + + +void colvarparams::register_param_grad(std::string const ¶m_name, + colvarvalue *param_grad_ptr) +{ + param_grad_map[param_name] = param_grad_ptr; +} + + +int colvarparams::param_exists(std::string const ¶m_name) +{ + if (param_map.count(param_name) > 0) { + return COLVARS_OK; + } + return COLVARS_INPUT_ERROR; +} + + +std::vector colvarparams::get_param_names() +{ + std::vector result; + for (std::map::const_iterator elem = + param_map.begin(); elem != param_map.end(); elem++) { + result.push_back(elem->first); + } + return result; +} + + +std::vector colvarparams::get_param_grad_names() +{ + std::vector result; + for (std::map::const_iterator elem = + param_grad_map.begin(); elem != param_grad_map.end(); elem++) { + result.push_back(elem->first); + } + return result; +} + + +void const *colvarparams::get_param_ptr(std::string const ¶m_name) +{ + if (param_map.count(param_name) > 0) { + return param_map[param_name]; + } + cvm::error("Error: parameter \""+param_name+"\" not found.\n", COLVARS_INPUT_ERROR); + return NULL; +} + + +void const *colvarparams::get_param_grad_ptr(std::string const ¶m_name) +{ + if (param_grad_map.count(param_name) > 0) { + return param_grad_map[param_name]; + } + cvm::error("Error: gradient of parameter \""+param_name+"\" not found.\n", + COLVARS_INPUT_ERROR); + return NULL; +} + + +cvm::real colvarparams::get_param(std::string const ¶m_name) +{ + cvm::real const *ptr = + reinterpret_cast(get_param_ptr(param_name)); + return ptr != NULL ? *ptr : 0.0; +} + + +int colvarparams::set_param(std::string const ¶m_name, + void const * /* new_value */) +{ + if (param_map.count(param_name) > 0) { + return cvm::error("Error: parameter \""+param_name+"\" cannot be " + "modified.\n", COLVARS_NOT_IMPLEMENTED); + } + return cvm::error("Error: parameter \""+param_name+"\" not found.\n", + COLVARS_INPUT_ERROR); +} diff --git a/src/external/colvars/colvarparams.h b/src/external/colvars/colvarparams.h new file mode 100644 index 00000000000..1d00300a8b3 --- /dev/null +++ b/src/external/colvars/colvarparams.h @@ -0,0 +1,66 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARPARAMS_H +#define COLVARPARAMS_H + +#include + +/// \file colvarparams.h Functions to handle scalar parameters used in objects + + +class colvarparams { + + public: + + /// Whether the parameter param_name exists + int param_exists(std::string const ¶m_name); + + /// Get a copy of the names of registered parameters + virtual std::vector get_param_names(); + + /// Get a copy of the names of registered parameter gradients + virtual std::vector get_param_grad_names(); + + /// Pointer to the parameter param_name + virtual void const *get_param_ptr(std::string const ¶m_name); + + /// Pointer to the gradient of parameter param_name + virtual void const *get_param_grad_ptr(std::string const ¶m_name); + + /// Value of the parameter param_name (must be a scalar) + virtual cvm::real get_param(std::string const ¶m_name); + + /// Set the named parameter to the given value + virtual int set_param(std::string const ¶m_name, void const *new_value); + + protected: + + /// Default constructor + colvarparams(); + + /// Default destructor + virtual ~colvarparams(); + + /// Pointers to relevant parameters that may be accessed by other objects + std::map param_map; + + /// Derivatives of the object with respect to internal parameters + std::map param_grad_map; + + /// Register the given parameter + void register_param(std::string const ¶m_name, void *param_ptr); + + /// Register the gradient of the given parameter + void register_param_grad(std::string const ¶m_name, + colvarvalue *param_grad_ptr); + +}; + +#endif diff --git a/src/external/colvars/colvarparse.cpp b/src/external/colvars/colvarparse.cpp new file mode 100644 index 00000000000..c189a9e890c --- /dev/null +++ b/src/external/colvars/colvarparse.cpp @@ -0,0 +1,1021 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvars_memstream.h" + + +// space & tab +char const * const colvarparse::white_space = " \t"; + + +namespace { + + // Avoid having to put the bool assignment in the template :-( + void set_bool(void *p, bool val) + { + bool *v = reinterpret_cast(p); + *v = val; + } + +} + + +colvarparse::colvarparse() + : keyword_delimiters_left("\n"+std::string(white_space)+"}"), + keyword_delimiters_right("\n"+std::string(white_space)+"{") +{ + colvarparse::clear(); +} + + +void colvarparse::clear() +{ + config_string.clear(); + clear_keyword_registry(); +} + + +colvarparse::colvarparse(const std::string& conf) + : keyword_delimiters_left("\n"+std::string(white_space)+"}"), + keyword_delimiters_right("\n"+std::string(white_space)+"{") +{ + colvarparse::set_string(conf); +} + + +void colvarparse::set_string(std::string const &conf) +{ + if (! config_string.size()) { + colvarparse::clear(); + config_string = conf; + } +} + + +colvarparse::~colvarparse() +{ + colvarparse::clear(); +} + + + +bool colvarparse::get_key_string_value(std::string const &conf, + char const *key, std::string &data) +{ + bool b_found = false, b_found_any = false; + size_t save_pos = 0, found_count = 0; + + do { + std::string data_this = ""; + b_found = key_lookup(conf, key, &data_this, &save_pos); + if (b_found) { + if (!b_found_any) + b_found_any = true; + found_count++; + data = data_this; + } + } while (b_found); + + if (found_count > 1) { + cvm::error("Error: found more than one instance of \""+ + std::string(key)+"\".\n", COLVARS_INPUT_ERROR); + } + + return b_found_any; +} + +bool colvarparse::get_key_string_multi_value(std::string const &conf, + char const *key, std::vector& data) +{ + bool b_found = false, b_found_any = false; + size_t save_pos = 0; + + data.clear(); + + do { + std::string data_this = ""; + b_found = key_lookup(conf, key, &data_this, &save_pos); + if (b_found) { + if (!b_found_any) + b_found_any = true; + data.push_back(data_this); + } + } while (b_found); + + return b_found_any; +} + + +template +void colvarparse::mark_key_set_user(std::string const &key_str, + TYPE const &value, + Parse_Mode const &parse_mode) +{ + key_set_modes[to_lower_cppstr(key_str)] = key_set_user; + if (parse_mode & parse_echo) { + cvm::log("# "+key_str+" = "+cvm::to_str(value)+"\n", + cvm::log_user_params()); + } + if (parse_mode & parse_deprecation_warning) { + cvm::log("Warning: keyword "+key_str+ + " is deprecated. Check the documentation for the current equivalent.\n"); + } +} + + +template +void colvarparse::mark_key_set_default(std::string const &key_str, + TYPE const &def_value, + Parse_Mode const &parse_mode) +{ + key_set_modes[to_lower_cppstr(key_str)] = key_set_default; + if (parse_mode & parse_echo_default) { + cvm::log("# "+key_str+" = "+cvm::to_str(def_value)+ + " [default]\n", cvm::log_default_params()); + } +} + + +void colvarparse::error_key_required(std::string const &key_str, + Parse_Mode const &parse_mode) +{ + if (key_already_set(key_str)) { + return; + } + if (parse_mode & parse_restart) { + cvm::error("Error: keyword \""+key_str+ + "\" is missing from the restart.\n", COLVARS_INPUT_ERROR); + } else { + cvm::error("Error: keyword \""+key_str+ + "\" is required.\n", COLVARS_INPUT_ERROR); + } +} + + +template +int colvarparse::_get_keyval_scalar_value_(std::string const &key_str, + std::string const &data, + TYPE &value, + TYPE const &def_value) +{ + std::istringstream is(data); + size_t value_count = 0; + TYPE x(def_value); + + while (is >> x) { + value = x; + value_count++; + } + + if (value_count == 0) { + return cvm::error("Error: in parsing \""+ + key_str+"\".\n", COLVARS_INPUT_ERROR); + } + + if (value_count > 1) { + return cvm::error("Error: multiple values " + "are not allowed for keyword \""+ + key_str+"\".\n", COLVARS_INPUT_ERROR); + } + + return COLVARS_OK; +} + + +template<> +int colvarparse::_get_keyval_scalar_value_(std::string const &key_str, + std::string const &data, + bool &value, + bool const & /* def_value */) +{ + if ( (data == std::string("on")) || + (data == std::string("yes")) || + (data == std::string("true")) ) { + set_bool(reinterpret_cast(&value), true); + } else if ( (data == std::string("off")) || + (data == std::string("no")) || + (data == std::string("false")) ) { + set_bool(reinterpret_cast(&value), false); + } else { + return cvm::error("Error: boolean values only are allowed " + "for \""+key_str+"\".\n", COLVARS_INPUT_ERROR); + } + return COLVARS_OK; +} + + +template +int colvarparse::_get_keyval_scalar_novalue_(std::string const &key_str, + TYPE & /* value */, + Parse_Mode const & /* parse_mode */) +{ + return cvm::error("Error: improper or missing value " + "for \""+key_str+"\".\n", COLVARS_INPUT_ERROR); +} + +template<> +int colvarparse::_get_keyval_scalar_novalue_(std::string const &key_str, + bool &value, + Parse_Mode const &parse_mode) +{ + set_bool(reinterpret_cast(&value), true); + mark_key_set_user(key_str, value, parse_mode); + return COLVARS_OK; +} + + +template +bool colvarparse::_get_keyval_scalar_(std::string const &conf, + char const *key, + TYPE &value, + TYPE const &def_value, + Parse_Mode const &parse_mode) +{ + std::string const key_str(key); + + std::string data; + bool const b_found_any = get_key_string_value(conf, key, data); + + if (data.size()) { + + _get_keyval_scalar_value_(key_str, data, value, def_value); + + mark_key_set_user(key_str, value, parse_mode); + + } else { // No string value + + if (b_found_any) { + + _get_keyval_scalar_novalue_(key_str, value, parse_mode); + + } else { + + if (parse_mode & parse_required) { + if (cvm::debug()) { + cvm::log("get_keyval, parse_required = "+cvm::to_str(parse_mode & parse_required)+ + "\n"); + } + error_key_required(key_str, parse_mode); + return false; + } + + if ( (parse_mode & parse_override) || !(key_already_set(key)) ) { + value = def_value; + mark_key_set_default(key_str, value, parse_mode); + } + } + } + + return b_found_any; +} + + +template +bool colvarparse::_get_keyval_vector_(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const &parse_mode) +{ + std::string const key_str(key); + + std::string data; + bool const b_found_any = get_key_string_value(conf, key, data); + + if (data.size()) { + std::istringstream is(data); + + if (values.size() == 0) { + + std::vector x; + if (def_values.size()) { + x = def_values; + } else { + x.assign(1, TYPE()); + } + + for (size_t i = 0; + ( is >> x[ ((i> x) { + values[i] = x; + } else { + cvm::error("Error: in parsing \""+ + key_str+"\".\n", COLVARS_INPUT_ERROR); + } + } + } + + mark_key_set_user< std::vector >(key_str, values, parse_mode); + + } else { + + if (b_found_any) { + cvm::error("Error: improper or missing values for \""+ + key_str+"\".\n", COLVARS_INPUT_ERROR); + } else { + + if ((values.size() > 0) && (values.size() != def_values.size())) { + cvm::error("Error: the number of default values for \""+ + key_str+"\" is different from the number of " + "current values.\n", COLVARS_BUG_ERROR); + } + + if (parse_mode & parse_required) { + error_key_required(key_str, parse_mode); + return false; + } + + if ( (parse_mode & parse_override) || !(key_already_set(key)) ) { + for (size_t i = 0; i < values.size(); i++) { + values[i] = def_values[i]; + } + mark_key_set_default< std::vector >(key_str, def_values, + parse_mode); + } + + } + } + + return b_found_any; +} + + +// single-value keyword parsers + + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + int &value, + int const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + size_t &value, + size_t const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + long &value, + long const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + cvm::step_number &value, + cvm::step_number const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::string &value, + std::string const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + cvm::real &value, + cvm::real const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + cvm::rvector &value, + cvm::rvector const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + cvm::quaternion &value, + cvm::quaternion const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + colvarvalue &value, + colvarvalue const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + bool &value, + bool const &def_value, + Parse_Mode const parse_mode) +{ + return _get_keyval_scalar_(conf, key, value, def_value, parse_mode); +} + + +// multiple-value keyword parsers + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + +bool colvarparse::get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const parse_mode) +{ + return _get_keyval_vector_(conf, key, values, def_values, parse_mode); +} + + +void colvarparse::add_keyword(char const *key) +{ + std::string const key_str_lower(to_lower_cppstr(std::string(key))); + + if (key_set_modes.find(key_str_lower) != key_set_modes.end()) { + return; + } + + key_set_modes[key_str_lower] = key_not_set; + + allowed_keywords.push_back(key_str_lower); +} + + +bool colvarparse::key_already_set(std::string const &key_str) +{ + std::string const key_str_lower(to_lower_cppstr(key_str)); + + if (key_set_modes.find(key_str_lower) == key_set_modes.end()) { + return false; + } + + return (key_set_modes[key_str_lower] > 0); +} + + +void colvarparse::strip_values(std::string &conf) +{ + size_t offset = 0; + data_begin_pos.sort(); + data_end_pos.sort(); + std::list::iterator data_begin_pos_last = std::unique(data_begin_pos.begin(), data_begin_pos.end()); + data_begin_pos.erase(data_begin_pos_last, data_begin_pos.end()); + std::list::iterator data_end_pos_last = std::unique(data_end_pos.begin(), data_end_pos.end()); + data_end_pos.erase(data_end_pos_last, data_end_pos.end()); + + std::list::iterator data_begin = data_begin_pos.begin(); + std::list::iterator data_end = data_end_pos.begin(); + + for ( ; (data_begin != data_begin_pos.end()) && + (data_end != data_end_pos.end()) ; + data_begin++, data_end++) { + size_t const nchars = *data_end-*data_begin; + conf.erase(*data_begin - offset, nchars); + offset += nchars; + } +} + + +void colvarparse::clear_keyword_registry() +{ + key_set_modes.clear(); + allowed_keywords.clear(); + data_begin_pos.clear(); + data_end_pos.clear(); +} + + +int colvarparse::check_keywords(std::string &conf, char const *key) +{ + if (cvm::debug()) + cvm::log("Configuration string for \""+std::string(key)+ + "\": \"\n"+conf+"\".\n"); + + strip_values(conf); + // after stripping, the config string has either empty lines, or + // lines beginning with a keyword + + std::string line; + std::istringstream is(conf); + while (cvm::getline(is, line)) { + if (line.size() == 0) + continue; + if (line.find_first_not_of(white_space) == + std::string::npos) + continue; + + std::string uk; + std::istringstream line_is(line); + line_is >> uk; + // if (cvm::debug()) + // cvm::log ("Checking the validity of \""+uk+"\" from line:\n" + line); + uk = to_lower_cppstr(uk); + + bool found_keyword = false; + for (std::list::iterator ki = allowed_keywords.begin(); + ki != allowed_keywords.end(); ki++) { + if (uk == *ki) { + found_keyword = true; + break; + } + } + if (!found_keyword) { + cvm::error("Error: keyword \""+uk+"\" is not supported, " + "or not recognized in this context.\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + } + + clear_keyword_registry(); + + return COLVARS_OK; +} + + +std::istream & colvarparse::read_config_line(std::istream &is, + std::string &line) +{ + cvm::getline(is, line); + config_string += line+'\n'; + size_t const comment = line.find('#'); + if (comment != std::string::npos) { + line.erase(comment); + } + return is; +} + + +std::istream & colvarparse::getline_nocomments(std::istream &is, + std::string &line) +{ + cvm::getline(is, line); + size_t const comment = line.find('#'); + if (comment != std::string::npos) { + line.erase(comment); + } + return is; +} + + +bool colvarparse::key_lookup(std::string const &conf, + char const *key_in, + std::string *data, + size_t *save_pos) +{ + if (cvm::debug()) { + cvm::log("Looking for the keyword \""+std::string(key_in)+ + "\" and its value.\n"); + } + + // add this keyword to the register (in its camelCase version) + add_keyword(key_in); + + // use the lowercase version from now on + std::string const key(to_lower_cppstr(key_in)); + + // "conf_lower" is only used to lookup the keyword, but its value + // will be read from "conf", in order not to mess up file names + std::string const conf_lower(to_lower_cppstr(conf)); + + // by default, there is no value, unless we found one + if (data != NULL) { + data->clear(); + } + + // start from the first occurrence of key + size_t pos = conf_lower.find(key, (save_pos != NULL) ? *save_pos : 0); + + // iterate over all instances of the substring until it finds it as isolated keyword + while (true) { + + if (pos == std::string::npos) { + // no valid instance of the keyword has been found + if (cvm::debug()) { + cvm::log("Keyword \""+std::string(key_in)+"\" not found.\n"); + } + return false; + } + + bool b_isolated_left = true, b_isolated_right = true; + + if (pos > 0) { + if (keyword_delimiters_left.find(conf[pos-1]) == std::string::npos) { + // none of the valid delimiting characters is on the left of key + b_isolated_left = false; + } else { + size_t const pl = conf_lower.rfind("\n", pos); + size_t const line_begin = (pl == std::string::npos) ? 0 : pl+1; + size_t const pchar = + conf_lower.find_first_not_of(keyword_delimiters_left, line_begin); + size_t const first_text = (pchar == std::string::npos) ? pos : pchar; + if (first_text < pos) { + // There are some non-delimiting characters to the left of the + // keyword on the same line + b_isolated_left = false; + } + } + } + + if (pos < conf.size()-key.size()-1) { + if (keyword_delimiters_right.find(conf[pos+key.size()]) == + std::string::npos) { + // none of the valid delimiting characters is on the right of key + b_isolated_right = false; + } + } + + // check that there are matching braces between here and the end of conf + bool const b_not_within_block = (check_braces(conf, pos) == COLVARS_OK); + + bool const b_isolated = (b_isolated_left && b_isolated_right && + b_not_within_block); + + if (b_isolated) { + // found it + break; + } else { + // try the next occurrence of key + pos = conf_lower.find(key, pos+key.size()); + } + } + + if (save_pos != NULL) { + // save the pointer for a future call (when iterating over multiple + // valid instances of the same keyword) + *save_pos = pos + key.size(); + } + + // get the remainder of the line + size_t pl = conf.rfind("\n", pos); + size_t line_begin = (pl == std::string::npos) ? 0 : pl+1; + size_t nl = conf.find("\n", pos); + size_t line_end = (nl == std::string::npos) ? conf.size() : nl; + std::string line(conf, line_begin, (line_end-line_begin)); + + size_t data_begin = (to_lower_cppstr(line)).find(key) + key.size(); + data_begin = line.find_first_not_of(white_space, data_begin+1); + + if (data_begin != std::string::npos) { + + size_t data_end = line.find_last_not_of(white_space) + 1; + data_end = (data_end == std::string::npos) ? line.size() : data_end; + + size_t brace = line.find('{', data_begin); // look for an opening brace + size_t brace_last = brace; + + if (brace != std::string::npos) { + + // find the matching closing brace + +// if (cvm::debug()) { +// cvm::log("Multi-line value, config is now \""+line+"\".\n"); +// } + + int brace_count = 1; + + while (brace_count > 0) { + + brace = line.find_first_of("{}", brace_last+1); + // find all braces within this line + while (brace < std::string::npos) { + brace_last = brace; + if (line[brace] == '{') brace_count++; + if (line[brace] == '}') brace_count--; + if (brace_count == 0) { + break; + } + brace = line.find_first_of("{}", brace+1); + } + + if (brace_count == 0) { + break; + } + + if (brace == std::string::npos) { + + // add a new line + if (line_end >= conf.size()) { + cvm::error("Parse error: reached the end while " + "looking for closing brace; until now " + "the following was parsed: \"\n"+ + line+"\".\n", COLVARS_INPUT_ERROR); + return false; + } + + line_begin = line_end; + nl = conf.find('\n', line_begin+1); + if (nl == std::string::npos) + line_end = conf.size(); + else + line_end = nl; + line.append(conf, line_begin, (line_end-line_begin)); + +// if (cvm::debug()) { +// cvm::log("Added a new line, config is now \""+line+"\".\n"); +// } + } + + if (brace_count < 0) { + cvm::error("Error: found closing brace without opening brace.\n", COLVARS_INPUT_ERROR); + } + } + + // strip the leading and trailing braces + data_begin = line.find_first_of('{') + 1; + data_begin = line.find_first_not_of(white_space, + data_begin); + + data_end = line.find_last_of('}', line.size()) - 1; + data_end = line.find_last_not_of(white_space, + data_end) + 1; + } + + if (data != NULL) { + data->append(line, data_begin, (data_end-data_begin)); + + if (cvm::debug()) { + cvm::log("Keyword value = \""+*data+"\".\n"); + } + + if (data->size()) { + data_begin_pos.push_back(conf.find(*data, pos+key.size())); + data_end_pos.push_back(data_begin_pos.back()+data->size()); + } + } + } + + if (save_pos != NULL) *save_pos = line_end; + + return true; +} + + +colvarparse::read_block::read_block(std::string const &key_in, + std::string *data_in) + : key(key_in), data(data_in) +{ +} + + +colvarparse::read_block::~read_block() +{} + + +std::istream & operator>> (std::istream &is, colvarparse::read_block const &rb) +{ + auto start_pos = is.tellg(); + + std::string read_key; + if ( !(is >> read_key) || !(read_key == rb.key) ) { + // the requested keyword has not been found + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + std::string next; + if (is >> next) { + if (next == "{") { + // Parse a formatted brace-delimited block + rb.read_block_contents(is); + } else { + if (rb.data) { + *(rb.data) = next; + } + } + } else { + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::badbit); + } + + return is; +} + + +std::istream &colvarparse::read_block::read_block_contents(std::istream &is, + bool block_only) const +{ + int brace_count = block_only ? 0 : 1; + auto const start_pos = is.tellg(); + std::string line; + while (colvarparse::getline_nocomments(is, line)) { + size_t br = 0, br_old = 0; + while ((br = line.find_first_of("{}", br)) != std::string::npos) { + if (line[br] == '{') + brace_count++; + if (line[br] == '}') + brace_count--; + br_old = br; + br++; + } + if (brace_count || block_only) { + // Add whole line if (1) brace are unmatched or (2) we're reading the whole stream anyway + if (data) { + data->append(line + "\n"); + } + } else { + // Not reading whole block and braces are matched; add until before the last brace + if (data) { + data->append(line.substr(0, br_old) + "\n"); + } + break; + } + } + + if (block_only) { + if (is.rdstate() & std::ios::eofbit) { + // Clear EOF errors if we were meant to read the whole block + is.clear(); + } + } else { + if (brace_count) { + // Could not match braces, restore initial position and set fail bit + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + } + + return is; +} + + +cvm::memory_stream &operator>>(cvm::memory_stream &is, colvarparse::read_block const &rb) +{ + auto const start_pos = is.tellg(); + + std::string read_key; + if ( !(is >> read_key) || !(read_key == rb.key) ) { + // the requested keyword has not been found + is.clear(); + is.seekg(start_pos); + is.setstate(std::ios::failbit); + return is; + } + + std::string content; + if (is >> content) { + std::istringstream iss(content); + if (!rb.read_block_contents(iss, true)) { + is.seekg(start_pos); + is.setstate(std::ios::failbit); + } + } + + return is; +} + + +int colvarparse::check_braces(std::string const &conf, + size_t const start_pos) +{ + int brace_count = 0; + size_t brace = start_pos; + while ((brace = conf.find_first_of("{}", brace)) != std::string::npos) { + if (conf[brace] == '{') brace_count++; + if (conf[brace] == '}') brace_count--; + brace++; + } + return (brace_count != 0) ? COLVARS_INPUT_ERROR : COLVARS_OK; +} + + +int colvarparse::check_ascii(std::string const &conf) +{ + // Check for non-ASCII characters + std::string line; + std::istringstream is(conf); + while (cvm::getline(is, line)) { + unsigned char const * const uchars = + reinterpret_cast(line.c_str()); + for (size_t i = 0; i < line.size(); i++) { + if (uchars[i] & 0x80U) { + cvm::log("Warning: non-ASCII character detected in this line: \""+ + line+"\".\n"); + } + } + } + return COLVARS_OK; +} + + +void colvarparse::split_string(const std::string& data, const std::string& delim, std::vector& dest) { + size_t index = 0, new_index = 0; + std::string tmpstr; + while (index != data.length()) { + new_index = data.find(delim, index); + if (new_index != std::string::npos) tmpstr = data.substr(index, new_index - index); + else tmpstr = data.substr(index, data.length()); + if (!tmpstr.empty()) { + dest.push_back(tmpstr); + } + if (new_index == std::string::npos) break; + index = new_index + 1; + } +} diff --git a/src/external/colvars/colvarparse.h b/src/external/colvars/colvarparse.h new file mode 100644 index 00000000000..0ac9c111778 --- /dev/null +++ b/src/external/colvars/colvarparse.h @@ -0,0 +1,395 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARPARSE_H +#define COLVARPARSE_H + +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparams.h" + + +/// \file colvarparse.h Parsing functions for collective variables + + +/// \brief Base class containing parsing functions; all objects which +/// need to parse input inherit from this +class colvarparse : public colvarparams { + +public: + + /// Default constructor + colvarparse(); + + /// Constructor that stores the object's config string + colvarparse(const std::string& conf); + + /// Set the object ready to parse a new configuration string + void clear(); + + /// Set a new config string for this object + void set_string(std::string const &conf); + + /// Default destructor + ~colvarparse() override; + + /// Get the configuration string (includes comments) + inline std::string const & get_config() const + { + return config_string; + } + + /// How a keyword is parsed in a string + enum Parse_Mode { + /// Zero for all flags + parse_null = 0, + /// Print the value of a keyword if it is given + parse_echo = (1<<1), + /// Print the default value of a keyword, if it is NOT given + parse_echo_default = (1<<2), + /// Print a deprecation warning if the keyword is given + parse_deprecation_warning = (1<<3), + /// Do not print the keyword + parse_silent = 0, + /// Raise error if the keyword is not provided + parse_required = (1<<16), + /// Successive calls to get_keyval() will override the previous values + /// when the keyword is not given any more + parse_override = (1<<17), + /// The call is being executed from a read_restart() function + parse_restart = (1<<18), + /// Alias for old default behavior (should be phased out) + parse_normal = (1<<1) | (1<<2) | (1<<17), + /// Settings for a deprecated keyword + parse_deprecated = (1<<1) | (1<<3) | (1<<17) + }; + + /// \brief Check that all the keywords within "conf" are in the list + /// of allowed keywords; this will invoke strip_values() first and + /// then loop over all words + int check_keywords(std::string &conf, char const *key); + + /// \brief Use this after parsing a config string (note that check_keywords() calls it already) + void clear_keyword_registry(); + + /// \fn get_keyval bool const get_keyval (std::string const &conf, + /// char const *key, _type_ &value, _type_ const &def_value, + /// Parse_Mode const parse_mode) \brief Helper function to parse + /// keywords in the configuration and get their values + /// + /// In normal circumstances, you should use either version the + /// get_keyval function. Both of them look for the C string "key" + /// in the C++ string "conf", and assign the corresponding value (if + /// available) to the variable "value" (first version), or assign as + /// many values as found to the vector "values" (second version). + /// + /// If "key" is found but no value is associated to it, the default + /// value is provided (either one copy or as many copies as the + /// current length of the vector "values" specifies). A message + /// will print, unless parse_mode is equal to parse_silent. The + /// return value of both forms of get_keyval is true if "key" is + /// found (with or without value), and false when "key" is absent in + /// the string "conf". If there is more than one instance of the + /// keyword, a warning will be raised; instead, to loop over + /// multiple instances key_lookup() should be invoked directly. + /// + /// If you introduce a new data type, add two new instances of this + /// functions, or insert this type in the \link colvarvalue \endlink + /// wrapper class (colvarvalue.h). + + bool get_keyval(std::string const &conf, + char const *key, + int &value, + int const &def_value = 0, + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + size_t &value, + size_t const &def_value = 0, + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + long &value, + long const &def_value = 0, + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + cvm::step_number &value, + cvm::step_number const &def_value = 0, + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::string &value, + std::string const &def_value = std::string(""), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + cvm::real &value, + cvm::real const &def_value = 0.0, + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + cvm::rvector &value, + cvm::rvector const &def_value = cvm::rvector(), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + cvm::quaternion &value, + cvm::quaternion const &def_value = cvm::quaternion(), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + colvarvalue &value, + colvarvalue const &def_value = colvarvalue(colvarvalue::type_notset), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + bool &value, + bool const &def_value = false, + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, 0), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, 0), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, 0), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, std::string("")), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, 0.0), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, cvm::rvector()), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, cvm::quaternion()), + Parse_Mode const parse_mode = parse_normal); + bool get_keyval(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values = std::vector(0, colvarvalue(colvarvalue::type_notset)), + Parse_Mode const parse_mode = parse_normal); + +protected: + + /// Get the string value of a keyword, and save it for later parsing + bool get_key_string_value(std::string const &conf, + char const *key, std::string &data); + + /// Get multiple strings from repeated instances of a same keyword + bool get_key_string_multi_value(std::string const &conf, + char const *key, std::vector& data); + + /// Template for single-value keyword parsers + template + bool _get_keyval_scalar_(std::string const &conf, + char const *key, + TYPE &value, + TYPE const &def_value, + Parse_Mode const &parse_mode); + + /// Template for multiple-value keyword parsers + template + bool _get_keyval_vector_(std::string const &conf, + char const *key, + std::vector &values, + std::vector const &def_values, + Parse_Mode const &parse_mode); + + /// Extract the value of a variable from a string + template + int _get_keyval_scalar_value_(std::string const &key_str, + std::string const &data, + TYPE &value, + TYPE const &def_value); + + /// Handle the case where the user provides a keyword without value + template + int _get_keyval_scalar_novalue_(std::string const &key_str, + TYPE &value, + Parse_Mode const &parse_mode); + + /// Record that the keyword has just been user-defined + template + void mark_key_set_user(std::string const &key_str, + TYPE const &value, + Parse_Mode const &parse_mode); + + /// Record that the keyword has just been set to its default value + template + void mark_key_set_default(std::string const &key_str, + TYPE const &def_value, + Parse_Mode const &parse_mode); + + /// Raise error condition due to the keyword being required! + void error_key_required(std::string const &key_str, + Parse_Mode const &parse_mode); + + /// True if the keyword has been set already + bool key_already_set(std::string const &key_str); + +public: + + /// \brief Return a lowercased copy of the string + static inline std::string to_lower_cppstr(std::string const &in) + { + std::string out = ""; + for (size_t i = 0; i < in.size(); i++) { + out.append(1, static_cast( ::tolower(in[i])) ); + } + return out; + } + + /// Helper class to read a block "key { ... }" from a stream and store it in a string + /// + /// Useful on restarts, where the file is too big to be loaded in a string + /// by key_lookup(); it can only check that the keyword is correct and the + /// block is properly delimited by braces, not skipping other blocks + class read_block { + public: + + read_block(std::string const &key, std::string *data = nullptr); + + ~read_block(); + + /// Read block from stream, first check that key matches, then call read_contents() + friend std::istream & operator >> (std::istream &is, read_block const &rb); + + /// Read block from stream, first check that key matches, then call read_contents() + friend cvm::memory_stream & operator >> (cvm::memory_stream &is, read_block const &rb); + + private: + + /// Keyword that identifies the block + std::string const key; + + /// Where to keep the data + std::string * const data; + + /// Read the contents of a formatted block after checking that the keyword matches + /// \param[in] is Stream object + /// \param[in] block_only If true, stream is assumed to contain only the block without braces + std::istream & read_block_contents(std::istream &is, bool block_only = false) const; + }; + + + /// Accepted white space delimiters, used in key_lookup() + static const char * const white_space; + + /// \brief Low-level function for parsing configuration strings; + /// automatically adds the requested keyword to the list of valid + /// ones. \param conf the content of the configuration file or one + /// of its blocks \param key the keyword to search within "conf" \param + /// data (optional) holds the string provided after "key", if any + /// \param save_pos (optional) stores the position of the keyword + /// within "conf", useful when doing multiple calls + bool key_lookup(std::string const &conf, + char const *key, + std::string *data = nullptr, + size_t *save_pos = nullptr); + + /// \brief Reads a configuration line, adds it to config_string, and returns + /// the stream \param is Input stream \param line String that will hold the + /// configuration line, with comments stripped + std::istream & read_config_line(std::istream &is, std::string &line); + + /// \brief Works as std::getline() but also removes everything + /// between a comment character and the following newline + static std::istream & getline_nocomments(std::istream &is, std::string &s); + + /// \brief Check if the content of a config string has matching braces + /// \param conf The configuration string \param start_pos Start the count + /// from this position + static int check_braces(std::string const &conf, size_t const start_pos); + + /// \brief Check that a config string contains non-ASCII characters + /// \param conf The configuration string + static int check_ascii(std::string const &conf); + + /// \brief Split a string with a specified delimiter into a vector + /// \param data The string to be splitted + /// \param delim A delimiter + /// \param dest A destination vector to store the splitted results + static void split_string(const std::string& data, const std::string& delim, std::vector& dest); + +protected: + + /// Characters allowed immediately to the left of a kewyord + std::string const keyword_delimiters_left; + + /// Characters allowed immediately to the right of a kewyord + std::string const keyword_delimiters_right; + + /// \brief List of legal keywords for this object: this is updated + /// by each call to colvarparse::get_keyval() or + /// colvarparse::key_lookup() + std::list allowed_keywords; + + /// How a keyword has been set + enum key_set_mode { + key_not_set = 0, + key_set_user = 1, + key_set_default = 2 + }; + + /// Track which keywords have been already set, and how + std::map key_set_modes; + + /// \brief List of delimiters for the values of each keyword in the + /// configuration string; all keywords will be stripped of their + /// values before the keyword check is performed + std::list data_begin_pos; + + /// \brief List of delimiters for the values of each keyword in the + /// configuration string; all keywords will be stripped of their + /// values before the keyword check is performed + std::list data_end_pos; + + /// \brief Add a new valid keyword to the list + void add_keyword(char const *key); + + /// \brief Remove all the values from the config string + void strip_values(std::string &conf); + + /// \brief Configuration string of the object (includes comments) + std::string config_string; + +}; + + +/// Bitwise OR between two Parse_mode flags +inline colvarparse::Parse_Mode operator | (colvarparse::Parse_Mode const &mode1, + colvarparse::Parse_Mode const &mode2) +{ + return static_cast(static_cast(mode1) | + static_cast(mode2)); +} + +#endif diff --git a/src/external/colvars/colvarproxy.cpp b/src/external/colvars/colvarproxy.cpp new file mode 100644 index 00000000000..2f262bfdc0c --- /dev/null +++ b/src/external/colvars/colvarproxy.cpp @@ -0,0 +1,747 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarscript.h" +#include "colvaratoms.h" +#include "colvarmodule_utils.h" + + + +colvarproxy_atoms::colvarproxy_atoms() +{ + atoms_rms_applied_force_ = atoms_max_applied_force_ = 0.0; + atoms_max_applied_force_id_ = -1; + modified_atom_list_ = false; + updated_masses_ = updated_charges_ = false; +} + + +colvarproxy_atoms::~colvarproxy_atoms() +{ + reset(); +} + + +int colvarproxy_atoms::reset() +{ + atoms_ids.clear(); + atoms_refcount.clear(); + atoms_masses.clear(); + atoms_charges.clear(); + atoms_positions.clear(); + atoms_total_forces.clear(); + atoms_new_colvar_forces.clear(); + return COLVARS_OK; +} + + +int colvarproxy_atoms::add_atom_slot(int atom_id) +{ + atoms_ids.push_back(atom_id); + atoms_refcount.push_back(1); + atoms_masses.push_back(1.0); + atoms_charges.push_back(0.0); + atoms_positions.push_back(cvm::rvector(0.0, 0.0, 0.0)); + atoms_total_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + atoms_new_colvar_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + modified_atom_list_ = true; + return (atoms_ids.size() - 1); +} + + +int colvarproxy_atoms::init_atom(int /* atom_number */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_atoms::check_atom_id(int /* atom_number */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_atoms::init_atom(cvm::residue_id const & /* residue */, + std::string const & /* atom_name */, + std::string const & /* segment_id */) +{ + cvm::error("Error: initializing an atom by name and residue number is currently not supported.\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_atoms::check_atom_id(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id) +{ + colvarproxy_atoms::init_atom(residue, atom_name, segment_id); + return COLVARS_NOT_IMPLEMENTED; +} + + +void colvarproxy_atoms::clear_atom(int index) +{ + if (((size_t) index) >= atoms_ids.size()) { + cvm::error("Error: trying to disable an atom that was not previously requested.\n", + COLVARS_INPUT_ERROR); + } + if (atoms_refcount[index] > 0) { + atoms_refcount[index] -= 1; + } +} + + +size_t colvarproxy_atoms::get_num_active_atoms() const +{ + size_t result = 0; + for (size_t i = 0; i < atoms_refcount.size(); i++) { + if (atoms_refcount[i] > 0) result++; + } + return result; +} + + +int colvarproxy_atoms::load_atoms(char const * /* filename */, + cvm::atom_group & /* atoms */, + std::string const & /* pdb_field */, + double) +{ + return cvm::error("Error: loading atom identifiers from a file " + "is currently not implemented.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy_atoms::load_coords(char const * /* filename */, + std::vector & /* pos */, + std::vector const & /* sorted_ids */, + std::string const & /* pdb_field */, + double) +{ + return cvm::error("Error: loading atomic coordinates from a file " + "is currently not implemented.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +void colvarproxy_atoms::compute_rms_atoms_applied_force() +{ + atoms_rms_applied_force_ = + compute_norm2_stats(atoms_new_colvar_forces); +} + + +void colvarproxy_atoms::compute_max_atoms_applied_force() +{ + int minmax_index = -1; + size_t const n_atoms_ids = atoms_ids.size(); + if ((n_atoms_ids > 0) && (n_atoms_ids == atoms_new_colvar_forces.size())) { + atoms_max_applied_force_ = + compute_norm2_stats(atoms_new_colvar_forces, + &minmax_index); + if (minmax_index >= 0) { + atoms_max_applied_force_id_ = atoms_ids[minmax_index]; + } else { + atoms_max_applied_force_id_ = -1; + } + } else { + atoms_max_applied_force_ = + compute_norm2_stats(atoms_new_colvar_forces); + atoms_max_applied_force_id_ = -1; + } +} + + + +colvarproxy_atom_groups::colvarproxy_atom_groups() +{ + atom_groups_rms_applied_force_ = atom_groups_max_applied_force_ = 0.0; +} + + +colvarproxy_atom_groups::~colvarproxy_atom_groups() +{ + reset(); +} + + +int colvarproxy_atom_groups::reset() +{ + atom_groups_ids.clear(); + atom_groups_refcount.clear(); + atom_groups_masses.clear(); + atom_groups_charges.clear(); + atom_groups_coms.clear(); + atom_groups_total_forces.clear(); + atom_groups_new_colvar_forces.clear(); + return COLVARS_OK; +} + + +int colvarproxy_atom_groups::add_atom_group_slot(int atom_group_id) +{ + atom_groups_ids.push_back(atom_group_id); + atom_groups_refcount.push_back(1); + atom_groups_masses.push_back(1.0); + atom_groups_charges.push_back(0.0); + atom_groups_coms.push_back(cvm::rvector(0.0, 0.0, 0.0)); + atom_groups_total_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + atom_groups_new_colvar_forces.push_back(cvm::rvector(0.0, 0.0, 0.0)); + return (atom_groups_ids.size() - 1); +} + + +int colvarproxy_atom_groups::scalable_group_coms() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_atom_groups::init_atom_group(std::vector const & /* atoms_ids */) +{ + cvm::error("Error: initializing a group outside of the Colvars module " + "is currently not supported.\n", + COLVARS_NOT_IMPLEMENTED); + return COLVARS_NOT_IMPLEMENTED; +} + + +void colvarproxy_atom_groups::clear_atom_group(int index) +{ + if (((size_t) index) >= atom_groups_ids.size()) { + cvm::error("Error: trying to disable an atom group " + "that was not previously requested.\n", + COLVARS_INPUT_ERROR); + } + if (atom_groups_refcount[index] > 0) { + atom_groups_refcount[index] -= 1; + } +} + + +size_t colvarproxy_atom_groups::get_num_active_atom_groups() const +{ + size_t result = 0; + for (size_t i = 0; i < atom_groups_refcount.size(); i++) { + if (atom_groups_refcount[i] > 0) result++; + } + return result; +} + + +void colvarproxy_atom_groups::compute_rms_atom_groups_applied_force() +{ + atom_groups_rms_applied_force_ = + compute_norm2_stats(atom_groups_new_colvar_forces); +} + + +void colvarproxy_atom_groups::compute_max_atom_groups_applied_force() +{ + atom_groups_max_applied_force_ = + compute_norm2_stats(atom_groups_new_colvar_forces); +} + + + +colvarproxy_smp::colvarproxy_smp() +{ + b_smp_active = true; // May be disabled by user option + omp_lock_state = NULL; +#if defined(_OPENMP) + if (omp_get_thread_num() == 0) { + omp_lock_state = new omp_lock_t; + omp_init_lock(omp_lock_state); + } +#endif +} + + +colvarproxy_smp::~colvarproxy_smp() +{ +#if defined(_OPENMP) + if (omp_get_thread_num() == 0) { + if (omp_lock_state) { + delete omp_lock_state; + } + } +#endif +} + + +int colvarproxy_smp::smp_enabled() +{ +#if defined(_OPENMP) + if (b_smp_active) { + return COLVARS_OK; + } + return COLVARS_ERROR; +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + +int colvarproxy_smp::smp_colvars_loop() +{ +#if defined(_OPENMP) + colvarmodule *cv = cvm::main(); + colvarproxy *proxy = cv->proxy; +#pragma omp parallel for + for (size_t i = 0; i < cv->variables_active_smp()->size(); i++) { + colvar *x = (*(cv->variables_active_smp()))[i]; + int x_item = (*(cv->variables_active_smp_items()))[i]; + if (cvm::debug()) { + cvm::log("["+cvm::to_str(proxy->smp_thread_id())+"/"+ + cvm::to_str(proxy->smp_num_threads())+ + "]: calc_colvars_items_smp(), i = "+cvm::to_str(i)+", cv = "+ + x->name+", cvc = "+cvm::to_str(x_item)+"\n"); + } + x->calc_cvcs(x_item, 1); + } + return cvm::get_error(); +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + +int colvarproxy_smp::smp_biases_loop() +{ +#if defined(_OPENMP) + colvarmodule *cv = cvm::main(); +#pragma omp parallel + { +#pragma omp for + for (size_t i = 0; i < cv->biases_active()->size(); i++) { + colvarbias *b = (*(cv->biases_active()))[i]; + if (cvm::debug()) { + cvm::log("Calculating bias \""+b->name+"\" on thread "+ + cvm::to_str(smp_thread_id())+"\n"); + } + b->update(); + } + } + return cvm::get_error(); +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + +int colvarproxy_smp::smp_biases_script_loop() +{ +#if defined(_OPENMP) + colvarmodule *cv = cvm::main(); +#pragma omp parallel + { +#pragma omp single nowait + { + cv->calc_scripted_forces(); + } +#pragma omp for + for (size_t i = 0; i < cv->biases_active()->size(); i++) { + colvarbias *b = (*(cv->biases_active()))[i]; + if (cvm::debug()) { + cvm::log("Calculating bias \""+b->name+"\" on thread "+ + cvm::to_str(smp_thread_id())+"\n"); + } + b->update(); + } + } + return cvm::get_error(); +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + + + +int colvarproxy_smp::smp_thread_id() +{ +#if defined(_OPENMP) + return omp_get_thread_num(); +#else + return -1; +#endif +} + + +int colvarproxy_smp::smp_num_threads() +{ +#if defined(_OPENMP) + return omp_get_max_threads(); +#else + return -1; +#endif +} + + +int colvarproxy_smp::smp_lock() +{ +#if defined(_OPENMP) + omp_set_lock(omp_lock_state); +#endif + return COLVARS_OK; +} + + +int colvarproxy_smp::smp_trylock() +{ +#if defined(_OPENMP) + return omp_test_lock(omp_lock_state) ? COLVARS_OK : COLVARS_ERROR; +#else + return COLVARS_OK; +#endif +} + + +int colvarproxy_smp::smp_unlock() +{ +#if defined(_OPENMP) + omp_unset_lock(omp_lock_state); +#endif + return COLVARS_OK; +} + + + +colvarproxy_script::colvarproxy_script() +{ + script = NULL; + have_scripts = false; +} + + +colvarproxy_script::~colvarproxy_script() +{ + if (script != NULL) { + delete script; + script = NULL; + } +} + + +int colvarproxy_script::run_force_callback() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_script::run_colvar_callback(std::string const & /* name */, + std::vector const & /* cvcs */, + colvarvalue & /* value */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_script::run_colvar_gradient_callback(std::string const & /* name */, + std::vector const & /* cvcs */, + std::vector > & /* gradient */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + + +colvarproxy::colvarproxy() +{ + colvars = NULL; + // By default, simulation engines allow to immediately request atoms + engine_ready_ = true; + b_simulation_running = true; + b_simulation_continuing = false; + b_delete_requested = false; + version_int = -1; + features_hash = 0; + config_queue_ = reinterpret_cast(new std::list >); +} + + +colvarproxy::~colvarproxy() +{ + close_output_streams(); + if (colvars != NULL) { + delete colvars; + colvars = NULL; + } + delete reinterpret_cast > *>(config_queue_); +} + + +bool colvarproxy::io_available() +{ + return (smp_enabled() == COLVARS_OK && smp_thread_id() == 0) || + (smp_enabled() != COLVARS_OK); +} + + +int colvarproxy::reset() +{ + if (cvm::debug()) { + cvm::log("colvarproxy::reset()\n"); + } + int error_code = COLVARS_OK; + error_code |= colvarproxy_atoms::reset(); + error_code |= colvarproxy_atom_groups::reset(); + error_code |= colvarproxy_volmaps::reset(); + total_force_requested = false; + return error_code; +} + + +int colvarproxy::request_deletion() +{ + return cvm::error("Error: \"delete\" command is only available in VMD; " + "please use \"reset\" instead.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +void colvarproxy::add_config(std::string const &cmd, std::string const &conf) +{ + reinterpret_cast > *>(config_queue_)->push_back(std::make_pair(cmd, conf)); +} + + +int colvarproxy::setup() +{ + return COLVARS_OK; +} + + +int colvarproxy::parse_module_config() +{ + int error_code = COLVARS_OK; + // Read any configuration queued up for Colvars + std::list > *config_queue = reinterpret_cast > *>(config_queue_); + while (config_queue->size() > 0) { + std::pair const &p = config_queue->front(); + if (p.first == "config") { + error_code |= colvars->read_config_string(p.second); + } else if (p.first == "configfile") { + error_code |= colvars->read_config_file(p.second.c_str()); + } else { + error_code |= cvm::error(std::string("Error: invalid keyword \"") + + p.first + + std::string("\" in colvarproxy::setup()\n"), + COLVARS_BUG_ERROR); + } + config_queue->pop_front(); + } + return error_code; +} + + +int colvarproxy::update_input() +{ + return COLVARS_OK; +} + + +int colvarproxy::update_output() +{ + return COLVARS_OK; +} + + +int colvarproxy::end_of_step() +{ + // Disable flags that Colvars doesn't need any more + updated_masses_ = updated_charges_ = false; + + // Compute force statistics + compute_rms_atoms_applied_force(); + compute_max_atoms_applied_force(); + compute_rms_atom_groups_applied_force(); + compute_max_atom_groups_applied_force(); + compute_rms_volmaps_applied_force(); + compute_max_volmaps_applied_force(); + + if (cached_alch_lambda_changed) { + send_alch_lambda(); + cached_alch_lambda_changed = false; + } + return COLVARS_OK; +} + + +int colvarproxy::post_run() +{ + int error_code = COLVARS_OK; + if (colvars->output_prefix().size()) { + error_code |= colvars->write_restart_file(cvm::output_prefix()+".colvars.state"); + error_code |= colvars->write_output_files(); + } + error_code |= flush_output_streams(); + return error_code; +} + + +void colvarproxy::print_input_atomic_data() +{ + cvm::log(cvm::line_marker); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_ids[size = "+cvm::to_str(atoms_ids.size())+ + "] = "+cvm::to_str(atoms_ids)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_refcount[size = "+cvm::to_str(atoms_refcount.size())+ + "] = "+cvm::to_str(atoms_refcount)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_masses[size = "+cvm::to_str(atoms_masses.size())+ + "] = "+cvm::to_str(atoms_masses)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_charges[size = "+cvm::to_str(atoms_charges.size())+ + "] = "+cvm::to_str(atoms_charges)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_positions[size = "+cvm::to_str(atoms_positions.size())+ + "] = "+cvm::to_str(atoms_positions, + cvm::cv_width, + cvm::cv_prec)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_total_forces[size = "+ + cvm::to_str(atoms_total_forces.size())+ + "] = "+cvm::to_str(atoms_total_forces, + cvm::cv_width, + cvm::cv_prec)+"\n"); + + cvm::log(cvm::line_marker); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_ids[size = "+cvm::to_str(atom_groups_ids.size())+ + "] = "+cvm::to_str(atom_groups_ids)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_refcount[size = "+ + cvm::to_str(atom_groups_refcount.size())+ + "] = "+cvm::to_str(atom_groups_refcount)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_masses[size = "+ + cvm::to_str(atom_groups_masses.size())+ + "] = "+cvm::to_str(atom_groups_masses)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_charges[size = "+ + cvm::to_str(atom_groups_charges.size())+ + "] = "+cvm::to_str(atom_groups_charges)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_coms[size = "+ + cvm::to_str(atom_groups_coms.size())+ + "] = "+cvm::to_str(atom_groups_coms, + cvm::cv_width, + cvm::cv_prec)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_total_forces[size = "+ + cvm::to_str(atom_groups_total_forces.size())+ + "] = "+cvm::to_str(atom_groups_total_forces, + cvm::cv_width, + cvm::cv_prec)+"\n"); + + cvm::log(cvm::line_marker); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "volmaps_ids[size = "+cvm::to_str(volmaps_ids.size())+ + "] = "+cvm::to_str(volmaps_ids)+"\n"); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "volmaps_values[size = "+cvm::to_str(volmaps_values.size())+ + "] = "+cvm::to_str(volmaps_values)+"\n"); + + cvm::log(cvm::line_marker); +} + + +void colvarproxy::print_output_atomic_data() +{ + cvm::log(cvm::line_marker); + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atoms_new_colvar_forces = "+cvm::to_str(atoms_new_colvar_forces, + colvarmodule::cv_width, + colvarmodule::cv_prec)+"\n"); + cvm::log(cvm::line_marker); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "atom_groups_new_colvar_forces = "+ + cvm::to_str(atom_groups_new_colvar_forces, + colvarmodule::cv_width, + colvarmodule::cv_prec)+"\n"); + + cvm::log(cvm::line_marker); + + cvm::log("Step "+cvm::to_str(cvm::step_absolute())+", "+ + "volmaps_new_colvar_forces = "+ + cvm::to_str(volmaps_new_colvar_forces)+"\n"); + + cvm::log(cvm::line_marker); +} + + +void colvarproxy::log(std::string const &message) +{ + fprintf(stdout, "colvars: %s", message.c_str()); +} + + +void colvarproxy::error(std::string const &message) +{ + // TODO handle errors? + colvarproxy::log(message); +} + + +void colvarproxy::add_error_msg(std::string const &message) +{ + std::istringstream is(message); + std::string line; + while (std::getline(is, line)) { + error_output += line+"\n"; + } +} + + +void colvarproxy::clear_error_msgs() +{ + error_output.clear(); +} + + +std::string const & colvarproxy::get_error_msgs() +{ + return error_output; +} + + +int colvarproxy::get_version_from_string(char const *version_string) +{ + std::string const v(version_string); + std::istringstream is(v.substr(0, 4) + v.substr(5, 2) + v.substr(8, 2)); + int newint; + is >> newint; + return newint; +} + + diff --git a/src/external/colvars/colvarproxy.h b/src/external/colvars/colvarproxy.h new file mode 100644 index 00000000000..b0bb335919a --- /dev/null +++ b/src/external/colvars/colvarproxy.h @@ -0,0 +1,725 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARPROXY_H +#define COLVARPROXY_H + +#include "colvarmodule.h" +#include "colvartypes.h" +#include "colvarvalue.h" +#include "colvarproxy_io.h" +#include "colvarproxy_system.h" +#include "colvarproxy_tcl.h" +#include "colvarproxy_volmaps.h" + +/// \file colvarproxy.h +/// \brief Colvars proxy classes +/// +/// This file declares the class for the object responsible for interfacing +/// Colvars with other codes (MD engines, VMD, Python). The \link colvarproxy +/// \endlink class is a derivative of multiple classes, each devoted to a +/// specific task (e.g. \link colvarproxy_atoms \endlink to access data for +/// individual atoms). +/// +/// To interface to a new MD engine, the simplest solution is to derive a new +/// class from \link colvarproxy \endlink. Currently implemented are: \link +/// colvarproxy_lammps, \endlink, \link colvarproxy_namd, \endlink, \link +/// colvarproxy_vmd \endlink. + + +// forward declarations +class colvarscript; + + +/// \brief Container of atomic data for processing by Colvars +class colvarproxy_atoms { + +public: + + /// Constructor + colvarproxy_atoms(); + + /// Destructor + virtual ~colvarproxy_atoms(); + + /// Prepare this atom for collective variables calculation, selecting it by + /// numeric index (1-based) + virtual int init_atom(int atom_number); + + /// Check that this atom number is valid, but do not initialize the + /// corresponding atom yet + virtual int check_atom_id(int atom_number); + + /// Select this atom for collective variables calculation, using name and + /// residue number. Not all programs support this: leave this function as + /// is in those cases. + virtual int init_atom(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id); + + /// Check that this atom is valid, but do not initialize it yet + virtual int check_atom_id(cvm::residue_id const &residue, + std::string const &atom_name, + std::string const &segment_id); + + /// \brief Used by the atom class destructor: rather than deleting the array slot + /// (costly) set the corresponding atoms_refcount to zero + virtual void clear_atom(int index); + + /// \brief Read a selection of atom IDs from a coordinate file (only PDB is supported) + /// \param[in] filename name of the file + /// \param[in,out] atoms array into which atoms will be read from "filename" + /// \param[in] pdb_field if the file is a PDB and this string is non-empty, + /// select atoms for which this field is non-zero + /// \param[in] pdb_field_value if non-zero, select only atoms whose pdb_field equals this + virtual int load_atoms(char const *filename, cvm::atom_group &atoms, std::string const &pdb_field, + double pdb_field_value); + + /// \brief Load a set of coordinates from a file (PDB or XYZ) + /// \param[in] filename name of the file + /// \param[in,out] pos array of coordinates; if not empty, the number of its elements must match + /// the number of entries in "filename" + /// \param[in] sorted_ids array of sorted internal IDs, used to loop through the file only once + /// \param[in] pdb_field if the file is a PDB and this string is non-empty, only atoms for which + /// this field is non-zero will be processed + /// \param[in] pdb_field_value if non-zero, process only atoms whose pdb_field equals this + virtual int load_coords(char const *filename, std::vector &pos, + std::vector const &sorted_ids, std::string const &pdb_field, + double pdb_field_value); + + /// Clear atomic data + int reset(); + + /// Get the numeric ID of the given atom + /// \param index Internal index in the Colvars arrays + inline int get_atom_id(int index) const + { + return atoms_ids[index]; + } + + /// Get the mass of the given atom + /// \param index Internal index in the Colvars arrays + inline cvm::real get_atom_mass(int index) const + { + return atoms_masses[index]; + } + + /// Increase the reference count of the given atom + /// \param index Internal index in the Colvars arrays + inline void increase_refcount(int index) + { + atoms_refcount[index] += 1; + } + + /// Get the charge of the given atom + /// \param index Internal index in the Colvars arrays + inline cvm::real get_atom_charge(int index) const + { + return atoms_charges[index]; + } + + /// Read the current position of the given atom + /// \param index Internal index in the Colvars arrays + inline cvm::rvector get_atom_position(int index) const + { + return atoms_positions[index]; + } + + /// Read the current total force of the given atom + /// \param index Internal index in the Colvars arrays + inline cvm::rvector get_atom_total_force(int index) const + { + return atoms_total_forces[index]; + } + + /// Request that this force is applied to the given atom + /// \param index Internal index in the Colvars arrays + /// \param new_force Force to add + inline void apply_atom_force(int index, cvm::rvector const &new_force) + { + atoms_new_colvar_forces[index] += new_force; + } + + /// Read the current velocity of the given atom + inline cvm::rvector get_atom_velocity(int /* index */) + { + cvm::error("Error: reading the current velocity of an atom " + "is not yet implemented.\n", + COLVARS_NOT_IMPLEMENTED); + return cvm::rvector(0.0); + } + + inline std::vector const *get_atom_ids() const + { + return &atoms_ids; + } + + /// Return number of atoms with positive reference count + size_t get_num_active_atoms() const; + + inline std::vector const *get_atom_masses() const + { + return &atoms_masses; + } + + inline std::vector *modify_atom_masses() + { + // assume that we are requesting masses to change them + updated_masses_ = true; + return &atoms_masses; + } + + inline std::vector const *get_atom_charges() + { + return &atoms_charges; + } + + inline std::vector *modify_atom_charges() + { + // assume that we are requesting charges to change them + updated_charges_ = true; + return &atoms_charges; + } + + inline std::vector const *get_atom_positions() const + { + return &atoms_positions; + } + + inline std::vector *modify_atom_positions() + { + return &atoms_positions; + } + + inline std::vector const *get_atom_total_forces() const + { + return &atoms_total_forces; + } + + inline std::vector *modify_atom_total_forces() + { + return &atoms_total_forces; + } + + inline std::vector const *get_atom_applied_forces() const + { + return &atoms_new_colvar_forces; + } + + inline std::vector *modify_atom_applied_forces() + { + return &atoms_new_colvar_forces; + } + + /// Compute the root-mean-square of the applied forces + void compute_rms_atoms_applied_force(); + + /// Compute the maximum norm among all applied forces + void compute_max_atoms_applied_force(); + + /// Get the root-mean-square of the applied forces + inline cvm::real rms_atoms_applied_force() const + { + return atoms_rms_applied_force_; + } + + /// Get the maximum norm among all applied forces + inline cvm::real max_atoms_applied_force() const + { + return atoms_max_applied_force_; + } + + /// Get the atom ID with the largest applied force + inline int max_atoms_applied_force_id() const + { + return atoms_max_applied_force_id_; + } + + /// Whether the atom list has been modified internally + inline bool modified_atom_list() const + { + return modified_atom_list_; + } + + /// Reset the modified atom list flag + inline void reset_modified_atom_list() + { + modified_atom_list_ = false; + } + + /// Record whether masses have been updated + inline bool updated_masses() const + { + return updated_masses_; + } + + /// Record whether masses have been updated + inline bool updated_charges() const + { + return updated_charges_; + } + +protected: + + /// \brief Array of 0-based integers used to uniquely associate atoms + /// within the host program + std::vector atoms_ids; + /// \brief Keep track of how many times each atom is used by a separate colvar object + std::vector atoms_refcount; + /// \brief Masses of the atoms (allow redefinition during a run, as done e.g. in LAMMPS) + std::vector atoms_masses; + /// \brief Charges of the atoms (allow redefinition during a run, as done e.g. in LAMMPS) + std::vector atoms_charges; + /// \brief Current three-dimensional positions of the atoms + std::vector atoms_positions; + /// \brief Most recent total forces on each atom + std::vector atoms_total_forces; + /// \brief Forces applied from colvars, to be communicated to the MD integrator + std::vector atoms_new_colvar_forces; + + /// Root-mean-square of the applied forces + cvm::real atoms_rms_applied_force_; + + /// Maximum norm among all applied forces + cvm::real atoms_max_applied_force_; + + /// ID of the atom with the maximum norm among all applied forces + int atoms_max_applied_force_id_; + + /// Whether the atom list has been modified internally + bool modified_atom_list_; + + /// Whether the masses and charges have been updated from the host code + bool updated_masses_, updated_charges_; + + /// Used by all init_atom() functions: create a slot for an atom not + /// requested yet; returns the index in the arrays + int add_atom_slot(int atom_id); + +}; + + +/// \brief Container of atom group data (allow collection of aggregated atomic +/// data) +class colvarproxy_atom_groups { + +public: + + /// Constructor + colvarproxy_atom_groups(); + + /// Destructor + virtual ~colvarproxy_atom_groups(); + + /// Clear atom group data + int reset(); + + /// \brief Whether this proxy implementation has capability for scalable groups + virtual int scalable_group_coms(); + + /// Prepare this group for collective variables calculation, selecting atoms by internal ids (0-based) + virtual int init_atom_group(std::vector const &atoms_ids); + + /// \brief Used by the atom_group class destructor + virtual void clear_atom_group(int index); + + /// Get the numeric ID of the given atom group (for the MD program) + inline int get_atom_group_id(int index) const + { + return atom_groups_ids[index]; + } + + /// Get the mass of the given atom group + inline cvm::real get_atom_group_mass(int index) const + { + return atom_groups_masses[index]; + } + + /// Get the charge of the given atom group + inline cvm::real get_atom_group_charge(int index) const + { + return atom_groups_charges[index]; + } + + /// Read the current position of the center of mass given atom group + inline cvm::rvector get_atom_group_com(int index) const + { + return atom_groups_coms[index]; + } + + /// Read the current total force of the given atom group + inline cvm::rvector get_atom_group_total_force(int index) const + { + return atom_groups_total_forces[index]; + } + + /// Request that this force is applied to the given atom group + inline void apply_atom_group_force(int index, cvm::rvector const &new_force) + { + atom_groups_new_colvar_forces[index] += new_force; + } + + /// Read the current velocity of the given atom group + inline cvm::rvector get_atom_group_velocity(int /* index */) + { + cvm::error("Error: reading the current velocity of an atom group is not yet implemented.\n", + COLVARS_NOT_IMPLEMENTED); + return cvm::rvector(0.0); + } + + inline std::vector const *get_atom_group_ids() const + { + return &atom_groups_ids; + } + + /// Return number of atom groups with positive reference count + size_t get_num_active_atom_groups() const; + + inline std::vector *modify_atom_group_masses() + { + // TODO updated_masses + return &atom_groups_masses; + } + + inline std::vector *modify_atom_group_charges() + { + // TODO updated masses + return &atom_groups_charges; + } + + inline std::vector *modify_atom_group_positions() + { + return &atom_groups_coms; + } + + inline std::vector *modify_atom_group_total_forces() + { + return &atom_groups_total_forces; + } + + inline std::vector *modify_atom_group_applied_forces() + { + return &atom_groups_new_colvar_forces; + } + + /// Compute the root-mean-square of the applied forces + void compute_rms_atom_groups_applied_force(); + + /// Compute the maximum norm among all applied forces + void compute_max_atom_groups_applied_force(); + + /// Get the root-mean-square of the applied forces + inline cvm::real rms_atom_groups_applied_force() const + { + return atom_groups_rms_applied_force_; + } + + /// Get the maximum norm among all applied forces + inline cvm::real max_atom_groups_applied_force() const + { + return atom_groups_max_applied_force_; + } + +protected: + + /// \brief Array of 0-based integers used to uniquely associate atom groups + /// within the host program + std::vector atom_groups_ids; + /// \brief Keep track of how many times each group is used by a separate cvc + std::vector atom_groups_refcount; + /// \brief Total masses of the atom groups + std::vector atom_groups_masses; + /// \brief Total charges of the atom groups (allow redefinition during a run, as done e.g. in LAMMPS) + std::vector atom_groups_charges; + /// \brief Current centers of mass of the atom groups + std::vector atom_groups_coms; + /// \brief Most recently updated total forces on the com of each group + std::vector atom_groups_total_forces; + /// \brief Forces applied from colvars, to be communicated to the MD integrator + std::vector atom_groups_new_colvar_forces; + + /// Root-mean-square of the applied group forces + cvm::real atom_groups_rms_applied_force_; + + /// Maximum norm among all applied group forces + cvm::real atom_groups_max_applied_force_; + + /// Used by all init_atom_group() functions: create a slot for an atom group not requested yet + int add_atom_group_slot(int atom_group_id); +}; + + +#if defined(_OPENMP) +#include +#else +struct omp_lock_t; +#endif + +/// \brief Methods for SMP parallelization +class colvarproxy_smp { + +public: + + /// Constructor + colvarproxy_smp(); + + /// Destructor + virtual ~colvarproxy_smp(); + + /// Whether threaded parallelization should be used (TODO: make this a + /// cvm::deps feature) + bool b_smp_active; + + /// Whether threaded parallelization is available (TODO: make this a cvm::deps feature) + virtual int smp_enabled(); + + /// Distribute calculation of colvars (and their components) across threads + virtual int smp_colvars_loop(); + + /// Distribute calculation of biases across threads + virtual int smp_biases_loop(); + + /// Distribute calculation of biases across threads 2nd through last, with all scripted biased on 1st thread + virtual int smp_biases_script_loop(); + + /// Index of this thread + virtual int smp_thread_id(); + + /// Number of threads sharing this address space + virtual int smp_num_threads(); + + /// Lock the proxy's shared data for access by a thread, if threads are implemented; if not implemented, does nothing + virtual int smp_lock(); + + /// Attempt to lock the proxy's shared data + virtual int smp_trylock(); + + /// Release the lock + virtual int smp_unlock(); + +protected: + + /// Lock state for OpenMP + omp_lock_t *omp_lock_state; +}; + + +/// \brief Methods for multiple-replica communication +class colvarproxy_replicas { + +public: + + /// Constructor + colvarproxy_replicas(); + + /// Destructor + virtual ~colvarproxy_replicas(); + + /// \brief Indicate if multi-replica support is available and active + virtual int replica_enabled(); + + /// \brief Index of this replica + virtual int replica_index(); + + /// \brief Total number of replicas + virtual int num_replicas(); + + /// \brief Synchronize replica with others + virtual void replica_comm_barrier(); + + /// \brief Receive data from other replica + virtual int replica_comm_recv(char* msg_data, int buf_len, int src_rep); + + /// \brief Send data to other replica + virtual int replica_comm_send(char* msg_data, int msg_len, int dest_rep); + +}; + + +/// Methods for scripting language interface (Tcl or Python) +class colvarproxy_script { + +public: + + /// Constructor + colvarproxy_script(); + + /// Destructor + virtual ~colvarproxy_script(); + + /// Pointer to the scripting interface object + /// (does not need to be allocated in a new interface) + colvarscript *script; + + /// Do we have a scripting interface? + bool have_scripts; + + /// Run a user-defined colvar forces script + virtual int run_force_callback(); + + virtual int run_colvar_callback( + std::string const &name, + std::vector const &cvcs, + colvarvalue &value); + + virtual int run_colvar_gradient_callback( + std::string const &name, + std::vector const &cvcs, + std::vector > &gradient); +}; + + + +/// Interface between Colvars and MD engine (GROMACS, LAMMPS, NAMD, VMD...) +/// +/// This is the base class: each engine is supported by a derived class. +class colvarproxy + : public colvarproxy_system, + public colvarproxy_atoms, + public colvarproxy_atom_groups, + public colvarproxy_volmaps, + public colvarproxy_smp, + public colvarproxy_replicas, + public colvarproxy_script, + public colvarproxy_tcl, + public colvarproxy_io +{ + +public: + + /// Pointer to the main object + colvarmodule *colvars; + + /// Constructor + colvarproxy(); + + /// Destructor + ~colvarproxy() override; + + bool io_available() override; + + /// Request deallocation of the module (currently only implemented by VMD) + virtual int request_deletion(); + + /// Whether deallocation was requested + inline bool delete_requested() const + { + return b_delete_requested; + } + + /// \brief Reset proxy state, e.g. requested atoms + virtual int reset(); + + /// (Re)initialize the module + virtual int parse_module_config(); + + /// (Re)initialize required member data (called after the module) + virtual int setup(); + + /// Whether the engine allows to fully initialize Colvars immediately + inline bool engine_ready() const + { + return engine_ready_; + } + + /// Enqueue new configuration text, to be parsed as soon as possible + void add_config(std::string const &cmd, std::string const &conf); + + /// Update data required by Colvars module (e.g. read atom positions) + /// + /// TODO Break up colvarproxy_namd and colvarproxy_lammps function into these + virtual int update_input(); + + /// Update data based on the results of a Colvars call (e.g. send forces) + virtual int update_output(); + + /// Carry out operations needed before next simulation step is run + int end_of_step(); + + /// Print a message to the main log + virtual void log(std::string const &message); + + /// Print a message to the main log and/or let the host code know about it + virtual void error(std::string const &message); + + /// Record error message (used by VMD to collect them after a script call) + void add_error_msg(std::string const &message); + + /// Retrieve accumulated error messages + std::string const & get_error_msgs(); + + /// As the name says + void clear_error_msgs(); + + /// Whether a simulation is running (warn against irrecovarable errors) + inline bool simulation_running() const + { + return b_simulation_running; + } + + /// Is the current step a repetition of a step just executed? + /// This is set to true when the step 0 of a new "run" command is being + /// executed, regardless of whether a state file has been loaded. + inline bool simulation_continuing() const + { + return b_simulation_continuing; + } + + /// Called at the end of a simulation segment (i.e. "run" command) + int post_run(); + + /// Print a full list of all input atomic arrays for debug purposes + void print_input_atomic_data(); + + /// Print a full list of all applied forces for debug purposes + void print_output_atomic_data(); + + /// Convert a version string "YYYY-MM-DD" into an integer + int get_version_from_string(char const *version_string); + + /// Get the version number (higher = more recent) + int version_number() const + { + return version_int; + } + +protected: + + /// Whether the engine allows to fully initialize Colvars immediately + bool engine_ready_; + + /// Collected error messages + std::string error_output; + + /// Whether a simulation is running (warn against irrecovarable errors) + bool b_simulation_running; + + /// Is the current step a repetition of a step just executed? + /// This is set to true when the step 0 of a new "run" command is being + /// executed, regardless of whether a state file has been loaded. + bool b_simulation_continuing; + + /// Whether the entire module should be deallocated by the host engine + bool b_delete_requested; + + /// Integer representing the version string (allows comparisons) + int version_int; + + /// Track which features have been acknowledged during the last run + size_t features_hash; + +private: + + /// Queue of config strings or files to be fed to the module + void *config_queue_; + +}; + + +#endif diff --git a/src/external/colvars/colvarproxy_io.cpp b/src/external/colvars/colvarproxy_io.cpp new file mode 100644 index 00000000000..61295b165df --- /dev/null +++ b/src/external/colvars/colvarproxy_io.cpp @@ -0,0 +1,440 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +// Using access() to check if a file exists (until we can assume C++14/17) +#if !defined(_WIN32) || defined(__CYGWIN__) +#include +#endif +#if defined(_WIN32) +#include +#endif + +#include +#include + +#include +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy_io.h" + + +colvarproxy_io::colvarproxy_io() +{ + restart_frequency_engine = 0; + input_stream_error_ = new std::istringstream(); + input_stream_error_->setstate(std::ios::badbit); + output_stream_error_ = new std::ostringstream(); + output_stream_error_->setstate(std::ios::badbit); +} + + +colvarproxy_io::~colvarproxy_io() +{ + delete input_stream_error_; + close_input_streams(); + delete output_stream_error_; + close_output_streams(); +} + + +bool colvarproxy_io::io_available() +{ + return false; +} + + +int colvarproxy_io::get_frame(long int&) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_io::set_frame(long int) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_io::backup_file(char const *filename) +{ + // Simplified version of NAMD_file_exists() + int exit_code; + do { +#if defined(_WIN32) && !defined(__CYGWIN__) + // We could use _access_s here, but it is probably too new + exit_code = _access(filename, 00); +#else + exit_code = access(filename, F_OK); +#endif + } while ((exit_code != 0) && (errno == EINTR)); + if (exit_code != 0) { + if (errno == ENOENT) { + // File does not exist + return COLVARS_OK; + } else { + return cvm::error("Unknown error while checking if file \""+ + std::string(filename)+"\" exists.\n", COLVARS_ERROR); + } + } + + // The file exists, then rename it + if (std::string(filename).rfind(std::string(".colvars.state")) != + std::string::npos) { + return rename_file(filename, (std::string(filename)+".old").c_str()); + } else { + return rename_file(filename, (std::string(filename)+".BAK").c_str()); + } +} + + +int colvarproxy_io::remove_file(char const *filename) +{ + int error_code = COLVARS_OK; +#if defined(_WIN32) && !defined(__CYGWIN__) + // Because the file may be open by other processes, rename it to filename.old + std::string const renamed_file(std::string(filename)+".old"); + // It may still be there from an interrupted run, so remove it to be safe + std::remove(renamed_file.c_str()); + int rename_exit_code = 0; + while ((rename_exit_code = std::rename(filename, + renamed_file.c_str())) != 0) { + if (errno == EINTR) continue; + error_code |= COLVARS_FILE_ERROR; + break; + } + // Ask to remove filename.old, but ignore any errors raised + std::remove(renamed_file.c_str()); +#else + if (std::remove(filename)) { + if (errno != ENOENT) { + error_code |= COLVARS_FILE_ERROR; + } + } +#endif + if (error_code != COLVARS_OK) { + return cvm::error("Error: in removing file \""+std::string(filename)+ + "\".\n.", + error_code); + } + return COLVARS_OK; +} + + +int colvarproxy_io::rename_file(char const *filename, char const *newfilename) +{ + int error_code = COLVARS_OK; +#if defined(_WIN32) && !defined(__CYGWIN__) + // On straight Windows, must remove the destination before renaming it + error_code |= remove_file(newfilename); +#endif + int rename_exit_code = 0; + while ((rename_exit_code = std::rename(filename, newfilename)) != 0) { + if (errno == EINTR) continue; + // Call log() instead of error to allow the next try + cvm::log("Error: in renaming file \""+std::string(filename)+"\" to \""+ + std::string(newfilename)+"\".\n."); + error_code |= COLVARS_FILE_ERROR; + if (errno == EXDEV) continue; + break; + } + return rename_exit_code ? error_code : COLVARS_OK; +} + + +int colvarproxy_io::set_input_prefix(std::string const &prefix) +{ + // set input restart name and strip the extension, if present + input_prefix_str = prefix; + if (input_prefix_str.rfind(".colvars.state") != std::string::npos) { + input_prefix_str.erase(input_prefix_str.rfind(".colvars.state"), + std::string(".colvars.state").size()); + } + return COLVARS_OK; +} + + +int colvarproxy_io::set_output_prefix(std::string const &prefix) +{ + // set input restart name and strip the extension, if present + output_prefix_str = prefix; + if (output_prefix_str.rfind(".colvars.state") != std::string::npos) { + output_prefix_str.erase(output_prefix_str.rfind(".colvars.state"), + std::string(".colvars.state").size()); + } + return COLVARS_OK; +} + + +int colvarproxy_io::set_restart_output_prefix(std::string const &prefix) +{ + // set input restart name and strip the extension, if present + restart_output_prefix_str = prefix; + if (restart_output_prefix_str.rfind(".colvars.state") != std::string::npos) { + restart_output_prefix_str.erase(restart_output_prefix_str.rfind(".colvars.state"), + std::string(".colvars.state").size()); + } + return COLVARS_OK; +} + + +int colvarproxy_io::set_default_restart_frequency(int freq) +{ + // TODO check for compatibility with colvarsRestartFrequency + restart_frequency_engine = freq; + return COLVARS_OK; +} + + + +std::istream &colvarproxy_io::input_stream(std::string const &input_name, + std::string const description, + bool error_on_fail) +{ + if (!io_available()) { + cvm::error("Error: trying to access an input file/channel " + "from the wrong thread.\n", COLVARS_BUG_ERROR); + return *input_stream_error_; + } + + if (colvarproxy_io::input_stream_exists(input_name)) { + std::ifstream *ifs = + dynamic_cast(input_streams_[input_name]); + if (ifs && !ifs->is_open()) { + // This file was opened before, re-open it. Using std::ios::binary to + // work around differences in line termination conventions + // See https://github.com/Colvars/colvars/commit/8236879f7de4 + ifs->open(input_name.c_str(), std::ios::binary); + } + } else { + input_streams_[input_name] = new std::ifstream(input_name.c_str(), + std::ios::binary); + } + + if (input_streams_[input_name]->fail() && error_on_fail) { + cvm::error("Error: cannot open "+description+" \""+input_name+"\".\n", + COLVARS_FILE_ERROR); + } + + return *(input_streams_[input_name]); +} + + +std::istream & +colvarproxy_io::input_stream_from_string(std::string const &input_name, + std::string const &content, + std::string const description) +{ + if (!io_available()) { + cvm::error("Error: trying to access an input file/channel " + "from the wrong thread.\n", COLVARS_BUG_ERROR); + return *input_stream_error_; + } + + if (colvarproxy_io::input_stream_exists(input_name)) { + + std::istringstream *iss = + dynamic_cast(input_streams_[input_name]); + if (iss) { + // If there is already a stringstream, replace it + delete iss; + } else { + std::ifstream *ifs = + dynamic_cast(input_streams_[input_name]); + if (ifs) { + if (ifs->is_open()) { + ifs->close(); + } + } + } + } + + input_streams_[input_name] = new std::istringstream(content); + + return *(input_streams_[input_name]); +} + + +bool colvarproxy_io::input_stream_exists(std::string const &input_name) +{ + return (input_streams_.count(input_name) > 0); +} + + +int colvarproxy_io::close_input_stream(std::string const &input_name) +{ + if (colvarproxy_io::input_stream_exists(input_name)) { + std::ifstream *ifs = dynamic_cast(input_streams_[input_name]); + if (ifs) { + if (ifs->is_open()) { + ifs->close(); + } + } else { + // From a string, just rewind to the begining + std::istringstream * iss = dynamic_cast(input_streams_[input_name]); + if (iss) { + iss->clear(); + iss->seekg(0); + } + } + return COLVARS_OK; + } + return cvm::error("Error: input file/channel \""+input_name+ + "\" does not exist.\n", COLVARS_FILE_ERROR); +} + + +int colvarproxy_io::delete_input_stream(std::string const &input_name) +{ + if (colvarproxy_io::close_input_stream(input_name) == COLVARS_OK) { + std::ifstream *ifs = dynamic_cast(input_streams_[input_name]); + if (ifs) { + delete ifs; + } else { + std::istringstream * iss = dynamic_cast(input_streams_[input_name]); + if (iss) { + delete iss; + } + } + input_streams_.erase(input_name); + return COLVARS_OK; + } + return cvm::error("Error: input file/channel \""+input_name+ + "\" does not exist.\n", COLVARS_FILE_ERROR); +} + + +int colvarproxy_io::close_input_streams() +{ + for (std::map::iterator ii = input_streams_.begin(); + ii != input_streams_.end(); + ii++) { + delete ii->second; + } + input_streams_.clear(); + return COLVARS_OK; +} + + +std::list colvarproxy_io::list_input_stream_names() const +{ + std::list result; + for (std::map::const_iterator ii = input_streams_.begin(); + ii != input_streams_.end(); + ii++) { + result.push_back(ii->first); + } + return result; +} + + +std::ostream & colvarproxy_io::output_stream(std::string const &output_name, + std::string const description) +{ + if (cvm::debug()) { + cvm::log("Using colvarproxy_io::output_stream()\n"); + } + + if (!io_available()) { + cvm::error("Error: trying to access an output file/channel " + "from the wrong thread.\n", COLVARS_BUG_ERROR); + return *output_stream_error_; + } + + if (colvarproxy_io::output_stream_exists(output_name)) { + return *(output_streams_[output_name]); + } + + backup_file(output_name.c_str()); + + output_streams_[output_name] = new std::ofstream(output_name.c_str(), std::ios::binary); + if (!*(output_streams_[output_name])) { + cvm::error("Error: cannot write to "+description+" \""+output_name+"\".\n", + COLVARS_FILE_ERROR); + } + + return *(output_streams_[output_name]); +} + + +bool colvarproxy_io::output_stream_exists(std::string const &output_name) +{ + return (output_streams_.count(output_name) > 0); +} + + +int colvarproxy_io::flush_output_stream(std::string const &output_name) +{ + if (!io_available()) { + // No-op + return COLVARS_OK; + } + + if (colvarproxy_io::output_stream_exists(output_name)) { + (dynamic_cast(output_streams_[output_name]))->flush(); + return COLVARS_OK; + } + + return COLVARS_OK; +} + + +int colvarproxy_io::flush_output_streams() +{ + if (!io_available()) { + return COLVARS_OK; + } + + for (std::map::iterator osi = output_streams_.begin(); + osi != output_streams_.end(); + osi++) { + (dynamic_cast(osi->second))->flush(); + } + + return COLVARS_OK; +} + + +int colvarproxy_io::close_output_stream(std::string const &output_name) +{ + if (!io_available()) { + return cvm::error("Error: trying to access an output file/channel " + "from the wrong thread.\n", COLVARS_BUG_ERROR); + } + + if (colvarproxy_io::output_stream_exists(output_name)) { + (dynamic_cast(output_streams_[output_name]))->close(); + delete output_streams_[output_name]; + output_streams_.erase(output_name); + } + + return COLVARS_OK; +} + + +int colvarproxy_io::close_output_streams() +{ + if (! io_available()) { + return COLVARS_OK; + } + + for (std::map::iterator osi = output_streams_.begin(); + osi != output_streams_.end(); + osi++) { + (dynamic_cast(osi->second))->close(); + delete osi->second; + } + output_streams_.clear(); + + return COLVARS_OK; +} diff --git a/src/external/colvars/colvarproxy_io.h b/src/external/colvars/colvarproxy_io.h new file mode 100644 index 00000000000..68f8482bc17 --- /dev/null +++ b/src/external/colvars/colvarproxy_io.h @@ -0,0 +1,187 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARPROXY_IO_H +#define COLVARPROXY_IO_H + +#include +#include +#include + + +/// Methods for data input/output +class colvarproxy_io { + +public: + + /// Constructor + colvarproxy_io(); + + /// Destructor + virtual ~colvarproxy_io(); + + /// Ensure that we're on the main thread (derived class will do actual check) + virtual bool io_available(); + + /// \brief Save the current frame number in the argument given + // Returns error code + virtual int get_frame(long int &); + + /// \brief Set the current frame number (as well as colvarmodule::it) + // Returns error code + virtual int set_frame(long int); + + /// \brief Rename the given file, before overwriting it + virtual int backup_file(char const *filename); + + /// \brief Rename the given file, before overwriting it + inline int backup_file(std::string const &filename) + { + return backup_file(filename.c_str()); + } + + /// Remove the given file (on Windows only, rename to filename.old) + virtual int remove_file(char const *filename); + + /// Remove the given file (on Windows only, rename to filename.old) + inline int remove_file(std::string const &filename) + { + return remove_file(filename.c_str()); + } + + /// Rename the given file + virtual int rename_file(char const *filename, char const *newfilename); + + /// Rename the given file + inline int rename_file(std::string const &filename, + std::string const &newfilename) + { + return rename_file(filename.c_str(), newfilename.c_str()); + } + + /// Prefix of the input state file to be read next + inline std::string const & input_prefix() const + { + return input_prefix_str; + } + + /// Initialize input_prefix (NOTE: it will be erased after state file is read) + virtual int set_input_prefix(std::string const &prefix); + + /// Default prefix to be used for all output files (final configuration) + inline std::string const & output_prefix() const + { + return output_prefix_str; + } + + /// Set default output prefix + virtual int set_output_prefix(std::string const &prefix); + + /// Prefix of the restart (checkpoint) file to be written next + inline std::string const & restart_output_prefix() const + { + return restart_output_prefix_str; + } + + /// Set default restart state file prefix + virtual int set_restart_output_prefix(std::string const &prefix); + + /// Default restart frequency (as set by the simulation engine) + inline int default_restart_frequency() const + { + return restart_frequency_engine; + } + + /// Communicate/set the restart frequency of the simulation engine + virtual int set_default_restart_frequency(int freq); + + // The input stream functions below are not virtual, because they currently + // rely on the fact that either std::ifstream or std::istringstream is used + + /// Returns a reference to given input stream, creating it if needed + /// \param input_name File name (later only a handle) + /// \param description Purpose of the file + /// \param error_on_fail Raise error when failing to open (allow testing) + std::istream & input_stream(std::string const &input_name, + std::string const description = "file/channel", + bool error_on_fail = true); + + /// Returns a reference to given input stream, creating it if needed + /// \param input_name Identifier of the input stream + /// \param content Set this string as the stream's buffer + /// \param description Purpose of the stream + std::istream & input_stream_from_string(std::string const &input_name, + std::string const &content, + std::string const description = "string"); + + /// Check if the file/channel is open (without opening it if not) + bool input_stream_exists(std::string const &input_name); + + /// Closes the given input stream + int close_input_stream(std::string const &input_name); + + /// Closes all input streams + int close_input_streams(); + + /// Same as close_input_stream(), but also removes the corresponding entry from memory + int delete_input_stream(std::string const &input_name); + + /// List all input streams that were opened at some point + std::list list_input_stream_names() const; + + /// Returns a reference to the named output file/channel (open it if needed) + /// \param output_name File name or identifier + /// \param description Purpose of the file + virtual std::ostream &output_stream(std::string const &output_name, + std::string const description); + + /// Check if the file/channel is open (without opening it if not) + virtual bool output_stream_exists(std::string const &output_name); + + /// Flushes the given output file/channel + virtual int flush_output_stream(std::string const &output_name); + + /// Flushes all output files/channels + virtual int flush_output_streams(); + + /// Closes the given output file/channel + virtual int close_output_stream(std::string const &output_name); + + /// Close all open files/channels to prevent data loss + virtual int close_output_streams(); + +protected: + + /// Prefix of the input state file to be read next + std::string input_prefix_str; + + /// Default prefix to be used for all output files (final configuration) + std::string output_prefix_str; + + /// Prefix of the restart (checkpoint) file to be written next + std::string restart_output_prefix_str; + + /// How often the simulation engine will write its own restart + int restart_frequency_engine; + + /// Container of input files/channels indexed by path name + std::map input_streams_; + + /// Object whose reference is returned when read errors occur + std::istream *input_stream_error_; + + /// Currently open output files/channels + std::map output_streams_; + + /// Object whose reference is returned when write errors occur + std::ostream *output_stream_error_; +}; + + +#endif diff --git a/src/external/colvars/colvarproxy_replicas.cpp b/src/external/colvars/colvarproxy_replicas.cpp new file mode 100644 index 00000000000..1f336d3e44d --- /dev/null +++ b/src/external/colvars/colvarproxy_replicas.cpp @@ -0,0 +1,56 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvarproxy.h" + + +colvarproxy_replicas::colvarproxy_replicas() {} + + +colvarproxy_replicas::~colvarproxy_replicas() {} + + +int colvarproxy_replicas::replica_enabled() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_replicas::replica_index() +{ + return 0; +} + + +int colvarproxy_replicas::num_replicas() +{ + return 1; +} + + +void colvarproxy_replicas::replica_comm_barrier() {} + + +int colvarproxy_replicas::replica_comm_recv(char* /* msg_data */, + int /* buf_len */, + int /* src_rep */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_replicas::replica_comm_send(char* /* msg_data */, + int /* msg_len */, + int /* dest_rep */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + diff --git a/src/external/colvars/colvarproxy_system.cpp b/src/external/colvars/colvarproxy_system.cpp new file mode 100644 index 00000000000..acab5fa4c35 --- /dev/null +++ b/src/external/colvars/colvarproxy_system.cpp @@ -0,0 +1,204 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include "colvarmodule.h" +#include "colvartypes.h" +#include "colvarproxy_system.h" + + + +colvarproxy_system::colvarproxy_system() +{ + angstrom_value_ = 0.0; + kcal_mol_value_ = 0.0; + timestep_ = 1.0; + target_temperature_ = 0.0; + boltzmann_ = 0.001987191; // Default: kcal/mol/K + boundaries_type = boundaries_unsupported; + total_force_requested = false; + indirect_lambda_biasing_force = 0.0; + cached_alch_lambda_changed = false; + cached_alch_lambda = -1.0; + reset_pbc_lattice(); +} + + +colvarproxy_system::~colvarproxy_system() {} + + +int colvarproxy_system::set_unit_system(std::string const & /* units */, + bool /* check_only */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_system::set_target_temperature(cvm::real T) +{ + target_temperature_ = T; + return COLVARS_OK; +} + + +int colvarproxy_system::set_integration_timestep(cvm::real dt) +{ + timestep_ = dt; + return COLVARS_OK; +} + + +cvm::real colvarproxy_system::rand_gaussian() +{ + // TODO define, document and implement a user method to set the value of this + return 0.0; +} + + +void colvarproxy_system::add_energy(cvm::real /* energy */) {} + + +void colvarproxy_system::request_total_force(bool yesno) +{ + if (yesno == true) + cvm::error("Error: total forces are currently not implemented.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +bool colvarproxy_system::total_forces_enabled() const +{ + return false; +} + + +bool colvarproxy_system::total_forces_same_step() const +{ + return false; +} + + +inline int round_to_integer(cvm::real x) +{ + return int(cvm::floor(x+0.5)); +} + + +void colvarproxy_system::update_pbc_lattice() +{ + // Periodicity is assumed in all directions + + if (boundaries_type == boundaries_unsupported || + boundaries_type == boundaries_non_periodic) { + cvm::error("Error: setting PBC lattice with unsupported boundaries.\n", + COLVARS_BUG_ERROR); + return; + } + + { + cvm::rvector const v = cvm::rvector::outer(unit_cell_y, unit_cell_z); + reciprocal_cell_x = v/(v*unit_cell_x); + } + { + cvm::rvector const v = cvm::rvector::outer(unit_cell_z, unit_cell_x); + reciprocal_cell_y = v/(v*unit_cell_y); + } + { + cvm::rvector const v = cvm::rvector::outer(unit_cell_x, unit_cell_y); + reciprocal_cell_z = v/(v*unit_cell_z); + } +} + + +void colvarproxy_system::reset_pbc_lattice() +{ + unit_cell_x.reset(); + unit_cell_y.reset(); + unit_cell_z.reset(); + reciprocal_cell_x.reset(); + reciprocal_cell_y.reset(); + reciprocal_cell_z.reset(); +} + + +cvm::rvector colvarproxy_system::position_distance(cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) + const +{ + if (boundaries_type == boundaries_unsupported) { + cvm::error("Error: unsupported boundary conditions.\n", COLVARS_INPUT_ERROR); + } + + cvm::rvector diff = (pos2 - pos1); + + if (boundaries_type == boundaries_non_periodic) return diff; + + cvm::real const x_shift = round_to_integer(reciprocal_cell_x*diff); + cvm::real const y_shift = round_to_integer(reciprocal_cell_y*diff); + cvm::real const z_shift = round_to_integer(reciprocal_cell_z*diff); + + diff.x -= x_shift*unit_cell_x.x + y_shift*unit_cell_y.x + + z_shift*unit_cell_z.x; + diff.y -= x_shift*unit_cell_x.y + y_shift*unit_cell_y.y + + z_shift*unit_cell_z.y; + diff.z -= x_shift*unit_cell_x.z + y_shift*unit_cell_y.z + + z_shift*unit_cell_z.z; + + return diff; +} + + +int colvarproxy_system::get_molid(int &) +{ + cvm::error("Error: only VMD allows the use of multiple \"molecules\", " + "i.e. multiple molecular systems.", COLVARS_NOT_IMPLEMENTED); + return -1; +} + + +int colvarproxy_system::get_alch_lambda(cvm::real * /* lambda */) +{ + return cvm::error("Error in get_alch_lambda: alchemical lambda dynamics is not supported by this build.", + COLVARS_NOT_IMPLEMENTED); +} + + +void colvarproxy_system::set_alch_lambda(cvm::real lambda) +{ + cached_alch_lambda = lambda; + cached_alch_lambda_changed = true; +} + + +int colvarproxy_system::send_alch_lambda() +{ + return cvm::error("Error in set_alch_lambda: alchemical lambda dynamics is not supported by this build.", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy_system::get_dE_dlambda(cvm::real * /* force */) +{ + return cvm::error("Error in get_dE_dlambda: alchemical lambda dynamics is not supported by this build.", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy_system::apply_force_dE_dlambda(cvm::real* /* force */) +{ + return cvm::error("Error in apply_force_dE_dlambda: function is not implemented by this build.", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy_system::get_d2E_dlambda2(cvm::real*) +{ + return cvm::error("Error in get_d2E_dlambda2: function is not implemented by this build.", + COLVARS_NOT_IMPLEMENTED); +} diff --git a/src/external/colvars/colvarproxy_system.h b/src/external/colvars/colvarproxy_system.h new file mode 100644 index 00000000000..67d0938e548 --- /dev/null +++ b/src/external/colvars/colvarproxy_system.h @@ -0,0 +1,186 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARPROXY_SYSTEM_H +#define COLVARPROXY_SYSTEM_H + + +/// Methods for accessing the simulation system (PBCs, integrator, etc) +class colvarproxy_system { + +public: + + /// Constructor + colvarproxy_system(); + + /// Destructor + virtual ~colvarproxy_system(); + + /// \brief Name of the unit system used internally by Colvars (by default, that of the back-end). + /// Supported depending on the back-end: real (A, kcal/mol), metal (A, eV), electron (Bohr, Hartree), gromacs (nm, kJ/mol) + /// Note: calls to back-end PBC functions assume back-end length unit + /// We use different unit from back-end in VMD bc using PBC functions from colvarproxy base class + /// Colvars internal units are user specified, because the module exchanges info in unknown + /// composite dimensions with user input, while it only exchanges quantities of known + /// dimension with the back-end (length and forces) + std::string units; + + /// \brief Request to set the units used internally by Colvars + virtual int set_unit_system(std::string const &units, bool check_only); + + /// \brief Convert a length from Angstrom to internal + inline cvm::real angstrom_to_internal(cvm::real l) const + { + return l * angstrom_value_; + } + + /// \brief Convert a length from internal to Angstrom + inline cvm::real internal_to_angstrom(cvm::real l) const + { + return l / angstrom_value_; + } + + /// Boltzmann constant, with unit the same as energy / K + inline cvm::real boltzmann() const + { + return boltzmann_; + } + + /// Current target temperature of the simulation (K units) + inline cvm::real target_temperature() const + { + return target_temperature_; + } + + /// Set the current target temperature of the simulation (K units) + virtual int set_target_temperature(cvm::real T); + + /// Time step of the simulation (fs units) + inline double dt() const + { + return timestep_; + } + + /// Set the current integration timestep of the simulation (fs units) + virtual int set_integration_timestep(cvm::real dt); + + /// \brief Pseudo-random number with Gaussian distribution + virtual cvm::real rand_gaussian(void); + + /// Pass restraint energy value for current timestep to MD engine + virtual void add_energy(cvm::real energy); + + /// \brief Get the PBC-aware distance vector between two positions + virtual cvm::rvector position_distance(cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) const; + + /// Recompute PBC reciprocal lattice (assumes XYZ periodicity) + void update_pbc_lattice(); + + /// Set the lattice vectors to zero + void reset_pbc_lattice(); + + /// \brief Tell the proxy whether total forces are needed (they may not + /// always be available) + virtual void request_total_force(bool yesno); + + /// Are total forces being used? + virtual bool total_forces_enabled() const; + + /// Are total forces from the current step available? + virtual bool total_forces_same_step() const; + + /// Get the molecule ID when called in VMD; raise error otherwise + /// \param molid Set this argument equal to the current VMD molid + virtual int get_molid(int &molid); + + /// Get value of alchemical lambda parameter from back-end (if available) + virtual int get_alch_lambda(cvm::real* lambda); + + /// Set value of alchemical lambda parameter to be sent to back-end at end of timestep + void set_alch_lambda(cvm::real lambda); + + /// Send cached value of alchemical lambda parameter to back-end (if available) + virtual int send_alch_lambda(); + + /// Get energy derivative with respect to lambda (if available) + virtual int get_dE_dlambda(cvm::real* dE_dlambda); + + /// Apply a scalar force on dE_dlambda (back-end distributes it onto atoms) + virtual int apply_force_dE_dlambda(cvm::real* force); + + /// Get energy second derivative with respect to lambda (if available) + virtual int get_d2E_dlambda2(cvm::real* d2E_dlambda2); + + /// Force to be applied onto alch. lambda, propagated from biasing forces on dE_dlambda + cvm::real indirect_lambda_biasing_force; + + /// Get weight factor from accelMD + virtual cvm::real get_accelMD_factor() const { + cvm::error("Error: accessing the reweighting factor of accelerated MD " + "is not yet implemented in the MD engine.\n", + COLVARS_NOT_IMPLEMENTED); + return 1.0; + } + virtual bool accelMD_enabled() const { + return false; + } + +protected: + + /// Next value of lambda to be sent to back-end + cvm::real cached_alch_lambda; + + /// Whether lambda has been set and needs to be updated in backend + bool cached_alch_lambda_changed; + + /// Boltzmann constant in internal Colvars units + cvm::real boltzmann_; + + /// Most up to date target temperature (K units); default to 0.0 if undefined + cvm::real target_temperature_; + + /// Current integration timestep (engine units); default to 1.0 if undefined + double timestep_; + + /// \brief Value of 1 Angstrom in the internal (front-end) Colvars unit for atomic coordinates + /// * defaults to 0 in the base class; derived proxy classes must set it + /// * in VMD proxy, can only be changed when no variables are defined + /// as user-defined values in composite units must be compatible with that system + cvm::real angstrom_value_; + + /// \brief Value of 1 kcal/mol in the internal Colvars unit for energy + cvm::real kcal_mol_value_; + + /// Whether the total forces have been requested + bool total_force_requested; + + /// \brief Type of boundary conditions + /// + /// Orthogonal and triclinic cells are made available to objects. + /// For any other conditions (mixed periodicity, triclinic cells in LAMMPS) + /// minimum-image distances are computed by the host engine regardless. + enum Boundaries_type { + boundaries_non_periodic, + boundaries_pbc_ortho, + boundaries_pbc_triclinic, + boundaries_unsupported + }; + + /// Type of boundary conditions + Boundaries_type boundaries_type; + + /// Bravais lattice vectors + cvm::rvector unit_cell_x, unit_cell_y, unit_cell_z; + + /// Reciprocal lattice vectors + cvm::rvector reciprocal_cell_x, reciprocal_cell_y, reciprocal_cell_z; +}; + +#endif diff --git a/src/external/colvars/colvarproxy_tcl.cpp b/src/external/colvars/colvarproxy_tcl.cpp new file mode 100644 index 00000000000..5bf97a0d980 --- /dev/null +++ b/src/external/colvars/colvarproxy_tcl.cpp @@ -0,0 +1,225 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include + +#include "colvarmodule.h" +#include "colvarproxy.h" +#include "colvarproxy_tcl.h" +#include "colvaratoms.h" + +#ifdef COLVARS_TCL +#include +#endif + + + +colvarproxy_tcl::colvarproxy_tcl() +{ + tcl_interp_ = NULL; +} + + +colvarproxy_tcl::~colvarproxy_tcl() +{ +} + + +void colvarproxy_tcl::init_tcl_pointers() +{ + // This is overloaded by NAMD and VMD proxies to use the local interpreters +#if defined(COLVARS_TCL) + if (tcl_interp_ == NULL) { + // Allocate a dedicated Tcl interpreter for Colvars + std::cout << "colvars: Allocating Tcl interpreter." << std::endl; + set_tcl_interp(Tcl_CreateInterp()); + } else { + std::cerr << "Error: init_tcl_pointers called with non-NULL tcl_interp_" << std::endl; + } +#else + std::cerr << "Error: Tcl support is not available in this build." << std::endl; +#endif +} + + +char const *colvarproxy_tcl::tcl_get_str(void *obj) +{ +#if defined(COLVARS_TCL) + return Tcl_GetString(reinterpret_cast(obj)); +#else + (void) obj; + return NULL; +#endif +} + + +int colvarproxy_tcl::tcl_run_script(std::string const &script) +{ +#if defined(COLVARS_TCL) + Tcl_Interp *const interp = get_tcl_interp(); + int err = Tcl_Eval(interp, script.c_str()); + if (err != TCL_OK) { + cvm::log("Error while executing Tcl script:\n"); + cvm::error(Tcl_GetStringResult(interp)); + return COLVARS_ERROR; + } + return cvm::get_error(); +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + +int colvarproxy_tcl::tcl_run_file(std::string const &fileName) +{ +#if defined(COLVARS_TCL) + Tcl_Interp *const interp = get_tcl_interp(); + int err = Tcl_EvalFile(interp, fileName.c_str()); + if (err != TCL_OK) { + cvm::log("Error while executing Tcl script file" + fileName + ":\n"); + cvm::error(Tcl_GetStringResult(interp)); + return COLVARS_ERROR; + } + return cvm::get_error(); +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + +int colvarproxy_tcl::tcl_run_force_callback() +{ +#if defined(COLVARS_TCL) + Tcl_Interp *const interp = get_tcl_interp(); + if (Tcl_FindCommand(interp, "calc_colvar_forces", NULL, 0) == NULL) { + cvm::error("Error: Colvars force procedure calc_colvar_forces is not defined.\n"); + return COLVARS_ERROR; + } + + std::string cmd = std::string("calc_colvar_forces ") + + cvm::to_str(cvm::step_absolute()); + int err = Tcl_Eval(interp, cmd.c_str()); + if (err != TCL_OK) { + cvm::log("Error while executing calc_colvar_forces:\n"); + cvm::error(Tcl_GetStringResult(interp)); + return COLVARS_ERROR; + } + return cvm::get_error(); +#else + return COLVARS_NOT_IMPLEMENTED; +#endif +} + + +int colvarproxy_tcl::tcl_run_colvar_callback( + std::string const &name, + std::vector const &cvc_values, + colvarvalue &value) +{ +#if defined(COLVARS_TCL) + + Tcl_Interp *const interp = get_tcl_interp(); + size_t i; + + std::string cmd = std::string("calc_") + name; + if (Tcl_FindCommand(interp, cmd.c_str(), NULL, 0) == NULL) { + cvm::error("Error: scripted colvar procedure \"" + cmd + "\" is not defined.\n"); + return COLVARS_ERROR; + } + + for (i = 0; i < cvc_values.size(); i++) { + cmd += std::string(" {") + (*(cvc_values[i])).to_simple_string() + + std::string("}"); + } + int err = Tcl_Eval(interp, cmd.c_str()); + const char *result = Tcl_GetStringResult(interp); + if (err != TCL_OK) { + return cvm::error(std::string("Error while executing ") + + cmd + std::string(":\n") + + std::string(Tcl_GetStringResult(interp)), + COLVARS_ERROR); + } + std::istringstream is(result); + if (value.from_simple_string(is.str()) != COLVARS_OK) { + cvm::log("Error parsing colvar value from script:"); + cvm::error(result); + return COLVARS_ERROR; + } + return cvm::get_error(); + +#else + + (void) name; + (void) cvc_values; + (void) value; + return COLVARS_NOT_IMPLEMENTED; + +#endif +} + + +int colvarproxy_tcl::tcl_run_colvar_gradient_callback( + std::string const &name, + std::vector const &cvc_values, + std::vector > &gradient) +{ +#if defined(COLVARS_TCL) + + Tcl_Interp *const interp = get_tcl_interp(); + size_t i; + + std::string cmd = std::string("calc_") + name + "_gradient"; + if (Tcl_FindCommand(interp, cmd.c_str(), NULL, 0) == NULL) { + cvm::error("Error: scripted colvar gradient procedure \"" + cmd + "\" is not defined.\n"); + return COLVARS_ERROR; + } + + for (i = 0; i < cvc_values.size(); i++) { + cmd += std::string(" {") + (*(cvc_values[i])).to_simple_string() + + std::string("}"); + } + int err = Tcl_Eval(interp, cmd.c_str()); + if (err != TCL_OK) { + return cvm::error(std::string("Error while executing ") + + cmd + std::string(":\n") + + std::string(Tcl_GetStringResult(interp)), + COLVARS_ERROR); + } + Tcl_Obj **list; + int n; + Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), + &n, &list); + if (n != int(gradient.size())) { + cvm::error("Error parsing list of gradient values from script: found " + + cvm::to_str(n) + " values instead of " + + cvm::to_str(gradient.size())); + return COLVARS_ERROR; + } + for (i = 0; i < gradient.size(); i++) { + std::istringstream is(Tcl_GetString(list[i])); + if (gradient[i].from_simple_string(is.str()) != COLVARS_OK) { + cvm::log("Gradient matrix size: " + cvm::to_str(gradient[i].size())); + cvm::log("Gradient string: " + cvm::to_str(Tcl_GetString(list[i]))); + cvm::error("Error parsing gradient value from script", COLVARS_ERROR); + return COLVARS_ERROR; + } + } + + return cvm::get_error(); + +#else + + (void) name; + (void) cvc_values; + (void) gradient; + return COLVARS_NOT_IMPLEMENTED; + +#endif +} diff --git a/src/external/colvars/colvarproxy_tcl.h b/src/external/colvars/colvarproxy_tcl.h new file mode 100644 index 00000000000..ceebd4c765a --- /dev/null +++ b/src/external/colvars/colvarproxy_tcl.h @@ -0,0 +1,89 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARPROXY_TCL_H +#define COLVARPROXY_TCL_H + +#if defined(NAMD_TCL) || defined(VMDTCL) +#define COLVARS_TCL +#endif + +#ifdef COLVARS_TCL +#include +#else +// Allow for placeholders Tcl_Interp* variables +typedef void Tcl_Interp; +#endif + +#include + + +/// Methods for using Tcl within Colvars +class colvarproxy_tcl { + +public: + + /// Constructor + colvarproxy_tcl(); + + /// Destructor + virtual ~colvarproxy_tcl(); + + /// Is Tcl available? (trigger initialization if needed) + inline bool tcl_available() { +#if defined(COLVARS_TCL) + return true; +#else + return false; +#endif + } + + /// Get a string representation of the Tcl object pointed to by obj + char const *tcl_get_str(void *obj); + + int tcl_run_script(std::string const &script); + + int tcl_run_file(std::string const &fileName); + + /// Tcl implementation of run_force_callback() + int tcl_run_force_callback(); + + /// Tcl implementation of run_colvar_callback() + int tcl_run_colvar_callback( + std::string const &name, + std::vector const &cvcs, + colvarvalue &value); + + /// Tcl implementation of run_colvar_gradient_callback() + int tcl_run_colvar_gradient_callback( + std::string const &name, + std::vector const &cvcs, + std::vector > &gradient); + + /// Get a pointer to the Tcl interpreter + inline Tcl_Interp *get_tcl_interp() + { + return tcl_interp_; + } + + /// Set the pointer to the Tcl interpreter + inline void set_tcl_interp(Tcl_Interp *interp) + { + tcl_interp_ = interp; + } + + /// Set Tcl pointers + virtual void init_tcl_pointers(); + +protected: + /// Pointer to Tcl interpreter object + Tcl_Interp *tcl_interp_; +}; + +#endif diff --git a/src/external/colvars/colvarproxy_volmaps.cpp b/src/external/colvars/colvarproxy_volmaps.cpp new file mode 100644 index 00000000000..6c1f11e32ec --- /dev/null +++ b/src/external/colvars/colvarproxy_volmaps.cpp @@ -0,0 +1,134 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include "colvarmodule.h" +#include "colvarproxy_volmaps.h" +#include "colvarmodule_utils.h" + + +colvarproxy_volmaps::colvarproxy_volmaps() +{ + volmaps_rms_applied_force_ = volmaps_max_applied_force_ = 0.0; +} + + +colvarproxy_volmaps::~colvarproxy_volmaps() {} + + +int colvarproxy_volmaps::volmaps_available() +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +int colvarproxy_volmaps::reset() +{ + for (size_t i = 0; i < volmaps_ids.size(); i++) { + clear_volmap(i); + } + volmaps_ids.clear(); + volmaps_refcount.clear(); + volmaps_values.clear(); + volmaps_new_colvar_forces.clear(); + return COLVARS_OK; +} + + +int colvarproxy_volmaps::add_volmap_slot(int volmap_id) +{ + volmaps_ids.push_back(volmap_id); + volmaps_refcount.push_back(1); + volmaps_values.push_back(0.0); + volmaps_new_colvar_forces.push_back(0.0); + return (volmaps_ids.size() - 1); +} + + +int colvarproxy_volmaps::check_volmap_by_id(int /* volmap_id */) +{ + return cvm::error("Error: selecting volumetric maps is not available.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy_volmaps::check_volmap_by_name(const char * /* volmap_name */) +{ + return cvm::error("Error: selecting volumetric maps by name is not " + "available.\n", COLVARS_NOT_IMPLEMENTED); +} + + +int colvarproxy_volmaps::init_volmap_by_name(char const * /* volmap_name */) +{ + return -1; +} + + +int colvarproxy_volmaps::init_volmap_by_id(int /* volmap_id */) +{ + return -1; +} + + +int colvarproxy_volmaps::init_volmap_by_name(std::string const &volmap_name) +{ + return init_volmap_by_name(volmap_name.c_str()); +} + + +int colvarproxy_volmaps::check_volmap_by_name(std::string const &volmap_name) +{ + return check_volmap_by_name(volmap_name.c_str()); +} + + +void colvarproxy_volmaps::clear_volmap(int index) +{ + if (((size_t) index) >= volmaps_ids.size()) { + cvm::error("Error: trying to unrequest a volumetric map that was not " + "previously requested.\n", COLVARS_INPUT_ERROR); + } + + if (volmaps_refcount[index] > 0) { + volmaps_refcount[index] -= 1; + } +} + + +int colvarproxy_volmaps::get_volmap_id_from_name(char const *volmap_name) +{ + // Raise error + colvarproxy_volmaps::check_volmap_by_name(volmap_name); + return -1; +} + + +int colvarproxy_volmaps::compute_volmap(int /* flags */, + int /* volmap_id */, + cvm::atom_iter /* atom_begin */, + cvm::atom_iter /* atom_end */, + cvm::real * /* value */, + cvm::real * /* atom_field */) +{ + return COLVARS_NOT_IMPLEMENTED; +} + + +void colvarproxy_volmaps::compute_rms_volmaps_applied_force() +{ + volmaps_rms_applied_force_ = + compute_norm2_stats(volmaps_new_colvar_forces); +} + + +void colvarproxy_volmaps::compute_max_volmaps_applied_force() +{ + volmaps_max_applied_force_ = + compute_norm2_stats(volmaps_new_colvar_forces); +} diff --git a/src/external/colvars/colvarproxy_volmaps.h b/src/external/colvars/colvarproxy_volmaps.h new file mode 100644 index 00000000000..f8c9ba85398 --- /dev/null +++ b/src/external/colvars/colvarproxy_volmaps.h @@ -0,0 +1,128 @@ +// -*- c++ -*- + +#ifndef COLVARPROXY_VOLMAPS_H +#define COLVARPROXY_VOLMAPS_H + + +/// \brief Container of grid-based objects +class colvarproxy_volmaps { + +public: + + /// Contructor + colvarproxy_volmaps(); + + /// Destructor + virtual ~colvarproxy_volmaps(); + + /// Clear volumetric map data + int reset(); + + /// \brief Whether this implementation has capability to use volumetric maps + virtual int volmaps_available(); + + /// Create a slot for a volumetric map not requested yet + int add_volmap_slot(int volmap_id); + + /// Request and prepare this volumetric map for use by Colvars + /// \param volmap_id Numeric ID used by the MD engine + /// \returns Index of the map in the colvarproxy arrays + virtual int init_volmap_by_id(int volmap_id); + + /// Request and prepare this volumetric map for use by Colvars + /// \param volmap_name Name used by the MD engine + /// \returns Index of the map in the colvarproxy arrays + virtual int init_volmap_by_name(char const *volmap_name); + + /// Check that the given volmap ID is valid (return COLVARS_OK if it is) + /// \param volmap_id Numeric ID used by the MD engine + /// \returns Error code + virtual int check_volmap_by_id(int volmap_id); + + /// Check that the given volmap name is valid (return COLVARS_OK if it is) + /// \param volmap_name Name used by the MD engine + /// \returns Error code + virtual int check_volmap_by_name(char const *volmap_name); + + /// Request and prepare this volumetric map for use by Colvars + int init_volmap_by_name(std::string const &volmap_name); + + /// Check that the given volmap name is valid (return COLVARS_OK if it is) + int check_volmap_by_name(std::string const &volmap_name); + + /// \brief Used by the CVC destructors + virtual void clear_volmap(int index); + + /// Get the numeric ID of the given volumetric map (for the MD program) + virtual int get_volmap_id_from_name(char const *volmap_name); + + /// Get the numeric ID of the given volumetric map (for the MD program) + inline int get_volmap_id(int index) const + { + return volmaps_ids[index]; + } + + /// Read the current value of the volumetric map + inline cvm::real get_volmap_value(int index) const + { + return volmaps_values[index]; + } + + /// Request that this force is applied to the given volumetric map + inline void apply_volmap_force(int index, cvm::real const &new_force) + { + volmaps_new_colvar_forces[index] += new_force; + } + + /// Re-weigh an atomic field (e.g. a colvar) by the value of a volumetric map + /// \param flags Combination of flags + /// \param volmap_id Numeric index of the map (no need to request it) + /// \param atom_begin Iterator pointing to first atom + /// \param atom_end Iterator pointing past the last atom + /// \param value Pointer to location of total to increment + /// \param atom_field Array of atomic field values (if NULL, ones are used) + virtual int compute_volmap(int flags, + int volmap_id, + cvm::atom_iter atom_begin, + cvm::atom_iter atom_end, + cvm::real *value, + cvm::real *atom_field); + + /// Flags controlling what computation is done on the map + enum { + volmap_flag_null = 0, + volmap_flag_gradients = 1, + volmap_flag_use_atom_field = (1<<8) + }; + + /// Compute the root-mean-square of the applied forces + void compute_rms_volmaps_applied_force(); + + /// Compute the maximum norm among all applied forces + void compute_max_volmaps_applied_force(); + +protected: + + /// \brief Array of numeric IDs of volumetric maps + std::vector volmaps_ids; + + /// \brief Keep track of how many times each vol map is used by a + /// separate colvar object + std::vector volmaps_refcount; + + /// \brief Current values of the vol maps + std::vector volmaps_values; + + /// \brief Forces applied from colvars, to be communicated to the MD + /// integrator + std::vector volmaps_new_colvar_forces; + + /// Root-mean-square of the the applied forces + cvm::real volmaps_rms_applied_force_; + + /// Maximum norm among all applied forces + cvm::real volmaps_max_applied_force_; +}; + + +#endif diff --git a/src/external/colvars/colvars_memstream.cpp b/src/external/colvars/colvars_memstream.cpp new file mode 100644 index 00000000000..13cb8fb3433 --- /dev/null +++ b/src/external/colvars/colvars_memstream.cpp @@ -0,0 +1,102 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include "colvarmodule.h" +#include "colvartypes.h" +#include "colvarvalue.h" +#include "colvars_memstream.h" + + +bool cvm::memory_stream::expand_output_buffer(size_t add_bytes) +{ + auto &buffer = external_output_buffer_ ? *external_output_buffer_ : internal_buffer_; + if ((buffer.size() + add_bytes) <= max_length_) { + buffer.resize((buffer.size() + add_bytes)); + } else { + setstate(std::ios::badbit); + } + return bool(*this); +} + + +template <> void cvm::memory_stream::write_object(std::string const &t) +{ + size_t const string_length = t.size(); + size_t const new_data_size = sizeof(size_t) + sizeof(char) * string_length; + if (expand_output_buffer(new_data_size)) { + std::memcpy(output_location(), &string_length, sizeof(size_t)); + incr_write_pos(sizeof(size_t)); + std::memcpy(output_location(), t.c_str(), t.size() * sizeof(char)); + incr_write_pos(t.size() * sizeof(char)); + } +} + +template <> cvm::memory_stream &operator<<(cvm::memory_stream &os, std::string const &t) +{ + os.write_object(t); + return os; +} + +template <> void cvm::memory_stream::write_object(colvarvalue const &t) +{ + *this << t; +} + +template <> void cvm::memory_stream::write_object(cvm::vector1d const &t) +{ + return write_vector(t.data_array()); +} + +template <> +cvm::memory_stream &operator<<(cvm::memory_stream &os, cvm::vector1d const &t) +{ + os.write_vector(t.data_array()); + return os; +} + + +template <> void cvm::memory_stream::read_object(std::string &t) +{ + begin_reading(); + size_t string_length = 0; + if (has_remaining(sizeof(size_t))) { + std::memcpy(&string_length, input_location(), sizeof(size_t)); + incr_read_pos(sizeof(size_t)); + if (has_remaining(string_length * sizeof(char))) { + t.assign(reinterpret_cast(input_location()), string_length); + incr_read_pos(string_length * sizeof(char)); + done_reading(); + } else { + setstate(std::ios::failbit); + } + } +} + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, std::string &t) +{ + is.read_object(t); + return is; +} + +template <> void cvm::memory_stream::read_object(colvarvalue &t) +{ + *this >> t; +} + +template <> void cvm::memory_stream::read_object(cvm::vector1d &t) +{ + return read_vector(t.data_array()); +} + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, cvm::vector1d &t) +{ + is.read_vector(t.data_array()); + return is; +} diff --git a/src/external/colvars/colvars_memstream.h b/src/external/colvars/colvars_memstream.h new file mode 100644 index 00000000000..0d80d2794d3 --- /dev/null +++ b/src/external/colvars/colvars_memstream.h @@ -0,0 +1,289 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef MEMORY_STREAM_H +#define MEMORY_STREAM_H + +#include +#include +#include +#include +#include + + +// Work around missing std::is_trivially_copyable in old GCC and Clang versions +// TODO remove this after CentOS 7 has been beyond EOL for a while +#if (defined(__GNUC__) && (__GNUC__ < 5) && !defined(__clang__)) || (defined(__clang__) && (__clang_major__ < 7)) +// Clang needs an exception, because it defines __GNUC__ as well +#define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) +#else +#define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable::value +#endif + + +class cvm::memory_stream { + +public: + + /// Set up an empty stream with an internal buffer, suitable for writing to + /// \param max_length Maximum allowed capacity (default is 64 GiB) + memory_stream(size_t max_length = (static_cast(1L) << 36)) : max_length_(max_length) {} + + /// Set up a stream based on an external input buffer + memory_stream(size_t n, unsigned char const *buf) + : external_input_buffer_(buf), internal_buffer_(), data_length_(n), max_length_(data_length_) + { + } + + /// Set up a stream based on an external output buffer + memory_stream(std::vector &buf) : memory_stream() + { + external_output_buffer_ = &buf; + } + + /// Length of the buffer + inline size_t length() const { return data_length_; } + + /// Output buffer + inline unsigned char *output_buffer() + { + return (external_output_buffer_ ? external_output_buffer_->data() : internal_buffer_.data()); + } + + /// Next location to write to + inline unsigned char *output_location() { return output_buffer() + data_length_; } + + /// Input buffer + inline unsigned char const *input_buffer() const + { + return (external_input_buffer_ ? external_input_buffer_ : internal_buffer_.data()); + } + + /// Next location to read from + inline unsigned char const *input_location() const { return input_buffer() + read_pos_; } + + /// Cast operator to be used to test for errors + inline explicit operator bool() const { return state_ == std::ios::goodbit; } + + /// Write a simple object to the output buffer + template void write_object(T const &t); + + /// Wrapper to write_object() + template friend memory_stream &operator<<(memory_stream &os, T const &t); + + /// Write a vector of simple objects to the output buffer + template void write_vector(std::vector const &t); + + /// Wrapper to write_vector() + template + friend memory_stream &operator<<(memory_stream &os, std::vector const &t); + + /// Read a simple object from the buffer + template void read_object(T &t); + + /// Wrapper to read_object() + template friend memory_stream &operator>>(memory_stream &is, T &t); + + /// Read a vector of simple objects from the buffer + template void read_vector(std::vector &t); + + /// Wrapper to read_vector() + template friend memory_stream &operator>>(memory_stream &is, std::vector &t); + + + // Compatibility with STL stream functions + + /// Report the current position in the buffer + inline size_t tellg() const { return read_pos_; } + + /// Report the current position in the buffer + inline memory_stream & seekg(size_t pos) { read_pos_ = pos; return *this; } + + /// Ignore formatting operators + inline void setf(decltype(std::ios::fmtflags(0)), decltype(std::ios::floatfield)) {} + + /// Ignore formatting operators + inline void flags(decltype(std::ios::fmtflags(0))) {} + + /// Get the current formatting flags (i.e. none because this stream is unformatted) + inline decltype(std::ios::fmtflags(0)) flags() const { return std::ios::fmtflags(0); } + + /// Get the error code + inline std::ios::iostate rdstate() const { return state_; } + + /// Set the error code + inline void setstate(std::ios::iostate new_state) { state_ |= new_state; } + + /// Clear the error code + inline void clear() { state_ = std::ios::goodbit; } + +protected: + + /// External output buffer + std::vector *external_output_buffer_ = nullptr; + + /// External input buffer + unsigned char const *external_input_buffer_ = nullptr; + + /// Internal buffer (may server for both input and output) + std::vector internal_buffer_; + + /// Length of the data buffer (either internal or external) + size_t data_length_ = 0L; + + /// Largest allowed capacity of the data buffer + size_t const max_length_; + + /// Error status + std::ios::iostate state_ = std::ios::goodbit; + + /// Add the requester number of bytes to the array capacity; return false if buffer is external + bool expand_output_buffer(size_t add_bytes); + + /// Move the buffer position past the data just written + inline void incr_write_pos(size_t c) { data_length_ += c; } + + /// Current position when reading from the buffer + size_t read_pos_ = 0L; + + /// Begin an attempt to read an object; assume EOF unless there is space remaining + inline void begin_reading() { setstate(std::ios::eofbit); } + + /// Mark the reading attempt succesful + inline void done_reading() { clear(); } + + /// Move the buffer position past the data just read + inline void incr_read_pos(size_t c) { read_pos_ += c; } + + /// Check that the buffer contains enough bytes to read as the argument says, set error + /// otherwise + inline bool has_remaining(size_t c) { return c <= (data_length_ - read_pos_); } + }; + +template void cvm::memory_stream::write_object(T const &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use write_object() on complex type"); + size_t const new_data_size = sizeof(T); + if (expand_output_buffer(new_data_size)) { + std::memcpy(output_location(), &t, sizeof(T)); + incr_write_pos(new_data_size); + } +} + +template cvm::memory_stream &operator<<(cvm::memory_stream &os, T const &t) +{ + os.write_object(t); + return os; +} + +template void cvm::memory_stream::write_vector(std::vector const &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use write_vector() on complex type"); + size_t const vector_length = t.size(); + size_t const new_data_size = sizeof(size_t) + sizeof(T) * vector_length; + if (expand_output_buffer(new_data_size)) { + std::memcpy(output_location(), &vector_length, sizeof(size_t)); + incr_write_pos(sizeof(T)); + std::memcpy(output_location(), t.data(), t.size() * sizeof(T)); + incr_write_pos(t.size() * sizeof(T)); + } +} + +template +cvm::memory_stream &operator<<(cvm::memory_stream &os, std::vector const &t) +{ + os.write_vector(t); + return os; +} + +template void cvm::memory_stream::read_object(T &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use read_object() on complex type"); + begin_reading(); + if (has_remaining(sizeof(T))) { + std::memcpy(&t, input_location(), sizeof(T)); + incr_read_pos(sizeof(T)); + done_reading(); + } +} + +template cvm::memory_stream &operator>>(cvm::memory_stream &is, T &t) +{ + is.read_object(t); + return is; +} + +template void cvm::memory_stream::read_vector(std::vector &t) +{ + static_assert(IS_TRIVIALLY_COPYABLE(T), "Cannot use read_vector() on complex type"); + begin_reading(); + size_t vector_length = 0; + if (has_remaining(sizeof(size_t))) { + std::memcpy(&vector_length, input_location(), sizeof(size_t)); + incr_read_pos(sizeof(size_t)); + if (has_remaining(vector_length * sizeof(T))) { + t.resize(vector_length); + std::memcpy(t.data(), input_location(), vector_length * sizeof(T)); + incr_read_pos(vector_length * sizeof(T)); + done_reading(); + } else { + setstate(std::ios::failbit); + } + } +} + +template cvm::memory_stream &operator>>(cvm::memory_stream &is, std::vector &t) +{ + is.read_vector(t); + return is; +} + +template cvm::memory_stream &operator<<(cvm::memory_stream &os, + decltype(std::setprecision(10)) const &) +{ + return os; +} + +#if !defined(_MSC_VER) && !defined(__SUNPRO_CC) +// Visual Studio and MSVC use the same return type for both modifiers +template cvm::memory_stream &operator<<(cvm::memory_stream &os, + decltype(std::setw(10)) const &) +{ + return os; +} +#endif + +// Declare specializations + +template <> void cvm::memory_stream::write_object(std::string const &t); + +template <> cvm::memory_stream &operator<<(cvm::memory_stream &os, std::string const &t); + +template <> void cvm::memory_stream::write_object(colvarvalue const &t); + +template <> cvm::memory_stream &operator<<(cvm::memory_stream &os, colvarvalue const &x); + +template <> void cvm::memory_stream::write_object(cvm::vector1d const &t); + +template <> +cvm::memory_stream &operator<<(cvm::memory_stream &os, cvm::vector1d const &t); + +template <> void cvm::memory_stream::read_object(std::string &t); + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, std::string &t); + +template <> void cvm::memory_stream::read_object(colvarvalue &t); + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, colvarvalue &t); + +template <> void cvm::memory_stream::read_object(cvm::vector1d &t); + +template <> cvm::memory_stream &operator>>(cvm::memory_stream &is, cvm::vector1d &t); + +#endif diff --git a/src/external/colvars/colvars_version.h b/src/external/colvars/colvars_version.h new file mode 100644 index 00000000000..de4e9d47d6f --- /dev/null +++ b/src/external/colvars/colvars_version.h @@ -0,0 +1,3 @@ +#ifndef COLVARS_VERSION +#define COLVARS_VERSION "2023-10-23" +#endif diff --git a/src/external/colvars/colvarscript.cpp b/src/external/colvars/colvarscript.cpp new file mode 100644 index 00000000000..0a04dd68279 --- /dev/null +++ b/src/external/colvars/colvarscript.cpp @@ -0,0 +1,923 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarproxy.h" +#include "colvardeps.h" +#include "colvarscript.h" +#include "colvarscript_commands.h" + + + +#ifdef COLVARS_TCL +/// Run the script API via Tcl command-line interface +/// \param clientData Not used +/// \param my_interp Pointer to Tcl_Interp object (read from Colvars if NULL) +/// \param objc Number of Tcl command parameters +/// \param objv Array of command parameters +/// \return Result of the script command +extern "C" int tcl_run_colvarscript_command(ClientData clientData, + Tcl_Interp *interp_in, + int objc, Tcl_Obj *const objv[]); +#endif + + +colvarscript::colvarscript(colvarproxy *p, colvarmodule *m) + : proxy_(p), + colvars(m) +{ + cmdline_main_cmd_ = std::string("cv"); + cmd_names = NULL; + init_commands(); +#ifdef COLVARS_TCL + // must be called after constructing derived proxy class to allow for overloading + proxy()->init_tcl_pointers(); + // TODO put this in backend functions so we don't have to delete + Tcl_Interp *const interp = proxy()->get_tcl_interp(); + if (interp == NULL) { + cvm::error("Error: trying to construct colvarscript without a Tcl interpreter.\n"); + return; + } + Tcl_DeleteCommand(interp, "cv"); + Tcl_CreateObjCommand(interp, "cv", tcl_run_colvarscript_command, + (ClientData) this, (Tcl_CmdDeleteProc *) NULL); + cvm::log("Redefining the Tcl \"cv\" command to the new script interface.\n"); +#endif +} + + +colvarscript::~colvarscript() +{ + if (cmd_names) { + delete [] cmd_names; + cmd_names = NULL; + } +} + + +int colvarscript::init_commands() +{ + if (cvm::debug()) { + cvm::log("Called colvarcript::init_commands()\n"); + } + + cmd_help.resize(colvarscript::cv_n_commands); + cmd_rethelp.resize(colvarscript::cv_n_commands); + cmd_n_args_min.resize(colvarscript::cv_n_commands); + cmd_n_args_max.resize(colvarscript::cv_n_commands); + cmd_arghelp.resize(colvarscript::cv_n_commands); + cmd_full_help.resize(colvarscript::cv_n_commands); + cmd_fns.resize(colvarscript::cv_n_commands); + + if (cmd_names) { + delete [] cmd_names; + cmd_names = NULL; + } + cmd_names = new char const * [colvarscript::cv_n_commands]; + +#undef COLVARSCRIPT_COMMANDS_H // disable include guard +#if defined(CVSCRIPT) +#undef CVSCRIPT // disable default macro +#endif +#define CVSCRIPT_COMM_INIT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGHELP) { \ + init_command(COMM,#COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGHELP,&(CVSCRIPT_COMM_FNAME(COMM))); \ + } +#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + CVSCRIPT_COMM_INIT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS) + +#include "colvarscript_commands.h" + +#undef CVSCRIPT_COMM_INIT +#undef CVSCRIPT + + return COLVARS_OK; +} + + +int colvarscript::init_command(colvarscript::command const &comm, + char const *name, char const *help, + int n_args_min, int n_args_max, + char const *arghelp, + int (*fn)(void *, int, unsigned char * const *)) +{ + cmd_str_map[std::string(name)] = comm; + cmd_names[comm] = name; + + // Initialize short help string and return-value help string (if present) + { + std::string const help_str(help); + std::istringstream is(help_str); + std::string line; + std::getline(is, line); + cmd_help[comm] = line; + cmd_rethelp[comm] = ""; + while (std::getline(is, line)) { + cmd_rethelp[comm] += line + "\n"; + } + } + + // Initialize arguments' help strings + cmd_n_args_min[comm] = n_args_min; + cmd_n_args_max[comm] = n_args_max; + { + std::string const arghelp_str(arghelp); + std::istringstream is(arghelp_str); + std::string line; + for (int iarg = 0; iarg < n_args_max; iarg++) { + if (! std::getline(is, line)) { + return cvm::error("Error: could not initialize help string for scripting " + "command \""+std::string(name)+"\".\n", COLVARS_BUG_ERROR); + } + cmd_arghelp[comm].push_back(line); + } + } + + cmd_full_help[comm] = cmd_help[comm]+"\n"; + if (cmd_n_args_max[comm] > 0) { + cmd_full_help[comm] += "\nParameters\n"; + cmd_full_help[comm] += "----------\n\n"; + size_t i; + for (i = 0; i < cmd_n_args_min[comm]; i++) { + cmd_full_help[comm] += cmd_arghelp[comm][i]+"\n"; + } + for (i = cmd_n_args_min[comm]; i < cmd_n_args_max[comm]; i++) { + cmd_full_help[comm] += cmd_arghelp[comm][i]+" (optional)\n"; + } + } + if (cmd_rethelp[comm].size() > 0) { + cmd_full_help[comm] += "\nReturns\n"; + cmd_full_help[comm] += "-------\n\n"; + cmd_full_help[comm] += cmd_rethelp[comm]+"\n"; + } + + cmd_fns[comm] = fn; + if (cvm::debug()) { + cvm::log("Defined command \""+std::string(name)+"\", with help string:\n"); + cvm::log(get_command_full_help(name)); + } + + return COLVARS_OK; +} + + +std::string colvarscript::get_cmd_prefix(colvarscript::Object_type t) +{ + switch (t) { + case use_module: + return std::string("cv_"); break; + case use_colvar: + return std::string("colvar_"); break; + case use_bias: + return std::string("bias_"); break; + default: + cvm::error("Error: undefined colvarscript object type.", COLVARS_BUG_ERROR); + return std::string(""); + } +} + + + +char const *colvarscript::get_command_help(char const *cmd) +{ + if (cmd_str_map.count(cmd) > 0) { + colvarscript::command const c = cmd_str_map[std::string(cmd)]; + return cmd_help[c].c_str(); + } + cvm::error("Error: command "+std::string(cmd)+ + " is not implemented.\n", COLVARS_INPUT_ERROR); + return NULL; +} + + +char const *colvarscript::get_command_rethelp(char const *cmd) +{ + if (cmd_str_map.count(cmd) > 0) { + colvarscript::command const c = cmd_str_map[std::string(cmd)]; + return cmd_rethelp[c].c_str(); + } + cvm::error("Error: command "+std::string(cmd)+ + " is not implemented.\n", COLVARS_INPUT_ERROR); + return NULL; +} + + +char const *colvarscript::get_command_arghelp(char const *cmd, int i) +{ + if (cmd_str_map.count(cmd) > 0) { + colvarscript::command const c = cmd_str_map[std::string(cmd)]; + return cmd_arghelp[c][i].c_str(); + } + cvm::error("Error: command "+std::string(cmd)+ + " is not implemented.\n", COLVARS_INPUT_ERROR); + return NULL; +} + + +int colvarscript::get_command_n_args_min(char const *cmd) +{ + if (cmd_str_map.count(cmd) > 0) { + colvarscript::command const c = cmd_str_map[std::string(cmd)]; + return cmd_n_args_min[c]; + } + cvm::error("Error: command "+std::string(cmd)+ + " is not implemented.\n", COLVARS_INPUT_ERROR); + return -1; +} + + +int colvarscript::get_command_n_args_max(char const *cmd) +{ + if (cmd_str_map.count(cmd) > 0) { + colvarscript::command const c = cmd_str_map[std::string(cmd)]; + return cmd_n_args_max[c]; + } + cvm::error("Error: command "+std::string(cmd)+ + " is not implemented.\n", COLVARS_INPUT_ERROR); + return -1; +} + + +char const *colvarscript::get_command_full_help(char const *cmd) +{ + if (cmd_str_map.count(cmd) > 0) { + colvarscript::command const c = cmd_str_map[std::string(cmd)]; + return cmd_full_help[c].c_str(); + } + cvm::error("Error: command "+std::string(cmd)+ + " is not implemented.\n", COLVARS_INPUT_ERROR); + return NULL; +} + + +std::string colvarscript::get_command_cmdline_syntax(colvarscript::Object_type t, + colvarscript::command cmd) +{ + std::string const prefix = get_cmd_prefix(t); + std::string const cmdstr(cmd_names[cmd]); + + // Get the sub-command as used in the command line + std::string const cmdline_cmd(cmdstr, prefix.size()); + std::string cmdline_args; + + size_t i; + for (i = 0; i < cmd_n_args_min[cmd]; i++) { + std::string const &arghelp = cmd_arghelp[cmd][i]; + size_t space = arghelp.find(" : "); + cmdline_args += " <"+cmd_arghelp[cmd][i].substr(0, space)+">"; + } + for (i = cmd_n_args_min[cmd]; i < cmd_n_args_max[cmd]; i++) { + std::string const &arghelp = cmd_arghelp[cmd][i]; + size_t space = arghelp.find(" : "); + cmdline_args += " ["+cmd_arghelp[cmd][i].substr(0, space)+"]"; + } + + switch (t) { + case use_module: + return std::string(cmdline_main_cmd_ + " " + cmdline_cmd + cmdline_args); + break; + case use_colvar: + return std::string(cmdline_main_cmd_ + " colvar name " + cmdline_cmd+ + cmdline_args); + break; + case use_bias: + return std::string(cmdline_main_cmd_ + " bias name " + cmdline_cmd+cmdline_args); + break; + default: + // Already handled, but silence the warning + return std::string(""); + } + + return std::string(""); +} + + +std::string colvarscript::get_cmdline_help_summary(colvarscript::Object_type t) +{ + std::string output; + output += "List of commands:\n\n"; + + for (size_t i = 0; i < cmd_help.size(); i++) { + std::string const prefix = get_cmd_prefix(t); + command const c = cmd_str_map[std::string(cmd_names[i])]; + if (std::string(cmd_names[i], prefix.size()) == prefix) { + output += get_command_cmdline_syntax(t, c)+std::string("\n"); + } + } + if (t == use_module) { + output += "\nFor detailed help on each command use:\n" + " cv help \n"; + output += "\nTo list all commands acting on collective variables use:\n" + " cv help colvar\n"; + output += "\nTo list all commands acting on biases use:\n" + " cv help bias\n"; + } + if (t == use_colvar) { + output += "\nFor detailed help on each command use:\n" + " cv colvar name help (\"name\" does not need to exist)\n"; + } + if (t == use_bias) { + output += "\nFor detailed help on each command use:\n" + " cv bias name help (\"name\" does not need to exist)\n"; + } + return output; +} + + +std::string colvarscript::get_command_cmdline_help(colvarscript::Object_type t, + std::string const &cmd) +{ + std::string const cmdkey(get_cmd_prefix(t)+cmd); + if (cmd_str_map.count(cmdkey) > 0) { + command const c = cmd_str_map[cmdkey]; + return get_command_cmdline_syntax(t, c)+"\n\n"+ + get_command_full_help(cmd_names[c]); + } + cvm::set_error_bits(COLVARS_INPUT_ERROR); + return std::string("Could not find scripting command \""+cmd+"\"."); +} + + +int colvarscript::run(int objc, unsigned char *const objv[]) +{ + clear_str_result(); + + if (cvm::debug()) { + cvm::log("Called script run with " + cvm::to_str(objc) + " args:"); + for (int i = 0; i < objc; i++) { + cvm::log(obj_to_str(objv[i])); + } + } + + if (objc < 2) { + set_result_str("No commands given: use \""+cmdline_main_cmd_+" help\" " + "for a list of commands."); + return COLVARSCRIPT_ERROR; + } + + // Main command; usually "cv" + std::string const main_cmd(std::string(obj_to_str(objv[0]))); + + // Name of the (sub)command + std::string const cmd(obj_to_str(objv[1])); + + // Build a safe-to-print command line to print in case of error + std::string cmdline(main_cmd+std::string(" ")+cmd); + + // Pointer to the function implementing it + int (*cmd_fn)(void *, int, unsigned char * const *) = NULL; + + // Pointer to object handling the command (the functions are C) + void *obj_for_cmd = NULL; + + if (cmd == "colvar") { + + if (objc < 4) { + add_error_msg("Missing parameters: use \""+main_cmd+ + " help colvar\" for a summary"); + return COLVARSCRIPT_ERROR; + } + std::string const name(obj_to_str(objv[2])); + std::string const subcmd(obj_to_str(objv[3])); + obj_for_cmd = reinterpret_cast(cvm::colvar_by_name(name)); + if (obj_for_cmd == NULL) { + if (subcmd != std::string("help")) { + // Unless asking for help, a valid colvar name must be given + add_error_msg("Colvar not found: " + name); + return COLVARSCRIPT_ERROR; + } + } + cmd_fn = get_cmd_fn(get_cmd_prefix(use_colvar)+subcmd); + cmdline += std::string(" name ")+subcmd; + if (objc > 4) { + cmdline += " ..."; + } + + } else if (cmd == "bias") { + + if (objc < 4) { + add_error_msg("Missing parameters: use \""+main_cmd+ + " help bias\" for a summary"); + return COLVARSCRIPT_ERROR; + } + std::string const name(obj_to_str(objv[2])); + std::string const subcmd(obj_to_str(objv[3])); + obj_for_cmd = reinterpret_cast(cvm::bias_by_name(name)); + if (obj_for_cmd == NULL) { + if ((subcmd == "") || (subcmd != std::string("help"))) { + // Unless asking for help, a valid bias name must be given + add_error_msg("Bias not found: " + name); + return COLVARSCRIPT_ERROR; + } + } + cmd_fn = get_cmd_fn(get_cmd_prefix(use_bias)+subcmd); + cmdline += std::string(" name ")+subcmd; + if (objc > 4) { + cmdline += " ..."; + } + + } else { + + cmd_fn = get_cmd_fn(get_cmd_prefix(use_module)+cmd); + obj_for_cmd = reinterpret_cast(this); + + if (objc > 2) { + cmdline += " ..."; + } + } + + int error_code = COLVARS_OK; + + // If command was found in map, execute it + if (cmd_fn) { + error_code = (*cmd_fn)(obj_for_cmd, objc, objv); + } else { + add_error_msg("Syntax error: "+cmdline+"\n" + " Run \""+main_cmd+" help\" or \""+ + main_cmd+" help \" " + "to get the correct syntax.\n"); + error_code = COLVARSCRIPT_ERROR; + } + + return error_code; +} + + +char *colvarscript::obj_to_str(unsigned char *obj) +{ + char *strobj = reinterpret_cast(obj); + if (cvm::debug()) { + cvm::log("Using simple-cast script::obj_to_str(): result = \"" + + (strobj ? std::string(strobj) : std::string("(null)")) + "\""); + } + return strobj; +} + + +std::vector colvarscript::obj_to_str_vector(unsigned char *obj) +{ + if (cvm::debug()) { + cvm::log("Using simple-cast colvarscript::obj_to_str_vector().\n"); + } + + std::vector new_result; + std::string const str(reinterpret_cast(obj)); + + // TODO get rid of this once colvarscript can handle both fix_modify and Tcl? + // LAMMPS has a nicer function in the utils class + + for (size_t i = 0; i < str.length(); i++) { + char const c = str[i]; + if (c == '\"') { + i++; + if (i >= str.length()) { + cvm::error("Error: could not split the following string:\n"+ + str+"\n", COLVARS_INPUT_ERROR); + break; + } + new_result.push_back(std::string("")); + while (str[i] != '\"') { + new_result.back().append(1, str[i]); + if (i >= str.length()) { + cvm::error("Error: could not split the following string:\n"+ + str+"\n", COLVARS_INPUT_ERROR); + break; + } else { + i++; + } + } + } + } + + if (cvm::debug()) { + cvm::log("result = "+cvm::to_str(new_result)+".\n"); + } + + return new_result; +} + + +int colvarscript::proc_features(colvardeps *obj, + int objc, unsigned char *const objv[]) { + + // size was already checked before calling + std::string const subcmd(obj_to_str(objv[3])); + + if (cvm::debug()) { + cvm::log("Called proc_features() with " + cvm::to_str(objc) + " args:"); + for (int i = 0; i < objc; i++) { + cvm::log(obj_to_str(objv[i])); + } + } + + if ((subcmd == "get") || (subcmd == "set")) { + std::vector const &features = obj->features(); + std::string const req_feature(obj_to_str(objv[4])); + colvardeps::feature *f = NULL; + int fid = 0; + for (fid = 0; fid < int(features.size()); fid++) { + if (features[fid]->description == + colvarparse::to_lower_cppstr(req_feature)) { + f = features[fid]; + break; + } + } + + if (f == NULL) { + + add_error_msg("Error: feature \""+req_feature+"\" does not exist.\n"); + return COLVARSCRIPT_ERROR; + + } else { + + if (! obj->is_available(fid)) { + add_error_msg("Error: feature \""+req_feature+"\" is unavailable.\n"); + return COLVARSCRIPT_ERROR; + } + + if (subcmd == "get") { + set_result_str(cvm::to_str(obj->is_enabled(fid) ? 1 : 0)); + return COLVARS_OK; + } + + if (subcmd == "set") { + if (objc == 6) { + std::string const yesno = + colvarparse::to_lower_cppstr(std::string(obj_to_str(objv[5]))); + if ((yesno == std::string("yes")) || + (yesno == std::string("on")) || + (yesno == std::string("1"))) { + obj->enable(fid); + return COLVARS_OK; + } else if ((yesno == std::string("no")) || + (yesno == std::string("off")) || + (yesno == std::string("0"))) { + obj->disable(fid); + return COLVARS_OK; + } + } + add_error_msg("Missing value when setting feature \""+req_feature+ + "\".\n"); + return COLVARSCRIPT_ERROR; + } + } + } + + // This shouldn't be reached any more + return COLVARSCRIPT_ERROR; +} + + +int colvarscript::unsupported_op() +{ + return cvm::error("Error: unsupported script operation.\n", + COLVARS_NOT_IMPLEMENTED); +} + + +int colvarscript::set_result_str(std::string const &s) +{ + if (cvm::get_error() != COLVARS_OK) { + // Avoid overwriting the error message + modify_str_result() += s; + } else { + modify_str_result() = s; + } + return COLVARS_OK; +} + + +void colvarscript::add_error_msg(std::string const &s) +{ + modify_str_result() += s; + // Ensure terminating newlines + if (s[s.size()-1] != '\n') { + modify_str_result() += "\n"; + } +} + + +int colvarscript::clear_str_result() +{ + modify_str_result().clear(); + return COLVARS_OK; +} + + +extern "C" +int run_colvarscript_command(int objc, unsigned char *const objv[]) +{ + colvarmodule *cv = cvm::main(); + colvarscript *script = cv ? cv->proxy->script : NULL; + if (!script) { + cvm::error("Called run_colvarscript_command without a script object.\n", + COLVARS_BUG_ERROR); + return -1; + } + int retval = script->run(objc, objv); + return retval; +} + + +extern "C" +const char * get_colvarscript_result() +{ + colvarscript *script = colvarscript_obj(); + if (!script) { + cvm::error("Called get_colvarscript_result without a script object.\n"); + return NULL; + } + return script->str_result().c_str(); +} + + +#if defined(COLVARS_TCL) + +#if defined(VMDTCL) +// Function used by VMD to set up the module +int tcl_colvars_vmd_init(Tcl_Interp *interp, int molid); +#endif + +#if !defined(VMDTCL) && !defined(NAMD_TCL) +// Initialize Colvars when loaded as a shared library into Tcl interpreter +extern "C" { + int Colvars_Init(Tcl_Interp *interp) { + colvarproxy *proxy = new colvarproxy(); + colvarmodule *colvars = new colvarmodule(proxy); + proxy->set_tcl_interp(interp); + proxy->colvars = colvars; + Tcl_CreateObjCommand(interp, "cv", tcl_run_colvarscript_command, + (ClientData *) NULL, (Tcl_CmdDeleteProc *) NULL); + Tcl_EvalEx(interp, "package provide colvars", -1, 0); + return TCL_OK; + } +} +#endif + + +extern "C" int tcl_run_colvarscript_command(ClientData /* clientData */, + Tcl_Interp *my_interp, + int objc, Tcl_Obj *const objv[]) +{ + colvarmodule *colvars = cvm::main(); + + if (!colvars) { +#if defined(VMDTCL) + + if (objc == 2) { + if (!strcmp(Tcl_GetString(objv[1]), "molid")) { + // return invalid molid + Tcl_SetResult(my_interp, (char *) "-1", TCL_STATIC); + } + if (!strcmp(Tcl_GetString(objv[1]), "delete") || + !strcmp(Tcl_GetString(objv[1]), "reset")) { + // nothing to delete or reset + Tcl_SetResult(my_interp, NULL, TCL_STATIC); + } + if (!strcmp(Tcl_GetString(objv[1]), "help")) { + // print message + Tcl_SetResult(my_interp, + (char *) "First, setup the Colvars module with: " + "cv molid |top", TCL_STATIC); + } + return TCL_OK; + } + + if (objc >= 3) { + // require a molid to create the module + if (!strcmp(Tcl_GetString(objv[1]), "molid")) { + int molid = -(1<<16); // This value is used to indicate "top" + if (strcmp(Tcl_GetString(objv[2]), "top")) { + // If this is not "top", get the integer value + Tcl_GetIntFromObj(my_interp, objv[2], &molid); + } + return tcl_colvars_vmd_init(my_interp, molid); + } else { + Tcl_SetResult(my_interp, (char *) "Syntax error. First, setup the Colvars module with cv molid |top", TCL_STATIC); + return TCL_ERROR; + } + } + + Tcl_SetResult(my_interp, (char *) "First, setup the Colvars module with: " + "cv molid |top", TCL_STATIC); + +#else + Tcl_SetResult(my_interp, + const_cast("Error: Colvars module not yet initialized"), + TCL_STATIC); +#endif + return TCL_ERROR; + } + + colvarproxy *proxy = colvars->proxy; + Tcl_Interp *interp = my_interp ? my_interp : proxy->get_tcl_interp(); + colvarscript *script = colvarscript_obj(); + if (!script) { + char const *errstr = "Called tcl_run_colvarscript_command " + "without a Colvars script interface set up.\n"; + Tcl_SetResult(interp, const_cast(errstr), TCL_VOLATILE); + return TCL_ERROR; + } + + cvm::clear_error(); + + unsigned char * arg_pointers_[100]; + if (objc > 100) { + std::string const errstr = "Too many positional arguments ("+ + cvm::to_str(objc)+") passed to the \"cv\" command.\n"; + Tcl_SetResult(interp, const_cast(errstr.c_str()), TCL_VOLATILE); + return TCL_ERROR; + } + for (int i = 0; i < objc; i++) { + arg_pointers_[i] = reinterpret_cast(const_cast(proxy->tcl_get_str(objv[i]))); + } + int retval = script->run(objc, arg_pointers_); + + std::string result = proxy->get_error_msgs() + script->str_result(); + + Tcl_SetResult(interp, const_cast(result.c_str()), + TCL_VOLATILE); + + if (proxy->delete_requested()) { + if (!proxy->simulation_running()) { + // Running in VMD + Tcl_SetResult(interp, + const_cast("Deleting Colvars module" + ": to recreate, use cv molid "), + TCL_STATIC); + } + delete proxy; + proxy = NULL; + } + + return (retval == COLVARS_OK) ? TCL_OK : TCL_ERROR; +} + +#endif // #if defined(COLVARS_TCL) + + + + +int colvarscript::set_result_text_from_str(std::string const &x_str, + unsigned char *obj) { + if (obj) { + strcpy(reinterpret_cast(obj), x_str.c_str()); + } else { + set_result_str(x_str); + } + return COLVARS_OK; +} + +// Template to convert everything to string and use the above + +template +int colvarscript::set_result_text(T const &x, unsigned char *obj) { + std::string const x_str = x.to_simple_string(); + return set_result_text_from_str(x_str, obj); +} + + +template +int colvarscript::pack_vector_elements_text(std::vector const &x, + std::string &x_str) { + x_str.clear(); + for (size_t i = 0; i < x.size(); ++i) { + if (i > 0) x_str.append(1, ' '); + x_str += cvm::to_str(x[i]); + } + return COLVARS_OK; +} + + +// Specializations for plain old data types that don't have a stringifier member + +template <> +int colvarscript::set_result_text(int const &x, unsigned char *obj) { + std::string const x_str = cvm::to_str(x); + return set_result_text_from_str(x_str, obj); +} + +template <> +int colvarscript::set_result_text(std::vector const &x, + unsigned char *obj) { + std::string x_str(""); + pack_vector_elements_text(x, x_str); + return set_result_text_from_str(x_str, obj); +} + + +template <> +int colvarscript::set_result_text(long int const &x, unsigned char *obj) { + std::string const x_str = cvm::to_str(x); + return set_result_text_from_str(x_str, obj); +} + +template <> +int colvarscript::set_result_text(std::vector const &x, + unsigned char *obj) { + std::string x_str(""); + pack_vector_elements_text(x, x_str); + return set_result_text_from_str(x_str, obj); +} + + +template <> +int colvarscript::set_result_text(cvm::real const &x, unsigned char *obj) { + std::string const x_str = cvm::to_str(x); + return set_result_text_from_str(x_str, obj); +} + +template <> +int colvarscript::set_result_text(std::vector const &x, + unsigned char *obj) { + std::string x_str(""); + pack_vector_elements_text(x, x_str); + return set_result_text_from_str(x_str, obj); +} + + +// TODO these can be removed after the Tcl backend is ready (otherwise, the +// default template syntax may break scripts or the Dashboard) + +template <> +int colvarscript::set_result_text(std::vector const &x, + unsigned char *obj) { + std::string x_str(""); + for (size_t i = 0; i < x.size(); i++) { + if (i > 0) x_str.append(1, ' '); + x_str += "{ "+x[i].to_simple_string()+" }"; + } + return set_result_text_from_str(x_str, obj); +} + +template <> +int colvarscript::set_result_text(std::vector const &x, + unsigned char *obj) { + std::string x_str(""); + for (size_t i = 0; i < x.size(); i++) { + if (i > 0) x_str.append(1, ' '); + x_str += "{ "+x[i].to_simple_string()+" }"; + } + return set_result_text_from_str(x_str, obj); +} + + +// Member functions to set script results for each typexc + +int colvarscript::set_result_int(int const &x, unsigned char *obj) { + return set_result_text(x, obj); +} + +int colvarscript::set_result_int_vec(std::vector const &x, + unsigned char *obj) { + return set_result_text< std::vector >(x, obj); +} + + +int colvarscript::set_result_long_int(long int const &x, unsigned char *obj) { + return set_result_text(x, obj); +} + +int colvarscript::set_result_long_int_vec(std::vector const &x, + unsigned char *obj) { + return set_result_text< std::vector >(x, obj); +} + + +int colvarscript::set_result_real(cvm::real const &x, unsigned char *obj) { + return set_result_text(x, obj); +} + +int colvarscript::set_result_real_vec(std::vector const &x, + unsigned char *obj) { + return set_result_text< std::vector >(x, obj); +} + + +int colvarscript::set_result_rvector(cvm::rvector const &x, unsigned char *obj) { + return set_result_text(x, obj); +} + +int colvarscript::set_result_rvector_vec(std::vector const &x, + unsigned char *obj) { + return set_result_text< std::vector >(x, obj); +} + + +int colvarscript::set_result_colvarvalue(colvarvalue const &x, + unsigned char *obj) { + return set_result_text(x, obj); +} + +int colvarscript::set_result_colvarvalue_vec(std::vector const &x, + unsigned char *obj) { + return set_result_text< std::vector >(x, obj); +} diff --git a/src/external/colvars/colvarscript.h b/src/external/colvars/colvarscript.h new file mode 100644 index 00000000000..540f56b658a --- /dev/null +++ b/src/external/colvars/colvarscript.h @@ -0,0 +1,455 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARSCRIPT_H +#define COLVARSCRIPT_H + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarbias.h" +#include "colvarproxy.h" + + +// Only these error values are part of the scripting interface +#define COLVARSCRIPT_ERROR -1 +#define COLVARSCRIPT_OK 0 + + +class colvarscript { + +private: + + colvarproxy *proxy_; + colvarmodule *colvars; + + inline colvarscript() {} // no-argument construction forbidden + +public: + + friend class colvarproxy; + + colvarscript(colvarproxy *p, colvarmodule *m); + + ~colvarscript(); + + /// String representation of the result of a script call + std::string str_result_; + + /// Run a script command with space-separated positional arguments (objects) + int run(int objc, unsigned char *const objv[]); + + /// Get the string result of the current scripting call + inline std::string const &str_result() const + { + return str_result_; + } + + /// Modify the string result of the current scripting call + inline std::string &modify_str_result() + { + return str_result_; + } + + /// Set the return value to the given string + int set_result_str(std::string const &s); + + /// Clear the string result + int clear_str_result(); + + /// Add the given string to the error message of the script interface + void add_error_msg(std::string const &s); + + /// Commands available + enum command { +#define CVSCRIPT_ENUM_COMM(COMM) COMM, +#undef CVSCRIPT +#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + CVSCRIPT_ENUM_COMM(COMM) +#ifdef COLVARSCRIPT_COMMANDS_H +#undef COLVARSCRIPT_COMMANDS_H +#endif +#include "colvarscript_commands.h" +#undef COLVARSCRIPT_COMMANDS_H +#undef CVSCRIPT +#undef CVSCRIPT_ENUM_COMM + cv_n_commands + }; + + /// Type of object handling a script command + enum Object_type { + use_module, + use_colvar, + use_bias + }; + + /// Return the prefix of the individual command for each object function + std::string get_cmd_prefix(Object_type t); + + /// Get a pointer to the i-th argument of the command (NULL if not given) + template + unsigned char *get_cmd_arg(int iarg, int objc, unsigned char *const objv[]); + + /// Instantiation of get_cmd_arg<> for module-level commands + unsigned char *get_module_cmd_arg(int iarg, int objc, + unsigned char *const objv[]); + + /// Instantiation of get_cmd_arg<> for colvar-level commands + unsigned char *get_colvar_cmd_arg(int iarg, int objc, + unsigned char *const objv[]); + + /// Instantiation of get_cmd_arg<> for bias-level commands + unsigned char *get_bias_cmd_arg(int iarg, int objc, + unsigned char *const objv[]); + + /// Check the argument count of the command + template + int check_cmd_nargs(char const *cmd, int objc, + int n_args_min, int n_args_max); + + /// Instantiation of check_cmd_nargs<> for module-level commands + int check_module_cmd_nargs(char const *cmd, int objc, + int n_args_min, int n_args_max); + + /// Instantiation of check_cmd_nargs<> for colvar-level commands + int check_colvar_cmd_nargs(char const *cmd, int objc, + int n_args_min, int n_args_max); + + /// Instantiation of get_cmd_arg<> for bias-level commands + int check_bias_cmd_nargs(char const *cmd, int objc, + int n_args_min, int n_args_max); + + /// Number of positional arguments to shift for each object type + template + int cmd_arg_shift(); + + /// Get names of all commands + inline char const **get_command_names() const + { + return cmd_names; + } + + /// Get one-line help summary for a command + /// \param cmd Name of the command's function (e.g. "cv_units") + char const *get_command_help(char const *cmd); + + /// Get description of the return value of a command + /// \param cmd Name of the command's function (e.g. "cv_units") + char const *get_command_rethelp(char const *cmd); + + /// Get description of the argument of a command (excluding prefix) + /// \param cmd Name of the command's function (e.g. "cv_units") + /// \param i Index of the argument; 0 is the first argument after the + /// prefix, e.g. "value" has an index of 0 in the array of arguments: + /// { "cv", "colvar", "xi", "value" } + char const *get_command_arghelp(char const *cmd, int i); + + /// Get number of required arguments (excluding prefix) + /// \param cmd Name of the command's function (e.g. "cv_units") + int get_command_n_args_min(char const *cmd); + + /// Get number of total arguments (excluding prefix) + /// \param cmd Name of the command's function (e.g. "cv_units") + int get_command_n_args_max(char const *cmd); + + /// Set the main command for the CLI, when it is not "cv" (e.g. LAMMPS) + inline void set_cmdline_main_cmd(std::string const &cmd) { + cmdline_main_cmd_ = cmd; + } + + /// Get help string for a command (does not specify how it is launched) + /// \param cmd Name of the command's function (e.g. "cv_units") + char const *get_command_full_help(char const *cmd); + + /// Get summary of command line syntax for all commands of a given context + /// \param t One of use_module, use_colvar or use_bias + std::string get_cmdline_help_summary(Object_type t); + + /// Get a description of how the command should be used in a command line + /// \param t One of use_module, use_colvar or use_bias + /// \param c Value of the \link command \endlink enum + std::string get_command_cmdline_syntax(Object_type t, command c); + + /// Get the command line syntax following by the help string + /// \param t One of use_module, use_colvar or use_bias + /// \param cmd Name of the subcommand (e.g. "units") + std::string get_command_cmdline_help(Object_type t, std::string const &cmd); + + /// Set error code for unsupported script operation + int unsupported_op(); + + /// Pointer to the Colvars main object + inline colvarmodule *module() + { + return this->colvars; + } + + /// Pointer to the colvarproxy object (interface with host engine) + inline colvarproxy *proxy() + { + return this->proxy_; + } + + // Input functions - get the string reps of script argument objects + + /// Get the string representation of an object (by default, a simple cast) + char *obj_to_str(unsigned char *obj); + + /// Get a list of strings from an object (does not work with a simple cast) + std::vector obj_to_str_vector(unsigned char *obj); + + + // Output functions - convert internal objects to representations suitable + // for use in the scripting language. At the moment only conversion to C + // strings is supported, and obj is assumed to be a char * pointer. + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_int(int const &x, unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_int_vec(std::vector const &x, unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_long_int(long int const &x, unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_long_int_vec(std::vector const &x, + unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_real(cvm::real const &x, unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_real_vec(std::vector const &x, + unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_rvector(cvm::rvector const &x, unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_rvector_vec(std::vector const &x, + unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_colvarvalue(colvarvalue const &x, unsigned char *obj = NULL); + + /// Copy x into obj if not NULL, or into the script object's result otherwise + int set_result_colvarvalue_vec(std::vector const &x, + unsigned char *obj = NULL); + +private: + + /// Set up all script API functions + int init_commands(); + + /// Set up a single script API function + int init_command(colvarscript::command const &comm, + char const *name, char const *help, + int n_args_min, int n_args_max, char const *arghelp, + int (*fn)(void *, int, unsigned char * const *)); + +public: // TODO this function will be removed soon + + /// Run subcommands on base colvardeps object (colvar, bias, ...) + int proc_features(colvardeps *obj, + int argc, unsigned char *const argv[]); + +private: // TODO + + /// Internal identifiers of command strings + std::map cmd_str_map; + + /// Main command used in command line ("cv" by default) + std::string cmdline_main_cmd_; + + /// Inverse of cmd_str_map (to be exported outside this class) + char const **cmd_names; + + /// Help strings for each command + std::vector cmd_help; + + /// Description of the return values of each command (may be empty) + std::vector cmd_rethelp; + + /// Minimum number of arguments for each command + std::vector cmd_n_args_min; + + /// Maximum number of arguments for each command + std::vector cmd_n_args_max; + + /// Help strings for each command argument + std::vector< std::vector > cmd_arghelp; + + /// Full help strings for each command + std::vector cmd_full_help; + + /// Implementations of each command + std::vector cmd_fns; + + /// Get a pointer to the implementation of the given command + inline int (*get_cmd_fn(std::string const &cmd_key))(void *, + int, + unsigned char * const *) + { + if (cmd_str_map.count(cmd_key) > 0) { + return cmd_fns[cmd_str_map[cmd_key]]; + } + return NULL; + } + + /// Set obj equal to x, using its string representation + template + int set_result_text(T const &x, unsigned char *obj); + + /// Code reused by instances of set_result_text() + template + int pack_vector_elements_text(std::vector const &x, std::string &x_str); + + /// Code reused by all instances of set_result_text() + int set_result_text_from_str(std::string const &x_str, unsigned char *obj); + + +}; + + +/// Get a pointer to the main colvarscript object +inline static colvarscript *colvarscript_obj() +{ + return cvm::main()->proxy->script; +} + + +/// Get a pointer to the colvar object pointed to by pobj +inline static colvar *colvar_obj(void *pobj) +{ + return reinterpret_cast(pobj); +} + + +/// Get a pointer to the colvarbias object pointed to by pobj +inline static colvarbias *colvarbias_obj(void *pobj) +{ + return reinterpret_cast(pobj); +} + + + +template +unsigned char *colvarscript::get_cmd_arg(int iarg, + int objc, + unsigned char *const objv[]) +{ + int const shift = cmd_arg_shift(); + return (shift+iarg < objc) ? objv[shift+iarg] : NULL; +} + + +inline unsigned char *colvarscript::get_module_cmd_arg(int iarg, int objc, + unsigned char *const objv[]) +{ + return get_cmd_arg(iarg, objc, objv); +} + + +inline unsigned char *colvarscript::get_colvar_cmd_arg(int iarg, int objc, + unsigned char *const objv[]) +{ + return get_cmd_arg(iarg, objc, objv); +} + + +inline unsigned char *colvarscript::get_bias_cmd_arg(int iarg, int objc, + unsigned char *const objv[]) +{ + return get_cmd_arg(iarg, objc, objv); +} + + +template +int colvarscript::check_cmd_nargs(char const *cmd, + int objc, + int n_args_min, + int n_args_max) +{ + int const shift = cmd_arg_shift(); + if (objc < shift+n_args_min) { + add_error_msg("Insufficient number of arguments ("+cvm::to_str(objc)+ + ") for script function \""+std::string(cmd)+ + "\":\n"+get_command_full_help(cmd)); + return COLVARSCRIPT_ERROR; + } + if (objc > shift+n_args_max) { + add_error_msg("Too many arguments ("+cvm::to_str(objc)+ + ") for script function \""+std::string(cmd)+ + "\":\n"+get_command_full_help(cmd)); + return COLVARSCRIPT_ERROR; + } + return COLVARSCRIPT_OK; +} + + +inline int colvarscript::check_module_cmd_nargs(char const *cmd, + int objc, + int n_args_min, + int n_args_max) +{ + return check_cmd_nargs(cmd, objc, n_args_min, n_args_max); +} + + +inline int colvarscript::check_colvar_cmd_nargs(char const *cmd, + int objc, + int n_args_min, + int n_args_max) +{ + return check_cmd_nargs(cmd, objc, n_args_min, n_args_max); +} + + +inline int colvarscript::check_bias_cmd_nargs(char const *cmd, + int objc, + int n_args_min, + int n_args_max) +{ + return check_cmd_nargs(cmd, objc, n_args_min, n_args_max); +} + + +template +int colvarscript::cmd_arg_shift() +{ + int shift = 0; + if (T == use_module) { + // "cv" and "COMMAND" are 1st and 2nd argument, and shift is equal to 2 + shift = 2; + } else if (T == use_colvar) { + // Same as above with additional arguments "colvar" and "NAME" + shift = 4; + } else if (T == use_bias) { + shift = 4; + } + return shift; +} + + +extern "C" { + + /// Generic wrapper for string-based scripting + int run_colvarscript_command(int objc, unsigned char *const objv[]); + + /// Get the string result of a script call + const char * get_colvarscript_result(); + +} + + +#endif // #ifndef COLVARSCRIPT_H diff --git a/src/external/colvars/colvarscript_commands.cpp b/src/external/colvars/colvarscript_commands.cpp new file mode 100644 index 00000000000..c0a28825bf1 --- /dev/null +++ b/src/external/colvars/colvarscript_commands.cpp @@ -0,0 +1,118 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarproxy.h" +#include "colvardeps.h" +#include "colvarscript.h" +#include "colvarscript_commands.h" + + + +extern "C" +int cvscript_n_commands() +{ + return static_cast(colvarscript::cv_n_commands); +} + + +extern "C" +char const **cvscript_command_names() +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_names(); +} + + +extern "C" +char const *cvscript_command_help(char const *c) +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_help(c); +} + + +extern "C" +char const *cvscript_command_rethelp(char const *c) +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_rethelp(c); +} + + +extern "C" +char const *cvscript_command_arghelp(char const *c, int i) +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_arghelp(c, i); +} + + +extern "C" +char const *cvscript_command_full_help(char const *c) +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_full_help(c); +} + + +extern "C" +int cvscript_command_n_args_min(char const *c) +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_n_args_min(c); +} + + +extern "C" +int cvscript_command_n_args_max(char const *c) +{ + colvarscript *script = colvarscript_obj(); + return script->get_command_n_args_max(c); +} + + +// Instantiate the body of all script commands + +#define CVSCRIPT_COMM_FN(COMM,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + int CVSCRIPT_COMM_FNAME(COMM)(void *pobj, \ + int objc, unsigned char *const objv[]) \ + { \ + if (cvm::debug()) { \ + cvm::log("Executing script function \""+std::string(#COMM)+"\""); \ + } \ + colvarscript *script = colvarscript_obj(); \ + script->clear_str_result(); \ + if (script->check_module_cmd_nargs(#COMM, \ + objc, N_ARGS_MIN, N_ARGS_MAX) != \ + COLVARSCRIPT_OK) { \ + return COLVARSCRIPT_ERROR; \ + } \ + if (objc > 1) { \ + /* Silence unused parameter warning */ \ + (void) pobj; \ + (void) objv[0]; \ + } \ + FN_BODY; \ + } +#undef CVSCRIPT +#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + CVSCRIPT_COMM_FN(COMM,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) + +// Skips the colvar- and bias- specific commands +#define COLVARSCRIPT_COMMANDS_GLOBAL + +#undef COLVARSCRIPT_COMMANDS_H +#include "colvarscript_commands.h" + +#undef CVSCRIPT_COMM_FN +#undef CVSCRIPT diff --git a/src/external/colvars/colvarscript_commands.h b/src/external/colvars/colvarscript_commands.h new file mode 100644 index 00000000000..bdad74e433e --- /dev/null +++ b/src/external/colvars/colvarscript_commands.h @@ -0,0 +1,674 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARSCRIPT_COMMANDS_H +#define COLVARSCRIPT_COMMANDS_H + +// The following is a complete definition of the scripting API. + +// The CVSCRIPT macro is used in four distinct contexts: +// 1) Expand to the functions' prototypes (when included generically) +// 2) List colvarscript::command entries (when included in colvarscript.h) +// 3) Implement colvarscript::init() (when included in colvarscript.cpp) +// 4) Define the functions' bodies (when included in colvarscript_commands.cpp) + + +// Each command is created by an instance of the CVSCRIPT macro + +// The arguments of the CVSCRIPT macro are: + +// COMM = the id of the command (must be a member of colvarscript::command) + +// HELP = short description (C string literal) for the command; the second line +// is optional, and documents the return value (if any) + +// N_ARGS_MIN = the lowest number of arguments allowed + +// N_ARGS_MAX = the highest number of arguments allowed + +// ARGS = multi-line string literal describing each parameter; each line +// follows the format "name : type - description" + +// FN_BODY = the implementation of the function; this should be a thin wrapper +// over existing functions; the "script" pointer to the colvarscript +// object is already set by the CVSCRIPT_COMM_FN macro; see also the +// functions in colvarscript_commands.h. + +#ifndef CVSCRIPT_COMM_FNAME +#define CVSCRIPT_COMM_FNAME(COMM) cvscript_ ## COMM +#endif + +// If CVSCRIPT is not defined, this file yields the function prototypes +#ifndef CVSCRIPT + +#define CVSCRIPT_COMM_PROTO(COMM) \ + extern "C" int CVSCRIPT_COMM_FNAME(COMM)(void *, \ + int, \ + unsigned char *const *); + +#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + CVSCRIPT_COMM_PROTO(COMM) + + +// Utility functions used to query the command database +extern "C" { + + /// Get the number of colvarscript commands + int cvscript_n_commands(); + + /// Get the names of all commands (array of strings) + char const ** cvscript_command_names(); + + /// Get the help summary of the given command + /// \param cmd Name of the command's function (e.g. "cv_units") + char const *cvscript_command_help(char const *cmd); + + /// Get description of the return value of a command + /// \param cmd Name of the command's function (e.g. "cv_units") + char const *cvscript_command_rethelp(char const *cmd); + + /// Get description of the arguments of a command (excluding prefix) + /// \param cmd Name of the command's function (e.g. "cv_units") + /// \param i Index of the argument; 0 is the first argument after the + /// prefix, e.g. "value" has an index of 0 in the array of arguments: + /// { "cv", "colvar", "xi", "value" } + char const *cvscript_command_arghelp(char const *cmd, int i); + + /// Get the full help string of a command + /// \param cmd Name of the command's function (e.g. "cv_units") + char const *cvscript_command_full_help(char const *cmd); + + /// Get number of required arguments (excluding prefix) + /// \param cmd Name of the command's function (e.g. "cv_units") + int cvscript_command_n_args_min(char const *cmd); + + /// Get number of total arguments (excluding prefix) + /// \param cmd Name of the command's function (e.g. "cv_units") + int cvscript_command_n_args_max(char const *cmd); + +} + +#endif + + +CVSCRIPT(cv_addenergy, + "Add an energy to the MD engine (no effect in VMD)", + 1, 1, + "E : float - Amount of energy to add", + char const *Earg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + cvm::main()->total_bias_energy += strtod(Earg, NULL); + return cvm::get_error(); // TODO Make this multi-language + ) + +CVSCRIPT(cv_bias, + "Prefix for bias-specific commands", + 0, 0, + "", + // This cannot be executed from a command line + return COLVARS_OK; + ) + +CVSCRIPT(cv_colvar, + "Prefix for colvar-specific commands", + 0, 0, + "", + // This cannot be executed from a command line + return COLVARS_OK; + ) + +CVSCRIPT(cv_config, + "Read configuration from the given string", + 1, 1, + "conf : string - Configuration string", + char const *conf_str = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + std::string const conf(conf_str); + script->proxy()->add_config("config", conf); + if (script->proxy()->engine_ready()) { + // Engine allows immediate initialization + if ((script->proxy()->parse_module_config() | + script->proxy()->setup()) == COLVARS_OK) { + return COLVARS_OK; + } else { + script->add_error_msg("Error parsing configuration string"); + return COLVARSCRIPT_ERROR; + } + } + // Engine not ready, config will be read during proxy->setup() + return COLVARS_OK; + ) + +CVSCRIPT(cv_configfile, + "Read configuration from a file", + 1, 1, + "conf_file : string - Path to configuration file", + char const *conf_file_name = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + script->proxy()->add_config("configfile", std::string(conf_file_name)); + if (script->proxy()->engine_ready()) { + // Engine allows immediate initialization + if ((script->proxy()->parse_module_config() | + script->proxy()->setup()) == COLVARS_OK) { + return COLVARS_OK; + } else { + script->add_error_msg("Error parsing configuration file"); + return COLVARSCRIPT_ERROR; + } + } + // Engine not ready, config will be read during proxy->setup() + return COLVARS_OK; + ) + +CVSCRIPT(cv_delete, + "Delete this Colvars module instance (VMD only)", + 0, 0, + "", + return script->proxy()->request_deletion(); + ) + +CVSCRIPT(cv_featurereport, + "Return a summary of Colvars features used so far and their citations\n" + "report : string - Feature report and citations", + 0, 0, + "", + return script->set_result_str(script->module()->feature_report()); + ) + +CVSCRIPT(cv_frame, + "Get or set current frame number (VMD only)\n" + "frame : integer - Frame number", + 0, 1, + "frame : integer - Frame number", + char const *arg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + if (arg == NULL) { + long int f = -1; + if (script->proxy()->get_frame(f) == COLVARS_OK) { + script->set_result_long_int(f); + return COLVARS_OK; + } else { + script->add_error_msg("Frame number is not available"); + return COLVARSCRIPT_ERROR; + } + } else { + int const f = strtol(const_cast(arg), NULL, 10); + int error_code = script->proxy()->set_frame(f); + if (error_code == COLVARS_NO_SUCH_FRAME) { + script->add_error_msg("Invalid frame number: \""+std::string(arg)+ + "\"\n"); + } + return error_code; + } + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomappliedforces, + "Get the list of forces applied by Colvars to atoms\n" + "forces : array of arrays of floats - Atomic forces", + 0, 0, + "", + script->set_result_rvector_vec(*(script->proxy()->get_atom_applied_forces())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomappliedforcesmax, + "Get the maximum norm of forces applied by Colvars to atoms\n" + "force : float - Maximum atomic force", + 0, 0, + "", + script->set_result_real(script->proxy()->max_atoms_applied_force()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomappliedforcesmaxid, + "Get the atom ID with the largest applied force\n" + "id : int - ID of the atom with the maximum atomic force", + 0, 0, + "", + script->set_result_int(script->proxy()->max_atoms_applied_force_id()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomappliedforcesrms, + "Get the root-mean-square norm of forces applied by Colvars to atoms\n" + "force : float - RMS atomic force", + 0, 0, + "", + script->set_result_real(script->proxy()->rms_atoms_applied_force()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_resetatomappliedforces, + "Reset forces applied by Colvars to atoms", + 0, 0, + "", + size_t i; + std::vector *f = script->proxy()->modify_atom_applied_forces(); + for (i = 0; i < f->size(); i++) { + (*f)[i].reset(); + } + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomids, + "Get the list of indices of atoms used in Colvars\n" + "indices : array of ints - Atom indices", + 0, 0, + "", + script->set_result_int_vec(*(script->proxy()->get_atom_ids())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomcharges, + "Get the list of charges of atoms used in Colvars\n" + "charges : array of floats - Atomic charges", + 0, 0, + "", + script->set_result_real_vec(*(script->proxy()->get_atom_charges())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatommasses, + "Get the list of masses of atoms used in Colvars\n" + "masses : array of floats - Atomic masses", + 0, 0, + "", + script->set_result_real_vec(*(script->proxy()->get_atom_masses())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatompositions, + "Get the list of cached positions of atoms used in Colvars\n" + "positions : array of arrays of floats - Atomic positions", + 0, 0, + "", + script->set_result_rvector_vec(*(script->proxy()->get_atom_positions())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getatomtotalforces, + "Get the list of cached total forces of atoms used in Colvars\n" + "forces : array of arrays of floats - Atomic total foces", + 0, 0, + "", + script->set_result_rvector_vec(*(script->proxy()->get_atom_total_forces())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getconfig, + "Get the module's configuration string read so far\n" + "conf : string - Current configuration string", + 0, 0, + "", + script->set_result_str(cvm::main()->get_config()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getenergy, + "Get the current Colvars energy\n" + "E : float - Amount of energy (internal units)", + 0, 0, + "", + script->set_result_real(cvm::main()->total_bias_energy); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getnumactiveatomgroups, + "Get the number of atom groups that currently have positive ref counts\n" + "count : integer - Total number of atom groups", + 0, 0, + "", + script->set_result_int(static_cast(script->proxy()->get_num_active_atom_groups())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getnumactiveatoms, + "Get the number of atoms that currently have positive ref counts\n" + "count : integer - Total number of atoms", + 0, 0, + "", + script->set_result_int(static_cast(script->proxy()->get_num_active_atoms())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getnumatoms, + "Get the number of requested atoms, including those not in use now\n" + "count : integer - Total number of atoms", + 0, 0, + "", + script->set_result_int(static_cast(script->proxy()->get_atom_ids()->size())); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getstepabsolute, + "Get the current step number of the simulation (including restarts)\n" + "step : int - Absolute step number", + 0, 0, + "", + script->set_result_int(cvm::step_absolute()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_getsteprelative, + "Get the current step number from the start of this job\n" + "step : int - Relative step number", + 0, 0, + "", + script->set_result_int(cvm::step_relative()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_help, + "Get the help string of the Colvars scripting interface\n" + "help : string - Help string", + 0, 1, + "command : string - Get the help string of this specific command", + unsigned char *const cmdobj = + script->get_module_cmd_arg(0, objc, objv); + if (cmdobj) { + std::string const cmdstr(script->obj_to_str(cmdobj)); + if (cmdstr.size()) { + if (cmdstr == std::string("colvar")) { + script->set_result_str(script->get_cmdline_help_summary(colvarscript::use_colvar)); + } else if (cmdstr == std::string("bias")) { + script->set_result_str(script->get_cmdline_help_summary(colvarscript::use_bias)); + } else { + script->set_result_str(script->get_command_cmdline_help(colvarscript::use_module, + cmdstr)); + } + return cvm::get_error(); + } else { + return COLVARSCRIPT_ERROR; + } + } else { + script->set_result_str(script->get_cmdline_help_summary(colvarscript::use_module)); + return COLVARS_OK; + } + ) + +CVSCRIPT(cv_languageversion, + "Get the C++ language version number\n" + "version : string - C++ language version", + 0, 0, + "", + script->set_result_int(__cplusplus); + return COLVARS_OK; + ) + +CVSCRIPT(cv_list, + "Return a list of all variables or biases\n" + "list : sequence of strings - List of elements", + 0, 1, + "param : string - \"colvars\" or \"biases\"; default is \"colvars\"", + std::string res; + unsigned char *const kwarg = script->get_module_cmd_arg(0, objc, objv); + std::string const kwstr = kwarg ? script->obj_to_str(kwarg) : + std::string("colvars"); + if (kwstr == "colvars") { + for (std::vector::iterator cvi = script->module()->variables()->begin(); + cvi != script->module()->variables()->end(); + ++cvi) { + res += (cvi == script->module()->variables()->begin() ? "" : " ") + (*cvi)->name; + } + script->set_result_str(res); + return COLVARS_OK; + } else if (kwstr == "biases") { + for (std::vector::iterator bi = script->module()->biases.begin(); + bi != script->module()->biases.end(); + ++bi) { + res += (bi == script->module()->biases.begin() ? "" : " ") + (*bi)->name; + } + script->set_result_str(res); + return COLVARS_OK; + } else { + script->add_error_msg("Wrong arguments to command \"list\"\n"); + return COLVARSCRIPT_ERROR; + } + ) + +CVSCRIPT(cv_listcommands, + "Get the list of script functions, prefixed with \"cv_\", \"colvar_\" or \"bias_\"\n" + "list : sequence of strings - List of commands", + 0, 0, + "", + int const n_commands = cvscript_n_commands(); + char const **command_names = cvscript_command_names(); + std::string result; + for (int i = 0; i < n_commands; i++) { + if (i > 0) result.append(1, ' '); + result.append(std::string(command_names[i])); + } + script->set_result_str(result); + return COLVARS_OK; + ) + +CVSCRIPT(cv_listindexfiles, + "Get a list of the index files loaded in this session\n" + "list : sequence of strings - List of index file names", + 0, 0, + "", + int const n_files = script->module()->index_file_names.size(); + std::string result; + for (int i = 0; i < n_files; i++) { + if (i > 0) result.append(1, ' '); + result.append(script->module()->index_file_names[i]); + } + script->set_result_str(result); + return COLVARS_OK; + ) + +CVSCRIPT(cv_listinputfiles, + "Get a list of all input/configuration files loaded in this session\n" + "list : sequence of strings - List of file names", + 0, 0, + "", + std::list const l = + script->proxy()->list_input_stream_names(); + std::string result; + for (std::list::const_iterator li = l.begin(); + li != l.end(); li++) { + if (li != l.begin()) result.append(1, ' '); + result.append(*li); + } + script->set_result_str(result); + return COLVARS_OK; + ) + +CVSCRIPT(cv_load, + "Load data from a state file into all matching colvars and biases", + 1, 1, + "prefix : string - Path to existing state file or input prefix", + char const *arg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + int error_code = + script->proxy()->set_input_prefix(cvm::state_file_prefix(arg)); + error_code |= script->module()->setup_input(); + if (error_code != COLVARS_OK) { + script->add_error_msg("Error loading state file"); + } + return error_code; + ) + +CVSCRIPT(cv_loadfromstring, + "Load state data from a string into all matching colvars and biases", + 1, 1, + "buffer : string - String buffer containing the state information", + char const *arg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + script->proxy()->input_stream_from_string("input state string", + std::string(arg)); + if (script->module()->setup_input() == COLVARS_OK) { + return COLVARS_OK; + } else { + script->add_error_msg("Error loading state string"); + return COLVARSCRIPT_ERROR; + } + ) + +CVSCRIPT(cv_molid, + "Get or set the molecule ID on which Colvars is defined (VMD only)\n" + "molid : integer - Current molecule ID", + 0, 1, + "molid : integer - New molecule ID; -1 means undefined", + char const *arg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + if (arg == NULL) { + int molid = -1; + script->proxy()->get_molid(molid); + script->set_result_int(molid); + return COLVARS_OK; + } else { + script->add_error_msg("Error: To change the molecule ID in VMD, use cv delete first."); + return COLVARS_NOT_IMPLEMENTED; + } + ) + +CVSCRIPT(cv_printframe, + "Return the values that would be written to colvars.traj\n" + "values : string - The values\n", + 0, 0, + "", + std::ostringstream os; + script->module()->write_traj(os); + script->set_result_str(os.str()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_printframelabels, + "Return the labels that would be written to colvars.traj\n" + "Labels : string - The labels", + 0, 0, + "", + std::ostringstream os; + script->module()->write_traj_label(os); + script->set_result_str(os.str()); + return COLVARS_OK; + ) + +CVSCRIPT(cv_reset, + "Delete all internal configuration", + 0, 0, + "", + cvm::log("Resetting the Collective Variables module."); + return script->module()->reset(); + ) + +CVSCRIPT(cv_resetindexgroups, + "Clear the index groups loaded so far, allowing to replace them", + 0, 0, + "", + cvm::main()->index_group_names.clear(); + cvm::main()->index_groups.clear(); + return COLVARS_OK; + ) + +CVSCRIPT(cv_save, + "Change the prefix of all output files and save them", + 1, 1, + "prefix : string - Output prefix with trailing \".colvars.state\" gets removed)", + std::string const prefix = + cvm::state_file_prefix(script->obj_to_str(script->get_module_cmd_arg(0, objc, objv))); + int error_code = script->proxy()->set_output_prefix(prefix); + error_code |= script->module()->setup_output(); + error_code |= script->module()->write_restart_file(prefix+ + ".colvars.state"); + error_code |= script->module()->write_output_files(); + return error_code; + ) + +CVSCRIPT(cv_savetostring, + "Write the Colvars state to a string and return it\n" + "state : string - The saved state", + 0, 0, + "", + return script->module()->write_restart_string(script->modify_str_result()); + ) + +CVSCRIPT(cv_targettemperature, + "Get/set target temperature, overriding internally what the MD engine reports\n" + "T : float - Current target temperature in K", + 0, 1, + "T : float - New target temperature in K (internal use)", + char const *Targ = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + if (Targ == NULL) { + return script->set_result_real(script->proxy()->target_temperature()); + } else { + return script->proxy()->set_target_temperature(strtod(Targ, NULL)); + } + ) + +CVSCRIPT(cv_timestep, + "Get/set integration timestep, overriding internally what the MD engine reports\n" + "dt : float - Current integration timestep in MD engine units", + 0, 1, + "dt : float - New integration timestep in MD engine units", + char const *arg = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + if (arg == NULL) { + return script->set_result_real(script->proxy()->dt()); + } else { + return script->proxy()->set_integration_timestep(strtod(arg, NULL)); + } + ) + +CVSCRIPT(cv_units, + "Get or set the current Colvars unit system\n" + "units : string - The current unit system", + 0, 1, + "units : string - The new unit system", + char const *argstr = + script->obj_to_str(script->get_module_cmd_arg(0, objc, objv)); + if (argstr) { + return cvm::proxy->set_unit_system(argstr, false); + } else { + script->set_result_str(cvm::proxy->units); + return COLVARS_OK; + } + ) + +CVSCRIPT(cv_update, + "Recalculate colvars and biases", + 0, 0, + "", + int error_code = script->proxy()->update_input(); + if (error_code) { + script->add_error_msg("Error updating the Colvars module (input)"); + return error_code; + } + error_code |= script->module()->calc(); + if (error_code) { + script->add_error_msg("Error updating the Colvars module (calc)"); + return error_code; + } + error_code |= script->proxy()->update_output(); + if (error_code) { + script->add_error_msg("Error updating the Colvars module (output)"); + } + return error_code; + ) + +CVSCRIPT(cv_version, + "Get the Colvars Module version string\n" + "version : string - Colvars version", + 0, 0, + "", + script->set_result_str(COLVARS_VERSION); + return COLVARS_OK; + ) + +// This guard allows compiling colvar and bias function bodies in their +// respecitve files instead of colvarscript_commands.o +#ifndef COLVARSCRIPT_COMMANDS_GLOBAL +#include "colvarscript_commands_colvar.h" +#include "colvarscript_commands_bias.h" +#endif + +#endif // #ifndef COLVARSCRIPT_COMMANDS_H diff --git a/src/external/colvars/colvarscript_commands_bias.cpp b/src/external/colvars/colvarscript_commands_bias.cpp new file mode 100644 index 00000000000..2a94efb07e2 --- /dev/null +++ b/src/external/colvars/colvarscript_commands_bias.cpp @@ -0,0 +1,53 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include +#include +#include +#include + +#include "colvarproxy.h" +#include "colvardeps.h" +#include "colvarscript.h" +#include "colvarscript_commands.h" + + + +// Instantiate the body of all bias-specific script commands + +#define CVSCRIPT_COMM_FN(COMM,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + int CVSCRIPT_COMM_FNAME(COMM)(void *pobj, \ + int objc, unsigned char *const objv[]) \ + { \ + if (cvm::debug()) { \ + cvm::log("Executing script function \""+std::string(#COMM)+"\""); \ + } \ + colvarscript *script = colvarscript_obj(); \ + script->clear_str_result(); \ + if (script->check_bias_cmd_nargs(#COMM, \ + objc, N_ARGS_MIN, N_ARGS_MAX) != \ + COLVARSCRIPT_OK) { \ + return COLVARSCRIPT_ERROR; \ + } \ + if (objc > 1) { \ + /* Silence unused parameter warning */ \ + (void) objv; \ + } \ + colvarbias *this_bias = colvarbias_obj(pobj); \ + FN_BODY; \ + } +#undef CVSCRIPT +#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + CVSCRIPT_COMM_FN(COMM,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) + +#include "colvarscript_commands_bias.h" + +#undef CVSCRIPT_COMM_FN +#undef CVSCRIPT diff --git a/src/external/colvars/colvarscript_commands_bias.h b/src/external/colvars/colvarscript_commands_bias.h new file mode 100644 index 00000000000..420bbabcc01 --- /dev/null +++ b/src/external/colvars/colvarscript_commands_bias.h @@ -0,0 +1,192 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +CVSCRIPT(bias_bin, + "Get the current grid bin index (1D ABF only for now)\n" + "bin : integer - Bin index", + 0, 0, + "", + script->set_result_int(this_bias->current_bin()); + return COLVARS_OK; + ) + +CVSCRIPT(bias_bincount, + "Get the number of samples at the given grid bin (1D ABF only for now)\n" + "samples : integer - Number of samples", + 0, 1, + "index : integer - Grid index; defaults to current bin", + int index = this_bias->current_bin(); + char const *indexarg = + script->obj_to_str(script->get_bias_cmd_arg(0, objc, objv)); + if (indexarg) { + std::string const param(indexarg); + if (!(std::istringstream(param) >> index)) { + script->add_error_msg("bincount: error parsing bin index"); + return COLVARSCRIPT_ERROR; + } + } + script->set_result_int(this_bias->bin_count(index)); + return COLVARS_OK; + ) + +CVSCRIPT(bias_binnum, + "Get the total number of grid points of this bias (1D ABF only for now)\n" + "Bins : integer - Number of grid points", + 0, 0, + "", + int r = this_bias->bin_num(); + if (r < 0) { + script->add_error_msg("Error: calling bin_num() for bias " + + this_bias->name); + return COLVARSCRIPT_ERROR; + } + script->set_result_int(r); + return COLVARS_OK; + ) + +CVSCRIPT(bias_delete, + "Delete this bias", + 0, 0, + "", + delete this_bias; + return COLVARS_OK; + ) + +CVSCRIPT(bias_energy, + "Get the current energy of this bias\n" + "E : float - Energy value", + 0, 0, + "", + script->set_result_real(this_bias->get_energy()); + return COLVARS_OK; + ) + +CVSCRIPT(bias_get, + "Get the value of the given feature for this bias\n" + "state : 1/0 - State of the given feature", + 1, 1, + "feature : string - Name of the feature", + return script->proc_features(this_bias, objc, objv); + ) + +CVSCRIPT(bias_getconfig, + "Return the configuration string of this bias\n" + "conf : string - Current configuration string", + 0, 0, + "", + script->set_result_str(this_bias->get_config()); + return COLVARS_OK; + ) + +CVSCRIPT(bias_help, + "Get a help summary or the help string of one bias subcommand\n" + "help : string - Help string", + 0, 1, + "command : string - Get the help string of this specific command", + unsigned char *const cmdobj = + script->get_colvar_cmd_arg(0, objc, objv); + if (this_bias) { + } + if (cmdobj) { + std::string const cmdstr(script->obj_to_str(cmdobj)); + if (cmdstr.size()) { + script->set_result_str(script->get_command_cmdline_help(colvarscript::use_bias, + cmdstr)); + return cvm::get_error(); + } else { + return COLVARSCRIPT_ERROR; + } + } else { + script->set_result_str(script->get_cmdline_help_summary(colvarscript::use_bias)); + return COLVARS_OK; + } + ) + +CVSCRIPT(bias_load, + "Load data into this bias", + 1, 1, + "prefix : string - Read from a file with this name or prefix", + char const *arg = + script->obj_to_str(script->get_bias_cmd_arg(0, objc, objv)); + return this_bias->read_state_prefix(std::string(arg)); + ) + +CVSCRIPT(bias_loadfromstring, + "Load state data into this bias from a string", + 1, 1, + "buffer : string - String buffer containing the state information", + char const *buffer = script->obj_to_str(script->get_bias_cmd_arg(0, objc, objv)); + return this_bias->read_state_string(buffer); + ) + +CVSCRIPT(bias_save, + "Save data from this bias into a file with the given prefix", + 1, 1, + "prefix : string - Prefix for the state file of this bias", + std::string const prefix = + cvm::state_file_prefix(script->obj_to_str(script->get_bias_cmd_arg(0, objc, objv))); + return this_bias->write_state_prefix(prefix); + ) + +CVSCRIPT(bias_savetostring, + "Save data from this bias into a string and return it\n" + "state : string - The bias state", + 0, 0, + "", + return this_bias->write_state_string(script->modify_str_result()); + ) + +CVSCRIPT(bias_set, + "Set the given feature of this bias to a new value", + 2, 2, + "feature : string - Name of the feature\n" + "value : string - String representation of the new feature value", + return script->proc_features(this_bias, objc, objv); + ) + +CVSCRIPT(bias_share, + "Share bias information with other replicas (multiple-walker scheme)", + 0, 0, + "", + if (this_bias->replica_share() != COLVARS_OK) { + script->add_error_msg("Error: calling replica_share() for bias " + + this_bias->name); + return COLVARSCRIPT_ERROR; + } + return COLVARS_OK; + ) + +CVSCRIPT(bias_state, + "Print a string representation of the feature state of this bias\n" + "state : string - String representation of the bias features", + 0, 0, + "", + this_bias->print_state(); + return COLVARS_OK; + ) + +CVSCRIPT(bias_type, + "Print the type of this bias object\n" + "type : string - Type of this bias object (e.g. metadynamics)", + 0, 0, + "", + script->set_result_str(this_bias->bias_type); + return COLVARS_OK; + ) + +CVSCRIPT(bias_update, + "Recompute this bias and return its up-to-date energy\n" + "E : float - Energy value", + 0, 0, + "", + this_bias->update(); + script->set_result_colvarvalue(this_bias->get_energy()); + return COLVARS_OK; + ) diff --git a/src/external/colvars/colvarscript_commands_colvar.cpp b/src/external/colvars/colvarscript_commands_colvar.cpp new file mode 100644 index 00000000000..da1f74d11d8 --- /dev/null +++ b/src/external/colvars/colvarscript_commands_colvar.cpp @@ -0,0 +1,53 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +#include +#include +#include +#include + +#include "colvarproxy.h" +#include "colvardeps.h" +#include "colvarscript.h" +#include "colvarscript_commands.h" + + + +// Instantiate the body of all colvar-specific script commands + +#define CVSCRIPT_COMM_FN(COMM,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + int CVSCRIPT_COMM_FNAME(COMM)(void *pobj, \ + int objc, unsigned char *const objv[]) \ + { \ + if (cvm::debug()) { \ + cvm::log("Executing script function \""+std::string(#COMM)+"\""); \ + } \ + colvarscript *script = colvarscript_obj(); \ + script->clear_str_result(); \ + if (script->check_colvar_cmd_nargs(#COMM, \ + objc, N_ARGS_MIN, N_ARGS_MAX) != \ + COLVARSCRIPT_OK) { \ + return COLVARSCRIPT_ERROR; \ + } \ + if (objc > 1) { \ + /* Silence unused parameter warning */ \ + (void) objv[0]; \ + } \ + colvar *this_colvar = colvar_obj(pobj); \ + FN_BODY; \ + } +#undef CVSCRIPT +#define CVSCRIPT(COMM,HELP,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) \ + CVSCRIPT_COMM_FN(COMM,N_ARGS_MIN,N_ARGS_MAX,ARGS,FN_BODY) + +#include "colvarscript_commands_colvar.h" + +#undef CVSCRIPT_COMM_FN +#undef CVSCRIPT diff --git a/src/external/colvars/colvarscript_commands_colvar.h b/src/external/colvars/colvarscript_commands_colvar.h new file mode 100644 index 00000000000..f6bb6b8c989 --- /dev/null +++ b/src/external/colvars/colvarscript_commands_colvar.h @@ -0,0 +1,260 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + + +CVSCRIPT(colvar_addforce, + "Apply the given force onto this colvar (no effects outside run)\n" + "force : float or array - Applied force; matches colvar dimensionality", + 1, 1, + "force : float or array - Applied force; must match colvar dimensionality", + std::string const f_str(script->obj_to_str(script->get_colvar_cmd_arg(0, objc, objv))); + std::istringstream is(f_str); + is.width(cvm::cv_width); + is.precision(cvm::cv_prec); + colvarvalue force(this_colvar->value()); + force.is_derivative(); + if (force.from_simple_string(is.str()) != COLVARS_OK) { + script->add_error_msg("addforce : error parsing force value"); + return COLVARSCRIPT_ERROR; + } + this_colvar->add_bias_force(force); + script->set_result_colvarvalue(force); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_communicateforces, + "Communicate bias forces from this colvar to atoms", + 0, 0, + "", + this_colvar->communicate_forces(); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_cvcflags, + "Enable or disable individual components by setting their active flags", + 1, 1, + "flags : integer array - Zero/nonzero value disables/enables the CVC", + std::string const flags_str(script->obj_to_str(script->get_colvar_cmd_arg(0, objc, objv))); + std::istringstream is(flags_str); + std::vector flags; + int flag; + while (is >> flag) { + flags.push_back(flag != 0); + } + int res = this_colvar->set_cvc_flags(flags); + if (res != COLVARS_OK) { + script->add_error_msg("Error setting CVC flags"); + return COLVARSCRIPT_ERROR; + } + script->set_result_str("0"); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_delete, + "Delete this colvar, along with all biases that depend on it", + 0, 0, + "", + delete this_colvar; + return COLVARS_OK; + ) + +CVSCRIPT(colvar_get, + "Get the value of the given feature for this colvar\n" + "state : 1/0 - State of the given feature", + 1, 1, + "feature : string - Name of the feature", + return script->proc_features(this_colvar, objc, objv); + ) + +CVSCRIPT(colvar_getappliedforce, + "Return the total of the forces applied to this colvar\n" + "force : float - Applied force; matches the colvar dimensionality", + 0, 0, + "", + script->set_result_colvarvalue(this_colvar->applied_force()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_resetbiasforce, + "Return the total of the forces applied to this colvar", + 0, 0, + "", + this_colvar->reset_bias_force(); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_getatomgroups, + "Return the atom indices used by this colvar as a list of lists\n" + "groups : array of arrays of ints - Atom indices", + 0, 0, + "", + std::string result; + std::vector > lists = this_colvar->get_atom_lists(); + std::vector >::iterator li = lists.begin(); + for ( ; li != lists.end(); ++li) { + result += "{"; + std::vector::iterator lj = (*li).begin(); + for ( ; lj != (*li).end(); ++lj) { + result += cvm::to_str(*lj); + result += " "; + } + result += "} "; + } + script->set_result_str(result); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_getatomids, + "Return the list of atom indices used by this colvar\n" + "indices : array of ints - Atom indices", + 0, 0, + "", + script->set_result_int_vec(this_colvar->atom_ids); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_getconfig, + "Return the configuration string of this colvar\n" + "conf : string - Current configuration string", + 0, 0, + "", + script->set_result_str(this_colvar->get_config()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_getgradients, + "Return the atomic gradients of this colvar\n" + "gradients : array of arrays of floats - Atomic gradients", + 0, 0, + "", + script->set_result_rvector_vec(this_colvar->atomic_gradients); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_gettotalforce, + "Return the sum of internal and external forces to this colvar\n" + "force : float - Total force; matches the colvar dimensionality", + 0, 0, + "", + script->set_result_colvarvalue(this_colvar->total_force()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_getvolmapids, + "Return the list of volumetric map indices used by this colvar", + 0, 0, + "", + script->set_result_int_vec(this_colvar->get_volmap_ids()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_help, + "Get a help summary or the help string of one colvar subcommand\n" + "help : string - Help string", + 0, 1, + "command : string - Get the help string of this specific command", + unsigned char *const cmdobj = + script->get_colvar_cmd_arg(0, objc, objv); + if (this_colvar) { + } + if (cmdobj) { + std::string const cmdstr(script->obj_to_str(cmdobj)); + if (cmdstr.size()) { + script->set_result_str(script->get_command_cmdline_help(colvarscript::use_colvar, + cmdstr)); + return cvm::get_error(); + } else { + return COLVARSCRIPT_ERROR; + } + } else { + script->set_result_str(script->get_cmdline_help_summary(colvarscript::use_colvar)); + return COLVARS_OK; + } + ) + +CVSCRIPT(colvar_modifycvcs, + "Modify configuration of individual components by passing string arguments", + 1, 1, + "confs : sequence of strings - New configurations; empty strings are skipped", + std::vector const confs(script->obj_to_str_vector(script->get_colvar_cmd_arg(0, objc, objv))); + cvm::increase_depth(); + int res = this_colvar->update_cvc_config(confs); + cvm::decrease_depth(); + if (res != COLVARS_OK) { + script->add_error_msg("Error setting CVC flags"); + return COLVARSCRIPT_ERROR; + } + script->set_result_str("0"); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_run_ave, + "Get the current running average of the value of this colvar\n" + "value : float or array - Averaged value; matches the colvar dimensionality", + 0, 0, + "", + script->set_result_colvarvalue(this_colvar->run_ave()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_set, + "Set the given feature of this colvar to a new value", + 2, 2, + "feature : string - Name of the feature\n" + "value : string - String representation of the new feature value", + return script->proc_features(this_colvar, objc, objv); + ) + +CVSCRIPT(colvar_state, + "Print a string representation of the feature state of this colvar\n" + "state : string - The feature state", + 0, 0, + "", + this_colvar->print_state(); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_type, + "Get the type description of this colvar\n" + "type : string - Type description", + 0, 0, + "", + script->set_result_str(this_colvar->value().type_desc(this_colvar->value().value_type)); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_update, + "Recompute this colvar and return its up-to-date value\n" + "value : float or array - Current value; matches the colvar dimensionality", + 0, 0, + "", + this_colvar->calc(); + this_colvar->update_forces_energy(); + script->set_result_colvarvalue(this_colvar->value()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_value, + "Get the current value of this colvar\n" + "value : float or array - Current value; matches the colvar dimensionality", + 0, 0, + "", + script->set_result_colvarvalue(this_colvar->value()); + return COLVARS_OK; + ) + +CVSCRIPT(colvar_width, + "Get the width of this colvar\n" + "width : float - Value of the width", + 0, 0, + "", + script->set_result_str(cvm::to_str(this_colvar->width, 0, + cvm::cv_prec)); + return COLVARS_OK; + ) diff --git a/src/external/colvars/colvartypes.cpp b/src/external/colvars/colvartypes.cpp new file mode 100644 index 00000000000..b75bef8ea6c --- /dev/null +++ b/src/external/colvars/colvartypes.cpp @@ -0,0 +1,456 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include + +#include "colvarmodule.h" +#include "colvartypes.h" +#include "colvarparse.h" +#include "colvaratoms.h" +#include "colvar_rotation_derivative.h" + +#ifdef COLVARS_LAMMPS +// Use open-source Jacobi implementation +#include "math_eigen_impl.h" +#else +// Fall back to NR routine +#include "nr_jacobi.h" +#endif + + +bool colvarmodule::rotation::monitor_crossings = false; +cvm::real colvarmodule::rotation::crossing_threshold = 1.0E-02; + + +std::string cvm::rvector::to_simple_string() const +{ + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + os.precision(cvm::cv_prec); + os << x << " " << y << " " << z; + return os.str(); +} + + +int cvm::rvector::from_simple_string(std::string const &s) +{ + std::stringstream stream(s); + if ( !(stream >> x) || + !(stream >> y) || + !(stream >> z) ) { + return COLVARS_ERROR; + } + return COLVARS_OK; +} + + +std::ostream & operator << (std::ostream &os, colvarmodule::rvector const &v) +{ + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + os.width(2); + os << "( "; + os.width(w); os.precision(p); + os << v.x << " , "; + os.width(w); os.precision(p); + os << v.y << " , "; + os.width(w); os.precision(p); + os << v.z << " )"; + return os; +} + + +std::istream & operator >> (std::istream &is, colvarmodule::rvector &v) +{ + std::streampos const start_pos = is.tellg(); + char sep; + if ( !(is >> sep) || !(sep == '(') || + !(is >> v.x) || !(is >> sep) || !(sep == ',') || + !(is >> v.y) || !(is >> sep) || !(sep == ',') || + !(is >> v.z) || !(is >> sep) || !(sep == ')') ) { + is.clear(); + is.seekg(start_pos, std::ios::beg); + is.setstate(std::ios::failbit); + return is; + } + return is; +} + +std::string cvm::quaternion::to_simple_string() const +{ + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + os.precision(cvm::cv_prec); + os << q0 << " " << q1 << " " << q2 << " " << q3; + return os.str(); +} + +int cvm::quaternion::from_simple_string(std::string const &s) +{ + std::stringstream stream(s); + if ( !(stream >> q0) || + !(stream >> q1) || + !(stream >> q2) || + !(stream >> q3) ) { + return COLVARS_ERROR; + } + return COLVARS_OK; +} + +std::ostream & operator << (std::ostream &os, colvarmodule::quaternion const &q) +{ + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + os.width(2); + os << "( "; + os.width(w); os.precision(p); + os << q.q0 << " , "; + os.width(w); os.precision(p); + os << q.q1 << " , "; + os.width(w); os.precision(p); + os << q.q2 << " , "; + os.width(w); os.precision(p); + os << q.q3 << " )"; + return os; +} + + +std::istream & operator >> (std::istream &is, colvarmodule::quaternion &q) +{ + std::streampos const start_pos = is.tellg(); + char sep; + if ( !(is >> sep) || !(sep == '(') || + !(is >> q.q0) || !(is >> sep) || !(sep == ',') || + !(is >> q.q1) || !(is >> sep) || !(sep == ',') || + !(is >> q.q2) || !(is >> sep) || !(sep == ',') || + !(is >> q.q3) || !(is >> sep) || !(sep == ')') ) { + is.clear(); + is.seekg(start_pos, std::ios::beg); + is.setstate(std::ios::failbit); + } + return is; +} + + +cvm::quaternion +cvm::quaternion::position_derivative_inner(cvm::rvector const &pos, + cvm::rvector const &vec) const +{ + cvm::quaternion result(0.0, 0.0, 0.0, 0.0); + + + result.q0 = 2.0 * pos.x * q0 * vec.x + +2.0 * pos.y * q0 * vec.y + +2.0 * pos.z * q0 * vec.z + + -2.0 * pos.y * q3 * vec.x + +2.0 * pos.z * q2 * vec.x + + +2.0 * pos.x * q3 * vec.y + -2.0 * pos.z * q1 * vec.y + + -2.0 * pos.x * q2 * vec.z + +2.0 * pos.y * q1 * vec.z; + + + result.q1 = +2.0 * pos.x * q1 * vec.x + -2.0 * pos.y * q1 * vec.y + -2.0 * pos.z * q1 * vec.z + + +2.0 * pos.y * q2 * vec.x + +2.0 * pos.z * q3 * vec.x + + +2.0 * pos.x * q2 * vec.y + -2.0 * pos.z * q0 * vec.y + + +2.0 * pos.x * q3 * vec.z + +2.0 * pos.y * q0 * vec.z; + + + result.q2 = -2.0 * pos.x * q2 * vec.x + +2.0 * pos.y * q2 * vec.y + -2.0 * pos.z * q2 * vec.z + + +2.0 * pos.y * q1 * vec.x + +2.0 * pos.z * q0 * vec.x + + +2.0 * pos.x * q1 * vec.y + +2.0 * pos.z * q3 * vec.y + + -2.0 * pos.x * q0 * vec.z + +2.0 * pos.y * q3 * vec.z; + + + result.q3 = -2.0 * pos.x * q3 * vec.x + -2.0 * pos.y * q3 * vec.y + +2.0 * pos.z * q3 * vec.z + + -2.0 * pos.y * q0 * vec.x + +2.0 * pos.z * q1 * vec.x + + +2.0 * pos.x * q0 * vec.y + +2.0 * pos.z * q2 * vec.y + + +2.0 * pos.x * q1 * vec.z + +2.0 * pos.y * q2 * vec.z; + + return result; +} + +#ifdef COLVARS_LAMMPS +namespace { + inline void *new_Jacobi_solver(int size) { + return reinterpret_cast(new MathEigen::Jacobi &, + cvm::matrix2d &>(4)); + } +} +#endif + + +int colvarmodule::rotation::init() +{ + b_debug_gradients = false; + // lambda = 0.0; + cvm::main()->cite_feature("Optimal rotation via flexible fitting"); + return COLVARS_OK; +} + + +colvarmodule::rotation::rotation() +{ + init(); +#ifdef COLVARS_LAMMPS + jacobi = new_Jacobi_solver(4); +#else + jacobi = NULL; +#endif +} + + +colvarmodule::rotation::rotation(cvm::quaternion const &qi) + : q(qi) +{ + init(); +#ifdef COLVARS_LAMMPS + jacobi = new_Jacobi_solver(4); +#else + jacobi = NULL; +#endif +} + + +colvarmodule::rotation::rotation(cvm::real angle, cvm::rvector const &axis) +{ + init(); + cvm::rvector const axis_n = axis.unit(); + cvm::real const sina = cvm::sin(angle/2.0); + q = cvm::quaternion(cvm::cos(angle/2.0), + sina * axis_n.x, sina * axis_n.y, sina * axis_n.z); +#ifdef COLVARS_LAMMPS + jacobi = new_Jacobi_solver(4); +#else + jacobi = NULL; +#endif +} + + +colvarmodule::rotation::~rotation() +{ +#ifdef COLVARS_LAMMPS + delete reinterpret_cast< + MathEigen::Jacobi &, + cvm::matrix2d &> *>(jacobi); +#endif +} + + +void colvarmodule::rotation::build_correlation_matrix( + std::vector const &pos1, + std::vector const &pos2) +{ + // build the correlation matrix + size_t i; + for (i = 0; i < pos1.size(); i++) { + C.xx += pos1[i].x * pos2[i].x; + C.xy += pos1[i].x * pos2[i].y; + C.xz += pos1[i].x * pos2[i].z; + C.yx += pos1[i].y * pos2[i].x; + C.yy += pos1[i].y * pos2[i].y; + C.yz += pos1[i].y * pos2[i].z; + C.zx += pos1[i].z * pos2[i].x; + C.zy += pos1[i].z * pos2[i].y; + C.zz += pos1[i].z * pos2[i].z; + } +} + +void colvarmodule::rotation::build_correlation_matrix( + std::vector const &pos1, + std::vector const &pos2) +{ + // build the correlation matrix + size_t i; + for (i = 0; i < pos1.size(); i++) { + C.xx += pos1[i].pos.x * pos2[i].x; + C.xy += pos1[i].pos.x * pos2[i].y; + C.xz += pos1[i].pos.x * pos2[i].z; + C.yx += pos1[i].pos.y * pos2[i].x; + C.yy += pos1[i].pos.y * pos2[i].y; + C.yz += pos1[i].pos.y * pos2[i].z; + C.zx += pos1[i].pos.z * pos2[i].x; + C.zy += pos1[i].pos.z * pos2[i].y; + C.zz += pos1[i].pos.z * pos2[i].z; + } +} + + +void colvarmodule::rotation::compute_overlap_matrix() +{ + // build the "overlap" matrix, whose eigenvectors are stationary + // points of the RMSD in the space of rotations + S[0][0] = C.xx + C.yy + C.zz; + S[1][0] = C.yz - C.zy; + S[0][1] = S[1][0]; + S[2][0] = - C.xz + C.zx ; + S[0][2] = S[2][0]; + S[3][0] = C.xy - C.yx; + S[0][3] = S[3][0]; + S[1][1] = C.xx - C.yy - C.zz; + S[2][1] = C.xy + C.yx; + S[1][2] = S[2][1]; + S[3][1] = C.xz + C.zx; + S[1][3] = S[3][1]; + S[2][2] = - C.xx + C.yy - C.zz; + S[3][2] = C.yz + C.zy; + S[2][3] = S[3][2]; + S[3][3] = - C.xx - C.yy + C.zz; +} + + +#ifndef COLVARS_LAMMPS +namespace NR { + +void diagonalize_matrix(cvm::real m[4][4], + cvm::real eigval[4], + cvm::real eigvec[4][4]) +{ + std::memset(eigval, 0, sizeof(cvm::real) * 4); + std::memset(eigvec, 0, sizeof(cvm::real) * 4 * 4); + + // diagonalize + int jac_nrot = 0; + if (NR_Jacobi::jacobi(m, eigval, eigvec, &jac_nrot) != + COLVARS_OK) { + cvm::error("Too many iterations in jacobi diagonalization.\n" + "This is usually the result of an ill-defined set of atoms for " + "rotational alignment (RMSD, rotateReference, etc).\n"); + } + NR_Jacobi::eigsrt(eigval, eigvec); + // jacobi saves eigenvectors by columns + NR_Jacobi::transpose(eigvec); + + // normalize eigenvectors + for (size_t ie = 0; ie < 4; ie++) { + cvm::real norm2 = 0.0; + size_t i; + for (i = 0; i < 4; i++) { + norm2 += eigvec[ie][i] * eigvec[ie][i]; + } + cvm::real const norm = cvm::sqrt(norm2); + for (i = 0; i < 4; i++) { + eigvec[ie][i] /= norm; + } + } +} + +} +#endif + + +// Calculate the rotation, plus its derivatives + +void colvarmodule::rotation::calc_optimal_rotation( + std::vector const &pos1, + std::vector const &pos2) +{ + C.reset(); + build_correlation_matrix(pos1, pos2); + + calc_optimal_rotation_impl(); + + if (b_debug_gradients) debug_gradients(*this, pos1, pos2); +} + +void colvarmodule::rotation::calc_optimal_rotation( + std::vector const &pos1, + std::vector const &pos2) +{ + C.reset(); + build_correlation_matrix(pos1, pos2); + + calc_optimal_rotation_impl(); + + if (b_debug_gradients) debug_gradients(*this, pos1, pos2); +} + +// Calculate the optimal rotation between two groups, and implement it +// as a quaternion. Uses the method documented in: Coutsias EA, +// Seok C, Dill KA. Using quaternions to calculate RMSD. J Comput +// Chem. 25(15):1849-57 (2004) DOI: 10.1002/jcc.20110 PubMed: 15376254 +void colvarmodule::rotation::calc_optimal_rotation_impl() { + compute_overlap_matrix(); + + // S_backup = S; + std::memcpy(&S_backup[0][0], &S, 4*4*sizeof(cvm::real)); + + if (b_debug_gradients) { + cvm::matrix2d S_backup_out(4, 4); + for (size_t i = 0; i < 4; ++i) { + for (size_t j = 0; j < 4; ++j) { + S_backup_out[i][j] = S_backup[i][j]; + } + } + cvm::log("S = "+cvm::to_str(S_backup_out, cvm::cv_width, cvm::cv_prec)+"\n"); + } + + +#ifdef COLVARS_LAMMPS + MathEigen::Jacobi *ecalc = + reinterpret_cast *>(jacobi); + + int ierror = ecalc->Diagonalize(S, S_eigval, S_eigvec); + if (ierror) { + cvm::error("Too many iterations in jacobi diagonalization.\n" + "This is usually the result of an ill-defined set of atoms for " + "rotational alignment (RMSD, rotateReference, etc).\n"); + } +#else + NR::diagonalize_matrix(S, S_eigval, S_eigvec); +#endif + q = cvm::quaternion{S_eigvec[0][0], S_eigvec[0][1], S_eigvec[0][2], S_eigvec[0][3]}; + + if (cvm::rotation::monitor_crossings) { + if (q_old.norm2() > 0.0) { + q.match(q_old); + if (q_old.inner(q) < (1.0 - crossing_threshold)) { + cvm::log("Warning: one molecular orientation has changed by more than "+ + cvm::to_str(crossing_threshold)+": discontinuous rotation ?\n"); + } + } + q_old = q; + } +} diff --git a/src/external/colvars/colvartypes.h b/src/external/colvars/colvartypes.h new file mode 100644 index 00000000000..455e628f1b9 --- /dev/null +++ b/src/external/colvars/colvartypes.h @@ -0,0 +1,1509 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARTYPES_H +#define COLVARTYPES_H + +#include // TODO specialize templates and replace this with iosfwd +#include + +#ifdef COLVARS_LAMMPS +// Use open-source Jacobi implementation +#include "math_eigen_impl.h" +#endif + +#include "colvarmodule.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +// ---------------------------------------------------------------------- +/// Linear algebra functions and data types used in the collective +/// variables implemented so far +// ---------------------------------------------------------------------- + + +/// \brief Arbitrary size array (one dimensions) suitable for linear +/// algebra operations (i.e. for floating point numbers it can be used +/// with library functions) +template class colvarmodule::vector1d +{ +protected: + + std::vector data; + +public: + + /// Default constructor + inline vector1d(size_t const n = 0) + { + data.resize(n); + reset(); + } + + /// Constructor from C array + inline vector1d(size_t const n, T const *t) + { + data.resize(n); + reset(); + size_t i; + for (i = 0; i < size(); i++) { + data[i] = t[i]; + } + } + + /// Explicit Copy constructor + inline vector1d(const vector1d&) = default; + + /// Explicit Copy assignement + inline vector1d& operator=(const vector1d&) = default; + + /// Return a pointer to the data location + inline T * c_array() + { + if (data.size() > 0) { + return &(data[0]); + } else { + return NULL; + } + } + + /// Return a reference to the data + inline std::vector &data_array() + { + return data; + } + + /// Return a reference to the data + inline std::vector const &data_array() const + { + return data; + } + + inline ~vector1d() + { + data.clear(); + } + + /// Set all elements to zero + inline void reset() + { + data.assign(data.size(), T(0.0)); + } + + inline size_t size() const + { + return data.size(); + } + + inline void resize(size_t const n) + { + data.resize(n); + } + + inline void clear() + { + data.clear(); + } + + inline T & operator [] (size_t const i) { + return data[i]; + } + + inline T const & operator [] (size_t const i) const { + return data[i]; + } + + inline static void check_sizes(vector1d const &v1, vector1d const &v2) + { + if (v1.size() != v2.size()) { + cvm::error("Error: trying to perform an operation between vectors of different sizes, "+ + cvm::to_str(v1.size())+" and "+cvm::to_str(v2.size())+".\n"); + } + } + + inline void operator += (vector1d const &v) + { + check_sizes(*this, v); + size_t i; + for (i = 0; i < this->size(); i++) { + (*this)[i] += v[i]; + } + } + + inline void operator -= (vector1d const &v) + { + check_sizes(*this, v); + size_t i; + for (i = 0; i < this->size(); i++) { + (*this)[i] -= v[i]; + } + } + + inline void operator *= (cvm::real a) + { + size_t i; + for (i = 0; i < this->size(); i++) { + (*this)[i] *= a; + } + } + + inline void operator /= (cvm::real a) + { + size_t i; + for (i = 0; i < this->size(); i++) { + (*this)[i] /= a; + } + } + + inline friend vector1d operator + (vector1d const &v1, + vector1d const &v2) + { + check_sizes(v1.size(), v2.size()); + vector1d result(v1.size()); + size_t i; + for (i = 0; i < v1.size(); i++) { + result[i] = v1[i] + v2[i]; + } + return result; + } + + inline friend vector1d operator - (vector1d const &v1, + vector1d const &v2) + { + check_sizes(v1.size(), v2.size()); + vector1d result(v1.size()); + size_t i; + for (i = 0; i < v1.size(); i++) { + result[i] = v1[i] - v2[i]; + } + return result; + } + + inline friend vector1d operator * (vector1d const &v, cvm::real a) + { + vector1d result(v.size()); + size_t i; + for (i = 0; i < v.size(); i++) { + result[i] = v[i] * a; + } + return result; + } + + inline friend vector1d operator * (cvm::real a, vector1d const &v) + { + return v * a; + } + + inline friend vector1d operator / (vector1d const &v, cvm::real a) + { + vector1d result(v.size()); + size_t i; + for (i = 0; i < v.size(); i++) { + result[i] = v[i] / a; + } + return result; + } + + /// Inner product + inline friend T operator * (vector1d const &v1, vector1d const &v2) + { + check_sizes(v1.size(), v2.size()); + T prod(0.0); + size_t i; + for (i = 0; i < v1.size(); i++) { + prod += v1[i] * v2[i]; + } + return prod; + } + + /// Squared norm + inline cvm::real norm2() const + { + cvm::real result = 0.0; + size_t i; + for (i = 0; i < this->size(); i++) { + result += (*this)[i] * (*this)[i]; + } + return result; + } + + inline cvm::real norm() const + { + return cvm::sqrt(this->norm2()); + } + + inline cvm::real sum() const + { + cvm::real result = 0.0; + size_t i; + for (i = 0; i < this->size(); i++) { + result += (*this)[i]; + } + return result; + } + + /// Slicing + inline vector1d const slice(size_t const i1, size_t const i2) const + { + if ((i2 < i1) || (i2 >= this->size())) { + cvm::error("Error: trying to slice a vector using incorrect boundaries.\n"); + } + vector1d result(i2 - i1); + size_t i; + for (i = 0; i < (i2 - i1); i++) { + result[i] = (*this)[i1+i]; + } + return result; + } + + /// Assign a vector to a slice of this vector + inline void sliceassign(size_t const i1, size_t const i2, + vector1d const &v) + { + if ((i2 < i1) || (i2 >= this->size())) { + cvm::error("Error: trying to slice a vector using incorrect boundaries.\n"); + } + size_t i; + for (i = 0; i < (i2 - i1); i++) { + (*this)[i1+i] = v[i]; + } + } + + /// Formatted output + + inline size_t output_width(size_t real_width) const + { + return real_width*(this->size()) + 3*(this->size()-1) + 4; + } + + inline friend std::istream & operator >> (std::istream &is, + cvm::vector1d &v) + { + if (v.size() == 0) return is; + std::streampos const start_pos = is.tellg(); + char sep; + if ( !(is >> sep) || !(sep == '(') ) { + is.clear(); + is.seekg(start_pos, std::ios::beg); + is.setstate(std::ios::failbit); + return is; + } + size_t count = 0; + while ( (is >> v[count]) && + (count < (v.size()-1) ? ((is >> sep) && (sep == ',')) : true) ) { + if (++count == v.size()) break; + } + if (count < v.size()) { + is.clear(); + is.seekg(start_pos, std::ios::beg); + is.setstate(std::ios::failbit); + } + return is; + } + + inline friend std::ostream & operator << (std::ostream &os, + cvm::vector1d const &v) + { + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + os.width(2); + os << "( "; + size_t i; + for (i = 0; i < v.size()-1; i++) { + os.width(w); os.precision(p); + os << v[i] << " , "; + } + os.width(w); os.precision(p); + os << v[v.size()-1] << " )"; + return os; + } + + inline std::string to_simple_string() const + { + if (this->size() == 0) return std::string(""); + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + os.precision(cvm::cv_prec); + os << (*this)[0]; + size_t i; + for (i = 1; i < this->size(); i++) { + os << " " << (*this)[i]; + } + return os.str(); + } + + inline int from_simple_string(std::string const &s) + { + std::stringstream stream(s); + size_t i = 0; + if (this->size()) { + while ((stream >> (*this)[i]) && (i < this->size())) { + i++; + } + if (i < this->size()) { + return COLVARS_ERROR; + } + } else { + T input; + while (stream >> input) { + if ((i % 100) == 0) { + data.reserve(data.size()+100); + } + data.resize(data.size()+1); + data[i] = input; + i++; + } + } + return COLVARS_OK; + } + +}; + + +/// \brief Arbitrary size array (two dimensions) suitable for linear +/// algebra operations (i.e. for floating point numbers it can be used +/// with library functions) +template class colvarmodule::matrix2d +{ +public: + + friend class row; + size_t outer_length; + size_t inner_length; + +protected: + + class row { + public: + T * data; + size_t length; + inline row(T * const row_data, size_t const inner_length) + : data(row_data), length(inner_length) + {} + inline T & operator [] (size_t const j) { + return *(data+j); + } + inline T const & operator [] (size_t const j) const { + return *(data+j); + } + inline operator vector1d() const + { + return vector1d(length, data); + } + inline int set(cvm::vector1d const &v) const + { + if (v.size() != length) { + return cvm::error("Error: setting a matrix row from a vector of " + "incompatible size.\n", COLVARS_BUG_ERROR); + } + for (size_t i = 0; i < length; i++) data[i] = v[i]; + return COLVARS_OK; + } + }; + + std::vector data; + std::vector rows; + std::vector pointers; + +public: + + /// Allocation routine, used by all constructors + inline void resize(size_t const ol, size_t const il) + { + if ((ol > 0) && (il > 0)) { + + if (data.size() > 0) { + // copy previous data + size_t i, j; + std::vector new_data(ol * il); + for (i = 0; i < outer_length; i++) { + for (j = 0; j < inner_length; j++) { + new_data[il*i+j] = data[inner_length*i+j]; + } + } + data.resize(ol * il); + // copy them back + data = new_data; + } else { + data.resize(ol * il); + } + + outer_length = ol; + inner_length = il; + + if (data.size() > 0) { + // rebuild rows + size_t i; + rows.clear(); + rows.reserve(outer_length); + pointers.clear(); + pointers.reserve(outer_length); + for (i = 0; i < outer_length; i++) { + rows.push_back(row(&(data[0])+inner_length*i, inner_length)); + pointers.push_back(&(data[0])+inner_length*i); + } + } + } else { + // zero size + data.clear(); + rows.clear(); + } + } + + /// Deallocation routine + inline void clear() { + rows.clear(); + data.clear(); + } + + /// Set all elements to zero + inline void reset() + { + data.assign(data.size(), T(0.0)); + } + + inline size_t size() const + { + return data.size(); + } + + /// Default constructor + inline matrix2d() + : outer_length(0), inner_length(0) + { + this->resize(0, 0); + } + + inline matrix2d(size_t const ol, size_t const il) + : outer_length(ol), inner_length(il) + { + this->resize(outer_length, inner_length); + reset(); + } + + /// Copy constructor + inline matrix2d(matrix2d const &m) + : outer_length(m.outer_length), inner_length(m.inner_length) + { + // reinitialize data and rows arrays + this->resize(outer_length, inner_length); + // copy data + data = m.data; + } + + /// Destructor + inline ~matrix2d() { + this->clear(); + } + + /// Return a reference to the data + inline std::vector &data_array() + { + return data; + } + + /// Return a reference to the data + inline std::vector const &data_array() const + { + return data; + } + + inline row & operator [] (size_t const i) + { + return rows[i]; + } + inline row const & operator [] (size_t const i) const + { + return rows[i]; + } + + /// Assignment + inline matrix2d & operator = (matrix2d const &m) + { + if ((outer_length != m.outer_length) || (inner_length != m.inner_length)){ + this->clear(); + outer_length = m.outer_length; + inner_length = m.inner_length; + this->resize(outer_length, inner_length); + } + data = m.data; + return *this; + } + + /// Return the 2-d C array + inline T ** c_array() { + if (rows.size() > 0) { + return &(pointers[0]); + } else { + return NULL; + } + } + + inline static void check_sizes(matrix2d const &m1, matrix2d const &m2) + { + if ((m1.outer_length != m2.outer_length) || + (m1.inner_length != m2.inner_length)) { + cvm::error("Error: trying to perform an operation between " + "matrices of different sizes, "+ + cvm::to_str(m1.outer_length)+"x"+ + cvm::to_str(m1.inner_length)+" and "+ + cvm::to_str(m2.outer_length)+"x"+ + cvm::to_str(m2.inner_length)+".\n"); + } + } + + inline void operator += (matrix2d const &m) + { + check_sizes(*this, m); + size_t i; + for (i = 0; i < data.size(); i++) { + data[i] += m.data[i]; + } + } + + inline void operator -= (matrix2d const &m) + { + check_sizes(*this, m); + size_t i; + for (i = 0; i < data.size(); i++) { + data[i] -= m.data[i]; + } + } + + inline void operator *= (cvm::real a) + { + size_t i; + for (i = 0; i < data.size(); i++) { + data[i] *= a; + } + } + + inline void operator /= (cvm::real a) + { + size_t i; + for (i = 0; i < data.size(); i++) { + data[i] /= a; + } + } + + inline friend matrix2d operator + (matrix2d const &m1, + matrix2d const &m2) + { + check_sizes(m1, m2); + matrix2d result(m1.outer_length, m1.inner_length); + size_t i; + for (i = 0; i < m1.data.size(); i++) { + result.data[i] = m1.data[i] + m2.data[i]; + } + return result; + } + + inline friend matrix2d operator - (matrix2d const &m1, + matrix2d const &m2) + { + check_sizes(m1, m2); + matrix2d result(m1.outer_length, m1.inner_length); + size_t i; + for (i = 0; i < m1.data.size(); i++) { + result.data[i] = m1.data[i] - m1.data[i]; + } + return result; + } + + inline friend matrix2d operator * (matrix2d const &m, cvm::real a) + { + matrix2d result(m.outer_length, m.inner_length); + size_t i; + for (i = 0; i < m.data.size(); i++) { + result.data[i] = m.data[i] * a; + } + return result; + } + + inline friend matrix2d operator * (cvm::real a, matrix2d const &m) + { + return m * a; + } + + inline friend matrix2d operator / (matrix2d const &m, cvm::real a) + { + matrix2d result(m.outer_length, m.inner_length); + size_t i; + for (i = 0; i < m.data.size(); i++) { + result.data[i] = m.data[i] * a; + } + return result; + } + + /// vector-matrix multiplication + inline friend vector1d operator * (vector1d const &v, + matrix2d const &m) + { + vector1d result(m.inner_length); + if (m.outer_length != v.size()) { + cvm::error("Error: trying to multiply a vector and a matrix " + "of incompatible sizes, "+ + cvm::to_str(v.size()) + " and " + + cvm::to_str(m.outer_length)+"x"+cvm::to_str(m.inner_length) + + ".\n"); + } else { + size_t i, k; + for (i = 0; i < m.inner_length; i++) { + for (k = 0; k < m.outer_length; k++) { + result[i] += m[k][i] * v[k]; + } + } + } + return result; + } + + /// Formatted output + friend std::ostream & operator << (std::ostream &os, + matrix2d const &m) + { + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + os.width(2); + os << "( "; + size_t i; + for (i = 0; i < m.outer_length; i++) { + os << " ( "; + size_t j; + for (j = 0; j < m.inner_length-1; j++) { + os.width(w); + os.precision(p); + os << m[i][j] << " , "; + } + os.width(w); + os.precision(p); + os << m[i][m.inner_length-1] << " )"; + } + + os << " )"; + return os; + } + + inline std::string to_simple_string() const + { + if (this->size() == 0) return std::string(""); + std::ostringstream os; + os.setf(std::ios::scientific, std::ios::floatfield); + os.precision(cvm::cv_prec); + os << (*this)[0]; + size_t i; + for (i = 1; i < data.size(); i++) { + os << " " << data[i]; + } + return os.str(); + } + + inline int from_simple_string(std::string const &s) + { + std::stringstream stream(s); + size_t i = 0; + while ((i < data.size()) && (stream >> data[i])) { + i++; + } + if (i < data.size()) { + return COLVARS_ERROR; + } + return COLVARS_OK; + } + +}; + + +/// vector of real numbers with three components +class colvarmodule::rvector { + +public: + + cvm::real x, y, z; + + inline rvector() + { + reset(); + } + + /// \brief Set all components to zero + inline void reset() + { + set(0.0); + } + + inline rvector(cvm::real x_i, cvm::real y_i, cvm::real z_i) + { + set(x_i, y_i, z_i); + } + + inline rvector(cvm::vector1d const &v) + { + set(v[0], v[1], v[2]); + } + + inline rvector(cvm::real t) + { + set(t); + } + + /// \brief Set all components to a scalar + inline void set(cvm::real value) + { + x = y = z = value; + } + + /// \brief Assign all components + inline void set(cvm::real x_i, cvm::real y_i, cvm::real z_i) + { + x = x_i; + y = y_i; + z = z_i; + } + + /// \brief Access cartesian components by index + inline cvm::real & operator [] (int i) { + return (i == 0) ? x : (i == 1) ? y : (i == 2) ? z : x; + } + + /// \brief Access cartesian components by index + inline cvm::real operator [] (int i) const { + return (i == 0) ? x : (i == 1) ? y : (i == 2) ? z : x; + } + + inline cvm::vector1d const as_vector() const + { + cvm::vector1d result(3); + result[0] = this->x; + result[1] = this->y; + result[2] = this->z; + return result; + } + + inline void operator += (cvm::rvector const &v) + { + x += v.x; + y += v.y; + z += v.z; + } + + inline void operator -= (cvm::rvector const &v) + { + x -= v.x; + y -= v.y; + z -= v.z; + } + + inline void operator *= (cvm::real v) + { + x *= v; + y *= v; + z *= v; + } + + inline void operator /= (cvm::real const& v) + { + x /= v; + y /= v; + z /= v; + } + + inline cvm::real norm2() const + { + return (x*x + y*y + z*z); + } + + inline cvm::real norm() const + { + return cvm::sqrt(this->norm2()); + } + + inline cvm::rvector unit() const + { + const cvm::real n = this->norm(); + return (n > 0. ? cvm::rvector(x, y, z)/n : cvm::rvector(1., 0., 0.)); + } + + static inline size_t output_width(size_t real_width) + { + return 3*real_width + 10; + } + + + static inline cvm::rvector outer(cvm::rvector const &v1, + cvm::rvector const &v2) + { + return cvm::rvector( v1.y*v2.z - v2.y*v1.z, + -v1.x*v2.z + v2.x*v1.z, + v1.x*v2.y - v2.x*v1.y); + } + + friend inline cvm::rvector operator - (cvm::rvector const &v) + { + return cvm::rvector(-v.x, -v.y, -v.z); + } + + friend inline cvm::rvector operator + (cvm::rvector const &v1, + cvm::rvector const &v2) + { + return cvm::rvector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); + } + friend inline cvm::rvector operator - (cvm::rvector const &v1, + cvm::rvector const &v2) + { + return cvm::rvector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + /// Inner (dot) product + friend inline cvm::real operator * (cvm::rvector const &v1, + cvm::rvector const &v2) + { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + } + + friend inline cvm::rvector operator * (cvm::real a, cvm::rvector const &v) + { + return cvm::rvector(a*v.x, a*v.y, a*v.z); + } + + friend inline cvm::rvector operator * (cvm::rvector const &v, cvm::real a) + { + return cvm::rvector(a*v.x, a*v.y, a*v.z); + } + + friend inline cvm::rvector operator / (cvm::rvector const &v, cvm::real a) + { + return cvm::rvector(v.x/a, v.y/a, v.z/a); + } + + std::string to_simple_string() const; + int from_simple_string(std::string const &s); +}; + + +/// \brief 2-dimensional array of real numbers with three components +/// along each dimension (works with colvarmodule::rvector) +class colvarmodule::rmatrix { + +public: + + cvm::real xx, xy, xz, yx, yy, yz, zx, zy, zz; + + /// Default constructor + inline rmatrix() + { + reset(); + } + + /// Constructor component by component + inline rmatrix(cvm::real xxi, cvm::real xyi, cvm::real xzi, + cvm::real yxi, cvm::real yyi, cvm::real yzi, + cvm::real zxi, cvm::real zyi, cvm::real zzi) + { + xx = xxi; + xy = xyi; + xz = xzi; + yx = yxi; + yy = yyi; + yz = yzi; + zx = zxi; + zy = zyi; + zz = zzi; + } + + + inline void reset() + { + xx = xy = xz = yx = yy = yz = zx = zy = zz = 0.0; + } + + /// Return the determinant + inline cvm::real determinant() const + { + return + ( xx * (yy*zz - zy*yz)) + - (yx * (xy*zz - zy*xz)) + + (zx * (xy*yz - yy*xz)); + } + + inline cvm::rmatrix transpose() const + { + return cvm::rmatrix(xx, yx, zx, + xy, yy, zy, + xz, yz, zz); + } + + inline friend cvm::rvector operator * (cvm::rmatrix const &m, + cvm::rvector const &r) + { + return cvm::rvector(m.xx*r.x + m.xy*r.y + m.xz*r.z, + m.yx*r.x + m.yy*r.y + m.yz*r.z, + m.zx*r.x + m.zy*r.y + m.zz*r.z); + } +}; + + + +/// \brief 1-dimensional vector of real numbers with four components and +/// a quaternion algebra +class colvarmodule::quaternion { + +public: + + cvm::real q0, q1, q2, q3; + + /// Constructor from a 3-d vector + inline quaternion(cvm::real x, cvm::real y, cvm::real z) + : q0(0.0), q1(x), q2(y), q3(z) + {} + + /// Constructor component by component + inline quaternion(cvm::real const qv[4]) + : q0(qv[0]), q1(qv[1]), q2(qv[2]), q3(qv[3]) + {} + + /// Constructor component by component + inline quaternion(cvm::real q0i, + cvm::real q1i, + cvm::real q2i, + cvm::real q3i) + : q0(q0i), q1(q1i), q2(q2i), q3(q3i) + {} + + inline quaternion(cvm::vector1d const &v) + : q0(v[0]), q1(v[1]), q2(v[2]), q3(v[3]) + {} + + /// "Constructor" after Euler angles (in radians) + /// + /// http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles + inline void set_from_euler_angles(cvm::real phi_in, + cvm::real theta_in, + cvm::real psi_in) + { + q0 = ( (cvm::cos(phi_in/2.0)) * (cvm::cos(theta_in/2.0)) * (cvm::cos(psi_in/2.0)) + + (cvm::sin(phi_in/2.0)) * (cvm::sin(theta_in/2.0)) * (cvm::sin(psi_in/2.0)) ); + + q1 = ( (cvm::sin(phi_in/2.0)) * (cvm::cos(theta_in/2.0)) * (cvm::cos(psi_in/2.0)) - + (cvm::cos(phi_in/2.0)) * (cvm::sin(theta_in/2.0)) * (cvm::sin(psi_in/2.0)) ); + + q2 = ( (cvm::cos(phi_in/2.0)) * (cvm::sin(theta_in/2.0)) * (cvm::cos(psi_in/2.0)) + + (cvm::sin(phi_in/2.0)) * (cvm::cos(theta_in/2.0)) * (cvm::sin(psi_in/2.0)) ); + + q3 = ( (cvm::cos(phi_in/2.0)) * (cvm::cos(theta_in/2.0)) * (cvm::sin(psi_in/2.0)) - + (cvm::sin(phi_in/2.0)) * (cvm::sin(theta_in/2.0)) * (cvm::cos(psi_in/2.0)) ); + } + + /// \brief Default constructor + inline quaternion() + { + reset(); + } + + /// \brief Set all components to a scalar + inline void set(cvm::real value) + { + q0 = q1 = q2 = q3 = value; + } + + /// \brief Set all components to zero (null quaternion) + inline void reset() + { + set(0.0); + } + + /// \brief Set the q0 component to 1 and the others to 0 (quaternion + /// representing no rotation) + inline void reset_rotation() + { + q0 = 1.0; + q1 = q2 = q3 = 0.0; + } + + /// Tell the number of characters required to print a quaternion, given that of a real number + static inline size_t output_width(size_t real_width) + { + return 4*real_width + 13; + } + + std::string to_simple_string() const; + int from_simple_string(std::string const &s); + + /// \brief Formatted output operator + friend std::ostream & operator << (std::ostream &os, cvm::quaternion const &q); + /// \brief Formatted input operator + friend std::istream & operator >> (std::istream &is, cvm::quaternion &q); + + /// Access the quaternion as a 4-d array (return a reference) + inline cvm::real & operator [] (int i) { + switch (i) { + case 0: + return this->q0; + case 1: + return this->q1; + case 2: + return this->q2; + case 3: + return this->q3; + default: + cvm::error("Error: incorrect quaternion component.\n"); + return q0; + } + } + + /// Access the quaternion as a 4-d array (return a value) + inline cvm::real operator [] (int i) const { + switch (i) { + case 0: + return this->q0; + case 1: + return this->q1; + case 2: + return this->q2; + case 3: + return this->q3; + default: + cvm::error("Error: trying to access a quaternion " + "component which is not between 0 and 3.\n"); + return 0.0; + } + } + + inline cvm::vector1d const as_vector() const + { + cvm::vector1d result(4); + result[0] = q0; + result[1] = q1; + result[2] = q2; + result[3] = q3; + return result; + } + + /// Square norm of the quaternion + inline cvm::real norm2() const + { + return q0*q0 + q1*q1 + q2*q2 + q3*q3; + } + + /// Norm of the quaternion + inline cvm::real norm() const + { + return cvm::sqrt(this->norm2()); + } + + /// Return the conjugate quaternion + inline cvm::quaternion conjugate() const + { + return cvm::quaternion(q0, -q1, -q2, -q3); + } + + inline void operator *= (cvm::real a) + { + q0 *= a; q1 *= a; q2 *= a; q3 *= a; + } + + inline void operator /= (cvm::real a) + { + q0 /= a; q1 /= a; q2 /= a; q3 /= a; + } + + inline void set_positive() + { + if (q0 > 0.0) return; + q0 = -q0; + q1 = -q1; + q2 = -q2; + q3 = -q3; + } + + inline void operator += (cvm::quaternion const &h) + { + q0+=h.q0; q1+=h.q1; q2+=h.q2; q3+=h.q3; + } + inline void operator -= (cvm::quaternion const &h) + { + q0-=h.q0; q1-=h.q1; q2-=h.q2; q3-=h.q3; + } + + /// Return the vector component + inline cvm::rvector get_vector() const + { + return cvm::rvector(q1, q2, q3); + } + + + friend inline cvm::quaternion operator + (cvm::quaternion const &h, + cvm::quaternion const &q) + { + return cvm::quaternion(h.q0+q.q0, h.q1+q.q1, h.q2+q.q2, h.q3+q.q3); + } + + friend inline cvm::quaternion operator - (cvm::quaternion const &h, + cvm::quaternion const &q) + { + return cvm::quaternion(h.q0-q.q0, h.q1-q.q1, h.q2-q.q2, h.q3-q.q3); + } + + /// \brief Provides the quaternion product. \b NOTE: for the inner + /// product use: `h.inner (q);` + friend inline cvm::quaternion operator * (cvm::quaternion const &h, + cvm::quaternion const &q) + { + return cvm::quaternion(h.q0*q.q0 - h.q1*q.q1 - h.q2*q.q2 - h.q3*q.q3, + h.q0*q.q1 + h.q1*q.q0 + h.q2*q.q3 - h.q3*q.q2, + h.q0*q.q2 + h.q2*q.q0 + h.q3*q.q1 - h.q1*q.q3, + h.q0*q.q3 + h.q3*q.q0 + h.q1*q.q2 - h.q2*q.q1); + } + + friend inline cvm::quaternion operator * (cvm::real c, + cvm::quaternion const &q) + { + return cvm::quaternion(c*q.q0, c*q.q1, c*q.q2, c*q.q3); + } + friend inline cvm::quaternion operator * (cvm::quaternion const &q, + cvm::real c) + { + return cvm::quaternion(q.q0*c, q.q1*c, q.q2*c, q.q3*c); + } + friend inline cvm::quaternion operator / (cvm::quaternion const &q, + cvm::real c) + { + return cvm::quaternion(q.q0/c, q.q1/c, q.q2/c, q.q3/c); + } + + + /// \brief Rotate v through this quaternion (put it in the rotated + /// reference frame) + inline cvm::rvector rotate(cvm::rvector const &v) const + { + return ( (*this) * cvm::quaternion(0.0, v.x, v.y, v.z) * + this->conjugate() ).get_vector(); + } + + /// \brief Rotate Q2 through this quaternion (put it in the rotated + /// reference frame) + inline cvm::quaternion rotate(cvm::quaternion const &Q2) const + { + cvm::rvector const vq_rot = this->rotate(Q2.get_vector()); + return cvm::quaternion(Q2.q0, vq_rot.x, vq_rot.y, vq_rot.z); + } + + /// Return the 3x3 matrix associated to this quaternion + inline cvm::rmatrix rotation_matrix() const + { + cvm::rmatrix R; + + R.xx = q0*q0 + q1*q1 - q2*q2 - q3*q3; + R.yy = q0*q0 - q1*q1 + q2*q2 - q3*q3; + R.zz = q0*q0 - q1*q1 - q2*q2 + q3*q3; + + R.xy = 2.0 * (q1*q2 - q0*q3); + R.xz = 2.0 * (q0*q2 + q1*q3); + + R.yx = 2.0 * (q0*q3 + q1*q2); + R.yz = 2.0 * (q2*q3 - q0*q1); + + R.zx = 2.0 * (q1*q3 - q0*q2); + R.zy = 2.0 * (q0*q1 + q2*q3); + + return R; + } + + + /// \brief Multiply the given vector by the derivative of the given + /// (rotated) position with respect to the quaternion + cvm::quaternion position_derivative_inner(cvm::rvector const &pos, + cvm::rvector const &vec) const; + + + /// \brief Return the cosine between the orientation frame + /// associated to this quaternion and another + inline cvm::real cosine(cvm::quaternion const &q) const + { + cvm::real const iprod = this->inner(q); + return 2.0*iprod*iprod - 1.0; + } + + /// \brief Square distance from another quaternion on the + /// 4-dimensional unit sphere: returns the square of the angle along + /// the shorter of the two geodesics + inline cvm::real dist2(cvm::quaternion const &Q2) const + { + cvm::real const cos_omega = this->q0*Q2.q0 + this->q1*Q2.q1 + + this->q2*Q2.q2 + this->q3*Q2.q3; + + cvm::real const omega = cvm::acos( (cos_omega > 1.0) ? 1.0 : + ( (cos_omega < -1.0) ? -1.0 : cos_omega) ); + + // get the minimum distance: x and -x are the same quaternion + if (cos_omega > 0.0) + return omega * omega; + else + return (PI-omega) * (PI-omega); + } + + /// Gradient of the square distance: returns a 4-vector equivalent + /// to that provided by slerp + inline cvm::quaternion dist2_grad(cvm::quaternion const &Q2) const + { + cvm::real const cos_omega = this->q0*Q2.q0 + this->q1*Q2.q1 + this->q2*Q2.q2 + this->q3*Q2.q3; + cvm::real const omega = cvm::acos( (cos_omega > 1.0) ? 1.0 : + ( (cos_omega < -1.0) ? -1.0 : cos_omega) ); + cvm::real const sin_omega = cvm::sin(omega); + + if (cvm::fabs(sin_omega) < 1.0E-14) { + // return a null 4d vector + return cvm::quaternion(0.0, 0.0, 0.0, 0.0); + } + + cvm::quaternion const + grad1((-1.0)*sin_omega*Q2.q0 + cos_omega*(this->q0-cos_omega*Q2.q0)/sin_omega, + (-1.0)*sin_omega*Q2.q1 + cos_omega*(this->q1-cos_omega*Q2.q1)/sin_omega, + (-1.0)*sin_omega*Q2.q2 + cos_omega*(this->q2-cos_omega*Q2.q2)/sin_omega, + (-1.0)*sin_omega*Q2.q3 + cos_omega*(this->q3-cos_omega*Q2.q3)/sin_omega); + + if (cos_omega > 0.0) { + return 2.0*omega*grad1; + } else { + return -2.0*(PI-omega)*grad1; + } + } + + /// \brief Choose the closest between Q2 and -Q2 and save it back. + /// Not required for dist2() and dist2_grad() + inline void match(cvm::quaternion &Q2) const + { + cvm::real const cos_omega = this->q0*Q2.q0 + this->q1*Q2.q1 + + this->q2*Q2.q2 + this->q3*Q2.q3; + if (cos_omega < 0.0) Q2 *= -1.0; + } + + /// \brief Inner product (as a 4-d vector) with Q2; requires match() + /// if the largest overlap is looked for + inline cvm::real inner(cvm::quaternion const &Q2) const + { + cvm::real const prod = this->q0*Q2.q0 + this->q1*Q2.q1 + + this->q2*Q2.q2 + this->q3*Q2.q3; + return prod; + } + + +}; + +#ifndef COLVARS_LAMMPS +namespace NR { +void diagonalize_matrix(cvm::real m[4][4], + cvm::real eigval[4], + cvm::real eigvec[4][4]); +} +#endif + + +/// \brief A rotation between two sets of coordinates (for the moment +/// a wrapper for colvarmodule::quaternion) +class colvarmodule::rotation +{ +private: + /// Correlation matrix C (3, 3) + cvm::rmatrix C; + + /// Overlap matrix S (4, 4) + cvm::real S[4][4]; + + /// Eigenvalues of S + cvm::real S_eigval[4]; + + /// Eigenvectors of S + cvm::real S_eigvec[4][4]; + + /// Used for debugging gradients + cvm::real S_backup[4][4]; + +public: + /// \brief Perform gradient tests + bool b_debug_gradients; + + /// \brief The rotation itself (implemented as a quaternion) + cvm::quaternion q; + + template + friend struct rotation_derivative; + + template + friend void debug_gradients( + cvm::rotation &rot, + const std::vector &pos1, + const std::vector &pos2); + + /// \brief Calculate the optimal rotation and store the + /// corresponding eigenvalue and eigenvector in the arguments l0 and + /// q0; if the gradients have been previously requested, calculate + /// them as well + /// + /// The method to derive the optimal rotation is defined in: + /// Coutsias EA, Seok C, Dill KA. + /// Using quaternions to calculate RMSD. + /// J Comput Chem. 25(15):1849-57 (2004) + /// DOI: 10.1002/jcc.20110 PubMed: 15376254 + void calc_optimal_rotation(std::vector const &pos1, + std::vector const &pos2); + void calc_optimal_rotation(std::vector const &pos1, + std::vector const &pos2); + + /// Initialize member data + int init(); + + /// Default constructor + rotation(); + + /// Constructor after a quaternion + rotation(cvm::quaternion const &qi); + + /// Constructor after an axis of rotation and an angle (in radians) + rotation(cvm::real angle, cvm::rvector const &axis); + + /// Destructor + ~rotation(); + + /// Return the rotated vector + inline cvm::rvector rotate(cvm::rvector const &v) const + { + return q.rotate(v); + } + + /// Return the inverse of this rotation + inline cvm::rotation inverse() const + { + return cvm::rotation(this->q.conjugate()); + } + + /// Return the associated 3x3 matrix + inline cvm::rmatrix matrix() const + { + return q.rotation_matrix(); + } + + /// \brief Return the spin angle (in degrees) with respect to the + /// provided axis (which MUST be normalized) + inline cvm::real spin_angle(cvm::rvector const &axis) const + { + cvm::rvector const q_vec = q.get_vector(); + cvm::real alpha = (180.0/PI) * 2.0 * cvm::atan2(axis * q_vec, q.q0); + while (alpha > 180.0) alpha -= 360; + while (alpha < -180.0) alpha += 360; + return alpha; + } + + /// \brief Return the derivative of the spin angle with respect to + /// the quaternion + inline cvm::quaternion dspin_angle_dq(cvm::rvector const &axis) const + { + cvm::rvector const q_vec = q.get_vector(); + cvm::real const iprod = axis * q_vec; + + if (q.q0 != 0.0) { + + cvm::real const dspindx = + (180.0/PI) * 2.0 * (1.0 / (1.0 + (iprod*iprod)/(q.q0*q.q0))); + + return cvm::quaternion( dspindx * (iprod * (-1.0) / (q.q0*q.q0)), + dspindx * ((1.0/q.q0) * axis.x), + dspindx * ((1.0/q.q0) * axis.y), + dspindx * ((1.0/q.q0) * axis.z)); + } else { + // (1/(1+x^2)) ~ (1/x)^2 + // The documentation of spinAngle discourages its use when q_vec and + // axis are not close + return cvm::quaternion((180.0/PI) * 2.0 * ((-1.0)/iprod), 0.0, 0.0, 0.0); + } + } + + /// \brief Return the projection of the orientation vector onto a + /// predefined axis + inline cvm::real cos_theta(cvm::rvector const &axis) const + { + cvm::rvector const q_vec = q.get_vector(); + cvm::real const alpha = + (180.0/PI) * 2.0 * cvm::atan2(axis * q_vec, q.q0); + + cvm::real const cos_spin_2 = cvm::cos(alpha * (PI/180.0) * 0.5); + cvm::real const cos_theta_2 = ( (cos_spin_2 != 0.0) ? + (q.q0 / cos_spin_2) : + (0.0) ); + // cos(2t) = 2*cos(t)^2 - 1 + return 2.0 * (cos_theta_2*cos_theta_2) - 1.0; + } + + /// Return the derivative of the tilt wrt the quaternion + inline cvm::quaternion dcos_theta_dq(cvm::rvector const &axis) const + { + cvm::rvector const q_vec = q.get_vector(); + cvm::real const iprod = axis * q_vec; + + cvm::real const cos_spin_2 = cvm::cos(cvm::atan2(iprod, q.q0)); + + if (q.q0 != 0.0) { + + cvm::real const d_cos_theta_dq0 = + (4.0 * q.q0 / (cos_spin_2*cos_spin_2)) * + (1.0 - (iprod*iprod)/(q.q0*q.q0) / (1.0 + (iprod*iprod)/(q.q0*q.q0))); + + cvm::real const d_cos_theta_dqn = + (4.0 * q.q0 / (cos_spin_2*cos_spin_2) * + (iprod/q.q0) / (1.0 + (iprod*iprod)/(q.q0*q.q0))); + + return cvm::quaternion(d_cos_theta_dq0, + d_cos_theta_dqn * axis.x, + d_cos_theta_dqn * axis.y, + d_cos_theta_dqn * axis.z); + } else { + + cvm::real const d_cos_theta_dqn = + (4.0 / (cos_spin_2*cos_spin_2 * iprod)); + + return cvm::quaternion(0.0, + d_cos_theta_dqn * axis.x, + d_cos_theta_dqn * axis.y, + d_cos_theta_dqn * axis.z); + } + } + + /// \brief Whether to test for eigenvalue crossing + static bool monitor_crossings; + /// \brief Threshold for the eigenvalue crossing test + static cvm::real crossing_threshold; + +protected: + + /// \brief Previous value of the rotation (used to warn the user + /// when the structure changes too much, and there may be an + /// eigenvalue crossing) + cvm::quaternion q_old; + + /// Build the correlation matrix C (used by calc_optimal_rotation()) + void build_correlation_matrix(std::vector const &pos1, + std::vector const &pos2); + void build_correlation_matrix(std::vector const &pos1, + std::vector const &pos2); + + /// \brief Actual implementation of `calc_optimal_rotation` (and called by it) + void calc_optimal_rotation_impl(); + + /// Compute the overlap matrix S (used by calc_optimal_rotation()) + void compute_overlap_matrix(); + + /// Pointer to instance of Jacobi solver + void *jacobi; +}; + + +#endif diff --git a/src/external/colvars/colvarvalue.cpp b/src/external/colvars/colvarvalue.cpp new file mode 100644 index 00000000000..64436db9801 --- /dev/null +++ b/src/external/colvars/colvarvalue.cpp @@ -0,0 +1,1029 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvars_memstream.h" + + + +colvarvalue::colvarvalue() + : value_type(type_scalar), real_value(0.0) +{} + + +colvarvalue::colvarvalue(Type const &vti) + : value_type(vti), real_value(0.0) +{ + reset(); +} + +colvarvalue::colvarvalue(cvm::real const &x) + : value_type(type_scalar), real_value(x) +{} + + +colvarvalue::colvarvalue(cvm::rvector const &v, colvarvalue::Type vti) + : value_type(vti), real_value(0.0), rvector_value(v) +{} + + +colvarvalue::colvarvalue(cvm::quaternion const &q, colvarvalue::Type vti) + : value_type(vti), real_value(0.0), quaternion_value(q) +{} + + +colvarvalue::colvarvalue(colvarvalue const &x) + : value_type(x.type()), real_value(0.0) +{ + switch (x.type()) { + case type_scalar: + real_value = x.real_value; + break; + case type_3vector: + case type_unit3vector: + case type_unit3vectorderiv: + rvector_value = x.rvector_value; + break; + case type_quaternion: + case type_quaternionderiv: + quaternion_value = x.quaternion_value; + break; + case type_vector: + vector1d_value = x.vector1d_value; + elem_types = x.elem_types; + elem_indices = x.elem_indices; + elem_sizes = x.elem_sizes; + case type_notset: + default: + break; + } +} + + +colvarvalue::colvarvalue(cvm::vector1d const &v, + colvarvalue::Type vti) + : real_value(0.0) +{ + if ((vti != type_vector) && (v.size() != num_dimensions(vti))) { + cvm::error("Error: trying to initialize a variable of type \""+type_desc(vti)+ + "\" using a vector of size "+cvm::to_str(v.size())+ + ".\n"); + value_type = type_notset; + } else { + value_type = vti; + switch (vti) { + case type_scalar: + real_value = v[0]; + break; + case type_3vector: + case type_unit3vector: + case type_unit3vectorderiv: + rvector_value = cvm::rvector(v); + break; + case type_quaternion: + case type_quaternionderiv: + quaternion_value = cvm::quaternion(v); + break; + case type_vector: + vector1d_value = v; + break; + case type_notset: + default: + break; + } + } +} + + +std::string const colvarvalue::type_desc(Type t) +{ + switch (t) { + case colvarvalue::type_scalar: + return "scalar number"; break; + case colvarvalue::type_3vector: + return "3-dimensional vector"; break; + case colvarvalue::type_unit3vector: + return "3-dimensional unit vector"; break; + case colvarvalue::type_unit3vectorderiv: + return "derivative of a 3-dimensional unit vector"; break; + case colvarvalue::type_quaternion: + return "4-dimensional unit quaternion"; break; + case colvarvalue::type_quaternionderiv: + return "4-dimensional tangent vector"; break; + case colvarvalue::type_vector: + return "n-dimensional vector"; break; + case colvarvalue::type_notset: + // fallthrough + default: + return "not set"; break; + } +} + + +std::string const colvarvalue::type_keyword(Type t) +{ + switch (t) { + case colvarvalue::type_notset: + default: + return "not_set"; break; + case colvarvalue::type_scalar: + return "scalar"; break; + case colvarvalue::type_3vector: + return "vector3"; break; + case colvarvalue::type_unit3vector: + return "unit_vector3"; break; + case colvarvalue::type_unit3vectorderiv: + return ""; break; + case colvarvalue::type_quaternion: + return "unit_quaternion"; break; + case colvarvalue::type_quaternionderiv: + return ""; break; + case colvarvalue::type_vector: + return "vector"; break; + } +} + + +size_t colvarvalue::num_df(Type t) +{ + switch (t) { + case colvarvalue::type_notset: + default: + return 0; break; + case colvarvalue::type_scalar: + return 1; break; + case colvarvalue::type_3vector: + return 3; break; + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return 2; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return 3; break; + case colvarvalue::type_vector: + // the size of a vector is unknown without its object + return 0; break; + } +} + + +size_t colvarvalue::num_dimensions(Type t) +{ + switch (t) { + case colvarvalue::type_notset: + default: + return 0; break; + case colvarvalue::type_scalar: + return 1; break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return 3; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return 4; break; + case colvarvalue::type_vector: + // the size of a vector is unknown without its object + return 0; break; + } +} + + +void colvarvalue::reset() +{ + switch (value_type) { + case colvarvalue::type_scalar: + real_value = 0.0; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + rvector_value.reset(); + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + quaternion_value.reset(); + break; + case colvarvalue::type_vector: + vector1d_value.reset(); + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +void colvarvalue::apply_constraints() +{ + switch (value_type) { + case colvarvalue::type_scalar: + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vectorderiv: + case colvarvalue::type_quaternionderiv: + break; + case colvarvalue::type_unit3vector: + rvector_value /= cvm::sqrt(rvector_value.norm2()); + break; + case colvarvalue::type_quaternion: + quaternion_value /= cvm::sqrt(quaternion_value.norm2()); + break; + case colvarvalue::type_vector: + if (elem_types.size() > 0) { + // if we have information about non-scalar types, use it + size_t i; + for (i = 0; i < elem_types.size(); i++) { + if (elem_sizes[i] == 1) continue; // TODO this can be optimized further + colvarvalue cvtmp(vector1d_value.slice(elem_indices[i], + elem_indices[i] + elem_sizes[i]), elem_types[i]); + cvtmp.apply_constraints(); + set_elem(i, cvtmp); + } + } + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +void colvarvalue::type(Type const &vti) +{ + if (vti != value_type) { + // reset the value based on the previous type + reset(); + if ((value_type == type_vector) && (vti != type_vector)) { + vector1d_value.clear(); + } + value_type = vti; + } +} + + +void colvarvalue::type(colvarvalue const &x) +{ + if (x.type() != value_type) { + // reset the value based on the previous type + reset(); + if (value_type == type_vector) { + vector1d_value.clear(); + } + value_type = x.type(); + } + + if (x.type() == type_vector) { + vector1d_value.resize(x.vector1d_value.size()); + } +} + + +void colvarvalue::is_derivative() +{ + switch (value_type) { + case colvarvalue::type_scalar: + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vectorderiv: + case colvarvalue::type_quaternionderiv: + break; + case colvarvalue::type_unit3vector: + type(colvarvalue::type_unit3vectorderiv); + break; + case colvarvalue::type_quaternion: + type(colvarvalue::type_quaternionderiv); + break; + case colvarvalue::type_vector: + // TODO + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +void colvarvalue::add_elem(colvarvalue const &x) +{ + if (this->value_type != type_vector) { + cvm::error("Error: trying to set an element for a variable that is not set to be a vector.\n"); + return; + } + size_t const n = vector1d_value.size(); + size_t const nd = num_dimensions(x.value_type); + elem_types.push_back(x.value_type); + elem_indices.push_back(n); + elem_sizes.push_back(nd); + vector1d_value.resize(n + nd); + set_elem(n, x); +} + + +colvarvalue const colvarvalue::get_elem(int const i_begin, int const i_end, Type const vt) const +{ + if (vector1d_value.size() > 0) { + cvm::vector1d const v(vector1d_value.slice(i_begin, i_end)); + return colvarvalue(v, vt); + } else { + cvm::error("Error: trying to get an element from a variable that is not a vector.\n"); + return colvarvalue(type_notset); + } +} + + +void colvarvalue::set_elem(int const i_begin, int const i_end, colvarvalue const &x) +{ + if (vector1d_value.size() > 0) { + vector1d_value.sliceassign(i_begin, i_end, x.as_vector()); + } else { + cvm::error("Error: trying to set an element for a variable that is not a vector.\n"); + } +} + + +colvarvalue const colvarvalue::get_elem(int const icv) const +{ + if (elem_types.size() > 0) { + return get_elem(elem_indices[icv], elem_indices[icv] + elem_sizes[icv], + elem_types[icv]); + } else { + cvm::error("Error: trying to get a colvarvalue element from a vector colvarvalue that was initialized as a plain array.\n"); + return colvarvalue(type_notset); + } +} + + +void colvarvalue::set_elem(int const icv, colvarvalue const &x) +{ + if (elem_types.size() > 0) { + check_types_assign(elem_types[icv], x.value_type); + set_elem(elem_indices[icv], elem_indices[icv] + elem_sizes[icv], x); + } else { + cvm::error("Error: trying to set a colvarvalue element for a colvarvalue that was initialized as a plain array.\n"); + } +} + + +void colvarvalue::set_random() +{ + size_t ic; + switch (this->type()) { + case colvarvalue::type_scalar: + this->real_value = cvm::rand_gaussian(); + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + this->rvector_value.x = cvm::rand_gaussian(); + this->rvector_value.y = cvm::rand_gaussian(); + this->rvector_value.z = cvm::rand_gaussian(); + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + this->quaternion_value.q0 = cvm::rand_gaussian(); + this->quaternion_value.q1 = cvm::rand_gaussian(); + this->quaternion_value.q2 = cvm::rand_gaussian(); + this->quaternion_value.q3 = cvm::rand_gaussian(); + break; + case colvarvalue::type_vector: + for (ic = 0; ic < this->vector1d_value.size(); ic++) { + this->vector1d_value[ic] = cvm::rand_gaussian(); + } + break; + case colvarvalue::type_notset: + default: + undef_op(); + break; + } +} + + +void colvarvalue::set_ones(cvm::real assigned_value) +{ + size_t ic; + switch (this->type()) { + case colvarvalue::type_scalar: + this->real_value = assigned_value; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + this->rvector_value.x = assigned_value; + this->rvector_value.y = assigned_value; + this->rvector_value.z = assigned_value; + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + this->quaternion_value.q0 = assigned_value; + this->quaternion_value.q1 = assigned_value; + this->quaternion_value.q2 = assigned_value; + this->quaternion_value.q3 = assigned_value; + break; + case colvarvalue::type_vector: + for (ic = 0; ic < this->vector1d_value.size(); ic++) { + this->vector1d_value[ic] = assigned_value; + } + break; + case colvarvalue::type_notset: + default: + undef_op(); + break; + } +} + + +void colvarvalue::undef_op() const +{ + cvm::error("Error: Undefined operation on a colvar of type \""+ + type_desc(this->type())+"\".\n"); +} + + +// binary operations between two colvarvalues + +colvarvalue operator + (colvarvalue const &x1, + colvarvalue const &x2) +{ + colvarvalue::check_types(x1, x2); + + switch (x1.value_type) { + case colvarvalue::type_scalar: + return colvarvalue(x1.real_value + x2.real_value); + case colvarvalue::type_3vector: + return colvarvalue(x1.rvector_value + x2.rvector_value); + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return colvarvalue(x1.rvector_value + x2.rvector_value, + colvarvalue::type_unit3vector); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return colvarvalue(x1.quaternion_value + x2.quaternion_value); + case colvarvalue::type_vector: + return colvarvalue(x1.vector1d_value + x2.vector1d_value, colvarvalue::type_vector); + case colvarvalue::type_notset: + default: + x1.undef_op(); + return colvarvalue(colvarvalue::type_notset); + }; +} + + +colvarvalue operator - (colvarvalue const &x1, + colvarvalue const &x2) +{ + colvarvalue::check_types(x1, x2); + + switch (x1.value_type) { + case colvarvalue::type_scalar: + return colvarvalue(x1.real_value - x2.real_value); + case colvarvalue::type_3vector: + return colvarvalue(x1.rvector_value - x2.rvector_value); + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return colvarvalue(x1.rvector_value - x2.rvector_value, + colvarvalue::type_unit3vector); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return colvarvalue(x1.quaternion_value - x2.quaternion_value); + case colvarvalue::type_vector: + return colvarvalue(x1.vector1d_value - x2.vector1d_value, colvarvalue::type_vector); + case colvarvalue::type_notset: + default: + x1.undef_op(); + return colvarvalue(colvarvalue::type_notset); + }; +} + + +// binary operations with real numbers + +colvarvalue operator * (cvm::real const &a, + colvarvalue const &x) +{ + switch (x.value_type) { + case colvarvalue::type_scalar: + return colvarvalue(a * x.real_value); + case colvarvalue::type_3vector: + return colvarvalue(a * x.rvector_value); + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return colvarvalue(a * x.rvector_value, + colvarvalue::type_unit3vector); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return colvarvalue(a * x.quaternion_value); + case colvarvalue::type_vector: + return colvarvalue(x.vector1d_value * a, colvarvalue::type_vector); + case colvarvalue::type_notset: + default: + x.undef_op(); + return colvarvalue(colvarvalue::type_notset); + } +} + + +colvarvalue operator * (colvarvalue const &x, + cvm::real const &a) +{ + return a * x; +} + + +colvarvalue operator / (colvarvalue const &x, + cvm::real const &a) +{ + switch (x.value_type) { + case colvarvalue::type_scalar: + return colvarvalue(x.real_value / a); + case colvarvalue::type_3vector: + return colvarvalue(x.rvector_value / a); + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return colvarvalue(x.rvector_value / a, + colvarvalue::type_unit3vector); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return colvarvalue(x.quaternion_value / a); + case colvarvalue::type_vector: + return colvarvalue(x.vector1d_value / a, colvarvalue::type_vector); + case colvarvalue::type_notset: + default: + x.undef_op(); + return colvarvalue(colvarvalue::type_notset); + } +} + + +// inner product between two colvarvalues + +cvm::real operator * (colvarvalue const &x1, + colvarvalue const &x2) +{ + colvarvalue::check_types(x1, x2); + + switch (x1.value_type) { + case colvarvalue::type_scalar: + return (x1.real_value * x2.real_value); + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return (x1.rvector_value * x2.rvector_value); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + // the "*" product is the quaternion product, here the inner + // member function is used instead + return (x1.quaternion_value.inner(x2.quaternion_value)); + case colvarvalue::type_vector: + return (x1.vector1d_value * x2.vector1d_value); + case colvarvalue::type_notset: + default: + x1.undef_op(); + return 0.0; + }; +} + + +colvarvalue colvarvalue::dist2_grad(colvarvalue const &x2) const +{ + colvarvalue::check_types(*this, x2); + + switch (this->value_type) { + case colvarvalue::type_scalar: + return 2.0 * (this->real_value - x2.real_value); + case colvarvalue::type_3vector: + return 2.0 * (this->rvector_value - x2.rvector_value); + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + { + cvm::rvector const &v1 = this->rvector_value; + cvm::rvector const &v2 = x2.rvector_value; + cvm::real const cos_t = v1 * v2; + return colvarvalue(2.0 * (cos_t * v1 - v2), colvarvalue::type_unit3vectorderiv); + } + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return this->quaternion_value.dist2_grad(x2.quaternion_value); + case colvarvalue::type_vector: + return colvarvalue(2.0 * (this->vector1d_value - x2.vector1d_value), colvarvalue::type_vector); + break; + case colvarvalue::type_notset: + default: + this->undef_op(); + return colvarvalue(colvarvalue::type_notset); + }; +} + + +/// Return the midpoint between x1 and x2, optionally weighted by lambda +/// (which must be between 0.0 and 1.0) +colvarvalue const colvarvalue::interpolate(colvarvalue const &x1, + colvarvalue const &x2, + cvm::real const lambda) +{ + colvarvalue::check_types(x1, x2); + + if ((lambda < 0.0) || (lambda > 1.0)) { + cvm::error("Error: trying to interpolate between two colvarvalues with a " + "lamdba outside [0:1].\n", COLVARS_BUG_ERROR); + } + + colvarvalue interp = ((1.0-lambda)*x1 + lambda*x2); + cvm::real const d2 = x1.dist2(x2); + + switch (x1.type()) { + case colvarvalue::type_scalar: + case colvarvalue::type_3vector: + case colvarvalue::type_vector: + case colvarvalue::type_unit3vectorderiv: + case colvarvalue::type_quaternionderiv: + return interp; + break; + case colvarvalue::type_unit3vector: + case colvarvalue::type_quaternion: + if (interp.norm()/cvm::sqrt(d2) < 1.0e-6) { + cvm::error("Error: interpolation between "+cvm::to_str(x1)+" and "+ + cvm::to_str(x2)+" with lambda = "+cvm::to_str(lambda)+ + " is undefined: result = "+cvm::to_str(interp)+"\n", + COLVARS_INPUT_ERROR); + } + interp.apply_constraints(); + return interp; + break; + case colvarvalue::type_notset: + default: + x1.undef_op(); + break; + } + return colvarvalue(colvarvalue::type_notset); +} + + +std::string colvarvalue::to_simple_string() const +{ + switch (type()) { + case colvarvalue::type_scalar: + return cvm::to_str(real_value, 0, cvm::cv_prec); + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return rvector_value.to_simple_string(); + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return quaternion_value.to_simple_string(); + break; + case colvarvalue::type_vector: + return vector1d_value.to_simple_string(); + break; + case colvarvalue::type_notset: + default: + undef_op(); + break; + } + return std::string(); +} + + +int colvarvalue::from_simple_string(std::string const &s) +{ + switch (type()) { + case colvarvalue::type_scalar: + return ((std::istringstream(s) >> real_value) + ? COLVARS_OK : COLVARS_ERROR); + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return rvector_value.from_simple_string(s); + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return quaternion_value.from_simple_string(s); + break; + case colvarvalue::type_vector: + return vector1d_value.from_simple_string(s); + break; + case colvarvalue::type_notset: + default: + undef_op(); + break; + } + return COLVARS_ERROR; +} + + +template void colvarvalue::write_to_stream_template_(OST &os) const +{ + switch (type()) { + case colvarvalue::type_scalar: + os << real_value; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + os << rvector_value; + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + os << quaternion_value; + break; + case colvarvalue::type_vector: + os << vector1d_value; + break; + case colvarvalue::type_notset: + default: + os << "not set"; + break; + } +} + + +std::ostream & operator << (std::ostream &os, colvarvalue const &x) +{ + x.write_to_stream_template_(os); + return os; +} + + +cvm::memory_stream & operator << (cvm::memory_stream &os, colvarvalue const &x) +{ + x.write_to_stream_template_(os); + return os; +} + + +std::ostream & operator << (std::ostream &os, std::vector const &v) +{ + size_t i; + for (i = 0; i < v.size(); i++) { + os << v[i]; + } + return os; +} + + +template void colvarvalue::read_from_stream_template_(IST &is) +{ + if (type() == colvarvalue::type_notset) { + cvm::error("Trying to read from a stream a colvarvalue, " + "which has not yet been assigned a data type.\n"); + } + + switch (type()) { + case colvarvalue::type_scalar: + is >> real_value; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vectorderiv: + is >> rvector_value; + break; + case colvarvalue::type_unit3vector: + is >> rvector_value; + apply_constraints(); + break; + case colvarvalue::type_quaternion: + is >> quaternion_value; + apply_constraints(); + break; + case colvarvalue::type_quaternionderiv: + is >> quaternion_value; + break; + case colvarvalue::type_vector: + is >> vector1d_value; + break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + + +std::istream & operator >> (std::istream &is, colvarvalue &x) +{ + x.read_from_stream_template_(is); + return is; +} + + +cvm::memory_stream & operator >> (cvm::memory_stream &is, colvarvalue &x) +{ + x.read_from_stream_template_(is); + return is; +} + +size_t colvarvalue::output_width(size_t const &real_width) const +{ + switch (this->value_type) { + case colvarvalue::type_scalar: + return real_width; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return cvm::rvector::output_width(real_width); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return cvm::quaternion::output_width(real_width); + case colvarvalue::type_vector: + // note how this depends on the vector's size + return vector1d_value.output_width(real_width); + case colvarvalue::type_notset: + default: + return 0; + } +} + + +void colvarvalue::inner_opt(colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &result) +{ + // doing type check only once, here + colvarvalue::check_types(x, *xv); + + std::vector::iterator &xvi = xv; + std::vector::iterator &ii = result; + + switch (x.value_type) { + case colvarvalue::type_scalar: + while (xvi != xv_end) { + *(ii++) += (xvi++)->real_value * x.real_value; + } + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + while (xvi != xv_end) { + *(ii++) += (xvi++)->rvector_value * x.rvector_value; + } + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + while (xvi != xv_end) { + *(ii++) += ((xvi++)->quaternion_value).cosine(x.quaternion_value); + } + break; + case colvarvalue::type_vector: + while (xvi != xv_end) { + *(ii++) += (xvi++)->vector1d_value * x.vector1d_value; + } + break; + default: + x.undef_op(); + }; +} + + +void colvarvalue::inner_opt(colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &result) +{ + // doing type check only once, here + colvarvalue::check_types(x, *xv); + + std::list::iterator &xvi = xv; + std::vector::iterator &ii = result; + + switch (x.value_type) { + case colvarvalue::type_scalar: + while (xvi != xv_end) { + *(ii++) += (xvi++)->real_value * x.real_value; + } + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + while (xvi != xv_end) { + *(ii++) += (xvi++)->rvector_value * x.rvector_value; + } + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + while (xvi != xv_end) { + *(ii++) += ((xvi++)->quaternion_value).cosine(x.quaternion_value); + } + break; + case colvarvalue::type_vector: + while (xvi != xv_end) { + *(ii++) += (xvi++)->vector1d_value * x.vector1d_value; + } + break; + default: + x.undef_op(); + }; +} + + +void colvarvalue::p2leg_opt(colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &result) +{ + // doing type check only once, here + colvarvalue::check_types(x, *xv); + + std::vector::iterator &xvi = xv; + std::vector::iterator &ii = result; + + switch (x.value_type) { + case colvarvalue::type_scalar: + cvm::error("Error: cannot calculate Legendre polynomials " + "for scalar variables.\n"); + return; + break; + case colvarvalue::type_3vector: + while (xvi != xv_end) { + cvm::real const cosine = + ((xvi)->rvector_value * x.rvector_value) / + ((xvi)->rvector_value.norm() * x.rvector_value.norm()); + xvi++; + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + while (xvi != xv_end) { + cvm::real const cosine = (xvi++)->rvector_value * x.rvector_value; + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + while (xvi != xv_end) { + cvm::real const cosine = (xvi++)->quaternion_value.cosine(x.quaternion_value); + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + case colvarvalue::type_vector: + while (xvi != xv_end) { + cvm::real const cosine = + ((xvi)->vector1d_value * x.vector1d_value) / + ((xvi)->vector1d_value.norm() * x.rvector_value.norm()); + xvi++; + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + default: + x.undef_op(); + }; +} + + +void colvarvalue::p2leg_opt(colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &result) +{ + // doing type check only once, here + colvarvalue::check_types(x, *xv); + + std::list::iterator &xvi = xv; + std::vector::iterator &ii = result; + + switch (x.value_type) { + case colvarvalue::type_scalar: + cvm::error("Error: cannot calculate Legendre polynomials " + "for scalar variables.\n"); + break; + case colvarvalue::type_3vector: + while (xvi != xv_end) { + cvm::real const cosine = + ((xvi)->rvector_value * x.rvector_value) / + ((xvi)->rvector_value.norm() * x.rvector_value.norm()); + xvi++; + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + while (xvi != xv_end) { + cvm::real const cosine = (xvi++)->rvector_value * x.rvector_value; + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + while (xvi != xv_end) { + cvm::real const cosine = (xvi++)->quaternion_value.cosine(x.quaternion_value); + *(ii++) += 1.5*cosine*cosine - 0.5; + } + break; + default: + x.undef_op(); + }; +} + + + diff --git a/src/external/colvars/colvarvalue.h b/src/external/colvars/colvarvalue.h new file mode 100644 index 00000000000..5670906cd80 --- /dev/null +++ b/src/external/colvars/colvarvalue.h @@ -0,0 +1,755 @@ +// -*- c++ -*- + +// This file is part of the Collective Variables module (Colvars). +// The original version of Colvars and its updates are located at: +// https://github.com/Colvars/colvars +// Please update all Colvars source files before making any changes. +// If you wish to distribute your changes, please submit them to the +// Colvars repository at GitHub. + +#ifndef COLVARVALUE_H +#define COLVARVALUE_H + +#include "colvarmodule.h" +#include "colvartypes.h" + + +/// \brief Value of a collective variable: this is a metatype which +/// can be set at runtime. By default it is set to be a scalar +/// number, and can be treated as such in all operations (this is +/// done by most \link colvar::cvc \endlink implementations). +/// +/// \link colvarvalue \endlink allows \link colvar \endlink to be +/// treat different data types. By default, a \link colvarvalue +/// \endlink variable is a scalar number. To use it as +/// another type, declare and initialize it as +/// `colvarvalue x(colvarvalue::type_xxx)`, use `x.type (colvarvalue::type_xxx)` +/// at a later stage, or if unset, +/// assign the type with `x = y;`, provided y is correctly set. +/// +/// All operators (either unary or binary) on a \link +/// colvarvalue \endlink object performs one or more checks on the +/// \link Type \endlink, except when reading from a stream, when there is no way to +/// detect the \link Type \endlink. To use `is >> x;` x \b MUST +/// already have a type correcly set up for properly parsing the +/// stream. No problem of course with the output streams: `os << x;` +/// +/// \em Note \em on \em performance: to avoid type checks in a long array of \link +/// colvarvalue \endlink objects, use one of the existing "_opt" functions or implement a new one + + +class colvarvalue { + +public: + + /// \brief Possible types of value + /// + /// These three cover most possibilities of data type one can + /// devise. If you need to implement a new colvar with a very + /// complex data type, it's better to put an allocatable class here + enum Type { + /// Undefined type + type_notset, + /// Scalar number, implemented as \link colvarmodule::real \endlink (default) + type_scalar, + /// 3-dimensional vector, implemented as \link colvarmodule::rvector \endlink + type_3vector, + /// 3-dimensional unit vector, implemented as \link colvarmodule::rvector \endlink + type_unit3vector, + /// 3-dimensional vector that is a derivative of a unitvector + type_unit3vectorderiv, + /// 4-dimensional unit vector representing a rotation, implemented as \link colvarmodule::quaternion \endlink + type_quaternion, + /// 4-dimensional vector that is a derivative of a quaternion + type_quaternionderiv, + /// vector (arbitrary dimension) + type_vector, + /// Needed to iterate through enum + type_all + }; + + /// Current type of this colvarvalue object + Type value_type; + + /// \brief Real data member + cvm::real real_value; + + /// \brief 3-dimensional vector data member + cvm::rvector rvector_value; + + /// \brief Quaternion data member + cvm::quaternion quaternion_value; + + /// \brief Generic vector data member + cvm::vector1d vector1d_value; + + /// \brief If \link vector1d_value \endlink is a concatenation of colvarvalues, + /// keep track of the individual types + std::vector elem_types; + + /// \brief If \link vector1d_value \endlink is a concatenation of colvarvalues, + /// these mark the initial components of each colvarvalue + std::vector elem_indices; + + /// \brief If \link vector1d_value \endlink is a concatenation of colvarvalues, + /// these mark how many components for each colvarvalue + std::vector elem_sizes; + + /// \brief Whether or not the type check is enforced + static inline bool type_checking() + { + return true; + } + + /// Runtime description of value types + static std::string const type_desc(Type t); + + /// User keywords for specifying value types in the configuration + static std::string const type_keyword(Type t); + + /// Number of degrees of freedom for each supported type + static size_t num_df(Type t); + + /// Number of dimensions for each supported type (used to allocate vector1d_value) + static size_t num_dimensions(Type t); + + /// Number of dimensions of this variable + size_t size() const; + + /// \brief Default constructor: this class defaults to a scalar + /// number and always behaves like it unless you change its type + colvarvalue(); + + /// Constructor from a type flag (note: type_vector also needs the vector length to be set) + /// \param[in] vti Value of the \link Type \endlink enum + colvarvalue(Type const &vti); + + /// Copy constructor from real base type + colvarvalue(cvm::real const &x); + + /// \brief Copy constructor from rvector base type (Note: this sets + /// by default a type \link type_3vector \endlink , if you want a + /// \link type_unit3vector \endlink you must set it explicitly) + colvarvalue(cvm::rvector const &v, Type vti = type_3vector); + + /// \brief Copy constructor from quaternion base type + colvarvalue(cvm::quaternion const &q, Type vti = type_quaternion); + + /// Copy constructor from vector1d base type + colvarvalue(cvm::vector1d const &v, Type vti = type_vector); + + /// Copy constructor from another \link colvarvalue \endlink + colvarvalue(colvarvalue const &x); + + + /// Set to the null value for the data type currently defined + void reset(); + + /// \brief If the variable has constraints (e.g. unitvector or + /// quaternion), transform it to satisfy them; this function needs + /// to be called only when the \link colvarvalue \endlink + /// is calculated outside of \link colvar::cvc \endlink objects + void apply_constraints(); + + /// Get the current type + inline Type type() const + { + return value_type; + } + + /// Set the type explicitly + void type(Type const &vti); + + /// Set the type after another \link colvarvalue \endlink + void type(colvarvalue const &x); + + /// Make the type a derivative of the original type + /// (so that its constraints do not apply) + void is_derivative(); + + /// Square norm of this colvarvalue + cvm::real norm2() const; + + /// Norm of this colvarvalue + inline cvm::real norm() const + { + return cvm::sqrt(this->norm2()); + } + + /// Sum of the components of this colvarvalue (if more than one dimension) + cvm::real sum() const; + + /// Return a colvarvalue object of the same type and all components set to 1 + colvarvalue ones() const; + + /// Square distance between this \link colvarvalue \endlink and another + cvm::real dist2(colvarvalue const &x2) const; + + /// Derivative with respect to this \link colvarvalue \endlink of the square distance + colvarvalue dist2_grad(colvarvalue const &x2) const; + + /// Return the midpoint between x1 and x2, optionally weighted by lambda + /// (which must be between 0.0 and 1.0) + static colvarvalue const interpolate(colvarvalue const &x1, + colvarvalue const &x2, + cvm::real const lambda = 0.5); + + /// Assignment operator (type of x is checked) + colvarvalue & operator = (colvarvalue const &x); + + void operator += (colvarvalue const &x); + void operator -= (colvarvalue const &x); + void operator *= (cvm::real const &a); + void operator /= (cvm::real const &a); + + // Binary operators (return values) + friend colvarvalue operator + (colvarvalue const &x1, colvarvalue const &x2); + friend colvarvalue operator - (colvarvalue const &x1, colvarvalue const &x2); + friend colvarvalue operator * (colvarvalue const &x, cvm::real const &a); + friend colvarvalue operator * (cvm::real const &a, colvarvalue const &x); + friend colvarvalue operator / (colvarvalue const &x, cvm::real const &a); + + /// Inner product + friend cvm::real operator * (colvarvalue const &x1, colvarvalue const &x2); + + // Cast to scalar + inline operator cvm::real() const + { + if (value_type != type_scalar) { + cvm::error("Error: trying to use a variable of type \""+ + type_desc(value_type)+"\" as one of type \""+ + type_desc(type_scalar)+"\".\n"); + } + return real_value; + } + + // Cast to 3-vector + inline operator cvm::rvector() const + { + if ((value_type != type_3vector) && + (value_type != type_unit3vector) && + (value_type != type_unit3vectorderiv)) { + cvm::error("Error: trying to use a variable of type \""+ + type_desc(value_type)+"\" as one of type \""+ + type_desc(type_3vector)+"\".\n"); + } + return rvector_value; + } + + // Cast to quaternion + inline operator cvm::quaternion() const + { + if ((value_type != type_quaternion) && + (value_type != type_quaternionderiv)) { + cvm::error("Error: trying to use a variable of type \""+ + type_desc(value_type)+"\" as one of type \""+ + type_desc(type_quaternion)+"\".\n"); + } + return quaternion_value; + } + + // Create a n-dimensional vector from one of the basic types, or return the existing vector + cvm::vector1d const as_vector() const; + + + /// Whether this variable is a real number + inline bool is_scalar() const + { + return (value_type == type_scalar); + } + + + /// Add an element to the vector (requires that type_vector is already set). + /// This is only needed to use this object as a vector of "complex" colvar values. + /// To use it instead as a plain n-dimensional vector, access vector1d_value directly. + void add_elem(colvarvalue const &x); + + /// Get a single colvarvalue out of elements of the vector + colvarvalue const get_elem(int const i_begin, int const i_end, Type const vt) const; + + /// Get a single colvarvalue out of elements of the vector + colvarvalue const get_elem(int const icv) const; + + /// Set elements of the vector from a single colvarvalue (uses the rank of x + /// to compute the length) + void set_elem(int const icv, colvarvalue const &x); + + /// Set elements of the vector from a single colvarvalue + void set_elem(int const i_begin, int const i_end, colvarvalue const &x); + + /// Make each element a random number in N(0,1) + void set_random(); + + /// Make each element equal to the given argument + void set_ones(cvm::real assigned_value = 1.0); + + /// Get a scalar number out of an element of the vector + cvm::real operator [] (int const i) const; + + /// Use an element of the vector as a scalar number + cvm::real & operator [] (int const i); + + /// Ensure that the two types are the same within a binary operator + static int check_types(colvarvalue const &x1, colvarvalue const &x2); + + /// Ensure that the two types are the same within an assignment, or that the left side is type_notset + static int check_types_assign(Type const &vt1, Type const &vt2); + + /// Undefined operation + void undef_op() const; + +private: + + /// Generic stream writing function (formatted and not) + template void write_to_stream_template_(OST &os) const; + +public: + + /// Formatted output operator + friend std::ostream & operator << (std::ostream &os, colvarvalue const &x); + + /// Unformatted output operator + friend cvm::memory_stream & operator << (cvm::memory_stream &os, colvarvalue const &x); + +private: + + /// Generic stream reading function (formatted and not) + template void read_from_stream_template_(IST &is); + +public: + + /// Formatted input operator + friend std::istream & operator >> (std::istream &is, colvarvalue &x); + + /// Unformatted input operator + friend cvm::memory_stream & operator >> (cvm::memory_stream &is, colvarvalue &x); + + /// Give the number of characters required to output this + /// colvarvalue, given the current type assigned and the number of + /// characters for a real number + size_t output_width(size_t const &real_width) const; + + /// Formats value as a script-friendly string (space separated list) + std::string to_simple_string() const; + + /// Parses value from a script-friendly string (space separated list) + int from_simple_string(std::string const &s); + + + // optimized routines for operations on arrays of colvar values; + // xv and result are assumed to have the same number of elements + + /// \brief Optimized routine for the inner product of one collective + /// variable with an array + static void inner_opt(colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &result); + + /// \brief Optimized routine for the inner product of one collective + /// variable with an array + static void inner_opt(colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &result); + + /// \brief Optimized routine for the second order Legendre + /// polynomial, (3cos^2(w)-1)/2, of one collective variable with an + /// array + static void p2leg_opt(colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &result); + + /// \brief Optimized routine for the second order Legendre + /// polynomial of one collective variable with an array + static void p2leg_opt(colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &result); + +}; + + +inline size_t colvarvalue::size() const +{ + switch (value_type) { + case colvarvalue::type_notset: + default: + return 0; break; + case colvarvalue::type_scalar: + return 1; break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return 3; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return 4; break; + case colvarvalue::type_vector: + return vector1d_value.size(); break; + } +} + + +inline cvm::real colvarvalue::operator [] (int const i) const +{ + switch (value_type) { + case colvarvalue::type_notset: + default: + cvm::error("Error: trying to access a colvar value " + "that is not initialized.\n", COLVARS_BUG_ERROR); + return 0.0; break; + case colvarvalue::type_scalar: + return real_value; break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return rvector_value[i]; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return quaternion_value[i]; break; + case colvarvalue::type_vector: + return vector1d_value[i]; break; + } +} + + +inline cvm::real & colvarvalue::operator [] (int const i) +{ + switch (value_type) { + case colvarvalue::type_notset: + default: + cvm::error("Error: trying to access a colvar value " + "that is not initialized.\n", COLVARS_BUG_ERROR); + return real_value; break; + case colvarvalue::type_scalar: + return real_value; break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return rvector_value[i]; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return quaternion_value[i]; break; + case colvarvalue::type_vector: + return vector1d_value[i]; break; + } +} + + +inline int colvarvalue::check_types(colvarvalue const &x1, + colvarvalue const &x2) +{ + if (!colvarvalue::type_checking()) { + return COLVARS_OK; + } + + if (x1.type() != x2.type()) { + if (((x1.type() == type_unit3vector) && + (x2.type() == type_unit3vectorderiv)) || + ((x2.type() == type_unit3vector) && + (x1.type() == type_unit3vectorderiv)) || + ((x1.type() == type_quaternion) && + (x2.type() == type_quaternionderiv)) || + ((x2.type() == type_quaternion) && + (x1.type() == type_quaternionderiv))) { + return COLVARS_OK; + } else { + cvm::error("Trying to perform an operation between two colvar " + "values with different types, \""+ + colvarvalue::type_desc(x1.type())+ + "\" and \""+ + colvarvalue::type_desc(x2.type())+ + "\".\n"); + return COLVARS_ERROR; + } + } + + if (x1.type() == type_vector) { + if (x1.vector1d_value.size() != x2.vector1d_value.size()) { + cvm::error("Trying to perform an operation between two vector colvar " + "values with different sizes, "+ + cvm::to_str(x1.vector1d_value.size())+ + " and "+ + cvm::to_str(x2.vector1d_value.size())+ + ".\n"); + return COLVARS_ERROR; + } + } + return COLVARS_OK; +} + + +inline int colvarvalue::check_types_assign(colvarvalue::Type const &vt1, + colvarvalue::Type const &vt2) +{ + if (!colvarvalue::type_checking()) { + return COLVARS_OK; + } + + if (vt1 != type_notset) { + if (((vt1 == type_unit3vector) && + (vt2 == type_unit3vectorderiv)) || + ((vt2 == type_unit3vector) && + (vt1 == type_unit3vectorderiv)) || + ((vt1 == type_quaternion) && + (vt2 == type_quaternionderiv)) || + ((vt2 == type_quaternion) && + (vt1 == type_quaternionderiv))) { + return COLVARS_OK; + } else { + if (vt1 != vt2) { + cvm::error("Trying to assign a colvar value with type \""+ + type_desc(vt2)+"\" to one with type \""+ + type_desc(vt1)+"\".\n"); + return COLVARS_ERROR; + } + } + } + return COLVARS_OK; +} + + +inline colvarvalue & colvarvalue::operator = (colvarvalue const &x) +{ + check_types_assign(this->type(), x.type()); + value_type = x.type(); + + switch (this->type()) { + case colvarvalue::type_scalar: + this->real_value = x.real_value; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + this->rvector_value = x.rvector_value; + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + this->quaternion_value = x.quaternion_value; + break; + case colvarvalue::type_vector: + vector1d_value = x.vector1d_value; + elem_types = x.elem_types; + elem_indices = x.elem_indices; + elem_sizes = x.elem_sizes; + break; + case colvarvalue::type_notset: + default: + undef_op(); + break; + } + return *this; +} + + +inline void colvarvalue::operator += (colvarvalue const &x) +{ + colvarvalue::check_types(*this, x); + + switch (this->type()) { + case colvarvalue::type_scalar: + this->real_value += x.real_value; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + this->rvector_value += x.rvector_value; + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + this->quaternion_value += x.quaternion_value; + break; + case colvarvalue::type_vector: + this->vector1d_value += x.vector1d_value; + break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + + +inline void colvarvalue::operator -= (colvarvalue const &x) +{ + colvarvalue::check_types(*this, x); + + switch (value_type) { + case colvarvalue::type_scalar: + real_value -= x.real_value; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + rvector_value -= x.rvector_value; + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + quaternion_value -= x.quaternion_value; + break; + case colvarvalue::type_vector: + this->vector1d_value -= x.vector1d_value; + break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + + +inline void colvarvalue::operator *= (cvm::real const &a) +{ + switch (value_type) { + case colvarvalue::type_scalar: + real_value *= a; + break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vectorderiv: + rvector_value *= a; + break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + quaternion_value *= a; + break; + case colvarvalue::type_vector: + this->vector1d_value *= a; + break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + + +inline void colvarvalue::operator /= (cvm::real const &a) +{ + switch (value_type) { + case colvarvalue::type_scalar: + real_value /= a; break; + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + rvector_value /= a; break; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + quaternion_value /= a; break; + case colvarvalue::type_vector: + this->vector1d_value /= a; + break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + + +inline cvm::vector1d const colvarvalue::as_vector() const +{ + switch (value_type) { + case colvarvalue::type_scalar: + { + cvm::vector1d v(1); + v[0] = real_value; + return v; + } + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return rvector_value.as_vector(); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return quaternion_value.as_vector(); + case colvarvalue::type_vector: + return vector1d_value; + case colvarvalue::type_notset: + default: + return cvm::vector1d(0); + } +} + + +inline cvm::real colvarvalue::norm2() const +{ + switch (value_type) { + case colvarvalue::type_scalar: + return (this->real_value)*(this->real_value); + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return (this->rvector_value).norm2(); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return (this->quaternion_value).norm2(); + case colvarvalue::type_vector: + if (elem_types.size() > 0) { + // if we have information about non-scalar types, use it + cvm::real result = 0.0; + size_t i; + for (i = 0; i < elem_types.size(); i++) { + result += (this->get_elem(i)).norm2(); + } + return result; + } else { + return vector1d_value.norm2(); + } + break; + case colvarvalue::type_notset: + default: + return 0.0; + } +} + + +inline cvm::real colvarvalue::sum() const +{ + switch (value_type) { + case colvarvalue::type_scalar: + return (this->real_value); + case colvarvalue::type_3vector: + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + return (this->rvector_value).x + (this->rvector_value).y + + (this->rvector_value).z; + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + return (this->quaternion_value).q0 + (this->quaternion_value).q1 + + (this->quaternion_value).q2 + (this->quaternion_value).q3; + case colvarvalue::type_vector: + return (this->vector1d_value).sum(); + case colvarvalue::type_notset: + default: + return 0.0; + } +} + + +inline cvm::real colvarvalue::dist2(colvarvalue const &x2) const +{ + colvarvalue::check_types(*this, x2); + + switch (this->type()) { + case colvarvalue::type_scalar: + return (this->real_value - x2.real_value)*(this->real_value - x2.real_value); + case colvarvalue::type_3vector: + return (this->rvector_value - x2.rvector_value).norm2(); + case colvarvalue::type_unit3vector: + case colvarvalue::type_unit3vectorderiv: + // angle between (*this) and x2 is the distance + return cvm::acos(this->rvector_value * x2.rvector_value) * cvm::acos(this->rvector_value * x2.rvector_value); + case colvarvalue::type_quaternion: + case colvarvalue::type_quaternionderiv: + // angle between (*this) and x2 is the distance, the quaternion + // object has it implemented internally + return this->quaternion_value.dist2(x2.quaternion_value); + case colvarvalue::type_vector: + return (this->vector1d_value - x2.vector1d_value).norm2(); + case colvarvalue::type_notset: + default: + this->undef_op(); + return 0.0; + }; +} + + +#endif diff --git a/src/external/colvars/nr_jacobi.cpp b/src/external/colvars/nr_jacobi.cpp new file mode 100644 index 00000000000..51c24e86cf6 --- /dev/null +++ b/src/external/colvars/nr_jacobi.cpp @@ -0,0 +1,139 @@ +// -*- c++ -*- + +#include "colvartypes.h" +#include "nr_jacobi.h" + + +#define ROTATE(a,i,j,k,l) g=a[i][j]; \ + h=a[k][l]; \ + a[i][j]=g-s*(h+g*tau); \ + a[k][l]=h+s*(g-h*tau); + +#define n 4 + + +namespace NR_Jacobi { + +int jacobi(cvm::real a[4][4], cvm::real d[4], cvm::real v[4][4], int *nrot) +{ + int j,iq,ip,i; + cvm::real tresh,theta,tau,t,sm,s,h,g,c; + + cvm::real b[n]; + cvm::real z[n]; + + for (ip=0;ip 4 && (cvm::real)(cvm::fabs(d[ip])+g) == (cvm::real)cvm::fabs(d[ip]) + && (cvm::real)(cvm::fabs(d[iq])+g) == (cvm::real)cvm::fabs(d[iq])) + a[ip][iq]=0.0; + else if (cvm::fabs(a[ip][iq]) > tresh) { + h=d[iq]-d[ip]; + if ((cvm::real)(cvm::fabs(h)+g) == (cvm::real)cvm::fabs(h)) + t=(a[ip][iq])/h; + else { + theta=0.5*h/(a[ip][iq]); + t=1.0/(cvm::fabs(theta)+cvm::sqrt(1.0+theta*theta)); + if (theta < 0.0) t = -t; + } + c=1.0/cvm::sqrt(1+t*t); + s=t*c; + tau=s/(1.0+c); + h=t*a[ip][iq]; + z[ip] -= h; + z[iq] += h; + d[ip] -= h; + d[iq] += h; + a[ip][iq]=0.0; + for (j=0;j<=ip-1;j++) { + ROTATE(a,j,ip,j,iq) + } + for (j=ip+1;j<=iq-1;j++) { + ROTATE(a,ip,j,j,iq) + } + for (j=iq+1;j= p) p=d[k=j]; + if (k != i) { + d[k]=d[i]; + d[i]=p; + for (j=0;j +#include +#include +#include +#include +#ifdef LEPTON_USE_JIT +#if defined(__ARM__) || defined(__ARM64__) +#include "asmjit/a64.h" +#else +#include "asmjit/x86.h" +#endif +#endif + +namespace Lepton { + +class Operation; +class ParsedExpression; + +/** + * A CompiledExpression is a highly optimized representation of an expression for cases when you want to evaluate + * it many times as quickly as possible. You should treat it as an opaque object; none of the internal representation + * is visible. + * + * A CompiledExpression is created by calling createCompiledExpression() on a ParsedExpression. + * + * WARNING: CompiledExpression is NOT thread safe. You should never access a CompiledExpression from two threads at + * the same time. + */ + +class LEPTON_EXPORT CompiledExpression { +public: + CompiledExpression(); + CompiledExpression(const CompiledExpression& expression); + ~CompiledExpression(); + CompiledExpression& operator=(const CompiledExpression& expression); + /** + * Get the names of all variables used by this expression. + */ + const std::set& getVariables() const; + /** + * Get a reference to the memory location where the value of a particular variable is stored. This can be used + * to set the value of the variable before calling evaluate(). + */ + double& getVariableReference(const std::string& name); + /** + * You can optionally specify the memory locations from which the values of variables should be read. + * This is useful, for example, when several expressions all use the same variable. You can then set + * the value of that variable in one place, and it will be seen by all of them. + */ + void setVariableLocations(std::map& variableLocations); + /** + * Evaluate the expression. The values of all variables should have been set before calling this. + */ + double evaluate() const; +private: + friend class ParsedExpression; + CompiledExpression(const ParsedExpression& expression); + void compileExpression(const ExpressionTreeNode& node, std::vector >& temps); + int findTempIndex(const ExpressionTreeNode& node, std::vector >& temps); + std::map variablePointers; + std::vector > variablesToCopy; + std::vector > arguments; + std::vector target; + std::vector operation; + std::map variableIndices; + std::set variableNames; + mutable std::vector workspace; + mutable std::vector argValues; + std::map dummyVariables; + double (*jitCode)(); +#ifdef LEPTON_USE_JIT + void findPowerGroups(std::vector >& groups, std::vector >& groupPowers, std::vector& stepGroup); + void generateJitCode(); +#if defined(__ARM__) || defined(__ARM64__) + void generateSingleArgCall(asmjit::a64::Compiler& c, asmjit::arm::Vec& dest, asmjit::arm::Vec& arg, double (*function)(double)); + void generateTwoArgCall(asmjit::a64::Compiler& c, asmjit::arm::Vec& dest, asmjit::arm::Vec& arg1, asmjit::arm::Vec& arg2, double (*function)(double, double)); +#else + void generateSingleArgCall(asmjit::x86::Compiler& c, asmjit::x86::Xmm& dest, asmjit::x86::Xmm& arg, double (*function)(double)); + void generateTwoArgCall(asmjit::x86::Compiler& c, asmjit::x86::Xmm& dest, asmjit::x86::Xmm& arg1, asmjit::x86::Xmm& arg2, double (*function)(double, double)); +#endif + std::vector constants; + asmjit::JitRuntime runtime; +#endif +}; + +} // namespace Lepton + +#endif /*LEPTON_COMPILED_EXPRESSION_H_*/ diff --git a/src/external/lepton/include/lepton/CompiledVectorExpression.h b/src/external/lepton/include/lepton/CompiledVectorExpression.h new file mode 100644 index 00000000000..a9dd9367507 --- /dev/null +++ b/src/external/lepton/include/lepton/CompiledVectorExpression.h @@ -0,0 +1,145 @@ +#ifndef LEPTON_VECTOR_EXPRESSION_H_ +#define LEPTON_VECTOR_EXPRESSION_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2013-2022 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "ExpressionTreeNode.h" +#include "windowsIncludes.h" +#include +#include +#include +#include +#include +#include +#ifdef LEPTON_USE_JIT +#if defined(__ARM__) || defined(__ARM64__) +#include "asmjit/a64.h" +#else +#include "asmjit/x86.h" +#endif +#endif + +namespace Lepton { + +class Operation; +class ParsedExpression; + +/** + * A CompiledVectorExpression is a highly optimized representation of an expression for cases when you want to evaluate + * it many times as quickly as possible. It is similar to CompiledExpression, with the extra feature that it uses the CPU's + * vector unit (AVX on x86, NEON on ARM) to evaluate the expression for multiple sets of arguments at once. It also differs + * from CompiledExpression and ParsedExpression in using single precision rather than double precision to evaluate the expression. + * You should treat it as an opaque object; none of the internal representation is visible. + * + * A CompiledVectorExpression is created by calling createCompiledVectorExpression() on a ParsedExpression. When you create + * it, you must specify the width of the vectors on which to compute the expression. The allowed widths depend on the type of + * CPU it is running on. 4 is always allowed, and 8 is allowed on x86 processors with AVX. Call getAllowedWidths() to query + * the allowed values. + * + * WARNING: CompiledVectorExpression is NOT thread safe. You should never access a CompiledVectorExpression from two threads at + * the same time. + */ + +class LEPTON_EXPORT CompiledVectorExpression { +public: + CompiledVectorExpression(); + CompiledVectorExpression(const CompiledVectorExpression& expression); + ~CompiledVectorExpression(); + CompiledVectorExpression& operator=(const CompiledVectorExpression& expression); + /** + * Get the width of the vectors on which the expression is computed. + */ + int getWidth() const; + /** + * Get the names of all variables used by this expression. + */ + const std::set& getVariables() const; + /** + * Get a pointer to the memory location where the value of a particular variable is stored. This can be used + * to set the value of the variable before calling evaluate(). + * + * @param name the name of the variable to query + * @return a pointer to N floating point values, where N is the vector width + */ + float* getVariablePointer(const std::string& name); + /** + * You can optionally specify the memory locations from which the values of variables should be read. + * This is useful, for example, when several expressions all use the same variable. You can then set + * the value of that variable in one place, and it will be seen by all of them. The location should + * be a pointer to N floating point values, where N is the vector width. + */ + void setVariableLocations(std::map& variableLocations); + /** + * Evaluate the expression. The values of all variables should have been set before calling this. + * + * @return a pointer to N floating point values, where N is the vector width + */ + const float* evaluate() const; + /** + * Get the list of vector widths that are supported on the current processor. + */ + static const std::vector& getAllowedWidths(); +private: + friend class ParsedExpression; + CompiledVectorExpression(const ParsedExpression& expression, int width); + void compileExpression(const ExpressionTreeNode& node, std::vector >& temps, int& workspaceSize); + int findTempIndex(const ExpressionTreeNode& node, std::vector >& temps); + int width; + std::map variablePointers; + std::vector > variablesToCopy; + std::vector > arguments; + std::vector target; + std::vector operation; + std::map variableIndices; + std::set variableNames; + mutable std::vector workspace; + mutable std::vector argValues; + std::map dummyVariables; + void (*jitCode)(); +#ifdef LEPTON_USE_JIT + void findPowerGroups(std::vector >& groups, std::vector >& groupPowers, std::vector& stepGroup); + void generateJitCode(); +#if defined(__ARM__) || defined(__ARM64__) + void generateSingleArgCall(asmjit::a64::Compiler& c, asmjit::arm::Vec& dest, asmjit::arm::Vec& arg, float (*function)(float)); + void generateTwoArgCall(asmjit::a64::Compiler& c, asmjit::arm::Vec& dest, asmjit::arm::Vec& arg1, asmjit::arm::Vec& arg2, float (*function)(float, float)); +#else + void generateSingleArgCall(asmjit::x86::Compiler& c, asmjit::x86::Ymm& dest, asmjit::x86::Ymm& arg, float (*function)(float)); + void generateTwoArgCall(asmjit::x86::Compiler& c, asmjit::x86::Ymm& dest, asmjit::x86::Ymm& arg1, asmjit::x86::Ymm& arg2, float (*function)(float, float)); +#endif + std::vector constants; + asmjit::JitRuntime runtime; +#endif +}; + +} // namespace Lepton + +#endif /*LEPTON_VECTOR_EXPRESSION_H_*/ diff --git a/src/external/lepton/include/lepton/CustomFunction.h b/src/external/lepton/include/lepton/CustomFunction.h new file mode 100644 index 00000000000..7b6a2b68342 --- /dev/null +++ b/src/external/lepton/include/lepton/CustomFunction.h @@ -0,0 +1,109 @@ +#ifndef LEPTON_CUSTOM_FUNCTION_H_ +#define LEPTON_CUSTOM_FUNCTION_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "windowsIncludes.h" + +namespace Lepton { + +/** + * This class is the interface for defining your own function that may be included in expressions. + * To use it, create a concrete subclass that implements all of the virtual methods for each new function + * you want to define. Then when you call Parser::parse() to parse an expression, pass a map of + * function names to CustomFunction objects. + */ + +class LEPTON_EXPORT CustomFunction { +public: + virtual ~CustomFunction() { + } + /** + * Get the number of arguments this function expects. + */ + virtual int getNumArguments() const = 0; + /** + * Evaluate the function. + * + * @param arguments the array of argument values + */ + virtual double evaluate(const double* arguments) const = 0; + /** + * Evaluate a derivative of the function. + * + * @param arguments the array of argument values + * @param derivOrder an array specifying the number of times the function has been differentiated + * with respect to each of its arguments. For example, the array {0, 2} indicates + * a second derivative with respect to the second argument. + */ + virtual double evaluateDerivative(const double* arguments, const int* derivOrder) const = 0; + /** + * Create a new duplicate of this object on the heap using the "new" operator. + */ + virtual CustomFunction* clone() const = 0; +}; + +/** + * This class is an implementation of CustomFunction that does no computation. It just returns + * 0 for the value and derivatives. This is useful when using the parser to analyze expressions + * rather than to evaluate them. You can just create PlaceholderFunctions to represent any custom + * functions that may appear in expressions. + */ + +class LEPTON_EXPORT PlaceholderFunction : public CustomFunction { +public: + /** + * Create a Placeholder function. + * + * @param numArgs the number of arguments the function expects + */ + PlaceholderFunction(int numArgs) : numArgs(numArgs) { + } + int getNumArguments() const { + return numArgs; + } + double evaluate(const double* arguments) const { + return 0.0; + } + double evaluateDerivative(const double* arguments, const int* derivOrder) const { + return 0.0; + } + CustomFunction* clone() const { + return new PlaceholderFunction(numArgs); + }; +private: + int numArgs; +}; + +} // namespace Lepton + +#endif /*LEPTON_CUSTOM_FUNCTION_H_*/ diff --git a/src/external/lepton/include/lepton/Exception.h b/src/external/lepton/include/lepton/Exception.h new file mode 100644 index 00000000000..5ad55714d18 --- /dev/null +++ b/src/external/lepton/include/lepton/Exception.h @@ -0,0 +1,59 @@ +#ifndef LEPTON_EXCEPTION_H_ +#define LEPTON_EXCEPTION_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include +#include + +namespace Lepton { + +/** + * This class is used for all exceptions thrown by Lepton. + */ + +class Exception : public std::exception { +public: + Exception(const std::string& message) : message(message) { + } + ~Exception() throw() { + } + const char* what() const throw() { + return message.c_str(); + } +private: + std::string message; +}; + +} // namespace Lepton + +#endif /*LEPTON_EXCEPTION_H_*/ diff --git a/src/external/lepton/include/lepton/ExpressionProgram.h b/src/external/lepton/include/lepton/ExpressionProgram.h new file mode 100644 index 00000000000..e9899062886 --- /dev/null +++ b/src/external/lepton/include/lepton/ExpressionProgram.h @@ -0,0 +1,103 @@ +#ifndef LEPTON_EXPRESSION_PROGRAM_H_ +#define LEPTON_EXPRESSION_PROGRAM_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2018 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "ExpressionTreeNode.h" +#include "windowsIncludes.h" +#include +#include +#include + +namespace Lepton { + +class ParsedExpression; + +/** + * An ExpressionProgram is a linear sequence of Operations for evaluating an expression. The evaluation + * is done with a stack. The arguments to each Operation are first taken off the stack in order, then it is + * evaluated and the result is pushed back onto the stack. At the end, the stack contains a single value, + * which is the value of the expression. + * + * An ExpressionProgram is created by calling createProgram() on a ParsedExpression. + */ + +class LEPTON_EXPORT ExpressionProgram { +public: + ExpressionProgram(); + ExpressionProgram(const ExpressionProgram& program); + ~ExpressionProgram(); + ExpressionProgram& operator=(const ExpressionProgram& program); + /** + * Get the number of Operations that make up this program. + */ + int getNumOperations() const; + /** + * Get an Operation in this program. + */ + const Operation& getOperation(int index) const; + /** + * Change an Operation in this program. + * + * The Operation must have been allocated on the heap with the "new" operator. + * The ExpressionProgram assumes ownership of it and will delete it when it + * is no longer needed. + */ + void setOperation(int index, Operation* operation); + /** + * Get the size of the stack needed to execute this program. This is the largest number of elements present + * on the stack at any point during evaluation. + */ + int getStackSize() const; + /** + * Evaluate the expression. If the expression involves any variables, this method will throw an exception. + */ + double evaluate() const; + /** + * Evaluate the expression. + * + * @param variables a map specifying the values of all variables that appear in the expression. If any + * variable appears in the expression but is not included in this map, an exception + * will be thrown. + */ + double evaluate(const std::map& variables) const; +private: + friend class ParsedExpression; + ExpressionProgram(const ParsedExpression& expression); + void buildProgram(const ExpressionTreeNode& node); + std::vector operations; + int maxArgs, stackSize; +}; + +} // namespace Lepton + +#endif /*LEPTON_EXPRESSION_PROGRAM_H_*/ diff --git a/src/external/lepton/include/lepton/ExpressionTreeNode.h b/src/external/lepton/include/lepton/ExpressionTreeNode.h new file mode 100644 index 00000000000..dde26103cba --- /dev/null +++ b/src/external/lepton/include/lepton/ExpressionTreeNode.h @@ -0,0 +1,111 @@ +#ifndef LEPTON_EXPRESSION_TREE_NODE_H_ +#define LEPTON_EXPRESSION_TREE_NODE_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2021 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "windowsIncludes.h" +#include +#include + +namespace Lepton { + +class Operation; +class ParsedExpression; + +/** + * This class represents a node in the abstract syntax tree representation of an expression. + * Each node is defined by an Operation and a set of children. When the expression is + * evaluated, each child is first evaluated in order, then the resulting values are passed + * as the arguments to the Operation's evaluate() method. + */ + +class LEPTON_EXPORT ExpressionTreeNode { +public: + /** + * Create a new ExpressionTreeNode. + * + * @param operation the operation for this node. The ExpressionTreeNode takes over ownership + * of this object, and deletes it when the node is itself deleted. + * @param children the children of this node + */ + ExpressionTreeNode(Operation* operation, const std::vector& children); + /** + * Create a new ExpressionTreeNode with two children. + * + * @param operation the operation for this node. The ExpressionTreeNode takes over ownership + * of this object, and deletes it when the node is itself deleted. + * @param child1 the first child of this node + * @param child2 the second child of this node + */ + ExpressionTreeNode(Operation* operation, const ExpressionTreeNode& child1, const ExpressionTreeNode& child2); + /** + * Create a new ExpressionTreeNode with one child. + * + * @param operation the operation for this node. The ExpressionTreeNode takes over ownership + * of this object, and deletes it when the node is itself deleted. + * @param child the child of this node + */ + ExpressionTreeNode(Operation* operation, const ExpressionTreeNode& child); + /** + * Create a new ExpressionTreeNode with no children. + * + * @param operation the operation for this node. The ExpressionTreeNode takes over ownership + * of this object, and deletes it when the node is itself deleted. + */ + ExpressionTreeNode(Operation* operation); + ExpressionTreeNode(const ExpressionTreeNode& node); + ExpressionTreeNode(ExpressionTreeNode&& node); + ExpressionTreeNode(); + ~ExpressionTreeNode(); + bool operator==(const ExpressionTreeNode& node) const; + bool operator!=(const ExpressionTreeNode& node) const; + ExpressionTreeNode& operator=(const ExpressionTreeNode& node); + ExpressionTreeNode& operator=(ExpressionTreeNode&& node); + /** + * Get the Operation performed by this node. + */ + const Operation& getOperation() const; + /** + * Get this node's child nodes. + */ + const std::vector& getChildren() const; +private: + friend class ParsedExpression; + void assignTags(std::vector& examples) const; + Operation* operation; + std::vector children; + mutable int tag; +}; + +} // namespace Lepton + +#endif /*LEPTON_EXPRESSION_TREE_NODE_H_*/ diff --git a/src/external/lepton/include/lepton/Operation.h b/src/external/lepton/include/lepton/Operation.h new file mode 100644 index 00000000000..4b8969cd59e --- /dev/null +++ b/src/external/lepton/include/lepton/Operation.h @@ -0,0 +1,1193 @@ +#ifndef LEPTON_OPERATION_H_ +#define LEPTON_OPERATION_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2019 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "windowsIncludes.h" +#include "CustomFunction.h" +#include "Exception.h" +#include +#include +#include +#include +#include +#include + +namespace Lepton { + +class ExpressionTreeNode; + +/** + * An Operation represents a single step in the evaluation of an expression, such as a function, + * an operator, or a constant value. Each Operation takes some number of values as arguments + * and produces a single value. + * + * This is an abstract class with subclasses for specific operations. + */ + +class LEPTON_EXPORT Operation { +public: + virtual ~Operation() { + } + /** + * This enumeration lists all Operation subclasses. This is provided so that switch statements + * can be used when processing or analyzing parsed expressions. + */ + enum Id {CONSTANT, VARIABLE, CUSTOM, ADD, SUBTRACT, MULTIPLY, DIVIDE, POWER, NEGATE, SQRT, EXP, LOG, + SIN, COS, SEC, CSC, TAN, COT, ASIN, ACOS, ATAN, ATAN2, SINH, COSH, TANH, ERF, ERFC, STEP, DELTA, SQUARE, CUBE, RECIPROCAL, + ADD_CONSTANT, MULTIPLY_CONSTANT, POWER_CONSTANT, MIN, MAX, ABS, FLOOR, CEIL, SELECT}; + /** + * Get the name of this Operation. + */ + virtual std::string getName() const = 0; + /** + * Get this Operation's ID. + */ + virtual Id getId() const = 0; + /** + * Get the number of arguments this operation expects. + */ + virtual int getNumArguments() const = 0; + /** + * Create a clone of this Operation. + */ + virtual Operation* clone() const = 0; + /** + * Perform the computation represented by this operation. + * + * @param args the array of arguments + * @param variables a map containing the values of all variables + * @return the result of performing the computation. + */ + virtual double evaluate(double* args, const std::map& variables) const = 0; + /** + * Return an ExpressionTreeNode which represents the analytic derivative of this Operation with respect to a variable. + * + * @param children the child nodes + * @param childDerivs the derivatives of the child nodes with respect to the variable + * @param variable the variable with respect to which the derivate should be taken + */ + virtual ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const = 0; + /** + * Get whether this operation should be displayed with infix notation. + */ + virtual bool isInfixOperator() const { + return false; + } + /** + * Get whether this is a symmetric binary operation, such that exchanging its arguments + * does not affect the result. + */ + virtual bool isSymmetric() const { + return false; + } + virtual bool operator!=(const Operation& op) const { + return op.getId() != getId(); + } + virtual bool operator==(const Operation& op) const { + return !(*this != op); + } + class Constant; + class Variable; + class Custom; + class Add; + class Subtract; + class Multiply; + class Divide; + class Power; + class Negate; + class Sqrt; + class Exp; + class Log; + class Sin; + class Cos; + class Sec; + class Csc; + class Tan; + class Cot; + class Asin; + class Acos; + class Atan; + class Atan2; + class Sinh; + class Cosh; + class Tanh; + class Erf; + class Erfc; + class Step; + class Delta; + class Square; + class Cube; + class Reciprocal; + class AddConstant; + class MultiplyConstant; + class PowerConstant; + class Min; + class Max; + class Abs; + class Floor; + class Ceil; + class Select; +}; + +class LEPTON_EXPORT Operation::Constant : public Operation { +public: + Constant(double value) : value(value) { + } + std::string getName() const { + std::stringstream name; + name << value; + return name.str(); + } + Id getId() const { + return CONSTANT; + } + int getNumArguments() const { + return 0; + } + Operation* clone() const { + return new Constant(value); + } + double evaluate(double* args, const std::map& variables) const { + return value; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + double getValue() const { + return value; + } + bool operator!=(const Operation& op) const { + const Constant* o = dynamic_cast(&op); + return (o == NULL || o->value != value); + } +private: + double value; +}; + +class LEPTON_EXPORT Operation::Variable : public Operation { +public: + Variable(const std::string& name) : name(name) { + } + std::string getName() const { + return name; + } + Id getId() const { + return VARIABLE; + } + int getNumArguments() const { + return 0; + } + Operation* clone() const { + return new Variable(name); + } + double evaluate(double* args, const std::map& variables) const { + std::map::const_iterator iter = variables.find(name); + if (iter == variables.end()) + throw Exception("No value specified for variable "+name); + return iter->second; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + bool operator!=(const Operation& op) const { + const Variable* o = dynamic_cast(&op); + return (o == NULL || o->name != name); + } +private: + std::string name; +}; + +class LEPTON_EXPORT Operation::Custom : public Operation { +public: + Custom(const std::string& name, CustomFunction* function) : name(name), function(function), isDerivative(false), derivOrder(function->getNumArguments(), 0) { + } + Custom(const std::string& name, CustomFunction* function, const std::vector& derivOrder) : name(name), function(function), isDerivative(false), derivOrder(derivOrder) { + for (int order : derivOrder) + if (order != 0) + isDerivative = true; + } + Custom(const Custom& base, int derivIndex) : name(base.name), function(base.function->clone()), isDerivative(true), derivOrder(base.derivOrder) { + derivOrder[derivIndex]++; + } + ~Custom() { + delete function; + } + std::string getName() const { + return name; + } + Id getId() const { + return CUSTOM; + } + int getNumArguments() const { + return function->getNumArguments(); + } + Operation* clone() const { + Custom* clone = new Custom(name, function->clone()); + clone->isDerivative = isDerivative; + clone->derivOrder = derivOrder; + return clone; + } + double evaluate(double* args, const std::map& variables) const { + if (isDerivative) + return function->evaluateDerivative(args, &derivOrder[0]); + return function->evaluate(args); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + const std::vector& getDerivOrder() const { + return derivOrder; + } + bool operator!=(const Operation& op) const { + const Custom* o = dynamic_cast(&op); + return (o == NULL || o->name != name || o->isDerivative != isDerivative || o->derivOrder != derivOrder); + } +private: + std::string name; + CustomFunction* function; + bool isDerivative; + std::vector derivOrder; +}; + +class LEPTON_EXPORT Operation::Add : public Operation { +public: + Add() { + } + std::string getName() const { + return "+"; + } + Id getId() const { + return ADD; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Add(); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]+args[1]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + bool isInfixOperator() const { + return true; + } + bool isSymmetric() const { + return true; + } +}; + +class LEPTON_EXPORT Operation::Subtract : public Operation { +public: + Subtract() { + } + std::string getName() const { + return "-"; + } + Id getId() const { + return SUBTRACT; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Subtract(); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]-args[1]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + bool isInfixOperator() const { + return true; + } +}; + +class LEPTON_EXPORT Operation::Multiply : public Operation { +public: + Multiply() { + } + std::string getName() const { + return "*"; + } + Id getId() const { + return MULTIPLY; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Multiply(); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]*args[1]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + bool isInfixOperator() const { + return true; + } + bool isSymmetric() const { + return true; + } +}; + +class LEPTON_EXPORT Operation::Divide : public Operation { +public: + Divide() { + } + std::string getName() const { + return "/"; + } + Id getId() const { + return DIVIDE; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Divide(); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]/args[1]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + bool isInfixOperator() const { + return true; + } +}; + +class LEPTON_EXPORT Operation::Power : public Operation { +public: + Power() { + } + std::string getName() const { + return "^"; + } + Id getId() const { + return POWER; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Power(); + } + double evaluate(double* args, const std::map& variables) const { + return std::pow(args[0], args[1]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + bool isInfixOperator() const { + return true; + } +}; + +class LEPTON_EXPORT Operation::Negate : public Operation { +public: + Negate() { + } + std::string getName() const { + return "-"; + } + Id getId() const { + return NEGATE; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Negate(); + } + double evaluate(double* args, const std::map& variables) const { + return -args[0]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Sqrt : public Operation { +public: + Sqrt() { + } + std::string getName() const { + return "sqrt"; + } + Id getId() const { + return SQRT; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Sqrt(); + } + double evaluate(double* args, const std::map& variables) const { + return std::sqrt(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Exp : public Operation { +public: + Exp() { + } + std::string getName() const { + return "exp"; + } + Id getId() const { + return EXP; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Exp(); + } + double evaluate(double* args, const std::map& variables) const { + return std::exp(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Log : public Operation { +public: + Log() { + } + std::string getName() const { + return "log"; + } + Id getId() const { + return LOG; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Log(); + } + double evaluate(double* args, const std::map& variables) const { + return std::log(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Sin : public Operation { +public: + Sin() { + } + std::string getName() const { + return "sin"; + } + Id getId() const { + return SIN; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Sin(); + } + double evaluate(double* args, const std::map& variables) const { + return std::sin(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Cos : public Operation { +public: + Cos() { + } + std::string getName() const { + return "cos"; + } + Id getId() const { + return COS; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Cos(); + } + double evaluate(double* args, const std::map& variables) const { + return std::cos(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Sec : public Operation { +public: + Sec() { + } + std::string getName() const { + return "sec"; + } + Id getId() const { + return SEC; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Sec(); + } + double evaluate(double* args, const std::map& variables) const { + return 1.0/std::cos(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Csc : public Operation { +public: + Csc() { + } + std::string getName() const { + return "csc"; + } + Id getId() const { + return CSC; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Csc(); + } + double evaluate(double* args, const std::map& variables) const { + return 1.0/std::sin(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Tan : public Operation { +public: + Tan() { + } + std::string getName() const { + return "tan"; + } + Id getId() const { + return TAN; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Tan(); + } + double evaluate(double* args, const std::map& variables) const { + return std::tan(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Cot : public Operation { +public: + Cot() { + } + std::string getName() const { + return "cot"; + } + Id getId() const { + return COT; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Cot(); + } + double evaluate(double* args, const std::map& variables) const { + return 1.0/std::tan(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Asin : public Operation { +public: + Asin() { + } + std::string getName() const { + return "asin"; + } + Id getId() const { + return ASIN; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Asin(); + } + double evaluate(double* args, const std::map& variables) const { + return std::asin(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Acos : public Operation { +public: + Acos() { + } + std::string getName() const { + return "acos"; + } + Id getId() const { + return ACOS; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Acos(); + } + double evaluate(double* args, const std::map& variables) const { + return std::acos(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Atan : public Operation { +public: + Atan() { + } + std::string getName() const { + return "atan"; + } + Id getId() const { + return ATAN; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Atan(); + } + double evaluate(double* args, const std::map& variables) const { + return std::atan(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Atan2 : public Operation { +public: + Atan2() { + } + std::string getName() const { + return "atan2"; + } + Id getId() const { + return ATAN2; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Atan2(); + } + double evaluate(double* args, const std::map& variables) const { + return std::atan2(args[0], args[1]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Sinh : public Operation { +public: + Sinh() { + } + std::string getName() const { + return "sinh"; + } + Id getId() const { + return SINH; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Sinh(); + } + double evaluate(double* args, const std::map& variables) const { + return std::sinh(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Cosh : public Operation { +public: + Cosh() { + } + std::string getName() const { + return "cosh"; + } + Id getId() const { + return COSH; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Cosh(); + } + double evaluate(double* args, const std::map& variables) const { + return std::cosh(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Tanh : public Operation { +public: + Tanh() { + } + std::string getName() const { + return "tanh"; + } + Id getId() const { + return TANH; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Tanh(); + } + double evaluate(double* args, const std::map& variables) const { + return std::tanh(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Erf : public Operation { +public: + Erf() { + } + std::string getName() const { + return "erf"; + } + Id getId() const { + return ERF; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Erf(); + } + double evaluate(double* args, const std::map& variables) const; + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Erfc : public Operation { +public: + Erfc() { + } + std::string getName() const { + return "erfc"; + } + Id getId() const { + return ERFC; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Erfc(); + } + double evaluate(double* args, const std::map& variables) const; + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Step : public Operation { +public: + Step() { + } + std::string getName() const { + return "step"; + } + Id getId() const { + return STEP; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Step(); + } + double evaluate(double* args, const std::map& variables) const { + return (args[0] >= 0.0 ? 1.0 : 0.0); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Delta : public Operation { +public: + Delta() { + } + std::string getName() const { + return "delta"; + } + Id getId() const { + return DELTA; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Delta(); + } + double evaluate(double* args, const std::map& variables) const { + return (args[0] == 0.0 ? 1.0 : 0.0); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Square : public Operation { +public: + Square() { + } + std::string getName() const { + return "square"; + } + Id getId() const { + return SQUARE; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Square(); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]*args[0]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Cube : public Operation { +public: + Cube() { + } + std::string getName() const { + return "cube"; + } + Id getId() const { + return CUBE; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Cube(); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]*args[0]*args[0]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Reciprocal : public Operation { +public: + Reciprocal() { + } + std::string getName() const { + return "recip"; + } + Id getId() const { + return RECIPROCAL; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Reciprocal(); + } + double evaluate(double* args, const std::map& variables) const { + return 1.0/args[0]; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::AddConstant : public Operation { +public: + AddConstant(double value) : value(value) { + } + std::string getName() const { + std::stringstream name; + name << value << "+"; + return name.str(); + } + Id getId() const { + return ADD_CONSTANT; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new AddConstant(value); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]+value; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + double getValue() const { + return value; + } + bool operator!=(const Operation& op) const { + const AddConstant* o = dynamic_cast(&op); + return (o == NULL || o->value != value); + } +private: + double value; +}; + +class LEPTON_EXPORT Operation::MultiplyConstant : public Operation { +public: + MultiplyConstant(double value) : value(value) { + } + std::string getName() const { + std::stringstream name; + name << value << "*"; + return name.str(); + } + Id getId() const { + return MULTIPLY_CONSTANT; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new MultiplyConstant(value); + } + double evaluate(double* args, const std::map& variables) const { + return args[0]*value; + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + double getValue() const { + return value; + } + bool operator!=(const Operation& op) const { + const MultiplyConstant* o = dynamic_cast(&op); + return (o == NULL || o->value != value); + } +private: + double value; +}; + +class LEPTON_EXPORT Operation::PowerConstant : public Operation { +public: + PowerConstant(double value) : value(value) { + intValue = (int) value; + isIntPower = (intValue == value); + } + std::string getName() const { + std::stringstream name; + name << "^" << value; + return name.str(); + } + Id getId() const { + return POWER_CONSTANT; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new PowerConstant(value); + } + double evaluate(double* args, const std::map& variables) const { + if (isIntPower) { + // Integer powers can be computed much more quickly by repeated multiplication. + + int exponent = intValue; + double base = args[0]; + if (exponent < 0) { + exponent = -exponent; + base = 1.0/base; + } + double result = 1.0; + while (exponent != 0) { + if ((exponent&1) == 1) + result *= base; + base *= base; + exponent = exponent>>1; + } + return result; + } + else + return std::pow(args[0], value); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; + double getValue() const { + return value; + } + bool operator!=(const Operation& op) const { + const PowerConstant* o = dynamic_cast(&op); + return (o == NULL || o->value != value); + } + bool isInfixOperator() const { + return true; + } +private: + double value; + int intValue; + bool isIntPower; +}; + +class LEPTON_EXPORT Operation::Min : public Operation { +public: + Min() { + } + std::string getName() const { + return "min"; + } + Id getId() const { + return MIN; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Min(); + } + double evaluate(double* args, const std::map& variables) const { + // parens around (std::min) are workaround for horrible microsoft max/min macro trouble + return (std::min)(args[0], args[1]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Max : public Operation { +public: + Max() { + } + std::string getName() const { + return "max"; + } + Id getId() const { + return MAX; + } + int getNumArguments() const { + return 2; + } + Operation* clone() const { + return new Max(); + } + double evaluate(double* args, const std::map& variables) const { + // parens around (std::min) are workaround for horrible microsoft max/min macro trouble + return (std::max)(args[0], args[1]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Abs : public Operation { +public: + Abs() { + } + std::string getName() const { + return "abs"; + } + Id getId() const { + return ABS; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Abs(); + } + double evaluate(double* args, const std::map& variables) const { + return std::abs(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Floor : public Operation { +public: + + Floor() { + } + std::string getName() const { + return "floor"; + } + Id getId() const { + return FLOOR; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Floor(); + } + double evaluate(double* args, const std::map& variables) const { + return std::floor(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Ceil : public Operation { +public: + Ceil() { + } + std::string getName() const { + return "ceil"; + } + Id getId() const { + return CEIL; + } + int getNumArguments() const { + return 1; + } + Operation* clone() const { + return new Ceil(); + } + double evaluate(double* args, const std::map& variables) const { + return std::ceil(args[0]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +class LEPTON_EXPORT Operation::Select : public Operation { +public: + Select() { + } + std::string getName() const { + return "select"; + } + Id getId() const { + return SELECT; + } + int getNumArguments() const { + return 3; + } + Operation* clone() const { + return new Select(); + } + double evaluate(double* args, const std::map& variables) const { + return (args[0] != 0.0 ? args[1] : args[2]); + } + ExpressionTreeNode differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const; +}; + +} // namespace Lepton + +#endif /*LEPTON_OPERATION_H_*/ diff --git a/src/external/lepton/include/lepton/ParsedExpression.h b/src/external/lepton/include/lepton/ParsedExpression.h new file mode 100644 index 00000000000..6c6526e5254 --- /dev/null +++ b/src/external/lepton/include/lepton/ParsedExpression.h @@ -0,0 +1,142 @@ +#ifndef LEPTON_PARSED_EXPRESSION_H_ +#define LEPTON_PARSED_EXPRESSION_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2022 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "ExpressionTreeNode.h" +#include "windowsIncludes.h" +#include +#include + +namespace Lepton { + +class CompiledExpression; +class ExpressionProgram; +class CompiledVectorExpression; + +/** + * This class represents the result of parsing an expression. It provides methods for working with the + * expression in various ways, such as evaluating it, getting the tree representation of the expresson, etc. + */ + +class LEPTON_EXPORT ParsedExpression { +public: + /** + * Create an uninitialized ParsedExpression. This exists so that ParsedExpressions can be put in STL containers. + * Doing anything with it will produce an exception. + */ + ParsedExpression(); + /** + * Create a ParsedExpression. Normally you will not call this directly. Instead, use the Parser class + * to parse expression. + */ + ParsedExpression(const ExpressionTreeNode& rootNode); + /** + * Get the root node of the expression's abstract syntax tree. + */ + const ExpressionTreeNode& getRootNode() const; + /** + * Evaluate the expression. If the expression involves any variables, this method will throw an exception. + */ + double evaluate() const; + /** + * Evaluate the expression. + * + * @param variables a map specifying the values of all variables that appear in the expression. If any + * variable appears in the expression but is not included in this map, an exception + * will be thrown. + */ + double evaluate(const std::map& variables) const; + /** + * Create a new ParsedExpression which produces the same result as this one, but is faster to evaluate. + */ + ParsedExpression optimize() const; + /** + * Create a new ParsedExpression which produces the same result as this one, but is faster to evaluate. + * + * @param variables a map specifying values for a subset of variables that appear in the expression. + * All occurrences of these variables in the expression are replaced with the values + * specified. + */ + ParsedExpression optimize(const std::map& variables) const; + /** + * Create a new ParsedExpression which is the analytic derivative of this expression with respect to a + * particular variable. + * + * @param variable the variable with respect to which the derivate should be taken + */ + ParsedExpression differentiate(const std::string& variable) const; + /** + * Create an ExpressionProgram that represents the same calculation as this expression. + */ + ExpressionProgram createProgram() const; + /** + * Create a CompiledExpression that represents the same calculation as this expression. + */ + CompiledExpression createCompiledExpression() const; + /** + * Create a CompiledVectorExpression that allows the expression to be evaluated efficiently + * using the CPU's vector unit. + * + * @param width the width of the vectors to evaluate it on. The allowed values + * depend on the CPU. 4 is always allowed, and 8 is allowed on + * x86 processors with AVX. Call CompiledVectorExpression::getAllowedWidths() + * to query the allowed widths on the current processor. + */ + CompiledVectorExpression createCompiledVectorExpression(int width) const; + /** + * Create a new ParsedExpression which is identical to this one, except that the names of some + * variables have been changed. + * + * @param replacements a map whose keys are the names of variables, and whose values are the + * new names to replace them with + */ + ParsedExpression renameVariables(const std::map& replacements) const; +private: + static double evaluate(const ExpressionTreeNode& node, const std::map& variables); + static ExpressionTreeNode preevaluateVariables(const ExpressionTreeNode& node, const std::map& variables); + static ExpressionTreeNode precalculateConstantSubexpressions(const ExpressionTreeNode& node, std::map& nodeCache); + static ExpressionTreeNode substituteSimplerExpression(const ExpressionTreeNode& node, std::map& nodeCache); + static ExpressionTreeNode differentiate(const ExpressionTreeNode& node, const std::string& variable, std::map& nodeCache); + static bool isConstant(const ExpressionTreeNode& node); + static double getConstantValue(const ExpressionTreeNode& node); + static ExpressionTreeNode renameNodeVariables(const ExpressionTreeNode& node, const std::map& replacements); + ExpressionTreeNode rootNode; +}; + +LEPTON_EXPORT std::ostream& operator<<(std::ostream& out, const ExpressionTreeNode& node); + +LEPTON_EXPORT std::ostream& operator<<(std::ostream& out, const ParsedExpression& exp); + +} // namespace Lepton + +#endif /*LEPTON_PARSED_EXPRESSION_H_*/ diff --git a/src/external/lepton/include/lepton/Parser.h b/src/external/lepton/include/lepton/Parser.h new file mode 100644 index 00000000000..63d5988d5fa --- /dev/null +++ b/src/external/lepton/include/lepton/Parser.h @@ -0,0 +1,77 @@ +#ifndef LEPTON_PARSER_H_ +#define LEPTON_PARSER_H_ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "windowsIncludes.h" +#include +#include +#include + +namespace Lepton { + +class CustomFunction; +class ExpressionTreeNode; +class Operation; +class ParsedExpression; +class ParseToken; + +/** + * This class provides the main interface for parsing expressions. + */ + +class LEPTON_EXPORT Parser { +public: + /** + * Parse a mathematical expression and return a representation of it as an abstract syntax tree. + */ + static ParsedExpression parse(const std::string& expression); + /** + * Parse a mathematical expression and return a representation of it as an abstract syntax tree. + * + * @param customFunctions a map specifying user defined functions that may appear in the expression. + * The key are function names, and the values are corresponding CustomFunction objects. + */ + static ParsedExpression parse(const std::string& expression, const std::map& customFunctions); +private: + static std::string trim(const std::string& expression); + static std::vector tokenize(const std::string& expression); + static ParseToken getNextToken(const std::string& expression, int start); + static ExpressionTreeNode parsePrecedence(const std::vector& tokens, int& pos, const std::map& customFunctions, + const std::map& subexpressionDefs, int precedence); + static Operation* getOperatorOperation(const std::string& name); + static Operation* getFunctionOperation(const std::string& name, const std::map& customFunctions); +}; + +} // namespace Lepton + +#endif /*LEPTON_PARSER_H_*/ diff --git a/src/external/lepton/include/lepton/windowsIncludes.h b/src/external/lepton/include/lepton/windowsIncludes.h new file mode 100644 index 00000000000..798229850e7 --- /dev/null +++ b/src/external/lepton/include/lepton/windowsIncludes.h @@ -0,0 +1,41 @@ +#ifndef LEPTON_WINDOW_INCLUDE_H_ +#define LEPTON_WINDOW_INCLUDE_H_ + +/* + * Shared libraries are messy in Visual Studio. We have to distinguish three + * cases: + * (1) this header is being used to build the Lepton shared library + * (dllexport) + * (2) this header is being used by a *client* of the Lepton shared + * library (dllimport) + * (3) we are building the Lepton static library, or the client is + * being compiled with the expectation of linking with the + * Lepton static library (nothing special needed) + * In the CMake script for building this library, we define one of the symbols + * Lepton_BUILDING_{SHARED|STATIC}_LIBRARY + * Client code normally has no special symbol defined, in which case we'll + * assume it wants to use the shared library. However, if the client defines + * the symbol LEPTON_USE_STATIC_LIBRARIES we'll suppress the dllimport so + * that the client code can be linked with static libraries. Note that + * the client symbol is not library dependent, while the library symbols + * affect only the Lepton library, meaning that other libraries can + * be clients of this one. However, we are assuming all-static or all-shared. + */ + +#ifdef _MSC_VER + // We don't want to hear about how sprintf is "unsafe". + #pragma warning(disable:4996) + // Keep MS VC++ quiet about lack of dll export of private members. + #pragma warning(disable:4251) + #if defined(LEPTON_BUILDING_SHARED_LIBRARY) + #define LEPTON_EXPORT __declspec(dllexport) + #elif defined(LEPTON_BUILDING_STATIC_LIBRARY) || defined(LEPTON_USE_STATIC_LIBRARIES) + #define LEPTON_EXPORT + #else + #define LEPTON_EXPORT __declspec(dllimport) // i.e., a client of a shared library + #endif +#else + #define LEPTON_EXPORT // Linux, Mac +#endif + +#endif // LEPTON_WINDOW_INCLUDE_H_ diff --git a/src/external/lepton/src/CompiledExpression.cpp b/src/external/lepton/src/CompiledExpression.cpp new file mode 100644 index 00000000000..8a8707dfd73 --- /dev/null +++ b/src/external/lepton/src/CompiledExpression.cpp @@ -0,0 +1,812 @@ +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2013-2022 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "lepton/CompiledExpression.h" +#include "lepton/Operation.h" +#include "lepton/ParsedExpression.h" +#include + +using namespace Lepton; +using namespace std; +#ifdef LEPTON_USE_JIT + using namespace asmjit; +#endif + +CompiledExpression::CompiledExpression() : jitCode(NULL) { +} + +CompiledExpression::CompiledExpression(const ParsedExpression& expression) : jitCode(NULL) { + ParsedExpression expr = expression.optimize(); // Just in case it wasn't already optimized. + vector > temps; + compileExpression(expr.getRootNode(), temps); + int maxArguments = 1; + for (int i = 0; i < (int) operation.size(); i++) + if (operation[i]->getNumArguments() > maxArguments) + maxArguments = operation[i]->getNumArguments(); + argValues.resize(maxArguments); +#ifdef LEPTON_USE_JIT + generateJitCode(); +#endif +} + +CompiledExpression::~CompiledExpression() { + for (int i = 0; i < (int) operation.size(); i++) + if (operation[i] != NULL) + delete operation[i]; +} + +CompiledExpression::CompiledExpression(const CompiledExpression& expression) : jitCode(NULL) { + *this = expression; +} + +CompiledExpression& CompiledExpression::operator=(const CompiledExpression& expression) { + arguments = expression.arguments; + target = expression.target; + variableIndices = expression.variableIndices; + variableNames = expression.variableNames; + workspace.resize(expression.workspace.size()); + argValues.resize(expression.argValues.size()); + operation.resize(expression.operation.size()); + for (int i = 0; i < (int) operation.size(); i++) + operation[i] = expression.operation[i]->clone(); + setVariableLocations(variablePointers); + return *this; +} + +void CompiledExpression::compileExpression(const ExpressionTreeNode& node, vector >& temps) { + if (findTempIndex(node, temps) != -1) + return; // We have already processed a node identical to this one. + + // Process the child nodes. + + vector args; + for (int i = 0; i < node.getChildren().size(); i++) { + compileExpression(node.getChildren()[i], temps); + args.push_back(findTempIndex(node.getChildren()[i], temps)); + } + + // Process this node. + + if (node.getOperation().getId() == Operation::VARIABLE) { + variableIndices[node.getOperation().getName()] = (int) workspace.size(); + variableNames.insert(node.getOperation().getName()); + } + else { + int stepIndex = (int) arguments.size(); + arguments.push_back(vector()); + target.push_back((int) workspace.size()); + operation.push_back(node.getOperation().clone()); + if (args.size() == 0) + arguments[stepIndex].push_back(0); // The value won't actually be used. We just need something there. + else { + // If the arguments are sequential, we can just pass a pointer to the first one. + + bool sequential = true; + for (int i = 1; i < args.size(); i++) + if (args[i] != args[i-1]+1) + sequential = false; + if (sequential) + arguments[stepIndex].push_back(args[0]); + else + arguments[stepIndex] = args; + } + } + temps.push_back(make_pair(node, (int) workspace.size())); + workspace.push_back(0.0); +} + +int CompiledExpression::findTempIndex(const ExpressionTreeNode& node, vector >& temps) { + for (int i = 0; i < (int) temps.size(); i++) + if (temps[i].first == node) + return i; + return -1; +} + +const set& CompiledExpression::getVariables() const { + return variableNames; +} + +double& CompiledExpression::getVariableReference(const string& name) { + map::iterator pointer = variablePointers.find(name); + if (pointer != variablePointers.end()) + return *pointer->second; + map::iterator index = variableIndices.find(name); + if (index == variableIndices.end()) + throw Exception("getVariableReference: Unknown variable '"+name+"'"); + return workspace[index->second]; +} + +void CompiledExpression::setVariableLocations(map& variableLocations) { + variablePointers = variableLocations; +#ifdef LEPTON_USE_JIT + // Rebuild the JIT code. + + if (workspace.size() > 0) + generateJitCode(); +#endif + // Make a list of all variables we will need to copy before evaluating the expression. + + variablesToCopy.clear(); + for (map::const_iterator iter = variableIndices.begin(); iter != variableIndices.end(); ++iter) { + map::iterator pointer = variablePointers.find(iter->first); + if (pointer != variablePointers.end()) + variablesToCopy.push_back(make_pair(&workspace[iter->second], pointer->second)); + } +} + +double CompiledExpression::evaluate() const { + if (jitCode) + return jitCode(); + for (int i = 0; i < variablesToCopy.size(); i++) + *variablesToCopy[i].first = *variablesToCopy[i].second; + + // Loop over the operations and evaluate each one. + + for (int step = 0; step < operation.size(); step++) { + const vector& args = arguments[step]; + if (args.size() == 1) + workspace[target[step]] = operation[step]->evaluate(&workspace[args[0]], dummyVariables); + else { + for (int i = 0; i < args.size(); i++) + argValues[i] = workspace[args[i]]; + workspace[target[step]] = operation[step]->evaluate(&argValues[0], dummyVariables); + } + } + return workspace[workspace.size()-1]; +} + +#ifdef LEPTON_USE_JIT +static double evaluateOperation(Operation* op, double* args) { + static map dummyVariables; + return op->evaluate(args, dummyVariables); +} + +void CompiledExpression::findPowerGroups(vector >& groups, vector >& groupPowers, vector& stepGroup) { + // Identify every step that raises an argument to an integer power. + + vector stepPower(operation.size(), 0); + vector stepArg(operation.size(), -1); + for (int step = 0; step < operation.size(); step++) { + Operation& op = *operation[step]; + int power = 0; + if (op.getId() == Operation::SQUARE) + power = 2; + else if (op.getId() == Operation::CUBE) + power = 3; + else if (op.getId() == Operation::POWER_CONSTANT) { + double realPower = dynamic_cast(&op)->getValue(); + if (realPower == (int) realPower) + power = (int) realPower; + } + if (power != 0) { + stepPower[step] = power; + stepArg[step] = arguments[step][0]; + } + } + + // Find groups that operate on the same argument and whose powers have the same sign. + + stepGroup.resize(operation.size(), -1); + for (int i = 0; i < operation.size(); i++) { + if (stepGroup[i] != -1) + continue; + vector group, power; + for (int j = i; j < operation.size(); j++) { + if (stepArg[i] == stepArg[j] && stepPower[i]*stepPower[j] > 0) { + stepGroup[j] = groups.size(); + group.push_back(j); + power.push_back(stepPower[j]); + } + } + groups.push_back(group); + groupPowers.push_back(power); + } +} + +#if defined(__ARM__) || defined(__ARM64__) +void CompiledExpression::generateJitCode() { + CodeHolder code; + code.init(runtime.environment()); + a64::Compiler c(&code); + c.addFunc(FuncSignatureT()); + vector workspaceVar(workspace.size()); + for (int i = 0; i < (int) workspaceVar.size(); i++) + workspaceVar[i] = c.newVecD(); + arm::Gp argsPointer = c.newIntPtr(); + c.mov(argsPointer, imm(&argValues[0])); + vector > groups, groupPowers; + vector stepGroup; + findPowerGroups(groups, groupPowers, stepGroup); + + // Load the arguments into variables. + + for (set::const_iterator iter = variableNames.begin(); iter != variableNames.end(); ++iter) { + map::iterator index = variableIndices.find(*iter); + arm::Gp variablePointer = c.newIntPtr(); + c.mov(variablePointer, imm(&getVariableReference(index->first))); + c.ldr(workspaceVar[index->second], arm::ptr(variablePointer, 0)); + } + + // Make a list of all constants that will be needed for evaluation. + + vector operationConstantIndex(operation.size(), -1); + for (int step = 0; step < (int) operation.size(); step++) { + // Find the constant value (if any) used by this operation. + + Operation& op = *operation[step]; + double value; + if (op.getId() == Operation::CONSTANT) + value = dynamic_cast(op).getValue(); + else if (op.getId() == Operation::ADD_CONSTANT) + value = dynamic_cast(op).getValue(); + else if (op.getId() == Operation::MULTIPLY_CONSTANT) + value = dynamic_cast(op).getValue(); + else if (op.getId() == Operation::RECIPROCAL) + value = 1.0; + else if (op.getId() == Operation::STEP) + value = 1.0; + else if (op.getId() == Operation::DELTA) + value = 1.0; + else if (op.getId() == Operation::POWER_CONSTANT) { + if (stepGroup[step] == -1) + value = dynamic_cast(op).getValue(); + else + value = 1.0; + } + else + continue; + + // See if we already have a variable for this constant. + + for (int i = 0; i < (int) constants.size(); i++) + if (value == constants[i]) { + operationConstantIndex[step] = i; + break; + } + if (operationConstantIndex[step] == -1) { + operationConstantIndex[step] = constants.size(); + constants.push_back(value); + } + } + + // Load constants into variables. + + vector constantVar(constants.size()); + if (constants.size() > 0) { + arm::Gp constantsPointer = c.newIntPtr(); + c.mov(constantsPointer, imm(&constants[0])); + for (int i = 0; i < (int) constants.size(); i++) { + constantVar[i] = c.newVecD(); + c.ldr(constantVar[i], arm::ptr(constantsPointer, 8*i)); + } + } + + // Evaluate the operations. + + vector hasComputedPower(operation.size(), false); + for (int step = 0; step < (int) operation.size(); step++) { + if (hasComputedPower[step]) + continue; + + // When one or more steps involve raising the same argument to multiple integer + // powers, we can compute them all together for efficiency. + + if (stepGroup[step] != -1) { + vector& group = groups[stepGroup[step]]; + vector& powers = groupPowers[stepGroup[step]]; + arm::Vec multiplier = c.newVecD(); + if (powers[0] > 0) + c.fmov(multiplier, workspaceVar[arguments[step][0]]); + else { + c.fdiv(multiplier, constantVar[operationConstantIndex[step]], workspaceVar[arguments[step][0]]); + for (int i = 0; i < powers.size(); i++) + powers[i] = -powers[i]; + } + vector hasAssigned(group.size(), false); + bool done = false; + while (!done) { + done = true; + for (int i = 0; i < group.size(); i++) { + if (powers[i]%2 == 1) { + if (!hasAssigned[i]) + c.fmov(workspaceVar[target[group[i]]], multiplier); + else + c.fmul(workspaceVar[target[group[i]]], workspaceVar[target[group[i]]], multiplier); + hasAssigned[i] = true; + } + powers[i] >>= 1; + if (powers[i] != 0) + done = false; + } + if (!done) + c.fmul(multiplier, multiplier, multiplier); + } + for (int step : group) + hasComputedPower[step] = true; + continue; + } + + // Evaluate the step. + + Operation& op = *operation[step]; + vector args = arguments[step]; + if (args.size() == 1) { + // One or more sequential arguments. Fill out the list. + + for (int i = 1; i < op.getNumArguments(); i++) + args.push_back(args[0]+i); + } + + // Generate instructions to execute this operation. + + switch (op.getId()) { + case Operation::CONSTANT: + c.fmov(workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::ADD: + c.fadd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::SUBTRACT: + c.fsub(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::MULTIPLY: + c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::DIVIDE: + c.fdiv(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::POWER: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], pow); + break; + case Operation::NEGATE: + c.fneg(workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::SQRT: + c.fsqrt(workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::EXP: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], exp); + break; + case Operation::LOG: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], log); + break; + case Operation::SIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sin); + break; + case Operation::COS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cos); + break; + case Operation::TAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tan); + break; + case Operation::ASIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], asin); + break; + case Operation::ACOS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], acos); + break; + case Operation::ATAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], atan); + break; + case Operation::ATAN2: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], atan2); + break; + case Operation::SINH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinh); + break; + case Operation::COSH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cosh); + break; + case Operation::TANH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanh); + break; + case Operation::STEP: + c.cmge(workspaceVar[target[step]], workspaceVar[args[0]], imm(0)); + c.and_(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::DELTA: + c.cmeq(workspaceVar[target[step]], workspaceVar[args[0]], imm(0)); + c.and_(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::SQUARE: + c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + break; + case Operation::CUBE: + c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + c.fmul(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::RECIPROCAL: + c.fdiv(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], workspaceVar[args[0]]); + break; + case Operation::ADD_CONSTANT: + c.fadd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::MULTIPLY_CONSTANT: + c.fmul(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::POWER_CONSTANT: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]], pow); + break; + case Operation::MIN: + c.fmin(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::MAX: + c.fmax(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::ABS: + c.fabs(workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::FLOOR: + c.frintm(workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::CEIL: + c.frintp(workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::SELECT: + c.fcmeq(workspaceVar[target[step]], workspaceVar[args[0]], imm(0)); + c.bsl(workspaceVar[target[step]], workspaceVar[args[2]], workspaceVar[args[1]]); + break; + default: + // Just invoke evaluateOperation(). + + for (int i = 0; i < (int) args.size(); i++) + c.str(workspaceVar[args[i]], arm::ptr(argsPointer, 8*i)); + arm::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) evaluateOperation)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, imm(&op)); + invoke->setArg(1, imm(&argValues[0])); + invoke->setRet(0, workspaceVar[target[step]]); + } + } + c.ret(workspaceVar[workspace.size()-1]); + c.endFunc(); + c.finalize(); + runtime.add(&jitCode, &code); +} + +void CompiledExpression::generateSingleArgCall(a64::Compiler& c, arm::Vec& dest, arm::Vec& arg, double (*function)(double)) { + arm::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) function)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, arg); + invoke->setRet(0, dest); +} + +void CompiledExpression::generateTwoArgCall(a64::Compiler& c, arm::Vec& dest, arm::Vec& arg1, arm::Vec& arg2, double (*function)(double, double)) { + arm::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) function)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, arg1); + invoke->setArg(1, arg2); + invoke->setRet(0, dest); +} +#else +void CompiledExpression::generateJitCode() { + const CpuInfo& cpu = CpuInfo::host(); + if (!cpu.hasFeature(CpuFeatures::X86::kAVX)) + return; + CodeHolder code; + code.init(runtime.environment()); + x86::Compiler c(&code); + FuncNode* funcNode = c.addFunc(FuncSignatureT()); + funcNode->frame().setAvxEnabled(); + vector workspaceVar(workspace.size()); + for (int i = 0; i < (int) workspaceVar.size(); i++) + workspaceVar[i] = c.newXmmSd(); + x86::Gp argsPointer = c.newIntPtr(); + c.mov(argsPointer, imm(&argValues[0])); + vector > groups, groupPowers; + vector stepGroup; + findPowerGroups(groups, groupPowers, stepGroup); + + // Load the arguments into variables. + + x86::Gp variablePointer = c.newIntPtr(); + for (set::const_iterator iter = variableNames.begin(); iter != variableNames.end(); ++iter) { + map::iterator index = variableIndices.find(*iter); + c.mov(variablePointer, imm(&getVariableReference(index->first))); + c.vmovsd(workspaceVar[index->second], x86::ptr(variablePointer, 0, 0)); + } + + // Make a list of all constants that will be needed for evaluation. + + vector operationConstantIndex(operation.size(), -1); + for (int step = 0; step < (int) operation.size(); step++) { + // Find the constant value (if any) used by this operation. + + Operation& op = *operation[step]; + double value; + if (op.getId() == Operation::CONSTANT) + value = dynamic_cast(op).getValue(); + else if (op.getId() == Operation::ADD_CONSTANT) + value = dynamic_cast(op).getValue(); + else if (op.getId() == Operation::MULTIPLY_CONSTANT) + value = dynamic_cast(op).getValue(); + else if (op.getId() == Operation::RECIPROCAL) + value = 1.0; + else if (op.getId() == Operation::STEP) + value = 1.0; + else if (op.getId() == Operation::DELTA) + value = 1.0; + else if (op.getId() == Operation::ABS) { + long long mask = 0x7FFFFFFFFFFFFFFF; + value = *reinterpret_cast(&mask); + } + else if (op.getId() == Operation::POWER_CONSTANT) { + if (stepGroup[step] == -1) + value = dynamic_cast(op).getValue(); + else + value = 1.0; + } + else + continue; + + // See if we already have a variable for this constant. + + for (int i = 0; i < (int) constants.size(); i++) + if (value == constants[i]) { + operationConstantIndex[step] = i; + break; + } + if (operationConstantIndex[step] == -1) { + operationConstantIndex[step] = constants.size(); + constants.push_back(value); + } + } + + // Load constants into variables. + + vector constantVar(constants.size()); + if (constants.size() > 0) { + x86::Gp constantsPointer = c.newIntPtr(); + c.mov(constantsPointer, imm(&constants[0])); + for (int i = 0; i < (int) constants.size(); i++) { + constantVar[i] = c.newXmmSd(); + c.vmovsd(constantVar[i], x86::ptr(constantsPointer, 8*i, 0)); + } + } + + // Evaluate the operations. + + vector hasComputedPower(operation.size(), false); + for (int step = 0; step < (int) operation.size(); step++) { + if (hasComputedPower[step]) + continue; + + // When one or more steps involve raising the same argument to multiple integer + // powers, we can compute them all together for efficiency. + + if (stepGroup[step] != -1) { + vector& group = groups[stepGroup[step]]; + vector& powers = groupPowers[stepGroup[step]]; + x86::Xmm multiplier = c.newXmmSd(); + if (powers[0] > 0) + c.vmovsd(multiplier, workspaceVar[arguments[step][0]], workspaceVar[arguments[step][0]]); + else { + c.vdivsd(multiplier, constantVar[operationConstantIndex[step]], workspaceVar[arguments[step][0]]); + for (int i = 0; i < powers.size(); i++) + powers[i] = -powers[i]; + } + vector hasAssigned(group.size(), false); + bool done = false; + while (!done) { + done = true; + for (int i = 0; i < group.size(); i++) { + if (powers[i]%2 == 1) { + if (!hasAssigned[i]) + c.vmovsd(workspaceVar[target[group[i]]], multiplier, multiplier); + else + c.vmulsd(workspaceVar[target[group[i]]], workspaceVar[target[group[i]]], multiplier); + hasAssigned[i] = true; + } + powers[i] >>= 1; + if (powers[i] != 0) + done = false; + } + if (!done) + c.vmulsd(multiplier, multiplier, multiplier); + } + for (int step : group) + hasComputedPower[step] = true; + continue; + } + + // Evaluate the step. + + Operation& op = *operation[step]; + vector args = arguments[step]; + if (args.size() == 1) { + // One or more sequential arguments. Fill out the list. + + for (int i = 1; i < op.getNumArguments(); i++) + args.push_back(args[0]+i); + } + + // Generate instructions to execute this operation. + + switch (op.getId()) { + case Operation::CONSTANT: + c.vmovsd(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::ADD: + c.vaddsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::SUBTRACT: + c.vsubsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::MULTIPLY: + c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::DIVIDE: + c.vdivsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::POWER: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], pow); + break; + case Operation::NEGATE: + c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]); + c.vsubsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::SQRT: + c.vsqrtsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + break; + case Operation::EXP: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], exp); + break; + case Operation::LOG: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], log); + break; + case Operation::SIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sin); + break; + case Operation::COS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cos); + break; + case Operation::TAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tan); + break; + case Operation::ASIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], asin); + break; + case Operation::ACOS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], acos); + break; + case Operation::ATAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], atan); + break; + case Operation::ATAN2: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], atan2); + break; + case Operation::SINH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinh); + break; + case Operation::COSH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cosh); + break; + case Operation::TANH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanh); + break; + case Operation::STEP: + c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]); + c.vcmpsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]], imm(18)); // Comparison mode is _CMP_LE_OQ = 18 + c.vandps(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::DELTA: + c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]); + c.vcmpsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]], imm(16)); // Comparison mode is _CMP_EQ_OS = 16 + c.vandps(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::SQUARE: + c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + break; + case Operation::CUBE: + c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + c.vmulsd(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::RECIPROCAL: + c.vdivsd(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], workspaceVar[args[0]]); + break; + case Operation::ADD_CONSTANT: + c.vaddsd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::MULTIPLY_CONSTANT: + c.vmulsd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::POWER_CONSTANT: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]], pow); + break; + case Operation::MIN: + c.vminsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::MAX: + c.vmaxsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::ABS: + c.vandpd(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::FLOOR: + c.vroundsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]], imm(1)); + break; + case Operation::CEIL: + c.vroundsd(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]], imm(2)); + break; + case Operation::SELECT: + { + x86::Xmm mask = c.newXmmSd(); + c.vxorps(mask, mask, mask); + c.vcmpsd(mask, mask, workspaceVar[args[0]], imm(0)); // Comparison mode is _CMP_EQ_OQ = 0 + c.vblendvps(workspaceVar[target[step]], workspaceVar[args[1]], workspaceVar[args[2]], mask); + break; + } + default: + // Just invoke evaluateOperation(). + + for (int i = 0; i < (int) args.size(); i++) + c.vmovsd(x86::ptr(argsPointer, 8*i, 0), workspaceVar[args[i]]); + x86::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) evaluateOperation)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, imm(&op)); + invoke->setArg(1, imm(&argValues[0])); + invoke->setRet(0, workspaceVar[target[step]]); + } + } + c.ret(workspaceVar[workspace.size()-1]); + c.endFunc(); + c.finalize(); + runtime.add(&jitCode, &code); +} + +void CompiledExpression::generateSingleArgCall(x86::Compiler& c, x86::Xmm& dest, x86::Xmm& arg, double (*function)(double)) { + x86::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) function)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, arg); + invoke->setRet(0, dest); +} + +void CompiledExpression::generateTwoArgCall(x86::Compiler& c, x86::Xmm& dest, x86::Xmm& arg1, x86::Xmm& arg2, double (*function)(double, double)) { + x86::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) function)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, arg1); + invoke->setArg(1, arg2); + invoke->setRet(0, dest); +} +#endif +#endif diff --git a/src/external/lepton/src/CompiledVectorExpression.cpp b/src/external/lepton/src/CompiledVectorExpression.cpp new file mode 100644 index 00000000000..7c01a986bb0 --- /dev/null +++ b/src/external/lepton/src/CompiledVectorExpression.cpp @@ -0,0 +1,933 @@ +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2013-2022 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "lepton/CompiledVectorExpression.h" +#include "lepton/Operation.h" +#include "lepton/ParsedExpression.h" +#include +#include + +using namespace Lepton; +using namespace std; +#ifdef LEPTON_USE_JIT +using namespace asmjit; +#endif + +CompiledVectorExpression::CompiledVectorExpression() : jitCode(NULL) { +} + +CompiledVectorExpression::CompiledVectorExpression(const ParsedExpression& expression, int width) : jitCode(NULL), width(width) { + const vector allowedWidths = getAllowedWidths(); + if (find(allowedWidths.begin(), allowedWidths.end(), width) == allowedWidths.end()) + throw Exception("Unsupported width for vector expression: "+to_string(width)); + ParsedExpression expr = expression.optimize(); // Just in case it wasn't already optimized. + vector > temps; + int workspaceSize = 0; + compileExpression(expr.getRootNode(), temps, workspaceSize); + workspace.resize(workspaceSize*width); + int maxArguments = 1; + for (int i = 0; i < (int) operation.size(); i++) + if (operation[i]->getNumArguments() > maxArguments) + maxArguments = operation[i]->getNumArguments(); + argValues.resize(maxArguments); +#ifdef LEPTON_USE_JIT + generateJitCode(); +#endif +} + +CompiledVectorExpression::~CompiledVectorExpression() { + for (int i = 0; i < (int) operation.size(); i++) + if (operation[i] != NULL) + delete operation[i]; +} + +CompiledVectorExpression::CompiledVectorExpression(const CompiledVectorExpression& expression) : jitCode(NULL) { + *this = expression; +} + +CompiledVectorExpression& CompiledVectorExpression::operator=(const CompiledVectorExpression& expression) { + arguments = expression.arguments; + width = expression.width; + target = expression.target; + variableIndices = expression.variableIndices; + variableNames = expression.variableNames; + workspace.resize(expression.workspace.size()); + argValues.resize(expression.argValues.size()); + operation.resize(expression.operation.size()); + for (int i = 0; i < (int) operation.size(); i++) + operation[i] = expression.operation[i]->clone(); + setVariableLocations(variablePointers); + return *this; +} + +const vector& CompiledVectorExpression::getAllowedWidths() { + static vector widths; + if (widths.size() == 0) { + widths.push_back(4); +#ifdef LEPTON_USE_JIT + const CpuInfo& cpu = CpuInfo::host(); + if (cpu.hasFeature(CpuFeatures::X86::kAVX)) + widths.push_back(8); +#endif + } + return widths; +} + +void CompiledVectorExpression::compileExpression(const ExpressionTreeNode& node, vector >& temps, int& workspaceSize) { + if (findTempIndex(node, temps) != -1) + return; // We have already processed a node identical to this one. + + // Process the child nodes. + + vector args; + for (int i = 0; i < node.getChildren().size(); i++) { + compileExpression(node.getChildren()[i], temps, workspaceSize); + args.push_back(findTempIndex(node.getChildren()[i], temps)); + } + + // Process this node. + + if (node.getOperation().getId() == Operation::VARIABLE) { + variableIndices[node.getOperation().getName()] = workspaceSize; + variableNames.insert(node.getOperation().getName()); + } + else { + int stepIndex = (int) arguments.size(); + arguments.push_back(vector()); + target.push_back(workspaceSize); + operation.push_back(node.getOperation().clone()); + if (args.size() == 0) + arguments[stepIndex].push_back(0); // The value won't actually be used. We just need something there. + else { + // If the arguments are sequential, we can just pass a pointer to the first one. + + bool sequential = true; + for (int i = 1; i < args.size(); i++) + if (args[i] != args[i - 1] + 1) + sequential = false; + if (sequential) + arguments[stepIndex].push_back(args[0]); + else + arguments[stepIndex] = args; + } + } + temps.push_back(make_pair(node, workspaceSize)); + workspaceSize++; +} + +int CompiledVectorExpression::findTempIndex(const ExpressionTreeNode& node, vector >& temps) { + for (int i = 0; i < (int) temps.size(); i++) + if (temps[i].first == node) + return i; + return -1; +} + +int CompiledVectorExpression::getWidth() const { + return width; +} + +const set& CompiledVectorExpression::getVariables() const { + return variableNames; +} + +float* CompiledVectorExpression::getVariablePointer(const string& name) { + map::iterator pointer = variablePointers.find(name); + if (pointer != variablePointers.end()) + return pointer->second; + map::iterator index = variableIndices.find(name); + if (index == variableIndices.end()) + throw Exception("getVariableReference: Unknown variable '" + name + "'"); + return &workspace[index->second*width]; +} + +void CompiledVectorExpression::setVariableLocations(map& variableLocations) { + variablePointers = variableLocations; +#ifdef LEPTON_USE_JIT + // Rebuild the JIT code. + + if (workspace.size() > 0) + generateJitCode(); +#endif + // Make a list of all variables we will need to copy before evaluating the expression. + + variablesToCopy.clear(); + for (map::const_iterator iter = variableIndices.begin(); iter != variableIndices.end(); ++iter) { + map::iterator pointer = variablePointers.find(iter->first); + if (pointer != variablePointers.end()) + variablesToCopy.push_back(make_pair(&workspace[iter->second*width], pointer->second)); + } +} + +const float* CompiledVectorExpression::evaluate() const { + if (jitCode) { + jitCode(); + return &workspace[workspace.size()-width]; + } + for (int i = 0; i < variablesToCopy.size(); i++) + for (int j = 0; j < width; j++) + variablesToCopy[i].first[j] = variablesToCopy[i].second[j]; + + // Loop over the operations and evaluate each one. + + for (int step = 0; step < operation.size(); step++) { + const vector& args = arguments[step]; + if (args.size() == 1) { + for (int j = 0; j < width; j++) { + for (int i = 0; i < operation[step]->getNumArguments(); i++) + argValues[i] = workspace[(args[0]+i)*width+j]; + workspace[target[step]*width+j] = operation[step]->evaluate(&argValues[0], dummyVariables); + } + } else { + for (int j = 0; j < width; j++) { + for (int i = 0; i < args.size(); i++) + argValues[i] = workspace[args[i]*width+j]; + workspace[target[step]*width+j] = operation[step]->evaluate(&argValues[0], dummyVariables); + } + } + } + return &workspace[workspace.size()-width]; +} + +#ifdef LEPTON_USE_JIT + +static double evaluateOperation(Operation* op, double* args) { + static map dummyVariables; + return op->evaluate(args, dummyVariables); +} + +void CompiledVectorExpression::findPowerGroups(vector >& groups, vector >& groupPowers, vector& stepGroup) { + // Identify every step that raises an argument to an integer power. + + vector stepPower(operation.size(), 0); + vector stepArg(operation.size(), -1); + for (int step = 0; step < operation.size(); step++) { + Operation& op = *operation[step]; + int power = 0; + if (op.getId() == Operation::SQUARE) + power = 2; + else if (op.getId() == Operation::CUBE) + power = 3; + else if (op.getId() == Operation::POWER_CONSTANT) { + double realPower = dynamic_cast (&op)->getValue(); + if (realPower == (int) realPower) + power = (int) realPower; + } + if (power != 0) { + stepPower[step] = power; + stepArg[step] = arguments[step][0]; + } + } + + // Find groups that operate on the same argument and whose powers have the same sign. + + stepGroup.resize(operation.size(), -1); + for (int i = 0; i < operation.size(); i++) { + if (stepGroup[i] != -1) + continue; + vector group, power; + for (int j = i; j < operation.size(); j++) { + if (stepArg[i] == stepArg[j] && stepPower[i] * stepPower[j] > 0) { + stepGroup[j] = groups.size(); + group.push_back(j); + power.push_back(stepPower[j]); + } + } + groups.push_back(group); + groupPowers.push_back(power); + } +} + +#if defined(__ARM__) || defined(__ARM64__) + +void CompiledVectorExpression::generateJitCode() { + CodeHolder code; + code.init(runtime.environment()); + a64::Compiler c(&code); + c.addFunc(FuncSignatureT()); + vector workspaceVar(workspace.size()/width); + for (int i = 0; i < (int) workspaceVar.size(); i++) + workspaceVar[i] = c.newVecQ(); + arm::Gp argsPointer = c.newIntPtr(); + c.mov(argsPointer, imm(&argValues[0])); + vector > groups, groupPowers; + vector stepGroup; + findPowerGroups(groups, groupPowers, stepGroup); + + // Load the arguments into variables. + + arm::Gp variablePointer = c.newIntPtr(); + for (set::const_iterator iter = variableNames.begin(); iter != variableNames.end(); ++iter) { + map::iterator index = variableIndices.find(*iter); + c.mov(variablePointer, imm(getVariablePointer(index->first))); + c.ldr(workspaceVar[index->second].s4(), arm::ptr(variablePointer, 0)); + } + + // Make a list of all constants that will be needed for evaluation. + + vector operationConstantIndex(operation.size(), -1); + for (int step = 0; step < (int) operation.size(); step++) { + // Find the constant value (if any) used by this operation. + + Operation& op = *operation[step]; + float value; + if (op.getId() == Operation::CONSTANT) + value = dynamic_cast (op).getValue(); + else if (op.getId() == Operation::ADD_CONSTANT) + value = dynamic_cast (op).getValue(); + else if (op.getId() == Operation::MULTIPLY_CONSTANT) + value = dynamic_cast (op).getValue(); + else if (op.getId() == Operation::RECIPROCAL) + value = 1.0; + else if (op.getId() == Operation::STEP) + value = 1.0; + else if (op.getId() == Operation::DELTA) + value = 1.0; + else if (op.getId() == Operation::POWER_CONSTANT) { + if (stepGroup[step] == -1) + value = dynamic_cast (op).getValue(); + else + value = 1.0; + } else + continue; + + // See if we already have a variable for this constant. + + for (int i = 0; i < (int) constants.size(); i++) + if (value == constants[i]) { + operationConstantIndex[step] = i; + break; + } + if (operationConstantIndex[step] == -1) { + operationConstantIndex[step] = constants.size(); + constants.push_back(value); + } + } + + // Load constants into variables. + + vector constantVar(constants.size()); + if (constants.size() > 0) { + arm::Gp constantsPointer = c.newIntPtr(); + for (int i = 0; i < (int) constants.size(); i++) { + c.mov(constantsPointer, imm(&constants[i])); + constantVar[i] = c.newVecQ(); + c.ld1r(constantVar[i].s4(), arm::ptr(constantsPointer)); + } + } + + // Evaluate the operations. + + vector hasComputedPower(operation.size(), false); + arm::Vec argReg = c.newVecS(); + arm::Vec doubleArgReg = c.newVecD(); + arm::Vec doubleResultReg = c.newVecD(); + for (int step = 0; step < (int) operation.size(); step++) { + if (hasComputedPower[step]) + continue; + + // When one or more steps involve raising the same argument to multiple integer + // powers, we can compute them all together for efficiency. + + if (stepGroup[step] != -1) { + vector& group = groups[stepGroup[step]]; + vector& powers = groupPowers[stepGroup[step]]; + arm::Vec multiplier = c.newVecQ(); + if (powers[0] > 0) + c.mov(multiplier.s4(), workspaceVar[arguments[step][0]].s4()); + else { + c.fdiv(multiplier.s4(), constantVar[operationConstantIndex[step]].s4(), workspaceVar[arguments[step][0]].s4()); + for (int i = 0; i < powers.size(); i++) + powers[i] = -powers[i]; + } + vector hasAssigned(group.size(), false); + bool done = false; + while (!done) { + done = true; + for (int i = 0; i < group.size(); i++) { + if (powers[i] % 2 == 1) { + if (!hasAssigned[i]) + c.mov(workspaceVar[target[group[i]]].s4(), multiplier.s4()); + else + c.fmul(workspaceVar[target[group[i]]].s4(), workspaceVar[target[group[i]]].s4(), multiplier.s4()); + hasAssigned[i] = true; + } + powers[i] >>= 1; + if (powers[i] != 0) + done = false; + } + if (!done) + c.fmul(multiplier.s4(), multiplier.s4(), multiplier.s4()); + } + for (int step : group) + hasComputedPower[step] = true; + continue; + } + + // Evaluate the step. + + Operation& op = *operation[step]; + vector args = arguments[step]; + if (args.size() == 1) { + // One or more sequential arguments. Fill out the list. + + for (int i = 1; i < op.getNumArguments(); i++) + args.push_back(args[0] + i); + } + + // Generate instructions to execute this operation. + + switch (op.getId()) { + case Operation::CONSTANT: + c.mov(workspaceVar[target[step]].s4(), constantVar[operationConstantIndex[step]].s4()); + break; + case Operation::ADD: + c.fadd(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[1]].s4()); + break; + case Operation::SUBTRACT: + c.fsub(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[1]].s4()); + break; + case Operation::MULTIPLY: + c.fmul(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[1]].s4()); + break; + case Operation::DIVIDE: + c.fdiv(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[1]].s4()); + break; + case Operation::POWER: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], powf); + break; + case Operation::NEGATE: + c.fneg(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::SQRT: + c.fsqrt(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::EXP: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], expf); + break; + case Operation::LOG: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], logf); + break; + case Operation::SIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinf); + break; + case Operation::COS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cosf); + break; + case Operation::TAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanf); + break; + case Operation::ASIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], asinf); + break; + case Operation::ACOS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], acosf); + break; + case Operation::ATAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], atanf); + break; + case Operation::ATAN2: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], atan2f); + break; + case Operation::SINH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinhf); + break; + case Operation::COSH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], coshf); + break; + case Operation::TANH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanhf); + break; + case Operation::STEP: + c.cmge(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), imm(0)); + c.and_(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::DELTA: + c.cmeq(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), imm(0)); + c.and_(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::SQUARE: + c.fmul(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::CUBE: + c.fmul(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[0]].s4()); + c.fmul(workspaceVar[target[step]].s4(), workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::RECIPROCAL: + c.fdiv(workspaceVar[target[step]].s4(), constantVar[operationConstantIndex[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::ADD_CONSTANT: + c.fadd(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), constantVar[operationConstantIndex[step]].s4()); + break; + case Operation::MULTIPLY_CONSTANT: + c.fmul(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), constantVar[operationConstantIndex[step]].s4()); + break; + case Operation::POWER_CONSTANT: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]], powf); + break; + case Operation::MIN: + c.fmin(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[1]].s4()); + break; + case Operation::MAX: + c.fmax(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), workspaceVar[args[1]].s4()); + break; + case Operation::ABS: + c.fabs(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::FLOOR: + c.frintm(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::CEIL: + c.frintp(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4()); + break; + case Operation::SELECT: + c.fcmeq(workspaceVar[target[step]].s4(), workspaceVar[args[0]].s4(), imm(0)); + c.bsl(workspaceVar[target[step]], workspaceVar[args[2]], workspaceVar[args[1]]); + break; + default: + // Just invoke evaluateOperation(). + for (int element = 0; element < width; element++) { + for (int i = 0; i < (int) args.size(); i++) { + c.ins(argReg.s(0), workspaceVar[args[i]].s(element)); + c.fcvt(doubleArgReg, argReg); + c.str(doubleArgReg, arm::ptr(argsPointer, 8*i)); + } + arm::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) evaluateOperation)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, imm(&op)); + invoke->setArg(1, imm(&argValues[0])); + invoke->setRet(0, doubleResultReg); + c.fcvt(argReg, doubleResultReg); + c.ins(workspaceVar[target[step]].s(element), argReg.s(0)); + } + } + } + arm::Gp resultPointer = c.newIntPtr(); + c.mov(resultPointer, imm(&workspace[workspace.size()-width])); + c.str(workspaceVar.back().s4(), arm::ptr(resultPointer, 0)); + c.endFunc(); + c.finalize(); + runtime.add(&jitCode, &code); +} + +void CompiledVectorExpression::generateSingleArgCall(a64::Compiler& c, arm::Vec& dest, arm::Vec& arg, float (*function)(float)) { + arm::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) function)); + arm::Vec a = c.newVecS(); + arm::Vec d = c.newVecS(); + for (int element = 0; element < width; element++) { + c.ins(a.s(0), arg.s(element)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, a); + invoke->setRet(0, d); + c.ins(dest.s(element), d.s(0)); + } +} + +void CompiledVectorExpression::generateTwoArgCall(a64::Compiler& c, arm::Vec& dest, arm::Vec& arg1, arm::Vec& arg2, float (*function)(float, float)) { + arm::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) function)); + arm::Vec a1 = c.newVecS(); + arm::Vec a2 = c.newVecS(); + arm::Vec d = c.newVecS(); + for (int element = 0; element < width; element++) { + c.ins(a1.s(0), arg1.s(element)); + c.ins(a2.s(0), arg2.s(element)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, a1); + invoke->setArg(1, a2); + invoke->setRet(0, d); + c.ins(dest.s(element), d.s(0)); + } +} +#else + +void CompiledVectorExpression::generateJitCode() { + const CpuInfo& cpu = CpuInfo::host(); + if (!cpu.hasFeature(CpuFeatures::X86::kAVX)) + return; + CodeHolder code; + code.init(runtime.environment()); + x86::Compiler c(&code); + FuncNode* funcNode = c.addFunc(FuncSignatureT()); + funcNode->frame().setAvxEnabled(); + vector workspaceVar(workspace.size()/width); + for (int i = 0; i < (int) workspaceVar.size(); i++) + workspaceVar[i] = c.newYmmPs(); + x86::Gp argsPointer = c.newIntPtr(); + c.mov(argsPointer, imm(&argValues[0])); + vector > groups, groupPowers; + vector stepGroup; + findPowerGroups(groups, groupPowers, stepGroup); + + // Load the arguments into variables. + + for (set::const_iterator iter = variableNames.begin(); iter != variableNames.end(); ++iter) { + map::iterator index = variableIndices.find(*iter); + x86::Gp variablePointer = c.newIntPtr(); + c.mov(variablePointer, imm(getVariablePointer(index->first))); + if (width == 4) + c.vmovdqu(workspaceVar[index->second].xmm(), x86::ptr(variablePointer, 0, 0)); + else + c.vmovdqu(workspaceVar[index->second], x86::ptr(variablePointer, 0, 0)); + } + + // Make a list of all constants that will be needed for evaluation. + + vector operationConstantIndex(operation.size(), -1); + for (int step = 0; step < (int) operation.size(); step++) { + // Find the constant value (if any) used by this operation. + + Operation& op = *operation[step]; + double value; + if (op.getId() == Operation::CONSTANT) + value = dynamic_cast (op).getValue(); + else if (op.getId() == Operation::ADD_CONSTANT) + value = dynamic_cast (op).getValue(); + else if (op.getId() == Operation::MULTIPLY_CONSTANT) + value = dynamic_cast (op).getValue(); + else if (op.getId() == Operation::RECIPROCAL) + value = 1.0; + else if (op.getId() == Operation::STEP) + value = 1.0; + else if (op.getId() == Operation::DELTA) + value = 1.0; + else if (op.getId() == Operation::ABS) { + int mask = 0x7FFFFFFF; + value = *reinterpret_cast(&mask); + } + else if (op.getId() == Operation::POWER_CONSTANT) { + if (stepGroup[step] == -1) + value = dynamic_cast (op).getValue(); + else + value = 1.0; + } else + continue; + + // See if we already have a variable for this constant. + + for (int i = 0; i < (int) constants.size(); i++) + if (value == constants[i]) { + operationConstantIndex[step] = i; + break; + } + if (operationConstantIndex[step] == -1) { + operationConstantIndex[step] = constants.size(); + constants.push_back(value); + } + } + + // Load constants into variables. + + vector constantVar(constants.size()); + if (constants.size() > 0) { + x86::Gp constantsPointer = c.newIntPtr(); + c.mov(constantsPointer, imm(&constants[0])); + for (int i = 0; i < (int) constants.size(); i++) { + constantVar[i] = c.newYmmPs(); + c.vbroadcastss(constantVar[i], x86::ptr(constantsPointer, 4*i, 0)); + } + } + + // Evaluate the operations. + + vector hasComputedPower(operation.size(), false); + x86::Ymm argReg = c.newYmm(); + x86::Ymm doubleArgReg = c.newYmm(); + x86::Ymm doubleResultReg = c.newYmm(); + for (int step = 0; step < (int) operation.size(); step++) { + if (hasComputedPower[step]) + continue; + + // When one or more steps involve raising the same argument to multiple integer + // powers, we can compute them all together for efficiency. + + if (stepGroup[step] != -1) { + vector& group = groups[stepGroup[step]]; + vector& powers = groupPowers[stepGroup[step]]; + x86::Ymm multiplier = c.newYmmPs(); + if (powers[0] > 0) + c.vmovdqu(multiplier, workspaceVar[arguments[step][0]]); + else { + c.vdivps(multiplier, constantVar[operationConstantIndex[step]], workspaceVar[arguments[step][0]]); + for (int i = 0; i < powers.size(); i++) + powers[i] = -powers[i]; + } + vector hasAssigned(group.size(), false); + bool done = false; + while (!done) { + done = true; + for (int i = 0; i < group.size(); i++) { + if (powers[i] % 2 == 1) { + if (!hasAssigned[i]) + c.vmovdqu(workspaceVar[target[group[i]]], multiplier); + else + c.vmulps(workspaceVar[target[group[i]]], workspaceVar[target[group[i]]], multiplier); + hasAssigned[i] = true; + } + powers[i] >>= 1; + if (powers[i] != 0) + done = false; + } + if (!done) + c.vmulps(multiplier, multiplier, multiplier); + } + for (int step : group) + hasComputedPower[step] = true; + continue; + } + + // Evaluate the step. + + Operation& op = *operation[step]; + vector args = arguments[step]; + if (args.size() == 1) { + // One or more sequential arguments. Fill out the list. + + for (int i = 1; i < op.getNumArguments(); i++) + args.push_back(args[0] + i); + } + + // Generate instructions to execute this operation. + + switch (op.getId()) { + case Operation::CONSTANT: + c.vmovdqu(workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::ADD: + c.vaddps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::SUBTRACT: + c.vsubps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::MULTIPLY: + c.vmulps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::DIVIDE: + c.vdivps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::POWER: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], powf); + break; + case Operation::NEGATE: + c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]); + c.vsubps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::SQRT: + c.vsqrtps(workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::EXP: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], expf); + break; + case Operation::LOG: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], logf); + break; + case Operation::SIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinf); + break; + case Operation::COS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], cosf); + break; + case Operation::TAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanf); + break; + case Operation::ASIN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], asinf); + break; + case Operation::ACOS: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], acosf); + break; + case Operation::ATAN: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], atanf); + break; + case Operation::ATAN2: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]], atan2f); + break; + case Operation::SINH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], sinhf); + break; + case Operation::COSH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], coshf); + break; + case Operation::TANH: + generateSingleArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], tanhf); + break; + case Operation::STEP: + c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]); + c.vcmpps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]], imm(18)); // Comparison mode is _CMP_LE_OQ = 18 + c.vandps(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::DELTA: + c.vxorps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[target[step]]); + c.vcmpps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]], imm(16)); // Comparison mode is _CMP_EQ_OQ = 0 + c.vandps(workspaceVar[target[step]], workspaceVar[target[step]], constantVar[operationConstantIndex[step]]); + break; + case Operation::SQUARE: + c.vmulps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + break; + case Operation::CUBE: + c.vmulps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[0]]); + c.vmulps(workspaceVar[target[step]], workspaceVar[target[step]], workspaceVar[args[0]]); + break; + case Operation::RECIPROCAL: + c.vdivps(workspaceVar[target[step]], constantVar[operationConstantIndex[step]], workspaceVar[args[0]]); + break; + case Operation::ADD_CONSTANT: + c.vaddps(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::MULTIPLY_CONSTANT: + c.vmulps(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::POWER_CONSTANT: + generateTwoArgCall(c, workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]], powf); + break; + case Operation::MIN: + c.vminps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::MAX: + c.vmaxps(workspaceVar[target[step]], workspaceVar[args[0]], workspaceVar[args[1]]); + break; + case Operation::ABS: + c.vandps(workspaceVar[target[step]], workspaceVar[args[0]], constantVar[operationConstantIndex[step]]); + break; + case Operation::FLOOR: + c.vroundps(workspaceVar[target[step]], workspaceVar[args[0]], imm(1)); + break; + case Operation::CEIL: + c.vroundps(workspaceVar[target[step]], workspaceVar[args[0]], imm(2)); + break; + case Operation::SELECT: + { + x86::Ymm mask = c.newYmmPs(); + c.vxorps(mask, mask, mask); + c.vcmpps(mask, mask, workspaceVar[args[0]], imm(0)); // Comparison mode is _CMP_EQ_OQ = 0 + c.vblendvps(workspaceVar[target[step]], workspaceVar[args[1]], workspaceVar[args[2]], mask); + break; + } + default: + // Just invoke evaluateOperation(). + + for (int element = 0; element < width; element++) { + for (int i = 0; i < (int) args.size(); i++) { + if (element < 4) + c.vshufps(argReg, workspaceVar[args[i]], workspaceVar[args[i]], imm(element)); + else { + c.vperm2f128(argReg, workspaceVar[args[i]], workspaceVar[args[i]], imm(1)); + c.vshufps(argReg, argReg, argReg, imm(element-4)); + } + c.vcvtss2sd(doubleArgReg.xmm(), doubleArgReg.xmm(), argReg.xmm()); + c.vmovsd(x86::ptr(argsPointer, 8*i, 0), doubleArgReg.xmm()); + } + x86::Gp fn = c.newIntPtr(); + c.mov(fn, imm((void*) evaluateOperation)); + InvokeNode* invoke; + c.invoke(&invoke, fn, FuncSignatureT()); + invoke->setArg(0, imm(&op)); + invoke->setArg(1, imm(&argValues[0])); + invoke->setRet(0, doubleResultReg); + c.vcvtsd2ss(argReg.xmm(), argReg.xmm(), doubleResultReg.xmm()); + if (element > 3) + c.vperm2f128(argReg, argReg, argReg, imm(0)); + if (element != 0) + c.vshufps(argReg, argReg, argReg, imm(0)); + c.vblendps(workspaceVar[target[step]], workspaceVar[target[step]], argReg, 1<()); + invoke->setArg(0, a); + invoke->setRet(0, d); + if (element > 3) + c.vperm2f128(d, d, d, imm(0)); + if (element != 0) + c.vshufps(d, d, d, imm(0)); + c.vblendps(dest, dest, d, 1<()); + invoke->setArg(0, a1); + invoke->setArg(1, a2); + invoke->setRet(0, d); + if (element > 3) + c.vperm2f128(d, d, d, imm(0)); + if (element != 0) + c.vshufps(d, d, d, imm(0)); + c.vblendps(dest, dest, d, 1<getNumArguments(); + if (args > maxArgs) + maxArgs = args; + currentStackSize += 1-args; + if (currentStackSize > stackSize) + stackSize = currentStackSize; + } +} + +ExpressionProgram::~ExpressionProgram() { + for (int i = 0; i < (int) operations.size(); i++) + delete operations[i]; +} + +ExpressionProgram::ExpressionProgram(const ExpressionProgram& program) { + *this = program; +} + +ExpressionProgram& ExpressionProgram::operator=(const ExpressionProgram& program) { + maxArgs = program.maxArgs; + stackSize = program.stackSize; + operations.resize(program.operations.size()); + for (int i = 0; i < (int) operations.size(); i++) + operations[i] = program.operations[i]->clone(); + return *this; +} + +void ExpressionProgram::buildProgram(const ExpressionTreeNode& node) { + for (int i = (int) node.getChildren().size()-1; i >= 0; i--) + buildProgram(node.getChildren()[i]); + operations.push_back(node.getOperation().clone()); +} + +int ExpressionProgram::getNumOperations() const { + return (int) operations.size(); +} + +const Operation& ExpressionProgram::getOperation(int index) const { + return *operations[index]; +} + +void ExpressionProgram::setOperation(int index, Operation* operation) { + delete operations[index]; + operations[index] = operation; +} + +int ExpressionProgram::getStackSize() const { + return stackSize; +} + +double ExpressionProgram::evaluate() const { + return evaluate(map()); +} + +double ExpressionProgram::evaluate(const std::map& variables) const { + vector stack(stackSize+1); + int stackPointer = stackSize; + for (int i = 0; i < (int) operations.size(); i++) { + int numArgs = operations[i]->getNumArguments(); + double result = operations[i]->evaluate(&stack[stackPointer], variables); + stackPointer += numArgs-1; + stack[stackPointer] = result; + } + return stack[stackSize-1]; +} diff --git a/src/external/lepton/src/ExpressionTreeNode.cpp b/src/external/lepton/src/ExpressionTreeNode.cpp new file mode 100644 index 00000000000..78432c1bfc7 --- /dev/null +++ b/src/external/lepton/src/ExpressionTreeNode.cpp @@ -0,0 +1,153 @@ +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2021 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "lepton/ExpressionTreeNode.h" +#include "lepton/Exception.h" +#include "lepton/Operation.h" +#include + +using namespace Lepton; +using namespace std; + +ExpressionTreeNode::ExpressionTreeNode(Operation* operation, const vector& children) : operation(operation), children(children) { + if (operation->getNumArguments() != children.size()) + throw Exception("wrong number of arguments to function: "+operation->getName()); +} + +ExpressionTreeNode::ExpressionTreeNode(Operation* operation, const ExpressionTreeNode& child1, const ExpressionTreeNode& child2) : operation(operation) { + children.push_back(child1); + children.push_back(child2); + if (operation->getNumArguments() != children.size()) + throw Exception("wrong number of arguments to function: "+operation->getName()); +} + +ExpressionTreeNode::ExpressionTreeNode(Operation* operation, const ExpressionTreeNode& child) : operation(operation) { + children.push_back(child); + if (operation->getNumArguments() != children.size()) + throw Exception("wrong number of arguments to function: "+operation->getName()); +} + +ExpressionTreeNode::ExpressionTreeNode(Operation* operation) : operation(operation) { + if (operation->getNumArguments() != children.size()) + throw Exception("wrong number of arguments to function: "+operation->getName()); +} + +ExpressionTreeNode::ExpressionTreeNode(const ExpressionTreeNode& node) : operation(node.operation == NULL ? NULL : node.operation->clone()), children(node.getChildren()) { +} + +ExpressionTreeNode::ExpressionTreeNode(ExpressionTreeNode&& node) : operation(node.operation), children(move(node.children)) { + node.operation = NULL; + node.children.clear(); +} + +ExpressionTreeNode::ExpressionTreeNode() : operation(NULL) { +} + +ExpressionTreeNode::~ExpressionTreeNode() { + if (operation != NULL) + delete operation; +} + +bool ExpressionTreeNode::operator!=(const ExpressionTreeNode& node) const { + if (node.getOperation() != getOperation()) + return true; + if (getOperation().isSymmetric() && getChildren().size() == 2) { + if (getChildren()[0] == node.getChildren()[0] && getChildren()[1] == node.getChildren()[1]) + return false; + if (getChildren()[0] == node.getChildren()[1] && getChildren()[1] == node.getChildren()[0]) + return false; + return true; + } + for (int i = 0; i < (int) getChildren().size(); i++) + if (getChildren()[i] != node.getChildren()[i]) + return true; + return false; +} + +bool ExpressionTreeNode::operator==(const ExpressionTreeNode& node) const { + return !(*this != node); +} + +ExpressionTreeNode& ExpressionTreeNode::operator=(const ExpressionTreeNode& node) { + if (operation != NULL) + delete operation; + operation = node.getOperation().clone(); + children = node.getChildren(); + return *this; +} + +ExpressionTreeNode& ExpressionTreeNode::operator=(ExpressionTreeNode&& node) { + if (operation != NULL) + delete operation; + operation = node.operation; + children = move(node.children); + node.operation = NULL; + node.children.clear(); + return *this; +} + +const Operation& ExpressionTreeNode::getOperation() const { + return *operation; +} + +const vector& ExpressionTreeNode::getChildren() const { + return children; +} + +void ExpressionTreeNode::assignTags(vector& examples) const { + // Assign tag values to all nodes in a tree, such that two nodes have the same + // tag if and only if they (and all their children) are equal. This is used to + // optimize other operations. + + int numTags = examples.size(); + for (const ExpressionTreeNode& child : getChildren()) + child.assignTags(examples); + if (numTags == examples.size()) { + // All the children matched existing tags, so possibly this node does too. + + for (int i = 0; i < examples.size(); i++) { + const ExpressionTreeNode& example = *examples[i]; + bool matches = (getChildren().size() == example.getChildren().size() && getOperation() == example.getOperation()); + for (int j = 0; matches && j < getChildren().size(); j++) + if (getChildren()[j].tag != example.getChildren()[j].tag) + matches = false; + if (matches) { + tag = i; + return; + } + } + } + + // This node does not match any previous node, so assign a new tag. + + tag = examples.size(); + examples.push_back(this); +} diff --git a/src/external/lepton/src/MSVC_erfc.h b/src/external/lepton/src/MSVC_erfc.h new file mode 100644 index 00000000000..2c6b619e895 --- /dev/null +++ b/src/external/lepton/src/MSVC_erfc.h @@ -0,0 +1,91 @@ +#ifndef LEPTON_MSVC_ERFC_H_ +#define LEPTON_MSVC_ERFC_H_ + +/* + * Up to version 11 (VC++ 2012), Microsoft does not support the + * standard C99 erf() and erfc() functions so we have to fake them here. + * These were added in version 12 (VC++ 2013), which sets _MSC_VER=1800 + * (VC11 has _MSC_VER=1700). + */ + +#if defined(_MSC_VER) || defined(__MINGW32__) +#if !defined(M_PI) +#define M_PI 3.14159265358979323846264338327950288 +#endif +#endif + +#if defined(_MSC_VER) +#if _MSC_VER <= 1700 // 1700 is VC11, 1800 is VC12 +/*************************** +* erf.cpp +* author: Steve Strand +* written: 29-Jan-04 +***************************/ + +#include + +static const double rel_error= 1E-12; //calculate 12 significant figures +//you can adjust rel_error to trade off between accuracy and speed +//but don't ask for > 15 figures (assuming usual 52 bit mantissa in a double) + +static double erfc(double x); + +static double erf(double x) +//erf(x) = 2/sqrt(pi)*integral(exp(-t^2),t,0,x) +// = 2/sqrt(pi)*[x - x^3/3 + x^5/5*2! - x^7/7*3! + ...] +// = 1-erfc(x) +{ + static const double two_sqrtpi= 1.128379167095512574; // 2/sqrt(pi) + if (fabs(x) > 2.2) { + return 1.0 - erfc(x); //use continued fraction when fabs(x) > 2.2 + } + double sum= x, term= x, xsqr= x*x; + int j= 1; + do { + term*= xsqr/j; + sum-= term/(2*j+1); + ++j; + term*= xsqr/j; + sum+= term/(2*j+1); + ++j; + } while (fabs(term)/sum > rel_error); + return two_sqrtpi*sum; +} + + +static double erfc(double x) +//erfc(x) = 2/sqrt(pi)*integral(exp(-t^2),t,x,inf) +// = exp(-x^2)/sqrt(pi) * [1/x+ (1/2)/x+ (2/2)/x+ (3/2)/x+ (4/2)/x+ ...] +// = 1-erf(x) +//expression inside [] is a continued fraction so '+' means add to denominator only +{ + static const double one_sqrtpi= 0.564189583547756287; // 1/sqrt(pi) + if (fabs(x) < 2.2) { + return 1.0 - erf(x); //use series when fabs(x) < 2.2 + } + // Don't look for x==0 here! + if (x < 0) { //continued fraction only valid for x>0 + return 2.0 - erfc(-x); + } + double a=1, b=x; //last two convergent numerators + double c=x, d=x*x+0.5; //last two convergent denominators + double q1, q2= b/d; //last two convergents (a/c and b/d) + double n= 1.0, t; + do { + t= a*n+b*x; + a= b; + b= t; + t= c*n+d*x; + c= d; + d= t; + n+= 0.5; + q1= q2; + q2= b/d; + } while (fabs(q1-q2)/q2 > rel_error); + return one_sqrtpi*exp(-x*x)*q2; +} + +#endif // _MSC_VER <= 1700 +#endif // _MSC_VER + +#endif // LEPTON_MSVC_ERFC_H_ diff --git a/src/external/lepton/src/Operation.cpp b/src/external/lepton/src/Operation.cpp new file mode 100644 index 00000000000..b5a958b2f7d --- /dev/null +++ b/src/external/lepton/src/Operation.cpp @@ -0,0 +1,425 @@ + +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2021 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "lepton/Operation.h" +#include "lepton/ExpressionTreeNode.h" +#include "MSVC_erfc.h" + +using namespace Lepton; +using namespace std; + +static bool isZero(const ExpressionTreeNode& node) { + if (node.getOperation().getId() != Operation::CONSTANT) + return false; + return dynamic_cast(node.getOperation()).getValue() == 0.0; +} + +double Operation::Erf::evaluate(double* args, const map& variables) const { + return erf(args[0]); +} + +double Operation::Erfc::evaluate(double* args, const map& variables) const { + return erfc(args[0]); +} + +ExpressionTreeNode Operation::Constant::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Variable::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (variable == name) + return ExpressionTreeNode(new Operation::Constant(1.0)); + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Custom::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (function->getNumArguments() == 0) + return ExpressionTreeNode(new Operation::Constant(0.0)); + ExpressionTreeNode result; + bool foundTerm = false; + for (int i = 0; i < getNumArguments(); i++) { + if (!isZero(childDerivs[i])) { + if (foundTerm) + result = ExpressionTreeNode(new Operation::Add(), + result, + ExpressionTreeNode(new Operation::Multiply(), ExpressionTreeNode(new Operation::Custom(*this, i), children), childDerivs[i])); + else { + result = ExpressionTreeNode(new Operation::Multiply(), ExpressionTreeNode(new Operation::Custom(*this, i), children), childDerivs[i]); + foundTerm = true; + } + } + } + if (foundTerm) + return result; + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Add::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return childDerivs[1]; + if (isZero(childDerivs[1])) + return childDerivs[0]; + return ExpressionTreeNode(new Operation::Add(), childDerivs[0], childDerivs[1]); +} + +ExpressionTreeNode Operation::Subtract::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) { + if (isZero(childDerivs[1])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Negate(), childDerivs[1]); + } + if (isZero(childDerivs[1])) + return childDerivs[0]; + return ExpressionTreeNode(new Operation::Subtract(), childDerivs[0], childDerivs[1]); +} + +ExpressionTreeNode Operation::Multiply::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) { + if (isZero(childDerivs[1])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), children[0], childDerivs[1]); + } + if (isZero(childDerivs[1])) + return ExpressionTreeNode(new Operation::Multiply(), children[1], childDerivs[0]); + return ExpressionTreeNode(new Operation::Add(), + ExpressionTreeNode(new Operation::Multiply(), children[0], childDerivs[1]), + ExpressionTreeNode(new Operation::Multiply(), children[1], childDerivs[0])); +} + +ExpressionTreeNode Operation::Divide::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + ExpressionTreeNode subexp; + if (isZero(childDerivs[0])) { + if (isZero(childDerivs[1])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + subexp = ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Multiply(), children[0], childDerivs[1])); + } + else if (isZero(childDerivs[1])) + subexp = ExpressionTreeNode(new Operation::Multiply(), children[1], childDerivs[0]); + else + subexp = ExpressionTreeNode(new Operation::Subtract(), + ExpressionTreeNode(new Operation::Multiply(), children[1], childDerivs[0]), + ExpressionTreeNode(new Operation::Multiply(), children[0], childDerivs[1])); + return ExpressionTreeNode(new Operation::Divide(), subexp, ExpressionTreeNode(new Operation::Square(), children[1])); +} + +ExpressionTreeNode Operation::Power::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Add(), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Multiply(), + children[1], + ExpressionTreeNode(new Operation::Power(), + children[0], ExpressionTreeNode(new Operation::AddConstant(-1.0), children[1]))), + childDerivs[0]), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Log(), children[0]), + ExpressionTreeNode(new Operation::Power(), children[0], children[1])), + childDerivs[1])); +} + +ExpressionTreeNode Operation::Negate::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Negate(), childDerivs[0]); +} + +ExpressionTreeNode Operation::Sqrt::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::MultiplyConstant(0.5), + ExpressionTreeNode(new Operation::Reciprocal(), + ExpressionTreeNode(new Operation::Sqrt(), children[0]))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Exp::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Exp(), children[0]), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Log::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Reciprocal(), children[0]), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Sin::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Cos(), children[0]), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Cos::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Sin(), children[0])), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Sec::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Sec(), children[0]), + ExpressionTreeNode(new Operation::Tan(), children[0])), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Csc::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Csc(), children[0]), + ExpressionTreeNode(new Operation::Cot(), children[0]))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Tan::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Square(), + ExpressionTreeNode(new Operation::Sec(), children[0])), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Cot::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Square(), + ExpressionTreeNode(new Operation::Csc(), children[0]))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Asin::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Reciprocal(), + ExpressionTreeNode(new Operation::Sqrt(), + ExpressionTreeNode(new Operation::Subtract(), + ExpressionTreeNode(new Operation::Constant(1.0)), + ExpressionTreeNode(new Operation::Square(), children[0])))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Acos::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Reciprocal(), + ExpressionTreeNode(new Operation::Sqrt(), + ExpressionTreeNode(new Operation::Subtract(), + ExpressionTreeNode(new Operation::Constant(1.0)), + ExpressionTreeNode(new Operation::Square(), children[0]))))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Atan::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Reciprocal(), + ExpressionTreeNode(new Operation::AddConstant(1.0), + ExpressionTreeNode(new Operation::Square(), children[0]))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Atan2::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Divide(), + ExpressionTreeNode(new Operation::Subtract(), + ExpressionTreeNode(new Operation::Multiply(), children[1], childDerivs[0]), + ExpressionTreeNode(new Operation::Multiply(), children[0], childDerivs[1])), + ExpressionTreeNode(new Operation::Add(), + ExpressionTreeNode(new Operation::Square(), children[0]), + ExpressionTreeNode(new Operation::Square(), children[1]))); +} + +ExpressionTreeNode Operation::Sinh::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Cosh(), + children[0]), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Cosh::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Sinh(), + children[0]), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Tanh::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Subtract(), + ExpressionTreeNode(new Operation::Constant(1.0)), + ExpressionTreeNode(new Operation::Square(), + ExpressionTreeNode(new Operation::Tanh(), children[0]))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Erf::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Constant(2.0/sqrt(M_PI))), + ExpressionTreeNode(new Operation::Exp(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Square(), children[0])))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Erfc::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Constant(-2.0/sqrt(M_PI))), + ExpressionTreeNode(new Operation::Exp(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Square(), children[0])))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Step::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Delta::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Square::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::MultiplyConstant(2.0), + children[0]), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Cube::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::MultiplyConstant(3.0), + ExpressionTreeNode(new Operation::Square(), children[0])), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Reciprocal::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::Negate(), + ExpressionTreeNode(new Operation::Reciprocal(), + ExpressionTreeNode(new Operation::Square(), children[0]))), + childDerivs[0]); +} + +ExpressionTreeNode Operation::AddConstant::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return childDerivs[0]; +} + +ExpressionTreeNode Operation::MultiplyConstant::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::MultiplyConstant(value), + childDerivs[0]); +} + +ExpressionTreeNode Operation::PowerConstant::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + return ExpressionTreeNode(new Operation::Multiply(), + ExpressionTreeNode(new Operation::MultiplyConstant(value), + ExpressionTreeNode(new Operation::PowerConstant(value-1), + children[0])), + childDerivs[0]); +} + +ExpressionTreeNode Operation::Min::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + ExpressionTreeNode step(new Operation::Step(), + ExpressionTreeNode(new Operation::Subtract(), children[0], children[1])); + return ExpressionTreeNode(new Operation::Select(), {step, childDerivs[1], childDerivs[0]}); +} + +ExpressionTreeNode Operation::Max::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + ExpressionTreeNode step(new Operation::Step(), + ExpressionTreeNode(new Operation::Subtract(), children[0], children[1])); + return ExpressionTreeNode(new Operation::Select(), {step, childDerivs[0], childDerivs[1]}); +} + +ExpressionTreeNode Operation::Abs::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + if (isZero(childDerivs[0])) + return ExpressionTreeNode(new Operation::Constant(0.0)); + ExpressionTreeNode step(new Operation::Step(), children[0]); + return ExpressionTreeNode(new Operation::Multiply(), + childDerivs[0], + ExpressionTreeNode(new Operation::AddConstant(-1), + ExpressionTreeNode(new Operation::MultiplyConstant(2), step))); +} + +ExpressionTreeNode Operation::Floor::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Ceil::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Constant(0.0)); +} + +ExpressionTreeNode Operation::Select::differentiate(const std::vector& children, const std::vector& childDerivs, const std::string& variable) const { + return ExpressionTreeNode(new Operation::Select(), {children[0], childDerivs[1], childDerivs[2]}); +} diff --git a/src/external/lepton/src/ParsedExpression.cpp b/src/external/lepton/src/ParsedExpression.cpp new file mode 100644 index 00000000000..1145215b0e7 --- /dev/null +++ b/src/external/lepton/src/ParsedExpression.cpp @@ -0,0 +1,430 @@ +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2022 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "lepton/ParsedExpression.h" +#include "lepton/CompiledExpression.h" +#include "lepton/CompiledVectorExpression.h" +#include "lepton/ExpressionProgram.h" +#include "lepton/Operation.h" +#include +#include + +using namespace Lepton; +using namespace std; + +ParsedExpression::ParsedExpression() : rootNode(ExpressionTreeNode()) { +} + +ParsedExpression::ParsedExpression(const ExpressionTreeNode& rootNode) : rootNode(rootNode) { +} + +const ExpressionTreeNode& ParsedExpression::getRootNode() const { + if (&rootNode.getOperation() == NULL) + throw Exception("Illegal call to an initialized ParsedExpression"); + return rootNode; +} + +double ParsedExpression::evaluate() const { + return evaluate(getRootNode(), map()); +} + +double ParsedExpression::evaluate(const map& variables) const { + return evaluate(getRootNode(), variables); +} + +double ParsedExpression::evaluate(const ExpressionTreeNode& node, const map& variables) { + int numArgs = (int) node.getChildren().size(); + vector args(max(numArgs, 1)); + for (int i = 0; i < numArgs; i++) + args[i] = evaluate(node.getChildren()[i], variables); + return node.getOperation().evaluate(&args[0], variables); +} + +ParsedExpression ParsedExpression::optimize() const { + ExpressionTreeNode result = getRootNode(); + vector examples; + result.assignTags(examples); + map nodeCache; + result = precalculateConstantSubexpressions(result, nodeCache); + while (true) { + examples.clear(); + result.assignTags(examples); + nodeCache.clear(); + ExpressionTreeNode simplified = substituteSimplerExpression(result, nodeCache); + if (simplified == result) + break; + result = simplified; + } + return ParsedExpression(result); +} + +ParsedExpression ParsedExpression::optimize(const map& variables) const { + ExpressionTreeNode result = preevaluateVariables(getRootNode(), variables); + vector examples; + result.assignTags(examples); + map nodeCache; + result = precalculateConstantSubexpressions(result, nodeCache); + while (true) { + examples.clear(); + result.assignTags(examples); + nodeCache.clear(); + ExpressionTreeNode simplified = substituteSimplerExpression(result, nodeCache); + if (simplified == result) + break; + result = simplified; + } + return ParsedExpression(result); +} + +ExpressionTreeNode ParsedExpression::preevaluateVariables(const ExpressionTreeNode& node, const map& variables) { + if (node.getOperation().getId() == Operation::VARIABLE) { + const Operation::Variable& var = dynamic_cast(node.getOperation()); + map::const_iterator iter = variables.find(var.getName()); + if (iter == variables.end()) + return node; + return ExpressionTreeNode(new Operation::Constant(iter->second)); + } + vector children(node.getChildren().size()); + for (int i = 0; i < (int) children.size(); i++) + children[i] = preevaluateVariables(node.getChildren()[i], variables); + return ExpressionTreeNode(node.getOperation().clone(), children); +} + +ExpressionTreeNode ParsedExpression::precalculateConstantSubexpressions(const ExpressionTreeNode& node, map& nodeCache) { + auto cached = nodeCache.find(node.tag); + if (cached != nodeCache.end()) + return cached->second; + vector children(node.getChildren().size()); + for (int i = 0; i < (int) children.size(); i++) + children[i] = precalculateConstantSubexpressions(node.getChildren()[i], nodeCache); + ExpressionTreeNode result = ExpressionTreeNode(node.getOperation().clone(), children); + if (node.getOperation().getId() == Operation::VARIABLE || node.getOperation().getId() == Operation::CUSTOM) { + nodeCache[node.tag] = result; + return result; + } + for (int i = 0; i < (int) children.size(); i++) + if (children[i].getOperation().getId() != Operation::CONSTANT) { + nodeCache[node.tag] = result; + return result; + } + result = ExpressionTreeNode(new Operation::Constant(evaluate(result, map()))); + nodeCache[node.tag] = result; + return result; +} + +ExpressionTreeNode ParsedExpression::substituteSimplerExpression(const ExpressionTreeNode& node, map& nodeCache) { + vector children(node.getChildren().size()); + for (int i = 0; i < (int) children.size(); i++) { + const ExpressionTreeNode& child = node.getChildren()[i]; + auto cached = nodeCache.find(child.tag); + if (cached == nodeCache.end()) { + children[i] = substituteSimplerExpression(child, nodeCache); + nodeCache[child.tag] = children[i]; + } + else + children[i] = cached->second; + } + + // Collect some info on constant expressions in children + bool first_const = children.size() > 0 && isConstant(children[0]); // is first child constant? + bool second_const = children.size() > 1 && isConstant(children[1]); // is second child constant? + double first, second; // if yes, value of first and second child + if (first_const) + first = getConstantValue(children[0]); + if (second_const) + second = getConstantValue(children[1]); + + switch (node.getOperation().getId()) { + case Operation::ADD: + { + if (first_const) { + if (first == 0.0) { // Add 0 + return children[1]; + } else { // Add a constant + return ExpressionTreeNode(new Operation::AddConstant(first), children[1]); + } + } + if (second_const) { + if (second == 0.0) { // Add 0 + return children[0]; + } else { // Add a constant + return ExpressionTreeNode(new Operation::AddConstant(second), children[0]); + } + } + if (children[1].getOperation().getId() == Operation::NEGATE) // a+(-b) = a-b + return ExpressionTreeNode(new Operation::Subtract(), children[0], children[1].getChildren()[0]); + if (children[0].getOperation().getId() == Operation::NEGATE) // (-a)+b = b-a + return ExpressionTreeNode(new Operation::Subtract(), children[1], children[0].getChildren()[0]); + break; + } + case Operation::SUBTRACT: + { + if (children[0] == children[1]) + return ExpressionTreeNode(new Operation::Constant(0.0)); // Subtracting anything from itself is 0 + if (first_const) { + if (first == 0.0) // Subtract from 0 + return ExpressionTreeNode(new Operation::Negate(), children[1]); + } + if (second_const) { + if (second == 0.0) { // Subtract 0 + return children[0]; + } else { // Subtract a constant + return ExpressionTreeNode(new Operation::AddConstant(-second), children[0]); + } + } + if (children[1].getOperation().getId() == Operation::NEGATE) // a-(-b) = a+b + return ExpressionTreeNode(new Operation::Add(), children[0], children[1].getChildren()[0]); + break; + } + case Operation::MULTIPLY: + { + if ((first_const && first == 0.0) || (second_const && second == 0.0)) // Multiply by 0 + return ExpressionTreeNode(new Operation::Constant(0.0)); + if (first_const && first == 1.0) // Multiply by 1 + return children[1]; + if (second_const && second == 1.0) // Multiply by 1 + return children[0]; + if (first_const) { // Multiply by a constant + if (children[1].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine two multiplies into a single one + return ExpressionTreeNode(new Operation::MultiplyConstant(first*dynamic_cast(&children[1].getOperation())->getValue()), children[1].getChildren()[0]); + return ExpressionTreeNode(new Operation::MultiplyConstant(first), children[1]); + } + if (second_const) { // Multiply by a constant + if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine two multiplies into a single one + return ExpressionTreeNode(new Operation::MultiplyConstant(second*dynamic_cast(&children[0].getOperation())->getValue()), children[0].getChildren()[0]); + return ExpressionTreeNode(new Operation::MultiplyConstant(second), children[0]); + } + if (children[0].getOperation().getId() == Operation::NEGATE && children[1].getOperation().getId() == Operation::NEGATE) // The two negations cancel + return ExpressionTreeNode(new Operation::Multiply(), children[0].getChildren()[0], children[1].getChildren()[0]); + if (children[0].getOperation().getId() == Operation::NEGATE && children[1].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Negate the constant + return ExpressionTreeNode(new Operation::Multiply(), children[0].getChildren()[0], ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast(&children[1].getOperation())->getValue()), children[1].getChildren()[0])); + if (children[1].getOperation().getId() == Operation::NEGATE && children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Negate the constant + return ExpressionTreeNode(new Operation::Multiply(), ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast(&children[0].getOperation())->getValue()), children[0].getChildren()[0]), children[1].getChildren()[0]); + if (children[0].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further + return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Multiply(), children[0].getChildren()[0], children[1])); + if (children[1].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further + return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Multiply(), children[0], children[1].getChildren()[0])); + if (children[1].getOperation().getId() == Operation::RECIPROCAL) // a*(1/b) = a/b + return ExpressionTreeNode(new Operation::Divide(), children[0], children[1].getChildren()[0]); + if (children[0].getOperation().getId() == Operation::RECIPROCAL) // (1/a)*b = b/a + return ExpressionTreeNode(new Operation::Divide(), children[1], children[0].getChildren()[0]); + if (children[0] == children[1]) + return ExpressionTreeNode(new Operation::Square(), children[0]); // x*x = square(x) + if (children[0].getOperation().getId() == Operation::SQUARE && children[0].getChildren()[0] == children[1]) + return ExpressionTreeNode(new Operation::Cube(), children[1]); // x*x*x = cube(x) + if (children[1].getOperation().getId() == Operation::SQUARE && children[1].getChildren()[0] == children[0]) + return ExpressionTreeNode(new Operation::Cube(), children[0]); // x*x*x = cube(x) + break; + } + case Operation::DIVIDE: + { + if (children[0] == children[1]) + return ExpressionTreeNode(new Operation::Constant(1.0)); // Dividing anything from itself is 0 + if (first_const && first == 0.0) // 0 divided by something + return ExpressionTreeNode(new Operation::Constant(0.0)); + if (first_const && first == 1.0) // 1 divided by something + return ExpressionTreeNode(new Operation::Reciprocal(), children[1]); + if (second_const && second == 1.0) // Divide by 1 + return children[0]; + if (second_const) { + if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine a multiply and a divide into one multiply + return ExpressionTreeNode(new Operation::MultiplyConstant(dynamic_cast(&children[0].getOperation())->getValue()/second), children[0].getChildren()[0]); + return ExpressionTreeNode(new Operation::MultiplyConstant(1.0/second), children[0]); // Replace a divide with a multiply + } + if (children[0].getOperation().getId() == Operation::NEGATE && children[1].getOperation().getId() == Operation::NEGATE) // The two negations cancel + return ExpressionTreeNode(new Operation::Divide(), children[0].getChildren()[0], children[1].getChildren()[0]); + if (children[1].getOperation().getId() == Operation::NEGATE && children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Negate the constant + return ExpressionTreeNode(new Operation::Divide(), ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast(&children[0].getOperation())->getValue()), children[0].getChildren()[0]), children[1].getChildren()[0]); + if (children[0].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further + return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Divide(), children[0].getChildren()[0], children[1])); + if (children[1].getOperation().getId() == Operation::NEGATE) // Pull the negation out so it can possibly be optimized further + return ExpressionTreeNode(new Operation::Negate(), ExpressionTreeNode(new Operation::Divide(), children[0], children[1].getChildren()[0])); + if (children[1].getOperation().getId() == Operation::RECIPROCAL) // a/(1/b) = a*b + return ExpressionTreeNode(new Operation::Multiply(), children[0], children[1].getChildren()[0]); + break; + } + case Operation::POWER: + { + if (first_const && first == 0.0) // 0 to any power is 0 + return ExpressionTreeNode(new Operation::Constant(0.0)); + if (first_const && first == 1.0) // 1 to any power is 1 + return ExpressionTreeNode(new Operation::Constant(1.0)); + if (second_const) { // Constant exponent + if (second == 0.0) // x^0 = 1 + return ExpressionTreeNode(new Operation::Constant(1.0)); + if (second == 1.0) // x^1 = x + return children[0]; + if (second == -1.0) // x^-1 = recip(x) + return ExpressionTreeNode(new Operation::Reciprocal(), children[0]); + if (second == 2.0) // x^2 = square(x) + return ExpressionTreeNode(new Operation::Square(), children[0]); + if (second == 3.0) // x^3 = cube(x) + return ExpressionTreeNode(new Operation::Cube(), children[0]); + if (second == 0.5) // x^0.5 = sqrt(x) + return ExpressionTreeNode(new Operation::Sqrt(), children[0]); + // Constant power + return ExpressionTreeNode(new Operation::PowerConstant(second), children[0]); + } + break; + } + case Operation::NEGATE: + { + if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine a multiply and a negate into a single multiply + return ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast(&children[0].getOperation())->getValue()), children[0].getChildren()[0]); + if (first_const) // Negate a constant + return ExpressionTreeNode(new Operation::Constant(-first)); + if (children[0].getOperation().getId() == Operation::NEGATE) // The two negations cancel + return children[0].getChildren()[0]; + break; + } + case Operation::MULTIPLY_CONSTANT: + { + if (children[0].getOperation().getId() == Operation::MULTIPLY_CONSTANT) // Combine two multiplies into a single one + return ExpressionTreeNode(new Operation::MultiplyConstant(dynamic_cast(&node.getOperation())->getValue()*dynamic_cast(&children[0].getOperation())->getValue()), children[0].getChildren()[0]); + if (first_const) // Multiply two constants + return ExpressionTreeNode(new Operation::Constant(dynamic_cast(&node.getOperation())->getValue()*getConstantValue(children[0]))); + if (children[0].getOperation().getId() == Operation::NEGATE) // Combine a multiply and a negate into a single multiply + return ExpressionTreeNode(new Operation::MultiplyConstant(-dynamic_cast(&node.getOperation())->getValue()), children[0].getChildren()[0]); + break; + } + case Operation::SQRT: + { + if (children[0].getOperation().getId() == Operation::SQUARE) // sqrt(square(x)) = abs(x) + return ExpressionTreeNode(new Operation::Abs(), children[0].getChildren()[0]); + break; + } + case Operation::SQUARE: + { + if (children[0].getOperation().getId() == Operation::SQRT) // square(sqrt(x)) = x + return children[0].getChildren()[0]; + break; + } + case Operation::SELECT: + { + if (children[1] == children[2]) // Select between two identical values + return children[1]; + break; + } + default: + { + // If operation ID is not one of the above, + // we don't substitute a simpler expression. + break; + } + + } + return ExpressionTreeNode(node.getOperation().clone(), children); +} + +ParsedExpression ParsedExpression::differentiate(const string& variable) const { + vector examples; + getRootNode().assignTags(examples); + map nodeCache; + return differentiate(getRootNode(), variable, nodeCache); +} + +ExpressionTreeNode ParsedExpression::differentiate(const ExpressionTreeNode& node, const string& variable, map& nodeCache) { + auto cached = nodeCache.find(node.tag); + if (cached != nodeCache.end()) + return cached->second; + vector childDerivs(node.getChildren().size()); + for (int i = 0; i < (int) childDerivs.size(); i++) + childDerivs[i] = differentiate(node.getChildren()[i], variable, nodeCache); + ExpressionTreeNode result = node.getOperation().differentiate(node.getChildren(), childDerivs, variable); + nodeCache[node.tag] = result; + return result; +} + +bool ParsedExpression::isConstant(const ExpressionTreeNode& node) { + return (node.getOperation().getId() == Operation::CONSTANT); +} + +double ParsedExpression::getConstantValue(const ExpressionTreeNode& node) { + if (node.getOperation().getId() != Operation::CONSTANT) { + throw Exception("getConstantValue called on a non-constant ExpressionNode"); + } + return dynamic_cast(node.getOperation()).getValue(); +} + +ExpressionProgram ParsedExpression::createProgram() const { + return ExpressionProgram(*this); +} + +CompiledExpression ParsedExpression::createCompiledExpression() const { + return CompiledExpression(*this); +} + +CompiledVectorExpression ParsedExpression::createCompiledVectorExpression(int width) const { + return CompiledVectorExpression(*this, width); +} + +ParsedExpression ParsedExpression::renameVariables(const map& replacements) const { + return ParsedExpression(renameNodeVariables(getRootNode(), replacements)); +} + +ExpressionTreeNode ParsedExpression::renameNodeVariables(const ExpressionTreeNode& node, const map& replacements) { + if (node.getOperation().getId() == Operation::VARIABLE) { + map::const_iterator replace = replacements.find(node.getOperation().getName()); + if (replace != replacements.end()) + return ExpressionTreeNode(new Operation::Variable(replace->second)); + } + vector children; + for (int i = 0; i < (int) node.getChildren().size(); i++) + children.push_back(renameNodeVariables(node.getChildren()[i], replacements)); + return ExpressionTreeNode(node.getOperation().clone(), children); +} + +ostream& Lepton::operator<<(ostream& out, const ExpressionTreeNode& node) { + if (node.getOperation().isInfixOperator() && node.getChildren().size() == 2) { + out << "(" << node.getChildren()[0] << ")" << node.getOperation().getName() << "(" << node.getChildren()[1] << ")"; + } + else if (node.getOperation().isInfixOperator() && node.getChildren().size() == 1) { + out << "(" << node.getChildren()[0] << ")" << node.getOperation().getName(); + } + else { + out << node.getOperation().getName(); + if (node.getChildren().size() > 0) { + out << "("; + for (int i = 0; i < (int) node.getChildren().size(); i++) { + if (i > 0) + out << ", "; + out << node.getChildren()[i]; + } + out << ")"; + } + } + return out; +} + +ostream& Lepton::operator<<(ostream& out, const ParsedExpression& exp) { + out << exp.getRootNode(); + return out; +} diff --git a/src/external/lepton/src/Parser.cpp b/src/external/lepton/src/Parser.cpp new file mode 100644 index 00000000000..47ebac464ab --- /dev/null +++ b/src/external/lepton/src/Parser.cpp @@ -0,0 +1,409 @@ +/* -------------------------------------------------------------------------- * + * Lepton * + * -------------------------------------------------------------------------- * + * This is part of the Lepton expression parser originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org. * + * * + * Portions copyright (c) 2009-2019 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the "Software"), * + * to deal in the Software without restriction, including without limitation * + * the rights to use, copy, modify, merge, publish, distribute, sublicense, * + * and/or sell copies of the Software, and to permit persons to whom the * + * Software is furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * + * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE * + * USE OR OTHER DEALINGS IN THE SOFTWARE. * + * -------------------------------------------------------------------------- */ + +#include "lepton/Parser.h" +#include "lepton/CustomFunction.h" +#include "lepton/Exception.h" +#include "lepton/ExpressionTreeNode.h" +#include "lepton/Operation.h" +#include "lepton/ParsedExpression.h" +#include +#include + +using namespace Lepton; +using namespace std; + +static const string Digits = "0123456789"; +static const string Operators = "+-*/^"; +static const bool LeftAssociative[] = {true, true, true, true, false}; +static const int Precedence[] = {0, 0, 1, 1, 3}; +static const Operation::Id OperationId[] = {Operation::ADD, Operation::SUBTRACT, Operation::MULTIPLY, Operation::DIVIDE, Operation::POWER}; + +class Lepton::ParseToken { +public: + enum Type {Number, Operator, Variable, Function, LeftParen, RightParen, Comma, Whitespace}; + + ParseToken(string text, Type type) : text(text), type(type) { + } + const string& getText() const { + return text; + } + Type getType() const { + return type; + } +private: + string text; + Type type; +}; + +string Parser::trim(const string& expression) { + // Remove leading and trailing spaces. + + int start, end; + for (start = 0; start < (int) expression.size() && isspace(expression[start]); start++) + ; + for (end = (int) expression.size()-1; end > start && isspace(expression[end]); end--) + ; + if (start == end && isspace(expression[end])) + return ""; + return expression.substr(start, end-start+1); +} + +ParseToken Parser::getNextToken(const string& expression, int start) { + char c = expression[start]; + if (c == '(') + return ParseToken("(", ParseToken::LeftParen); + if (c == ')') + return ParseToken(")", ParseToken::RightParen); + if (c == ',') + return ParseToken(",", ParseToken::Comma); + if (Operators.find(c) != string::npos) + return ParseToken(string(1, c), ParseToken::Operator); + if (isspace(c)) { + // White space + + for (int pos = start+1; pos < (int) expression.size(); pos++) { + if (!isspace(expression[pos])) + return ParseToken(expression.substr(start, pos-start), ParseToken::Whitespace); + } + return ParseToken(expression.substr(start, string::npos), ParseToken::Whitespace); + } + if (c == '.' || Digits.find(c) != string::npos) { + // A number + + bool foundDecimal = (c == '.'); + bool foundExp = false; + int pos; + for (pos = start+1; pos < (int) expression.size(); pos++) { + c = expression[pos]; + if (Digits.find(c) != string::npos) + continue; + if (c == '.' && !foundDecimal) { + foundDecimal = true; + continue; + } + if ((c == 'e' || c == 'E') && !foundExp) { + foundExp = true; + if (pos < (int) expression.size()-1 && (expression[pos+1] == '-' || expression[pos+1] == '+')) + pos++; + continue; + } + break; + } + return ParseToken(expression.substr(start, pos-start), ParseToken::Number); + } + + // A variable, function, or left parenthesis + + for (int pos = start; pos < (int) expression.size(); pos++) { + c = expression[pos]; + if (c == '(') + return ParseToken(expression.substr(start, pos-start+1), ParseToken::Function); + if (Operators.find(c) != string::npos || c == ',' || c == ')' || isspace(c)) + return ParseToken(expression.substr(start, pos-start), ParseToken::Variable); + } + return ParseToken(expression.substr(start, string::npos), ParseToken::Variable); +} + +vector Parser::tokenize(const string& expression) { + vector tokens; + int pos = 0; + while (pos < (int) expression.size()) { + ParseToken token = getNextToken(expression, pos); + if (token.getType() != ParseToken::Whitespace) + tokens.push_back(token); + pos += (int) token.getText().size(); + } + return tokens; +} + +ParsedExpression Parser::parse(const string& expression) { + return parse(expression, map()); +} + +ParsedExpression Parser::parse(const string& expression, const map& customFunctions) { + try { + // First split the expression into subexpressions. + + string primaryExpression = expression; + vector subexpressions; + while (true) { + string::size_type pos = primaryExpression.find_last_of(';'); + if (pos == string::npos) + break; + string sub = trim(primaryExpression.substr(pos+1)); + if (sub.size() > 0) + subexpressions.push_back(sub); + primaryExpression = primaryExpression.substr(0, pos); + } + + // Parse the subexpressions. + + map subexpDefs; + for (int i = 0; i < (int) subexpressions.size(); i++) { + string::size_type equalsPos = subexpressions[i].find('='); + if (equalsPos == string::npos) + throw Exception("subexpression does not specify a name"); + string name = trim(subexpressions[i].substr(0, equalsPos)); + if (name.size() == 0) + throw Exception("subexpression does not specify a name"); + vector tokens = tokenize(subexpressions[i].substr(equalsPos+1)); + int pos = 0; + subexpDefs[name] = parsePrecedence(tokens, pos, customFunctions, subexpDefs, 0); + if (pos != tokens.size()) + throw Exception("unexpected text at end of subexpression: "+tokens[pos].getText()); + } + + // Now parse the primary expression. + + vector tokens = tokenize(primaryExpression); + int pos = 0; + ExpressionTreeNode result = parsePrecedence(tokens, pos, customFunctions, subexpDefs, 0); + if (pos != tokens.size()) + throw Exception("unexpected text at end of expression: "+tokens[pos].getText()); + return ParsedExpression(result); + } + catch (Exception& ex) { + throw Exception("Parse error in expression \""+expression+"\": "+ex.what()); + } +} + +ExpressionTreeNode Parser::parsePrecedence(const vector& tokens, int& pos, const map& customFunctions, + const map& subexpressionDefs, int precedence) { + if (pos == tokens.size()) + throw Exception("unexpected end of expression"); + + // Parse the next value (number, variable, function, parenthesized expression) + + ParseToken token = tokens[pos]; + ExpressionTreeNode result; + if (token.getType() == ParseToken::Number) { + double value; + stringstream(token.getText()) >> value; + result = ExpressionTreeNode(new Operation::Constant(value)); + pos++; + } + else if (token.getType() == ParseToken::Variable) { + map::const_iterator subexp = subexpressionDefs.find(token.getText()); + if (subexp == subexpressionDefs.end()) { + Operation* op = new Operation::Variable(token.getText()); + result = ExpressionTreeNode(op); + } + else + result = subexp->second; + pos++; + } + else if (token.getType() == ParseToken::LeftParen) { + pos++; + result = parsePrecedence(tokens, pos, customFunctions, subexpressionDefs, 0); + if (pos == tokens.size() || tokens[pos].getType() != ParseToken::RightParen) + throw Exception("unbalanced parentheses"); + pos++; + } + else if (token.getType() == ParseToken::Function) { + pos++; + vector args; + bool moreArgs; + do { + args.push_back(parsePrecedence(tokens, pos, customFunctions, subexpressionDefs, 0)); + moreArgs = (pos < (int) tokens.size() && tokens[pos].getType() == ParseToken::Comma); + if (moreArgs) + pos++; + } while (moreArgs); + if (pos == tokens.size() || tokens[pos].getType() != ParseToken::RightParen) + throw Exception("unbalanced parentheses"); + pos++; + Operation* op = getFunctionOperation(token.getText(), customFunctions); + try { + result = ExpressionTreeNode(op, args); + } + catch (...) { + delete op; + throw; + } + } + else if (token.getType() == ParseToken::Operator && token.getText() == "-") { + pos++; + ExpressionTreeNode toNegate = parsePrecedence(tokens, pos, customFunctions, subexpressionDefs, 2); + result = ExpressionTreeNode(new Operation::Negate(), toNegate); + } + else + throw Exception("unexpected token: "+token.getText()); + + // Now deal with the next binary operator. + + while (pos < (int) tokens.size() && tokens[pos].getType() == ParseToken::Operator) { + token = tokens[pos]; + int opIndex = (int) Operators.find(token.getText()); + int opPrecedence = Precedence[opIndex]; + if (opPrecedence < precedence) + return result; + pos++; + ExpressionTreeNode arg = parsePrecedence(tokens, pos, customFunctions, subexpressionDefs, LeftAssociative[opIndex] ? opPrecedence+1 : opPrecedence); + Operation* op = getOperatorOperation(token.getText()); + try { + result = ExpressionTreeNode(op, result, arg); + } + catch (...) { + delete op; + throw; + } + } + return result; +} + +Operation* Parser::getOperatorOperation(const std::string& name) { + switch (OperationId[Operators.find(name)]) { + case Operation::ADD: + return new Operation::Add(); + case Operation::SUBTRACT: + return new Operation::Subtract(); + case Operation::MULTIPLY: + return new Operation::Multiply(); + case Operation::DIVIDE: + return new Operation::Divide(); + case Operation::POWER: + return new Operation::Power(); + default: + throw Exception("unknown operator"); + } +} + +Operation* Parser::getFunctionOperation(const std::string& name, const map& customFunctions) { + + static map opMap; + if (opMap.size() == 0) { + opMap["sqrt"] = Operation::SQRT; + opMap["exp"] = Operation::EXP; + opMap["log"] = Operation::LOG; + opMap["sin"] = Operation::SIN; + opMap["cos"] = Operation::COS; + opMap["sec"] = Operation::SEC; + opMap["csc"] = Operation::CSC; + opMap["tan"] = Operation::TAN; + opMap["cot"] = Operation::COT; + opMap["asin"] = Operation::ASIN; + opMap["acos"] = Operation::ACOS; + opMap["atan"] = Operation::ATAN; + opMap["atan2"] = Operation::ATAN2; + opMap["sinh"] = Operation::SINH; + opMap["cosh"] = Operation::COSH; + opMap["tanh"] = Operation::TANH; + opMap["erf"] = Operation::ERF; + opMap["erfc"] = Operation::ERFC; + opMap["step"] = Operation::STEP; + opMap["delta"] = Operation::DELTA; + opMap["square"] = Operation::SQUARE; + opMap["cube"] = Operation::CUBE; + opMap["recip"] = Operation::RECIPROCAL; + opMap["min"] = Operation::MIN; + opMap["max"] = Operation::MAX; + opMap["abs"] = Operation::ABS; + opMap["floor"] = Operation::FLOOR; + opMap["ceil"] = Operation::CEIL; + opMap["select"] = Operation::SELECT; + } + string trimmed = name.substr(0, name.size()-1); + + // First check custom functions. + + map::const_iterator custom = customFunctions.find(trimmed); + if (custom != customFunctions.end()) + return new Operation::Custom(trimmed, custom->second->clone()); + + // Now try standard functions. + + map::const_iterator iter = opMap.find(trimmed); + if (iter == opMap.end()) + throw Exception("unknown function: "+trimmed); + switch (iter->second) { + case Operation::SQRT: + return new Operation::Sqrt(); + case Operation::EXP: + return new Operation::Exp(); + case Operation::LOG: + return new Operation::Log(); + case Operation::SIN: + return new Operation::Sin(); + case Operation::COS: + return new Operation::Cos(); + case Operation::SEC: + return new Operation::Sec(); + case Operation::CSC: + return new Operation::Csc(); + case Operation::TAN: + return new Operation::Tan(); + case Operation::COT: + return new Operation::Cot(); + case Operation::ASIN: + return new Operation::Asin(); + case Operation::ACOS: + return new Operation::Acos(); + case Operation::ATAN: + return new Operation::Atan(); + case Operation::ATAN2: + return new Operation::Atan2(); + case Operation::SINH: + return new Operation::Sinh(); + case Operation::COSH: + return new Operation::Cosh(); + case Operation::TANH: + return new Operation::Tanh(); + case Operation::ERF: + return new Operation::Erf(); + case Operation::ERFC: + return new Operation::Erfc(); + case Operation::STEP: + return new Operation::Step(); + case Operation::DELTA: + return new Operation::Delta(); + case Operation::SQUARE: + return new Operation::Square(); + case Operation::CUBE: + return new Operation::Cube(); + case Operation::RECIPROCAL: + return new Operation::Reciprocal(); + case Operation::MIN: + return new Operation::Min(); + case Operation::MAX: + return new Operation::Max(); + case Operation::ABS: + return new Operation::Abs(); + case Operation::FLOOR: + return new Operation::Floor(); + case Operation::CEIL: + return new Operation::Ceil(); + case Operation::SELECT: + return new Operation::Select(); + default: + throw Exception("unknown function"); + } +} diff --git a/src/gromacs/CMakeLists.txt b/src/gromacs/CMakeLists.txt index fc4d299190e..f5322d09b06 100644 --- a/src/gromacs/CMakeLists.txt +++ b/src/gromacs/CMakeLists.txt @@ -142,6 +142,11 @@ if (WIN32) endif() list(APPEND libgromacs_object_library_dependencies thread_mpi) +gmx_manage_colvars() +gmx_manage_lepton() +list(APPEND libgromacs_object_library_dependencies colvars) +list(APPEND libgromacs_object_library_dependencies lepton) + if(GMX_INSTALL_LEGACY_API) install(FILES analysisdata.h @@ -187,6 +192,8 @@ else() add_library(libgromacs ${LIBGROMACS_SOURCES}) endif() +gmx_include_colvars_headers() + if (TARGET Heffte::Heffte) target_link_libraries(libgromacs PRIVATE Heffte::Heffte) endif() diff --git a/src/gromacs/CMakeLists.txt.orig b/src/gromacs/CMakeLists.txt.orig new file mode 100644 index 00000000000..fc4d299190e --- /dev/null +++ b/src/gromacs/CMakeLists.txt.orig @@ -0,0 +1,540 @@ +# +# This file is part of the GROMACS molecular simulation package. +# +# Copyright 2010- The GROMACS Authors +# and the project initiators Erik Lindahl, Berk Hess and David van der Spoel. +# Consult the AUTHORS/COPYING files and https://www.gromacs.org for details. +# +# GROMACS 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 2.1 +# of the License, or (at your option) any later version. +# +# GROMACS 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 GROMACS; if not, see +# https://www.gnu.org/licenses, or write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# If you want to redistribute modifications to GROMACS, please +# consider that scientific software is very special. Version +# control is crucial - bugs must be traceable. We will be happy to +# consider code for inclusion in the official distribution, but +# derived work must not be called official GROMACS. Details are found +# in the README & COPYING files - if they are missing, get the +# official version at https://www.gromacs.org. +# +# To help us fund GROMACS development, we humbly ask that you cite +# the research papers on the package. Check out https://www.gromacs.org. + +set(LIBGROMACS_SOURCES) + +if (GMX_CLANG_CUDA) + include(gmxClangCudaUtils) +endif() + +set_property(GLOBAL PROPERTY GMX_LIBGROMACS_SOURCES) +set_property(GLOBAL PROPERTY CUDA_SOURCES) +set_property(GLOBAL PROPERTY GMX_INSTALLED_HEADERS) +set_property(GLOBAL PROPERTY GMX_AVX_512_SOURCE) + +set(libgromacs_object_library_dependencies "") +function (_gmx_add_files_to_property PROPERTY) + foreach (_file ${ARGN}) + if (IS_ABSOLUTE "${_file}") + set_property(GLOBAL APPEND PROPERTY ${PROPERTY} ${_file}) + else() + set_property(GLOBAL APPEND PROPERTY ${PROPERTY} + ${CMAKE_CURRENT_LIST_DIR}/${_file}) + endif() + endforeach() +endfunction () + +function (gmx_add_libgromacs_sources) + _gmx_add_files_to_property(GMX_LIBGROMACS_SOURCES ${ARGN}) +endfunction () + +# Permit the configuration to disable compiling the many nbnxm kernels +# and others involved in force calculations. Currently only +# short-ranged and bonded kernels are disabled this way, but in future +# others may be appropriate. Thus the cmake option is not specific to +# nbnxm module. +option(GMX_USE_SIMD_KERNELS "Whether to compile NBNXM and other SIMD kernels" ON) +mark_as_advanced(GMX_USE_SIMD_KERNELS) + +# pulling is using the muparser target as well, so detect it early +gmx_manage_muparser() + +# Add these contents first because linking their tests can take a lot +# of time, so we want lots of parallel work still available after +# linking starts. +add_subdirectory(utility) +# Add normal contents +add_subdirectory(gmxlib) +add_subdirectory(mdlib) +add_subdirectory(applied_forces) +add_subdirectory(listed_forces) +add_subdirectory(nbnxm) +add_subdirectory(commandline) +add_subdirectory(domdec) +add_subdirectory(ewald) +add_subdirectory(fft) +add_subdirectory(gpu_utils) +add_subdirectory(hardware) +add_subdirectory(linearalgebra) +add_subdirectory(math) +add_subdirectory(mdrun) +add_subdirectory(mdrunutility) +add_subdirectory(mdspan) +add_subdirectory(mdtypes) +add_subdirectory(onlinehelp) +add_subdirectory(options) +add_subdirectory(pbcutil) +add_subdirectory(random) +add_subdirectory(restraint) +add_subdirectory(tables) +add_subdirectory(taskassignment) +add_subdirectory(timing) +add_subdirectory(topology) +add_subdirectory(trajectory) +add_subdirectory(swap) +add_subdirectory(essentialdynamics) +add_subdirectory(pulling) +add_subdirectory(simd) +add_subdirectory(imd) +add_subdirectory(compat) +add_subdirectory(mimic) +add_subdirectory(modularsimulator) +add_subdirectory(gmxana) +add_subdirectory(gmxpreprocess) +add_subdirectory(correlationfunctions) +add_subdirectory(statistics) +add_subdirectory(analysisdata) +add_subdirectory(coordinateio) +add_subdirectory(trajectoryanalysis) +add_subdirectory(energyanalysis) +add_subdirectory(tools) + +get_property(PROPERTY_SOURCES GLOBAL PROPERTY GMX_LIBGROMACS_SOURCES) +list(APPEND LIBGROMACS_SOURCES ${GMXLIB_SOURCES} ${MDLIB_SOURCES} ${PROPERTY_SOURCES}) + +# This would be the standard way to include thread_mpi, but +# we want libgromacs to link the functions directly +#if(GMX_THREAD_MPI) +# add_subdirectory(thread_mpi) +#endif() +#target_link_libraries(gmx ${GMX_EXTRA_LIBRARIES} ${THREAD_MPI_LIB}) +tmpi_get_source_list(THREAD_MPI_SOURCES ${PROJECT_SOURCE_DIR}/src/external/thread_mpi/src) +add_library(thread_mpi OBJECT ${THREAD_MPI_SOURCES}) +target_compile_definitions(thread_mpi PRIVATE HAVE_CONFIG_H) +target_compile_definitions(thread_mpi PRIVATE TMPI_EXPORTS TMPI_USE_VISIBILITY) +if(CYGWIN) + # Needs POSIX-isms for strdup, not just std-isms + target_compile_definitions(thread_mpi PRIVATE _POSIX_C_SOURCE=200809L) +endif() +gmx_target_compile_options(thread_mpi) +if (WIN32) + gmx_target_warning_suppression(thread_mpi /wd4996 HAS_NO_MSVC_UNSAFE_FUNCTION) +endif() +list(APPEND libgromacs_object_library_dependencies thread_mpi) + +if(GMX_INSTALL_LEGACY_API) + install(FILES + analysisdata.h + options.h + selection.h + trajectoryanalysis.h + DESTINATION include/gromacs) +endif() + +# This code is here instead of utility/CMakeLists.txt, because CMake +# custom commands and source file properties can only be set in the directory +# that contains the target that uses them. +# TODO: Generate a header instead that can be included from baseversion.cpp. +# That probably simplifies things somewhat. +set(GENERATED_VERSION_FILE utility/baseversion-gen.cpp) +gmx_configure_version_file( + utility/baseversion-gen.cpp.cmakein ${GENERATED_VERSION_FILE} + REMOTE_HASH + EXTRA_VARS + GMX_SOURCE_DOI + GMX_RELEASE_HASH + GMX_SOURCE_HASH + ) +list(APPEND LIBGROMACS_SOURCES ${GENERATED_VERSION_FILE}) + +# Mark some shared GPU implementation files to compile with CUDA if needed +if (GMX_GPU_CUDA) + get_property(CUDA_SOURCES GLOBAL PROPERTY CUDA_SOURCES) + set_source_files_properties(${CUDA_SOURCES} PROPERTIES CUDA_SOURCE_PROPERTY_FORMAT OBJ) +endif() + +if (GMX_GPU_CUDA) + # Work around FindCUDA that prevents using target_link_libraries() + # with keywords otherwise... + set(CUDA_LIBRARIES PRIVATE ${CUDA_LIBRARIES}) + if (NOT GMX_CLANG_CUDA) + gmx_cuda_add_library(libgromacs ${LIBGROMACS_SOURCES}) + else() + add_library(libgromacs ${LIBGROMACS_SOURCES}) + endif() + target_link_libraries(libgromacs PRIVATE ${CUDA_CUFFT_LIBRARIES}) +else() + add_library(libgromacs ${LIBGROMACS_SOURCES}) +endif() + +if (TARGET Heffte::Heffte) + target_link_libraries(libgromacs PRIVATE Heffte::Heffte) +endif() + +if (GMX_SYCL_HIPSYCL AND GMX_HIPSYCL_HAVE_HIP_TARGET) + target_link_libraries(libgromacs PUBLIC roc::rocfft) +endif() + +target_link_libraries(libgromacs PRIVATE $) +# As long as the libgromacs target has source files that reference headers from +# modules that don't provide CMake targets, libgromacs needs to use `src/` +# amongst its include directories (to support `#include "gromacs/module/header.h"`). +add_library(legacy_modules INTERFACE) +target_include_directories(legacy_modules INTERFACE $) +target_link_libraries(libgromacs PRIVATE $) + +# Add these contents first because linking their tests can take a lot +# of time, so we want lots of parallel work still available after +# linking starts. +add_subdirectory(fileio) +add_subdirectory(selection) + +# Handle the object libraries that contain the source file +# dependencies that need special handling because they are generated +# or external code. +foreach(object_library ${libgromacs_object_library_dependencies}) + if (BUILD_SHARED_LIBS) + set_target_properties(${object_library} PROPERTIES POSITION_INDEPENDENT_CODE true) + endif() + target_include_directories(${object_library} SYSTEM BEFORE PRIVATE ${PROJECT_SOURCE_DIR}/src/external/thread_mpi/include) + target_link_libraries(${object_library} PRIVATE common) + + # Add the sources from the object libraries to the main library. + target_sources(libgromacs PRIVATE $) +endforeach() +gmx_target_compile_options(libgromacs) +target_compile_definitions(libgromacs PRIVATE HAVE_CONFIG_H + TMPI_EXPORTS + PUBLIC TMPI_USE_VISIBILITY) + +target_include_directories(libgromacs SYSTEM BEFORE PRIVATE ${PROJECT_SOURCE_DIR}/src/external/thread_mpi/include) + +if (GMX_GPU_OPENCL) + option(GMX_EXTERNAL_CLFFT "True if an external clFFT is required to be used" FALSE) + mark_as_advanced(GMX_EXTERNAL_CLFFT) + + # Default to using clFFT found on the system + # switch to quiet at the second run. + if (DEFINED clFFT_LIBRARY) + set (clFFT_FIND_QUIETLY TRUE) + endif() + find_package(clFFT) + if (NOT clFFT_FOUND) + if (GMX_EXTERNAL_CLFFT) + message(FATAL_ERROR "Did not find required external clFFT library, consider setting clFFT_ROOT_DIR") + endif() + + if(MSVC) + message(FATAL_ERROR +"An OpenCL build was requested with Visual Studio compiler, but GROMACS +requires clFFT, which was not found on your system. GROMACS does bundle +clFFT to help with building for OpenCL, but that clFFT has not yet been +ported to the more recent versions of that compiler that GROMACS itself +requires. Thus for now, OpenCL is not available with MSVC and the internal +build of clFFT in GROMACS 2019. Either change compiler, try installing +a clFFT package, or use the latest GROMACS 2018 point release.") + endif() + + # Fall back on the internal version + set (_clFFT_dir ../external/clFFT/src) + add_subdirectory(${_clFFT_dir} clFFT-build) + target_sources(libgromacs PRIVATE + $ + ) + target_include_directories(libgromacs SYSTEM PRIVATE ${_clFFT_dir}/include) + # Use the magic variable for how to link any library needed for + # dlopen, etc. which is -ldl where needed, and empty otherwise + # (e.g. Windows, BSD, Mac). + target_link_libraries(libgromacs PRIVATE "${CMAKE_DL_LIBS}") + else() + target_link_libraries(libgromacs PRIVATE clFFT) + endif() +endif() + +# Permit GROMACS code to include externally developed headers, such as +# the functionality from the nonstd project that we use for +# gmx::compat::optional. These are included as system headers so that +# no warnings are issued from them. +# +# TODO Perhaps generalize this for all headers from src/external +target_include_directories(libgromacs SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/src/external) + +if(SIMD_AVX_512_CXX_SUPPORTED AND NOT ("${GMX_SIMD_ACTIVE}" STREQUAL "AVX_512_KNL")) + # Since we might be overriding -march=core-avx2, add a flag so we don't warn for this specific file. + # On KNL this can cause illegal instruction because the compiler might use non KNL AVX instructions + # with the SIMD_AVX_512_CXX_FLAGS flags. + set_source_files_properties(hardware/identifyavx512fmaunits.cpp PROPERTIES COMPILE_FLAGS "${SIMD_AVX_512_CXX_FLAGS} ${CXX_NO_UNUSED_OPTION_WARNING_FLAGS}") +endif() + +# Do any special handling needed for .cpp files that use +# CUDA runtime headers +if (GMX_GPU_CUDA AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + foreach(_compile_flag ${NVCC_CLANG_SUPPRESSIONS_CXXFLAGS}) + set(GMX_CUDA_CLANG_FLAGS "${GMX_CUDA_CLANG_FLAGS} ${_compile_flag}") + endforeach() + if (GMX_CLANG_CUDA) + foreach (_file ${LIBGROMACS_SOURCES}) + get_filename_component(_ext ${_file} EXT) + get_source_file_property(_cuda_source_format ${_file} CUDA_SOURCE_PROPERTY_FORMAT) + if ("${_ext}" STREQUAL ".cu" OR _cuda_source_format) + gmx_compile_cuda_file_with_clang(${_file}) + endif() + endforeach() + else() + get_property(CUDA_SOURCES GLOBAL PROPERTY CUDA_SOURCES) + set_source_files_properties(${CUDA_SOURCES} PROPERTIES COMPILE_FLAGS ${GMX_CUDA_CLANG_FLAGS}) + endif() +endif() + +# Only add the -fsycl flag to sources that really need it +if (GMX_GPU_SYCL) + get_property(SYCL_SOURCES GLOBAL PROPERTY SYCL_SOURCES) + add_sycl_to_target(TARGET libgromacs SOURCES ${SYCL_SOURCES}) +endif() + +gmx_setup_tng_for_libgromacs() + +# We apply the SYCL flag explicitly just for libgromacs, since bugs in the beta versions of +# icpx/dpcpp leads to crashes if we try to link an library without any SYCL code with the +# -fsycl flag enabled. Once that bug is fixed, we should change it to simply add +# SYCL_CXX_FLAGS to GMX_SHARED_LINKER_FLAGS. +target_link_libraries(libgromacs + PRIVATE + ${EXTRAE_LIBRARIES} + ${GMX_EXTRA_LIBRARIES} + ${GMX_COMMON_LIBRARIES} + ${FFT_LIBRARIES} ${LINEAR_ALGEBRA_LIBRARIES} + ${THREAD_LIB} ${GMX_SHARED_LINKER_FLAGS} + ${SYCL_CXX_FLAGS} + ${OpenCL_LIBRARIES} + $<$:socket> + PUBLIC + ${GMX_PUBLIC_LIBRARIES} + ) +target_link_libraries(libgromacs PUBLIC legacy_api) +# Dependencies from libgromacs to the modules are set up here, but +# once the add_subdirectory() commands are re-ordered then +# responsibility for setting this up will move to the respective +# modules. +target_link_libraries(libgromacs PRIVATE + $ + $ + $ + $ + $ + $ + $ +# $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + ) +if (GMX_OPENMP) + target_link_libraries(libgromacs PUBLIC OpenMP::OpenMP_CXX) +endif() +set_target_properties(libgromacs PROPERTIES + OUTPUT_NAME "gromacs${GMX_LIBS_SUFFIX}" + SOVERSION ${LIBRARY_SOVERSION_MAJOR} + VERSION ${LIBRARY_VERSION} + ) + +gmx_manage_lmfit() +target_link_libraries(libgromacs PRIVATE lmfit) +target_link_libraries(libgromacs PRIVATE muparser::muparser) + +# Make sure we fix "everything" found by compilers that support that +gmx_warn_on_everything(libgromacs) +if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(libgromacs PRIVATE $<$:/analyze /analyze:stacksize 70000 + #Control flow warnings are disabled because the commond line output is insufficient. There is no tool + #to convert the xml report to e.g. HTML and even in Visual Studio the viewer doesn't work with cmake support. + /wd6001 #unitialized memory + /wd6011 #derefencing NULL + /wd6053 #prior call not zero-terminate + /wd6054 #might not be zero-terminated + /wd6385 #reading invalid data + /wd6386 #buffer overrun + /wd6387 #could be '0' + /wd28199 #uninitialized memory + # For compile time constant (e.g. templates) the following warnings have flase postives + /wd6239 #( && ) + /wd6240 #( && ) + /wd6294 #Ill-defined for-loop + /wd6326 #comparison of constant with other constant + /wd28020 #expression involving paramter is not true + # Misc + /wd6330 #incorrect type to function (warns for char (instead of unsigned) for isspace/isalpha/isdigit/..)) + /wd6993 #OpenMP ignored + #TODO + /wd6031 #return value ignored (important - mostly warnigns about sscanf) + /wd6244 #hides declaration (known issue - we ingore similar warnings for other compilers) + /wd6246 #hides declaration + > + ) +endif() + +if (GMX_CLANG_TIDY) + set_target_properties(libgromacs PROPERTIES CXX_CLANG_TIDY + "${CLANG_TIDY_EXE};-warnings-as-errors=*") +endif() + +# clang-3.6 warns about a number of issues that are not reported by more modern compilers +# and we know they are not real issues. So we only check that it can compile without error +# but ignore all warnings. +if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_VERSION MATCHES "^3\.6") + target_compile_options(libgromacs PRIVATE $<$:-w>) +endif() + +# TODO: Stop installing libgromacs. Possibly allow installation during deprecation period with GMX_INSTALL_LEGACY_API. +if (BUILD_SHARED_LIBS) + install(TARGETS libgromacs + EXPORT libgromacs + LIBRARY + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT libraries + RUNTIME + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT libraries + ARCHIVE + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT libraries + INCLUDES DESTINATION include) + target_compile_definitions(libgromacs PUBLIC $) + # legacy headers use c++17 features, so consumer codes need to use that standard, too + if(GMX_INSTALL_LEGACY_API) + target_compile_features(libgromacs INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) + endif() + # only install pkg-config and cmake target files when library is installed + include(InstallLibInfo.cmake) +endif() +add_library(Gromacs::libgromacs ALIAS libgromacs) + + +# Technically, the user could want to do this for an OpenCL build +# using the CUDA runtime, but currently there's no reason to want to +# do that. +if (INSTALL_CUDART_LIB) #can be set manual by user + if (GMX_GPU_CUDA) + foreach(CUDA_LIB ${CUDA_LIBRARIES}) + string(REGEX MATCH "cudart" IS_CUDART ${CUDA_LIB}) + if(IS_CUDART) #libcuda should not be installed + #install also name-links (linker uses those) + file(GLOB CUDA_LIBS ${CUDA_LIB}*) + install(FILES ${CUDA_LIBS} DESTINATION + ${CMAKE_INSTALL_LIBDIR} COMPONENT libraries) + endif() + endforeach() + else() + message(WARNING "INSTALL_CUDART_LIB only makes sense when configuring for CUDA support") + endif() +endif() + +if(GMX_GPU_OPENCL) + # Install the utility headers + file(GLOB OPENCL_INSTALLED_FILES + gpu_utils/vectype_ops.clh + gpu_utils/device_utils.clh + ) + install(FILES ${OPENCL_INSTALLED_FILES} + DESTINATION ${GMX_INSTALL_OCLDIR}/gromacs/gpu_utils + COMPONENT libraries) + file(GLOB OPENCL_INSTALLED_FILES + pbcutil/ishift.h + ) + install(FILES ${OPENCL_INSTALLED_FILES} + DESTINATION ${GMX_INSTALL_OCLDIR}/gromacs/pbcutil + COMPONENT libraries) + + # Install the NBNXM source and headers + file(GLOB OPENCL_INSTALLED_FILES + nbnxm/constants.h + ) + install(FILES ${OPENCL_INSTALLED_FILES} + DESTINATION ${GMX_INSTALL_OCLDIR}/gromacs/nbnxm + COMPONENT libraries) + file(GLOB OPENCL_INSTALLED_FILES + nbnxm/opencl/nbnxm_ocl_kernels.cl + nbnxm/opencl/nbnxm_ocl_kernel.clh + nbnxm/opencl/nbnxm_ocl_kernel_pruneonly.clh + nbnxm/opencl/nbnxm_ocl_kernels.clh + nbnxm/opencl/nbnxm_ocl_kernels_fastgen.clh + nbnxm/opencl/nbnxm_ocl_kernels_fastgen_add_twincut.clh + nbnxm/opencl/nbnxm_ocl_kernel_utils.clh + nbnxm/opencl/nbnxm_ocl_consts.h + ) + install(FILES ${OPENCL_INSTALLED_FILES} + DESTINATION ${GMX_INSTALL_OCLDIR}/gromacs/nbnxm/opencl + COMPONENT libraries) + + # Install the PME source and headers + file(GLOB OPENCL_INSTALLED_FILES + ewald/pme_spread.clh + ewald/pme_solve.clh + ewald/pme_gather.clh + ewald/pme_gpu_calculate_splines.clh + ewald/pme_program.cl + ewald/pme_gpu_types.h + ) + install(FILES ${OPENCL_INSTALLED_FILES} + DESTINATION ${GMX_INSTALL_OCLDIR}/gromacs/ewald + COMPONENT libraries) +endif() + +include(GenerateExportHeader) +generate_export_header(libgromacs EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/api/legacy/include/gromacs/libgromacs_export.h") diff --git a/src/gromacs/applied_forces/CMakeLists.txt b/src/gromacs/applied_forces/CMakeLists.txt index 34114cd590d..8e4b3cfb4d8 100644 --- a/src/gromacs/applied_forces/CMakeLists.txt +++ b/src/gromacs/applied_forces/CMakeLists.txt @@ -73,6 +73,7 @@ gmx_add_libgromacs_sources( add_subdirectory(awh) add_subdirectory(densityfitting) add_subdirectory(qmmm) +add_subdirectory(colvars) if (BUILD_TESTING) add_subdirectory(tests) diff --git a/src/gromacs/applied_forces/colvars/CMakeLists.txt b/src/gromacs/applied_forces/colvars/CMakeLists.txt new file mode 100644 index 00000000000..551c252d326 --- /dev/null +++ b/src/gromacs/applied_forces/colvars/CMakeLists.txt @@ -0,0 +1,35 @@ +# +# This file is part of the GROMACS molecular simulation package. +# +# Copyright 2014- The GROMACS Authors +# and the project initiators Erik Lindahl, Berk Hess and David van der Spoel. +# Consult the AUTHORS/COPYING files and https://www.gromacs.org for details. +# +# GROMACS 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 2.1 +# of the License, or (at your option) any later version. +# +# GROMACS 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 GROMACS; if not, see +# https://www.gnu.org/licenses, or write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# If you want to redistribute modifications to GROMACS, please +# consider that scientific software is very special. Version +# control is crucial - bugs must be traceable. We will be happy to +# consider code for inclusion in the official distribution, but +# derived work must not be called official GROMACS. Details are found +# in the README & COPYING files - if they are missing, get the +# official version at https://www.gromacs.org. +# +# To help us fund GROMACS development, we humbly ask that you cite +# the research papers on the package. Check out https://www.gromacs.org. + +gmx_add_libgromacs_sources(colvarproxy_gromacs.cpp) + diff --git a/src/gromacs/applied_forces/colvars/colvarproxy_gromacs.cpp b/src/gromacs/applied_forces/colvars/colvarproxy_gromacs.cpp new file mode 100644 index 00000000000..02b673f0257 --- /dev/null +++ b/src/gromacs/applied_forces/colvars/colvarproxy_gromacs.cpp @@ -0,0 +1,582 @@ +/// -*- c++ -*- + +#include +#include +#include +#include + + +#include "gromacs/math/units.h" +#include "gromacs/mdtypes/inputrec.h" +#include "gromacs/mdtypes/forceoutput.h" +#include "gromacs/mdtypes/enerdata.h" +#include "gromacs/pbcutil/pbc.h" +#include "gromacs/utility/fatalerror.h" +#include "gromacs/utility/futil.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/domdec/domdec_struct.h" +#include "gromacs/gmxlib/network.h" +#include "gromacs/domdec/ga2la.h" +#include "gromacs/mdtypes/colvarshistory.h" +#include "gromacs/topology/ifunc.h" +#include "gromacs/topology/mtop_util.h" +#include "gromacs/mdlib/broadcaststructs.h" + + +#include "colvarproxy_gromacs.h" + + +//************************************************************ +// colvarproxy_gromacs +colvarproxy_gromacs::colvarproxy_gromacs() : colvarproxy() {} + +// Colvars Initialization +void colvarproxy_gromacs::init(t_inputrec *ir, int64_t step, const gmx_mtop_t &mtop, + ObservablesHistory* oh, + const std::string &prefix, + gmx::ArrayRef filenames_config, + const std::string &filename_restart, + const t_commrec *cr, + const rvec x[], + rvec **xa_old_whole_colvars_state_p, + int *n_colvars_atoms_state_p) { + + + // Initialize colvars. + first_timestep = true; + restart_frequency_s = 0; + + // User-scripted forces are not available in GROMACS + have_scripts = false; + + angstrom_value_ = 0.1; + + boltzmann_ = gmx::c_boltz; + + // Get the thermostat temperature. + // NOTE: Considers only the first temperature coupling group! + set_target_temperature(ir->opts.ref_t[0]); + + // GROMACS random number generation. + // Seed with the mdp parameter ld_seed, the Langevin dynamics seed. + rng.seed(ir->ld_seed); + + /// Handle input filenames and prefix/suffix for colvars files. + /// + /// filename_config is the colvars configuration file collected from "-colvars" option. + /// The output prefix will be the prefix of Gromacs log filename. + /// or "output" otherwise. + /// + /// For restart, 'filename_restart' is the colvars input file for restart, + /// set by the "-cv_restart" option. It will be NULL otherwise. + /// + + if(!prefix.empty()) + { + output_prefix_str = prefix; + } + else { + output_prefix_str = "output"; + } + + restart_output_prefix_str = prefix + ".restart"; + + colvars_restart = false; + + if(!filename_restart.empty()) + { + colvars_restart = true; + input_prefix_str = filename_restart; + input_prefix_str.erase(input_prefix_str.rfind(".dat")); + input_prefix_str.erase(input_prefix_str.rfind(".colvars.state")); + } + + // Retrieve masses and charges from input file + updated_masses_ = updated_charges_ = true; + + // Get GROMACS timestep (picosecond to femtosecond) + set_integration_timestep(ir->delta_t * 1000.0); + // Retrieve the topology of all atoms + gmx_atoms = gmx_mtop_global_atoms(mtop); + + // Read configuration file and set up the proxy only on the master node. + if (MASTER(cr)) + { + + // initiate module: this object will be the communication proxy + // colvarmodule pointer is only defined on the Master due to the static pointer to colvarproxy. + colvars = new colvarmodule(this); + + version_int = get_version_from_string(COLVARPROXY_VERSION); + + colvars->cite_feature("GROMACS engine"); + colvars->cite_feature("Colvars-GROMACS interface"); + + if (cvm::debug()) { + log("Initializing the colvars proxy object.\n"); + } + + cvm::log("Using GROMACS interface, version "+ + cvm::to_str(COLVARPROXY_VERSION)+".\n"); + + auto i = filenames_config.begin(); + for(; i != filenames_config.end(); ++i) { + add_config("configfile", i->c_str()); + } + + colvarproxy::parse_module_config(); + colvars->update_engine_parameters(); + colvars->setup_input(); + + // Citation Reporter + cvm::log(std::string("\n")+colvars->feature_report(0)+std::string("\n")); + + colvars->setup_output(); + + if (step != 0) { + cvm::log("Initializing step number to "+cvm::to_str(step)+".\n"); + } + + colvars->it = colvars->it_restart = step; + + + } // end master + + + // MPI initialisation + + // Initialise attributs for the MPI communication + if(MASTER(cr)) { + // Retrieve the number of colvar atoms + n_colvars_atoms = atoms_ids.size(); + // Copy their global indices + ind = atoms_ids.data(); // This has to be updated if the vector is reallocated + } + + + if(PAR(cr)) { + // Let the other nodes know the number of colvar atoms. + block_bc(cr->mpi_comm_mygroup, n_colvars_atoms); + + // Initialise atoms_new_colvar_forces on non-master nodes + if(!MASTER(cr)) { + atoms_new_colvar_forces.reserve(n_colvars_atoms); + } + } + + snew(x_colvars_unwrapped, n_colvars_atoms); + snew(xa_ind, n_colvars_atoms); + snew(xa_shifts, n_colvars_atoms); + snew(xa_eshifts, n_colvars_atoms); + snew(xa_old_whole, n_colvars_atoms); + snew(f_colvars, n_colvars_atoms); + + // Prepare data + + // Manage restart with .cpt + if (MASTER(cr)) + { + /* colvarsHistory is the struct holding the data saved in the cpt + + If we dont start with from a .cpt, prepare the colvarsHistory struct for proper .cpt writing, + If we did start from .cpt, we copy over the last whole structures from .cpt, + In any case, for subsequent checkpoint writing, we set the pointers (xa_old_whole_p) in + the xa_old_whole arrays, which contain the correct PBC representation of + colvars atoms at the last time step. + */ + + if (oh->colvarsHistory == nullptr) + { + oh->colvarsHistory = std::make_unique(colvarshistory_t{}); + } + colvarshistory_t *colvarshist = oh->colvarsHistory.get(); + + + snew(colvarshist->xa_old_whole_p, n_colvars_atoms); + + /* We always need the last whole positions such that + * in the next time step we can make the colvars atoms whole again in PBC */ + if (colvarshist->bFromCpt) + { + for (int i = 0; i < n_colvars_atoms; i++) + { + copy_rvec(colvarshist->xa_old_whole[i], xa_old_whole[i]); + } + } + else + { + colvarshist->n_atoms = n_colvars_atoms; + for (int i = 0; i < n_colvars_atoms; i++) + { + int ii = ind[i]; + copy_rvec(x[ii], xa_old_whole[i]); + } + } + + /* For subsequent checkpoint writing, set the pointers (xa_old_whole_p) to the xa_old_whole + * arrays that get updated at every NS step */ + colvarshist->xa_old_whole_p = xa_old_whole; + //Initialize number of colvars atoms from the global state + *n_colvars_atoms_state_p = n_colvars_atoms; + // Point the shifts array from the global state to the local shifts array + *xa_old_whole_colvars_state_p = xa_old_whole; + } + + + // Communicate initial coordinates and global indices to all processes + if (PAR(cr)) + { + nblock_bc(cr->mpi_comm_mygroup, n_colvars_atoms, xa_old_whole); + snew_bc(MASTER(cr), ind, n_colvars_atoms); + nblock_bc(cr->mpi_comm_mygroup, n_colvars_atoms, ind); + } + + // Serial Run + if (!PAR(cr)) + { + nat_loc = n_colvars_atoms; + nalloc_loc = n_colvars_atoms; + ind_loc = ind; + + // xa_ind[i] needs to be set to i for serial runs + for (int i = 0; i < n_colvars_atoms; i++) + { + xa_ind[i] = i; + } + } + + if (MASTER(cr) && cvm::debug()) { + cvm::log ("atoms_ids = "+cvm::to_str (atoms_ids)+"\n"); + cvm::log ("atoms_refcount = "+cvm::to_str (atoms_refcount)+"\n"); + cvm::log ("positions = "+cvm::to_str (atoms_positions)+"\n"); + cvm::log ("atoms_new_colvar_forces = "+cvm::to_str (atoms_new_colvar_forces)+"\n"); + cvm::log (cvm::line_marker); + log("done initializing the colvars proxy object.\n"); + } + + +} // End colvars initialization. + + +colvarproxy_gromacs::~colvarproxy_gromacs() +{} + +void colvarproxy_gromacs::finish(const t_commrec *cr) +{ + if(MASTER(cr)) { + colvars->write_restart_file(output_prefix_str+".colvars.state"); + colvars->write_output_files(); + } +} + +cvm::real colvarproxy_gromacs::rand_gaussian() +{ + return normal_distribution(rng); +} + +size_t colvarproxy_gromacs::restart_frequency() +{ + return restart_frequency_s; +} + +// **************** PERIODIC BOUNDARY CONDITIONS **************** +// Get the PBC-aware distance vector between two positions +cvm::rvector colvarproxy_gromacs::position_distance (cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) const +{ + rvec r1, r2, dr; + r1[0] = pos1.x; + r1[1] = pos1.y; + r1[2] = pos1.z; + r2[0] = pos2.x; + r2[1] = pos2.y; + r2[2] = pos2.z; + + pbc_dx(&gmx_pbc, r2, r1, dr); + return cvm::atom_pos( dr[0], dr[1], dr[2] ); +} + + +void colvarproxy_gromacs::log (std::string const &message) +{ + std::istringstream is(message); + std::string line; + while (std::getline(is, line)) + // Gromacs prints messages on the stderr FILE + fprintf(stderr, "colvars: %s\n", line.c_str()); +} + +void colvarproxy_gromacs::error (std::string const &message) +{ + // In GROMACS, all errors are fatal. + fatal_error (message); +} + +void colvarproxy_gromacs::fatal_error (std::string const &message) +{ + log(message); + if (!cvm::debug()) + log("If this error message is unclear, " + "try recompiling with -DCOLVARS_DEBUG.\n"); + gmx_fatal(FARGS,"Error in collective variables module.\n"); +} + +void colvarproxy_gromacs::exit (std::string const gmx_unused &message) +{ + gmx_fatal(FARGS,"SUCCESS: %s\n", message.c_str()); +} + +int colvarproxy_gromacs::load_atoms (char const gmx_unused *filename, std::vector gmx_unused &atoms, + std::string const gmx_unused &pdb_field, double const gmx_unused pdb_field_value) +{ + cvm::error("Selecting collective variable atoms " + "from a PDB file is currently not supported.\n"); + return COLVARS_NOT_IMPLEMENTED; +} + +int colvarproxy_gromacs::load_coords (char const gmx_unused *filename, std::vector gmx_unused &pos, + const std::vector gmx_unused &indices, std::string const gmx_unused &pdb_field_str, + double const gmx_unused pdb_field_value) +{ + cvm::error("Loading atoms coordinates from a PDB or GRO file is currently not supported." + "Please use an XYZ file.\n"); + return COLVARS_NOT_IMPLEMENTED; +} + +int colvarproxy_gromacs::set_unit_system(std::string const &units_in, bool /*colvars_defined*/) +{ + if (units_in != "gromacs") { + cvm::error("Specified unit system \"" + units_in + "\" is unsupported in Gromacs. Supported units are \"gromacs\" (nm, kJ/mol).\n"); + return COLVARS_ERROR; + } + return COLVARS_OK; +} + +int colvarproxy_gromacs::backup_file (char const *filename) +{ + // Incremental gromacs backup system will be use only for those file + if (std::string(filename).rfind(std::string(".colvars.traj")) != std::string::npos) { + + // GROMACS function + make_backup(filename); + + // Otherwise, just keep one backup. + } else { + + //Handle filename of the backup file + const char *extension = ".old"; + char *backup = new char[strlen(filename)+strlen(extension)+1]; + strcpy(backup, filename); + strcat(backup, extension); + + gmx_file_copy(filename, backup, FALSE); + + delete [] backup; + + } + return COLVARS_OK; +} + + +void colvarproxy_gromacs::update_data(const t_commrec *cr, int64_t const step, t_pbc const &pbc, const matrix box, bool bNS) +{ + + if (MASTER(cr)) { + + if(cvm::debug()) { + cvm::log(cvm::line_marker); + cvm::log("colvarproxy_gromacs, step no. "+cvm::to_str(colvars->it)+"\n"+ + "Updating internal data.\n"); + } + + // step update on master only due to the call of colvars pointer. + if (first_timestep) { + first_timestep = false; + } else { + // Use the time step number inherited from GROMACS + if ( step - previous_gmx_step == 1 ) + colvars->it++; + // Other cases? + } + } // end master + + gmx_pbc = pbc; + gmx_box = box; + gmx_bNS = bNS; + + previous_gmx_step = step; + + // Prepare data for MPI communication + if(PAR(cr) && bNS) { + dd_make_local_group_indices(cr->dd->ga2la, n_colvars_atoms, ind, &nat_loc, &ind_loc, &nalloc_loc, xa_ind); + } +} + + +void colvarproxy_gromacs::calculateForces( + const gmx::ForceProviderInput &forceProviderInput, + gmx::ForceProviderOutput *forceProviderOutput) +{ + + const t_commrec *cr = &(forceProviderInput.cr_); + // Local atom coords + const gmx::ArrayRef x = forceProviderInput.x_; + // Local atom coords (coerced into into old gmx type) + const rvec *x_pointer = &(x.data()->as_vec()); + + + // Eventually there needs to be an interface to update local data upon neighbor search + // We could check if by chance all atoms are in one node, and skip communication + communicate_group_positions(cr, x_colvars_unwrapped, xa_shifts, xa_eshifts, + gmx_bNS, x_pointer, n_colvars_atoms, nat_loc, + ind_loc, xa_ind, xa_old_whole, gmx_box); + + // Communicate_group_positions takes care of removing shifts (unwrapping) + // in single node jobs, communicate_group_positions() is efficient and adds no overhead + + if (MASTER(cr)) + { + // On non-master nodes, jump directly to applying the forces + + // Zero the forces on the atoms, so that they can be accumulated by the colvars. + for (size_t i = 0; i < atoms_new_colvar_forces.size(); i++) { + atoms_new_colvar_forces[i].x = atoms_new_colvar_forces[i].y = atoms_new_colvar_forces[i].z = 0.0; + } + + // Get the atom positions from the Gromacs array. + for (size_t i = 0; i < atoms_ids.size(); i++) { + atoms_positions[i] = cvm::rvector(x_colvars_unwrapped[i][0], x_colvars_unwrapped[i][1], x_colvars_unwrapped[i][2]); + } + + bias_energy = 0.0; + // Call the collective variable module to fill atoms_new_colvar_forces + if (colvars->calc() != COLVARS_OK) { + cvm::error("Error calling colvars->calc()\n"); + } + + // Copy the forces to C array for broadcasting + for (int i = 0; i < n_colvars_atoms; i++) + { + f_colvars[i][0] = atoms_new_colvar_forces[i].x; + f_colvars[i][1] = atoms_new_colvar_forces[i].y; + f_colvars[i][2] = atoms_new_colvar_forces[i].z; + } + + forceProviderOutput->enerd_.term[F_COM_PULL] += bias_energy; + } // master node + + //Broadcast the forces to all the nodes + if (PAR(cr)) + { + nblock_bc(cr->mpi_comm_mygroup, n_colvars_atoms, f_colvars); + } + + const gmx::ArrayRef &f_out = forceProviderOutput->forceWithVirial_.force_; + matrix local_colvars_virial = { { 0 } }; + const bool computeVirial = forceProviderOutput->forceWithVirial_.computeVirial_; + + // Pass the applied forces back to GROMACS + for (int i = 0; i < n_colvars_atoms; i++) + { + int i_global = ind[i]; + + // check if this is a local atom and find out locndx + if (PAR(cr)) { + const int *locndx = cr->dd->ga2la->findHome(i_global); + if (locndx) { + f_out[*locndx] += f_colvars[i]; + if (computeVirial) { + add_virial_term(local_colvars_virial, f_colvars[i], x_colvars_unwrapped[i]); + } + } + // Do nothing if atom is not local + } else { // Non MPI-parallel + f_out[i_global] += f_colvars[i]; + if (computeVirial) { + add_virial_term(local_colvars_virial, f_colvars[i], x_colvars_unwrapped[i]); + } + } + } + + if (computeVirial) { + forceProviderOutput->forceWithVirial_.addVirialContribution(local_colvars_virial); + } + return; +} + + +void colvarproxy_gromacs::add_virial_term(matrix vir, rvec const f, gmx::RVec const x) +{ + for (int j = 0; j < DIM; j++) { + for (int m = 0; m < DIM; m++) { + vir[j][m] -= 0.5 * f[j] * x[m]; + } + } +} + + +// Pass restraint energy value for current timestep to MD engine +void colvarproxy_gromacs::add_energy (cvm::real energy) +{ + bias_energy += energy; +} + +// **************** ATOMS **************** + +int colvarproxy_gromacs::check_atom_id(int atom_number) +{ + // GROMACS uses zero-based arrays. + int const aid = (atom_number-1); + + if (cvm::debug()) + log("Adding atom "+cvm::to_str(atom_number)+ + " for collective variables calculation.\n"); + + if ( (aid < 0) || (aid >= gmx_atoms.nr) ) { + cvm::error("Error: invalid atom number specified, "+ + cvm::to_str(atom_number)+"\n", COLVARS_INPUT_ERROR); + return COLVARS_INPUT_ERROR; + } + + return aid; +} + + +int colvarproxy_gromacs::init_atom(int atom_number) +{ + // GROMACS uses zero-based arrays. + int aid = atom_number-1; + + for (size_t i = 0; i < atoms_ids.size(); i++) { + if (atoms_ids[i] == aid) { + // this atom id was already recorded + atoms_refcount[i] += 1; + return i; + } + } + + aid = check_atom_id(atom_number); + + if(aid < 0) { + return COLVARS_INPUT_ERROR; + } + + int const index = add_atom_slot(aid); + update_atom_properties(index); + return index; +} + +void colvarproxy_gromacs::update_atom_properties(int index) +{ + + // update mass + double const mass = gmx_atoms.atom[atoms_ids[index]].m; + if (mass <= 0.001) { + this->log("Warning: near-zero mass for atom "+ + cvm::to_str(atoms_ids[index]+1)+ + "; expect unstable dynamics if you apply forces to it.\n"); + } + atoms_masses[index] = mass; + // update charge + atoms_charges[index] = gmx_atoms.atom[atoms_ids[index]].q; +} diff --git a/src/gromacs/applied_forces/colvars/colvarproxy_gromacs.h b/src/gromacs/applied_forces/colvars/colvarproxy_gromacs.h new file mode 100644 index 00000000000..3f331e82d53 --- /dev/null +++ b/src/gromacs/applied_forces/colvars/colvarproxy_gromacs.h @@ -0,0 +1,144 @@ +/// -*- c++ -*- +/* Based on Jeff Comer's old code */ +#ifndef GMX_COLVARS_COLVARPROXY_GROMACS_H +#define GMX_COLVARS_COLVARPROXY_GROMACS_H + +#include "colvarmodule.h" +#include "colvaratoms.h" +#include "colvarproxy.h" +#include "gromacs/random/tabulatednormaldistribution.h" +#include "gromacs/random/threefry.h" +#include "gromacs/mdtypes/forceoutput.h" +#include "gromacs/mdlib/groupcoord.h" +#include "gromacs/mdtypes/iforceprovider.h" +#include "gromacs/mdtypes/observableshistory.h" +#include "gromacs/topology/atoms.h" +#include "colvarproxy_gromacs_version.h" + +/// \brief Communication between colvars and Gromacs (implementation of +/// \link colvarproxy \endlink) +class colvarproxy_gromacs : public colvarproxy, public gmx::IForceProvider { +public: + // GROMACS structures. + //PBC struct + t_pbc gmx_pbc; + //Box + const real (*gmx_box)[3]; + // + t_atoms gmx_atoms; +protected: + bool first_timestep; + + bool colvars_restart; + std::string config_file; + size_t restart_frequency_s; + int previous_gmx_step; + double bias_energy; + + bool gmx_bNS; // Is this a neighbor-search step? Eventually will become unnecessary + + // GROMACS random number generation. + gmx::DefaultRandomEngine rng; // gromacs random number generator + gmx::TabulatedNormalDistribution<> normal_distribution; + + + // Node-local bookkepping data + //! Total number of Colvars atoms + int n_colvars_atoms = 0; + //! Part of the atoms that are local. + int nat_loc = 0; + //! Global indices of the Colvars atoms. + int *ind = nullptr; + //! Local indices of the Colvars atoms. + int *ind_loc = nullptr; + //! Allocation size for ind_loc. + int nalloc_loc = 0; + //! Unwrapped positions for all Colvars atoms, communicated to all nodes. + rvec *x_colvars_unwrapped = nullptr; + //! Shifts for all Colvars atoms, to make molecule(s) whole. + ivec *xa_shifts = nullptr; + //! Extra shifts since last DD step. + ivec *xa_eshifts = nullptr; + //! Old positions for all Colvars atoms on master. + rvec *xa_old_whole = nullptr; + //! Position of each local atom in the collective array. + int *xa_ind = nullptr; + //! Bias forces on all Colvars atoms + rvec *f_colvars = nullptr; +public: + friend class cvm::atom; + colvarproxy_gromacs(); + ~colvarproxy_gromacs(); + + // Initialize colvars. + void init(t_inputrec *gmx_inp, int64_t step, const gmx_mtop_t &mtop, ObservablesHistory* oh, + const std::string &prefix, gmx::ArrayRef filenames_config, + const std::string &filename_restart, const t_commrec *cr, + const rvec x[], rvec **xa_old_whole_colvars_state_p, int *n_colvars_atoms_state_p); + + void dd_make_local_atoms(const t_commrec *cr); + // Called each step before evaluating the force provider + // Should eventually be replaced by the MDmodule interface? + void update_data(const t_commrec *cr, int64_t const step, t_pbc const &pbc, const matrix box, bool bNS); + /*! \brief + * Computes forces. + * + * \param[in] forceProviderInput struct that collects input data for the force providers + * \param[in,out] forceProviderOutput struct that collects output data of the force providers + */ + virtual void calculateForces(const gmx::ForceProviderInput &forceProviderInput, + gmx::ForceProviderOutput *forceProviderOutput); + + // Compute virial tensor for position r and force f, and add to matrix vir + void add_virial_term(matrix vir, rvec const f, gmx::RVec const r); + + void add_energy (cvm::real energy); + void finish(const t_commrec *cr); + + // **************** SYSTEM-WIDE PHYSICAL QUANTITIES **************** + cvm::real rand_gaussian(); + // **************** SIMULATION PARAMETERS **************** + size_t restart_frequency(); + std::string restart_output_prefix(); + std::string output_prefix(); + // **************** PERIODIC BOUNDARY CONDITIONS **************** + cvm::rvector position_distance (cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) const; + + // **************** INPUT/OUTPUT **************** + /// Print a message to the main log + void log (std::string const &message); + /// Print a message to the main log and let the rest of the program handle the error + void error (std::string const &message); + /// Print a message to the main log and exit with error code + void fatal_error (std::string const &message); + /// Print a message to the main log and exit normally + void exit (std::string const &message); + /// Request to set the units used internally by Colvars + int set_unit_system(std::string const &units_in, bool colvars_defined); + int backup_file (char const *filename); + /// Read atom identifiers from a file \param filename name of + /// the file (usually a PDB) \param atoms array to which atoms read + /// from "filename" will be appended \param pdb_field (optiona) if + /// "filename" is a PDB file, use this field to determine which are + /// the atoms to be set + int load_atoms (char const *filename, + std::vector &atoms, + std::string const &pdb_field, + double const pdb_field_value = 0.0); + /// Load the coordinates for a group of atoms from a file + /// (usually a PDB); if "pos" is already allocated, the number of its + /// elements must match the number of atoms in "filename" + int load_coords (char const *filename, + std::vector &pos, + const std::vector &indices, + std::string const &pdb_field, + double const pdb_field_value = 0.0); + + int init_atom(int atom_number); + + int check_atom_id(int atom_number); + void update_atom_properties(int index); +}; + +#endif diff --git a/src/gromacs/applied_forces/colvars/colvarproxy_gromacs_version.h b/src/gromacs/applied_forces/colvars/colvarproxy_gromacs_version.h new file mode 100644 index 00000000000..5f7e40b6627 --- /dev/null +++ b/src/gromacs/applied_forces/colvars/colvarproxy_gromacs_version.h @@ -0,0 +1,3 @@ +#ifndef COLVARPROXY_VERSION +#define COLVARPROXY_VERSION "2023-10-03" +#endif diff --git a/src/gromacs/fileio/checkpoint.cpp b/src/gromacs/fileio/checkpoint.cpp index 5ba8136cefa..c55048a05aa 100644 --- a/src/gromacs/fileio/checkpoint.cpp +++ b/src/gromacs/fileio/checkpoint.cpp @@ -69,6 +69,7 @@ #include "gromacs/mdtypes/pullhistory.h" #include "gromacs/mdtypes/state.h" #include "gromacs/mdtypes/swaphistory.h" +#include "gromacs/mdtypes/colvarshistory.h" #include "gromacs/modularsimulator/modularsimulator.h" #include "gromacs/trajectory/trajectoryframe.h" #include "gromacs/utility/arrayref.h" @@ -1287,6 +1288,14 @@ static void do_cpt_header(XDR* xd, gmx_bool bRead, FILE* list, CheckpointHeaderC { contents->isModularSimulatorCheckpoint = false; } + if (contents->file_version >= CheckPointVersion::Colvars) + { + do_cpt_int_err(xd, "colvars atoms", &contents->ecolvars, list); + } + else + { + contents->ecolvars = 0; + } } static int do_cpt_footer(XDR* xd, CheckPointVersion file_version) @@ -2035,6 +2044,36 @@ static int do_cpt_EDstate(XDR* xd, gmx_bool bRead, int nED, edsamhistory_t* EDst return 0; } +/* This function stores the last whole configuration of the colvars atoms in the .cpt file */ +static int do_cpt_colvars(XDR* xd, gmx_bool bRead, int ecolvars, colvarshistory_t* colvarsstate, FILE* list) +{ + + if (ecolvars == 0) + { + return 0; + } + + colvarsstate->bFromCpt = bRead; + colvarsstate->n_atoms = ecolvars; + + /* Write data */ + char buf[STRLEN]; + sprintf(buf, "Colvars atoms in reference structure : %d", ecolvars); + sprintf(buf, "Colvars xa_old"); + if (bRead) + { + snew(colvarsstate->xa_old_whole, colvarsstate->n_atoms); + do_cpt_n_rvecs_err(xd, buf, colvarsstate->n_atoms, colvarsstate->xa_old_whole, list); + } + else + { + do_cpt_n_rvecs_err(xd, buf, colvarsstate->n_atoms, colvarsstate->xa_old_whole_p, list); + } + + return 0; +} + + static int do_cpt_correlation_grid(XDR* xd, gmx_bool bRead, gmx_unused int fflags, @@ -2433,6 +2472,7 @@ void write_checkpoint_data(t_fileio* fp, observablesHistory->swapHistory.get(), nullptr) < 0) + || (do_cpt_colvars(gmx_fio_getxdr(fp), FALSE, headerContents.ecolvars, observablesHistory->colvarsHistory.get(), nullptr) < 0) || (do_cpt_files(gmx_fio_getxdr(fp), FALSE, outputfiles, nullptr, headerContents.file_version) < 0)) { gmx_file("Cannot read/write checkpoint; corrupt file, or maybe you are out of disk space?"); @@ -2826,6 +2866,17 @@ static void read_checkpoint(const char* fn, cp_error(); } + if (headerContents->ecolvars != 0 && observablesHistory->colvarsHistory == nullptr) + { + observablesHistory->colvarsHistory = std::make_unique(colvarshistory_t{}); + } + ret = do_cpt_colvars(gmx_fio_getxdr(fp), TRUE, headerContents->ecolvars, + observablesHistory->colvarsHistory.get(), nullptr); + if (ret) + { + cp_error(); + } + std::vector outputfiles; ret = do_cpt_files(gmx_fio_getxdr(fp), TRUE, &outputfiles, nullptr, headerContents->file_version); if (ret) @@ -3001,6 +3052,13 @@ static CheckpointHeaderContents read_checkpoint_data(t_fileio* cp_error(); } + colvarshistory_t colvarshist = {}; + ret = do_cpt_colvars(gmx_fio_getxdr(fp), TRUE, headerContents.ecolvars, &colvarshist, nullptr); + if (ret) + { + cp_error(); + } + ret = do_cpt_files(gmx_fio_getxdr(fp), TRUE, outputfiles, nullptr, headerContents.file_version); if (ret) @@ -3121,6 +3179,12 @@ void list_checkpoint(const char* fn, FILE* out) ret = do_cpt_swapstate(gmx_fio_getxdr(fp), TRUE, headerContents.eSwapCoords, &swaphist, out); } + if (ret == 0) + { + colvarshistory_t colvarshist = {}; + ret = do_cpt_colvars(gmx_fio_getxdr(fp), TRUE, headerContents.ecolvars, &colvarshist, out); + } + if (ret == 0) { std::vector outputfiles; diff --git a/src/gromacs/fileio/checkpoint.cpp.orig b/src/gromacs/fileio/checkpoint.cpp.orig new file mode 100644 index 00000000000..5ba8136cefa --- /dev/null +++ b/src/gromacs/fileio/checkpoint.cpp.orig @@ -0,0 +1,3167 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright 2008- The GROMACS Authors + * and the project initiators Erik Lindahl, Berk Hess and David van der Spoel. + * Consult the AUTHORS/COPYING files and https://www.gromacs.org for details. + * + * GROMACS 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 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS 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 GROMACS; if not, see + * https://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at https://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out https://www.gromacs.org. + */ + +/* The source code in this file should be thread-safe. + Please keep it that way. */ +#include "gmxpre.h" + +#include "checkpoint.h" + +#include +#include +#include + +#include +#include + +#include "buildinfo.h" +#include "gromacs/fileio/filetypes.h" +#include "gromacs/fileio/gmxfio.h" +#include "gromacs/fileio/gmxfio_xdr.h" +#include "gromacs/fileio/xdr_datatype.h" +#include "gromacs/fileio/xdrf.h" +#include "gromacs/gmxlib/network.h" +#include "gromacs/math/vec.h" +#include "gromacs/math/vecdump.h" +#include "gromacs/math/vectypes.h" +#include "gromacs/mdtypes/awh_correlation_history.h" +#include "gromacs/mdtypes/awh_history.h" +#include "gromacs/mdtypes/checkpointdata.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/mdtypes/df_history.h" +#include "gromacs/mdtypes/edsamhistory.h" +#include "gromacs/mdtypes/energyhistory.h" +#include "gromacs/mdtypes/inputrec.h" +#include "gromacs/mdtypes/md_enums.h" +#include "gromacs/mdtypes/mdrunoptions.h" +#include "gromacs/mdtypes/observableshistory.h" +#include "gromacs/mdtypes/pullhistory.h" +#include "gromacs/mdtypes/state.h" +#include "gromacs/mdtypes/swaphistory.h" +#include "gromacs/modularsimulator/modularsimulator.h" +#include "gromacs/trajectory/trajectoryframe.h" +#include "gromacs/utility/arrayref.h" +#include "gromacs/utility/baseversion.h" +#include "gromacs/utility/cstringutil.h" +#include "gromacs/utility/enumerationhelpers.h" +#include "gromacs/utility/fatalerror.h" +#include "gromacs/utility/futil.h" +#include "gromacs/utility/gmxassert.h" +#include "gromacs/utility/int64_to_int.h" +#include "gromacs/utility/keyvaluetree.h" +#include "gromacs/utility/keyvaluetreebuilder.h" +#include "gromacs/utility/keyvaluetreeserializer.h" +#include "gromacs/utility/mdmodulesnotifiers.h" +#include "gromacs/utility/programcontext.h" +#include "gromacs/utility/smalloc.h" +#include "gromacs/utility/sysinfo.h" +#include "gromacs/utility/textwriter.h" +#include "gromacs/utility/txtdump.h" + +#define CPT_MAGIC1 171817 +#define CPT_MAGIC2 171819 + +namespace gmx +{ + +template +void readKvtCheckpointValue(compat::not_null value, + const std::string& name, + const std::string& identifier, + const KeyValueTreeObject& kvt) +{ + const std::string key = identifier + "-" + name; + if (!kvt.keyExists(key)) + { + std::string errorMessage = "Cannot read requested checkpoint value " + key + " ."; + GMX_THROW(InternalError(errorMessage)); + } + *value = kvt[key].cast(); +} + +template void readKvtCheckpointValue(compat::not_null value, + const std::string& name, + const std::string& identifier, + const KeyValueTreeObject& kvt); +template void readKvtCheckpointValue(compat::not_null value, + const std::string& name, + const std::string& identifier, + const KeyValueTreeObject& kvt); + +template +void writeKvtCheckpointValue(const ValueType& value, + const std::string& name, + const std::string& identifier, + KeyValueTreeObjectBuilder kvtBuilder) +{ + kvtBuilder.addValue(identifier + "-" + name, value); +} + +template void writeKvtCheckpointValue(const std::int64_t& value, + const std::string& name, + const std::string& identifier, + KeyValueTreeObjectBuilder kvtBuilder); +template void writeKvtCheckpointValue(const real& value, + const std::string& name, + const std::string& identifier, + KeyValueTreeObjectBuilder kvtBuilder); + + +} // namespace gmx + +/*! \brief Version number of the file format written to checkpoint + * files by this version of the code. + * + * cpt_version should normally only be changed, via adding a new field + * to cptv enumeration, when the header or footer format changes. + * + * The state data format itself is backward and forward compatible. + * But old code can not read a new entry that is present in the file + * (but can read a new format when new entries are not present). + * + * The cpt_version increases whenever the file format in the main + * development branch changes, due to an extension of the CheckPointVersion + * enum (see src/gromacs/fileio/checkpoint.h). + * Backward compatibility for reading old run input files is maintained + * by checking this version number against that of the file and then using + * the correct code path. */ +static constexpr CheckPointVersion cpt_version = CheckPointVersion::CurrentVersion; + +const char* enumValueToString(StateEntry enumValue) +{ + static constexpr gmx::EnumerationArray stateEntryNames = { + "FE-lambda", + "box", + "box-rel", + "box-v", + "pres_prev", + "nosehoover-xi", + "thermostat-integral", + "x", + "v", + "sdx-unsupported", + "CGp", + "LD-rng-unsupported", + "LD-rng-i-unsupported", + "disre_initf", + "disre_rm3tav", + "orire_initf", + "orire_Dtav", + "svir_prev", + "nosehoover-vxi", + "v_eta", + "vol0", + "nhpres_xi", + "nhpres_vxi", + "fvir_prev", + "fep_state", + "MC-rng-unsupported", + "MC-rng-i-unsupported", + "barostat-integral" + }; + return stateEntryNames[enumValue]; +} + +enum class StateKineticEntry : int +{ + EkinNumber, + EkinHalfStep, + DEkinDLambda, + Mvcos, + EkinFullStep, + EkinHalfStepOld, + EkinNoseHooverScaleFullStep, + EkinNoseHooverScaleHalfStep, + VelocityScale, + EkinTotal, + Count +}; + +static const char* enumValueToString(StateKineticEntry enumValue) +{ + static constexpr gmx::EnumerationArray stateKineticEntryNames = { + "Ekin_n", "Ekinh", "dEkindlambda", "mv_cos", "Ekinf", + "Ekinh_old", "EkinScaleF_NHC", "EkinScaleH_NHC", "Vscale_NHC", "Ekin_Total" + }; + return stateKineticEntryNames[enumValue]; +} + +enum class StateEnergyEntry : int +{ + N, + Aver, + Sum, + NumSum, + SumSim, + NumSumSim, + NumSteps, + NumStepsSim, + DeltaHNN, + DeltaHList, + DeltaHStartTime, + DeltaHStartLambda, + Count +}; + +enum class StatePullEntry : int +{ + NumCoordinates, + NumGroups, + NumValuesInXSum, + NumValuesInFSum, + Count +}; + +enum class StatePullCoordEntry : int +{ + ValueReferenceSum, + ValueSum, + DR01Sum, + DR23Sum, + DR45Sum, + FScalarSum, + DynaxSum, + Count +}; + +static const char* enumValueToString(StatePullCoordEntry enumValue) +{ + static constexpr gmx::EnumerationArray statePullCoordEntryNames = { + "reference-sum", "sum", "dr01-sum", "dr23-sum", "dr45-sum", "fscal-sum", "dynax-sum" + }; + return statePullCoordEntryNames[enumValue]; +} + +enum class StatePullGroupEntry : int +{ + XSum, + Count +}; + +static const char* enumValueToString(StatePullGroupEntry enumValue) +{ + static constexpr gmx::EnumerationArray statePullGroupEntryNames = { + "coordinate-sum" + }; + return statePullGroupEntryNames[enumValue]; +} + +static const char* enumValueToString(StateEnergyEntry enumValue) +{ + static constexpr gmx::EnumerationArray stateEnergyEntryNames = { + "energy_n", + "energy_aver", + "energy_sum", + "energy_nsum", + "energy_sum_sim", + "energy_nsum_sim", + "energy_nsteps", + "energy_nsteps_sim", + "energy_delta_h_nn", + "energy_delta_h_list", + "energy_delta_h_start_time", + "energy_delta_h_start_lambda" + }; + return stateEnergyEntryNames[enumValue]; +} + +static const char* enumValueToString(StatePullEntry enumValue) +{ + static constexpr gmx::EnumerationArray statePullEntryNames = { + "pullhistory_numcoordinates", + "pullhistory_numgroups", + "pullhistory_numvaluesinxsum", + "pullhistory_numvaluesinfsum" + }; + return statePullEntryNames[enumValue]; +} + +/* free energy history variables -- need to be preserved over checkpoint */ +enum class StateFepEntry : int +{ + IsEquilibrated, + NumAtLambda, + WangLandauHistogram, + WangLandauDelta, + SumWeights, + SumDG, + SumMinVar, + SumVar, + Accump, + Accumm, + Accump2, + Accumm2, + Tij, + TijEmp, + Count +}; + +//! free energy history names +static const char* enumValueToString(StateFepEntry enumValue) +{ + static constexpr gmx::EnumerationArray stateFepEntryNames = { + "bEquilibrated", + "N_at_state", + "Wang-Landau Histogram", + "Wang-Landau Delta", + "Weights", + "Free Energies", + "minvar", + "variance", + "accumulated_plus", + "accumulated_minus", + "accumulated_plus_2", + "accumulated_minus_2", + "Tij", + "Tij_empirical" + }; + return stateFepEntryNames[enumValue]; +} + +//! AWH biasing history variables +enum class StateAwhEntry : int +{ + InInitial, + EquilibrateHistogram, + HistogramSize, + NumPoints, + CoordPoint, + UmbrellaGridPoint, + UpdateList, + LogScaledSampleWeight, + NumUpdates, + ForceCorrelationGrid, + Count +}; + +static const char* enumValueToString(StateAwhEntry enumValue) +{ + static constexpr gmx::EnumerationArray stateAwhEntryNames = { + "awh_in_initial", "awh_equilibrateHistogram", "awh_histsize", "awh_npoints", + "awh_coordpoint", "awh_umbrellaGridpoint", "awh_updatelist", "awh_logScaledSampleWeight", + "awh_numupdates", "awh_forceCorrelationGrid" + }; + return stateAwhEntryNames[enumValue]; +} + +enum class StatePullCommunicationEntry : int +{ + PreviousStepCom, + Count +}; + +//! Higher level vector element type, only used for formatting checkpoint dumps +enum class CptElementType +{ + integer, //!< integer + real, //!< float or double, not linked to precision of type real + real3, //!< float[3] or double[3], not linked to precision of type real + matrix3x3 //!< float[3][3] or double[3][3], not linked to precision of type real +}; + +static void cp_warning(FILE* fp) +{ + fprintf(fp, "\nWARNING: Checkpoint file is corrupted or truncated\n\n"); +} + +[[noreturn]] static void cp_error() +{ + gmx_fatal(FARGS, "Checkpoint file corrupted/truncated, or maybe you are out of disk space?"); +} + +static void do_cpt_string_err(XDR* xd, const char* desc, gmx::ArrayRef s, FILE* list) +{ + char* data = s.data(); + if (xdr_string(xd, &data, s.size()) == 0) + { + cp_error(); + } + if (list) + { + fprintf(list, "%s = %s\n", desc, data); + } +} + +static int do_cpt_int(XDR* xd, const char* desc, int* i, FILE* list) +{ + if (xdr_int(xd, i) == 0) + { + return -1; + } + if (list) + { + fprintf(list, "%s = %d\n", desc, *i); + } + return 0; +} + +static int do_cpt_u_chars(XDR* xd, const char* desc, int n, unsigned char* i, FILE* list) +{ + if (list) + { + fprintf(list, "%s = ", desc); + } + bool_t res = 1; + for (int j = 0; j < n && res; j++) + { + res &= xdr_u_char(xd, &i[j]); + if (list) + { + fprintf(list, "%02x", i[j]); + } + } + if (list) + { + fprintf(list, "\n"); + } + if (res == 0) + { + return -1; + } + + return 0; +} + +template +static int do_cpt_enum_as_int(XDR* xd, const char* desc, EnumType* enumValue, FILE* list) +{ + static_assert(std::is_same, int>::value, + "Only enums with underlying type int are supported."); + auto castedValue = static_cast(*enumValue); + if (xdr_int(xd, &castedValue) == 0) + { + return -1; + } + *enumValue = static_cast(castedValue); + if (list) + { + fprintf(list, "%s = %d\n", desc, castedValue); + } + return 0; +} + +template +static int do_cpt_n_enum_as_int(XDR* xd, const char* desc, int n, EnumType* enumValue, FILE* list) +{ + bool_t res = 1; + for (int j = 0; j < n && res; j++) + { + res &= do_cpt_enum_as_int(xd, desc, &enumValue[j], list); + } + if (res == 0) + { + return -1; + } + + return 0; +} + +static void do_cpt_int_err(XDR* xd, const char* desc, int* i, FILE* list) +{ + if (do_cpt_int(xd, desc, i, list) < 0) + { + cp_error(); + } +} + +static void do_cpt_bool_err(XDR* xd, const char* desc, bool* b, FILE* list) +{ + int i = static_cast(*b); + + if (do_cpt_int(xd, desc, &i, list) < 0) + { + cp_error(); + } + + *b = (i != 0); +} + +static void do_cpt_step_err(XDR* xd, const char* desc, int64_t* i, FILE* list) +{ + char buf[STEPSTRSIZE]; + + if (xdr_int64(xd, i) == 0) + { + cp_error(); + } + if (list) + { + fprintf(list, "%s = %s\n", desc, gmx_step_str(*i, buf)); + } +} + +static void do_cpt_double_err(XDR* xd, const char* desc, double* f, FILE* list) +{ + if (xdr_double(xd, f) == 0) + { + cp_error(); + } + if (list) + { + fprintf(list, "%s = %f\n", desc, *f); + } +} + +static void do_cpt_real_err(XDR* xd, real* f) +{ +#if GMX_DOUBLE + bool_t res = xdr_double(xd, f); +#else + bool_t res = xdr_float(xd, f); +#endif + if (res == 0) + { + cp_error(); + } +} + +static void do_cpt_n_rvecs_err(XDR* xd, const char* desc, int n, rvec f[], FILE* list) +{ + for (int i = 0; i < n; i++) + { + for (int j = 0; j < DIM; j++) + { + do_cpt_real_err(xd, &f[i][j]); + } + } + + if (list) + { + pr_rvecs(list, 0, desc, f, n); + } +} + +template +struct xdr_type +{ +}; + +template<> +struct xdr_type +{ + static const XdrDataType value = XdrDataType::Int; +}; + +template<> +struct xdr_type +{ + static const XdrDataType value = XdrDataType::Float; +}; + +template<> +struct xdr_type +{ + static const XdrDataType value = XdrDataType::Double; +}; + +//! \brief Returns size in byte of an XdrDataType +static inline unsigned int sizeOfXdrType(XdrDataType xdrType) +{ + switch (xdrType) + { + case XdrDataType::Int: return sizeof(int); + case XdrDataType::Float: return sizeof(float); + case XdrDataType::Double: return sizeof(double); + default: GMX_RELEASE_ASSERT(false, "XDR data type not implemented"); + } + + return 0; +} + +//! \brief Returns the XDR process function for i/o of an XDR type +static inline xdrproc_t xdrProc(XdrDataType xdrType) +{ + switch (xdrType) + { + case XdrDataType::Int: return reinterpret_cast(xdr_int); + case XdrDataType::Float: return reinterpret_cast(xdr_float); + case XdrDataType::Double: return reinterpret_cast(xdr_double); + default: GMX_RELEASE_ASSERT(false, "XDR data type not implemented"); + } + + return nullptr; +} + +/*! \brief Lists or only reads an xdr vector from checkpoint file + * + * When list!=NULL reads and lists the \p nf vector elements of type \p xdrType. + * The header for the print is set by \p part and \p ecpt. + * The formatting of the printing is set by \p cptElementType. + * When list==NULL only reads the elements. + */ +template +static bool_t listXdrVector(XDR* xd, Enum ecpt, int nf, XdrDataType xdrType, FILE* list, CptElementType cptElementType) +{ + bool_t res = 0; + + const unsigned int elemSize = sizeOfXdrType(xdrType); + std::vector data(nf * elemSize); + res = xdr_vector(xd, data.data(), nf, elemSize, xdrProc(xdrType)); + + if (list != nullptr) + { + switch (xdrType) + { + case XdrDataType::Int: + pr_ivec(list, 0, enumValueToString(ecpt), reinterpret_cast(data.data()), nf, TRUE); + break; + case XdrDataType::Float: +#if !GMX_DOUBLE + if (cptElementType == CptElementType::real3) + { + pr_rvecs(list, 0, enumValueToString(ecpt), reinterpret_cast(data.data()), nf / 3); + } + else +#endif + { + /* Note: With double precision code dumping a single precision rvec will produce float iso rvec print, but that's a minor annoyance */ + pr_fvec(list, 0, enumValueToString(ecpt), reinterpret_cast(data.data()), nf, TRUE); + } + break; + case XdrDataType::Double: +#if GMX_DOUBLE + if (cptElementType == CptElementType::real3) + { + pr_rvecs(list, 0, enumValueToString(ecpt), reinterpret_cast(data.data()), nf / 3); + } + else +#endif + { + /* Note: With single precision code dumping a double precision rvec will produce float iso rvec print, but that's a minor annoyance */ + pr_dvec(list, 0, enumValueToString(ecpt), reinterpret_cast(data.data()), nf, TRUE); + } + break; + default: GMX_RELEASE_ASSERT(false, "Data type not implemented for listing"); + } + } + + return res; +} + +//! \brief Convert a double array, typed char*, to float +gmx_unused static void convertArrayRealPrecision(const char* c, float* v, int n) +{ + const double* d = reinterpret_cast(c); + for (int i = 0; i < n; i++) + { + v[i] = static_cast(d[i]); + } +} + +//! \brief Convert a float array, typed char*, to double +static void convertArrayRealPrecision(const char* c, double* v, int n) +{ + const float* f = reinterpret_cast(c); + for (int i = 0; i < n; i++) + { + v[i] = static_cast(f[i]); + } +} + +//! \brief Generate an error for trying to convert to integer +static void convertArrayRealPrecision(const char gmx_unused* c, int gmx_unused* v, int gmx_unused n) +{ + GMX_RELEASE_ASSERT(false, + "We only expect type mismatches between float and double, not integer"); +} + +/*! \brief Low-level routine for reading/writing a vector of reals from/to file. + * + * This is the only routine that does the actually i/o of real vector, + * all other routines are intermediate level routines for specific real + * data types, calling this routine. + * Currently this routine is (too) complex, since it handles both real * + * and std::vector. Using real * is deprecated and this routine + * will simplify a lot when only std::vector needs to be supported. + * + * The routine is generic to vectors with different allocators, + * because as part of reading a checkpoint there are vectors whose + * size is not known until reading has progressed far enough, so a + * resize method must be called. + * + * When not listing, we use either v or vector, depending on which is !=NULL. + * If nval >= 0, nval is used; on read this should match the passed value. + * If nval n<0, *nptr (with v) or vector->size() is used. On read using v, + * the value is stored in nptr + */ +template +static int doVectorLow(XDR* xd, + Enum ecpt, + int sflags, + int nval, + int* nptr, + T** v, + std::vector* vector, + FILE* list, + CptElementType cptElementType) +{ + GMX_RELEASE_ASSERT(list != nullptr || (v != nullptr && vector == nullptr) + || (v == nullptr && vector != nullptr), + "Without list, we should have exactly one of v and vector != NULL"); + + bool_t res = 0; + + int numElemInTheFile; + if (list == nullptr) + { + if (nval >= 0) + { + GMX_RELEASE_ASSERT(nptr == nullptr, "With nval>=0 we should have nptr==NULL"); + numElemInTheFile = nval; + } + else + { + if (v != nullptr) + { + GMX_RELEASE_ASSERT(nptr != nullptr, "With nval<0 we should have nptr!=NULL"); + numElemInTheFile = *nptr; + } + else + { + numElemInTheFile = vector->size(); + } + } + } + /* Read/write the vector element count */ + res = xdr_int(xd, &numElemInTheFile); + if (res == 0) + { + return -1; + } + /* Read/write the element data type */ + constexpr XdrDataType xdrTypeInTheCode = xdr_type::value; + XdrDataType xdrTypeInTheFile = xdrTypeInTheCode; + int xdrTypeInTheFileAsInt = static_cast(xdrTypeInTheFile); + res = xdr_int(xd, &xdrTypeInTheFileAsInt); + xdrTypeInTheFile = static_cast(xdrTypeInTheFileAsInt); + if (res == 0) + { + return -1; + } + + if (list == nullptr && (sflags & enumValueToBitMask(ecpt))) + { + if (nval >= 0) + { + if (numElemInTheFile != nval) + { + gmx_fatal(FARGS, + "Count mismatch for state entry %s, code count is %d, file count is %d\n", + enumValueToString(ecpt), + nval, + numElemInTheFile); + } + } + else if (nptr != nullptr) + { + *nptr = numElemInTheFile; + } + + bool typesMatch = (xdrTypeInTheFile == xdrTypeInTheCode); + if (!typesMatch) + { + char buf[STRLEN]; + sprintf(buf, + "mismatch for state entry %s, code precision is %s, file precision is %s", + enumValueToString(ecpt), + enumValueToString(xdrTypeInTheCode), + enumValueToString(xdrTypeInTheFile)); + + /* Matching int and real should never occur, but check anyhow */ + if (xdrTypeInTheFile == XdrDataType::Int || xdrTypeInTheCode == XdrDataType::Int) + { + gmx_fatal(FARGS, + "Type %s: incompatible checkpoint formats or corrupted checkpoint file.", + buf); + } + } + + T* vp; + if (v != nullptr) + { + if (*v == nullptr) + { + snew(*v, numElemInTheFile); + } + vp = *v; + } + else + { + /* This conditional ensures that we don't resize on write. + * In particular in the state where this code was written + * vector has a size of numElemInThefile and we + * don't want to lose that padding here. + */ + if (vector->size() < static_cast(numElemInTheFile)) + { + vector->resize(numElemInTheFile); + } + vp = vector->data(); + } + + char* vChar; + if (typesMatch) + { + vChar = reinterpret_cast(vp); + } + else + { + snew(vChar, numElemInTheFile * sizeOfXdrType(xdrTypeInTheFile)); + } + res = xdr_vector( + xd, vChar, numElemInTheFile, sizeOfXdrType(xdrTypeInTheFile), xdrProc(xdrTypeInTheFile)); + if (res == 0) + { + return -1; + } + + if (!typesMatch) + { + /* In the old code float-double conversion came for free. + * In the new code we still support it, mainly because + * the tip4p_continue regression test makes use of this. + * It's an open question if we do or don't want to allow this. + */ + convertArrayRealPrecision(vChar, vp, numElemInTheFile); + sfree(vChar); + } + } + else + { + res = listXdrVector(xd, ecpt, numElemInTheFile, xdrTypeInTheFile, list, cptElementType); + } + + return 0; +} + +//! \brief Read/Write a std::vector, on read checks the number of elements matches \p numElements, if specified. +template +static int doVector(XDR* xd, Enum ecpt, int sflags, std::vector* vector, FILE* list, int numElements = -1) +{ + return doVectorLow( + xd, ecpt, sflags, numElements, nullptr, nullptr, vector, list, CptElementType::real); +} + +//! \brief Read/Write an ArrayRef. +template +static int doRealArrayRef(XDR* xd, Enum ecpt, int sflags, gmx::ArrayRef vector, FILE* list) +{ + real* v_real = vector.data(); + return doVectorLow>( + xd, ecpt, sflags, vector.size(), nullptr, &v_real, nullptr, list, CptElementType::real); +} + +//! Convert from view of RVec to view of real. +static gmx::ArrayRef realArrayRefFromRVecArrayRef(gmx::ArrayRef ofRVecs) +{ + return gmx::arrayRefFromArray(reinterpret_cast(ofRVecs.data()), ofRVecs.size() * DIM); +} + +//! \brief Read/Write a PaddedVector whose value_type is RVec. +template +static int doRvecVector(XDR* xd, Enum ecpt, int sflags, PaddedVectorOfRVecType* v, int numAtoms, FILE* list) +{ + const int numReals = numAtoms * DIM; + + if (list == nullptr) + { + GMX_RELEASE_ASSERT( + sflags & enumValueToBitMask(ecpt), + "When not listing, the flag for the entry should be set when requesting i/o"); + GMX_RELEASE_ASSERT(v->size() == numAtoms, "v should have sufficient size for numAtoms"); + + return doRealArrayRef(xd, ecpt, sflags, realArrayRefFromRVecArrayRef(makeArrayRef(*v)), list); + } + else + { + // Use the rebind facility to change the value_type of the + // allocator from RVec to real. + using realAllocator = + typename std::allocator_traits::template rebind_alloc; + return doVectorLow( + xd, ecpt, sflags, numReals, nullptr, nullptr, nullptr, list, CptElementType::real); + } +} + +/* This function stores n along with the reals for reading, + * but on reading it assumes that n matches the value in the checkpoint file, + * a fatal error is generated when this is not the case. + */ +template +static int do_cpte_reals(XDR* xd, Enum ecpt, int sflags, int n, real** v, FILE* list) +{ + return doVectorLow>( + xd, ecpt, sflags, n, nullptr, v, nullptr, list, CptElementType::real); +} + +template +static int do_cpte_real(XDR* xd, Enum ecpt, int sflags, real* r, FILE* list) +{ + return doVectorLow>( + xd, ecpt, sflags, 1, nullptr, &r, nullptr, list, CptElementType::real); +} + +template +static int do_cpte_ints(XDR* xd, Enum ecpt, int sflags, int n, int** v, FILE* list) +{ + return doVectorLow>( + xd, ecpt, sflags, n, nullptr, v, nullptr, list, CptElementType::integer); +} + +template +static int do_cpte_int(XDR* xd, Enum ecpt, int sflags, int* i, FILE* list) +{ + return do_cpte_ints(xd, ecpt, sflags, 1, &i, list); +} + +template +static int do_cpte_bool(XDR* xd, Enum ecpt, int sflags, bool* b, FILE* list) +{ + int i = static_cast(*b); + int ret = do_cpte_int(xd, ecpt, sflags, &i, list); + *b = (i != 0); + return ret; +} + +template +static int do_cpte_doubles(XDR* xd, Enum ecpt, int sflags, int n, double** v, FILE* list) +{ + return doVectorLow>( + xd, ecpt, sflags, n, nullptr, v, nullptr, list, CptElementType::real); +} + +template +static int do_cpte_double(XDR* xd, Enum ecpt, int sflags, double* r, FILE* list) +{ + return do_cpte_doubles(xd, ecpt, sflags, 1, &r, list); +} + +template +static int do_cpte_matrix(XDR* xd, Enum ecpt, int sflags, matrix v, FILE* list) +{ + real* vr; + int ret; + + vr = &(v[0][0]); + ret = doVectorLow>( + xd, ecpt, sflags, DIM * DIM, nullptr, &vr, nullptr, nullptr, CptElementType::matrix3x3); + + if (list && ret == 0) + { + pr_rvecs(list, 0, enumValueToString(ecpt), v, DIM); + } + + return ret; +} + +template +static int do_cpte_nmatrix(XDR* xd, Enum ecpt, int sflags, int n, real** v, FILE* list) +{ + int i; + int ret, reti; + char name[CPTSTRLEN]; + + ret = 0; + if (v == nullptr) + { + snew(v, n); + } + for (i = 0; i < n; i++) + { + reti = doVectorLow>( + xd, ecpt, sflags, n, nullptr, &(v[i]), nullptr, nullptr, CptElementType::matrix3x3); + if (list && reti == 0) + { + sprintf(name, "%s[%d]", enumValueToString(ecpt), i); + pr_reals(list, 0, name, v[i], n); + } + if (reti != 0) + { + ret = reti; + } + } + return ret; +} + +template +static int do_cpte_matrices(XDR* xd, Enum ecpt, int sflags, int n, matrix** v, FILE* list) +{ + bool_t res = 0; + matrix *vp, *va = nullptr; + real* vr; + int nf, i, j, k; + int ret; + + nf = n; + res = xdr_int(xd, &nf); + if (res == 0) + { + return -1; + } + if (list == nullptr && nf != n) + { + gmx_fatal(FARGS, + "Count mismatch for state entry %s, code count is %d, file count is %d\n", + enumValueToString(ecpt), + n, + nf); + } + if (list || !(sflags & enumValueToBitMask(ecpt))) + { + snew(va, nf); + vp = va; + } + else + { + if (*v == nullptr) + { + snew(*v, nf); + } + vp = *v; + } + snew(vr, nf * DIM * DIM); + for (i = 0; i < nf; i++) + { + for (j = 0; j < DIM; j++) + { + for (k = 0; k < DIM; k++) + { + vr[(i * DIM + j) * DIM + k] = vp[i][j][k]; + } + } + } + ret = doVectorLow>( + xd, ecpt, sflags, nf * DIM * DIM, nullptr, &vr, nullptr, nullptr, CptElementType::matrix3x3); + for (i = 0; i < nf; i++) + { + for (j = 0; j < DIM; j++) + { + for (k = 0; k < DIM; k++) + { + vp[i][j][k] = vr[(i * DIM + j) * DIM + k]; + } + } + } + sfree(vr); + + if (list && ret == 0) + { + for (i = 0; i < nf; i++) + { + pr_rvecs(list, 0, enumValueToString(ecpt), vp[i], DIM); + } + } + if (va) + { + sfree(va); + } + + return ret; +} + +static void do_cpt_header(XDR* xd, gmx_bool bRead, FILE* list, CheckpointHeaderContents* contents) +{ + bool_t res = 0; + int magic; + + if (bRead) + { + magic = -1; + } + else + { + magic = CPT_MAGIC1; + } + res = xdr_int(xd, &magic); + if (res == 0) + { + gmx_fatal(FARGS, + "The checkpoint file is empty/corrupted, or maybe you are out of disk space?"); + } + if (magic != CPT_MAGIC1) + { + gmx_fatal(FARGS, + "Start of file magic number mismatch, checkpoint file has %d, should be %d\n" + "The checkpoint file is corrupted or not a checkpoint file", + magic, + CPT_MAGIC1); + } + char fhost[255]; + if (!bRead) + { + gmx_gethostname(fhost, 255); + } + do_cpt_string_err(xd, "GROMACS version", contents->version, list); + // The following fields are no longer ever written with meaningful + // content, but because they precede the file version, there is no + // good way for new code to read the old and new formats, nor a + // good way for old code to avoid giving an error while reading a + // new format. So we read and write a field that no longer has a + // purpose. + do_cpt_string_err(xd, "GROMACS build time UNUSED", contents->btime_UNUSED, list); + do_cpt_string_err(xd, "GROMACS build user UNUSED", contents->buser_UNUSED, list); + do_cpt_string_err(xd, "GROMACS build host UNUSED", contents->bhost_UNUSED, list); + do_cpt_string_err(xd, "generating program", contents->fprog, list); + do_cpt_string_err(xd, "generation time", contents->ftime, list); + contents->file_version = cpt_version; + do_cpt_enum_as_int(xd, "checkpoint file version", &contents->file_version, list); + if (contents->file_version > cpt_version) + { + gmx_fatal(FARGS, + "Attempting to read a checkpoint file of version %d with code of version %d\n", + static_cast(contents->file_version), + static_cast(cpt_version)); + } + if (contents->file_version >= CheckPointVersion::DoublePrecisionBuild) + { + do_cpt_int_err(xd, "GROMACS double precision", &contents->double_prec, list); + } + else + { + contents->double_prec = -1; + } + if (contents->file_version >= CheckPointVersion::HostInformation) + { + do_cpt_string_err(xd, "generating host", fhost, list); + } + do_cpt_int_err(xd, "#atoms", &contents->natoms, list); + do_cpt_int_err(xd, "#T-coupling groups", &contents->ngtc, list); + if (contents->file_version >= CheckPointVersion::NoseHooverThermostat) + { + do_cpt_int_err(xd, "#Nose-Hoover T-chains", &contents->nhchainlength, list); + } + else + { + contents->nhchainlength = 1; + } + if (contents->file_version >= CheckPointVersion::NoseHooverBarostat) + { + do_cpt_int_err(xd, "#Nose-Hoover T-chains for barostat ", &contents->nnhpres, list); + } + else + { + contents->nnhpres = 0; + } + if (contents->file_version >= CheckPointVersion::LambdaStateAndHistory) + { + do_cpt_int_err(xd, "# of total lambda states ", &contents->nlambda, list); + } + else + { + contents->nlambda = 0; + } + { + int integrator = static_cast(contents->eIntegrator); + do_cpt_int_err(xd, "integrator", &integrator, list); + if (bRead) + { + contents->eIntegrator = static_cast(integrator); + } + } + if (contents->file_version >= CheckPointVersion::SafeSimulationPart) + { + do_cpt_int_err(xd, "simulation part #", &contents->simulation_part, list); + } + else + { + contents->simulation_part = 1; + } + if (contents->file_version >= CheckPointVersion::SafeSteps) + { + do_cpt_step_err(xd, "step", &contents->step, list); + } + else + { + int idum = 0; + do_cpt_int_err(xd, "step", &idum, list); + contents->step = static_cast(idum); + } + do_cpt_double_err(xd, "t", &contents->t, list); + do_cpt_int_err(xd, "#PP-ranks", &contents->nnodes, list); + do_cpt_int_err(xd, "dd_nc[x]", &contents->dd_nc[XX], list); + do_cpt_int_err(xd, "dd_nc[y]", &contents->dd_nc[YY], list); + do_cpt_int_err(xd, "dd_nc[z]", &contents->dd_nc[ZZ], list); + do_cpt_int_err(xd, "#PME-only ranks", &contents->npme, list); + do_cpt_int_err(xd, "state flags", &contents->flags_state, list); + if (contents->file_version >= CheckPointVersion::EkinDataAndFlags) + { + do_cpt_int_err(xd, "ekin data flags", &contents->flags_eks, list); + do_cpt_int_err(xd, "energy history flags", &contents->flags_enh, list); + } + else + { + contents->flags_eks = 0; + contents->flags_enh = (contents->flags_state >> (static_cast(StateEntry::OrireDtav) + 1)); + contents->flags_state = (contents->flags_state + & ~((1 << (static_cast(StateEntry::OrireDtav) + 1)) + | (1 << (static_cast(StateEntry::OrireDtav) + 2)) + | (1 << (static_cast(StateEntry::OrireDtav) + 3)))); + } + if (contents->file_version >= CheckPointVersion::LambdaStateAndHistory) + { + do_cpt_int_err(xd, "df history flags", &contents->flags_dfh, list); + } + else + { + contents->flags_dfh = 0; + } + + if (contents->file_version >= CheckPointVersion::EssentialDynamics) + { + do_cpt_int_err(xd, "ED data sets", &contents->nED, list); + } + else + { + contents->nED = 0; + } + + if (contents->file_version >= CheckPointVersion::SwapState) + { + int swapState = static_cast(contents->eSwapCoords); + do_cpt_int_err(xd, "swap", &swapState, list); + if (bRead) + { + contents->eSwapCoords = static_cast(swapState); + } + } + else + { + contents->eSwapCoords = SwapType::No; + } + + if (contents->file_version >= CheckPointVersion::AwhHistoryFlags) + { + do_cpt_int_err(xd, "AWH history flags", &contents->flags_awhh, list); + } + else + { + contents->flags_awhh = 0; + } + + if (contents->file_version >= CheckPointVersion::RemoveBuildMachineInformation) + { + do_cpt_int_err(xd, "pull history flags", &contents->flagsPullHistory, list); + } + else + { + contents->flagsPullHistory = 0; + } + + if (contents->file_version >= CheckPointVersion::ModularSimulator) + { + do_cpt_bool_err( + xd, "Is modular simulator checkpoint", &contents->isModularSimulatorCheckpoint, list); + } + else + { + contents->isModularSimulatorCheckpoint = false; + } +} + +static int do_cpt_footer(XDR* xd, CheckPointVersion file_version) +{ + bool_t res = 0; + int magic; + + if (file_version >= CheckPointVersion::AddMagicNumber) + { + magic = CPT_MAGIC2; + res = xdr_int(xd, &magic); + if (res == 0) + { + cp_error(); + } + if (magic != CPT_MAGIC2) + { + return -1; + } + } + + return 0; +} + +static int do_cpt_state(XDR* xd, int fflags, t_state* state, FILE* list) +{ + int ret = 0; + const int sflags = state->flags; + // If reading, state->natoms was probably just read, so + // allocations need to be managed. If writing, this won't change + // anything that matters. + using StateFlags = gmx::EnumerationArray; + state_change_natoms(state, state->natoms); + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + if (fflags & enumValueToBitMask(*i)) + { + switch (*i) + { + case StateEntry::Lambda: + ret = doRealArrayRef(xd, *i, sflags, state->lambda, list); + break; + case StateEntry::FepState: + ret = do_cpte_int(xd, *i, sflags, &state->fep_state, list); + break; + case StateEntry::Box: ret = do_cpte_matrix(xd, *i, sflags, state->box, list); break; + case StateEntry::BoxRel: + ret = do_cpte_matrix(xd, *i, sflags, state->box_rel, list); + break; + case StateEntry::BoxV: + ret = do_cpte_matrix(xd, *i, sflags, state->boxv, list); + break; + case StateEntry::PressurePrevious: + ret = do_cpte_matrix(xd, *i, sflags, state->pres_prev, list); + break; + case StateEntry::SVirPrev: + ret = do_cpte_matrix(xd, *i, sflags, state->svir_prev, list); + break; + case StateEntry::FVirPrev: + ret = do_cpte_matrix(xd, *i, sflags, state->fvir_prev, list); + break; + case StateEntry::Nhxi: + ret = doVector(xd, *i, sflags, &state->nosehoover_xi, list); + break; + case StateEntry::Nhvxi: + ret = doVector(xd, *i, sflags, &state->nosehoover_vxi, list); + break; + case StateEntry::Nhpresxi: + ret = doVector(xd, *i, sflags, &state->nhpres_xi, list); + break; + case StateEntry::Nhpresvxi: + ret = doVector(xd, *i, sflags, &state->nhpres_vxi, list); + break; + case StateEntry::ThermInt: + ret = doVector(xd, *i, sflags, &state->therm_integral, list); + break; + case StateEntry::BarosInt: + ret = do_cpte_double(xd, *i, sflags, &state->baros_integral, list); + break; + case StateEntry::Veta: + ret = do_cpte_real(xd, *i, sflags, &state->veta, list); + break; + case StateEntry::Vol0: + ret = do_cpte_real(xd, *i, sflags, &state->vol0, list); + break; + case StateEntry::X: + ret = doRvecVector(xd, *i, sflags, &state->x, state->natoms, list); + break; + case StateEntry::V: + ret = doRvecVector(xd, *i, sflags, &state->v, state->natoms, list); + break; + /* The RNG entries are no longer written, + * the next 4 lines are only for reading old files. + * It's OK that three case statements fall through. + */ + case StateEntry::LDRngNotSupported: + case StateEntry::LDRngINotSupported: + case StateEntry::MCRngNotSupported: + case StateEntry::MCRngINotSupported: + ret = do_cpte_ints(xd, *i, sflags, 0, nullptr, list); + break; + case StateEntry::DisreInitF: + ret = do_cpte_real(xd, *i, sflags, &state->hist.disre_initf, list); + break; + case StateEntry::DisreRm3Tav: + ret = doVector(xd, *i, sflags, &state->hist.disre_rm3tav, list); + break; + case StateEntry::OrireInitF: + ret = do_cpte_real(xd, *i, sflags, &state->hist.orire_initf, list); + break; + case StateEntry::OrireDtav: + ret = doVector(xd, *i, sflags, &state->hist.orire_Dtav, list); + break; + case StateEntry::PullComPrevStep: + ret = doVector(xd, *i, sflags, &state->pull_com_prev_step, list); + break; + default: + gmx_fatal(FARGS, + "Unknown state entry %d\n" + "You are reading a checkpoint file written by different code, which " + "is not supported", + enumValueToBitMask(*i)); + } + } + } + return ret; +} + +static int do_cpt_ekinstate(XDR* xd, int fflags, ekinstate_t* ekins, FILE* list) +{ + int ret = 0; + + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + if (fflags & enumValueToBitMask(*i)) + { + switch (*i) + { + + case StateKineticEntry::EkinNumber: + ret = do_cpte_int(xd, *i, fflags, &ekins->ekin_n, list); + break; + case StateKineticEntry::EkinHalfStep: + ret = do_cpte_matrices(xd, *i, fflags, ekins->ekin_n, &ekins->ekinh, list); + break; + case StateKineticEntry::EkinFullStep: + ret = do_cpte_matrices(xd, *i, fflags, ekins->ekin_n, &ekins->ekinf, list); + break; + case StateKineticEntry::EkinHalfStepOld: + ret = do_cpte_matrices(xd, *i, fflags, ekins->ekin_n, &ekins->ekinh_old, list); + break; + case StateKineticEntry::EkinTotal: + ret = do_cpte_matrix(xd, *i, fflags, ekins->ekin_total, list); + break; + case StateKineticEntry::EkinNoseHooverScaleFullStep: + ret = doVector(xd, *i, fflags, &ekins->ekinscalef_nhc, list); + break; + case StateKineticEntry::VelocityScale: + ret = doVector(xd, *i, fflags, &ekins->vscale_nhc, list); + break; + case StateKineticEntry::EkinNoseHooverScaleHalfStep: + ret = doVector(xd, *i, fflags, &ekins->ekinscaleh_nhc, list); + break; + case StateKineticEntry::DEkinDLambda: + ret = do_cpte_real(xd, *i, fflags, &ekins->dekindl, list); + break; + case StateKineticEntry::Mvcos: + ret = do_cpte_real(xd, *i, fflags, &ekins->mvcos, list); + break; + default: + gmx_fatal(FARGS, + "Unknown ekin data state entry %d\n" + "You are probably reading a new checkpoint file with old code", + enumValueToBitMask(*i)); + } + } + } + + return ret; +} + + +static int do_cpt_swapstate(XDR* xd, gmx_bool bRead, SwapType eSwapCoords, swaphistory_t* swapstate, FILE* list) +{ + int swap_cpt_version = 2; + + if (eSwapCoords == SwapType::No) + { + return 0; + } + + swapstate->bFromCpt = bRead; + swapstate->eSwapCoords = eSwapCoords; + + do_cpt_int_err(xd, "swap checkpoint version", &swap_cpt_version, list); + if (bRead && swap_cpt_version < 2) + { + gmx_fatal(FARGS, + "Cannot read checkpoint files that were written with old versions" + "of the ion/water position swapping protocol.\n"); + } + + do_cpt_int_err(xd, "swap coupling steps", &swapstate->nAverage, list); + + /* When reading, init_swapcoords has not been called yet, + * so we have to allocate memory first. */ + do_cpt_int_err(xd, "number of ion types", &swapstate->nIonTypes, list); + if (bRead) + { + snew(swapstate->ionType, swapstate->nIonTypes); + } + + for (auto ic : gmx::EnumerationWrapper{}) + { + for (int ii = 0; ii < swapstate->nIonTypes; ii++) + { + swapstateIons_t* gs = &swapstate->ionType[ii]; + + if (bRead) + { + do_cpt_int_err(xd, "swap requested atoms", &gs->nMolReq[ic], list); + } + else + { + do_cpt_int_err(xd, "swap requested atoms p", gs->nMolReq_p[ic], list); + } + + if (bRead) + { + do_cpt_int_err(xd, "swap influx net", &gs->inflow_net[ic], list); + } + else + { + do_cpt_int_err(xd, "swap influx net p", gs->inflow_net_p[ic], list); + } + + if (bRead && (nullptr == gs->nMolPast[ic])) + { + snew(gs->nMolPast[ic], swapstate->nAverage); + } + + for (int j = 0; j < swapstate->nAverage; j++) + { + if (bRead) + { + do_cpt_int_err(xd, "swap past atom counts", &gs->nMolPast[ic][j], list); + } + else + { + do_cpt_int_err(xd, "swap past atom counts p", &gs->nMolPast_p[ic][j], list); + } + } + } + } + + /* Ion flux per channel */ + for (auto ic : gmx::EnumerationWrapper{}) + { + for (int ii = 0; ii < swapstate->nIonTypes; ii++) + { + swapstateIons_t* gs = &swapstate->ionType[ii]; + + if (bRead) + { + do_cpt_int_err(xd, "channel flux A->B", &gs->fluxfromAtoB[ic], list); + } + else + { + do_cpt_int_err(xd, "channel flux A->B p", gs->fluxfromAtoB_p[ic], list); + } + } + } + + /* Ion flux leakage */ + if (bRead) + { + do_cpt_int_err(xd, "flux leakage", &swapstate->fluxleak, list); + } + else + { + do_cpt_int_err(xd, "flux leakage", swapstate->fluxleak_p, list); + } + + /* Ion history */ + for (int ii = 0; ii < swapstate->nIonTypes; ii++) + { + swapstateIons_t* gs = &swapstate->ionType[ii]; + + do_cpt_int_err(xd, "number of ions", &gs->nMol, list); + + if (bRead) + { + snew(gs->channel_label, gs->nMol); + snew(gs->comp_from, gs->nMol); + } + + do_cpt_n_enum_as_int(xd, "channel history", gs->nMol, gs->channel_label, list); + do_cpt_n_enum_as_int(xd, "domain history", gs->nMol, gs->comp_from, list); + } + + /* Save the last known whole positions to checkpoint + * file to be able to also make multimeric channels whole in PBC */ + do_cpt_int_err(xd, "Ch0 atoms", &swapstate->nat[Channel::Zero], list); + do_cpt_int_err(xd, "Ch1 atoms", &swapstate->nat[Channel::One], list); + if (bRead) + { + snew(swapstate->xc_old_whole[Channel::Zero], swapstate->nat[Channel::Zero]); + snew(swapstate->xc_old_whole[Channel::One], swapstate->nat[Channel::One]); + do_cpt_n_rvecs_err( + xd, "Ch0 whole x", swapstate->nat[Channel::Zero], swapstate->xc_old_whole[Channel::Zero], list); + do_cpt_n_rvecs_err( + xd, "Ch1 whole x", swapstate->nat[Channel::One], swapstate->xc_old_whole[Channel::One], list); + } + else + { + do_cpt_n_rvecs_err(xd, + "Ch0 whole x", + swapstate->nat[Channel::Zero], + *swapstate->xc_old_whole_p[Channel::Zero], + list); + do_cpt_n_rvecs_err( + xd, "Ch1 whole x", swapstate->nat[Channel::One], *swapstate->xc_old_whole_p[Channel::One], list); + } + + return 0; +} + + +static int do_cpt_enerhist(XDR* xd, gmx_bool bRead, int fflags, energyhistory_t* enerhist, FILE* list) +{ + int ret = 0; + + if (fflags == 0) + { + return ret; + } + + GMX_RELEASE_ASSERT(enerhist != nullptr, "With energy history, we need a valid enerhist pointer"); + + /* This is stored/read for backward compatibility */ + int energyHistoryNumEnergies = 0; + if (bRead) + { + enerhist->nsteps = 0; + enerhist->nsum = 0; + enerhist->nsteps_sim = 0; + enerhist->nsum_sim = 0; + } + else if (enerhist != nullptr) + { + energyHistoryNumEnergies = enerhist->ener_sum_sim.size(); + } + + delta_h_history_t* deltaH = enerhist->deltaHForeignLambdas.get(); + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + if (fflags & enumValueToBitMask(*i)) + { + switch (*i) + { + case StateEnergyEntry::N: + ret = do_cpte_int(xd, *i, fflags, &energyHistoryNumEnergies, list); + break; + case StateEnergyEntry::Aver: + ret = doVector(xd, *i, fflags, &enerhist->ener_ave, list); + break; + case StateEnergyEntry::Sum: + ret = doVector(xd, *i, fflags, &enerhist->ener_sum, list); + break; + case StateEnergyEntry::NumSum: + do_cpt_step_err(xd, enumValueToString(*i), &enerhist->nsum, list); + break; + case StateEnergyEntry::SumSim: + ret = doVector(xd, *i, fflags, &enerhist->ener_sum_sim, list); + break; + case StateEnergyEntry::NumSumSim: + do_cpt_step_err(xd, enumValueToString(*i), &enerhist->nsum_sim, list); + break; + case StateEnergyEntry::NumSteps: + do_cpt_step_err(xd, enumValueToString(*i), &enerhist->nsteps, list); + break; + case StateEnergyEntry::NumStepsSim: + do_cpt_step_err(xd, enumValueToString(*i), &enerhist->nsteps_sim, list); + break; + case StateEnergyEntry::DeltaHNN: + { + int numDeltaH = 0; + if (!bRead && deltaH != nullptr) + { + numDeltaH = deltaH->dh.size(); + } + do_cpt_int_err(xd, enumValueToString(*i), &numDeltaH, list); + if (bRead) + { + if (deltaH == nullptr) + { + enerhist->deltaHForeignLambdas = std::make_unique(); + deltaH = enerhist->deltaHForeignLambdas.get(); + } + deltaH->dh.resize(numDeltaH); + deltaH->start_lambda_set = FALSE; + } + break; + } + case StateEnergyEntry::DeltaHList: + for (auto dh : deltaH->dh) + { + ret = doVector(xd, *i, fflags, &dh, list); + } + break; + case StateEnergyEntry::DeltaHStartTime: + ret = do_cpte_double(xd, *i, fflags, &(deltaH->start_time), list); + break; + case StateEnergyEntry::DeltaHStartLambda: + ret = do_cpte_double(xd, *i, fflags, &(deltaH->start_lambda), list); + break; + default: + gmx_fatal(FARGS, + "Unknown energy history entry %d\n" + "You are probably reading a new checkpoint file with old code", + enumValueToBitMask(*i)); + } + } + } + + if ((fflags & enumValueToBitMask(StateEnergyEntry::Sum)) + && !(fflags & enumValueToBitMask(StateEnergyEntry::SumSim))) + { + /* Assume we have an old file format and copy sum to sum_sim */ + enerhist->ener_sum_sim = enerhist->ener_sum; + } + + if ((fflags & enumValueToBitMask(StateEnergyEntry::NumSum)) + && !(fflags & enumValueToBitMask(StateEnergyEntry::NumSteps))) + { + /* Assume we have an old file format and copy nsum to nsteps */ + enerhist->nsteps = enerhist->nsum; + } + if ((fflags & enumValueToBitMask(StateEnergyEntry::NumSumSim)) + && !(fflags & enumValueToBitMask(StateEnergyEntry::NumStepsSim))) + { + /* Assume we have an old file format and copy nsum to nsteps */ + enerhist->nsteps_sim = enerhist->nsum_sim; + } + + return ret; +} + +static int doCptPullCoordHist(XDR* xd, PullCoordinateHistory* pullCoordHist, FILE* list) +{ + int ret = 0; + int flags = 0; + + flags |= (enumValueToBitMask(StatePullCoordEntry::ValueReferenceSum) + | enumValueToBitMask(StatePullCoordEntry::ValueSum) + | enumValueToBitMask(StatePullCoordEntry::DR01Sum) + | enumValueToBitMask(StatePullCoordEntry::DR23Sum) + | enumValueToBitMask(StatePullCoordEntry::DR45Sum) + | enumValueToBitMask(StatePullCoordEntry::FScalarSum) + | enumValueToBitMask(StatePullCoordEntry::DynaxSum)); + + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + switch (*i) + { + case StatePullCoordEntry::ValueReferenceSum: + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->valueRef), list); + break; + case StatePullCoordEntry::ValueSum: + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->value), list); + break; + case StatePullCoordEntry::DR01Sum: + for (int j = 0; j < DIM && ret == 0; j++) + { + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->dr01[j]), list); + } + break; + case StatePullCoordEntry::DR23Sum: + for (int j = 0; j < DIM && ret == 0; j++) + { + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->dr23[j]), list); + } + break; + case StatePullCoordEntry::DR45Sum: + for (int j = 0; j < DIM && ret == 0; j++) + { + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->dr45[j]), list); + } + break; + case StatePullCoordEntry::FScalarSum: + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->scalarForce), list); + break; + case StatePullCoordEntry::DynaxSum: + for (int j = 0; j < DIM && ret == 0; j++) + { + ret = do_cpte_double(xd, *i, flags, &(pullCoordHist->dynaX[j]), list); + } + break; + default: + gmx_fatal(FARGS, "Unhandled StatePullCoordEntry enum value: %d", enumValueToBitMask(*i)); + } + } + + return ret; +} + +static int doCptPullGroupHist(XDR* xd, PullGroupHistory* pullGroupHist, FILE* list) +{ + int ret = 0; + int flags = 0; + + flags |= (enumValueToBitMask(StatePullGroupEntry::XSum)); + + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + switch (*i) + { + case StatePullGroupEntry::XSum: + for (int j = 0; j < DIM && ret == 0; j++) + { + ret = do_cpte_double(xd, *i, flags, &(pullGroupHist->x[j]), list); + } + break; + default: gmx_fatal(FARGS, "Unhandled pull group state entry"); + } + } + + return ret; +} + + +static int doCptPullHist(XDR* xd, gmx_bool bRead, int fflags, PullHistory* pullHist, FILE* list) +{ + int ret = 0; + int pullHistoryNumCoordinates = 0; + int pullHistoryNumGroups = 0; + + /* Retain the number of terms in the sum and the number of coordinates (used for writing + * average pull forces and coordinates) in the pull history, in temporary variables, + * in case they cannot be read from the checkpoint, in order to have backward compatibility */ + if (bRead) + { + pullHist->numValuesInXSum = 0; + pullHist->numValuesInFSum = 0; + } + else if (pullHist != nullptr) + { + pullHistoryNumCoordinates = pullHist->pullCoordinateSums.size(); + pullHistoryNumGroups = pullHist->pullGroupSums.size(); + } + else + { + GMX_RELEASE_ASSERT(fflags == 0, "Without pull history, all flags should be off"); + } + + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); i != StateFlags::keys().end(); i++) + { + if (fflags & enumValueToBitMask(*i)) + { + switch (*i) + { + case StatePullEntry::NumCoordinates: + do_cpt_int_err(xd, enumValueToString(*i), &pullHistoryNumCoordinates, list); + break; + case StatePullEntry::NumGroups: + do_cpt_int_err(xd, enumValueToString(*i), &pullHistoryNumGroups, list); + break; + case StatePullEntry::NumValuesInXSum: + do_cpt_int_err(xd, enumValueToString(*i), &pullHist->numValuesInXSum, list); + break; + case StatePullEntry::NumValuesInFSum: + do_cpt_int_err(xd, enumValueToString(*i), &pullHist->numValuesInFSum, list); + break; + default: + gmx_fatal(FARGS, + "Unknown pull history entry %d\n" + "You are probably reading a new checkpoint file with old code", + enumValueToBitMask(*i)); + } + } + } + if (bRead) + { + pullHist->pullCoordinateSums.resize(pullHistoryNumCoordinates); + pullHist->pullGroupSums.resize(pullHistoryNumGroups); + } + if (pullHist->numValuesInXSum > 0 || pullHist->numValuesInFSum > 0) + { + for (size_t i = 0; i < pullHist->pullCoordinateSums.size() && ret == 0; i++) + { + ret = doCptPullCoordHist(xd, &(pullHist->pullCoordinateSums[i]), list); + } + for (size_t i = 0; i < pullHist->pullGroupSums.size() && ret == 0; i++) + { + ret = doCptPullGroupHist(xd, &(pullHist->pullGroupSums[i]), list); + } + } + + return ret; +} + +static int do_cpt_df_hist(XDR* xd, int fflags, int nlambda, df_history_t** dfhistPtr, FILE* list) +{ + int ret = 0; + + if (fflags == 0) + { + return 0; + } + + std::unique_ptr localDFHistory = nullptr; + if (*dfhistPtr == nullptr) + { + localDFHistory = std::make_unique(); + *dfhistPtr = localDFHistory.get(); + (*dfhistPtr)->nlambda = nlambda; + init_df_history(*dfhistPtr, nlambda); + } + df_history_t* dfhist = *dfhistPtr; + + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + if (fflags & enumValueToBitMask(*i)) + { + switch (*i) + { + case StateFepEntry::IsEquilibrated: + ret = do_cpte_bool(xd, *i, fflags, &dfhist->bEquil, list); + break; + case StateFepEntry::NumAtLambda: + ret = do_cpte_ints(xd, *i, fflags, nlambda, &dfhist->n_at_lam, list); + break; + case StateFepEntry::WangLandauHistogram: + ret = do_cpte_reals(xd, *i, fflags, nlambda, &dfhist->wl_histo, list); + break; + case StateFepEntry::WangLandauDelta: + ret = do_cpte_real(xd, *i, fflags, &dfhist->wl_delta, list); + break; + case StateFepEntry::SumWeights: + ret = do_cpte_reals(xd, *i, fflags, nlambda, &dfhist->sum_weights, list); + break; + case StateFepEntry::SumDG: + ret = do_cpte_reals(xd, *i, fflags, nlambda, &dfhist->sum_dg, list); + break; + case StateFepEntry::SumMinVar: + ret = do_cpte_reals(xd, *i, fflags, nlambda, &dfhist->sum_minvar, list); + break; + case StateFepEntry::SumVar: + ret = do_cpte_reals(xd, *i, fflags, nlambda, &dfhist->sum_variance, list); + break; + case StateFepEntry::Accump: + ret = do_cpte_nmatrix(xd, *i, fflags, nlambda, dfhist->accum_p, list); + break; + case StateFepEntry::Accumm: + ret = do_cpte_nmatrix(xd, *i, fflags, nlambda, dfhist->accum_m, list); + break; + case StateFepEntry::Accump2: + ret = do_cpte_nmatrix(xd, *i, fflags, nlambda, dfhist->accum_p2, list); + break; + case StateFepEntry::Accumm2: + ret = do_cpte_nmatrix(xd, *i, fflags, nlambda, dfhist->accum_m2, list); + break; + case StateFepEntry::Tij: + ret = do_cpte_nmatrix(xd, *i, fflags, nlambda, dfhist->Tij, list); + break; + case StateFepEntry::TijEmp: + ret = do_cpte_nmatrix(xd, *i, fflags, nlambda, dfhist->Tij_empirical, list); + break; + + default: + gmx_fatal(FARGS, + "Unknown df history entry %d\n" + "You are probably reading a new checkpoint file with old code", + enumValueToBitMask(*i)); + } + } + } + + return ret; +} + + +/* This function stores the last whole configuration of the reference and + * average structure in the .cpt file + */ +static int do_cpt_EDstate(XDR* xd, gmx_bool bRead, int nED, edsamhistory_t* EDstate, FILE* list) +{ + if (nED == 0) + { + return 0; + } + + EDstate->bFromCpt = bRead; + EDstate->nED = nED; + + /* When reading, init_edsam has not been called yet, + * so we have to allocate memory first. */ + if (bRead) + { + snew(EDstate->nref, EDstate->nED); + snew(EDstate->old_sref, EDstate->nED); + snew(EDstate->nav, EDstate->nED); + snew(EDstate->old_sav, EDstate->nED); + } + + /* Read/write the last whole conformation of SREF and SAV for each ED dataset (usually only one) */ + for (int i = 0; i < EDstate->nED; i++) + { + char buf[STRLEN]; + + /* Reference structure SREF */ + sprintf(buf, "ED%d # of atoms in reference structure", i + 1); + do_cpt_int_err(xd, buf, &EDstate->nref[i], list); + sprintf(buf, "ED%d x_ref", i + 1); + if (bRead) + { + snew(EDstate->old_sref[i], EDstate->nref[i]); + do_cpt_n_rvecs_err(xd, buf, EDstate->nref[i], EDstate->old_sref[i], list); + } + else + { + do_cpt_n_rvecs_err(xd, buf, EDstate->nref[i], EDstate->old_sref_p[i], list); + } + + /* Average structure SAV */ + sprintf(buf, "ED%d # of atoms in average structure", i + 1); + do_cpt_int_err(xd, buf, &EDstate->nav[i], list); + sprintf(buf, "ED%d x_av", i + 1); + if (bRead) + { + snew(EDstate->old_sav[i], EDstate->nav[i]); + do_cpt_n_rvecs_err(xd, buf, EDstate->nav[i], EDstate->old_sav[i], list); + } + else + { + do_cpt_n_rvecs_err(xd, buf, EDstate->nav[i], EDstate->old_sav_p[i], list); + } + } + + return 0; +} + +static int do_cpt_correlation_grid(XDR* xd, + gmx_bool bRead, + gmx_unused int fflags, + gmx::CorrelationGridHistory* corrGrid, + FILE* list, + StateAwhEntry eawhh) +{ + int ret = 0; + + do_cpt_int_err(xd, enumValueToString(eawhh), &(corrGrid->numCorrelationTensors), list); + do_cpt_int_err(xd, enumValueToString(eawhh), &(corrGrid->tensorSize), list); + do_cpt_int_err(xd, enumValueToString(eawhh), &(corrGrid->blockDataListSize), list); + + if (bRead) + { + initCorrelationGridHistory( + corrGrid, corrGrid->numCorrelationTensors, corrGrid->tensorSize, corrGrid->blockDataListSize); + } + + for (gmx::CorrelationBlockDataHistory& blockData : corrGrid->blockDataBuffer) + { + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.blockSumWeight), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.blockSumSquareWeight), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.blockSumWeightX), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.blockSumWeightY), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.sumOverBlocksSquareBlockWeight), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.sumOverBlocksBlockSquareWeight), list); + do_cpt_double_err( + xd, enumValueToString(eawhh), &(blockData.sumOverBlocksBlockWeightBlockWeightX), list); + do_cpt_double_err( + xd, enumValueToString(eawhh), &(blockData.sumOverBlocksBlockWeightBlockWeightY), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.blockLength), list); + do_cpt_int_err(xd, enumValueToString(eawhh), &(blockData.previousBlockIndex), list); + do_cpt_double_err(xd, enumValueToString(eawhh), &(blockData.correlationIntegral), list); + } + + return ret; +} + +static int do_cpt_awh_bias(XDR* xd, gmx_bool bRead, int fflags, gmx::AwhBiasHistory* biasHistory, FILE* list) +{ + int ret = 0; + + gmx::AwhBiasStateHistory* state = &biasHistory->state; + using StateFlags = gmx::EnumerationArray; + for (auto i = StateFlags::keys().begin(); (i != StateFlags::keys().end() && ret == 0); i++) + { + if (fflags & enumValueToBitMask(*i)) + { + switch (*i) + { + case StateAwhEntry::InInitial: + do_cpt_bool_err(xd, enumValueToString(*i), &state->in_initial, list); + break; + case StateAwhEntry::EquilibrateHistogram: + do_cpt_bool_err(xd, enumValueToString(*i), &state->equilibrateHistogram, list); + break; + case StateAwhEntry::HistogramSize: + do_cpt_double_err(xd, enumValueToString(*i), &state->histSize, list); + break; + case StateAwhEntry::NumPoints: + { + int numPoints; + if (!bRead) + { + numPoints = biasHistory->pointState.size(); + } + do_cpt_int_err(xd, enumValueToString(*i), &numPoints, list); + if (bRead) + { + biasHistory->pointState.resize(numPoints); + } + } + break; + case StateAwhEntry::CoordPoint: + for (auto& psh : biasHistory->pointState) + { + do_cpt_double_err(xd, enumValueToString(*i), &psh.target, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.free_energy, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.bias, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.weightsum_iteration, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.weightsum_covering, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.weightsum_tot, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.weightsum_ref, list); + do_cpt_step_err(xd, enumValueToString(*i), &psh.last_update_index, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.log_pmfsum, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.visits_iteration, list); + do_cpt_double_err(xd, enumValueToString(*i), &psh.visits_tot, list); + } + break; + case StateAwhEntry::UmbrellaGridPoint: + do_cpt_int_err(xd, enumValueToString(*i), &(state->umbrellaGridpoint), list); + break; + case StateAwhEntry::UpdateList: + do_cpt_int_err(xd, enumValueToString(*i), &(state->origin_index_updatelist), list); + do_cpt_int_err(xd, enumValueToString(*i), &(state->end_index_updatelist), list); + break; + case StateAwhEntry::LogScaledSampleWeight: + do_cpt_double_err(xd, enumValueToString(*i), &(state->logScaledSampleWeight), list); + do_cpt_double_err(xd, enumValueToString(*i), &(state->maxLogScaledSampleWeight), list); + break; + case StateAwhEntry::NumUpdates: + do_cpt_step_err(xd, enumValueToString(*i), &(state->numUpdates), list); + break; + case StateAwhEntry::ForceCorrelationGrid: + ret = do_cpt_correlation_grid( + xd, bRead, fflags, &biasHistory->forceCorrelationGrid, list, *i); + break; + default: gmx_fatal(FARGS, "Unknown awh history entry %d\n", enumValueToBitMask(*i)); + } + } + } + + return ret; +} + +static int do_cpt_awh(XDR* xd, gmx_bool bRead, int fflags, gmx::AwhHistory* awhHistory, FILE* list) +{ + int ret = 0; + + if (fflags != 0) + { + std::shared_ptr awhHistoryLocal; + + if (awhHistory == nullptr) + { + GMX_RELEASE_ASSERT(bRead, + "do_cpt_awh should not be called for writing without an AwhHistory"); + + awhHistoryLocal = std::make_shared(); + awhHistory = awhHistoryLocal.get(); + } + + /* To be able to read and write the AWH data several parameters determining the layout of the AWH structs need to be known + (nbias, npoints, etc.). The best thing (?) would be to have these passed to this function. When writing to a checkpoint + these parameters are available in awh_history (after calling init_awh_history). When reading from a checkpoint though, there + is no initialized awh_history (it is initialized and set in this function). The AWH parameters have not always been read + at the time when this function is called for reading so I don't know how to pass them as input. Here, this is solved by + when writing a checkpoint, also storing parameters needed for future reading of the checkpoint. + + Another issue is that some variables that AWH checkpoints don't have a registered enum and string (e.g. nbias below). + One difficulty is the multilevel structure of the data which would need to be represented somehow. */ + + int numBias; + if (!bRead) + { + numBias = awhHistory->bias.size(); + } + do_cpt_int_err(xd, "awh_nbias", &numBias, list); + + if (bRead) + { + awhHistory->bias.resize(numBias); + } + for (auto& bias : awhHistory->bias) + { + ret = do_cpt_awh_bias(xd, bRead, fflags, &bias, list); + if (ret) + { + return ret; + } + } + do_cpt_double_err(xd, "awh_potential_offset", &awhHistory->potentialOffset, list); + } + return ret; +} + +static void do_cpt_mdmodules(CheckPointVersion fileVersion, + t_fileio* checkpointFileHandle, + const gmx::MDModulesNotifiers& mdModulesNotifiers, + FILE* outputFile) +{ + if (fileVersion >= CheckPointVersion::MDModules) + { + gmx::FileIOXdrSerializer serializer(checkpointFileHandle); + gmx::KeyValueTreeObject mdModuleCheckpointParameterTree = + gmx::deserializeKeyValueTree(&serializer); + if (outputFile) + { + gmx::TextWriter textWriter(outputFile); + gmx::dumpKeyValueTree(&textWriter, mdModuleCheckpointParameterTree); + } + gmx::MDModulesCheckpointReadingDataOnMaster mdModuleCheckpointReadingDataOnMaster = { + mdModuleCheckpointParameterTree, fileVersion + }; + mdModulesNotifiers.checkpointingNotifier_.notify(mdModuleCheckpointReadingDataOnMaster); + } +} + +static int do_cpt_files(XDR* xd, + gmx_bool bRead, + std::vector* outputfiles, + FILE* list, + CheckPointVersion file_version) +{ + gmx_off_t offset; + gmx_off_t mask = 0xFFFFFFFFL; + int offset_high, offset_low; + std::array buf; + GMX_RELEASE_ASSERT(outputfiles, "Must have valid outputfiles"); + + // Ensure that reading pre-allocates outputfiles, while writing + // writes what is already there. + int nfiles = outputfiles->size(); + if (do_cpt_int(xd, "number of output files", &nfiles, list) != 0) + { + return -1; + } + if (bRead) + { + outputfiles->resize(nfiles); + } + + for (auto& outputfile : *outputfiles) + { + /* 64-bit XDR numbers are not portable, so it is stored as separate high/low fractions */ + if (bRead) + { + do_cpt_string_err(xd, "output filename", buf, list); + std::copy(std::begin(buf), std::end(buf), std::begin(outputfile.filename)); + + if (do_cpt_int(xd, "file_offset_high", &offset_high, list) != 0) + { + return -1; + } + if (do_cpt_int(xd, "file_offset_low", &offset_low, list) != 0) + { + return -1; + } + outputfile.offset = (static_cast(offset_high) << 32) + | (static_cast(offset_low) & mask); + } + else + { + do_cpt_string_err(xd, "output filename", outputfile.filename, list); + /* writing */ + offset = outputfile.offset; + if (offset == -1) + { + offset_low = -1; + offset_high = -1; + } + else + { + offset_low = static_cast(offset & mask); + offset_high = static_cast((offset >> 32) & mask); + } + if (do_cpt_int(xd, "file_offset_high", &offset_high, list) != 0) + { + return -1; + } + if (do_cpt_int(xd, "file_offset_low", &offset_low, list) != 0) + { + return -1; + } + } + if (file_version >= CheckPointVersion::FileChecksumAndSize) + { + if (do_cpt_int(xd, "file_checksum_size", &outputfile.checksumSize, list) != 0) + { + return -1; + } + if (do_cpt_u_chars(xd, "file_checksum", outputfile.checksum.size(), outputfile.checksum.data(), list) + != 0) + { + return -1; + } + } + else + { + outputfile.checksumSize = -1; + } + } + return 0; +} + +void write_checkpoint_data(t_fileio* fp, + CheckpointHeaderContents headerContents, + gmx_bool bExpanded, + LambdaWeightCalculation elamstats, + t_state* state, + ObservablesHistory* observablesHistory, + const gmx::MDModulesNotifiers& mdModulesNotifiers, + std::vector* outputfiles, + gmx::WriteCheckpointDataHolder* modularSimulatorCheckpointData) +{ + headerContents.flags_eks = 0; + if (state->ekinstate.bUpToDate) + { + // Likely only EkinNumber, EkinHalfStep, EkinFullStep and DEkinDLambda + // are necessary and the rest can go + headerContents.flags_eks = (enumValueToBitMask(StateKineticEntry::EkinNumber) + | enumValueToBitMask(StateKineticEntry::EkinHalfStep) + | enumValueToBitMask(StateKineticEntry::EkinFullStep) + | enumValueToBitMask(StateKineticEntry::EkinNoseHooverScaleFullStep) + | enumValueToBitMask(StateKineticEntry::EkinNoseHooverScaleHalfStep) + | enumValueToBitMask(StateKineticEntry::VelocityScale) + | enumValueToBitMask(StateKineticEntry::DEkinDLambda) + | enumValueToBitMask(StateKineticEntry::Mvcos)); + } + headerContents.isModularSimulatorCheckpoint = !modularSimulatorCheckpointData->empty(); + + energyhistory_t* enerhist = observablesHistory->energyHistory.get(); + headerContents.flags_enh = 0; + if (enerhist != nullptr && (enerhist->nsum > 0 || enerhist->nsum_sim > 0)) + { + headerContents.flags_enh |= enumValueToBitMask(StateEnergyEntry::N) + | enumValueToBitMask(StateEnergyEntry::NumSteps) + | enumValueToBitMask(StateEnergyEntry::NumStepsSim); + if (enerhist->nsum > 0) + { + headerContents.flags_enh |= (enumValueToBitMask(StateEnergyEntry::Aver) + | enumValueToBitMask(StateEnergyEntry::Sum) + | enumValueToBitMask(StateEnergyEntry::NumSum)); + } + if (enerhist->nsum_sim > 0) + { + headerContents.flags_enh |= (enumValueToBitMask(StateEnergyEntry::SumSim) + | enumValueToBitMask(StateEnergyEntry::NumSumSim)); + } + if (enerhist->deltaHForeignLambdas != nullptr) + { + headerContents.flags_enh |= (enumValueToBitMask(StateEnergyEntry::DeltaHNN) + | enumValueToBitMask(StateEnergyEntry::DeltaHList) + | enumValueToBitMask(StateEnergyEntry::DeltaHStartTime) + | enumValueToBitMask(StateEnergyEntry::DeltaHStartLambda)); + } + } + + PullHistory* pullHist = observablesHistory->pullHistory.get(); + headerContents.flagsPullHistory = 0; + if (pullHist != nullptr && (pullHist->numValuesInXSum > 0 || pullHist->numValuesInFSum > 0)) + { + headerContents.flagsPullHistory |= enumValueToBitMask(StatePullEntry::NumCoordinates); + headerContents.flagsPullHistory |= (enumValueToBitMask(StatePullEntry::NumGroups) + | enumValueToBitMask(StatePullEntry::NumValuesInXSum) + | enumValueToBitMask(StatePullEntry::NumValuesInFSum)); + } + + headerContents.flags_dfh = 0; + // Modular simulator uses the modularSimulatorCheckpointData object to store the expanded ensemble history + if (bExpanded && !headerContents.isModularSimulatorCheckpoint) + { + headerContents.flags_dfh = + (enumValueToBitMask(StateFepEntry::IsEquilibrated) + | enumValueToBitMask(StateFepEntry::NumAtLambda) + | enumValueToBitMask(StateFepEntry::SumWeights) | enumValueToBitMask(StateFepEntry::SumDG) + | enumValueToBitMask(StateFepEntry::Tij) | enumValueToBitMask(StateFepEntry::TijEmp)); + if (EWL(elamstats)) + { + headerContents.flags_dfh |= (enumValueToBitMask(StateFepEntry::WangLandauDelta) + | enumValueToBitMask(StateFepEntry::WangLandauHistogram)); + } + if ((elamstats == LambdaWeightCalculation::Minvar) || (elamstats == LambdaWeightCalculation::Barker) + || (elamstats == LambdaWeightCalculation::Metropolis)) + { + headerContents.flags_dfh |= (enumValueToBitMask(StateFepEntry::Accump) + | enumValueToBitMask(StateFepEntry::Accumm) + | enumValueToBitMask(StateFepEntry::Accump2) + | enumValueToBitMask(StateFepEntry::Accumm2) + | enumValueToBitMask(StateFepEntry::SumMinVar) + | enumValueToBitMask(StateFepEntry::SumVar)); + } + } + + headerContents.flags_awhh = 0; + if (state->awhHistory != nullptr && !state->awhHistory->bias.empty()) + { + headerContents.flags_awhh |= (enumValueToBitMask(StateAwhEntry::InInitial) + | enumValueToBitMask(StateAwhEntry::EquilibrateHistogram) + | enumValueToBitMask(StateAwhEntry::HistogramSize) + | enumValueToBitMask(StateAwhEntry::NumPoints) + | enumValueToBitMask(StateAwhEntry::CoordPoint) + | enumValueToBitMask(StateAwhEntry::UmbrellaGridPoint) + | enumValueToBitMask(StateAwhEntry::UpdateList) + | enumValueToBitMask(StateAwhEntry::LogScaledSampleWeight) + | enumValueToBitMask(StateAwhEntry::NumUpdates) + | enumValueToBitMask(StateAwhEntry::ForceCorrelationGrid)); + } + + do_cpt_header(gmx_fio_getxdr(fp), FALSE, nullptr, &headerContents); + + if ((do_cpt_state(gmx_fio_getxdr(fp), state->flags, state, nullptr) < 0) + || (do_cpt_ekinstate(gmx_fio_getxdr(fp), headerContents.flags_eks, &state->ekinstate, nullptr) < 0) + || (do_cpt_enerhist(gmx_fio_getxdr(fp), FALSE, headerContents.flags_enh, enerhist, nullptr) < 0) + || (doCptPullHist(gmx_fio_getxdr(fp), FALSE, headerContents.flagsPullHistory, pullHist, nullptr) < 0) + || (do_cpt_df_hist(gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state->dfhist, nullptr) + < 0) + || (do_cpt_EDstate( + gmx_fio_getxdr(fp), FALSE, headerContents.nED, observablesHistory->edsamHistory.get(), nullptr) + < 0) + || (do_cpt_awh(gmx_fio_getxdr(fp), FALSE, headerContents.flags_awhh, state->awhHistory.get(), nullptr) < 0) + || (do_cpt_swapstate(gmx_fio_getxdr(fp), + FALSE, + headerContents.eSwapCoords, + observablesHistory->swapHistory.get(), + nullptr) + < 0) + || (do_cpt_files(gmx_fio_getxdr(fp), FALSE, outputfiles, nullptr, headerContents.file_version) < 0)) + { + gmx_file("Cannot read/write checkpoint; corrupt file, or maybe you are out of disk space?"); + } + + // Checkpointing MDModules + { + gmx::KeyValueTreeBuilder builder; + gmx::MDModulesWriteCheckpointData mdModulesWriteCheckpoint = { builder.rootObject(), + headerContents.file_version }; + mdModulesNotifiers.checkpointingNotifier_.notify(mdModulesWriteCheckpoint); + auto tree = builder.build(); + gmx::FileIOXdrSerializer serializer(fp); + gmx::serializeKeyValueTree(tree, &serializer); + } + + // Checkpointing modular simulator + { + gmx::FileIOXdrSerializer serializer(fp); + modularSimulatorCheckpointData->serialize(&serializer); + } + + do_cpt_footer(gmx_fio_getxdr(fp), headerContents.file_version); +#if GMX_FAHCORE + /* Always FAH checkpoint immediately after a Gromacs checkpoint. + * + * Note that it is critical that we save a FAH checkpoint directly + * after writing a Gromacs checkpoint. If the program dies, either + * by the machine powering off suddenly or the process being, + * killed, FAH can recover files that have only appended data by + * truncating them to the last recorded length. The Gromacs + * checkpoint does not just append data, it is fully rewritten each + * time so a crash between moving the new Gromacs checkpoint file in + * to place and writing a FAH checkpoint is not recoverable. Thus + * the time between these operations must be kept as short a + * possible. + */ + fcCheckpoint(); +#endif +} + +static void check_int(FILE* fplog, const char* type, int p, int f, gmx_bool* mm) +{ + bool foundMismatch = (p != f); + if (!foundMismatch) + { + return; + } + *mm = TRUE; + if (fplog) + { + fprintf(fplog, " %s mismatch,\n", type); + fprintf(fplog, " current program: %d\n", p); + fprintf(fplog, " checkpoint file: %d\n", f); + fprintf(fplog, "\n"); + } +} + +static void check_string(FILE* fplog, const char* type, const char* p, const char* f, gmx_bool* mm) +{ + bool foundMismatch = (std::strcmp(p, f) != 0); + if (!foundMismatch) + { + return; + } + *mm = TRUE; + if (fplog) + { + fprintf(fplog, " %s mismatch,\n", type); + fprintf(fplog, " current program: %s\n", p); + fprintf(fplog, " checkpoint file: %s\n", f); + fprintf(fplog, "\n"); + } +} + +static void check_match(FILE* fplog, + const t_commrec* cr, + const ivec dd_nc, + const CheckpointHeaderContents& headerContents, + gmx_bool reproducibilityRequested) +{ + /* Note that this check_string on the version will also print a message + * when only the minor version differs. But we only print a warning + * message further down with reproducibilityRequested=TRUE. + */ + gmx_bool versionDiffers = FALSE; + check_string(fplog, "Version", gmx_version(), headerContents.version, &versionDiffers); + + gmx_bool precisionDiffers = FALSE; + check_int(fplog, "Double prec.", GMX_DOUBLE, headerContents.double_prec, &precisionDiffers); + if (precisionDiffers) + { + const char msg_precision_difference[] = + "You are continuing a simulation with a different precision. Not matching\n" + "mixed/double precision will lead to precision or performance loss.\n"; + if (fplog) + { + fprintf(fplog, "%s\n", msg_precision_difference); + } + } + + gmx_bool mm = (versionDiffers || precisionDiffers); + + if (reproducibilityRequested) + { + check_string( + fplog, "Program name", gmx::getProgramContext().fullBinaryPath(), headerContents.fprog, &mm); + + check_int(fplog, "#ranks", cr->nnodes, headerContents.nnodes, &mm); + } + + if (cr->sizeOfDefaultCommunicator > 1 && reproducibilityRequested) + { + // TODO: These checks are incorrect (see redmine #3309) + check_int(fplog, "#PME-ranks", cr->npmenodes, headerContents.npme, &mm); + + int npp = cr->sizeOfDefaultCommunicator; + if (cr->npmenodes >= 0) + { + npp -= cr->npmenodes; + } + int npp_f = headerContents.nnodes; + if (headerContents.npme >= 0) + { + npp_f -= headerContents.npme; + } + if (npp == npp_f) + { + check_int(fplog, "#DD-cells[x]", dd_nc[XX], headerContents.dd_nc[XX], &mm); + check_int(fplog, "#DD-cells[y]", dd_nc[YY], headerContents.dd_nc[YY], &mm); + check_int(fplog, "#DD-cells[z]", dd_nc[ZZ], headerContents.dd_nc[ZZ], &mm); + } + } + + if (mm) + { + /* Gromacs should be able to continue from checkpoints between + * different patch level versions, but we do not guarantee + * compatibility between different major/minor versions - check this. + */ + int gmx_major; + int cpt_major; + sscanf(gmx_version(), "%5d", &gmx_major); + int ret = sscanf(headerContents.version, "%5d", &cpt_major); + gmx_bool majorVersionDiffers = (ret < 1 || gmx_major != cpt_major); + + const char msg_major_version_difference[] = + "The current GROMACS major version is not identical to the one that\n" + "generated the checkpoint file. In principle GROMACS does not support\n" + "continuation from checkpoints between different versions, so we advise\n" + "against this. If you still want to try your luck we recommend that you use\n" + "the -noappend flag to keep your output files from the two versions separate.\n" + "This might also work around errors where the output fields in the energy\n" + "file have changed between the different versions.\n"; + + const char msg_mismatch_notice[] = + "GROMACS patchlevel, binary or parallel settings differ from previous run.\n" + "Continuation is exact, but not guaranteed to be binary identical.\n"; + + if (majorVersionDiffers) + { + if (fplog) + { + fprintf(fplog, "%s\n", msg_major_version_difference); + } + } + else if (reproducibilityRequested) + { + /* Major & minor versions match at least, but something is different. */ + if (fplog) + { + fprintf(fplog, "%s\n", msg_mismatch_notice); + } + } + } +} + +static void read_checkpoint(const char* fn, + t_fileio* logfio, + const t_commrec* cr, + const ivec dd_nc, + IntegrationAlgorithm eIntegrator, + int* init_fep_state, + CheckpointHeaderContents* headerContents, + t_state* state, + ObservablesHistory* observablesHistory, + gmx_bool reproducibilityRequested, + const gmx::MDModulesNotifiers& mdModulesNotifiers, + gmx::ReadCheckpointDataHolder* modularSimulatorCheckpointData, + bool useModularSimulator) +{ + t_fileio* fp; + char buf[STEPSTRSIZE]; + int ret; + + fp = gmx_fio_open(fn, "r"); + do_cpt_header(gmx_fio_getxdr(fp), TRUE, nullptr, headerContents); + + // If we are appending, then we don't want write to the open log + // file because we still need to compute a checksum for it. In + // that case, the filehandle will be nullptr. Otherwise, we report + // to the new log file about the checkpoint file that we are + // reading from. + FILE* fplog = gmx_fio_getfp(logfio); + if (fplog) + { + fprintf(fplog, "\n"); + fprintf(fplog, "Reading checkpoint file %s\n", fn); + fprintf(fplog, " file generated by: %s\n", headerContents->fprog); + fprintf(fplog, " file generated at: %s\n", headerContents->ftime); + fprintf(fplog, " GROMACS double prec.: %d\n", headerContents->double_prec); + fprintf(fplog, " simulation part #: %d\n", headerContents->simulation_part); + fprintf(fplog, " step: %s\n", gmx_step_str(headerContents->step, buf)); + fprintf(fplog, " time: %f\n", headerContents->t); + fprintf(fplog, "\n"); + } + + if (headerContents->natoms != state->natoms) + { + gmx_fatal(FARGS, + "Checkpoint file is for a system of %d atoms, while the current system consists " + "of %d atoms", + headerContents->natoms, + state->natoms); + } + if (headerContents->ngtc != state->ngtc) + { + gmx_fatal(FARGS, + "Checkpoint file is for a system of %d T-coupling groups, while the current " + "system consists of %d T-coupling groups", + headerContents->ngtc, + state->ngtc); + } + if (headerContents->nnhpres != state->nnhpres) + { + gmx_fatal(FARGS, + "Checkpoint file is for a system of %d NH-pressure-coupling variables, while the " + "current system consists of %d NH-pressure-coupling variables", + headerContents->nnhpres, + state->nnhpres); + } + + int nlambdaHistory = (state->dfhist ? state->dfhist->nlambda : 0); + if (headerContents->nlambda != nlambdaHistory) + { + gmx_fatal(FARGS, + "Checkpoint file is for a system with %d lambda states, while the current system " + "consists of %d lambda states", + headerContents->nlambda, + nlambdaHistory); + } + + init_gtc_state(state, + state->ngtc, + state->nnhpres, + headerContents->nhchainlength); /* need to keep this here to keep the tpr format working */ + /* write over whatever was read; we use the number of Nose-Hoover chains from the checkpoint */ + + if (headerContents->eIntegrator != eIntegrator) + { + gmx_fatal(FARGS, + "Cannot change integrator during a checkpoint restart. Perhaps you should make a " + "new .tpr with grompp -f new.mdp -t %s", + fn); + } + + // For modular simulator, no state object is populated, so we cannot do this check here! + if (headerContents->flags_state != state->flags && !useModularSimulator) + { + gmx_fatal(FARGS, + "Cannot change a simulation algorithm during a checkpoint restart. Perhaps you " + "should make a new .tpr with grompp -f new.mdp -t %s", + fn); + } + + GMX_RELEASE_ASSERT(!(headerContents->isModularSimulatorCheckpoint && !useModularSimulator), + "Checkpoint file was written by modular simulator, but the current " + "simulation uses the legacy simulator.\n\n" + "Try the following steps:\n" + "1. Make sure the GMX_DISABLE_MODULAR_SIMULATOR environment variable is not " + "set to return to the default behavior. Retry running the simulation.\n" + "2. If the problem persists, set the environment variable " + "GMX_USE_MODULAR_SIMULATOR=ON to overwrite the default behavior and use " + "modular simulator for all implemented use cases."); + GMX_RELEASE_ASSERT(!(!headerContents->isModularSimulatorCheckpoint && useModularSimulator), + "Checkpoint file was written by legacy simulator, but the current " + "simulation uses the modular simulator.\n\n" + "Try the following steps:\n" + "1. Make sure the GMX_USE_MODULAR_SIMULATOR environment variable is not set " + "to return to the default behavior. Retry running the simulation.\n" + "2. If the problem persists, set the environment variable " + "GMX_DISABLE_MODULAR_SIMULATOR=ON to overwrite the default behavior and use " + "legacy simulator for all implemented use cases."); + + if (MASTER(cr)) + { + check_match(fplog, cr, dd_nc, *headerContents, reproducibilityRequested); + } + + ret = do_cpt_state(gmx_fio_getxdr(fp), headerContents->flags_state, state, nullptr); + *init_fep_state = state->fep_state; /* there should be a better way to do this than setting it + here. Investigate for 5.0. */ + if (ret) + { + cp_error(); + } + ret = do_cpt_ekinstate(gmx_fio_getxdr(fp), headerContents->flags_eks, &state->ekinstate, nullptr); + if (ret) + { + cp_error(); + } + state->ekinstate.hasReadEkinState = + (((headerContents->flags_eks & enumValueToBitMask(StateKineticEntry::EkinHalfStep)) != 0) + || ((headerContents->flags_eks & enumValueToBitMask(StateKineticEntry::EkinFullStep)) != 0) + || ((headerContents->flags_eks & enumValueToBitMask(StateKineticEntry::EkinHalfStepOld)) != 0) + || (((headerContents->flags_eks & enumValueToBitMask(StateKineticEntry::EkinNoseHooverScaleFullStep)) + | (headerContents->flags_eks & enumValueToBitMask(StateKineticEntry::EkinNoseHooverScaleHalfStep)) + | (headerContents->flags_eks & enumValueToBitMask(StateKineticEntry::VelocityScale))) + != 0)); + + if (headerContents->flags_enh && observablesHistory->energyHistory == nullptr) + { + observablesHistory->energyHistory = std::make_unique(); + } + ret = do_cpt_enerhist( + gmx_fio_getxdr(fp), TRUE, headerContents->flags_enh, observablesHistory->energyHistory.get(), nullptr); + if (ret) + { + cp_error(); + } + + if (headerContents->flagsPullHistory) + { + if (observablesHistory->pullHistory == nullptr) + { + observablesHistory->pullHistory = std::make_unique(); + } + ret = doCptPullHist(gmx_fio_getxdr(fp), + TRUE, + headerContents->flagsPullHistory, + observablesHistory->pullHistory.get(), + nullptr); + if (ret) + { + cp_error(); + } + } + + if (headerContents->file_version < CheckPointVersion::Version45) + { + gmx_fatal(FARGS, + "Continuing from checkpoint files written before GROMACS 4.5 is not supported"); + } + + ret = do_cpt_df_hist( + gmx_fio_getxdr(fp), headerContents->flags_dfh, headerContents->nlambda, &state->dfhist, nullptr); + if (ret) + { + cp_error(); + } + + if (headerContents->nED > 0 && observablesHistory->edsamHistory == nullptr) + { + observablesHistory->edsamHistory = std::make_unique(edsamhistory_t{}); + } + ret = do_cpt_EDstate( + gmx_fio_getxdr(fp), TRUE, headerContents->nED, observablesHistory->edsamHistory.get(), nullptr); + if (ret) + { + cp_error(); + } + + if (headerContents->flags_awhh != 0 && state->awhHistory == nullptr) + { + state->awhHistory = std::make_shared(); + } + ret = do_cpt_awh(gmx_fio_getxdr(fp), TRUE, headerContents->flags_awhh, state->awhHistory.get(), nullptr); + if (ret) + { + cp_error(); + } + + if (headerContents->eSwapCoords != SwapType::No && observablesHistory->swapHistory == nullptr) + { + observablesHistory->swapHistory = std::make_unique(swaphistory_t{}); + } + ret = do_cpt_swapstate( + gmx_fio_getxdr(fp), TRUE, headerContents->eSwapCoords, observablesHistory->swapHistory.get(), nullptr); + if (ret) + { + cp_error(); + } + + std::vector outputfiles; + ret = do_cpt_files(gmx_fio_getxdr(fp), TRUE, &outputfiles, nullptr, headerContents->file_version); + if (ret) + { + cp_error(); + } + do_cpt_mdmodules(headerContents->file_version, fp, mdModulesNotifiers, nullptr); + if (headerContents->file_version >= CheckPointVersion::ModularSimulator) + { + gmx::FileIOXdrSerializer serializer(fp); + modularSimulatorCheckpointData->deserialize(&serializer); + } + ret = do_cpt_footer(gmx_fio_getxdr(fp), headerContents->file_version); + if (ret) + { + cp_error(); + } + if (gmx_fio_close(fp) != 0) + { + gmx_file("Cannot read/write checkpoint; corrupt file, or maybe you are out of disk space?"); + } +} + + +void load_checkpoint(const char* fn, + t_fileio* logfio, + const t_commrec* cr, + const ivec dd_nc, + t_inputrec* ir, + t_state* state, + ObservablesHistory* observablesHistory, + gmx_bool reproducibilityRequested, + const gmx::MDModulesNotifiers& mdModulesNotifiers, + gmx::ReadCheckpointDataHolder* modularSimulatorCheckpointData, + bool useModularSimulator) +{ + CheckpointHeaderContents headerContents; + if (SIMMASTER(cr)) + { + /* Read the state from the checkpoint file */ + read_checkpoint(fn, + logfio, + cr, + dd_nc, + ir->eI, + &(ir->fepvals->init_fep_state), + &headerContents, + state, + observablesHistory, + reproducibilityRequested, + mdModulesNotifiers, + modularSimulatorCheckpointData, + useModularSimulator); + } + if (PAR(cr)) + { + gmx_bcast(sizeof(headerContents.step), &headerContents.step, cr->mpiDefaultCommunicator); + gmx::MDModulesCheckpointReadingBroadcast broadcastCheckPointData = { + cr->mpiDefaultCommunicator, PAR(cr), headerContents.file_version + }; + mdModulesNotifiers.checkpointingNotifier_.notify(broadcastCheckPointData); + } + ir->bContinuation = TRUE; + if (ir->nsteps >= 0) + { + // TODO Should the following condition be <=? Currently if you + // pass a checkpoint written by an normal completion to a restart, + // mdrun will read all input, does some work but no steps, and + // write successful output. But perhaps that is not desirable. + // Note that we do not intend to support the use of mdrun + // -nsteps to circumvent this condition. + if (ir->nsteps + ir->init_step < headerContents.step) + { + char buf[STEPSTRSIZE]; + std::string message = + gmx::formatString("The input requested %s steps, ", gmx_step_str(ir->nsteps, buf)); + if (ir->init_step > 0) + { + message += gmx::formatString("starting from step %s, ", gmx_step_str(ir->init_step, buf)); + } + message += gmx::formatString( + "however the checkpoint " + "file has already reached step %s. The simulation will not " + "proceed, because either your simulation is already complete, " + "or your combination of input files don't match.", + gmx_step_str(headerContents.step, buf)); + gmx_fatal(FARGS, "%s", message.c_str()); + } + ir->nsteps += ir->init_step - headerContents.step; + } + ir->init_step = headerContents.step; + ir->simulation_part = headerContents.simulation_part + 1; +} + +void read_checkpoint_part_and_step(const char* filename, int* simulation_part, int64_t* step) +{ + t_fileio* fp; + + if (filename == nullptr || !gmx_fexist(filename) || ((fp = gmx_fio_open(filename, "r")) == nullptr)) + { + *simulation_part = 0; + *step = 0; + return; + } + + CheckpointHeaderContents headerContents; + do_cpt_header(gmx_fio_getxdr(fp), TRUE, nullptr, &headerContents); + gmx_fio_close(fp); + *simulation_part = headerContents.simulation_part; + *step = headerContents.step; +} + +static CheckpointHeaderContents read_checkpoint_data(t_fileio* fp, + t_state* state, + std::vector* outputfiles, + gmx::ReadCheckpointDataHolder* modularSimulatorCheckpointData) +{ + CheckpointHeaderContents headerContents; + do_cpt_header(gmx_fio_getxdr(fp), TRUE, nullptr, &headerContents); + state->natoms = headerContents.natoms; + state->ngtc = headerContents.ngtc; + state->nnhpres = headerContents.nnhpres; + state->nhchainlength = headerContents.nhchainlength; + state->flags = headerContents.flags_state; + int ret = do_cpt_state(gmx_fio_getxdr(fp), state->flags, state, nullptr); + if (ret) + { + cp_error(); + } + ret = do_cpt_ekinstate(gmx_fio_getxdr(fp), headerContents.flags_eks, &state->ekinstate, nullptr); + if (ret) + { + cp_error(); + } + + energyhistory_t enerhist; + ret = do_cpt_enerhist(gmx_fio_getxdr(fp), TRUE, headerContents.flags_enh, &enerhist, nullptr); + if (ret) + { + cp_error(); + } + PullHistory pullHist = {}; + ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE, headerContents.flagsPullHistory, &pullHist, nullptr); + if (ret) + { + cp_error(); + } + + ret = do_cpt_df_hist( + gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state->dfhist, nullptr); + if (ret) + { + cp_error(); + } + + edsamhistory_t edsamhist = {}; + ret = do_cpt_EDstate(gmx_fio_getxdr(fp), TRUE, headerContents.nED, &edsamhist, nullptr); + if (ret) + { + cp_error(); + } + + ret = do_cpt_awh(gmx_fio_getxdr(fp), TRUE, headerContents.flags_awhh, state->awhHistory.get(), nullptr); + if (ret) + { + cp_error(); + } + + swaphistory_t swaphist = {}; + ret = do_cpt_swapstate(gmx_fio_getxdr(fp), TRUE, headerContents.eSwapCoords, &swaphist, nullptr); + if (ret) + { + cp_error(); + } + + ret = do_cpt_files(gmx_fio_getxdr(fp), TRUE, outputfiles, nullptr, headerContents.file_version); + + if (ret) + { + cp_error(); + } + gmx::MDModulesNotifiers mdModuleNotifiers; + do_cpt_mdmodules(headerContents.file_version, fp, mdModuleNotifiers, nullptr); + if (headerContents.file_version >= CheckPointVersion::ModularSimulator) + { + // Store modular checkpoint data into modularSimulatorCheckpointData + gmx::FileIOXdrSerializer serializer(fp); + modularSimulatorCheckpointData->deserialize(&serializer); + } + ret = do_cpt_footer(gmx_fio_getxdr(fp), headerContents.file_version); + if (ret) + { + cp_error(); + } + return headerContents; +} + +void read_checkpoint_trxframe(t_fileio* fp, t_trxframe* fr) +{ + t_state state; + std::vector outputfiles; + gmx::ReadCheckpointDataHolder modularSimulatorCheckpointData; + CheckpointHeaderContents headerContents = + read_checkpoint_data(fp, &state, &outputfiles, &modularSimulatorCheckpointData); + if (headerContents.isModularSimulatorCheckpoint) + { + gmx::ModularSimulator::readCheckpointToTrxFrame(fr, &modularSimulatorCheckpointData, headerContents); + return; + } + + fr->natoms = state.natoms; + fr->bStep = TRUE; + fr->step = int64_to_int(headerContents.step, "conversion of checkpoint to trajectory"); + fr->bTime = TRUE; + fr->time = headerContents.t; + fr->bLambda = TRUE; + fr->lambda = state.lambda[FreeEnergyPerturbationCouplingType::Fep]; + fr->fep_state = state.fep_state; + fr->bAtoms = FALSE; + fr->bX = ((state.flags & enumValueToBitMask(StateEntry::X)) != 0); + if (fr->bX) + { + fr->x = makeRvecArray(state.x, state.natoms); + } + fr->bV = ((state.flags & enumValueToBitMask(StateEntry::V)) != 0); + if (fr->bV) + { + fr->v = makeRvecArray(state.v, state.natoms); + } + fr->bF = FALSE; + fr->bBox = ((state.flags & enumValueToBitMask(StateEntry::Box)) != 0); + if (fr->bBox) + { + copy_mat(state.box, fr->box); + } +} + +void list_checkpoint(const char* fn, FILE* out) +{ + t_fileio* fp; + int ret; + + t_state state; + + fp = gmx_fio_open(fn, "r"); + CheckpointHeaderContents headerContents; + do_cpt_header(gmx_fio_getxdr(fp), TRUE, out, &headerContents); + state.natoms = headerContents.natoms; + state.ngtc = headerContents.ngtc; + state.nnhpres = headerContents.nnhpres; + state.nhchainlength = headerContents.nhchainlength; + state.flags = headerContents.flags_state; + ret = do_cpt_state(gmx_fio_getxdr(fp), state.flags, &state, out); + if (ret) + { + cp_error(); + } + ret = do_cpt_ekinstate(gmx_fio_getxdr(fp), headerContents.flags_eks, &state.ekinstate, out); + if (ret) + { + cp_error(); + } + + energyhistory_t enerhist; + ret = do_cpt_enerhist(gmx_fio_getxdr(fp), TRUE, headerContents.flags_enh, &enerhist, out); + + if (ret == 0) + { + PullHistory pullHist = {}; + ret = doCptPullHist(gmx_fio_getxdr(fp), TRUE, headerContents.flagsPullHistory, &pullHist, out); + } + + if (ret == 0) + { + ret = do_cpt_df_hist( + gmx_fio_getxdr(fp), headerContents.flags_dfh, headerContents.nlambda, &state.dfhist, out); + } + + if (ret == 0) + { + edsamhistory_t edsamhist = {}; + ret = do_cpt_EDstate(gmx_fio_getxdr(fp), TRUE, headerContents.nED, &edsamhist, out); + } + + if (ret == 0) + { + ret = do_cpt_awh(gmx_fio_getxdr(fp), TRUE, headerContents.flags_awhh, state.awhHistory.get(), out); + } + + if (ret == 0) + { + swaphistory_t swaphist = {}; + ret = do_cpt_swapstate(gmx_fio_getxdr(fp), TRUE, headerContents.eSwapCoords, &swaphist, out); + } + + if (ret == 0) + { + std::vector outputfiles; + ret = do_cpt_files(gmx_fio_getxdr(fp), TRUE, &outputfiles, out, headerContents.file_version); + } + gmx::MDModulesNotifiers mdModuleNotifiers; + do_cpt_mdmodules(headerContents.file_version, fp, mdModuleNotifiers, out); + if (headerContents.file_version >= CheckPointVersion::ModularSimulator) + { + gmx::FileIOXdrSerializer serializer(fp); + gmx::ReadCheckpointDataHolder modularSimulatorCheckpointData; + modularSimulatorCheckpointData.deserialize(&serializer); + modularSimulatorCheckpointData.dump(out); + } + + if (ret == 0) + { + ret = do_cpt_footer(gmx_fio_getxdr(fp), headerContents.file_version); + } + + if (ret) + { + cp_warning(out); + } + if (gmx_fio_close(fp) != 0) + { + gmx_file("Cannot read/write checkpoint; corrupt file, or maybe you are out of disk space?"); + } +} + +/* This routine cannot print tons of data, since it is called before the log file is opened. */ +CheckpointHeaderContents read_checkpoint_simulation_part_and_filenames(t_fileio* fp, + std::vector* outputfiles) +{ + t_state state; + gmx::ReadCheckpointDataHolder modularSimulatorCheckpointData; + CheckpointHeaderContents headerContents = + read_checkpoint_data(fp, &state, outputfiles, &modularSimulatorCheckpointData); + if (gmx_fio_close(fp) != 0) + { + gmx_file("Cannot read/write checkpoint; corrupt file, or maybe you are out of disk space?"); + } + return headerContents; +} diff --git a/src/gromacs/fileio/checkpoint.h b/src/gromacs/fileio/checkpoint.h index 5145e1db58c..30544622c2f 100644 --- a/src/gromacs/fileio/checkpoint.h +++ b/src/gromacs/fileio/checkpoint.h @@ -220,6 +220,8 @@ enum class CheckPointVersion : int MDModules, //! Added checkpointing for modular simulator. ModularSimulator, + //! Added Colvars + Colvars, //! The total number of checkpoint versions. Count, //! Current version @@ -302,6 +304,8 @@ struct CheckpointHeaderContents int nED; //! Enum for coordinate swapping. SwapType eSwapCoords; + //! Colvars + int ecolvars; //! Whether the checkpoint was written by modular simulator. bool isModularSimulatorCheckpoint = false; }; diff --git a/src/gromacs/mdlib/energyoutput.cpp b/src/gromacs/mdlib/energyoutput.cpp index 6c0fca57cc0..e06e5119c5d 100644 --- a/src/gromacs/mdlib/energyoutput.cpp +++ b/src/gromacs/mdlib/energyoutput.cpp @@ -254,7 +254,7 @@ EnergyOutput::EnergyOutput(ener_file* fp_ene, bEner_[F_DISPCORR] = (inputrec.eDispCorr != DispersionCorrectionType::No); bEner_[F_DISRESVIOL] = (gmx_mtop_ftype_count(mtop, F_DISRES) > 0); bEner_[F_ORIRESDEV] = (gmx_mtop_ftype_count(mtop, F_ORIRES) > 0); - bEner_[F_COM_PULL] = ((inputrec.bPull && pull_have_potential(*pull_work)) || inputrec.bRot); + bEner_[F_COM_PULL] = ((inputrec.bPull && pull_have_potential(*pull_work)) || inputrec.bRot || inputrec.bColvars); // Check MDModules for any energy output MDModulesEnergyOutputToDensityFittingRequestChecker mdModulesAddOutputToDensityFittingFieldRequest; diff --git a/src/gromacs/mdlib/mdoutf.cpp b/src/gromacs/mdlib/mdoutf.cpp index 0a8653ae7ad..b75c8b1d439 100644 --- a/src/gromacs/mdlib/mdoutf.cpp +++ b/src/gromacs/mdlib/mdoutf.cpp @@ -60,6 +60,7 @@ #include "gromacs/mdtypes/observableshistory.h" #include "gromacs/mdtypes/state.h" #include "gromacs/mdtypes/swaphistory.h" +#include "gromacs/mdtypes/colvarshistory.h" #include "gromacs/timing/wallcycle.h" #include "gromacs/topology/topology.h" #include "gromacs/utility/baseversion.h" @@ -353,6 +354,10 @@ static void write_checkpoint(const char* fn, swaphistory_t* swaphist = observablesHistory->swapHistory.get(); SwapType eSwapCoords = (swaphist ? swaphist->eSwapCoords : SwapType::No); + /* COLVARS */ + colvarshistory_t* colvarshist = observablesHistory->colvarsHistory.get(); + int ecolvars = (colvarshist ? colvarshist->n_atoms : 0); + CheckpointHeaderContents headerContents = { CheckPointVersion::UnknownVersion0, { 0 }, { 0 }, @@ -381,6 +386,7 @@ static void write_checkpoint(const char* fn, 0, nED, eSwapCoords, + ecolvars, false }; std::strcpy(headerContents.version, gmx_version()); std::strcpy(headerContents.fprog, gmx::getProgramContext().fullBinaryPath()); diff --git a/src/gromacs/mdlib/sim_util.cpp b/src/gromacs/mdlib/sim_util.cpp index 55512275899..944eed4d9dc 100644 --- a/src/gromacs/mdlib/sim_util.cpp +++ b/src/gromacs/mdlib/sim_util.cpp @@ -122,6 +122,8 @@ #include "gromacs/utility/stringutil.h" #include "gromacs/utility/sysinfo.h" +#include "gromacs/applied_forces/colvars/colvarproxy_gromacs.h" + #include "gpuforcereduction.h" using gmx::ArrayRef; @@ -648,6 +650,16 @@ static void computeSpecialForces(FILE* fplog, */ if (stepWork.computeForces) { + + /* COLVARS */ + /* Colvars Module needs some updated data - just PBC & step number - before calling its ForceProvider */ + if (inputrec.bColvars) + { + t_pbc pbc; + set_pbc(&pbc, inputrec.pbcType, box); + inputrec.colvars_proxy->update_data(cr, step, pbc, box, didNeighborSearch); + } + gmx::ForceProviderInput forceProviderInput( x, mdatoms->homenr, diff --git a/src/gromacs/mdrun/legacymdrunoptions.h b/src/gromacs/mdrun/legacymdrunoptions.h index 20d793e2e3b..b37d82260b7 100644 --- a/src/gromacs/mdrun/legacymdrunoptions.h +++ b/src/gromacs/mdrun/legacymdrunoptions.h @@ -124,7 +124,9 @@ class LegacyMdrunOptions { efTOP, "-mp", "membed", ffOPTRD }, { efNDX, "-mn", "membed", ffOPTRD }, { efXVG, "-if", "imdforces", ffOPTWR }, - { efXVG, "-swap", "swapions", ffOPTWR } } }; + { efXVG, "-swap", "swapions", ffOPTWR }, + { efDAT, "-colvars", "colvars", ffOPTRDMULT }, /* COLVARS */ + { efDAT, "-colvars_restart", "colvars", ffOPTRD }, /* COLVARS */}}; //! Print a warning if any force is larger than this (in kJ/mol nm). real pforce = -1; diff --git a/src/gromacs/mdrun/replicaexchange.cpp b/src/gromacs/mdrun/replicaexchange.cpp index 9fa553c191b..9c5be82469e 100644 --- a/src/gromacs/mdrun/replicaexchange.cpp +++ b/src/gromacs/mdrun/replicaexchange.cpp @@ -617,6 +617,7 @@ static void exchange_state(const gmx_multisim_t* ms, int b, t_state* state) exchange_doubles(ms, b, &state->baros_integral, 1); exchange_rvecs(ms, b, state->x.rvec_array(), state->natoms); exchange_rvecs(ms, b, state->v.rvec_array(), state->natoms); + exchange_rvecs(ms, b, state->xa_old_whole_colvars, state->n_colvars_atoms); } static void copy_state_serial(const t_state* src, t_state* dest) diff --git a/src/gromacs/mdrun/runner.cpp b/src/gromacs/mdrun/runner.cpp index ea7f402ded5..3bd96417da5 100644 --- a/src/gromacs/mdrun/runner.cpp +++ b/src/gromacs/mdrun/runner.cpp @@ -126,6 +126,7 @@ #include "gromacs/mdtypes/mdrunoptions.h" #include "gromacs/mdtypes/multipletimestepping.h" #include "gromacs/mdtypes/observableshistory.h" +#include "gromacs/mdtypes/colvarshistory.h" #include "gromacs/mdtypes/observablesreducer.h" #include "gromacs/mdtypes/simulation_workload.h" #include "gromacs/mdtypes/state.h" @@ -170,6 +171,8 @@ #include "gromacs/utility/stringutil.h" #include "gromacs/utility/mpiinfo.h" +#include "gromacs/applied_forces/colvars/colvarproxy_gromacs.h" + #include "isimulator.h" #include "membedholder.h" #include "replicaexchange.h" @@ -2055,6 +2058,52 @@ int Mdrunner::mdrunner() mdrunOptions.imdOptions, startingBehavior); + /* COLVARS */ + if (opt2bSet("-colvars",filenames.size(), filenames.data())) + { + + gmx::ArrayRef filenames_colvars; + std::string filename_restart; + std::string prefix; + + inputrec->bColvars = TRUE; + + /* Retrieve filenames */ + filenames_colvars = opt2fns("-colvars", filenames.size(), filenames.data()); + if (opt2bSet("-colvars_restart",filenames.size(), filenames.data())) + { + filename_restart = opt2fn("-colvars_restart",filenames.size(), filenames.data()); + } + + /* Determine the prefix for the colvars output files, based on the logfile name. */ + std::string logfile = ftp2fn(efLOG, filenames.size(), filenames.data()); + /* 4 = ".log".length() */ + if(logfile.length() > 4) + { + prefix = logfile.substr(0,logfile.length()-4); + } + + inputrec->colvars_proxy = new colvarproxy_gromacs(); + inputrec->colvars_proxy->init(inputrec.get(),inputrec->init_step,mtop, &observablesHistory, prefix, filenames_colvars,filename_restart, cr, MASTER(cr) ? globalState->x.rvec_array() : nullptr, + MASTER(cr) ? &globalState->xa_old_whole_colvars : nullptr, MASTER(cr) ? &globalState->n_colvars_atoms : nullptr); + fr->forceProviders->addForceProvider(inputrec->colvars_proxy); + } + else + { + inputrec->bColvars = FALSE; + if (opt2bSet("-colvars_restart",filenames.size(), filenames.data())) + { + gmx_fatal(FARGS, "-colvars_restart can only be used together with the -colvars option."); + } + if(observablesHistory.colvarsHistory) { + gmx_fatal(FARGS, + "The checkpoint is from a run with colvars, " + "but the current run did not specify the -colvars option. " + "Either specify the -colvars option to mdrun, or do not use this checkpoint file."); + } + } + + if (haveDDAtomOrdering(*cr)) { GMX_RELEASE_ASSERT(fr, "fr was NULL while cr->duty was DUTY_PP"); @@ -2225,6 +2274,16 @@ int Mdrunner::mdrunner() releaseDevice(deviceInfo); } + /* COLVARS */ + if (inputrec->bColvars) + { + GMX_RELEASE_ASSERT(inputrec->colvars_proxy, "inputrec->colvars_proxy was NULL while colvars module was enabled."); + + inputrec->colvars_proxy->finish(cr); + delete inputrec->colvars_proxy; + inputrec->colvars_proxy = nullptr; + } + /* Does what it says */ print_date_and_time(fplog, cr->nodeid, "Finished mdrun", gmx_gettime()); walltime_accounting_destroy(walltime_accounting); diff --git a/src/gromacs/mdtypes/colvarshistory.h b/src/gromacs/mdtypes/colvarshistory.h new file mode 100644 index 00000000000..6605e6fce2e --- /dev/null +++ b/src/gromacs/mdtypes/colvarshistory.h @@ -0,0 +1,20 @@ +#ifndef GMX_MDLIB_COLVARSHISTORY_H +#define GMX_MDLIB_COLVARSHISTORY_H + +#include "gromacs/math/vectypes.h" +#include "gromacs/utility/basedefinitions.h" + +/* Helper structure to be able to make colvars group(s) whole + * + * To also make colvars group(s) whole, we save the last whole configuration + * of the atoms in the checkpoint file. + */ +typedef struct colvarshistory_t +{ + gmx_bool bFromCpt; // Did we start from a checkpoint file? + int n_atoms; // Number of colvars atoms + rvec* xa_old_whole; // Last known whole positions of the colvars atoms + rvec* xa_old_whole_p; // Pointer to these positions +} colvarshistory_t; + +#endif diff --git a/src/gromacs/mdtypes/inputrec.h b/src/gromacs/mdtypes/inputrec.h index 922a22e3127..ec34a6eb3cc 100644 --- a/src/gromacs/mdtypes/inputrec.h +++ b/src/gromacs/mdtypes/inputrec.h @@ -51,6 +51,8 @@ struct gmx_enfrot; struct gmx_enfrotgrp; struct pull_params_t; +class colvarproxy_gromacs; + namespace gmx { class Awh; @@ -577,6 +579,10 @@ struct t_inputrec // NOLINT (clang-analyzer-optin.performance.Padding) //! KVT for storing simulation parameters that are not part of the mdp file. std::unique_ptr internalParameters; + + /* COLVARS */ + bool bColvars = false; /* Do we do colvars calculations ? */ + colvarproxy_gromacs *colvars_proxy = nullptr; /* The object for the colvars calculations */ }; int ir_optimal_nstcalcenergy(const t_inputrec* ir); diff --git a/src/gromacs/mdtypes/observableshistory.cpp b/src/gromacs/mdtypes/observableshistory.cpp index 12230eed9c4..13f5df10303 100644 --- a/src/gromacs/mdtypes/observableshistory.cpp +++ b/src/gromacs/mdtypes/observableshistory.cpp @@ -40,6 +40,7 @@ #include "gromacs/mdtypes/energyhistory.h" #include "gromacs/mdtypes/pullhistory.h" #include "gromacs/mdtypes/swaphistory.h" +#include "gromacs/mdtypes/colvarshistory.h" ObservablesHistory::ObservablesHistory() = default; ObservablesHistory::~ObservablesHistory() = default; diff --git a/src/gromacs/mdtypes/observableshistory.h b/src/gromacs/mdtypes/observableshistory.h index 6ed0e3fc000..172ead081fd 100644 --- a/src/gromacs/mdtypes/observableshistory.h +++ b/src/gromacs/mdtypes/observableshistory.h @@ -58,6 +58,7 @@ class energyhistory_t; class PullHistory; struct edsamhistory_t; struct swaphistory_t; +struct colvarshistory_t; /*! \libinternal \brief Observables history, for writing/reading to/from checkpoint file */ @@ -75,6 +76,9 @@ struct ObservablesHistory //! Ion/water position swapping history std::unique_ptr swapHistory; + //! Colvars + std::unique_ptr colvarsHistory; + ObservablesHistory(); ~ObservablesHistory(); diff --git a/src/gromacs/mdtypes/state.cpp b/src/gromacs/mdtypes/state.cpp index 99b315e036b..d49a386eee7 100644 --- a/src/gromacs/mdtypes/state.cpp +++ b/src/gromacs/mdtypes/state.cpp @@ -369,7 +369,9 @@ t_state::t_state() : dfhist(nullptr), awhHistory(nullptr), ddp_count(0), - ddp_count_cg_gl(0) + ddp_count_cg_gl(0), + xa_old_whole_colvars(nullptr), + n_colvars_atoms(0) { // It would be nicer to initialize these with {} or {{0}} in the diff --git a/src/gromacs/mdtypes/state.h b/src/gromacs/mdtypes/state.h index 579e3439a4d..725e841daea 100644 --- a/src/gromacs/mdtypes/state.h +++ b/src/gromacs/mdtypes/state.h @@ -282,6 +282,9 @@ class t_state std::vector cg_gl; //!< The global cg number of the local cgs std::vector pull_com_prev_step; //!< The COM of the previous step of each pull group + + int n_colvars_atoms; //!< number of colvars atoms + rvec* xa_old_whole_colvars; //!< last whole positions of colvars atoms }; #ifndef DOXYGEN diff --git a/src/programs/mdrun/tests/refdata/MdrunTest_WritesHelp.xml b/src/programs/mdrun/tests/refdata/MdrunTest_WritesHelp.xml index c2973bb1af5..cb4d1da2544 100644 --- a/src/programs/mdrun/tests/refdata/MdrunTest_WritesHelp.xml +++ b/src/programs/mdrun/tests/refdata/MdrunTest_WritesHelp.xml @@ -6,7 +6,8 @@ gmx [-s [<.tpr>]] [-cpi [<.cpt>]] [-table [<.xvg>]] [-tablep [<.xvg>]] [-tableb [<.xvg> [...]]] [-rerun [<.xtc/.trr/...>]] [-ei [<.edi>]] [-multidir [<dir> [...]]] [-awh [<.xvg>]] [-membed [<.dat>]] - [-mp [<.top>]] [-mn [<.ndx>]] [-o [<.trr/.cpt/...>]] [-x [<.xtc/.tng>]] + [-mp [<.top>]] [-mn [<.ndx>]] [-colvars [<.dat> [...]]] + [-colvars_restart [<.dat>]] [-o [<.trr/.cpt/...>]] [-x [<.xtc/.tng>]] [-cpo [<.cpt>]] [-c [<.gro/.g96/...>]] [-e [<.edr>]] [-g [<.log>]] [-dhdl [<.xvg>]] [-field [<.xvg>]] [-tpi [<.xvg>]] [-tpid [<.xvg>]] [-eo [<.xvg>]] [-px [<.xvg>]] [-pf [<.xvg>]] [-ro [<.xvg>]] @@ -163,6 +164,10 @@ Options to specify input files: Topology file -mn [<.ndx>] (membed.ndx) (Opt.) Index file + -colvars [<.dat> [...]] (colvars.dat) (Opt.) + Generic data file + -colvars_restart [<.dat>] (colvars.dat) (Opt.) + Generic data file Options to specify output files: