diff --git a/lib/colvars/colvar.cpp b/lib/colvars/colvar.cpp index 52c78c32a..0aaaab0e0 100644 --- a/lib/colvars/colvar.cpp +++ b/lib/colvars/colvar.cpp @@ -1,1600 +1,1612 @@ // -*- 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+ + cvm::fatal_error ("Error: this colvar cannot have 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 ("average distance weighted by inverse sixth power", + "distance6", distance6); 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 ("moment of " + "inertia", "inertia", inertia); + initialize_components ("moment of inertia around an axis", + "inertia_z", inertia_z); 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); } } + if (tasks[task_lower_boundary]) { + get_keyval (conf, "hardLowerBoundary", hard_lower_boundary, false); + } + if (tasks[task_upper_boundary]) { + get_keyval (conf, "hardUpperBoundary", hard_upper_boundary, false); + } + // 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 index 4f4c57535..a9542a606 100644 --- a/lib/colvars/colvar.h +++ b/lib/colvars/colvar.h @@ -1,561 +1,576 @@ // -*- 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 actual value (not extended DOF) + colvarvalue const & actual_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 Whether this colvar has a hard lower boundary + bool hard_lower_boundary; /// \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 Whether this colvar has a hard upper boundary + bool hard_upper_boundary; /// \brief Is the interval defined by the two boundaries periodic? bool periodic_boundaries() const; /// \brief Is the interval defined by the two boundaries periodic? bool periodic_boundaries (colvarvalue const &lb, colvarvalue const &ub) const; /// Constructor colvar (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 distance6; 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 inertia; + class inertia_z; 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::actual_value() const +{ + return x; +} + + 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/colvarbias.cpp b/lib/colvars/colvarbias.cpp index 596e8810c..a0b988b5d 100644 --- a/lib/colvars/colvarbias.cpp +++ b/lib/colvars/colvarbias.cpp @@ -1,486 +1,492 @@ #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)); + for (std::vector::iterator bi = cvm::biases.begin(); + bi != cvm::biases.end(); + bi++) { + if ((*bi)->name == this->name) + cvm::fatal_error ("Error: this bias cannot have the same name, \""+this->name+ + "\", of another bias.\n"); + } + // 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) + target_nsteps (0), + target_nstages (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 index 314733eba..2130c4ba1 100644 --- a/lib/colvars/colvarbias.h +++ b/lib/colvars/colvarbias.h @@ -1,167 +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; + int target_nstages; /// \brief Number of current stage of the perturbation - size_t stage; + int 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_meta.cpp b/lib/colvars/colvarbias_meta.cpp index 4d482b579..607fd94ab 100644 --- a/lib/colvars/colvarbias_meta.cpp +++ b/lib/colvars/colvarbias_meta.cpp @@ -1,1639 +1,1705 @@ #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"); } + // for well-tempered metadynamics + get_keyval (conf, "wellTempered", well_tempered, false); + get_keyval (conf, "biasTemperature", bias_temperature, -1.0); + if ((bias_temperature == -1.0) && well_tempered) { + cvm::fatal_error ("Error: biasTemperature is not set.\n"); + } + if (well_tempered) { + cvm::log("Well-tempered metadynamics is used.\n"); + cvm::log("The bias temperature is "+cvm::to_str(bias_temperature)+".\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); + cvm::real const min_dist = hills_energy->bin_distance_from_boundaries (h.centers, true); 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)); + if (well_tempered) { + std::vector curr_bin = hills_energy->get_colvars_index(); + cvm::real const hills_energy_sum_here = hills_energy->value(curr_bin); + cvm::real const exp_weight = std::exp(-hills_energy_sum_here/(bias_temperature*cvm::boltzmann())); + create_hill (hill ((hill_weight*exp_weight), colvars, hill_width)); + } else { + create_hill (hill (hill_weight, colvars, hill_width)); + } break; case multiple_replicas: - create_hill (hill (hill_weight, colvars, hill_width, replica_id)); + if (well_tempered) { + std::vector curr_bin = hills_energy->get_colvars_index(); + cvm::real const hills_energy_sum_here = hills_energy->value(curr_bin); + cvm::real const exp_weight = std::exp(-hills_energy_sum_here/(bias_temperature*cvm::boltzmann())); + create_hill (hill ((hill_weight*exp_weight), colvars, hill_width, replica_id)); + } else { + 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) + colvar_grid_gradient *hg, + bool print_progress) { if (cvm::debug()) cvm::log ("Metadynamics bias \""+this->name+"\""+ ((comm != single_replica) ? ", replica \""+replica_id+"\"" : "")+ ": projecting hills.\n"); // TODO: improve it by looping over a small subgrid instead of the whole grid std::vector 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); + size_t count = 0; + size_t const print_frequency = ((hills.size() >= 1000000) ? 1 : (1000000/(hills.size()+1))); + if (hg != NULL) { // loop over the points of the grid for ( ; (he->index_ok (he_ix)) && (hg->index_ok (hg_ix)); - ) { + count++) { 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); + + if ((count % print_frequency) == 0) { + if (print_progress) { + cvm::real const progress = cvm::real (count) / cvm::real (hg->number_of_points()); + std::ostringstream os; + os.setf (std::ios::fixed, std::ios::floatfield); + os << std::setw (6) << std::setprecision (2) + << 100.0 * progress + << "% done."; + cvm::log (os.str()); + } + } } } else { // 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); + + count++; + if ((count % print_frequency) == 0) { + if (print_progress) { + cvm::real const progress = cvm::real (count) / cvm::real (he->number_of_points()); + std::ostringstream os; + os.setf (std::ios::fixed, std::ios::floatfield); + os << std::setw (6) << std::setprecision (2) + << 100.0 * progress + << "% done."; + cvm::log (os.str()); + } + } } } + if (print_progress) { + cvm::log ("100.00% done."); + } + 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); + cvm::real const min_dist = hills_energy->bin_distance_from_boundaries (h->centers, true); 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) + if (!rebin_grids) { + if (hills_energy_backup == NULL) 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); + new_hills_energy, new_hills_energy_gradients, true); cvm::log ("rebinning done.\n"); } else { // otherwise, use the grids in the restart file cvm::log ("Rebinning the energy and forces grids " "from the grids in the restart file.\n"); new_hills_energy->map_grid (*hills_energy); new_hills_energy_gradients->map_grid (*hills_energy_gradients); } delete hills_energy; delete hills_energy_gradients; hills_energy = new_hills_energy; hills_energy_gradients = new_hills_energy_gradients; // assuming that some boundaries have expanded, eliminate those // off-grid hills that aren't necessary any more if (hills.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); + hills_energy->bin_distance_from_boundaries ((hills.back()).centers, true); 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); + if (well_tempered) { + cvm::real const well_temper_scale = (bias_temperature + cvm::temperature()) / bias_temperature; + pmf->multiply_constant (well_temper_scale); + } 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); + if (well_tempered) { + cvm::real const well_temper_scale = (bias_temperature + cvm::temperature()) / bias_temperature; + pmf->multiply_constant (well_temper_scale); + } 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 index 6e3adb1c5..1f51e5469 100644 --- a/lib/colvars/colvarbias_meta.h +++ b/lib/colvars/colvarbias_meta.h @@ -1,418 +1,425 @@ #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 Whether to use well-tempered metadynamics + bool well_tempered; + + /// \brief Biasing temperature in well-tempered metadynamics + cvm::real bias_temperature; + /// \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); + colvar_grid_scalar *ge, colvar_grid_gradient *gf, + bool print_progress = false); // Multiple Replicas variables and functions /// \brief Identifier for this replica std::string replica_id; /// \brief File containing the paths to the output files from this replica std::string replica_file_name; /// \brief Read the existing replicas on registry virtual 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.h b/lib/colvars/colvarcomp.h index 93278400f..2e916c9c2 100644 --- a/lib/colvars/colvarcomp.h +++ b/lib/colvars/colvarcomp.h @@ -1,1414 +1,1468 @@ #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 +/// \brief Colvar component: average distance between two groups of atoms, weighted as the sixth power, +/// as in NMR refinements (colvarvalue::type_scalar type, range (0:*)) +class colvar::distance6 : 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() {} + distance6 (std::string const &conf); + distance6(); + virtual inline ~distance6() {} 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: moment of inertia of an atom group +/// (colvarvalue::type_scalar type, range [0:*)) +class colvar::inertia + : public colvar::cvc +{ +protected: + /// Atoms involved + cvm::atom_group atoms; +public: + /// Constructor + inertia (std::string const &conf); + inertia(); + virtual inline ~inertia() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force (colvarvalue const &force); + virtual cvm::real dist2 (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual cvm::real compare (colvarvalue const &x1, + colvarvalue const &x2) const; +}; + + +/// \brief Colvar component: moment of inertia of an atom group +/// around a user-defined axis (colvarvalue::type_scalar type, range [0:*)) +class colvar::inertia_z + : public colvar::inertia +{ +protected: + /// Vector on which the inertia tensor is projected + cvm::rvector axis; +public: + /// Constructor + inertia_z (std::string const &conf); + inertia_z(); + virtual inline ~inertia_z() {} + virtual void calc_value(); + virtual void calc_gradients(); + virtual void apply_force (colvarvalue const &force); + virtual cvm::real dist2 (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_lgrad (colvarvalue const &x1, + colvarvalue const &x2) const; + virtual colvarvalue dist2_rgrad (colvarvalue const &x1, + colvarvalue const &x2) const; + 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 \ { \ - const cvm::real tmp = x1.real_value - x2.real_value; \ - return tmp*tmp; \ + return (x1.real_value - x2.real_value)*(x1.real_value - x2.real_value); \ } \ \ 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 (distance6) 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 (inertia) + simple_scalar_dist_functions (inertia_z) 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_distances.cpp b/lib/colvars/colvarcomp_distances.cpp index b69658248..438929a7a 100644 --- a/lib/colvars/colvarcomp_distances.cpp +++ b/lib/colvars/colvarcomp_distances.cpp @@ -1,1113 +1,1208 @@ #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); + group1.apply_colvar_force (force.real_value); if (!group2.noforce) - group2.apply_colvar_force (force); + group2.apply_colvar_force (force.real_value); } 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) +colvar::distance_dir::distance_dir (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())); + function_type = "distance_dir"; + x.type (colvarvalue::type_unitvector); } -colvar::min_distance::min_distance() + +colvar::distance_dir::distance_dir() : distance() { - function_type = "min_distance"; - x.type (colvarvalue::type_scalar); + function_type = "distance_dir"; + x.type (colvarvalue::type_unitvector); } -void colvar::min_distance::calc_value() + +void colvar::distance_dir::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; - } + 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 = zero_dist ? 0.0 : smoothing/(std::log (x.real_value)); + x.rvector_value = dist_v.unit(); } -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::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::min_distance::apply_force (colvarvalue const &force) + +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_colvar_force (force.real_value); + group1.apply_force (-1.0 * force_tang); if (!group2.noforce) - group2.apply_colvar_force (force.real_value); + group2.apply_force ( force_tang); } -colvar::distance_dir::distance_dir (std::string const &conf) +colvar::distance6::distance6 (std::string const &conf) : distance (conf) { - function_type = "distance_dir"; - x.type (colvarvalue::type_unitvector); + function_type = "distance6"; + b_inverse_gradients = false; + b_Jacobian_derivative = false; + x.type (colvarvalue::type_scalar); } - -colvar::distance_dir::distance_dir() - : distance() +colvar::distance6::distance6() { - function_type = "distance_dir"; - x.type (colvarvalue::type_unitvector); + function_type = "distance6"; + b_inverse_gradients = false; + b_Jacobian_derivative = false; + b_1site_force = false; + x.type (colvarvalue::type_scalar); } - -void colvar::distance_dir::calc_value() +void colvar::distance6::calc_value() { group1.reset_atoms_data(); group2.reset_atoms_data(); group1.read_positions(); group2.read_positions(); + x.real_value = 0.0; if (b_no_PBC) { - dist_v = group2.center_of_mass() - group1.center_of_mass(); + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) { + for (cvm::atom_iter ai2 = group2.begin(); ai2 != group2.end(); ai2++) { + cvm::rvector const dv = ai2->pos - ai1->pos; + cvm::real const d2 = dv.norm2(); + x.real_value += 1.0/(d2*d2*d2); + cvm::rvector const dsumddv = -6.0/(d2*d2*d2*d2) * dv; + ai1->grad += -1.0 * dsumddv; + ai2->grad += dsumddv; + } + } } else { - dist_v = cvm::position_distance (group1.center_of_mass(), - group2.center_of_mass()); + for (cvm::atom_iter ai1 = group1.begin(); ai1 != group1.end(); ai1++) { + for (cvm::atom_iter ai2 = group2.begin(); ai2 != group2.end(); ai2++) { + cvm::rvector const dv = cvm::position_distance (ai1->pos, ai2->pos); + cvm::real const d2 = dv.norm2(); + x.real_value += 1.0/(d2*d2*d2); + cvm::rvector const dsumddv = -6.0/(d2*d2*d2*d2) * dv; + ai1->grad += -1.0 * dsumddv; + ai2->grad += dsumddv; + } + } } - x.rvector_value = dist_v.unit(); -} + x.real_value = std::pow (x.real_value, -1.0/6.0); +} -void colvar::distance_dir::calc_gradients() +void colvar::distance6::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) +void colvar::distance6::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; + cvm::real const dxdsum = (-1.0/6.0) * std::pow (x.real_value, -7.0/6.0); if (!group1.noforce) - group1.apply_force (-1.0 * force_tang); + group1.apply_colvar_force (dxdsum * force.real_value); if (!group2.noforce) - group2.apply_force ( force_tang); + group2.apply_colvar_force (dxdsum * force.real_value); } - 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 += (ai->pos).norm2(); } - x.real_value = std::sqrt (x.real_value); + x.real_value = std::sqrt (x.real_value / cvm::real (atoms.size())); } void colvar::gyration::calc_gradients() { cvm::real const drdx = 1.0/(cvm::real (atoms.size()) * x.real_value); for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { ai->grad = drdx * ai->pos; } } void colvar::gyration::calc_force_invgrads() { atoms.read_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::inertia::inertia (std::string const &conf) + : cvc (conf) +{ + function_type = "inertia"; + parse_group (conf, "atoms", atoms); + atom_groups.push_back (&atoms); + x.type (colvarvalue::type_scalar); +} + + +colvar::inertia::inertia() +{ + function_type = "inertia"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::inertia::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 * (ai->pos).norm2(); + } + x.real_value = std::sqrt (x.real_value / atoms.total_mass); +} + + +void colvar::inertia::calc_gradients() +{ + cvm::real const drdx = 1.0/(atoms.total_mass * x.real_value); + for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { + ai->grad = drdx * ai->mass * ai->pos; + } +} + + +void colvar::inertia::apply_force (colvarvalue const &force) +{ + if (!atoms.noforce) + atoms.apply_colvar_force (force.real_value); +} + + +colvar::inertia_z::inertia_z (std::string const &conf) + : inertia (conf) +{ + function_type = "inertia_z"; + 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(); + } + x.type (colvarvalue::type_scalar); +} + + +colvar::inertia_z::inertia_z() +{ + function_type = "inertia_z"; + x.type (colvarvalue::type_scalar); +} + + +void colvar::inertia_z::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++) { + cvm::real const iprod = ai->pos * axis; + x.real_value += ai->mass * iprod * iprod; + } + x.real_value = std::sqrt (x.real_value / atoms.total_mass); +} + + +void colvar::inertia_z::calc_gradients() +{ + cvm::real const drdx = 1.0/(atoms.total_mass * x.real_value); + for (cvm::atom_iter ai = atoms.begin(); ai != atoms.end(); ai++) { + ai->grad = drdx * ai->mass * (ai->pos * axis) * axis; + } +} + + +void colvar::inertia_z::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/colvargrid.h b/lib/colvars/colvargrid.h index f5171fa01..c6e4d780a 100644 --- a/lib/colvars/colvargrid.h +++ b/lib/colvars/colvargrid.h @@ -1,1163 +1,1187 @@ // -*- 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; + /// Do we request actual value (for extended-system colvars)? + std::vector actual_value; + /// Get the low-level index corresponding to an index inline size_t address (std::vector const &ix) const { size_t addr = 0; for (size_t i = 0; i < nd; i++) { addr += ix[i]*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; + /// Whether some colvars have hard lower boundaries + std::vector hard_lower_boundaries; + + /// Whether some colvars have hard upper boundaries + std::vector hard_upper_boundaries; + /// Widths of the colvars in this grid std::vector widths; /// True if this is a count grid related to another grid of data bool has_parent_data; /// Whether this grid has been filled with data or is still empty bool has_data; /// Return the number of 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), + hard_lower_boundaries (g.hard_lower_boundaries), + hard_upper_boundaries (g.hard_upper_boundaries), periodic (g.periodic), widths (g.widths), + actual_value (g.actual_value), 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); + hard_lower_boundaries.push_back (cv[i]->hard_lower_boundary); + hard_upper_boundaries.push_back (cv[i]->hard_upper_boundary); periodic.push_back (cv[i]->periodic_boundaries()); + // By default, get reported colvar value (for extended Lagrangian colvars) + actual_value.push_back (false); + + // except if a colvar is specified twice in a row + // then the first instance is the actual value + if (i > 0 && cv[i-1] == cv[i]) { + actual_value[i-1] = true; + } + 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); + return value_to_bin_scalar (actual_value[i] ? cv[i]->actual_value() : 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) + inline cvm::real bin_distance_from_boundaries (std::vector const &values, + bool skip_hard_boundaries = false) { cvm::real minimum = 1.0E+16; for (size_t i = 0; i < nd; i++) { if (periodic[i]) continue; cvm::real dl = 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) + if ( ((!skip_hard_boundaries) || (!hard_lower_boundaries[i])) && (dl < minimum)) minimum = dl; - if (du < minimum) + if ( ((!skip_hard_boundaries) || (!hard_upper_boundaries[i])) && (du < minimum)) minimum = du; } return minimum; } /// \brief Add data from another grid of the same type /// /// Note: this function maps other_grid inside this one regardless /// of whether it fits or not. void map_grid (colvar_grid const &other_grid) { if (other_grid.multiplicity() != this->multiplicity()) cvm::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 index 6a9f5a3e8..5d1bc9249 100644 --- a/lib/colvars/colvarmodule.cpp +++ b/lib/colvars/colvarmodule.cpp @@ -1,1360 +1,1362 @@ #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); + if (cv_traj_freq) { + // 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); } - 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/colvartypes.h b/lib/colvars/colvartypes.h index 29289cc76..090be30d7 100644 --- a/lib/colvars/colvartypes.h +++ b/lib/colvars/colvartypes.h @@ -1,1110 +1,1111 @@ #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(); + const cvm::real n = this->norm(); + return (n > 0. ? cvm::rvector (x, y, z)/n : cvm::rvector (1., 0., 0.)); } static inline size_t output_width (size_t 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.h b/lib/colvars/colvarvalue.h index d78c082af..134585af0 100644 --- a/lib/colvars/colvarvalue.h +++ b/lib/colvars/colvarvalue.h @@ -1,731 +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: { - const cvm::real tmp = this->real_value - x2.real_value; - return tmp*tmp; - } + case colvarvalue::type_scalar: + return (this->real_value - x2.real_value)*(this->real_value - x2.real_value); case colvarvalue::type_vector: return (this->rvector_value - x2.rvector_value).norm2(); - case colvarvalue::type_unitvector: { + case colvarvalue::type_unitvector: // angle between (*this) and x2 is the distance - const cvm::real tmp = std::acos (this->rvector_value * x2.rvector_value); - return tmp*tmp; - } + return std::acos (this->rvector_value * x2.rvector_value) * std::acos (this->rvector_value * x2.rvector_value); 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: