Loading...
Searching...
No Matches
ProcessSearchJob.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#ifdef WITH_ARTN
15#endif
19#include "eon/EpiCenters.h"
20#include "eon/HelperFunctions.h"
22#include "eon/Optimizer.h"
23#include "eon/Prefactor.h"
24#include <thread>
25
26#include <format>
27#include <fstream>
28#include <memory>
29#include <stdexcept>
30#include <string>
31
32#include "eon/EonLogger.h"
33
34std::vector<std::string> ProcessSearchJob::run() {
35 std::string reactantFilename("pos.con");
36 std::string displacementFilename("displacement.con");
37 std::string modeFilename("direction.dat");
38 size_t fctmp{0};
39 initial = std::make_shared<Matter>(pot, params);
40 if (params.saddle_search_options.method == "min_mode" ||
41 params.saddle_search_options.method == "basin_hopping" ||
42 params.saddle_search_options.method == "bgsd") {
43 displacement = std::make_shared<Matter>(pot, params);
44 } else if (params.saddle_search_options.method == "dynamics") {
45 displacement = nullptr;
46 }
47 saddle = std::make_shared<Matter>(pot, params);
48 // Give min2 its own potential for parallel endpoint minimization
49 auto min2Pot = (pot->needsPerImageInstance() && params.main_options.parallel)
51 : pot;
52 min1 = std::make_shared<Matter>(pot, params);
53 min2 = std::make_shared<Matter>(min2Pot, params);
54
55 if (!eonc::io::io_ok(initial->con2matter(reactantFilename))) {
56 EONC_LOG_CRITICAL("Failed to load {}", reactantFilename);
57 exit(1);
58 }
59
60 if (params.process_search_options.minimize_first) {
61 QUILL_LOG_DEBUG(log, "Minimizing initial structure\n");
62 fctmp = initial->getPotentialCalls();
63 initial->relax();
64 fCallsMin += initial->getPotentialCalls() - fctmp;
65 QUILL_LOG_DEBUG(log, "Initial minimization took {} fcalls",
66 initial->getPotentialCalls() - fctmp);
67 }
68
71
72 if (params.saddle_search_options.method == "min_mode" ||
73 params.saddle_search_options.method == "basin_hopping" ||
74 params.saddle_search_options.method == "bgsd") {
75 if (params.saddle_search_options.displace_type ==
77 // Load displacement.con, or synthesize from pos.con + direction.dat
78 // (#79).
80 *saddle, *initial, displacementFilename, modeFilename,
81 params.saddle_search_options.displace_magnitude)) {
82 EONC_LOG_CRITICAL("Failed to load {} (and no usable {})",
83 displacementFilename, modeFilename);
84 exit(1);
85 }
86 *min1 = *min2 = *initial;
87 } else {
88 *saddle = *min1 = *min2 = *initial;
89 }
90 } else {
91 // ARTn and dynamics start from the initial minimum
92 *saddle = *min1 = *min2 = *initial;
93 }
94
95 AtomMatrix mode;
96 const bool useARTnAsMinMode =
97 params.saddle_search_options.method == "min_mode" &&
98 params.saddle_search_options.minmode_method == "artn";
99
100 if (params.saddle_search_options.method == "min_mode") {
101 if (params.saddle_search_options.displace_type ==
103 mode = eonc::helpers::loadMode(modeFilename, initial->numberOfAtoms());
104 }
105#ifdef WITH_ARTN
106 // ARTn as a min-mode drop-in: eOn displaces, seeds the mode, ARTn
107 // takes over from the displaced structure.
108 if (useARTnAsMinMode) {
110 std::make_unique<ARTnSaddleSearch>(saddle, pot, mode, params);
111 } else
112#endif
113 {
114 saddleSearch = std::make_unique<MinModeSaddleSearch>(
115 saddle, mode, initial->getPotentialEnergy(), params, pot);
116 }
117#ifdef WITH_ARTN
118 } else if (params.saddle_search_options.method == "artn") {
119 // ARTn handles its own push from the minimum, eigenmode estimation,
120 // and perpendicular relaxation internally.
121 AtomMatrix artnMode = AtomMatrix::Zero(initial->numberOfAtoms(), 3);
122 if (params.saddle_search_options.displace_type ==
124 artnMode =
125 eonc::helpers::loadMode(modeFilename, initial->numberOfAtoms());
126 }
128 std::make_unique<ARTnSaddleSearch>(saddle, pot, artnMode, params);
129#endif
130 } else if (params.saddle_search_options.method == "basin_hopping") {
132 std::make_unique<BasinHoppingSaddleSearch>(min1, saddle, pot, params);
133 } else if (params.saddle_search_options.method == "dynamics") {
134 saddleSearch = std::make_unique<DynamicsSaddleSearch>(saddle, params);
135 } else if (params.saddle_search_options.method == "bgsd") {
136 saddleSearch = std::make_unique<BiasedGradientSquaredDescent>(
137 saddle, initial->getPotentialEnergy(), params);
138 }
139
140#ifndef WITH_ARTN
141 // Post-dispatch guard for both ARTn entry points so users without a
142 // WITH_ARTN build get a clean per-case error instead of a silent
143 // fall-through. Two distinct messages so downstream tooling and the
144 // integration tests can match on the specific entry point.
145 if (params.saddle_search_options.method == "artn") {
146 throw std::runtime_error(
147 "saddle_search.method=artn requires a build with ARTn support "
148 "(reconfigure with -Dwith_artn=true)");
149 }
150 if (useARTnAsMinMode) {
151 throw std::runtime_error(
152 "saddle_search.minmode_method=artn requires a build with ARTn "
153 "support (reconfigure with -Dwith_artn=true)");
154 }
155#endif
156
157 int status = doProcessSearch();
158
159 printEndState(status);
160 saveData(status);
161
162 return returnFiles;
163}
164
166 Matter matterTemp(pot, params);
167 long status;
168 size_t fctmp{0};
169
170 fctmp = pot->forceCallCounter;
171 status = saddleSearch->run();
172 if (params.saddle_search_options.method == "min_mode" &&
173 params.saddle_search_options.minmode_method ==
175 fCallsSaddle += saddleSearch->getForceCalls();
176 } else if (params.saddle_search_options.method == "artn") {
177 fCallsSaddle += saddleSearch->getForceCalls();
178 } else {
179 fCallsSaddle += pot->forceCallCounter - fctmp;
180 }
181 EONC_LOG_DEBUG("Got {} calls in the saddle search, with previous {}",
182 fCallsSaddle, fctmp);
183
184 if (status != MinModeSaddleSearch::STATUS_GOOD) {
185 return status;
186 }
187
188 AtomMatrix posSaddle = saddle->getPositions();
189 AtomMatrix displacedPos;
190
191 *min1 = *saddle;
192
193 displacedPos =
194 posSaddle - saddleSearch->getEigenvector() *
195 params.process_search_options.minimization_offset;
196 min1->setPositions(displacedPos);
197
198 *min2 = *saddle;
199 displacedPos =
200 posSaddle + saddleSearch->getEigenvector() *
201 params.process_search_options.minimization_offset;
202 min2->setPositions(displacedPos);
203
204 // Minimize both endpoints concurrently when the shared potential instance is
205 // safe to call from multiple threads, or when each endpoint owns a separate
206 // potential instance.
207 QUILL_LOG_DEBUG(log, "Starting Minimization 1 & 2");
208 bool converged1{false}, converged2{false};
209 long fc1_before = min1->getPotentialCalls();
210 long fc2_before = min2->getPotentialCalls();
211
212 bool canParallel =
213 pot->isSharedInstanceThreadSafe() || pot->needsPerImageInstance();
214 if (params.main_options.parallel && canParallel) {
215 std::thread t1([&] {
216 converged1 =
217 min1->relax(false, params.debug_options.write_movies, false, "min1");
218 });
219 converged2 =
220 min2->relax(false, params.debug_options.write_movies, false, "min2");
221 t1.join();
222 } else {
223 converged1 =
224 min1->relax(false, params.debug_options.write_movies, false, "min1");
225 converged2 =
226 min2->relax(false, params.debug_options.write_movies, false, "min2");
227 }
228
229 fCallsMin += (min1->getPotentialCalls() - fc1_before) +
230 (min2->getPotentialCalls() - fc2_before);
231 QUILL_LOG_DEBUG(log, "Min1: {} fcalls, Min2: {} fcalls",
232 min1->getPotentialCalls() - fc1_before,
233 min2->getPotentialCalls() - fc2_before);
234
235 if (!converged1 || !converged2) {
237 }
238
239 if (!(initial->compare(*min1)) && initial->compare(*min2)) {
240 matterTemp = *min1;
241 *min1 = *min2;
242 *min2 = matterTemp;
243 }
244
245 if (!initial->compare(*min1)) {
246 // Report how far off the endpoint landed. Whether the minimisation
247 // stopped just outside the state-identity tolerance or relaxed into a
248 // different state entirely calls for opposite fixes, and the status
249 // alone does not distinguish them.
250 const double tol = params.structure_comparison_options.distance_difference;
251 auto countMoved = [&](const Matter &m) {
252 long moved = 0;
253 for (long i = 0; i < initial->numberOfAtoms(); ++i) {
254 if (initial
255 ->pbc(initial->getPositions().row(i) - m.getPositions().row(i))
256 .norm() > tol) {
257 ++moved;
258 }
259 }
260 return moved;
261 };
262 QUILL_LOG_INFO(log,
263 "initial != min1: {} of {} atoms past the {} A tolerance "
264 "for min1 ({} for min2); largest separation {} A",
265 countMoved(*min1), initial->numberOfAtoms(), tol,
266 countMoved(*min2), initial->perAtomNorm(*min1));
268 }
269
270 if (initial->compare(*min2)) {
271 QUILL_LOG_DEBUG(log, "both minima are the initial state");
273 }
274
275 if (!params.process_search_options.minimize_first) {
276 min1 = initial;
277 }
278
279 barriersValues[0] = saddle->getPotentialEnergy() - min1->getPotentialEnergy();
280 barriersValues[1] = saddle->getPotentialEnergy() - min2->getPotentialEnergy();
281
282 if ((params.saddle_search_options.max_energy < barriersValues[0]) ||
283 (params.saddle_search_options.max_energy < barriersValues[1])) {
285 }
286
287 if (barriersValues[0] < 0.0 || barriersValues[1] < 0.0) {
289 }
290
291 if (!params.prefactor_options.default_value) {
292 fctmp = min1->getPotentialCalls();
293 int prefStatus;
294 double pref1, pref2;
296 params, min1.get(), saddle.get(), min2.get(), pref1, pref2);
297 if (prefStatus == -1) {
298 EONC_LOG_ERROR("Prefactor: bad calculation");
300 }
301 fCallsPrefactors += min1->getPotentialCalls() - fctmp;
302
303 if ((pref1 > params.prefactor_options.max_value) ||
304 (pref1 < params.prefactor_options.min_value)) {
305 EONC_LOG_ERROR("Bad reactant-to-saddle prefactor: {}", pref1);
307 }
308 if ((pref2 > params.prefactor_options.max_value) ||
309 (pref2 < params.prefactor_options.min_value)) {
310 EONC_LOG_ERROR("Bad product-to-saddle prefactor: {}", pref2);
312 }
313 prefactorsValues[0] = pref1;
314 prefactorsValues[1] = pref2;
315
316 } else {
317 prefactorsValues[0] = params.prefactor_options.default_value;
318 prefactorsValues[1] = params.prefactor_options.default_value;
319 }
321}
322
324 std::string resultsFilename("results.dat");
325 returnFiles.push_back(resultsFilename);
326
327 std::ofstream out(resultsFilename, std::ios::binary);
328 if (out) {
329 out << std::format("{} termination_reason\n", status);
330 out << std::format("{} termination_reason_text\n",
331 saddleSearch->describeStatus(status));
332 out << std::format("{} random_seed\n", params.main_options.randomSeed);
333 out << std::format(
334 "{} potential_type\n",
335 magic_enum::enum_name<PotType>(params.potential_options.potential));
336 out << std::format("{} total_force_calls\n",
338 out << std::format("{} force_calls_minimization\n", fCallsMin);
339 out << std::format("{} force_calls_saddle\n", fCallsSaddle);
340 out << std::format("{:.12e} potential_energy_saddle\n",
341 saddle->getPotentialEnergy());
342 out << std::format("{:.12e} potential_energy_reactant\n",
343 min1->getPotentialEnergy());
344 out << std::format("{:.12e} potential_energy_product\n",
345 min2->getPotentialEnergy());
346 out << std::format("{:.12e} barrier_reactant_to_product\n",
347 barriersValues[0]);
348 out << std::format("{:.12e} barrier_product_to_reactant\n",
349 barriersValues[1]);
350 if (params.saddle_search_options.method == "min_mode") {
351 out << std::format("{:.12e} displacement_saddle_distance\n",
352 displacement->perAtomNorm(*saddle));
353 } else {
354 out << std::format("{:.12e} displacement_saddle_distance\n", 0.0);
355 }
356 if (params.saddle_search_options.method == "dynamics") {
357 auto ds = dynamic_cast<DynamicsSaddleSearch &>(*saddleSearch);
358 out << std::format("{:.12e} simulation_time\n",
359 ds.time * params.constants.timeUnit);
360 out << std::format("{:.12e} md_temperature\n",
361 params.saddle_search_options.dynamics.temperature);
362 }
363 out << std::format("{} force_calls_prefactors\n", fCallsPrefactors);
364 out << std::format("{:.12e} prefactor_reactant_to_product\n",
366 out << std::format("{:.12e} prefactor_product_to_reactant\n",
368 }
369
370 std::string reactantFilename("reactant.con");
371 returnFiles.push_back(reactantFilename);
372 if (!eonc::io::io_ok(min1->matter2con(reactantFilename))) {
373 QUILL_LOG_ERROR(log, "Failed to write {}", reactantFilename);
374 }
375
376 std::string modeFilename("mode.dat");
377 returnFiles.push_back(modeFilename);
378 eonc::helpers::saveMode(modeFilename, saddle, saddleSearch->getEigenvector());
379
380 std::string saddleFilename("saddle.con");
381 returnFiles.push_back(saddleFilename);
382 if (!eonc::io::io_ok(saddle->matter2con(saddleFilename))) {
383 QUILL_LOG_ERROR(log, "Failed to write {}", saddleFilename);
384 }
385
386 std::string productFilename("product.con");
387 returnFiles.push_back(productFilename);
388 if (!eonc::io::io_ok(min2->matter2con(productFilename))) {
389 QUILL_LOG_ERROR(log, "Failed to write {}", productFilename);
390 }
391}
392
394 auto msg = saddleSearch->describeStatus(status);
395 if (status == MinModeSaddleSearch::STATUS_GOOD) {
396 QUILL_LOG_DEBUG(log, "[Saddle Search] {}", msg);
397 } else {
398 QUILL_LOG_ERROR(log, "[Saddle Search] {}", msg);
399 }
400}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_DEBUG(...)
Definition EonLogger.h:244
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:262
#define EONC_LOG_CRITICAL(...)
Definition EonLogger.h:268
The optimizer class is used to serve as an abstract class for all optimizers, as well as to call an o...
Finds possible escape mecahnisms from a state.
std::vector< std::string > run(void) override
Kicks off the Process Search.
std::shared_ptr< Potential > pot
Definition Job.h:55
Parameters params
Definition Job.h:54
static const char MINMODE_GPRDIMER[]
std::shared_ptr< Matter > displacement
Configuration used during the saddle point search.
size_t fCallsPrefactors
Force calls to find the prefactors.
eonc::log::Scoped log
size_t fCallsMin
Force calls to minimize.
std::shared_ptr< Matter > min1
First minimum from the saddle.
std::vector< std::string > returnFiles
Container for the results of the run.
std::shared_ptr< Matter > min2
Second minimum from the saddle.
std::shared_ptr< Matter > saddle
Configuration used during the saddle point search.
int doProcessSearch(void)
Runs the correct saddle search; also checks if the run was successful.
std::shared_ptr< Matter > initial
Initial configuration.
std::unique_ptr< SaddleSearchMethod > saddleSearch
Pulled from parameters.
size_t fCallsSaddle
Force calls to find the saddle.
void saveData(int status)
Writes the results from the run to file.
void printEndState(int status)
Logs the run status and makes sure the run was successful.
const char DISP_LOAD[]
Definition EpiCenters.h:20
int getPrefactors(const Parameters &parameters, Matter *min1, Matter *saddle, Matter *min2, double &pref1, double &pref2)
Definition Prefactor.cpp:22
bool loadOrSynthesizeDisplacement(Matter &target, const Matter &initial, const std::string &displacementPath, const std::string &modePath, double scale)
AtomMatrix loadMode(FILE *modeFile, int nAtoms)
void saveMode(FILE *modeFile, std::shared_ptr< Matter > matter, AtomMatrix mode)
Write a mode; constrained axes are emitted as 0.
std::shared_ptr< Potential > makePotential(const Parameters &params)
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38