From a8a52bcc644cab1ac7b9d48719c74bd4fd1b3f2d Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Thu, 13 Nov 2025 11:25:54 -0500 Subject: [PATCH 01/25] cleanup: Remove unused flag --- .../colvars/colvarproxygromacs.cpp.patch | 14 ++++++++++++++ namd/src/colvarproxy_namd.C | 6 ------ src/colvarproxy.cpp | 10 +++------- src/colvarproxy.h | 5 +---- vmd/src/colvarproxy_vmd.C | 6 ------ 5 files changed, 18 insertions(+), 23 deletions(-) create mode 100644 gromacs/src/applied_forces/colvars/colvarproxygromacs.cpp.patch diff --git a/gromacs/src/applied_forces/colvars/colvarproxygromacs.cpp.patch b/gromacs/src/applied_forces/colvars/colvarproxygromacs.cpp.patch new file mode 100644 index 000000000..00a64b7b8 --- /dev/null +++ b/gromacs/src/applied_forces/colvars/colvarproxygromacs.cpp.patch @@ -0,0 +1,14 @@ +diff --git a/src/gromacs/applied_forces/colvars/colvarproxygromacs.cpp b/src/gromacs/applied_forces/colvars/colvarproxygromacs.cpp +index 76b350c611..37e5b53647 100644 +--- a/src/gromacs/applied_forces/colvars/colvarproxygromacs.cpp ++++ b/src/gromacs/applied_forces/colvars/colvarproxygromacs.cpp +@@ -79,9 +79,6 @@ ColvarProxyGromacs::ColvarProxyGromacs(const std::string& colvarsConfigString, + // Retrieve masses and charges from input file + updated_masses_ = updated_charges_ = true; + +- // User-scripted forces are not available in GROMACS +- have_scripts = false; +- + angstrom_value_ = 0.1; + + // From Gnu units diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index f17215a7e..d9c3fa12f 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -134,12 +134,6 @@ colvarproxy_namd::colvarproxy_namd(GlobalMasterColvars *gm) // save to Node for Tcl script access Node::Object()->colvars = colvars; -#ifdef NAMD_TCL - have_scripts = true; -#else - have_scripts = false; -#endif - if (simparams->firstTimestep != 0) { colvars->set_initial_step(static_cast(simparams->firstTimestep)); } diff --git a/src/colvarproxy.cpp b/src/colvarproxy.cpp index d1315c529..73ebce48d 100644 --- a/src/colvarproxy.cpp +++ b/src/colvarproxy.cpp @@ -416,18 +416,14 @@ int colvarproxy_smp::smp_unlock() -colvarproxy_script::colvarproxy_script() -{ - script = NULL; - have_scripts = false; -} +colvarproxy_script::colvarproxy_script() {} colvarproxy_script::~colvarproxy_script() { - if (script != NULL) { + if (script) { delete script; - script = NULL; + script = nullptr; } } diff --git a/src/colvarproxy.h b/src/colvarproxy.h index a1e99eff0..e38b1b299 100644 --- a/src/colvarproxy.h +++ b/src/colvarproxy.h @@ -529,10 +529,7 @@ class colvarproxy_script { /// Pointer to the scripting interface object /// (does not need to be allocated in a new interface) - colvarscript *script; - - /// Do we have a scripting interface? - bool have_scripts; + colvarscript *script = nullptr; /// Run a user-defined colvar forces script virtual int run_force_callback(); diff --git a/vmd/src/colvarproxy_vmd.C b/vmd/src/colvarproxy_vmd.C index ff2644dae..f5f310807 100644 --- a/vmd/src/colvarproxy_vmd.C +++ b/vmd/src/colvarproxy_vmd.C @@ -85,15 +85,9 @@ colvarproxy_vmd::colvarproxy_vmd(Tcl_Interp *interp, VMDApp *v, int molid) // both fields are taken from data structures already available updated_masses_ = updated_charges_ = true; - // Do we have scripts? - // For now colvars depend on Tcl, but this may not always be the case - // in the future #if defined(VMDTCL) - have_scripts = true; // Need to set this before constructing colvarmodule, which creates colvarscript object set_tcl_interp(interp); -#else - have_scripts = false; #endif colvars = new colvarmodule(this); From c36e645cbf948b49854362f30d922283cac8aa30 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Tue, 25 Nov 2025 15:59:25 -0600 Subject: [PATCH 02/25] fix: try fixing the crash Node.h is needed for macros like `CMK_SMP && USE_CKLOOP`, and "NamdTypes.h" is used for NAMD_UNIFIED_REDUCTION and AtomIDList. --- namd/src/colvarproxy_namd.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/namd/src/colvarproxy_namd.h b/namd/src/colvarproxy_namd.h index ef86f615d..57f114b58 100644 --- a/namd/src/colvarproxy_namd.h +++ b/namd/src/colvarproxy_namd.h @@ -19,6 +19,11 @@ #include "colvarproxy_namd_version.h" +// For NAMD_UNIFIED_REDUCTION and AtomIDList +#include "NamdTypes.h" +// For CMK_SMP && USE_CKLOOP +#include "Node.h" + #include "colvarmodule.h" #include "colvarproxy.h" #include "colvarvalue.h" From c65a47a9f63072de9fb0fefa251f0e162e7ec5c5 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Sun, 7 Dec 2025 14:05:24 -0500 Subject: [PATCH 03/25] opt: Do not invalidate NAMD GlobalMaster arrays when not needed Now that we are overriding the GlobalMaster class functions, we can pass protected members through a const accessor --- namd/src/GlobalMasterColvars.h | 10 ++++++++++ namd/src/colvarproxy_namd.C | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/namd/src/GlobalMasterColvars.h b/namd/src/GlobalMasterColvars.h index 5ceaaf13c..bbabd687b 100644 --- a/namd/src/GlobalMasterColvars.h +++ b/namd/src/GlobalMasterColvars.h @@ -39,6 +39,11 @@ class GlobalMasterColvars : public GlobalMaster { return modifyAppliedForces(); } + inline ResizeArray const &getRequestedGroups() const + { + return reqGroups; + } + inline ResizeArray &modifyRequestedGroupsPublic() { return modifyRequestedGroups(); @@ -49,6 +54,11 @@ class GlobalMasterColvars : public GlobalMaster { return modifyGroupForces(); } + inline IntList const &getRequestedGridObjects() const + { + return reqGridObjs; + } + inline IntList &modifyRequestedGridObjectsPublic() { return modifyRequestedGridObjects(); diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index d9c3fa12f..8e67af534 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -262,8 +262,8 @@ int colvarproxy_namd::setup() } size_t n_group_atoms = 0; - for (int ig = 0; ig < globalmaster->modifyRequestedGroupsPublic().size(); ig++) { - n_group_atoms += globalmaster->modifyRequestedGroupsPublic()[ig].size(); + for (int ig = 0; ig < globalmaster->getRequestedGroups().size(); ig++) { + n_group_atoms += globalmaster->getRequestedGroups()[ig].size(); } log("updating group data ("+cvm::to_str(atom_groups_ids.size())+ @@ -271,7 +271,7 @@ int colvarproxy_namd::setup() cvm::to_str(n_group_atoms)+" atoms in total).\n"); // Note: groupMassBegin, groupMassEnd may be used here, but they won't work for charges - for (int ig = 0; ig < globalmaster->modifyRequestedGroupsPublic().size(); ig++) { + for (int ig = 0; ig < globalmaster->getRequestedGroups().size(); ig++) { // update mass and charge update_group_properties(ig); @@ -284,7 +284,7 @@ int colvarproxy_namd::setup() #if NAMD_VERSION_NUMBER >= 34471681 log("updating grid object data ("+cvm::to_str(volmaps_ids.size())+ " grid objects in total).\n"); - for (int imap = 0; imap < globalmaster->modifyGridObjForcesPublic().size(); imap++) { + for (int imap = 0; imap < globalmaster->getRequestedGridObjects().size(); imap++) { volmaps_new_colvar_forces[imap] = 0.0; } #endif From 3874c86b0a9721b2e4256d45667ceb85556f621d Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Sun, 7 Dec 2025 15:05:03 -0500 Subject: [PATCH 04/25] fix: Update atoms_map in NAMD proxy after requesting new atoms --- namd/src/colvarproxy_namd.C | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index 8e67af534..404c81acb 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -414,11 +414,15 @@ void colvarproxy_namd::calculate() // If new atomic positions or forces have been requested by other // GlobalMaster objects, add these to the atom map as well size_t const n_all_atoms = Node::Object()->molecule->numAtoms; - if ( (atoms_map.size() != n_all_atoms) || - (int(atoms_ids.size()) < (globalmaster->getAtomIdEndPublic() - globalmaster->getAtomIdBeginPublic())) || - (int(atoms_ids.size()) < (globalmaster->getForceIdEndPublic() - globalmaster->getForceIdBeginPublic())) ) { + if (modified_atom_list() || /* Colvars just requested new atoms */ + (atoms_map.size() != n_all_atoms) || /* The system topology has changed */ + (int(atoms_ids.size()) < /* Another GlobalMaster requested new atoms */ + (globalmaster->getAtomIdEndPublic() - globalmaster->getAtomIdBeginPublic())) || + (int(atoms_ids.size()) < /* Another GlobalMaster requested new total forces */ + (globalmaster->getForceIdEndPublic() - globalmaster->getForceIdBeginPublic()))) { update_atoms_map(globalmaster->getAtomIdBeginPublic(), globalmaster->getAtomIdEndPublic()); update_atoms_map(globalmaster->getForceIdBeginPublic(), globalmaster->getForceIdEndPublic()); + reset_modified_atom_list(); // reset the flag as needed } // prepare local arrays From 2b838f2ba0a5ed9d3fdc331db34f9486aa22d639 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Sun, 7 Dec 2025 17:15:04 -0600 Subject: [PATCH 05/25] test: force the boundary checks to reveal the bug This is just to trigger the bug on CI. You can revert this commit later after solving the issue. --- namd/src/colvarproxy_namd.C | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index 404c81acb..f0c06d2ad 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -478,6 +478,9 @@ void colvarproxy_namd::calculate() ForceList::const_iterator f_i = globalmaster->getTotalForcePublic(); for ( ; a_i != a_e; ++a_i, ++f_i ) { + if (atoms_map[*a_i] < 0) { + cvm::error("Bug: atoms_map at " + cvm::to_str(*a_i) + " is less than zero!\n"); + } atoms_total_forces[atoms_map[*a_i]] = cvm::rvector((*f_i).x, (*f_i).y, (*f_i).z); n_total_forces++; } @@ -834,6 +837,9 @@ int colvarproxy_namd::init_atom(cvm::residue_id const &residue, ") for collective variables calculation.\n"); int const index = add_atom_slot(aid); + if (atoms_map.empty()) { + cvm::error("Bug: atoms_map is empty in colvarproxy_namd::init_atom!"); + } atoms_map[aid] = index; globalmaster->modifyRequestedAtomsPublic().add(aid); update_atom_properties(index); From 28dadf8a5734b13a9bee4354670a0eeb53f701d9 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Mon, 8 Dec 2025 09:18:31 -0600 Subject: [PATCH 06/25] fix: reset the atoms_map to -1 instead of clearing it This might to solve the resetting and reloading crash in the NAMD interface. --- namd/src/colvarproxy_namd.C | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index f0c06d2ad..bde310d74 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -37,6 +37,8 @@ #include #endif +#include + #include "colvarmodule.h" #include "colvar.h" #include "colvarbias.h" @@ -319,7 +321,13 @@ int colvarproxy_namd::reset() globalmaster->reset(); - atoms_map.clear(); + // atoms_map.clear(); + // TODO: There's no other way to re-initialize the atoms_map after + // clearing and then reloading a new configuration file, so we just + // assume that the number of atoms is unchanged, and reset the atoms_map + // to -1. However, this might be problematic if NAMD supports to + // reload a new system with different number of atoms in the future. + std::fill(atoms_map.begin(), atoms_map.end(), -1); // Clear internal atomic data error_code |= colvarproxy::reset(); From e8a7da2924e41b18bab8854f8aff16ae502ec584 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Mon, 8 Dec 2025 09:20:45 -0600 Subject: [PATCH 07/25] test: try the 003_reinitatoms test on ARM64 --- namd/tests/interface/003_reinitatoms/skip_test.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/namd/tests/interface/003_reinitatoms/skip_test.sh b/namd/tests/interface/003_reinitatoms/skip_test.sh index 80117a457..f0a71c276 100644 --- a/namd/tests/interface/003_reinitatoms/skip_test.sh +++ b/namd/tests/interface/003_reinitatoms/skip_test.sh @@ -1,7 +1,7 @@ #!/bin/bash -ARCH=$(arch) -if [ "$ARCH" = "aarch64" ]; then - exit 0 -else - exit 1 -fi +# ARCH=$(arch) +# if [ "$ARCH" = "aarch64" ]; then +# exit 0 +# else +# exit 1 +# fi From 72f50edc174bf70a891030d2012b7dec92f85d01 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Mon, 8 Dec 2025 09:31:25 -0600 Subject: [PATCH 08/25] test: remove the skip_test.sh and try the test with ARM64 again --- namd/tests/interface/003_reinitatoms/skip_test.sh | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 namd/tests/interface/003_reinitatoms/skip_test.sh diff --git a/namd/tests/interface/003_reinitatoms/skip_test.sh b/namd/tests/interface/003_reinitatoms/skip_test.sh deleted file mode 100644 index f0a71c276..000000000 --- a/namd/tests/interface/003_reinitatoms/skip_test.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# ARCH=$(arch) -# if [ "$ARCH" = "aarch64" ]; then -# exit 0 -# else -# exit 1 -# fi From f3789cefd862e7b8779fc49c77fdad72d3b5802c Mon Sep 17 00:00:00 2001 From: HanatoK Date: Mon, 8 Dec 2025 09:58:19 -0600 Subject: [PATCH 09/25] test: revert the previous commits to disable ARM64 test again Currently spiff has no way to ignore the comparison of nan and -nan. --- namd/tests/interface/003_reinitatoms/skip_test.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 namd/tests/interface/003_reinitatoms/skip_test.sh diff --git a/namd/tests/interface/003_reinitatoms/skip_test.sh b/namd/tests/interface/003_reinitatoms/skip_test.sh new file mode 100644 index 000000000..80117a457 --- /dev/null +++ b/namd/tests/interface/003_reinitatoms/skip_test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +ARCH=$(arch) +if [ "$ARCH" = "aarch64" ]; then + exit 0 +else + exit 1 +fi From 1c072e6cf1d59a64a237c7e8088bb4646ba3f492 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 15:27:59 -0500 Subject: [PATCH 10/25] fix: Track when total forces from the engine are valid or not --- lammps/src/COLVARS/fix_colvars.cpp | 15 ++++++++++----- namd/src/colvarproxy_namd.C | 14 ++++++++++++-- src/colvar.cpp | 10 ++++++++++ src/colvarproxy.cpp | 15 +++++++++++++++ src/colvarproxy.h | 15 +++++++++++++++ src/colvarproxy_system.cpp | 1 + 6 files changed, 63 insertions(+), 7 deletions(-) diff --git a/lammps/src/COLVARS/fix_colvars.cpp b/lammps/src/COLVARS/fix_colvars.cpp index 468d8aa9e..dafb64352 100644 --- a/lammps/src/COLVARS/fix_colvars.cpp +++ b/lammps/src/COLVARS/fix_colvars.cpp @@ -497,7 +497,6 @@ void FixColvars::setup(int vflag) std::vector &tp = *(proxy->modify_atom_types()); std::vector &cd = *(proxy->modify_atom_positions()); - std::vector &of = *(proxy->modify_atom_total_forces()); std::vector &m = *(proxy->modify_atom_masses()); std::vector &q = *(proxy->modify_atom_charges()); @@ -508,8 +507,6 @@ void FixColvars::setup(int vflag) const tagint k = atom->map(taglist[i]); if ((k >= 0) && (k < nlocal)) { - of[i].x = of[i].y = of[i].z = 0.0; - if (unwrap_flag) { const int ix = (image[k] & IMGMASK) - IMGMAX; const int iy = (image[k] >> IMGBITS & IMGMASK) - IMGMAX; @@ -558,11 +555,14 @@ void FixColvars::setup(int vflag) m[j] = comm_buf[k].m; q[j] = comm_buf[k].q; - - of[j].x = of[j].y = of[j].z = 0.0; } } } + + if (proxy->total_forces_enabled()) { + proxy->set_total_forces_invalid(); + } + } else { // me != 0 // copy coordinate data into communication buffer @@ -871,6 +871,11 @@ void FixColvars::end_of_step() MPI_Recv(&tmp, 0, MPI_INT, 0, 0, world, MPI_STATUS_IGNORE); MPI_Rsend(comm_buf, nme*size_one, MPI_BYTE, 0, 0, world); } + + if (proxy->total_forces_enabled()) { + proxy->set_total_forces_valid(); + } + } } diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index bde310d74..ad3fef023 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -259,7 +259,6 @@ int colvarproxy_namd::setup() // zero out mutable arrays atoms_positions[i] = cvm::rvector(0.0, 0.0, 0.0); - atoms_total_forces[i] = cvm::rvector(0.0, 0.0, 0.0); atoms_new_colvar_forces[i] = cvm::rvector(0.0, 0.0, 0.0); } @@ -279,7 +278,6 @@ int colvarproxy_namd::setup() update_group_properties(ig); atom_groups_coms[ig] = cvm::rvector(0.0, 0.0, 0.0); - atom_groups_total_forces[ig] = cvm::rvector(0.0, 0.0, 0.0); atom_groups_new_colvar_forces[ig] = cvm::rvector(0.0, 0.0, 0.0); } @@ -291,6 +289,13 @@ int colvarproxy_namd::setup() } #endif + if (total_force_requested && modified_atom_list()) { + if (cvm::debug()) { + log("zeroing out buffers total forces on atom and groups.\n"); + } + set_total_forces_invalid(); + } + size_t const new_features_hash = std::hash{}(colvars->feature_report(0)); if (new_features_hash != features_hash) { @@ -570,6 +575,11 @@ void colvarproxy_namd::calculate() cvm::error("Error in the collective variables module.\n", COLVARS_ERROR); } + if (total_force_requested) { + // Total forces will be valid at the next step (this function is only called once) + set_total_forces_valid(); + } + if (cvm::debug()) { print_output_atomic_data(); } diff --git a/src/colvar.cpp b/src/colvar.cpp index 0686e1f5e..7295dc8db 100644 --- a/src/colvar.cpp +++ b/src/colvar.cpp @@ -1644,6 +1644,16 @@ int colvar::collect_cvc_gradients() int colvar::calc_cvc_total_force(int first_cvc, size_t num_cvcs) { + auto *proxy = cvm::main()->proxy; + if (!proxy->total_forces_valid()) { + if (cvm::debug()) { + cvm::log("Skipping total force computation for colvar \"" + name + + "\", because we do not have up to date total forces at this step."); + } + ft.reset(); + return COLVARS_OK; + } + size_t const cvc_max_count = num_cvcs ? num_cvcs : num_active_cvcs(); size_t i, cvc_count; diff --git a/src/colvarproxy.cpp b/src/colvarproxy.cpp index 73ebce48d..59ed83b6e 100644 --- a/src/colvarproxy.cpp +++ b/src/colvarproxy.cpp @@ -609,6 +609,21 @@ int colvarproxy::post_run() } +void colvarproxy::set_total_forces_invalid() +{ + std::fill(atoms_total_forces.begin(), atoms_total_forces.end(), cvm::rvector(0.0, 0.0, 0.0)); + std::fill(atom_groups_total_forces.begin(), atom_groups_total_forces.end(), + cvm::rvector(0.0, 0.0, 0.0)); + total_forces_valid_ = false; +} + + +void colvarproxy::set_total_forces_valid() +{ + total_forces_valid_ = true; +} + + void colvarproxy::print_input_atomic_data() { cvm::log(cvm::line_marker); diff --git a/src/colvarproxy.h b/src/colvarproxy.h index e38b1b299..c05380003 100644 --- a/src/colvarproxy.h +++ b/src/colvarproxy.h @@ -626,6 +626,18 @@ class colvarproxy return engine_ready_; } + /// Are total forces currently valid? (They would not be right after configuration change) + inline bool total_forces_valid() const + { + return total_forces_valid_; + } + + /// Mark the total forces as invalid (due to e.g. a configuration change) + void set_total_forces_invalid(); + + /// Mark the total forces as up to date + void set_total_forces_valid(); + /// Enqueue new configuration text, to be parsed as soon as possible void add_config(std::string const &cmd, std::string const &conf); @@ -692,6 +704,9 @@ class colvarproxy /// Whether the engine allows to fully initialize Colvars immediately bool engine_ready_; + /// Whether the total forces are currently valid + bool total_forces_valid_ = false; + /// Collected error messages std::string error_output; diff --git a/src/colvarproxy_system.cpp b/src/colvarproxy_system.cpp index 401bb4c59..6b082b8ed 100644 --- a/src/colvarproxy_system.cpp +++ b/src/colvarproxy_system.cpp @@ -7,6 +7,7 @@ // If you wish to distribute your changes, please submit them to the // Colvars repository at GitHub. +#include #include "colvarmodule.h" #include "colvartypes.h" From da9a81f8742db085e37c85700cb32bb43cdaeaa7 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 15:32:14 -0500 Subject: [PATCH 11/25] test: Update regtest reference file to remove nan --- namd/tests/interface/003_reinitatoms/AutoDiff/test.colvars.traj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namd/tests/interface/003_reinitatoms/AutoDiff/test.colvars.traj b/namd/tests/interface/003_reinitatoms/AutoDiff/test.colvars.traj index 181c17ed2..cc26f2d68 100644 --- a/namd/tests/interface/003_reinitatoms/AutoDiff/test.colvars.traj +++ b/namd/tests/interface/003_reinitatoms/AutoDiff/test.colvars.traj @@ -43,7 +43,7 @@ 39 6.88300617631749e+00 -3.72442767426713e+01 -8.83006176317490e-01 40 6.88218411453200e+00 -2.66371930519242e+01 -8.82184114532001e-01 # step rgyr ft_rgyr fa_rgyr - 40 6.79302548471894e+00 -nan -7.93025484718935e-01 + 40 6.79302548471894e+00 0.00000000000000e+00 -7.93025484718935e-01 41 6.79123478804224e+00 1.47884739101504e+01 -7.91234788042243e-01 42 6.78960135015216e+00 6.31388772773012e+01 -7.89601350152160e-01 43 6.78822556441034e+00 1.06473629117400e+02 -7.88225564410344e-01 From e364f83920a2f92c6160169d75ffe0c48477e3dd Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 15:32:56 -0500 Subject: [PATCH 12/25] test: Re-enable 003_reinitatoms test in ARM64 --- namd/tests/interface/003_reinitatoms/skip_test.sh | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 namd/tests/interface/003_reinitatoms/skip_test.sh diff --git a/namd/tests/interface/003_reinitatoms/skip_test.sh b/namd/tests/interface/003_reinitatoms/skip_test.sh deleted file mode 100644 index 80117a457..000000000 --- a/namd/tests/interface/003_reinitatoms/skip_test.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -ARCH=$(arch) -if [ "$ARCH" = "aarch64" ]; then - exit 0 -else - exit 1 -fi From 2c13dda6c0330fe801995148de25ae9acab8a53b Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 16:10:55 -0500 Subject: [PATCH 13/25] doc: Set bug error flag upon bug occurrence --- namd/src/colvarproxy_namd.C | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/namd/src/colvarproxy_namd.C b/namd/src/colvarproxy_namd.C index ad3fef023..799834fe3 100644 --- a/namd/src/colvarproxy_namd.C +++ b/namd/src/colvarproxy_namd.C @@ -492,7 +492,8 @@ void colvarproxy_namd::calculate() for ( ; a_i != a_e; ++a_i, ++f_i ) { if (atoms_map[*a_i] < 0) { - cvm::error("Bug: atoms_map at " + cvm::to_str(*a_i) + " is less than zero!\n"); + cvm::error("Bug: atoms_map at " + cvm::to_str(*a_i) + " is less than zero!\n", + COLVARS_BUG_ERROR); } atoms_total_forces[atoms_map[*a_i]] = cvm::rvector((*f_i).x, (*f_i).y, (*f_i).z); n_total_forces++; @@ -856,7 +857,7 @@ int colvarproxy_namd::init_atom(cvm::residue_id const &residue, int const index = add_atom_slot(aid); if (atoms_map.empty()) { - cvm::error("Bug: atoms_map is empty in colvarproxy_namd::init_atom!"); + cvm::error("Bug: atoms_map is empty in colvarproxy_namd::init_atom!", COLVARS_BUG_ERROR); } atoms_map[aid] = index; globalmaster->modifyRequestedAtomsPublic().add(aid); From 67a26da0d477d4c86cfb3f5cd5333ab2e4c4b00d Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 16:55:24 -0500 Subject: [PATCH 14/25] fix: Backport changes from LAMMPS repository --- lammps/src/COLVARS/colvarproxy_lammps.h | 11 +++++++ lammps/src/COLVARS/fix_colvars.cpp | 44 ++++++++++--------------- lammps/src/COLVARS/fix_colvars.h | 6 ++-- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lammps/src/COLVARS/colvarproxy_lammps.h b/lammps/src/COLVARS/colvarproxy_lammps.h index e1bd552e5..d5a59896e 100644 --- a/lammps/src/COLVARS/colvarproxy_lammps.h +++ b/lammps/src/COLVARS/colvarproxy_lammps.h @@ -85,7 +85,18 @@ class colvarproxy_lammps : public colvarproxy { cvm::real rand_gaussian() override; int init_atom(int atom_number) override; + int init_atom(cvm::residue_id const &residue, std::string const &atom_name, + std::string const &segment_id) override + { + return colvarproxy::init_atom(residue, atom_name, segment_id); + } + int check_atom_id(int atom_number) override; + int check_atom_id(cvm::residue_id const &residue, std::string const &atom_name, + std::string const &segment_id) override + { + return colvarproxy::check_atom_id(residue, atom_name, segment_id); + } inline std::vector *modify_atom_types() { return &atoms_types; } }; diff --git a/lammps/src/COLVARS/fix_colvars.cpp b/lammps/src/COLVARS/fix_colvars.cpp index dafb64352..c490bff12 100644 --- a/lammps/src/COLVARS/fix_colvars.cpp +++ b/lammps/src/COLVARS/fix_colvars.cpp @@ -27,7 +27,6 @@ ------------------------------------------------------------------------- */ #include "fix_colvars.h" -#include "inthash.h" #include "atom.h" #include "citeme.h" @@ -61,7 +60,6 @@ struct LAMMPS_NS::commdata { using namespace LAMMPS_NS; using namespace FixConst; -using namespace IntHash_NS; // initialize static class members int FixColvars::instances = 0; @@ -122,7 +120,6 @@ FixColvars::FixColvars(LAMMPS *lmp, int narg, char **arg) : comm_buf = nullptr; taglist = nullptr; force_buf = nullptr; - idmap = nullptr; script_args[0] = reinterpret_cast(utils::strdup("fix_modify")); @@ -227,11 +224,6 @@ FixColvars::~FixColvars() if (proxy) delete proxy; - if (idmap) { - inthash_destroy(idmap); - delete idmap; - } - if (root2root != MPI_COMM_NULL) MPI_Comm_free(&root2root); @@ -349,16 +341,11 @@ void FixColvars::init_taglist() std::vector const &tl = *(proxy->get_atom_ids()); - if (idmap) { - delete idmap; - idmap = nullptr; - } - - idmap = new inthash_t; - inthash_init(idmap, num_coords); + idmap.clear(); + idmap.reserve(num_coords); for (int i = 0; i < num_coords; ++i) { taglist[i] = tl[i]; - inthash_insert(idmap, tl[i], i); + idmap[tl[i]] = i; } } @@ -542,10 +529,9 @@ void FixColvars::setup(int vflag) ndata /= size_one; for (int k=0; ksecond; tp[j] = comm_buf[k].type; @@ -701,8 +687,10 @@ void FixColvars::post_force(int /*vflag*/) ndata /= size_one; for (int k=0; ksecond; + cd[j].x = comm_buf[k].x; cd[j].y = comm_buf[k].y; cd[j].z = comm_buf[k].z; @@ -826,8 +814,10 @@ void FixColvars::end_of_step() const tagint k = atom->map(taglist[i]); if ((k >= 0) && (k < nlocal)) { - const int j = inthash_lookup(idmap, tag[k]); - if (j != HASH_FAIL) { + auto search = idmap.find(tag[k]); + if (search != idmap.end()) { + const int j = search->second; + of[j].x = f[k][0]; of[j].y = f[k][1]; of[j].z = f[k][2]; @@ -845,8 +835,10 @@ void FixColvars::end_of_step() ndata /= size_one; for (int k=0; ksecond; + of[j].x = comm_buf[k].x; of[j].y = comm_buf[k].y; of[j].z = comm_buf[k].z; diff --git a/lammps/src/COLVARS/fix_colvars.h b/lammps/src/COLVARS/fix_colvars.h index 01c5b0e48..40872d5bd 100644 --- a/lammps/src/COLVARS/fix_colvars.h +++ b/lammps/src/COLVARS/fix_colvars.h @@ -35,11 +35,9 @@ FixStyle(colvars,FixColvars); #define LMP_FIX_COLVARS_H #include "fix.h" +#include // Forward declarations -namespace IntHash_NS { -struct inthash_t; -} class colvarproxy_lammps; namespace LAMMPS_NS { @@ -87,7 +85,7 @@ class FixColvars : public Fix { /// Arguments passed from fix_modify to the Colvars script interface unsigned char *script_args[100]; - IntHash_NS::inthash_t *idmap; // hash for mapping atom indices to consistent order. + std::unordered_map idmap; // for mapping atom indices to consistent order. int nlevels_respa; // flag to determine respa levels. int store_forces; // flag to determine whether to store total forces From fa8dcfad8d447e198b588f3085cc57d6e7df6d33 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 17:37:46 -0500 Subject: [PATCH 15/25] test: Try downloading container a couple of times before raising error --- .github/workflows/backend-template.yml | 5 +--- .github/workflows/test-library.yml | 32 ++++++-------------------- devel-tools/get_container | 20 ++++++++++++++++ 3 files changed, 28 insertions(+), 29 deletions(-) create mode 100755 devel-tools/get_container diff --git a/.github/workflows/backend-template.yml b/.github/workflows/backend-template.yml index 37879bdcb..84e31e4f5 100644 --- a/.github/workflows/backend-template.yml +++ b/.github/workflows/backend-template.yml @@ -135,10 +135,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container images for build dependencies - shell: bash - working-directory: devel-tools - run: | - apptainer pull ${{ inputs.container_name }}.sif oras://ghcr.io/colvars/devel-containers:${{ inputs.container_name }} + run: devel-tools/get_container ${{ inputs.container_name }} - name: Update and build ${{ inputs.backend_name }} shell: bash diff --git a/.github/workflows/test-library.yml b/.github/workflows/test-library.yml index b3d8d00de..a4501dd62 100644 --- a/.github/workflows/test-library.yml +++ b/.github/workflows/test-library.yml @@ -92,10 +92,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container image - shell: bash - working-directory: devel-tools - run: | - apptainer pull texlive.sif oras://ghcr.io/colvars/devel-containers:texlive + run: devel-tools/get_container texlive - name: Checkout website repository uses: actions/checkout@v4 @@ -191,10 +188,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container images for build dependencies - shell: bash - working-directory: devel-tools - run: | - apptainer pull CentOS9-devel.sif oras://ghcr.io/colvars/devel-containers:CentOS9-devel + run: devel-tools/get_container CentOS9-devel - name: Run build with Clang static analyser env: @@ -252,10 +246,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container images for build dependencies - shell: bash - working-directory: devel-tools - run: | - apptainer pull CentOS9-devel.sif oras://ghcr.io/colvars/devel-containers:CentOS9-devel + run: devel-tools/get_container CentOS9-devel - name: Build with Clang and address sanitizer env: @@ -307,11 +298,8 @@ jobs: sudo apt update sudo apt install -y apptainer-suid - - name: Get container images for build environment - shell: bash - working-directory: devel-tools - run: | - apptainer pull CentOS9-devel.sif oras://ghcr.io/colvars/devel-containers:CentOS9-devel + - name: Get container images for build dependencies + run: devel-tools/get_container CentOS9-devel - name: GCC 11, C++11 (CentOS 9) env: @@ -447,10 +435,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container images for build dependencies - shell: bash - working-directory: devel-tools - run: | - apptainer pull CentOS9-oneAPI.sif oras://ghcr.io/colvars/devel-containers:CentOS9-oneAPI + run: devel-tools/get_container CentOS9-devel-oneAPI - name: Intel oneAPI 2024.2, C++11 shell: bash @@ -573,10 +558,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container images for build dependencies - shell: bash - working-directory: devel-tools - run: | - apptainer pull CentOS9-devel-arm64.sif oras://ghcr.io/colvars/devel-containers:CentOS9-devel-arm64 + run: devel-tools/get_container CentOS9-devel-arm64 - name: GCC 11, C++20 (CentOS 9) env: diff --git a/devel-tools/get_container b/devel-tools/get_container new file mode 100755 index 000000000..9c341208c --- /dev/null +++ b/devel-tools/get_container @@ -0,0 +1,20 @@ +#!/bin/bash + +DOWNLOAD_DIR=$(dirname $0) + +label=$1 +if [ -n "${label}" ] ; then + if [ ! -f "${DOWNLOAD_DIR}/${label}.sif" ] ; then + echo "Downloading container image \"${label}\"..." + for try in {1..3}; do + apptainer pull "${DOWNLOAD_DIR}/${label}.sif" oras://ghcr.io/colvars/devel-containers:"${label}" && break + retcode=$? + if [ ${retcode} != 0 ] ; then + echo "(download failed, retrying...)" + sleep 15s + fi + done + fi +fi + +exit ${retcode} From e1c3d4fe24aab82a7251ad1e8f26ba575be233d4 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Tue, 9 Dec 2025 17:59:54 -0500 Subject: [PATCH 16/25] fix: Fix container name in CI job --- .github/workflows/test-library.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-library.yml b/.github/workflows/test-library.yml index a4501dd62..d034195f2 100644 --- a/.github/workflows/test-library.yml +++ b/.github/workflows/test-library.yml @@ -435,7 +435,7 @@ jobs: sudo apt install -y apptainer-suid - name: Get container images for build dependencies - run: devel-tools/get_container CentOS9-devel-oneAPI + run: devel-tools/get_container CentOS9-oneAPI - name: Intel oneAPI 2024.2, C++11 shell: bash From 3608b6807a639020b392d6d7b367054574af53a8 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Wed, 10 Dec 2025 00:33:29 -0500 Subject: [PATCH 17/25] fix: Access colvarproxy_lammps object from correct rank --- lammps/src/COLVARS/fix_colvars.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lammps/src/COLVARS/fix_colvars.cpp b/lammps/src/COLVARS/fix_colvars.cpp index c490bff12..ff32528d8 100644 --- a/lammps/src/COLVARS/fix_colvars.cpp +++ b/lammps/src/COLVARS/fix_colvars.cpp @@ -846,6 +846,10 @@ void FixColvars::end_of_step() } } + if (proxy->total_forces_enabled()) { + proxy->set_total_forces_valid(); + } + } else { // me != 0 /* copy total force data into communication buffer */ nme = 0; @@ -864,10 +868,6 @@ void FixColvars::end_of_step() MPI_Rsend(comm_buf, nme*size_one, MPI_BYTE, 0, 0, world); } - if (proxy->total_forces_enabled()) { - proxy->set_total_forces_valid(); - } - } } From 095031c4bc0179997d6d07c1a59792eae1665c8c Mon Sep 17 00:00:00 2001 From: HanatoK Date: Wed, 10 Dec 2025 10:46:21 -0600 Subject: [PATCH 18/25] fix: skip the total force calc in the GPU code path if unavailable --- src/colvar_gpu_calc.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/colvar_gpu_calc.cpp b/src/colvar_gpu_calc.cpp index 555e2f61f..229c723d9 100644 --- a/src/colvar_gpu_calc.cpp +++ b/src/colvar_gpu_calc.cpp @@ -29,6 +29,7 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( #if defined (COLVARS_NVTX_PROFILING) cvc_calc_total_force_prof.start(); #endif // defined (COLVARS_NVTX_PROFILING) + const bool total_force_valid = colvar_module->proxy ? colvar_module->proxy->total_forces_valid() : false; if (!g.graph_exec_initialized) { using node_map_t = std::unordered_map; for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { @@ -91,8 +92,15 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( const auto all_cvcs = (*cvi)->get_cvcs(); for (auto cvc = all_cvcs.begin(); cvc != all_cvcs.end(); ++cvc) { if (!(*cvc)->is_enabled(colvardeps::f_cvc_active)) continue; +// <<<<<<< HEAD if (!(*cvc)->has_gpu_implementation()) { (*cvc)->calc_force_invgrads(); + if (total_force_valid) { + (*cvc)->calc_force_invgrads(); + } else { + // TODO: (*cvc)->ft is a protected member. How could I do the reset? + } +// >>>>>>> ddd82771 (fix: skip the total force calc in the GPU code path if unavailable) } } if (colvar_module->debug()) { From 38aae724a822cb716f167d24a4c510c662837b30 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Wed, 10 Dec 2025 17:41:56 -0500 Subject: [PATCH 19/25] refactor: Invalidate colvar total force from a function used also by GPU scheduler --- src/colvar.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/colvar.cpp b/src/colvar.cpp index 7295dc8db..6660899c7 100644 --- a/src/colvar.cpp +++ b/src/colvar.cpp @@ -1407,6 +1407,10 @@ int colvar::calc() error_code |= update_cvc_flags(); if (error_code != COLVARS_OK) return error_code; error_code |= calc_cvcs(); + if (!cvm::main()->proxy->total_forces_valid()) { + // Zero out the colvar total force when atomic total forces are not available + ft.reset(); + } if (error_code != COLVARS_OK) return error_code; error_code |= collect_cvc_data(); } @@ -1427,9 +1431,13 @@ int colvar::calc_cvcs(int first_cvc, size_t num_cvcs) return error_code; } - if ((cvm::step_relative() > 0) && (!is_enabled(f_cv_total_force_current_step))){ - // Use Jacobian derivative from previous timestep + if (cvm::main()->proxy->total_forces_valid() && (!is_enabled(f_cv_total_force_current_step))) { + // Use Jacobian derivative computed at previous timestep and the total forces from the same + // step, collected just now from the engine error_code |= calc_cvc_total_force(first_cvc, num_cvcs); + } else { + // Zero out the colvar total force when atomic total forces are not available + ft.reset(); } // atom coordinates are updated by the next line error_code |= calc_cvc_values(first_cvc, num_cvcs); @@ -1644,13 +1652,8 @@ int colvar::collect_cvc_gradients() int colvar::calc_cvc_total_force(int first_cvc, size_t num_cvcs) { - auto *proxy = cvm::main()->proxy; - if (!proxy->total_forces_valid()) { - if (cvm::debug()) { - cvm::log("Skipping total force computation for colvar \"" + name + - "\", because we do not have up to date total forces at this step."); - } - ft.reset(); + if (!cvm::main()->proxy->total_forces_valid()) { + // This is not a step when valid total forces are available return COLVARS_OK; } From 803caa562e96bb98ce25c528ff6fb23a0f6aefe0 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Wed, 10 Dec 2025 21:53:24 -0500 Subject: [PATCH 20/25] refactor: Define accessor to reset the colvar total force --- src/colvar.cpp | 5 +++-- src/colvar.h | 6 ++++++ src/colvar_gpu_calc.cpp | 12 ++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/colvar.cpp b/src/colvar.cpp index 6660899c7..c0e888b24 100644 --- a/src/colvar.cpp +++ b/src/colvar.cpp @@ -1409,7 +1409,7 @@ int colvar::calc() error_code |= calc_cvcs(); if (!cvm::main()->proxy->total_forces_valid()) { // Zero out the colvar total force when atomic total forces are not available - ft.reset(); + reset_total_force(); } if (error_code != COLVARS_OK) return error_code; error_code |= collect_cvc_data(); @@ -1437,7 +1437,7 @@ int colvar::calc_cvcs(int first_cvc, size_t num_cvcs) error_code |= calc_cvc_total_force(first_cvc, num_cvcs); } else { // Zero out the colvar total force when atomic total forces are not available - ft.reset(); + reset_total_force(); } // atom coordinates are updated by the next line error_code |= calc_cvc_values(first_cvc, num_cvcs); @@ -1687,6 +1687,7 @@ int colvar::calc_cvc_total_force(int first_cvc, size_t num_cvcs) int colvar::collect_cvc_total_forces() { if (is_enabled(f_cv_total_force_calc)) { + ft.reset(); for (size_t i = 0; i < cvcs.size(); i++) { diff --git a/src/colvar.h b/src/colvar.h index a8ed2dedc..6407f4c9f 100644 --- a/src/colvar.h +++ b/src/colvar.h @@ -82,6 +82,12 @@ class colvar : public colvarparse, public colvardeps { /// subtracted. colvarvalue const & total_force() const; + /// Reset the total force to zero + inline void reset_total_force() + { + ft.reset(); + } + /// \brief Typical fluctuation amplitude for this collective /// variable (e.g. local width of a free energy basin) /// diff --git a/src/colvar_gpu_calc.cpp b/src/colvar_gpu_calc.cpp index 229c723d9..97d85e594 100644 --- a/src/colvar_gpu_calc.cpp +++ b/src/colvar_gpu_calc.cpp @@ -81,6 +81,9 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { // Calculate CVC total force if (!(*cvi)->is_enabled(colvardeps::f_cv_total_force_calc)) continue; + if (!total_force_valid) { + (*cvi)->reset_total_force(); + } const bool do_total_force = use_current_step ? (*cvi)->is_enabled(colvardeps::f_cv_total_force_current_step) : @@ -92,15 +95,8 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( const auto all_cvcs = (*cvi)->get_cvcs(); for (auto cvc = all_cvcs.begin(); cvc != all_cvcs.end(); ++cvc) { if (!(*cvc)->is_enabled(colvardeps::f_cvc_active)) continue; -// <<<<<<< HEAD - if (!(*cvc)->has_gpu_implementation()) { + if ((*cvc)->is_enabled(colvardeps::f_cvc_active) && total_force_valid) { (*cvc)->calc_force_invgrads(); - if (total_force_valid) { - (*cvc)->calc_force_invgrads(); - } else { - // TODO: (*cvc)->ft is a protected member. How could I do the reset? - } -// >>>>>>> ddd82771 (fix: skip the total force calc in the GPU code path if unavailable) } } if (colvar_module->debug()) { From 7d1d30db9dd9449769a5f871b51c6af5a5fef790 Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Thu, 11 Dec 2025 16:21:22 -0500 Subject: [PATCH 21/25] fix: Apply consistent check between total-force compute / collect steps --- src/colvar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/colvar.cpp b/src/colvar.cpp index c0e888b24..e28f26e43 100644 --- a/src/colvar.cpp +++ b/src/colvar.cpp @@ -1462,7 +1462,7 @@ int colvar::collect_cvc_data() int error_code = COLVARS_OK; - if ((cvm::step_relative() > 0) && (!is_enabled(f_cv_total_force_current_step))){ + if (cvm::main()->proxy->total_forces_valid() && (!is_enabled(f_cv_total_force_current_step))) { // Total force depends on Jacobian derivative from previous timestep // collect_cvc_total_forces() uses the previous value of jd error_code |= collect_cvc_total_forces(); From c92b32c139e3355bcbcffe18c31db49054ba437a Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Thu, 11 Dec 2025 16:38:48 -0500 Subject: [PATCH 22/25] refactor: When atomic total forces are invalid, ensure colvar total force is zero after collection --- src/colvar.cpp | 12 +++++------- src/colvar_gpu_calc.cpp | 21 ++++++++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/colvar.cpp b/src/colvar.cpp index e28f26e43..a307237b9 100644 --- a/src/colvar.cpp +++ b/src/colvar.cpp @@ -1407,10 +1407,6 @@ int colvar::calc() error_code |= update_cvc_flags(); if (error_code != COLVARS_OK) return error_code; error_code |= calc_cvcs(); - if (!cvm::main()->proxy->total_forces_valid()) { - // Zero out the colvar total force when atomic total forces are not available - reset_total_force(); - } if (error_code != COLVARS_OK) return error_code; error_code |= collect_cvc_data(); } @@ -1435,9 +1431,6 @@ int colvar::calc_cvcs(int first_cvc, size_t num_cvcs) // Use Jacobian derivative computed at previous timestep and the total forces from the same // step, collected just now from the engine error_code |= calc_cvc_total_force(first_cvc, num_cvcs); - } else { - // Zero out the colvar total force when atomic total forces are not available - reset_total_force(); } // atom coordinates are updated by the next line error_code |= calc_cvc_values(first_cvc, num_cvcs); @@ -1476,6 +1469,11 @@ int colvar::collect_cvc_data() } error_code |= calc_colvar_properties(); + if (!cvm::main()->proxy->total_forces_valid()) { + // Zero out the colvar total force when atomic total forces are not available + reset_total_force(); + } + if (cvm::debug()) cvm::log("Done calculating colvar \""+this->name+"\"'s properties.\n"); diff --git a/src/colvar_gpu_calc.cpp b/src/colvar_gpu_calc.cpp index 97d85e594..54e5790bc 100644 --- a/src/colvar_gpu_calc.cpp +++ b/src/colvar_gpu_calc.cpp @@ -24,12 +24,15 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( colvarmodule* colvar_module, bool use_current_step) { int error_code = COLVARS_OK; + const bool total_force_valid = colvar_module->proxy ? colvar_module->proxy->total_forces_valid() : false; + if (!total_force_valid) { + return error_code; + } colvarproxy* p = colvar_module->proxy; cudaStream_t stream = p->get_default_stream(); #if defined (COLVARS_NVTX_PROFILING) cvc_calc_total_force_prof.start(); #endif // defined (COLVARS_NVTX_PROFILING) - const bool total_force_valid = colvar_module->proxy ? colvar_module->proxy->total_forces_valid() : false; if (!g.graph_exec_initialized) { using node_map_t = std::unordered_map; for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { @@ -78,12 +81,12 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( error_code |= checkGPUError(cudaGraphLaunch(g.graph_exec, stream)); if (error_code != COLVARS_OK) return error_code; } +#if defined (COLVARS_NVTX_PROFILING) + cvc_calc_total_force_prof.start(); +#endif // defined (COLVARS_NVTX_PROFILING) for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { // Calculate CVC total force if (!(*cvi)->is_enabled(colvardeps::f_cv_total_force_calc)) continue; - if (!total_force_valid) { - (*cvi)->reset_total_force(); - } const bool do_total_force = use_current_step ? (*cvi)->is_enabled(colvardeps::f_cv_total_force_current_step) : @@ -94,8 +97,7 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( } const auto all_cvcs = (*cvi)->get_cvcs(); for (auto cvc = all_cvcs.begin(); cvc != all_cvcs.end(); ++cvc) { - if (!(*cvc)->is_enabled(colvardeps::f_cvc_active)) continue; - if ((*cvc)->is_enabled(colvardeps::f_cvc_active) && total_force_valid) { + if ((*cvc)->is_enabled(colvardeps::f_cvc_active)) { (*cvc)->calc_force_invgrads(); } } @@ -617,11 +619,16 @@ int colvarmodule_gpu_calc::cvc_calc_Jacobian_derivative( int colvarmodule_gpu_calc::cv_collect_cvc_data(const std::vector& colvars, colvarmodule* colvar_module) { int error_code = COLVARS_OK; + const bool total_force_valid = colvar_module->proxy ? colvar_module->proxy->total_forces_valid() : false; #if defined (COLVARS_NVTX_PROFILING) cv_collect_cvc_data_prof.start(); #endif // defined (COLVARS_NVTX_PROFILING) for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { - error_code |= (*cvi)->collect_cvc_data(); + if (total_force_valid) { + error_code |= (*cvi)->collect_cvc_data(); + } else { + (*cvi)->reset_total_force(); + } if (colvar_module->get_error()) { return COLVARS_ERROR; } From 480a613dc984fa10a48a59425e6a94ec1b61c27f Mon Sep 17 00:00:00 2001 From: Giacomo Fiorin Date: Fri, 12 Dec 2025 11:53:49 -0500 Subject: [PATCH 23/25] fix: Ensure that reported colvar total force is zeroed immediately --- src/colvar.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/colvar.h b/src/colvar.h index 6407f4c9f..8e049771f 100644 --- a/src/colvar.h +++ b/src/colvar.h @@ -86,6 +86,7 @@ class colvar : public colvarparse, public colvardeps { inline void reset_total_force() { ft.reset(); + ft_reported.reset(); } /// \brief Typical fluctuation amplitude for this collective From 1e3d5690f4cfe353ffe4b3126d7ee6a7c4bdc763 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Fri, 12 Dec 2025 11:24:20 -0600 Subject: [PATCH 24/25] fix: ensure that total forces are reset before calc_colvar_properties if invalid This commit simplifies the logic of resetting the total forces by just checking if they are available before accumulating them in collect_cvc_total_forces. This can ensure that ft_reported is not reset after calc_colvar_properties. This commit also simplifies the GPU code path by just reusing the check in collect_cvc_total_forces. --- src/colvar.cpp | 43 +++++++++++++++++++---------------------- src/colvar.h | 7 ------- src/colvar_gpu_calc.cpp | 9 +++------ 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/src/colvar.cpp b/src/colvar.cpp index a307237b9..90f288112 100644 --- a/src/colvar.cpp +++ b/src/colvar.cpp @@ -1455,7 +1455,7 @@ int colvar::collect_cvc_data() int error_code = COLVARS_OK; - if (cvm::main()->proxy->total_forces_valid() && (!is_enabled(f_cv_total_force_current_step))) { + if (!is_enabled(f_cv_total_force_current_step)) { // Total force depends on Jacobian derivative from previous timestep // collect_cvc_total_forces() uses the previous value of jd error_code |= collect_cvc_total_forces(); @@ -1469,11 +1469,6 @@ int colvar::collect_cvc_data() } error_code |= calc_colvar_properties(); - if (!cvm::main()->proxy->total_forces_valid()) { - // Zero out the colvar total force when atomic total forces are not available - reset_total_force(); - } - if (cvm::debug()) cvm::log("Done calculating colvar \""+this->name+"\"'s properties.\n"); @@ -1688,24 +1683,26 @@ int colvar::collect_cvc_total_forces() ft.reset(); - for (size_t i = 0; i < cvcs.size(); i++) { - if (!cvcs[i]->is_enabled()) continue; - if (cvm::debug()) - cvm::log("Colvar component no. "+cvm::to_str(i+1)+ - " within colvar \""+this->name+"\" has total force "+ - cvm::to_str((cvcs[i])->total_force(), - cvm::cv_width, cvm::cv_prec)+".\n"); - // linear combination is assumed - ft += (cvcs[i])->total_force() * (cvcs[i])->sup_coeff / active_cvc_square_norm; - } + if (cvm::main()->proxy->total_forces_valid()) { + for (size_t i = 0; i < cvcs.size(); i++) { + if (!cvcs[i]->is_enabled()) continue; + if (cvm::debug()) + cvm::log("Colvar component no. "+cvm::to_str(i+1)+ + " within colvar \""+this->name+"\" has total force "+ + cvm::to_str((cvcs[i])->total_force(), + cvm::cv_width, cvm::cv_prec)+".\n"); + // linear combination is assumed + ft += (cvcs[i])->total_force() * (cvcs[i])->sup_coeff / active_cvc_square_norm; + } - if (!(is_enabled(f_cv_hide_Jacobian) && is_enabled(f_cv_subtract_applied_force))) { - // This is by far the most common case - // Add the Jacobian force to the total force, and don't apply any silent - // correction internally: biases such as colvarbias_abf will handle it - // If f_cv_hide_Jacobian is enabled, a force of -fj is present in ft due to the - // Jacobian-compensating force - ft += fj; + if (!(is_enabled(f_cv_hide_Jacobian) && is_enabled(f_cv_subtract_applied_force))) { + // This is by far the most common case + // Add the Jacobian force to the total force, and don't apply any silent + // correction internally: biases such as colvarbias_abf will handle it + // If f_cv_hide_Jacobian is enabled, a force of -fj is present in ft due to the + // Jacobian-compensating force + ft += fj; + } } } diff --git a/src/colvar.h b/src/colvar.h index 8e049771f..a8ed2dedc 100644 --- a/src/colvar.h +++ b/src/colvar.h @@ -82,13 +82,6 @@ class colvar : public colvarparse, public colvardeps { /// subtracted. colvarvalue const & total_force() const; - /// Reset the total force to zero - inline void reset_total_force() - { - ft.reset(); - ft_reported.reset(); - } - /// \brief Typical fluctuation amplitude for this collective /// variable (e.g. local width of a free energy basin) /// diff --git a/src/colvar_gpu_calc.cpp b/src/colvar_gpu_calc.cpp index 54e5790bc..759af1a30 100644 --- a/src/colvar_gpu_calc.cpp +++ b/src/colvar_gpu_calc.cpp @@ -619,16 +619,13 @@ int colvarmodule_gpu_calc::cvc_calc_Jacobian_derivative( int colvarmodule_gpu_calc::cv_collect_cvc_data(const std::vector& colvars, colvarmodule* colvar_module) { int error_code = COLVARS_OK; - const bool total_force_valid = colvar_module->proxy ? colvar_module->proxy->total_forces_valid() : false; #if defined (COLVARS_NVTX_PROFILING) cv_collect_cvc_data_prof.start(); #endif // defined (COLVARS_NVTX_PROFILING) for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { - if (total_force_valid) { - error_code |= (*cvi)->collect_cvc_data(); - } else { - (*cvi)->reset_total_force(); - } + // If the total forces are not available, it will be reset in + // collect_cvc_total_forces anyway (called by collect_cvc_data) + error_code |= (*cvi)->collect_cvc_data(); if (colvar_module->get_error()) { return COLVARS_ERROR; } From 45ea12a47b7e24892998c9cee364faf559770e17 Mon Sep 17 00:00:00 2001 From: HanatoK Date: Fri, 12 Dec 2025 15:41:43 -0600 Subject: [PATCH 25/25] fix: fix the typos in previous rebase --- src/colvar_gpu_calc.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/colvar_gpu_calc.cpp b/src/colvar_gpu_calc.cpp index 759af1a30..c0a9bd82c 100644 --- a/src/colvar_gpu_calc.cpp +++ b/src/colvar_gpu_calc.cpp @@ -81,9 +81,6 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( error_code |= checkGPUError(cudaGraphLaunch(g.graph_exec, stream)); if (error_code != COLVARS_OK) return error_code; } -#if defined (COLVARS_NVTX_PROFILING) - cvc_calc_total_force_prof.start(); -#endif // defined (COLVARS_NVTX_PROFILING) for (auto cvi = colvars.begin(); cvi != colvars.end(); cvi++) { // Calculate CVC total force if (!(*cvi)->is_enabled(colvardeps::f_cv_total_force_calc)) continue; @@ -97,7 +94,8 @@ int colvarmodule_gpu_calc::cvc_calc_total_force( } const auto all_cvcs = (*cvi)->get_cvcs(); for (auto cvc = all_cvcs.begin(); cvc != all_cvcs.end(); ++cvc) { - if ((*cvc)->is_enabled(colvardeps::f_cvc_active)) { + if (!(*cvc)->is_enabled(colvardeps::f_cvc_active)) continue; + if (!(*cvc)->has_gpu_implementation()) { (*cvc)->calc_force_invgrads(); } }