Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * LocalOrbitalHamiltonian — the Hamiltonian of a periodic system expressed in
5 * a basis of localized orbitals: one N x N block H(R) per lattice vector R,
6 * plus the overlap S(R) of the orbitals when they are not orthonormal.
7 *
8 * This is the second REPRESENTATION beside the plane-wave one, and it is
9 * named for the basis, not for how the orbitals were obtained. Wannier
10 * functions, numerical atomic orbitals, Gaussian-type orbitals and
11 * Slater-Koster tight-binding parameters all produce an object of exactly
12 * this shape, and everything that consumes it -- Bloch sums, interpolated
13 * bands, densities of states, transport -- is the same for all of them.
14 * Nothing here knows about plane waves, gauges, or k-meshes.
15 *
16 * Two conventions are fixed by this class:
17 *
18 * - k-points are CRYSTAL (fractional) coordinates, so the Bloch phase is
19 * e^{+2 pi i k.R} with the integer translation R directly; no Cartesian
20 * conversion is ever needed.
21 * - Each lattice vector carries a weight applied in the Bloch sum. For a
22 * Hamiltonian obtained by real-space integrals the weight is 1. For one
23 * obtained by Fourier transform from a k-mesh, vectors on the boundary
24 * of the Wigner-Seitz supercell are shared between periodic images and
25 * carry 1/degeneracy; keeping that weight with the vector rather than
26 * folded into the block is what lets the same class hold both kinds.
27 *
28 * The overlap is optional. Wannier functions are orthonormal by
29 * construction, so S(R) = delta_{R,0} I and the Bloch sum returns the
30 * identity; a non-orthogonal basis sets its S(R) blocks and the eigenproblem
31 * downstream becomes the generalized one, H C = e S C -- the same shape the
32 * plane-wave representation already solves for ultrasoft pseudopotentials.
33 */
34
35#include <algorithm>
36#include <complex>
37#include <stdexcept>
38#include <string>
39#include <utility>
40#include <vector>
41
44
45namespace q3 {
46
47/// One lattice vector of the Hamiltonian's real-space domain.
48struct [[nodiscard]] LocalOrbitalLatticeVector {
50 /// Cartesian R in whatever length unit the caller used; decay diagnostics only.
52 /**
53 * Weight applied in the Bloch sum: 1 for a block obtained from real-space
54 * integrals, 1/degeneracy for a Wigner-Seitz boundary vector shared by
55 * that many periodic images.
56 */
57 Real bloch_sum_weight = 1.0;
58};
59
61 public:
62 /**
63 * Fix the basis size and the real-space domain. Any blocks previously
64 * set are discarded, since their shape and order are no longer defined.
65 */
67 std::vector<LocalOrbitalLatticeVector> lattice_vectors) {
68 if (number_of_orbitals <= 0) {
69 throw std::invalid_argument(
70 "LocalOrbitalHamiltonian::set_domain: number_of_orbitals must be positive");
71 }
72 if (lattice_vectors.empty()) {
73 throw std::invalid_argument(
74 "LocalOrbitalHamiltonian::set_domain: the domain must hold at least one lattice "
75 "vector");
76 }
77 number_of_orbitals_ = number_of_orbitals;
78 lattice_vectors_ = std::move(lattice_vectors);
79 hamiltonian_blocks_.clear();
80 overlap_blocks_.clear();
81
82 /**
83 * The Bloch sum takes the translations and weights as parallel arrays.
84 * Unpacking them once here rather than per call keeps the dense-mesh
85 * sampling path -- tens of thousands of calls -- allocation-free.
86 */
87 translations_.clear();
88 bloch_sum_weights_.clear();
89 translations_.reserve(lattice_vectors_.size());
90 bloch_sum_weights_.reserve(lattice_vectors_.size());
91 for (const LocalOrbitalLatticeVector& entry : lattice_vectors_) {
92 translations_.push_back(entry.translation);
93 bloch_sum_weights_.push_back(entry.bloch_sum_weight);
94 }
95 }
96
97 /**
98 * H(R): one column-major number_of_orbitals x number_of_orbitals block
99 * per lattice vector, in the domain's order.
100 */
101 void set_hamiltonian(std::vector<std::vector<Complex>> blocks) {
102 require_block_shapes(blocks, "set_hamiltonian");
103 hamiltonian_blocks_ = std::move(blocks);
104 }
105
106 /**
107 * S(R), same layout as the Hamiltonian blocks. Not setting it means the
108 * orbitals are orthonormal and S(k) is the identity for every k.
109 */
110 void set_overlap(std::vector<std::vector<Complex>> blocks) {
111 require_block_shapes(blocks, "set_overlap");
112 overlap_blocks_ = std::move(blocks);
113 }
114
115 [[nodiscard]] bool has_nontrivial_overlap() const {
116 return !overlap_blocks_.empty();
117 }
118
119 [[nodiscard]] int number_of_orbitals() const {
120 return number_of_orbitals_;
121 }
122
123 [[nodiscard]] const std::vector<LocalOrbitalLatticeVector>& lattice_vectors() const {
124 return lattice_vectors_;
125 }
126
127 [[nodiscard]] const std::vector<std::vector<Complex>>& hamiltonian_blocks() const {
128 return hamiltonian_blocks_;
129 }
130
131 [[nodiscard]] const std::vector<std::vector<Complex>>& overlap_blocks() const {
132 return overlap_blocks_;
133 }
134
135 /**
136 * H(k) = sum_R w_R e^{+2 pi i k.R} H(R), column-major, for `kpoint` in
137 * crystal coordinates. `kpoint` need not lie on any mesh -- evaluating
138 * this anywhere is what the representation is for.
139 */
140 [[nodiscard]] std::vector<Complex> hamiltonian_at_kpoint(const Vector3D& kpoint) const {
141 if (hamiltonian_blocks_.empty()) {
142 throw std::runtime_error(
143 "LocalOrbitalHamiltonian::hamiltonian_at_kpoint: no Hamiltonian blocks set");
144 }
145 return bloch_sum(hamiltonian_blocks_, kpoint);
146 }
147
148 /// S(k) by the same Bloch sum, or the identity when the basis is orthonormal.
149 [[nodiscard]] std::vector<Complex> overlap_at_kpoint(const Vector3D& kpoint) const {
150 if (overlap_blocks_.empty()) {
151 std::vector<Complex> identity(static_cast<std::size_t>(number_of_orbitals_) *
152 number_of_orbitals_,
153 Complex(0.0, 0.0));
154 for (int index = 0; index < number_of_orbitals_; index++) {
155 identity[static_cast<std::size_t>(index) * number_of_orbitals_ + index] =
156 Complex(1.0, 0.0);
157 }
158 return identity;
159 }
160 return bloch_sum(overlap_blocks_, kpoint);
161 }
162
163 /**
164 * Largest |H(R)| over the blocks whose lattice vector is at least
165 * `minimum_distance` from the origin in Cartesian length -- the direct
166 * measure of how fast the Hamiltonian decays, and therefore of how far
167 * a Bloch sum truncated to this domain can be trusted between the
168 * k-points it was built from.
169 */
170 [[nodiscard]] Real maximum_hamiltonian_magnitude_beyond(Real minimum_distance) const {
171 if (hamiltonian_blocks_.size() != lattice_vectors_.size()) {
172 throw std::runtime_error("LocalOrbitalHamiltonian::maximum_hamiltonian_magnitude_"
173 "beyond: no Hamiltonian blocks set");
174 }
175 Real worst = 0.0;
176 for (std::size_t vector_index = 0; vector_index < lattice_vectors_.size(); vector_index++) {
177 if (lattice_vectors_[vector_index].cartesian.norm() < minimum_distance) {
178 continue;
179 }
180 for (const Complex& element : hamiltonian_blocks_[vector_index]) {
181 worst = std::max(worst, std::abs(element));
182 }
183 }
184 return worst;
185 }
186
187 private:
188 [[nodiscard]] std::vector<Complex> bloch_sum(const std::vector<std::vector<Complex>>& blocks,
189 const Vector3D& kpoint) const {
191 kpoint, blocks, translations_, bloch_sum_weights_);
192 }
193
194 void require_block_shapes(const std::vector<std::vector<Complex>>& blocks,
195 const char* what) const {
196 if (lattice_vectors_.empty()) {
197 throw std::runtime_error(std::string("LocalOrbitalHamiltonian::") + what +
198 ": set_domain() has not been called");
199 }
200 if (blocks.size() != lattice_vectors_.size()) {
201 throw std::invalid_argument(std::string("LocalOrbitalHamiltonian::") + what + ": " +
202 std::to_string(blocks.size()) + " blocks were given for " +
203 std::to_string(lattice_vectors_.size()) +
204 " lattice vectors");
205 }
206 const std::size_t block_size =
207 static_cast<std::size_t>(number_of_orbitals_) * number_of_orbitals_;
208 for (std::size_t vector_index = 0; vector_index < blocks.size(); vector_index++) {
209 if (blocks[vector_index].size() != block_size) {
210 throw std::invalid_argument(std::string("LocalOrbitalHamiltonian::") + what +
211 ": block " + std::to_string(vector_index) + " holds " +
212 std::to_string(blocks[vector_index].size()) +
213 " entries, not " + std::to_string(number_of_orbitals_) +
214 " x " + std::to_string(number_of_orbitals_));
215 }
216 }
217 }
218
219 int number_of_orbitals_ = 0;
220 std::vector<LocalOrbitalLatticeVector> lattice_vectors_;
221 /// The domain unpacked for lattice_fourier_transform::to_kpoint; see set_domain.
222 std::vector<IntegerVector3D> translations_;
223 std::vector<Real> bloch_sum_weights_;
224 std::vector<std::vector<Complex>> hamiltonian_blocks_;
225 std::vector<std::vector<Complex>> overlap_blocks_;
226};
227
228} // namespace q3
void set_overlap(std::vector< std::vector< Complex > > blocks)
Definition init.hpp:110
int number_of_orbitals() const
Definition init.hpp:119
const std::vector< std::vector< Complex > > & hamiltonian_blocks() const
Definition init.hpp:127
Real maximum_hamiltonian_magnitude_beyond(Real minimum_distance) const
Definition init.hpp:170
const std::vector< std::vector< Complex > > & overlap_blocks() const
Definition init.hpp:131
std::vector< Complex > overlap_at_kpoint(const Vector3D &kpoint) const
S(k) by the same Bloch sum, or the identity when the basis is orthonormal.
Definition init.hpp:149
bool has_nontrivial_overlap() const
Definition init.hpp:115
void set_domain(int number_of_orbitals, std::vector< LocalOrbitalLatticeVector > lattice_vectors)
Definition init.hpp:66
const std::vector< LocalOrbitalLatticeVector > & lattice_vectors() const
Definition init.hpp:123
void set_hamiltonian(std::vector< std::vector< Complex > > blocks)
Definition init.hpp:101
std::vector< Complex > hamiltonian_at_kpoint(const Vector3D &kpoint) const
Definition init.hpp:140
std::vector< Complex > to_kpoint(const Vector3D &kpoint_crystal, const std::vector< std::vector< Complex > > &values_at_lattice_vectors, const std::vector< IntegerVector3D > &lattice_translations, const std::vector< Real > &weights)
double Real
std::complex< double > Complex
Integer 3-vector for Miller indices / FFT grid indices.
One lattice vector of the Hamiltonian's real-space domain.
Definition init.hpp:48
Vector3D cartesian
Cartesian R in whatever length unit the caller used; decay diagnostics only.
Definition init.hpp:51
IntegerVector3D translation
Definition init.hpp:49