Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
martyna_tuckerman.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * MartynaTuckermanCorrectionCalculator — reciprocal-space isolated-system
5 * (finite-size electrostatic) correction, Martyna-Tuckerman kernel.
6 *
7 * G. J. Martyna and M. E. Tuckerman, "A reciprocal space based method for
8 * treating long range interactions in ab initio and force-field-based
9 * calculations in clusters," J. Chem. Phys. 110, 2810 (1999).
10 *
11 * *** SCOPE NOTICE — read before relying on this class ***
12 * This is the POST-SCF, perturbative (non-self-consistent) evaluation of the
13 * Martyna-Tuckerman correction: it is evaluated ONCE, on the density that
14 * already converged under the ordinary PERIODIC Coulomb kernel. It is NOT
15 * the same quantity as Quantum ESPRESSO's `assume_isolated='m-t'`, which is
16 * self-consistent -- QE folds the MT-corrected local potential into the SCF
17 * Hamiltonian on every iteration, so its density relaxes under the *corrected*
18 * potential before its energy is ever evaluated. Q3's
19 * isolated-system-correction interface (see init.hpp) is architecturally
20 * post-SCF -- every scheme is evaluated once, after convergence, exactly
21 * like MakovPayneCorrectionCalculator -- so
22 * this class reports what the MT kernel difference contributes to the
23 * ALREADY-CONVERGED (periodic-kernel) density, not what QE would converge to
24 * under its own corrected potential. The two numbers coincide only to the
25 * extent that MT's potential correction perturbs the self-consistent density
26 * weakly (a reasonable expectation for compact, weakly-polarizable systems;
27 * not guaranteed for strongly localized or highly charged ones). The
28 * self-consistent version (feeding the correction back into V_eff every SCF
29 * iteration) is tracked as future work -- see TODOs.md.
30 *
31 * Physical quantity computed: the difference between the classical
32 * electrostatic energy of the system's TOTAL (ionic + electronic) charge
33 * distribution evaluated with the Martyna-Tuckerman cutoff Coulomb kernel,
34 * and the same quantity evaluated with the ordinary periodic (4*pi/G^2)
35 * Coulomb kernel -- i.e. exactly the spurious periodic-image electrostatic
36 * self-interaction energy that MakovPayneCorrectionCalculator's
37 * monopole+dipole+quadrupole SERIES approximates, but summed EXACTLY here
38 * (every multipole order at once, via a reciprocal-space kernel difference)
39 * rather than via a truncated multipole expansion:
40 *
41 * E_MT = (e^2 / 2) * Omega * sum_G |rho_total(G)|^2 * W(G)
42 *
43 * where:
44 * rho_total(G) = rho_ion(G) - rho_el(G) (net charge density, G-space;
45 * positive sign = net positive/"hole" charge, matching
46 * MakovPayneCorrectionCalculator's net_charge convention)
47 * rho_ion(G) = (1/Omega) * sum_atoms Z_valence * exp(-i * 2*pi * G . tau)
48 * (ionic structure factor over the cell volume, same convention as
49 * compute_ewald())
50 * rho_el(G) = forward FFT of the spin-summed converged electron density
51 * (PlanewaveBasis::rho_r_to_g())
52 * W(G) = the MT kernel-difference function (Eq. 4.3 of the paper above;
53 * Quantum ESPRESSO calls the same quantity "wg_corr"): the
54 * reciprocal-space representation of "the truncated, erf-smoothed
55 * Coulomb potential, periodized by minimum-image wrapping into the
56 * simulation cell" MINUS "the ordinary periodic erf-smoothed Coulomb
57 * kernel," damped by a residual Gaussian
58 * filter for numerical convergence. The sum runs over ALL G including
59 * G=0 (unlike the ordinary Hartree/Ewald sums, which omit G=0 by
60 * convention) -- W(0) is finite by construction, which is exactly what
61 * lets this scheme handle a net cell charge without an external
62 * compensating-background assumption.
63 *
64 * W(G) construction, following Eq. 4.3 of Martyna & Tuckerman (1999) and its
65 * surrounding discussion, cross-checked numerically against Quantum ESPRESSO:
66 * 1. Pick the convergence parameter alpha (bohr^-2) by scanning DOWNWARD
67 * from 2.9 in steps of 0.1 and stopping at the first value whose
68 * erfc-tail truncation error at the charge-density G-cutoff is below
69 * math::tolerances::ewald_convergence_threshold (1e-7). That bound rises
70 * monotonically with alpha, so the descending scan returns the LARGEST
71 * admissible alpha on that grid (never 2.9 itself: the decrement
72 * precedes the first test), not the smallest one --
73 * structurally the same kind of erf/erfc-smoothed reciprocal-space
74 * search Q3's own Ewald sum performs, but NOT numerically identical to
75 * it (Q3's compute_ewald() search bounds a charge-scaled self-energy
76 * criterion with a `sqrt(2*alpha/pi)` factor; the MT search below uses
77 * `sqrt(2*alpha/(2*pi))` with no charge scaling) --
78 * select_convergence_parameters() below matches the MT criterion, which
79 * Quantum ESPRESSO uses as well, not compute_ewald()'s. beta = 0.5/alpha
80 * is a free filter-width choice, not a derived value; step 3 shows what
81 * it is coupled to.
82 * 2. Evaluate, at every point of the SAME dense real-space grid the charge
83 * density lives on, evaluate_smoothed_coulomb_kernel_real_space(r) =
84 * erf(sqrt(alpha)*r)/r (r -> 0 limit: 2*sqrt(alpha/pi)), where r is the
85 * point's MINIMUM-IMAGE distance from the origin (the lattice's own
86 * periodicity, independent of where any atom sits). Forward-FFT that
87 * array (PlanewaveBasis::rho_r_to_g()) to obtain, at every G, the
88 * reciprocal-space coefficient of the periodized truncated kernel.
89 * 3. W(G) = [Omega * Re(that coefficient) -
90 * evaluate_smoothed_coulomb_kernel_reciprocal_space(G^2)], multiplied
91 * by exp(-G^2*beta/4)^2, where the reciprocal-space kernel is the
92 * free-space FT of the real-space one for G != 0 (the paper's Eq. 4.3),
93 * and at G = 0 the one value that keeps the FILTERED total kernel
94 * continuous there: -4*pi*(1/(4*alpha) + 2*beta/4). Its first term is
95 * the analytic free-space integral of the complementary (erfc)
96 * short-range part, its second the O(G^2) coefficient the filter leaves
97 * behind against the 4*pi/G^2 pole -- so the value is tied to beta and
98 * to the filter's form, not to alpha alone. Both terms are derived in
99 * evaluate_smoothed_coulomb_kernel_reciprocal_space() below; Quantum
100 * ESPRESSO arrives at the same value.
101 *
102 * MINIMUM-IMAGE DISTANCE CAVEAT: step 2's "minimum-image distance from the
103 * origin" wraps fractional coordinates into [-0.5, 0.5) (frac -= round(frac))
104 * under the cell's OWN (possibly oblique) lattice vectors -- the same
105 * approximation MakovPayneCorrectionCalculator already uses for its own
106 * electronic dipole/quadrupole moments -- and is NOT the exact shortest
107 * distance to the Bravais lattice (the Wigner-Seitz distance), which Quantum
108 * ESPRESSO obtains by starting from this same fractional wrap and then
109 * scanning neighboring images for a shorter one. The two coincide exactly
110 * for orthogonal lattices (simple cubic, bravais_lattice_index 1), and for
111 * ANY lattice inside the centered PARALLELEPIPED's inscribed sphere --
112 * radius = half the smallest interplanar spacing, min_i(Omega/|a_j x a_k|),
113 * which is strictly smaller than half the shortest lattice vector (the
114 * Wigner-Seitz cell's own inscribed radius) whenever the primitive cell is
115 * oblique: FCC 0.289*a vs 0.354*a, BCC 0.354*a vs 0.433*a. Outside that
116 * smaller sphere the wrap can return a farther image than the true nearest
117 * one (FCC example: a point at 0.30*a along the normal to the (a2,a3) plane
118 * has fractional coordinate 0.52, so it wraps to a representative at
119 * 0.494*a), i.e. near the cell boundary of an oblique primitive cell
120 * (e.g. FCC/BCC, bravais_lattice_index 2/3) -- see the validity check below.
121 *
122 * VALIDITY ASSUMPTION ("density localized within half the cell"): the total
123 * charge density must be negligible outside the region where the
124 * parallelepiped minimum-image proxy above is exact. The check below uses
125 * a sphere of radius = half the shortest lattice vector, centered on the
126 * valence-charge-weighted ionic center x0 (same x0 convention as
127 * MakovPayneCorrectionCalculator). This is checked cheaply -- folded into
128 * the same real-space grid pass that builds the kernel array, so it adds no
129 * extra asymptotic cost -- and logs a warning (NOT a throw, unlike
130 * Makov-Payne's cubic-lattice gate) when a nontrivial fraction of the
131 * density sits outside that sphere: (a) the check is itself a heuristic
132 * proxy, not an exact geometric membership test against the true
133 * Wigner-Seitz polyhedron, and (b) MT is legitimately lattice-general (no
134 * lattice-type gate at all -- contrast MakovPayneCorrectionCalculator's
135 * cubic-only throw). A fired warning means this correction's accuracy is
136 * not guaranteed for that system, but the run is not blocked.
137 *
138 * RELATION TO QE's wg_corr_h()/wg_corr_ewald(): QE's own public API returns
139 * TWO separate ENERGY numbers built on this same W(G) -- an electron-electron
140 * Hartree-like correction (wg_corr_h(), using rho_el(G) alone) and an
141 * ion-ion Ewald-like correction (wg_corr_ewald(), using rho_ion(G) alone) --
142 * alongside a force correction (wg_corr_force()) that this class has no
143 * counterpart for: the energy reported here carries no matching force term,
144 * consistent with IsolatedSystemCorrectionCalculator reporting labeled energy
145 * terms only (init.hpp) rather than feeding forces or relaxation.
146 * The electron-ion CROSS term is not a third public number in QE: it is
147 * captured only through wg_corr_loc(), a correction to the local potential
148 * that feeds back into the SCF Hamiltonian and therefore into the
149 * eigenvalue sum/double-counting bookkeeping -- exactly the self-consistent
150 * machinery this class does not have (see the scope notice above). To still
151 * capture that cross term -- needed for physical completeness, and for this
152 * class's own "reduces to the Makov-Payne monopole term as the cell grows"
153 * limit, see test_martyna_tuckerman.cpp -- this class sums
154 * |rho_ion(G) - rho_el(G)|^2 * W(G) directly over the TOTAL charge, which
155 * expands into wg_corr_ewald + wg_corr_h plus the (otherwise-missing)
156 * electron-ion cross term: the mathematically complete, scheme-consistent
157 * generalization of "the classical electrostatic energy of the whole
158 * system's charge distribution, MT kernel minus periodic kernel" -- not
159 * merely the sum of QE's own two public functions.
160 *
161 * Reported as a single term ("energy"), matching this interface's
162 * scheme-agnostic design (IsolatedSystemCorrectionResult's docstring) --
163 * unlike Makov-Payne, MT has no natural split into "orders," since the
164 * reciprocal-space sum captures every multipole at once.
165 */
166
167#include <algorithm>
168#include <cmath>
169#include <complex>
170#include <stdexcept>
171#include <string>
172#include <utility>
173#include <vector>
174
175#include "io/logger.hpp"
183
184namespace q3 {
185
187 public:
189 compute(const NormConservingPlanewaveKohnShamSCFState& scf_state) const override {
190 const Crystal& crystal_structure = *scf_state.crystal_structure;
191 const PlanewaveBasis& planewave_basis = *scf_state.planewave_basis;
192
193 Real cell_volume = crystal_structure.cell_volume();
194 Real alat = crystal_structure.lattice_parameter();
195 Real two_pi_over_lattice_parameter_squared =
196 crystal_structure.two_pi_over_lattice_parameter_squared();
197
198 auto [convergence_alpha, convergence_beta] =
199 select_convergence_parameters(planewave_basis.charge_density_energy_cutoff());
200
201 Matrix3x3 lattice_vectors = crystal_structure.lattice_vectors(); // alat units
202 Matrix3x3 lattice_vectors_transpose = lattice_vectors.transpose();
203 Matrix3x3 lattice_inverse_transpose = lattice_vectors.inverse().transpose();
204
205 /**
206 * x0 = valence-charge-weighted ionic center (alat units), same convention as
207 * MakovPayneCorrectionCalculator.
208 */
209 int number_of_atoms = crystal_structure.number_of_atoms();
210 Real total_valence_charge = 0.0;
211 Vector3D ionic_center_alat{0.0, 0.0, 0.0};
212 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
213 Real valence_charge =
214 crystal_structure.valence_charge(crystal_structure.species_type_index(atom_index));
215 total_valence_charge += valence_charge;
216 ionic_center_alat +=
217 valence_charge * crystal_structure.convert_from_crystal_to_cartesian(atom_index);
218 }
219 ionic_center_alat = ionic_center_alat / total_valence_charge;
220
221 /**
222 * Cheap locality-validity radius: half the shortest lattice vector, in Bohr.
223 * That is exactly the region the parallelepiped proxy and the true
224 * Wigner-Seitz distance share for an ORTHOGONAL cell; for an oblique one it
225 * is optimistic (too large) -- the guaranteed-exact radius there is half the
226 * smallest interplanar spacing -- so the warning can stay silent on a density
227 * that already reaches points where the proxy is wrong. See the class
228 * docstring's "VALIDITY ASSUMPTION" section.
229 */
230 Real shortest_lattice_vector_length_alat = std::min(
231 {lattice_vectors[0].norm(), lattice_vectors[1].norm(), lattice_vectors[2].norm()});
232 Real locality_radius_in_bohr = 0.5 * shortest_lattice_vector_length_alat * alat;
233
234 const FFTGrid& density_grid = planewave_basis.fft_rho();
235 int grid_dimension_1 = density_grid.grid_dimension_1();
236 int grid_dimension_2 = density_grid.grid_dimension_2();
237 int grid_dimension_3 = density_grid.grid_dimension_3();
238 int local_grid_points = density_grid.local_grid_points();
239 int number_of_spin_components = static_cast<int>(scf_state.charge_density_real.size());
240
241 /**
242 * Sweep this rank's part of the dense grid, asking the grid where each
243 * local point sits. The local kernel and density arrays are exactly the
244 * input layout required by the serial or distributed rho_r_to_g FFT.
245 */
246 std::vector<Real> kernel_real_space(local_grid_points);
247 std::vector<Real> total_charge_density_real(local_grid_points, 0.0);
248 Real charge_density_magnitude_within_locality_radius = 0.0;
249 Real charge_density_magnitude_total = 0.0;
250
251 for (int local_flat_index = 0; local_flat_index < local_grid_points; local_flat_index++) {
252 int global_flat_index = density_grid.global_real_space_index(local_flat_index);
253 int index_3 = global_flat_index % grid_dimension_3;
254 int index_2 = (global_flat_index / grid_dimension_3) % grid_dimension_2;
255 int index_1 = global_flat_index / (grid_dimension_3 * grid_dimension_2);
256 Vector3D fractional_grid_point{static_cast<Real>(index_1) / grid_dimension_1,
257 static_cast<Real>(index_2) / grid_dimension_2,
258 static_cast<Real>(index_3) / grid_dimension_3};
259 Vector3D grid_point_alat = lattice_vectors_transpose * fractional_grid_point;
260
261 Real distance_from_origin_in_bohr = minimum_image_distance(grid_point_alat,
262 Vector3D{0.0, 0.0, 0.0},
263 lattice_vectors_transpose,
264 lattice_inverse_transpose) *
265 alat;
266 kernel_real_space[local_flat_index] = evaluate_smoothed_coulomb_kernel_real_space(
267 distance_from_origin_in_bohr, convergence_alpha);
268
269 Real total_density_at_point = 0.0;
270 for (int spin_index = 0; spin_index < number_of_spin_components; spin_index++) {
271 total_density_at_point +=
272 scf_state.charge_density_real[spin_index][local_flat_index];
273 }
274 total_charge_density_real[local_flat_index] = total_density_at_point;
275
276 Real distance_from_center_in_bohr = minimum_image_distance(grid_point_alat,
277 ionic_center_alat,
278 lattice_vectors_transpose,
279 lattice_inverse_transpose) *
280 alat;
281 Real density_magnitude = std::abs(total_density_at_point);
282 charge_density_magnitude_total += density_magnitude;
283 if (distance_from_center_in_bohr <= locality_radius_in_bohr) {
284 charge_density_magnitude_within_locality_radius += density_magnitude;
285 }
286 }
287
288 std::vector<Real> locality_magnitudes = {
289 charge_density_magnitude_within_locality_radius,
290 charge_density_magnitude_total,
291 };
292 if (scf_state.execution_policy != nullptr) {
293 scf_state.execution_policy->reduce_within_pool_vector(locality_magnitudes);
294 }
295 charge_density_magnitude_within_locality_radius = locality_magnitudes[0];
296 charge_density_magnitude_total = locality_magnitudes[1];
297
298 if (charge_density_magnitude_total > 0.0) {
299 Real localized_fraction =
300 charge_density_magnitude_within_locality_radius / charge_density_magnitude_total;
301 if (localized_fraction < locality_warning_fraction_threshold) {
302 log::warn(
303 "MartynaTuckermanCorrectionCalculator: only {:.1f}% of |charge density| lies "
304 "within the cheap Wigner-Seitz-proxy locality radius ({:.4f} bohr, half the "
305 "shortest lattice vector, centered on the ionic center) -- the "
306 "Martyna-Tuckerman post-SCF correction assumes the density is well localized "
307 "inside the cell (see this class's docstring); this system may violate that "
308 "assumption and the reported correction may not be accurate.",
309 localized_fraction * 100.0,
310 locality_radius_in_bohr);
311 }
312 }
313
314 /**
315 * Forward-FFT both the kernel-construction array and the (spin-summed) electron
316 * density onto the same charge-density G-vector set (PlanewaveBasis::g_vectors()).
317 */
318 std::vector<Complex> kernel_g_space;
319 planewave_basis.rho_r_to_g(kernel_real_space, kernel_g_space);
320 std::vector<Complex> electron_density_g_space;
321 planewave_basis.rho_r_to_g(total_charge_density_real, electron_density_g_space);
322
323 int number_of_g_vectors = planewave_basis.number_of_g_vectors();
324 int first_nonzero_g_index = planewave_basis.first_nonzero_g_index();
325 bool is_gamma_only = planewave_basis.is_gamma_only();
326
327 Real energy_sum = 0.0;
328 for (int g_index = 0; g_index < number_of_g_vectors; g_index++) {
329 const GVector& g_vector = planewave_basis.g_vectors()[g_index];
330 Real g_squared_in_inverse_bohr_squared =
331 g_vector.magnitude_squared * two_pi_over_lattice_parameter_squared;
332
333 Complex ionic_structure_factor(0.0, 0.0);
334 for (int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
335 Vector3D atomic_position_alat =
336 crystal_structure.convert_from_crystal_to_cartesian(atom_index);
337 Real phase_angle = constants::two_pi * g_vector.g_cart.dot(atomic_position_alat);
338 ionic_structure_factor += crystal_structure.valence_charge(
339 crystal_structure.species_type_index(atom_index)) *
340 compute_phase_factor(phase_angle);
341 }
342 Complex ionic_charge_density_g = ionic_structure_factor / cell_volume;
343
344 Real reciprocal_space_kernel = evaluate_smoothed_coulomb_kernel_reciprocal_space(
345 g_squared_in_inverse_bohr_squared, convergence_alpha, convergence_beta);
346 Real gaussian_filter =
347 std::exp(-g_squared_in_inverse_bohr_squared * convergence_beta / 4.0);
348 Real kernel_difference =
349 (cell_volume * kernel_g_space[g_index].real() - reciprocal_space_kernel) *
350 gaussian_filter * gaussian_filter;
351
352 Complex total_charge_density_g =
353 ionic_charge_density_g - electron_density_g_space[g_index];
354
355 /**
356 * Gamma-only half-sphere: each G != 0 represents both G and -G, and
357 * |rho(-G)|^2 = |rho(G)|^2 by Hermitian symmetry, so double the
358 * contribution -- matches compute_ewald()/compute_hartree_potential()'s own
359 * convention. G=0 (g_index < first_nonzero_g_index) is never doubled: it has
360 * no separate -G partner.
361 */
362 Real gamma_factor = (is_gamma_only && g_index >= first_nonzero_g_index) ? 2.0 : 1.0;
363 energy_sum += std::norm(total_charge_density_g) * kernel_difference * gamma_factor;
364 }
365
366 Real martyna_tuckerman_energy_in_ry =
367 0.5 * constants::electron_charge_squared_rydberg * cell_volume * energy_sum;
368
370 result.applied = true;
371 result.method = name();
372 result.terms_in_ry = {{"energy", martyna_tuckerman_energy_in_ry}};
373 return result;
374 }
375
376 [[nodiscard]] std::string name() const override {
377 return "martyna-tuckerman";
378 }
379
380 private:
381 /**
382 * Fraction of |charge density| that must lie within the locality radius before
383 * MartynaTuckermanCorrectionCalculator logs a validity warning. A heuristic
384 * threshold (not derived from error-propagation analysis) -- see the class
385 * docstring's "VALIDITY ASSUMPTION" section.
386 */
387 static constexpr Real locality_warning_fraction_threshold = 0.99;
388
389 /**
390 * Minimum-image displacement norm (alat units) of `point` from `reference`, under
391 * the (possibly oblique) lattice given by `lattice_vectors_transpose` /
392 * `lattice_inverse_transpose` -- wraps the fractional displacement into [-0.5, 0.5)
393 * exactly like MakovPayneCorrectionCalculator's own electronic dipole/quadrupole
394 * grid sum. See the class docstring's minimum-image-distance caveat for why this is
395 * a proxy for, not identical to, the true Wigner-Seitz-cell distance.
396 */
397 [[nodiscard]] static Real minimum_image_distance(const Vector3D& point,
398 const Vector3D& reference,
399 const Matrix3x3& lattice_vectors_transpose,
400 const Matrix3x3& lattice_inverse_transpose) {
401 Vector3D displacement = point - reference;
402 Vector3D fractional_displacement = lattice_inverse_transpose * displacement;
403 fractional_displacement.x -= std::round(fractional_displacement.x);
404 fractional_displacement.y -= std::round(fractional_displacement.y);
405 fractional_displacement.z -= std::round(fractional_displacement.z);
406 Vector3D wrapped_displacement = lattice_vectors_transpose * fractional_displacement;
407 return wrapped_displacement.norm();
408 }
409
410 /**
411 * Select the Martyna-Tuckerman convergence parameter alpha (bohr^-2) and its
412 * derived beta = 0.5/alpha, matching the MT convergence criterion as Quantum
413 * ESPRESSO also applies it (NOT compute_ewald()'s differently-normalized search
414 * -- see the class docstring): scanning downward from 2.9 in steps of 0.1, take
415 * the first alpha reached -- i.e. the LARGEST one on that grid, since the bound
416 * below rises monotonically with alpha -- whose erfc-tail truncation error at
417 * the given charge-density energy cutoff is below
418 * math::tolerances::ewald_convergence_threshold.
419 *
420 * The 2.9 start, the 0.1 step and beta's 0.5 factor are free choices of the
421 * search schedule, the same values Quantum ESPRESSO uses, not quantities
422 * derived here; any schedule meeting the same tolerance (different start or
423 * step, bisection, or direct inversion of the bound) would serve equally.
424 */
425 [[nodiscard]] static std::pair<Real, Real>
426 select_convergence_parameters(Real charge_density_energy_cutoff_in_ry) {
427 Real convergence_alpha = 2.9;
428 Real truncation_error_upper_bound = 1.0;
429 while (truncation_error_upper_bound > math::tolerances::ewald_convergence_threshold) {
430 convergence_alpha -= 0.1;
431 if (convergence_alpha <= 0.0) {
432 throw std::runtime_error(
433 "MartynaTuckermanCorrectionCalculator: could not find a convergence "
434 "parameter alpha for the given charge-density cutoff (the same "
435 "failure mode Quantum ESPRESSO reports here; should not happen for "
436 "any physically reasonable cutoff)");
437 }
438 truncation_error_upper_bound = constants::electron_charge_squared_rydberg *
439 std::sqrt(2.0 * convergence_alpha / constants::two_pi) *
440 std::erfc(std::sqrt(charge_density_energy_cutoff_in_ry /
441 (4.0 * convergence_alpha)));
442 }
443 Real convergence_beta = 0.5 / convergence_alpha;
444 return {convergence_alpha, convergence_beta};
445 }
446
447 /// The MT smoothed kernel erf(sqrt(alpha)*r)/r, r -> 0 limit 2*sqrt(alpha/pi).
448 [[nodiscard]] static Real evaluate_smoothed_coulomb_kernel_real_space(Real distance_in_bohr,
449 Real convergence_alpha) {
450 if (distance_in_bohr > constants::epsilon_1e_minus_6) {
451 return std::erf(std::sqrt(convergence_alpha) * distance_in_bohr) / distance_in_bohr;
452 }
453 return 2.0 / constants::sqrt_pi * std::sqrt(convergence_alpha);
454 }
455
456 /**
457 * Free-space Fourier transform of evaluate_smoothed_coulomb_kernel_real_space()
458 * for G != 0 (the paper's Eq. 4.3), and at G = 0 the value that keeps the
459 * FILTERED total kernel continuous.
460 *
461 * Derivation, in kernel units: with A(G) the (finite) G-space coefficient of
462 * the periodized real-space kernel, the total kernel for G != 0 is
463 * v(G) = 4*pi/G^2 + [A(G) - 4*pi*exp(-G^2/(4*alpha))/G^2] * exp(-G^2*beta/2).
464 * Expanding 4*pi*exp(-G^2/(4*alpha))/G^2 = 4*pi/G^2 - pi/alpha + O(G^2) and
465 * exp(-G^2*beta/2) = 1 - G^2*beta/2 + O(G^4), the poles cancel and the filter
466 * converts the pole into a finite term:
467 * lim_{G->0} v(G) = A(0) + pi/alpha + 2*pi*beta.
468 * Returning -4*pi*(1/(4*alpha) + 2*beta/4) = -(pi/alpha + 2*pi*beta) at G = 0
469 * makes W(0) = A(0) + pi/alpha + 2*pi*beta reproduce exactly that limit. The
470 * first term is the analytic free-space integral of the erfc short-range part
471 * (integral of erfc(sqrt(alpha)*r)/r over all space = pi/alpha); the second is
472 * the filter's own O(G^2) coefficient. The value therefore moves with beta and
473 * with the filter's form, not with alpha alone. Quantum ESPRESSO arrives at the
474 * same value.
475 */
476 [[nodiscard]] static Real evaluate_smoothed_coulomb_kernel_reciprocal_space(
477 Real g_squared_in_inverse_bohr_squared, Real convergence_alpha, Real convergence_beta) {
478 if (g_squared_in_inverse_bohr_squared > constants::epsilon_1e_minus_6) {
479 return constants::four_pi *
480 std::exp(-g_squared_in_inverse_bohr_squared / (4.0 * convergence_alpha)) /
481 g_squared_in_inverse_bohr_squared;
482 }
483 return -constants::four_pi *
484 (1.0 / (4.0 * convergence_alpha) + 2.0 * convergence_beta / 4.0);
485 }
486};
487
488} // namespace q3
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
Real valence_charge(int species_index) const
Definition crystal.hpp:317
Real cell_volume() const
Definition crystal.hpp:193
const Matrix3x3 & lattice_vectors() const
Definition crystal.hpp:202
int species_type_index(int atom_index) const
Definition crystal.hpp:273
int number_of_atoms() const
Definition crystal.hpp:209
Real lattice_parameter() const
Definition crystal.hpp:180
virtual int global_real_space_index(int local_index) const
Definition fft_grid.hpp:64
virtual int grid_dimension_1() const =0
virtual int grid_dimension_3() const =0
virtual int grid_dimension_2() const =0
virtual int local_grid_points() const =0
IsolatedSystemCorrectionResult compute(const NormConservingPlanewaveKohnShamSCFState &scf_state) const override
std::string name() const override
Scheme name as accepted by the input ("none", "makov-payne", ...).
int number_of_g_vectors() const
Definition init.hpp:145
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
void rho_r_to_g(const std::vector< Real > &charge_density_real_space, std::vector< Complex > &charge_density_g_space) const
Definition init.hpp:334
int first_nonzero_g_index() const
Definition init.hpp:151
FFTGrid & fft_rho()
Definition init.hpp:286
Real charge_density_energy_cutoff() const
Definition init.hpp:345
bool is_gamma_only() const
Definition init.hpp:157
virtual void reduce_within_pool_vector(std::vector< Real > &values) const =0
constexpr Real epsilon_1e_minus_6
constexpr Real sqrt_pi
Definition constants.hpp:27
constexpr Real electron_charge_squared_rydberg
Definition constants.hpp:32
constexpr Real two_pi
Definition constants.hpp:25
constexpr Real four_pi
Definition constants.hpp:26
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
constexpr double ewald_convergence_threshold
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex
A reciprocal lattice vector with Miller indices, used in plane-wave calculations.
Definition g_vector.hpp:17
Real magnitude_squared
Definition g_vector.hpp:28
Vector3D g_cart
Cartesian coordinates in units of 2π/alat.
Definition g_vector.hpp:22
std::vector< std::pair< std::string, Real > > terms_in_ry
Definition init.hpp:49
3×3 matrix stored row-major: row i = mat[i]
Matrix3x3 transpose() const
Transpose.
Matrix3x3 inverse() const
Inverse (assumes non-singular)
std::vector< std::vector< Real > > charge_density_real
Definition init.hpp:37
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
Real norm() const
Definition vector_3d.hpp:68
Real dot(const Vector3D &other) const
Definition vector_3d.hpp:59