Loading...
Searching...
No Matches
maximally_localized_gauge_choice.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * MaximallyLocalizedGaugeChoice — the Marzari-Vanderbilt spread minimised
5 * over the gauge after the subspace is fixed (SpreadMinimizer), the frames
6 * rotated by the unitaries it found.
7 *
8 * Two things a record can lack make this impossible rather than merely
9 * poor, and both are refused before anything is iterated. The spread is
10 * defined through the neighbour overlaps M_mn(k,b), and a record without
11 * them has nothing to minimise. And the centres are finite-difference Berry
12 * phases, which need k+b and k-b to be distinct: on a mesh with a two-point
13 * axis they coincide, and a minimiser run there reports a spread bounded by
14 * the mesh rather than by the physics -- a number that looks like an answer.
15 * require_supported() states both, so the factory can ask before the
16 * subspace is chosen and the class can insist when it is called directly.
17 */
18
19#include <array>
20#include <cstddef>
21#include <optional>
22#include <stdexcept>
23#include <string>
24#include <vector>
25
40
41namespace q3 {
42
44 public:
46 : settings_(settings) {}
47
48 /// Throws WannierDiagnosticError, with the way out named, when the record cannot be localised.
49 static void require_supported(const WannierConstructionInputs& inputs) {
50 if (!inputs.has_overlaps()) {
53 "MaximallyLocalizedGaugeChoice: localization needs the neighbour overlaps "
54 "M_mn(k,b), and this record carries none. The spread is defined through them; "
55 "without them there is nothing to minimise. Recapture the record with Q3, or ask "
56 "for control.interpolation.localization: none and accept the projection gauge.");
57 }
58 const std::optional<int> two_point_direction = inputs.two_point_axis_direction();
59 if (two_point_direction) {
60 const std::array<int, 3>& grid = inputs.record().kpoint_grid_dimensions;
61 const IntegerVector3D& step = inputs.stencil().grid_step(*two_point_direction);
64 "MaximallyLocalizedGaugeChoice: the k-mesh is " + std::to_string(grid[0]) + "x" +
65 std::to_string(grid[1]) + "x" + std::to_string(grid[2]) +
66 ", and the neighbour direction (" + std::to_string(step.x) + ", " +
67 std::to_string(step.y) + ", " + std::to_string(step.z) +
68 ") reaches the same k-point as its opposite. The Wannier centres are "
69 "finite-difference Berry phases, which need k+b and k-b to be distinct, so "
70 "localization cannot produce meaningful centres or spreads on this mesh. Use "
71 "at least three k-points along every periodic direction, or set "
72 "control.interpolation.localization: none and accept the projection gauge.");
73 }
74 }
75
76 [[nodiscard]] WannierGauge choose(const WannierConstructionInputs& inputs,
77 const WannierSubspace& subspace) const override {
78 require_supported(inputs);
79 const WannierInputs& record = inputs.record();
80 const bool has_trial_centers = record.has_trial_orbital_centers();
81 if (!has_trial_centers) {
82 log::info("localization: the record carries no trial-orbital centres (older format "
83 "or imported), so the projection gauge's own centres guide the phase "
84 "branches; recapture with Q3 to guide by the trial orbitals instead");
85 }
86 const BlochOverlaps overlaps =
87 inputs.overlaps_in_gauge(subspace.classification, subspace.frames);
88 /**
89 * The guiding centres are taken once and then held for the whole run.
90 *
91 * The trial-orbital positions when the record carries them, which is
92 * Wannier90's choice and the right one; the projection gauge's own
93 * centres only as a fallback. The difference is not academic. On a
94 * rattled four-atom Si3C cell the fallback started at Omega = 186 A^2
95 * and stalled at 170, with centres 5 A from any atom, while Wannier90
96 * on the same matrices started at 36.7 and finished at 25.9: with the
97 * phase branches inconsistent across k, the Berry-phase centres of the
98 * starting gauge are already garbage, and guiding to garbage keeps it.
99 */
100 const SpreadFunctional functional(
101 inputs.stencil(),
103 std::make_shared<const GuidedPhaseBranch>(
104 has_trial_centers
107 .evaluate(overlaps)
108 .centers_bohr));
109 const SpreadMinimizationResult localized =
110 SpreadMinimizer(inputs.stencil(), settings_).run(overlaps, functional);
111
112 WannierGauge gauge;
113 gauge.frames.reserve(subspace.frames.size());
114 for (std::size_t kpoint_index = 0; kpoint_index < subspace.frames.size(); kpoint_index++) {
115 gauge.frames.push_back(multiply(MatrixOperation::None,
116 subspace.frames[kpoint_index],
118 localized.gauges[kpoint_index]));
119 }
120 gauge.spread = SpreadReport::evaluated_as(localized.terms,
121 has_trial_centers,
124 SpreadReport& spread = gauge.spread;
125 spread.localized = true;
126 spread.localization_iterations = localized.iterations;
127 spread.localization_converged = localized.stationary;
128 spread.localization_gradient_norm = localized.gradient_norm;
129 return gauge;
130 }
131
132 private:
134};
135
136} // namespace q3
const IntegerVector3D & grid_step(int direction) const
MaximallyLocalizedGaugeChoice(SpreadMinimizationSettings settings)
WannierGauge choose(const WannierConstructionInputs &inputs, const WannierSubspace &subspace) const override
static void require_supported(const WannierConstructionInputs &inputs)
Throws WannierDiagnosticError, with the way out named, when the record cannot be localised.
SpreadTerms evaluate(const BlochOverlaps &rotated_overlaps) const
rotated_overlaps: N x N at every (k, b), between the Wannier-gauge states at k and k+b.
SpreadMinimizationResult run(const BlochOverlaps &overlaps, const SpreadFunctional &functional) const
std::optional< int > two_point_axis_direction() const
The direction of a two-point axis, if any; KPointStencil::two_point_axis_direction says why.
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 KPointStencil & stencil() const
BlochOverlaps overlaps_in_gauge(const BandClassification &classification, const std::vector< DenseMatrix< Complex > > &frames) const
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.
@ two_point_axis
The k-mesh has an axis of two points, on which k+b and k-b coincide.
@ localization_unsupported
The record lacks the overlaps that localisation minimises.
Integer 3-vector for Miller indices / FFT grid indices.
Real gradient_norm
Root-mean-square gradient per k-point where the minimisation stopped.
Real initial_minimizable_bohr_squared
Omega_D + Omega_OD before any rotation, for the log line and the report.
std::vector< DenseMatrix< Complex > > gauges
One N x N unitary per k-point; post-multiplies the gauge.
static SpreadReport evaluated_as(const SpreadTerms &terms, bool guided_by_trial_orbitals, std::vector< Vector3D > trial_orbital_centers_bohr, Real initial_minimizable_bohr_squared)
bool localized
Whether the gauge was rotated by the spread minimiser at all.
std::vector< Vector3D > centers_bohr
One centre per Wannier function, in bohr, in the same Cartesian frame as b.
SpreadReport spread
std::vector< DenseMatrix< Complex > > frames
std::vector< Vector3D > trial_orbital_centers_bohr
Definition inputs.hpp:87
std::array< int, 3 > kpoint_grid_dimensions
Definition inputs.hpp:37
bool has_trial_orbital_centers() const
Whether every trial orbital's position is known: the localiser's guiding centres.
Definition inputs.hpp:102
BandClassification classification
std::vector< DenseMatrix< Complex > > frames