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 * Stress tensor calculator — orchestrator and public API.
5 *
6 * Coordinates all stress tensor contributions and provides:
7 * - The compute_stress() free function (top-level public API)
8 * - Symmetrization of individual contributions
9 * - Merging of augmentation into nonlocal
10 * - Total assembly, unit conversion, and logging
11 *
12 * Returns std::pair<StressTensorResult, StressTensorContributions>:
13 * - StressTensorResult: ESSE-schema total stress tensor and pressure (values, pressure fields)
14 * - StressTensorContributions: diagnostic per-contribution breakdown
15 *
16 * Individual contributions are computed by their respective calculator classes:
17 * KineticStressTensorCalculator — internal/kinetic.hpp
18 * HartreeStressTensorCalculator — internal/hartree.hpp
19 * EwaldStressTensorCalculator — internal/ewald.hpp
20 * NormConservingPlanewaveStressTensorCalculator — pseudopotential/init.hpp
21 * PseudopotentialExchangeCorrelationStressTensorCalculator —
22 * pseudopotential/internal/exchange_correlation.hpp LocalPseudopotentialStressTensorCalculator —
23 * pseudopotential/internal/local.hpp CoreCorrectionStressTensorCalculator —
24 * pseudopotential/internal/core_correction.hpp NonlocalPseudopotentialStressTensorCalculator —
25 * pseudopotential/internal/nonlocal.hpp UltrasoftAugmentationStressTensorCalculator —
26 * pseudopotential/ultrasoft/internal/augmentation.hpp
27 */
28
29#include <chrono>
30#include <string>
31#include <utility>
32
33#include "io/logger.hpp"
48
49namespace q3 {
50
51/// Apply crystal symmetry operations to all stress tensor contributions.
52static inline void symmetrize_stress_contributions(const Crystal& crystal_structure,
53 StressTensorContributions& contributions) {
54 auto symmetry_operations = SymmetryAnalyzer::get_symmetry_operations(crystal_structure);
55 if (symmetry_operations.size() <= 1)
56 return;
57
58 auto symmetrize_contribution = [&](Real stress_tensor[3][3]) {
59 Matrix3x3 stress_matrix;
60 for (int polarization_index_a = 0; polarization_index_a < 3; polarization_index_a++) {
61 for (int polarization_index_b = 0; polarization_index_b < 3; polarization_index_b++) {
62 stress_matrix[polarization_index_a][polarization_index_b] =
63 stress_tensor[polarization_index_a][polarization_index_b];
64 }
65 }
66 SymmetryAnalyzer::symmetrize_matrix(crystal_structure, symmetry_operations, stress_matrix);
67 for (int polarization_index_a = 0; polarization_index_a < 3; polarization_index_a++) {
68 for (int polarization_index_b = 0; polarization_index_b < 3; polarization_index_b++) {
69 stress_tensor[polarization_index_a][polarization_index_b] =
70 stress_matrix[polarization_index_a][polarization_index_b];
71 }
72 }
73 };
74
75 symmetrize_contribution(contributions.kinetic);
76 symmetrize_contribution(contributions.hartree);
77 symmetrize_contribution(contributions.exchange_correlation);
78 symmetrize_contribution(contributions.ewald);
79 symmetrize_contribution(contributions.local);
80 symmetrize_contribution(contributions.nonlocal);
81 symmetrize_contribution(contributions.core_correction);
82 symmetrize_contribution(contributions.augmentation);
83 symmetrize_contribution(contributions.hubbard);
84 symmetrize_contribution(contributions.dispersion);
85}
86
87/// Merge US-PP augmentation stress into nonlocal contribution (zero for norm-conserving).
88static inline void merge_augmentation_into_nonlocal(bool has_ultrasoft,
89 StressTensorContributions& contributions) {
90 if (!has_ultrasoft)
91 return;
92 for (int polarization_index_a = 0; polarization_index_a < 3; polarization_index_a++) {
93 for (int polarization_index_b = 0; polarization_index_b < 3; polarization_index_b++) {
94 contributions.nonlocal[polarization_index_a][polarization_index_b] +=
95 contributions.augmentation[polarization_index_a][polarization_index_b];
96 }
97 }
98}
99
100/**
101 * Sum all contributions into total, convert to kbar, log results, and return StressTensorResult.
102 *
103 * Modifies contributions in-place to convert from Ry/bohr³ to kbar.
104 * Returns the ESSE-aligned StressTensorResult (values + pressure).
105 */
106static inline StressTensorTotal
108 bool has_dispersion,
109 bool has_hubbard,
110 StressTensorContributions& contributions) {
111 // Accumulate total from contributions
112 StressTensorTotal stress_tensor_result;
113 for (int polarization_index_a = 0; polarization_index_a < 3; polarization_index_a++) {
114 for (int polarization_index_b = 0; polarization_index_b < 3; polarization_index_b++) {
115 stress_tensor_result.values[polarization_index_a][polarization_index_b] =
116 contributions.kinetic[polarization_index_a][polarization_index_b] +
117 contributions.hartree[polarization_index_a][polarization_index_b] +
118 contributions.exchange_correlation[polarization_index_a][polarization_index_b] +
119 contributions.ewald[polarization_index_a][polarization_index_b] +
120 contributions.local[polarization_index_a][polarization_index_b] +
121 contributions.nonlocal[polarization_index_a][polarization_index_b] +
122 contributions.core_correction[polarization_index_a][polarization_index_b] +
123 contributions.hubbard[polarization_index_a][polarization_index_b] +
124 contributions.dispersion[polarization_index_a][polarization_index_b];
125 }
126 }
127
128 stress_tensor_result.pressure =
129 (stress_tensor_result.values[0][0] + stress_tensor_result.values[1][1] +
130 stress_tensor_result.values[2][2]) /
131 3.0;
132
133 // Convert all fields from Ry/bohr³ to kbar
134 for (int direction_a = 0; direction_a < 3; direction_a++) {
135 for (int direction_b = 0; direction_b < 3; direction_b++) {
136 stress_tensor_result.values[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
137 contributions.kinetic[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
138 contributions.hartree[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
139 contributions.exchange_correlation[direction_a][direction_b] *=
141 contributions.ewald[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
142 contributions.local[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
143 contributions.nonlocal[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
144 contributions.core_correction[direction_a][direction_b] *=
146 contributions.augmentation[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
147 contributions.hubbard[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
148 contributions.dispersion[direction_a][direction_b] *= constants::RYDBERG_TO_KILOBAR;
149 }
150 }
151 stress_tensor_result.pressure *= constants::RYDBERG_TO_KILOBAR;
152
153 log::section("Stress tensor (kbar):");
154 log::info(" ┌──────────────────────────────────────────┐");
155 for (int direction_a = 0; direction_a < 3; direction_a++) {
156 log::info(" │ {:>12.2f} {:>12.2f} {:>12.2f} │",
157 stress_tensor_result.values[direction_a][0],
158 stress_tensor_result.values[direction_a][1],
159 stress_tensor_result.values[direction_a][2]);
160 }
161 log::info(" └──────────────────────────────────────────┘");
162 log::info(" Pressure = {:.2f} kbar", stress_tensor_result.pressure);
163 log::info("");
164
165 log::info(" Contributions:");
166 auto print_contribution = [](const char* name, const Real stress_tensor[3][3]) {
167 Real stress_pressure =
168 (stress_tensor[0][0] + stress_tensor[1][1] + stress_tensor[2][2]) / 3.0;
169 log::info(" {:<12s}: {:>10.2f} {:>10.2f} {:>10.2f} (P={:.2f})",
170 name,
171 stress_tensor[0][0],
172 stress_tensor[1][1],
173 stress_tensor[2][2],
174 stress_pressure);
175 };
176 print_contribution("kinetic", contributions.kinetic);
177 print_contribution("Hartree", contributions.hartree);
178 print_contribution("XC", contributions.exchange_correlation);
179 print_contribution("Ewald", contributions.ewald);
180 print_contribution("local PP", contributions.local);
181 print_contribution("nonlocal", contributions.nonlocal);
182 print_contribution("core corr", contributions.core_correction);
183 if (has_ultrasoft)
184 print_contribution("augment", contributions.augmentation);
185 if (has_hubbard)
186 print_contribution("DFT+U", contributions.hubbard);
187 if (has_dispersion)
188 print_contribution("dispersion", contributions.dispersion);
189 log::info("");
190
191 return stress_tensor_result;
192}
193
194/**
195 * Compute the stress tensor from converged SCF.
196 * Uses the Nielsen-Martin formalism.
197 *
198 * The formalism is that of O. H. Nielsen & R. M. Martin, Phys. Rev. B 32, 3780
199 * (1985); its plane-wave expressions are in O. H. Nielsen & R. M. Martin,
200 * Phys. Rev. B 32, 3792 (1985).
201 *
202 * This is the top-level public API that orchestrates all component calculators.
203 * Extracts crystal, planewave_basis, kpoint_grid, and projector_overlap_coefficients
204 * from the state. Returns (StressTensorResult, StressTensorContributions).
205 *
206 * Every contribution is averaged over the crystal's space group unless disable_symmetry
207 * is set, in which case the tensors are returned as computed -- the run used no symmetry
208 * anywhere else either (InputData::disable_symmetry).
209 */
210inline std::pair<StressTensorTotal, StressTensorContributions>
212 const std::string& nonlocal_stress_derivative_method = "analytical",
213 const std::string& dispersion_correction = "none",
214 bool disable_symmetry = false) {
215 const auto& crystal_structure = *scf_state.crystal_structure;
216 const auto& planewave_basis = *scf_state.planewave_basis;
217 const auto& kpoint_grid = *scf_state.kpoint_grid;
218 const auto& projector_overlap_coefficients = scf_state.projector_overlap_coefficients;
219
220 StressTensorContributions contributions;
221
222 auto timer_start = std::chrono::high_resolution_clock::now();
223
224 // 1. Kinetic stress
225 KineticStressTensorCalculator kinetic_calculator(
226 crystal_structure, planewave_basis, kpoint_grid, scf_state, contributions);
227 kinetic_calculator.compute();
228 auto timer_kinetic = std::chrono::high_resolution_clock::now();
229
230 // 2. Hartree stress
231 HartreeStressTensorCalculator hartree_calculator(
232 crystal_structure, planewave_basis, scf_state, contributions);
233 hartree_calculator.compute();
234 auto timer_hartree = std::chrono::high_resolution_clock::now();
235
236 // 3. Ewald stress
237 EwaldStressTensorCalculator ewald_calculator(crystal_structure, planewave_basis, contributions);
238 ewald_calculator.compute();
239 auto timer_ewald = std::chrono::high_resolution_clock::now();
240
241 /**
242 * 3b. Dispersion stress -- geometry-only, independent of the electronic
243 * structure, so no pool reduction is needed below. The factory resolves
244 * the scheme ("none" contributes exact zeros), so no per-scheme branching
245 * happens here; has_dispersion only gates the reporting rows.
246 */
247 auto dispersion_calculator =
248 create_dispersion_calculator(dispersion_correction, crystal_structure);
249 bool has_dispersion = dispersion_calculator->name() != "none";
250 Matrix3x3 dispersion_stress = dispersion_calculator->compute_gradients().stress;
251 for (int direction_a = 0; direction_a < 3; direction_a++) {
252 for (int direction_b = 0; direction_b < 3; direction_b++) {
253 contributions.dispersion[direction_a][direction_b] =
254 dispersion_stress[direction_a][direction_b];
255 }
256 }
257
258 // 4. Pseudopotential stress (XC + local + core-correction + nonlocal + augmentation)
259 NormConservingPlanewaveStressTensorCalculator pseudopotential_calculator(
260 crystal_structure,
261 planewave_basis,
262 kpoint_grid,
263 scf_state,
264 projector_overlap_coefficients,
265 contributions,
266 nonlocal_stress_derivative_method);
267 pseudopotential_calculator.compute();
268 auto timer_pseudo = std::chrono::high_resolution_clock::now();
269
270 bool has_ultrasoft = scf_state.quantum_system().has_ultrasoft();
271
272 /**
273 * Pool reduction for k-point-dependent stress contributions.
274 *
275 * Kinetic, nonlocal and Hubbard stress are k-point sums: each pool accumulates only
276 * its subset of k-points, so a global sum is required before symmetrization.
277 *
278 * Hartree, Ewald, XC, local, and core-correction stress terms are computed from
279 * the complete density or crystal positions (identical on every rank) and must
280 * NOT be reduced.
281 */
282 if (scf_state.execution_policy != nullptr) {
283 // Flatten 3x3 C-arrays to vectors, reduce, then write back.
284 auto reduce_within_pool = [&](Real contribution[3][3]) {
285 std::vector<Real> flat(9);
286 for (int row = 0; row < 3; ++row) {
287 for (int column = 0; column < 3; ++column) {
288 flat[static_cast<std::size_t>(row * 3 + column)] = contribution[row][column];
289 }
290 }
292 for (int row = 0; row < 3; ++row) {
293 for (int column = 0; column < 3; ++column) {
294 contribution[row][column] = flat[static_cast<std::size_t>(row * 3 + column)];
295 }
296 }
297 };
298 auto reduce_across_pools = [&](Real contribution[3][3]) {
299 std::vector<Real> flat(9);
300 for (int row = 0; row < 3; ++row) {
301 for (int column = 0; column < 3; ++column) {
302 flat[static_cast<std::size_t>(row * 3 + column)] = contribution[row][column];
303 }
304 }
305 scf_state.execution_policy->reduce_vector(flat);
306 for (int row = 0; row < 3; ++row) {
307 for (int column = 0; column < 3; ++column) {
308 contribution[row][column] = flat[static_cast<std::size_t>(row * 3 + column)];
309 }
310 }
311 };
312
313 /**
314 * 1. All contributions are partial sums over G-vectors or grid slabs within a pool.
315 * Reduce all of them within the pool first.
316 */
317 reduce_within_pool(contributions.kinetic);
318 reduce_within_pool(contributions.hartree);
319 reduce_within_pool(contributions.exchange_correlation);
320 reduce_within_pool(contributions.ewald);
321 reduce_within_pool(contributions.local);
322 reduce_within_pool(contributions.nonlocal);
323 reduce_within_pool(contributions.core_correction);
324 reduce_within_pool(contributions.augmentation);
325 reduce_within_pool(contributions.hubbard);
326
327 /**
328 * 2. Kinetic, nonlocal and Hubbard stress are k-point sums: each pool accumulates
329 * only its subset of k-points, so a global sum across pools is required.
330 */
331 reduce_across_pools(contributions.kinetic);
332 reduce_across_pools(contributions.nonlocal);
333 reduce_across_pools(contributions.hubbard);
334 }
335
336 // Symmetrize unless the run uses no symmetry; merge augmentation into nonlocal; log
337 if (!disable_symmetry) {
338 symmetrize_stress_contributions(crystal_structure, contributions);
339 }
340 merge_augmentation_into_nonlocal(has_ultrasoft, contributions);
342 has_ultrasoft, has_dispersion, !scf_state.hubbard_manifolds.empty(), contributions);
343
344 log::debug(" Stress timing breakdown:");
345 log::debug(" kinetic: {:.1f} ms",
346 std::chrono::duration<double, std::milli>(timer_kinetic - timer_start).count());
347 log::debug(" hartree: {:.1f} ms",
348 std::chrono::duration<double, std::milli>(timer_hartree - timer_kinetic).count());
349 log::debug(" ewald: {:.1f} ms",
350 std::chrono::duration<double, std::milli>(timer_ewald - timer_hartree).count());
351 log::debug(" pseudo(total): {:.1f} ms",
352 std::chrono::duration<double, std::milli>(timer_pseudo - timer_ewald).count());
353
354 return {stress_tensor_result, contributions};
355}
356
357} // namespace q3
static std::vector< SymmetryOperation > get_symmetry_operations(const Crystal &crystal, Real symmetry_precision=math::tolerances::default_symmetry_precision)
Definition symmetry.hpp:484
static void symmetrize_matrix(const Crystal &crystal, const std::vector< SymmetryOperation > &symmetry_operations, Matrix3x3 &tensor)
Definition symmetry.hpp:740
virtual void reduce_vector(std::vector< Real > &values) const =0
virtual void reduce_within_pool_vector(std::vector< Real > &values) const =0
constexpr Real RYDBERG_TO_KILOBAR
Rydberg → kbar (RY_KBAR): 10 * AU_GPA / 2.
void section(const std::string &title)
Definition logger.hpp:206
void debug(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:179
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
static void symmetrize_stress_contributions(const Crystal &crystal_structure, StressTensorContributions &contributions)
Apply crystal symmetry operations to all stress tensor contributions.
Definition init.hpp:52
double Real
std::pair< StressTensorTotal, StressTensorContributions > compute_stress(const NormConservingPlanewaveKohnShamSCFState &scf_state, const std::string &nonlocal_stress_derivative_method="analytical", const std::string &dispersion_correction="none", bool disable_symmetry=false)
Definition init.hpp:211
static void merge_augmentation_into_nonlocal(bool has_ultrasoft, StressTensorContributions &contributions)
Merge US-PP augmentation stress into nonlocal contribution (zero for norm-conserving).
Definition init.hpp:88
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
static StressTensorTotal assemble_total_stress_and_log(bool has_ultrasoft, bool has_dispersion, bool has_hubbard, StressTensorContributions &contributions)
Definition init.hpp:107
std::unique_ptr< DispersionCalculator > create_dispersion_calculator(const std::string &dispersion_correction, const Crystal &crystal_structure)
Definition factory.hpp:27
3×3 matrix stored row-major: row i = mat[i]
const NormConservingPlanewaveKohnShamQuantumSystem & quantum_system() const override
Definition init.hpp:82
std::vector< HubbardManifold > hubbard_manifolds
DFT+U (Hubbard) resolved manifolds at convergence; empty means no correction is active.
Definition init.hpp:62
const KPointsGrid * kpoint_grid
Definition init.hpp:56
const Crystal * crystal_structure
Definition init.hpp:55
const execution::PlanewaveExecutionPolicy * execution_policy
Definition init.hpp:64
const PlanewaveBasis * planewave_basis
Definition init.hpp:53
Per-contribution stress tensor breakdown (kbar after conversion).