35 std::string manifest_directory = std::filesystem::path(filename).parent_path().string();
36 if (manifest_directory.empty())
37 manifest_directory =
".";
39 YAML::Node root = YAML::LoadFile(filename);
41 resolve_includes(root, manifest_directory);
43 reject_unknown_keys(root,
"");
45 if (!root[
"material"])
46 throw std::runtime_error(
"input.yml must contain 'material' key");
48 const auto& material_node = root[
"material"];
49 if (material_node.IsScalar()) {
50 std::string material_path =
54 }
else if (material_node.IsMap()) {
55 nlohmann::json material_json;
57 if (material_node[
"lattice"]) {
58 nlohmann::json lattice;
59 const auto& lattice_node = material_node[
"lattice"];
60 if (lattice_node[
"type"])
61 lattice[
"type"] = lattice_node[
"type"].as<std::string>();
62 for (
const char* length_key : {
"a",
"b",
"c",
"alpha",
"beta",
"gamma"}) {
63 if (lattice_node[length_key])
64 lattice[length_key] = lattice_node[length_key].as<
Real>();
66 if (lattice_node[
"units"]) {
68 if (lattice_node[
"units"].IsMap()) {
69 nlohmann::json units_json;
70 if (lattice_node[
"units"][
"length"])
71 units_json[
"length"] =
72 lattice_node[
"units"][
"length"].as<std::string>();
73 if (lattice_node[
"units"][
"angle"])
74 units_json[
"angle"] = lattice_node[
"units"][
"angle"].as<std::string>();
75 lattice[
"units"] = units_json;
77 lattice[
"units"] = lattice_node[
"units"].as<std::string>();
80 if (lattice_node[
"vectors"]) {
81 nlohmann::json vectors;
82 const auto& vectors_node = lattice_node[
"vectors"];
83 for (
const char* row_key : {
"a",
"b",
"c"}) {
84 if (vectors_node[row_key]) {
85 nlohmann::json row = nlohmann::json::array();
86 for (
const auto& value : vectors_node[row_key])
87 row.push_back(value.as<
Real>());
88 vectors[row_key] = row;
91 if (vectors_node[
"alat"])
92 vectors[
"alat"] = vectors_node[
"alat"].as<
Real>();
93 if (vectors_node[
"units"]) {
99 if (vectors_node[
"units"].IsMap()) {
100 nlohmann::json units_json;
101 if (vectors_node[
"units"][
"length"])
102 units_json[
"length"] =
103 vectors_node[
"units"][
"length"].as<std::string>();
104 vectors[
"units"] = units_json;
106 vectors[
"units"] = vectors_node[
"units"].as<std::string>();
109 lattice[
"vectors"] = vectors;
111 material_json[
"lattice"] = lattice;
114 if (material_node[
"basis"]) {
115 nlohmann::json basis;
116 const auto& basis_node = material_node[
"basis"];
117 if (basis_node[
"elements"]) {
118 nlohmann::json elements = nlohmann::json::array();
119 for (
const auto& element_node : basis_node[
"elements"]) {
120 elements.push_back({{
"id", element_node[
"id"].as<
int>()},
121 {
"value", element_node[
"value"].as<std::string>()}});
123 basis[
"elements"] = elements;
125 if (basis_node[
"coordinates"]) {
126 nlohmann::json coordinates = nlohmann::json::array();
127 for (
const auto& coordinate_node : basis_node[
"coordinates"]) {
128 nlohmann::json coordinate_value = nlohmann::json::array();
129 for (
const auto& value : coordinate_node[
"value"])
130 coordinate_value.push_back(value.as<
Real>());
131 coordinates.push_back(
132 {{
"id", coordinate_node[
"id"].as<
int>()}, {
"value", coordinate_value}});
134 basis[
"coordinates"] = coordinates;
136 if (basis_node[
"units"])
137 basis[
"units"] = basis_node[
"units"].as<std::string>();
138 material_json[
"basis"] = basis;
143 throw std::runtime_error(
"'material' must be a file path (string) or inline object");
147 parse_model(root[
"model"], data);
150 if (root[
"method"]) {
151 parse_method(root[
"method"], manifest_directory, data);
154 if (root[
"control"]) {
155 parse_control(root[
"control"], data);
180 static void reject_unknown_keys(
const YAML::Node& node,
const std::string& schema_path) {
183 const std::vector<std::string>& known_keys =
184 generated::input_schema::allowed_keys(schema_path);
185 const std::string context = describe_schema_path(schema_path);
186 for (
auto iterator = node.begin(); iterator != node.end(); ++iterator) {
187 std::string key = iterator->first.as<std::string>();
188 if (std::find(known_keys.begin(), known_keys.end(), key) == known_keys.end()) {
189 throw std::runtime_error(
"Unknown key '" + key +
"' in " + context +
190 " -- check for a typo");
200 static std::string describe_schema_path(
const std::string& schema_path) {
201 if (schema_path.empty())
202 return "manifest root";
203 const std::string items_suffix =
"[]";
204 if (schema_path.size() > items_suffix.size() &&
206 schema_path.size() - items_suffix.size(), items_suffix.size(), items_suffix) == 0)
207 return schema_path.substr(0, schema_path.size() - items_suffix.size()) +
" entry";
215 static std::string parse_include_directive(
const std::string& value) {
216 const std::string prefix =
"include_yaml(";
217 if (value.size() < prefix.size() + 3)
219 if (value.substr(0, prefix.size()) != prefix)
221 if (value.back() !=
')')
224 std::string inner = value.substr(prefix.size(), value.size() - prefix.size() - 1);
225 if (inner.size() >= 2 && ((inner.front() ==
'"' && inner.back() ==
'"') ||
226 (inner.front() ==
'\'' && inner.back() ==
'\''))) {
227 inner = inner.substr(1, inner.size() - 2);
235 static void resolve_includes(YAML::Node node,
const std::string& base_directory) {
236 if (node.IsScalar()) {
237 std::string include_path = parse_include_directive(node.as<std::string>());
238 if (!include_path.empty()) {
240 YAML::Node included = YAML::LoadFile(full_path);
241 std::string include_directory =
242 std::filesystem::path(full_path).parent_path().string();
243 if (include_directory.empty())
244 include_directory =
".";
245 resolve_includes(included, include_directory);
248 }
else if (node.IsMap()) {
249 for (
auto iterator = node.begin(); iterator != node.end(); ++iterator) {
250 resolve_includes(iterator->second, base_directory);
252 }
else if (node.IsSequence()) {
253 for (
size_t index = 0; index < node.size(); index++) {
254 resolve_includes(node[index], base_directory);
263 static std::string read_lattice_length_units(
const nlohmann::json& lattice_node) {
264 if (lattice_node.contains(
"units")) {
265 if (lattice_node[
"units"].is_string()) {
266 return lattice_node[
"units"].get<std::string>();
268 if (lattice_node[
"units"].is_object() && lattice_node[
"units"].contains(
"length")) {
269 return lattice_node[
"units"][
"length"].get<std::string>();
278 if (!material_json.contains(
"lattice"))
279 throw std::runtime_error(
"Material JSON must contain 'lattice'");
281 const auto& lattice_node = material_json[
"lattice"];
282 std::string lattice_units = read_lattice_length_units(lattice_node);
283 Real length_to_bohr =
286 if (lattice_node.contains(
"vectors")) {
291 const auto& vectors_node = lattice_node[
"vectors"];
292 if (!vectors_node.contains(
"a") || !vectors_node.contains(
"b") ||
293 !vectors_node.contains(
"c"))
294 throw std::runtime_error(
"lattice.vectors must contain 'a', 'b' and 'c' rows");
299 std::string vector_units = vectors_node.contains(
"units")
300 ? read_lattice_length_units(vectors_node)
302 Real vector_to_bohr =
304 Real alat_multiplier =
305 vectors_node.contains(
"alat") ? vectors_node[
"alat"].get<
Real>() : 1.0;
308 const char* row_keys[3] = {
"a",
"b",
"c"};
309 for (
int row = 0; row < 3; ++row) {
310 const auto& row_node = vectors_node[row_keys[row]];
311 if (!row_node.is_array() || row_node.size() != 3)
312 throw std::runtime_error(std::string(
"lattice.vectors.") + row_keys[row] +
313 " must be an array of 3 numbers");
314 for (
int column = 0; column < 3; ++column) {
315 lattice_vectors_bohr[row][column] =
316 row_node[column].get<
Real>() * alat_multiplier * vector_to_bohr;
320 }
else if (lattice_node.contains(
"type") && lattice_node.contains(
"a")) {
321 std::string lattice_type = lattice_node[
"type"].get<std::string>();
322 for (
auto& character : lattice_type)
323 character =
static_cast<char>(toupper(character));
324 Real lattice_parameter_a = lattice_node[
"a"].get<
Real>();
326 if (lattice_type ==
"FCC" || lattice_type ==
"BCC" || lattice_type ==
"SC" ||
327 lattice_type ==
"CUB") {
333 if (lattice_type ==
"FCC") {
335 }
else if (lattice_type ==
"BCC") {
353 Real lattice_parameter_b = lattice_node.contains(
"b")
354 ? lattice_node[
"b"].get<
Real>()
355 : lattice_parameter_a;
356 Real lattice_parameter_c = lattice_node.contains(
"c")
357 ? lattice_node[
"c"].get<
Real>()
358 : lattice_parameter_a;
360 lattice_node.contains(
"alpha") ? lattice_node[
"alpha"].get<
Real>() : 90.0;
362 lattice_node.contains(
"beta") ? lattice_node[
"beta"].get<
Real>() : 90.0;
363 Real default_gamma = (lattice_type ==
"HEX") ? 120.0 : 90.0;
364 Real gamma_degrees = lattice_node.contains(
"gamma")
365 ? lattice_node[
"gamma"].get<
Real>()
370 lattice_parameter_b * length_to_bohr,
371 lattice_parameter_c * length_to_bohr,
378 throw std::runtime_error(
"Lattice must specify either 'type' + 'a' or 'vectors'");
381 if (!material_json.contains(
"basis"))
382 throw std::runtime_error(
"Material JSON must contain 'basis'");
384 const auto& basis_node = material_json[
"basis"];
385 if (!basis_node.contains(
"elements") || !basis_node.contains(
"coordinates"))
386 throw std::runtime_error(
"Basis must contain 'elements' and 'coordinates'");
388 const auto& elements_node = basis_node[
"elements"];
389 const auto& coordinates_node = basis_node[
"coordinates"];
391 if (elements_node.size() != coordinates_node.size())
392 throw std::runtime_error(
"elements and coordinates arrays must have same length");
394 std::map<std::string, int> species_map;
395 for (
const auto& element_node : elements_node) {
396 std::string element_symbol = element_node[
"value"].get<std::string>();
397 if (species_map.find(element_symbol) == species_map.end()) {
398 int species_index =
static_cast<int>(species_map.size());
399 species_map[element_symbol] = species_index;
401 species_data.
symbol = element_symbol;
402 species_data.
mass = 0.0;
404 data.
species.push_back(species_data);
409 for (
size_t element_index = 0; element_index < elements_node.size(); element_index++) {
410 std::string element_symbol = elements_node[element_index][
"value"].get<std::string>();
411 const auto& coordinate_value = coordinates_node[element_index][
"value"];
415 coordinate_value[1].get<Real>(),
416 coordinate_value[2].get<
Real>()};
417 data.
atoms.push_back(atom_data);
425 if (basis_node.contains(
"constraints")) {
426 for (
const auto& constraint_node : basis_node[
"constraints"]) {
427 for (
size_t atom_index = 0; atom_index < coordinates_node.size(); ++atom_index) {
428 if (coordinates_node[atom_index][
"id"] != constraint_node[
"id"])
430 for (
int axis = 0; axis < 3; ++axis)
431 data.
atoms[atom_index].movable_components[axis] =
432 constraint_node[
"value"][axis].get<
bool>();
439 static void parse_model(
const YAML::Node& model_node,
InputData& data) {
446 reject_unknown_keys(model_node,
"model");
449 if (model_node[
"functional"])
451 if (model_node[
"dispersion"]) {
452 std::string dispersion_name = model_node[
"dispersion"].as<std::string>();
453 for (
auto& character : dispersion_name)
454 character = static_cast<char>(tolower(character));
461 if (dispersion_name !=
"none" && dispersion_name !=
"d3bj")
462 throw std::runtime_error(
"model.dispersion must be 'none' or 'd3bj', got '" +
463 dispersion_name +
"'");
468 static void parse_method(
const YAML::Node& method_node,
469 const std::string& manifest_directory,
475 reject_unknown_keys(method_node,
"method");
477 if (method_node[
"pseudopotentials"]) {
478 const auto& pseudopotentials_node = method_node[
"pseudopotentials"];
479 for (
auto iterator = pseudopotentials_node.begin();
480 iterator != pseudopotentials_node.end();
482 std::string element = iterator->first.as<std::string>();
483 std::string pseudopotential_filename = iterator->second.as<std::string>();
484 std::string pseudopotential_path =
488 for (
auto& species_data : data.species) {
489 if (species_data.symbol == element) {
490 species_data.pseudopotential_file = pseudopotential_path;
496 "Pseudopotential specified for element '{}' but not found in material "
502 if (!data.species.empty() && !data.species[0].pseudopotential_file.empty()) {
503 data.pseudopotential_directory =
504 std::filesystem::path(data.species[0].pseudopotential_file)
507 if (data.pseudopotential_directory.empty())
508 data.pseudopotential_directory =
".";
512 if (method_node[
"cutoffs"]) {
513 const auto& cutoffs_node = method_node[
"cutoffs"];
514 reject_unknown_keys(cutoffs_node,
"method.cutoffs");
515 if (cutoffs_node[
"wavefunction"])
516 data.wavefunction_energy_cutoff = cutoffs_node[
"wavefunction"].as<
Real>();
517 if (cutoffs_node[
"density"])
518 data.charge_density_energy_cutoff = cutoffs_node[
"density"].as<
Real>();
521 YAML::Node self_consistent_field_node;
522 if (method_node[
"self_consistent_field"]) {
523 self_consistent_field_node = method_node[
"self_consistent_field"];
524 }
else if (method_node[
"scf"]) {
525 self_consistent_field_node = method_node[
"scf"];
528 if (self_consistent_field_node) {
536 if (self_consistent_field_node[
"nspin"])
537 throw std::runtime_error(
538 "'nspin' is not a manifest key; use method.scf.number_of_spin_components");
545 reject_unknown_keys(self_consistent_field_node,
"method.scf");
547 if (self_consistent_field_node[
"convergence_threshold"])
548 data.convergence_threshold =
549 self_consistent_field_node[
"convergence_threshold"].as<
Real>();
550 if (self_consistent_field_node[
"max_iterations"])
551 data.maximum_iterations = self_consistent_field_node[
"max_iterations"].as<
int>();
552 if (self_consistent_field_node[
"diagonalization"])
553 data.diagonalization_method =
554 self_consistent_field_node[
"diagonalization"].as<std::string>();
555 if (self_consistent_field_node[
"number_of_bands"])
556 data.number_of_bands = self_consistent_field_node[
"number_of_bands"].as<
int>();
557 if (self_consistent_field_node[
"total_charge"])
558 data.total_charge = self_consistent_field_node[
"total_charge"].as<
Real>();
560 if (self_consistent_field_node[
"hubbard"]) {
561 const auto& hubbard_node = self_consistent_field_node[
"hubbard"];
562 if (!hubbard_node.IsSequence())
563 throw std::runtime_error(
564 "method.scf.hubbard must be a list of {species, orbital_label, u_ev}");
565 for (
const auto& entry : hubbard_node) {
566 reject_unknown_keys(entry,
"method.scf.hubbard[]");
567 if (!entry[
"species"] || !entry[
"orbital_label"] || !entry[
"u_ev"])
568 throw std::runtime_error(
569 "each method.scf.hubbard entry needs species, orbital_label, and "
571 Real u_ev = entry[
"u_ev"].as<
Real>();
572 data.hubbard.push_back({entry[
"species"].as<std::string>(),
573 entry[
"orbital_label"].as<std::string>(),
578 if (self_consistent_field_node[
"hubbard_projectors"]) {
579 std::string hubbard_projector_type =
580 self_consistent_field_node[
"hubbard_projectors"].as<std::string>();
581 if (hubbard_projector_type !=
"atomic" && hubbard_projector_type !=
"ortho-atomic")
582 throw std::runtime_error(
583 "method.scf.hubbard_projectors must be 'atomic' or 'ortho-atomic', got '" +
584 hubbard_projector_type +
"'");
585 data.hubbard_projector_type = hubbard_projector_type;
588 if (self_consistent_field_node[
"hubbard_fixed_occupation_iterations"]) {
589 int fixed_iterations =
590 self_consistent_field_node[
"hubbard_fixed_occupation_iterations"].as<
int>();
591 if (fixed_iterations < 0)
592 throw std::runtime_error(
593 "method.scf.hubbard_fixed_occupation_iterations must be >= 0, got " +
594 std::to_string(fixed_iterations));
595 data.hubbard_fixed_occupation_iterations = fixed_iterations;
598 if (self_consistent_field_node[
"hubbard_starting_eigenvalues"]) {
599 const auto& seeding_node =
600 self_consistent_field_node[
"hubbard_starting_eigenvalues"];
601 if (!seeding_node.IsSequence())
602 throw std::runtime_error(
"method.scf.hubbard_starting_eigenvalues must be a "
603 "list of {species, eigenvalue, spin, value}");
604 for (
const auto& entry : seeding_node) {
605 reject_unknown_keys(entry,
"method.scf.hubbard_starting_eigenvalues[]");
606 if (!entry[
"species"] || !entry[
"eigenvalue"] || !entry[
"spin"] ||
608 throw std::runtime_error(
609 "each method.scf.hubbard_starting_eigenvalues entry needs species, "
610 "eigenvalue, spin, and value");
620 entry[
"species"].as<std::string>(),
621 entry[
"eigenvalue"].as<int>(),
622 entry[
"spin"].as<int>(),
623 entry[
"value"].as<Real>(),
624 "method.scf.hubbard_starting_eigenvalues"));
628 if (self_consistent_field_node[
"davidson"]) {
629 const auto& davidson_node = self_consistent_field_node[
"davidson"];
630 reject_unknown_keys(davidson_node,
"method.scf.davidson");
631 if (davidson_node[
"basis_expansion_factor"]) {
632 int factor = davidson_node[
"basis_expansion_factor"].as<
int>();
634 throw std::runtime_error(
635 "method.scf.davidson.basis_expansion_factor must be at least 2: the "
636 "working basis has to be able to grow past the bands it is solving "
638 std::to_string(factor));
640 data.davidson_basis_expansion_factor = factor;
641 data.davidson_basis_expansion_factor_set_by_deck =
true;
645 if (self_consistent_field_node[
"mixing"]) {
646 const auto& mixing_node = self_consistent_field_node[
"mixing"];
647 reject_unknown_keys(mixing_node,
"method.scf.mixing");
648 if (mixing_node[
"mode"])
649 data.mixing_mode = mixing_node[
"mode"].as<std::string>();
650 if (mixing_node[
"beta"])
651 data.mixing_beta = mixing_node[
"beta"].as<
Real>();
652 if (mixing_node[
"ndim"])
653 data.mixing_history_size = mixing_node[
"ndim"].as<
int>();
654 if (mixing_node[
"preconditioning"])
655 data.mixing_preconditioning = mixing_node[
"preconditioning"].as<std::string>();
656 if (mixing_node[
"q0"])
657 data.mixing_q0 = mixing_node[
"q0"].as<
Real>();
668 if (self_consistent_field_node[
"number_of_spin_components"]) {
669 data.number_of_spin_components =
670 self_consistent_field_node[
"number_of_spin_components"].as<
int>();
671 if (data.number_of_spin_components != 1 && data.number_of_spin_components != 2)
672 throw std::runtime_error(
673 "number_of_spin_components must be 1 (unpolarized) or 2 (collinear "
675 std::to_string(data.number_of_spin_components));
678 if (self_consistent_field_node[
"starting_magnetization"]) {
679 const auto& magnetization_node =
680 self_consistent_field_node[
"starting_magnetization"];
681 if (!magnetization_node.IsMap())
682 throw std::runtime_error(
683 "starting_magnetization must be a map of species symbol to value, "
685 data.starting_magnetization.assign(data.species.size(), 0.0);
686 for (
auto iterator = magnetization_node.begin();
687 iterator != magnetization_node.end();
689 std::string species_symbol = iterator->first.as<std::string>();
690 Real magnetization_value = iterator->second.as<
Real>();
692 for (
size_t species_index = 0; species_index < data.species.size();
694 if (data.species[species_index].symbol == species_symbol) {
695 data.starting_magnetization[species_index] = magnetization_value;
700 throw std::runtime_error(
"starting_magnetization specified for species '" +
702 "' which is not in the material basis");
714 if (self_consistent_field_node[
"atomic_starting_magnetization"]) {
715 const auto& atomic_node =
716 self_consistent_field_node[
"atomic_starting_magnetization"];
717 if (!atomic_node.IsSequence())
718 throw std::runtime_error(
719 "atomic_starting_magnetization must be a list with one value per "
720 "atom of the material basis, e.g. [0.5, -0.5]");
721 if (self_consistent_field_node[
"starting_magnetization"])
722 throw std::runtime_error(
"give either starting_magnetization (per species) or "
723 "atomic_starting_magnetization (per atom), not both");
724 if (atomic_node.size() != data.atoms.size())
725 throw std::runtime_error(
"atomic_starting_magnetization lists " +
726 std::to_string(atomic_node.size()) +
727 " values but the material basis has " +
728 std::to_string(data.atoms.size()) +
" atoms");
729 data.atomic_starting_magnetization.clear();
730 for (
const auto& value_node : atomic_node) {
731 Real magnetization_value = value_node.as<
Real>();
732 if (magnetization_value < -1.0 || magnetization_value > 1.0)
733 throw std::runtime_error(
734 "atomic_starting_magnetization values must be in [-1, 1], got " +
735 std::to_string(magnetization_value));
736 data.atomic_starting_magnetization.push_back(magnetization_value);
740 if (self_consistent_field_node[
"smearing"]) {
741 const auto& smearing_node = self_consistent_field_node[
"smearing"];
742 reject_unknown_keys(smearing_node,
"method.scf.smearing");
743 if (smearing_node[
"type"])
744 data.smearing = smearing_node[
"type"].as<std::string>();
745 if (smearing_node[
"degauss"])
746 data.smearing_width = smearing_node[
"degauss"].as<
Real>();
747 if (smearing_node[
"order"]) {
748 data.methfessel_paxton_order = smearing_node[
"order"].as<
int>();
749 const std::string& kind = data.smearing;
750 if (kind !=
"methfessel-paxton" && kind !=
"m-p" && kind !=
"mp")
751 throw std::runtime_error(
752 "method.scf.smearing.order applies to Methfessel-Paxton smearing "
753 "only; smearing type is '" +
755 if (data.methfessel_paxton_order < 1)
756 throw std::runtime_error(
"method.scf.smearing.order must be >= 1, got " +
757 std::to_string(data.methfessel_paxton_order));
761 if (self_consistent_field_node[
"kgrid"]) {
762 const auto& kpoint_grid_node = self_consistent_field_node[
"kgrid"];
763 reject_unknown_keys(kpoint_grid_node,
"method.scf.kgrid");
764 if (kpoint_grid_node[
"dimensions"]) {
765 auto dimensions_node = kpoint_grid_node[
"dimensions"];
766 data.kpoint_grid_dimension_1 = dimensions_node[0].as<
int>();
767 data.kpoint_grid_dimension_2 = dimensions_node[1].as<
int>();
768 data.kpoint_grid_dimension_3 = dimensions_node[2].as<
int>();
770 if (kpoint_grid_node[
"shifts"]) {
771 auto shifts_node = kpoint_grid_node[
"shifts"];
772 data.kpoint_grid_shift_1 = shifts_node[0].as<
int>();
773 data.kpoint_grid_shift_2 = shifts_node[1].as<
int>();
774 data.kpoint_grid_shift_3 = shifts_node[2].as<
int>();
776 if (kpoint_grid_node[
"ibz_reduction"]) {
777 data.ibz_reduction_method = kpoint_grid_node[
"ibz_reduction"].as<std::string>();
782 if (method_node[
"band_structure"]) {
783 const auto& bs_node = method_node[
"band_structure"];
784 reject_unknown_keys(bs_node,
"method.band_structure");
785 if (bs_node[
"number_of_bands"])
786 data.number_of_bands = bs_node[
"number_of_bands"].as<
int>();
802 template <
typename T>
804 read_positive_interpolation_value(
const YAML::Node& block,
const char* key, T& value) {
808 value = block[key].as<T>();
809 if (!(value > T{0})) {
810 throw std::runtime_error(std::string(
"control.interpolation.") + key +
811 " must be positive.");
816 static void read_interpolation_choice(
const YAML::Node& block,
818 const std::vector<std::string>& choices,
819 std::string& value) {
823 value = block[key].as<std::string>();
824 if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
829 for (std::size_t index = 0; index < choices.size(); index++) {
830 named += (index == 0 ?
"" : (index + 1 == choices.size() ?
" or " :
", "));
831 named +=
"'" + choices[index] +
"'";
833 throw std::runtime_error(std::string(
"control.interpolation.") + key +
" must be " + named +
834 ", got '" + value +
"'.");
842 read_interpolation_mesh(
const YAML::Node& block,
const char* key, std::array<int, 3>& mesh) {
843 const YAML::Node node = block[key];
847 const std::string refusal = std::string(
"control.interpolation.") + key +
848 " must be three positive integers [n1, n2, n3].";
849 if (!node.IsSequence() || node.size() != 3) {
850 throw std::runtime_error(refusal);
852 for (std::size_t axis = 0; axis < 3; axis++) {
853 mesh[axis] = node[axis].as<
int>();
854 if (mesh[axis] <= 0) {
855 throw std::runtime_error(refusal);
861 static constexpr std::array<const char*, 6> interpolation_build_step_keys = {
864 "projection_centers",
865 "projection_decay_bohr",
868 static constexpr std::array<const char*, 9> interpolation_evaluation_step_keys = {
871 "disentanglement_maximum_iterations",
872 "disentanglement_convergence_tolerance",
874 "localization_maximum_iterations",
875 "projectability_minimum",
876 "projectability_maximum",
877 "frozen_window_above_fermi_ev"};
880 static void parse_interpolation_build_step(
const YAML::Node& interpolation_node,
881 InterpolationSpecification& specification) {
882 read_interpolation_mesh(interpolation_node,
"grid", specification.grid);
883 if (interpolation_node[
"bands"]) {
884 const YAML::Node node = interpolation_node[
"bands"];
885 if (!node.IsSequence() || node.size() != 2) {
886 throw std::runtime_error(
887 "control.interpolation.bands must be [first, last], two 1-based band indices.");
889 specification.bands[0] = node[0].as<
int>();
890 specification.bands[1] = node[1].as<
int>();
891 if (specification.bands[0] < 1 || specification.bands[1] < specification.bands[0]) {
892 throw std::runtime_error(
893 "control.interpolation.bands must be [first, last] with 1 <= first <= last, "
895 std::to_string(specification.bands[0]) +
", " +
896 std::to_string(specification.bands[1]) +
"].");
899 read_positive_interpolation_value(
900 interpolation_node,
"projection_decay_bohr", specification.projection_decay_bohr);
901 if (interpolation_node[
"projection_centers"]) {
902 for (
const auto& center_node : interpolation_node[
"projection_centers"]) {
903 if (!center_node.IsSequence() || center_node.size() != 3) {
904 throw std::runtime_error(
905 "control.interpolation.projection_centers: each entry must be three "
906 "crystal (fractional) coordinates.");
908 specification.projection_centers.emplace_back(center_node[0].as<Real>(),
909 center_node[1].as<Real>(),
910 center_node[2].as<Real>());
913 if (interpolation_node[
"trial_shells"]) {
914 const YAML::Node node = interpolation_node[
"trial_shells"];
916 throw std::runtime_error(
917 "control.interpolation.trial_shells must map each species to a list of "
918 "0-based shell indices, e.g. {Si: [0, 2]}.");
920 for (
const auto& entry : node) {
921 const std::string species = entry.first.as<std::string>();
922 if (!entry.second.IsSequence() || entry.second.size() == 0) {
923 throw std::runtime_error(
"control.interpolation.trial_shells." + species +
924 " must be a non-empty list of 0-based shell indices.");
926 std::vector<int> shells;
927 for (
const auto& index_node : entry.second) {
928 const int index = index_node.as<
int>();
930 std::find(shells.begin(), shells.end(), index) != shells.end()) {
931 throw std::runtime_error(
932 "control.interpolation.trial_shells." + species +
933 " must list distinct, non-negative shell indices.");
935 shells.push_back(index);
937 specification.trial_shells[species] = shells;
940 if (!specification.trial_shells.empty() && !specification.projection_centers.empty()) {
941 throw std::runtime_error(
942 "control.interpolation.trial_shells chooses among the pseudo-atomic orbitals and "
943 "projection_centers replaces them with hydrogenic ones; give one or the other.");
945 if (interpolation_node[
"export_formats"]) {
946 for (
const auto& format_node : interpolation_node[
"export_formats"]) {
947 const std::string format = format_node.as<std::string>();
948 if (format !=
"wannier90") {
949 throw std::runtime_error(
950 "control.interpolation.export_formats entries must be 'wannier90', got "
952 format +
"' (refused here rather than after the NSCF has run).");
954 specification.export_formats.push_back(format);
960 static void parse_interpolation_evaluation_step(
const YAML::Node& interpolation_node,
961 InterpolationSpecification& specification) {
962 read_interpolation_mesh(interpolation_node,
"dos_mesh", specification.dos_mesh);
963 read_interpolation_choice(interpolation_node,
965 {
"none",
"projectability"},
966 specification.disentanglement);
967 if (interpolation_node[
"projectability_minimum"]) {
968 specification.projectability_minimum =
969 interpolation_node[
"projectability_minimum"].as<
Real>();
971 if (interpolation_node[
"projectability_maximum"]) {
972 specification.projectability_maximum =
973 interpolation_node[
"projectability_maximum"].as<
Real>();
975 if (interpolation_node[
"frozen_window_above_fermi_ev"]) {
981 const YAML::Node node = interpolation_node[
"frozen_window_above_fermi_ev"];
982 const std::string text = node.IsScalar() ? node.as<std::string>() :
"";
983 if (text ==
"auto") {
984 specification.frozen_window_is_automatic =
true;
987 specification.frozen_window_above_fermi_ev = node.as<
Real>();
988 }
catch (
const YAML::Exception&) {
989 throw std::runtime_error(
990 "control.interpolation.frozen_window_above_fermi_ev must be a number in "
991 "electronvolts or 'auto', got '" +
994 specification.frozen_window_is_automatic =
false;
997 read_positive_interpolation_value(interpolation_node,
998 "localization_maximum_iterations",
999 specification.localization_maximum_iterations);
1000 read_positive_interpolation_value(interpolation_node,
1001 "disentanglement_maximum_iterations",
1002 specification.disentanglement_maximum_iterations);
1003 read_positive_interpolation_value(interpolation_node,
1004 "disentanglement_convergence_tolerance",
1005 specification.disentanglement_convergence_tolerance);
1006 if (!(specification.projectability_minimum < specification.projectability_maximum)) {
1007 throw std::runtime_error(
"control.interpolation.projectability_minimum must be below "
1008 "projectability_maximum, got " +
1009 std::to_string(specification.projectability_minimum) +
1011 std::to_string(specification.projectability_maximum) +
".");
1013 read_interpolation_choice(interpolation_node,
1015 {
"auto",
"maximal",
"none"},
1016 specification.localization);
1027 template <std::
size_t Count>
1028 static void refuse_misplaced_interpolation_keys(
const YAML::Node& interpolation_node,
1029 const std::array<const char*, Count>& keys,
1030 const std::string& calculation,
1031 const char* where_it_belongs) {
1032 for (
const char* key : keys) {
1033 if (interpolation_node[key]) {
1034 throw std::runtime_error(std::string(
"control.interpolation.") + key +
1035 " has no effect on calculation: " + calculation +
1036 ", so it is refused rather than ignored. " +
1042 static void parse_interpolation(
const YAML::Node& interpolation_node, InputData& data) {
1043 reject_unknown_keys(interpolation_node,
"control.interpolation");
1044 InterpolationSpecification& specification = data.interpolation;
1046 if (!interpolation_node[
"method"]) {
1047 throw std::runtime_error(
1048 "control.interpolation.method is required (supported: wannier).");
1050 specification.method = interpolation_node[
"method"].as<std::string>();
1051 if (specification.method !=
"wannier") {
1052 throw std::runtime_error(
"control.interpolation.method must be 'wannier', got '" +
1053 specification.method +
"'.");
1055 parse_interpolation_build_step(interpolation_node, specification);
1056 parse_interpolation_evaluation_step(interpolation_node, specification);
1058 const bool builds = data.calculation ==
"non_self_consistent_field";
1059 const bool evaluates = data.calculation ==
"interpolation";
1060 if (!builds && !evaluates) {
1061 throw std::runtime_error(
1062 "control.interpolation is set on calculation: " + data.calculation +
1063 ", which would ignore it. It is built by calculation: nscf "
1064 "(non_self_consistent_field) and evaluated by calculation: interpolation "
1065 "(run with q3_utility interpolate).");
1068 refuse_misplaced_interpolation_keys(
1070 interpolation_evaluation_step_keys,
1072 "It is read when the model is evaluated -- put it in the manifest you pass to "
1073 "q3_utility interpolate.");
1075 refuse_misplaced_interpolation_keys(
1077 interpolation_build_step_keys,
1079 "It is read when the record is captured -- put it in the NSCF manifest that "
1080 "builds the record.");
1083 if (builds && !specification.has_grid()) {
1084 throw std::runtime_error(
1085 "control.interpolation.grid is required on the NSCF build step: the k-points "
1086 "arrive as an explicit control.kpoints_list, which carries no grid shape of its "
1087 "own, and k+b is resolved onto the grid by exact integer indexing.");
1091 static void parse_control(
const YAML::Node& control_node, InputData& data) {
1092 reject_unknown_keys(control_node,
"control");
1094 if (control_node[
"calculation"]) {
1095 std::string calculation_type = control_node[
"calculation"].as<std::string>();
1096 if (calculation_type ==
"scf")
1097 calculation_type =
"self_consistent_field";
1098 if (calculation_type ==
"nscf")
1099 calculation_type =
"non_self_consistent_field";
1100 if (calculation_type ==
"bands")
1101 calculation_type =
"band_structure";
1102 if (calculation_type ==
"relax")
1103 calculation_type =
"relaxation";
1104 if (calculation_type ==
"dos")
1105 calculation_type =
"density_of_states";
1106 data.calculation = calculation_type;
1108 if (control_node[
"outdir"])
1109 data.output_directory = control_node[
"outdir"].as<std::string>();
1110 if (control_node[
"prefix"])
1111 data.prefix = control_node[
"prefix"].as<std::string>();
1112 if (control_node[
"verbosity"]) {
1119 std::string verbosity = control_node[
"verbosity"].as<std::string>();
1120 if (verbosity !=
"default" && verbosity !=
"high")
1121 throw std::runtime_error(
"control.verbosity must be 'default' or 'high', got '" +
1123 data.verbosity = verbosity;
1125 if (control_node[
"disable_symmetry"])
1126 data.disable_symmetry = control_node[
"disable_symmetry"].as<
bool>();
1127 if (control_node[
"nonlocal_stress_derivative_method"])
1128 data.nonlocal_stress_derivative_method =
1129 control_node[
"nonlocal_stress_derivative_method"].as<std::string>();
1130 if (control_node[
"number_of_kpoint_pools"])
1131 data.number_of_kpoint_pools = control_node[
"number_of_kpoint_pools"].as<
int>();
1132 if (control_node[
"use_deterministic_fft_planning"])
1133 data.use_deterministic_fft_planning =
1134 control_node[
"use_deterministic_fft_planning"].as<
bool>();
1135 if (control_node[
"use_exact_wavefunction_grid"])
1136 data.use_exact_wavefunction_grid =
1137 control_node[
"use_exact_wavefunction_grid"].as<
bool>();
1138 if (control_node[
"isolated_system_correction"]) {
1139 std::string isolated_system_correction =
1140 control_node[
"isolated_system_correction"].as<std::string>();
1141 if (isolated_system_correction !=
"none" &&
1142 isolated_system_correction !=
"makov-payne" &&
1143 isolated_system_correction !=
"martyna-tuckerman")
1144 throw std::runtime_error(
1145 "control.isolated_system_correction must be 'none', 'makov-payne', or "
1146 "'martyna-tuckerman', got '" +
1147 isolated_system_correction +
"'");
1148 data.isolated_system_correction = isolated_system_correction;
1150 if (control_node[
"mean_field_export"]) {
1151 std::string mean_field_export = control_node[
"mean_field_export"].as<std::string>();
1152 if (mean_field_export !=
"none" && mean_field_export !=
"berkeleygw")
1153 throw std::runtime_error(
1154 "control.mean_field_export must be 'none' or 'berkeleygw', got '" +
1155 mean_field_export +
"'");
1156 data.mean_field_export = mean_field_export;
1158 if (control_node[
"mean_field_export_kleinman_bylander_projectors"])
1159 data.mean_field_export_kleinman_bylander_projectors =
1160 control_node[
"mean_field_export_kleinman_bylander_projectors"].as<
bool>();
1172 if (control_node[
"band_path"]) {
1173 const auto& band_path_node = control_node[
"band_path"];
1174 data.band_path.clear();
1176 if (band_path_node.IsSequence()) {
1177 for (
const auto& vertex_node : band_path_node) {
1179 Vector3D coordinates{0.0, 0.0, 0.0};
1180 bool has_coordinates =
false;
1182 if (vertex_node.IsScalar()) {
1184 label = vertex_node.as<std::string>();
1185 }
else if (vertex_node.IsMap()) {
1187 reject_unknown_keys(vertex_node,
"control.band_path[]");
1188 if (vertex_node[
"label"])
1189 label = vertex_node[
"label"].as<std::string>();
1190 if (vertex_node[
"coordinates"]) {
1191 has_coordinates =
true;
1192 const auto& coords_node = vertex_node[
"coordinates"];
1193 coordinates.x = coords_node[0].as<
Real>();
1194 coordinates.y = coords_node[1].as<
Real>();
1195 coordinates.z = coords_node[2].as<
Real>();
1199 if (!label.empty()) {
1208 if (!has_coordinates && data.calculation ==
"interpolation") {
1209 throw std::runtime_error(
1210 "control.band_path vertex '" + label +
1211 "' has no coordinates. On calculation: interpolation every "
1212 "vertex needs `coordinates: [k1, k2, k3]` (crystal), or let "
1213 "q3.workflows.interpolation resolve the labels into an explicit "
1214 "control.kpoints_list.");
1216 data.band_path.emplace_back(label, coordinates);
1222 if (control_node[
"band_path_points_per_segment"])
1223 data.band_path_points_per_segment =
1224 control_node[
"band_path_points_per_segment"].as<
int>();
1233 if (control_node[
"kpoints_list"]) {
1234 const auto& kpoints_list_node = control_node[
"kpoints_list"];
1235 data.kpoints_list.clear();
1237 if (kpoints_list_node.IsSequence()) {
1238 for (
const auto& kpoint_node : kpoints_list_node) {
1239 if (kpoint_node.IsSequence() && kpoint_node.size() == 4) {
1240 Vector3D coordinates{kpoint_node[0].as<
Real>(),
1241 kpoint_node[1].as<Real>(),
1242 kpoint_node[2].as<
Real>()};
1243 Real weight = kpoint_node[3].as<
Real>();
1244 data.kpoints_list.emplace_back(coordinates, weight);
1251 if (control_node[
"dos_broadening"])
1252 data.dos_broadening = control_node[
"dos_broadening"].as<
Real>();
1253 if (control_node[
"dos_energy_min"])
1254 data.dos_energy_min = control_node[
"dos_energy_min"].as<
Real>();
1255 if (control_node[
"dos_energy_max"])
1256 data.dos_energy_max = control_node[
"dos_energy_max"].as<
Real>();
1257 if (control_node[
"dos_energy_steps"])
1258 data.dos_energy_steps = control_node[
"dos_energy_steps"].as<
int>();
1261 if (control_node[
"compute_projections"])
1262 data.compute_projections = control_node[
"compute_projections"].as<
bool>();
1264 if (control_node[
"interpolation"])
1265 parse_interpolation(control_node[
"interpolation"], data);
1268 if (control_node[
"relax_max_steps"])
1269 data.relax_maximum_steps = control_node[
"relax_max_steps"].as<
int>();
1270 if (control_node[
"relax_force_threshold"])
1271 data.relax_force_threshold = control_node[
"relax_force_threshold"].as<
Real>();
1272 if (control_node[
"relax_energy_threshold"])
1273 data.relax_energy_threshold = control_node[
"relax_energy_threshold"].as<
Real>();