Loading...
Searching...
No Matches
seeding.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Seeding the DFT+U occupation matrix: the user names an eigenvalue and where
5 * it should start.
6 *
7 * Why this exists: a Hubbard functional at practical U has *multiple*
8 * self-consistent solutions -- FeO with U = 4.6 eV on Fe 3d has an
9 * orbitally-ordered minimum a full 123 mRy below the orbitally-uniform one,
10 * and which of them an unseeded SCF finds is a property of the starting
11 * guess and the mixing trajectory, not of the physics. Reproducible minimum
12 * selection therefore has to be an input, and cross-code energy comparisons
13 * are only defined once both codes are known to sit in the same minimum.
14 * This is also the precondition of the NiO acceptance gate, which compares
15 * energies only after the converged occupation eigenvalues match.
16 *
17 * Semantics follow the established drop-in convention
18 * (`starting_ns_eigenvalue(m,ispin,ityp) = f`): on the *first* SCF
19 * iteration, after the occupation matrix n has been accumulated, reduced and
20 * symmetrized, diagonalize it, replace eigenvalue m (ascending order) of the
21 * named spin channel with f for every Hubbard atom of the named species, and
22 * rebuild n = V diag(lambda') V^H from the *same* eigenvectors. Later
23 * iterations recompute n from the wavefunctions as always -- the seed biases
24 * the first Hubbard potential toward the wanted minimum and then lets
25 * self-consistency take over, so a seed pointing at the basin the SCF was
26 * already in is a no-op by construction, and a seed the crystal symmetry
27 * forbids is washed back out by the per-iteration symmetrization exactly as
28 * an unrepresentable density fluctuation would be.
29 */
30
31#include <algorithm>
32#include <complex>
33#include <stdexcept>
34#include <string>
35#include <vector>
36
40
41namespace q3 {
42
43/**
44 * Replace one eigenvalue of a Hermitian occupation matrix in place.
45 *
46 * `matrix` is `size` x `size`, and the storage convention does not matter
47 * here: the matrix is Hermitian, so row-major and column-major differ by a
48 * conjugate-transpose, which commutes with "diagonalize, edit the spectrum,
49 * reassemble from the same eigenvectors" -- the result is the same matrix
50 * either way, just re-expressed in the caller's own convention.
51 *
52 * ZHEEV returns eigenvalues ascending, which pins the meaning of
53 * `eigenvalue_index` = m-1: m = size selects the largest. Throws on a
54 * diagonalization failure or an out-of-range index; the caller supplies the
55 * context (atom, species) for a useful message.
56 */
57inline void apply_hubbard_starting_eigenvalue(std::vector<Complex>& matrix,
58 int size,
59 int eigenvalue_index,
60 Real value) {
61 if (size <= 0)
62 return;
63 if (eigenvalue_index < 0 || eigenvalue_index >= size)
64 throw std::runtime_error("hubbard starting eigenvalue index " +
65 std::to_string(eigenvalue_index + 1) +
66 " is outside the manifold (size " + std::to_string(size) + ")");
67
68 std::vector<Complex> eigenvectors = matrix; // ZHEEV overwrites input with eigenvectors.
69 std::vector<Real> eigenvalues(static_cast<size_t>(size), 0.0);
70 int work_size = std::max(1, 2 * size - 1);
71 std::vector<Complex> work(static_cast<size_t>(work_size));
72 std::vector<Real> rwork(static_cast<size_t>(std::max(1, 3 * size - 2)));
73 int info = 0;
74 char jobz = 'V';
75 char uplo = 'U';
76 int dimension = size;
77 lapack::zheev_(&jobz,
78 &uplo,
79 &dimension,
80 reinterpret_cast<std::complex<double>*>(eigenvectors.data()),
81 &dimension,
82 eigenvalues.data(),
83 reinterpret_cast<std::complex<double>*>(work.data()),
84 &work_size,
85 rwork.data(),
86 &info);
87 if (info != 0)
88 throw std::runtime_error("apply_hubbard_starting_eigenvalue: ZHEEV failed, info=" +
89 std::to_string(info));
90
91 eigenvalues[static_cast<size_t>(eigenvalue_index)] = value;
92
93 /**
94 * n' = W diag(lambda') W^H, in the same layout `eigenvectors` came back
95 * in. ZHEEV's W is column-major (eigenvector k = column k), so
96 * W(a,k) = eigenvectors[a + k*size].
97 */
98 for (int row = 0; row < size; row++) {
99 for (int column = 0; column < size; column++) {
100 Complex accumulated(0.0, 0.0);
101 for (int k = 0; k < size; k++) {
102 accumulated +=
103 eigenvectors[static_cast<size_t>(row) + static_cast<size_t>(k) * size] *
104 eigenvalues[static_cast<size_t>(k)] *
105 std::conj(
106 eigenvectors[static_cast<size_t>(column) + static_cast<size_t>(k) * size]);
107 }
108 matrix[static_cast<size_t>(row) + static_cast<size_t>(column) * size] = accumulated;
109 }
110 }
111}
112
113} // namespace q3
void zheev_(const char *jobz, const char *uplo, const int *n, std::complex< double > *a, const int *lda, double *w, std::complex< double > *work, const int *lwork, double *rwork, int *info)
void apply_hubbard_starting_eigenvalue(std::vector< Complex > &matrix, int size, int eigenvalue_index, Real value)
Definition seeding.hpp:57
double Real
std::complex< double > Complex