Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
d_matrix.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * The one-center contribution to D_ij.
5 *
6 * D_ij is assembled from three pieces, and this file owns exactly one of them.
7 * The split is the formalism's own (Blöchl, PRB 50, 17953 (1994); Kresse &
8 * Joubert, PRB 59, 1758 (1999)): only the Hxc part of D¹ − D̃¹ depends on the
9 * current density, so it is the only piece an SCF step recomputes, and it adds
10 * to the D the ultrasoft pathway already produced rather than replacing any of
11 * it:
12 *
13 * D_total = D⁰ (dataset) + ∫ v_eff Q_ij (grid side) + (D¹ − D̃¹)_Hxc
14 * ^^^^^^^^^^^^^^ here
15 *
16 * The kinetic and core-reference terms are baked into `PP_DIJ` / `DION` at
17 * generation time and are **not** recomputed — doing so would double-count
18 * them silently. So this is a decoration of the ultrasoft result, never a
19 * replacement for it.
20 *
21 * In equation numbers, the three pieces are the D_ij of Eq. (47) of
22 * G. Kresse & D. Joubert, Phys. Rev. B 59, 1758 (1999): the grid side is their
23 * D̂_ij, Eq. (44), and this file builds the Hartree and exchange-correlation
24 * part of D¹_ij − D̃¹_ij, Eqs. (45)–(46), whose kinetic part is in D⁰.
25 *
26 * The construction is the derivative written out. The one-center energy depends
27 * on ρ_ij only through the two densities, so ∂E/∂ρ_ij is that pair's own density
28 * — φ_i φ_j on the all-electron side, φ̃_i φ̃_j + Q_ij on the pseudo side —
29 * integrated against that branch's potential, the pseudo branch entering
30 * negatively. No other closed form exists; the alternative is finite
31 * differences, which is what the gate uses as an independent check rather than
32 * as the implementation.
33 *
34 * That makes D_ij the derivative of the one-center energy with respect to
35 * ρ_ij by construction, which is what the finite-difference gate asserts. The
36 * gate covers everything inside this module; it cannot see the grid-side term
37 * or the split between grid and sphere, which the A2.0 memo owns instead.
38 */
39
40#include <vector>
41
47
48namespace q3 {
49namespace paw_one_center {
50
51/**
52 * One-center D_ij correction, as a dense (nh × nh) matrix for one spin.
53 *
54 * @param energy the branches produced by compute_one_center_energy, whose
55 * `potential_lm` are integrated against
56 */
57inline std::vector<Real> compute_one_center_d_matrix(const PawPseudopotential& pseudopotential,
58 const ChannelExpansion& expansion,
59 const GauntCoefficientTable& gaunt,
60 const OneCenterEnergy& energy) {
61 int number_of_channels = expansion.size();
62 int number_of_radial = pseudopotential.number_of_projectors;
63 int mesh_size = pseudopotential.mesh_size;
64 int number_of_lm = density_lm_count(pseudopotential);
65
66 std::vector<Real> d_matrix(static_cast<size_t>(number_of_channels) * number_of_channels, 0.0);
67
68 std::vector<Real> integrand(mesh_size);
69 for (int first = 0; first < number_of_channels; first++) {
70 for (int second = 0; second < number_of_channels; second++) {
71 int radial_first = expansion.radial_channel[first];
72 int radial_second = expansion.radial_channel[second];
73 int lm_first = expansion.lm_index[first];
74 int lm_second = expansion.lm_index[second];
75 int pair_index = radial_first * number_of_radial + radial_second;
76
77 const auto& all_electron_first =
78 pseudopotential.partial_waves_all_electron[radial_first].radial_values;
79 const auto& all_electron_second =
80 pseudopotential.partial_waves_all_electron[radial_second].radial_values;
81 const auto& pseudo_first =
82 pseudopotential.partial_waves_pseudo[radial_first].radial_values;
83 const auto& pseudo_second =
84 pseudopotential.partial_waves_pseudo[radial_second].radial_values;
85
86 Real accumulated = 0.0;
87
88 /**
89 * For a gradient-corrected functional the perturbation's own
90 * channels are needed, not just their values: g_lm(r) = G·p(r)/r²
91 * and its radial derivative, paired with the by-parts weights the
92 * energy routine produced.
93 */
94 const bool gradient_corrected = energy.all_electron.has_gradient_correction();
95 std::vector<Real> all_electron_channel;
96 std::vector<Real> pseudo_channel;
97 std::vector<Real> all_electron_channel_derivative;
98 std::vector<Real> pseudo_channel_derivative;
99
100 for (int lm = 0; lm < number_of_lm; lm++) {
101 Real angular = gaunt.coefficients[lm][lm_first][lm_second];
102 if (angular == 0.0)
103 continue;
104 int angular_momentum = angular_momentum_of_lm(lm);
105
106 const std::vector<Real>* augmentation = nullptr;
107 if (pair_index <
108 static_cast<int>(
109 pseudopotential.augmentation_function_by_angular_momentum.size()) &&
110 angular_momentum <
111 static_cast<int>(
112 pseudopotential.augmentation_function_by_angular_momentum[pair_index]
113 .size())) {
114 const auto& candidate =
115 pseudopotential.augmentation_function_by_angular_momentum[pair_index]
116 [angular_momentum];
117 if (!candidate.empty())
118 augmentation = &candidate;
119 }
120
121 // All-electron branch, positive.
122 for (int point = 0; point < mesh_size; point++) {
123 integrand[point] = angular * all_electron_first[point] *
124 all_electron_second[point] *
125 energy.all_electron.potential_lm[lm][point];
126 }
127 auto cumulative = cumulative_integral(
128 integrand, pseudopotential.radial_grid_derivative, mesh_size);
129 accumulated += cumulative[mesh_size - 1];
130
131 if (gradient_corrected) {
132 all_electron_channel.assign(mesh_size, 0.0);
133 for (int point = 0; point < mesh_size; point++) {
134 Real radius = pseudopotential.radial_grid[point];
135 all_electron_channel[point] = angular * all_electron_first[point] *
136 all_electron_second[point] /
137 (radius * radius);
138 }
139 all_electron_channel_derivative = radial_derivative(
140 all_electron_channel, pseudopotential.radial_grid_derivative, mesh_size);
141 for (int point = 0; point < mesh_size; point++) {
142 Real radius = pseudopotential.radial_grid[point];
143 integrand[point] =
144 radius * radius * all_electron_channel_derivative[point] *
145 energy.all_electron.gradient_radial_weight_lm[lm][point] +
146 all_electron_channel[point] *
147 energy.all_electron.gradient_angular_weight_lm[lm][point];
148 }
149 cumulative = cumulative_integral(
150 integrand, pseudopotential.radial_grid_derivative, mesh_size);
151 accumulated += cumulative[mesh_size - 1];
152 }
153
154 // Pseudo branch, negative, compensation charge included.
155 for (int point = 0; point < mesh_size; point++) {
156 Real pseudo_value = pseudo_first[point] * pseudo_second[point];
157 if (augmentation != nullptr)
158 pseudo_value += (*augmentation)[point];
159 integrand[point] =
160 angular * pseudo_value * energy.pseudo.potential_lm[lm][point];
161 }
162 cumulative = cumulative_integral(
163 integrand, pseudopotential.radial_grid_derivative, mesh_size);
164 accumulated -= cumulative[mesh_size - 1];
165
166 if (gradient_corrected) {
167 pseudo_channel.assign(mesh_size, 0.0);
168 for (int point = 0; point < mesh_size; point++) {
169 Real radius = pseudopotential.radial_grid[point];
170 Real pseudo_value = pseudo_first[point] * pseudo_second[point];
171 if (augmentation != nullptr)
172 pseudo_value += (*augmentation)[point];
173 pseudo_channel[point] = angular * pseudo_value / (radius * radius);
174 }
175 pseudo_channel_derivative = radial_derivative(
176 pseudo_channel, pseudopotential.radial_grid_derivative, mesh_size);
177 for (int point = 0; point < mesh_size; point++) {
178 Real radius = pseudopotential.radial_grid[point];
179 integrand[point] = radius * radius * pseudo_channel_derivative[point] *
180 energy.pseudo.gradient_radial_weight_lm[lm][point] +
181 pseudo_channel[point] *
182 energy.pseudo.gradient_angular_weight_lm[lm][point];
183 }
184 cumulative = cumulative_integral(
185 integrand, pseudopotential.radial_grid_derivative, mesh_size);
186 accumulated -= cumulative[mesh_size - 1];
187 }
188 }
189 d_matrix[static_cast<size_t>(first) * number_of_channels + second] = accumulated;
190 }
191 }
192 return d_matrix;
193}
194
195} // namespace paw_one_center
196} // namespace q3
PAW pseudopotential — extends ultrasoft with one-center (on-site) data.
Definition init.hpp:33
std::vector< PartialWave > partial_waves_pseudo
Pseudo partial waves φ̃_i(r), one per projector channel.
Definition init.hpp:76
std::vector< PartialWave > partial_waves_all_electron
All-electron partial waves φ_i(r), one per projector channel.
Definition init.hpp:74
std::vector< Real > radial_grid_derivative
Definition init.hpp:77
int number_of_projectors
Definition init.hpp:72
std::vector< Real > radial_grid
Definition init.hpp:76
std::vector< std::vector< std::vector< Real > > > augmentation_function_by_angular_momentum
Definition init.hpp:47
std::vector< Real > radial_derivative(const std::vector< Real > &values, const std::vector< Real > &measures, int mesh_size)
Definition gradient.hpp:45
int angular_momentum_of_lm(int lm_index)
The L of a flat lm index.
std::vector< Real > compute_one_center_d_matrix(const PawPseudopotential &pseudopotential, const ChannelExpansion &expansion, const GauntCoefficientTable &gaunt, const OneCenterEnergy &energy)
Definition d_matrix.hpp:57
int density_lm_count(const PawPseudopotential &pseudopotential)
Number of lm channels a density needs: products of two l_max harmonics.
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
std::vector< std::vector< std::vector< Real > > > coefficients
std::vector< int > lm_index
flat (l,m) index
Definition densities.hpp:62
std::vector< int > radial_channel
index into partial_waves_*
Definition densities.hpp:60
Both branches, kept separate so a gate failure can be localized.
Definition energy.hpp:137