Loading...
Searching...
No Matches
ParametersJSON.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/ParametersJSON.h"
13#include "eon/HelperFunctions.h"
14#include "eon/Parameters.h"
15#include "eon/ParametersINI.h"
16#include "magic_enum/magic_enum.hpp"
17
18#include <cctype>
19#include <format>
20#include <nlohmann/json.hpp>
21#include <stdexcept>
22
23using json = nlohmann::json;
24
25// Helper: enum <-> string via magic_enum
26template <typename E> static json enum_to_json(E val) {
27 return std::string(magic_enum::enum_name(val));
28}
29
30template <typename E> static E enum_from_json(const json &j, E fallback) {
31 if (j.is_string()) {
32 auto result = magic_enum::enum_cast<E>(j.get<std::string>(),
33 magic_enum::case_insensitive);
34 return result.value_or(fallback);
35 }
36 return fallback;
37}
38
39// Macro for optional JSON field extraction with default
40#define JSON_OPT(j, key, target) \
41 if ((j).contains(key)) \
42 (target) = (j).at(key).get<decltype(target)>()
43
44namespace eonc::config {
45
47 json j;
48
49 // [Main]
50 j["Main"] = {
51 {"job", enum_to_json(p.main_options.job)},
52 {"random_seed", p.main_options.randomSeed},
53 {"temperature", p.main_options.temperature},
54 {"quiet", p.main_options.quiet},
55 {"write_log", p.main_options.writeLog},
56 {"checkpoint", p.main_options.checkpoint},
57 {"ini_filename", p.main_options.iniFilename},
58 {"con_filename", p.main_options.conFilename},
59 {"finite_difference", p.main_options.finiteDifference},
60 {"max_force_calls", p.main_options.maxForceCalls},
61 {"remove_net_force", p.main_options.removeNetForce},
62 {"write_con_forces", p.main_options.writeConForces},
63 };
64
65 // [Potential]
66 j["Potential"] = {
68 {"mpi_poll_period", p.potential_options.MPIPollPeriod},
69 {"lammps_logging", p.potential_options.LAMMPSLogging},
70 {"lammps_threads", p.potential_options.LAMMPSThreads},
71 {"emt_rasmussen", p.potential_options.EMTRasmussen},
72 {"log_potential", p.potential_options.LogPotential},
73 {"ext_pot_path", p.potential_options.extPotPath},
74 {"potentials_path", p.potential_options.potentialsPath},
75 };
76
77 // [Structure Comparison]
78 j["Structure Comparison"] = {
79 {"distance_difference",
81 {"neighbor_cutoff", p.structure_comparison_options.neighbor_cutoff},
82 {"check_rotation", p.structure_comparison_options.check_rotation},
83 {"indistinguishable_atoms",
85 {"energy_difference", p.structure_comparison_options.energy_difference},
86 {"remove_translation", p.structure_comparison_options.remove_translation},
87 };
88
89 // [Optimizer]
90 j["Optimizer"] = {
91 {"opt_method", enum_to_json(p.optimizer_options.method)},
92 {"convergence_metric", p.optimizer_options.convergence_metric},
93 {"max_iterations", p.optimizer_options.max_iterations},
94 {"max_move", p.optimizer_options.max_move},
95 {"converged_force", p.optimizer_options.converged_force},
96 {"time_step", p.optimizer_options.time_step_input},
97 {"max_time_step", p.optimizer_options.max_time_step_input},
98 };
99 j["Optimizer"]["LBFGS"] = {
100 {"memory", p.optimizer_options.lbfgs.memory},
101 {"inverse_curvature", p.optimizer_options.lbfgs.inverse_curvature},
102 {"auto_scale", p.optimizer_options.lbfgs.auto_scale},
103 {"angle_reset", p.optimizer_options.lbfgs.angle_reset},
104 {"distance_reset", p.optimizer_options.lbfgs.distance_reset},
105 };
106
107 // [Dynamics]
108 j["Dynamics"] = {
109 {"time_step", p.dynamics_options.time_step_input},
110 {"time", p.dynamics_options.time_input},
111 };
112
113 // [Thermostat]
114 j["Thermostat"] = {
115 {"kind", p.thermostat_options.kind},
116 {"andersen_alpha", p.thermostat_options.andersen_alpha},
117 {"andersen_collision_period", p.thermostat_options.andersen_tcol_input},
118 {"nose_mass", p.thermostat_options.nose_mass},
119 {"langevin_friction", p.thermostat_options.langevin_friction_input},
120 };
121
122 // [Nudged Elastic Band]
123 j["Nudged Elastic Band"] = {
124 {"images", p.neb_options.image_count},
125 {"max_iterations", p.neb_options.max_iterations},
126 {"opt_method", enum_to_json(p.neb_options.opt_method)},
127 {"converged_force", p.neb_options.force_tolerance},
128 };
129 j["Nudged Elastic Band"]["spring"] = {
130 {"constant", p.neb_options.spring.constant},
131 {"elastic_band", p.neb_options.spring.use_elastic_band},
132 {"doubly_nudged", p.neb_options.spring.doubly_nudged},
133 };
134 j["Nudged Elastic Band"]["climbing_image"] = {
135 {"enabled", p.neb_options.climbing_image.enabled},
136 {"converged_only", p.neb_options.climbing_image.converged_only},
137 };
138
139 // [Dimer]
140 j["Dimer"] = {
141 {"rotation_angle", p.dimer_options.rotation_angle},
142 {"improved", p.dimer_options.improved},
143 {"converged_angle", p.dimer_options.converged_angle},
144 {"max_iterations", p.dimer_options.max_iterations},
145 {"opt_method", p.dimer_options.opt_method},
146 {"rotation_backend", enum_to_json(p.dimer_options.rotation_backend)},
147 {"lor_residual_tol", p.dimer_options.lor_residual_tol},
148 };
149
150 // [Saddle Search]
151 j["Saddle Search"] = {
152 {"method", p.saddle_search_options.method},
153 {"min_mode_method", p.saddle_search_options.minmode_method},
154 {"max_energy", p.saddle_search_options.max_energy},
155 {"max_iterations", p.saddle_search_options.max_iterations},
156 {"displace_magnitude", p.saddle_search_options.displace_magnitude},
157 {"displace_radius", p.saddle_search_options.displace_radius},
158 };
159
160 // [Prefactor]
161 j["Prefactor"] = {
162 {"default_value", p.prefactor_options.default_value},
163 {"max_value", p.prefactor_options.max_value},
164 {"min_value", p.prefactor_options.min_value},
165 };
166
167 // [Lanczos]
168 j["Lanczos"] = {
169 {"tolerance", p.lanczos_options.tolerance},
170 {"max_iterations", p.lanczos_options.max_iterations},
171 {"quit_early", p.lanczos_options.quit_early},
172 {"phva_atoms", p.lanczos_options.phva_atoms},
173 };
174
175 // [Davidson]
176 j["Davidson"] = {
177 {"tolerance", p.davidson_options.tolerance},
178 {"max_iterations", p.davidson_options.max_iterations},
179 {"diagonal_preconditioner", p.davidson_options.diagonal_preconditioner},
180 {"phva_atoms", p.davidson_options.phva_atoms},
181 };
182
183 // [Hessian]
184 j["Hessian"] = {
185 {"phva_atoms", p.hessian_options.phva_atoms},
186 {"zero_freq_value", p.hessian_options.zero_freq_value},
187 {"fd_scheme", p.hessian_options.fd_scheme},
188 {"resume", p.hessian_options.resume},
189 {"checkpoint_path", p.hessian_options.checkpoint_path},
190 };
191
192 // [Debug]
193 j["Debug"] = {
194 {"write_movies", p.debug_options.write_movies},
195 {"write_movies_interval", p.debug_options.write_movies_interval},
196 {"write_deprecated_outs", p.debug_options.write_deprecated_outs},
197 };
198
199 // [Serve]
200 j["Serve"] = {
201 {"host", p.serve_options.host},
202 {"port", p.serve_options.port},
203 {"replicas", p.serve_options.replicas},
204 {"gateway_port", p.serve_options.gateway_port},
205 {"endpoints", p.serve_options.endpoints},
206 };
207
208 return j;
209}
210
211void from_json(const json &j, Parameters &p) {
212 // [Main]
213 if (j.contains("Main")) {
214 auto &m = j.at("Main");
215 if (m.contains("job"))
216 p.main_options.job = enum_from_json(m.at("job"), p.main_options.job);
217 JSON_OPT(m, "random_seed", p.main_options.randomSeed);
218 JSON_OPT(m, "temperature", p.main_options.temperature);
219 JSON_OPT(m, "quiet", p.main_options.quiet);
220 JSON_OPT(m, "write_log", p.main_options.writeLog);
221 JSON_OPT(m, "checkpoint", p.main_options.checkpoint);
222 JSON_OPT(m, "ini_filename", p.main_options.iniFilename);
223 JSON_OPT(m, "con_filename", p.main_options.conFilename);
224 JSON_OPT(m, "finite_difference", p.main_options.finiteDifference);
225 JSON_OPT(m, "max_force_calls", p.main_options.maxForceCalls);
226 JSON_OPT(m, "remove_net_force", p.main_options.removeNetForce);
227 JSON_OPT(m, "write_con_forces", p.main_options.writeConForces);
228 }
229
230 // [Potential]
231 if (j.contains("Potential")) {
232 auto &s = j.at("Potential");
233 if (s.contains("potential"))
235 enum_from_json(s.at("potential"), p.potential_options.potential);
236 JSON_OPT(s, "mpi_poll_period", p.potential_options.MPIPollPeriod);
237 JSON_OPT(s, "lammps_logging", p.potential_options.LAMMPSLogging);
238 JSON_OPT(s, "lammps_threads", p.potential_options.LAMMPSThreads);
239 JSON_OPT(s, "emt_rasmussen", p.potential_options.EMTRasmussen);
240 JSON_OPT(s, "log_potential", p.potential_options.LogPotential);
241 JSON_OPT(s, "ext_pot_path", p.potential_options.extPotPath);
242 JSON_OPT(s, "potentials_path", p.potential_options.potentialsPath);
243 }
244
245 // [Structure Comparison]
246 if (j.contains("Structure Comparison")) {
247 auto &s = j.at("Structure Comparison");
248 JSON_OPT(s, "distance_difference",
250 JSON_OPT(s, "neighbor_cutoff",
252 JSON_OPT(s, "check_rotation",
254 JSON_OPT(s, "indistinguishable_atoms",
256 JSON_OPT(s, "energy_difference",
258 JSON_OPT(s, "remove_translation",
260 }
261
262 // [Optimizer]
263 if (j.contains("Optimizer")) {
264 auto &s = j.at("Optimizer");
265 if (s.contains("opt_method"))
267 enum_from_json(s.at("opt_method"), p.optimizer_options.method);
268 JSON_OPT(s, "convergence_metric", p.optimizer_options.convergence_metric);
269 for (char &c : p.optimizer_options.convergence_metric) {
270 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
271 }
274 p.optimizer_options.convergence_metric_label = std::string(*label);
275 } else {
276 throw std::invalid_argument(
277 std::format("unknown convergence_metric: {}",
279 }
280 JSON_OPT(s, "max_iterations", p.optimizer_options.max_iterations);
281 JSON_OPT(s, "max_move", p.optimizer_options.max_move);
282 JSON_OPT(s, "converged_force", p.optimizer_options.converged_force);
283 JSON_OPT(s, "time_step", p.optimizer_options.time_step_input);
284 JSON_OPT(s, "max_time_step", p.optimizer_options.max_time_step_input);
285 if (s.contains("LBFGS")) {
286 auto &l = s.at("LBFGS");
287 JSON_OPT(l, "memory", p.optimizer_options.lbfgs.memory);
288 JSON_OPT(l, "inverse_curvature",
290 JSON_OPT(l, "auto_scale", p.optimizer_options.lbfgs.auto_scale);
291 JSON_OPT(l, "angle_reset", p.optimizer_options.lbfgs.angle_reset);
292 JSON_OPT(l, "distance_reset", p.optimizer_options.lbfgs.distance_reset);
293 }
294 }
295
296 // [Dynamics]
297 if (j.contains("Dynamics")) {
298 auto &s = j.at("Dynamics");
299 JSON_OPT(s, "time_step", p.dynamics_options.time_step_input);
301 }
302
303 // [Thermostat]
304 if (j.contains("Thermostat")) {
305 auto &s = j.at("Thermostat");
306 JSON_OPT(s, "kind", p.thermostat_options.kind);
307 JSON_OPT(s, "andersen_alpha", p.thermostat_options.andersen_alpha);
308 JSON_OPT(s, "andersen_collision_period",
310 JSON_OPT(s, "nose_mass", p.thermostat_options.nose_mass);
311 JSON_OPT(s, "langevin_friction",
313 }
314
315 // [Nudged Elastic Band]
316 if (j.contains("Nudged Elastic Band")) {
317 auto &s = j.at("Nudged Elastic Band");
318 JSON_OPT(s, "images", p.neb_options.image_count);
319 JSON_OPT(s, "max_iterations", p.neb_options.max_iterations);
320 if (s.contains("opt_method"))
322 enum_from_json(s.at("opt_method"), p.neb_options.opt_method);
323 JSON_OPT(s, "converged_force", p.neb_options.force_tolerance);
324 if (s.contains("spring")) {
325 auto &sp = s.at("spring");
326 JSON_OPT(sp, "constant", p.neb_options.spring.constant);
327 JSON_OPT(sp, "elastic_band", p.neb_options.spring.use_elastic_band);
328 JSON_OPT(sp, "doubly_nudged", p.neb_options.spring.doubly_nudged);
329 }
330 if (s.contains("climbing_image")) {
331 auto &ci = s.at("climbing_image");
332 JSON_OPT(ci, "enabled", p.neb_options.climbing_image.enabled);
333 JSON_OPT(ci, "converged_only",
335 }
336 }
337
338 // [Dimer]
339 if (j.contains("Dimer")) {
340 auto &s = j.at("Dimer");
341 JSON_OPT(s, "rotation_angle", p.dimer_options.rotation_angle);
342 JSON_OPT(s, "improved", p.dimer_options.improved);
343 JSON_OPT(s, "converged_angle", p.dimer_options.converged_angle);
344 JSON_OPT(s, "max_iterations", p.dimer_options.max_iterations);
345 JSON_OPT(s, "opt_method", p.dimer_options.opt_method);
346 if (s.contains("rotation_backend"))
348 s.at("rotation_backend"), p.dimer_options.rotation_backend);
349 JSON_OPT(s, "lor_residual_tol", p.dimer_options.lor_residual_tol);
350 }
351
352 // [Saddle Search]
353 if (j.contains("Saddle Search")) {
354 auto &s = j.at("Saddle Search");
355 JSON_OPT(s, "method", p.saddle_search_options.method);
356 JSON_OPT(s, "min_mode_method", p.saddle_search_options.minmode_method);
357 JSON_OPT(s, "max_energy", p.saddle_search_options.max_energy);
358 JSON_OPT(s, "max_iterations", p.saddle_search_options.max_iterations);
359 JSON_OPT(s, "displace_magnitude",
361 JSON_OPT(s, "displace_radius", p.saddle_search_options.displace_radius);
362 }
363
364 // [Serve]
365 if (j.contains("Serve")) {
366 auto &s = j.at("Serve");
367 JSON_OPT(s, "host", p.serve_options.host);
368 if (s.contains("port"))
369 p.serve_options.port = s.at("port").get<uint16_t>();
370 if (s.contains("replicas"))
371 p.serve_options.replicas = s.at("replicas").get<size_t>();
372 if (s.contains("gateway_port"))
373 p.serve_options.gateway_port = s.at("gateway_port").get<uint16_t>();
374 JSON_OPT(s, "endpoints", p.serve_options.endpoints);
375 }
376
377 // [Debug]
378 if (j.contains("Debug")) {
379 auto &s = j.at("Debug");
380 JSON_OPT(s, "write_movies", p.debug_options.write_movies);
381 JSON_OPT(s, "write_movies_interval", p.debug_options.write_movies_interval);
382 JSON_OPT(s, "write_deprecated_outs", p.debug_options.write_deprecated_outs);
383 }
384
385 // Resolve computed fields
387}
388
389int load_json(const std::string &json_str, Parameters &params) {
390 try {
391 auto j = json::parse(json_str);
392 from_json(j, params);
393 return 0;
394 } catch (const json::exception &e) {
395 return 1;
396 } catch (const std::invalid_argument &) {
397 return 1;
398 }
399}
400
401} // namespace eonc::config
#define JSON_OPT(j, key, target)
nlohmann::json json
static E enum_from_json(const json &j, E fallback)
static json enum_to_json(E val)
struct eonc::Parameters::optimizer_options_t optimizer_options
struct eonc::Parameters::neb_options_t neb_options
struct eonc::Parameters::thermostat_options_t thermostat_options
struct eonc::Parameters::structure_comparison_options_t structure_comparison_options
struct eonc::Parameters::dimer_options_t dimer_options
struct eonc::Parameters::potential_options_t potential_options
struct eonc::Parameters::debug_options_t debug_options
struct eonc::Parameters::saddle_search_options_t saddle_search_options
struct eonc::Parameters::davidson_options_t davidson_options
struct eonc::Parameters::prefactor_options_t prefactor_options
struct eonc::Parameters::hessian_options_t hessian_options
struct eonc::Parameters::dynamics_options_t dynamics_options
struct eonc::Parameters::lanczos_options_t lanczos_options
struct eonc::Parameters::main_options_t main_options
struct eonc::Parameters::serve_options_t serve_options
void from_json(const json &j, Parameters &p)
Deserialize JSON into Parameters, then resolve computed fields.
json to_json(const Parameters &p)
Serialize all Parameters to JSON.
void validate_and_link(Parameters &params)
Resolve cross-group defaults and time unit conversions.
int load_json(const std::string &json_str, Parameters &params)
Load Parameters from a JSON string. Returns 0 on success.
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::string phva_atoms
PHVA mobile/active set for the Ritz space; "All" = all free atoms.
Definition Parameters.h:433
bool diagonal_preconditioner
Heuristic | (H v)_i / v_i | preconditioner (not true diag(H); off by default).
Definition Parameters.h:431
DimerRotationBackend rotation_backend
Definition Parameters.h:273
bool writeConForces
Opt-in "Forces of Component" sections in written .con frames (enables force+energy co-load on restart...
Definition Parameters.h:62
struct eonc::Parameters::neb_options_t::climbing_image_options_t climbing_image
struct eonc::Parameters::neb_options_t::spring_options_t spring
struct eonc::Parameters::optimizer_options_t::lbfgs_t lbfgs