44 {
45 if (!initial) {
46 throw std::runtime_error("ReplicaExchangeJob::runFromMatter: null Matter");
47 }
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
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
88
89 const bool perImage =
pot->needsPerImageInstance();
90 for (long i = 0; i < nReplicas; i++) {
92 replica[i] = std::make_shared<Matter>(replicaPot,
params);
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
142 const bool canParallel =
params.main_options().parallel &&
144
145 for (long step = 1; step <= samplingSteps; step++) {
146 if (canParallel && nReplicas > 1) {
147
148 std::vector<std::thread> threads;
149 threads.reserve(nReplicas);
150 for (long i = 0; i < nReplicas; i++) {
151 threads.emplace_back([&, i] {
152
153
154 const long userSeed =
params.main_options().randomSeed;
155 const long base = (userSeed > 0) ? userSeed : 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
170 if (nReplicas >= 2 && (step % exchangePeriodSteps) == 0) {
171 for (long trial = 0;
172 trial <
params.replica_exchange_options().exchange_trials; trial++) {
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) *
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
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
207 if (!replica.empty()) {
209 }
212}
static PotRegistry & get() noexcept
Process-lifetime singleton.
size_t total_force_calls() const noexcept
quill::Logger * get() noexcept
Get or create the default "combi" logger.
double random(long newSeed=0)
long randomInt(int lower, int upper)
constexpr double safe_recip(double x, double fallback=0.0)
bool potAllowsSharedInstance(const P &p) noexcept
static replica_exchange_options_t & replica_exchange_options(Parameters &p)