Loading...
Searching...
No Matches
hubbard.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * DFT+U (Dudarev rotationally-invariant form), energy and potential.
5 *
6 * E_U = sum_{I,sigma} (U_I/2) Tr[n^{Isigma}(1 - n^{Isigma})]
7 * V_U|psi> = sum_{I,m,m'} (U_I/2)(delta_mm' - 2 n^{Isigma}_{m'm}) |phi^I_m><phi^I_m'|psi>
8 *
9 * where n^{Isigma}_{mm'} = sum_{k,v} w_k f_{kv} <psi_{kv}|phi^I_m><phi^I_m'|psi_{kv}> is the
10 * occupation matrix of the Hubbard-active atomic orbitals (indices m, m' run over the 2l+1
11 * states of one atom's chosen shell). "Atomic" projector convention (raw atomic pseudo-orbitals,
12 * no Lowdin orthogonalization) -- matches QE's HUBBARD (atomic) card.
13 *
14 * S.L. Dudarev et al., Phys. Rev. B 57, 1505 (1998).
15 */
16
17#include <cctype>
18#include <complex>
19#include <stdexcept>
20#include <string>
21#include <vector>
22
31
32namespace q3 {
33
34/// One user-specified Hubbard-active shell (input.hubbard[i]).
36 std::string species_symbol;
37 std::string orbital_label; // matches the UPF PP_CHI label verbatim, e.g. "3D"
39};
40
41/**
42 * One seeding directive. Species is matched by ATOMIC_SPECIES symbol (the
43 * same per-sublattice granularity the Hubbard specification itself uses, so
44 * an antiferromagnet's Fe1/Fe2 can be seeded independently). Indices are
45 * 0-based here; the input parsers convert from the 1-based drop-in and YAML
46 * forms and bounds-check what they can before the manifold sizes are known.
47 */
49 std::string species_symbol;
50 int eigenvalue_index; ///< 0-based position in the ascending eigenvalue list.
51 int spin_index; ///< 0-based spin channel.
52 Real target_occupation; ///< [0, 1] in the per-spin convention.
53};
54
55/**
56 * Build a seeding directive from the 1-based indices both input formats use,
57 * validating once.
58 *
59 * Both parsers accept the same directive with the same numbering, so the
60 * range rules and the 1-based to 0-based conversion live here rather than
61 * being written out twice and drifting apart. Named arguments in, so a
62 * reordering of the struct cannot silently mis-assign at a call site.
63 */
64[[nodiscard]] inline HubbardStartingEigenvalue
65make_hubbard_starting_eigenvalue(const std::string& species_symbol,
66 int one_based_eigenvalue_index,
67 int one_based_spin_index,
68 Real target_occupation,
69 const std::string& context) {
70 if (one_based_eigenvalue_index < 1)
71 throw std::runtime_error(context +
72 " eigenvalue index must be >= 1 (1-based, ascending), got " +
73 std::to_string(one_based_eigenvalue_index));
74 if (one_based_spin_index != 1 && one_based_spin_index != 2)
75 throw std::runtime_error(context + " spin must be 1 or 2, got " +
76 std::to_string(one_based_spin_index));
77 if (target_occupation < 0.0 || target_occupation > 1.0)
78 throw std::runtime_error(context + " value must be in [0, 1], got " +
79 std::to_string(target_occupation));
80 return {species_symbol,
81 one_based_eigenvalue_index - 1,
82 one_based_spin_index - 1,
83 target_occupation};
84}
85
86/**
87 * One resolved Hubbard-active manifold: a specific atom's specific shell, with the
88 * indices of its 2l+1 orbitals into the full enumerate_atomic_orbitals() list.
89 */
94 std::vector<int> orbital_indices; // size 2*angular_momentum + 1
95};
96
97/**
98 * Whether a user's shell specification names this orbital.
99 *
100 * The primary form is the dataset's own PP_CHI label, e.g. "3D". But labels are
101 * optional in the UPF format and PseudoDojo's PAW files omit them entirely —
102 * they give `l` and `occupation` and nothing else — which put every PAW dataset
103 * out of reach of a label-only match. So a bare orbital letter ("d", "D", "f")
104 * is also accepted and matches on angular momentum.
105 *
106 * That is unambiguous for what DFT+U is actually used for: a Hubbard U is
107 * applied to the one partially-filled d or f shell, and a dataset does not ship
108 * two of those. When the dataset does label its shells, the explicit label
109 * still selects among them exactly as before.
110 */
111[[nodiscard]] inline bool shell_specification_matches(const std::string& specification,
112 const AtomicOrbitalLabel& label) {
113 auto upper = [](std::string text) {
114 for (char& character : text)
115 character = static_cast<char>(std::toupper(static_cast<unsigned char>(character)));
116 return text;
117 };
118 std::string wanted = upper(specification);
119 if (!label.shell_label.empty() && upper(label.shell_label) == wanted)
120 return true;
121 if (wanted.size() != 1)
122 return false;
123 static const std::string orbital_letters = "SPDFGH";
124 size_t position = orbital_letters.find(wanted[0]);
125 return position != std::string::npos && static_cast<int>(position) == label.angular_momentum;
126}
127
128/**
129 * Resolves user Hubbard specifications against the crystal and the full atomic-orbital
130 * label list, failing loudly (not silently) if a species or shell is not found.
131 */
132[[nodiscard]] inline std::vector<HubbardManifold>
133resolve_hubbard_manifolds(const Crystal& crystal_structure,
134 const std::vector<AtomicOrbitalLabel>& orbital_labels,
135 const std::vector<HubbardSpecification>& specifications) {
136 std::vector<HubbardManifold> manifolds;
137 for (const auto& specification : specifications) {
138 bool species_found = false;
139 for (int atom_index = 0; atom_index < crystal_structure.number_of_atoms(); atom_index++) {
140 if (crystal_structure.element(atom_index) != specification.species_symbol) {
141 continue;
142 }
143 species_found = true;
144
145 /**
146 * Group the matches by shell, then keep the most occupied one.
147 *
148 * A dataset may carry several channels of the same l — the Ni PAW
149 * file has two d channels, occupancies 8 and 0 — so a bare orbital
150 * letter matches more than one. The Hubbard U belongs to the
151 * partially-filled valence shell, so the most occupied match is the
152 * intended one; an explicit label still picks a specific shell, and
153 * a genuine tie is refused below rather than resolved by file order.
154 */
155 std::vector<std::vector<int>> matched_shells;
156 std::vector<Real> matched_occupations;
157 int angular_momentum = -1;
158 for (int label_index = 0; label_index < static_cast<int>(orbital_labels.size());
159 label_index++) {
160 const auto& label = orbital_labels[label_index];
161 if (label.atom_index != atom_index ||
162 !shell_specification_matches(specification.orbital_label, label))
163 continue;
164 if (label.magnetic_quantum_number == 0) {
165 matched_shells.emplace_back();
166 matched_occupations.push_back(label.shell_occupation);
167 }
168 if (!matched_shells.empty())
169 matched_shells.back().push_back(label_index);
170 angular_momentum = label.angular_momentum;
171 }
172
173 std::vector<int> orbital_indices;
174 if (!matched_shells.empty()) {
175 size_t best = 0;
176 for (size_t candidate = 1; candidate < matched_shells.size(); candidate++)
177 if (matched_occupations[candidate] > matched_occupations[best])
178 best = candidate;
179 int ties = 0;
180 for (Real occupation : matched_occupations)
181 if (occupation == matched_occupations[best])
182 ties++;
183 if (matched_shells.size() > 1 && ties > 1)
184 throw std::runtime_error(
185 "Hubbard: '" + specification.orbital_label + "' matches " +
186 std::to_string(matched_shells.size()) + " shells on atom " +
187 std::to_string(atom_index) +
188 " with equal occupancy, so the intended one cannot be inferred. "
189 "Name the shell explicitly.");
190 orbital_indices = matched_shells[best];
191 }
192 if (orbital_indices.empty()) {
193 std::string available;
194 for (const auto& label : orbital_labels) {
195 if (label.atom_index != atom_index || label.magnetic_quantum_number != 0)
196 continue;
197 available +=
198 (available.empty() ? "" : ", ") +
199 (label.shell_label.empty()
200 ? "(unlabelled, l=" + std::to_string(label.angular_momentum) + ")"
201 : label.shell_label);
202 }
203 throw std::runtime_error(
204 "Hubbard: orbital shell '" + specification.orbital_label +
205 "' not found in the pseudopotential for species '" +
206 specification.species_symbol + "' (atom " + std::to_string(atom_index) +
207 "). Available: " + (available.empty() ? "none" : available) +
208 ". A bare orbital letter such as 'd' selects by angular momentum, which "
209 "is what unlabelled datasets need.");
210 }
211 /**
212 * A single specification must not sweep up two shells of the same
213 * l — an unlabelled dataset with two d channels would otherwise
214 * silently build a 10-orbital "manifold" where 5 are meant.
215 */
216 if (static_cast<int>(orbital_indices.size()) != 2 * angular_momentum + 1) {
217 throw std::runtime_error(
218 "Hubbard: '" + specification.orbital_label + "' matched " +
219 std::to_string(orbital_indices.size()) + " orbitals on atom " +
220 std::to_string(atom_index) +
221 ", but a shell with l=" + std::to_string(angular_momentum) + " has " +
222 std::to_string(2 * angular_momentum + 1) +
223 ". Name the shell explicitly to disambiguate.");
224 }
225 manifolds.push_back(
226 {atom_index, specification.u_rydberg, angular_momentum, orbital_indices});
227 }
228 if (!species_found) {
229 throw std::runtime_error("Hubbard: species '" + specification.species_symbol +
230 "' specified in the hubbard input list is not present in "
231 "the crystal");
232 }
233 }
234 return manifolds;
235}
236
237/**
238 * q_ij for one atom's projector channel pair, or zero when the species has none.
239 *
240 * The augmentation overlap is diagonal in the combined lm index -- the same rule
241 * `augment_hubbard_projectors_with_overlap` applies when the SCF builds S|phi>. The
242 * derivative kernels must agree with it or the force and stress differentiate a
243 * different S than the energy used.
244 *
245 * The species gate is the part that must not be dropped.
246 * `UltrasoftPlanewaveBasis::augmentation_overlap_integral` reaches the dataset through
247 * `PseudopotentialSet::as_ultrasoft`, whose null check is an `assert` and therefore
248 * compiled out in Release. A mixed cell -- one ultrasoft or PAW species alongside one
249 * norm-conserving species -- with a Hubbard manifold walks the norm-conserving atoms
250 * into that cast and dereferences null in exactly the builds users run. Debug builds
251 * and the single-species test cells both hide it.
252 *
253 * Shared rather than repeated because it already went wrong: five hand-rolled copies of
254 * this assembly existed across the force and stress kernels and only the one in
255 * `forces/.../nonlocal.hpp` carried the gate.
256 */
257[[nodiscard]] inline Real
259 const NormConservingPlanewaveBasis& pseudopotential_basis,
260 int species_index,
261 int channel_row,
262 int channel_column) {
263 if (!pseudopotential_basis.pseudopotential_set()[species_index].has_augmentation_charges()) {
264 return 0.0;
265 }
266 if (pseudopotential_basis.projector_combined_lm_index(species_index, channel_row) !=
267 pseudopotential_basis.projector_combined_lm_index(species_index, channel_column)) {
268 return 0.0;
269 }
270 return ultrasoft_basis.augmentation_overlap_integral(
271 species_index,
272 pseudopotential_basis.projector_to_beta_index(species_index, channel_row),
273 pseudopotential_basis.projector_to_beta_index(species_index, channel_column));
274}
275
276/**
277 * All Hubbard projector overlaps <phi_j|chi_b> at one k-point, in a single ZGEMM.
278 *
279 * Both DFT+U consumers need the same contraction over plane waves: the SCF iterator needs
280 * <phi_j|psi_b> to build the occupation matrix each iteration, and HubbardForceCalculator
281 * needs that plus <phi_j|-i(k+G)_d|psi_b> for the three Cartesian gradients. Written as
282 * scalar loops it is an O(N_pw * N_orb * N_band) nest running at dot-product speed with no
283 * reuse of either operand; it is a matrix-matrix product, so it is one ZGEMM here. Both
284 * call sites batch over ALL Hubbard orbitals at once (not per manifold), so the cost does
285 * not grow with the number of Hubbard atoms beyond the GEMM's own N_orb dimension.
286 *
287 * This mirrors the Kleinman-Bylander projector overlaps in
288 * layer_5_properties/forces/planewave/pseudopotential/internal/nonlocal.hpp, which has
289 * always batched its own structurally identical "4 overlaps per band" the same way.
290 *
291 * `packed_orbitals` is column-major (number_of_plane_waves x total_hubbard_orbitals): the
292 * Hubbard orbitals laid out contiguously by GLOBAL orbital index, i.e. manifold i's orbital
293 * m occupies column `manifold_row_offsets[i] + m`.
294 * `band_functions` is column-major (number_of_plane_waves x number_of_bands): the bands to
295 * contract against -- psi itself, or psi pre-multiplied by -i(k+G)_direction.
296 * `overlaps` is resized to and written as column-major
297 * (total_hubbard_orbitals x number_of_bands), so element (j, b) is at
298 * `j + b * total_hubbard_orbitals`.
299 *
300 * Under MPI band (G-vector) parallelism each rank passes only its local plane waves, making
301 * the result a partial sum -- callers must reduce across the band-parallel group, exactly as
302 * they did with the scalar loops.
303 */
304inline void compute_hubbard_projector_overlaps(int number_of_plane_waves,
305 int total_hubbard_orbitals,
306 int number_of_bands,
307 const Complex* packed_orbitals,
308 const Complex* band_functions,
309 std::vector<Complex>& overlaps) {
310 overlaps.assign(static_cast<size_t>(total_hubbard_orbitals) * number_of_bands,
311 Complex(0.0, 0.0));
312 if (number_of_bands == 0 || total_hubbard_orbitals == 0 || number_of_plane_waves == 0) {
313 return;
314 }
315
316 Complex alpha_blas(1.0, 0.0);
317 Complex beta_blas(0.0, 0.0);
318 blas::zgemm_("C",
319 "N",
320 &total_hubbard_orbitals,
321 &number_of_bands,
322 &number_of_plane_waves,
323 reinterpret_cast<const std::complex<double>*>(&alpha_blas),
324 reinterpret_cast<const std::complex<double>*>(packed_orbitals),
325 &number_of_plane_waves,
326 reinterpret_cast<const std::complex<double>*>(band_functions),
327 &number_of_plane_waves,
328 reinterpret_cast<const std::complex<double>*>(&beta_blas),
329 reinterpret_cast<std::complex<double>*>(overlaps.data()),
330 &total_hubbard_orbitals);
331}
332
333/**
334 * The full-sphere overlap that a Gamma-only half-sphere sum stands for.
335 *
336 * At a single unshifted Gamma point Q3 keeps one G of each +-G pair, and its
337 * half sphere *includes* G = 0 (the selection rule is `index_1 >= 0` when the
338 * other two vanish). Every function this file contracts -- the Hubbard atomic
339 * orbitals, the beta projectors inside S, and the wavefunctions -- is real in
340 * real space, so f(-G) = conj(f(G)), and the -G half of sum_G conj(f) g is the
341 * complex conjugate of the +G half. The full-sphere sum is therefore real and
342 * equal to 2*Re[half], with the G = 0 term -- present once in the half sphere
343 * and counted twice by the doubling -- removed again.
344 *
345 * A summand carrying a factor of G_alpha, which is every position-derivative
346 * overlap here, vanishes at G = 0 and passes `g_zero_product = 0`. Getting that
347 * distinction wrong is exactly what made four force terms wrong at once on
348 * 2026-08-08 (KNOWN-ISSUES.md, "Gamma-only forces were wrong in four terms"),
349 * and the KB nonlocal force and Hamiltonian apply both carry the same pair of
350 * rules inline.
351 */
352[[nodiscard]] inline Complex gamma_only_full_sphere_overlap(Complex half_sphere_sum,
353 Real g_zero_product) {
354 return {2.0 * half_sphere_sum.real() - g_zero_product, 0.0};
355}
356
357/**
358 * Position of G = 0 in one k-point's plane-wave list, or -1 if it is absent.
359 *
360 * It is the first entry in practice (the list is sorted by |k+G|^2), but the
361 * index is searched rather than assumed so that the Gamma correction is simply
362 * skipped, not misapplied, anywhere the entry is missing.
363 */
364[[nodiscard]] inline int find_g_zero_plane_wave_index(const PlanewaveBasis& planewave_basis,
365 const std::vector<int>& plane_wave_indices,
366 int number_of_plane_waves) {
367 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
368 if (planewave_basis.g_vectors()[plane_wave_indices[static_cast<size_t>(g_index)]]
369 .magnitude_squared < math::tolerances::numerical_zero_threshold) {
370 return g_index;
371 }
372 }
373 return -1;
374}
375
376/**
377 * Rewrite `compute_hubbard_projector_overlaps`' result in place as full-sphere values.
378 *
379 * `overlaps` is the column-major (total_hubbard_orbitals x number_of_bands)
380 * block that function produces, and `packed_orbitals` / `band_functions` are
381 * the operands it was given, unchanged. `band_functions_carry_g_component`
382 * says whether the band operand was pre-multiplied by -i(k+G)_direction, in
383 * which case the summand vanishes at G = 0 and no correction is subtracted.
384 *
385 * `g_zero_index` is `find_g_zero_plane_wave_index`'s answer; -1 skips the
386 * subtraction. Both operations are linear in the G-sum, so applying them to a
387 * rank's partial sum and reducing afterwards gives the same result as the
388 * other order -- which is what lets a rank that does not hold G = 0 pass -1 and
389 * contribute its half of the sum untouched.
390 */
391inline void
393 int total_hubbard_orbitals,
394 int number_of_bands,
395 const Complex* packed_orbitals,
396 const Complex* band_functions,
397 int g_zero_index,
398 bool band_functions_carry_g_component,
399 std::vector<Complex>& overlaps) {
400 for (int orbital_index = 0; orbital_index < total_hubbard_orbitals; orbital_index++) {
401 for (int band_index = 0; band_index < number_of_bands; band_index++) {
402 Real g_zero_product = 0.0;
403 if (!band_functions_carry_g_component && g_zero_index >= 0) {
404 g_zero_product =
405 (std::conj(packed_orbitals[static_cast<size_t>(orbital_index) *
406 number_of_plane_waves +
407 g_zero_index]) *
408 band_functions[static_cast<size_t>(band_index) * number_of_plane_waves +
409 g_zero_index])
410 .real();
411 }
412 size_t flat_index =
413 static_cast<size_t>(band_index) * total_hubbard_orbitals + orbital_index;
414 overlaps[flat_index] =
415 gamma_only_full_sphere_overlap(overlaps[flat_index], g_zero_product);
416 }
417 }
418}
419
420/**
421 * Accumulates one (k-point, spin, band-set)'s contribution into a manifold's occupation
422 * matrix. `overlaps(band_index, m)` = <phi_m|psi_{kv}> for this manifold's orbitals m,
423 * this k-point, this spin. `occupation_matrix` is row-major manifold_size x manifold_size,
424 * accumulated INTO (call once per k-point per spin; caller sums over all k-points).
425 *
426 * Kept as a scalar (band x m x m') nest deliberately: manifold_size is 2l+1, so it is 5 for
427 * a d shell and 7 for an f shell and cannot grow. Measured against the equivalent ZGEMM
428 * (n = O~^H O~ with O~ = sqrt(w) O) on OpenBLAS 0.3.26, this kernel costs 0.35 us at the
429 * d-shell/10-band size and 20 us at f-shell/300-band, and the ZGEMM form is 1.5-2.9x faster
430 * -- i.e. it would save single-digit microseconds per k-point/spin/manifold against an
431 * overlap build (above) that costs milliseconds to seconds, while adding a weight-scaling
432 * pass, a temporary, and the loss of this function's accumulate-in-place (beta=1) contract.
433 */
434inline void accumulate_hubbard_occupation_matrix(int number_of_bands,
435 int manifold_size,
436 const Matrix<Complex>& overlaps,
437 const std::vector<Real>& band_occupations,
438 Real kpoint_weight,
439 std::vector<Complex>& occupation_matrix) {
440 for (int band_index = 0; band_index < number_of_bands; band_index++) {
441 Real weight = kpoint_weight * band_occupations[band_index];
442 if (weight <= 0.0) {
443 continue;
444 }
445 for (int m = 0; m < manifold_size; m++) {
446 Complex overlap_m = overlaps(static_cast<size_t>(band_index), static_cast<size_t>(m));
447 for (int m_prime = 0; m_prime < manifold_size; m_prime++) {
448 Complex overlap_m_prime =
449 overlaps(static_cast<size_t>(band_index), static_cast<size_t>(m_prime));
450 occupation_matrix[static_cast<size_t>(m * manifold_size + m_prime)] +=
451 weight * std::conj(overlap_m) * overlap_m_prime;
452 }
453 }
454 }
455}
456
457/// Result of the Dudarev energy/potential formula for one manifold, one spin channel.
460 std::vector<Complex> d_matrix; // row-major manifold_size x manifold_size
461};
462
463/**
464 * Hellmann-Feynman gradient of E_U = (U/2)Tr[n(1-n)] w.r.t. one Cartesian component of one
465 * manifold's own atom position, for one k-point/spin's contribution (caller sums over
466 * k-points and spins). Derivation: dE_U/dtau = sum_mm' D_mm' dn_mm'/dtau (chain rule through
467 * d/dn Tr[n(1-n)] = 1-2n, which is exactly D/U up to the 1/2 already in D's definition), and
468 * dn_mm'/dtau expands via the product rule on n_mm' = <psi|phi_m><phi_m'|psi> into two terms
469 * that collapse to 2*Re[...] using D's Hermiticity (D_mm' = conj(D_m'm), mirroring how the
470 * energy/potential formula above is itself built from Hermitian n) -- exactly the same
471 * collapse the Kleinman-Bylander nonlocal force
472 * (forces/planewave/pseudopotential/internal/nonlocal.hpp) already relies on for its own,
473 * structurally identical D_ab-weighted sum.
474 *
475 * `overlaps(band,m)` = <phi_m|psi_v> (as accumulate_hubbard_occupation_matrix's own
476 * docstring); `direction_derivative_overlaps(band,m)` = <phi_m|-i(k+G)_direction|psi_v>,
477 * built by pre-multiplying psi_v(G) by -i(k+G)_direction before the SAME overlap sum --
478 * exactly nonlocal.hpp's own "component 1,2,3" construction for KB projectors, reused here
479 * verbatim because Q3's atomic orbitals and KB projectors share the identical G-space
480 * structure-factor convention exp(-i(k+G)*tau) (both built from
481 * atomic_orbital_builder.hpp / projector.hpp's shared phase_factor.hpp), so the position
482 * derivative of either has the identical -i(k+G) form.
483 *
484 * `d_matrix` is this manifold/spin's Dudarev D-matrix from compute_hubbard_energy_and_potential
485 * (row-major manifold_size x manifold_size). Returns the scalar force contribution (Ry/bohr)
486 * to add to this manifold's own atom, this Cartesian direction.
487 */
488[[nodiscard]] inline Real
490 int manifold_size,
491 const Matrix<Complex>& overlaps,
492 const Matrix<Complex>& direction_derivative_overlaps,
493 const std::vector<Real>& band_occupations,
494 Real kpoint_weight,
495 const std::vector<Complex>& d_matrix) {
496 Real force_contribution = 0.0;
497 for (int band_index = 0; band_index < number_of_bands; band_index++) {
498 Real weight = kpoint_weight * band_occupations[band_index];
499 if (weight <= 0.0) {
500 continue;
501 }
502 Complex band_sum(0.0, 0.0);
503 for (int m = 0; m < manifold_size; m++) {
504 Complex derivative_overlap_m = direction_derivative_overlaps(
505 static_cast<size_t>(band_index), static_cast<size_t>(m));
506 Complex derivative_overlap_m_conj = std::conj(derivative_overlap_m);
507 for (int m_prime = 0; m_prime < manifold_size; m_prime++) {
508 Complex overlap_m_prime =
509 overlaps(static_cast<size_t>(band_index), static_cast<size_t>(m_prime));
510 band_sum += d_matrix[static_cast<size_t>(m * manifold_size + m_prime)] *
511 derivative_overlap_m_conj * overlap_m_prime;
512 }
513 }
514 force_contribution += weight * 2.0 * band_sum.real();
515 }
516 return force_contribution;
517}
518
519/**
520 * E_U = (U/2) Tr[n(1-n)]; V_mm' = U(delta_mm'/2 - n_m'm). `occupation_matrix` is row-major
521 * manifold_size x manifold_size (n_mm' at index m*manifold_size + m').
522 */
524 int manifold_size, Real u_rydberg, const std::vector<Complex>& occupation_matrix) {
526 result.d_matrix.assign(static_cast<size_t>(manifold_size) * manifold_size, Complex(0.0, 0.0));
527
528 Real trace_n = 0.0;
529 Real trace_n_squared = 0.0;
530 for (int m = 0; m < manifold_size; m++) {
531 trace_n += occupation_matrix[static_cast<size_t>(m * manifold_size + m)].real();
532 for (int m_prime = 0; m_prime < manifold_size; m_prime++) {
533 Complex element = occupation_matrix[static_cast<size_t>(m * manifold_size + m_prime)];
534 trace_n_squared += std::norm(element); // |n_mm'|^2
535 }
536 }
537 result.energy = 0.5 * u_rydberg * (trace_n - trace_n_squared);
538
539 for (int m = 0; m < manifold_size; m++) {
540 for (int m_prime = 0; m_prime < manifold_size; m_prime++) {
541 Real kronecker_delta = (m == m_prime) ? 1.0 : 0.0;
542 /**
543 * Note the index order: n_m'm, not n_mm' (Dudarev's formula couples V_mm' to
544 * the TRANSPOSE-indexed occupation-matrix element).
545 */
546 Complex occupation_m_prime_m =
547 occupation_matrix[static_cast<size_t>(m_prime * manifold_size + m)];
548 result.d_matrix[static_cast<size_t>(m * manifold_size + m_prime)] =
549 u_rydberg * (0.5 * kronecker_delta - occupation_m_prime_m);
550 }
551 }
552 return result;
553}
554
555} // namespace q3
const std::string & element(int atom_index) const
Definition crystal.hpp:346
int number_of_atoms() const
Definition crystal.hpp:209
int projector_combined_lm_index(int species_type, int projector_index) const
Definition init.hpp:230
const PseudopotentialSet & pseudopotential_set() const
Definition init.hpp:156
int projector_to_beta_index(int species_type, int projector_index) const
Definition init.hpp:223
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
bool has_augmentation_charges() const
Definition set.hpp:129
Real augmentation_overlap_integral(int species_type, int projector_a, int projector_b) const
Definition init.hpp:123
void zgemm_(const char *transa, const char *transb, const int *m, const int *n, const int *k, const std::complex< double > *alpha, const std::complex< double > *a, const int *lda, const std::complex< double > *b, const int *ldb, const std::complex< double > *beta, std::complex< double > *c, const int *ldc)
constexpr double numerical_zero_threshold
void accumulate_hubbard_occupation_matrix(int number_of_bands, int manifold_size, const Matrix< Complex > &overlaps, const std::vector< Real > &band_occupations, Real kpoint_weight, std::vector< Complex > &occupation_matrix)
Definition hubbard.hpp:434
Real hubbard_augmentation_overlap(const UltrasoftPlanewaveBasis &ultrasoft_basis, const NormConservingPlanewaveBasis &pseudopotential_basis, int species_index, int channel_row, int channel_column)
Definition hubbard.hpp:258
void compute_hubbard_projector_overlaps(int number_of_plane_waves, int total_hubbard_orbitals, int number_of_bands, const Complex *packed_orbitals, const Complex *band_functions, std::vector< Complex > &overlaps)
Definition hubbard.hpp:304
double Real
void convert_gamma_only_projector_overlaps_to_full_sphere(int number_of_plane_waves, int total_hubbard_orbitals, int number_of_bands, const Complex *packed_orbitals, const Complex *band_functions, int g_zero_index, bool band_functions_carry_g_component, std::vector< Complex > &overlaps)
Definition hubbard.hpp:392
HubbardStartingEigenvalue make_hubbard_starting_eigenvalue(const std::string &species_symbol, int one_based_eigenvalue_index, int one_based_spin_index, Real target_occupation, const std::string &context)
Definition hubbard.hpp:65
HubbardEnergyAndPotential compute_hubbard_energy_and_potential(int manifold_size, Real u_rydberg, const std::vector< Complex > &occupation_matrix)
Definition hubbard.hpp:523
Real accumulate_hubbard_force_gradient(int number_of_bands, int manifold_size, const Matrix< Complex > &overlaps, const Matrix< Complex > &direction_derivative_overlaps, const std::vector< Real > &band_occupations, Real kpoint_weight, const std::vector< Complex > &d_matrix)
Definition hubbard.hpp:489
bool shell_specification_matches(const std::string &specification, const AtomicOrbitalLabel &label)
Definition hubbard.hpp:111
int find_g_zero_plane_wave_index(const PlanewaveBasis &planewave_basis, const std::vector< int > &plane_wave_indices, int number_of_plane_waves)
Definition hubbard.hpp:364
Complex gamma_only_full_sphere_overlap(Complex half_sphere_sum, Real g_zero_product)
Definition hubbard.hpp:352
std::complex< double > Complex
std::vector< HubbardManifold > resolve_hubbard_manifolds(const Crystal &crystal_structure, const std::vector< AtomicOrbitalLabel > &orbital_labels, const std::vector< HubbardSpecification > &specifications)
Definition hubbard.hpp:133
Identifies one atomic orbital in the ordering produced by build_atomic_orbitals_g_space().
Result of the Dudarev energy/potential formula for one manifold, one spin channel.
Definition hubbard.hpp:458
std::vector< Complex > d_matrix
Definition hubbard.hpp:460
std::vector< int > orbital_indices
Definition hubbard.hpp:94
One user-specified Hubbard-active shell (input.hubbard[i]).
Definition hubbard.hpp:35
std::string orbital_label
Definition hubbard.hpp:37
std::string species_symbol
Definition hubbard.hpp:36
Real target_occupation
[0, 1] in the per-spin convention.
Definition hubbard.hpp:52
int eigenvalue_index
0-based position in the ascending eigenvalue list.
Definition hubbard.hpp:50
int spin_index
0-based spin channel.
Definition hubbard.hpp:51