Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
results.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * The results of a finished flavor run, as the struct generated from
5 * schemas/q3/results.json (`generated::Results`).
6 *
7 * A flavor fills that struct directly: its ESSE records under
8 * properties.records, the Q3 blocks beside them and the runtime log. There is
9 * no second, hand-written copy of the results to convert from. What is here is
10 * the little the generated struct cannot say for itself:
11 *
12 * - properties.records is a list of ESSE records of different types (the
13 * schema's oneOf), so the generated struct holds them as JSON;
14 * find_record() and set_record() read and write one by its generated type,
15 * as q3.io.results' record() does in Python;
16 * - Q3's vectors and matrices, copied into the shapes ESSE lists them in;
17 * - writing the file.
18 */
19
20#include <cstdint>
21#include <optional>
22#include <string>
23#include <vector>
24
25#include <nlohmann/json.hpp>
26
27#include "generated/esse/core/reusable/energy.hpp"
28#include "generated/esse/properties/non_scalar/stress_tensor.hpp"
29#include "generated/esse/properties/non_scalar/total_energy_contributions.hpp"
30#include "generated/esse/properties/scalar/fermi_energy.hpp"
31#include "generated/esse/properties/scalar/total_energy.hpp"
32#include "generated/esse/properties/structural/atomic_forces.hpp"
33#include "generated/q3/results.hpp"
34#include "io/json_file.hpp"
38
39namespace q3 {
40
41/**
42 * The results of a `flavor` run before it has computed anything: the runtime
43 * log names the flavor, and the execution is one process with every thread
44 * the run may use, until the execution policy's execution_record() states the real topology.
45 */
46[[nodiscard]] inline generated::Results new_results(const std::string& flavor) {
47 generated::Results results;
48 results.runtime_log.flavor = flavor;
49 generated::ResultsExecution& execution = results.runtime_log.execution;
50 execution.number_of_processes = 1;
51 execution.number_of_threads_per_process = execution::threading::maximum_threads();
52 execution.number_of_kpoint_pools = 1;
53 execution.band_group_size = 1;
54 return results;
55}
56
57/// The `name` every record of the generated ESSE type `Record` carries.
58template <typename Record> [[nodiscard]] std::string record_name() {
59 return nlohmann::json(Record{}.name).template get<std::string>();
60}
61
62/// The record of type `Record` the results list, or nothing when they list none.
63template <typename Record>
64[[nodiscard]] std::optional<Record> find_record(const generated::Results& results) {
65 const std::string name = record_name<Record>();
66 for (const auto& record : results.properties.records) {
67 if (record.value("name", "") == name) {
68 return record.template get<Record>();
69 }
70 }
71 return std::nullopt;
72}
73
74/// List `record` in the results, in place of any record of the same name.
75template <typename Record> void set_record(generated::Results& results, const Record& record) {
76 const nlohmann::json value = record;
77 const std::string name = record_name<Record>();
78 auto& records = results.properties.records;
79 for (auto& existing : records) {
80 if (existing.value("name", "") == name) {
81 existing = value;
82 return;
83 }
84 }
85 records.push_back(value);
86}
87
88/// A vector as ESSE lists one: [x, y, z].
89[[nodiscard]] inline std::vector<double> to_vector(const Vector3D& vector) {
90 return {vector.x, vector.y, vector.z};
91}
92
93/// A 3x3 matrix as ESSE lists one: three rows of three.
94[[nodiscard]] inline std::vector<std::vector<double>> to_matrix(const Real (&matrix)[3][3]) {
95 std::vector<std::vector<double>> rows;
96 for (const auto& row : matrix) {
97 rows.push_back({row[0], row[1], row[2]});
98 }
99 return rows;
100}
101
102/// One of Q3's energies that ESSE has no property for, as an ESSE energy in rydberg.
103[[nodiscard]] inline generated::Energy energy(const std::string& name, Real value_in_ry) {
104 return {name, generated::EnergyUnitsEnum::RY, value_in_ry};
105}
106
107/// Forces in Ry/bohr as ESSE's atomic_forces, atoms by 1-based id: the total or one contribution.
108[[nodiscard]] inline generated::AtomicForces
109atomic_forces(const std::vector<Vector3D>& values_in_ry_per_bohr) {
110 generated::AtomicForces forces;
111 forces.name = generated::AtomicForcesNameEnum::ATOMIC_FORCES;
112 forces.units = generated::AtomicForcesUnitsEnum::RY_A_U;
113 for (size_t atom_index = 0; atom_index < values_in_ry_per_bohr.size(); atom_index++) {
114 forces.values.push_back(
115 {to_vector(values_in_ry_per_bohr[atom_index]), static_cast<int64_t>(atom_index) + 1});
116 }
117 return forces;
118}
119
120/// A stress in kbar as ESSE's stress_tensor: the total or one contribution to it.
121[[nodiscard]] inline generated::StressTensor stress_tensor(const Real (&values_in_kbar)[3][3]) {
122 return {to_matrix(values_in_kbar),
123 generated::StressTensorNameEnum::STRESS_TENSOR,
124 generated::StressTensorUnitsEnum::KBAR};
125}
126
127/**
128 * The run's energies: ESSE's total_energy record (`total_energy_in_ry`) and
129 * total_energy_contributions record (the terms ESSE has keys for), and beside
130 * them in energy_contributions the terms it has none for yet. The double
131 * counting is always listed; hubbard (DFT+U, Dudarev), dispersion (already
132 * inside the total) and one_center (PAW) only when nonzero. A flavor with no
133 * energy of its own passes zeros, as a non-self-consistent pass does.
134 */
135inline void set_energy_records(generated::Results& results,
136 Real total_energy_in_ry,
137 const PlanewaveKohnShamEnergies& energies) {
138 set_record(results,
139 generated::TotalEnergy{generated::TotalEnergyNameEnum::TOTAL_ENERGY,
140 generated::TotalEnergyUnitsEnum::RY,
141 total_energy_in_ry});
142
143 generated::TotalEnergyContributions contributions;
144 contributions.name = generated::TotalEnergyContributionsNameEnum::TOTAL_ENERGY_CONTRIBUTIONS;
145 contributions.units = generated::TotalEnergyContributionsUnitsEnum::RY;
146 contributions.eigenvalues = generated::TotalEnergyContributionsEigenvalues{
147 generated::TotalEnergyContributionsEigenvaluesNameEnum::EIGENVALUES, energies.band_energy};
148 contributions.hartree = generated::TotalEnergyContributionsHartree{
149 generated::TotalEnergyContributionsHartreeNameEnum::HARTREE, energies.hartree_energy};
150 contributions.exchange_correlation = generated::TotalEnergyContributionsExchangeCorrelation{
151 generated::TotalEnergyContributionsExchangeCorrelationNameEnum::EXCHANGE_CORRELATION,
153 contributions.ewald = generated::TotalEnergyContributionsEwald{
154 generated::TotalEnergyContributionsEwaldNameEnum::EWALD, energies.ewald_energy};
155 contributions.temperature_entropy = generated::TotalEnergyContributionsTemperatureEntropy{
156 generated::TotalEnergyContributionsTemperatureEntropyNameEnum::TEMPERATURE_ENTROPY,
158 set_record(results, contributions);
159
160 generated::ResultsEnergyContributions& terms = results.properties.energy_contributions;
161 terms.double_counting = energy("double_counting", energies.double_counting_correction);
162 if (energies.hubbard_energy_contribution() != 0.0) {
163 terms.hubbard = energy("hubbard", energies.hubbard_energy_contribution());
164 }
165 if (energies.dispersion_energy() != 0.0) {
166 terms.dispersion = energy("dispersion", energies.dispersion_energy());
167 }
168 if (energies.one_center_energy_contribution() != 0.0) {
169 terms.one_center = energy("one_center", energies.one_center_energy_contribution());
170 }
171}
172
173/// ESSE's fermi_energy record, in rydberg.
174inline void set_fermi_energy(generated::Results& results, Real fermi_energy_in_ry) {
175 set_record(results,
176 generated::FermiEnergy{generated::FermiEnergyNameEnum::FERMI_ENERGY,
177 generated::FermiEnergyUnitsEnum::RY,
178 fermi_energy_in_ry});
179}
180
181/**
182 * The results as the text of results.json. write_results_json() writes the
183 * same text, so the file a run leaves and the string the Python bindings
184 * return cannot drift apart.
185 */
186[[nodiscard]] inline std::string results_json_string(const generated::Results& results) {
187 return nlohmann::json(results).dump(4) + "\n";
188}
189
190/// Write the results to `output_path` as results.json.
191inline void write_results_json(const generated::Results& results, const std::string& output_path) {
192 write_json_file(output_path, results);
193}
194
195} // namespace q3
void set_record(generated::Results &results, const Record &record)
List record in the results, in place of any record of the same name.
Definition results.hpp:75
std::optional< Record > find_record(const generated::Results &results)
The record of type Record the results list, or nothing when they list none.
Definition results.hpp:64
double Real
generated::Results new_results(const std::string &flavor)
Definition results.hpp:46
generated::AtomicForces atomic_forces(const std::vector< Vector3D > &values_in_ry_per_bohr)
Forces in Ry/bohr as ESSE's atomic_forces, atoms by 1-based id: the total or one contribution.
Definition results.hpp:109
generated::Energy energy(const std::string &name, Real value_in_ry)
One of Q3's energies that ESSE has no property for, as an ESSE energy in rydberg.
Definition results.hpp:103
void set_fermi_energy(generated::Results &results, Real fermi_energy_in_ry)
ESSE's fermi_energy record, in rydberg.
Definition results.hpp:174
void write_results_json(const generated::Results &results, const std::string &output_path)
Write the results to output_path as results.json.
Definition results.hpp:191
std::vector< std::vector< double > > to_matrix(const Real(&matrix)[3][3])
A 3x3 matrix as ESSE lists one: three rows of three.
Definition results.hpp:94
std::string results_json_string(const generated::Results &results)
Definition results.hpp:186
generated::StressTensor stress_tensor(const Real(&values_in_kbar)[3][3])
A stress in kbar as ESSE's stress_tensor: the total or one contribution to it.
Definition results.hpp:121
std::vector< double > to_vector(const Vector3D &vector)
A vector as ESSE lists one: [x, y, z].
Definition results.hpp:89
void set_energy_records(generated::Results &results, Real total_energy_in_ry, const PlanewaveKohnShamEnergies &energies)
Definition results.hpp:135
std::string record_name()
The name every record of the generated ESSE type Record carries.
Definition results.hpp:58
void write_json_file(const std::string &path, const nlohmann::json &document)
Definition json_file.hpp:20
Real double_counting_correction
Definition init.hpp:16
Real exchange_correlation_energy
Definition init.hpp:18
virtual Real dispersion_energy() const
Definition init.hpp:28
virtual Real one_center_energy_contribution() const
Definition init.hpp:44
virtual Real hubbard_energy_contribution() const
Definition init.hpp:36