Loading...
Searching...
No Matches
kpoint_stencil.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * KPointStencil — the finite-difference stencil on a k-mesh: for every
5 * k-point, which k-point each neighbour direction lands on, and for every
6 * direction its b-vector and weight.
7 *
8 * Everything that sums over the neighbours of a k-point -- a subspace
9 * iteration, a spread, its gradient -- reads this. Resolved here once, from
10 * the grid and the shells, it is the one object those consumers share and the
11 * one place a malformed table is refused.
12 *
13 * It is built one of two ways, each by name. from_grid() resolves k + b on a
14 * full grid from its neighbour shells. from_table() takes the neighbours as
15 * given, and carries no b-vectors unless given them: the gauge-invariant
16 * spread needs only weights and neighbours, and a ring of k-points has none
17 * to give. Anything that reads a phase -- the spread's centres, its gradient
18 * -- asks has_b_vectors() first. The two are named rather than overloaded
19 * because both take two braced lists, and a call written with empty braces
20 * fits either.
21 */
22
23#include <array>
24#include <cstddef>
25#include <optional>
26#include <stdexcept>
27#include <string>
28#include <utility>
29#include <vector>
30
33
34namespace q3 {
35
37 public:
38 /// From a full grid and its neighbour shells: k + b resolved for every k and every direction.
39 [[nodiscard]] static KPointStencil
40 from_grid(const std::array<int, 3>& grid_dimensions,
41 const std::vector<KPointNeighborDirection>& directions) {
42 return KPointStencil(grid_dimensions, directions);
43 }
44
45 /**
46 * From an explicit table -- one row of neighbour indices per k-point, one
47 * entry per direction -- and the weights; b-vectors may be omitted.
48 */
49 [[nodiscard]] static KPointStencil from_table(std::vector<std::vector<int>> neighbor_table,
50 std::vector<Real> weights,
51 std::vector<Vector3D> b_vectors = {}) {
52 return KPointStencil(std::move(neighbor_table), std::move(weights), std::move(b_vectors));
53 }
54
55 [[nodiscard]] int number_of_kpoints() const {
56 return static_cast<int>(neighbor_table_.size());
57 }
58 [[nodiscard]] int number_of_directions() const {
59 return static_cast<int>(weights_.size());
60 }
61
62 /// The k-point that k + b lands on.
63 [[nodiscard]] int neighbor(int kpoint, int direction) const {
64 return neighbor_table_[static_cast<std::size_t>(kpoint)]
65 [static_cast<std::size_t>(direction)];
66 }
67
68 [[nodiscard]] Real weight(int direction) const {
69 return weights_[static_cast<std::size_t>(direction)];
70 }
71 [[nodiscard]] const std::vector<Real>& weights() const {
72 return weights_;
73 }
74 [[nodiscard]] Real total_weight() const {
75 Real total = 0.0;
76 for (const Real weight : weights_) {
77 total += weight;
78 }
79 return total;
80 }
81
82 [[nodiscard]] bool has_b_vectors() const {
83 return !b_vectors_.empty();
84 }
85 /// The b-vector of a direction, in whatever units the shells were solved in.
86 [[nodiscard]] const Vector3D& b_vector(int direction) const {
87 require_b_vectors();
88 return b_vectors_[static_cast<std::size_t>(direction)];
89 }
90 [[nodiscard]] const std::vector<Vector3D>& b_vectors() const {
91 require_b_vectors();
92 return b_vectors_;
93 }
94
95 /// Whether the stencil was built from a grid, so its directions carry integer steps.
96 [[nodiscard]] bool has_grid_steps() const {
97 return !grid_steps_.empty();
98 }
99 [[nodiscard]] const IntegerVector3D& grid_step(int direction) const {
100 if (!has_grid_steps()) {
101 throw std::logic_error("KPointStencil: a stencil built from a table carries no grid "
102 "steps");
103 }
104 return grid_steps_[static_cast<std::size_t>(direction)];
105 }
106 [[nodiscard]] const std::array<int, 3>& grid_dimensions() const {
107 return grid_dimensions_;
108 }
109
110 /**
111 * The first direction whose step reaches the same k-point as its
112 * opposite, if there is one. A Wannier centre is a finite-difference Berry phase,
113 * and it needs b and -b to be different k-points. On an axis with two grid
114 * points they are the SAME point: k+b and k-b coincide, so the two
115 * directions carry one shared overlap block and two opposite b-vectors,
116 * and the terms [Im ln M_nn + b . r_n]^2 they contribute cannot both be
117 * small whatever r_n is. Omega_D is then bounded away from zero by the
118 * mesh rather than by the physics, and the centres along that axis are
119 * noise. Only a stencil built from a grid knows its steps; one built from
120 * a table has no answer to give.
121 */
122 [[nodiscard]] std::optional<int> two_point_axis_direction() const {
123 for (std::size_t direction = 0; direction < grid_steps_.size(); direction++) {
124 const IntegerVector3D& step = grid_steps_[direction];
125 const IntegerVector3D opposite = {-step.x, -step.y, -step.z};
126 if (KPointNeighborShells::neighbor_kpoint_index(0, step, grid_dimensions_) ==
127 KPointNeighborShells::neighbor_kpoint_index(0, opposite, grid_dimensions_)) {
128 return static_cast<int>(direction);
129 }
130 }
131 return std::nullopt;
132 }
133
134 private:
135 KPointStencil(const std::array<int, 3>& grid_dimensions,
136 const std::vector<KPointNeighborDirection>& directions)
137 : grid_dimensions_(grid_dimensions) {
138 for (int axis = 0; axis < 3; axis++) {
139 if (grid_dimensions[axis] <= 0) {
140 throw std::invalid_argument(
141 "KPointStencil: the grid dimensions must be positive, got " +
142 std::to_string(grid_dimensions[0]) + "x" + std::to_string(grid_dimensions[1]) +
143 "x" + std::to_string(grid_dimensions[2]));
144 }
145 }
146 for (const KPointNeighborDirection& direction : directions) {
147 weights_.push_back(direction.weight);
148 b_vectors_.push_back(direction.reciprocal_lattice_translation);
149 grid_steps_.push_back(direction.grid_step);
150 }
152 neighbor_table_.reserve(static_cast<std::size_t>(number_of_kpoints));
153 for (int kpoint = 0; kpoint < number_of_kpoints; kpoint++) {
154 std::vector<int> row;
155 row.reserve(directions.size());
156 for (const KPointNeighborDirection& direction : directions) {
158 kpoint, direction.grid_step, grid_dimensions));
159 }
160 neighbor_table_.push_back(std::move(row));
161 }
162 require_consistent();
163 }
164
165 KPointStencil(std::vector<std::vector<int>> neighbor_table,
166 std::vector<Real> weights,
167 std::vector<Vector3D> b_vectors)
168 : neighbor_table_(std::move(neighbor_table))
169 , weights_(std::move(weights))
170 , b_vectors_(std::move(b_vectors)) {
171 require_consistent();
172 }
173
174 void require_consistent() const {
175 if (neighbor_table_.empty()) {
176 throw std::invalid_argument("KPointStencil: no k-points");
177 }
178 if (weights_.empty()) {
179 throw std::invalid_argument(
180 "KPointStencil: no neighbour directions; there is nothing to make a subspace "
181 "smooth against and no phase to read");
182 }
183 if (!b_vectors_.empty() && b_vectors_.size() != weights_.size()) {
184 throw std::invalid_argument("KPointStencil: one b-vector per direction or none; " +
185 std::to_string(b_vectors_.size()) + " were given for " +
186 std::to_string(weights_.size()) + " directions");
187 }
188 const int number_of_kpoints = static_cast<int>(neighbor_table_.size());
189 for (std::size_t kpoint = 0; kpoint < neighbor_table_.size(); kpoint++) {
190 if (neighbor_table_[kpoint].size() != weights_.size()) {
191 throw std::invalid_argument("KPointStencil: k-point " + std::to_string(kpoint) +
192 " does not carry one neighbour per direction");
193 }
194 for (const int neighbor : neighbor_table_[kpoint]) {
195 if (neighbor < 0 || neighbor >= number_of_kpoints) {
196 throw std::invalid_argument(
197 "KPointStencil: k-point " + std::to_string(kpoint) + " names k-point " +
198 std::to_string(neighbor) + " as a neighbour, which is outside the " +
199 std::to_string(number_of_kpoints) + " k-points given");
200 }
201 }
202 }
203 }
204
205 void require_b_vectors() const {
206 if (b_vectors_.empty()) {
207 throw std::logic_error("KPointStencil: this stencil carries no b-vectors, so nothing "
208 "that reads a phase can run on it");
209 }
210 }
211
212 std::vector<std::vector<int>> neighbor_table_;
213 std::vector<Real> weights_;
214 std::vector<Vector3D> b_vectors_;
215 std::vector<IntegerVector3D> grid_steps_;
216 std::array<int, 3> grid_dimensions_ = {0, 0, 0};
217};
218
219} // namespace q3
static int neighbor_kpoint_index(int starting_kpoint_index, const IntegerVector3D &grid_step, const std::array< int, 3 > &grid_dimensions)
bool has_b_vectors() const
std::optional< int > two_point_axis_direction() const
const std::vector< Vector3D > & b_vectors() const
const IntegerVector3D & grid_step(int direction) const
bool has_grid_steps() const
Whether the stencil was built from a grid, so its directions carry integer steps.
static KPointStencil from_grid(const std::array< int, 3 > &grid_dimensions, const std::vector< KPointNeighborDirection > &directions)
From a full grid and its neighbour shells: k + b resolved for every k and every direction.
static KPointStencil from_table(std::vector< std::vector< int > > neighbor_table, std::vector< Real > weights, std::vector< Vector3D > b_vectors={})
const Vector3D & b_vector(int direction) const
The b-vector of a direction, in whatever units the shells were solved in.
int number_of_directions() const
const std::vector< Real > & weights() const
int number_of_kpoints() const
Real weight(int direction) const
Real total_weight() const
const std::array< int, 3 > & grid_dimensions() const
int neighbor(int kpoint, int direction) const
The k-point that k + b lands on.
double Real
Integer 3-vector for Miller indices / FFT grid indices.