1#![allow(clippy::indexing_slicing, clippy::needless_range_loop)] use std::collections::BTreeMap;
10
11use crate::{Error, Lattice, Site, Structure, StructureBuilder};
12
13pub fn voigt_to_tensor(voigt: [f64; 6]) -> Result<[[f64; 3]; 3], Error> {
20 if !voigt.iter().all(|value| value.is_finite()) {
21 return Err(Error::InvalidInput(
22 "strain components must be finite".to_owned(),
23 ));
24 }
25 let [xx, yy, zz, yz, xz, xy] = voigt;
26 Ok([
27 [xx, 0.5 * xy, 0.5 * xz],
28 [0.5 * xy, yy, 0.5 * yz],
29 [0.5 * xz, 0.5 * yz, zz],
30 ])
31}
32
33#[must_use]
35pub fn tensor_to_voigt(tensor: [[f64; 3]; 3]) -> [f64; 6] {
36 [
37 tensor[0][0],
38 tensor[1][1],
39 tensor[2][2],
40 2.0 * tensor[1][2],
41 2.0 * tensor[0][2],
42 2.0 * tensor[0][1],
43 ]
44}
45
46pub fn strain(structure: &Structure, tensor: [[f64; 3]; 3]) -> Result<Structure, Error> {
52 let lattice = structure.lattice().strained(tensor)?;
53 copy_with_lattice(structure, lattice)
54}
55
56pub fn deform(structure: &Structure, gradient: [[f64; 3]; 3]) -> Result<Structure, Error> {
63 if !gradient.iter().flatten().all(|value| value.is_finite()) {
64 return Err(Error::InvalidInput(
65 "deformation gradient must be finite".to_owned(),
66 ));
67 }
68 let determinant = determinant(gradient);
69 if determinant.abs() < 1e-9 || determinant <= 0.0 {
70 return Err(Error::InvalidInput(
71 "deformation gradient must have a positive determinant".to_owned(),
72 ));
73 }
74 let transpose = [
77 [gradient[0][0], gradient[1][0], gradient[2][0]],
78 [gradient[0][1], gradient[1][1], gradient[2][1]],
79 [gradient[0][2], gradient[1][2], gradient[2][2]],
80 ];
81 let eps = [
82 [transpose[0][0] - 1.0, transpose[0][1], transpose[0][2]],
83 [transpose[1][0], transpose[1][1] - 1.0, transpose[1][2]],
84 [transpose[2][0], transpose[2][1], transpose[2][2] - 1.0],
85 ];
86 strain(structure, eps)
87}
88
89pub fn scale_to_volume(structure: &Structure, volume: f64) -> Result<Structure, Error> {
95 let lattice = structure.lattice().scaled_to_volume(volume)?;
96 copy_with_lattice(structure, lattice)
97}
98
99pub fn scale_to_density(structure: &Structure, density: f64) -> Result<Structure, Error> {
106 if !density.is_finite() || density <= 0.0 {
107 return Err(Error::InvalidInput(format!(
108 "target density must be positive and finite, got {density}"
109 )));
110 }
111 let mass = structure.cell_mass()?;
112 let volume = mass / (density * 0.602_214_076_9);
113 scale_to_volume(structure, volume)
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub struct RattleOptions {
119 pub amplitude: f64,
122 pub seed: u64,
124 pub bounded: bool,
126 pub per_element: BTreeMap<String, f64>,
128}
129
130impl Default for RattleOptions {
131 fn default() -> Self {
132 Self {
133 amplitude: 0.05,
134 seed: 0,
135 bounded: false,
136 per_element: BTreeMap::new(),
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq)]
143pub struct RattleReport {
144 pub structure: Structure,
146 pub max_displacement: f64,
148 pub rms_displacement: f64,
150 pub n_atoms: usize,
152}
153
154pub fn rattle(structure: &Structure, options: &RattleOptions) -> Result<RattleReport, Error> {
161 if !options.amplitude.is_finite() || options.amplitude < 0.0 {
162 return Err(Error::InvalidInput(
163 "rattle amplitude must be non-negative and finite".to_owned(),
164 ));
165 }
166 for (element, amplitude) in &options.per_element {
167 if !amplitude.is_finite() || *amplitude < 0.0 {
168 return Err(Error::InvalidInput(format!(
169 "rattle amplitude for {element} must be non-negative and finite"
170 )));
171 }
172 }
173 let p1 = structure.p1()?;
174 let mut rng = SplitMix64::new(options.seed);
175 let mut builder = StructureBuilder::new(structure.name(), p1.lattice);
176 let mut squared = 0.0;
177 let mut largest = 0.0_f64;
178 for atom in &p1.atoms {
179 let amplitude = options
180 .per_element
181 .get(&atom.element)
182 .copied()
183 .unwrap_or(options.amplitude);
184 let displacement = if options.bounded {
185 let direction = rng.unit_vector();
186 [
187 direction[0] * amplitude,
188 direction[1] * amplitude,
189 direction[2] * amplitude,
190 ]
191 } else {
192 let scale = amplitude * (rng.gaussian() / std::f64::consts::SQRT_2);
193 let direction = rng.unit_vector();
194 [
195 direction[0] * scale,
196 direction[1] * scale,
197 direction[2] * scale,
198 ]
199 };
200 let cart = p1.lattice.to_cart(atom.frac);
201 let moved = [
202 cart[0] + displacement[0],
203 cart[1] + displacement[1],
204 cart[2] + displacement[2],
205 ];
206 let frac = Lattice::wrap(p1.lattice.to_frac(moved));
207 let site =
208 Site::new(atom.label.clone(), &atom.element, frac)?.with_occupancy(atom.occupancy)?;
209 builder = builder.site(site);
210 let norm = (displacement[0] * displacement[0]
211 + displacement[1] * displacement[1]
212 + displacement[2] * displacement[2])
213 .sqrt();
214 squared += norm * norm;
215 largest = largest.max(norm);
216 }
217 Ok(RattleReport {
218 structure: builder.build()?,
219 max_displacement: largest,
220 rms_displacement: if p1.atoms.is_empty() {
221 0.0
222 } else {
223 (squared / p1.atoms.len() as f64).sqrt()
224 },
225 n_atoms: p1.atoms.len(),
226 })
227}
228
229mod sites;
230mod supercell;
231
232pub use sites::{
233 SiteSort, SpeciesReplacement, add_sites, move_site, remove_sites, replace_site,
234 replace_species, sort_sites, wrap_sites,
235};
236pub use supercell::{SupercellChoice, optimal_supercell};
237
238fn copy_with_lattice(structure: &Structure, lattice: Lattice) -> Result<Structure, Error> {
239 let mut builder = StructureBuilder::new(structure.name(), lattice).hall(structure.hall());
240 for operation in structure.operations() {
241 builder = builder.operation(*operation);
242 }
243 for site in structure.sites() {
244 builder = builder.site(site.clone());
245 }
246 for (key, value) in structure.metadata() {
247 builder = builder.metadata(key.clone(), value.clone());
248 }
249 builder.build()
250}
251
252#[derive(Debug, Clone)]
254pub(crate) struct SplitMix64 {
255 state: u64,
256}
257
258impl SplitMix64 {
259 pub(crate) fn new(seed: u64) -> Self {
260 Self { state: seed }
261 }
262
263 pub(crate) fn next_u64(&mut self) -> u64 {
264 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
265 let mut z = self.state;
266 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
267 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
268 z ^ (z >> 31)
269 }
270
271 fn uniform(&mut self) -> f64 {
272 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
273 }
274
275 pub(crate) fn gaussian(&mut self) -> f64 {
276 let u1 = self.uniform().max(1e-300);
277 let u2 = self.uniform();
278 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
279 }
280
281 pub(crate) fn unit_vector(&mut self) -> [f64; 3] {
282 let z = 2.0 * self.uniform() - 1.0;
283 let phi = 2.0 * std::f64::consts::PI * self.uniform();
284 let radius = (1.0 - z * z).max(0.0).sqrt();
285 [radius * phi.cos(), radius * phi.sin(), z]
286 }
287
288 pub(crate) fn shuffle<T>(&mut self, values: &mut [T]) {
289 for index in (1..values.len()).rev() {
290 let other = (self.next_u64() % (index as u64 + 1)) as usize;
291 values.swap(index, other);
292 }
293 }
294}
295
296fn determinant(matrix: [[f64; 3]; 3]) -> f64 {
297 let [[a, b, c], [d, e, f], [g, h, i]] = matrix;
298 a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
299}