#![allow(clippy::indexing_slicing, clippy::needless_range_loop)]
use std::collections::BTreeMap;
use crate::{Error, Lattice, Site, Structure, StructureBuilder};
pub fn voigt_to_tensor(voigt: [f64; 6]) -> Result<[[f64; 3]; 3], Error> {
if !voigt.iter().all(|value| value.is_finite()) {
return Err(Error::InvalidInput(
"strain components must be finite".to_owned(),
));
}
let [xx, yy, zz, yz, xz, xy] = voigt;
Ok([
[xx, 0.5 * xy, 0.5 * xz],
[0.5 * xy, yy, 0.5 * yz],
[0.5 * xz, 0.5 * yz, zz],
])
}
#[must_use]
pub fn tensor_to_voigt(tensor: [[f64; 3]; 3]) -> [f64; 6] {
[
tensor[0][0],
tensor[1][1],
tensor[2][2],
2.0 * tensor[1][2],
2.0 * tensor[0][2],
2.0 * tensor[0][1],
]
}
pub fn strain(structure: &Structure, tensor: [[f64; 3]; 3]) -> Result<Structure, Error> {
let lattice = structure.lattice().strained(tensor)?;
copy_with_lattice(structure, lattice)
}
pub fn deform(structure: &Structure, gradient: [[f64; 3]; 3]) -> Result<Structure, Error> {
if !gradient.iter().flatten().all(|value| value.is_finite()) {
return Err(Error::InvalidInput(
"deformation gradient must be finite".to_owned(),
));
}
let determinant = determinant(gradient);
if determinant.abs() < 1e-9 || determinant <= 0.0 {
return Err(Error::InvalidInput(
"deformation gradient must have a positive determinant".to_owned(),
));
}
let transpose = [
[gradient[0][0], gradient[1][0], gradient[2][0]],
[gradient[0][1], gradient[1][1], gradient[2][1]],
[gradient[0][2], gradient[1][2], gradient[2][2]],
];
let eps = [
[transpose[0][0] - 1.0, transpose[0][1], transpose[0][2]],
[transpose[1][0], transpose[1][1] - 1.0, transpose[1][2]],
[transpose[2][0], transpose[2][1], transpose[2][2] - 1.0],
];
strain(structure, eps)
}
pub fn scale_to_volume(structure: &Structure, volume: f64) -> Result<Structure, Error> {
let lattice = structure.lattice().scaled_to_volume(volume)?;
copy_with_lattice(structure, lattice)
}
pub fn scale_to_density(structure: &Structure, density: f64) -> Result<Structure, Error> {
if !density.is_finite() || density <= 0.0 {
return Err(Error::InvalidInput(format!(
"target density must be positive and finite, got {density}"
)));
}
let mass = structure.cell_mass()?;
let volume = mass / (density * 0.602_214_076_9);
scale_to_volume(structure, volume)
}
#[derive(Debug, Clone, PartialEq)]
pub struct RattleOptions {
pub amplitude: f64,
pub seed: u64,
pub bounded: bool,
pub per_element: BTreeMap<String, f64>,
}
impl Default for RattleOptions {
fn default() -> Self {
Self {
amplitude: 0.05,
seed: 0,
bounded: false,
per_element: BTreeMap::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RattleReport {
pub structure: Structure,
pub max_displacement: f64,
pub rms_displacement: f64,
pub n_atoms: usize,
}
pub fn rattle(structure: &Structure, options: &RattleOptions) -> Result<RattleReport, Error> {
if !options.amplitude.is_finite() || options.amplitude < 0.0 {
return Err(Error::InvalidInput(
"rattle amplitude must be non-negative and finite".to_owned(),
));
}
for (element, amplitude) in &options.per_element {
if !amplitude.is_finite() || *amplitude < 0.0 {
return Err(Error::InvalidInput(format!(
"rattle amplitude for {element} must be non-negative and finite"
)));
}
}
let p1 = structure.p1()?;
let mut rng = SplitMix64::new(options.seed);
let mut builder = StructureBuilder::new(structure.name(), p1.lattice);
let mut squared = 0.0;
let mut largest = 0.0_f64;
for atom in &p1.atoms {
let amplitude = options
.per_element
.get(&atom.element)
.copied()
.unwrap_or(options.amplitude);
let displacement = if options.bounded {
let direction = rng.unit_vector();
[
direction[0] * amplitude,
direction[1] * amplitude,
direction[2] * amplitude,
]
} else {
let scale = amplitude * (rng.gaussian() / std::f64::consts::SQRT_2);
let direction = rng.unit_vector();
[
direction[0] * scale,
direction[1] * scale,
direction[2] * scale,
]
};
let cart = p1.lattice.to_cart(atom.frac);
let moved = [
cart[0] + displacement[0],
cart[1] + displacement[1],
cart[2] + displacement[2],
];
let frac = Lattice::wrap(p1.lattice.to_frac(moved));
let site =
Site::new(atom.label.clone(), &atom.element, frac)?.with_occupancy(atom.occupancy)?;
builder = builder.site(site);
let norm = (displacement[0] * displacement[0]
+ displacement[1] * displacement[1]
+ displacement[2] * displacement[2])
.sqrt();
squared += norm * norm;
largest = largest.max(norm);
}
Ok(RattleReport {
structure: builder.build()?,
max_displacement: largest,
rms_displacement: if p1.atoms.is_empty() {
0.0
} else {
(squared / p1.atoms.len() as f64).sqrt()
},
n_atoms: p1.atoms.len(),
})
}
mod sites;
mod supercell;
pub use sites::{
SiteSort, SpeciesReplacement, add_sites, move_site, remove_sites, replace_site,
replace_species, sort_sites, wrap_sites,
};
pub use supercell::{SupercellChoice, optimal_supercell};
fn copy_with_lattice(structure: &Structure, lattice: Lattice) -> Result<Structure, Error> {
let mut builder = StructureBuilder::new(structure.name(), lattice).hall(structure.hall());
for operation in structure.operations() {
builder = builder.operation(*operation);
}
for site in structure.sites() {
builder = builder.site(site.clone());
}
for (key, value) in structure.metadata() {
builder = builder.metadata(key.clone(), value.clone());
}
builder.build()
}
#[derive(Debug, Clone)]
pub(crate) struct SplitMix64 {
state: u64,
}
impl SplitMix64 {
pub(crate) fn new(seed: u64) -> Self {
Self { state: seed }
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn uniform(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
}
pub(crate) fn gaussian(&mut self) -> f64 {
let u1 = self.uniform().max(1e-300);
let u2 = self.uniform();
(-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
pub(crate) fn unit_vector(&mut self) -> [f64; 3] {
let z = 2.0 * self.uniform() - 1.0;
let phi = 2.0 * std::f64::consts::PI * self.uniform();
let radius = (1.0 - z * z).max(0.0).sqrt();
[radius * phi.cos(), radius * phi.sin(), z]
}
pub(crate) fn shuffle<T>(&mut self, values: &mut [T]) {
for index in (1..values.len()).rev() {
let other = (self.next_u64() % (index as u64 + 1)) as usize;
values.swap(index, other);
}
}
}
fn determinant(matrix: [[f64; 3]; 3]) -> f64 {
let [[a, b, c], [d, e, f], [g, h, i]] = matrix;
a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
}