Loading...
Searching...
No Matches
hydrogenic_orbital_set.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * PlanewaveHydrogenicOrbitalSet — s-type orbitals at arbitrary centres,
5 * rendered in the plane-wave basis at one k-point.
6 *
7 * The pseudo-atomic orbitals a pseudopotential ships are all centred on
8 * atoms, which is the wrong shape for a material whose Wannier functions
9 * are not. Silicon is the standard example: its four valence Wannier
10 * functions sit on the Si-Si BONDS, not on either atom, and no subset of
11 * atom-centred orbitals spans that manifold well. Being able to place
12 * orbitals freely is what makes an isolated-manifold Wannierization
13 * possible at all without a minimiser.
14 *
15 * A hydrogenic 1s of decay a,
16 *
17 * g(r) = (pi a^3)^(-1/2) exp(-|r - tau| / a)
18 *
19 * has an analytic Fourier transform,
20 *
21 * g(q) = (pi a^3)^(-1/2) * 8 pi a^3 / (1 + (q a)^2)^2 * exp(-i q.tau)
22 *
23 * so this needs no radial table and no interpolation -- unlike the
24 * pseudo-atomic set, which reads a tabulated chi(q). That also means there
25 * is no interpolation error here to trade off against anything.
26 *
27 * Only l = 0 is implemented. An s orbital is spherically symmetric, so its
28 * angular factor is the constant Y_00 and the i^l phase is 1; both drop out
29 * of the Loewdin orthonormalization that follows anyway. Higher l would need
30 * the real spherical harmonics and the i^l convention the atomic-orbital
31 * builder already uses, and is deliberately left until something needs it.
32 *
33 * The overall normalization is likewise irrelevant downstream: consumers
34 * orthonormalize these in the S metric before projecting, which divides it
35 * out. It is kept correct anyway so that a raw projection is interpretable
36 * on its own.
37 */
38
39#include <cmath>
40#include <sstream>
41#include <stdexcept>
42#include <string>
43#include <utility>
44#include <vector>
45
53
54namespace q3 {
55
56/// One s-type orbital: where it sits and how fast it decays.
57struct [[nodiscard]] HydrogenicOrbital {
58 /// Centre in crystal (fractional) coordinates.
60 /// Decay length in bohr; the Wannier90 convention's `zona` is 1/a.
61 Real decay_length_bohr = 1.0;
62};
63
65 public:
66 explicit PlanewaveHydrogenicOrbitalSet(std::vector<HydrogenicOrbital> orbitals)
67 : orbitals_(std::move(orbitals)) {
68 for (const HydrogenicOrbital& orbital : orbitals_) {
69 if (orbital.decay_length_bohr <= 0.0) {
70 throw std::invalid_argument(
71 "PlanewaveHydrogenicOrbitalSet: decay_length_bohr must be positive, got " +
72 std::to_string(orbital.decay_length_bohr));
73 }
74 }
75 }
76
77 [[nodiscard]] int number_of_orbitals() const override {
78 return static_cast<int>(orbitals_.size());
79 }
80
81 [[nodiscard]] const std::vector<HydrogenicOrbital>& orbitals() const {
82 return orbitals_;
83 }
84
85 /**
86 * Follows the same conventions as build_atomic_orbitals_g_space: the
87 * structure factor is e^{-i 2*pi (k+G).tau} with both the k-point and
88 * the centre in alat-relative Cartesian units, and |k+G| carries the
89 * 2*pi/alat factor to become a physical wavevector.
90 */
91 void build(int kpoint_index,
92 int number_of_plane_waves,
93 const std::vector<int>& g_vector_indices,
94 const Crystal& crystal_structure,
95 const KPointsGrid& kpoint_grid,
96 const PlanewaveBasis& planewave_basis,
97 std::vector<std::vector<Complex>>& orbitals) const override {
98 const Real two_pi_over_lattice_parameter =
99 crystal_structure.two_pi_over_lattice_parameter();
100 const Vector3D kpoint_crystal = kpoint_grid[kpoint_index].coordinates;
101 const Matrix3x3& reciprocal_lattice = crystal_structure.reciprocal_lattice_vectors();
102
103 Vector3D kpoint_cartesian;
104 for (int dimension = 0; dimension < 3; dimension++) {
105 kpoint_cartesian[dimension] = kpoint_crystal.x * reciprocal_lattice[0][dimension] +
106 kpoint_crystal.y * reciprocal_lattice[1][dimension] +
107 kpoint_crystal.z * reciprocal_lattice[2][dimension];
108 }
109
110 orbitals.assign(orbitals_.size(),
111 std::vector<Complex>(number_of_plane_waves, Complex(0.0, 0.0)));
112
113 for (std::size_t orbital_index = 0; orbital_index < orbitals_.size(); orbital_index++) {
114 const HydrogenicOrbital& orbital = orbitals_[orbital_index];
115 /**
116 * The centre arrives in crystal coordinates; carry it to the
117 * alat-relative Cartesian frame the structure factor below uses,
118 * exactly as Crystal::convert_from_crystal_to_cartesian does for
119 * an atom.
120 */
121 const Matrix3x3& lattice_vectors = crystal_structure.lattice_vectors();
122 Vector3D center_cartesian;
123 for (int dimension = 0; dimension < 3; dimension++) {
124 center_cartesian[dimension] =
125 orbital.center_crystal.x * lattice_vectors[0][dimension] +
126 orbital.center_crystal.y * lattice_vectors[1][dimension] +
127 orbital.center_crystal.z * lattice_vectors[2][dimension];
128 }
129
130 const Real kpoint_phase_argument =
132 (kpoint_cartesian.x * center_cartesian.x + kpoint_cartesian.y * center_cartesian.y +
133 kpoint_cartesian.z * center_cartesian.z);
134 const Complex kpoint_phase = compute_phase_factor(kpoint_phase_argument);
135
136 const Real decay = orbital.decay_length_bohr;
137 /**
138 * g(q) = 8 sqrt(pi a^3) / (1 + a^2 q^2)^2 is the transform of the
139 * unit-normalized 1s orbital; the 1/sqrt(Omega) carries it to the
140 * plane-wave coefficient convention the Kleinman-Bylander
141 * projectors and the pseudo-atomic orbitals use.
142 */
143 const Real normalisation = 8.0 * constants::pi * decay * decay * decay /
144 std::sqrt(constants::pi * decay * decay * decay) /
145 std::sqrt(crystal_structure.cell_volume());
146
147 for (int g_index = 0; g_index < number_of_plane_waves; g_index++) {
148 const GVector& g_vector = planewave_basis.g_vectors()[g_vector_indices[g_index]];
149 const Real component_x = kpoint_cartesian.x + g_vector.g_cart.x;
150 const Real component_y = kpoint_cartesian.y + g_vector.g_cart.y;
151 const Real component_z = kpoint_cartesian.z + g_vector.g_cart.z;
152 const Real magnitude =
153 std::sqrt(component_x * component_x + component_y * component_y +
154 component_z * component_z) *
155 two_pi_over_lattice_parameter;
156
157 const Real denominator = 1.0 + magnitude * magnitude * decay * decay;
158 const Real radial = normalisation / (denominator * denominator);
159
160 const Real structure_phase_argument =
161 constants::two_pi * (g_vector.g_cart.x * center_cartesian.x +
162 g_vector.g_cart.y * center_cartesian.y +
163 g_vector.g_cart.z * center_cartesian.z);
164 orbitals[orbital_index][g_index] =
165 kpoint_phase * compute_phase_factor(structure_phase_argument) * radial;
166 }
167 }
168 }
169
170 /// Exactly the centres the manifest named, in the order it named them.
171 [[nodiscard]] std::vector<Vector3D> centers_crystal() const override {
172 std::vector<Vector3D> centers;
173 centers.reserve(orbitals_.size());
174 for (const HydrogenicOrbital& orbital : orbitals_) {
175 centers.push_back(orbital.center_crystal);
176 }
177 return centers;
178 }
179
180 [[nodiscard]] std::string description() const override {
181 std::ostringstream text;
182 text << number_of_orbitals() << " hydrogenic s orbitals at explicit centres (decay "
183 << (orbitals_.empty() ? 0.0 : orbitals_.front().decay_length_bohr) << " bohr)";
184 return text.str();
185 }
186
187 private:
188 std::vector<HydrogenicOrbital> orbitals_;
189};
190
191} // namespace q3
Real cell_volume() const
Definition crystal.hpp:193
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
const Matrix3x3 & reciprocal_lattice_vectors() const
Definition crystal.hpp:205
const Matrix3x3 & lattice_vectors() const
Definition crystal.hpp:202
const std::vector< GVector > & g_vectors() const
Definition init.hpp:148
std::string description() const override
One line for logs, naming the source and the count.
void build(int kpoint_index, int number_of_plane_waves, const std::vector< int > &g_vector_indices, const Crystal &crystal_structure, const KPointsGrid &kpoint_grid, const PlanewaveBasis &planewave_basis, std::vector< std::vector< Complex > > &orbitals) const override
const std::vector< HydrogenicOrbital > & orbitals() const
std::vector< Vector3D > centers_crystal() const override
Exactly the centres the manifest named, in the order it named them.
PlanewaveHydrogenicOrbitalSet(std::vector< HydrogenicOrbital > orbitals)
constexpr Real pi
Definition constants.hpp:24
constexpr Real two_pi
Definition constants.hpp:25
double Real
Complex compute_phase_factor(Real phase)
Compute e^{-iφ} = cos(φ) - i·sin(φ) (standard structure factor phase).
std::complex< double > Complex
A reciprocal lattice vector with Miller indices, used in plane-wave calculations.
Definition g_vector.hpp:17
Vector3D g_cart
Cartesian coordinates in units of 2π/alat.
Definition g_vector.hpp:22
One s-type orbital: where it sits and how fast it decays.
Vector3D center_crystal
Centre in crystal (fractional) coordinates.
Real decay_length_bohr
Decay length in bohr; the Wannier90 convention's zona is 1/a.
3×3 matrix stored row-major: row i = mat[i]