Loading...
Searching...
No Matches
ParallelReplicaJob.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
13#include "eon/BaseStructures.h"
14#include "eon/BondBoost.h"
15#include "eon/Dynamics.h"
16#include "eon/ForceCallTimer.h"
17#include "eon/HelperFunctions.h"
18#include "eon/Matter.h"
19#include <stdexcept>
20
21#include <cmath>
22#include <format>
23#include <fstream>
24#include <memory>
25
26namespace eonc {
27
28std::vector<std::string> ParallelReplicaJob::run() {
29 reactant = std::make_shared<Matter>(pot, params);
30 {
31 const auto posIn =
32 eonc::helpers::getRelevantFile(params.main_options().conFilename);
33 if (!eonc::io::io_ok(reactant->con2matter(posIn))) {
34 QUILL_LOG_CRITICAL(log, "Failed to load {}", posIn);
35 throw std::runtime_error("failed to load " + posIn);
36 }
37 }
39 return returnFiles;
40}
41
42std::shared_ptr<Matter>
43ParallelReplicaJob::runFromMatter(std::shared_ptr<Matter> initial) {
44 if (!initial) {
45 throw std::runtime_error("ParallelReplicaJob::runFromMatter: null Matter");
46 }
47 reactant = initial;
48 reactant->setPotential(pot);
49
50 QUILL_LOG_DEBUG(log, "[ParallelReplica] Minimizing initial position");
51 reactant->relax();
52 if (!eonc::io::io_ok(reactant->matter2con("reactant.con"))) {
53 QUILL_LOG_ERROR(log, "Failed to write reactant.con");
54 }
55
56 auto trajectory = std::make_shared<Matter>(pot, params);
57 *trajectory = *reactant;
58 Dynamics dynamics(trajectory.get(), params);
59 BondBoost bondBoost(trajectory.get(), params);
60
61 BondBoost *bias = nullptr;
62 if (params.hyperdynamics_options().bias_potential ==
64 bondBoost.initialize();
65 trajectory->setBiasPotential(&bondBoost);
66 bias = &bondBoost;
67 }
68
69 dephase(*trajectory, bias);
70
71 int stateCheckInterval = static_cast<int>(
72 std::floor(params.parallel_replica_options().state_check_interval /
73 params.dynamics_options().time_step +
74 0.5));
75 int recordInterval = static_cast<int>(
76 std::floor(params.parallel_replica_options().record_interval /
77 params.dynamics_options().time_step +
78 0.5));
79 if (stateCheckInterval < 1)
80 stateCheckInterval = 1;
81 if (recordInterval < 1)
82 recordInterval = 1;
83
84 std::vector<std::shared_ptr<Matter>> mdSnapshots;
85 std::vector<double> mdTimes;
86 double transitionTime = 0;
87 Matter transitionStructure(pot, params);
88 size_t refineForceCalls = 0;
89
90 double simulationTime = 0.0;
91 if (params.hyperdynamics_options().bias_potential == Hyperdynamics::NONE) {
92 QUILL_LOG_DEBUG(
93 log, "[ParallelReplica] {:>8} {:>12} {:>10} {:>12} {:>12} {:>10}",
94 "Step", "Time (s)", "KE", "PE", "TE", "KinT");
95 } else {
96 QUILL_LOG_DEBUG(
97 log,
98 "[ParallelReplica] {:>8} {:>12} {:>10} {:>10} {:>12} {:>12} {:>10}",
99 "Step", "Time (s)", "Boost", "KE", "PE", "TE", "KinT");
100 }
101
102 for (int step = 1; step <= params.dynamics_options().steps; step++) {
103 if (params.hyperdynamics_options().bias_potential ==
105 // oneStep() evaluates accelerations (and therefore boost()) more than
106 // once. Advance the equilibration counter here, once per MD step, so
107 // ParallelReplica and SafeHyper share the same rmd_time schedule.
108 bondBoost.advance();
109 }
110 dynamics.oneStep();
111 double boost = 1.0;
112 if (params.hyperdynamics_options().bias_potential ==
114 double boostPotential = bondBoost.boost();
115 double kB = params.constants().kB;
116 boost = std::exp(boostPotential / kB / params.main_options().temperature);
117 simulationTime += params.dynamics_options().time_step * boost;
118 } else {
119 simulationTime += params.dynamics_options().time_step;
120 }
121
122 double kinE = trajectory->getKineticEnergy();
123 double potE = trajectory->getPotentialEnergy();
124 double kinT = (2.0 * kinE / (trajectory->numberOfFreeAtoms() * 3) /
125 params.constants().kB);
126
127 if (step % params.debug_options().write_movies_interval == 0) {
128 if (params.hyperdynamics_options().bias_potential ==
130 QUILL_LOG_DEBUG(log,
131 "[ParallelReplica] {:>8} {:>12.4e} {:>10.4f} "
132 "{:>12.4f} {:>12.4f} {:>10.2f}",
133 step,
134 simulationTime * params.constants().timeUnit * 1e-15,
135 kinE, potE, kinE + potE, kinT);
136 } else {
137 double boostPotential = bondBoost.boost();
138 QUILL_LOG_DEBUG(
139 log,
140 "[ParallelReplica] {:>8} {:>12.4e} {:>10.3e} "
141 "{:>10.4f} {:>12.4f} {:>12.4f} {:>10.2f}",
142 step, simulationTime * params.constants().timeUnit * 1e-15, boost,
143 kinE, potE + boostPotential, kinE + potE + boostPotential, kinT);
144 }
145 }
146
147 // Snapshots for refinement
148 if (step % recordInterval == 0 &&
149 params.parallel_replica_options().refine_transition) {
150 auto snap = std::make_shared<Matter>(pot, params);
151 *snap = *trajectory;
152 mdSnapshots.push_back(std::move(snap));
153 mdTimes.push_back(simulationTime);
154 }
155
156 // Check for transition
157 if (step % stateCheckInterval == 0 ||
158 step == params.dynamics_options().steps) {
159 QUILL_LOG_DEBUG(log, "[ParallelReplica] Checking for transition");
160
161 Matter minimized(pot, params);
162 minimized = *trajectory;
163 minimized.relax();
164
165 if (!minimized.compare(*reactant) && transitionTime == 0) {
166 QUILL_LOG_DEBUG(log, "[ParallelReplica] Transition occurred");
167
168 if (params.parallel_replica_options().refine_transition &&
169 !mdSnapshots.empty() && !mdTimes.empty()) {
170 QUILL_LOG_DEBUG(log, "[ParallelReplica] Refining transition time");
171 int snapshotIndex;
172 {
173 eonc::ForceCallTimer timer(refineForceCalls);
174 snapshotIndex = refineTransition(mdSnapshots);
175 }
176 if (snapshotIndex < 0)
177 snapshotIndex = 0;
178 if (snapshotIndex >= static_cast<int>(mdSnapshots.size()))
179 snapshotIndex = static_cast<int>(mdSnapshots.size()) - 1;
180
181 transitionTime = mdTimes[static_cast<size_t>(snapshotIndex)];
182 transitionStructure =
183 *mdSnapshots[static_cast<size_t>(snapshotIndex)];
184 } else {
185 transitionStructure = *trajectory;
186 transitionTime = simulationTime;
187 }
188 QUILL_LOG_DEBUG(log, "[ParallelReplica] Transition time: {:.3e} s",
189 transitionTime * params.constants().timeUnit * 1e-15);
190 // A run that keeps going after the transition keeps the bond boost
191 // it started with; plain copy assignment would clear it.
192 trajectory->assignKeepingBias(transitionStructure);
193 // A false stop_after_transition keeps the remaining dynamics steps.
194 if (params.parallel_replica_options().auto_stop) {
195 break;
196 }
197
198 } else if (step + 1 == params.dynamics_options().steps &&
199 transitionTime == 0) {
200 // Fake refinement to prevent force-call bias (only if snapshots exist)
201 if (params.parallel_replica_options().refine_transition &&
202 !mdSnapshots.empty()) {
203 QUILL_LOG_DEBUG(
204 log,
205 "[ParallelReplica] Simulation ended without seeing a transition");
206 QUILL_LOG_DEBUG(
207 log, "[ParallelReplica] Refining anyways to prevent bias...");
208 {
209 eonc::ForceCallTimer timer(refineForceCalls);
210 refineTransition(mdSnapshots, true);
211 }
212 }
213 transitionStructure = *trajectory;
214 }
215
216 mdSnapshots.clear();
217 mdTimes.clear();
218 }
219 }
220
221 std::unique_ptr<Matter> product;
222 if (transitionTime != 0) {
223 int decorrelationSteps = static_cast<int>(
224 std::floor(params.parallel_replica_options().corr_time /
225 params.dynamics_options().time_step +
226 0.5));
227 if (decorrelationSteps < 0) {
228 decorrelationSteps = 0;
229 }
230 QUILL_LOG_DEBUG(log, "[ParallelReplica] Decorrelating: {} steps",
231 decorrelationSteps);
232 for (int dstep = 1; dstep <= decorrelationSteps; ++dstep) {
233 dynamics.oneStep(dstep);
234 }
235 product = std::make_unique<Matter>(pot, params);
236 *product = *trajectory;
237 product->relax();
238 if (!eonc::io::io_ok(product->matter2con("product.con"))) {
239 QUILL_LOG_ERROR(log, "Failed to write product.con");
240 }
241 }
242
243 // Write results
244 std::string resultsFilename("results.dat");
245 returnFiles.push_back(resultsFilename);
246 {
247 std::ofstream out(resultsFilename, std::ios::binary);
248 if (out) {
249 out << std::format(
250 "{} potential_type\n",
251 magic_enum::enum_name<PotType>(params.potential_options().potential));
252 out << std::format("{} random_seed\n", params.main_options().randomSeed);
253 out << std::format("{:f} potential_energy_reactant\n",
254 reactant->getPotentialEnergy());
255 out << std::format("{} force_calls_refine\n", refineForceCalls);
256 out << std::format("{} total_force_calls\n",
257 PotRegistry::get().total_force_calls());
258
259 if (transitionTime == 0) {
260 out << "0 transition_found\n";
261 out << std::format("{:e} simulation_time_s\n",
262 simulationTime * params.constants().timeUnit *
263 1.0e-15);
264 } else {
265 out << "1 transition_found\n";
266 out << std::format("{:e} transition_time_s\n",
267 transitionTime * params.constants().timeUnit *
268 1.0e-15);
269 out << std::format("{:e} correlation_time_s\n",
270 params.parallel_replica_options().corr_time *
271 params.constants().timeUnit * 1.0e-15);
272 out << std::format("{:f} potential_energy_product\n",
273 product->getPotentialEnergy());
274 }
275 out << std::format("{:f} speedup\n",
276 simulationTime /
277 (params.dynamics_options().steps *
278 params.dynamics_options().time_step));
279 }
280 }
281
282 // bondBoost is a stack local of this function and setBiasPotential does not
283 // own it, so the trajectory must not carry the pointer past the return.
284 // getBiasForces reads a null bias potential as zero bias, which is what a
285 // Matter that is no longer being boosted means.
286 trajectory->setBiasPotential(nullptr);
287
288 return trajectory;
289}
290
292 Dynamics dynamics(&trajectory, params);
293 // The boost object names this Matter. Attach it here, and reset the
294 // trajectory with assignKeepingBias so each dephase loop keeps it.
295 trajectory.setBiasPotential(bias);
296
297 const double dt = params.dynamics_options().time_step;
298 if (!(dt > 0.0)) {
299 throw std::invalid_argument(
300 "ParallelReplicaJob::dephase: time_step must be positive");
301 }
302 int dephaseSteps = static_cast<int>(
303 std::floor(params.parallel_replica_options().dephase_time / dt + 0.5));
304 if (dephaseSteps < 1)
305 dephaseSteps = 1;
306 const long maxLoops =
307 std::max(1L, params.parallel_replica_options().dephase_loop_max);
308 QUILL_LOG_DEBUG(log, "[ParallelReplica] Dephasing: {} steps (max {} loops)",
309 dephaseSteps, maxLoops);
310
311 Matter initial(pot, params);
312 initial = trajectory;
313
314 for (long loop = 0; loop < maxLoops; ++loop) {
315 trajectory.assignKeepingBias(initial);
316 dynamics.setThermalVelocity();
317
318 for (int step = 1; step <= dephaseSteps; step++) {
319 dynamics.oneStep(step);
320 }
321
322 Matter minimized(pot, params);
323 minimized = trajectory;
324 minimized.relax();
325
326 if (minimized.compare(*reactant)) {
327 QUILL_LOG_DEBUG(log, "[ParallelReplica] Dephasing successful");
328 return;
329 }
330 QUILL_LOG_DEBUG(
331 log,
332 "[ParallelReplica] Transition occured during dephasing; Restarting");
333 }
334 // Exhausted retries: keep last dephased trajectory rather than hang.
335 QUILL_LOG_DEBUG(log,
336 "[ParallelReplica] Dephase loop max reached; continuing");
337}
338
340 const std::vector<std::shared_ptr<Matter>> &snapshots, bool fake) {
341 int lo = 0;
342 int hi = static_cast<int>(snapshots.size()) - 1;
343
344 while ((hi - lo) > 1) {
345 int mid = lo + (hi - lo) / 2;
346 Matter snapshot(*snapshots[mid]);
347 snapshot.relax(true);
348
349 bool stillReactant;
350 if (!fake) {
351 stillReactant = snapshot.compare(*reactant);
352 } else {
353 stillReactant = static_cast<bool>(eonc::rng::randomInt(0, 1));
354 }
355
356 if (stillReactant) {
357 lo = mid;
358 } else {
359 hi = mid;
360 }
361 }
362
363 return (lo + hi) / 2 + 1;
364}
365
366} // namespace eonc
Functionality relying on the conjugate gradients algorithm.
Definition BondBoost.h:25
double boost()
Evaluate the current bias potential and write bias forces.
void advance()
Advance the equilibration / boost schedule by one MD step.
void setThermalVelocity()
Definition Dynamics.cpp:247
void oneStep(int stepNumber=-1)
Definition Dynamics.cpp:57
RAII wrapper for tracking force calls over a scope.
static const char BOND_BOOST[]
Definition BondBoost.h:75
static const char NONE[]
Definition BondBoost.h:74
std::shared_ptr< Potential > pot
Definition Job.h:63
Parameters params
Definition Job.h:58
void setBiasPotential(BondBoost *bondBoost)
Definition Matter.cpp:394
bool relax(bool quiet=false, bool writeMovie=false, bool checkpoint=false, std::string prefixMovie=std::string(), std::string prefixCheckpoint=std::string(), bool retainMovieFrames=false)
Definition Matter.cpp:334
bool compare(const Matter &matter, bool indistinguishable=false)
Definition Matter.cpp:184
void assignKeepingBias(const Matter &other)
Copy another structure in and keep this Matter's own bias potential.
Definition Matter.cpp:400
std::vector< std::string > returnFiles
std::vector< std::string > run() override
Virtual run; used solely for dynamic dispatch.
std::shared_ptr< Matter > reactant
void dephase(Matter &trajectory, BondBoost *bias)
std::shared_ptr< Matter > runFromMatter(std::shared_ptr< Matter > initial)
Matter-first entry; returns final trajectory Matter.
int refineTransition(const std::vector< std::shared_ptr< Matter > > &snapshots, bool fake=false)
static PotRegistry & get() noexcept
Process-lifetime singleton.
std::string getRelevantFile(std::string filename)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
long randomInt(int lower, int upper)
RAII resource manager for the ARTn C library with global synchronization.