Skip to main content

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}