diff --git a/lib/colvars/Makefile.fermi b/lib/colvars/Makefile.fermi new file mode 100644 index 0000000000..0abf6ad28e --- /dev/null +++ b/lib/colvars/Makefile.fermi @@ -0,0 +1,96 @@ +# library build makefile for colvars module + +# ------ SETTINGS ------ + +CXX = g++ +CXXFLAGS = -O2 -mpc64 -march=native -funroll-loops \ + -fno-rtti -fno-exceptions # -DCOLVARS_DEBUG +ARCHIVE = ar +ARCHFLAG = -rscv +SHELL = /bin/sh + +# ------ DEFINITIONS ------ + +SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias.cpp colvarbias_meta.cpp \ + colvar.cpp colvarcomp_angles.cpp colvarcomp.cpp colvarcomp_coordnums.cpp \ + colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ + colvargrid.cpp colvarmodule.cpp colvarparse.cpp colvarvalue.cpp + +LIB = libcolvars.a +OBJ = $(SRC:.cpp=.o) +EXE = #colvars_standalone + +# ------ MAKE PROCEDURE ------ + +default: $(LIB) $(EXE) + +$(LIB): $(OBJ) + $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) + +colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) + $(CXX) -o $@ $(CXXFLAGS) $^ + +# ------ MAKE FLAGS ------ + +.SUFFIXES: +.SUFFIXES: .cpp .o + +.PHONY: default clean + +# ------ COMPILE RULES ------ + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< + +# ------ DEPENDENCIES ------ +# +colvars_main.o: colvars_main.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarproxy_standalone.h colvaratoms.h colvarparse.h colvarvalue.h +colvarproxy_standalone.o: colvarproxy_standalone.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvaratoms.h colvarparse.h colvarvalue.h \ + colvarproxy_standalone.h +colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarparse.h colvarvalue.h colvaratoms.h +colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ + colvarproxy.h colvar.h colvarvalue.h colvarparse.h colvarbias_abf.h \ + colvarbias.h colvargrid.h +colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarbias.h colvar.h colvarparse.h +colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h \ + colvarbias_meta.h colvarbias.h colvargrid.h +colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvarcomp.h colvaratoms.h +colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ + colvarproxy.h colvar.h colvarvalue.h colvarparse.h colvarcomp.h \ + colvaratoms.h +colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvar.h colvarparse.h colvarcomp.h colvaratoms.h +colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvarparse.h colvarvalue.h colvaratoms.h \ + colvar.h colvarcomp.h +colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvar.h \ + colvarcomp.h colvaratoms.h +colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ + colvarproxy.h colvarvalue.h colvarparse.h colvar.h colvarcomp.h \ + colvaratoms.h +colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvar.h \ + colvarcomp.h colvaratoms.h +colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvarcomp.h colvaratoms.h \ + colvargrid.h +colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarparse.h colvarvalue.h colvar.h colvarbias.h colvarbias_meta.h \ + colvargrid.h colvarbias_abf.h +colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h +colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h + +# ------ CLEAN ------ + +clean: + -rm *.o *~ $(LIB) + diff --git a/lib/colvars/Makefile.g++ b/lib/colvars/Makefile.g++ new file mode 100644 index 0000000000..ff2e5c5925 --- /dev/null +++ b/lib/colvars/Makefile.g++ @@ -0,0 +1,95 @@ +# library build makefile for colvars module + +# ------ SETTINGS ------ + +CXX = g++ +CXXFLAGS = -O2 -g -funroll-loops # -DCOLVARS_DEBUG +ARCHIVE = ar +ARCHFLAG = -rscv +SHELL = /bin/sh + +# ------ DEFINITIONS ------ + +SRC = colvaratoms.cpp colvarbias_abf.cpp colvarbias.cpp colvarbias_meta.cpp \ + colvar.cpp colvarcomp_angles.cpp colvarcomp.cpp colvarcomp_coordnums.cpp \ + colvarcomp_distances.cpp colvarcomp_protein.cpp colvarcomp_rotations.cpp \ + colvargrid.cpp colvarmodule.cpp colvarparse.cpp colvarvalue.cpp + +LIB = libcolvars.a +OBJ = $(SRC:.cpp=.o) +EXE = #colvars_standalone + +# ------ MAKE PROCEDURE ------ + +default: $(LIB) $(EXE) + +$(LIB): $(OBJ) + $(ARCHIVE) $(ARFLAGS) $(LIB) $(OBJ) + +colvars_standalone: colvars_main.o colvarproxy_standalone.o $(LIB) + $(CXX) -o $@ $(CXXFLAGS) $^ + +# ------ MAKE FLAGS ------ + +.SUFFIXES: +.SUFFIXES: .cpp .o + +.PHONY: default clean + +# ------ COMPILE RULES ------ + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< + +# ------ DEPENDENCIES ------ +# +colvars_main.o: colvars_main.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarproxy_standalone.h colvaratoms.h colvarparse.h colvarvalue.h +colvarproxy_standalone.o: colvarproxy_standalone.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvaratoms.h colvarparse.h colvarvalue.h \ + colvarproxy_standalone.h +colvaratoms.o: colvaratoms.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarparse.h colvarvalue.h colvaratoms.h +colvarbias_abf.o: colvarbias_abf.cpp colvarmodule.h colvartypes.h \ + colvarproxy.h colvar.h colvarvalue.h colvarparse.h colvarbias_abf.h \ + colvarbias.h colvargrid.h +colvarbias.o: colvarbias.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarbias.h colvar.h colvarparse.h +colvarbias_meta.o: colvarbias_meta.cpp colvar.h colvarmodule.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h \ + colvarbias_meta.h colvarbias.h colvargrid.h +colvar.o: colvar.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvarcomp.h colvaratoms.h +colvarcomp_angles.o: colvarcomp_angles.cpp colvarmodule.h colvartypes.h \ + colvarproxy.h colvar.h colvarvalue.h colvarparse.h colvarcomp.h \ + colvaratoms.h +colvarcomp.o: colvarcomp.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvar.h colvarparse.h colvarcomp.h colvaratoms.h +colvarcomp_coordnums.o: colvarcomp_coordnums.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvarparse.h colvarvalue.h colvaratoms.h \ + colvar.h colvarcomp.h +colvarcomp_distances.o: colvarcomp_distances.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvar.h \ + colvarcomp.h colvaratoms.h +colvarcomp_protein.o: colvarcomp_protein.cpp colvarmodule.h colvartypes.h \ + colvarproxy.h colvarvalue.h colvarparse.h colvar.h colvarcomp.h \ + colvaratoms.h +colvarcomp_rotations.o: colvarcomp_rotations.cpp colvarmodule.h \ + colvartypes.h colvarproxy.h colvarvalue.h colvarparse.h colvar.h \ + colvarcomp.h colvaratoms.h +colvargrid.o: colvargrid.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h colvar.h colvarcomp.h colvaratoms.h \ + colvargrid.h +colvarmodule.o: colvarmodule.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarparse.h colvarvalue.h colvar.h colvarbias.h colvarbias_meta.h \ + colvargrid.h colvarbias_abf.h +colvarparse.o: colvarparse.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h colvarparse.h +colvarvalue.o: colvarvalue.cpp colvarmodule.h colvartypes.h colvarproxy.h \ + colvarvalue.h + +# ------ CLEAN ------ + +clean: + -rm *.o *~ $(LIB) + diff --git a/lib/colvars/Makefile.lammps b/lib/colvars/Makefile.lammps new file mode 100644 index 0000000000..1ef229d58a --- /dev/null +++ b/lib/colvars/Makefile.lammps @@ -0,0 +1,5 @@ +# Settings that the LAMMPS build will import when this package library is used + +colvars_SYSINC = # -DCOLVARS_DEBUG +colvars_SYSLIB = +colvars_SYSPATH = diff --git a/lib/colvars/README b/lib/colvars/README new file mode 100644 index 0000000000..ea1bfa3184 --- /dev/null +++ b/lib/colvars/README @@ -0,0 +1,50 @@ +This directory has source files to build a library that LAMMPS +links against when using the USER-COLVARS package. + +When you are done building this library, two files should +exist in this directory: + +libcolvars.a the library LAMMPS will link against +Makefile.lammps settings the LAMMPS Makefile will import + +The latter file will have settings like this (can be omitted if blank): + +colvars_SYSINC = +colvars_SYSLIB = +colvars_SYSPATH = + +SYSINC is for settings needed to compile LAMMPS source files +SYSLIB is for additional system libraries needed by this package +SYSPATH is the path(s) to where those libraries are + +You must insure these settings are correct for your system, else +the LAMMPS build will likely fail. + +------------------------------------------------------------------------- + +The following publication describes the principles of +the implementation of this library: + + Exploring Multidimensional Free Energy Landscapes Using + Time-Dependent Biases on Collective Variables, + J. Hénin, G. Fiorin, C. Chipot, and M. L. Klein, + J. Chem. Theory Comput., 6, 35-47 (2010). + +This library is the portable "colvars" module, originally interfaced +with the NAMD MD code, to provide an extensible software framework, +that allows enhanced sampling in molecular dynamics simulations. +The module is written to maximize performance, portability, +flexibility of usage for the user, and extensibility for the developer. + +This library must be built with a C++ compiler, before LAMMPS is +built, so LAMMPS can link against it. + +-------------- + +Build the library using one of the provided Makefiles or creating your +own, specific to your compiler and system. For example: + +make -f Makefile.g++ + +If the build is successful, you should end up with a libcolvars.a file. + diff --git a/lib/colvars/colvar.cpp b/lib/colvars/colvar.cpp new file mode 100644 index 0000000000..52c78c32a5 --- /dev/null +++ b/lib/colvars/colvar.cpp @@ -0,0 +1,1600 @@ +// -*- c++ -*- + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include + + +// XX TODO make the acf code handle forces as well as values and velocities + + +colvar::colvar (std::string const &conf) +{ + cvm::log ("Initializing a new collective variable.\n"); + + get_keyval (conf, "name", this->name, + (std::string ("colvar")+cvm::to_str (cvm::colvars.size()+1))); + + for (std::vector::iterator cvi = cvm::colvars.begin(); + cvi < cvm::colvars.end(); + cvi++) { + if ((*cvi)->name == this->name) + cvm::fatal_error ("Error: this colvar has the same name, \""+this->name+ + "\", as another colvar.\n"); + } + + // all tasks disabled by default + for (size_t i = 0; i < task_ntot; i++) { + tasks[i] = false; + } + + kinetic_energy = 0.0; + potential_energy = 0.0; + + // read the configuration and set up corresponding instances, for + // each type of component implemented +#define initialize_components(def_desc,def_config_key,def_class_name) \ + { \ + 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")); \ + cvm::increase_depth(); \ + cvc *cvcp = new colvar::def_class_name (def_conf); \ + if (cvcp != NULL) { \ + cvcs.push_back (cvcp); \ + cvcp->check_keywords (def_conf, def_config_key); \ + cvm::decrease_depth(); \ + } else { \ + cvm::fatal_error ("Error: in allocating component \"" \ + def_config_key"\".\n"); \ + } \ + if ( (cvcp->period != 0.0) || (cvcp->wrap_center != 0.0) ) { \ + if ( (cvcp->function_type != std::string ("distance_z")) && \ + (cvcp->function_type != std::string ("dihedral")) && \ + (cvcp->function_type != std::string ("spin_angle")) ) { \ + cvm::fatal_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));\ + } \ + } \ + if ( ! cvcs.back()->name.size()) \ + cvcs.back()->name = std::string (def_config_key)+ \ + (cvm::to_str (++def_count)); \ + if (cvm::debug()) \ + cvm::log ("Done initializing a \""+ \ + std::string (def_config_key)+ \ + "\" component"+ \ + (cvm::debug() ? \ + ", named \""+cvcs.back()->name+"\"" \ + : "")+".\n"); \ + def_conf = ""; \ + if (cvm::debug()) \ + cvm::log ("Parsed "+cvm::to_str (cvcs.size())+ \ + " components at this time.\n"); \ + } \ + } + + + initialize_components ("distance", "distance", distance); + initialize_components ("distance vector", "distanceVec", distance_vec); + initialize_components ("distance vector " + "direction", "distanceDir", distance_dir); + initialize_components ("distance projection " + "on an axis", "distanceZ", distance_z); + initialize_components ("distance projection " + "on a plane", "distanceXY", distance_xy); + initialize_components ("minimum distance", "minDistance", min_distance); + + initialize_components ("coordination " + "number", "coordNum", coordnum); + initialize_components ("self-coordination " + "number", "selfCoordNum", selfcoordnum); + + initialize_components ("angle", "angle", angle); + initialize_components ("dihedral", "dihedral", dihedral); + + initialize_components ("hydrogen bond", "hBond", h_bond); + + // initialize_components ("alpha helix", "alphaDihedrals", alpha_dihedrals); + initialize_components ("alpha helix", "alpha", alpha_angles); + + initialize_components ("dihedral principal " + "component", "dihedralPC", dihedPC); + + initialize_components ("orientation", "orientation", orientation); + initialize_components ("orientation " + "angle", "orientationAngle",orientation_angle); + initialize_components ("tilt", "tilt", tilt); + initialize_components ("spin angle", "spinAngle", spin_angle); + + initialize_components ("RMSD", "rmsd", rmsd); + + // initialize_components ("logarithm of MSD", "logmsd", logmsd); + + initialize_components ("radius of " + "gyration", "gyration", gyration); + initialize_components ("eigenvector", "eigenvector", eigenvector); + + if (!cvcs.size()) + cvm::fatal_error ("Error: no valid components were provided " + "for this collective variable.\n"); + + cvm::log ("All components initialized.\n"); + + + // this is set false if any of the components has an exponent + // different from 1 in the polynomial + b_linear = true; + + // these will be set to false if any of the cvcs has them false + b_inverse_gradients = true; + b_Jacobian_force = true; + + // Decide whether the colvar is periodic + // Used to wrap extended DOF if extendedLagrangian is on + if (cvcs.size() == 1 && (cvcs[0])->b_periodic && (cvcs[0])->sup_np == 1 + && (cvcs[0])->sup_coeff == 1.0 ) { + this->b_periodic = true; + this->period = (cvcs[0])->period; + // TODO write explicit wrap() function for colvars to allow for + // sup_coeff different from 1 + // this->period = (cvcs[0])->period * (cvcs[0])->sup_coeff; + } else { + this->b_periodic = false; + this->period = 0.0; + } + + // check the available features of each cvc + for (size_t i = 0; i < cvcs.size(); i++) { + + if ((cvcs[i])->sup_np != 1) { + if (cvm::debug() && b_linear) + cvm::log ("Warning: You are using a non-linear polynomial " + "combination to define this collective variable, " + "some biasing methods may be unavailable.\n"); + b_linear = 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"); + } + } + + if ((cvcs[i])->b_periodic && !b_periodic) { + cvm::log ("Warning: although this component is periodic, the colvar will " + "not be treated as periodic, either because the exponent is not " + "1, or because multiple components are present. Make sure that " + "you know what you are doing!"); + } + + if (! (cvcs[i])->b_inverse_gradients) + b_inverse_gradients = false; + + if (! (cvcs[i])->b_Jacobian_derivative) + b_Jacobian_force = false; + + for (size_t j = i; j < cvcs.size(); j++) { + if ( (cvcs[i])->type() != (cvcs[j])->type() ) { + cvm::fatal_error ("ERROR: you are definining this collective variable " + "by using components of different types, \""+ + colvarvalue::type_desc[(cvcs[i])->type()]+ + "\" and \""+ + colvarvalue::type_desc[(cvcs[j])->type()]+ + "\". " + "You must use the same type in order to " + " sum them together.\n"); + } + } + } + + + { + colvarvalue::Type const value_type = (cvcs[0])->type(); + if (cvm::debug()) + cvm::log ("This collective variable is a "+ + colvarvalue::type_desc[value_type]+", corresponding to "+ + cvm::to_str (colvarvalue::dof_num[value_type])+ + " internal degrees of freedom.\n"); + x.type (value_type); + x_reported.type (value_type); + } + + get_keyval (conf, "width", width, 1.0); + if (width <= 0.0) + cvm::fatal_error ("Error: \"width\" must be positive.\n"); + + lower_boundary.type (this->type()); + lower_wall.type (this->type()); + + upper_boundary.type (this->type()); + upper_wall.type (this->type()); + + if (this->type() == colvarvalue::type_scalar) { + + if (get_keyval (conf, "lowerBoundary", lower_boundary, lower_boundary)) { + enable (task_lower_boundary); + } + + get_keyval (conf, "lowerWallConstant", lower_wall_k, 0.0); + if (lower_wall_k > 0.0) { + get_keyval (conf, "lowerWall", lower_wall, lower_boundary); + enable (task_lower_wall); + } + + if (get_keyval (conf, "upperBoundary", upper_boundary, upper_boundary)) { + enable (task_upper_boundary); + } + + get_keyval (conf, "upperWallConstant", upper_wall_k, 0.0); + if (upper_wall_k > 0.0) { + get_keyval (conf, "upperWall", upper_wall, upper_boundary); + enable (task_upper_wall); + } + } + + // consistency checks for boundaries and walls + if (tasks[task_lower_boundary] && tasks[task_upper_boundary]) { + if (lower_boundary >= upper_boundary) { + cvm::fatal_error ("Error: the upper boundary, "+ + cvm::to_str (upper_boundary)+ + ", is not higher than the lower boundary, "+ + cvm::to_str (lower_boundary)+".\n"); + } + } + + if (tasks[task_lower_wall] && tasks[task_upper_wall]) { + if (lower_wall >= upper_wall) { + cvm::fatal_error ("Error: the upper wall, "+ + cvm::to_str (upper_wall)+ + ", is not higher than the lower wall, "+ + cvm::to_str (lower_wall)+".\n"); + } + + if (dist2 (lower_wall, upper_wall) < 1.0E-12) { + cvm::log ("Lower wall and upper wall are equal " + "in the periodic domain of the colvar: disabling walls.\n"); + disable (task_lower_wall); + disable (task_upper_wall); + } + } + + get_keyval (conf, "expandBoundaries", expand_boundaries, false); + if (expand_boundaries && periodic_boundaries()) { + cvm::fatal_error ("Error: trying to expand boundaries that already " + "cover a whole period of a periodic colvar.\n"); + } + + { + bool b_extended_lagrangian; + get_keyval (conf, "extendedLagrangian", b_extended_lagrangian, false); + + if (b_extended_lagrangian) { + cvm::real temp, tolerance, period; + + cvm::log ("Enabling the extended Lagrangian term for colvar \""+ + this->name+"\".\n"); + + enable (task_extended_lagrangian); + + xr.type (this->type()); + vr.type (this->type()); + fr.type (this->type()); + + const bool found = get_keyval (conf, "extendedTemp", temp, cvm::temperature()); + if (temp <= 0.0) { + if (found) + cvm::fatal_error ("Error: \"extendedTemp\" must be positive.\n"); + else + cvm::fatal_error ("Error: a positive temperature must be provided, either " + "by enabling a thermostat, or through \"extendedTemp\".\n"); + } + + get_keyval (conf, "extendedFluctuation", tolerance, 0.2*width); + if (tolerance <= 0.0) + cvm::fatal_error ("Error: \"extendedFluctuation\" must be positive.\n"); + ext_force_k = cvm::boltzmann() * temp / (tolerance * tolerance); + cvm::log ("Computed extended system force constant: " + cvm::to_str(ext_force_k) + " kcal/mol/U^2"); + + get_keyval (conf, "extendedTimeConstant", period, 40.0 * cvm::dt()); + if (period <= 0.0) + cvm::fatal_error ("Error: \"extendedTimeConstant\" must be positive.\n"); + ext_mass = (cvm::boltzmann() * temp * period * period) + / (4.0 * PI * PI * tolerance * tolerance); + cvm::log ("Computed fictitious mass: " + cvm::to_str(ext_mass) + " kcal/mol/(U/fs)^2 (U: colvar unit)"); + + { + bool b_output_energy; + get_keyval (conf, "outputEnergy", b_output_energy, false); + if (b_output_energy) { + enable (task_output_energy); + } + } + + get_keyval (conf, "extendedLangevinDamping", ext_gamma, 0.0); + if (ext_gamma < 0.0) + cvm::fatal_error ("Error: \"extendedLangevinDamping\" may not be negative.\n"); + if (ext_gamma != 0.0) { + enable (task_langevin); + ext_gamma *= 1.0e-3; // convert from ps-1 to fs-1 + ext_sigma = std::sqrt(2.0 * cvm::boltzmann() * temp * ext_gamma * ext_mass / cvm::dt()); + } + } + } + + { + bool b_output_value; + get_keyval (conf, "outputValue", b_output_value, true); + if (b_output_value) { + enable (task_output_value); + } + } + + { + bool b_output_velocity; + get_keyval (conf, "outputVelocity", b_output_velocity, false); + if (b_output_velocity) { + enable (task_output_velocity); + } + } + + { + bool b_output_system_force; + get_keyval (conf, "outputSystemForce", b_output_system_force, false); + if (b_output_system_force) { + enable (task_output_system_force); + } + } + + { + bool b_output_applied_force; + get_keyval (conf, "outputAppliedForce", b_output_applied_force, false); + if (b_output_applied_force) { + enable (task_output_applied_force); + } + } + + if (cvm::b_analysis) + parse_analysis (conf); + + if (cvm::debug()) + cvm::log ("Done initializing collective variable \""+this->name+"\".\n"); +} + + + +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++) { + for (size_t k = 0; k < cvcs[i]->atom_groups[j]->size(); k++) { + temp_id_list.push_back (cvcs[i]->atom_groups[j]->at(k).id); + } + } + } + + temp_id_list.sort(); + temp_id_list.unique(); + atom_ids = std::vector (temp_id_list.begin(), temp_id_list.end()); + 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"); + } +} + + +void 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 (task_runave); + + get_keyval (conf, "runAveLength", runave_length, 1000); + get_keyval (conf, "runAveStride", runave_stride, 1); + + if ((cvm::restart_out_freq % runave_stride) != 0) + cvm::fatal_error ("Error: runAveStride must be commensurate with the restart frequency.\n"); + + std::string runave_outfile; + get_keyval (conf, "runAveOutputFile", runave_outfile, + std::string (cvm::output_prefix+"."+ + this->name+".runave.traj")); + + size_t const this_cv_width = x.output_width (cvm::cv_width); + runave_os.open (runave_outfile.c_str()); + 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"; + } + + acf_length = 0; + bool b_acf = false; + if (get_keyval (conf, "corrFunc", b_acf) && b_acf) { + + enable (task_corrfunc); + + std::string acf_colvar_name; + 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 (task_fdiff_velocity); + if (acf_colvar_name.size()) + (cvm::colvar_p (acf_colvar_name))->enable (task_fdiff_velocity); + } else if (acf_type_str == to_lower_cppstr (std::string ("coordinate_p2"))) { + acf_type = acf_p2coor; + } else { + cvm::fatal_error ("Unknown type of correlation function, \""+ + acf_type_str+"\".\n"); + } + + 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::fatal_error ("Error: corrFuncStride must be commensurate with the restart frequency.\n"); + + get_keyval (conf, "corrFuncNormalize", acf_normalize, true); + get_keyval (conf, "corrFuncOutputFile", acf_outfile, + std::string (cvm::output_prefix+"."+this->name+ + ".corrfunc.dat")); + } +} + + +void colvar::enable (colvar::task const &t) +{ + switch (t) { + + case task_output_system_force: + enable (task_system_force); + break; + + case task_report_Jacobian_force: + enable (task_Jacobian_force); + enable (task_system_force); + if (cvm::debug()) + cvm::log ("Adding the Jacobian force to the system force, " + "rather than applying its opposite silently.\n"); + break; + + case task_Jacobian_force: + // checks below do not apply to extended-system colvars + if ( !tasks[task_extended_lagrangian] ) { + enable (task_gradients); + + if (!b_Jacobian_force) + cvm::fatal_error ("Error: colvar \""+this->name+ + "\" does not have Jacobian forces implemented.\n"); + if (!b_linear) + cvm::fatal_error ("Error: colvar \""+this->name+ + "\" must be defined as a linear combination " + "to calculate the Jacobian force.\n"); + if (cvm::debug()) + cvm::log ("Enabling calculation of the Jacobian force " + "on this colvar.\n"); + } + fj.type (this->type()); + break; + + case task_system_force: + if (!tasks[task_extended_lagrangian]) { + if (!b_inverse_gradients) { + cvm::fatal_error ("Error: one or more of the components of " + "colvar \""+this->name+ + "\" does not support system force calculation.\n"); + } + cvm::request_system_force(); + } + ft.type (this->type()); + ft_reported.type (this->type()); + break; + + case task_output_applied_force: + case task_lower_wall: + case task_upper_wall: + // all of the above require gradients + enable (task_gradients); + break; + + case task_fdiff_velocity: + x_old.type (this->type()); + v_fdiff.type (this->type()); + v_reported.type (this->type()); + break; + + case task_output_velocity: + enable (task_fdiff_velocity); + break; + + case task_grid: + if (this->type() != colvarvalue::type_scalar) + cvm::fatal_error ("Cannot calculate a grid for collective variable, \""+ + this->name+"\", because its value is not a scalar number.\n"); + break; + + case task_extended_lagrangian: + enable (task_gradients); + v_reported.type (this->type()); + break; + + case task_lower_boundary: + case task_upper_boundary: + if (this->type() != colvarvalue::type_scalar) { + cvm::fatal_error ("Error: this colvar is not a scalar value " + "and cannot produce a grid.\n"); + } + break; + + + case task_output_value: + case task_runave: + case task_corrfunc: + case task_ntot: + break; + + case task_gradients: + f.type (this->type()); + fb.type (this->type()); + break; + + case task_collect_gradients: + if (this->type() != colvarvalue::type_scalar) + cvm::fatal_error ("Collecting atomic gradients for non-scalar collective variable \""+ + this->name+"\" is not yet implemented.\n"); + enable (task_gradients); + if (atom_ids.size() == 0) { + build_atom_list(); + } + break; + } + + + tasks[t] = true; +} + + +void colvar::disable (colvar::task const &t) +{ + // check dependencies + switch (t) { + case task_gradients: + disable (task_upper_wall); + disable (task_lower_wall); + disable (task_output_applied_force); + disable (task_system_force); + disable (task_Jacobian_force); + break; + + case task_system_force: + disable (task_output_system_force); + break; + + case task_Jacobian_force: + disable (task_report_Jacobian_force); + break; + + case task_fdiff_velocity: + disable (task_output_velocity); + break; + + case task_lower_boundary: + case task_upper_boundary: + disable (task_grid); + break; + + case task_extended_lagrangian: + case task_report_Jacobian_force: + case task_output_value: + case task_output_velocity: + case task_output_applied_force: + case task_output_system_force: + case task_runave: + case task_corrfunc: + case task_grid: + case task_lower_wall: + case task_upper_wall: + case task_ntot: + break; + } + + tasks[t] = false; +} + + +colvar::~colvar() +{ + if (cvm::b_analysis) { + + if (acf.size()) { + cvm::log ("Writing acf to file \""+acf_outfile+"\".\n"); + + std::ofstream acf_os (acf_outfile.c_str()); + if (! acf_os.good()) + cvm::fatal_error ("Cannot open file \""+acf_outfile+"\".\n"); + write_acf (acf_os); + acf_os.close(); + } + + if (runave_os.good()) { + runave_os.close(); + } + } + + for (size_t i = 0; i < cvcs.size(); i++) { + delete cvcs[i]; + } +} + + + +// ******************** CALC FUNCTIONS ******************** + + +void colvar::calc() +{ + if (cvm::debug()) + cvm::log ("Calculating colvar \""+this->name+"\".\n"); + + // calculate the value of the colvar + + x.reset(); + if (x.type() == colvarvalue::type_scalar) { + + // scalar variable, polynomial combination allowed + for (size_t i = 0; i < cvcs.size(); i++) { + cvm::increase_depth(); + (cvcs[i])->calc_value(); + cvm::decrease_depth(); + 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"); + x += (cvcs[i])->sup_coeff * + ( ((cvcs[i])->sup_np != 1) ? + std::pow ((cvcs[i])->value().real_value, (cvcs[i])->sup_np) : + (cvcs[i])->value().real_value ); + } + } else { + + for (size_t i = 0; i < cvcs.size(); i++) { + cvm::increase_depth(); + (cvcs[i])->calc_value(); + cvm::decrease_depth(); + 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"); + 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 (tasks[task_gradients]) { + // calculate the gradients + for (size_t i = 0; i < cvcs.size(); i++) { + cvm::increase_depth(); + (cvcs[i])->calc_gradients(); + cvm::decrease_depth(); + } + if (tasks[task_collect_gradients]) { + // Collect the atomic gradients inside colvar object + for (int a = 0; a < atomic_gradients.size(); a++) { + atomic_gradients[a].reset(); + } + for (size_t i = 0; i < cvcs.size(); i++) { + // Coefficient: d(a * x^n) = a * n * x^(n-1) * dx + cvm::real coeff = (cvcs[i])->sup_coeff * cvm::real ((cvcs[i])->sup_np) * + std::pow ((cvcs[i])->value().real_value, (cvcs[i])->sup_np-1); + + for (size_t j = 0; j < cvcs[i]->atom_groups.size(); j++) { + + // If necessary, apply inverse rotation to get atomic + // gradient in the laboratory frame + if (cvcs[i]->atom_groups[j]->b_rotate) { + cvm::rotation const rot_inv = cvcs[i]->atom_groups[j]->rot.inverse(); + + for (size_t k = 0; k < cvcs[i]->atom_groups[j]->size(); k++) { + int a = std::lower_bound (atom_ids.begin(), atom_ids.end(), + cvcs[i]->atom_groups[j]->at(k).id) - atom_ids.begin(); + atomic_gradients[a] += coeff * + rot_inv.rotate (cvcs[i]->atom_groups[j]->at(k).grad); + } + + } else { + + for (size_t k = 0; k < cvcs[i]->atom_groups[j]->size(); k++) { + int a = std::lower_bound (atom_ids.begin(), atom_ids.end(), + cvcs[i]->atom_groups[j]->at(k).id) - atom_ids.begin(); + atomic_gradients[a] += coeff * cvcs[i]->atom_groups[j]->at(k).grad; + } + } + } + } + } + } + + if (tasks[task_system_force]) { + ft.reset(); + + if(!tasks[task_extended_lagrangian] && (cvm::step_relative() > 0)) { + // get from the cvcs the system forces from the PREVIOUS step + for (size_t i = 0; i < cvcs.size(); i++) { + (cvcs[i])->calc_force_invgrads(); + // linear combination is assumed + cvm::increase_depth(); + ft += (cvcs[i])->system_force() / ((cvcs[i])->sup_coeff * cvm::real (cvcs.size())); + cvm::decrease_depth(); + } + } + + if (tasks[task_report_Jacobian_force]) { + // add the Jacobian force to the system force, and don't apply any silent + // correction internally: biases such as colvarbias_abf will handle it + ft += fj; + } + } + + if (tasks[task_fdiff_velocity]) { + // calculate the velocity by finite differences + if (cvm::step_relative() == 0) + x_old = x; + else { + v_fdiff = fdiff_velocity (x_old, x); + v_reported = v_fdiff; + } + } + + if (tasks[task_extended_lagrangian]) { + + // initialize the restraint center in the first step to the value + // just calculated from the cvcs + // TODO: put it in the restart information + if (cvm::step_relative() == 0) { + xr = x; + vr = 0.0; // (already 0; added for clarity) + } + + // report the restraint center as "value" + x_reported = xr; + v_reported = vr; + // the "system force" with the extended Lagrangian is just the + // harmonic term acting on the extended coordinate + // Note: this is the force for current timestep + ft_reported = (-0.5 * ext_force_k) * this->dist2_lgrad (xr, x); + + } else { + + x_reported = x; + ft_reported = ft; + } + + if (cvm::debug()) + cvm::log ("Done calculating colvar \""+this->name+"\".\n"); +} + + +cvm::real colvar::update() +{ + if (cvm::debug()) + cvm::log ("Updating colvar \""+this->name+"\".\n"); + + + // set to zero the applied force + f.reset(); + + // add the biases' force, which at this point should already have + // been summed over each bias using this colvar + f += fb; + + + if (tasks[task_lower_wall] || tasks[task_upper_wall]) { + + // wall force + colvarvalue fw (this->type()); + + // if the two walls are applied concurrently, decide which is the + // closer one (on a periodic colvar, both walls may be applicable + // at the same time) + if ( (!tasks[task_upper_wall]) || + (this->dist2 (x, lower_wall) < this->dist2 (x, upper_wall)) ) { + + cvm::real const grad = this->dist2_lgrad (x, lower_wall); + if (grad < 0.0) { + fw = -0.5 * lower_wall_k * grad; + if (cvm::debug()) + cvm::log ("Applying a lower wall force ("+ + cvm::to_str (fw)+") to \""+this->name+"\".\n"); + f += fw; + + } + + } else { + + cvm::real const grad = this->dist2_lgrad (x, upper_wall); + if (grad > 0.0) { + fw = -0.5 * upper_wall_k * grad; + if (cvm::debug()) + cvm::log ("Applying an upper wall force ("+ + cvm::to_str (fw)+") to \""+this->name+"\".\n"); + f += fw; + } + } + } + + if (tasks[task_Jacobian_force]) { + + cvm::increase_depth(); + for (size_t i = 0; i < cvcs.size(); i++) { + (cvcs[i])->calc_Jacobian_derivative(); + } + cvm::decrease_depth(); + + fj.reset(); + for (size_t i = 0; i < cvcs.size(); i++) { + // linear combination is assumed + fj += 1.0 / ( cvm::real (cvcs.size()) * cvm::real ((cvcs[i])->sup_coeff) ) * + (cvcs[i])->Jacobian_derivative(); + } + fj *= cvm::boltzmann() * cvm::temperature(); + + // the instantaneous Jacobian force was not included in the reported system force; + // instead, it is subtracted from the applied force (silent Jacobian correction) + if (! tasks[task_report_Jacobian_force]) + f -= fj; + } + + + if (tasks[task_extended_lagrangian]) { + + cvm::real dt = cvm::dt(); + + // the total force is applied to the fictitious mass, while the + // atoms only feel the harmonic force + // fr: extended coordinate force; f: colvar force applied to atomic coordinates + fr = f; + fr += (-0.5 * ext_force_k) * this->dist2_lgrad (xr, x); + f = (-0.5 * ext_force_k) * this->dist2_rgrad (xr, x); + + // leap frog: starting from x_i, f_i, v_(i-1/2) + vr += (0.5 * dt) * fr / ext_mass; + // Because of leapfrog, kinetic energy at time i is approximate + kinetic_energy = 0.5 * ext_mass * vr * vr; + potential_energy = 0.5 * ext_force_k * this->dist2(xr, x); + // leap to v_(i+1/2) + if (tasks[task_langevin]) { + vr -= dt * ext_gamma * vr.real_value; + vr += dt * ext_sigma * cvm::rand_gaussian() / ext_mass; + } + vr += (0.5 * dt) * fr / ext_mass; + xr += dt * vr; + xr.apply_constraints(); + if (this->b_periodic) this->wrap (xr); + } + + + if (tasks[task_fdiff_velocity]) { + // set it for the next step + x_old = x; + } + + if (cvm::debug()) + cvm::log ("Done updating colvar \""+this->name+"\".\n"); + return (potential_energy + kinetic_energy); +} + + +void colvar::communicate_forces() +{ + if (cvm::debug()) + cvm::log ("Communicating forces from colvar \""+this->name+"\".\n"); + + if (x.type() == colvarvalue::type_scalar) { + + for (size_t i = 0; i < cvcs.size(); i++) { + cvm::increase_depth(); + (cvcs[i])->apply_force (f * (cvcs[i])->sup_coeff * + cvm::real ((cvcs[i])->sup_np) * + (std::pow ((cvcs[i])->value().real_value, + (cvcs[i])->sup_np-1)) ); + cvm::decrease_depth(); + } + + } else { + + for (size_t i = 0; i < cvcs.size(); i++) { + cvm::increase_depth(); + (cvcs[i])->apply_force (f * (cvcs[i])->sup_coeff); + cvm::decrease_depth(); + } + } + + if (cvm::debug()) + cvm::log ("Done communicating forces from colvar \""+this->name+"\".\n"); +} + + + +// ******************** METRIC FUNCTIONS ******************** +// Use the metrics defined by \link cvc \endlink objects + + +bool colvar::periodic_boundaries (colvarvalue const &lb, colvarvalue const &ub) const +{ + if ( (!tasks[task_lower_boundary]) || (!tasks[task_upper_boundary]) ) { + cvm::fatal_error ("Error: requesting to histogram the " + "collective variable \""+this->name+"\", but a " + "pair of lower and upper boundaries must be " + "defined.\n"); + } + + if (period > 0.0) { + if ( ((std::sqrt (this->dist2 (lb, ub))) / this->width) + < 1.0E-10 ) { + return true; + } + } + + return false; +} + +bool colvar::periodic_boundaries() const +{ + if ( (!tasks[task_lower_boundary]) || (!tasks[task_upper_boundary]) ) { + cvm::fatal_error ("Error: requesting to histogram the " + "collective variable \""+this->name+"\", but a " + "pair of lower and upper boundaries must be " + "defined.\n"); + } + + return periodic_boundaries (lower_boundary, upper_boundary); +} + + +cvm::real colvar::dist2 (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (cvcs[0])->dist2 (x1, x2); +} + +colvarvalue colvar::dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (cvcs[0])->dist2_lgrad (x1, x2); +} + +colvarvalue colvar::dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (cvcs[0])->dist2_rgrad (x1, x2); +} + +cvm::real colvar::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (cvcs[0])->compare (x1, x2); +} + +void colvar::wrap (colvarvalue &x) const +{ + (cvcs[0])->wrap (x); + return; +} + + +// ******************** INPUT FUNCTIONS ******************** + +std::istream & colvar::read_restart (std::istream &is) +{ + size_t const start_pos = is.tellg(); + + std::string conf; + if ( !(is >> colvarparse::read_block ("colvar", conf)) ) { + // this is not a colvar block + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + { + std::string check_name = ""; + if ( (get_keyval (conf, "name", check_name, + std::string (""), colvarparse::parse_silent)) && + (check_name != name) ) { + cvm::fatal_error ("Error: the state file does not match the " + "configuration file, at colvar \""+name+"\".\n"); + } + if (check_name.size() == 0) { + cvm::fatal_error ("Error: Collective variable in the " + "restart file without any identifier.\n"); + } + } + + if ( !(get_keyval (conf, "x", x, + colvarvalue (x.type()), colvarparse::parse_silent)) ) { + cvm::log ("Error: restart file does not contain " + "the value of the colvar \""+ + name+"\" .\n"); + } else { + cvm::log ("Restarting collective variable \""+name+"\" from value: "+ + cvm::to_str (x)+"\n"); + } + + if (tasks[colvar::task_extended_lagrangian]) { + + if ( !(get_keyval (conf, "extended_x", xr, + colvarvalue (x.type()), colvarparse::parse_silent)) && + !(get_keyval (conf, "extended_v", vr, + colvarvalue (x.type()), colvarparse::parse_silent)) ) { + cvm::log ("Error: restart file does not contain " + "\"extended_x\" or \"extended_v\" for the colvar \""+ + name+"\", but you requested \"extendedLagrangian\".\n"); + } + } + + if (tasks[task_extended_lagrangian]) { + x_reported = xr; + } else { + x_reported = x; + } + + if (tasks[task_output_velocity]) { + + if ( !(get_keyval (conf, "v", v_fdiff, + colvarvalue (x.type()), colvarparse::parse_silent)) ) { + cvm::log ("Error: restart file does not contain " + "the velocity for the colvar \""+ + name+"\", but you requested \"outputVelocity\".\n"); + } + + if (tasks[task_extended_lagrangian]) { + v_reported = vr; + } else { + v_reported = v_fdiff; + } + } + + return is; +} + + +std::istream & colvar::read_traj (std::istream &is) +{ + size_t const start_pos = is.tellg(); + + if (tasks[task_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, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + if (tasks[task_extended_lagrangian]) { + is >> xr; + x_reported = xr; + } else { + x_reported = x; + } + } + + if (tasks[task_output_velocity]) { + + is >> v_fdiff; + + if (tasks[task_extended_lagrangian]) { + is >> vr; + v_reported = vr; + } else { + v_reported = v_fdiff; + } + } + + if (tasks[task_output_system_force]) { + + is >> ft; + + if (tasks[task_extended_lagrangian]) { + is >> fr; + ft_reported = fr; + } else { + ft_reported = ft; + } + } + + if (tasks[task_output_applied_force]) { + is >> f; + } + + return is; +} + + +// ******************** OUTPUT FUNCTIONS ******************** + +std::ostream & colvar::write_restart (std::ostream &os) { + + os << "colvar {\n" + << " name " << name << "\n" + << " x " + << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) + << x << "\n"; + + if (tasks[task_output_velocity]) { + os << " v " + << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) + << v_reported << "\n"; + } + + if (tasks[task_extended_lagrangian]) { + os << " extended_x " + << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) + << xr << "\n" + << " extended_v " + << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) + << vr << "\n"; + } + + os << "}\n\n"; + + 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 (tasks[task_output_value]) { + + os << " " + << cvm::wrap_string (this->name, this_cv_width); + + if (tasks[task_extended_lagrangian]) { + // restraint center + os << " r_" + << cvm::wrap_string (this->name, this_cv_width-2); + } + } + + if (tasks[task_output_velocity]) { + + os << " v_" + << cvm::wrap_string (this->name, this_cv_width-2); + + if (tasks[task_extended_lagrangian]) { + // restraint center + os << " vr_" + << cvm::wrap_string (this->name, this_cv_width-3); + } + } + + if (tasks[task_output_energy]) { + os << " Ep_" + << cvm::wrap_string (this->name, this_cv_width-3) + << " Ek_" + << cvm::wrap_string (this->name, this_cv_width-3); + } + + if (tasks[task_output_system_force]) { + + os << " fs_" + << cvm::wrap_string (this->name, this_cv_width-2); + + if (tasks[task_extended_lagrangian]) { + // restraint center + os << " fr_" + << cvm::wrap_string (this->name, this_cv_width-3); + } + } + + if (tasks[task_output_applied_force]) { + os << " fa_" + << cvm::wrap_string (this->name, this_cv_width-2); + } + + return os; +} + + +std::ostream & colvar::write_traj (std::ostream &os) +{ + os << " "; + + if (tasks[task_output_value]) { + + if (tasks[task_extended_lagrangian]) { + 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 (tasks[task_output_velocity]) { + + if (tasks[task_extended_lagrangian]) { + 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 (tasks[task_output_energy]) { + os << " " + << std::setprecision (cvm::cv_prec) << std::setw (cvm::cv_width) + << potential_energy + << " " + << kinetic_energy; + } + + + if (tasks[task_output_system_force]) { + + if (tasks[task_extended_lagrangian]) { + os << " " + << std::setprecision (cvm::cv_prec) << std::setw (cvm::cv_width) + << ft; + } + + os << " " + << std::setprecision (cvm::cv_prec) << std::setw (cvm::cv_width) + << ft_reported; + } + + if (tasks[task_output_applied_force]) { + os << " " + << std::setprecision (cvm::cv_prec) << std::setw (cvm::cv_width) + << f; + } + + return os; +} + + + +// ******************** ANALYSIS FUNCTIONS ******************** + +void colvar::analyse() +{ + if (tasks[task_runave]) { + calc_runave(); + } + + if (tasks[task_corrfunc]) { + calc_acf(); + } +} + + +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(); +} + + +void 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 + + if (! (acf_x_history.size() || acf_v_history.size()) ) { + + // first-step operations + + colvar *cfcv = (acf_colvar_name.size() ? + cvm::colvar_p (acf_colvar_name) : + this); + if (cfcv->type() != this->type()) + cvm::fatal_error ("Error: correlation function between \""+cfcv->name+ + "\" and \""+this->name+"\" cannot be calculated, " + "because their value types are different.\n"); + acf_nframes = 0; + + cvm::log ("Colvar \""+this->name+"\": initializing ACF calculation.\n"); + + if (acf.size() < acf_length+1) + acf.resize (acf_length+1, 0.0); + + switch (acf_type) { + + case acf_vel: + // allocate space for the velocities history + for (size_t 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 (size_t i = 0; i < acf_stride; i++) { + acf_x_history.push_back (std::list()); + } + acf_x_history_p = acf_x_history.begin(); + break; + + default: + break; + } + + } else { + + colvar *cfcv = (acf_colvar_name.size() ? + cvm::colvar_p (acf_colvar_name) : + this); + + switch (acf_type) { + + case acf_vel: + + if (tasks[task_fdiff_velocity]) { + // calc() should do this already, but this only happens in a + // simulation; better do it again in case a trajectory is + // being read + v_reported = v_fdiff = fdiff_velocity (x_old, cfcv->value()); + } + + calc_vel_acf ((*acf_v_history_p), cfcv->velocity()); + // store this value in the history + history_add_value (acf_length+acf_offset, *acf_v_history_p, cfcv->velocity()); + // if stride is larger than one, cycle among different histories + 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; + + default: + break; + } + } + + if (tasks[task_fdiff_velocity]) { + // set it for the next step + x_old = x; + } +} + + +void colvar::calc_vel_acf (std::list &v_list, + colvarvalue const &v) +{ + // loop over stored velocities and add to the ACF, but only 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(); + + // 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) +{ + // 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, xs_i, x_list.end(), acf_i); + + acf_nframes++; + } +} + + +void colvar::calc_p2coor_acf (std::list &x_list, + colvarvalue const &x) +{ + // 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, xs_i, x_list.end(), acf_i); + + acf_nframes++; + } +} + + +void colvar::write_acf (std::ostream &os) +{ + if (!acf_nframes) + cvm::log ("Warning: ACF was not calculated (insufficient frames).\n"); + os.setf (std::ios::scientific, std::ios::floatfield); + os << "# Autocorrelation function for collective variable \"" + << this->name << "\"\n"; + // one frame is used for normalization, the statistical sample is + // hence decreased + os << "# nframes = " << (acf_normalize ? + acf_nframes - 1 : + acf_nframes) << "\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"; + } +} + + +void colvar::calc_runave() +{ + if (!x_history.size()) { + + runave.type (x.type()); + runave.reset(); + + // first-step operations + + 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) { + + if ((*x_history_p).size() >= runave_length-1) { + + runave = x; + for (std::list::iterator 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 (std::list::iterator 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); + + 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) + << std::sqrt (runave_variance) << "\n"; + } + + history_add_value (runave_length, *x_history_p, x); + } + } + +} + + diff --git a/lib/colvars/colvar.h b/lib/colvars/colvar.h new file mode 100644 index 0000000000..4f4c575358 --- /dev/null +++ b/lib/colvars/colvar.h @@ -0,0 +1,561 @@ +// -*- c++ -*- + +#ifndef COLVAR_H +#define COLVAR_H + +#include +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" + + +/// \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::x by +/// using the colvarvalue::type() member function 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: + + /// Name + std::string name; + + /// Type of value + colvarvalue::Type type() const; + + /// \brief Current value (previously obtained from calc() or read_traj()) + colvarvalue const & value() const; + + /// \brief Current velocity (previously obtained from calc() or read_traj()) + colvarvalue const & velocity() const; + + /// \brief Current system 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 & system_force() const; + + /// \brief + + /// \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; + + /// \brief True if this \link colvar \endlink is a linear + /// combination of \link cvc \endlink elements + bool b_linear; + + /// \brief True if all \link cvc \endlink objects are capable + /// of calculating inverse gradients + bool b_inverse_gradients; + + /// \brief True if all \link cvc \endlink objects are capable + /// of calculating Jacobian forces + bool b_Jacobian_force; + + /// \brief Options controlling the behaviour of the colvar during + /// the simulation, which are set from outside the cvcs + enum task { + /// \brief Gradients are calculated and temporarily stored, so + /// that external forces can be applied + task_gradients, + /// \brief Collect atomic gradient data from all cvcs into vector + /// atomic_gradients + task_collect_gradients, + /// \brief Calculate the velocity with finite differences + task_fdiff_velocity, + /// \brief The system force is calculated, projecting the atomic + /// forces on the inverse gradients + task_system_force, + /// \brief The variable has a harmonic restraint around a moving + /// center with fictitious mass; bias forces will be applied to + /// the center + task_extended_lagrangian, + /// \brief The extended system coordinate undergoes Langevin + /// dynamics + task_langevin, + /// \brief Output the potential and kinetic energies + /// (for extended Lagrangian colvars only) + task_output_energy, + /// \brief Compute analytically the "force" arising from the + /// geometric entropy component (for example, that from the angular + /// states orthogonal to a distance vector) + task_Jacobian_force, + /// \brief Report the Jacobian force as part of the system force + /// if disabled, apply a correction internally to cancel it + task_report_Jacobian_force, + /// \brief Output the value to the trajectory file (on by default) + task_output_value, + /// \brief Output the velocity to the trajectory file + task_output_velocity, + /// \brief Output the applied force to the trajectory file + task_output_applied_force, + /// \brief Output the system force to the trajectory file + task_output_system_force, + /// \brief A lower boundary is defined + task_lower_boundary, + /// \brief An upper boundary is defined + task_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) + task_grid, + /// \brief Apply a restraining potential (|x-xb|^2) to the colvar + /// when it goes below the lower wall + task_lower_wall, + /// \brief Apply a restraining potential (|x-xb|^2) to the colvar + /// when it goes above the upper wall + task_upper_wall, + /// \brief Compute running average + task_runave, + /// \brief Compute time correlation function + task_corrfunc, + /// \brief Number of possible tasks + task_ntot + }; + + /// Tasks performed by this colvar + bool tasks[task_ntot]; + +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 task_extended_lagrangian + /// Restraint center + colvarvalue xr; + /// Velocity of the restraint center + colvarvalue vr; + /// Mass of the restraint center + cvm::real ext_mass; + /// Restraint force constant + cvm::real ext_force_k; + /// Friction coefficient for Langevin extended dynamics + cvm::real ext_gamma; + /// Amplitude of Gaussian white noise for Langevin extended dynamics + cvm::real ext_sigma; + + /// \brief Harmonic restraint force + colvarvalue fr; + + /// \brief Jacobian force, when task_Jacobian_force is enabled + colvarvalue fj; + + /// Cached reported system force + colvarvalue ft_reported; + +public: + + + /// \brief Bias force; reset_bias_force() should be called before + /// the biases are updated + colvarvalue fb; + + /// \brief Total \em applied force; fr (if task_extended_lagrangian + /// is defined), fb (if biases are applied) and the walls' forces + /// (if defined) contribute to it + colvarvalue f; + + /// \brief Total force, as derived from the atomic trajectory; + /// should equal the total system force plus \link f \endlink + colvarvalue ft; + + + /// Period, if it is a constant + cvm::real period; + + /// \brief Same as above, but also takes into account components + /// with a variable period, such as distanceZ + bool b_periodic; + + + /// \brief Expand the boundaries of multiples of width, to keep the + /// value always within range + bool expand_boundaries; + + /// \brief Location of the lower boundary + colvarvalue lower_boundary; + /// \brief Location of the lower wall + colvarvalue lower_wall; + /// \brief Force constant for the lower boundary potential (|x-xb|^2) + cvm::real lower_wall_k; + + /// \brief Location of the upper boundary + colvarvalue upper_boundary; + /// \brief Location of the upper wall + colvarvalue upper_wall; + /// \brief Force constant for the upper boundary potential (|x-xb|^2) + cvm::real upper_wall_k; + + /// \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 (std::string const &conf); + + /// Enable the specified task + void enable (colvar::task const &t); + + /// Disable the specified task + void disable (colvar::task const &t); + + /// Destructor + ~colvar(); + + + /// \brief Calculate the colvar value and all the other requested + /// quantities + void calc(); + + /// \brief Calculate just the value, and store it in the argument + void calc_value (colvarvalue &xn); + + /// 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); + + /// \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(); + + /// \brief Communicate forces (previously calculated in + /// colvar::update()) to the external degrees of freedom + void communicate_forces(); + + + /// \brief Use the internal metrics (as from \link 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 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 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 cvc + /// \endlink objects) to compare colvar values + /// + /// Handles correctly symmetries and periodic boundary conditions + cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Use the internal metrics (as from \link cvc + /// \endlink objects) to wrap a value into a standard interval + /// + /// Handles correctly symmetries and periodic boundary conditions + void wrap (colvarvalue &x) const; + + + /// Read the analysis tasks + void parse_analysis (std::string const &conf); + /// Perform analysis tasks + void analyse(); + + + /// 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 collective variable from a restart file + std::istream & read_restart (std::istream &is); + /// Write the collective variable to a restart file + std::ostream & write_restart (std::ostream &os); + + +protected: + + /// Previous value (to calculate velocities during analysis) + colvarvalue x_old; + + /// 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) + void calc_acf(); + /// Save the ACF to a file + void 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::ofstream runave_os; + /// Current value of the running average + colvarvalue runave; + /// Current value of the square deviation from the running average + cvm::real runave_variance; + + /// Calculate the running average and its standard deviation + void calc_runave(); + + /// If extended Lagrangian active: colvar energies (kinetic and harmonic potential) + cvm::real kinetic_energy; + cvm::real potential_energy; +public: + + + // collective variable component base class + class cvc; + + // currently available collective variable components + + // scalar colvar components + class distance; + class distance_z; + class distance_xy; + class min_distance; + class angle; + class dihedral; + class coordnum; + class selfcoordnum; + class h_bond; + class rmsd; + class logmsd; + class orientation_angle; + class tilt; + class spin_angle; + class gyration; + class eigenvector; + class alpha_dihedrals; + class alpha_angles; + class dihedPC; + + // non-scalar components + class distance_vec; + class distance_dir; + class orientation; + +protected: + + /// \brief Array of \link cvc \endlink objects + std::vector cvcs; + + /// \brief Initialize the sorted list of atom IDs for atoms involved + /// in all cvcs (called when enabling task_collect_gradients) + void build_atom_list (void); + +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; + + inline size_t n_components () const { + return cvcs.size(); + } +}; + + +inline colvar * cvm::colvar_p (std::string const &name) +{ + for (std::vector::iterator cvi = cvm::colvars.begin(); + cvi != cvm::colvars.end(); + cvi++) { + if ((*cvi)->name == name) { + return (*cvi); + } + } + return NULL; +} + + +inline colvarvalue::Type colvar::type() const +{ + return x.type(); +} + + +inline colvarvalue const & colvar::value() const +{ + return x_reported; +} + + +inline colvarvalue const & colvar::velocity() const +{ + return v_reported; +} + + +inline colvarvalue const & colvar::system_force() const +{ + return ft_reported; +} + + +inline void colvar::add_bias_force (colvarvalue const &force) +{ + fb += force; +} + + +inline void colvar::reset_bias_force() { + fb.reset(); +} + +#endif + diff --git a/lib/colvars/colvaratoms.cpp b/lib/colvars/colvaratoms.cpp new file mode 100644 index 0000000000..17107b40f6 --- /dev/null +++ b/lib/colvars/colvaratoms.cpp @@ -0,0 +1,761 @@ +#include "colvarmodule.h" +#include "colvarparse.h" +#include "colvaratoms.h" + + +// atom member functions depend tightly on the MD interface, and are +// thus defined in colvarproxy_xxx.cpp + + +// atom_group member functions + +cvm::atom_group::atom_group (std::string const &conf, + char const *key, + atom_group *ref_pos_group_in) + : b_center (false), b_rotate (false), + ref_pos_group (NULL), // this is always set within parse(), + // regardless of ref_pos_group_in + noforce (false) +{ + cvm::log ("Defining atom group \""+ + std::string (key)+"\".\n"); + parse (conf, key, ref_pos_group_in); +} + + +void cvm::atom_group::parse (std::string const &conf, + char const *key, + atom_group *ref_pos_group_in) +{ + std::string group_conf; + + // save_delimiters is set to false for this call, because "conf" is + // not the config string of this group, but of its parent object + // (which has already taken care of the delimiters) + save_delimiters = false; + key_lookup (conf, key, group_conf, dummy_pos); + // restoring the normal value, because we do want keywords checked + // inside "group_conf" + save_delimiters = true; + + if (group_conf.size() == 0) { + cvm::fatal_error ("Error: atom group \""+ + std::string (key)+"\" is set, but " + "has no definition.\n"); + } + + cvm::increase_depth(); + + cvm::log ("Initializing atom group \""+std::string (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; + } + + { + // get the atoms by numbers + std::vector atom_indexes; + if (get_keyval (group_conf, "atomNumbers", atom_indexes, atom_indexes, mode)) { + if (atom_indexes.size()) { + this->reserve (this->size()+atom_indexes.size()); + for (size_t i = 0; i < atom_indexes.size(); i++) { + this->push_back (cvm::atom (atom_indexes[i])); + } + } else + cvm::fatal_error ("Error: no numbers provided for \"" + "atomNumbers\".\n"); + } + } + + { + std::string range_conf = ""; + size_t pos = 0; + while (key_lookup (group_conf, "atomNumbersRange", + range_conf, pos)) { + + 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) ) { + for (int anum = initial; anum <= final; anum++) { + this->push_back (cvm::atom (anum)); + } + range_conf = ""; + continue; + } + } + + cvm::fatal_error ("Error: no valid definition for \"" + "atomNumbersRange\", \""+ + range_conf+"\".\n"); + } + } + + std::vector psf_segids; + get_keyval (group_conf, "psfSegID", psf_segids, std::vector (), mode); + for (std::vector::iterator psii = psf_segids.begin(); + psii < psf_segids.end(); psii++) { + + if ( (psii->size() == 0) || (psii->size() > 4) ) { + cvm::fatal_error ("Error: invalid segmend identifier provided, \""+ + (*psii)+"\".\n"); + } + } + + { + std::string range_conf = ""; + size_t pos = 0; + size_t range_count = 0; + std::vector::iterator psii = psf_segids.begin(); + while (key_lookup (group_conf, "atomNameResidueRange", + range_conf, pos)) { + + if (psii > psf_segids.end()) { + cvm::fatal_error ("Error: more instances of \"atomNameResidueRange\" than " + "values of \"psfSegID\".\n"); + } + + std::string const &psf_segid = psf_segids.size() ? *psii : std::string (""); + + 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) ) { + for (int resid = initial; resid <= final; resid++) { + this->push_back (cvm::atom (resid, atom_name, psf_segid)); + } + range_conf = ""; + } else { + cvm::fatal_error ("Error: cannot parse definition for \"" + "atomNameResidueRange\", \""+ + range_conf+"\".\n"); + } + + } else { + cvm::fatal_error ("Error: atomNameResidueRange with empty definition.\n"); + } + + if (psf_segid.size()) + psii++; + } + } + + { + // read the atoms from a file + std::string atoms_file_name; + if (get_keyval (group_conf, "atomsFile", atoms_file_name, std::string (""), mode)) { + + std::string atoms_col; + if (!get_keyval (group_conf, "atomsCol", atoms_col, std::string (""), mode)) { + cvm::fatal_error ("Error: parameter atomsCol is required if atomsFile is set.\n"); + } + + double atoms_col_value; + bool const atoms_col_value_defined = get_keyval (group_conf, "atomsColValue", atoms_col_value, 0.0, mode); + if (atoms_col_value_defined && (!atoms_col_value)) + cvm::fatal_error ("Error: atomsColValue, " + "if provided, must be non-zero.\n"); + + cvm::load_atoms (atoms_file_name.c_str(), *this, atoms_col, atoms_col_value); + } + } + + for (std::vector::iterator a1 = this->begin(); + a1 != this->end(); a1++) { + std::vector::iterator a2 = a1; + ++a2; + for ( ; a2 != this->end(); a2++) { + if (a1->id == a2->id) { + if (cvm::debug()) + cvm::log ("Discarding doubly counted atom with number "+ + cvm::to_str (a1->id+1)+".\n"); + a2 = this->erase (a2); + if (a2 == this->end()) + break; + } + } + } + + if (get_keyval (group_conf, "dummyAtom", dummy_atom_pos, cvm::atom_pos(), mode)) { + b_dummy = true; + this->total_mass = 1.0; + } else + b_dummy = false; + + if (b_dummy && (this->size())) + cvm::fatal_error ("Error: cannot set up group \""+ + std::string (key)+"\" as a dummy atom " + "and provide it with atom definitions.\n"); + +#if (! defined (COLVARS_STANDALONE)) + if ( (!b_dummy) && (!cvm::b_analysis) && (!(this->size())) ) { + cvm::fatal_error ("Error: no atoms defined for atom group \""+ + std::string (key)+"\".\n"); + } +#endif + + if (!b_dummy) { + this->total_mass = 0.0; + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + this->total_mass += ai->mass; + } + } + + get_keyval (group_conf, "disableForces", noforce, false, mode); + + get_keyval (group_conf, "centerReference", b_center, false, mode); + get_keyval (group_conf, "rotateReference", b_rotate, false, mode); + + if (b_center || b_rotate) { + + if (b_dummy) + cvm::fatal_error ("Error: cannot set \"centerReference\" or " + "\"rotateReference\" with \"dummyAtom\".\n"); + + // use refPositionsGroup instead of this group as the one which is + // used to fit the coordinates + if (key_lookup (group_conf, "refPositionsGroup")) { + if (ref_pos_group) { + cvm::fatal_error ("Error: the atom group \""+ + std::string (key)+"\" has already a reference group " + "for the rototranslational fit, which was communicated by the " + "colvar component. You should not use refPositionsGroup " + "in this case.\n"); + } + cvm::log ("Within atom group \""+std::string (key)+"\":\n"); + ref_pos_group = new atom_group (group_conf, "refPositionsGroup"); + } + + atom_group *ag = ref_pos_group ? ref_pos_group : this; + + if (get_keyval (group_conf, "refPositions", ref_pos, ref_pos, mode)) { + cvm::log ("Using reference positions from input file.\n"); + if (ref_pos.size() != ag->size()) { + cvm::fatal_error ("Error: the number of reference positions provided ("+ + cvm::to_str (ref_pos.size())+ + ") does not match the number of atoms within \""+ + std::string (key)+ + "\" ("+cvm::to_str (ag->size())+").\n"); + } + } + + std::string ref_pos_file; + if (get_keyval (group_conf, "refPositionsFile", ref_pos_file, std::string (""), mode)) { + + if (ref_pos.size()) { + cvm::fatal_error ("Error: cannot specify \"refPositionsFile\" and " + "\"refPositions\" at the same time.\n"); + } + + std::string ref_pos_col; + double ref_pos_col_value; + + if (get_keyval (group_conf, "refPositionsCol", ref_pos_col, std::string (""), mode)) { + // if provided, use PDB column to select coordinates + bool found = get_keyval (group_conf, "refPositionsColValue", ref_pos_col_value, 0.0, mode); + if (found && !ref_pos_col_value) + cvm::fatal_error ("Error: refPositionsColValue, " + "if provided, must be non-zero.\n"); + } else { + // if not, rely on existing atom indices for the group + ag->create_sorted_ids(); + } + cvm::load_coords (ref_pos_file.c_str(), ref_pos, ag->sorted_ids, + ref_pos_col, ref_pos_col_value); + } + + if (ref_pos.size()) { + + if (b_rotate) { + if (ref_pos.size() != ag->size()) + cvm::fatal_error ("Error: the number of reference positions provided ("+ + cvm::to_str (ref_pos.size())+ + ") does not match the number of atoms within \""+ + std::string (key)+ + "\" ("+cvm::to_str (ag->size())+").\n"); + } + + // save the center of mass of ref_pos and then subtract it from + // them; in this way it is possible to use the coordinates for + // the rotational fit, if needed + ref_pos_cog = cvm::atom_pos (0.0, 0.0, 0.0); + std::vector::iterator pi = ref_pos.begin(); + for ( ; pi != ref_pos.end(); pi++) { + ref_pos_cog += *pi; + } + ref_pos_cog /= (cvm::real) ref_pos.size(); + + for (std::vector::iterator pi = ref_pos.begin(); + pi != ref_pos.end(); pi++) { + (*pi) -= ref_pos_cog; + } + } else { +#if (! defined (COLVARS_STANDALONE)) + if (!cvm::b_analysis) + cvm::fatal_error ("Error: no reference positions provided.\n"); +#endif + } + + if (b_rotate && !noforce) { + cvm::log ("Warning: atom group \""+std::string (key)+ + "\" is set to be rotated to a reference orientation: " + "a torque different than zero on this group " + "could make the simulation unstable. " + "If this happens, set \"disableForces\" to yes " + "for this group.\n"); + } + + } + + + if (cvm::debug()) + cvm::log ("Done initializing atom group with name \""+ + std::string (key)+"\".\n"); + + this->check_keywords (group_conf, key); + + cvm::log ("Atom group \""+std::string (key)+"\" defined, "+ + cvm::to_str (this->size())+" initialized: total mass = "+ + cvm::to_str (this->total_mass)+".\n"); + + cvm::decrease_depth(); +} + + +cvm::atom_group::atom_group (std::vector const &atoms) + : b_dummy (false), b_center (false), b_rotate (false), + ref_pos_group (NULL), noforce (false) +{ + this->reserve (atoms.size()); + for (size_t i = 0; i < atoms.size(); i++) { + this->push_back (atoms[i]); + } + total_mass = 0.0; + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + total_mass += ai->mass; + } +} + + +cvm::atom_group::atom_group() + : b_dummy (false), b_center (false), b_rotate (false), + ref_pos_group (NULL), noforce (false) +{ + total_mass = 0.0; +} + + +cvm::atom_group::~atom_group() +{ + if (ref_pos_group) { + delete ref_pos_group; + ref_pos_group = NULL; + } +} + + +void cvm::atom_group::add_atom (cvm::atom const &a) +{ + if (b_dummy) { + cvm::fatal_error ("Error: cannot add atoms to a dummy group.\n"); + } else { + this->push_back (a); + total_mass += a.mass; + } +} + + +void cvm::atom_group::create_sorted_ids (void) +{ + // Only do the work if the vector is not yet populated + if (sorted_ids.size()) + return; + + std::list temp_id_list; + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) { + temp_id_list.push_back (ai->id); + } + temp_id_list.sort(); + temp_id_list.unique(); + if (temp_id_list.size() != this->size()) { + cvm::fatal_error ("Error: duplicate atom IDs in atom group? (found " + + cvm::to_str(temp_id_list.size()) + + " unique atom IDs instead of" + + cvm::to_str(this->size()) + ").\n"); + } + sorted_ids = std::vector (temp_id_list.begin(), temp_id_list.end()); + return; +} + + +void cvm::atom_group::read_positions() +{ + if (b_dummy) return; + +#if (! defined (COLVARS_STANDALONE)) + if (!this->size()) + cvm::fatal_error ("Error: no atoms defined in the requested group.\n"); +#endif + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->read_position(); + } + + if (ref_pos_group) + ref_pos_group->read_positions(); + + atom_group *fit_group = ref_pos_group ? ref_pos_group : this; + + if (b_center) { + // store aside the current center of geometry (all positions will be + // set to the closest images to the first one) and then center on + // the origin + cvm::atom_pos const cog = fit_group->center_of_geometry(); + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->pos -= cog; + } + } + + if (b_rotate) { + // rotate the group (around the center of geometry if b_center is + // true, around the origin otherwise); store the rotation, in + // order to bring back the forces to the original frame before + // applying them + rot.calc_optimal_rotation (fit_group->positions(), ref_pos); + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->pos = rot.rotate (ai->pos); + } + } + + if (b_center) { + // use the center of geometry of ref_pos + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->pos += ref_pos_cog; + } + } +} + + +void cvm::atom_group::apply_translation (cvm::rvector const &t) +{ + if (b_dummy) return; + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->pos += t; + } +} + + +void cvm::atom_group::apply_rotation (cvm::rotation const &rot) +{ + if (b_dummy) return; + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->pos = rot.rotate (ai->pos); + } +} + + +void cvm::atom_group::read_velocities() +{ + if (b_dummy) return; + + if (b_rotate) { + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->read_velocity(); + ai->vel = rot.rotate (ai->vel); + } + + } else { + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->read_velocity(); + } + } +} + +void cvm::atom_group::read_system_forces() +{ + if (b_dummy) return; + + if (b_rotate) { + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->read_system_force(); + ai->system_force = rot.rotate (ai->system_force); + } + + } else { + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->read_system_force(); + } + } +} + +cvm::atom_pos cvm::atom_group::center_of_geometry (cvm::atom_pos const &ref_pos) +{ + if (b_dummy) { + cvm::select_closest_image (dummy_atom_pos, ref_pos); + return dummy_atom_pos; + } + + cvm::atom_pos cog (0.0, 0.0, 0.0); + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + cvm::select_closest_image (ai->pos, ref_pos); + cog += ai->pos; + } + cog /= this->size(); + return cog; +} + +cvm::atom_pos cvm::atom_group::center_of_geometry() const +{ + if (b_dummy) + return dummy_atom_pos; + + cvm::atom_pos cog (0.0, 0.0, 0.0); + for (cvm::atom_const_iter ai = this->begin(); + ai != this->end(); ai++) { + cog += ai->pos; + } + cog /= this->size(); + return cog; +} + +cvm::atom_pos cvm::atom_group::center_of_mass (cvm::atom_pos const &ref_pos) +{ + if (b_dummy) { + cvm::select_closest_image (dummy_atom_pos, ref_pos); + return dummy_atom_pos; + } + + cvm::atom_pos com (0.0, 0.0, 0.0); + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + cvm::select_closest_image (ai->pos, ref_pos); + com += ai->mass * ai->pos; + } + com /= this->total_mass; + return com; +} + +cvm::atom_pos cvm::atom_group::center_of_mass() const +{ + if (b_dummy) + return dummy_atom_pos; + + cvm::atom_pos com (0.0, 0.0, 0.0); + for (cvm::atom_const_iter ai = this->begin(); + ai != this->end(); ai++) { + com += ai->mass * ai->pos; + } + com /= this->total_mass; + return com; +} + + +void cvm::atom_group::set_weighted_gradient (cvm::rvector const &grad) +{ + if (b_dummy) return; + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->grad = (ai->mass/this->total_mass) * grad; + } +} + + +std::vector cvm::atom_group::positions() const +{ + if (b_dummy) + cvm::fatal_error ("Error: positions are not available " + "from a dummy atom group.\n"); + + 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::fatal_error ("Error: positions are not available " + "from a dummy atom group.\n"); + + 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::fatal_error ("Error: velocities are not available " + "from a dummy atom group.\n"); + + 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::system_forces() const +{ + if (b_dummy) + cvm::fatal_error ("Error: system forces are not available " + "from a dummy atom group.\n"); + + 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->system_force; + } + return f; +} + +cvm::rvector cvm::atom_group::system_force() const +{ + if (b_dummy) + cvm::fatal_error ("Error: system forces are not available " + "from a dummy atom group.\n"); + + cvm::rvector f (0.0); + for (cvm::atom_const_iter ai = this->begin(); ai != this->end(); ai++) { + f += ai->system_force; + } + return f; +} + + +void cvm::atom_group::apply_colvar_force (cvm::real const &force) +{ + if (b_dummy) + return; + + if (noforce) + cvm::fatal_error ("Error: sending a force to a group that has " + "\"disableForces\" defined.\n"); + + if (b_rotate) { + + // get the forces back from the rotated frame + cvm::rotation const rot_inv = rot.inverse(); + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->apply_force (rot_inv.rotate (force * ai->grad)); + } + + } else { + + // no need to manipulate gradients, they are still in the original + // frame + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->apply_force (force * ai->grad); + } + } +} + + +void cvm::atom_group::apply_force (cvm::rvector const &force) +{ + if (b_dummy) + return; + + if (noforce) + cvm::fatal_error ("Error: sending a force to a group that has " + "\"disableForces\" defined.\n"); + + if (b_rotate) { + + cvm::rotation const rot_inv = rot.inverse(); + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->apply_force (rot_inv.rotate ((ai->mass/this->total_mass) * force)); + } + + } else { + + for (cvm::atom_iter ai = this->begin(); + ai != this->end(); ai++) { + ai->apply_force ((ai->mass/this->total_mass) * force); + } + } +} + + +void cvm::atom_group::apply_forces (std::vector const &forces) +{ + if (b_dummy) + return; + + if (noforce) + cvm::fatal_error ("Error: sending a force to a group that has " + "\"disableForces\" defined.\n"); + + if (forces.size() != this->size()) { + cvm::fatal_error ("Error: trying to apply an array of forces to an atom " + "group which does not have the same length.\n"); + } + + if (b_rotate) { + + cvm::rotation const rot_inv = rot.inverse(); + cvm::atom_iter ai = this->begin(); + std::vector::const_iterator fi = forces.begin(); + for ( ; ai != this->end(); fi++, ai++) { + ai->apply_force (rot_inv.rotate (*fi)); + } + + } else { + + cvm::atom_iter ai = this->begin(); + std::vector::const_iterator fi = forces.begin(); + for ( ; ai != this->end(); fi++, ai++) { + ai->apply_force (*fi); + } + } +} + + diff --git a/lib/colvars/colvaratoms.h b/lib/colvars/colvaratoms.h new file mode 100644 index 0000000000..481a93a9bd --- /dev/null +++ b/lib/colvars/colvaratoms.h @@ -0,0 +1,318 @@ +#ifndef COLVARATOMS_H +#define COLVARATOMS_H + +#include "colvarmodule.h" +#include "colvarparse.h" + +/// \brief Stores numeric id, mass and all mutable data for an atom, +/// mostly used by a \link cvc \endlink +/// +/// This class may be used (although not necessarily) to keep atomic +/// data (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. +/// +/// Read/write operations depend on the underlying code: hence, some +/// member functions are defined in colvarproxy_xxx.h. +class colvarmodule::atom { + +protected: + + /// \brief Index in the list of atoms involved by the colvars (\b + /// NOT in the global topology!) + size_t index; + +public: + + /// Internal identifier (zero-based) + int id; + + /// Mass + cvm::real mass; + + /// \brief Current position (copied from the program, can be + /// manipulated) + cvm::atom_pos pos; + + /// \brief Current velocity (copied from the program, can be + /// manipulated) + cvm::rvector vel; + + /// \brief System force at the previous step (copied from the + /// program, can be manipulated) + cvm::rvector system_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 cvc \endlink + /// implementation + cvm::rvector grad; + + /// \brief Default constructor, setting id to a non-valid value + inline atom() {} + + /// \brief Initialize an atom for collective variable calculation + /// and get its internal identifier \param atom_number Atom index in + /// the system topology (starting from 1) + atom (int const &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 = std::string ("")); + + /// Copy constructor + atom (atom const &a); + + /// Destructor + ~atom(); + + /// Set non-constant data (everything except id and mass) to zero + inline void reset_data() { + pos = atom_pos (0.0); + vel = grad = system_force = rvector (0.0); + } + + /// Get the current position + void read_position(); + + /// Get the current velocity + void read_velocity(); + + /// Get the system force + void read_system_force(); + + /// \brief Apply a force to the atom + /// + /// The force will be used later by the MD integrator, the + /// collective variables 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 add to the existing MD force. + void apply_force (cvm::rvector const &new_force); +}; + + + + +/// \brief Group of \link atom \endlink objects, mostly used by a +/// \link cvc \endlink +/// +/// This class inherits from \link colvarparse \endlink and from +/// std::vector, and hence all functions and +/// operators (including the bracket operator, group[i]) can be used +/// on an \link atom_group \endlink object. It can be initialized as +/// a vector, or by parsing a keyword in the configuration. +class colvarmodule::atom_group + : public std::vector, + public colvarparse +{ +public: + // Note: all members here are kept public, to make possible to any + // object accessing and manipulating them + + + /// \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; + /// \brief dummy atom position + cvm::atom_pos dummy_atom_pos; + + /// Sorted list of zero-based (internal) atom ids + /// (populated on-demand by create_sorted_ids) + std::vector sorted_ids; + + /// Allocates and populates the sorted list of atom ids + void create_sorted_ids (void); + + + /// \brief Before calculating colvars, move the group to overlap the + /// center of mass of reference coordinates + bool b_center; + + /// \brief Right after updating atom coordinates (and after + /// centering coordinates, if b_center is true), rotate the group to + /// overlap the reference coordinates. You should not manipulate + /// atoms individually if you turn on this flag. + /// + /// Note: gradients will be calculated in the rotated frame: when + /// forces will be applied, they will rotated back to the original + /// frame + bool b_rotate; + /// Rotation between the group and its reference coordinates + cvm::rotation rot; + + /// \brief In case b_center or b_rotate is true, use these reference + /// coordinates + std::vector ref_pos; + /// \brief Center of geometry of the reference coordinates; regardless + /// of whether b_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 In case b_center or b_rotate is true, fit this group to + /// the reference positions (default: the parent group itself) + atom_group *ref_pos_group; + + + /// Total mass of the atom group + cvm::real total_mass; + + /// \brief Don't apply any force on this group (use its coordinates + /// only to calculate a colvar) + bool noforce; + + + /// \brief Initialize the group by looking up its configuration + /// string in conf and parsing it; this is actually done by parse(), + /// which is a member function so that a group can be initialized + /// also after construction + atom_group (std::string const &conf, + char const *key, + atom_group *ref_pos_group = NULL); + + /// \brief Initialize the group by looking up its configuration + /// string in conf and parsing it + void parse (std::string const &conf, + char const *key, + atom_group *ref_pos_group = NULL); + + /// \brief Initialize the group after a temporary vector of atoms + atom_group (std::vector const &atoms); + + /// \brief Add an atom to this group + void add_atom (cvm::atom const &a); + + /// \brief Default constructor + atom_group(); + + /// \brief Destructor + ~atom_group(); + + /// \brief Get the current positions; if b_center or b_rotate are + /// true, center and/or rotate the coordinates right after reading + /// them + void read_positions(); + + /// \brief Move all positions + void apply_translation (cvm::rvector const &t); + + /// \brief Rotate all positions + void apply_rotation (cvm::rotation const &q); + + + /// \brief Get the current velocities; this must be called always + /// *after* read_positions(); if b_rotate is defined, the same + /// rotation applied to the coordinates will be used + void read_velocities(); + + /// \brief Get the current system_forces; this must be called always + /// *after* read_positions(); if b_rotate is defined, the same + /// rotation applied to the coordinates will be used + void read_system_forces(); + + /// Call reset_data() for each atom + inline void reset_atoms_data() + { + for (cvm::atom_iter ai = this->begin(); ai != this->end(); ai++) + ai->reset_data(); + } + + /// \brief Return a copy of the current atom positions + std::vector positions() const; + + /// \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 the center of geometry of the positions \param ref_pos + /// Use the closest periodic images to this position + cvm::atom_pos center_of_geometry (cvm::atom_pos const &ref_pos); + /// \brief Return the center of geometry of the positions, assuming + /// that coordinates are already pbc-wrapped + cvm::atom_pos center_of_geometry() const; + + /// \brief Return the center of mass of the positions \param ref_pos + /// Use the closest periodic images to this position + cvm::atom_pos center_of_mass (cvm::atom_pos const &ref_pos); + /// \brief Return the center of mass of the positions, assuming that + /// coordinates are already pbc-wrapped + cvm::atom_pos center_of_mass() const; + + /// \brief Store atom positions from the previous step + std::vector old_pos; + + + /// \brief Return a copy of the current atom velocities + std::vector velocities() const; + + + /// \brief Return a copy of the system forces + std::vector system_forces() const; + /// \brief Return a copy of the aggregated total force on the group + cvm::rvector system_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 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 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. + void apply_force (cvm::rvector const &force); + + /// \brief Apply an array of forces directly on the individual + /// atoms; the length of the specified vector must be the same of + /// this \link atom_group \endlink. + /// + /// If the group is being rotated to a reference frame (e.g. to + /// express the colvar independently from the solute rotation), the + /// forces are 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 forces. + void apply_forces (std::vector const &forces); +}; + + + + +#endif + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarbias.cpp b/lib/colvars/colvarbias.cpp new file mode 100644 index 0000000000..596e8810c6 --- /dev/null +++ b/lib/colvars/colvarbias.cpp @@ -0,0 +1,486 @@ +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarbias.h" + + +colvarbias::colvarbias (std::string const &conf, char const *key) + : colvarparse(), has_data (false) +{ + cvm::log ("Initializing a new \""+std::string (key)+"\" instance.\n"); + + size_t rank = 1; + std::string const key_str (key); + + if (to_lower_cppstr (key_str) == std::string ("abf")) { + rank = cvm::n_abf_biases+1; + } + if (to_lower_cppstr (key_str) == std::string ("harmonic")) { + rank = cvm::n_harm_biases+1; + } + if (to_lower_cppstr (key_str) == std::string ("histogram")) { + rank = cvm::n_histo_biases+1; + } + if (to_lower_cppstr (key_str) == std::string ("metadynamics")) { + rank = cvm::n_meta_biases+1; + } + + get_keyval (conf, "name", name, key_str+cvm::to_str (rank)); + + // lookup the associated colvars + std::vector colvars_str; + if (get_keyval (conf, "colvars", colvars_str)) { + for (size_t i = 0; i < colvars_str.size(); i++) { + add_colvar (colvars_str[i]); + } + } + + if (!colvars.size()) { + cvm::fatal_error ("Error: no collective variables specified.\n"); + } +} + + +colvarbias::colvarbias() + : colvarparse(), has_data (false) +{} + + +void colvarbias::add_colvar (std::string const &cv_name) +{ + if (colvar *cvp = cvm::colvar_p (cv_name)) { + cvp->enable (colvar::task_gradients); + if (cvm::debug()) + cvm::log ("Applying this bias to collective variable \""+ + cvp->name+"\".\n"); + colvars.push_back (cvp); + colvar_forces.push_back (colvarvalue (cvp->type())); + } else { + cvm::fatal_error ("Error: cannot find a colvar named \""+ + cv_name+"\".\n"); + } +} + + +void colvarbias::communicate_forces() +{ + for (size_t i = 0; i < colvars.size(); i++) { + if (cvm::debug()) { + cvm::log ("Communicating a force to colvar \""+ + colvars[i]->name+"\", of type \""+ + colvarvalue::type_desc[colvars[i]->type()]+"\".\n"); + } + colvars[i]->add_bias_force (colvar_forces[i]); + } +} + + + +colvarbias_harmonic::colvarbias_harmonic (std::string const &conf, + char const *key) + : colvarbias (conf, key), + target_nsteps (0) +{ + get_keyval (conf, "forceConstant", force_k, 1.0); + for (size_t i = 0; i < colvars.size(); i++) { + if (colvars[i]->width != 1.0) + cvm::log ("The force constant for colvar \""+colvars[i]->name+ + "\" will be rescaled to "+ + cvm::to_str (force_k/(colvars[i]->width*colvars[i]->width))+ + " according to the specified width.\n"); + } + + // get the initial restraint centers + colvar_centers.resize (colvars.size()); + colvar_centers_raw.resize (colvars.size()); + for (size_t i = 0; i < colvars.size(); i++) { + colvar_centers[i].type (colvars[i]->type()); + colvar_centers_raw[i].type (colvars[i]->type()); + } + if (get_keyval (conf, "centers", colvar_centers, colvar_centers)) { + for (size_t i = 0; i < colvars.size(); i++) { + colvar_centers[i].apply_constraints(); + colvar_centers_raw[i] = colvar_centers[i]; + } + } else { + colvar_centers.clear(); + cvm::fatal_error ("Error: must define the initial centers of the restraints.\n"); + } + + if (colvar_centers.size() != colvars.size()) + cvm::fatal_error ("Error: number of harmonic centers does not match " + "that of collective variables.\n"); + + if (get_keyval (conf, "targetCenters", target_centers, colvar_centers)) { + b_chg_centers = true; + target_nstages = 0; + for (size_t i = 0; i < target_centers.size(); i++) { + target_centers[i].apply_constraints(); + } + } else { + b_chg_centers = false; + target_centers.clear(); + } + + if (get_keyval (conf, "targetForceConstant", target_force_k, 0.0)) { + if (b_chg_centers) + cvm::fatal_error ("Error: cannot specify both targetCenters and targetForceConstant.\n"); + + starting_force_k = force_k; + b_chg_force_k = true; + + get_keyval (conf, "targetEquilSteps", target_equil_steps, 0); + + get_keyval (conf, "lambdaSchedule", lambda_schedule, lambda_schedule); + if (lambda_schedule.size()) { + // There is one more lambda-point than stages + target_nstages = lambda_schedule.size() - 1; + } else { + target_nstages = 0; + } + } else { + b_chg_force_k = false; + } + + if (b_chg_centers || b_chg_force_k) { + get_keyval (conf, "targetNumSteps", target_nsteps, 0); + if (!target_nsteps) + cvm::fatal_error ("Error: targetNumSteps must be non-zero.\n"); + + if (get_keyval (conf, "targetNumStages", target_nstages, target_nstages) && + lambda_schedule.size()) { + cvm::fatal_error ("Error: targetNumStages and lambdaSchedule are incompatible.\n"); + } + + if (target_nstages) { + // This means that either numStages of lambdaSchedule has been provided + stage = -1; + restraint_FE = 0.0; + } + + if (get_keyval (conf, "targetForceExponent", force_k_exp, 1.0)) { + if (! b_chg_force_k) + cvm::log ("Warning: not changing force constant: targetForceExponent will be ignored\n"); + if (force_k_exp < 1.0) + cvm::log ("Warning: for all practical purposes, targetForceExponent should be 1.0 or greater.\n"); + } + } + + if (cvm::debug()) + cvm::log ("Done initializing a new harmonic restraint bias.\n"); +} + + +void colvarbias::change_configuration(std::string const &conf) +{ + cvm::fatal_error ("Error: change_configuration() not implemented.\n"); +} + + +cvm::real colvarbias::energy_difference(std::string const &conf) +{ + cvm::fatal_error ("Error: energy_difference() not implemented.\n"); + return 0.; +} + + +void colvarbias_harmonic::change_configuration(std::string const &conf) +{ + get_keyval (conf, "forceConstant", force_k, force_k); + if (get_keyval (conf, "centers", colvar_centers, colvar_centers)) { + for (size_t i = 0; i < colvars.size(); i++) { + colvar_centers[i].apply_constraints(); + colvar_centers_raw[i] = colvar_centers[i]; + } + } +} + + +cvm::real colvarbias_harmonic::energy_difference(std::string const &conf) +{ + std::vector alt_colvar_centers; + cvm::real alt_force_k; + cvm::real alt_bias_energy = 0.0; + + get_keyval (conf, "forceConstant", alt_force_k, force_k); + + alt_colvar_centers.resize (colvars.size()); + for (size_t i = 0; i < colvars.size(); i++) { + alt_colvar_centers[i].type (colvars[i]->type()); + } + if (get_keyval (conf, "centers", alt_colvar_centers, colvar_centers)) { + for (size_t i = 0; i < colvars.size(); i++) { + colvar_centers[i].apply_constraints(); + } + } + + for (size_t i = 0; i < colvars.size(); i++) { + alt_bias_energy += 0.5 * alt_force_k / (colvars[i]->width * colvars[i]->width) * + colvars[i]->dist2(colvars[i]->value(), alt_colvar_centers[i]); + } + + return alt_bias_energy - bias_energy; +} + + +cvm::real colvarbias_harmonic::update() +{ + bias_energy = 0.0; + + if (cvm::debug()) + cvm::log ("Updating the harmonic bias \""+this->name+"\".\n"); + + // Setup first stage of staged variable force constant calculation + if (b_chg_force_k && target_nstages && stage == -1) { + stage = 0; + cvm::real lambda; + if (lambda_schedule.size()) { + lambda = lambda_schedule[0]; + } else { + lambda = 0.0; + } + force_k = starting_force_k + (target_force_k - starting_force_k) + * std::pow (lambda, force_k_exp); + cvm::log ("Harmonic restraint " + this->name + ", stage " + + cvm::to_str(stage) + " : lambda = " + cvm::to_str(lambda)); + cvm::log ("Setting force constant to " + cvm::to_str (force_k)); + } + + // Force and energy calculation + for (size_t i = 0; i < colvars.size(); i++) { + colvar_forces[i] = + (-0.5) * force_k / + (colvars[i]->width * colvars[i]->width) * + colvars[i]->dist2_lgrad (colvars[i]->value(), + colvar_centers[i]); + bias_energy += 0.5 * force_k / (colvars[i]->width * colvars[i]->width) * + colvars[i]->dist2(colvars[i]->value(), colvar_centers[i]); + if (cvm::debug()) + cvm::log ("dist_grad["+cvm::to_str (i)+ + "] = "+cvm::to_str (colvars[i]->dist2_lgrad (colvars[i]->value(), + colvar_centers[i]))+"\n"); + } + + if (cvm::debug()) + cvm::log ("Current forces for the harmonic bias \""+ + this->name+"\": "+cvm::to_str (colvar_forces)+".\n"); + + if (b_chg_centers) { + + if (!centers_incr.size()) { + // if this is the first calculation, calculate the advancement + // at each simulation step (or stage, if applicable) + // (take current stage into account: it can be non-zero + // if we are restarting a staged calculation) + centers_incr.resize (colvars.size()); + for (size_t i = 0; i < colvars.size(); i++) { + centers_incr[i].type (colvars[i]->type()); + centers_incr[i] = (target_centers[i] - colvar_centers[i]) / + cvm::real ( target_nstages ? (target_nstages - stage) : + (target_nsteps - cvm::step_absolute())); + } + if (cvm::debug()) + cvm::log ("Center increment for the harmonic bias \""+ + this->name+"\": "+cvm::to_str (centers_incr)+".\n"); + } + + if (cvm::debug()) + cvm::log ("Current centers for the harmonic bias \""+ + this->name+"\": "+cvm::to_str (colvar_centers)+".\n"); + + if (target_nstages) { + if (cvm::step_absolute() > 0 + && (cvm::step_absolute() % target_nsteps) == 0 + && stage < target_nstages) { + + // any per-stage calculation, e.g. free energy stuff + // should be done here + for (size_t i = 0; i < colvars.size(); i++) { + colvar_centers_raw[i] += centers_incr[i]; + colvar_centers[i] = colvar_centers_raw[i]; + colvars[i]->wrap(colvar_centers[i]); + colvar_centers[i].apply_constraints(); + } + stage++; + cvm::log ("Moving restraint stage " + cvm::to_str(stage) + + " : setting centers to " + cvm::to_str (colvar_centers)); + } + } else if (cvm::step_absolute() < target_nsteps) { + // move the restraint centers in the direction of the targets + // (slow growth) + for (size_t i = 0; i < colvars.size(); i++) { + colvar_centers_raw[i] += centers_incr[i]; + colvar_centers[i] = colvar_centers_raw[i]; + colvars[i]->wrap(colvar_centers[i]); + colvar_centers[i].apply_constraints(); + } + } + } + + if (b_chg_force_k) { + // Coupling parameter, between 0 and 1 + cvm::real lambda; + + if (target_nstages) { + // 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 (target_equil_steps == 0 || cvm::step_absolute() % target_nsteps >= target_equil_steps) { + // Start averaging after equilibration period, if requested + + // Square distance normalized by square colvar width + cvm::real dist_sq = 0.0; + for (size_t i = 0; i < colvars.size(); i++) { + dist_sq += colvars[i]->dist2 (colvars[i]->value(), colvar_centers[i]) + / (colvars[i]->width * colvars[i]->width); + } + + restraint_FE += 0.5 * force_k_exp * std::pow(lambda, force_k_exp - 1.0) + * (target_force_k - starting_force_k) * dist_sq; + } + + // Finish current stage... + if (cvm::step_absolute() % target_nsteps == 0 && + cvm::step_absolute() > 0) { + + cvm::log ("Lambda= " + cvm::to_str (lambda) + " dA/dLambda= " + + cvm::to_str (restraint_FE / cvm::real(target_nsteps - target_equil_steps))); + + // ...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); + } + force_k = starting_force_k + (target_force_k - starting_force_k) + * std::pow (lambda, force_k_exp); + cvm::log ("Harmonic restraint " + this->name + ", stage " + + cvm::to_str(stage) + " : lambda = " + cvm::to_str(lambda)); + cvm::log ("Setting force constant to " + cvm::to_str (force_k)); + } + } + } else if (cvm::step_absolute() <= target_nsteps) { + // update force constant (slow growth) + lambda = cvm::real(cvm::step_absolute()) / cvm::real(target_nsteps); + force_k = starting_force_k + (target_force_k - starting_force_k) + * std::pow (lambda, force_k_exp); + } + } + + if (cvm::debug()) + cvm::log ("Done updating the harmonic bias \""+this->name+"\".\n"); + return bias_energy; +} + + +std::istream & colvarbias_harmonic::read_restart (std::istream &is) +{ + size_t const start_pos = is.tellg(); + + cvm::log ("Restarting harmonic bias \""+ + this->name+"\".\n"); + + std::string key, brace, conf; + if ( !(is >> key) || !(key == "harmonic") || + !(is >> brace) || !(brace == "{") || + !(is >> colvarparse::read_block ("configuration", conf)) ) { + + cvm::log ("Error: in reading restart configuration for harmonic bias \""+ + this->name+"\" at position "+ + cvm::to_str (is.tellg())+" in stream.\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + +// int id = -1; + std::string name = ""; +// if ( ( (colvarparse::get_keyval (conf, "id", id, -1, colvarparse::parse_silent)) && +// (id != this->id) ) || + if ( (colvarparse::get_keyval (conf, "name", name, std::string (""), colvarparse::parse_silent)) && + (name != this->name) ) + cvm::fatal_error ("Error: in the restart file, the " + "\"harmonic\" block has a wrong name\n"); +// if ( (id == -1) && (name == "") ) { + if (name.size() == 0) { + cvm::fatal_error ("Error: \"harmonic\" block in the restart file " + "has no identifiers.\n"); + } + + if (b_chg_centers) { + cvm::log ("Reading the updated restraint centers from the restart.\n"); + if (!get_keyval (conf, "centers", colvar_centers)) + cvm::fatal_error ("Error: restraint centers are missing from the restart.\n"); + if (!get_keyval (conf, "centers_raw", colvar_centers_raw)) + cvm::fatal_error ("Error: \"raw\" restraint centers are missing from the restart.\n"); + } + + if (b_chg_force_k) { + cvm::log ("Reading the updated force constant from the restart.\n"); + if (!get_keyval (conf, "forceConstant", force_k)) + cvm::fatal_error ("Error: force constant is missing from the restart.\n"); + } + + if (target_nstages) { + cvm::log ("Reading current stage from the restart.\n"); + if (!get_keyval (conf, "stage", stage)) + cvm::fatal_error ("Error: current stage is missing from the restart.\n"); + } + + is >> brace; + if (brace != "}") { + cvm::fatal_error ("Error: corrupt restart information for harmonic bias \""+ + this->name+"\": no matching brace at position "+ + cvm::to_str (is.tellg())+" in the restart file.\n"); + is.setstate (std::ios::failbit); + } + return is; +} + + +std::ostream & colvarbias_harmonic::write_restart (std::ostream &os) +{ + os << "harmonic {\n" + << " configuration {\n" + // << " id " << this->id << "\n" + << " name " << this->name << "\n"; + + if (b_chg_centers) { + os << " centers "; + for (size_t i = 0; i < colvars.size(); i++) { + os << " " << colvar_centers[i]; + } + os << "\n"; + os << " centers_raw "; + for (size_t i = 0; i < colvars.size(); i++) { + os << " " << colvar_centers_raw[i]; + } + os << "\n"; + } + + if (b_chg_force_k) { + os << " forceConstant " + << std::setprecision (cvm::en_prec) + << std::setw (cvm::en_width) << force_k << "\n"; + } + + if (target_nstages) { + os << " stage " << std::setw (cvm::it_width) + << stage << "\n"; + } + + os << " }\n" + << "}\n\n"; + + return os; +} + diff --git a/lib/colvars/colvarbias.h b/lib/colvars/colvarbias.h new file mode 100644 index 0000000000..314733eba9 --- /dev/null +++ b/lib/colvars/colvarbias.h @@ -0,0 +1,167 @@ +#ifndef COLVARBIAS_H +#define COLVARBIAS_H + +#include "colvar.h" +#include "colvarparse.h" + + +/// \brief Collective variable bias, base class +class colvarbias : public colvarparse { +public: + + /// Numeric id of this bias + int id; + + /// Name of this bias + std::string name; + + /// Add a new collective variable to this bias + void add_colvar (std::string const &cv_name); + + /// Retrieve colvar values and calculate their biasing forces + /// Return bias energy + virtual cvm::real update() = 0; + + /// Load new configuration - force constant and/or centers only + virtual void change_configuration(std::string const &conf); + + /// Calculate change in energy from using alternate configuration + virtual cvm::real energy_difference(std::string const &conf); + + /// Perform analysis tasks + virtual inline void analyse() {} + + /// Send forces to the collective variables + void communicate_forces(); + + /// \brief Constructor + /// + /// The constructor of the colvarbias base class is protected, so + /// that it can only be called from inherited classes + colvarbias (std::string const &conf, char const *key); + + /// Default constructor + colvarbias(); + + /// Destructor + virtual inline ~colvarbias() {} + + /// Read the bias configuration from a restart file + virtual std::istream & read_restart (std::istream &is) = 0; + + /// Write the bias configuration to a restart file + virtual std::ostream & write_restart (std::ostream &os) = 0; + +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 Current forces from this bias to the colvars + std::vector colvar_forces; + + /// \brief Current energy of this bias (colvar_forces should be + /// obtained by deriving this) + cvm::real bias_energy; + + /// \brief Whether this bias has already accumulated information + /// (when relevant) + bool has_data; + +}; + + +/// \brief Harmonic restraint, optionally moving towards a target +/// (implementation of \link colvarbias \endlink) +class colvarbias_harmonic : public colvarbias { + +public: + + /// Retrieve colvar values and calculate their biasing forces + virtual cvm::real update(); + + /// Load new configuration - force constant and/or centers only + virtual void change_configuration(std::string const &conf); + + /// Calculate change in energy from using alternate configuration + virtual cvm::real energy_difference(std::string const &conf); + + /// Read the bias configuration from a restart file + virtual std::istream & read_restart (std::istream &is); + + /// Write the bias configuration to a restart file + virtual std::ostream & write_restart (std::ostream &os); + + /// \brief Constructor + colvarbias_harmonic (std::string const &conf, char const *key); + + /// Destructor + virtual inline ~colvarbias_harmonic() {} + + +protected: + + /// \brief Restraint centers + std::vector colvar_centers; + + /// \brief Restraint centers without wrapping or constraints applied + std::vector colvar_centers_raw; + + /// \brief Restraint force constant + cvm::real force_k; + + /// \brief Moving target? + bool b_chg_centers; + + /// \brief Changing force constant? + bool b_chg_force_k; + + /// \brief Restraint force constant (target value) + cvm::real target_force_k; + + /// \brief Equilibration steps for restraint FE calculation through TI + cvm::real target_equil_steps; + + /// \brief Restraint force constant (starting value) + cvm::real starting_force_k; + + /// \brief Lambda-schedule for custom varying force constant + std::vector lambda_schedule; + + /// \brief New restraint centers + std::vector target_centers; + + /// \brief Amplitude of the restraint centers' increment at each step + /// (or stage) towards the new values (calculated from target_nsteps) + std::vector centers_incr; + + /// \brief Exponent for varying the force constant + cvm::real force_k_exp; + + /// \brief Number of steps required to reach the target force constant + /// or restraint centers + size_t target_nsteps; + + /// \brief Number of stages over which to perform the change + /// If zero, perform a continuous change + size_t target_nstages; + + /// \brief Number of current stage of the perturbation + size_t stage; + + /// \brief Intermediate quantity to compute the restraint free energy + /// (in TI, would be the accumulating FE derivative) + cvm::real restraint_FE; +}; + + +#endif + + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarbias_abf.cpp b/lib/colvars/colvarbias_abf.cpp new file mode 100644 index 0000000000..677bedffbd --- /dev/null +++ b/lib/colvars/colvarbias_abf.cpp @@ -0,0 +1,492 @@ +/******************************************************************************** + * Implementation of the ABF and histogram biases * + ********************************************************************************/ + +#include "colvarmodule.h" +#include "colvar.h" +#include "colvarbias_abf.h" + +/// ABF bias constructor; parses the config file + +colvarbias_abf::colvarbias_abf (std::string const &conf, char const *key) + : colvarbias (conf, key), + gradients (NULL), + samples (NULL) +{ + if (cvm::temperature() == 0.0) + cvm::log ("WARNING: ABF should not be run without a thermostat or at 0 Kelvin!\n"); + + // ************* parsing general ABF options *********************** + + get_keyval (conf, "applyBias", apply_bias, true); + if (!apply_bias) cvm::log ("WARNING: ABF biases will *not* be applied!\n"); + + get_keyval (conf, "updateBias", update_bias, true); + if (!update_bias) 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, "outputFreq", output_freq, cvm::restart_out_freq); + get_keyval (conf, "historyFreq", history_freq, 0); + b_history_files = (history_freq > 0); + + // ************* checking the associated colvars ******************* + + if (colvars.size() == 0) { + cvm::fatal_error ("Error: no collective variables specified for the ABF bias.\n"); + } + + for (size_t i = 0; i < colvars.size(); i++) { + + if (colvars[i]->type() != colvarvalue::type_scalar) { + cvm::fatal_error ("Error: ABF bias can only use scalar-type variables.\n"); + } + + colvars[i]->enable (colvar::task_gradients); + + if (update_bias) { + // Request calculation of system force (which also checks for availability) + colvars[i]->enable (colvar::task_system_force); + + if (!colvars[i]->tasks[colvar::task_extended_lagrangian]) { + // request computation of Jacobian force + colvars[i]->enable (colvar::task_Jacobian_force); + + // request Jacobian force as part as system force + // except if the user explicitly requires the "silent" Jacobian + // correction AND the colvar has a single component + if (hide_Jacobian) { + if (colvars[i]->n_components() > 1) { + cvm::log ("WARNING: colvar \"" + colvars[i]->name + + "\" has multiple components; reporting its Jacobian forces\n"); + colvars[i]->enable (colvar::task_report_Jacobian_force); + } + } else { + colvars[i]->enable (colvar::task_report_Jacobian_force); + } + } + } + + // Here we could check for orthogonality of the Cartesian coordinates + // and make it just a warning if some parameter is set? + } + + bin.assign (colvars.size(), 0); + force_bin.assign (colvars.size(), 0); + force = new cvm::real [colvars.size()]; + + // Construct empty grids based on the colvars + samples = new colvar_grid_count (colvars); + gradients = new colvar_grid_gradient (colvars); + gradients->samples = samples; + samples->has_parent_data = true; + + // If custom grids are provided, read them + if ( input_prefix.size() > 0 ) { + read_gradients_samples (); + } + + cvm::log ("Finished ABF setup.\n"); +} + +/// Destructor +colvarbias_abf::~colvarbias_abf() +{ + if (samples) { + delete samples; + samples = NULL; + } + + if (gradients) { + delete gradients; + gradients = NULL; + } + + delete [] force; +} + + +/// Update the FE gradient, compute and apply biasing force +/// also output data to disk if needed + +cvm::real colvarbias_abf::update() +{ + if (cvm::debug()) cvm::log ("Updating ABF bias " + this->name); + + if (cvm::step_relative() == 0) { + + // At first timestep, do only: + // initialization stuff (file operations relying on n_abf_biases + // compute current value of colvars + + if ( cvm::n_abf_biases == 1 && cvm::n_meta_biases == 0 ) { + // This is the only ABF bias + output_prefix = cvm::output_prefix; + } else { + output_prefix = cvm::output_prefix + "." + this->name; + } + + for (size_t i=0; icurrent_bin_scalar(i); + } + + } else { + + for (size_t i=0; icurrent_bin_scalar(i); + } + + if ( update_bias && samples->index_ok (force_bin) ) { + // Only if requested and within bounds of the grid... + + for (size_t i=0; isystem_force(); + } + gradients->acc_force (force_bin, force); + } + } + + // save bin for next timestep + force_bin = bin; + + // Reset biasing forces from previous timestep + for (size_t i=0; iindex_ok (bin) ) { + + size_t count = 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)); + } + + const cvm::real * grad = &(gradients->value (bin)); + + if ( fact != 0.0 ) { + + if ( (colvars.size() == 1) && colvars[0]->periodic_boundaries() ) { + // Enforce a zero-mean bias on periodic, 1D coordinates + colvar_forces[0].real_value += fact * (grad[0] / cvm::real (count) - gradients->average ()); + } else { + for (size_t i=0; i 0 + // otherwise, backup and replace + write_gradients_samples (output_prefix + ".hist", (cvm::step_absolute() > 0)); + } + return 0.0; // TODO compute bias energy whenever possible (i.e. 1D with updateBias off) +} + + +void colvarbias_abf::write_gradients_samples (const std::string &prefix, bool append) +{ + std::string samples_out_name = prefix + ".count"; + std::string gradients_out_name = prefix + ".grad"; + std::ios::openmode mode = (append ? std::ios::app : std::ios::out); + + std::ofstream samples_os; + std::ofstream gradients_os; + + if (!append) cvm::backup_file (samples_out_name.c_str()); + samples_os.open (samples_out_name.c_str(), mode); + if (!samples_os.good()) cvm::fatal_error ("Error opening ABF samples file " + samples_out_name + " for writing"); + samples->write_multicol (samples_os); + samples_os.close (); + + if (!append) cvm::backup_file (gradients_out_name.c_str()); + gradients_os.open (gradients_out_name.c_str(), mode); + if (!gradients_os.good()) cvm::fatal_error ("Error opening ABF gradient file " + gradients_out_name + " for writing"); + gradients->write_multicol (gradients_os); + gradients_os.close (); + + if (colvars.size () == 1) { + std::string pmf_out_name = prefix + ".pmf"; + if (!append) cvm::backup_file (pmf_out_name.c_str()); + std::ofstream pmf_os; + // Do numerical integration and output a PMF + pmf_os.open (pmf_out_name.c_str(), mode); + if (!pmf_os.good()) cvm::fatal_error ("Error opening pmf file " + pmf_out_name + " for writing"); + gradients->write_1D_integral (pmf_os); + pmf_os << std::endl; + pmf_os.close (); + } + return; +} + +void colvarbias_abf::read_gradients_samples () +{ + std::string samples_in_name, 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"; + // For user-provided files, the per-bias naming scheme may not apply + + std::ifstream is; + + cvm::log ("Reading sample count from " + samples_in_name + " and gradients from " + gradients_in_name); + is.open (samples_in_name.c_str()); + if (!is.good()) cvm::fatal_error ("Error opening ABF samples file " + samples_in_name + " for reading"); + samples->read_multicol (is, true); + is.close (); + is.clear(); + + is.open (gradients_in_name.c_str()); + if (!is.good()) cvm::fatal_error ("Error opening ABF gradient file " + gradients_in_name + " for reading"); + gradients->read_multicol (is, true); + is.close (); + } + return; +} + + +std::ostream & colvarbias_abf::write_restart (std::ostream& os) +{ + + std::ios::fmtflags flags (os.flags ()); + os.setf(std::ios::fmtflags (0), std::ios::floatfield); // default floating-point format + + os << "abf {\n" + << " configuration {\n" + << " name " << this->name << "\n"; + os << " }\n"; + + os << "samples\n"; + samples->write_raw (os, 8); + + os << "\ngradient\n"; + gradients->write_raw (os); + + os << "}\n\n"; + + os.flags (flags); + return os; +} + + +std::istream & colvarbias_abf::read_restart (std::istream& is) +{ + if ( input_prefix.size() > 0 ) { + cvm::fatal_error ("ERROR: cannot provide both inputPrefix and restart information (colvarsInput)"); + } + + size_t const start_pos = is.tellg(); + + cvm::log ("Restarting ABF bias \""+ + this->name+"\".\n"); + std::string key, brace, conf; + + if ( !(is >> key) || !(key == "abf") || + !(is >> brace) || !(brace == "{") || + !(is >> colvarparse::read_block ("configuration", conf)) ) { + cvm::log ("Error: in reading restart configuration for ABF bias \""+ + this->name+"\" at position "+ + cvm::to_str (is.tellg())+" in stream.\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + std::string name = ""; + if ( (colvarparse::get_keyval (conf, "name", name, std::string (""), colvarparse::parse_silent)) && + (name != this->name) ) + cvm::fatal_error ("Error: in the restart file, the " + "\"abf\" block has wrong name (" + name + ")\n"); + if ( name == "" ) { + cvm::fatal_error ("Error: \"abf\" block in the restart file has no name.\n"); + } + + if ( !(is >> key) || !(key == "samples")) { + cvm::log ("Error: in reading restart configuration for ABF bias \""+ + this->name+"\" at position "+ + cvm::to_str (is.tellg())+" in stream.\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + if (! samples->read_raw (is)) { + samples->read_raw_error(); + } + + if ( !(is >> key) || !(key == "gradient")) { + cvm::log ("Error: in reading restart configuration for ABF bias \""+ + this->name+"\" at position "+ + cvm::to_str (is.tellg())+" in stream.\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + if (! gradients->read_raw (is)) { + gradients->read_raw_error(); + } + + is >> brace; + if (brace != "}") { + cvm::fatal_error ("Error: corrupt restart information for ABF bias \""+ + this->name+"\": no matching brace at position "+ + cvm::to_str (is.tellg())+" in the restart file.\n"); + is.setstate (std::ios::failbit); + } + return is; +} + + + + +/// Histogram "bias" constructor + +colvarbias_histogram::colvarbias_histogram (std::string const &conf, char const *key) + : colvarbias (conf, key), + grid (NULL) +{ + get_keyval (conf, "outputfreq", output_freq, cvm::restart_out_freq); + + if ( output_freq == 0 ) { + cvm::fatal_error ("User required histogram with zero output frequency"); + } + + grid = new colvar_grid_count (colvars); + bin.assign (colvars.size(), 0); + + out_name = cvm::output_prefix + "." + this->name + ".dat"; + cvm::log ("Histogram will be written to file " + out_name); + + cvm::log ("Finished histogram setup.\n"); +} + +/// Destructor +colvarbias_histogram::~colvarbias_histogram() +{ + if (grid_os.good()) grid_os.close(); + + if (grid) { + delete grid; + grid = NULL; + } +} + +/// Update the grid +cvm::real colvarbias_histogram::update() +{ + if (cvm::debug()) cvm::log ("Updating Grid bias " + this->name); + + for (size_t i=0; icurrent_bin_scalar(i); + } + + if ( grid->index_ok (bin) ) { // Only within bounds of the grid... + grid->incr_count (bin); + } + + if (output_freq && (cvm::step_absolute() % output_freq) == 0) { + if (cvm::debug()) cvm::log ("Histogram bias trying to write grid to disk"); + + grid_os.open (out_name.c_str()); + if (!grid_os.good()) cvm::fatal_error ("Error opening histogram file " + out_name + " for writing"); + grid->write_multicol (grid_os); + grid_os.close (); + } + return 0.0; // no bias energy for histogram +} + + +std::istream & colvarbias_histogram::read_restart (std::istream& is) +{ + size_t const start_pos = is.tellg(); + + cvm::log ("Restarting collective variable histogram \""+ + this->name+"\".\n"); + std::string key, brace, conf; + + if ( !(is >> key) || !(key == "histogram") || + !(is >> brace) || !(brace == "{") || + !(is >> colvarparse::read_block ("configuration", conf)) ) { + cvm::log ("Error: in reading restart configuration for histogram \""+ + this->name+"\" at position "+ + cvm::to_str (is.tellg())+" in stream.\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + int id = -1; + std::string name = ""; + if ( (colvarparse::get_keyval (conf, "name", name, std::string (""), colvarparse::parse_silent)) && + (name != this->name) ) + cvm::fatal_error ("Error: in the restart file, the " + "\"histogram\" block has a wrong name: different system?\n"); + if ( (id == -1) && (name == "") ) { + cvm::fatal_error ("Error: \"histogram\" block in the restart file " + "has no name.\n"); + } + + if ( !(is >> key) || !(key == "grid")) { + cvm::log ("Error: in reading restart configuration for histogram \""+ + this->name+"\" at position "+ + cvm::to_str (is.tellg())+" in stream.\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + if (! grid->read_raw (is)) { + grid->read_raw_error(); + } + + is >> brace; + if (brace != "}") { + cvm::fatal_error ("Error: corrupt restart information for ABF bias \""+ + this->name+"\": no matching brace at position "+ + cvm::to_str (is.tellg())+" in the restart file.\n"); + is.setstate (std::ios::failbit); + } + return is; +} + +std::ostream & colvarbias_histogram::write_restart (std::ostream& os) +{ + os << "histogram {\n" + << " configuration {\n" + << " name " << this->name << "\n"; + os << " }\n"; + + os << "grid\n"; + grid->write_raw (os, 8); + + os << "}\n\n"; + + return os; +} diff --git a/lib/colvars/colvarbias_abf.h b/lib/colvars/colvarbias_abf.h new file mode 100644 index 0000000000..f107b78248 --- /dev/null +++ b/lib/colvars/colvarbias_abf.h @@ -0,0 +1,95 @@ +/************************************************************************ + * Headers for the ABF and histogram biases * + ************************************************************************/ + +#ifndef COLVARBIAS_ABF_H +#define COLVARBIAS_ABF_H + +#include +#include +#include +#include +//#include + +#include "colvarbias.h" +#include "colvargrid.h" + +typedef cvm::real* gradient_t; + + +/// ABF bias +class colvarbias_abf : public colvarbias { + +public: + + colvarbias_abf (std::string const &conf, char const *key); + ~colvarbias_abf (); + + cvm::real update (); + +private: + + /// Filename prefix for human-readable gradient/sample count output + std::string output_prefix; + + /// Base filename(s) for reading previous gradient data (replaces data from restart file) + std::vector input_prefix; + + bool apply_bias; + bool update_bias; + bool hide_Jacobian; + size_t full_samples; + size_t min_samples; + /// frequency for updating output files (default: same as restartFreq?) + int output_freq; + /// Write combined files with a history of all output data? + bool b_history_files; + size_t history_freq; + + // Internal data and methods + + std::vector bin, force_bin; + gradient_t force; + + /// n-dim grid of free energy gradients + colvar_grid_gradient *gradients; + /// n-dim grid of number of samples + colvar_grid_count *samples; + + /// Write human-readable FE gradients and sample count + void write_gradients_samples (const std::string &prefix, bool append = false); + + /// Read human-readable FE gradients and sample count (if not using restart) + void read_gradients_samples (); + + std::istream& read_restart (std::istream&); + std::ostream& write_restart (std::ostream&); +}; + + +/// Histogram "bias" (does as the name says) +class colvarbias_histogram : public colvarbias { + +public: + + colvarbias_histogram (std::string const &conf, char const *key); + ~colvarbias_histogram (); + + cvm::real update (); + +private: + + /// n-dim histogram + colvar_grid_count *grid; + std::vector bin; + std::string out_name; + + int output_freq; + void write_grid (); + std::ofstream grid_os; /// Stream for writing grid to disk + + std::istream& read_restart (std::istream&); + std::ostream& write_restart (std::ostream&); +}; + +#endif diff --git a/lib/colvars/colvarbias_meta.cpp b/lib/colvars/colvarbias_meta.cpp new file mode 100644 index 0000000000..4d482b579c --- /dev/null +++ b/lib/colvars/colvarbias_meta.cpp @@ -0,0 +1,1639 @@ +#include +#include +#include +#include +#include +#include + +// used to set the absolute path of a replica file +#if defined (WIN32) && !defined(__CYGWIN__) +#include +#define CHDIR ::_chdir +#define GETCWD ::_getcwd +#define PATHSEP "\\" +#else +#include +#define CHDIR ::chdir +#define GETCWD ::getcwd +#define PATHSEP "/" +#endif + + +#include "colvar.h" +#include "colvarbias_meta.h" + + +colvarbias_meta::colvarbias_meta() + : colvarbias(), + state_file_step (0), + new_hills_begin (hills.end()) +{ +} + + +colvarbias_meta::colvarbias_meta (std::string const &conf, char const *key) + : colvarbias (conf, key), + state_file_step (0), + new_hills_begin (hills.end()) +{ + if (cvm::n_abf_biases > 0) + cvm::log ("Warning: ABF and metadynamics running at the " + "same time can lead to inconsistent results.\n"); + + get_keyval (conf, "hillWeight", hill_weight, 0.01); + if (hill_weight == 0.0) + cvm::log ("Warning: hillWeight has been set to zero, " + "this bias will have no effect.\n"); + + get_keyval (conf, "newHillFrequency", new_hill_freq, 1000); + + get_keyval (conf, "hillWidth", hill_width, std::sqrt (2.0 * PI) / 2.0); + + get_keyval (conf, "useGrids", use_grids, true); + + if (use_grids) { + get_keyval (conf, "gridsUpdateFrequency", grids_freq, new_hill_freq); + get_keyval (conf, "rebinGrids", rebin_grids, false); + + expand_grids = false; + for (size_t i = 0; i < colvars.size(); i++) { + if (colvars[i]->expand_boundaries) { + expand_grids = true; + cvm::log ("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": Will expand grids when the colvar \""+ + colvars[i]->name+"\" approaches its boundaries.\n"); + } + } + + get_keyval (conf, "keepHills", keep_hills, false); + get_keyval (conf, "dumpFreeEnergyFile", dump_fes, true); + get_keyval (conf, "saveFreeEnergyFile", dump_fes_save, false); + + for (size_t i = 0; i < colvars.size(); i++) { + colvars[i]->enable (colvar::task_grid); + } + + hills_energy = new colvar_grid_scalar (colvars); + hills_energy_gradients = new colvar_grid_gradient (colvars); + } else { + rebin_grids = false; + keep_hills = false; + dump_fes = false; + dump_fes_save = false; + dump_replica_fes = false; + } + + { + bool b_replicas = false; + get_keyval (conf, "multipleReplicas", b_replicas, false); + if (b_replicas) + comm = multiple_replicas; + else + comm = single_replica; + } + + if (comm != single_replica) { + + if (expand_grids) + cvm::fatal_error ("Error: expandBoundaries is not supported when " + "using more than one replicas; please allocate " + "wide enough boundaries for each colvar" + "ahead of time.\n"); + + if (get_keyval (conf, "dumpPartialFreeEnergyFile", dump_replica_fes, false)) { + if (dump_replica_fes && (! dump_fes)) { + cvm::log ("Enabling \"dumpFreeEnergyFile\".\n"); + } + } + + get_keyval (conf, "replicaID", replica_id, std::string ("")); + if (!replica_id.size()) + cvm::fatal_error ("Error: replicaID must be defined " + "when using more than one replica.\n"); + + get_keyval (conf, "replicasRegistry", + replicas_registry_file, + (this->name+".replicas.registry.txt")); + + get_keyval (conf, "replicaUpdateFrequency", + replica_update_freq, new_hill_freq); + + if (keep_hills) + cvm::log ("Warning: in metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": keepHills with more than one replica can lead to a very " + "large amount input/output and slow down your calculations. " + "Please consider disabling it.\n"); + + + { + // TODO: one may want to specify the path manually for intricated filesystems? + char *pwd = new char[3001]; + if (GETCWD (pwd, 3000) == NULL) + cvm::fatal_error ("Error: cannot get the path of the current working directory.\n"); + 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.good()) { // 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 + replica_hills_os.open (replica_hills_file.c_str()); + if (!replica_hills_os.good()) + cvm::fatal_error ("Error: in opening file \""+ + replica_hills_file+"\" for writing.\n"); + replica_hills_os.setf (std::ios::scientific, std::ios::floatfield); + + // 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::ofstream list_os (replica_list_file.c_str(), + (use_grids ? std::ios::trunc : std::ios::app)); + if (! list_os.good()) + cvm::fatal_error ("Error: in opening file \""+ + replica_list_file+"\" for writing.\n"); + list_os << "stateFile " << replica_state_file << "\n"; + list_os << "hillsFile " << replica_hills_file << "\n"; + list_os.close(); + + // finally, if 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.good()) + cvm::fatal_error ("Error: in opening file \""+ + replicas_registry_file+"\" for writing.\n"); + reg_os << replica_id << " " << replica_list_file << "\n"; + reg_os.close(); + } + } + + get_keyval (conf, "writeHillsTrajectory", b_hills_traj, false); + if (b_hills_traj) { + std::string const traj_file_name (cvm::output_prefix+ + ".colvars."+this->name+ + ( (comm != single_replica) ? + ("."+replica_id) : + ("") )+ + ".hills.traj"); + hills_traj_os.open (traj_file_name.c_str()); + if (!hills_traj_os.good()) + cvm::fatal_error ("Error: in opening hills output file \"" + + traj_file_name + "\".\n"); + } + + if (cvm::debug()) + cvm::log ("Done initializing the metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+".\n"); + + save_delimiters = false; +} + + +colvarbias_meta::~colvarbias_meta() +{ + if (hills_energy) { + delete hills_energy; + hills_energy = NULL; + } + + if (hills_energy_gradients) { + delete hills_energy_gradients; + hills_energy_gradients = NULL; + } + + if (replica_hills_os.good()) + replica_hills_os.close(); + + if (hills_traj_os.good()) + hills_traj_os.close(); +} + + + +// ********************************************************************** +// Hill management member functions +// ********************************************************************** + +std::list::const_iterator +colvarbias_meta::create_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); + if (min_dist < (3.0 * std::floor (hill_width)) + 1.0) { + hills_off_grid.push_back (h); + } + } + + // output to trajectory (if specified) + if (hills_traj_os.good()) { + hills_traj_os << (hills.back()).output_traj(); + if (cvm::debug()) { + hills_traj_os.flush(); + } + } + + 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.size()) { + for (hill_iter hoff = hills_off_grid.begin(); + hoff != hills_off_grid.end(); hoff++) { + if (*h == *hoff) { + hills_off_grid.erase (hoff); + break; + } + } + } + + if (hills_traj_os.good()) { + // output to the trajectory + hills_traj_os << "# DELETED this hill: " + << (hills.back()).output_traj() + << "\n"; + if (cvm::debug()) + hills_traj_os.flush(); + } + + return hills.erase (h); +} + + +cvm::real colvarbias_meta::update() +{ + if (cvm::debug()) + cvm::log ("Updating the metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+".\n"); + + if (use_grids) { + + std::vector curr_bin = hills_energy->get_colvars_index(); + + if (expand_grids) { + + // first of all, expand the grids, if specified + 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"); + + bool changed_grids = false; + int const min_buffer = + (3 * (size_t) std::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 < colvars.size(); i++) { + + if (! colvars[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 (curr_bin[i] < min_buffer) { + int const extra_points = (min_buffer - curr_bin[i]); + new_lb -= extra_points * colvars[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 \""+ + colvars[i]->name+"\", at "+ + cvm::to_str (new_lower_boundaries[i])+".\n"); + } + + 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 * colvars[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 \""+ + colvars[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->create (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->create (new_sizes, 0.0, colvars.size()); + + 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"); + } + } + } + + // add a new hill if the required time interval has passed + if ((cvm::step_absolute() % new_hill_freq) == 0) { + + 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"); + + switch (comm) { + + case single_replica: + create_hill (hill (hill_weight, colvars, hill_width)); + break; + + case multiple_replicas: + create_hill (hill (hill_weight, colvars, hill_width, replica_id)); + if (replica_hills_os.good()) { + replica_hills_os << hills.back(); + } else { + cvm::fatal_error ("Error: in metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + " while writing hills for the other replicas.\n"); + } + break; + } + } + + // sync with the other replicas (if needed) + if (comm != single_replica) { + + // reread the replicas registry + if ((cvm::step_absolute() % replica_update_freq) == 0) { + update_replicas_registry(); + // empty the output buffer + replica_hills_os.flush(); + + read_replica_files(); + } + } + + // calculate the biasing energy and forces + bias_energy = 0.0; + for (size_t ir = 0; ir < colvars.size(); ir++) { + colvar_forces[ir].reset(); + } + if (comm == multiple_replicas) + for (size_t ir = 0; ir < replicas.size(); ir++) { + replicas[ir]->bias_energy = 0.0; + for (size_t ic = 0; ic < colvars.size(); ic++) { + replicas[ir]->colvar_forces[ic].reset(); + } + } + + if (use_grids) { + + // get the forces from the grid + + 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(); + } + } + } + + 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 (hills_energy->index_ok (curr_bin)) { + + // within the grid: add the energy and the forces from there + + bias_energy += hills_energy->value (curr_bin); + for (size_t ic = 0; ic < colvars.size(); ic++) { + cvm::real const *f = &(hills_energy_gradients->value (curr_bin)); + colvar_forces[ic].real_value += -1.0 * f[ic]; + // the gradients are stored, not the forces + } + if (comm == multiple_replicas) + for (size_t ir = 0; ir < replicas.size(); ir++) { + bias_energy += replicas[ir]->hills_energy->value (curr_bin); + cvm::real const *f = &(replicas[ir]->hills_energy_gradients->value (curr_bin)); + for (size_t ic = 0; ic < colvars.size(); ic++) { + colvar_forces[ic].real_value += -1.0 * f[ic]; + } + } + + } else { + + // off the grid: compute analytically only the hills at the grid's edges + + calc_hills (hills_off_grid.begin(), hills_off_grid.end(), bias_energy); + for (size_t ic = 0; ic < colvars.size(); ic++) { + calc_hills_force (ic, hills_off_grid.begin(), hills_off_grid.end(), colvar_forces); + } + + if (comm == multiple_replicas) + for (size_t ir = 0; ir < replicas.size(); ir++) { + calc_hills (replicas[ir]->hills_off_grid.begin(), + replicas[ir]->hills_off_grid.end(), + bias_energy); + for (size_t ic = 0; ic < colvars.size(); ic++) { + calc_hills_force (ic, + replicas[ir]->hills_off_grid.begin(), + replicas[ir]->hills_off_grid.end(), + colvar_forces); + } + } + } + } + + // now include the hills that have not been binned yet (starting + // from new_hills_begin) + + calc_hills (new_hills_begin, hills.end(), bias_energy); + for (size_t ic = 0; ic < colvars.size(); ic++) { + calc_hills_force (ic, new_hills_begin, hills.end(), colvar_forces); + } + + if (cvm::debug()) + cvm::log ("Hills energy = "+cvm::to_str (bias_energy)+ + ", hills forces = "+cvm::to_str (colvar_forces)+".\n"); + + if (cvm::debug()) + cvm::log ("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": adding the forces from the other replicas.\n"); + + if (comm == multiple_replicas) + for (size_t ir = 0; ir < replicas.size(); ir++) { + calc_hills (replicas[ir]->new_hills_begin, + replicas[ir]->hills.end(), + bias_energy); + for (size_t ic = 0; ic < colvars.size(); ic++) { + calc_hills_force (ic, + replicas[ir]->new_hills_begin, + replicas[ir]->hills.end(), + colvar_forces); + } + if (cvm::debug()) + cvm::log ("Hills energy = "+cvm::to_str (bias_energy)+ + ", hills forces = "+cvm::to_str (colvar_forces)+".\n"); + } + + return bias_energy; +} + + +void colvarbias_meta::calc_hills (colvarbias_meta::hill_iter h_first, + colvarbias_meta::hill_iter h_last, + cvm::real &energy, + std::vector const &colvar_values) +{ + std::vector curr_values (colvars.size()); + for (size_t i = 0; i < colvars.size(); i++) { + curr_values[i].type (colvars[i]->type()); + } + + if (colvar_values.size()) { + for (size_t i = 0; i < colvars.size(); i++) { + curr_values[i] = colvar_values[i]; + } + } else { + for (size_t i = 0; i < colvars.size(); i++) { + curr_values[i] = colvars[i]->value(); + } + } + + for (hill_iter h = h_first; h != h_last; h++) { + + // compute the gaussian exponent + cvm::real cv_sqdev = 0.0; + for (size_t i = 0; i < colvars.size(); i++) { + colvarvalue const &x = curr_values[i]; + colvarvalue const ¢er = h->centers[i]; + cvm::real const half_width = 0.5 * h->widths[i]; + cv_sqdev += (colvars[i]->dist2 (x, center)) / (half_width*half_width); + } + + // compute the gaussian + if (cv_sqdev > 23.0) { + // set it to zero if the exponent is more negative than log(1.0E-05) + h->value (0.0); + } else { + h->value (std::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 x (values.size() ? values[i].type() : colvars[i]->type()); + x = (values.size() ? values[i] : colvars[i]->value()); + + // do the type check only once (all colvarvalues in the hills series + // were already saved with their types matching those in the + // colvars) + + switch (colvars[i]->type()) { + + case colvarvalue::type_scalar: + for (hill_iter h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const half_width = 0.5 * h->widths[i]; + forces[i].real_value += + ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * + (colvars[i]->dist2_lgrad (x, center)).real_value ); + } + break; + + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + for (hill_iter h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const half_width = 0.5 * h->widths[i]; + forces[i].rvector_value += + ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * + (colvars[i]->dist2_lgrad (x, center)).rvector_value ); + } + break; + + case colvarvalue::type_quaternion: + for (hill_iter h = h_first; h != h_last; h++) { + if (h->value() == 0.0) continue; + colvarvalue const ¢er = h->centers[i]; + cvm::real const half_width = 0.5 * h->widths[i]; + forces[i].quaternion_value += + ( h->weight() * h->value() * (0.5 / (half_width*half_width)) * + (colvars[i]->dist2_lgrad (x, center)).quaternion_value ); + } + break; + + case colvarvalue::type_notset: + 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) +{ + 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 colvar_values (colvars.size()); + std::vector colvar_forces_scalar (colvars.size()); + + 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 (colvars.size(), 0.0); + + if (hg != NULL) { + + // loop over the points of the grid + for ( ; + (he->index_ok (he_ix)) && (hg->index_ok (hg_ix)); + ) { + + for (size_t i = 0; i < colvars.size(); i++) { + colvar_values[i] = hills_energy->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, colvar_values); + he->acc_value (he_ix, hills_energy_here); + + for (size_t i = 0; i < colvars.size(); i++) { + hills_forces_here[i].reset(); + calc_hills_force (i, h_first, h_last, hills_forces_here, 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); + } + + } else { + + // simpler version, with just the energy + + for ( ; (he->index_ok (he_ix)); ) { + + for (size_t i = 0; i < colvars.size(); i++) { + colvar_values[i] = hills_energy->bin_to_value_scalar (he_ix[i], i); + } + + hills_energy_here = 0.0; + calc_hills (h_first, h_last, hills_energy_here, colvar_values); + he->acc_value (he_ix, hills_energy_here); + + he->incr (he_ix); + } + } + + 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); + if (min_dist < (3.0 * std::floor (hill_width)) + 1.0) { + hills_off_grid.push_back (*h); + } + } +} + + + +// ********************************************************************** +// multiple replicas functions +// ********************************************************************** + + +void colvarbias_meta::update_replicas_registry() +{ + 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.good()) { + replicas_registry.clear(); + while (colvarparse::getline_nocomments (reg_file, line)) + replicas_registry.append (line+"\n"); + } else { + cvm::fatal_error ("Error: failed to open file \""+replicas_registry_file+ + "\" for reading.\n"); + } + } + + // 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 + if (cvm::debug()) + cvm::log ("Metadynamics bias \""+this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": skipping this replica's own record: \""+ + new_replica+"\", \""+new_replica_file+"\"\n"); + 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()); + (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); + } + } + } + + // continue the cycle + new_replica_file = ""; + new_replica = ""; + } else { + cvm::fatal_error ("Error: cannot read the replicas registry file \""+ + replicas_registry+"\".\n"); + } + + // 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 { + (replicas[ir])->update_status = 0; + 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"); +} + + +void colvarbias_meta::read_replica_files() +{ + for (size_t ir = 0; ir < replicas.size(); ir++) { + + 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; + } + + // (re)read the state file if necessary + if ( (! (replicas[ir])->has_data) || + (! (replicas[ir])->replica_state_file_in_sync) ) { + + 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_restart (is)) { + cvm::log ("Reading from file \""+(replicas[ir])->replica_state_file+ + "\" failed or incomplete: will try again in "+ + cvm::to_str (replica_update_freq)+" steps.\n"); + } else { + // state file has been read successfully + (replicas[ir])->replica_state_file_in_sync = true; + (replicas[ir])->update_status = 0; + } + is.close(); + } + + // 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; for each file, resume + // the position recorded previously + + std::ifstream is ((replicas[ir])->replica_hills_file.c_str()); + if (is.good()) { + + // try to resume the previous position + is.seekg ((replicas[ir])->replica_hills_file_pos, std::ios::beg); + if (!is.good()){ + // 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)) { + // if (cvm::debug()) + 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+1) { + (replicas[ir])->update_status++; + } else { + (replicas[ir])->update_status = 0; + } + } + + } else { + cvm::fatal_error ("Error: cannot read from file \""+ + (replicas[ir])->replica_hills_file+"\".\n"); + } + 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: in metadynamics bias \""+this->name+"\""+ + ": failet do read completely output files from replica \""+ + (replicas[ir])->replica_id+ + "\" after more than "+ + cvm::to_str (n_flush*new_hill_freq)+ + " steps. Please check that that simulation is still running.\n"); + } + } +} + + +// ********************************************************************** +// input functions +// ********************************************************************** + + +std::istream & colvarbias_meta::read_restart (std::istream& is) +{ + size_t const start_pos = is.tellg(); + + if (comm == single_replica) { + // if using a multiple replicas scheme, output messages + // are printed before and after calling this function + cvm::log ("Restarting metadynamics bias \""+this->name+"\""+ + ".\n"); + } + std::string key, brace, conf; + if ( !(is >> key) || !(key == "metadynamics") || + !(is >> brace) || !(brace == "{") || + !(is >> colvarparse::read_block ("configuration", conf)) ) { + + if (comm == single_replica) + cvm::log ("Error: in reading restart configuration for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + (replica_state_file_in_sync ? ("at position "+ + cvm::to_str (start_pos)+ + " in the state file") : "")+".\n"); + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + std::string name = ""; + if ( colvarparse::get_keyval (conf, "name", name, + std::string (""), colvarparse::parse_silent) && + (name != this->name) ) + cvm::fatal_error ("Error: in the restart file, the " + "\"metadynamics\" block has a different name: different system?\n"); + + if (name.size() == 0) { + cvm::fatal_error ("Error: \"metadynamics\" block within the restart file " + "has no identifiers.\n"); + } + + if (comm != single_replica) { + std::string replica = ""; + if (colvarparse::get_keyval (conf, "replicaID", replica, + std::string (""), colvarparse::parse_silent) && + (replica != this->replica_id)) + cvm::fatal_error ("Error: in the restart file, the " + "\"metadynamics\" block has a different replicaID: different system?\n"); + + colvarparse::get_keyval (conf, "step", state_file_step, + cvm::step_absolute(), colvarparse::parse_silent); + } + + bool grids_from_restart_file = use_grids; + + if (use_grids) { + + if (expand_grids) { + // the boundaries of the colvars may have been changed; TODO: + // this reallocation is only for backward-compatibility, and may + // be deleted when grid_parameters (i.e. colvargrid's own + // internal reallocation) has kicked in + delete hills_energy; + delete hills_energy_gradients; + hills_energy = new colvar_grid_scalar (colvars); + hills_energy_gradients = new colvar_grid_gradient (colvars); + } + + 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); + } + + size_t const hills_energy_pos = is.tellg(); + if (!(is >> key)) { + if (hills_energy_backup != NULL) { + delete hills_energy; + delete hills_energy_gradients; + hills_energy = hills_energy_backup; + hills_energy_gradients = hills_energy_gradients_backup; + } + is.clear(); + is.seekg (hills_energy_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } else if (!(key == std::string ("hills_energy")) || + !(hills_energy->read_restart (is))) { + is.clear(); + is.seekg (hills_energy_pos, std::ios::beg); + grids_from_restart_file = false; + if (!rebin_grids) { + if (hills_energy_backup == NULL) + cvm::fatal_error ("Error: couldn't read the free energy grid for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + "; if useGrids was off when the state file was written, " + "enable rebinGrids now to regenerate the grids.\n"); + else { + if (comm == single_replica) + cvm::log ("Error: couldn't read the free energy grid for metadynamics bias \""+ + this->name+"\".\n"); + delete hills_energy; + delete hills_energy_gradients; + hills_energy = hills_energy_backup; + hills_energy_gradients = hills_energy_gradients_backup; + is.setstate (std::ios::failbit); + return is; + } + } + } + + size_t const hills_energy_gradients_pos = is.tellg(); + if (!(is >> key)) { + if (hills_energy_backup != NULL) { + delete hills_energy; + delete hills_energy_gradients; + hills_energy = hills_energy_backup; + hills_energy_gradients = hills_energy_gradients_backup; + } + is.clear(); + is.seekg (hills_energy_gradients_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } else if (!(key == std::string ("hills_energy_gradients")) || + !(hills_energy_gradients->read_restart (is))) { + is.clear(); + is.seekg (hills_energy_gradients_pos, std::ios::beg); + grids_from_restart_file = false; + if (hills_energy_backup == NULL) { + if (!rebin_grids) + cvm::fatal_error ("Error: couldn't read the free energy gradients grid for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + "; if useGrids was off when the state file was written, " + "enable rebinGrids now to regenerate the grids.\n"); + else { + if (comm == single_replica) + cvm::log ("Error: couldn't read the free energy gradients grid for metadynamics bias \""+ + this->name+"\".\n"); + delete hills_energy; + delete hills_energy_gradients; + hills_energy = hills_energy_backup; + hills_energy_gradients = hills_energy_gradients_backup; + is.setstate (std::ios::failbit); + return is; + } + } + } + + if (cvm::debug()) + cvm::log ("Successfully read new grids for bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+"\n"); + + if (hills_energy_backup != NULL) { + // now that we have successfully updated the grids, delete the + // backup copies + if (cvm::debug()) + cvm::log ("Deallocating the older grids.\n"); + + delete hills_energy_backup; + delete hills_energy_gradients_backup; + } + } + + bool const existing_hills = (hills.size() > 0); + 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(); + + // read the hills explicitly written (if there are 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)+".\n"); + } + is.clear(); + new_hills_begin = hills.end(); + if (grids_from_restart_file) { + if (hills.size() > old_hills_size) + cvm::log ("Read "+cvm::to_str (hills.size())+ + " hills in addition to the grids.\n"); + } else { + if (hills.size()) + cvm::log ("Read "+cvm::to_str (hills.size())+" hills.\n"); + } + + 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 (!grids_from_restart_file || (keep_hills && hills.size())) { + // 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); + 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.size()) + recount_hills_off_grid (hills.begin(), hills.end(), hills_energy); + } + + if (use_grids) { + if (hills_off_grid.size()) { + 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"); + } + } + + is >> brace; + if (brace != "}") { + cvm::log ("Incomplete restart information for metadynamics bias \""+ + this->name+"\""+ + ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ + ": no closing brace at position "+ + cvm::to_str (is.tellg())+" in the file.\n"); + is.setstate (std::ios::failbit); + return is; + } + + if (cvm::debug()) + cvm::log ("colvarbias_meta::read_restart() done\n"); + + if (existing_hills) { + hills.erase (hills.begin(), old_hills_end); + hills_off_grid.erase (hills_off_grid.begin(), old_hills_off_grid_end); + } + + has_data = true; + + if (comm != single_replica) { + read_replica_files(); + } + + return is; +} + + +std::istream & colvarbias_meta::read_hill (std::istream &is) +{ + if (!is) return is; // do nothing if failbit is set + + size_t const start_pos = is.tellg(); + + std::string data; + if ( !(is >> read_block ("hill", data)) ) { + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + size_t h_it; + get_keyval (data, "step", h_it, 0, parse_silent); + if (h_it <= state_file_step) { + 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; + } + + cvm::real h_weight; + get_keyval (data, "weight", h_weight, hill_weight, parse_silent); + + std::vector h_centers (colvars.size()); + for (size_t i = 0; i < colvars.size(); i++) { + h_centers[i].type ((colvars[i]->value()).type()); + } + { + // it is safer to read colvarvalue objects one at a time; + // TODO: change this it later + std::string centers_input; + key_lookup (data, "centers", centers_input); + std::istringstream centers_is (centers_input); + for (size_t i = 0; i < colvars.size(); i++) { + centers_is >> h_centers[i]; + } + } + + std::vector h_widths (colvars.size()); + get_keyval (data, "widths", h_widths, + std::vector (colvars.size(), (std::sqrt (2.0 * PI) / 2.0)), + parse_silent); + + std::string h_replica = ""; + if (comm != single_replica) { + get_keyval (data, "replicaID", h_replica, replica_id, parse_silent); + if (h_replica != replica_id) + cvm::fatal_error ("Error: trying to read a hill created by replica \""+h_replica+ + "\" for replica \""+replica_id+ + "\"; did you swap output files?\n"); + } + + hill_iter const hills_end = hills.end(); + hills.push_back (hill (h_it, h_weight, h_centers, h_widths, 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); + if (min_dist < (3.0 * std::floor (hill_width)) + 1.0) { + hills_off_grid.push_back (hills.back()); + } + } + + has_data = true; + return is; +} + + + + +// ********************************************************************** +// output functions +// ********************************************************************** + +std::ostream & colvarbias_meta::write_restart (std::ostream& os) +{ + os << "metadynamics {\n" + << " configuration {\n" + << " step " << cvm::step_absolute() << "\n" + << " name " << this->name << "\n"; + if (this->comm != single_replica) + os << " replicaID " << this->replica_id << "\n"; + os << " }\n\n"; + + 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 + os << " hills_energy\n"; + hills_energy->write_restart (os); + os << " hills_energy_gradients\n"; + 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++) { + 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++) { + os << *h; + } + } + + os << "}\n\n"; + + if (comm != single_replica) { + write_replica_state_file(); + // schedule to reread the state files of the other replicas (they + // have also rewritten them) + for (size_t ir = 0; ir < replicas.size(); ir++) { + (replicas[ir])->replica_state_file_in_sync = false; + } + } + + if (dump_fes) { + write_pmf(); + } + + return os; +} + + +void colvarbias_meta::write_pmf() +{ + // allocate a new grid to store the pmf + colvar_grid_scalar *pmf = new colvar_grid_scalar (colvars); + + std::string fes_file_name_prefix (cvm::output_prefix); + + if ((cvm::n_meta_biases > 1) || (cvm::n_abf_biases > 0)) { + // 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 + // TODO: fix for ABF with updateBias == no + fes_file_name_prefix += ("."+this->name); + } + + if ((comm == single_replica) || (dump_replica_fes)) { + // output the PMF from this instance or replica + pmf->reset(); + pmf->add_grid (*hills_energy); + cvm::real const max = pmf->maximum_value(); + pmf->add_constant (-1.0 * max); + pmf->multiply_constant (-1.0); + std::string const fes_file_name (fes_file_name_prefix + + ((comm != single_replica) ? ".partial" : "") + + (dump_fes_save ? + "."+cvm::to_str (cvm::step_absolute()) : "") + + ".pmf"); + cvm::backup_file (fes_file_name.c_str()); + std::ofstream fes_os (fes_file_name.c_str()); + pmf->write_multicol (fes_os); + fes_os.close(); + } + + if (comm != single_replica) { + // output the combined PMF from all replicas + pmf->reset(); + pmf->add_grid (*hills_energy); + for (size_t ir = 0; ir < replicas.size(); ir++) { + pmf->add_grid (*(replicas[ir]->hills_energy)); + } + cvm::real const max = pmf->maximum_value(); + pmf->add_constant (-1.0 * max); + pmf->multiply_constant (-1.0); + std::string const fes_file_name (fes_file_name_prefix + + (dump_fes_save ? + "."+cvm::to_str (cvm::step_absolute()) : "") + + ".pmf"); + cvm::backup_file (fes_file_name.c_str()); + std::ofstream fes_os (fes_file_name.c_str()); + pmf->write_multicol (fes_os); + fes_os.close(); + } + + delete pmf; +} + + + +void colvarbias_meta::write_replica_state_file() +{ + // write down also the restart for the other replicas: TODO: this + // is duplicated code, that could be cleaned up later + cvm::backup_file (replica_state_file.c_str()); + std::ofstream rep_state_os (replica_state_file.c_str()); + if (!rep_state_os.good()) + cvm::fatal_error ("Error: in opening file \""+ + replica_state_file+"\" for writing.\n"); + + rep_state_os.setf (std::ios::scientific, std::ios::floatfield); + rep_state_os << "\n" + << "metadynamics {\n" + << " configuration {\n" + << " name " << this->name << "\n" + << " step " << cvm::step_absolute() << "\n"; + if (this->comm != single_replica) { + rep_state_os << " replicaID " << this->replica_id << "\n"; + } + rep_state_os << " }\n\n"; + rep_state_os << " hills_energy\n"; + rep_state_os << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width); + hills_energy->write_restart (rep_state_os); + rep_state_os << " hills_energy_gradients\n"; + rep_state_os << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width); + hills_energy_gradients->write_restart (rep_state_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++) { + rep_state_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++) { + rep_state_os << *h; + } + } + + rep_state_os << "}\n\n"; + rep_state_os.close(); + + // reopen the hills file + replica_hills_os.close(); + replica_hills_os.open (replica_hills_file.c_str()); + if (!replica_hills_os.good()) + cvm::fatal_error ("Error: in opening file \""+ + replica_hills_file+"\" for writing.\n"); + replica_hills_os.setf (std::ios::scientific, std::ios::floatfield); +} + +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); + + os << " "; + for (size_t i = 0; i < centers.size(); i++) { + os << " "; + os << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) << centers[i]; + } + + os << " "; + for (size_t i = 0; i < widths.size(); i++) { + os << " "; + os << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) << widths[i]; + } + + os << " "; + os << std::setprecision (cvm::en_prec) + << std::setw (cvm::en_width) << W << "\n"; + + return os.str(); +} + + +std::ostream & operator << (std::ostream &os, colvarbias_meta::hill const &h) +{ + os.setf (std::ios::scientific, std::ios::floatfield); + + os << "hill {\n"; + os << " step " << std::setw (cvm::it_width) << h.it << "\n"; + os << " weight " + << std::setprecision (cvm::en_prec) + << std::setw (cvm::en_width) + << h.W << "\n"; + + if (h.replica.size()) + os << " replicaID " << h.replica << "\n"; + + os << " centers "; + for (size_t i = 0; i < (h.centers).size(); i++) { + os << " " + << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) + << h.centers[i]; + } + os << "\n"; + + os << " widths "; + for (size_t i = 0; i < (h.widths).size(); i++) { + os << " " + << std::setprecision (cvm::cv_prec) + << std::setw (cvm::cv_width) + << h.widths[i]; + } + os << "\n"; + + os << "}\n"; + + return os; +} diff --git a/lib/colvars/colvarbias_meta.h b/lib/colvars/colvarbias_meta.h new file mode 100644 index 0000000000..6e3adb1c54 --- /dev/null +++ b/lib/colvars/colvarbias_meta.h @@ -0,0 +1,418 @@ +#ifndef COLVARBIAS_META_H +#define COLVARBIAS_META_H + +#include +#include +#include +#include + +#include "colvarbias.h" +#include "colvargrid.h" + +/// Metadynamics bias (implementation of \link colvarbias \endlink) +class colvarbias_meta : public colvarbias { + +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; + + /// Constructor + colvarbias_meta (std::string const &conf, char const *key); + + /// Default constructor + colvarbias_meta(); + + /// Destructor + virtual ~colvarbias_meta(); + + virtual cvm::real update(); + + virtual std::istream & read_restart (std::istream &is); + + virtual std::ostream & write_restart (std::ostream &os); + + virtual void write_pmf(); + + class hill; + typedef std::list::iterator hill_iter; + +protected: + + /// \brief width of a hill + /// + /// The local width of each collective variable, multiplied by this + /// number, provides the hill width along that direction + cvm::real hill_width; + + /// \brief Number of simulation steps between two hills + size_t new_hill_freq; + + /// Write the hill logfile + bool b_hills_traj; + /// Logfile of hill management (creation and deletion) + std::ofstream hills_traj_os; + + /// \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); + + /// Read a hill from a file + std::istream & read_hill (std::istream &is); + + /// \brief step present in a state file + /// + /// When using grids and reading state files containing them + /// (multiple replicas), this is used to check whether a hill is + /// newer or older than the grids + size_t state_file_step; + + /// \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 + virtual std::list::const_iterator create_hill (hill const &h); + + /// \brief Remove a previously saved hill (returns an iterator for + /// the next hill in the list) + virtual 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 = std::vector (0)); + + /// \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 = std::vector (0)); + + + /// 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; + + /// \brief Whether to keep the hills in the restart file (e.g. to do + /// meaningful accurate rebinning afterwards) + bool 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 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); + + + // 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 void update_replicas_registry(); + + /// \brief Read new data from replicas' files + virtual void read_replica_files(); + + /// \brief Write data to other replicas + virtual void write_replica_state_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; + + /// \brief Output stream corresponding to replica_hills_file + std::ofstream replica_hills_os; + + /// Position within replica_hills_file (when reading it) + size_t replica_hills_file_pos; + +}; + + + + +/// \brief A hill for the metadynamics bias +class colvarbias_meta::hill { + +protected: + + /// 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; + + /// Center of the hill in the collective variable space + std::vector centers; + + /// Widths of the hill in the collective variable space + std::vector widths; + +public: + + friend class colvarbias_meta; + + /// Time step at which this hill was added + size_t it; + + /// Identity of the replica who added this hill (only in multiple replica simulations) + std::string replica; + + /// \brief Runtime constructor: data are read directly from + /// collective variables \param weight Weight of the hill \param + /// cv Pointer to the array of collective variables involved \param + /// replica (optional) Identity of the replica which creates the + /// hill + inline hill (cvm::real const &W_in, + std::vector &cv, + cvm::real const &hill_width, + std::string const &replica_in = "") + : sW (1.0), + W (W_in), + centers (cv.size()), + widths (cv.size()), + it (cvm::it), + replica (replica_in) + { + for (size_t i = 0; i < cv.size(); i++) { + centers[i].type (cv[i]->type()); + centers[i] = cv[i]->value(); + widths[i] = cv[i]->width * hill_width; + } + if (cvm::debug()) + cvm::log ("New hill, applied to "+cvm::to_str (cv.size())+ + " collective variables, with centers "+ + cvm::to_str (centers)+", widths "+ + cvm::to_str (widths)+" and weight "+ + cvm::to_str (W)+".\n"); + } + + /// \brief General constructor: all data are explicitly passed as + /// arguments (used for instance when reading hills saved on a + /// file) \param it Time step of creation of the hill \param + /// weight Weight of the hill \param centers Center of the hill + /// \param widths Width of the hill around centers \param replica + /// (optional) Identity of the replica which creates the hill + inline hill (size_t const &it_in, + cvm::real const &W_in, + std::vector const ¢ers_in, + std::vector const &widths_in, + std::string const &replica_in = "") + : sW (1.0), + W (W_in), + centers (centers_in), + widths (widths_in), + it (it_in), + replica (replica_in) + {} + + /// Copy constructor + inline hill (colvarbias_meta::hill const &h) + : sW (1.0), + W (h.W), + centers (h.centers), + widths (h.widths), + it (h.it), + replica (h.replica) + {} + + /// Destructor + inline ~hill() + {} + + /// 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(); + + /// Write the hill to an output stream + inline friend std::ostream & operator << (std::ostream &os, + hill const &h); + +}; + + +#endif + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarcomp.cpp b/lib/colvars/colvarcomp.cpp new file mode 100644 index 0000000000..5490f697ae --- /dev/null +++ b/lib/colvars/colvarcomp.cpp @@ -0,0 +1,91 @@ +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvar.h" +#include "colvarcomp.h" + + + +colvar::cvc::cvc() + : sup_coeff (1.0), sup_np (1), + b_periodic (false), + b_debug_gradients (false), + b_inverse_gradients (false), + b_Jacobian_derivative (false) +{} + + +colvar::cvc::cvc (std::string const &conf) + : sup_coeff (1.0), sup_np (1), + b_periodic (false), + b_debug_gradients (false), + b_inverse_gradients (false), + b_Jacobian_derivative (false) +{ + if (cvm::debug()) + cvm::log ("Initializing cvc base object.\n"); + + get_keyval (conf, "name", this->name, std::string (""), parse_silent); + + get_keyval (conf, "componentCoeff", sup_coeff, 1.0); + get_keyval (conf, "componentExp", sup_np, 1); + + get_keyval (conf, "period", period, 0.0); + get_keyval (conf, "wrapAround", wrap_center, 0.0); + + get_keyval (conf, "debugGradients", b_debug_gradients, false, parse_silent); + + if (cvm::debug()) + cvm::log ("Done initializing cvc base object.\n"); +} + + +void colvar::cvc::parse_group (std::string const &conf, + char const *group_key, + cvm::atom_group &group, + bool optional) +{ + if (key_lookup (conf, group_key)) { + group.parse (conf, group_key); + } else { + if (! optional) { + cvm::fatal_error ("Error: definition for atom group \""+ + std::string (group_key)+"\" not found.\n"); + } + } +} + + +colvar::cvc::~cvc() +{} + + +void colvar::cvc::calc_force_invgrads() +{ + cvm::fatal_error ("Error: calculation of inverse gradients is not implemented " + "for colvar components of type \""+function_type+"\".\n"); +} + + +void colvar::cvc::calc_Jacobian_derivative() +{ + cvm::fatal_error ("Error: calculation of inverse gradients is not implemented " + "for colvar components of type \""+function_type+"\".\n"); +} + + +colvarvalue colvar::cvc::fdiff_change (cvm::atom_group &group) +{ + colvarvalue change (x.type()); + + if (group.old_pos.size()) { + for (size_t i = 0; i < group.size(); i++) { + cvm::rvector const &pold = group.old_pos[i]; + cvm::rvector const &p = group[i].pos; + change += group[i].grad * (p - pold); + } + } + + // save for next step + group.old_pos = group.positions(); + return change; +} diff --git a/lib/colvars/colvarcomp.h b/lib/colvars/colvarcomp.h new file mode 100644 index 0000000000..fd58983c00 --- /dev/null +++ b/lib/colvars/colvarcomp.h @@ -0,0 +1,1413 @@ +#ifndef COLVARDEF_H +#define COLVARDEF_H + +#include +#include + + +#include "colvarmodule.h" +#include "colvar.h" +#include "colvaratoms.h" + + +/// \brief Colvar component (base class); most implementations of +/// \link cvc \endlink utilize one or more \link +/// colvarmodule::atom \endlink or \link colvarmodule::atom_group +/// \endlink objects to access atoms. +/// +/// A \link cvc \endlink object (or an object of a +/// cvc-derived class) specifies how to calculate a collective +/// variable, its gradients and other related physical quantities +/// which do not depend only on the numeric value (the \link colvar +/// \endlink class already serves this purpose). +/// +/// No restriction is set to what kind of calculation a \link +/// cvc \endlink object performs (usually calculate an +/// analytical function of atomic coordinates). The only constraint +/// is that the value calculated is implemented as a \link colvarvalue +/// \endlink object. This serves to provide a unique way to calculate +/// scalar and non-scalar collective variables, and specify if and how +/// they can be combined together by the parent \link colvar \endlink +/// object. +/// +/// If you wish to implement a new collective variable component, you +/// should write your own class by inheriting directly from \link +/// cvc \endlink, or one of its derived classes (for instance, +/// \link 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. add the name of this class under colvar.h \par +/// 2. add a call to the parser in colvar.C, within the function colvar::colvar() \par +/// 3. declare the class in colvarcomp.h \par +/// 4. implement the class in one of the files colvarcomp_*.C +/// +/// +/// The cvm::atom and cvm::atom_group classes are available to +/// transparently communicate with the simulation program. However, +/// they are not strictly needed, as long as all the degrees of +/// freedom associated to the cvc are properly evolved by a simple +/// call to e.g. apply_force(). + +class colvar::cvc + : public colvarparse +{ +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 cvc \endlink + /// objects are initialized; therefore the main "config string" + /// constructor does not need to define it. If a \link cvc + /// \endlink is initialized and/or a different constructor is used, + /// this variable definition should be set within the constructor. + std::string function_type; + + /// \brief Type of \link colvarvalue \endlink that this cvc + /// provides + colvarvalue::Type type() const; + + /// \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 this cvc value, (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; + + bool b_periodic; + + /// \brief Constructor + /// + /// At least one constructor which reads a string should be defined + /// for every class inheriting from cvc \param conf Contents + /// of the configuration file pertaining to this \link cvc + /// \endlink + cvc (std::string const &conf); + + /// \brief Within the constructor, make a group parse its own + /// options from the provided configuration string + void parse_group (std::string const &conf, + char const *group_key, + cvm::atom_group &group, + bool optional = false); + + /// \brief Default constructor (used when \link cvc \endlink + /// objects are declared within other ones) + cvc(); + + /// Destructor + virtual ~cvc(); + + /// \brief If true, calc_gradients() will calculate + /// finite-difference gradients alongside the analytical ones and + /// report their differences + bool b_debug_gradients; + + /// \brief When b_debug_gradients is true, this function can be used + /// to calculate the estimated change in the value using the change + /// in the atomic coordinates times the atomic gradients + colvarvalue fdiff_change (cvm::atom_group &group); + + /// \brief If this flag is false (default), inverse gradients + /// (derivatives of atom coordinates with respect to x) are + /// unavailable; it should be set to true by the constructor of each + /// derived object capable of calculating them + bool b_inverse_gradients; + + /// \brief If this flag is false (default), the Jacobian derivative + /// (divergence of the inverse gradients) is unavailable; it should + /// be set to true by the constructor of each derived object capable + /// of calculating it + bool b_Jacobian_derivative; + + /// \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() = 0; + + /// \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 + virtual colvarvalue value() const; + + /// \brief Return the previously calculated system force + virtual colvarvalue system_force() const; + + /// \brief Return the previously calculated divergence of the + /// inverse atomic gradients + virtual colvarvalue 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 Return a positive number if x2>x1, zero if x2==x1, + /// negative otherwise (can be redefined to transparently implement + /// constraints, symmetries and periodicities) \b Note: \b it \b + /// only \b works \b with \b scalar \b variables, otherwise raises + /// an error + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; + + /// \brief Wrapp value (for periodic/symmetric cvcs) + virtual void wrap (colvarvalue &x) const; + + /// \brief Pointers to all atom groups, to let colvars collect info + /// e.g. atomic gradients + std::vector atom_groups; + +protected: + + /// \brief Cached value + colvarvalue x; + + /// \brief Value at the previous step + colvarvalue x_old; + + /// \brief Calculated system 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; +}; + + + + +inline colvarvalue::Type colvar::cvc::type() const +{ + return x.type(); +} + +inline colvarvalue colvar::cvc::value() const +{ + return x; +} + +inline colvarvalue colvar::cvc::system_force() const +{ + return ft; +} + +inline colvarvalue colvar::cvc::Jacobian_derivative() const +{ + return jd; +} + + +inline cvm::real colvar::cvc::dist2 (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.dist2 (x2); +} + +inline colvarvalue colvar::cvc::dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.dist2_grad (x2); +} + +inline colvarvalue colvar::cvc::dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x2.dist2_grad (x1); +} + +inline cvm::real colvar::cvc::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + if (this->type() == colvarvalue::type_scalar) { + return cvm::real (x1 - x2); + } else { + cvm::fatal_error ("Error: you requested an operation which requires " + "comparison between two non-scalar values.\n"); + return 0.0; + } +} + +inline void colvar::cvc::wrap (colvarvalue &x) const +{ + return; +} + + +/// \brief Colvar component: distance between the centers of mass of +/// two groups (colvarvalue::type_scalar type, range [0:*)) +/// +/// This class also serves as the template for many collective +/// variables with two atom groups: in this case, the +/// distance::distance() constructor should be called on the same +/// configuration string, to make the same member data and functions +/// available to the derived object +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; + /// Use absolute positions, ignoring PBCs when present + bool b_no_PBC; + /// Compute system force on first site only to avoid unwanted + /// coupling to other colvars (see e.g. Ciccotti et al., 2005) + bool b_1site_force; +public: + distance (std::string const &conf, bool twogroups = true); + distance(); + virtual inline ~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; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +// \brief Colvar component: distance vector between centers of mass +// of two groups (\link colvarvalue::type_vector \endlink type, +// range (-*:*)x(-*:*)x(-*:*)) +class colvar::distance_vec + : public colvar::distance +{ +public: + distance_vec (std::string const &conf); + distance_vec(); + virtual inline ~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; + /// Redefined to handle the box periodicity + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +/// \brief Colvar component: distance unit vector (direction) between +/// centers of mass of two groups (colvarvalue::type_unitvector 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 inline ~distance_dir() {} + 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; + virtual cvm::real compare (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; + /// Use absolute positions, ignoring PBCs when present + bool b_no_PBC; + /// Compute system force on one site only to avoid unwanted + /// coupling to other colvars (see e.g. Ciccotti et al., 2005) + bool b_1site_force; + /// 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 inline ~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; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; + /// \brief Redefined to make use of the user-provided period + virtual void wrap (colvarvalue &x) 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 inline ~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; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +/// \brief Colvar component: projection of the distance vector along +/// a fixed axis (colvarvalue::type_scalar type, range (-*:*)) +class colvar::min_distance + : public colvar::distance +{ +protected: + /// Components of the distance vector orthogonal to the axis + cvm::real smoothing; +public: + min_distance (std::string const &conf); + min_distance(); + virtual inline ~min_distance() {} + 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; + virtual cvm::real compare (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: + /// Constructor + gyration (std::string const &conf); + gyration(); + virtual inline ~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; + virtual cvm::real compare (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) + std::vector eigenvec; + + /// Inverse square norm of the eigenvector + cvm::real eigenvec_invnorm2; + +public: + + /// Constructor + eigenvector (std::string const &conf); + virtual inline ~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; + virtual cvm::real compare (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 system 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); + angle(); + virtual inline ~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; + virtual cvm::real compare (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 system 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 inline ~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 cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap (colvarvalue &x) const; +}; + + +/// \brief Colvar component: coordination number between two groups +/// (colvarvalue::type_scalar type, range [0:N1*N2]) +class colvar::coordnum + : 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; + /// \brief If true, group2 will be treated as a single atom + /// (default: loop over all pairs of atoms in group1 and group2) + bool b_group2_center_only; +public: + /// Constructor + coordnum (std::string const &conf); + coordnum(); + virtual inline ~coordnum() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force (colvarvalue const &force); + template + /// \brief Calculate a coordination number through the function + /// (1-x**n)/(1-x**m), x = |A1-A2|/r0 \param r0 "cutoff" for the + /// coordination number \param exp_num \i n exponent \param exp_den + /// \i m exponent \param A1 atom \param A2 atom + static cvm::real switching_function (cvm::real const &r0, + int const &exp_num, int const &exp_den, + cvm::atom &A1, cvm::atom &A2); + + template + /// \brief Calculate a coordination number through the function + /// (1-x**n)/(1-x**m), x = |(A1-A2)*(r0_vec)^-|1 \param r0_vec + /// vector of different cutoffs in the three directions \param + /// exp_num \i n exponent \param exp_den \i m exponent \param A1 + /// atom \param A2 atom + static cvm::real switching_function (cvm::rvector const &r0_vec, + int const &exp_num, int const &exp_den, + cvm::atom &A1, cvm::atom &A2); + + 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; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + +/// \brief Colvar component: self-coordination number within a group +/// (colvarvalue::type_scalar type, range [0:N*(N-1)/2]) +class colvar::selfcoordnum + : public colvar::distance +{ +protected: + /// \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; +public: + /// Constructor + selfcoordnum (std::string const &conf); + selfcoordnum(); + virtual inline ~selfcoordnum() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force (colvarvalue const &force); + template + /// \brief Calculate a coordination number through the function + /// (1-x**n)/(1-x**m), x = |A1-A2|/r0 \param r0 "cutoff" for the + /// coordination number \param exp_num \i n exponent \param exp_den + /// \i m exponent \param A1 atom \param A2 atom + static cvm::real switching_function (cvm::real const &r0, + int const &exp_num, int const &exp_den, + cvm::atom &A1, cvm::atom &A2); + + 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; + virtual cvm::real compare (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: + /// Atoms involved in the component + cvm::atom acceptor, donor; + /// \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; + virtual cvm::real compare (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 inline ~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; +// virtual cvm::real compare (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(); + 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; + virtual cvm::real compare (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(); + 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; + virtual cvm::real compare (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; + + /// 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; + +public: + + orientation (std::string const &conf); + orientation(); + virtual inline ~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; + virtual cvm::real compare (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); + orientation_angle(); + virtual inline ~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; + virtual cvm::real compare (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); + tilt(); + virtual inline ~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; + virtual cvm::real compare (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 inline ~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 cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; + /// Redefined to handle the 2*PI periodicity + virtual void wrap (colvarvalue &x) 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::orientation +{ +protected: + /// Sum of the squares of ref_coords + cvm::real ref_pos_sum2; + +public: + + /// Constructor + rmsd (std::string const &conf); + virtual inline ~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; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +/// \brief Colvar component: 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::logmsd + : public colvar::orientation +{ +protected: + + /// Sum of the squares of ref_coords + cvm::real ref_pos_sum2; + cvm::real MSD; + +public: + + /// Constructor + logmsd (std::string const &conf); + virtual inline ~logmsd() {} + 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; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +// metrics functions for cvc implementations with a periodicity + +inline 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; +} + +inline 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; +} + +inline 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; +} + +inline cvm::real colvar::dihedral::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return dist2_lgrad (x1, x2); +} + +inline void colvar::dihedral::wrap (colvarvalue &x) const +{ + if ((x.real_value - wrap_center) >= 180.0) { + x.real_value -= 360.0; + return; + } + + if ((x.real_value - wrap_center) < -180.0) { + x.real_value += 360.0; + return; + } + + return; +} + +inline 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; +} + +inline 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; +} + +inline 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; +} + +inline cvm::real colvar::spin_angle::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return dist2_lgrad (x1, x2); +} + +inline void colvar::spin_angle::wrap (colvarvalue &x) const +{ + if ((x.real_value - wrap_center) >= 180.0) { + x.real_value -= 360.0; + return; + } + + if ((x.real_value - wrap_center) < -180.0) { + x.real_value += 360.0; + return; + } + + return; +} + +// 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) \ + \ + inline cvm::real colvar::TYPE::dist2 (colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return std::pow (x1.real_value - x2.real_value, int (2)); \ + } \ + \ + inline colvarvalue colvar::TYPE::dist2_lgrad (colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return 2.0 * (x1.real_value - x2.real_value); \ + } \ + \ + inline colvarvalue colvar::TYPE::dist2_rgrad (colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return this->dist2_lgrad (x2, x1); \ + } \ + \ + inline cvm::real colvar::TYPE::compare (colvarvalue const &x1, \ + colvarvalue const &x2) const \ + { \ + return this->dist2_lgrad (x1, x2); \ + } \ + \ + + simple_scalar_dist_functions (distance) + // NOTE: distance_z has explicit functions, see below + simple_scalar_dist_functions (distance_xy) + simple_scalar_dist_functions (min_distance) + simple_scalar_dist_functions (angle) + simple_scalar_dist_functions (coordnum) + simple_scalar_dist_functions (selfcoordnum) + simple_scalar_dist_functions (h_bond) + simple_scalar_dist_functions (gyration) + simple_scalar_dist_functions (rmsd) + simple_scalar_dist_functions (logmsd) + simple_scalar_dist_functions (orientation_angle) + simple_scalar_dist_functions (tilt) + simple_scalar_dist_functions (eigenvector) + // simple_scalar_dist_functions (alpha_dihedrals) + simple_scalar_dist_functions (alpha_angles) + simple_scalar_dist_functions (dihedPC) + + +// Projected distance +// Differences should always be wrapped around 0 (ignoring wrap_center) +inline cvm::real colvar::distance_z::dist2 (colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + if (period != 0.0) { + cvm::real shift = std::floor (diff/period + 0.5); + diff -= shift * period; + } + return diff * diff; +} + +inline colvarvalue colvar::distance_z::dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + if (period != 0.0) { + cvm::real shift = std::floor (diff/period + 0.5); + diff -= shift * period; + } + return 2.0 * diff; +} + +inline colvarvalue colvar::distance_z::dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::real diff = x1.real_value - x2.real_value; + if (period != 0.0) { + cvm::real shift = std::floor (diff/period + 0.5); + diff -= shift * period; + } + return (-2.0) * diff; +} + +inline cvm::real colvar::distance_z::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return dist2_lgrad (x1, x2); +} + +inline void colvar::distance_z::wrap (colvarvalue &x) const +{ + if (! this->b_periodic) { + // don't wrap if the period has not been set + return; + } + + cvm::real shift = std::floor ((x.real_value - wrap_center) / period + 0.5); + x.real_value -= shift * period; + return; +} + + +// distance between three dimensional vectors +// +// TODO apply PBC to distance_vec +// Note: differences should be centered around (0, 0, 0)! + +inline cvm::real colvar::distance_vec::dist2 (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return cvm::position_dist2 (x1.rvector_value, x2.rvector_value); +} + +inline 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); +} + +inline 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); +} + +inline cvm::real colvar::distance_vec::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::fatal_error ("Error: cannot compare() two distance vectors.\n"); + return 0.0; +} + +inline cvm::real colvar::distance_dir::dist2 (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return (x1.rvector_value - x2.rvector_value).norm2(); +} + +inline colvarvalue colvar::distance_dir::dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return colvarvalue ((x1.rvector_value - x2.rvector_value), colvarvalue::type_unitvector); +} + +inline colvarvalue colvar::distance_dir::dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return colvarvalue ((x2.rvector_value - x1.rvector_value), colvarvalue::type_unitvector); +} + +inline cvm::real colvar::distance_dir::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::fatal_error ("Error: cannot compare() two distance directions.\n"); + return 0.0; +} + +// distance between quaternions + +inline cvm::real colvar::orientation::dist2 (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.quaternion_value.dist2 (x2); +} + +inline colvarvalue colvar::orientation::dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x1.quaternion_value.dist2_grad (x2); +} + +inline colvarvalue colvar::orientation::dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const +{ + return x2.quaternion_value.dist2_grad (x1); +} + +inline cvm::real colvar::orientation::compare (colvarvalue const &x1, + colvarvalue const &x2) const +{ + cvm::fatal_error ("Error: cannot compare() two quaternions.\n"); + return 0.0; +} + + + + + +#endif + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarcomp_angles.cpp b/lib/colvars/colvarcomp_angles.cpp new file mode 100644 index 0000000000..414c01df7e --- /dev/null +++ b/lib/colvars/colvarcomp_angles.cpp @@ -0,0 +1,377 @@ + +#include "colvarmodule.h" +#include "colvar.h" +#include "colvarcomp.h" + +#include + + + +colvar::angle::angle (std::string const &conf) + : cvc (conf) +{ + function_type = "angle"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + parse_group (conf, "group1", group1); + parse_group (conf, "group2", group2); + parse_group (conf, "group3", group3); + atom_groups.push_back (&group1); + atom_groups.push_back (&group2); + atom_groups.push_back (&group3); + if (get_keyval (conf, "oneSiteSystemForce", b_1site_force, false)) { + cvm::log ("Computing system force on group 1 only"); + } + x.type (colvarvalue::type_scalar); +} + + +colvar::angle::angle (cvm::atom const &a1, + cvm::atom const &a2, + cvm::atom const &a3) + : group1 (std::vector (1, a1)), + group2 (std::vector (1, a2)), + group3 (std::vector (1, a3)) +{ + function_type = "angle"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + b_1site_force = false; + atom_groups.push_back (&group1); + atom_groups.push_back (&group2); + atom_groups.push_back (&group3); + + x.type (colvarvalue::type_scalar); +} + + +colvar::angle::angle() +{ + function_type = "angle"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::angle::calc_value() +{ + group1.read_positions(); + group2.read_positions(); + group3.read_positions(); + + 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 = cvm::position_distance (g2_pos, g1_pos); + r21l = r21.norm(); + r23 = cvm::position_distance (g2_pos, g3_pos); + r23l = r23.norm(); + + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + + x.real_value = (180.0/PI) * std::acos (cos_theta); +} + + +void colvar::angle::calc_gradients() +{ + cvm::real const cos_theta = (r21*r23)/(r21l*r23l); + cvm::real const dxdcos = -1.0 / std::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 ); + + for (size_t i = 0; i < group1.size(); i++) { + group1[i].grad = (group1[i].mass/group1.total_mass) * + (dxdr1); + } + + for (size_t i = 0; i < group2.size(); i++) { + group2[i].grad = (group2[i].mass/group2.total_mass) * + (dxdr1 + dxdr3) * (-1.0); + } + + for (size_t i = 0; i < group3.size(); i++) { + group3[i].grad = (group3[i].mass/group3.total_mass) * + (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 (b_1site_force) { + group1.read_system_forces(); + cvm::real norm_fact = 1.0 / dxdr1.norm2(); + ft.real_value = norm_fact * dxdr1 * group1.system_force(); + } else { + group1.read_system_forces(); + group3.read_system_forces(); + cvm::real norm_fact = 1.0 / (dxdr1.norm2() + dxdr3.norm2()); + ft.real_value = norm_fact * ( dxdr1 * group1.system_force() + + dxdr3 * group3.system_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 ? std::cos(theta) / std::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); +} + + + + +colvar::dihedral::dihedral (std::string const &conf) + : cvc (conf) +{ + function_type = "dihedral"; + period = 360.0; + b_periodic = true; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + if (get_keyval (conf, "oneSiteSystemForce", b_1site_force, false)) { + cvm::log ("Computing system force on group 1 only"); + } + parse_group (conf, "group1", group1); + parse_group (conf, "group2", group2); + parse_group (conf, "group3", group3); + parse_group (conf, "group4", group4); + atom_groups.push_back (&group1); + atom_groups.push_back (&group2); + atom_groups.push_back (&group3); + atom_groups.push_back (&group4); + + x.type (colvarvalue::type_scalar); +} + + +colvar::dihedral::dihedral (cvm::atom const &a1, + cvm::atom const &a2, + cvm::atom const &a3, + cvm::atom const &a4) + : group1 (std::vector (1, a1)), + group2 (std::vector (1, a2)), + group3 (std::vector (1, a3)), + group4 (std::vector (1, a4)) +{ + if (cvm::debug()) + cvm::log ("Initializing dihedral object from atom groups.\n"); + + function_type = "dihedral"; + period = 360.0; + b_periodic = true; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + b_1site_force = false; + + atom_groups.push_back (&group1); + atom_groups.push_back (&group2); + atom_groups.push_back (&group3); + atom_groups.push_back (&group4); + + x.type (colvarvalue::type_scalar); + + if (cvm::debug()) + cvm::log ("Done initializing dihedral object from atom groups.\n"); +} + + +colvar::dihedral::dihedral() +{ + function_type = "dihedral"; + period = 360.0; + b_periodic = true; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + x.type (colvarvalue::type_scalar); +} + + +void colvar::dihedral::calc_value() +{ + group1.read_positions(); + group2.read_positions(); + group3.read_positions(); + group4.read_positions(); + + 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 = cvm::position_distance (g1_pos, g2_pos); + r23 = cvm::position_distance (g2_pos, g3_pos); + r34 = cvm::position_distance (g3_pos, g4_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) * std::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 (std::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); + } + + for (size_t i = 0; i < group1.size(); i++) + group1[i].grad = (group1[i].mass/group1.total_mass) * (-f1); + + for (size_t i = 0; i < group2.size(); i++) + group2[i].grad = (group2[i].mass/group2.total_mass) * (-f2 + f1); + + for (size_t i = 0; i < group3.size(); i++) + group3[i].grad = (group3[i].mass/group3.total_mass) * (-f3 + f2); + + for (size_t i = 0; i < group4.size(); i++) + group4[i].grad = (group4[i].mass/group4.total_mass) * (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 * std::sqrt (1.0 - dot1 * dot1); + cvm::real const fact4 = d34 * std::sqrt (1.0 - dot4 * dot4); + + group1.read_system_forces(); + if ( b_1site_force ) { + // This is only measuring the force on group 1 + ft.real_value = PI/180.0 * fact1 * (cross1 * group1.system_force()); + } else { + // Default case: use groups 1 and 4 + group4.read_system_forces(); + ft.real_value = PI/180.0 * 0.5 * (fact1 * (cross1 * group1.system_force()) + + fact4 * (cross4 * group4.system_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); +} + + diff --git a/lib/colvars/colvarcomp_coordnums.cpp b/lib/colvars/colvarcomp_coordnums.cpp new file mode 100644 index 0000000000..a33f95e072 --- /dev/null +++ b/lib/colvars/colvarcomp_coordnums.cpp @@ -0,0 +1,404 @@ +#include + +#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, + int const &en, + int const &ed, + cvm::atom &A1, + cvm::atom &A2) +{ + cvm::rvector const diff = cvm::position_distance (A1.pos, A2.pos); + cvm::real const l2 = diff.norm2()/(r0*r0); + + // 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 = std::pow (l2, en2); + cvm::real const xd = std::pow (l2, ed2); + cvm::real const func = (1.0-xn)/(1.0-xd); + + if (calculate_gradients) { + cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); + cvm::rvector const dl2dx = (2.0/(r0*r0))*diff; + A1.grad += (-1.0)*dFdl2*dl2dx; + A2.grad += dFdl2*dl2dx; + } + + return func; +} + + +template +cvm::real colvar::coordnum::switching_function (cvm::rvector const &r0_vec, + int const &en, + int const &ed, + cvm::atom &A1, + cvm::atom &A2) +{ + cvm::rvector const diff = cvm::position_distance (A1.pos, A2.pos); + cvm::rvector const scal_diff (diff.x/r0_vec.x, diff.y/r0_vec.y, diff.z/r0_vec.z); + 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 = std::pow (l2, en2); + cvm::real const xd = std::pow (l2, ed2); + cvm::real const func = (1.0-xn)/(1.0-xd); + + if (calculate_gradients) { + cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); + cvm::rvector const dl2dx ((2.0/(r0_vec.x*r0_vec.x))*diff.x, + (2.0/(r0_vec.y*r0_vec.y))*diff.y, + (2.0/(r0_vec.z*r0_vec.z))*diff.z); + A1.grad += (-1.0)*dFdl2*dl2dx; + A2.grad += dFdl2*dl2dx; + } + return func; +} + + + +colvar::coordnum::coordnum (std::string const &conf) + : distance (conf), b_anisotropic (false), b_group2_center_only (false) +{ + function_type = "coordnum"; + x.type (colvarvalue::type_scalar); + + // group1 and group2 are already initialized by distance() + + // need to specify this explicitly because the distance() constructor + // has set it to true + b_inverse_gradients = false; + + bool const b_scale = get_keyval (conf, "cutoff", r0, + cvm::real (4.0 * cvm::unit_angstrom())); + + if (get_keyval (conf, "cutoff3", r0_vec, + cvm::rvector (4.0, 4.0, 4.0), parse_silent)) { + + if (b_scale) + cvm::fatal_error ("Error: cannot specify \"scale\" and " + "\"scale3\" at the same time.\n"); + 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, int (6) ); + get_keyval (conf, "expDenom", ed, int (12)); + + if ( (en%2) || (ed%2) ) { + cvm::fatal_error ("Error: odd exponents provided, can only use even ones.\n"); + } + + get_keyval (conf, "group2CenterOnly", b_group2_center_only, false); +} + + +colvar::coordnum::coordnum() + : b_anisotropic (false), b_group2_center_only (false) +{ + function_type = "coordnum"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::coordnum::calc_value() +{ + x.real_value = 0.0; + + // these are necessary: for each atom, gradients are summed together + // by multiple calls to switching_function() + group1.reset_atoms_data(); + group2.reset_atoms_data(); + + group1.read_positions(); + group2.read_positions(); + + if (b_group2_center_only) { + + // create a fake atom to hold the group2 com coordinates + cvm::atom group2_com_atom (group2[0]); + group2_com_atom.pos = group2.center_of_mass(); + + if (b_anisotropic) { + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) + x.real_value += switching_function (r0_vec, en, ed, *ai1, group2_com_atom); + } else { + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) + x.real_value += switching_function (r0, en, ed, *ai1, group2_com_atom); + } + + } else { + + if (b_anisotropic) { + 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_vec, en, ed, *ai1, *ai2); + } + } 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, en, ed, *ai1, *ai2); + } + } + } +} + + +void colvar::coordnum::calc_gradients() +{ + if (b_group2_center_only) { + + // create a fake atom to hold the group2 com coordinates + cvm::atom group2_com_atom (group2[0]); + group2_com_atom.pos = group2.center_of_mass(); + + if (b_anisotropic) { + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) + switching_function (r0_vec, en, ed, *ai1, group2_com_atom); + } else { + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) + switching_function (r0, en, ed, *ai1, group2_com_atom); + } + + group2.set_weighted_gradient (group2_com_atom.grad); + + } else { + + if (b_anisotropic) { + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) + for (cvm::atom_iter ai2 = group2.begin(); ai2 != group2.end(); ai2++) { + switching_function (r0_vec, en, ed, *ai1, *ai2); + } + } else { + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) + for (cvm::atom_iter ai2 = group2.begin(); ai2 != group2.end(); ai2++) { + switching_function (r0, en, ed, *ai1, *ai2); + } + } + } + + // if (cvm::debug()) { + // for (size_t i = 0; i < group1.size(); i++) { + // cvm::log ("atom["+cvm::to_str (group1[i].id+1)+"] gradient: "+ + // cvm::to_str (group1[i].grad)+"\n"); + // } + + // for (size_t i = 0; i < group2.size(); i++) { + // cvm::log ("atom["+cvm::to_str (group2[i].id+1)+"] gradient: "+ + // cvm::to_str (group2[i].grad)+"\n"); + // } + // } +} + +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); +} + + + +// 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"); + + function_type = "h_bond"; + x.type (colvarvalue::type_scalar); + + int a_num, d_num; + get_keyval (conf, "acceptor", a_num, -1); + get_keyval (conf, "donor", d_num, -1); + + if ( (a_num == -1) || (d_num == -1) ) { + cvm::fatal_error ("Error: either acceptor or donor undefined.\n"); + } + + acceptor = cvm::atom (a_num); + donor = cvm::atom (d_num); + atom_groups.push_back (new cvm::atom_group); + atom_groups[0]->add_atom (acceptor); + atom_groups[0]->add_atom (donor); + + get_keyval (conf, "cutoff", r0, (3.3 * cvm::unit_angstrom())); + get_keyval (conf, "expNumer", en, 6); + get_keyval (conf, "expDenom", ed, 8); + + if ( (en%2) || (ed%2) ) { + cvm::fatal_error ("Error: odd exponents provided, can only use even ones.\n"); + } + + if (cvm::debug()) + cvm::log ("Done initializing h_bond object.\n"); +} + + +colvar::h_bond::h_bond (cvm::atom const &acceptor_i, + cvm::atom const &donor_i, + cvm::real r0_i, int en_i, int ed_i) + : acceptor (acceptor_i), + donor (donor_i), + r0 (r0_i), en (en_i), ed (ed_i) +{ + function_type = "h_bond"; + x.type (colvarvalue::type_scalar); + + atom_groups.push_back (new cvm::atom_group); + atom_groups[0]->add_atom (acceptor); + atom_groups[0]->add_atom (donor); +} + +colvar::h_bond::h_bond() + : cvc () +{ + function_type = "h_bond"; + x.type (colvarvalue::type_scalar); +} + + +colvar::h_bond::~h_bond() +{ + for (int i=0; i (r0, en, ed, acceptor, donor); +} + + +void colvar::h_bond::calc_gradients() +{ + colvar::coordnum::switching_function (r0, en, ed, acceptor, donor); + (*atom_groups[0])[0].grad = acceptor.grad; + (*atom_groups[0])[1].grad = donor.grad; +} + + +void colvar::h_bond::apply_force (colvarvalue const &force) +{ + acceptor.apply_force (force.real_value * acceptor.grad); + donor.apply_force (force.real_value * donor.grad); +} + + +// Self-coordination number for a group + +template +cvm::real colvar::selfcoordnum::switching_function (cvm::real const &r0, + int const &en, + int const &ed, + cvm::atom &A1, + cvm::atom &A2) +{ + cvm::rvector const diff = cvm::position_distance (A1.pos, A2.pos); + cvm::real const l2 = diff.norm2()/(r0*r0); + + // 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 = std::pow (l2, en2); + cvm::real const xd = std::pow (l2, ed2); + cvm::real const func = (1.0-xn)/(1.0-xd); + + if (calculate_gradients) { + cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - func*ed2*(xd/l2))*(-1.0); + cvm::rvector const dl2dx = (2.0/(r0*r0))*diff; + A1.grad += (-1.0)*dFdl2*dl2dx; + A2.grad += dFdl2*dl2dx; + } + + return func; +} + + +colvar::selfcoordnum::selfcoordnum (std::string const &conf) + : distance (conf, false) +{ + function_type = "selfcoordnum"; + x.type (colvarvalue::type_scalar); + + // group1 is already initialized by distance() + + // need to specify this explicitly because the distance() constructor + // has set it to true + b_inverse_gradients = false; + + get_keyval (conf, "cutoff", r0, cvm::real (4.0 * cvm::unit_angstrom())); + get_keyval (conf, "expNumer", en, int (6) ); + get_keyval (conf, "expDenom", ed, int (12)); + + if ( (en%2) || (ed%2) ) { + cvm::fatal_error ("Error: odd exponents provided, can only use even ones.\n"); + } +} + + +colvar::selfcoordnum::selfcoordnum() +{ + function_type = "selfcoordnum"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::selfcoordnum::calc_value() +{ + x.real_value = 0.0; + + // for each atom, gradients are summed by multiple calls to switching_function() + group1.reset_atoms_data(); + group1.read_positions(); + + for (size_t i = 0; i < group1.size() - 1; i++) + for (size_t j = i + 1; j < group1.size(); j++) + x.real_value += switching_function (r0, en, ed, group1[i], group1[j]); +} + + +void colvar::selfcoordnum::calc_gradients() +{ + for (size_t i = 0; i < group1.size() - 1; i++) + for (size_t j = i + 1; j < group1.size(); j++) + switching_function (r0, en, ed, group1[i], group1[j]); +} + +void colvar::selfcoordnum::apply_force (colvarvalue const &force) +{ + if (!group1.noforce) + group1.apply_colvar_force (force.real_value); +} + diff --git a/lib/colvars/colvarcomp_distances.cpp b/lib/colvars/colvarcomp_distances.cpp new file mode 100644 index 0000000000..b69658248c --- /dev/null +++ b/lib/colvars/colvarcomp_distances.cpp @@ -0,0 +1,1113 @@ +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + + + +/// \file cvc_distance.cpp \brief Collective variables +/// determining various type of distances between two groups + +// "twogroup" flag defaults to true; set to false by selfCoordNum +// (only distance-derived component based on only one group) + +colvar::distance::distance (std::string const &conf, bool twogroups) + : cvc (conf) +{ + function_type = "distance"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + if (get_keyval (conf, "forceNoPBC", b_no_PBC, false)) { + cvm::log ("Computing distance using absolute positions (not minimal-image)"); + } + if (twogroups && get_keyval (conf, "oneSiteSystemForce", b_1site_force, false)) { + cvm::log ("Computing system force on group 1 only"); + } + parse_group (conf, "group1", group1); + atom_groups.push_back (&group1); + if (twogroups) { + parse_group (conf, "group2", group2); + atom_groups.push_back (&group2); + } + x.type (colvarvalue::type_scalar); +} + + +colvar::distance::distance() + : cvc () +{ + function_type = "distance"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + b_1site_force = false; + x.type (colvarvalue::type_scalar); +} + +void colvar::distance::calc_value() +{ + group1.reset_atoms_data(); + group2.reset_atoms_data(); + + group1.read_positions(); + group2.read_positions(); + + if (b_no_PBC) { + 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_system_forces(); + if ( b_1site_force ) { + ft.real_value = -1.0 * (group1.system_force() * dist_v.unit()); + } else { + group2.read_system_forces(); + ft.real_value = 0.5 * ((group2.system_force() - group1.system_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); + + if (!group2.noforce) + group2.apply_colvar_force (force); +} + + + +colvar::distance_vec::distance_vec (std::string const &conf) + : distance (conf) +{ + function_type = "distance_vec"; + x.type (colvarvalue::type_vector); +} + +colvar::distance_vec::distance_vec() + : distance() +{ + function_type = "distance_vec"; + x.type (colvarvalue::type_vector); +} + +void colvar::distance_vec::calc_value() +{ + group1.reset_atoms_data(); + group2.reset_atoms_data(); + + group1.read_positions(); + group2.read_positions(); + + if (b_no_PBC) { + 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); +} + + + +colvar::distance_z::distance_z (std::string const &conf) + : cvc (conf) +{ + function_type = "distance_z"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + 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) + b_periodic = true; + + if ((wrap_center != 0.0) && (period == 0.0)) { + cvm::fatal_error ("Error: wrapAround was defined in a distanceZ component," + " but its period has not been set.\n"); + } + + parse_group (conf, "main", main); + parse_group (conf, "ref", ref1); + atom_groups.push_back (&main); + atom_groups.push_back (&ref1); + // this group is optional + parse_group (conf, "ref2", ref2, true); + + if (ref2.size()) { + atom_groups.push_back (&ref2); + cvm::log ("Using axis joining the centers of mass of groups \"ref\" and \"ref2\""); + fixed_axis = false; + if (key_lookup (conf, "axis")) + cvm::log ("Warning: explicit axis definition will be ignored!"); + } else { + if (get_keyval (conf, "axis", axis, cvm::rvector (0.0, 0.0, 1.0))) { + if (axis.norm2() == 0.0) + cvm::fatal_error ("Axis vector is zero!"); + axis = axis.unit(); + } + fixed_axis = true; + } + + if (get_keyval (conf, "forceNoPBC", b_no_PBC, false)) { + cvm::log ("Computing distance using absolute positions (not minimal-image)"); + } + if (get_keyval (conf, "oneSiteSystemForce", b_1site_force, false)) { + cvm::log ("Computing system force on group \"main\" only"); + } +} + +colvar::distance_z::distance_z() +{ + function_type = "distance_z"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + x.type (colvarvalue::type_scalar); +} + +void colvar::distance_z::calc_value() +{ + main.reset_atoms_data(); + ref1.reset_atoms_data(); + + main.read_positions(); + ref1.read_positions(); + + if (fixed_axis) { + if (b_no_PBC) { + 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 { + ref2.reset_atoms_data(); + ref2.read_positions(); + + if (b_no_PBC) { + 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 (b_no_PBC) { + 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_system_forces(); + + if (fixed_axis && !b_1site_force) { + ref1.read_system_forces(); + ft.real_value = 0.5 * ((main.system_force() - ref1.system_force()) * axis); + } else { + ft.real_value = main.system_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.size() && !ref2.noforce) + ref2.apply_colvar_force (force.real_value); + + if (!main.noforce) + main.apply_colvar_force (force.real_value); +} + + + +colvar::distance_xy::distance_xy (std::string const &conf) + : distance_z (conf) +{ + function_type = "distance_xy"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + x.type (colvarvalue::type_scalar); +} + +colvar::distance_xy::distance_xy() + : distance_z() +{ + function_type = "distance_xy"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + x.type (colvarvalue::type_scalar); +} + +void colvar::distance_xy::calc_value() +{ + ref1.reset_atoms_data(); + main.reset_atoms_data(); + + ref1.read_positions(); + main.read_positions(); + + if (b_no_PBC) { + 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) { + ref2.reset_atoms_data(); + ref2.read_positions(); + + if (b_no_PBC) { + 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 (b_no_PBC) { + 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_system_forces(); + + if (fixed_axis && !b_1site_force) { + ref1.read_system_forces(); + ft.real_value = 0.5 / x.real_value * ((main.system_force() - ref1.system_force()) * dist_v_ortho); + } else { + ft.real_value = 1.0 / x.real_value * main.system_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.size() && !ref2.noforce) + ref2.apply_colvar_force (force.real_value); + + if (!main.noforce) + main.apply_colvar_force (force.real_value); +} + + + +colvar::min_distance::min_distance (std::string const &conf) + : distance (conf) +{ + function_type = "min_distance"; + x.type (colvarvalue::type_scalar); + + get_keyval (conf, "smoothing", smoothing, (1.0 * cvm::unit_angstrom())); +} + +colvar::min_distance::min_distance() + : distance() +{ + function_type = "min_distance"; + x.type (colvarvalue::type_scalar); +} + +void colvar::min_distance::calc_value() +{ + group1.reset_atoms_data(); + group2.reset_atoms_data(); + + group1.read_positions(); + group2.read_positions(); + + x.real_value = 0.0; + + bool zero_dist = false; + + 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 d = dv.norm(); + if (d > 0.0) + x.real_value += std::exp (smoothing / d); + else + zero_dist = true; + } + } + + x.real_value = zero_dist ? 0.0 : smoothing/(std::log (x.real_value)); +} + +void colvar::min_distance::calc_gradients() +{ + if (x.real_value > 0.0) { + cvm::real const sum = std::exp (smoothing/x.real_value); + cvm::real const dxdsum = -1.0 * + (x.real_value/smoothing) * (x.real_value/smoothing) * + (1.0 / sum); + + 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 d = dv.norm(); + if (d > 0.0) { + cvm::rvector const dvu = dv / dv.norm(); + ai1->grad += dxdsum * std::exp (smoothing / d) * + smoothing * (-1.0/(d*d)) * (-1.0) * dvu; + ai2->grad += dxdsum * std::exp (smoothing / d) * + smoothing * (-1.0/(d*d)) * dvu; + } + } + } + } +} + +void colvar::min_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); +} + + + +colvar::distance_dir::distance_dir (std::string const &conf) + : distance (conf) +{ + function_type = "distance_dir"; + x.type (colvarvalue::type_unitvector); +} + + +colvar::distance_dir::distance_dir() + : distance() +{ + function_type = "distance_dir"; + x.type (colvarvalue::type_unitvector); +} + + +void colvar::distance_dir::calc_value() +{ + group1.reset_atoms_data(); + group2.reset_atoms_data(); + + group1.read_positions(); + group2.read_positions(); + + if (b_no_PBC) { + 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... +} + + +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); +} + + + + +colvar::gyration::gyration (std::string const &conf) + : cvc (conf) +{ + function_type = "gyration"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + parse_group (conf, "atoms", atoms); + atom_groups.push_back (&atoms); + x.type (colvarvalue::type_scalar); +} + + +colvar::gyration::gyration() +{ + function_type = "gyration"; + b_inverse_gradients = true; + b_Jacobian_derivative = true; + x.type (colvarvalue::type_scalar); +} + + +void colvar::gyration::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + atoms.apply_translation ((-1.0) * atoms.center_of_geometry()); + + x.real_value = 0.0; + for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { + x.real_value += (ai->mass/atoms.total_mass) * (ai->pos).norm2(); + } + x.real_value = std::sqrt (x.real_value); +} + + +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_system_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->system_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); +} + + + +colvar::rmsd::rmsd (std::string const &conf) + : orientation (conf) +{ + b_inverse_gradients = true; + b_Jacobian_derivative = true; + function_type = "rmsd"; + x.type (colvarvalue::type_scalar); + + ref_pos_sum2 = 0.0; + for (size_t i = 0; i < ref_pos.size(); i++) { + ref_pos_sum2 += ref_pos[i].norm2(); + } +} + + +void colvar::rmsd::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + + atoms_cog = atoms.center_of_geometry(); + rot.calc_optimal_rotation (ref_pos, atoms.positions_shifted (-1.0 * atoms_cog)); + + cvm::real group_pos_sum2 = 0.0; + for (size_t i = 0; i < atoms.size(); i++) { + group_pos_sum2 += (atoms[i].pos - atoms_cog).norm2(); + } + + // value of the RMSD (Coutsias et al) + cvm::real const MSD = 1.0/(cvm::real (atoms.size())) * + ( group_pos_sum2 + ref_pos_sum2 - 2.0 * rot.lambda ); + + x.real_value = (MSD > 0.0) ? std::sqrt (MSD) : 0.0; +} + + +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; + + for (size_t ia = 0; ia < atoms.size(); ia++) { + atoms[ia].grad = (drmsddx2 * 2.0 * (atoms[ia].pos - atoms_cog - + rot.q.rotate (ref_pos[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_system_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].system_force; + } + ft.real_value *= atoms.size(); +} + + +void colvar::rmsd::calc_Jacobian_derivative() +{ + // divergence of the back-rotated target coordinates + cvm::real divergence = 0.0; + + // gradient of the rotation matrix + cvm::matrix2d grad_rot_mat; + + // gradients of products of 2 quaternion components + cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; + + for (size_t ia = 0; ia < atoms.size(); ia++) { + + // Gradient of optimal quaternion wrt current Cartesian position + cvm::vector1d< cvm::rvector, 4 > &dq = rot.dQ0_2[ia]; + + g11 = 2.0 * (rot.q)[1]*dq[1]; + g22 = 2.0 * (rot.q)[2]*dq[2]; + g33 = 2.0 * (rot.q)[3]*dq[3]; + g01 = (rot.q)[0]*dq[1] + (rot.q)[1]*dq[0]; + g02 = (rot.q)[0]*dq[2] + (rot.q)[2]*dq[0]; + g03 = (rot.q)[0]*dq[3] + (rot.q)[3]*dq[0]; + g12 = (rot.q)[1]*dq[2] + (rot.q)[2]*dq[1]; + g13 = (rot.q)[1]*dq[3] + (rot.q)[3]*dq[1]; + g23 = (rot.q)[2]*dq[3] + (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 i = 0; i < 3; i++) { + for (size_t j = 0; j < 3; j++) { + divergence += grad_rot_mat[i][j][i] * y[j]; + } + } + } + + jd.real_value = x.real_value > 0.0 ? (3.0 * atoms.size() - 4.0 - divergence) / x.real_value : 0.0; +} + + + +colvar::logmsd::logmsd (std::string const &conf) + : orientation (conf) +{ + b_inverse_gradients = true; + b_Jacobian_derivative = true; + function_type = "logmsd"; + x.type (colvarvalue::type_scalar); + + ref_pos_sum2 = 0.0; + for (size_t i = 0; i < ref_pos.size(); i++) { + ref_pos_sum2 += ref_pos[i].norm2(); + } +} + + +void colvar::logmsd::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + + if (cvm::debug()) + cvm::log ("colvar::logmsd: current com: "+ + cvm::to_str (atoms.center_of_mass())+"\n"); + + atoms_cog = atoms.center_of_geometry(); + rot.calc_optimal_rotation (ref_pos, atoms.positions_shifted (-1.0 * atoms_cog)); + + cvm::real group_pos_sum2 = 0.0; + for (size_t i = 0; i < atoms.size(); i++) { + group_pos_sum2 += (atoms[i].pos-atoms_cog).norm2(); + } + + // value of the MSD (Coutsias et al) + MSD = 1.0/(cvm::real (atoms.size())) * + ( group_pos_sum2 + ref_pos_sum2 - 2.0 * rot.lambda ); + + x.real_value = (MSD > 0.0) ? std::log(MSD) : 0.0; +} + + +void colvar::logmsd::calc_gradients() +{ + cvm::real fact = (MSD > 0.0) ? 2.0/(cvm::real (atoms.size()) * MSD) : 0.0; + + for (size_t ia = 0; ia < atoms.size(); ia++) { + atoms[ia].grad = fact * (atoms[ia].pos - atoms_cog - rot.dL0_2[ia]); + } +} + + +void colvar::logmsd::apply_force (colvarvalue const &force) +{ + if (!atoms.noforce) + atoms.apply_colvar_force (force.real_value); +} + + +void colvar::logmsd::calc_force_invgrads() +{ + atoms.read_system_forces(); + ft.real_value = 0.0; + + // Note: gradient square norm is 4.0 / (N_atoms * E) + + for (size_t ia = 0; ia < atoms.size(); ia++) { + ft.real_value += atoms[ia].grad * atoms[ia].system_force; + } + ft.real_value *= atoms.size() * MSD / 4.0; +} + + +void colvar::logmsd::calc_Jacobian_derivative() +{ + // divergence of the back-rotated target coordinates + cvm::real divergence = 0.0; + + // gradient of the rotation matrix + cvm::matrix2d grad_rot_mat; + + // gradients of products of 2 quaternion components + cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; + + for (size_t ia = 0; ia < atoms.size(); ia++) { + + // Gradient of optimal quaternion wrt current Cartesian position + cvm::vector1d< cvm::rvector, 4 > &dq = rot.dQ0_2[ia]; + + g11 = 2.0 * (rot.q)[1]*dq[1]; + g22 = 2.0 * (rot.q)[2]*dq[2]; + g33 = 2.0 * (rot.q)[3]*dq[3]; + g01 = (rot.q)[0]*dq[1] + (rot.q)[1]*dq[0]; + g02 = (rot.q)[0]*dq[2] + (rot.q)[2]*dq[0]; + g03 = (rot.q)[0]*dq[3] + (rot.q)[3]*dq[0]; + g12 = (rot.q)[1]*dq[2] + (rot.q)[2]*dq[1]; + g13 = (rot.q)[1]*dq[3] + (rot.q)[3]*dq[1]; + g23 = (rot.q)[2]*dq[3] + (rot.q)[3]*dq[2]; + + // Gradient of the rotation matrix wrt current Cartesian position + // Note: we are only going to use "diagonal" terms: grad_rot_mat[i][j][i] + 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 i = 0; i < 3; i++) { + for (size_t j = 0; j < 3; j++) { + divergence += grad_rot_mat[i][j][i] * y[j]; + } + } + } + + jd.real_value = (3.0 * atoms.size() - 3.0 - divergence) / 2.0; +} + + + +colvar::eigenvector::eigenvector (std::string const &conf) + : cvc (conf) +{ + b_inverse_gradients = true; + b_Jacobian_derivative = true; + function_type = "eigenvector"; + x.type (colvarvalue::type_scalar); + + parse_group (conf, "atoms", atoms); + atom_groups.push_back (&atoms); + + if (atoms.b_rotate) { + cvm::fatal_error ("Error: rotateReference should be disabled:" + "eigenvector component will set it internally."); + } + + if (get_keyval (conf, "refPositions", ref_pos, ref_pos)) { + cvm::log ("Using reference positions from input file.\n"); + if (ref_pos.size() != atoms.size()) { + cvm::fatal_error ("Error: reference positions do not " + "match the number of requested atoms.\n"); + } + } + + { + std::string file_name; + if (get_keyval (conf, "refPositionsFile", file_name)) { + + std::string file_col; + double file_col_value; + 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) + cvm::fatal_error ("Error: refPositionsColValue, " + "if provided, must be non-zero.\n"); + } else { + // if not, use atom indices + atoms.create_sorted_ids(); + } + + ref_pos.resize (atoms.size()); + cvm::load_coords (file_name.c_str(), ref_pos, atoms.sorted_ids, file_col, file_col_value); + } + } + + // Set mobile frame of reference for atom group + atoms.b_center = true; + atoms.b_rotate = true; + atoms.ref_pos = ref_pos; + + // now load the eigenvector + if (get_keyval (conf, "vector", eigenvec, eigenvec)) { + cvm::log ("Using vector components from input file.\n"); + if (eigenvec.size() != atoms.size()) { + cvm::fatal_error ("Error: vector components do not " + "match the number of requested atoms.\n"); + } + } + + { + std::string file_name; + if (get_keyval (conf, "vectorFile", file_name)) { + + std::string file_col; + if (!get_keyval (conf, "vectorCol", file_col, std::string (""))) { + cvm::fatal_error ("Error: parameter vectorCol is required if vectorFile is set.\n"); + } + + double file_col_value; + bool found = get_keyval (conf, "vectorColValue", file_col_value, 0.0); + if (found && !file_col_value) + cvm::fatal_error ("Error: eigenvectorColValue, " + "if provided, must be non-zero.\n"); + + eigenvec.resize (atoms.size()); + cvm::load_coords (file_name.c_str(), eigenvec, atoms.sorted_ids, file_col, file_col_value); + } + } + + if (!ref_pos.size() || !eigenvec.size()) { + cvm::fatal_error ("Error: both reference coordinates" + "and eigenvector must be defined.\n"); + } + + cvm::rvector center (0.0, 0.0, 0.0); + eigenvec_invnorm2 = 0.0; + + for (size_t i = 0; i < atoms.size(); i++) { + center += eigenvec[i]; + } + + cvm::log ("Subtracting sum of eigenvector components: " + cvm::to_str (center) + "\n"); + + for (size_t i = 0; i < atoms.size(); i++) { + eigenvec[i] = eigenvec[i] - center; + eigenvec_invnorm2 += eigenvec[i].norm2(); + } + eigenvec_invnorm2 = 1.0 / eigenvec_invnorm2; + + // request derivatives of optimal rotation wrt 2nd group + // for Jacobian + atoms.rot.request_group1_gradients(atoms.size()); + atoms.rot.request_group2_gradients(atoms.size()); +} + + +void colvar::eigenvector::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); // this will also update atoms.rot + + 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() +{ + // There are two versions of this code + // The simple version is not formally exact, but its + // results are numerically indistinguishable from the + // exact one. The exact one is more expensive and possibly + // less stable in cases where the optimal rotation + // becomes ill-defined. + + // Version A: simple, intuitive, cheap, robust. Wrong. + // Works just fine in practice. + for (size_t ia = 0; ia < atoms.size(); ia++) { + atoms[ia].grad = eigenvec[ia]; + } + +/* + // Version B: complex, expensive, fragile. Right. + + // gradient of the rotation matrix + cvm::matrix2d grad_rot_mat; + + cvm::quaternion &quat0 = atoms.rot.q; + + // gradients of products of 2 quaternion components + cvm::rvector g11, g22, g33, g01, g02, g03, g12, g13, g23; + + // a term that involves the rotation gradients + cvm::rvector rot_grad_term; + + cvm::atom_pos x_relative; + cvm::atom_pos atoms_cog = atoms.center_of_geometry(); + + for (size_t ia = 0; ia < atoms.size(); ia++) { + + // Gradient of optimal quaternion wrt current Cartesian position + // WARNING: we want derivatives wrt the FIRST group here (unlike RMSD) + cvm::vector1d< cvm::rvector, 4 > &dq = atoms.rot.dQ0_1[ia]; + + g11 = 2.0 * quat0[1]*dq[1]; + g22 = 2.0 * quat0[2]*dq[2]; + g33 = 2.0 * quat0[3]*dq[3]; + g01 = quat0[0]*dq[1] + quat0[1]*dq[0]; + g02 = quat0[0]*dq[2] + quat0[2]*dq[0]; + g03 = quat0[0]*dq[3] + quat0[3]*dq[0]; + g12 = quat0[1]*dq[2] + quat0[2]*dq[1]; + g13 = quat0[1]*dq[3] + quat0[3]*dq[1]; + g23 = quat0[2]*dq[3] + quat0[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); + + // this term needs to be rotated back, so we sum it separately + rot_grad_term.reset(); + + for (size_t ja = 0; ja < atoms.size(); ja++) { + x_relative = atoms[ja].pos - atoms_cog; + + for (size_t i = 0; i < 3; i++) { + for (size_t j = 0; j < 3; j++) { + rot_grad_term += eigenvec[ja][i] * grad_rot_mat[i][j] * x_relative[j]; + } + } + } + + // Rotate correction term back to reference frame + atoms[ia].grad = eigenvec[ia] + quat0.rotate (rot_grad_term); + } +*/ +} + + +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_system_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].system_force; + } +} + + +void colvar::eigenvector::calc_Jacobian_derivative() +{ + // gradient of the rotation matrix + cvm::matrix2d grad_rot_mat; + 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::atom_pos x_relative; + cvm::real sum = 0.0; + + 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 + cvm::vector1d< cvm::rvector, 4 > &dq_1 = atoms.rot.dQ0_1[ia]; + + 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 * std::sqrt (eigenvec_invnorm2); +} diff --git a/lib/colvars/colvarcomp_protein.cpp b/lib/colvars/colvarcomp_protein.cpp new file mode 100644 index 0000000000..b30d50e1c1 --- /dev/null +++ b/lib/colvars/colvarcomp_protein.cpp @@ -0,0 +1,385 @@ +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + + +////////////////////////////////////////////////////////////////////// +// alpha component +////////////////////////////////////////////////////////////////////// + +colvar::alpha_angles::alpha_angles (std::string const &conf) + : cvc (conf) +{ + if (cvm::debug()) + cvm::log ("Initializing alpha_angles object.\n"); + + function_type = "alpha_angles"; + 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::fatal_error ("Error: no residues defined in \"residueRange\".\n"); + } + } + + if (residues.size() < 5) { + cvm::fatal_error ("Error: not enough residues defined in \"residueRange\".\n"); + } + + 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::fatal_error ("Error: hBondCoeff must be defined between 0 and 1.\n"); + } + + + 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))); + } + + } 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, (3.3 * cvm::unit_angstrom())); + 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)); + } + + } else { + cvm::log ("The hBondCoeff specified will disable the hydrogen bond terms.\n"); + } + } + + if (cvm::debug()) + cvm::log ("Done initializing alpha_angles object.\n"); +} + + +colvar::alpha_angles::alpha_angles() + : cvc () +{ + function_type = "alpha_angles"; + 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(); + } +} + +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 - std::pow (t, (int) 2)) / + (1.0 - std::pow (t, (int) 4)) ); + + 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() +{ + for (size_t i = 0; i < theta.size(); i++) + (theta[i])->calc_gradients(); + + for (size_t i = 0; i < hb.size(); i++) + (hb[i])->calc_gradients(); +} + + +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 - std::pow (t, (int) 2)) / + (1.0 - std::pow (t, (int) 4)) ); + + cvm::real const dfdt = + 1.0/(1.0 - std::pow (t, (int) 4)) * + ( (-2.0 * t) + (-1.0*f)*(-4.0 * std::pow (t, (int) 3)) ); + + (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); + } + } +} + + +////////////////////////////////////////////////////////////////////// +// dihedral principal component +////////////////////////////////////////////////////////////////////// + + // FIXME: this will not make collect_gradients work + // because gradients in individual atom groups + // are those of the sub-cvcs (angle, hb), not those + // of this cvc (alpha) + // This is true of all cvcs with sub-cvcs, and those + // that do not calculate explicit gradients + // SO: we need a flag giving the availability of + // atomic gradients +colvar::dihedPC::dihedPC (std::string const &conf) + : cvc (conf) +{ + if (cvm::debug()) + cvm::log ("Initializing dihedral PC object.\n"); + + function_type = "dihedPC"; + 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::fatal_error ("Error: no residues defined in \"residueRange\".\n"); + } + } + + if (residues.size() < 2) { + cvm::fatal_error ("Error: dihedralPC requires at least two residues.\n"); + } + + 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::fatal_error ("A positive value of vectorNumber is required."); + + std::ifstream vecFile; + vecFile.open (vecFileName.c_str()); + if (!vecFile.good()) + cvm::fatal_error ("Error opening dihedral PCA vector file " + vecFileName + " for reading"); + + // 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); + } + } + */ + vecFile.close(); + + } else { + get_keyval (conf, "vector", coeffs, coeffs); + } + + if ( coeffs.size() != 4 * (residues.size() - 1)) { + cvm::fatal_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"); + } + + 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))); + // 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))); + } + + if (cvm::debug()) + cvm::log ("Done initializing dihedPC object.\n"); +} + + +colvar::dihedPC::dihedPC() + : cvc () +{ + function_type = "dihedPC"; + x.type (colvarvalue::type_scalar); +} + + +colvar::dihedPC::~dihedPC() +{ + while (theta.size() != 0) { + delete theta.back(); + theta.pop_back(); + } +} + + +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 ] * std::cos(t) + + coeffs[2*i+1] * std::sin(t); + } +} + + +void colvar::dihedPC::calc_gradients() +{ + for (size_t i = 0; i < theta.size(); i++) { + theta[i]->calc_gradients(); + } +} + + +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.) * std::sin(t); + cvm::real const dsindt = (PI / 180.) * std::cos(t); + + theta[i]->apply_force((coeffs[2*i ] * dcosdt + + coeffs[2*i+1] * dsindt) * force); + } +} diff --git a/lib/colvars/colvarcomp_rotations.cpp b/lib/colvars/colvarcomp_rotations.cpp new file mode 100644 index 0000000000..449613fa30 --- /dev/null +++ b/lib/colvars/colvarcomp_rotations.cpp @@ -0,0 +1,371 @@ +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" + + + +colvar::orientation::orientation (std::string const &conf) + : cvc (conf) +{ + function_type = "orientation"; + parse_group (conf, "atoms", atoms); + atom_groups.push_back (&atoms); + x.type (colvarvalue::type_quaternion); + + 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()) { + cvm::fatal_error ("Error: reference positions do not " + "match the number of requested atoms.\n"); + } + } + + { + std::string file_name; + if (get_keyval (conf, "refPositionsFile", file_name)) { + + std::string file_col; + double file_col_value; + 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) + cvm::fatal_error ("Error: refPositionsColValue, " + "if provided, must be non-zero.\n"); + } else { + // if not, use atom indices + atoms.create_sorted_ids(); + } + ref_pos.resize (atoms.size()); + cvm::load_coords (file_name.c_str(), ref_pos, atoms.sorted_ids, file_col, file_col_value); + } + } + + if (!ref_pos.size()) { + cvm::fatal_error ("Error: must define a set of " + "reference coordinates.\n"); + } + + + cvm::log ("Centering the reference coordinates: it is " + "assumed that each atom is the closest " + "periodic image to the center of geometry.\n"); + cvm::rvector cog (0.0, 0.0, 0.0); + for (size_t i = 0; i < ref_pos.size(); i++) { + cog += ref_pos[i]; + } + cog /= cvm::real (ref_pos.size()); + for (size_t i = 0; i < ref_pos.size(); i++) { + ref_pos[i] -= cog; + } + + get_keyval (conf, "closestToQuaternion", ref_quat, cvm::quaternion (1.0, 0.0, 0.0, 0.0)); + + // initialize rot member data + if (!atoms.noforce) { + rot.request_group2_gradients (atoms.size()); + } + + rot.b_debug_gradients = this->b_debug_gradients; +} + + +colvar::orientation::orientation() + : cvc () +{ + function_type = "orientation"; + x.type (colvarvalue::type_quaternion); +} + + +void colvar::orientation::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + + atoms_cog = atoms.center_of_geometry(); + + rot.calc_optimal_rotation (ref_pos, atoms.positions_shifted (-1.0 * atoms_cog)); + + 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) { + for (size_t ia = 0; ia < atoms.size(); ia++) { + for (size_t i = 0; i < 4; i++) { + atoms[ia].apply_force (FQ[i] * rot.dQ0_2[ia][i]); + } + } + } +} + + + +colvar::orientation_angle::orientation_angle (std::string const &conf) + : orientation (conf) +{ + function_type = "orientation_angle"; + x.type (colvarvalue::type_scalar); +} + + +colvar::orientation_angle::orientation_angle() + : orientation() +{ + function_type = "orientation_angle"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::orientation_angle::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + + atoms_cog = atoms.center_of_geometry(); + + rot.calc_optimal_rotation (ref_pos, atoms.positions_shifted (-1.0 * atoms_cog)); + + if ((rot.q).q0 >= 0.0) { + x.real_value = (180.0/PI) * 2.0 * std::acos ((rot.q).q0); + } else { + x.real_value = (180.0/PI) * 2.0 * std::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) / std::sqrt (1.0 - ((rot.q).q0 * (rot.q).q0))) : + 0.0 ); + + for (size_t ia = 0; ia < atoms.size(); ia++) { + atoms[ia].grad = (dxdq0 * (rot.dQ0_2[ia])[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); + } +} + + +colvar::tilt::tilt (std::string const &conf) + : orientation (conf) +{ + function_type = "tilt"; + + 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"); + } + + x.type (colvarvalue::type_scalar); +} + + +colvar::tilt::tilt() + : orientation() +{ + function_type = "tilt"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::tilt::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + + atoms_cog = atoms.center_of_geometry(); + + rot.calc_optimal_rotation (ref_pos, atoms.positions_shifted (-1.0 * atoms_cog)); + + x.real_value = rot.cos_theta (axis); +} + + +void colvar::tilt::calc_gradients() +{ + cvm::quaternion const dxdq = rot.dcos_theta_dq (axis); + + for (size_t ia = 0; ia < atoms.size(); ia++) { + atoms[ia].grad = cvm::rvector (0.0, 0.0, 0.0); + for (size_t iq = 0; iq < 4; iq++) { + atoms[ia].grad += (dxdq[iq] * (rot.dQ0_2[ia])[iq]); + } + } + + if (cvm::debug()) { + + std::vector pos_test (atoms.positions_shifted (-1.0 * atoms_cog)); + + for (size_t comp = 0; comp < 3; comp++) { + + // correct this cartesian component for the "new" center of geometry + for (size_t ia = 0; ia < atoms.size(); ia++) { + pos_test[ia][comp] -= + cvm::debug_gradients_step_size / cvm::real (atoms.size()); + } + + for (size_t ia = 0; ia < atoms.size(); ia++) { + + pos_test[ia][comp] += cvm::debug_gradients_step_size; + rot.calc_optimal_rotation (ref_pos, pos_test); + pos_test[ia][comp] -= cvm::debug_gradients_step_size; + + cvm::log ("|dx(real) - dx(pred)|/dx(real) = "+ + cvm::to_str (std::fabs (rot.cos_theta (axis) - x.real_value - + cvm::debug_gradients_step_size * atoms[ia].grad[comp]) / + std::fabs (rot.cos_theta (axis) - x.real_value), + cvm::cv_width, cvm::cv_prec)+".\n"); + } + + for (size_t ia = 0; ia < atoms.size(); ia++) { + pos_test[ia][comp] += + cvm::debug_gradients_step_size / cvm::real (atoms.size()); + } + } + } +} + + +void colvar::tilt::apply_force (colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + + if (!atoms.noforce) { + atoms.apply_colvar_force (fw); + } +} + + + +colvar::spin_angle::spin_angle (std::string const &conf) + : orientation (conf) +{ + function_type = "spin_angle"; + + 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"); + } + + period = 360.0; + b_periodic = true; + x.type (colvarvalue::type_scalar); +} + + +colvar::spin_angle::spin_angle() + : orientation() +{ + function_type = "spin_angle"; + period = 360.0; + b_periodic = true; + x.type (colvarvalue::type_scalar); +} + + +void colvar::spin_angle::calc_value() +{ + atoms.reset_atoms_data(); + atoms.read_positions(); + + atoms_cog = atoms.center_of_geometry(); + + rot.calc_optimal_rotation (ref_pos, atoms.positions_shifted (-1.0 * atoms_cog)); + + 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); + + for (size_t ia = 0; ia < atoms.size(); ia++) { + atoms[ia].grad = cvm::rvector (0.0, 0.0, 0.0); + for (size_t iq = 0; iq < 4; iq++) { + atoms[ia].grad += (dxdq[iq] * (rot.dQ0_2[ia])[iq]); + } + } + + if (cvm::debug()) { + + std::vector pos_test (atoms.positions_shifted (-1.0 * atoms_cog)); + + for (size_t comp = 0; comp < 3; comp++) { + + // correct this cartesian component for the "new" center of geometry + for (size_t ia = 0; ia < atoms.size(); ia++) { + pos_test[ia][comp] -= + cvm::debug_gradients_step_size / cvm::real (atoms.size()); + } + + for (size_t ia = 0; ia < atoms.size(); ia++) { + + pos_test[ia][comp] += cvm::debug_gradients_step_size; + rot.calc_optimal_rotation (ref_pos, pos_test); + pos_test[ia][comp] -= cvm::debug_gradients_step_size; + + cvm::log ("|dx(real) - dx(pred)|/dx(real) = "+ + cvm::to_str (std::fabs (rot.spin_angle (axis) - x.real_value - + cvm::debug_gradients_step_size * atoms[ia].grad[comp]) / + std::fabs (rot.spin_angle (axis) - x.real_value), + cvm::cv_width, cvm::cv_prec)+".\n"); + } + + for (size_t ia = 0; ia < atoms.size(); ia++) { + pos_test[ia][comp] += + cvm::debug_gradients_step_size / cvm::real (atoms.size()); + } + } + } +} + + +void colvar::spin_angle::apply_force (colvarvalue const &force) +{ + cvm::real const &fw = force.real_value; + + if (!atoms.noforce) { + atoms.apply_colvar_force (fw); + } +} diff --git a/lib/colvars/colvargrid.cpp b/lib/colvars/colvargrid.cpp new file mode 100644 index 0000000000..c6099bcb8f --- /dev/null +++ b/lib/colvars/colvargrid.cpp @@ -0,0 +1,217 @@ +// -*- c++ -*- + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" +#include "colvar.h" +#include "colvarcomp.h" +#include "colvargrid.h" + + +colvar_grid_count::colvar_grid_count() + : colvar_grid() +{} + +colvar_grid_count::colvar_grid_count (std::vector const &nx_i, + size_t const &def_count) + : colvar_grid (nx_i, def_count) +{} + +colvar_grid_count::colvar_grid_count (std::vector &colvars, + size_t const &def_count) + : colvar_grid (colvars, def_count) +{} + +std::istream & colvar_grid_count::read_restart (std::istream &is) +{ + size_t const start_pos = is.tellg(); + std::string key, conf; + if ((is >> key) && (key == std::string ("grid_parameters"))) { + is.seekg (start_pos, std::ios::beg); + is >> colvarparse::read_block ("grid_parameters", conf); + parse_params (conf); + } else { + cvm::log ("Grid parameters are missing in the restart file, using those from the configuration.\n"); + is.seekg (start_pos, std::ios::beg); + } + read_raw (is); + return is; +} + +std::ostream & colvar_grid_count::write_restart (std::ostream &os) +{ + write_params (os); + write_raw (os); + return os; +} + + + +colvar_grid_scalar::colvar_grid_scalar() + : colvar_grid(), samples (NULL), grad (NULL) +{} + +colvar_grid_scalar::colvar_grid_scalar (colvar_grid_scalar const &g) + : colvar_grid (g), samples (NULL), grad (NULL) +{ + grad = new cvm::real[nd]; +} + +colvar_grid_scalar::colvar_grid_scalar (std::vector const &nx_i) + : colvar_grid (nx_i, 0.0, 1), samples (NULL) +{ + grad = new cvm::real[nd]; +} + +colvar_grid_scalar::colvar_grid_scalar (std::vector &colvars, bool margin) + : colvar_grid (colvars, 0.0, 1, margin), samples (NULL) +{ + grad = new cvm::real[nd]; +} + +colvar_grid_scalar::~colvar_grid_scalar() +{ + if (grad) { + delete [] grad; + grad = NULL; + } +} + +std::istream & colvar_grid_scalar::read_restart (std::istream &is) +{ + size_t const start_pos = is.tellg(); + std::string key, conf; + if ((is >> key) && (key == std::string ("grid_parameters"))) { + is.seekg (start_pos, std::ios::beg); + is >> colvarparse::read_block ("grid_parameters", conf); + parse_params (conf); + } else { + cvm::log ("Grid parameters are missing in the restart file, using those from the configuration.\n"); + is.seekg (start_pos, std::ios::beg); + } + read_raw (is); + return is; +} + +std::ostream & colvar_grid_scalar::write_restart (std::ostream &os) +{ + write_params (os); + write_raw (os); + return os; +} + + + +colvar_grid_gradient::colvar_grid_gradient() + : colvar_grid(), samples (NULL) +{} + +colvar_grid_gradient::colvar_grid_gradient (std::vector const &nx_i) + : colvar_grid (nx_i, 0.0, nx_i.size()), samples (NULL) +{} + +colvar_grid_gradient::colvar_grid_gradient (std::vector &colvars) + : colvar_grid (colvars, 0.0, colvars.size()), samples (NULL) +{} + +std::istream & colvar_grid_gradient::read_restart (std::istream &is) +{ + size_t const start_pos = is.tellg(); + std::string key, conf; + if ((is >> key) && (key == std::string ("grid_parameters"))) { + is.seekg (start_pos, std::ios::beg); + is >> colvarparse::read_block ("grid_parameters", conf); + parse_params (conf); + } else { + cvm::log ("Grid parameters are missing in the restart file, using those from the configuration.\n"); + is.seekg (start_pos, std::ios::beg); + } + read_raw (is); + return is; +} + +std::ostream & colvar_grid_gradient::write_restart (std::ostream &os) +{ + write_params (os); + write_raw (os); + return os; +} + +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::fatal_error ("Cannot write integral for multi-dimensional gradient grids."); + } + + integral = 0.0; + int_vals.push_back ( 0.0 ); + bin = 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), bin += 1.0 ) { + + 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; +} + + + + +// quaternion_grid::quaternion_grid (std::vector const &cv_i, +// std::vector const &grid_str) +// { +// cv = cv_i; + +// std::istringstream is (grid_str[0]); +// is >> grid_size; + +// min.assign (3, -1.0); +// max.assign (3, 1.0); +// np.assign (3, grid_size); +// dx.assign (3, 2.0/(cvm::real (grid_size))); + +// // assumes a uniform grid in the three directions; change +// // get_value() if you want to use different sizes +// cvm::log ("Allocating quaternion grid ("+cvm::to_str (np.size())+" dimensional)..."); +// data.create (np, 0.0); +// cvm::log ("done.\n"); +// if (cvm::debug()) cvm::log ("Grid size = "+data.size()); +// } + diff --git a/lib/colvars/colvargrid.h b/lib/colvars/colvargrid.h new file mode 100644 index 0000000000..f5171fa01b --- /dev/null +++ b/lib/colvars/colvargrid.h @@ -0,0 +1,1163 @@ +// -*- c++ -*- + +#ifndef COLVARGRID_H +#define COLVARGRID_H + +#include +#include +#include + +#include "colvar.h" +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" + +/// \brief Grid of values of a function of several collective +/// variables \param T The data type +/// +/// Only scalar colvars supported so far +template class colvar_grid : public colvarparse { + +protected: + + /// 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) + 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; + + /// 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]*nxc[i]; + if (cvm::debug()) { + if (ix[i] >= nx[i]) + cvm::fatal_error ("Error: exceeding bounds in colvar_grid.\n"); + } + } + 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; + + /// 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 colvars + inline size_t number_of_colvars() const + { + return nd; + } + + /// 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 Allocate data (allow initialization also after construction) + void create (std::vector const &nx_i, + T const &t = T(), + size_t const &mult_i = 1) + { + mult = mult_i; + nd = nx_i.size(); + nxc.resize (nd); + nx = nx_i; + + nt = mult; + for (int i = nd-1; i >= 0; i--) { + if (nx_i[i] <= 0) + cvm::fatal_error ("Error: providing an invalid number of points, "+ + cvm::to_str (nx_i[i])+".\n"); + nxc[i] = nt; + nt *= nx[i]; + } + + data.reserve (nt); + data.assign (nt, t); + } + + /// \brief Allocate data (allow initialization also after construction) + void create() + { + create (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) + { + save_delimiters = false; + nd = nt = 0; + } + + /// Destructor + virtual ~colvar_grid() + {} + + /// \brief "Almost copy-constructor": only copies configuration + /// parameters from another grid, but doesn't reallocate stuff; + /// create() must be called after that; + colvar_grid (colvar_grid const &g) : has_data (false), + nd (g.nd), + mult (g.mult), + cv (g.cv), + lower_boundaries (g.lower_boundaries), + upper_boundaries (g.upper_boundaries), + periodic (g.periodic), + widths (g.widths), + data() + { + save_delimiters = 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 const &mult_i = 1) : has_data (false) + { + save_delimiters = false; + this->create (nx_i, t, mult_i); + } + + /// \brief Constructor from a vector of colvars + colvar_grid (std::vector const &colvars, + T const &t = T(), + size_t const &mult_i = 1, + bool margin = false) + : cv (colvars), has_data (false) + { + save_delimiters = false; + + std::vector nx_i; + + if (cvm::debug()) + cvm::log ("Allocating a grid for "+cvm::to_str (colvars.size())+ + " collective variables.\n"); + + for (size_t i = 0; i < cv.size(); i++) { + + if (cv[i]->type() != colvarvalue::type_scalar) { + cvm::fatal_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"); + } + + if (cv[i]->width <= 0.0) { + cvm::fatal_error ("Tried to initialize a grid on a " + "variable with negative or zero width.\n"); + } + + if (!cv[i]->tasks[colvar::task_lower_boundary] || !cv[i]->tasks[colvar::task_upper_boundary]) { + cvm::fatal_error ("Tried to initialize a grid on a " + "variable with undefined boundaries.\n"); + } + + widths.push_back (cv[i]->width); + periodic.push_back (cv[i]->periodic_boundaries()); + + if (margin) { + 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); + } + + + { + cvm::real nbins = ( upper_boundaries[i].real_value - + lower_boundaries[i].real_value ) / widths[i]; + int nbins_round = (int)(nbins+0.5); + + if (std::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_i.push_back (nbins_round); + } + + } + + create (nx_i, t, mult_i); + } + + + /// 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::fatal_error ("Trying to wrap illegal index vector (non-PBC): " + + cvm::to_str (ix)); + } + } + } + + /// \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 (cv[i]->value(), 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) std::floor ( (value.real_value - lower_boundaries[i].real_value) / widths[i] ); + } + + /// \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) std::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; + } + + + /// \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 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 Get the bin indices corresponding to the provided values of + /// the colvars + inline std::vector 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 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) + { + cvm::real minimum = 1.0E+16; + for (size_t i = 0; i < nd; i++) { + + if (periodic[i]) continue; + + cvm::real dl = std::sqrt (cv[i]->dist2 (values[i], lower_boundaries[i])) / widths[i]; + cvm::real du = std::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 (dl < minimum) + minimum = dl; + if (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::fatal_error ("Error: trying to merge two grids with values of " + "different multiplicity.\n"); + + 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::fatal_error ("Error: trying to sum togetehr two grids with values of " + "different multiplicity.\n"); + if (scale_factor != 1.0) + for (size_t i = 0; i < data.size(); i++) { + data[i] += 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 inline T value_output (std::vector const &ix, + size_t const &imult = 0) + { + 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 inline 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; + } + } + } + + /// \brief Write the grid parameters (number of colvars, boundaries, width and number of points) + std::ostream & write_params (std::ostream &os) + { + os << "grid_parameters {\n n_colvars " << nd << "\n"; + + os << " lower_boundaries "; + for (size_t i = 0; i < nd; i++) + os << " " << lower_boundaries[i]; + os << "\n"; + + os << " upper_boundaries "; + for (size_t i = 0; i < nd; i++) + os << " " << upper_boundaries[i]; + os << "\n"; + + os << " widths "; + for (size_t i = 0; i < nd; i++) + os << " " << widths[i]; + os << "\n"; + + os << " sizes "; + for (size_t i = 0; i < nd; i++) + os << " " << nx[i]; + os << "\n"; + + os << "}\n"; + return os; + } + + + bool parse_params (std::string const &conf) + { + std::vector old_nx = nx; + std::vector old_lb = lower_boundaries; + + { + size_t nd_in = 0; + colvarparse::get_keyval (conf, "n_colvars", nd_in, nd, colvarparse::parse_silent); + if (nd_in != nd) + cvm::fatal_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"); + } + + 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); + + colvarparse::get_keyval (conf, "widths", widths, widths, colvarparse::parse_silent); + + colvarparse::get_keyval (conf, "sizes", nx, nx, colvarparse::parse_silent); + + bool new_params = false; + for (size_t i = 0; i < nd; i++) { + if ( (old_nx[i] != nx[i]) || + (std::sqrt (cv[i]->dist2 (old_lb[i], + lower_boundaries[i])) > 1.0E-10) ) { + new_params = true; + } + } + + // reallocate the array in case the grid params have just changed + if (new_params) { + data.resize (0); + this->create (nx, T(), mult); + } + + return true; + } + + /// \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 ( (std::sqrt (cv[i]->dist2 (cv[i]->lower_boundary, + lower_boundaries[i])) > 1.0E-10) || + (std::sqrt (cv[i]->dist2 (cv[i]->upper_boundary, + upper_boundaries[i])) > 1.0E-10) || + (std::sqrt (cv[i]->dist2 (cv[i]->width, + widths[i])) > 1.0E-10) ) { + cvm::fatal_error ("Error: restart information for a grid is " + "inconsistent with that of its colvars.\n"); + } + } + } + + + /// \brief Check that the grid information inside (boundaries, + /// widths, ...) is consistent with the one 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 ( (std::fabs (other_grid.lower_boundaries[i] - + lower_boundaries[i]) > 1.0E-10) || + (std::fabs (other_grid.upper_boundaries[i] - + upper_boundaries[i]) > 1.0E-10) || + (std::fabs (other_grid.widths[i] - + widths[i]) > 1.0E-10) || + (data.size() != other_grid.data.size()) ) { + cvm::fatal_error ("Error: inconsistency between " + "two grids that are supposed to be equal, " + "aside from the data stored.\n"); + } + } +} + + +/// \brief Write the grid data without labels, as they are +/// represented in memory +/// \param buf_size Number of values per line + std::ostream & write_raw (std::ostream &os, + size_t const buf_size = 3) + { + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + std::vector ix = new_index(); + size_t count = 0; + for ( ; 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; + } + +/// \brief Read data written by colvar_grid::write_raw() +std::istream & read_raw (std::istream &is) +{ + size_t const start_pos = is.tellg(); + + for (std::vector ix = new_index(); index_ok (ix); incr (ix)) { + for (size_t imult = 0; imult < mult; imult++) { + T new_value; + if (is >> new_value) { + value_input (ix, new_value, imult); + } else { + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + } + } + + has_data = true; + return is; +} + +/// \brief To be called after colvar_grid::read_raw() returns an error +void read_raw_error() +{ + cvm::fatal_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"); +} + +/// \brief Write the grid in a format which is both human readable +/// and suitable for visualization e.g. with gnuplot +void write_multicol (std::ostream &os) +{ + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + // Data in the header: nColvars, then for each + // xiMin, dXi, nPoints, periodic + + os << std::setw (2) << "# " << nd << "\n"; + for (size_t i = 0; i < nd; i++) { + os << "# " + << std::setw (10) << lower_boundaries[i] + << std::setw (10) << 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 (w) << std::setprecision (p) + << bin_to_value_scalar (ix[i], i); + } + os << " "; + for (size_t imult = 0; imult < mult; imult++) { + os << " " + << std::setw (w) << std::setprecision (p) + << value_output (ix, imult); + } + os << "\n"; + } +} + +/// \brief Read a grid written by colvar_grid::write_multicol() +/// Adding data if add is true, replacing if false +std::istream & read_multicol (std::istream &is, bool add = false) +{ + // Data in the header: nColvars, then for each + // xiMin, dXi, nPoints, periodic + + std::string hash; + cvm::real lower, width, x; + size_t n, periodic; + bool remap; + std::vector new_value; + std::vector nx_read; + std::vector bin; + + if ( cv.size() != nd ) { + cvm::fatal_error ("Cannot read grid file: missing reference to colvars."); + } + + if ( !(is >> hash) || (hash != "#") ) { + cvm::fatal_error ("Error reading grid at position "+ + cvm::to_str (is.tellg())+" in stream (read \"" + hash + "\")\n"); + } + + is >> n; + if ( n != nd ) { + cvm::fatal_error ("Error reading grid: wrong number of collective variables.\n"); + } + + 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::fatal_error ("Error reading grid at position "+ + cvm::to_str (is.tellg())+" in stream (read \"" + hash + "\")\n"); + } + + is >> lower >> width >> nx_read[i] >> periodic; + + + if ( (std::fabs (lower - lower_boundaries[i].real_value) > 1.0e-10) || + (std::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; +} + +}; + + + +/// \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 inline ~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); + + /// 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]; + } + + /// \brief Read the grid from a restart + std::istream & read_restart (std::istream &is); + + /// \brief Write the grid to a restart + std::ostream & write_restart (std::ostream &os); + + /// \brief Get the value from a formatted output and transform it + /// into the internal representation (it may have been rescaled or + /// manipulated) + virtual inline void value_input (std::vector const &ix, + size_t const &t, + size_t const &imult = 0, + bool add = false) + { + 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; + } +}; + + +/// 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 + ~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 margin = 0); + + /// Accumulate the value + inline void acc_value (std::vector const &ix, + cvm::real const &new_value, + size_t const &imult = 0) + { + // only legal value of imult here is 0 + data[address (ix)] += new_value; + if (samples) + samples->incr_count (ix); + has_data = true; + } + + /// Return the gradient of the scalar field from finite differences + inline const cvm::real * gradient_finite_diff ( const std::vector &ix0 ) + { + cvm::real A0, A1; + std::vector ix; + if (nd != 2) cvm::fatal_error ("Finite differences available in dimension 2 only."); + for (int n = 0; n < nd; n++) { + ix = ix0; + A0 = data[address (ix)]; + ix[n]++; wrap (ix); + A1 = data[address (ix)]; + ix[1-n]++; wrap (ix); + A1 += data[address (ix)]; + ix[n]--; wrap (ix); + A0 += data[address (ix)]; + grad[n] = 0.5 * (A1 - A0) / widths[n]; + } + return grad; + } + + /// \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) + { + if (imult > 0) + cvm::fatal_error ("Error: trying to access a component " + "larger than 1 in a scalar data grid.\n"); + if (samples) + return (samples->value (ix) > 0) ? + (data[address (ix)] / cvm::real (samples->value (ix))) : + 0.0; + else + return data[address (ix)]; + } + + /// \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 (imult > 0) + cvm::fatal_error ("Error: trying to access a component " + "larger than 1 in a scalar data grid.\n"); + 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 Read the grid from a restart + std::istream & read_restart (std::istream &is); + + /// \brief Write the grid to a restart + std::ostream & write_restart (std::ostream &os); + + /// \brief Return the highest value + inline cvm::real maximum_value() + { + cvm::real max = data[0]; + for (size_t i = 0; i < nt; i++) { + if (data[i] > max) max = data[i]; + } + return max; + } + + /// \brief Return the lowest value + inline cvm::real minimum_value() + { + cvm::real min = data[0]; + for (size_t i = 0; i < nt; i++) { + if (data[i] < min) min = data[i]; + } + return min; + } + +private: + // gradient + cvm::real * grad; +}; + + + +/// 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; + + /// Default constructor + colvar_grid_gradient(); + + /// Destructor + virtual inline ~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); + + /// \brief Accumulate the gradient + inline void acc_grad (std::vector const &ix, cvm::real const *grads) { + for (size_t imult = 0; imult < mult; imult++) { + data[address (ix) + imult] += grads[imult]; + } + 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 Return the value of the function at ix divided by its + /// number of samples (if the count grid is defined) + virtual inline cvm::real value_output (std::vector const &ix, + size_t const &imult = 0) + { + 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 inline 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; + } + + + /// \brief Read the grid from a restart + std::istream & read_restart (std::istream &is); + + /// \brief Write the grid to a restart + std::ostream & write_restart (std::ostream &os); + + /// 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); + +}; + + +#endif + diff --git a/lib/colvars/colvarmodule.cpp b/lib/colvars/colvarmodule.cpp new file mode 100644 index 0000000000..6a9f5a3e8d --- /dev/null +++ b/lib/colvars/colvarmodule.cpp @@ -0,0 +1,1360 @@ +#include "colvarmodule.h" +#include "colvarparse.h" +#include "colvarproxy.h" +#include "colvar.h" +#include "colvarbias.h" +#include "colvarbias_meta.h" +#include "colvarbias_abf.h" + + +colvarmodule::colvarmodule (char const *config_filename, + colvarproxy *proxy_in) +{ + // pointer to the proxy object + if (proxy == NULL) { + proxy = proxy_in; + parse = new colvarparse(); + } else { + cvm::fatal_error ("Error: trying to allocate twice the collective " + "variable module.\n"); + } + + cvm::log (cvm::line_marker); + cvm::log ("Initializing the collective variables module, version "+ + cvm::to_str(COLVARS_VERSION)+".\n"); + + // "it_restart" will be set by the input state file, if any; + // "it" should be updated by the proxy + it = it_restart = 0; + it_restart_from_state_file = true; + + // open the configfile + config_s.open (config_filename); + if (!config_s) + cvm::fatal_error ("Error: in opening configuration file \""+ + std::string (config_filename)+"\".\n"); + + // read the config file into a string + std::string conf = ""; + { + std::string line; + while (colvarparse::getline_nocomments (config_s, line)) + conf.append (line+"\n"); + // don't need the stream any more + config_s.close(); + } + + parse->get_keyval (conf, "analysis", b_analysis, false); + + if (cvm::debug()) + parse->get_keyval (conf, "debugGradientsStepSize", debug_gradients_step_size, 1.0e-03, + colvarparse::parse_silent); + + parse->get_keyval (conf, "eigenvalueCrossingThreshold", + colvarmodule::rotation::crossing_threshold, 1.0e-04, + colvarparse::parse_silent); + + parse->get_keyval (conf, "colvarsTrajFrequency", cv_traj_freq, 100); + parse->get_keyval (conf, "colvarsRestartFrequency", restart_out_freq, + proxy->restart_frequency()); + + // by default overwrite the existing trajectory file + parse->get_keyval (conf, "colvarsTrajAppend", cv_traj_append, false); + + // input restart file + restart_in_name = proxy->input_prefix().size() ? + std::string (proxy->input_prefix()+".colvars.state") : + std::string ("") ; + + // output restart file + restart_out_name = proxy->restart_output_prefix().size() ? + std::string (proxy->restart_output_prefix()+".colvars.state") : + std::string (""); + + if (restart_out_name.size()) + cvm::log ("The restart output state file will be \""+restart_out_name+"\".\n"); + + output_prefix = proxy->output_prefix(); + + cvm::log ("The final output state file will be \""+ + (output_prefix.size() ? + std::string (output_prefix+".colvars.state") : + std::string ("colvars.state"))+"\".\n"); + + cv_traj_name = + (output_prefix.size() ? + std::string (output_prefix+".colvars.traj") : + std::string ("colvars.traj")); + cvm::log ("The trajectory file will be \""+ + cv_traj_name+"\".\n"); + + // open trajectory file + if (cv_traj_append) { + cvm::log ("Appending to colvar trajectory file \""+cv_traj_name+ + "\".\n"); + cv_traj_os.open (cv_traj_name.c_str(), std::ios::app); + } else { + proxy->backup_file (cv_traj_name.c_str()); + cv_traj_os.open (cv_traj_name.c_str(), std::ios::out); + } + cv_traj_os.setf (std::ios::scientific, std::ios::floatfield); + + // parse the options for collective variables + init_colvars (conf); + + // parse the options for biases + init_biases (conf); + + // done with the parsing, check that all keywords are valid + parse->check_keywords (conf, "colvarmodule"); + cvm::log (cvm::line_marker); + + // read the restart configuration, if available + if (restart_in_name.size()) { + // read the restart file + std::ifstream input_is (restart_in_name.c_str()); + if (!input_is.good()) + fatal_error ("Error: in opening restart file \""+ + std::string (restart_in_name)+"\".\n"); + else { + cvm::log ("Restarting from file \""+restart_in_name+"\".\n"); + read_restart (input_is); + cvm::log (cvm::line_marker); + } + } + + // check if it is possible to save output configuration + if ((!output_prefix.size()) && (!restart_out_name.size())) { + cvm::fatal_error ("Error: neither the final output state file or " + "the output restart file could be defined, exiting.\n"); + } + + cvm::log ("Collective variables module initialized.\n"); + cvm::log (cvm::line_marker); +} + + +std::istream & colvarmodule::read_restart (std::istream &is) +{ + { + // read global restart information + std::string restart_conf; + if (is >> colvarparse::read_block ("configuration", restart_conf)) { + if (it_restart_from_state_file) { + parse->get_keyval (restart_conf, "step", + it_restart, (size_t) 0, + colvarparse::parse_silent); + it = it_restart; + } + } + is.clear(); + } + + // colvars restart + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + if ( !((*cvi)->read_restart (is)) ) + cvm::fatal_error ("Error: in reading restart configuration for collective variable \""+ + (*cvi)->name+"\".\n"); + } + + // biases restart + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if (!((*bi)->read_restart (is))) + fatal_error ("Error: in reading restart configuration for bias \""+ + (*bi)->name+"\".\n"); + } + cvm::decrease_depth(); + + return is; +} + + + +void colvarmodule::init_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 (colvar_conf)); + (colvars.back())->check_keywords (colvar_conf, "colvar"); + cvm::decrease_depth(); + } else { + cvm::log ("Warning: \"colvar\" keyword found without any configuration.\n"); + } + colvar_conf = ""; + } + + + if (!colvars.size()) + cvm::fatal_error ("Error: 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"); +} + + +void colvarmodule::init_biases (std::string const &conf) +{ + if (cvm::debug()) + cvm::log ("Initializing the collective variables biases.\n"); + + { + /// initialize ABF instances + std::string abf_conf = ""; + size_t abf_pos = 0; + while (parse->key_lookup (conf, "abf", abf_conf, abf_pos)) { + if (abf_conf.size()) { + cvm::log (cvm::line_marker); + cvm::increase_depth(); + biases.push_back (new colvarbias_abf (abf_conf, "abf")); + (biases.back())->check_keywords (abf_conf, "abf"); + cvm::decrease_depth(); + n_abf_biases++; + } else { + cvm::log ("Warning: \"abf\" keyword found without configuration.\n"); + } + abf_conf = ""; + } + } + + { + /// initialize harmonic restraints + std::string harm_conf = ""; + size_t harm_pos = 0; + while (parse->key_lookup (conf, "harmonic", harm_conf, harm_pos)) { + if (harm_conf.size()) { + cvm::log (cvm::line_marker); + cvm::increase_depth(); + biases.push_back (new colvarbias_harmonic (harm_conf, "harmonic")); + (biases.back())->check_keywords (harm_conf, "harmonic"); + cvm::decrease_depth(); + n_harm_biases++; + } else { + cvm::log ("Warning: \"harmonic\" keyword found without configuration.\n"); + } + harm_conf = ""; + } + } + + { + /// initialize histograms + std::string histo_conf = ""; + size_t histo_pos = 0; + while (parse->key_lookup (conf, "histogram", histo_conf, histo_pos)) { + if (histo_conf.size()) { + cvm::log (cvm::line_marker); + cvm::increase_depth(); + biases.push_back (new colvarbias_histogram (histo_conf, "histogram")); + (biases.back())->check_keywords (histo_conf, "histogram"); + cvm::decrease_depth(); + n_histo_biases++; + } else { + cvm::log ("Warning: \"histogram\" keyword found without configuration.\n"); + } + histo_conf = ""; + } + } + + { + /// initialize metadynamics instances + std::string meta_conf = ""; + size_t meta_pos = 0; + while (parse->key_lookup (conf, "metadynamics", meta_conf, meta_pos)) { + if (meta_conf.size()) { + cvm::log (cvm::line_marker); + cvm::increase_depth(); + biases.push_back (new colvarbias_meta (meta_conf, "metadynamics")); + (biases.back())->check_keywords (meta_conf, "metadynamics"); + cvm::decrease_depth(); + n_meta_biases++; + } else { + cvm::log ("Warning: \"metadynamics\" keyword found without configuration.\n"); + } + meta_conf = ""; + } + } + + if (biases.size()) + cvm::log (cvm::line_marker); + cvm::log ("Collective variables biases initialized, "+ + cvm::to_str (biases.size())+" in total.\n"); +} + + +void colvarmodule::change_configuration( + std::string const &name, std::string const &conf) +{ + cvm::increase_depth(); + int found = 0; + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if ( (*bi)->name == name ) { + ++found; + (*bi)->change_configuration(conf); + } + } + if ( found < 1 ) cvm::fatal_error ("Error: bias not found"); + if ( found > 1 ) cvm::fatal_error ("Error: duplicate bias name"); + cvm::decrease_depth(); +} + +cvm::real colvarmodule::energy_difference( + std::string const &name, std::string const &conf) +{ + cvm::increase_depth(); + cvm::real energy_diff = 0.; + int found = 0; + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + if ( (*bi)->name == name ) { + ++found; + energy_diff = (*bi)->energy_difference(conf); + } + } + if ( found < 1 ) cvm::fatal_error ("Error: bias not found"); + if ( found > 1 ) cvm::fatal_error ("Error: duplicate bias name"); + cvm::decrease_depth(); + return energy_diff; +} + + +void colvarmodule::calc() { + cvm::real total_bias_energy = 0.0; + cvm::real total_colvar_energy = 0.0; + + if (cvm::debug()) { + cvm::log (cvm::line_marker); + cvm::log ("Collective variables module, step no. "+ + cvm::to_str (cvm::step_absolute())+"\n"); + } + + // calculate collective variables and their gradients + if (cvm::debug()) + cvm::log ("Calculating collective variables.\n"); + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->calc(); + } + cvm::decrease_depth(); + + // update the biases and communicate their forces to the collective + // variables + if (cvm::debug() && biases.size()) + cvm::log ("Updating collective variable biases.\n"); + cvm::increase_depth(); + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + total_bias_energy += (*bi)->update(); + } + cvm::decrease_depth(); + + // sum the forces from all biases for each collective variable + if (cvm::debug() && biases.size()) + cvm::log ("Collecting forces from all biases.\n"); + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->reset_bias_force(); + } + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + (*bi)->communicate_forces(); + } + cvm::decrease_depth(); + + if (cvm::b_analysis) { + // perform runtime analysis of colvars and biases + if (cvm::debug() && biases.size()) + cvm::log ("Perform runtime analyses.\n"); + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->analyse(); + } + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + (*bi)->analyse(); + } + cvm::decrease_depth(); + } + + // sum up the forces for each colvar and integrate any internal + // equation of motion + if (cvm::debug()) + cvm::log ("Updating the internal degrees of freedom " + "of colvars (if they have any).\n"); + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + total_colvar_energy += (*cvi)->update(); + } + cvm::decrease_depth(); + proxy->add_energy (total_bias_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 (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + if ((*cvi)->tasks[colvar::task_gradients]) + (*cvi)->communicate_forces(); + } + cvm::decrease_depth(); + + // write restart file, if needed + if (restart_out_freq && !cvm::b_analysis) { + if ( (cvm::step_relative() > 0) && + ((cvm::step_absolute() % restart_out_freq) == 0) ) { + cvm::log ("Writing the state file \""+ + restart_out_name+"\".\n"); + proxy->backup_file (restart_out_name.c_str()); + restart_out_os.open (restart_out_name.c_str()); + restart_out_os.setf (std::ios::scientific, std::ios::floatfield); + if (!write_restart (restart_out_os)) + cvm::fatal_error ("Error: in writing restart file.\n"); + restart_out_os.close(); + } + } + + // write trajectory file, if needed + if (cv_traj_freq) { + + if (cvm::debug()) + cvm::log ("Writing trajectory file.\n"); + + // (re)open trajectory file + if (!cv_traj_os.good()) { + if (cv_traj_append) { + cvm::log ("Appending to colvar trajectory file \""+cv_traj_name+ + "\".\n"); + cv_traj_os.open (cv_traj_name.c_str(), std::ios::app); + } else { + cvm::log ("Overwriting colvar trajectory file \""+cv_traj_name+ + "\".\n"); + proxy->backup_file (cv_traj_name.c_str()); + cv_traj_os.open (cv_traj_name.c_str(), std::ios::out); + } + cv_traj_os.setf (std::ios::scientific, std::ios::floatfield); + } + + // write labels in the traj file every 1000 lines and at first ts + cvm::increase_depth(); + if ((cvm::step_absolute() % (cv_traj_freq * 1000)) == 0 || cvm::step_relative() == 0) { + cv_traj_os << "# " << cvm::wrap_string ("step", cvm::it_width-2) + << " "; + if (cvm::debug()) + cv_traj_os.flush(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->write_traj_label (cv_traj_os); + } + cv_traj_os << "\n"; + if (cvm::debug()) + cv_traj_os.flush(); + } + cvm::decrease_depth(); + + // write collective variable values to the traj file + cvm::increase_depth(); + if ((cvm::step_absolute() % cv_traj_freq) == 0) { + cv_traj_os << std::setw (cvm::it_width) << it + << " "; + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->write_traj (cv_traj_os); + } + cv_traj_os << "\n"; + if (cvm::debug()) + cv_traj_os.flush(); + } + cvm::decrease_depth(); + + if (restart_out_freq) { + // flush the trajectory file if we are at the restart frequency + if ( (cvm::step_relative() > 0) && + ((cvm::step_absolute() % restart_out_freq) == 0) ) { + cvm::log ("Synchronizing (emptying the buffer of) trajectory file \""+ + cv_traj_name+"\".\n"); + cv_traj_os.flush(); + } + } + } // end if (cv_traj_freq) +} + + +void colvarmodule::analyze() +{ + if (cvm::debug()) { + cvm::log ("colvarmodule::analyze(), step = "+cvm::to_str (it)+".\n"); + } + + if (cvm::step_relative() == 0) + cvm::log ("Performing analysis.\n"); + + // perform colvar-specific analysis + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + cvm::increase_depth(); + (*cvi)->analyse(); + cvm::decrease_depth(); + } + + // perform bias-specific analysis + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + cvm::increase_depth(); + (*bi)->analyse(); + cvm::decrease_depth(); + } + +} + + +colvarmodule::~colvarmodule() +{ + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + delete *cvi; + } + colvars.clear(); + + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + delete *bi; + } + biases.clear(); + + if (cv_traj_os.good()) { + cv_traj_os.close(); + } + + delete parse; + proxy = NULL; +} + + +void colvarmodule::write_output_files() +{ + // if this is a simulation run (i.e. not a postprocessing), output data + // must be written to be able to restart the simulation + std::string const out_name = + (output_prefix.size() ? + std::string (output_prefix+".colvars.state") : + std::string ("colvars.state")); + cvm::log ("Saving collective variables state to \""+out_name+"\".\n"); + proxy->backup_file (out_name.c_str()); + std::ofstream out (out_name.c_str()); + out.setf (std::ios::scientific, std::ios::floatfield); + this->write_restart (out); + out.close(); + + // do not close to avoid problems with multiple NAMD runs + cv_traj_os.flush(); +} + + + +bool colvarmodule::read_traj (char const *traj_filename, + size_t traj_read_begin, + size_t traj_read_end) +{ + cvm::log ("Opening trajectory file \""+ + std::string (traj_filename)+"\".\n"); + 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::log ("Reached the end of the trajectory, " + "read_end = "+cvm::to_str (traj_read_end)+"\n"); + return false; + } + + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + if (!(*cvi)->read_traj (is)) { + cvm::log ("Error: in reading colvar \""+(*cvi)->name+ + "\" from trajectory file \""+ + std::string (traj_filename)+"\".\n"); + return false; + } + } + + break; + } + } + } + + return true; +} + + +std::ostream & colvarmodule::write_restart (std::ostream &os) +{ + os << "configuration {\n" + << " step " << std::setw (it_width) + << it << "\n" + << " dt " << dt() << "\n" + << "}\n\n"; + + cvm::increase_depth(); + for (std::vector::iterator cvi = colvars.begin(); + cvi != colvars.end(); + cvi++) { + (*cvi)->write_restart (os); + } + + for (std::vector::iterator bi = biases.begin(); + bi != biases.end(); + bi++) { + (*bi)->write_restart (os); + } + cvm::decrease_depth(); + + return os; +} + + + +void cvm::log (std::string const &message) +{ + if (depth > 0) + proxy->log ((std::string (2*depth, ' '))+message); + else + proxy->log (message); +} + +void cvm::increase_depth() +{ + depth++; +} + +void cvm::decrease_depth() +{ + if (depth) depth--; +} + +void cvm::fatal_error (std::string const &message) +{ + proxy->fatal_error (message); +} + +void cvm::exit (std::string const &message) +{ + proxy->exit (message); +} + + + +// static pointers +std::vector colvarmodule::colvars; +std::vector colvarmodule::biases; +size_t colvarmodule::n_abf_biases = 0; +size_t colvarmodule::n_harm_biases = 0; +size_t colvarmodule::n_histo_biases = 0; +size_t colvarmodule::n_meta_biases = 0; +colvarproxy *colvarmodule::proxy = NULL; + + +// static runtime data +cvm::real colvarmodule::debug_gradients_step_size = 1.0e-03; +size_t colvarmodule::it = 0; +size_t colvarmodule::it_restart = 0; +size_t colvarmodule::restart_out_freq = 0; +size_t colvarmodule::cv_traj_freq = 0; +size_t colvarmodule::depth = 0; +bool colvarmodule::b_analysis = false; +cvm::real colvarmodule::rotation::crossing_threshold = 1.0E-04; + + +// file name prefixes +std::string colvarmodule::output_prefix = ""; +std::string colvarmodule::input_prefix = ""; +std::string colvarmodule::restart_in_name = ""; + + +// 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; +std::string const colvarmodule::line_marker = + "----------------------------------------------------------------------\n"; + + + + + + +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) +{ + size_t 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::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) +{ + size_t const start_pos = is.tellg(); + + std::string euler (""); + + if ( (is >> euler) && (colvarparse::to_lower_cppstr (euler) == + std::string ("euler")) ) { + + // parse the Euler angles + + char sep; + cvm::real phi, theta, psi; + if ( !(is >> sep) || !(sep == '(') || + !(is >> phi) || !(is >> sep) || !(sep == ',') || + !(is >> theta) || !(is >> sep) || !(sep == ',') || + !(is >> psi) || !(is >> sep) || !(sep == ')') ) { + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + q = colvarmodule::quaternion (phi, theta, psi); + + } else { + + // parse the quaternion components + + is.seekg (start_pos, std::ios::beg); + 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; + } + } + + 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; +} + + + + +// Calculate the optimal rotation between two groups, and implement it +// as a quaternion. The method is the one 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::build_matrix (std::vector const &pos1, + std::vector const &pos2, + matrix2d &S) +{ + cvm::rmatrix C; + + // build the correlation matrix + C.reset(); + for (size_t 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; + } + + // 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(); + + // if (cvm::debug()) { + // for (size_t i = 0; i < 4; i++) { + // std::string line (""); + // for (size_t j = 0; j < 4; j++) { + // line += std::string (" S["+cvm::to_str (i)+ + // "]["+cvm::to_str (j)+"] ="+cvm::to_str (S[i][j])); + // } + // cvm::log (line+"\n"); + // } + // } +} + + +void colvarmodule::rotation::diagonalize_matrix (matrix2d &S, + cvm::real S_eigval[4], + matrix2d &S_eigvec) +{ + // diagonalize + int jac_nrot = 0; + jacobi (S, 4, S_eigval, S_eigvec, &jac_nrot); + eigsrt (S_eigval, S_eigvec, 4); + // jacobi saves eigenvectors by columns + transpose (S_eigvec, 4); + + // normalize eigenvectors + for (size_t ie = 0; ie < 4; ie++) { + cvm::real norm2 = 0.0; + for (size_t i = 0; i < 4; i++) norm2 += std::pow (S_eigvec[ie][i], int (2)); + cvm::real const norm = std::sqrt (norm2); + for (size_t i = 0; i < 4; i++) S_eigvec[ie][i] /= norm; + } +} + + +// Calculate the rotation, plus its derivatives + +void colvarmodule::rotation::calc_optimal_rotation +(std::vector const &pos1, + std::vector const &pos2) +{ + matrix2d S; + matrix2d S_backup; + cvm::real S_eigval[4]; + matrix2d S_eigvec; + +// if (cvm::debug()) { +// cvm::atom_pos cog1 (0.0, 0.0, 0.0); +// for (size_t i = 0; i < pos1.size(); i++) { +// cog1 += pos1[i]; +// } +// cog1 /= cvm::real (pos1.size()); +// cvm::atom_pos cog2 (0.0, 0.0, 0.0); +// for (size_t i = 0; i < pos2.size(); i++) { +// cog2 += pos2[i]; +// } +// cog2 /= cvm::real (pos1.size()); +// cvm::log ("calc_optimal_rotation: centers of geometry are: "+ +// cvm::to_str (cog1, cvm::cv_width, cvm::cv_prec)+ +// " and "+cvm::to_str (cog2, cvm::cv_width, cvm::cv_prec)+".\n"); +// } + + build_matrix (pos1, pos2, S); + S_backup = S; + + if (cvm::debug()) { + if (b_debug_gradients) { + cvm::log ("S = "+cvm::to_str (cvm::to_str (S_backup), cvm::cv_width, cvm::cv_prec)+"\n"); + } + } + + diagonalize_matrix (S, S_eigval, S_eigvec); + + // eigenvalues and eigenvectors + cvm::real const &L0 = S_eigval[0]; + cvm::real const &L1 = S_eigval[1]; + cvm::real const &L2 = S_eigval[2]; + cvm::real const &L3 = S_eigval[3]; + cvm::real const *Q0 = S_eigvec[0]; + cvm::real const *Q1 = S_eigvec[1]; + cvm::real const *Q2 = S_eigvec[2]; + cvm::real const *Q3 = S_eigvec[3]; + + lambda = L0; + q = cvm::quaternion (Q0); + + if (q_old.norm2() > 0.0) { + q.match (q_old); + if (q_old.inner (q) < (1.0 - crossing_threshold)) { + cvm::log ("Warning: discontinuous rotation!\n"); + } + } + q_old = q; + + if (cvm::debug()) { + if (b_debug_gradients) { + cvm::log ("L0 = "+cvm::to_str (L0, cvm::cv_width, cvm::cv_prec)+ + ", Q0 = "+cvm::to_str (cvm::quaternion (Q0), cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q0 = "+cvm::to_str (cvm::quaternion (Q0).inner (cvm::quaternion (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 (cvm::quaternion (Q1), cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q1 = "+cvm::to_str (cvm::quaternion (Q0).inner (cvm::quaternion (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 (cvm::quaternion (Q2), cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q2 = "+cvm::to_str (cvm::quaternion (Q0).inner (cvm::quaternion (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 (cvm::quaternion (Q3), cvm::cv_width, cvm::cv_prec)+ + ", Q0*Q3 = "+cvm::to_str (cvm::quaternion (Q0).inner (cvm::quaternion (Q3)), cvm::cv_width, cvm::cv_prec)+ + "\n"); + } + } + + // calculate derivatives of L0 and Q0 with respect to each atom in + // either group; note: if dS_1 is a null vector, nothing will be + // calculated + for (size_t ia = 0; ia < dS_1.size(); ia++) { + + cvm::real const &a2x = pos2[ia].x; + cvm::real const &a2y = pos2[ia].y; + cvm::real const &a2z = pos2[ia].z; + + matrix2d &ds_1 = dS_1[ia]; + + // derivative of the S matrix + ds_1.reset(); + ds_1[0][0] = cvm::rvector ( a2x, a2y, a2z); + ds_1[1][0] = cvm::rvector ( 0.0, a2z, -a2y); + ds_1[0][1] = ds_1[1][0]; + ds_1[2][0] = cvm::rvector (-a2z, 0.0, a2x); + ds_1[0][2] = ds_1[2][0]; + ds_1[3][0] = cvm::rvector ( a2y, -a2x, 0.0); + ds_1[0][3] = ds_1[3][0]; + ds_1[1][1] = cvm::rvector ( a2x, -a2y, -a2z); + ds_1[2][1] = cvm::rvector ( a2y, a2x, 0.0); + ds_1[1][2] = ds_1[2][1]; + ds_1[3][1] = cvm::rvector ( a2z, 0.0, a2x); + ds_1[1][3] = ds_1[3][1]; + ds_1[2][2] = cvm::rvector (-a2x, a2y, -a2z); + ds_1[3][2] = cvm::rvector ( 0.0, a2z, a2y); + ds_1[2][3] = ds_1[3][2]; + ds_1[3][3] = cvm::rvector (-a2x, -a2y, a2z); + + cvm::rvector &dl0_1 = dL0_1[ia]; + vector1d &dq0_1 = dQ0_1[ia]; + + // matrix multiplications; derivatives of L_0 and Q_0 are + // calculated using Hellmann-Feynman theorem (i.e. exploiting the + // fact that the eigenvectors Q_i form an orthonormal basis) + + 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]; + } + } + + 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]; + } + } + } + } + + // do the same for the second group + for (size_t ia = 0; ia < dS_2.size(); ia++) { + + cvm::real const &a1x = pos1[ia].x; + cvm::real const &a1y = pos1[ia].y; + cvm::real const &a1z = pos1[ia].z; + + matrix2d &ds_2 = dS_2[ia]; + + ds_2.reset(); + ds_2[0][0] = cvm::rvector ( a1x, a1y, a1z); + ds_2[1][0] = cvm::rvector ( 0.0, -a1z, a1y); + ds_2[0][1] = ds_2[1][0]; + ds_2[2][0] = cvm::rvector ( a1z, 0.0, -a1x); + ds_2[0][2] = ds_2[2][0]; + ds_2[3][0] = cvm::rvector (-a1y, a1x, 0.0); + ds_2[0][3] = ds_2[3][0]; + ds_2[1][1] = cvm::rvector ( a1x, -a1y, -a1z); + ds_2[2][1] = cvm::rvector ( a1y, a1x, 0.0); + ds_2[1][2] = ds_2[2][1]; + ds_2[3][1] = cvm::rvector ( a1z, 0.0, a1x); + ds_2[1][3] = ds_2[3][1]; + ds_2[2][2] = cvm::rvector (-a1x, a1y, -a1z); + ds_2[3][2] = cvm::rvector ( 0.0, a1z, a1y); + ds_2[2][3] = ds_2[3][2]; + ds_2[3][3] = cvm::rvector (-a1x, -a1y, a1z); + + cvm::rvector &dl0_2 = dL0_2[ia]; + vector1d &dq0_2 = dQ0_2[ia]; + + dl0_2.reset(); + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < 4; j++) { + dl0_2 += Q0[i] * ds_2[i][j] * Q0[j]; + } + } + + dq0_2.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_2[p] += + (Q1[i] * ds_2[i][j] * Q0[j]) / (L0-L1) * Q1[p] + + (Q2[i] * ds_2[i][j] * Q0[j]) / (L0-L2) * Q2[p] + + (Q3[i] * ds_2[i][j] * Q0[j]) / (L0-L3) * Q3[p]; + } + } + } + + if (cvm::debug()) { + + if (b_debug_gradients) { + + matrix2d S_new; + cvm::real S_new_eigval[4]; + matrix2d S_new_eigvec; + + // 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++) { + + S_new = S_backup; + // diagonalize the new overlap matrix + 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]; + } + } + +// cvm::log ("S_new = "+cvm::to_str (cvm::to_str (S_new), cvm::cv_width, cvm::cv_prec)+"\n"); + + diagonalize_matrix (S_new, S_new_eigval, S_new_eigvec); + + cvm::real const &L0_new = S_new_eigval[0]; + cvm::real const *Q0_new = S_new_eigvec[0]; + + cvm::real const DL0 = (dl0_2[comp]) * colvarmodule::debug_gradients_step_size; + cvm::quaternion const q0 (Q0); + 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 (std::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"); + } + } + } + } +} + + + +// Numerical Recipes routine for diagonalization + +#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); +void jacobi(cvm::real **a, int n, cvm::real d[], cvm::real **v, int *nrot) +{ + int j,iq,ip,i; + cvm::real tresh,theta,tau,t,sm,s,h,g,c; + + std::vector b (n, 0.0); + std::vector z (n, 0.0); + + for (ip=0;ip 4 && (cvm::real)(std::fabs(d[ip])+g) == (cvm::real)std::fabs(d[ip]) + && (cvm::real)(std::fabs(d[iq])+g) == (cvm::real)std::fabs(d[iq])) + a[ip][iq]=0.0; + else if (std::fabs(a[ip][iq]) > tresh) { + h=d[iq]-d[ip]; + if ((cvm::real)(std::fabs(h)+g) == (cvm::real)std::fabs(h)) + t=(a[ip][iq])/h; + else { + theta=0.5*h/(a[ip][iq]); + t=1.0/(std::fabs(theta)+std::sqrt(1.0+theta*theta)); + if (theta < 0.0) t = -t; + } + c=1.0/std::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 +#include +#include +#include + + +class colvarparse; +class colvar; +class colvarbias; +class colvarproxy; + + +/// \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(); + +public: + + friend class colvarproxy; + + /// Defining an abstract real number allows to switch precision + typedef double real; + /// Residue identifier + typedef int residue_id; + + class rvector; + template class vector1d; + template class matrix2d; + class quaternion; + class rotation; + + /// \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; + + /// Current step number + static size_t it; + /// Starting step number for this run + static size_t it_restart; + + /// Return the current step number from the beginning of this run + static inline size_t step_relative() + { + return it - it_restart; + } + + /// Return the current step number from the beginning of the whole + /// calculation + static inline size_t step_absolute() + { + return it; + } + + /// If true, get it_restart from the state file; if set to false, + /// the MD program is providing it + bool it_restart_from_state_file; + + /// \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; + + /// Prefix for all output files for this run + static std::string output_prefix; + + /// Prefix for files from a previous run (including restart/output) + static std::string input_prefix; + + /// input restart file name (determined from input_prefix) + static std::string restart_in_name; + + + /// Array of collective variables + static std::vector colvars; + + /* 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); + } + */ + + /// Array of collective variable biases + static std::vector biases; + /// \brief Number of ABF biases initialized (in normal conditions + /// should be 1) + static size_t n_abf_biases; + /// \brief Number of metadynamics biases initialized (in normal + /// conditions should be 1) + static size_t n_meta_biases; + /// \brief Number of harmonic biases initialized (no limit on the + /// number) + static size_t n_harm_biases; + /// \brief Number of histograms initialized (no limit on the + /// number) + static size_t n_histo_biases; + + /// \brief Whether debug output should be enabled (compile-time option) + static inline bool debug() + { + return COLVARS_DEBUG; + } + + + /// \brief Constructor \param config_name Configuration file name + /// \param restart_name (optional) Restart file name + colvarmodule (char const *config_name, + colvarproxy *proxy_in); + /// Destructor + ~colvarmodule(); + + /// Initialize collective variables + void init_colvars (std::string const &conf); + + /// Initialize collective variable biases + void init_biases (std::string const &conf); + + /// Load new configuration - force constant and/or centers only + void change_configuration(std::string const &name, std::string const &conf); + + /// Calculate change in energy from using alternate configuration + real energy_difference(std::string const &name, std::string const &conf); + + /// Calculate collective variables and biases + void calc(); + /// Read the input restart file + std::istream & read_restart (std::istream &is); + /// Write the output restart file + std::ostream & write_restart (std::ostream &os); + /// Write all output files (called by the proxy) + void write_output_files(); + /// \brief Call colvarproxy::backup_file() + static void backup_file (char const *filename); + + /// Perform analysis + void analyze(); + /// \brief Read a collective variable trajectory (post-processing + /// only, not called at runtime) + bool read_traj (char const *traj_filename, + size_t traj_read_begin, + size_t traj_read_end); + + /// Get the pointer of a colvar from its name (returns NULL if not found) + static colvar * colvar_p (std::string const &name); + + /// Quick conversion of an object to a string + template static std::string to_str (T const &x, + size_t const &width = 0, + size_t const &prec = 0); + /// Quick conversion of a vector of objects to a string + template static std::string to_str (std::vector const &x, + size_t const &width = 0, + size_t const &prec = 0); + + /// Reduce the number of characters in a string + static inline std::string wrap_string (std::string const &s, + size_t const &nchars) + { + if (!s.size()) + return std::string (nchars, ' '); + else + return ( (s.size() <= size_t (nchars)) ? + (s+std::string (nchars-s.size(), ' ')) : + (std::string (s, 0, 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 std::string const line_marker; + + + // proxy functions + + /// \brief Value of the unit for atomic coordinates with respect to + /// angstroms (used by some variables for hard-coded default values) + static real unit_angstrom(); + + /// \brief Boltmann constant + static real boltzmann(); + + /// \brief Temperature of the simulation (K) + static real temperature(); + + /// \brief Time step of MD integrator (fs) + static real dt(); + + /// Request calculation of system force from MD engine + static void request_system_force(); + + /// Print a message to the main log + static void log (std::string const &message); + + /// Print a message to the main log and exit with error code + static void fatal_error (std::string const &message); + + /// Print a message to the main log and exit normally + static void exit (std::string const &message); + + + /// \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 Get the square distance between two positions (with + /// periodic boundary conditions handled transparently) + /// + /// Note: in the case of periodic boundary conditions, this provides + /// an analytical square distance (while taking the square of + /// position_distance() would produce leads to a cusp) + static real position_dist2 (atom_pos const &pos1, + atom_pos const &pos2); + + /// \brief Get the closest periodic image to a reference position + /// \param pos The position to look for the closest periodic image + /// \param ref_pos (optional) The reference position + static void select_closest_image (atom_pos &pos, + atom_pos const &ref_pos); + + /// \brief Perform select_closest_image() on a set of atomic positions + /// + /// After that, distance vectors can then be calculated directly, + /// without using position_distance() + static void select_closest_images (std::vector &pos, + atom_pos const &ref_pos); + + + /// \brief Create atoms from a file \param filename name of the file + /// (usually a PDB) \param atoms array of the atoms to be allocated + /// \param pdb_field (optiona) if "filename" is a PDB file, use this + /// field to determine which are the atoms to be set + static void load_atoms (char const *filename, + std::vector &atoms, + std::string const &pdb_field, + double const pdb_field_value = 0.0); + + /// \brief Load the coordinates for a group of atoms from a file + /// (usually a PDB); the number of atoms in "filename" must match + /// the number of elements in "pos" + static void load_coords (char const *filename, + std::vector &pos, + const std::vector &indices, + std::string const &pdb_field, + double const pdb_field_value = 0.0); + + + /// Frequency for collective variables trajectory output + static size_t cv_traj_freq; + + /// \brief True if only analysis is performed and not a run + static bool b_analysis; + + /// 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 (void); +protected: + + /// Configuration file + std::ifstream config_s; + + /// Configuration file parser object + colvarparse *parse; + + /// Name of the trajectory file + std::string cv_traj_name; + + /// Collective variables output trajectory file + std::ofstream cv_traj_os; + + /// Appending to the existing trajectory file? + bool cv_traj_append; + + /// Output restart file + std::ofstream restart_out_os; + + /// \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 Counter for the current depth in the object hierarchy (useg e.g. in outpu + static size_t depth; + +public: + + /// 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(); +}; + + +/// Shorthand for the frequently used type prefix +typedef colvarmodule cvm; + + +#include "colvartypes.h" + + +std::ostream & operator << (std::ostream &os, cvm::rvector const &v); +std::istream & operator >> (std::istream &is, cvm::rvector &v); + + +template std::string cvm::to_str (T const &x, + size_t const &width, + size_t const &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 cvm::to_str (std::vector const &x, + size_t const &width, + size_t const &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(); +} + + +#include "colvarproxy.h" + + +inline cvm::real cvm::unit_angstrom() +{ + return proxy->unit_angstrom(); +} + +inline cvm::real cvm::boltzmann() +{ + return proxy->boltzmann(); +} + +inline cvm::real cvm::temperature() +{ + return proxy->temperature(); +} + +inline cvm::real cvm::dt() +{ + return proxy->dt(); +} + +inline void cvm::request_system_force() +{ + proxy->request_system_force (true); +} + +inline void cvm::select_closest_image (atom_pos &pos, + atom_pos const &ref_pos) +{ + proxy->select_closest_image (pos, ref_pos); +} + +inline void cvm::select_closest_images (std::vector &pos, + atom_pos const &ref_pos) +{ + proxy->select_closest_images (pos, ref_pos); +} + +inline cvm::rvector cvm::position_distance (atom_pos const &pos1, + atom_pos const &pos2) +{ + return proxy->position_distance (pos1, pos2); +} + +inline cvm::real cvm::position_dist2 (cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) +{ + return proxy->position_dist2 (pos1, pos2); +} + +inline void cvm::load_atoms (char const *file_name, + std::vector &atoms, + std::string const &pdb_field, + double const pdb_field_value) +{ + proxy->load_atoms (file_name, atoms, pdb_field, pdb_field_value); +} + +inline void cvm::load_coords (char const *file_name, + std::vector &pos, + const std::vector &indices, + std::string const &pdb_field, + double const pdb_field_value) +{ + proxy->load_coords (file_name, pos, indices, pdb_field, pdb_field_value); +} + +inline void cvm::backup_file (char const *filename) +{ + proxy->backup_file (filename); +} + +inline cvm::real cvm::rand_gaussian (void) +{ + return proxy->rand_gaussian(); +} + +#endif + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarparse.cpp b/lib/colvars/colvarparse.cpp new file mode 100644 index 0000000000..b1f4290549 --- /dev/null +++ b/lib/colvars/colvarparse.cpp @@ -0,0 +1,636 @@ +#include +#include + + + +#include "colvarmodule.h" +#include "colvarvalue.h" +#include "colvarparse.h" + + + +// space & tab +std::string const colvarparse::white_space = " \t"; + +std::string colvarparse::dummy_string = ""; +size_t colvarparse::dummy_pos = 0; + + +// definition of single-value keyword parsers + +#define _get_keyval_scalar_string_(TYPE) \ + \ + bool colvarparse::get_keyval (std::string const &conf, \ + char const *key, \ + TYPE &value, \ + TYPE const &def_value, \ + Parse_Mode const parse_mode) \ + { \ + 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::log ("Warning: found more than one instance of \""+ \ + std::string (key)+"\".\n"); \ + \ + if (data.size()) { \ + std::istringstream is (data); \ + TYPE x (def_value); \ + if (is >> x) \ + value = x; \ + else \ + cvm::fatal_error ("Error: in parsing \""+ \ + std::string (key)+"\".\n"); \ + if (parse_mode != parse_silent) { \ + cvm::log ("# "+std::string (key)+" = "+ \ + cvm::to_str (value)+"\n"); \ + } \ + } else { \ + \ + if (b_found_any) \ + cvm::fatal_error ("Error: improper or missing value " \ + "for \""+std::string (key)+"\".\n"); \ + value = def_value; \ + if (parse_mode != parse_silent) { \ + cvm::log ("# "+std::string (key)+" = \""+ \ + cvm::to_str (def_value)+"\" [default]\n"); \ + } \ + } \ + \ + return b_found_any; \ + } + + +#define _get_keyval_scalar_(TYPE) \ + \ + bool colvarparse::get_keyval (std::string const &conf, \ + char const *key, \ + TYPE &value, \ + TYPE const &def_value, \ + Parse_Mode const parse_mode) \ + { \ + 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::log ("Warning: found more than one instance of \""+ \ + std::string (key)+"\".\n"); \ + \ + if (data.size()) { \ + std::istringstream is (data); \ + TYPE x (def_value); \ + if (is >> x) \ + value = x; \ + else \ + cvm::fatal_error ("Error: in parsing \""+ \ + std::string (key)+"\".\n"); \ + if (parse_mode != parse_silent) { \ + cvm::log ("# "+std::string (key)+" = "+ \ + cvm::to_str (value)+"\n"); \ + } \ + } else { \ + \ + if (b_found_any) \ + cvm::fatal_error ("Error: improper or missing value " \ + "for \""+std::string (key)+"\".\n"); \ + value = def_value; \ + if (parse_mode != parse_silent) { \ + cvm::log ("# "+std::string (key)+" = "+ \ + cvm::to_str (def_value)+" [default]\n"); \ + } \ + } \ + \ + return b_found_any; \ + } + + +// definition of multiple-value keyword parsers + +#define _get_keyval_vector_(TYPE) \ + \ + bool colvarparse::get_keyval (std::string const &conf, \ + char const *key, \ + std::vector &values, \ + std::vector const &def_values, \ + Parse_Mode const parse_mode) \ + { \ + 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::log ("Warning: found more than one instance of \""+ \ + std::string (key)+"\".\n"); \ + \ + 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::fatal_error ("Error: in parsing \""+ \ + std::string (key)+"\".\n"); \ + } \ + } \ + \ + if (parse_mode != parse_silent) { \ + cvm::log ("# "+std::string (key)+" = "+ \ + cvm::to_str (values)+"\n"); \ + } \ + \ + } else { \ + \ + if (b_found_any) \ + cvm::fatal_error ("Error: improper or missing values for \""+ \ + std::string (key)+"\".\n"); \ + \ + for (size_t i = 0; i < values.size(); i++) \ + values[i] = def_values[ (i > def_values.size()) ? 0 : i ]; \ + \ + if (parse_mode != parse_silent) { \ + cvm::log ("# "+std::string (key)+" = "+ \ + cvm::to_str (def_values)+" [default]\n"); \ + } \ + } \ + \ + return b_found_any; \ + } + + +// single-value keyword parsers + +_get_keyval_scalar_ (int); +_get_keyval_scalar_ (size_t); +_get_keyval_scalar_string_ (std::string); +_get_keyval_scalar_ (cvm::real); +_get_keyval_scalar_ (cvm::rvector); +_get_keyval_scalar_ (cvm::quaternion); +_get_keyval_scalar_ (colvarvalue); + + +// multiple-value keyword parsers + +_get_keyval_vector_ (int); +_get_keyval_vector_ (size_t); +_get_keyval_vector_ (std::string); +_get_keyval_vector_ (cvm::real); +_get_keyval_vector_ (cvm::rvector); +_get_keyval_vector_ (cvm::quaternion); +_get_keyval_vector_ (colvarvalue); + + + +bool colvarparse::get_keyval (std::string const &conf, + char const *key, + bool &value, + bool const &def_value, + Parse_Mode const parse_mode) +{ + 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::log ("Warning: found more than one instance of \""+ + std::string (key)+"\".\n"); + + if (data.size()) { + std::istringstream is (data); + if ( (data == std::string ("on")) || + (data == std::string ("yes")) || + (data == std::string ("true")) ) { + value = true; + } else if ( (data == std::string ("off")) || + (data == std::string ("no")) || + (data == std::string ("false")) ) { + value = false; + } else + cvm::fatal_error ("Error: boolean values only are allowed " + "for \""+std::string (key)+"\".\n"); + if (parse_mode != parse_silent) { + cvm::log ("# "+std::string (key)+" = "+ + (value ? "on" : "off")+"\n"); + } + } else { + + if (b_found_any) { + if (parse_mode != parse_silent) { + cvm::log ("# "+std::string (key)+" = on\n"); + } + value = true; + } else { + value = def_value; + if (parse_mode != parse_silent) { + cvm::log ("# "+std::string (key)+" = "+ + (def_value ? "on" : "off")+" [default]\n"); + } + } + } + + return b_found_any; +} + + +void colvarparse::add_keyword (char const *key) +{ + for (std::list::iterator ki = allowed_keywords.begin(); + ki != allowed_keywords.end(); ki++) { + if (to_lower_cppstr (std::string (key)) == *ki) + return; + } + // not found in the list + // if (cvm::debug()) + // cvm::log ("Registering a new keyword, \""+std::string (key)+"\".\n"); + allowed_keywords.push_back (to_lower_cppstr (std::string (key))); +} + + +void colvarparse::strip_values (std::string &conf) +{ + size_t offset = 0; + data_begin_pos.sort(); + data_end_pos.sort(); + + 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++) { + + // std::cerr << "data_begin, data_end " + // << *data_begin << ", " << *data_end + // << "\n"; + + size_t const nchars = *data_end-*data_begin; + + // std::cerr << "conf[data_begin:data_end] = \"" + // << std::string (conf, *data_begin - offset, nchars) + // << "\"\n"; + + conf.erase (*data_begin - offset, nchars); + offset += nchars; + + // std::cerr << ("Stripped config = \"\n"+conf+"\"\n"); + + } +} + + +void 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 (std::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::fatal_error ("Error: keyword \""+uk+"\" is not supported, " + "or not recognized in this context.\n"); + } +} + + +std::istream & colvarparse::getline_nocomments (std::istream &is, + std::string &line, + char const delim) +{ + std::getline (is, line, delim); + 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) +{ + // 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 + data = ""; + + // when the function is invoked without save_pos, ensure that we + // start from zero + colvarparse::dummy_pos = 0; + + // start from the first occurrence of key + size_t pos = conf_lower.find (key, save_pos); + + // iterate over all instances until it finds the isolated keyword + while (true) { + + if (pos == std::string::npos) { + // no valid instance of the keyword has been found + return false; + } + + bool b_isolated_left = true, b_isolated_right = true; + + if (pos > 0) { + if ( std::string ("\n"+white_space+ + "}").find (conf[pos-1]) == + std::string::npos ) { + // none of the valid delimiting characters is on the left of key + b_isolated_left = false; + } + } + + if (pos < conf.size()-key.size()-1) { + if ( std::string ("\n"+white_space+ + "{").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 = brace_check (conf, pos); + + 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()); + } + } + + // check it is not between quotes + // if ( (conf.find_last_of ("\"", + // conf.find_last_of (white_space, pos)) != + // std::string::npos) && + // (conf.find_first_of ("\"", + // conf.find_first_of (white_space, pos)) != + // std::string::npos) ) + // return false; + + + // 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 : pos; + 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); + + // size_t data_begin_absolute = data_begin + line_begin; + // size_t data_end_absolute = data_begin; + + 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; + // data_end_absolute = data_end + line_begin; + + if (line.find ('{', data_begin) != std::string::npos) { + + size_t brace_count = 1; + size_t brace = line.find ('{', data_begin); // start from the first opening brace + + while (brace_count > 0) { + + // find the matching closing brace + brace = line.find_first_of ("{}", brace+1); + while (brace == std::string::npos) { + // add a new line + if (line_end >= conf.size()) { + cvm::fatal_error ("Parse error: reached the end while " + "looking for closing brace; until now " + "the following was parsed: \"\n"+ + line+"\".\n"); + return false; + } + size_t const old_end = line.size(); + // data_end_absolute += old_end+1; + + 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)); + + brace = line.find_first_of ("{}", old_end); + } + + if (line[brace] == '{') brace_count++; + if (line[brace] == '}') brace_count--; + } + + // set data_begin afterthe opening brace + data_begin = line.find_first_of ('{', data_begin) + 1; + data_begin = line.find_first_not_of (white_space, + data_begin); + // set data_end before the closing brace + data_end = brace; + data_end = line.find_last_not_of (white_space+"}", + data_end) + 1; + // data_end_absolute = line_end; + + if (data_end > line.size()) + data_end = line.size(); + } + + data.append (line, data_begin, (data_end-data_begin)); + + if (data.size() && save_delimiters) { + data_begin_pos.push_back (conf.find (data, pos+key.size())); + data_end_pos.push_back (data_begin_pos.back()+data.size()); + // std::cerr << "key = " << key << ", data = \"" + // << data << "\", data_begin, data_end = " + // << data_begin_pos.back() << ", " << data_end_pos.back() + // << "\n"; + } + } + + save_pos = line_end; + + return true; +} + + +std::istream & operator>> (std::istream &is, colvarparse::read_block const &rb) +{ + size_t start_pos = is.tellg(); + std::string read_key, next; + + if ( !(is >> read_key) || !(read_key == rb.key) || + !(is >> next) ) { + // the requested keyword has not been found, or it is not possible + // to read data after it + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + return is; + } + + if (next != "{") { + (*rb.data) = next; + return is; + } + + size_t brace_count = 1; + std::string line; + while (colvarparse::getline_nocomments (is, line)) { + size_t br = 0, br_old; + 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) (*rb.data).append (line + "\n"); + else { + (*rb.data).append (line, 0, br_old); + break; + } + } + if (brace_count) { + // end-of-file reached + // restore initial position + is.clear(); + is.seekg (start_pos, std::ios::beg); + is.setstate (std::ios::failbit); + } + return is; +} + + +bool colvarparse::brace_check (std::string const &conf, + size_t const start_pos) +{ + size_t 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++; + } + + if (brace_count != 0) + return false; + else + return true; +} + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarparse.h b/lib/colvars/colvarparse.h new file mode 100644 index 0000000000..e7e1d96275 --- /dev/null +++ b/lib/colvars/colvarparse.h @@ -0,0 +1,201 @@ +#ifndef COLVARPARSE_H +#define COLVARPARSE_H + +#include +#include + +#include "colvarmodule.h" +#include "colvarvalue.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 { + +protected: + + /// \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; + + /// \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 Whether or not to accumulate data_begin_pos and + /// data_end_pos in key_lookup(); it may be useful to disable + /// this after the constructor is called, because other files may be + /// read (e.g. restart) that would mess up with the registry; in any + /// case, nothing serious happens until check_keywords() is invoked + /// (which should happen only right after construction) + bool save_delimiters; + + /// \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); + +public: + + inline colvarparse() + : save_delimiters (true) + {} + + /// How a keyword is parsed in a string + enum Parse_Mode { + /// \brief (default) Read the first instance of a keyword (if + /// any), report its value, and print a warning when there is more + /// than one + parse_normal, + /// \brief Like parse_normal, but don't send any message to the log + /// (useful e.g. in restart files when such messages are very + /// numerous and redundant) + parse_silent + }; + + /// \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). + +#define _get_keyval_scalar_proto_(_type_,_def_value_) \ + bool get_keyval (std::string const &conf, \ + char const *key, \ + _type_ &value, \ + _type_ const &def_value = _def_value_, \ + Parse_Mode const parse_mode = parse_normal) + + _get_keyval_scalar_proto_ (int, (int)0); + _get_keyval_scalar_proto_ (size_t, (size_t)0); + _get_keyval_scalar_proto_ (std::string, std::string ("")); + _get_keyval_scalar_proto_ (cvm::real, (cvm::real)0.0); + _get_keyval_scalar_proto_ (cvm::rvector, cvm::rvector()); + _get_keyval_scalar_proto_ (cvm::quaternion, cvm::quaternion()); + _get_keyval_scalar_proto_ (colvarvalue, colvarvalue (colvarvalue::type_notset)); + _get_keyval_scalar_proto_ (bool, false); + +#define _get_keyval_vector_proto_(_type_,_def_value_) \ + bool get_keyval (std::string const &conf, \ + char const *key, \ + std::vector<_type_> &values, \ + std::vector<_type_> const &def_values = \ + std::vector<_type_> (0, static_cast<_type_>(_def_value_)), \ + Parse_Mode const parse_mode = parse_normal) + + _get_keyval_vector_proto_ (int, 0); + _get_keyval_vector_proto_ (size_t, 0); + _get_keyval_vector_proto_ (std::string, std::string ("")); + _get_keyval_vector_proto_ (cvm::real, 0.0); + _get_keyval_vector_proto_ (cvm::rvector, cvm::rvector()); + _get_keyval_vector_proto_ (cvm::quaternion, cvm::quaternion()); + _get_keyval_vector_proto_ (colvarvalue, colvarvalue (colvarvalue::type_notset)); + + + /// \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 + void check_keywords (std::string &conf, char const *key); + + + /// \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, (char) ::tolower (in[i]) ); + } + return out; + } + + /// \brief Helper class to read a block of the type "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 { + + std::string const key; + std::string * const data; + + public: + inline read_block (std::string const &key_in, std::string &data_in) + : key (key_in), data (&data_in) + {} + inline ~read_block() {} + friend std::istream & operator >> (std::istream &is, read_block const &rb); + }; + + + /// Accepted white space delimiters, used in key_lookup() + static std::string const white_space; + + /// \brief Low-level function for parsing configuration strings; + /// automatically adds the requested keywords 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 in "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 \param + /// save_delimiters (optional) + bool key_lookup (std::string const &conf, + char const *key, + std::string &data = dummy_string, + size_t &save_pos = dummy_pos); + + /// Used as a default argument by key_lookup + static std::string dummy_string; + /// Used as a default argument by key_lookup + static size_t dummy_pos; + + /// \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, + char const delim = '\n'); + + /// Check if the content of the file has matching braces + bool brace_check (std::string const &conf, + size_t const start_pos = 0); + +}; + + +#endif + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarproxy.h b/lib/colvars/colvarproxy.h new file mode 100644 index 0000000000..0af4826c96 --- /dev/null +++ b/lib/colvars/colvarproxy.h @@ -0,0 +1,137 @@ +#ifndef COLVARPROXY_H +#define COLVARPROXY_H + + + +#include "colvarmodule.h" + + +/// \brief Interface class between the collective variables module and +/// the simulation program + +class colvarproxy { + +public: + + /// Pointer to the instance of colvarmodule + colvarmodule *colvars; + + /// \brief Value of the unit for atomic coordinates with respect to + /// angstroms (used by some variables for hard-coded default values) + virtual cvm::real unit_angstrom() = 0; + + /// \brief Boltzmann constant + virtual cvm::real boltzmann() = 0; + + /// \brief Temperature of the simulation (K) + virtual cvm::real temperature() = 0; + + /// \brief Time step of the simulation (fs) + virtual cvm::real dt() = 0; + + /// Pass restraint energy value for current timestep to MD engine + virtual void add_energy (cvm::real energy) = 0; + + /// Tell the proxy whether system forces are needed + virtual void request_system_force (bool yesno) = 0; + + /// Print a message to the main log + virtual void log (std::string const &message) = 0; + + /// Print a message to the main log and exit with error code + virtual void fatal_error (std::string const &message) = 0; + + /// Print a message to the main log and exit normally + virtual void exit (std::string const &message) = 0; + + + /// \brief Prefix to be used for input files (restarts, not + /// configuration) + virtual std::string input_prefix() = 0; + + /// \brief Prefix to be used for output restart files + virtual std::string restart_output_prefix() = 0; + + /// \brief Prefix to be used for output files (final system + /// configuration) + virtual std::string output_prefix() = 0; + + /// \brief Restarts will be fritten each time this number of steps has passed + virtual size_t restart_frequency() = 0; + + // **************** PERIODIC BOUNDARY CONDITIONS **************** + + /// \brief Get the simple distance vector between two positions + /// (with periodic boundary conditions handled transparently) + virtual cvm::rvector position_distance (cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) = 0; + + /// \brief Get the square distance between two positions (with + /// periodic boundary conditions handled transparently) + /// + /// Note: in the case of periodic boundary conditions, this provides + /// an analytical square distance (while taking the square of + /// position_distance() would produce leads to a cusp) + virtual cvm::real position_dist2 (cvm::atom_pos const &pos1, + cvm::atom_pos const &pos2) = 0; + + /// \brief Get the closest periodic image to a reference position + /// \param pos The position to look for the closest periodic image + /// \param ref_pos The reference position + virtual void select_closest_image (cvm::atom_pos &pos, + cvm::atom_pos const &ref_pos) = 0; + + /// \brief Perform select_closest_image() on a set of atomic positions + /// + /// After that, distance vectors can then be calculated directly, + /// without using position_distance() + void select_closest_images (std::vector &pos, + cvm::atom_pos const &ref_pos); + + + /// \brief 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 + virtual void load_atoms (char const *filename, + std::vector &atoms, + std::string const pdb_field, + double const pdb_field_value = 0.0) = 0; + + /// \brief 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" + virtual void load_coords (char const *filename, + std::vector &pos, + const std::vector &indices, + std::string const pdb_field, + double const pdb_field_value = 0.0) = 0; + + /// \brief Rename the given file, under the convention provided by + /// the MD program + virtual void backup_file (char const *filename) = 0; + + /// \brief Pseudo-random number with Gaussian distribution + virtual cvm::real rand_gaussian (void) = 0; + + virtual inline ~colvarproxy() {} +}; + + +inline void colvarproxy::select_closest_images (std::vector &pos, + cvm::atom_pos const &ref_pos) +{ + for (std::vector::iterator pi = pos.begin(); + pi != pos.end(); pi++) { + select_closest_image (*pi, ref_pos); + } +} + +#endif + + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvartypes.h b/lib/colvars/colvartypes.h new file mode 100644 index 0000000000..29289cc76c --- /dev/null +++ b/lib/colvars/colvartypes.h @@ -0,0 +1,1110 @@ +#ifndef COLVARTYPES_H +#define COLVARTYPES_H + +#include + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +// ---------------------------------------------------------------------- +/// Linear algebra functions and data types used in the collective +/// variables implemented so far +// ---------------------------------------------------------------------- + + +/// 1-dimensional vector of real numbers with three components +class colvarmodule::rvector { + +public: + + cvm::real x, y, z; + + inline rvector() + : x (0.0), y (0.0), z (0.0) + {} + + inline rvector (cvm::real const &x_i, + cvm::real const &y_i, + cvm::real const &z_i) + : x (x_i), y (y_i), z (z_i) + {} + + inline rvector (cvm::real v) + : x (v), y (v), z (v) + {} + + /// \brief Set all components to a scalar value + inline void set (cvm::real const value = 0.0) { + x = y = z = value; + } + + /// \brief Set all components to zero + inline void reset() { + x = y = z = 0.0; + } + + /// \brief Access cartesian components by index + inline cvm::real & operator [] (int const &i) { + return (i == 0) ? x : (i == 1) ? y : (i == 2) ? z : x; + } + + /// \brief Access cartesian components by index + inline cvm::real const & operator [] (int const &i) const { + return (i == 0) ? x : (i == 1) ? y : (i == 2) ? z : x; + } + + + inline cvm::rvector & operator = (cvm::real const &v) + { + x = v; + y = v; + z = v; + return *this; + } + + 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 const &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 std::sqrt (this->norm2()); + } + + inline cvm::rvector unit() const + { + return cvm::rvector (x, y, z)/this->norm(); + } + + static inline size_t output_width (size_t const &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 int operator == (cvm::rvector const &v1, cvm::rvector const &v2) + { + return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); + } + + friend inline int 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::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); + } + + 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 const &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 const &a) + { + return cvm::rvector (a*v.x, a*v.y, a*v.z); + } + + friend inline cvm::rvector operator / (cvm::rvector const &v, cvm::real const &a) + { + return cvm::rvector (v.x/a, v.y/a, v.z/a); + } + + +}; + + + +/// \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: + + /// Underlying C-array + T *array; + +public: + + /// Length of the array + inline size_t size() + { + return length; + } + + /// Default constructor + inline vector1d (T const &t = T()) + { + array = new T[length]; + reset(); + } + + /// Set all elements to zero + inline void reset() + { + for (size_t i = 0; i < length; i++) { + array[i] = T (0.0); + } + } + + /// Constructor from a 1-d C array + inline vector1d (T const *v) + { + array = new T[length]; + for (size_t i = 0; i < length; i++) { + array[i] = v[i]; + } + } + + /// Copy constructor + inline vector1d (vector1d const &v) + { + array = new T[length]; + for (size_t i = 0; i < length; i++) { + array[i] = v.array[i]; + } + } + + /// Assignment + inline vector1d & operator = (vector1d const &v) + { + for (size_t i = 0; i < length; i++) { + this->array[i] = v.array[i]; + } + return *this; + } + + /// Destructor + inline ~vector1d() { + delete [] array; + } + + /// Return the 1-d C array + inline T *c_array() { return array; } + + /// Return the 1-d C array + inline operator T *() { return array; } + + /// Inner product + inline friend T operator * (vector1d const &v1, + vector1d const &v2) + { + T prod (0.0); + for (size_t i = 0; i < length; i++) { + prod += v1.array[i] * v2.array[i]; + } + return prod; + } + + /// Formatted output + friend std::ostream & operator << (std::ostream &os, + vector1d const &v) + { + std::streamsize const w = os.width(); + std::streamsize const p = os.precision(); + + os << "( "; + for (size_t i = 0; i < length-1; i++) { + os.width (w); os.precision (p); + os << v.array[i] << " , "; + } + os.width (w); os.precision (p); + os << v.array[length-1] << " )"; + return os; + } + +}; + + + +/// \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 +{ +protected: + + /// Underlying C array + T **array; + +public: + + /// Allocation routine, used by all constructors + inline void alloc() { + array = new T * [outer_length]; + for (size_t i = 0; i < outer_length; i++) { + array[i] = new T [inner_length]; + } + } + + /// Set all elements to zero + inline void reset() + { + for (size_t i = 0; i < outer_length; i++) { + for (size_t j = 0; j < inner_length; j++) { + array[i][j] = T (0.0); + } + } + } + + /// Default constructor + inline matrix2d() + { + this->alloc(); + reset(); + } + + /// Constructor from a 2-d C array + inline matrix2d (T const **m) + { + this->alloc(); + for (size_t i = 0; i < outer_length; i++) { + for (size_t j = 0; j < inner_length; j++) + array[i][j] = m[i][j]; + } + } + + /// Copy constructor + inline matrix2d (matrix2d const &m) + { + this->alloc(); + for (size_t i = 0; i < outer_length; i++) { + for (size_t j = 0; j < inner_length; j++) + this->array[i][j] = m.array[i][j]; + } + } + + /// Assignment + inline matrix2d & + operator = (matrix2d const &m) + { + for (size_t i = 0; i < outer_length; i++) { + for (size_t j = 0; j < inner_length; j++) + this->array[i][j] = m.array[i][j]; + } + return *this; + } + + /// Destructor + inline ~matrix2d() { + for (size_t i = 0; i < outer_length; i++) { + delete [] array[i]; + } + delete [] array; + } + + /// Return the 2-d C array + inline T **c_array() { return array; } + + /// Return the 2-d C array + inline operator T **() { return array; } + +// /// Matrix addi(c)tion +// inline friend matrix2d +// operator + (matrix2d const &mat1, +// matrix2d const &mat2) { +// matrix2d sum; +// for (size_t i = 0; i < outer_length; i++) { +// for (size_t j = 0; j < inner_length; j++) { +// sum[i][j] = mat1[i][j] + mat2[i][j]; +// } +// } +// } + +// /// Matrix subtraction +// inline friend matrix2d +// operator - (matrix2d const &mat1, +// matrix2d const &mat2) { +// matrix2d sum; +// for (size_t i = 0; i < outer_length; i++) { +// for (size_t j = 0; j < inner_length; j++) { +// sum[i][j] = mat1[i][j] - mat2[i][j]; +// } +// } +// } + + /// 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 << "("; + for (size_t i = 0; i < outer_length; i++) { + os << " ( "; + for (size_t j = 0; j < inner_length-1; j++) { + os.width (w); + os.precision (p); + os << m.array[i][j] << " , "; + } + os.width (w); + os.precision (p); + os << m.array[i][inner_length-1] << " )"; + } + + os << " )"; + return os; + } + +}; + + +/// \brief 2-dimensional array of real numbers with three components +/// along each dimension (works with colvarmodule::rvector) +class colvarmodule::rmatrix + : public colvarmodule::matrix2d { +private: + +public: + + /// Return the xx element + inline cvm::real & xx() { return array[0][0]; } + /// Return the xy element + inline cvm::real & xy() { return array[0][1]; } + /// Return the xz element + inline cvm::real & xz() { return array[0][2]; } + /// Return the yx element + inline cvm::real & yx() { return array[1][0]; } + /// Return the yy element + inline cvm::real & yy() { return array[1][1]; } + /// Return the yz element + inline cvm::real & yz() { return array[1][2]; } + /// Return the zx element + inline cvm::real & zx() { return array[2][0]; } + /// Return the zy element + inline cvm::real & zy() { return array[2][1]; } + /// Return the zz element + inline cvm::real & zz() { return array[2][2]; } + + /// Return the xx element + inline cvm::real xx() const { return array[0][0]; } + /// Return the xy element + inline cvm::real xy() const { return array[0][1]; } + /// Return the xz element + inline cvm::real xz() const { return array[0][2]; } + /// Return the yx element + inline cvm::real yx() const { return array[1][0]; } + /// Return the yy element + inline cvm::real yy() const { return array[1][1]; } + /// Return the yz element + inline cvm::real yz() const { return array[1][2]; } + /// Return the zx element + inline cvm::real zx() const { return array[2][0]; } + /// Return the zy element + inline cvm::real zy() const { return array[2][1]; } + /// Return the zz element + inline cvm::real zz() const { return array[2][2]; } + + /// Constructor from a 2-d C array + inline rmatrix (cvm::real const **m) + : cvm::matrix2d (m) + {} + + /// Default constructor + inline rmatrix() + : cvm::matrix2d() + {} + + /// Constructor component by component + inline rmatrix (cvm::real const &xxi, + cvm::real const &xyi, + cvm::real const &xzi, + cvm::real const &yxi, + cvm::real const &yyi, + cvm::real const &yzi, + cvm::real const &zxi, + cvm::real const &zyi, + cvm::real const &zzi) + : cvm::matrix2d() + { + this->xx() = xxi; + this->xy() = xyi; + this->xz() = xzi; + this->yx() = yxi; + this->yy() = yyi; + this->yz() = yzi; + this->zx() = zxi; + this->zy() = zyi; + this->zz() = zzi; + } + + /// Destructor + inline ~rmatrix() + {} + + /// 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 (this->xx(), + this->yx(), + this->zx(), + this->xy(), + this->yy(), + this->zy(), + this->xz(), + this->yz(), + this->zz()); + } + + friend cvm::rvector operator * (cvm::rmatrix const &m, cvm::rvector const &r); + + // friend inline cvm::rmatrix const operator * (cvm::rmatrix const &m1, cvm::rmatrix const &m2) { + // return cvm::rmatrix (m1.xx()*m2.xx() + m1.xy()*m2.yx() + m1.xz()*m2.yz(), + // m1.xx()*m2.xy() + m1.xy()*m2.yy() + m1.xz()*m2.zy(), + // m1.xx()*m2.xz() + m1.xy()*m2.yz() + m1.xz()*m2.zz(), + // m1.yx()*m2.xx() + m1.yy()*m2.yx() + m1.yz()*m2.yz(), + // m1.yx()*m2.xy() + m1.yy()*m2.yy() + m1.yz()*m2.yy(), + // m1.yx()*m2.xz() + m1.yy()*m2.yz() + m1.yz()*m2.yz(), + // m1.zx()*m2.xx() + m1.zy()*m2.yx() + m1.zz()*m2.yz(), + // m1.zx()*m2.xy() + m1.zy()*m2.yy() + m1.zz()*m2.yy(), + // m1.zx()*m2.xz() + m1.zy()*m2.yz() + m1.zz()*m2.yz()); + // } + +}; + + +inline 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); +} + + +/// Numerical recipes diagonalization +void jacobi (cvm::real **a, int n, cvm::real d[], cvm::real **v, int *nrot); + +/// Eigenvector sort +void eigsrt (cvm::real d[], cvm::real **v, int n); + +/// Transpose the matrix +void transpose (cvm::real **v, int n); + + + + +/// \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 const &x, cvm::real const &y, cvm::real const &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 const &q0i, + cvm::real const &q1i, + cvm::real const &q2i, + cvm::real const &q3i) + : q0 (q0i), q1 (q1i), q2 (q2i), q3 (q3i) + {} + + /// "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 const &phi_in, + cvm::real const &theta_in, + cvm::real const &psi_in) + { + q0 = ( (std::cos (phi_in/2.0)) * (std::cos (theta_in/2.0)) * (std::cos (psi_in/2.0)) + + (std::sin (phi_in/2.0)) * (std::sin (theta_in/2.0)) * (std::sin (psi_in/2.0)) ); + + q1 = ( (std::sin (phi_in/2.0)) * (std::cos (theta_in/2.0)) * (std::cos (psi_in/2.0)) - + (std::cos (phi_in/2.0)) * (std::sin (theta_in/2.0)) * (std::sin (psi_in/2.0)) ); + + q2 = ( (std::cos (phi_in/2.0)) * (std::sin (theta_in/2.0)) * (std::cos (psi_in/2.0)) + + (std::sin (phi_in/2.0)) * (std::cos (theta_in/2.0)) * (std::sin (psi_in/2.0)) ); + + q3 = ( (std::cos (phi_in/2.0)) * (std::cos (theta_in/2.0)) * (std::sin (psi_in/2.0)) - + (std::sin (phi_in/2.0)) * (std::sin (theta_in/2.0)) * (std::cos (psi_in/2.0)) ); + } + + /// \brief Default constructor + inline quaternion() + { + reset(); + } + + /// \brief Set all components to a scalar + inline void set (cvm::real const value = 0.0) + { + q0 = q1 = q2 = q3 = value; + } + + /// \brief Set all components to zero (null quaternion) + inline void reset() + { + q0 = q1 = q2 = q3 = 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 const &real_width) + { + return 4*real_width + 13; + } + + /// \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 const &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::fatal_error ("Error: incorrect quaternion component.\n"); + return q0; + } + } + + /// Access the quaternion as a 4-d array (return a value) + inline cvm::real operator [] (int const &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::fatal_error ("Error: trying to access a quaternion " + "component which is not between 0 and 3.\n"); + return this->q0; + } + } + + /// 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 std::sqrt (this->norm2()); + } + + /// Return the conjugate quaternion + inline cvm::quaternion conjugate() const + { + return cvm::quaternion (q0, -q1, -q2, -q3); + } + + inline void operator *= (cvm::real const &a) + { + q0 *= a; q1 *= a; q2 *= a; q3 *= a; + } + + inline void operator /= (cvm::real const &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; + } + + /// Promote a 3-vector to a quaternion + static inline cvm::quaternion promote (cvm::rvector const &v) + { + return cvm::quaternion (0.0, v.x, v.y, v.z); + } + /// 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: \code h.inner (q); \endcode + 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 const &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 const &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 const &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) * promote (v) * ((*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 + { + if (q0*q0 >= (1.0-1.0E-10)) { + // null quaternion, return the square of the cosine of the other + // quaternion's rotation angle + return (2.0*q.q0*q.q0 - 1.0); + } else if (q.q0*q.q0 >= (1.0-1.0E-10)) { + return (2.0*q0*q0 - 1.0); + } else { + // get a vector orthogonal to both axes of rotation + cvm::rvector const op = (cvm::rvector::outer (this->get_vector(), q.get_vector())); + cvm::real const opl2 = op.norm2(); + // rotate it with both quaternions and get the normalized inner product + return ( (opl2 > 0.0) ? + ((this->rotate (op)) * (q.rotate (op))) / opl2 : + 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 = std::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 the one 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 = std::acos ( (cos_omega > 1.0) ? 1.0 : + ( (cos_omega < -1.0) ? -1.0 : cos_omega) ); + cvm::real const sin_omega = std::sin (omega); + + if (std::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; + } + + +}; + + +/// \brief A rotation between two sets of coordinates (for the moment +/// a wrapper for colvarmodule::quaternion) +class colvarmodule::rotation +{ +public: + + /// \brief The rotation itself (implemented as a quaternion) + cvm::quaternion q; + + /// \brief Eigenvalue corresponding to the optimal rotation + cvm::real lambda; + + /// \brief Perform gradient tests + bool b_debug_gradients; + + /// \brief Positions to superimpose: the rotation should brings pos1 + /// into pos2 + std::vector< cvm::atom_pos > pos1, pos2; + + /// Derivatives of S + std::vector< cvm::matrix2d > dS_1, dS_2; + /// Derivatives of leading eigenvalue + std::vector< cvm::rvector > dL0_1, dL0_2; + /// Derivatives of leading eigenvector + std::vector< cvm::vector1d > dQ0_1, dQ0_2; + + /// Allocate space for the derivatives of the rotation + inline void request_group1_gradients (size_t const &n) + { + dS_1.resize (n, cvm::matrix2d()); + dL0_1.resize (n, cvm::rvector (0.0, 0.0, 0.0)); + dQ0_1.resize (n, cvm::vector1d()); + } + + inline void request_group2_gradients (size_t const &n) + { + dS_2.resize (n, cvm::matrix2d()); + dL0_2.resize (n, cvm::rvector (0.0, 0.0, 0.0)); + dQ0_2.resize (n, cvm::vector1d()); + } + + /// \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); + + /// Default constructor + inline rotation() + : b_debug_gradients (false) + {} + + /// Constructor after a quaternion + inline rotation (cvm::quaternion const &qi) + : b_debug_gradients (false) + { + q = qi; + } + + /// Constructor after an axis of rotation and an angle (in radians) + inline rotation (cvm::real const &angle, cvm::rvector const &axis) + : b_debug_gradients (false) + { + cvm::rvector const axis_n = axis.unit(); + cvm::real const sina = std::sin (angle/2.0); + q = cvm::quaternion (std::cos (angle/2.0), + sina * axis_n.x, sina * axis_n.y, sina * axis_n.z); + } + + /// Destructor + inline ~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 * std::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 x = iprod/q.q0; + + 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 + return + cvm::quaternion ((180.0/PI) * 2.0 * ((-1.0)/iprod), 0.0, 0.0, 0.0); + // XX TODO: What if iprod == 0? XX + } + } + + /// \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 * std::atan2 (axis * q_vec, q.q0); + + cvm::real const cos_spin_2 = std::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 = std::cos (std::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 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 overlap matrix S (used by calc_optimal_rotation()) + void build_matrix (std::vector const &pos1, + std::vector const &pos2, + cvm::matrix2d &S); + + /// Diagonalize the overlap matrix S (used by calc_optimal_rotation()) + void diagonalize_matrix (cvm::matrix2d &S, + cvm::real S_eigval[4], + cvm::matrix2d &S_eigvec); +}; + + +#endif + +// Emacs +// Local Variables: +// mode: C++ +// End: diff --git a/lib/colvars/colvarvalue.cpp b/lib/colvars/colvarvalue.cpp new file mode 100644 index 0000000000..f7d6aa6bf6 --- /dev/null +++ b/lib/colvars/colvarvalue.cpp @@ -0,0 +1,261 @@ +#include + +#include "colvarmodule.h" +#include "colvarvalue.h" + + + +std::string const colvarvalue::type_desc[colvarvalue::type_quaternion+1] = + { "not_set", + "scalar number", + "3-dimensional vector", + "3-dimensional unit vector", + "4-dimensional unit vector" }; + +size_t const colvarvalue::dof_num[ colvarvalue::type_quaternion+1] = + { 0, 1, 3, 2, 3 }; + + +void colvarvalue::undef_op() const +{ + cvm::fatal_error ("Error: Undefined operation on a colvar of type \""+ + colvarvalue::type_desc[this->value_type]+"\".\n"); +} + +void colvarvalue::error_rside +(colvarvalue::Type const &vt) const +{ + cvm::fatal_error ("Trying to assign a colvar value with type \""+ + type_desc[this->value_type]+"\" to one with type \""+ + type_desc[vt]+"\".\n"); +} + +void colvarvalue::error_lside +(colvarvalue::Type const &vt) const +{ + cvm::fatal_error ("Trying to use a colvar value with type \""+ + type_desc[vt]+"\" as one of type \""+ + type_desc[this->value_type]+"\".\n"); +} + + + +void colvarvalue::inner_opt (colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &inner) +{ + // doing type check only once, here + colvarvalue::check_types (x, *xv); + + std::vector::iterator &xvi = xv; + std::vector::iterator &ii = inner; + + switch (x.value_type) { + case colvarvalue::type_scalar: + while (xvi != xv_end) { + *(ii++) += (xvi++)->real_value * x.real_value; + } + break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + while (xvi != xv_end) { + *(ii++) += (xvi++)->rvector_value * x.rvector_value; + } + break; + case colvarvalue::type_quaternion: + while (xvi != xv_end) { + *(ii++) += ((xvi++)->quaternion_value).cosine (x.quaternion_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 &inner) +{ + // doing type check only once, here + colvarvalue::check_types (x, *xv); + + std::list::iterator &xvi = xv; + std::vector::iterator &ii = inner; + + switch (x.value_type) { + case colvarvalue::type_scalar: + while (xvi != xv_end) { + *(ii++) += (xvi++)->real_value * x.real_value; + } + break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + while (xvi != xv_end) { + *(ii++) += (xvi++)->rvector_value * x.rvector_value; + } + break; + case colvarvalue::type_quaternion: + while (xvi != xv_end) { + *(ii++) += ((xvi++)->quaternion_value).cosine (x.quaternion_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 &inner) +{ + // doing type check only once, here + colvarvalue::check_types (x, *xv); + + std::vector::iterator &xvi = xv; + std::vector::iterator &ii = inner; + + switch (x.value_type) { + case colvarvalue::type_scalar: + cvm::fatal_error ("Error: cannot calculate Legendre polynomials " + "for scalar variables.\n"); + break; + case colvarvalue::type_vector: + 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_unitvector: + 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: + 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(); + }; +} + +void colvarvalue::p2leg_opt (colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &inner) +{ + // doing type check only once, here + colvarvalue::check_types (x, *xv); + + std::list::iterator &xvi = xv; + std::vector::iterator &ii = inner; + + switch (x.value_type) { + case colvarvalue::type_scalar: + cvm::fatal_error ("Error: cannot calculate Legendre polynomials " + "for scalar variables.\n"); + break; + case colvarvalue::type_vector: + 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_unitvector: + 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: + 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(); + }; +} + + +std::ostream & operator << (std::ostream &os, colvarvalue const &x) +{ + switch (x.type()) { + case colvarvalue::type_scalar: + os << x.real_value; break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + os << x.rvector_value; break; + case colvarvalue::type_quaternion: + os << x.quaternion_value; break; + case colvarvalue::type_notset: + os << "not set"; break; + } + return os; +} + + +std::ostream & operator << (std::ostream &os, std::vector const &v) +{ + for (size_t i = 0; i < v.size(); i++) os << v[i]; + return os; +} + + +std::istream & operator >> (std::istream &is, colvarvalue &x) +{ + if (x.type() == colvarvalue::type_notset) { + cvm::fatal_error ("Trying to read from a stream a colvarvalue, " + "which has not yet been assigned a data type.\n"); + } + + switch (x.type()) { + case colvarvalue::type_scalar: + is >> x.real_value; + break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + is >> x.rvector_value; + break; + case colvarvalue::type_quaternion: + is >> x.quaternion_value; + break; + default: + x.undef_op(); + } + x.apply_constraints(); + return is; +} + + +size_t colvarvalue::output_width (size_t const &real_width) +{ + switch (this->value_type) { + case colvarvalue::type_scalar: + return real_width; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + return cvm::rvector::output_width (real_width); + case colvarvalue::type_quaternion: + return cvm::quaternion::output_width (real_width); + case colvarvalue::type_notset: + default: + return 0; + } +} + + diff --git a/lib/colvars/colvarvalue.h b/lib/colvars/colvarvalue.h new file mode 100644 index 0000000000..42085bae5a --- /dev/null +++ b/lib/colvars/colvarvalue.h @@ -0,0 +1,727 @@ +#ifndef COLVARVALUE_H +#define COLVARVALUE_H + +#include "colvarmodule.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 like that in all operations (this is +/// done by most \link 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. If you want to use it as +/// another type, you should declare and initialize a variable as +/// \code colvarvalue x (colvarvalue::type_xxx); \endcode where +/// type_xxx is a value within the \link Type \endlink enum. +/// Alternatively, initialize x with \link x.type +/// (colvarvalue::type_xxx) \endlink at a later stage. +/// +/// Given a colvarvalue variable x which is not yet assigned (and +/// thus has not yet a type) it is also possible to correctly assign +/// the type with \code x = y; \endcode if y is correctly set. +/// Otherwise, an error will be raised if the \link Type \endlink of x +/// is different from the \link Type \endlink of y. +/// +/// Also, every operator (either unary or binary) on a \link +/// colvarvalue \endlink object performs one or more checks on the +/// \link Type \endlink to avoid errors such as initializing a +/// three-dimensional vector with a scalar number (legal otherwise). +/// +/// \b Special \b case: when reading from a stream, there is no way to +/// detect the \link Type \endlink and safely handle errors at the +/// same time. Hence, when using \code is >> x; \endcode x \b MUST +/// already have a type correcly set up for properly parsing the +/// stream. An error will be raised otherwise. Usually this is not +/// the problem, because \link colvarvalue \endlink objects are first +/// initialized in the configuration, and the stream operation will be +/// performed only when reading restart files. +/// +/// No problem of course with the output streams: \code os << x; +/// \endcode will print a different output according to the value of +/// colvarvalue::value_type, and the width of such output is returned +/// by colvarvalue::output_width() +/// +/// \em Note \em on \em performance: at every operation between two +/// \link colvarvalue \endlink objects, their two \link Type \endlink +/// flags will be checked for a match. In a long array of \link +/// colvarvalue \endlink objects this is time consuming: a few static +/// functions are defined ("xxx_opt" functions) within the \link +/// colvarvalue \endlink class, which only check the matching once for +/// a large array, and execute different loops according to the type. +/// You should do the same for every time consuming loop involving +/// operations on \link colvarvalue \endlink objects if you want +/// e.g. to optimize your colvar bias. + +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_vector, + /// 3-dimensional unit vector, implemented as \link colvarmodule::rvector \endlink + type_unitvector, + /// 4-dimensional unit vector representing a rotation, implemented as \link colvarmodule::quaternion \endlink + type_quaternion + }; + + /// Runtime description of value types + std::string static const type_desc[colvarvalue::type_quaternion+1]; + + /// Number of degrees of freedom for each type + size_t static const dof_num[ colvarvalue::type_quaternion+1]; + + /// \brief Real data member + cvm::real real_value; + + /// \brief Vector data member + cvm::rvector rvector_value; + + /// \brief Quaternion data member + cvm::quaternion quaternion_value; + + /// Current type of this colvarvalue object + Type value_type; + + static inline bool type_checking() + { + return true; + } + + /// \brief Default constructor: this class defaults to a scalar + /// number and always behaves like it unless you change its type + inline colvarvalue() + : real_value (0.0), value_type (type_scalar) + {} + + /// Constructor from a type specification + inline colvarvalue (Type const &vti) + : value_type (vti) + { + reset(); + } + + /// Copy constructor from real base type + inline colvarvalue (cvm::real const &x) + : real_value (x), value_type (type_scalar) + {} + + /// \brief Copy constructor from rvector base type (Note: this sets + /// automatically a type \link type_vector \endlink , if you want a + /// \link type_unitvector \endlink you must set it explicitly) + inline colvarvalue (cvm::rvector const &v) + : rvector_value (v), value_type (type_vector) + {} + + /// \brief Copy constructor from rvector base type (additional + /// argument to make possible to choose a \link type_unitvector + /// \endlink + inline colvarvalue (cvm::rvector const &v, Type const &vti) + : rvector_value (v), value_type (vti) + {} + + /// \brief Copy constructor from quaternion base type + inline colvarvalue (cvm::quaternion const &q) + : quaternion_value (q), value_type (type_quaternion) + {} + + /// Copy constructor from another \link colvarvalue \endlink + inline colvarvalue (colvarvalue const &x) + : value_type (x.value_type) + { + reset(); + + switch (x.value_type) { + case type_scalar: + real_value = x.real_value; + break; + case type_vector: + case type_unitvector: + rvector_value = x.rvector_value; + break; + case type_quaternion: + quaternion_value = x.quaternion_value; + break; + case type_notset: + default: + break; + } + } + + + + /// 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; use it when the \link + /// colvarvalue \endlink is not calculated from \link cvc + /// \endlink objects, but manipulated by you + void apply_constraints(); + + + /// Get the current type + inline Type type() const + { + return value_type; + } + + /// Set the type explicitly + inline void type (Type const &vti) + { + reset(); + value_type = vti; + reset(); + } + + /// Set the type after another \link colvarvalue \endlink + inline void type (colvarvalue const &x) + { + reset(); + value_type = x.value_type; + reset(); + } + + /// Square norm of this colvarvalue + cvm::real norm2() const; + + /// Norm of this colvarvalue + inline cvm::real norm() const + { + return std::sqrt (this->norm2()); + } + + /// \brief Return the value whose scalar product with this value is + /// 1 + inline colvarvalue inverse() 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; + + /// 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); + + + // Casting operator + inline operator cvm::real() const + { + if (value_type != type_scalar) error_rside (type_scalar); + return real_value; + } + + // Casting operator + inline operator cvm::rvector() const + { + if ( (value_type != type_vector) && (value_type != type_unitvector)) + error_rside (type_vector); + return rvector_value; + } + + // Casting operator + inline operator cvm::quaternion() const + { + if (value_type != type_quaternion) error_rside (type_quaternion); + return quaternion_value; + } + + /// Special case when the variable is a real number, and all operations are defined + inline bool is_scalar() + { + return (value_type == type_scalar); + } + + + /// Ensure that the two types are the same within a binary operator + void static check_types (colvarvalue const &x1, colvarvalue const &x2); + + /// Undefined operation + void undef_op() const; + + /// Trying to assign this \link colvarvalue \endlink object to + /// another object set with a different type + void error_lside (Type const &vt) const; + + /// Trying to assign another \link colvarvalue \endlink object set + /// with a different type to this object + void error_rside (Type const &vt) const; + + ///�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); + + + // optimized routines for operations with an array; xv and inner as + // vectors are assumed to have the same number of elements (i.e. the + // end for inner comes together with xv_end in the loop) + + /// \brief Optimized routine for the inner product of one collective + /// variable with an array + void static inner_opt (colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &inner); + + /// \brief Optimized routine for the inner product of one collective + /// variable with an array + void static inner_opt (colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &inner); + + /// \brief Optimized routine for the second order Legendre + /// polynomial, (3cos^2(w)-1)/2, of one collective variable with an + /// array + void static p2leg_opt (colvarvalue const &x, + std::vector::iterator &xv, + std::vector::iterator const &xv_end, + std::vector::iterator &inner); + + /// \brief Optimized routine for the second order Legendre + /// polynomial of one collective variable with an array + void static p2leg_opt (colvarvalue const &x, + std::list::iterator &xv, + std::list::iterator const &xv_end, + std::vector::iterator &inner); + +}; + + + +inline void colvarvalue::reset() +{ + switch (value_type) { + case colvarvalue::type_scalar: + real_value = cvm::real (0.0); + break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + rvector_value = cvm::rvector (0.0, 0.0, 0.0); + break; + case colvarvalue::type_quaternion: + quaternion_value = cvm::quaternion (0.0, 0.0, 0.0, 0.0); + break; + case colvarvalue::type_notset: + default: + break; + } +} + + +inline void colvarvalue::apply_constraints() +{ + switch (value_type) { + case colvarvalue::type_scalar: + break; + case colvarvalue::type_vector: + break; + case colvarvalue::type_unitvector: + rvector_value /= std::sqrt (rvector_value.norm2()); + break; + case colvarvalue::type_quaternion: + quaternion_value /= std::sqrt (quaternion_value.norm2()); + break; + case colvarvalue::type_notset: + default: + break; + } +} + + + +inline cvm::real colvarvalue::norm2() const +{ + switch (value_type) { + case colvarvalue::type_scalar: + return (this->real_value)*(this->real_value); + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + return (this->rvector_value).norm2(); + case colvarvalue::type_quaternion: + return (this->quaternion_value).norm2(); + case colvarvalue::type_notset: + default: + return 0.0; + } +} + + +inline colvarvalue colvarvalue::inverse() const +{ + switch (value_type) { + case colvarvalue::type_scalar: + return colvarvalue (1.0/real_value); + case colvarvalue::type_vector: + return colvarvalue (cvm::rvector (1.0/rvector_value.x, + 1.0/rvector_value.y, + 1.0/rvector_value.z)); + case colvarvalue::type_quaternion: + return colvarvalue (cvm::quaternion (1.0/quaternion_value.q0, + 1.0/quaternion_value.q1, + 1.0/quaternion_value.q2, + 1.0/quaternion_value.q3)); + case colvarvalue::type_notset: + default: + undef_op(); + } + return colvarvalue(); +} + + +inline colvarvalue & colvarvalue::operator = (colvarvalue const &x) +{ + if (this->value_type != type_notset) + if (this->value_type != x.value_type) + error_lside (x.value_type); + + this->value_type = x.value_type; + + switch (this->value_type) { + case colvarvalue::type_scalar: + this->real_value = x.real_value; + break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + this->rvector_value = x.rvector_value; + break; + case colvarvalue::type_quaternion: + this->quaternion_value = x.quaternion_value; + break; + case colvarvalue::type_notset: + default: + undef_op(); + break; + } + return *this; +} + +inline void colvarvalue::operator += (colvarvalue const &x) +{ + if (colvarvalue::type_checking()) + colvarvalue::check_types (*this, x); + + switch (this->value_type) { + case colvarvalue::type_scalar: + this->real_value += x.real_value; + break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + this->rvector_value += x.rvector_value; + break; + case colvarvalue::type_quaternion: + this->quaternion_value += x.quaternion_value; + break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + +inline void colvarvalue::operator -= (colvarvalue const &x) +{ + if (colvarvalue::type_checking()) + colvarvalue::check_types (*this, x); + + switch (value_type) { + case colvarvalue::type_scalar: + real_value -= x.real_value; break; + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + rvector_value -= x.rvector_value; break; + case colvarvalue::type_quaternion: + quaternion_value -= x.quaternion_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_vector: + case colvarvalue::type_unitvector: + rvector_value *= a; + break; + case colvarvalue::type_quaternion: + quaternion_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_vector: + case colvarvalue::type_unitvector: + rvector_value /= a; break; + case colvarvalue::type_quaternion: + quaternion_value /= a; break; + case colvarvalue::type_notset: + default: + undef_op(); + } +} + + +// binary operations between two colvarvalues + +inline colvarvalue operator + (colvarvalue const &x1, + colvarvalue const &x2) +{ + if (colvarvalue::type_checking()) + colvarvalue::check_types (x1, x2); + + switch (x1.value_type) { + case colvarvalue::type_scalar: + return colvarvalue (x1.real_value + x2.real_value); + case colvarvalue::type_vector: + return colvarvalue (x1.rvector_value + x2.rvector_value); + case colvarvalue::type_unitvector: + return colvarvalue (x1.rvector_value + x2.rvector_value, + colvarvalue::type_unitvector); + case colvarvalue::type_quaternion: + return colvarvalue (x1.quaternion_value + x2.quaternion_value); + case colvarvalue::type_notset: + default: + x1.undef_op(); + return colvarvalue (colvarvalue::type_notset); + }; +} + +inline colvarvalue operator - (colvarvalue const &x1, + colvarvalue const &x2) +{ + if (colvarvalue::type_checking()) + colvarvalue::check_types (x1, x2); + + switch (x1.value_type) { + case colvarvalue::type_scalar: + return colvarvalue (x1.real_value - x2.real_value); + case colvarvalue::type_vector: + return colvarvalue (x1.rvector_value - x2.rvector_value); + case colvarvalue::type_unitvector: + return colvarvalue (x1.rvector_value - x2.rvector_value, + colvarvalue::type_unitvector); + case colvarvalue::type_quaternion: + return colvarvalue (x1.quaternion_value - x2.quaternion_value); + default: + x1.undef_op(); + return colvarvalue (colvarvalue::type_notset); + }; +} + + +// binary operations with real numbers + +inline 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_vector: + return colvarvalue (a * x.rvector_value); + case colvarvalue::type_unitvector: + return colvarvalue (a * x.rvector_value, + colvarvalue::type_unitvector); + case colvarvalue::type_quaternion: + return colvarvalue (a * x.quaternion_value); + case colvarvalue::type_notset: + default: + x.undef_op(); + return colvarvalue (colvarvalue::type_notset); + } +} + +inline 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_vector: + return colvarvalue (x.rvector_value * a); + case colvarvalue::type_unitvector: + return colvarvalue (x.rvector_value * a, + colvarvalue::type_unitvector); + case colvarvalue::type_quaternion: + return colvarvalue (x.quaternion_value * a); + case colvarvalue::type_notset: + default: + x.undef_op(); + return colvarvalue (colvarvalue::type_notset); + } +} + +inline 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_vector: + return colvarvalue (x.rvector_value / a); + case colvarvalue::type_unitvector: + return colvarvalue (x.rvector_value / a, + colvarvalue::type_unitvector); + case colvarvalue::type_quaternion: + return colvarvalue (x.quaternion_value / a); + case colvarvalue::type_notset: + default: + x.undef_op(); + return colvarvalue (colvarvalue::type_notset); + } +} + + +// inner product between two colvarvalues + +inline cvm::real operator * (colvarvalue const &x1, + colvarvalue const &x2) +{ + if (colvarvalue::type_checking()) + colvarvalue::check_types (x1, x2); + + switch (x1.value_type) { + case colvarvalue::type_scalar: + return (x1.real_value * x2.real_value); + case colvarvalue::type_vector: + case colvarvalue::type_unitvector: + return (x1.rvector_value * x2.rvector_value); + case colvarvalue::type_quaternion: + // 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_notset: + default: + x1.undef_op(); + return 0.0; + }; +} + + + +inline cvm::real colvarvalue::dist2 (colvarvalue const &x2) const +{ + if (colvarvalue::type_checking()) + colvarvalue::check_types (*this, x2); + + switch (this->value_type) { + case colvarvalue::type_scalar: + return std::pow (this->real_value - x2.real_value, int (2)); + case colvarvalue::type_vector: + return (this->rvector_value - x2.rvector_value).norm2(); + case colvarvalue::type_unitvector: + // angle between (*this) and x2 is the distance + return std::pow (std::acos (this->rvector_value * x2.rvector_value), int (2)); + case colvarvalue::type_quaternion: + // 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_notset: + default: + this->undef_op(); + return 0.0; + }; +} + + +inline colvarvalue colvarvalue::dist2_grad (colvarvalue const &x2) const +{ + if (colvarvalue::type_checking()) + 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_vector: + return 2.0 * (this->rvector_value - x2.rvector_value); + case colvarvalue::type_unitvector: + { + cvm::rvector const &v1 = this->rvector_value; + cvm::rvector const &v2 = x2.rvector_value; + cvm::real const cos_t = v1 * v2; + cvm::real const sin_t = std::sqrt (1.0 - cos_t*cos_t); + return colvarvalue ( 2.0 * sin_t * + cvm::rvector ( (-1.0) * sin_t * v2.x + + cos_t/sin_t * (v1.x - cos_t*v2.x), + (-1.0) * sin_t * v2.y + + cos_t/sin_t * (v1.y - cos_t*v2.y), + (-1.0) * sin_t * v2.z + + cos_t/sin_t * (v1.z - cos_t*v2.z) + ), + colvarvalue::type_unitvector ); + } + case colvarvalue::type_quaternion: + return this->quaternion_value.dist2_grad (x2.quaternion_value); + case colvarvalue::type_notset: + default: + this->undef_op(); + return colvarvalue (colvarvalue::type_notset); + }; +} + + +inline void colvarvalue::check_types (colvarvalue const &x1, + colvarvalue const &x2) +{ + if (x1.value_type != x2.value_type) { + cvm::log ("x1 type = "+cvm::to_str (x1.value_type)+ + ", x2 type = "+cvm::to_str (x2.value_type)+"\n"); + cvm::fatal_error ("Performing an operation between two colvar " + "values with different types, \""+ + colvarvalue::type_desc[x1.value_type]+ + "\" and \""+ + colvarvalue::type_desc[x2.value_type]+ + "\".\n"); + } +} + + + + +std::ostream & operator << (std::ostream &os, colvarvalue const &x); +std::ostream & operator << (std::ostream &os, std::vector const &v); + +std::istream & operator >> (std::istream &is, colvarvalue &x); + + + + + +#endif + + +// Emacs +// Local Variables: +// mode: C++ +// End: