// -*- c++ -*- // This file is part of the Collective Variables module (Colvars). // The original version of Colvars and its updates are located at: // https://github.com/colvars/colvars // Please update all Colvars source files before making any changes. // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. #include "colvarmodule.h" #include "colvarparse.h" #include "colvaratoms.h" #include "colvarvalue.h" #include "colvar.h" #include "colvarcomp.h" template cvm::real colvar::coordnum::switching_function(cvm::real const &r0, cvm::rvector const &r0_vec, int en, int ed, cvm::atom &A1, cvm::atom &A2, bool **pairlist_elem, cvm::real pairlist_tol) { if ((flags & ef_use_pairlist) && !(flags & ef_rebuild_pairlist)) { bool const within = **pairlist_elem; (*pairlist_elem)++; if (!within) { return 0.0; } } cvm::rvector const r0sq_vec(r0_vec.x*r0_vec.x, r0_vec.y*r0_vec.y, r0_vec.z*r0_vec.z); cvm::rvector const diff = cvm::position_distance(A1.pos, A2.pos); cvm::rvector const scal_diff(diff.x/((flags & ef_anisotropic) ? r0_vec.x : r0), diff.y/((flags & ef_anisotropic) ? r0_vec.y : r0), diff.z/((flags & ef_anisotropic) ? r0_vec.z : r0)); cvm::real const l2 = scal_diff.norm2(); // Assume en and ed are even integers, and avoid sqrt in the following int const en2 = en/2; int const ed2 = ed/2; cvm::real const xn = cvm::integer_power(l2, en2); cvm::real const xd = cvm::integer_power(l2, ed2); //The subtraction and division stretches the function back to the range of [0,1] from [pairlist_tol,1] cvm::real const func = (((1.0-xn)/(1.0-xd)) - pairlist_tol) / (1.0-pairlist_tol); if (flags & ef_rebuild_pairlist) { //Particles just outside of the cutoff also are considered if they come near. **pairlist_elem = (func > (-pairlist_tol * 0.5)) ? true : false; (*pairlist_elem)++; } //If the value is too small, we need to exclude it, rather than let it contribute to the sum or the gradients. if (func < 0) return 0; if (flags & ef_gradients) { //This is the old, completely correct expression for dFdl2: //cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2) - // func*ed2*(xd/l2))*(-1.0); //This can become: //cvm::real const dFdl2 = (1.0/(1.0-xd))*(en2*(xn/l2)*(1.0-xn)/(1.0-xn) - // func*ed2*(xd/l2))*(-1.0); //Recognizing that func = (1.0-xn)/(1.0-xd), we can group together the "func" and get a version of dFdl2 that is 0 //when func=0, which lets us skip this gradient calculation when func=0. cvm::real const dFdl2 = func * ((ed2*xd/((1.0-xd)*l2)) - (en2*xn/((1.0-xn)*l2))); cvm::rvector const dl2dx((2.0/((flags & ef_anisotropic) ? r0sq_vec.x : r0*r0)) * diff.x, (2.0/((flags & ef_anisotropic) ? r0sq_vec.y : r0*r0)) * diff.y, (2.0/((flags & ef_anisotropic) ? r0sq_vec.z : r0*r0)) * diff.z); A1.grad += (-1.0)*dFdl2*dl2dx; A2.grad += dFdl2*dl2dx; } return func; } colvar::coordnum::coordnum(std::string const &conf) : cvc(conf), b_anisotropic(false), pairlist(NULL) { function_type = "coordnum"; x.type(colvarvalue::type_scalar); group1 = parse_group(conf, "group1"); group2 = parse_group(conf, "group2"); if (group1 == NULL || group2 == NULL) { cvm::error("Error: failed to initialize atom groups.\n", INPUT_ERROR); return; } if (int atom_number = cvm::atom_group::overlap(*group1, *group2)) { cvm::error("Error: group1 and group2 share a common atom (number: " + cvm::to_str(atom_number) + ")\n", INPUT_ERROR); return; } if (group1->b_dummy) { cvm::error("Error: only group2 is allowed to be a dummy atom\n", INPUT_ERROR); return; } bool const b_isotropic = get_keyval(conf, "cutoff", r0, cvm::real(4.0 * cvm::unit_angstrom())); if (get_keyval(conf, "cutoff3", r0_vec, cvm::rvector(4.0 * cvm::unit_angstrom(), 4.0 * cvm::unit_angstrom(), 4.0 * cvm::unit_angstrom()))) { if (b_isotropic) { cvm::error("Error: cannot specify \"cutoff\" and \"cutoff3\" " "at the same time.\n", INPUT_ERROR); return; } b_anisotropic = true; // remove meaningless negative signs if (r0_vec.x < 0.0) r0_vec.x *= -1.0; if (r0_vec.y < 0.0) r0_vec.y *= -1.0; if (r0_vec.z < 0.0) r0_vec.z *= -1.0; } get_keyval(conf, "expNumer", en, 6); get_keyval(conf, "expDenom", ed, 12); if ( (en%2) || (ed%2) ) { cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", INPUT_ERROR); } if ( (en <= 0) || (ed <= 0) ) { cvm::error("Error: negative exponent(s) provided.\n", INPUT_ERROR); } if (!is_enabled(f_cvc_pbc_minimum_image)) { cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } get_keyval(conf, "group2CenterOnly", b_group2_center_only, group2->b_dummy); get_keyval(conf, "tolerance", tolerance, 0.0); if (tolerance > 0) { get_keyval(conf, "pairListFrequency", pairlist_freq, 100); if ( ! (pairlist_freq > 0) ) { cvm::error("Error: non-positive pairlistfrequency provided.\n", INPUT_ERROR); return; // and do not allocate the pairlists below } if (b_group2_center_only) { pairlist = new bool[group1->size()]; } else { pairlist = new bool[group1->size() * group2->size()]; } } } colvar::coordnum::coordnum() : b_anisotropic(false), pairlist(NULL) { function_type = "coordnum"; x.type(colvarvalue::type_scalar); } colvar::coordnum::~coordnum() { if (pairlist != NULL) { delete [] pairlist; } } template void colvar::coordnum::main_loop(bool **pairlist_elem) { if (b_group2_center_only) { cvm::atom group2_com_atom; group2_com_atom.pos = group2->center_of_mass(); for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { x.real_value += switching_function(r0, r0_vec, en, ed, *ai1, group2_com_atom, pairlist_elem, tolerance); } if (b_group2_center_only) { group2->set_weighted_gradient(group2_com_atom.grad); } } else { for (cvm::atom_iter ai1 = group1->begin(); ai1 != group1->end(); ai1++) { for (cvm::atom_iter ai2 = group2->begin(); ai2 != group2->end(); ai2++) { x.real_value += switching_function(r0, r0_vec, en, ed, *ai1, *ai2, pairlist_elem, tolerance); } } } } template int colvar::coordnum::compute_coordnum() { bool const use_pairlist = (pairlist != NULL); bool const rebuild_pairlist = (pairlist != NULL) && (cvm::step_relative() % pairlist_freq == 0); bool *pairlist_elem = use_pairlist ? pairlist : NULL; if (b_anisotropic) { if (use_pairlist) { if (rebuild_pairlist) { int const flags = compute_flags | ef_anisotropic | ef_use_pairlist | ef_rebuild_pairlist; main_loop(&pairlist_elem); } else { int const flags = compute_flags | ef_anisotropic | ef_use_pairlist; main_loop(&pairlist_elem); } } else { int const flags = compute_flags | ef_anisotropic; main_loop(NULL); } } else { if (use_pairlist) { if (rebuild_pairlist) { int const flags = compute_flags | ef_use_pairlist | ef_rebuild_pairlist; main_loop(&pairlist_elem); } else { int const flags = compute_flags | ef_use_pairlist; main_loop(&pairlist_elem); } } else { int const flags = compute_flags; main_loop(NULL); } } return COLVARS_OK; } void colvar::coordnum::calc_value() { x.real_value = 0.0; if (is_enabled(f_cvc_gradient)) { compute_coordnum(); } else { compute_coordnum(); } } void colvar::coordnum::calc_gradients() { // Gradients are computed by calc_value() if f_cvc_gradients is enabled } void colvar::coordnum::apply_force(colvarvalue const &force) { if (!group1->noforce) group1->apply_colvar_force(force.real_value); if (!group2->noforce) group2->apply_colvar_force(force.real_value); } simple_scalar_dist_functions(coordnum) // h_bond member functions colvar::h_bond::h_bond(std::string const &conf) : cvc(conf) { if (cvm::debug()) cvm::log("Initializing h_bond object.\n"); function_type = "h_bond"; x.type(colvarvalue::type_scalar); int a_num, d_num; get_keyval(conf, "acceptor", a_num, -1); get_keyval(conf, "donor", d_num, -1); if ( (a_num == -1) || (d_num == -1) ) { cvm::error("Error: either acceptor or donor undefined.\n"); return; } cvm::atom acceptor = cvm::atom(a_num); cvm::atom donor = cvm::atom(d_num); register_atom_group(new cvm::atom_group); atom_groups[0]->add_atom(acceptor); atom_groups[0]->add_atom(donor); get_keyval(conf, "cutoff", r0, (3.3 * cvm::unit_angstrom())); get_keyval(conf, "expNumer", en, 6); get_keyval(conf, "expDenom", ed, 8); if ( (en%2) || (ed%2) ) { cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", INPUT_ERROR); } if ( (en <= 0) || (ed <= 0) ) { cvm::error("Error: negative exponent(s) provided.\n", INPUT_ERROR); } if (cvm::debug()) cvm::log("Done initializing h_bond object.\n"); } colvar::h_bond::h_bond(cvm::atom const &acceptor, cvm::atom const &donor, cvm::real r0_i, int en_i, int ed_i) : r0(r0_i), en(en_i), ed(ed_i) { function_type = "h_bond"; x.type(colvarvalue::type_scalar); register_atom_group(new cvm::atom_group); atom_groups[0]->add_atom(acceptor); atom_groups[0]->add_atom(donor); } colvar::h_bond::h_bond() : cvc() { function_type = "h_bond"; x.type(colvarvalue::type_scalar); } void colvar::h_bond::calc_value() { int const flags = coordnum::ef_null; cvm::rvector const r0_vec(0.0); // TODO enable the flag? x.real_value = coordnum::switching_function(r0, r0_vec, en, ed, (*atom_groups[0])[0], (*atom_groups[0])[1], NULL, 0.0); } void colvar::h_bond::calc_gradients() { int const flags = coordnum::ef_gradients; cvm::rvector const r0_vec(0.0); // TODO enable the flag? coordnum::switching_function(r0, r0_vec, en, ed, (*atom_groups[0])[0], (*atom_groups[0])[1], NULL, 0.0); } void colvar::h_bond::apply_force(colvarvalue const &force) { (atom_groups[0])->apply_colvar_force(force); } simple_scalar_dist_functions(h_bond) colvar::selfcoordnum::selfcoordnum(std::string const &conf) : cvc(conf), pairlist(NULL) { function_type = "selfcoordnum"; x.type(colvarvalue::type_scalar); group1 = parse_group(conf, "group1"); get_keyval(conf, "cutoff", r0, cvm::real(4.0 * cvm::unit_angstrom())); get_keyval(conf, "expNumer", en, 6); get_keyval(conf, "expDenom", ed, 12); if ( (en%2) || (ed%2) ) { cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", INPUT_ERROR); } if ( (en <= 0) || (ed <= 0) ) { cvm::error("Error: negative exponent(s) provided.\n", INPUT_ERROR); } if (!is_enabled(f_cvc_pbc_minimum_image)) { cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } get_keyval(conf, "tolerance", tolerance, 0.0); if (tolerance > 0) { get_keyval(conf, "pairListFrequency", pairlist_freq, 100); if ( ! (pairlist_freq > 0) ) { cvm::error("Error: non-positive pairlistfrequency provided.\n", INPUT_ERROR); return; } pairlist = new bool[(group1->size()-1) * (group1->size()-1)]; } } colvar::selfcoordnum::selfcoordnum() : pairlist(NULL) { function_type = "selfcoordnum"; x.type(colvarvalue::type_scalar); } colvar::selfcoordnum::~selfcoordnum() { if (pairlist != NULL) { delete [] pairlist; } } template int colvar::selfcoordnum::compute_selfcoordnum() { cvm::rvector const r0_vec(0.0); // TODO enable the flag? bool const use_pairlist = (pairlist != NULL); bool const rebuild_pairlist = (pairlist != NULL) && (cvm::step_relative() % pairlist_freq == 0); bool *pairlist_elem = use_pairlist ? pairlist : NULL; size_t i = 0, j = 0; size_t const n = group1->size(); // Always isotropic (TODO: enable the ellipsoid?) if (use_pairlist) { if (rebuild_pairlist) { int const flags = compute_flags | coordnum::ef_use_pairlist | coordnum::ef_rebuild_pairlist; for (i = 0; i < n - 1; i++) { for (j = i + 1; j < n; j++) { x.real_value += coordnum::switching_function(r0, r0_vec, en, ed, (*group1)[i], (*group1)[j], &pairlist_elem, tolerance); } } } else { int const flags = compute_flags | coordnum::ef_use_pairlist; for (i = 0; i < n - 1; i++) { for (j = i + 1; j < n; j++) { x.real_value += coordnum::switching_function(r0, r0_vec, en, ed, (*group1)[i], (*group1)[j], &pairlist_elem, tolerance); } } } } else { // if (use_pairlist) { int const flags = compute_flags | coordnum::ef_null; for (i = 0; i < n - 1; i++) { for (j = i + 1; j < n; j++) { x.real_value += coordnum::switching_function(r0, r0_vec, en, ed, (*group1)[i], (*group1)[j], &pairlist_elem, tolerance); } } } return COLVARS_OK; } void colvar::selfcoordnum::calc_value() { x.real_value = 0.0; if (is_enabled(f_cvc_gradient)) { compute_selfcoordnum(); } else { compute_selfcoordnum(); } } void colvar::selfcoordnum::calc_gradients() { // Gradients are computed by calc_value() if f_cvc_gradients is enabled } void colvar::selfcoordnum::apply_force(colvarvalue const &force) { if (!group1->noforce) { group1->apply_colvar_force(force.real_value); } } simple_scalar_dist_functions(selfcoordnum) // groupcoordnum member functions colvar::groupcoordnum::groupcoordnum(std::string const &conf) : distance(conf), b_anisotropic(false) { function_type = "groupcoordnum"; x.type(colvarvalue::type_scalar); // group1 and group2 are already initialized by distance() if (group1->b_dummy || group2->b_dummy) { cvm::error("Error: neither group can be a dummy atom\n"); return; } bool const b_scale = get_keyval(conf, "cutoff", r0, cvm::real(4.0 * cvm::unit_angstrom())); if (get_keyval(conf, "cutoff3", r0_vec, cvm::rvector(4.0, 4.0, 4.0), parse_silent)) { if (b_scale) { cvm::error("Error: cannot specify \"scale\" and " "\"scale3\" at the same time.\n"); return; } b_anisotropic = true; // remove meaningless negative signs if (r0_vec.x < 0.0) r0_vec.x *= -1.0; if (r0_vec.y < 0.0) r0_vec.y *= -1.0; if (r0_vec.z < 0.0) r0_vec.z *= -1.0; } get_keyval(conf, "expNumer", en, 6); get_keyval(conf, "expDenom", ed, 12); if ( (en%2) || (ed%2) ) { cvm::error("Error: odd exponent(s) provided, can only use even ones.\n", INPUT_ERROR); } if ( (en <= 0) || (ed <= 0) ) { cvm::error("Error: negative exponent(s) provided.\n", INPUT_ERROR); } if (!is_enabled(f_cvc_pbc_minimum_image)) { cvm::log("Warning: only minimum-image distances are used by this variable.\n"); } } colvar::groupcoordnum::groupcoordnum() : b_anisotropic(false) { function_type = "groupcoordnum"; x.type(colvarvalue::type_scalar); } void colvar::groupcoordnum::calc_value() { // create fake atoms to hold the com coordinates cvm::atom group1_com_atom; cvm::atom group2_com_atom; group1_com_atom.pos = group1->center_of_mass(); group2_com_atom.pos = group2->center_of_mass(); if (b_anisotropic) { int const flags = coordnum::ef_anisotropic; x.real_value = coordnum::switching_function(r0, r0_vec, en, ed, group1_com_atom, group2_com_atom, NULL, 0.0); } else { int const flags = coordnum::ef_null; x.real_value = coordnum::switching_function(r0, r0_vec, en, ed, group1_com_atom, group2_com_atom, NULL, 0.0); } } void colvar::groupcoordnum::calc_gradients() { cvm::atom group1_com_atom; cvm::atom group2_com_atom; group1_com_atom.pos = group1->center_of_mass(); group2_com_atom.pos = group2->center_of_mass(); if (b_anisotropic) { int const flags = coordnum::ef_gradients | coordnum::ef_anisotropic; coordnum::switching_function(r0, r0_vec, en, ed, group1_com_atom, group2_com_atom, NULL, 0.0); } else { int const flags = coordnum::ef_gradients; coordnum::switching_function(r0, r0_vec, en, ed, group1_com_atom, group2_com_atom, NULL, 0.0); } group1->set_weighted_gradient(group1_com_atom.grad); group2->set_weighted_gradient(group2_com_atom.grad); } void colvar::groupcoordnum::apply_force(colvarvalue const &force) { if (!group1->noforce) group1->apply_colvar_force(force.real_value); if (!group2->noforce) group2->apply_colvar_force(force.real_value); } simple_scalar_dist_functions(groupcoordnum)