Loading...
Searching...
No Matches
ReplicaExchangeJob.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/Dynamics.h"
15#include "eon/HelperFunctions.h"
16#include "eon/Matter.h"
17#include "eon/PotCapabilities.h"
18#include "eon/RandomNumbers.h"
19#include "eon/SafeMath.h"
20
21#include <algorithm>
22#include <cmath>
23#include <format>
24#include <fstream>
25#include <stdexcept>
26#include <string>
27#include <thread>
28
29namespace eonc {
30
31std::vector<std::string> ReplicaExchangeJob::run() {
32 std::string posFilename =
33 eonc::helpers::getRelevantFile(params.main_options().conFilename);
34 pos = std::make_shared<Matter>(pot, params);
35 if (!eonc::io::io_ok(pos->con2matter(posFilename))) {
36 QUILL_LOG_CRITICAL(log, "Failed to load {}", posFilename);
37 throw std::runtime_error("failed to load " + posFilename);
38 }
39 (void)runFromMatter(pos);
40 return returnFiles;
41}
42
43std::shared_ptr<Matter>
44ReplicaExchangeJob::runFromMatter(std::shared_ptr<Matter> initial) {
45 if (!initial) {
46 throw std::runtime_error("ReplicaExchangeJob::runFromMatter: null Matter");
47 }
48 pos = initial;
49 pos->setPotential(pot);
50
52 if (rex.replicas < 1) {
53 throw std::invalid_argument(
54 "ReplicaExchangeJob: replica_exchange.replicas must be >= 1");
55 }
56 if (rex.temperature_low <= 0.0) {
57 rex.temperature_low = params.main_options().temperature > 0.0
58 ? params.main_options().temperature
59 : 300.0;
60 }
61 if (rex.temperature_high <= rex.temperature_low) {
62 rex.temperature_high = rex.temperature_low * 1.5;
63 }
64
65 long samplingSteps =
66 static_cast<long>(params.replica_exchange_options().sampling_time /
67 params.dynamics_options().time_step +
68 0.5);
69 long exchangePeriodSteps =
70 static_cast<long>(params.replica_exchange_options().exchange_period /
71 params.dynamics_options().time_step +
72 0.5);
73 const double kB = params.constants().kB;
74 if (samplingSteps <= 0)
75 samplingSteps = 1;
76 if (exchangePeriodSteps <= 0)
77 exchangePeriodSteps = 1;
78
79 QUILL_LOG_DEBUG(log, "Running Replica Exchange");
80
81 long refForceCalls = PotRegistry::get().total_force_calls();
82
83 const long nReplicas = params.replica_exchange_options().replicas;
84 std::vector<std::shared_ptr<Matter>> replica(nReplicas);
85 std::vector<std::unique_ptr<Dynamics>> replicaDynamics(nReplicas);
86
87 // Per-image potentials: each replica gets its own potential instance when
88 // the potential requires it (e.g. ML potentials with internal state)
89 const bool perImage = pot->needsPerImageInstance();
90 for (long i = 0; i < nReplicas; i++) {
91 auto replicaPot = perImage ? eonc::helpers::makePotential(params) : pot;
92 replica[i] = std::make_shared<Matter>(replicaPot, params);
93 *replica[i] = *pos;
94 replica[i]->setPotential(replicaPot);
95 replicaDynamics[i] = std::make_unique<Dynamics>(replica[i].get(), params);
96 }
97
98 std::vector<double> replicaTemperature(nReplicas);
99
100 QUILL_LOG_DEBUG(log, "Temperature distribution:");
101 if (nReplicas < 2) {
102 replicaTemperature[0] = params.replica_exchange_options().temperature_low;
103 replicaDynamics[0]->setTemperature(replicaTemperature[0]);
104 replicaDynamics[0]->setThermalVelocity();
105 } else if (params.replica_exchange_options().temperature_distribution ==
106 "linear") {
107 for (long i = 0; i < nReplicas; i++) {
108 replicaTemperature[i] =
109 params.replica_exchange_options().temperature_low +
110 static_cast<double>(i) / static_cast<double>(nReplicas - 1) *
111 (params.replica_exchange_options().temperature_high -
112 params.replica_exchange_options().temperature_low);
113 replicaDynamics[i]->setTemperature(replicaTemperature[i]);
114 replicaDynamics[i]->setThermalVelocity();
115 }
116 } else if (params.replica_exchange_options().temperature_distribution ==
117 "exponential") {
118 double kTemp = std::log(params.replica_exchange_options().temperature_high /
119 params.replica_exchange_options().temperature_low) /
120 static_cast<double>(nReplicas - 1);
121 for (long i = 0; i < nReplicas; i++) {
122 replicaTemperature[i] =
123 params.replica_exchange_options().temperature_low *
124 std::exp(kTemp * static_cast<double>(i));
125 replicaDynamics[i]->setTemperature(replicaTemperature[i]);
126 replicaDynamics[i]->setThermalVelocity();
127 QUILL_LOG_DEBUG(log, "replica: {} temperature {:.0f}", i + 1,
128 replicaTemperature[i]);
129 }
130 } else {
131 throw std::invalid_argument(
132 "replica_exchange.temperature_distribution must be linear or "
133 "exponential");
134 }
135
136 QUILL_LOG_DEBUG(
137 log, "Replica Exchange sampling for {:.0f} fs; {} steps; {} replicas.",
138 params.replica_exchange_options().sampling_time * 10.18, samplingSteps,
139 params.replica_exchange_options().replicas);
140
141 // Parallel replica dynamics when enabled and potential supports it
142 const bool canParallel = params.main_options().parallel &&
143 (eonc::potAllowsSharedInstance(*pot) || perImage);
144
145 for (long step = 1; step <= samplingSteps; step++) {
146 if (canParallel && nReplicas > 1) {
147 // Parallel: each replica runs its MD step in a separate thread
148 std::vector<std::thread> threads;
149 threads.reserve(nReplicas);
150 for (long i = 0; i < nReplicas; i++) {
151 threads.emplace_back([&, i] {
152 // New std::thread, new TLS ran2. Split the stream by replica and
153 // step so Langevin/Andersen noise is not identical across replicas.
154 const long userSeed = params.main_options().randomSeed;
155 const long base = (userSeed > 0) ? userSeed : 1;
156 eonc::rng::random(base + (step + 1) * 10007 + (i + 1));
157 replicaDynamics[i]->oneStep();
158 });
159 }
160 for (auto &t : threads) {
161 t.join();
162 }
163 } else {
164 for (long i = 0; i < nReplicas; i++) {
165 replicaDynamics[i]->oneStep();
166 }
167 }
168
169 // Metropolis replica exchange
170 if (nReplicas >= 2 && (step % exchangePeriodSteps) == 0) {
171 for (long trial = 0;
172 trial < params.replica_exchange_options().exchange_trials; trial++) {
173 long i = eonc::rng::randomInt(0, nReplicas - 2);
174 double energyLow = replica[i]->getPotentialEnergy();
175 double energyHigh = replica[i + 1]->getPotentialEnergy();
176 double kbTLow = kB * replicaTemperature[i];
177 double kbTHigh = kB * replicaTemperature[i + 1];
178 double pAcc =
179 std::min(1.0, std::exp((energyHigh - energyLow) *
180 (eonc::safemath::safe_recip(kbTHigh, 0.0) -
181 eonc::safemath::safe_recip(kbTLow, 0.0))));
182 double rnd = eonc::rng::randomDouble();
183 QUILL_LOG_INFO(log,
184 "step: {} trial swap, i {}, elow: {:.5f}, ehigh: "
185 "{:.5f}, pAcc: {:.5f}, rand: {}",
186 step, i, energyLow, energyHigh, pAcc, rnd);
187 if (rnd < pAcc) {
188 QUILL_LOG_INFO(log, "swap");
189 // Swap configurations, not pointers. Dynamics stays on its T slot.
190 auto potLow = replica[i]->getPotential();
191 auto potHigh = replica[i + 1]->getPotential();
192 Matter tmp = *replica[i];
193 *replica[i] = *replica[i + 1];
194 *replica[i + 1] = tmp;
195 replica[i]->setPotential(potLow);
196 replica[i + 1]->setPotential(potHigh);
197 replicaDynamics[i]->setThermalVelocity();
198 replicaDynamics[i + 1]->setThermalVelocity();
199 } else {
200 QUILL_LOG_INFO(log, "no swap");
201 }
202 }
203 }
204 }
205
206 forceCalls = PotRegistry::get().total_force_calls() - refForceCalls;
207 if (!replica.empty()) {
208 *pos = *replica[0];
209 }
210 saveData();
211 return pos;
212}
213
215 std::string resultsFilename("results.dat");
216 returnFiles.push_back(resultsFilename);
217
218 std::ofstream out(resultsFilename, std::ios::binary);
219 if (out) {
220 out << std::format("{} termination_reason\n", 0);
221 out << "GOOD termination_reason_text\n";
222 out << "replica_exchange job_type\n";
223 out << std::format("{} random_seed\n", params.main_options().randomSeed);
224 out << std::format(
225 "{} potential_type\n",
226 magic_enum::enum_name<PotType>(params.potential_options().potential));
227 out << std::format("{} force_calls_sampling\n", forceCalls);
228 }
229
230 std::string posFilename("pos_out.con");
231 returnFiles.push_back(posFilename);
232 if (pos) {
233 if (!eonc::io::io_ok(pos->matter2con(posFilename))) {
234 QUILL_LOG_ERROR(log, "Failed to write {}", posFilename);
235 }
236 }
237}
238
239} // namespace eonc
std::shared_ptr< Potential > pot
Definition Job.h:63
Parameters params
Definition Job.h:58
static PotRegistry & get() noexcept
Process-lifetime singleton.
size_t total_force_calls() const noexcept
std::vector< std::string > returnFiles
std::vector< std::string > run(void) override
Virtual run; used solely for dynamic dispatch.
std::shared_ptr< Matter > pos
std::shared_ptr< Matter > runFromMatter(std::shared_ptr< Matter > initial)
Matter-first; returns replica-0 Matter after sampling.
std::string getRelevantFile(std::string filename)
std::shared_ptr< Potential > makePotential(const Parameters &params)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
double random(long newSeed=0)
long randomInt(int lower, int upper)
double randomDouble()
constexpr double safe_recip(double x, double fallback=0.0)
Definition SafeMath.h:29
RAII resource manager for the ARTn C library with global synchronization.
bool potAllowsSharedInstance(const P &p) noexcept
static replica_exchange_options_t & replica_exchange_options(Parameters &p)