Loading...
Searching...
No Matches
init.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * InterpolatedDensityOfStatesCalculator — a Gaussian-broadened density of
5 * states from a QuantumStateInterpolator evaluated on a dense uniform k-mesh.
6 *
7 * Bands and DOS are the same operation asked for different k-points:
8 * evaluate the model, then either keep the eigenvalues ordered along a path
9 * or histogram them over a mesh. That is why an interpolated DOS costs
10 * almost nothing once the model exists -- evaluating it at 10^5 k-points is
11 * cheap next to the calculation that produced it -- and why this calculator
12 * is written against the interpolator interface rather than any one
13 * method's model.
14 *
15 * Gaussian broadening matches what q3.workflows.dos does for directly
16 * computed eigenvalues, so a DOS from this class and a direct one can be
17 * compared at identical broadening and the difference attributed to the
18 * model rather than to the smearing. Q3 has no tetrahedron integrator; when
19 * one exists it belongs beside this as an alternative strategy.
20 */
21
22#include <algorithm>
23#include <cmath>
24#include <stdexcept>
25#include <vector>
26
30
31namespace q3 {
32
33/// A density of states on a uniform energy grid.
34struct [[nodiscard]] InterpolatedDensityOfStates {
35 std::vector<Real> energies_rydberg;
36 /// States per Rydberg per unit cell, spin degeneracy NOT included.
37 std::vector<Real> density_of_states;
38};
39
41 public:
43 : interpolator_(interpolator) {}
44
45 /**
46 * DOS on a uniform Gamma-centred `mesh_x` x `mesh_y` x `mesh_z` mesh,
47 * broadened by `broadening_rydberg` onto `energy_step_count` points.
48 *
49 * The energy window spans the eigenvalue range plus a margin on each
50 * side. The margin is at least `energy_margin_rydberg` and at least the
51 * Gaussian's own cutoff of 8 sigma: a fixed margin smaller than the
52 * broadening clips the tails of the band-edge states, and the integral
53 * below stops returning the band count -- which it must, since the
54 * normalisation is one state per band per k-point divided by the
55 * k-point count. Nothing here doubles for spin; a caller wanting states
56 * per cell for an unpolarised system multiplies by two, the convention
57 * q3.workflows.dos follows.
58 */
59 [[nodiscard]] InterpolatedDensityOfStates compute(int mesh_x,
60 int mesh_y,
61 int mesh_z,
62 Real broadening_rydberg,
63 int energy_step_count = 1000,
64 Real energy_margin_rydberg = 0.05) const {
65 if (mesh_x <= 0 || mesh_y <= 0 || mesh_z <= 0) {
66 throw std::invalid_argument(
67 "InterpolatedDensityOfStatesCalculator::compute: every mesh dimension must be "
68 "positive");
69 }
70 if (broadening_rydberg <= 0.0) {
71 throw std::invalid_argument(
72 "InterpolatedDensityOfStatesCalculator::compute: broadening must be positive");
73 }
74 if (energy_step_count < 2) {
75 throw std::invalid_argument(
76 "InterpolatedDensityOfStatesCalculator::compute: need at least 2 energy points");
77 }
78
79 std::vector<std::vector<Real>> sampled_eigenvalues;
80 sampled_eigenvalues.reserve(static_cast<std::size_t>(mesh_x) * mesh_y * mesh_z);
81 for (int index_x = 0; index_x < mesh_x; index_x++) {
82 for (int index_y = 0; index_y < mesh_y; index_y++) {
83 for (int index_z = 0; index_z < mesh_z; index_z++) {
84 Vector3D kpoint(static_cast<Real>(index_x) / mesh_x,
85 static_cast<Real>(index_y) / mesh_y,
86 static_cast<Real>(index_z) / mesh_z);
87 sampled_eigenvalues.push_back(interpolator_.eigenvalues_at(kpoint));
88 }
89 }
90 }
91
92 Real minimum_energy = sampled_eigenvalues.front().front();
93 Real maximum_energy = sampled_eigenvalues.front().front();
94 for (const std::vector<Real>& row : sampled_eigenvalues) {
95 for (Real value : row) {
96 minimum_energy = std::min(minimum_energy, value);
97 maximum_energy = std::max(maximum_energy, value);
98 }
99 }
100 const Real cutoff = 8.0 * broadening_rydberg;
101 const Real margin = std::max(energy_margin_rydberg, cutoff);
102 minimum_energy -= margin;
103 maximum_energy += margin;
104
106 result.energies_rydberg.resize(energy_step_count);
107 result.density_of_states.assign(energy_step_count, 0.0);
108 const Real energy_step =
109 (maximum_energy - minimum_energy) / static_cast<Real>(energy_step_count - 1);
110 for (int step = 0; step < energy_step_count; step++) {
111 result.energies_rydberg[step] = minimum_energy + step * energy_step;
112 }
113
114 /**
115 * Unit-area Gaussian per state. Contributions beyond the 8 sigma
116 * cutoff are below 1e-14 of the peak and skipped: with 10^5 k-points
117 * times a few bands times 10^3 energy points, evaluating every pair
118 * would dominate the runtime for no numerical gain.
119 */
120 const Real normalisation = 1.0 / (broadening_rydberg * std::sqrt(constants::two_pi) *
121 static_cast<Real>(sampled_eigenvalues.size()));
122 for (const std::vector<Real>& row : sampled_eigenvalues) {
123 for (Real eigenvalue : row) {
124 int first = static_cast<int>(
125 std::ceil((eigenvalue - cutoff - minimum_energy) / energy_step));
126 int last = static_cast<int>(
127 std::floor((eigenvalue + cutoff - minimum_energy) / energy_step));
128 first = std::max(first, 0);
129 last = std::min(last, energy_step_count - 1);
130 for (int step = first; step <= last; step++) {
131 const Real offset =
132 (result.energies_rydberg[step] - eigenvalue) / broadening_rydberg;
133 result.density_of_states[step] +=
134 normalisation * std::exp(-0.5 * offset * offset);
135 }
136 }
137 }
138 return result;
139 }
140
141 private:
142 const QuantumStateInterpolator& interpolator_;
143};
144
145} // namespace q3
InterpolatedDensityOfStates compute(int mesh_x, int mesh_y, int mesh_z, Real broadening_rydberg, int energy_step_count=1000, Real energy_margin_rydberg=0.05) const
Definition init.hpp:59
InterpolatedDensityOfStatesCalculator(const QuantumStateInterpolator &interpolator)
Definition init.hpp:42
virtual std::vector< Real > eigenvalues_at(const Vector3D &kpoint) const =0
Ascending eigenvalues in Rydberg at one k-point in crystal coordinates.
constexpr Real two_pi
Definition constants.hpp:25
double Real
A density of states on a uniform energy grid.
Definition init.hpp:34
std::vector< Real > density_of_states
States per Rydberg per unit cell, spin degeneracy NOT included.
Definition init.hpp:37
std::vector< Real > energies_rydberg
Definition init.hpp:35