Loading...
Searching...
No Matches
projection_gauge.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * ProjectionGauge — the one-shot ("projection-only") gauge
5 * U(k) = A(k) [A(k)^dagger A(k)]^(-1/2).
6 *
7 * This is the starting gauge for maximal localization, and on its own it
8 * already produces usable Wannier functions for an isolated manifold: it is
9 * the Lowdin-orthonormalization of the trial-orbital projections, i.e. the
10 * unitary closest to A in the least-squares sense. Marzari & Vanderbilt, PRB
11 * 56, 12847 (1997) §III.A introduces it as the initial guess; for a
12 * well-chosen trial set (Si sp3 for the valence manifold, say) the spread it
13 * gives is already close to the minimum, which is why interpolated bands from
14 * this gauge alone are meaningful.
15 *
16 * Computed through the singular value decomposition A = V S W^dagger, for
17 * which U = V W^dagger exactly. Going through the SVD rather than forming
18 * A^dagger A and inverting its square root matters: A^dagger A squares the
19 * condition number, so a trial set that merely spans the manifold poorly
20 * becomes numerically singular when it need not be. The SVD also hands us the
21 * singular values, which are the direct diagnostic of that spanning quality
22 * -- a near-zero one means the trial orbitals do not span the manifold, and
23 * this class refuses rather than returning a silently rank-deficient gauge.
24 *
25 * Rectangular A (more bands than Wannier functions) is the normal case once
26 * bands are being disentangled; U is then bands x functions with orthonormal
27 * columns, not a square unitary.
28 */
29
30#include <algorithm>
31#include <cmath>
32#include <complex>
33#include <cstddef>
34#include <sstream>
35#include <stdexcept>
36#include <string>
37#include <vector>
38
47
48namespace q3 {
49
51 public:
52 /**
53 * A smallest singular value below `singular_value_tolerance` means the
54 * trial orbitals fail to span the target manifold there, and every
55 * downstream quantity built on the resulting gauge would be quietly
56 * meaningless; it is refused.
57 */
58 explicit ProjectionGauge(Real singular_value_tolerance =
60 : singular_value_tolerance_(singular_value_tolerance) {}
61
62 /// U(k) for every k-point's projection block; a refusal names the k-point it happened at.
63 [[nodiscard]] std::vector<DenseMatrix<Complex>>
64 compute_all(const std::vector<DenseMatrix<Complex>>& projections) const {
65 std::vector<DenseMatrix<Complex>> gauges;
66 gauges.reserve(projections.size());
67 for (std::size_t kpoint_index = 0; kpoint_index < projections.size(); kpoint_index++) {
68 try {
69 gauges.push_back(compute(projections[kpoint_index]));
70 } catch (const WannierDiagnosticError& error) {
71 throw WannierDiagnosticError(error.diagnostic(),
72 std::string(error.what()) + " This is k-point " +
73 std::to_string(kpoint_index) + ".");
74 }
75 }
76 return gauges;
77 }
78
79 /**
80 * U from one k-point's projection block, bands x Wannier functions with
81 * the band as the row; U has the same shape.
82 */
83 [[nodiscard]] DenseMatrix<Complex> compute(const DenseMatrix<Complex>& projections) const {
84 if (projections.rows() < projections.columns()) {
85 throw std::invalid_argument("ProjectionGauge::compute: number_of_bands (" +
86 std::to_string(projections.rows()) +
87 ") must be at least number_of_wannier_functions (" +
88 std::to_string(projections.columns()) + ")");
89 }
90
91 const SingularValueDecompositionResult decomposition =
92 SingularValueDecomposition("ProjectionGauge").compute(projections);
93
94 const Real smallest =
95 decomposition.singular_values.empty() ? 0.0 : decomposition.singular_values.back();
96 if (smallest < singular_value_tolerance_) {
97 /**
98 * Scientific notation, not std::to_string: the values compared here
99 * are near zero by construction, and six fixed decimals would report
100 * both of them as "0.000000" -- a diagnostic that names no number.
101 */
102 std::ostringstream message;
103 message << "ProjectionGauge::compute: the trial orbitals do not span the target "
104 "manifold at this k-point -- smallest singular value of A is "
105 << std::scientific << smallest << ", below the tolerance "
106 << singular_value_tolerance_
107 << ". Choose different trial orbitals or a different band selection; a gauge "
108 "built from a rank-deficient A is meaningless.";
110 }
111
113 decomposition.left_vectors,
115 decomposition.right_vectors_adjoint);
116 }
117
118 private:
119 Real singular_value_tolerance_;
120};
121
122} // namespace q3
int rows() const
int columns() const
DenseMatrix< Complex > compute(const DenseMatrix< Complex > &projections) const
ProjectionGauge(Real singular_value_tolerance=math::tolerances::wannier_projection_singular_value_threshold)
std::vector< DenseMatrix< Complex > > compute_all(const std::vector< DenseMatrix< Complex > > &projections) const
U(k) for every k-point's projection block; a refusal names the k-point it happened at.
SingularValueDecompositionResult compute(DenseMatrix< Complex > matrix) const
The decomposition of matrix, taken by value because zgesvd destroys its input.
constexpr double wannier_projection_singular_value_threshold
double Real
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.
@ gauge_singular
The trial orbitals do not span the chosen manifold at some k-point.