Loading...
Searching...
No Matches
projectability_disentanglement_selection.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * ProjectabilityDisentanglementSelection — the N states chosen out of a
5 * wider window: classify every band at every k-point by its projectability
6 * and its energy (BandClassifier), iterate the subspace that is smoothest
7 * across the mesh (SubspaceDisentangler), and fix the projection gauge
8 * inside it.
9 *
10 * The frame handed on is V(k) U(k): the iteration's subspace times the
11 * projection gauge of A(k) restricted to it. The initial subspace of the
12 * iteration is the orthonormalised column space of the restricted A(k) --
13 * which is exactly what the projection gauge computes, so the same kernel
14 * serves both the guess and the gauge.
15 */
16
17#include <cstddef>
18#include <stdexcept>
19#include <string>
20#include <utility>
21#include <vector>
22
41
42namespace q3 {
43
45 public:
46 /// Inverted thresholds are refused here, by the classifier, before any record is read.
48 : settings_(settings), classifier_(settings.thresholds) {}
49
50 [[nodiscard]] WannierSubspace choose(const WannierConstructionInputs& inputs) const override {
51 require_supported(inputs);
52 const WannierInputs& record = inputs.record();
53
54 WannierSubspace subspace;
55 subspace.classification = classifier_.classify(record);
56 log_classification(subspace.classification.summary);
57
58 const std::vector<DenseMatrix<Complex>> projections =
60 const DisentanglementResult result =
62 subspace.classification.kpoints,
64 settings_.iteration)
65 .run(inputs.overlaps(), ProjectionGauge().compute_all(projections));
66 subspace.frames = frames_of(result.subspaces, projections, subspace.classification, record);
67
68 subspace.report.performed = true;
69 subspace.report.classification = subspace.classification.summary;
72 subspace.report.iterations = result.iterations;
73 return subspace;
74 }
75
76 private:
77 /**
78 * Disentanglement reads the overlaps and the projectabilities; a record without them is
79 * refused.
80 */
81 static void require_supported(const WannierConstructionInputs& inputs) {
82 if (!inputs.has_overlaps()) {
85 "ProjectabilityDisentanglementSelection: disentanglement needs the neighbour "
86 "overlaps M_mn(k,b), and this record carries none. A record imported from another "
87 "code's projections and eigenvalues alone cannot be disentangled; recapture it "
88 "with Q3.");
89 }
90 if (!inputs.record().has_projectabilities()) {
91 throw WannierDiagnosticError(
93 "ProjectabilityDisentanglementSelection: disentanglement classifies bands by "
94 "their projectability p_nk = sum_m |A_mn(k)|^2, and this record carries none. "
95 "Like the overlaps above this is a capability the record lacks rather than a "
96 "value to choose; recapture it with Q3.");
97 }
98 }
99
100 /// What the classifier decided, and -- never silently -- what it kept out of the freeze.
101 static void log_classification(const BandClassificationSummary& summary) {
102 log::info("disentanglement: {} ({} band{} crossing the Fermi level); frozen window "
103 "E_F + {:.3f} eV{}",
104 summary.metallic ? "a metal" : "an insulator",
105 summary.number_of_bands_crossing_fermi,
106 summary.number_of_bands_crossing_fermi == 1 ? "" : "s",
107 summary.frozen_window_above_fermi_rydberg * constants::RY_TO_EV,
108 summary.frozen_window_automatic ? " chosen automatically" : " as given");
109 for (const BandExcludedFromFreeze& band : summary.bands_excluded_from_freeze) {
110 log::warn("disentanglement: band {} lies inside the frozen window at {} k-point{} "
111 "where its projectability is below the minimum, so it was excluded from "
112 "the freeze there rather than frozen -- the trial orbitals cannot "
113 "represent it, and freezing it would make the gauge singular. The model "
114 "has no state of that character at those k-points.",
115 band.band_index + 1,
116 band.number_of_kpoints,
117 band.number_of_kpoints == 1 ? "" : "s");
118 }
119 }
120
121 /// V(k) U(k): each chosen subspace times the projection gauge of A(k) inside it.
122 [[nodiscard]] static std::vector<DenseMatrix<Complex>>
123 frames_of(const std::vector<DenseMatrix<Complex>>& chosen_subspaces,
124 const std::vector<DenseMatrix<Complex>>& projections,
125 const BandClassification& classification,
126 const WannierInputs& record) {
127 std::vector<DenseMatrix<Complex>> frames;
128 frames.reserve(chosen_subspaces.size());
129 for (std::size_t kpoint_index = 0; kpoint_index < chosen_subspaces.size(); kpoint_index++) {
130 const DenseMatrix<Complex>& chosen = chosen_subspaces[kpoint_index];
131 const DenseMatrix<Complex> reduced = multiply(MatrixOperation::ConjugateTranspose,
132 chosen,
134 projections[kpoint_index]);
135 frames.push_back(multiply(MatrixOperation::None,
136 chosen,
138 gauge_inside(reduced,
139 static_cast<int>(kpoint_index),
140 classification.kpoints[kpoint_index],
141 record)));
142 }
143 return frames;
144 }
145
146 /**
147 * The projection gauge of A(k) inside the chosen subspace.
148 *
149 * A rank-deficient A inside the chosen subspace has one cause that
150 * dominates every other, and the gauge cannot see it: a band frozen by
151 * the ENERGY rule despite having no projectability puts a direction in
152 * the subspace that the trial orbitals cannot reach. Freezing such a
153 * state is deliberate -- it is what keeps a metal's Fermi surface in the
154 * model -- so it is not refused during classification. But when it does
155 * go singular, the k-point and the offending band are what the user
156 * needs, and by the time the SVD speaks they are gone.
157 */
158 [[nodiscard]] static DenseMatrix<Complex> gauge_inside(const DenseMatrix<Complex>& reduced,
159 int kpoint_index,
160 const KPointBandClassification& entry,
161 const WannierInputs& record) {
162 try {
163 return ProjectionGauge().compute(reduced);
164 } catch (const WannierDiagnosticError& error) {
165 throw WannierDiagnosticError(
166 error.diagnostic(),
167 std::string(error.what()) + " This is k-point " + std::to_string(kpoint_index) +
168 " of the disentangled window, where " + std::to_string(entry.number_frozen()) +
169 " of " + std::to_string(entry.number_kept()) + " kept bands are frozen" +
170 likely_cause(kpoint_index, entry, record));
171 }
172 }
173
174 /**
175 * The rest of the sentence above: the least projectable frozen band and
176 * what to do about it -- or, where nothing is frozen, that the energy
177 * rule is not the cause. Naming a band there would name one that was
178 * never frozen, at a projectability nobody measured.
179 */
180 [[nodiscard]] static std::string likely_cause(int kpoint_index,
181 const KPointBandClassification& entry,
182 const WannierInputs& record) {
183 if (entry.frozen_positions.empty()) {
184 return ". No band is frozen there, so the energy rule is not the cause: the subspace "
185 "the iteration chose holds a direction the trial orbitals cannot reach. Raise "
186 "control.interpolation.projectability_minimum so bands they cannot represent "
187 "are not kept, or give the trial orbitals a shell that can represent them.";
188 }
189 const std::vector<Real>& projectabilities =
190 record.projectabilities[static_cast<std::size_t>(kpoint_index)];
191 int weakest_band = entry.kept_bands[static_cast<std::size_t>(entry.frozen_positions[0])];
192 for (const int position : entry.frozen_positions) {
193 const int band = entry.kept_bands[static_cast<std::size_t>(position)];
194 if (projectabilities[static_cast<std::size_t>(band)] <
195 projectabilities[static_cast<std::size_t>(weakest_band)]) {
196 weakest_band = band;
197 }
198 }
199 return " and the least projectable of them is band " + std::to_string(weakest_band + 1) +
200 " at p = " +
201 std::to_string(projectabilities[static_cast<std::size_t>(weakest_band)]) +
202 ". A frozen band with no projectability is a direction the trial orbitals cannot "
203 "reach, and it is the usual cause: lower "
204 "control.interpolation.frozen_window_above_fermi_ev so the energy rule stops "
205 "claiming it, or give the trial orbitals a shell that can represent it.";
206 }
207
208 ProjectabilityDisentanglementSettings settings_;
209 BandClassifier classifier_;
210};
211
212} // namespace q3
BandClassification classify(const WannierInputs &record) const
WannierSubspace choose(const WannierConstructionInputs &inputs) const override
ProjectabilityDisentanglementSelection(ProjectabilityDisentanglementSettings settings)
Inverted thresholds are refused here, by the classifier, before any record is read.
DisentanglementResult run(const BlochOverlaps &overlaps, std::vector< DenseMatrix< Complex > > initial_subspaces) const
std::vector< DenseMatrix< Complex > > projections_restricted_to(const BandClassification &classification) const
A(k) restricted to the bands classification keeps at each k-point: kept x N, band as row.
bool has_overlaps() const
Whether the record carries M_mn(k,b); implies has_stencil().
const WannierInputs & record() const
The inputs as the record stores them.
const BlochOverlaps & overlaps() const
const KPointStencil & stencil() const
constexpr Real RY_TO_EV
void warn(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:187
void error(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:192
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
DenseMatrix< Complex > multiply(MatrixOperation left_operation, const DenseMatrix< Complex > &left, MatrixOperation right_operation, const DenseMatrix< Complex > &right, Complex alpha=Complex(1.0, 0.0))
alpha * op(left) * op(right), as a new matrix of the product's shape.
@ disentanglement_unsupported
The record lacks the overlaps or the projectabilities that disentanglement reads.
std::vector< KPointBandClassification > kpoints
BandClassificationSummary summary
Real gauge_invariant_spread_bohr_squared
Omega_I of the subspace the iteration settled on, bohr squared.
bool performed
Whether the subspace was chosen by classification and iteration rather than taken as given.
BandClassificationSummary classification
The classifier's verdict on the system: metal or not, the window applied, the exclusions.
std::vector< DenseMatrix< Complex > > subspaces
V(k): kept_bands x N, one per k-point.
bool has_projectabilities() const
Whether the record carries p_nk: classification by projectability needs it.
Definition inputs.hpp:98
BandClassification classification
std::vector< DenseMatrix< Complex > > frames
DisentanglementReport report