Loading...
Searching...
No Matches
HelperFunctions.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*/
12#include "eon/HelperFunctions.h"
13#include "eon/EonLogger.h"
16#include "eon/Optimizer.h"
17#include "eon/SafeMath.h"
18
19#include <cassert>
20#include <cmath>
21#include <cstring>
22#include <ctime>
23#include <filesystem>
24#include <format>
25#include <fstream>
26#include <iostream>
27#include <memory>
28#include <sstream>
29#include <stdexcept>
30
31#ifndef _WIN32
32#include <sys/resource.h>
33#include <sys/time.h>
34#endif
35using std::ifstream;
36using std::string;
37
38// Vector functions.
39// Make v1 orthogonal to v2
41 const AtomMatrix v2) {
42 return v1 - matDot(v1, v2) * eonc::safemath::safe_normalized(v2);
43}
44
45void eonc::helpers::getTime(double *real, double *user, double *sys) {
46 // Wall-clock time via C++11 chrono (portable)
47 using namespace std::chrono;
48 auto now = steady_clock::now();
49 *real = duration<double>(now.time_since_epoch()).count();
50
51#ifdef _WIN32
52 if (user)
53 *user = 0.0;
54 if (sys)
55 *sys = 0.0;
56#else
57 struct rusage r_usage;
58 if (getrusage(RUSAGE_SELF, &r_usage) != 0) {
59 EONC_LOG_WARNING("problem getting usage info: {}", strerror(errno));
60 }
61 if (user) {
62 *user = static_cast<double>(r_usage.ru_utime.tv_sec) +
63 static_cast<double>(r_usage.ru_utime.tv_usec) / 1e6;
64 }
65 if (sys) {
66 *sys = static_cast<double>(r_usage.ru_stime.tv_sec) +
67 static_cast<double>(r_usage.ru_stime.tv_usec) / 1e6;
68 }
69#endif
70}
71
72bool eonc::helpers::existsFile(string filename) {
73 return std::filesystem::exists(filename);
74}
75
76string eonc::helpers::getRelevantFile(string filename) {
77 string filenameRelevant;
78 string filenamePrefix;
79 string filenamePostfix;
80
81 // check if the _cp version of the file is present
82 int i = filename.rfind(".");
83 filenamePrefix.assign(filename, 0, i);
84 filenamePostfix.assign(filename, i, filename.size());
85 filenameRelevant = filenamePrefix + "_cp" + filenamePostfix;
86 if (existsFile(filenameRelevant)) {
87 return filenameRelevant;
88 }
89 // check if the _in version of the file is present
90 filenameRelevant = filenamePrefix + "_in" + filenamePostfix;
91 if (existsFile(filenameRelevant)) {
92 return filenameRelevant;
93 }
94 // otherwise return original filename
95 return filename;
96}
97
98VectorXd eonc::helpers::loadMasses(string filename, int nAtoms) {
99 ifstream massFile(filename.c_str());
100 if (!massFile.is_open()) {
101 EONC_LOG_CRITICAL("File {} was not found", filename);
102 throw std::runtime_error(std::format("cannot open {}", filename));
103 }
104
105 VectorXd masses(nAtoms);
106 for (int i = 0; i < nAtoms; i++) {
107 double mass;
108 if (!(massFile >> mass)) {
109 EONC_LOG_CRITICAL("Error reading {}", filename);
110 throw std::runtime_error(
111 std::format("{} ended after {} of {} masses", filename, i, nAtoms));
112 }
113 masses(i) = mass;
114 }
115
116 massFile.close();
117
118 return masses;
119}
120
121AtomMatrix eonc::helpers::loadMode(FILE *modeFile, int nAtoms) {
122 AtomMatrix mode;
123 mode.resize(nAtoms, 3);
124 mode.setZero();
125 for (int i = 0; i < nAtoms; i++) {
126 if (fscanf(modeFile, "%lf %lf %lf", &mode(i, 0), &mode(i, 1),
127 &mode(i, 2)) != 3) {
128 EONC_LOG_CRITICAL("Mode file ended after {} of {} atoms", i, nAtoms);
129 throw std::runtime_error(
130 std::format("mode file ended after {} of {} atoms", i, nAtoms));
131 }
132 }
133 return mode;
134}
135
136AtomMatrix eonc::helpers::loadMode(string filename, int nAtoms) {
137 // Unique FILE* with RAII cleanup
138 auto closer = [](FILE *f) {
139 if (f)
140 std::fclose(f);
141 };
142 std::unique_ptr<FILE, decltype(closer)> modeFile(
143 std::fopen(filename.c_str(), "rb"), closer);
144 if (!modeFile) {
145 EONC_LOG_CRITICAL("File {} was not found", filename);
146 throw std::runtime_error(std::format("cannot open {}", filename));
147 }
148 return loadMode(modeFile.get(), nAtoms);
149}
150
152 Matter &target, const Matter &initial, const std::string &displacementPath,
153 const std::string &modePath, double scale) {
154 if (eonc::io::io_ok(target.con2matter(displacementPath))) {
155 if (target.numberOfAtoms() != initial.numberOfAtoms()) {
156 EONC_LOG_ERROR("{} holds {} atoms, the initial structure has {}",
157 displacementPath, target.numberOfAtoms(),
158 initial.numberOfAtoms());
159 return false;
160 }
161 // displacement.con may carry stale fixed-atom coordinates from a prior run.
162 const AtomMatrix &initPos = initial.getPositions();
163 AtomMatrix pos = target.getPositionsCopy();
164 const long n = initial.numberOfAtoms();
165 for (long i = 0; i < n; i++) {
166 if (initial.getFixed(i)) {
167 pos.row(i) = initPos.row(i);
168 }
169 }
170 target.setPositions(pos);
171 return true;
172 }
173 if (!existsFile(modePath)) {
174 return false;
175 }
176 AtomMatrix mode =
177 loadMode(modePath, static_cast<int>(initial.numberOfAtoms()));
178 const double norm = mode.norm();
179 if (!(norm > 0.0)) {
180 return false;
181 }
182 mode *= (scale / norm);
183 target = initial;
184 AtomMatrix pos = initial.getPositionsCopy();
185 pos += mode;
186 const AtomMatrix &initPos = initial.getPositions();
187 const long n = initial.numberOfAtoms();
188 for (long i = 0; i < n; i++) {
189 if (initial.getFixed(i)) {
190 pos.row(i) = initPos.row(i);
191 }
192 }
193 target.setPositions(pos);
194 EONC_LOG_INFO("Synthesized displacement from pos.con + scale {:.6g} * unit "
195 "mode in {} (missing {})",
196 scale, modePath, displacementPath);
197 return true;
198}
199
200void eonc::helpers::saveMode(FILE *modeFile, std::shared_ptr<Matter> matter,
201 AtomMatrix mode) {
202 const AtomMatrix free = matter->getFree();
203 long const nAtoms = matter->numberOfAtoms();
204 for (long i = 0; i < nAtoms; ++i) {
205 fprintf(modeFile, "%.17g\t%.17g\t%.17g\n", free(i, 0) * mode(i, 0),
206 free(i, 1) * mode(i, 1), free(i, 2) * mode(i, 2));
207 }
208 return;
209}
210
211void eonc::helpers::saveMode(const std::string &filename,
212 std::shared_ptr<Matter> matter, AtomMatrix mode) {
213 std::ofstream out(filename);
214 if (!out)
215 return;
216 const AtomMatrix free = matter->getFree();
217 long const nAtoms = matter->numberOfAtoms();
218 for (long i = 0; i < nAtoms; ++i) {
219 out << std::format("{:.17g}\t{:.17g}\t{:.17g}\n", free(i, 0) * mode(i, 0),
220 free(i, 1) * mode(i, 1), free(i, 2) * mode(i, 2));
221 }
222}
223
224std::vector<int> eonc::helpers::split_string_int(std::string s,
225 std::string delim) {
226 std::vector<int> list;
227 if (s.empty())
228 return list;
229
230 size_t start = 0;
231 size_t end = s.find_first_of(delim);
232 while (start < s.size()) {
233 auto token = s.substr(start, end - start);
234 if (!token.empty()) {
235 try {
236 list.push_back(std::stoi(token));
237 } catch (const std::exception &) {
238 return {}; // Parse error
239 }
240 }
241 if (end == std::string::npos)
242 break;
243 start = end + 1;
244 end = s.find_first_of(delim, start);
245 }
246 return list;
247}
248
249std::optional<std::string_view>
251 if (metric == "max_atom") {
252 return "Max atom force";
253 }
254 if (metric == "max_component") {
255 return "Max force comp";
256 }
257 if (metric == "norm") {
258 return "||Force||";
259 }
260 return std::nullopt;
261}
262
264 std::string_view context) {
265 if (convergenceMetricLabel(metric)) {
266 return;
267 }
268 throw std::invalid_argument(
269 std::format("{} unknown convergence_metric: {}", context, metric));
270}
271
272namespace {
273class MatterObjectiveFunction : public ObjectiveFunction {
274 Matter &m_matter; // non-owning reference, avoids copy
275public:
276 MatterObjectiveFunction(Matter &mat, const Parameters &parametersPassed)
277 : ObjectiveFunction(parametersPassed),
278 m_matter{mat} {
280 params.optimizer_options.convergence_metric, "[Matter]");
281 }
282 ~MatterObjectiveFunction() = default;
283 double getEnergy() { return m_matter.getPotentialEnergy(); }
284 VectorXd getGradient(bool fdstep = false) {
285 return -m_matter.getForcesFreeV();
286 }
287 void setPositions(const VectorXd &x) { m_matter.setPositionsFreeV(x); }
288 VectorXd getPositions() { return m_matter.getPositionsFreeV(); }
289 int degreesOfFreedom() { return 3 * m_matter.numberOfFreeAtoms(); }
290 bool isConverged() {
291 return getConvergence() < params.optimizer_options.converged_force;
292 }
293 double getConvergence() {
294 if (params.optimizer_options.convergence_metric == "norm") {
295 return m_matter.getForcesFreeV().norm();
296 } else if (params.optimizer_options.convergence_metric == "max_atom") {
297 return m_matter.maxForce();
298 } else if (params.optimizer_options.convergence_metric == "max_component") {
299 return m_matter.getForces().maxCoeff();
300 } else {
301 EONC_LOG_CRITICAL("{} Unknown opt_convergence_metric: {}", "[Matter]",
302 params.optimizer_options.convergence_metric);
303 throw std::invalid_argument(
304 std::format("[Matter] unknown convergence_metric: {}",
305 params.optimizer_options.convergence_metric));
306 }
307 }
308 VectorXd difference(const VectorXd &a, const VectorXd &b) {
309 return m_matter.pbcV(a - b);
310 }
311};
312} // namespace
313
314bool eonc::helpers::relaxMatter(Matter &matter, const Parameters &params,
315 bool quiet, bool writeMovie, bool checkpoint,
316 std::string prefixMovie,
317 std::string prefixCheckpoint,
318 std::vector<readcon::ConFrame> *outFrames) {
319 eonc::log::Scoped m_log;
320 auto objf = std::make_shared<MatterObjectiveFunction>(matter, params);
322 objf, params.optimizer_options.method, params);
323
324 std::ostringstream min;
325 min << prefixMovie;
326 std::string minDatFilename = prefixMovie + ".dat";
327 auto write_movie_frame = [&](uint64_t frameIndex, bool append,
328 double stepSize) {
330 metadata.frame_index = frameIndex;
331 metadata.energy = matter.getPotentialEnergy();
332 metadata.scalars.push_back({"step_size", stepSize});
333 metadata.scalars.push_back({"convergence", objf->getConvergence()});
334 if (outFrames) {
335 outFrames->push_back(eonc::io::matterToConFrame(matter, &metadata));
336 }
337 if (writeMovie) {
338 if (!eonc::io::io_ok(matter.matter2con(min.str(), append, &metadata))) {
339 QUILL_LOG_WARNING(m_log, "Failed to write movie frame {}", min.str());
340 }
341 }
342
344 std::ofstream minDat(minDatFilename,
345 append ? (std::ios::binary | std::ios::app)
346 : std::ios::binary);
347 if (minDat) {
348 if (!append) {
349 minDat << "iteration\tstep_size\tconvergence\tenergy\n";
350 }
351 minDat << std::format("{}\t{:.5e}\t{:.5e}\t{:.6f}\n", frameIndex,
352 stepSize, objf->getConvergence(),
353 matter.getPotentialEnergy());
354 }
355 }
356 };
357 if (writeMovie || outFrames) {
358 write_movie_frame(0, false, 0.0);
359 }
360
361 int iteration = 0;
362 if (!quiet) {
363 QUILL_LOG_DEBUG(m_log, "{} {:10s} {:14s} {:18s} {:13s}\n", "[Matter]",
364 "Iter", "Step size",
366 "Energy");
367 QUILL_LOG_DEBUG(m_log, "{} {:10} {:14.5e} {:18.5e} {:13.5f}\n",
368 "[Matter]", iteration, 0.0, objf->getConvergence(),
369 matter.getPotentialEnergy());
370 }
371
372 while (!objf->isConverged() &&
373 iteration < params.optimizer_options.max_iterations) {
374
375 AtomMatrix pos = matter.getPositions();
376
377 optim->step(params.optimizer_options.max_move);
378 iteration++;
379
380 double stepSize =
381 eonc::geometry::maxAtomMotion(matter.pbc(matter.getPositions() - pos));
382
383 if (!quiet) {
384 QUILL_LOG_DEBUG(m_log, "{} {:10} {:14.5e} {:18.5e} {:13.5f}",
385 "[Matter]", iteration, stepSize, objf->getConvergence(),
386 matter.getPotentialEnergy());
387 }
388
389 if (writeMovie || outFrames) {
390 write_movie_frame(static_cast<uint64_t>(iteration), true, stepSize);
391 }
392
393 if (checkpoint) {
394 std::ostringstream chk;
395 chk << prefixCheckpoint << "_cp";
396 if (!eonc::io::io_ok(matter.matter2con(chk.str(), false))) {
397 QUILL_LOG_WARNING(m_log, "Failed to write checkpoint {}", chk.str());
398 }
399 }
400 }
401
402 if (iteration == 0) {
403 if (!quiet) {
404 QUILL_LOG_DEBUG(m_log, "{} {:10} {:14.5e} {:18.5e} {:13.5f}",
405 "[Matter]", iteration, 0.0, objf->getConvergence(),
406 matter.getPotentialEnergy());
407 }
408 }
409 return objf->isConverged();
410}
double matDot(const AtomMatrix &a, const AtomMatrix &b)
SIMD-optimized dot product for contiguous Eigen matrices.
Definition Eigen.h:50
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:262
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:256
#define EONC_LOG_INFO(...)
Definition EonLogger.h:250
#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...
double getPotentialEnergy() const
Definition Matter.cpp:446
void setPositionsFreeV(const VectorXd &pos)
Definition Matter.cpp:301
const AtomMatrix & getForces() const
Definition Matter.cpp:324
double maxForce(void) const
Definition Matter.cpp:554
AtomMatrix pbc(const AtomMatrix &diff) const
Definition Matter.h:163
AtomMatrix getPositionsCopy() const
Definition Matter.cpp:238
long int numberOfAtoms() const
Definition Matter.cpp:209
VectorXd pbcV(const VectorXd &diff) const
Definition Matter.h:166
VectorXd getForcesFreeV() const
Definition Matter.cpp:354
VectorXd getPositionsFreeV() const
Definition Matter.cpp:268
long int numberOfFreeAtoms() const
Definition Matter.cpp:475
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:273
io::IoStatus matter2con(std::string filename, bool append=false, const io::ConFrameMetadata *metadata=nullptr)
Definition Matter.h:281
io::IoStatus con2matter(std::string filename)
Definition Matter.h:269
const AtomMatrix & getPositions() const
Definition Matter.cpp:236
int getFixed(long int atom) const
1 if every Cartesian axis of the atom is fixed, else 0.
Definition Matter.cpp:402
struct eonc::Parameters::optimizer_options_t optimizer_options
struct eonc::Parameters::debug_options_t debug_options
double maxAtomMotion(const AtomMatrix v1)
std::unique_ptr< Optimizer > mkOptim(std::shared_ptr< ObjectiveFunction > a_objf, OptType a_otype, const Parameters &a_params)
Definition Optimizer.cpp:21
VectorXd loadMasses(std::string filename, int nAtoms)
bool relaxMatter(Matter &matter, const Parameters &params, bool quiet=false, bool writeMovie=false, bool checkpoint=false, std::string prefixMovie=std::string(), std::string prefixCheckpoint=std::string(), std::vector< readcon::ConFrame > *outFrames=nullptr)
bool loadOrSynthesizeDisplacement(Matter &target, const Matter &initial, const std::string &displacementPath, const std::string &modePath, double scale)
AtomMatrix loadMode(FILE *modeFile, int nAtoms)
std::string getRelevantFile(std::string filename)
void saveMode(FILE *modeFile, std::shared_ptr< Matter > matter, AtomMatrix mode)
Write a mode; constrained axes are emitted as 0.
std::optional< std::string_view > convergenceMetricLabel(std::string_view metric)
Display label for a force-convergence metric, or nullopt when the name is none of the three the optim...
std::vector< int > split_string_int(std::string s, std::string delim)
void requireKnownConvergenceMetric(std::string_view metric, std::string_view context)
Throws std::invalid_argument naming context when metric is unrecognized.
void getTime(double *real, double *user, double *sys)
AtomMatrix makeOrthogonal(const AtomMatrix v1, const AtomMatrix v2)
bool existsFile(std::string filename)
readcon::ConFrame matterToConFrame(Matter &m, const ConFrameMetadata *metadata)
Build a single stamped ConFrame from Matter (same builder as matter2con).
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38
std::optional< uint64_t > frame_index
Definition ConFileIO.h:72
std::vector< ConMetadataValue > scalars
Definition ConFileIO.h:79
std::optional< double > energy
Definition ConFileIO.h:73
RAII helper for class-scoped logging.
Definition EonLogger.h:171