Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
verification.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * VerificationFlavor — restart-stability check of a converged charge density.
5 *
6 * Answers one question a third party cannot otherwise ask: is the density
7 * shipped with a published calculation actually the converged one? A total
8 * energy in a results file is an assertion. A density is checkable, because a
9 * converged density is a fixed point of the SCF map -- restart from it, iterate
10 * again, and the energy must not move. A density that was edited, taken from a
11 * different structure, or captured before convergence moves immediately, and by
12 * much more than the SCF threshold.
13 *
14 * Workflow:
15 * 1. Refuse an iteration budget that could not answer the question.
16 * 2. Load the charge density from <prefix>.save/effective-potential.q3cp.
17 * 3. Construct an SCF iterator over the same input, with the convergence
18 * exit disabled so the run always spends its whole budget.
19 * 4. Inject the density via load_charge_density(), so iteration 1 builds its
20 * Hamiltonian from the submitted density instead of a superposition of
21 * atomic densities.
22 * 5. Return the per-iteration energies as scf_energies_in_ry.
23 *
24 * Two decisions distinguish this from NSCF, which loads the same file:
25 *
26 * **The charge density is loaded, not V_eff.** Injecting the effective
27 * potential freezes it, which is exactly right for a band structure and
28 * useless here: a frozen potential never re-derives anything from the density,
29 * so a tampered density would produce a perfectly stable energy. Loading the
30 * density puts the submitted quantity back into the loop that has to reproduce
31 * it.
32 *
33 * **The convergence exit is disabled**, by setting the threshold to zero --
34 * the residual check is `<`, so no residual, not even an exactly-zero one,
35 * satisfies it. The run therefore always performs the full budget and always
36 * reports the same number of energies. Stopping early on convergence would
37 * make the length of the trajectory depend on the answer, and one energy is
38 * not evidence of stability.
39 *
40 * The flavor reports the trajectory and does not judge it. What counts as
41 * stable is a protocol parameter, versioned by the consumer that set it, and
42 * an engine that hard-coded a tolerance would silently outvote it.
43 */
44
45#include <algorithm>
46#include <cmath>
47#include <stdexcept>
48#include <string>
49#include <vector>
50
51#include "generated/esse/properties/workflow/convergence_electronic.hpp"
52#include "io/logger.hpp"
53#include "io/output/results.hpp"
58
59namespace q3 {
60
61class VerificationFlavor : public Flavor {
62 protected:
63 generated::Results run_calculation(const InputData& input) override {
64 /**
65 * ─── Step 1: Check the input before touching the disk ────────────────
66 * The budget is whatever the input asks for, not a number chosen here.
67 * A few iterations are enough to show drift and a verifier paying for
68 * the compute is the one entitled to decide how many; a cap applied in
69 * the engine would quietly overrule an input that meant what it said.
70 * What is refused is a budget that could not answer the question.
71 */
72 const int iteration_budget = input.maximum_iterations;
73 if (iteration_budget < 2) {
74 throw std::runtime_error(
75 "VerificationFlavor: 'max_iterations' is " + std::to_string(iteration_budget) +
76 "; a restart needs at least 2 iterations, since a single energy cannot show "
77 "whether the density moved.");
78 }
79
80 const std::string save_directory = input.output_directory + "/" + input.prefix + ".save";
81 const std::string checkpoint_path = save_directory + "/effective-potential.q3cp";
82
83 // ─── Step 2: Load the submitted charge density ───────────────────────
86
87 if (!checkpoint.load(checkpoint_path, snapshot)) {
88 throw std::runtime_error("VerificationFlavor: failed to load checkpoint at '" +
89 checkpoint_path +
90 "'. Place the submitted checkpoint there before verifying.");
91 }
92 if (snapshot.charge_density_real.empty()) {
93 throw std::runtime_error("VerificationFlavor: checkpoint at '" + checkpoint_path +
94 "' contains no charge density; there is nothing to restart "
95 "from.");
96 }
97
98 log::info("Verification: restarting from '{}' for {} iterations",
99 checkpoint_path,
100 iteration_budget);
101
102 /**
103 * ─── Step 3: Build the iterator with the exit disabled ───────────────
104 * A threshold of zero can never be met -- is_iteration_converged() is
105 * `density_residual_ < convergence_threshold_` -- so the loop runs the
106 * full budget whatever the density turns out to be.
107 */
108 InputData verification_input = input;
109 verification_input.maximum_iterations = iteration_budget;
110 verification_input.convergence_threshold = 0.0;
111
112 auto iterator = create_iterator(verification_input);
113
114 auto* planewave_iterator =
115 dynamic_cast<PlanewavePseudopotentialKohnShamSCFIterator*>(iterator.get());
116 if (!planewave_iterator) {
117 throw std::runtime_error(
118 "VerificationFlavor: expected a planewave pseudopotential iterator.");
119 }
120
121 // ─── Step 4: Inject the density and run ──────────────────────────────
122 planewave_iterator->load_charge_density(snapshot.charge_density_real);
123 iterator->run();
124
125 // ─── Step 5: Report the trajectory ───────────────────────────────────
126 generated::Results results = new_results("verification");
127 /**
128 * The run deliberately never satisfies its (zeroed) convergence test,
129 * so the iterator's own verdict says nothing here. What the flavor
130 * produced is a complete trajectory, and that is what "converged"
131 * reports -- the caller decides whether the trajectory is flat enough.
132 */
133 const std::vector<Real>& energies_in_ry = iterator->total_energy_history_in_ry();
134 results.runtime_log.converged = static_cast<int>(energies_in_ry.size()) == iteration_budget;
135 set_energy_records(results, iterator->total_energy(), PlanewaveKohnShamEnergies{});
136 set_fermi_energy(results, planewave_iterator->quantum_system().fermi_energy());
137 if (!energies_in_ry.empty()) {
138 set_record(results,
139 generated::ConvergenceElectronic{
140 generated::ConvergenceElectronicNameEnum::CONVERGENCE_ELECTRONIC,
141 generated::ConvergenceElectronicUnitsEnum::RY,
142 {std::vector<double>(energies_in_ry.begin(), energies_in_ry.end())}});
143 }
144
145 log::info("Verification: {} iterations, energy drift {:.3e} Ry",
146 energies_in_ry.size(),
147 energy_drift_in_ry(energies_in_ry));
148
149 return results;
150 }
151
152 private:
153 /// Largest departure from the first energy, which is the submitted one.
154 [[nodiscard]] static Real energy_drift_in_ry(const std::vector<Real>& energies_in_ry) {
155 Real largest_drift = 0.0;
156 for (const auto& energy_in_ry : energies_in_ry) {
157 largest_drift =
158 std::max(largest_drift, std::abs(energy_in_ry - energies_in_ry.front()));
159 }
160 return largest_drift;
161 }
162};
163
164} // namespace q3
bool load(const std::string &filename, KohnShamQuantumSystemSnapshot &snapshot) const override
Definition q3.hpp:56
generated::Results run_calculation(const InputData &input) override
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
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
double Real
generated::Results new_results(const std::string &flavor)
Definition results.hpp:46
void set_fermi_energy(generated::Results &results, Real fermi_energy_in_ry)
ESSE's fermi_energy record, in rydberg.
Definition results.hpp:174
std::unique_ptr< PlanewaveKohnShamSCFIterator > create_iterator(const InputData &input)
Definition factory.hpp:37
void set_energy_records(generated::Results &results, Real total_energy_in_ry, const PlanewaveKohnShamEnergies &energies)
Definition results.hpp:135
Parsed input data — holds raw values read from input files.
Definition data.hpp:27
std::string prefix
Definition data.hpp:178
int maximum_iterations
Definition data.hpp:33
Real convergence_threshold
Definition data.hpp:32
std::string output_directory
Definition data.hpp:177
std::vector< std::vector< Real > > charge_density_real
Definition snapshot.hpp:26