Loading...
Searching...
No Matches
quality_assessor.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * WannierQualityAssessor — measures a WannierQualityReport: the statistics of
5 * the per-function spreads, and the centres against the atoms and the bonds
6 * of the structure the model belongs to.
7 *
8 * The structure is fixed for the life of the assessor, so what depends on it
9 * alone -- which pairs of atoms are bonded, and where their midpoints are --
10 * is worked out once, when it is built. A bond is a pair of atoms, over
11 * periodic images, within BOND_TOLERANCE of the shortest interatomic
12 * distance; the image range of two covers a cell whose shortest bond crosses
13 * a boundary twice, which a doubled cell with a small basis can do.
14 *
15 * Two outlier flags sit beside the variance of the spreads: a function more
16 * than OUTLIER_RATIO times the median, and a function above
17 * LARGE_SPREAD_ANGSTROM_SQUARED, which no s or p Wannier function of a
18 * well-formed model reaches.
19 */
20
21#include <algorithm>
22#include <cmath>
23#include <cstddef>
24#include <utility>
25#include <vector>
26
31
32namespace q3 {
33
35 public:
36 /// A function this many times the median spread is flagged.
37 static constexpr Real OUTLIER_RATIO = 5.0;
38 /// A function above this spread is flagged whatever the others do.
39 static constexpr Real LARGE_SPREAD_ANGSTROM_SQUARED = 10.0;
40 /// Two atoms this close to the shortest interatomic distance count as bonded.
41 static constexpr Real BOND_TOLERANCE = 1.2;
42
43 /**
44 * `real_lattice_bohr` has rows a1, a2, a3 in the Cartesian frame the
45 * centres are in, and `atom_positions_crystal` are fractional. No atoms
46 * is a structure the centres cannot be held against -- a record imported
47 * from files may arrive without one -- and skips those checks rather
48 * than refusing.
49 */
50 WannierQualityAssessor(const Matrix3x3& real_lattice_bohr,
51 std::vector<Vector3D> atom_positions_crystal)
52 : cartesian_from_crystal_(real_lattice_bohr.transpose())
53 , crystal_from_cartesian_(real_lattice_bohr.inverse().transpose())
54 , atoms_crystal_(std::move(atom_positions_crystal)) {
55 find_bonds();
56 }
57
58 /// The spreads and centres of `construction`'s final gauge, and the decay of its H(R).
59 [[nodiscard]] WannierQualityReport assess(const WannierConstructionReport& construction) const {
61 report.decay = construction.hamiltonian_decay;
63 summarize_spreads(report);
64 if (!construction.spread.centers_bohr.empty() && !atoms_crystal_.empty()) {
65 locate_centers(report, construction.spread.centers_bohr);
66 }
67 return report;
68 }
69
70 private:
71 static void summarize_spreads(WannierQualityReport& report) {
72 const std::vector<Real>& spreads = report.spreads_bohr_squared;
73 if (spreads.empty()) {
74 return;
75 }
76 std::vector<Real> sorted = spreads;
77 std::sort(sorted.begin(), sorted.end());
78 const std::size_t middle = sorted.size() / 2;
80 sorted.size() % 2 == 1 ? sorted[middle] : 0.5 * (sorted[middle - 1] + sorted[middle]);
81 report.spread_maximum_bohr_squared = sorted.back();
82
83 Real mean = 0.0;
84 for (const Real spread : spreads) {
85 mean += spread;
86 }
87 mean /= static_cast<Real>(spreads.size());
88 Real variance = 0.0;
89 for (const Real spread : spreads) {
90 variance += (spread - mean) * (spread - mean);
91 }
92 report.spread_variance_bohr_squared = variance / static_cast<Real>(spreads.size());
93
96 for (std::size_t index = 0; index < spreads.size(); index++) {
97 if (report.spread_median_bohr_squared > 0.0 &&
98 spreads[index] > OUTLIER_RATIO * report.spread_median_bohr_squared) {
99 report.spread_outliers.push_back(static_cast<int>(index));
100 }
101 if (spreads[index] > large) {
102 report.spreads_above_ten_angstrom_squared.push_back(static_cast<int>(index));
103 }
104 }
105 }
106
107 void locate_centers(WannierQualityReport& report,
108 const std::vector<Vector3D>& centers_bohr) const {
109 report.shortest_bond_bohr = shortest_bond_bohr_;
110 for (const Vector3D& center : centers_bohr) {
111 const Vector3D center_crystal = crystal_from_cartesian_ * center;
112 report.nearest_atom_distance_bohr.push_back(
113 nearest_distance(center_crystal, atoms_crystal_));
114 report.nearest_bond_midpoint_distance_bohr.push_back(
115 bond_midpoints_crystal_.empty()
116 ? 0.0
117 : nearest_distance(center_crystal, bond_midpoints_crystal_));
118 }
119 }
120
121 /// The shortest minimum-image distance from `point_crystal` to any of `sites_crystal`.
122 [[nodiscard]] Real nearest_distance(const Vector3D& point_crystal,
123 const std::vector<Vector3D>& sites_crystal) const {
124 Real nearest = -1.0;
125 for (const Vector3D& site : sites_crystal) {
126 const Real distance = minimum_image_distance(point_crystal - site);
127 if (nearest < 0.0 || distance < nearest) {
128 nearest = distance;
129 }
130 }
131 return nearest;
132 }
133
134 /// The shortest |f . A| over the periodic images of a fractional offset.
135 [[nodiscard]] Real minimum_image_distance(Vector3D offset_crystal) const {
136 for (int axis = 0; axis < 3; axis++) {
137 offset_crystal[axis] -= std::round(offset_crystal[axis]);
138 }
139 Real shortest = -1.0;
140 for_each_image(1, [&](const IntegerVector3D& image) {
141 const Real distance =
142 (cartesian_from_crystal_ * (offset_crystal + translation(image))).norm();
143 if (shortest < 0.0 || distance < shortest) {
144 shortest = distance;
145 }
146 });
147 return shortest;
148 }
149
150 /**
151 * Every pair of atoms over the periodic images, the shortest of them,
152 * and the midpoints of the pairs within BOND_TOLERANCE of it. An atom
153 * paired with its own zero image is the atom, not a bond.
154 */
155 void find_bonds() {
156 struct Pair {
157 Vector3D midpoint_crystal;
158 Real length;
159 };
160 std::vector<Pair> pairs;
161 Real shortest = -1.0;
162 for (std::size_t first = 0; first < atoms_crystal_.size(); first++) {
163 for (std::size_t second = first; second < atoms_crystal_.size(); second++) {
164 for_each_image(BOND_IMAGE_RANGE, [&](const IntegerVector3D& image) {
165 if (first == second && image == IntegerVector3D{0, 0, 0}) {
166 return;
167 }
168 const Vector3D separation =
169 atoms_crystal_[second] + translation(image) - atoms_crystal_[first];
170 const Real length = (cartesian_from_crystal_ * separation).norm();
171 pairs.push_back({atoms_crystal_[first] + separation * 0.5, length});
172 if (shortest < 0.0 || length < shortest) {
173 shortest = length;
174 }
175 });
176 }
177 }
178 shortest_bond_bohr_ = shortest > 0.0 ? shortest : 0.0;
179 for (const Pair& pair : pairs) {
180 if (pair.length <= BOND_TOLERANCE * shortest) {
181 bond_midpoints_crystal_.push_back(pair.midpoint_crystal);
182 }
183 }
184 }
185
186 /// Calls `visit` with every lattice translation whose components lie within +-`range` cells.
187 template <typename Visitor> static void for_each_image(int range, Visitor visit) {
188 for (int first = -range; first <= range; first++) {
189 for (int second = -range; second <= range; second++) {
190 for (int third = -range; third <= range; third++) {
191 visit(IntegerVector3D{first, second, third});
192 }
193 }
194 }
195 }
196
197 [[nodiscard]] static Vector3D translation(const IntegerVector3D& image) {
198 return Vector3D{
199 static_cast<Real>(image.x), static_cast<Real>(image.y), static_cast<Real>(image.z)};
200 }
201
202 static constexpr int BOND_IMAGE_RANGE = 2;
203
204 Matrix3x3 cartesian_from_crystal_;
205 Matrix3x3 crystal_from_cartesian_;
206 std::vector<Vector3D> atoms_crystal_;
207 std::vector<Vector3D> bond_midpoints_crystal_;
208 Real shortest_bond_bohr_ = 0.0;
209};
210
211} // namespace q3
WannierQualityAssessor(const Matrix3x3 &real_lattice_bohr, std::vector< Vector3D > atom_positions_crystal)
static constexpr Real BOND_TOLERANCE
Two atoms this close to the shortest interatomic distance count as bonded.
WannierQualityReport assess(const WannierConstructionReport &construction) const
The spreads and centres of construction's final gauge, and the decay of its H(R).
static constexpr Real OUTLIER_RATIO
A function this many times the median spread is flagged.
static constexpr Real LARGE_SPREAD_ANGSTROM_SQUARED
A function above this spread is flagged whatever the others do.
constexpr Real BOHR_RADIUS_ANGSTROM
Bohr radius in Ångströms (BOHR_RADIUS_SI * 1e10; BOHR_RADIUS_ANGSTROM)
double Real
3×3 matrix stored row-major: row i = mat[i]
std::vector< Real > spreads_bohr_squared
Omega_n per function, bohr squared.
std::vector< Vector3D > centers_bohr
HamiltonianDecay hamiltonian_decay
How far H(R) has decayed by the edge of its Wigner-Seitz domain.
Definition init.hpp:28
std::vector< Real > spreads_bohr_squared
std::vector< int > spread_outliers
Zero-based indices of functions far above the median spread.
Real spread_variance_bohr_squared
Population variance of the spreads across functions, the no-reference criterion.
std::vector< int > spreads_above_ten_angstrom_squared
Zero-based indices of functions above ten square angstrom.