Loading...
Searching...
No Matches
radial_poisson.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Radial Poisson solve for one angular-momentum channel of a one-center
5 * density.
6 *
7 * A one-center density is carried as lm-decomposed radial channels, and its
8 * Hartree potential separates per channel into a pair of radial integrals:
9 *
10 * V_L(r) = 8π/(2L+1) · [ r^(-(L+1)) ∫₀^r u_L(r') r'^L dr'
11 * + r^L ∫_r^∞ u_L(r') r'^(-(L+1)) dr' ]
12 *
13 * in Rydberg units (e² = 2), with `u_L(r) = r² ρ_L(r)` — the r-weighted form,
14 * chosen to match how the partial waves are already stored (r·φ) and how
15 * Quantum ESPRESSO carries its own lm-decomposed one-center densities, so no
16 * factor of r² has to be inserted or removed at the boundary between modules.
17 *
18 * Quantum ESPRESSO solves the equivalent second-order ODE with a Numerov
19 * tridiagonal scheme rather than these quadratures. The two converge to the
20 * same answer; the direct form is used here because it can be checked against
21 * closed-form solutions to the accuracy of the quadrature alone, which is what
22 * makes this step gateable before any PAW physics exists. If the µRy gate
23 * later shows a discretization difference on the coarser dataset grids,
24 * switching to a Numerov solve is a contained change behind this same
25 * signature.
26 */
27
28#include <cmath>
29#include <vector>
30
33
34namespace q3 {
35namespace paw_one_center {
36
37/**
38 * Cumulative integral ∫₀^{r_i} g(r) dr on a non-uniform grid.
39 *
40 * Simpson over each pair of intervals, with a three-point half-step
41 * (5,8,−1)/12 rule for the odd points in between, so every grid point carries
42 * fourth-order accuracy rather than only the even ones. `values` are the
43 * integrand samples; `measures` are dr/di.
44 */
45inline std::vector<Real> cumulative_integral(const std::vector<Real>& values,
46 const std::vector<Real>& measures,
47 int mesh_size) {
48 std::vector<Real> weighted(mesh_size);
49 for (int index = 0; index < mesh_size; index++)
50 weighted[index] = values[index] * measures[index];
51
52 std::vector<Real> cumulative(mesh_size, 0.0);
53 if (mesh_size < 3)
54 return cumulative;
55
56 for (int index = 2; index < mesh_size; index += 2) {
57 cumulative[index] =
58 cumulative[index - 2] +
59 (weighted[index - 2] + 4.0 * weighted[index - 1] + weighted[index]) / 3.0;
60 }
61 for (int index = 1; index < mesh_size; index += 2) {
62 if (index + 1 < mesh_size) {
63 cumulative[index] =
64 cumulative[index - 1] +
65 (5.0 * weighted[index - 1] + 8.0 * weighted[index] - weighted[index + 1]) / 12.0;
66 } else {
67 cumulative[index] =
68 cumulative[index - 1] + (weighted[index - 1] + weighted[index]) / 2.0;
69 }
70 }
71 return cumulative;
72}
73
74/**
75 * Solve for the Hartree potential of one (L, M) density channel.
76 *
77 * @param angular_momentum L of this channel
78 * @param radial_grid r values
79 * @param radial_grid_derivative dr/di
80 * @param density_r_squared u_L(r) = r² ρ_L(r)
81 * @param mesh_size points to use
82 * @param potential [out] V_L(r), resized to mesh_size
83 */
84inline void solve_radial_hartree(int angular_momentum,
85 const std::vector<Real>& radial_grid,
86 const std::vector<Real>& radial_grid_derivative,
87 const std::vector<Real>& density_r_squared,
88 int mesh_size,
89 std::vector<Real>& potential) {
90 potential.assign(mesh_size, 0.0);
91 if (mesh_size < 3)
92 return;
93
94 std::vector<Real> inner_integrand(mesh_size);
95 std::vector<Real> outer_integrand(mesh_size);
96 for (int index = 0; index < mesh_size; index++) {
97 Real radius = radial_grid[index];
98 Real radius_to_l = std::pow(radius, angular_momentum);
99 inner_integrand[index] = density_r_squared[index] * radius_to_l;
100 outer_integrand[index] = density_r_squared[index] / (radius_to_l * radius);
101 }
102
103 std::vector<Real> inner =
104 cumulative_integral(inner_integrand, radial_grid_derivative, mesh_size);
105 std::vector<Real> outer_cumulative =
106 cumulative_integral(outer_integrand, radial_grid_derivative, mesh_size);
107 Real outer_total = outer_cumulative[mesh_size - 1];
108
109 const Real prefactor = 2.0 * constants::four_pi / static_cast<Real>(2 * angular_momentum + 1);
110 for (int index = 0; index < mesh_size; index++) {
111 Real radius = radial_grid[index];
112 Real radius_to_l = std::pow(radius, angular_momentum);
113 Real outer = outer_total - outer_cumulative[index];
114 potential[index] =
115 prefactor * (inner[index] / (radius_to_l * radius) + radius_to_l * outer);
116 }
117}
118
119/**
120 * Hartree energy of a set of one-center density channels against their own
121 * potentials: E_H = ½ Σ_lm ∫ V_lm(r) u_lm(r) dr.
122 *
123 * The one-half is the usual double-counting factor, applied once after the
124 * channels are summed rather than inside each integral — a scalar pulled out of
125 * a sum, so the two placements differ only in operation count.
126 */
127inline Real radial_hartree_energy(const std::vector<std::vector<Real>>& potential_channels,
128 const std::vector<std::vector<Real>>& density_channels,
129 const std::vector<Real>& radial_grid_derivative,
130 int mesh_size) {
131 Real energy = 0.0;
132 for (size_t channel = 0; channel < potential_channels.size(); channel++) {
133 std::vector<Real> integrand(mesh_size);
134 for (int index = 0; index < mesh_size; index++) {
135 integrand[index] =
136 potential_channels[channel][index] * density_channels[channel][index];
137 }
138 auto cumulative = cumulative_integral(integrand, radial_grid_derivative, mesh_size);
139 energy += cumulative[mesh_size - 1];
140 }
141 return 0.5 * energy;
142}
143
144} // namespace paw_one_center
145} // namespace q3
constexpr Real four_pi
Definition constants.hpp:26
void solve_radial_hartree(int angular_momentum, const std::vector< Real > &radial_grid, const std::vector< Real > &radial_grid_derivative, const std::vector< Real > &density_r_squared, int mesh_size, std::vector< Real > &potential)
Real radial_hartree_energy(const std::vector< std::vector< Real > > &potential_channels, const std::vector< std::vector< Real > > &density_channels, const std::vector< Real > &radial_grid_derivative, int mesh_size)
std::vector< Real > cumulative_integral(const std::vector< Real > &values, const std::vector< Real > &measures, int mesh_size)
double Real
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