17#include "magic_enum/magic_enum.hpp"
22#include <nlohmann/json.hpp>
27using json = nlohmann::json;
31 return std::string(magic_enum::enum_name(val));
36 auto result = magic_enum::enum_cast<E>(j.get<std::string>(),
37 magic_enum::case_insensitive);
38 return result.value_or(fallback);
44#define JSON_OPT(j, key, target) \
45 if ((j).contains(key)) \
46 (target) = (j).at(key).get<decltype(target)>()
92 j[
"Structure Comparison"] = {
93 {
"distance_difference",
100 {
"indistinguishable_atoms",
103 {
"energy_difference",
105 {
"remove_translation",
114 {
"convergence_metric",
125 j[
"Optimizer"][
"LBFGS"] = {
127 {
"inverse_curvature",
129 {
"max_inverse_curvature",
156 j[
"Optimizer"][
"Xtsci"] = {
176 {
"andersen_collision_period",
179 {
"langevin_friction",
184 {
"path_eco_omega_max",
196 j[
"Nudged Elastic Band"] = {
203 {
"solid_state_weight",
205 {
"solid_state_pressure",
208 j[
"Nudged Elastic Band"][
"spring"] = {
215 j[
"Nudged Elastic Band"][
"climbing_image"] = {
224 j[
"Nudged Elastic Band"][
"zoom"] = {
225 {
"enabled", zoom.enabled},
226 {
"alpha", zoom.alpha},
227 {
"offset", zoom.offset},
229 {
"activation_threshold", zoom.activation_threshold},
231 {
"stability_count", zoom.stability_count},
232 {
"max_iterations", zoom.max_iterations},
252 j[
"Saddle Search"] = {
259 {
"displace_magnitude",
287 {
"diagonal_preconditioner",
309 {
"reactant_filename", o.reactant_filename},
310 {
"product_filename", o.product_filename},
311 {
"initial_path", o.initial_path},
313 {
"beta_hbar_omega", o.beta_hbar_omega},
314 {
"max_iterations", o.max_iterations},
315 {
"force_tolerance", o.force_tolerance},
316 {
"hessian_stride", o.hessian_stride},
317 {
"saddle_filename", o.saddle_filename},
318 {
"temperature", o.temperature},
319 {
"temperatures", o.temperatures},
320 {
"half_ring", o.half_ring},
321 {
"initial_hessians", o.initial_hessians},
322 {
"energy_shift", o.energy_shift},
323 {
"bead_ladder", o.bead_ladder},
324 {
"hessian_final", o.hessian_final},
325 {
"springs", o.springs},
326 {
"pi_planes", o.pi_planes},
327 {
"pi_beads", o.pi_beads},
328 {
"pi_equilibration_steps", o.pi_equilibration_steps},
329 {
"pi_sampling_steps", o.pi_sampling_steps},
330 {
"pi_time_step", o.pi_time_step},
331 {
"pi_thermostat", o.pi_thermostat},
332 {
"pi_gle_file", o.pi_gle_file},
333 {
"pi_pile_tau", o.pi_pile_tau},
334 {
"pi_pile_scale", o.pi_pile_scale},
335 {
"pi_seed", o.pi_seed},
336 {
"pi_direction", o.pi_direction},
337 {
"pi_reactant_extent", o.pi_reactant_extent},
338 {
"pi_recrossing_parents", o.pi_recrossing_parents},
339 {
"pi_recrossing_children", o.pi_recrossing_children},
340 {
"pi_recrossing_time", o.pi_recrossing_time},
341 {
"pi_recrossing_spacing", o.pi_recrossing_spacing},
348 {
"write_movies_interval",
350 {
"write_deprecated_outs",
367 for (
char &ch : value) {
368 ch =
static_cast<char>(std::tolower(
static_cast<unsigned char>(ch)));
374 if (value.is_array()) {
375 return value.get<std::vector<double>>();
377 if (!value.is_string()) {
378 throw std::invalid_argument(
379 "[Instanton] temperatures must be a list or a comma-separated "
382 std::vector<double> out;
383 std::stringstream ss(value.get<std::string>());
385 while (std::getline(ss, token,
',')) {
386 const size_t start = token.find_first_not_of(
" \t");
387 const size_t end = token.find_last_not_of(
" \t");
388 if (start == std::string::npos) {
392 out.push_back(std::stod(token.substr(start, end - start + 1)));
393 }
catch (
const std::exception &) {
394 throw std::invalid_argument(
395 "[Instanton] temperatures must be comma-separated kelvin "
408 JSON_OPT(s,
"path_beads", th.path_beads);
409 if (s.contains(
"path_springs")) {
410 th.path_springs =
lowerCopy(s.at(
"path_springs").get<std::string>());
411 if (th.path_springs !=
"trotter" && th.path_springs !=
"eco") {
412 throw std::invalid_argument(
413 "[Dynamics] path_springs must be trotter or eco, not " +
417 JSON_OPT(s,
"path_eco_omega_max", th.path_eco_omega_max);
418 JSON_OPT(s,
"path_gle_file", th.path_gle_file);
419 JSON_OPT(s,
"path_pile_tau", th.path_pile_tau_input);
420 JSON_OPT(s,
"path_pile_scale", th.path_pile_scale);
421 if (s.contains(
"path_seed")) {
422 const long seed = s.at(
"path_seed").get<
long>();
424 throw std::invalid_argument(
"[Dynamics] path_seed must be non-negative");
426 th.path_seed =
static_cast<std::uint64_t
>(seed);
432 if (s.contains(
"thermostat")) {
433 th.kind =
lowerCopy(s.at(
"thermostat").get<std::string>());
435 if (s.contains(
"kind")) {
436 th.kind =
lowerCopy(s.at(
"kind").get<std::string>());
438 JSON_OPT(s,
"andersen_alpha", th.andersen_alpha);
439 JSON_OPT(s,
"andersen_collision_period", th.andersen_tcol_input);
440 JSON_OPT(s,
"nose_mass", th.nose_mass);
441 JSON_OPT(s,
"langevin_friction", th.langevin_friction_input);
447 if (j.contains(
"Main")) {
448 auto &m = j.at(
"Main");
449 if (m.contains(
"job"))
474 if (j.contains(
"Potential")) {
475 auto &s = j.at(
"Potential");
476 if (s.contains(
"potential"))
497 if (j.contains(
"Structure Comparison")) {
498 auto &s = j.at(
"Structure Comparison");
501 .distance_difference);
503 s,
"neighbor_cutoff",
508 JSON_OPT(s,
"indistinguishable_atoms",
510 .indistinguishable_atoms);
516 .remove_translation);
520 if (j.contains(
"Optimizer")) {
521 auto &s = j.at(
"Optimizer");
522 if (s.contains(
"opt_method"))
530 c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
537 throw std::invalid_argument(std::format(
538 "unknown convergence_metric: {}",
551 if (s.contains(
"LBFGS")) {
552 auto &l = s.at(
"LBFGS");
556 l,
"inverse_curvature",
558 JSON_OPT(l,
"max_inverse_curvature",
560 .lbfgs.max_inverse_curvature);
593 if (s.contains(
"Xtsci")) {
594 auto &x = s.at(
"Xtsci");
604 if (j.contains(
"Dynamics")) {
605 auto &s = j.at(
"Dynamics");
613 if (j.contains(
"Thermostat")) {
618 if (j.contains(
"Nudged Elastic Band")) {
619 auto &s = j.at(
"Nudged Elastic Band");
623 if (s.contains(
"opt_method"))
634 if (s.contains(
"spring")) {
635 auto &sp = s.at(
"spring");
643 if (s.contains(
"climbing_image")) {
644 auto &ci = s.at(
"climbing_image");
648 ci,
"converged_only",
653 if (s.contains(
"zoom")) {
654 auto &z = s.at(
"zoom");
656 JSON_OPT(z,
"enabled", zoom.enabled);
659 if (z.contains(
"mode")) {
662 JSON_OPT(z,
"activation_threshold", zoom.activation_threshold);
663 if (z.contains(
"interpolation")) {
667 JSON_OPT(z,
"stability_count", zoom.stability_count);
668 JSON_OPT(z,
"max_iterations", zoom.max_iterations);
673 if (j.contains(
"Dimer")) {
674 auto &s = j.at(
"Dimer");
682 if (s.contains(
"opt_method"))
686 if (s.contains(
"rotation_backend"))
688 s.at(
"rotation_backend"),
695 if (j.contains(
"Saddle Search")) {
696 auto &s = j.at(
"Saddle Search");
712 if (j.contains(
"Serve")) {
713 auto &s = j.at(
"Serve");
715 if (s.contains(
"port"))
717 s.at(
"port").get<uint16_t>();
718 if (s.contains(
"replicas"))
720 s.at(
"replicas").get<
size_t>();
721 if (s.contains(
"gateway_port"))
723 s.at(
"gateway_port").get<uint16_t>();
728 if (j.contains(
"Debug")) {
729 auto &s = j.at(
"Debug");
732 JSON_OPT(s,
"write_movies_interval",
734 JSON_OPT(s,
"write_deprecated_outs",
739 if (j.contains(
"Hessian")) {
740 auto &s = j.at(
"Hessian");
742 if (s.contains(
"phva_atoms")) {
743 h.phva_atoms =
lowerCopy(s.at(
"phva_atoms").get<std::string>());
744 }
else if (s.contains(
"atom_list")) {
745 h.phva_atoms =
lowerCopy(s.at(
"atom_list").get<std::string>());
747 JSON_OPT(s,
"zero_freq_value", h.zero_freq_value);
748 if (s.contains(
"fd_scheme")) {
749 h.fd_scheme =
lowerCopy(s.at(
"fd_scheme").get<std::string>());
752 JSON_OPT(s,
"checkpoint_path", h.checkpoint_path);
753 JSON_OPT(s,
"write_modes", h.write_modes);
757 if (j.contains(
"Instanton")) {
758 auto &s = j.at(
"Instanton");
761 JSON_OPT(s,
"reactant_filename", o.reactant_filename);
762 JSON_OPT(s,
"product_filename", o.product_filename);
763 JSON_OPT(s,
"initial_path", o.initial_path);
765 JSON_OPT(s,
"beta_hbar_omega", o.beta_hbar_omega);
766 JSON_OPT(s,
"max_iterations", o.max_iterations);
767 JSON_OPT(s,
"force_tolerance", o.force_tolerance);
768 JSON_OPT(s,
"hessian_stride", o.hessian_stride);
769 JSON_OPT(s,
"saddle_filename", o.saddle_filename);
770 JSON_OPT(s,
"temperature", o.temperature);
771 if (s.contains(
"temperatures")) {
774 JSON_OPT(s,
"half_ring", o.half_ring);
775 if (s.contains(
"initial_hessians")) {
776 o.initial_hessians = s.at(
"initial_hessians").get<std::string>();
778 if (o.initial_hessians !=
"saddle" &&
779 o.initial_hessians !=
"finite_difference") {
780 throw std::invalid_argument(
781 "[Instanton] initial_hessians must be saddle or "
782 "finite_difference, not " +
785 JSON_OPT(s,
"energy_shift", o.energy_shift);
786 JSON_OPT(s,
"bead_ladder", o.bead_ladder);
787 JSON_OPT(s,
"hessian_final", o.hessian_final);
788 if (s.contains(
"springs")) {
789 o.springs =
lowerCopy(s.at(
"springs").get<std::string>());
791 if (o.springs !=
"trotter" && o.springs !=
"eco") {
792 throw std::invalid_argument(
793 "[Instanton] springs must be trotter or eco, not " + o.springs);
795 if (o.mode !=
"splitting" && o.mode !=
"rate") {
796 throw std::invalid_argument(
"[Instanton] mode must be splitting or "
800 JSON_OPT(s,
"pi_planes", o.pi_planes);
801 JSON_OPT(s,
"pi_beads", o.pi_beads);
802 JSON_OPT(s,
"pi_equilibration_steps", o.pi_equilibration_steps);
803 JSON_OPT(s,
"pi_sampling_steps", o.pi_sampling_steps);
804 JSON_OPT(s,
"pi_time_step", o.pi_time_step);
805 if (s.contains(
"pi_thermostat")) {
806 o.pi_thermostat =
lowerCopy(s.at(
"pi_thermostat").get<std::string>());
808 JSON_OPT(s,
"pi_gle_file", o.pi_gle_file);
809 JSON_OPT(s,
"pi_pile_tau", o.pi_pile_tau);
810 JSON_OPT(s,
"pi_pile_scale", o.pi_pile_scale);
812 if (s.contains(
"pi_direction")) {
813 o.pi_direction =
lowerCopy(s.at(
"pi_direction").get<std::string>());
815 JSON_OPT(s,
"pi_reactant_extent", o.pi_reactant_extent);
816 JSON_OPT(s,
"pi_recrossing_parents", o.pi_recrossing_parents);
817 JSON_OPT(s,
"pi_recrossing_children", o.pi_recrossing_children);
818 JSON_OPT(s,
"pi_recrossing_time", o.pi_recrossing_time);
819 JSON_OPT(s,
"pi_recrossing_spacing", o.pi_recrossing_spacing);
828 th.andersen_tcol = timeUnit > 0.0 ? th.andersen_tcol_input / timeUnit : 0.0;
829 th.path_pile_tau = timeUnit > 0.0 ? th.path_pile_tau_input / timeUnit : 0.0;
838 auto j = json::parse(json_str);
841 }
catch (
const json::exception &e) {
843 }
catch (
const std::invalid_argument &) {
#define JSON_OPT(j, key, target)
static E enum_from_json(const json &j, E fallback)
static json enum_to_json(E val)
void from_json(const json &j, Parameters &p)
Deserialize JSON into Parameters, then resolve computed fields.
static std::string lowerCopy(std::string value)
static void readPathIntegralKeys(const json &s, Parameters &p)
int load_json(std::string_view json_str, Parameters ¶ms)
Load Parameters from a JSON string. Returns 0 on success.
static void readBathKeys(const json &s, Parameters &p)
json to_json(const Parameters &p)
Serialize all Parameters to JSON.
void validate_and_link(Parameters ¶ms)
Resolve cross-group defaults and time unit conversions.
static std::vector< double > temperaturesFromJson(const json &value)
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 four the optimi...
void validateOptions(const instanton_options_t &o)
Throws std::invalid_argument on an inconsistent [Instanton] pi_* key.
static lanczos_options_t & lanczos_options(Parameters &p)
static thermostat_options_t & thermostat_options(Parameters &p)
static dimer_options_t & dimer_options(Parameters &p)
static constants_t & constants(Parameters &p)
static debug_options_t & debug_options(Parameters &p)
static hessian_options_t & hessian_options(Parameters &p)
static instanton_options_t & instanton_options(Parameters &p)
static saddle_search_options_t & saddle_search_options(Parameters &p)
static dynamics_options_t & dynamics_options(Parameters &p)
static structure_comparison_options_t & structure_comparison_options(Parameters &p)
static main_options_t & main_options(Parameters &p)
static prefactor_options_t & prefactor_options(Parameters &p)
static neb_options_t & neb_options(Parameters &p)
static optimizer_options_t & optimizer_options(Parameters &p)
static serve_options_t & serve_options(Parameters &p)
static davidson_options_t & davidson_options(Parameters &p)
static potential_options_t & potential_options(Parameters &p)
bool indistinguishable_atoms
double distance_difference
bool diagonal_preconditioner
Heuristic | (H v)_i / v_i | preconditioner (not true diag(H); off by default).
std::string phva_atoms
PHVA mobile/active set for the Ritz space; "All" = all free atoms.
long write_movies_interval
bool write_deprecated_outs
DimerRotationBackend rotation_backend
std::string checkpoint_path
bool writeConForces
Opt-in "Forces of Component" sections in written .con frames (enables force+energy co-load on restart...
double band_slack
When converged_only is on, the rest of the band may still be this many times the force tolerance.
double pressure
Hydrostatic pressure in eV/Angstrom^3.
struct eonc::neb_options_t::climbing_image_options_t climbing_image
struct eonc::neb_options_t::zoom_options_t zoom
struct eonc::neb_options_t::spring_options_t spring
struct eonc::neb_options_t::solid_state_options_t solid_state
double max_inverse_curvature
double max_time_step_input
std::string convergence_metric
struct eonc::optimizer_options_t::xtsci_t xtsci
std::string convergence_metric_label
struct eonc::optimizer_options_t::lbfgs_t lbfgs
std::string potentialsPath
double displace_magnitude
std::string minmode_method
double path_pile_tau_input
std::string path_gle_file
double langevin_friction_input
double andersen_tcol_input
double path_eco_omega_max