lindhard/ion/crystal/lattice.rs
1//! Crystal lattices: Bravais vectors, a basis of sites with species, and
2//! lattice parameters stated with their temperature.
3//!
4//! # Cubic structures
5//!
6//! Both cubic structures are a face-centred cubic Bravais lattice with a
7//! two-site basis. The primitive vectors and the basis are taken as printed in
8//! M. J. Mehl, D. Hicks, C. Toher, O. Levy, R. M. Hanson, G. Hart and
9//! S. Curtarolo, "The AFLOW Library of Crystallographic Prototypes: Part 1",
10//! Comput. Mater. Sci. 136, S1 (2017), doi:10.1016/j.commatsci.2017.01.017
11//! (read as arXiv:1607.02532v1):
12//!
13//! * face-centred cubic primitive vectors `a1 = (0, a/2, a/2)`,
14//! `a2 = (a/2, 0, a/2)`, `a3 = (a/2, a/2, 0)` (both entries below);
15//! * **diamond** (Strukturbericht A4, prototype `A_cF8_227_a`, space group
16//! Fd-3m, No. 227), p. 616: two sites of one species, Wyckoff 8a, at
17//! lattice coordinates `(1/8, 1/8, 1/8)` and `(7/8, 7/8, 7/8)`; the page
18//! lists Si, Ge and Sn among the elements with this structure;
19//! * **zincblende** (B3, prototype `AB_cF8_216_c_a`, space group F-43m,
20//! No. 216), p. 543: Zn at `(0, 0, 0)` (Wyckoff 4a) and S at
21//! `(1/4, 1/4, 1/4)` (4c).
22//!
23//! For a zincblende compound the first species named in
24//! [`Lattice::zincblende`] takes the Zn (4a) site and the second the S (4c)
25//! site. Swapping them is the same crystal inverted through the origin; the
26//! choice only matters for the polarity of `{111}` faces, which this step
27//! does not use.
28//!
29//! The lattice coordinates `u` of a site give its Cartesian position
30//! `u1 a1 + u2 a2 + u3 a3` (Mehl et al., eq. (8)). The primitive cell volume
31//! is `V = a1 . (a2 x a3)` (eq. (6)), here `a^3 / 4`, so the atom number
32//! density is `(number of basis sites) / V = 8 / a^3` for both structures.
33//!
34//! # Hexagonal structures
35//!
36//! Wurtzite and the SiC polytypes 4H and 6H share the space group P6_3mc
37//! (No. 186) and the hexagonal primitive vectors of Mehl et al. (2017),
38//! pp. 423, 425 and 427:
39//!
40//! ```text
41//! a1 = (a/2, -sqrt(3) a/2, 0), a2 = (a/2, sqrt(3) a/2, 0), a3 = (0, 0, c),
42//! ```
43//!
44//! so the crystal-frame `z` axis is the `c` axis `[0001]` and `x` is along
45//! `a1 + a2`. The sites are on the Wyckoff positions 2a, `(0, 0, z)` and
46//! `(0, 0, 1/2 + z)`, and 2b, `(1/3, 2/3, z)` and `(2/3, 1/3, 1/2 + z)`, in
47//! lattice coordinates of `(a1, a2, a3)`:
48//!
49//! * **wurtzite** (B4, `AB_hP4_186_b_b`), p. 425: Zn on 2b with `z = 0` and S
50//! on 2b with `z = u`. Here the first species named in
51//! [`Lattice::wurtzite`] takes the Zn site and the second the S site.
52//! `u` is the anion-cation bond length along `[0001]` in units of `c`, the
53//! definition of F. Bernardini, V. Fiorentini and D. Vanderbilt, Phys. Rev.
54//! B 56, R10024 (1997), doi:10.1103/PhysRevB.56.R10024, p. 1 (read as
55//! arXiv:cond-mat/9705105v1);
56//! * **4H** (B5, `AB_hP8_186_ab_ab`), p. 423, stacking ABAC: Si and C on 2a
57//! and 2b, four parameters `z1..z4`;
58//! * **6H** (B6, `AB_hP12_186_ab_a2b`), p. 427, stacking ABCACB: Si and C on
59//! 2a and on two 2b sets, six parameters `z1..z6`.
60//!
61//! The pages name A, B and C the three close-packed column positions of the
62//! layers ("one possible stacking (ABAC) for tetrahedral structures. Compare
63//! this to Zincblende (ABCABC), Wurtzite (ABABAB), 6H (ABCACB)", p. 423). In
64//! lattice coordinates they are A = `(0, 0)` (the 2a column), B =
65//! `(1/3, 2/3)` and C = `(2/3, 1/3)` (the two columns of 2b); this matches the
66//! printed 4H and 6H sites. [`Lattice::polytype`] builds the ideal crystal of
67//! any such sequence; see its docs.
68//!
69//! The primitive cell volume is `a1 . (a2 x a3) = (sqrt(3)/2) a^2 c`, and the
70//! hexagonal cell is primitive, so it is also the conventional cell.
71//!
72//! # Miller indices
73//!
74//! Miller indices `(hkl)` and direction indices `[uvw]` refer to the
75//! **conventional** cell `(c1, c2, c3)`: the cube `(a x, a y, a z)` for the
76//! cubic structures (not the primitive vectors above), and `(a1, a2, a3)` for
77//! the hexagonal ones. The reciprocal vectors of a cell are
78//! `b_i = 2 pi (c_j x c_k) / V` (Mehl et al., eqs. (9)-(10)). The planes
79//! `(hkl)` are orthogonal to the reciprocal lattice vector
80//! `g = h b1 + k b2 + l b3` (the definition of Miller indices via the
81//! reciprocal lattice, as given in the Wikipedia article "Miller index",
82//! revision 1378193236 of 2026-10-03, which cites N. W. Ashcroft and
83//! N. D. Mermin, *Solid State Physics* (1976); the book itself was not
84//! opened). A direction `[uvw]` is `u c1 + v c2 + w c3`, and by
85//! `c_i . b_j = 2 pi delta_ij` (eq. (9)) it lies in the plane `(hkl)` exactly
86//! when `h u + k v + l w = 0` (the Weiss zone law, derived here from eq. (9)).
87//! For the cubic cell `g` is parallel to `[hkl]`.
88//!
89//! # Miller-Bravais (four-index) indices
90//!
91//! For the hexagonal structures, planes and directions may also be given with
92//! four indices. The convention, stated once here:
93//!
94//! * the three basal axes are `a1`, `a2` and `a3' = -(a1 + a2)` (the
95//! Wikipedia "Miller index" article, revision 1378193236, section
96//! "Hexagonal and rhombohedral structures": the `[100]`, `[010]` and
97//! `[-1-10]` directions are equivalent, and the third index is the inverse
98//! intercept on the `[-1-10]` axis), and the fourth axis is `c = a3`;
99//! * a plane `(hkil)` must satisfy `h + k + i = 0`, and `h`, `k`, `l` are its
100//! three-index Miller indices (same article: "h, k and l are identical to
101//! the corresponding Miller indices, and i is a redundant index"); its
102//! normal is `g = h b1 + k b2 + l b3` ([`plane_from_hkil`]);
103//! * a direction `[uvtw]` is the vector `u a1 + v a2 + t a3' + w c`, with the
104//! constraint `u + v + t = 0` that makes the four indices unique. Putting
105//! `a3' = -(a1 + a2)` gives the three-index direction
106//! `[U V W] = [u - t, v - t, w]` ([`direction_from_uvtw`]; derived here).
107//! The same substitution turns the four-index zone law
108//! `h u + k v + i t + l w = 0` into the three-index one, since
109//! `i = -(h + k)`.
110//!
111//! So `[0001]` is the `c` axis, `(0, 0, 1)` in the crystal frame, and
112//! `[11-20]` is `3 (a1 + a2) = 3 a x`, along `+x`. The article notes that ad
113//! hoc four-index schemes for lattice vectors exist (in the electron
114//! microscopy literature) that do not follow this rule; this crate uses only
115//! the one above. The original statement of the four-index direction symbol
116//! (F. C. Frank, Acta Cryst. 18, 862 (1965), doi:10.1107/S0365110X65002116)
117//! was not opened.
118//!
119//! # Lattice parameters
120//!
121//! A lattice parameter is a measured fact, so every preset cites the record it
122//! was read from and states the temperature it refers to where the record
123//! does; see [`LatticeConstant`], [`HexagonalConstants`] and the
124//! `docs/data-provenance.md` rows. The lattice is rigid: no thermal expansion
125//! is applied, and the temperature is carried as metadata of the cited value.
126
127use std::f64::consts::PI;
128
129use super::{add, unit, CrystalError};
130use crate::elements::element;
131use crate::units::M_PER_ANGSTROM;
132use crate::vec3::{cross, dot, scale};
133
134/// A cited lattice constant: value, the temperature it refers to, and where
135/// it was read.
136#[derive(Debug, Clone, Copy, PartialEq)]
137pub struct LatticeConstant {
138 /// Material the value belongs to (display only).
139 pub material: &'static str,
140 /// Cubic lattice constant (conventional cell edge), metres.
141 pub a_m: f64,
142 /// Temperature the value refers to, kelvin.
143 pub temperature_k: f64,
144 /// Short citation of the record the value was read from.
145 pub source: &'static str,
146}
147
148/// Cited hexagonal cell parameters: `a`, `c`, the temperature they refer to
149/// (`None` when the record read does not state it) and where they were read.
150#[derive(Debug, Clone, Copy, PartialEq)]
151pub struct HexagonalConstants {
152 /// Material the values belong to (display only).
153 pub material: &'static str,
154 /// Basal lattice parameter `a`, metres.
155 pub a_m: f64,
156 /// Lattice parameter `c` (the `[0001]` period), metres.
157 pub c_m: f64,
158 /// Temperature the values refer to, kelvin, if the record states it.
159 pub temperature_k: Option<f64>,
160 /// Short citation of the record the values were read from.
161 pub source: &'static str,
162}
163
164/// Longest stacking [`Lattice::polytype`] accepts. The orthohexagonal cell
165/// has `4n` sites, and `Candidate::basis` is a `u8`, so `n = 64` (256 sites)
166/// is the largest period whose site indices do not alias.
167pub const MAX_POLYTYPE_LAYERS: usize = 64;
168
169/// Silicon: `a = 5.431 020 511(89) x 10^-10 m`, in vacuum at 22.5 °C
170/// (295.65 K).
171///
172/// CODATA 2022: P. J. Mohr, D. B. Newell, B. N. Taylor and E. Tiesinga,
173/// "CODATA Recommended Values of the Fundamental Physical Constants: 2022",
174/// Rev. Mod. Phys. 97, 025002 (2025), read as arXiv:2409.03787v1,
175/// Table XXXIV "Values of some x-ray-related quantities", p. 56: "lattice
176/// parameter of Si (in vacuum, 22.5 °C)", with the footnote that it is the
177/// unit cell edge of an ideal single crystal of naturally occurring Si free
178/// of impurities and imperfections. The same value is in the NIST CODATA
179/// table (<https://physics.nist.gov/cuu/Constants/Table/allascii.txt>).
180pub const SI_LATTICE_CONSTANT: LatticeConstant = LatticeConstant {
181 material: "Si",
182 a_m: 5.431_020_511e-10,
183 temperature_k: 295.65,
184 source: "CODATA 2022 (Mohr et al., Rev. Mod. Phys. 97, 025002), Table XXXIV, \
185 in vacuum at 22.5 °C",
186};
187
188/// Germanium: `a = 5.6576 Å` at 25 °C (298.15 K).
189///
190/// H. E. Swanson and E. Tatge, *Standard X-ray Diffraction Powder Patterns*,
191/// NBS Circular 539, Vol. I (1953), section 2.6 "Germanium (Cubic)",
192/// pp. 18-19: the NBS lattice constant ("Swanson and Tatge, 5.6576") in the
193/// table of unit cells "in angstrom units at 25 °C"
194/// (<https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular539v1.pdf>).
195pub const GE_LATTICE_CONSTANT: LatticeConstant = LatticeConstant {
196 material: "Ge",
197 a_m: 5.6576 * M_PER_ANGSTROM,
198 temperature_k: 298.15,
199 source: "NBS Circular 539, Vol. I (1953), pp. 18-19, at 25 °C",
200};
201
202/// Gallium arsenide: `a = 5.652 Å` at 25 °C (298.15 K).
203///
204/// H. E. Swanson, M. C. Morris, E. H. Evans and L. Ulmer, *Standard X-ray
205/// Diffraction Powder Patterns*, NBS Monograph 25, Section 3 (1964),
206/// "Gallium Arsenide, GaAs (cubic)", p. 33: "1963 National Bureau of
207/// Standards at 25 °C, 5.652" Å, the average of the last five lines; the
208/// same page states the zinc sulfide structure, space group F-43m, and
209/// "The density of gallium arsenide calculated from the NBS lattice constant
210/// is 5.321 g/cm³ at 25 °C"
211/// (<https://nvlpubs.nist.gov/nistpubs/Legacy/MONO/nbsmonograph25-3.pdf>).
212pub const GAAS_LATTICE_CONSTANT: LatticeConstant = LatticeConstant {
213 material: "GaAs",
214 a_m: 5.652 * M_PER_ANGSTROM,
215 temperature_k: 298.15,
216 source: "NBS Monograph 25, Section 3 (1964), p. 33, at 25 °C",
217};
218
219/// The GaAs density printed with [`GAAS_LATTICE_CONSTANT`] on the same NBS
220/// page, g/cm³ at 25 °C. Used to cross-check the lattice number density
221/// against the [`crate::material`] module.
222pub const GAAS_DENSITY_G_CM3: f64 = 5.321;
223
224/// Cubic (3C) silicon carbide: `a = 4.3596 Å` at 297 K.
225///
226/// Read from the Ioffe Institute "New Semiconductor Materials" archive,
227/// "Basic Parameters of Silicon Carbide (SiC)"
228/// (<http://www.ioffe.ru/SVA/NSM/Semicond/SiC/basic.html>, read 2026-10-07):
229/// "Lattice constant, 3C-SiC, a = 4.3596 A, 297 K, Debye-Scherrer", citing
230/// A. Taylor and R. M. Jones (1960). This is a secondary compilation; the
231/// primary paper was **not** opened, see `docs/data-provenance.md`.
232pub const SIC_3C_LATTICE_CONSTANT: LatticeConstant = LatticeConstant {
233 material: "3C-SiC",
234 a_m: 4.3596 * M_PER_ANGSTROM,
235 temperature_k: 297.0,
236 source: "Ioffe NSM archive (citing Taylor & Jones 1960), at 297 K; \
237 primary not opened",
238};
239
240/// Wurtzite gallium nitride: `a = 3.1890 Å`, `c = 5.18500 Å` at 300 K.
241///
242/// Yu. V. Melnik, K. V. Vassilevski, I. P. Nikitina, A. I. Babanin,
243/// V. Yu. Davydov and V. A. Dmitriev, "Physical Properties of Bulk GaN
244/// Crystals Grown by HVPE", MRS Internet J. Nitride Semicond. Res. 2, 39
245/// (1997), doi:10.1557/S1092578300001654 (open access), section 3.2
246/// (PDF p. 3) and Table 1 (PDF p. 5, "GaN a and c lattice parameters
247/// measured at 300 K"): the free-standing bulk crystal after an anneal at
248/// 1100 K that removed the residual strain, "c = 5.18500 Å, a = 3.1890 Å"
249/// on both the N face and the Ga face, measured by the triple-crystal Bond
250/// method on the (0004) and (11-24) reflections with a stated accuracy of
251/// 0.00005 Å for `a` and 0.00002 Å for `c`.
252pub const GAN_LATTICE_CONSTANTS: HexagonalConstants = HexagonalConstants {
253 material: "GaN (wurtzite)",
254 a_m: 3.1890 * M_PER_ANGSTROM,
255 c_m: 5.18500 * M_PER_ANGSTROM,
256 temperature_k: Some(300.0),
257 source: "Melnik et al., MRS Internet J. Nitride Semicond. Res. 2, 39 (1997), \
258 Table 1, annealed bulk HVPE crystal, at 300 K",
259};
260
261/// Measured (not X-ray) density of wurtzite GaN, `6.1 g/cm³`, temperature
262/// not stated.
263///
264/// R. Juza and H. Hahn, "Über die Kristallstrukturen von Cu3N, GaN und InN",
265/// Z. Anorg. Allg. Chem. 239, 282 (1938), doi:10.1002/zaac.19382390307
266/// (closed access, **not opened**), as recorded in the Crystallography Open
267/// Database entry 1010168 (public domain): `_exptl_crystal_density_meas 6.1`,
268/// with that paper's cell `a = 3.180(4) Å`, `c = 5.166(5) Å`. Printed to two
269/// figures, so half a unit in the last place is 8.2e-3 relative; this is the
270/// tolerance of the number-density test against [`GAN_LATTICE_CONSTANTS`].
271pub const GAN_DENSITY_G_CM3: f64 = 6.1;
272
273/// 4H silicon carbide: `a = 3.08051 Å`, `c = 10.0848 Å`; temperature not
274/// stated in the record read.
275///
276/// A. Bauer, P. Reischauer, J. Kräusslich, N. Schell, W. Matz and K. Goetz,
277/// "Structure refinement of the silicon carbide polytypes 4H and 6H:
278/// unambiguous determination of the refinement parameters", Acta Cryst. A 57,
279/// 60 (2001), doi:10.1107/S0108767300012915, as reproduced in Mehl et al.
280/// (2017), prototype `AB_hP8_186_ab_ab` ("Moissanite-4H SiC (B5)"),
281/// pp. 423-424 and its CIF on p. 733: `_cell_length_a 3.08051`,
282/// `_cell_length_c 10.08480`. The primary paper is closed access and was
283/// **not** opened, so its measurement temperature is not known here.
284pub const SIC_4H_LATTICE_CONSTANTS: HexagonalConstants = HexagonalConstants {
285 material: "4H-SiC",
286 a_m: 3.08051 * M_PER_ANGSTROM,
287 c_m: 10.0848 * M_PER_ANGSTROM,
288 temperature_k: None,
289 source: "Bauer et al., Acta Cryst. A 57, 60 (2001), via AFLOW AB_hP8_186_ab_ab \
290 (Mehl et al. 2017, pp. 423, 733); temperature not stated",
291};
292
293/// The Wyckoff parameters `z1..z4` of 4H-SiC as printed with
294/// [`SIC_4H_LATTICE_CONSTANTS`] (Mehl et al. 2017, p. 733,
295/// `_aflow_params_values`): Si I (2a) `0.0`, C I (2a) `0.18784`, Si II (2b)
296/// `0.24982`, C II (2b) `0.43671`, from Bauer et al. (2001).
297pub const SIC_4H_Z: [f64; 4] = [0.0, 0.18784, 0.24982, 0.43671];
298
299/// 6H silicon carbide: `a = 3.08129 Å`, `c = 15.11976 Å`; temperature not
300/// stated in the record read.
301///
302/// Bauer et al. (2001) (see [`SIC_4H_LATTICE_CONSTANTS`]) as reproduced in
303/// Mehl et al. (2017), prototype `AB_hP12_186_ab_a2b` ("Moissanite-6H SiC
304/// (B6)"), pp. 427-428 and its CIF on p. 734: `_cell_length_a 3.08129`,
305/// `_cell_length_c 15.11976`. Primary not opened.
306pub const SIC_6H_LATTICE_CONSTANTS: HexagonalConstants = HexagonalConstants {
307 material: "6H-SiC",
308 a_m: 3.08129 * M_PER_ANGSTROM,
309 c_m: 15.11976 * M_PER_ANGSTROM,
310 temperature_k: None,
311 source: "Bauer et al., Acta Cryst. A 57, 60 (2001), via AFLOW AB_hP12_186_ab_a2b \
312 (Mehl et al. 2017, pp. 427, 734); temperature not stated",
313};
314
315/// The Wyckoff parameters `z1..z6` of 6H-SiC as printed with
316/// [`SIC_6H_LATTICE_CONSTANTS`] (Mehl et al. 2017, p. 734): Si I (2a) `0.0`,
317/// C I (2a) `0.1254`, Si II (2b) `0.16675`, C II (2b) `0.29215`, Si III (2b)
318/// `0.8335`, C III (2b) `-0.0415`, from Bauer et al. (2001).
319pub const SIC_6H_Z: [f64; 6] = [0.0, 0.1254, 0.16675, 0.29215, 0.8335, -0.0415];
320
321/// 4H-SiC density, "3.211 g cm-3, 300 K", credited to Gomes de Mesquita
322/// (1967) by the Ioffe NSM archive SiC basic-parameters page (read
323/// 2026-10-08; primary not opened). Provisional: that Acta Cryst. 23, 610
324/// (1967) is titled "Refinement of the crystal structure of SiC type 6H"
325/// (Crossref), so the attribution to 4H is doubtful. Used in the
326/// number-density test, which records the 4H disagreement as a gap.
327///
328/// No measured 4H bulk density could be opened (checked 2026-10-10). The
329/// one open primary found, N. W. Thibault, Am. Mineral. 29, 327 (1944),
330/// pp. 327-328 and Table 25 on p. 342, gives for α-SiC type III (4H)
331/// only a cell (`a = 3.073 Å`, `c = 10.053 Å`) and a **calculated** density
332/// of 3.217; its one measured (pycnometric) density, `d` at 30°/4° C =
333/// 3.218 (p. 341), is of type II (6H).
334///
335/// Two open measured 4H densities were found on 2026-10-11, and neither
336/// replaces this value: D. Lundqvist, Acta Chem. Scand. 2, 177 (1948), p. 182,
337/// `σ_obs. = 3.201 + 0.005` on impure type III with a little type I (Al
338/// 0.30 %), which the [`Lattice::silicon_carbide_4h`] cell exceeds by 3.88e-3,
339/// 2.5 times the stated 0.005; and ORNL/TM-2023/3059 (2023), Table 4, p. 7,
340/// 3.17 and 3.21 g/cm³ by dimensions and mass for two 4H wafers, a 1.3e-2
341/// spread with no temperature or uncertainty stated. Both are pinned in
342/// `tests/crystal_hex.rs`; the 4H density criterion of #179 stays unmet. See
343/// `docs/data-provenance.md`.
344pub const SIC_4H_DENSITY_G_CM3: f64 = 3.211;
345
346/// 6H-SiC density, "3.21 g cm-3, 300 K", credited to Harris et al. (1995b)
347/// by the same Ioffe page (primary not opened). Used in the number-density
348/// test.
349pub const SIC_6H_DENSITY_G_CM3: f64 = 3.21;
350
351/// The wurtzite `u` at which the bond along `[0001]` and the three other
352/// bonds of each atom have the same length: `u = 1/4 + a^2 / (3 c^2)`.
353///
354/// Derived here from the wurtzite sites above: a cation on column B at
355/// height 0 has its anion neighbour at `u c` straight above it, and three
356/// anions of the C layer at height `(u - 1/2) c`, displaced in the plane by
357/// the B-C column spacing `a / sqrt(3)`. Equal lengths,
358/// `(u c)^2 = a^2/3 + (1/2 - u)^2 c^2`, give the formula. At the ideal ratio
359/// `c/a = sqrt(8/3)` it is `3/8`.
360///
361/// This is a **geometric model value**, not a measurement. It is what
362/// [`Lattice::gallium_nitride`] uses until a measured `u` is sourced.
363pub fn equal_bond_wurtzite_u(a_m: f64, c_m: f64) -> f64 {
364 0.25 + a_m * a_m / (3.0 * c_m * c_m)
365}
366
367/// Which structure a [`Lattice`] has.
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum Structure {
370 /// Diamond (A4, Fd-3m): one species on both fcc sublattices.
371 Diamond,
372 /// Zincblende (B3, F-43m): two species, one per fcc sublattice.
373 Zincblende,
374 /// Wurtzite (B4, P6_3mc): the two-layer hexagonal stacking, 2H.
375 Wurtzite,
376 /// A hexagonal tetrahedral polytype with `layers` cation-anion bilayers
377 /// per `c` (4 for 4H, 6 for 6H).
378 Polytype {
379 /// Bilayers per `c` period.
380 layers: u8,
381 },
382}
383
384impl Structure {
385 /// Whether the structure has the hexagonal cell (`a1`, `a2`, `c`).
386 pub fn is_hexagonal(self) -> bool {
387 matches!(self, Self::Wurtzite | Self::Polytype { .. })
388 }
389}
390
391/// One basis site: species and lattice (fractional) coordinates with respect
392/// to the primitive vectors.
393#[derive(Debug, Clone, Copy, PartialEq)]
394pub struct Site {
395 /// Atomic number of the atom on this site.
396 pub z: u8,
397 /// Lattice coordinates `(u1, u2, u3)`: the position is
398 /// `u1 a1 + u2 a2 + u3 a3`.
399 pub fractional: [f64; 3],
400}
401
402/// A crystal: a Bravais lattice with a basis. Cubic (face-centred cubic
403/// Bravais lattice) or hexagonal; see the module docs.
404#[derive(Debug, Clone, PartialEq)]
405pub struct Lattice {
406 structure: Structure,
407 a_m: f64,
408 c_m: f64,
409 temperature_k: Option<f64>,
410 primitive: [[f64; 3]; 3],
411 basis: Vec<Site>,
412}
413
414fn check_z(z: u8) -> Result<u8, CrystalError> {
415 element(z)
416 .map(|_| z)
417 .ok_or(CrystalError::UnknownAtomicNumber(z))
418}
419
420fn check_length(x: f64) -> Result<f64, CrystalError> {
421 if x.is_finite() && x > 0.0 {
422 Ok(x)
423 } else {
424 Err(CrystalError::InvalidLatticeConstant(x))
425 }
426}
427
428fn check_temperature(t: Option<f64>) -> Result<Option<f64>, CrystalError> {
429 match t {
430 Some(t) if !(t.is_finite() && t >= 0.0) => Err(CrystalError::InvalidTemperature(t)),
431 _ => Ok(t),
432 }
433}
434
435/// Reciprocal vectors `b_i = 2 pi (c_j x c_k) / V` of the cell `c`
436/// (Mehl et al. 2017, eq. (10)).
437pub fn reciprocal_vectors(c: [[f64; 3]; 3]) -> [[f64; 3]; 3] {
438 let v = dot(c[0], cross(c[1], c[2]));
439 let f = 2.0 * PI / v;
440 [
441 scale(cross(c[1], c[2]), f),
442 scale(cross(c[2], c[0]), f),
443 scale(cross(c[0], c[1]), f),
444 ]
445}
446
447/// The hexagonal primitive vectors `a1 = (a/2, -sqrt(3) a/2, 0)`,
448/// `a2 = (a/2, sqrt(3) a/2, 0)`, `a3 = (0, 0, c)` (Mehl et al. 2017,
449/// pp. 423, 425, 427).
450pub fn hexagonal_vectors(a_m: f64, c_m: f64) -> [[f64; 3]; 3] {
451 let h = 0.5 * a_m;
452 let s = 0.5 * 3.0f64.sqrt() * a_m;
453 [[h, -s, 0.0], [h, s, 0.0], [0.0, 0.0, c_m]]
454}
455
456/// Three-index Miller indices `(hkl)` of the Miller-Bravais plane `(hkil)`.
457///
458/// # Errors
459/// [`CrystalError::InvalidMillerBravais`] unless `h + k + i = 0`.
460pub fn plane_from_hkil(hkil: [i32; 4]) -> Result<[i32; 3], CrystalError> {
461 let [h, k, i, l] = hkil;
462 if i64::from(h) + i64::from(k) + i64::from(i) != 0 {
463 return Err(CrystalError::InvalidMillerBravais {
464 indices: hkil,
465 why: "a plane (hkil) needs h + k + i = 0",
466 });
467 }
468 Ok([h, k, l])
469}
470
471/// Three-index direction `[U V W] = [u - t, v - t, w]` of the Miller-Bravais
472/// direction `[uvtw]` (see the module docs for the convention).
473///
474/// # Errors
475/// [`CrystalError::InvalidMillerBravais`] unless `u + v + t = 0`, or if an
476/// index overflows `i32`.
477pub fn direction_from_uvtw(uvtw: [i32; 4]) -> Result<[i32; 3], CrystalError> {
478 let [u, v, t, w] = uvtw;
479 if i64::from(u) + i64::from(v) + i64::from(t) != 0 {
480 return Err(CrystalError::InvalidMillerBravais {
481 indices: uvtw,
482 why: "a direction [uvtw] needs u + v + t = 0",
483 });
484 }
485 match (u.checked_sub(t), v.checked_sub(t)) {
486 (Some(big_u), Some(big_v)) => Ok([big_u, big_v, w]),
487 _ => Err(CrystalError::InvalidMillerBravais {
488 indices: uvtw,
489 why: "index overflow",
490 }),
491 }
492}
493
494fn combine(c: [[f64; 3]; 3], n: [f64; 3]) -> [f64; 3] {
495 add(add(scale(c[0], n[0]), scale(c[1], n[1])), scale(c[2], n[2]))
496}
497
498fn as_f64(n: [i32; 3]) -> [f64; 3] {
499 [f64::from(n[0]), f64::from(n[1]), f64::from(n[2])]
500}
501
502/// Wrap a fraction into `[0, 1)`. Values within `1e-12` below 1 (rounding of
503/// `1/3 + 2/3` and the like) become 0, so that every site has a single
504/// representative.
505fn wrap_unit(x: f64) -> f64 {
506 let w = x - x.floor();
507 if w >= 1.0 - 1e-12 {
508 0.0
509 } else {
510 w
511 }
512}
513
514/// Lattice coordinates `(f1, f2)` of the close-packed columns A, B, C.
515fn column(letter: char) -> Option<[f64; 2]> {
516 match letter {
517 'A' => Some([0.0, 0.0]),
518 'B' => Some([1.0 / 3.0, 2.0 / 3.0]),
519 'C' => Some([2.0 / 3.0, 1.0 / 3.0]),
520 _ => None,
521 }
522}
523
524/// Wyckoff 2a of P6_3mc: `(0, 0, z)`, `(0, 0, 1/2 + z)`.
525fn wyckoff_2a(z_atom: u8, z: f64) -> [(u8, [f64; 3]); 2] {
526 [(z_atom, [0.0, 0.0, z]), (z_atom, [0.0, 0.0, 0.5 + z])]
527}
528
529/// Wyckoff 2b of P6_3mc: `(1/3, 2/3, z)`, `(2/3, 1/3, 1/2 + z)`.
530fn wyckoff_2b(z_atom: u8, z: f64) -> [(u8, [f64; 3]); 2] {
531 [
532 (z_atom, [1.0 / 3.0, 2.0 / 3.0, z]),
533 (z_atom, [2.0 / 3.0, 1.0 / 3.0, 0.5 + z]),
534 ]
535}
536
537fn check_u(u: f64) -> Result<f64, CrystalError> {
538 if u.is_finite() && u > 0.0 && u < 0.5 {
539 Ok(u)
540 } else {
541 Err(CrystalError::InvalidInternalParameter(u))
542 }
543}
544
545impl Lattice {
546 fn build(
547 structure: Structure,
548 sites: [(u8, [f64; 3]); 2],
549 a_m: f64,
550 temperature_k: f64,
551 ) -> Result<Self, CrystalError> {
552 check_length(a_m)?;
553 check_temperature(Some(temperature_k))?;
554 let h = 0.5 * a_m;
555 // Face-centred cubic primitive vectors, Mehl et al. (2017),
556 // pp. 543 and 616.
557 let primitive = [[0.0, h, h], [h, 0.0, h], [h, h, 0.0]];
558 let basis = sites
559 .iter()
560 .map(|&(z, fractional)| {
561 Ok(Site {
562 z: check_z(z)?,
563 fractional,
564 })
565 })
566 .collect::<Result<Vec<_>, CrystalError>>()?;
567 Ok(Self {
568 structure,
569 a_m,
570 c_m: a_m,
571 temperature_k: Some(temperature_k),
572 primitive,
573 basis,
574 })
575 }
576
577 /// A hexagonal lattice on [`hexagonal_vectors`] with the given sites
578 /// (lattice coordinates, wrapped into `[0, 1)`).
579 fn build_hexagonal(
580 structure: Structure,
581 sites: &[(u8, [f64; 3])],
582 a_m: f64,
583 c_m: f64,
584 temperature_k: Option<f64>,
585 ) -> Result<Self, CrystalError> {
586 check_length(a_m)?;
587 check_length(c_m)?;
588 check_temperature(temperature_k)?;
589 let basis = sites
590 .iter()
591 .map(|&(z, f)| {
592 Ok(Site {
593 z: check_z(z)?,
594 fractional: f.map(wrap_unit),
595 })
596 })
597 .collect::<Result<Vec<_>, CrystalError>>()?;
598 Ok(Self {
599 structure,
600 a_m,
601 c_m,
602 temperature_k,
603 primitive: hexagonal_vectors(a_m, c_m),
604 basis,
605 })
606 }
607
608 /// Diamond structure of element `z` with lattice constant `a_m` (metres)
609 /// stated at `temperature_k`. Basis as printed by Mehl et al. (2017),
610 /// p. 616: `(1/8, 1/8, 1/8)` and `(7/8, 7/8, 7/8)`.
611 pub fn diamond(z: u8, a_m: f64, temperature_k: f64) -> Result<Self, CrystalError> {
612 Self::build(
613 Structure::Diamond,
614 [(z, [0.125; 3]), (z, [0.875; 3])],
615 a_m,
616 temperature_k,
617 )
618 }
619
620 /// Zincblende structure with `z_a` on the Zn (4a) site `(0, 0, 0)` and
621 /// `z_b` on the S (4c) site `(1/4, 1/4, 1/4)` (Mehl et al. 2017, p. 543).
622 pub fn zincblende(
623 z_a: u8,
624 z_b: u8,
625 a_m: f64,
626 temperature_k: f64,
627 ) -> Result<Self, CrystalError> {
628 Self::build(
629 Structure::Zincblende,
630 [(z_a, [0.0; 3]), (z_b, [0.25; 3])],
631 a_m,
632 temperature_k,
633 )
634 }
635
636 /// Wurtzite with `z_a` on the Zn sites and `z_b` on the S sites (Mehl et
637 /// al. 2017, p. 425, Wyckoff 2b): `z_a` at `(1/3, 2/3, 0)` and
638 /// `(2/3, 1/3, 1/2)`, `z_b` at `(1/3, 2/3, u)` and `(2/3, 1/3, 1/2 + u)`,
639 /// in that order. `a_m`, `c_m` in metres; `temperature_k` is the
640 /// temperature the parameters refer to, if known.
641 ///
642 /// # Errors
643 /// Invalid lengths or temperature, an unknown atomic number, or `u`
644 /// outside `(0, 1/2)` ([`CrystalError::InvalidInternalParameter`]).
645 pub fn wurtzite(
646 z_a: u8,
647 z_b: u8,
648 a_m: f64,
649 c_m: f64,
650 u: f64,
651 temperature_k: Option<f64>,
652 ) -> Result<Self, CrystalError> {
653 let u = check_u(u)?;
654 let [a1, a2] = wyckoff_2b(z_a, 0.0);
655 let [b1, b2] = wyckoff_2b(z_b, u);
656 Self::build_hexagonal(
657 Structure::Wurtzite,
658 &[a1, a2, b1, b2],
659 a_m,
660 c_m,
661 temperature_k,
662 )
663 }
664
665 /// The ideal tetrahedral polytype with the close-packed stacking
666 /// `stacking` (letters `A`, `B`, `C`, one per bilayer, e.g. `"ABCB"` for
667 /// 4H or `"ABCACB"` for 6H), cation `z_a` and anion `z_b`.
668 ///
669 /// With `n` letters, bilayer `k` (from 0) has its cation at the column of
670 /// letter `k` and height `k c / n`, and its anion straight above it at
671 /// height `k c / n + 2 u c / n`. So `u` is the bond length along
672 /// `[0001]` in units of the two-bilayer height `2 c / n`: the wurtzite `u`
673 /// for `n = 2`, and `3/8` for the ideal tetrahedron at any `n` (the bond
674 /// is three quarters of the bilayer spacing, as in the ideal wurtzite).
675 /// This generalisation is ours; the measured 4H and 6H presets use the
676 /// printed sites instead ([`Self::silicon_carbide_4h`],
677 /// [`Self::silicon_carbide_6h`]). Sites are listed per bilayer, cation
678 /// then anion, in stacking order.
679 ///
680 /// The Ramsdell number is `n` (Mehl et al. 2017, p. 423: "The 4H refers to
681 /// the fact that there are 4 CSi dimers in a hexagonal unit cell"). The
682 /// stacking `"BC"` with the same `u` gives the sites of
683 /// [`Self::wurtzite`] (listed in a different order).
684 ///
685 /// # Errors
686 /// [`CrystalError::InvalidStacking`] for a sequence shorter than 2 or
687 /// longer than [`MAX_POLYTYPE_LAYERS`] (64) letters, a letter other than `A`, `B`, `C`, or two
688 /// equal neighbours (cyclically: the last letter is followed by the
689 /// first); [`CrystalError::InvalidInternalParameter`] for `u` outside
690 /// `(0, 1/2)`; and the errors of [`Self::wurtzite`].
691 pub fn polytype(
692 z_a: u8,
693 z_b: u8,
694 stacking: &str,
695 a_m: f64,
696 c_m: f64,
697 u: f64,
698 temperature_k: Option<f64>,
699 ) -> Result<Self, CrystalError> {
700 let bad = |why: &'static str| CrystalError::InvalidStacking {
701 stacking: stacking.to_string(),
702 why,
703 };
704 let letters: Vec<char> = stacking.chars().collect();
705 let n = letters.len();
706 if n < 2 {
707 return Err(bad("needs at least two layers"));
708 }
709 if n > MAX_POLYTYPE_LAYERS {
710 return Err(bad("at most 64 layers"));
711 }
712 let layers = n as u8;
713 let columns = letters
714 .iter()
715 .map(|&l| column(l).ok_or_else(|| bad("letters must be A, B or C")))
716 .collect::<Result<Vec<_>, _>>()?;
717 if (0..n).any(|k| letters[k] == letters[(k + 1) % n]) {
718 return Err(bad("adjacent layers (cyclically) must differ"));
719 }
720 let u = check_u(u)?;
721 let nf = n as f64;
722 let mut sites = Vec::with_capacity(2 * n);
723 for (k, xy) in columns.iter().enumerate() {
724 let z = k as f64 / nf;
725 sites.push((z_a, [xy[0], xy[1], z]));
726 sites.push((z_b, [xy[0], xy[1], z + 2.0 * u / nf]));
727 }
728 Self::build_hexagonal(
729 Structure::Polytype { layers },
730 &sites,
731 a_m,
732 c_m,
733 temperature_k,
734 )
735 }
736
737 /// Diamond-structure Si with [`SI_LATTICE_CONSTANT`].
738 pub fn silicon() -> Self {
739 let c = SI_LATTICE_CONSTANT;
740 Self::diamond(14, c.a_m, c.temperature_k).expect("valid preset")
741 }
742
743 /// Diamond-structure Ge with [`GE_LATTICE_CONSTANT`].
744 pub fn germanium() -> Self {
745 let c = GE_LATTICE_CONSTANT;
746 Self::diamond(32, c.a_m, c.temperature_k).expect("valid preset")
747 }
748
749 /// Zincblende GaAs (Ga on 4a, As on 4c) with [`GAAS_LATTICE_CONSTANT`].
750 pub fn gallium_arsenide() -> Self {
751 let c = GAAS_LATTICE_CONSTANT;
752 Self::zincblende(31, 33, c.a_m, c.temperature_k).expect("valid preset")
753 }
754
755 /// Zincblende 3C-SiC (Si on 4a, C on 4c) with
756 /// [`SIC_3C_LATTICE_CONSTANT`].
757 pub fn silicon_carbide_3c() -> Self {
758 let c = SIC_3C_LATTICE_CONSTANT;
759 Self::zincblende(14, 6, c.a_m, c.temperature_k).expect("valid preset")
760 }
761
762 /// Wurtzite GaN (Ga on the Zn sites, N on the S sites) with
763 /// [`GAN_LATTICE_CONSTANTS`].
764 ///
765 /// **Placeholder `u`.** No measured `u` for GaN could be opened (see
766 /// `docs/data-provenance.md`, open questions), so this preset uses the
767 /// geometric [`equal_bond_wurtzite_u`] of the cited `a` and `c`, not a
768 /// measurement. Build with [`Self::wurtzite`] to supply a measured `u`.
769 pub fn gallium_nitride() -> Self {
770 let c = GAN_LATTICE_CONSTANTS;
771 let u = equal_bond_wurtzite_u(c.a_m, c.c_m);
772 Self::wurtzite(31, 7, c.a_m, c.c_m, u, c.temperature_k).expect("valid preset")
773 }
774
775 /// 4H-SiC with [`SIC_4H_LATTICE_CONSTANTS`] and the measured sites
776 /// [`SIC_4H_Z`] of Bauer et al. (2001) as printed by Mehl et al. (2017),
777 /// p. 423: Si I on 2a (`z1`), C I on 2a (`z2`), Si II on 2b (`z3`),
778 /// C II on 2b (`z4`), in that order. The stacking is ABAC, which is ABCB
779 /// relabelled.
780 pub fn silicon_carbide_4h() -> Self {
781 let c = SIC_4H_LATTICE_CONSTANTS;
782 let z = SIC_4H_Z;
783 let s: Vec<(u8, [f64; 3])> = [
784 wyckoff_2a(14, z[0]),
785 wyckoff_2a(6, z[1]),
786 wyckoff_2b(14, z[2]),
787 wyckoff_2b(6, z[3]),
788 ]
789 .concat();
790 Self::build_hexagonal(
791 Structure::Polytype { layers: 4 },
792 &s,
793 c.a_m,
794 c.c_m,
795 c.temperature_k,
796 )
797 .expect("valid preset")
798 }
799
800 /// 6H-SiC with [`SIC_6H_LATTICE_CONSTANTS`] and the measured sites
801 /// [`SIC_6H_Z`] of Bauer et al. (2001) as printed by Mehl et al. (2017),
802 /// p. 427: Si I, C I on 2a (`z1`, `z2`); Si II, C II, Si III, C III on 2b
803 /// (`z3..z6`), in that order. The stacking is ABCACB.
804 pub fn silicon_carbide_6h() -> Self {
805 let c = SIC_6H_LATTICE_CONSTANTS;
806 let z = SIC_6H_Z;
807 let s: Vec<(u8, [f64; 3])> = [
808 wyckoff_2a(14, z[0]),
809 wyckoff_2a(6, z[1]),
810 wyckoff_2b(14, z[2]),
811 wyckoff_2b(6, z[3]),
812 wyckoff_2b(14, z[4]),
813 wyckoff_2b(6, z[5]),
814 ]
815 .concat();
816 Self::build_hexagonal(
817 Structure::Polytype { layers: 6 },
818 &s,
819 c.a_m,
820 c.c_m,
821 c.temperature_k,
822 )
823 .expect("valid preset")
824 }
825
826 /// The structure type.
827 pub fn structure(&self) -> Structure {
828 self.structure
829 }
830
831 /// Whether the lattice is hexagonal (wurtzite or a polytype).
832 pub fn is_hexagonal(&self) -> bool {
833 self.structure.is_hexagonal()
834 }
835
836 /// Lattice constant `a`, metres: the cubic cell edge, or the hexagonal
837 /// basal parameter.
838 pub fn lattice_constant(&self) -> f64 {
839 self.a_m
840 }
841
842 /// Lattice constant `c`, metres: the hexagonal `[0001]` period, or the
843 /// cubic cell edge (equal to [`Self::lattice_constant`]).
844 pub fn lattice_constant_c(&self) -> f64 {
845 self.c_m
846 }
847
848 /// Temperature the lattice parameters refer to, kelvin, or `None` if the
849 /// cited record does not state it.
850 pub fn temperature_k(&self) -> Option<f64> {
851 self.temperature_k
852 }
853
854 /// The primitive (Bravais) vectors `a1, a2, a3`, metres, in the crystal
855 /// frame: face-centred cubic, or hexagonal ([`hexagonal_vectors`]).
856 pub fn primitive_vectors(&self) -> [[f64; 3]; 3] {
857 self.primitive
858 }
859
860 /// The conventional cell vectors, metres: the cube `a x, a y, a z`, or
861 /// for a hexagonal lattice the primitive `a1, a2, c`. Miller and
862 /// direction indices refer to this cell.
863 pub fn conventional_vectors(&self) -> [[f64; 3]; 3] {
864 if self.is_hexagonal() {
865 return self.primitive;
866 }
867 let a = self.a_m;
868 [[a, 0.0, 0.0], [0.0, a, 0.0], [0.0, 0.0, a]]
869 }
870
871 /// The basis sites, in the order the constructor lists them.
872 pub fn basis(&self) -> &[Site] {
873 &self.basis
874 }
875
876 /// Cartesian positions (metres, crystal frame) of the basis sites in the
877 /// primitive cell, with their atomic numbers.
878 pub fn basis_positions(&self) -> Vec<(u8, [f64; 3])> {
879 self.basis
880 .iter()
881 .map(|s| (s.z, combine(self.primitive, s.fractional)))
882 .collect()
883 }
884
885 /// The atoms of one conventional cell, as fractional coordinates of that
886 /// cell wrapped into `[0, 1)`.
887 ///
888 /// Cubic: each basis site plus the three face-centring translations
889 /// `(0, 1/2, 1/2)`, `(1/2, 0, 1/2)`, `(1/2, 1/2, 0)`; eight atoms for both
890 /// structures, in basis order then translation order. Hexagonal: the
891 /// basis itself (the hexagonal cell is primitive), in basis order.
892 pub fn conventional_cell_sites(&self) -> Vec<(u8, [f64; 3])> {
893 if self.is_hexagonal() {
894 return self.basis.iter().map(|s| (s.z, s.fractional)).collect();
895 }
896 const T: [[f64; 3]; 4] = [
897 [0.0, 0.0, 0.0],
898 [0.0, 0.5, 0.5],
899 [0.5, 0.0, 0.5],
900 [0.5, 0.5, 0.0],
901 ];
902 let mut out = Vec::with_capacity(4 * self.basis.len());
903 for s in &self.basis {
904 // Cartesian position in units of a.
905 let p = scale(combine(self.primitive, s.fractional), 1.0 / self.a_m);
906 for t in T {
907 let q = add(p, t);
908 out.push((s.z, q.map(|x| x - x.floor())));
909 }
910 }
911 out
912 }
913
914 /// A rectangular cell aligned with the crystal axes that tiles the
915 /// crystal: its edge lengths (metres, along `x`, `y`, `z`) and its atoms
916 /// as fractions of those edges, wrapped into `[0, 1)`. The lattice
917 /// neighbour search ([`super::search`]) walks these cells.
918 ///
919 /// Cubic: the conventional cube, edges `(a, a, a)`, and
920 /// [`Self::conventional_cell_sites`]. Hexagonal: the orthohexagonal cell
921 /// spanned by `a1 + a2 = (a, 0, 0)`, `a2 - a1 = (0, sqrt(3) a, 0)` and
922 /// `a3 = (0, 0, c)`, which are lattice vectors spanning a sublattice of
923 /// index 2 (determinant of `[[1, 1, 0], [-1, 1, 0], [0, 0, 1]]`), so the
924 /// cell holds every basis site twice: at its own position and shifted by
925 /// `a2`, which is not in the sublattice. A site at lattice coordinates
926 /// `(f1, f2, f3)` has the fractions `((f1 + f2)/2, (f2 - f1)/2, f3)`
927 /// (derived here from the vectors above). Basis order, then the shift.
928 pub fn orthogonal_cell(&self) -> ([f64; 3], Vec<(u8, [f64; 3])>) {
929 if !self.is_hexagonal() {
930 return ([self.a_m; 3], self.conventional_cell_sites());
931 }
932 let edges = [self.a_m, 3.0f64.sqrt() * self.a_m, self.c_m];
933 let mut out = Vec::with_capacity(2 * self.basis.len());
934 for s in &self.basis {
935 let [f1, f2, f3] = s.fractional;
936 for shift in [0.0, 1.0] {
937 let g2 = f2 + shift;
938 out.push((
939 s.z,
940 [
941 wrap_unit(0.5 * (f1 + g2)),
942 wrap_unit(0.5 * (g2 - f1)),
943 wrap_unit(f3),
944 ],
945 ));
946 }
947 }
948 (edges, out)
949 }
950
951 /// Primitive cell volume `a1 . (a2 x a3)`, m³ (Mehl et al. 2017, eq. (6)):
952 /// `a³/4` (cubic) or `(sqrt(3)/2) a² c` (hexagonal).
953 pub fn primitive_cell_volume(&self) -> f64 {
954 let p = self.primitive;
955 dot(p[0], cross(p[1], p[2]))
956 }
957
958 /// Atom number density, atoms per m³: basis sites per primitive cell
959 /// volume.
960 pub fn atom_number_density(&self) -> f64 {
961 self.basis.len() as f64 / self.primitive_cell_volume()
962 }
963
964 /// Number density of element `z`, atoms per m³ (zero if absent).
965 pub fn number_density_of(&self, z: u8) -> f64 {
966 let n = self.basis.iter().filter(|s| s.z == z).count();
967 n as f64 / self.primitive_cell_volume()
968 }
969
970 /// Unit normal (crystal frame) of the lattice planes `(hkl)`, indexed on
971 /// the conventional cell: `g = h b1 + k b2 + l b3` normalised. For a
972 /// cubic cell this is `[hkl] / |[hkl]|`.
973 pub fn plane_normal(&self, hkl: [i32; 3]) -> Result<[f64; 3], CrystalError> {
974 if hkl == [0; 3] {
975 return Err(CrystalError::ZeroIndex(hkl));
976 }
977 let b = reciprocal_vectors(self.conventional_vectors());
978 Ok(unit(combine(b, as_f64(hkl))))
979 }
980
981 /// Unit vector (crystal frame) along the lattice direction `[uvw]`,
982 /// indexed on the conventional cell: `u c1 + v c2 + w c3` normalised.
983 pub fn direction(&self, uvw: [i32; 3]) -> Result<[f64; 3], CrystalError> {
984 if uvw == [0; 3] {
985 return Err(CrystalError::ZeroIndex(uvw));
986 }
987 Ok(unit(combine(self.conventional_vectors(), as_f64(uvw))))
988 }
989
990 /// Unit normal of the Miller-Bravais planes `(hkil)` of a hexagonal
991 /// lattice: [`Self::plane_normal`] of [`plane_from_hkil`].
992 ///
993 /// # Errors
994 /// [`CrystalError::NotHexagonal`] for a cubic lattice, and the errors of
995 /// the two functions named.
996 pub fn plane_normal_hkil(&self, hkil: [i32; 4]) -> Result<[f64; 3], CrystalError> {
997 if !self.is_hexagonal() {
998 return Err(CrystalError::NotHexagonal);
999 }
1000 self.plane_normal(plane_from_hkil(hkil)?)
1001 }
1002
1003 /// Unit vector along the Miller-Bravais direction `[uvtw]` of a hexagonal
1004 /// lattice: [`Self::direction`] of [`direction_from_uvtw`].
1005 ///
1006 /// # Errors
1007 /// [`CrystalError::NotHexagonal`] for a cubic lattice, and the errors of
1008 /// the two functions named.
1009 pub fn direction_uvtw(&self, uvtw: [i32; 4]) -> Result<[f64; 3], CrystalError> {
1010 if !self.is_hexagonal() {
1011 return Err(CrystalError::NotHexagonal);
1012 }
1013 self.direction(direction_from_uvtw(uvtw)?)
1014 }
1015}
1016
1017#[cfg(test)]
1018mod tests {
1019 use super::*;
1020
1021 #[test]
1022 fn presets_carry_their_cited_constants() {
1023 let si = Lattice::silicon();
1024 assert_eq!(si.lattice_constant(), 5.431_020_511e-10);
1025 assert_eq!(si.lattice_constant_c(), 5.431_020_511e-10);
1026 assert_eq!(si.temperature_k(), Some(295.65));
1027 assert_eq!(si.structure(), Structure::Diamond);
1028 assert!(!si.is_hexagonal());
1029 let gaas = Lattice::gallium_arsenide();
1030 assert_eq!(gaas.structure(), Structure::Zincblende);
1031 assert_eq!(gaas.basis()[0].z, 31);
1032 assert_eq!(gaas.basis()[1].z, 33);
1033 assert_eq!(Lattice::germanium().basis()[0].z, 32);
1034 let sic = Lattice::silicon_carbide_3c();
1035 assert_eq!((sic.basis()[0].z, sic.basis()[1].z), (14, 6));
1036 assert_eq!(sic.temperature_k(), Some(297.0));
1037
1038 let gan = Lattice::gallium_nitride();
1039 assert_eq!(gan.structure(), Structure::Wurtzite);
1040 assert_eq!(gan.lattice_constant(), 3.1890 * M_PER_ANGSTROM);
1041 assert_eq!(gan.lattice_constant_c(), 5.18500 * M_PER_ANGSTROM);
1042 assert_eq!(gan.temperature_k(), Some(300.0));
1043 let z: Vec<u8> = gan.basis().iter().map(|s| s.z).collect();
1044 assert_eq!(z, [31, 31, 7, 7]);
1045 let h4 = Lattice::silicon_carbide_4h();
1046 assert_eq!(h4.structure(), Structure::Polytype { layers: 4 });
1047 assert_eq!(h4.basis().len(), 8);
1048 assert_eq!(h4.temperature_k(), None);
1049 let h6 = Lattice::silicon_carbide_6h();
1050 assert_eq!(h6.structure(), Structure::Polytype { layers: 6 });
1051 assert_eq!(h6.basis().len(), 12);
1052 assert!(h6.is_hexagonal());
1053 }
1054
1055 #[test]
1056 fn primitive_volume_is_a_quarter_cube() {
1057 let si = Lattice::silicon();
1058 let a = si.lattice_constant();
1059 assert!((si.primitive_cell_volume() / (a * a * a) - 0.25).abs() < 1e-14);
1060 assert!((si.atom_number_density() * a * a * a - 8.0).abs() < 1e-12);
1061 let gaas = Lattice::gallium_arsenide();
1062 let a = gaas.lattice_constant();
1063 assert!((gaas.number_density_of(31) * a * a * a - 4.0).abs() < 1e-12);
1064 assert!((gaas.number_density_of(33) * a * a * a - 4.0).abs() < 1e-12);
1065 assert_eq!(gaas.number_density_of(14), 0.0);
1066 }
1067
1068 #[test]
1069 fn hexagonal_volume_and_density() {
1070 for l in [
1071 Lattice::gallium_nitride(),
1072 Lattice::silicon_carbide_4h(),
1073 Lattice::silicon_carbide_6h(),
1074 ] {
1075 let (a, c) = (l.lattice_constant(), l.lattice_constant_c());
1076 let v = 0.5 * 3.0f64.sqrt() * a * a * c;
1077 assert!((l.primitive_cell_volume() / v - 1.0).abs() < 1e-14);
1078 let n = l.basis().len() as f64;
1079 assert!((l.atom_number_density() * v / n - 1.0).abs() < 1e-14);
1080 }
1081 }
1082
1083 #[test]
1084 fn diamond_basis_bond_is_a_quarter_body_diagonal() {
1085 // The two sites differ by (3/4)(1,1,1) a, which is -(1/4)(1,1,1) a
1086 // plus the lattice vector (1,1,1) a: the nearest-neighbour bond is
1087 // a sqrt(3) / 4 along <111>.
1088 let si = Lattice::silicon();
1089 let p = si.basis_positions();
1090 let a = si.lattice_constant();
1091 let d = [
1092 p[1].1[0] - p[0].1[0],
1093 p[1].1[1] - p[0].1[1],
1094 p[1].1[2] - p[0].1[2],
1095 ];
1096 for x in d {
1097 assert!((x / a - 0.75).abs() < 1e-14);
1098 }
1099 }
1100
1101 #[test]
1102 fn conventional_cell_has_eight_distinct_atoms() {
1103 for l in [Lattice::silicon(), Lattice::gallium_arsenide()] {
1104 let s = l.conventional_cell_sites();
1105 assert_eq!(s.len(), 8);
1106 for i in 0..8 {
1107 for j in 0..i {
1108 let d: f64 = (0..3).map(|k| (s[i].1[k] - s[j].1[k]).abs()).sum();
1109 assert!(d > 0.1, "{:?} and {:?} coincide", s[i], s[j]);
1110 }
1111 }
1112 }
1113 // Zincblende: Ga on the fcc sites of the cube, As shifted by 1/4.
1114 let s = Lattice::gallium_arsenide().conventional_cell_sites();
1115 assert_eq!(s[0], (31, [0.0, 0.0, 0.0]));
1116 assert_eq!(s[1], (31, [0.0, 0.5, 0.5]));
1117 assert_eq!(s[4], (33, [0.25, 0.25, 0.25]));
1118 assert_eq!(s[7], (33, [0.75, 0.75, 0.25]));
1119 }
1120
1121 #[test]
1122 fn cubic_orthogonal_cell_is_the_conventional_cube() {
1123 let l = Lattice::gallium_arsenide();
1124 let (e, s) = l.orthogonal_cell();
1125 assert_eq!(e, [l.lattice_constant(); 3]);
1126 assert_eq!(s, l.conventional_cell_sites());
1127 }
1128
1129 #[test]
1130 fn reciprocal_vectors_are_dual() {
1131 let l = Lattice::germanium();
1132 let h = Lattice::silicon_carbide_4h();
1133 for c in [
1134 l.primitive_vectors(),
1135 l.conventional_vectors(),
1136 h.conventional_vectors(),
1137 ] {
1138 let b = reciprocal_vectors(c);
1139 for (i, ci) in c.iter().enumerate() {
1140 for (j, bj) in b.iter().enumerate() {
1141 let want = if i == j { 2.0 * PI } else { 0.0 };
1142 assert!((dot(*ci, *bj) - want).abs() < 1e-12);
1143 }
1144 }
1145 }
1146 }
1147
1148 #[test]
1149 fn miller_normals_and_directions() {
1150 let si = Lattice::silicon();
1151 let n = si.plane_normal([1, 1, 0]).unwrap();
1152 let s = 0.5f64.sqrt();
1153 assert!((n[0] - s).abs() < 1e-15 && (n[1] - s).abs() < 1e-15 && n[2].abs() < 1e-15);
1154 let d = si.direction([1, 1, 1]).unwrap();
1155 let t = (1.0f64 / 3.0).sqrt();
1156 assert!(d.iter().all(|x| (x - t).abs() < 1e-15));
1157 assert_eq!(
1158 si.plane_normal([0, 0, 0]),
1159 Err(CrystalError::ZeroIndex([0; 3]))
1160 );
1161 assert_eq!(
1162 si.direction([0, 0, 0]),
1163 Err(CrystalError::ZeroIndex([0; 3]))
1164 );
1165 }
1166
1167 #[test]
1168 fn miller_bravais_conversion() {
1169 assert_eq!(plane_from_hkil([1, 1, -2, 0]), Ok([1, 1, 0]));
1170 assert_eq!(plane_from_hkil([0, 0, 0, 1]), Ok([0, 0, 1]));
1171 assert_eq!(direction_from_uvtw([1, 1, -2, 0]), Ok([3, 3, 0]));
1172 assert_eq!(direction_from_uvtw([2, -1, -1, 0]), Ok([3, 0, 0]));
1173 assert_eq!(direction_from_uvtw([0, 0, 0, 1]), Ok([0, 0, 1]));
1174 assert!(matches!(
1175 plane_from_hkil([1, 1, 1, 0]),
1176 Err(CrystalError::InvalidMillerBravais { .. })
1177 ));
1178 assert!(matches!(
1179 direction_from_uvtw([1, 0, 0, 0]),
1180 Err(CrystalError::InvalidMillerBravais { .. })
1181 ));
1182 assert!(matches!(
1183 direction_from_uvtw([i32::MAX, 0, -i32::MAX, 0]),
1184 Err(CrystalError::InvalidMillerBravais { .. })
1185 ));
1186 assert_eq!(
1187 Lattice::silicon().direction_uvtw([0, 0, 0, 1]),
1188 Err(CrystalError::NotHexagonal)
1189 );
1190 assert_eq!(
1191 Lattice::silicon().plane_normal_hkil([0, 0, 0, 1]),
1192 Err(CrystalError::NotHexagonal)
1193 );
1194 }
1195
1196 #[test]
1197 fn bad_inputs_are_rejected() {
1198 assert_eq!(
1199 Lattice::diamond(14, -1.0, 300.0),
1200 Err(CrystalError::InvalidLatticeConstant(-1.0))
1201 );
1202 assert!(matches!(
1203 Lattice::diamond(14, f64::NAN, 300.0),
1204 Err(CrystalError::InvalidLatticeConstant(_))
1205 ));
1206 assert_eq!(
1207 Lattice::diamond(14, 5e-10, -1.0),
1208 Err(CrystalError::InvalidTemperature(-1.0))
1209 );
1210 assert_eq!(
1211 Lattice::zincblende(31, 0, 5e-10, 300.0),
1212 Err(CrystalError::UnknownAtomicNumber(0))
1213 );
1214 assert_eq!(
1215 Lattice::wurtzite(31, 7, 3e-10, 0.0, 0.375, None),
1216 Err(CrystalError::InvalidLatticeConstant(0.0))
1217 );
1218 assert_eq!(
1219 Lattice::wurtzite(31, 7, 3e-10, 5e-10, 0.5, None),
1220 Err(CrystalError::InvalidInternalParameter(0.5))
1221 );
1222 assert_eq!(
1223 Lattice::wurtzite(31, 7, 3e-10, 5e-10, 0.375, Some(-2.0)),
1224 Err(CrystalError::InvalidTemperature(-2.0))
1225 );
1226 let too_long = "AB".repeat(MAX_POLYTYPE_LAYERS / 2 + 1);
1227 let longest = "AB".repeat(MAX_POLYTYPE_LAYERS / 2);
1228 assert!(Lattice::polytype(14, 6, &longest, 3e-10, 1e-9, 0.375, None).is_ok());
1229 for bad in ["A", "AAB", "ABA", "ABD", "", too_long.as_str()] {
1230 assert!(
1231 matches!(
1232 Lattice::polytype(14, 6, bad, 3e-10, 1e-9, 0.375, None),
1233 Err(CrystalError::InvalidStacking { .. })
1234 ),
1235 "{bad}"
1236 );
1237 }
1238 }
1239}