Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * The structure a run used, as an ESSE material, for results.json.
5 *
6 * The cell is the one the engine built -- any Bravais index, explicit vectors
7 * or a structure substituted with `--material` -- in its own Cartesian frame,
8 * so a reader gets back exactly the geometry the energy, forces and stress
9 * belong to. That is what lets q3.workflows start a relaxation from any input
10 * the engine reads, a pw.x deck with any ibrav included, without a reader of
11 * its own: it runs the input once and continues from this material.
12 */
13
14#include <algorithm>
15#include <cctype>
16#include <cmath>
17#include <string>
18#include <utility>
19
20#include <nlohmann/json.hpp>
21
22#include "io/input/data.hpp"
26
27namespace q3 {
28
29/**
30 * A species label split the ESSE way: the chemical element, and what tells
31 * this species apart from others of the same element ("Cr1" -> "Cr", 1).
32 *
33 * ESSE's basis.elements holds only periodic-table symbols; the sublattice a
34 * pw.x deck writes as Cr1/Cr2 goes in basis.labels. The element is the
35 * longest periodic-table symbol (two letters, else one) the name starts
36 * with; the label is the rest -- an integer when it is all digits, otherwise
37 * the text -- and null for a species named by its element alone. A name that
38 * starts with no element is kept whole as the element, unlabelled, for the
39 * results schema to refuse.
40 */
41[[nodiscard]] inline std::pair<std::string, nlohmann::json>
42element_and_label_of_species(const std::string& species_symbol) {
43 for (size_t length : {size_t{2}, size_t{1}}) {
44 if (species_symbol.size() < length)
45 continue;
46 const std::string element = species_symbol.substr(0, length);
47 if (!std::all_of(element.begin(), element.end(), [](unsigned char character) {
48 return std::isalpha(character);
49 }))
50 continue;
52 continue;
53 const std::string rest = species_symbol.substr(length);
54 if (rest.empty())
55 return {element, nullptr};
56 const bool all_digits = std::all_of(rest.begin(), rest.end(), [](unsigned char character) {
57 return std::isdigit(character);
58 });
59 if (all_digits && rest.size() < 10)
60 return {element, std::stoi(rest)};
61 return {element, rest};
62 }
63 return {species_symbol, nullptr};
64}
65
66/**
67 * The ESSE material of *input*: lattice (vectors in angstrom, with ESSE's lengths
68 * and angles), and basis (chemical elements, `labels` for the sites whose
69 * species is more than its element, crystal coordinates, and `constraints`
70 * -- per site, `true` where a Cartesian component may move, as mat3ra-made
71 * writes them).
72 */
73[[nodiscard]] inline nlohmann::json build_material_json(const InputData& input) {
74 const Matrix3x3 vectors_bohr =
76 const char* row_keys[3] = {"a", "b", "c"};
77 nlohmann::json vectors = {{"units", "angstrom"}};
78 Real lengths_angstrom[3];
79 for (int row = 0; row < 3; ++row) {
80 const Vector3D vector_angstrom = vectors_bohr[row] * constants::BOHR_RADIUS_ANGSTROM;
81 vectors[row_keys[row]] = {vector_angstrom[0], vector_angstrom[1], vector_angstrom[2]};
82 lengths_angstrom[row] = vector_angstrom.norm();
83 }
84 auto angle_degrees = [&](int first, int second) {
85 const Real cosine = vectors_bohr[first].dot(vectors_bohr[second]) /
86 (vectors_bohr[first].norm() * vectors_bohr[second].norm());
87 return std::acos(cosine) * 180.0 / constants::pi;
88 };
89
90 nlohmann::json basis;
91 basis["units"] = "crystal";
92 basis["elements"] = nlohmann::json::array();
93 basis["coordinates"] = nlohmann::json::array();
94 basis["constraints"] = nlohmann::json::array();
95 nlohmann::json labels = nlohmann::json::array();
96 for (size_t atom_index = 0; atom_index < input.atoms.size(); ++atom_index) {
97 const Atom& atom = input.atoms[atom_index];
98 const int site_id = static_cast<int>(atom_index) + 1;
99 auto [element, label] =
101 basis["elements"].push_back({{"id", site_id}, {"value", element}});
102 if (!label.is_null())
103 labels.push_back({{"id", site_id}, {"value", label}});
104 basis["coordinates"].push_back(
105 {{"id", site_id}, {"value", {atom.position[0], atom.position[1], atom.position[2]}}});
106 basis["constraints"].push_back({{"id", site_id},
107 {"value",
108 {atom.movable_components[0],
109 atom.movable_components[1],
110 atom.movable_components[2]}}});
111 }
112 if (!labels.empty())
113 basis["labels"] = labels;
114
115 return {{"name", input.prefix},
116 {"metadata", nlohmann::json::object()},
117 {"lattice",
118 {{"a", lengths_angstrom[0]},
119 {"b", lengths_angstrom[1]},
120 {"c", lengths_angstrom[2]},
121 {"alpha", angle_degrees(1, 2)},
122 {"beta", angle_degrees(0, 2)},
123 {"gamma", angle_degrees(0, 1)},
124 {"units", {{"length", "angstrom"}, {"angle", "degree"}}},
125 {"vectors", vectors}}},
126 {"basis", basis}};
127}
128
129} // namespace q3
Matrix3x3 lattice_vectors_bohr() const
Definition crystal.hpp:324
static int atomic_number_by_element_symbol(const std::string &symbol)
constexpr Real pi
Definition constants.hpp:24
constexpr Real BOHR_RADIUS_ANGSTROM
Bohr radius in Ångströms (BOHR_RADIUS_SI * 1e10; BOHR_RADIUS_ANGSTROM)
Crystal build_crystal_from_input(const InputData &input, const std::vector< AtomSpecies > &species)
nlohmann::json build_material_json(const InputData &input)
Definition init.hpp:73
std::pair< std::string, nlohmann::json > element_and_label_of_species(const std::string &species_symbol)
Definition init.hpp:42
double Real
int species_index
Definition atom.hpp:20
std::array< bool, 3 > movable_components
Which Cartesian components a relaxation may move (pw.x if_pos; ESSE basis.constraints).
Definition atom.hpp:23
Vector3D position
Definition atom.hpp:21
Parsed input data — holds raw values read from input files.
Definition data.hpp:27
std::vector< AtomSpecies > species
Definition data.hpp:153
std::string prefix
Definition data.hpp:178
std::vector< Atom > atoms
Definition data.hpp:156
3×3 matrix stored row-major: row i = mat[i]
Real norm() const
Definition vector_3d.hpp:68