Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
crystal.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Crystal: crystal structure container with lattice vectors, atomic species,
5 * atom positions, and derived reciprocal-space quantities.
6 *
7 * Holds the Bravais lattice, computes reciprocal lattice vectors and cell volume,
8 * and provides coordinate transformations between fractional and Cartesian frames.
9 */
10
11#include <cmath>
12#include <stdexcept>
13#include <string>
14#include <vector>
15
21
22namespace q3 {
23
24/**
25 * Crystal structure container.
26 *
27 * Holds lattice vectors, atomic species, atom positions, and derived
28 * quantities (reciprocal lattice, cell volume, etc.).
29 */
30class Crystal {
31 public:
32 Crystal() = default;
33
34 /**
35 * Construct a fully initialized crystal from raw input parameters.
36 *
37 * Builds the lattice from Bravais index, populates species and atoms,
38 * and computes all derived quantities (reciprocal lattice, volume, etc.).
39 */
41 Real cell_dimension_1,
42 const std::vector<AtomSpecies>& species,
43 const std::vector<Atom>& atoms,
44 Real cell_dimension_2 = 0,
45 Real cell_dimension_3 = 0,
46 Real cell_dimension_4 = 0,
47 Real cell_dimension_5 = 0,
48 Real cell_dimension_6 = 0) {
49 Crystal crystal;
51 cell_dimension_1,
52 cell_dimension_2,
53 cell_dimension_3,
54 cell_dimension_4,
55 cell_dimension_5,
56 cell_dimension_6);
57 crystal.bravais_lattice_index_ = bravais_lattice_index;
58 for (const auto& species_entry : species) {
59 crystal.add_species(species_entry);
60 }
61 for (const auto& atom_entry : atoms) {
62 crystal.add_atom(atom_entry);
63 }
65 return crystal;
66 }
67
68 /**
69 * Construct a fully initialized crystal from explicit lattice vectors
70 * ("free" lattice, Bravais index 0).
71 *
72 * @param lattice_parameter alat in bohr.
73 * @param lattice_vectors rows a1,a2,a3 in units of alat.
74 */
77 const std::vector<AtomSpecies>& species,
78 const std::vector<Atom>& atoms) {
79 Crystal crystal;
80 crystal.lattice_parameter_ = lattice_parameter;
82 for (const auto& species_entry : species) {
83 crystal.add_species(species_entry);
84 }
85 for (const auto& atom_entry : atoms) {
86 crystal.add_atom(atom_entry);
87 }
89 return crystal;
90 }
91
92 /**
93 * Build lattice from Bravais index convention.
94 * Supported bravais_lattice_index values: 0, 1 (SC), 2 (FCC), 3/-3 (BCC), 4 (hex), 5/-5 (trig
95 * R), 6 (tet), 7 (bct), 8 (ort), 9 (bco), 10 (fco), 11 (bort), 12/-12 (mono), 14 (tri).
96 */
97 void set_from_lattice_type(int bravais_lattice_index, Real cell_dimension_1) {
98 set_from_lattice_type(bravais_lattice_index, cell_dimension_1, 0, 0, 0, 0, 0);
99 }
101 Real cell_dimension_1,
102 Real cell_dimension_2,
103 Real cell_dimension_3,
104 Real cell_dimension_4,
105 Real cell_dimension_5,
106 Real cell_dimension_6) {
107 lattice_parameter_ = cell_dimension_1;
109 cell_dimension_1,
110 cell_dimension_2,
111 cell_dimension_3,
112 cell_dimension_4,
113 cell_dimension_5,
114 cell_dimension_6);
115 }
116
117 /**
118 * Set lattice vectors directly (in units of alat).
119 */
121 lattice_vectors_ = lattice_vectors;
122 }
123
124 /**
125 * Add an atomic species.
126 */
128 species_.push_back(species);
129 }
130
131 /**
132 * Add an atom (position in crystal coords). Any installed site symmetry
133 * partition no longer covers every atom, so it is reset to the default.
134 */
135 void add_atom(const Atom& atom) {
136 atoms_.push_back(atom);
137 site_symmetry_classes_.clear();
138 }
139
140 /**
141 * Update the positions of all atoms (in crystal/fractional coordinates).
142 */
143 void set_atomic_positions(const std::vector<Vector3D>& positions) {
144 for (std::size_t atom_index = 0;
145 atom_index < positions.size() && atom_index < atoms_.size();
146 ++atom_index) {
147 atoms_[atom_index].position = positions[atom_index];
148 }
149 }
150
151 /**
152 * Compute derived quantities from lattice vectors and alat:
153 * reciprocal lattice vectors, cell volume, two_pi_over_lattice_parameter,
154 * two_pi_over_lattice_parameter_squared. Must be called after setting lattice vectors and alat.
155 */
157 using namespace constants;
158
159 two_pi_over_lattice_parameter_ = two_pi / lattice_parameter_;
160 two_pi_over_lattice_parameter_squared_ =
161 two_pi_over_lattice_parameter_ * two_pi_over_lattice_parameter_;
162
163 // Cell volume = |a1 · (a2 × a3)| * alat^3
164
165 // lattice_vectors_ is in units of alat, so its determinant gives V/alat^3.
166 Real volume_fraction = lattice_vectors_.determinant();
167 cell_volume_ = std::abs(volume_fraction) * lattice_parameter_ * lattice_parameter_ *
168 lattice_parameter_;
169
170 // Reciprocal lattice: bg = (at^{-1})^T
171
172 // where at has rows = lattice vectors.
173
174 // This gives bg such that bg[i] · at[j] = delta_ij.
175 Matrix3x3 lattice_inverse = lattice_vectors_.inverse();
176 reciprocal_lattice_vectors_ = lattice_inverse.transpose();
177 }
178
179 // --- Accessors ---
180 [[nodiscard]] Real lattice_parameter() const {
181 return lattice_parameter_;
182 }
183 /**
184 * QE ibrav convention this crystal was built from (0 for "free"/explicit lattice
185 * vectors, i.e. Crystal::from_lattice_vectors() -- see set_from_lattice_type()'s
186 * docstring for the full ibrav -> Bravais-type mapping). Consumers that need a
187 * specific lattice symmetry (e.g. isolated_system_correction/'s Makov-Payne
188 * Madelung-constant lookup, cubic-only) must check this explicitly.
189 */
190 [[nodiscard]] int bravais_lattice_index() const {
191 return bravais_lattice_index_;
192 }
193 [[nodiscard]] Real cell_volume() const {
194 return cell_volume_;
195 }
196 [[nodiscard]] Real two_pi_over_lattice_parameter() const {
197 return two_pi_over_lattice_parameter_;
198 }
200 return two_pi_over_lattice_parameter_squared_;
201 }
202 [[nodiscard]] const Matrix3x3& lattice_vectors() const {
203 return lattice_vectors_;
204 }
205 [[nodiscard]] const Matrix3x3& reciprocal_lattice_vectors() const {
206 return reciprocal_lattice_vectors_;
207 }
208
209 [[nodiscard]] int number_of_atoms() const {
210 return static_cast<int>(atoms_.size());
211 }
212 [[nodiscard]] int number_of_species() const {
213 return static_cast<int>(species_.size());
214 }
215 [[nodiscard]] const std::vector<Atom>& atoms() const {
216 return atoms_;
217 }
218 [[nodiscard]] const std::vector<AtomSpecies>& species() const {
219 return species_;
220 }
221
222 /**
223 * Cartesian position of atom i (in units of alat).
224 */
225 [[nodiscard]] Vector3D convert_from_crystal_to_cartesian(int atom_index) const {
226 /**
227 * Convert from crystal coords to Cartesian (in units of alat)
228 * r_cart = tau[0]*a1 + tau[1]*a2 + tau[2]*a3
229 */
230 const Vector3D& fractional = atoms_[atom_index].position;
231 Vector3D cartesian_position;
232 for (int dimension = 0; dimension < 3; dimension++) {
233 cartesian_position[dimension] = fractional.x * lattice_vectors_[0][dimension] +
234 fractional.y * lattice_vectors_[1][dimension] +
235 fractional.z * lattice_vectors_[2][dimension];
236 }
237 return cartesian_position;
238 }
239
240 /**
241 * The separation r_a − r_b of two atoms (Cartesian, units of alat), moved by a lattice
242 * vector so that each of its crystal coordinates lies in [−1/2, 1/2].
243 *
244 * Positions are kept as given, and crystal coordinates outside [0, 1) are legal input, so
245 * the plain difference of two positions can lie several cells away. A real-space lattice
246 * sum that visits the images within a distance R by running ±ceil(R / h_k) cells along
247 * each axis around this separation -- h_k the spacing of the lattice planes parallel to
248 * the other two vectors -- reaches all of them: an image's crystal coordinate along k is
249 * at most R / h_k in magnitude, and this separation's own is at most 1/2. Around the plain
250 * difference the same loop can miss even the nearest image; the Ewald sums did, for an
251 * O-O pair 1.08 Å apart in camsai/q3#18.
252 */
253 [[nodiscard]] Vector3D home_cell_separation(int atom_index_a, int atom_index_b) const {
254 Vector3D fractional = atoms_[atom_index_a].position - atoms_[atom_index_b].position;
255 for (int axis = 0; axis < 3; axis++)
256 fractional[axis] -= std::round(fractional[axis]);
257 Vector3D cartesian_separation;
258 for (int dimension = 0; dimension < 3; dimension++) {
259 cartesian_separation[dimension] = fractional.x * lattice_vectors_[0][dimension] +
260 fractional.y * lattice_vectors_[1][dimension] +
261 fractional.z * lattice_vectors_[2][dimension];
262 }
263 return cartesian_separation;
264 }
265
266 /**
267 * Fractional (crystal) position of atom i.
268 */
269 [[nodiscard]] Vector3D fractional_position(int atom_index) const {
270 return atoms_[atom_index].position;
271 }
272
273 [[nodiscard]] int species_type_index(int atom_index) const {
274 return atoms_[atom_index].species_index;
275 }
276
277 /**
278 * Symmetry class of atom i: two atoms may be mapped onto each other by a
279 * symmetry operation only if their classes agree. By default the class is
280 * the species index. A finer partition (set_site_symmetry_classes) lets a
281 * caller distinguish atoms of one species that a higher layer treats as
282 * inequivalent, e.g. sites seeded with opposite starting moments.
283 */
284 [[nodiscard]] int site_symmetry_class(int atom_index) const {
285 return site_symmetry_classes_.empty() ? atoms_[atom_index].species_index
286 : site_symmetry_classes_[atom_index];
287 }
288
289 /**
290 * Install a per-atom symmetry partition (one non-negative integer per atom;
291 * equal integers = interchangeable sites). It must refine the species
292 * partition: atoms of different species never share a class. An empty
293 * vector restores the default (class = species index).
294 */
295 void set_site_symmetry_classes(const std::vector<int>& classes) {
296 if (!classes.empty()) {
297 if (classes.size() != atoms_.size())
298 throw std::runtime_error("site symmetry classes: expected " +
299 std::to_string(atoms_.size()) + " entries, got " +
300 std::to_string(classes.size()));
301 for (size_t first = 0; first < atoms_.size(); ++first) {
302 if (classes[first] < 0)
303 throw std::runtime_error("site symmetry classes must be non-negative");
304 for (size_t second = first + 1; second < atoms_.size(); ++second)
305 if (classes[first] == classes[second] &&
306 atoms_[first].species_index != atoms_[second].species_index)
307 throw std::runtime_error(
308 "site symmetry classes must not merge atoms of different species");
309 }
310 }
311 site_symmetry_classes_ = classes;
312 }
313
314 [[nodiscard]] bool has_site_symmetry_classes() const {
315 return !site_symmetry_classes_.empty();
316 }
317 [[nodiscard]] Real valence_charge(int species_index) const {
318 return species_[species_index].valence_charge;
319 }
320
321 /**
322 * Lattice vectors in Bohr (Cartesian).
323 */
324 [[nodiscard]] Matrix3x3 lattice_vectors_bohr() const {
325 return lattice_vectors_ * lattice_parameter_;
326 }
327
328 /**
329 * Reciprocal lattice vectors in 1/Bohr.
330 */
331 [[nodiscard]] Matrix3x3 reciprocal_vectors_bohr() const {
332 return reciprocal_lattice_vectors_ * two_pi_over_lattice_parameter_;
333 }
334
335 /**
336 * Atomic number (Z) for atom i. Inferred from element symbol.
337 */
338 [[nodiscard]] int atomic_number(int atom_index) const {
339 const std::string& symbol = species_[atoms_[atom_index].species_index].symbol;
341 }
342
343 /**
344 * Get element symbol for atom i.
345 */
346 [[nodiscard]] const std::string& element(int atom_index) const {
347 return species_[atoms_[atom_index].species_index].symbol;
348 }
349
350 private:
351 Real lattice_parameter_ = 0;
352 int bravais_lattice_index_ = 0;
353 Real cell_volume_ = 0;
354 Real two_pi_over_lattice_parameter_ = 0;
355 Real two_pi_over_lattice_parameter_squared_ = 0;
356
357 Matrix3x3 lattice_vectors_; // lattice vectors in units of alat (rows = a1,a2,a3)
358 Matrix3x3 reciprocal_lattice_vectors_; // reciprocal lattice vectors in units of 2π/alat
359
360 std::vector<AtomSpecies> species_;
361 std::vector<Atom> atoms_;
362 std::vector<int> site_symmetry_classes_; // empty = species index
363};
364
365} // namespace q3
int site_symmetry_class(int atom_index) const
Definition crystal.hpp:284
Vector3D convert_from_crystal_to_cartesian(int atom_index) const
Definition crystal.hpp:225
static Crystal from_lattice_vectors(Real lattice_parameter, const Matrix3x3 &lattice_vectors, const std::vector< AtomSpecies > &species, const std::vector< Atom > &atoms)
Definition crystal.hpp:75
void set_atomic_positions(const std::vector< Vector3D > &positions)
Definition crystal.hpp:143
Real two_pi_over_lattice_parameter_squared() const
Definition crystal.hpp:199
int number_of_species() const
Definition crystal.hpp:212
void add_atom(const Atom &atom)
Definition crystal.hpp:135
int bravais_lattice_index() const
Definition crystal.hpp:190
Real valence_charge(int species_index) const
Definition crystal.hpp:317
Crystal()=default
Real cell_volume() const
Definition crystal.hpp:193
static Crystal from_input_data(int bravais_lattice_index, Real cell_dimension_1, const std::vector< AtomSpecies > &species, const std::vector< Atom > &atoms, Real cell_dimension_2=0, Real cell_dimension_3=0, Real cell_dimension_4=0, Real cell_dimension_5=0, Real cell_dimension_6=0)
Definition crystal.hpp:40
Real two_pi_over_lattice_parameter() const
Definition crystal.hpp:196
bool has_site_symmetry_classes() const
Definition crystal.hpp:314
void add_species(const AtomSpecies &species)
Definition crystal.hpp:127
const Matrix3x3 & reciprocal_lattice_vectors() const
Definition crystal.hpp:205
Vector3D fractional_position(int atom_index) const
Definition crystal.hpp:269
const Matrix3x3 & lattice_vectors() const
Definition crystal.hpp:202
const std::string & element(int atom_index) const
Definition crystal.hpp:346
int species_type_index(int atom_index) const
Definition crystal.hpp:273
Vector3D home_cell_separation(int atom_index_a, int atom_index_b) const
Definition crystal.hpp:253
void set_from_lattice_type(int bravais_lattice_index, Real cell_dimension_1)
Definition crystal.hpp:97
void compute_derived_quantities()
Definition crystal.hpp:156
Matrix3x3 reciprocal_vectors_bohr() const
Definition crystal.hpp:331
void set_from_lattice_type(int bravais_lattice_index, Real cell_dimension_1, Real cell_dimension_2, Real cell_dimension_3, Real cell_dimension_4, Real cell_dimension_5, Real cell_dimension_6)
Definition crystal.hpp:100
int atomic_number(int atom_index) const
Definition crystal.hpp:338
Matrix3x3 lattice_vectors_bohr() const
Definition crystal.hpp:324
void set_site_symmetry_classes(const std::vector< int > &classes)
Definition crystal.hpp:295
const std::vector< AtomSpecies > & species() const
Definition crystal.hpp:218
int number_of_atoms() const
Definition crystal.hpp:209
const std::vector< Atom > & atoms() const
Definition crystal.hpp:215
void set_lattice(const Matrix3x3 &lattice_vectors)
Definition crystal.hpp:120
Real lattice_parameter() const
Definition crystal.hpp:180
static int atomic_number_by_element_symbol(const std::string &symbol)
Matrix3x3 get_lattice_vectors_from_bravais_index(int bravais_lattice_index, Real cell_dimension_1, Real cell_dimension_2=0, Real cell_dimension_3=0, Real cell_dimension_4=0, Real cell_dimension_5=0, Real cell_dimension_6=0)
Definition lattice.hpp:82
double Real
3×3 matrix stored row-major: row i = mat[i]
Matrix3x3 transpose() const
Transpose.
Real determinant() const
Determinant.
Matrix3x3 inverse() const
Inverse (assumes non-singular)