Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Force calculator — orchestrator and public API.
5 *
6 * Coordinates all Hellmann-Feynman force contributions and provides:
7 * - The ForceCalculator::compute_forces() static method (top-level public API)
8 * - Total assembly, symmetrization, and logging
9 *
10 * Returns std::pair<AtomicForcesResult, AtomicForcesContributions>:
11 * - AtomicForcesResult: ESSE-schema total forces (values field)
12 * - AtomicForcesContributions: diagnostic per-contribution breakdown
13 *
14 * Individual contributions are computed by their respective calculator classes:
15 * EwaldForceCalculator — internal/ewald.hpp
16 * NormConservingPlanewaveForceCalculator — pseudopotential/init.hpp
17 * LocalPseudopotentialForceCalculator — pseudopotential/internal/local.hpp
18 * NonlocalPseudopotentialForceCalculator — pseudopotential/internal/nonlocal.hpp
19 * CoreCorrectionForceCalculator — pseudopotential/internal/core_correction.hpp
20 * UltrasoftForceCalculator —
21 * pseudopotential/ultrasoft/internal/augmentation.hpp
22 */
23
24#include <cmath>
25#include <utility>
26#include <vector>
27
28#include "io/logger.hpp"
41
42namespace q3 {
43
44/// Compute Hellmann-Feynman forces on all atoms.
46 /**
47 * Compute all Hellmann-Feynman force contributions from the converged SCF state.
48 *
49 * Extracts crystal, planewave_basis, kpoint_grid, and projector_overlap_coefficients
50 * from the state. Returns (AtomicForcesResult, AtomicForcesContributions).
51 *
52 * The summed forces are averaged over the crystal's space group unless
53 * disable_symmetry is set, in which case they are returned as computed -- the
54 * run used no symmetry anywhere else either (InputData::disable_symmetry).
55 */
56 static std::pair<std::vector<Vector3D>, AtomicForcesContributions>
58 const std::string& dispersion_correction = "none",
59 bool disable_symmetry = false) {
60 const auto& crystal_structure = *scf_state.crystal_structure;
61 const auto& planewave_basis = *scf_state.planewave_basis;
62 const auto& kpoint_grid = *scf_state.kpoint_grid;
63 const auto& projector_overlap_coefficients = scf_state.projector_overlap_coefficients;
64 int number_of_atoms = crystal_structure.number_of_atoms();
65
66 // 1. Ewald forces
67 EwaldForceCalculator ewald_calculator(crystal_structure, planewave_basis);
68 auto ewald_forces = ewald_calculator.compute();
69
70 /**
71 * 1b. Dispersion forces -- geometry-only, independent of the
72 * electronic structure, so no pool reduction is needed below. The
73 * factory resolves the scheme ("none" contributes exact zeros), so
74 * no per-scheme branching happens here.
75 */
76 auto dispersion_calculator =
77 create_dispersion_calculator(dispersion_correction, crystal_structure);
78 std::vector<Vector3D> dispersion_forces = dispersion_calculator->compute_gradients().forces;
79 bool has_dispersion = dispersion_calculator->name() != "none";
80
81 // 2. Pseudopotential forces (local + nonlocal + core-correction + augmentation)
82 NormConservingPlanewaveForceCalculator pseudopotential_calculator(
83 crystal_structure,
84 planewave_basis,
85 kpoint_grid,
86 scf_state,
87 projector_overlap_coefficients);
88 auto pseudopotential_contributions = pseudopotential_calculator.compute();
89
90 /**
91 * 3. Pool reduction for k-point-dependent force contributions.
92 *
93 * Nonlocal KB projector and DFT+U (Hubbard) forces depend on wavefunctions: each
94 * pool accumulates only its subset of k-points, so a global sum is required.
95 * Within a pool whose ranks split the plane waves, both calculators sum their
96 * overlaps across the pool before multiplying them, so every rank of a pool
97 * holds the same complete value here and this sum counts each pool once.
98 *
99 * Ewald, local, core-correction, and augmentation forces are computed from the
100 * complete crystal structure or density (available on every rank), so they give
101 * the same value on all ranks and must NOT be reduced.
102 */
103 if (scf_state.execution_policy != nullptr) {
104 auto reduce_kpoint_dependent_force = [&](std::vector<Vector3D>& force) {
105 std::vector<Real> flat;
106 flat.reserve(static_cast<std::size_t>(number_of_atoms * 3));
107 for (const auto& value : force) {
108 flat.push_back(value.x);
109 flat.push_back(value.y);
110 flat.push_back(value.z);
111 }
112 scf_state.execution_policy->reduce_vector(flat);
113 for (int atom_index = 0; atom_index < number_of_atoms; ++atom_index) {
114 force[atom_index].x = flat[static_cast<std::size_t>(atom_index * 3)];
115 force[atom_index].y = flat[static_cast<std::size_t>(atom_index * 3 + 1)];
116 force[atom_index].z = flat[static_cast<std::size_t>(atom_index * 3 + 2)];
117 }
118 };
119 reduce_kpoint_dependent_force(pseudopotential_contributions.nonlocal);
120 if (!scf_state.hubbard_manifolds.empty()) {
121 reduce_kpoint_dependent_force(pseudopotential_contributions.hubbard);
122 }
123 }
124
125 return assemble_total(
126 crystal_structure,
127 number_of_atoms,
128 pseudopotential_contributions.local,
129 ewald_forces,
130 pseudopotential_contributions.nonlocal,
131 pseudopotential_contributions.core_correction,
132 pseudopotential_contributions.augmentation,
133 dispersion_forces,
134 pseudopotential_contributions.hubbard,
136 scf_state.quantum_system().has_ultrasoft(),
137 has_dispersion,
138 !scf_state.hubbard_manifolds.empty(),
139 disable_symmetry);
140 }
141
142 private:
143 static std::pair<std::vector<Vector3D>, AtomicForcesContributions>
144 assemble_total(const Crystal& crystal_structure,
145 int number_of_atoms,
146 const std::vector<Vector3D>& local_forces,
147 const std::vector<Vector3D>& ewald_forces,
148 const std::vector<Vector3D>& nonlocal_forces,
149 const std::vector<Vector3D>& core_correction_forces,
150 const std::vector<Vector3D>& augmentation_forces,
151 const std::vector<Vector3D>& dispersion_forces,
152 const std::vector<Vector3D>& hubbard_forces,
153 bool has_nonlinear_core_correction,
154 bool has_augmentation,
155 bool has_dispersion,
156 bool has_hubbard,
157 bool disable_symmetry) {
158 std::vector<Vector3D> total_forces(number_of_atoms, {0.0, 0.0, 0.0});
159 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
160 for (int direction = 0; direction < 3; direction++) {
161 total_forces[atom_index][direction] =
162 local_forces[atom_index][direction] + ewald_forces[atom_index][direction] +
163 nonlocal_forces[atom_index][direction] +
164 core_correction_forces[atom_index][direction] +
165 augmentation_forces[atom_index][direction] +
166 dispersion_forces[atom_index][direction] +
167 hubbard_forces[atom_index][direction];
168 }
169 }
170
171 if (!disable_symmetry) {
172 auto symmetry_operations = SymmetryAnalyzer::get_symmetry_operations(crystal_structure);
173 if (symmetry_operations.size() > 1) {
175 crystal_structure, symmetry_operations, total_forces);
176 }
177 }
178
179 log::section("Forces (Ry/bohr):");
180 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
181 log::info(" atom {:3d} ({:2s}): {:12.8f} {:12.8f} {:12.8f}",
182 atom_index + 1,
183 crystal_structure.species()[crystal_structure.species_type_index(atom_index)]
184 .symbol,
185 total_forces[atom_index].x,
186 total_forces[atom_index].y,
187 total_forces[atom_index].z);
188 }
189
190 log::info("");
191 log::info(" Force contributions per atom (Ry/bohr):");
192 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
193 log::info(" atom {:2d} local: {:12.8f} {:12.8f} {:12.8f}",
194 atom_index + 1,
195 local_forces[atom_index].x,
196 local_forces[atom_index].y,
197 local_forces[atom_index].z);
198 log::info(" atom {:2d} Ewald: {:12.8f} {:12.8f} {:12.8f}",
199 atom_index + 1,
200 ewald_forces[atom_index].x,
201 ewald_forces[atom_index].y,
202 ewald_forces[atom_index].z);
203 log::info(" atom {:2d} nonlocal: {:12.8f} {:12.8f} {:12.8f}",
204 atom_index + 1,
205 nonlocal_forces[atom_index].x,
206 nonlocal_forces[atom_index].y,
207 nonlocal_forces[atom_index].z);
208 log::info(" atom {:2d} NLCC: {:12.8f} {:12.8f} {:12.8f}",
209 atom_index + 1,
210 core_correction_forces[atom_index].x,
211 core_correction_forces[atom_index].y,
212 core_correction_forces[atom_index].z);
213 if (has_dispersion) {
214 log::info(" atom {:2d} disp: {:12.8f} {:12.8f} {:12.8f}",
215 atom_index + 1,
216 dispersion_forces[atom_index].x,
217 dispersion_forces[atom_index].y,
218 dispersion_forces[atom_index].z);
219 }
220 if (has_hubbard) {
221 log::info(" atom {:2d} hubbard: {:12.8f} {:12.8f} {:12.8f}",
222 atom_index + 1,
223 hubbard_forces[atom_index].x,
224 hubbard_forces[atom_index].y,
225 hubbard_forces[atom_index].z);
226 }
227 }
228 log::info("");
229 log::info(" Force contributions (diagonal RMS, Ry/bohr):");
230 auto compute_root_mean_square = [&](const std::vector<Vector3D>& forces_vector) -> Real {
231 Real sum_squared = 0.0;
232 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
233 sum_squared += forces_vector[atom_index].x * forces_vector[atom_index].x +
234 forces_vector[atom_index].y * forces_vector[atom_index].y +
235 forces_vector[atom_index].z * forces_vector[atom_index].z;
236 }
237 return std::sqrt(sum_squared / (3.0 * number_of_atoms +
239 };
240 log::info(" local: {:.8f}", compute_root_mean_square(local_forces));
241 log::info(" Ewald: {:.8f}", compute_root_mean_square(ewald_forces));
242 log::info(" nonlocal: {:.8f}", compute_root_mean_square(nonlocal_forces));
243 if (has_nonlinear_core_correction) {
244 log::info(" NLCC: {:.8f}", compute_root_mean_square(core_correction_forces));
245 }
246 if (has_augmentation) {
247 log::info(" augment: {:.8f}", compute_root_mean_square(augmentation_forces));
248 }
249 if (has_dispersion) {
250 log::info(" disp: {:.8f}", compute_root_mean_square(dispersion_forces));
251 }
252 if (has_hubbard) {
253 log::info(" hubbard: {:.8f}", compute_root_mean_square(hubbard_forces));
254 }
255 log::info(" TOTAL: {:.8f}", compute_root_mean_square(total_forces));
256
257 AtomicForcesContributions contributions;
258 contributions.local = local_forces;
259 contributions.ewald = ewald_forces;
260 contributions.nonlocal = nonlocal_forces;
261 contributions.core_correction = core_correction_forces;
262 contributions.augmentation = augmentation_forces;
263 contributions.dispersion = dispersion_forces;
264 contributions.hubbard = hubbard_forces;
265
266 return {total_forces, contributions};
267 }
268};
269
270} // namespace q3
int species_type_index(int atom_index) const
Definition crystal.hpp:273
const std::vector< AtomSpecies > & species() const
Definition crystal.hpp:218
std::vector< Vector3D > compute() const
Definition ewald.hpp:34
bool has_nonlinear_core_correction() const
Definition init.hpp:259
PseudopotentialForceContributions compute()
Definition init.hpp:47
const NormConservingPlanewaveBasis & pseudopotential_basis() const
Definition init.hpp:65
static void symmetrize_vector(const Crystal &crystal, const std::vector< SymmetryOperation > &symmetry_operations, std::vector< Vector3D > &vector_field)
Definition symmetry.hpp:634
static std::vector< SymmetryOperation > get_symmetry_operations(const Crystal &crystal, Real symmetry_precision=math::tolerances::default_symmetry_precision)
Definition symmetry.hpp:484
virtual void reduce_vector(std::vector< Real > &values) const =0
void section(const std::string &title)
Definition logger.hpp:206
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
constexpr double d_matrix_element_threshold
double Real
std::unique_ptr< DispersionCalculator > create_dispersion_calculator(const std::string &dispersion_correction, const Crystal &crystal_structure)
Definition factory.hpp:27
Per-contribution force breakdown (Ry/bohr).
Compute Hellmann-Feynman forces on all atoms.
Definition init.hpp:45
static std::pair< std::vector< Vector3D >, AtomicForcesContributions > compute_forces(const NormConservingPlanewaveKohnShamSCFState &scf_state, const std::string &dispersion_correction="none", bool disable_symmetry=false)
Definition init.hpp:57
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