#[allow(unused_imports)]
#[macro_use]
extern crate approx;
pub mod base;
pub mod data;
pub mod identify;
pub mod math;
pub mod search; pub mod utils;
mod symmetrize;
pub use base::MoyoError as Error;
use crate::base::{
AngleTolerance, Cell, LayerCell, Linear, MagneticCell, MagneticMoment, MagneticOperations,
MoyoError, Operation, Operations, OriginShift, RotationMagneticMomentAction, Transformation,
UnimodularTransformation,
};
use crate::data::{
ArithmeticCrystalClassEntry, HallNumber, HallSymbol, HallSymbolEntry,
LayerArithmeticCrystalClassEntry, LayerHallNumber, LayerHallSymbolEntry, LayerNumber,
LayerSetting, Number, Setting, UNINumber, arithmetic_crystal_class_entry, hall_symbol_entry,
layer_arithmetic_crystal_class_entry, layer_hall_symbol_entry,
};
use crate::identify::{LayerGroup, MagneticSpaceGroup, Normalizer, SpaceGroup};
use crate::search::{
LayerPrimitiveCell, iterative_layer_symmetry_search, iterative_magnetic_symmetry_search,
iterative_symmetry_search, magnetic_operations_in_magnetic_cell, operations_in_cell,
};
use crate::symmetrize::{
StandardizedCell, StandardizedLayerCell, StandardizedMagneticCell, orbits_in_cell,
};
use crate::utils::{to_3_slice, to_3x3_slice};
use nalgebra::Matrix3;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MoyoDataset {
pub number: Number,
pub hall_number: HallNumber,
pub hm_symbol: String,
pub operations: Operations,
pub orbits: Vec<usize>,
pub wyckoffs: Vec<char>,
pub site_symmetry_symbols: Vec<String>,
pub std_cell: Cell,
pub std_linear: Matrix3<f64>,
pub std_origin_shift: OriginShift,
pub std_rotation_matrix: Matrix3<f64>,
pub pearson_symbol: String,
pub prim_std_cell: Cell,
pub prim_std_linear: Matrix3<f64>,
pub prim_std_origin_shift: OriginShift,
pub mapping_std_prim: Vec<usize>,
pub symprec: f64,
pub angle_tolerance: AngleTolerance,
}
impl MoyoDataset {
pub fn new(
cell: &Cell,
symprec: f64,
angle_tolerance: AngleTolerance,
setting: Setting,
rotate_basis: bool,
) -> Result<Self, MoyoError> {
let (prim_cell, symmetry_search, symprec, angle_tolerance) =
iterative_symmetry_search(cell, symprec, angle_tolerance)?;
let operations = operations_in_cell(&prim_cell, &symmetry_search.operations);
let epsilon = symprec / prim_cell.cell.lattice.volume().powf(1.0 / 3.0);
let space_group = SpaceGroup::new(&symmetry_search.operations, setting, epsilon)?;
let std_cell = StandardizedCell::new(
&prim_cell.cell,
&symmetry_search.operations,
&symmetry_search.permutations,
&space_group,
symprec,
epsilon,
rotate_basis,
)?;
let orbits = orbits_in_cell(
prim_cell.cell.num_atoms(),
&symmetry_search.permutations,
&prim_cell.site_mapping,
);
let mapping_std_prim = prim_cell.site_mapping.clone();
let wyckoffs = lift_wyckoffs_to_input_cell(
prim_cell.cell.num_atoms(),
&std_cell.wyckoffs,
&std_cell.site_mapping,
&mapping_std_prim,
)?;
let (std_linear, std_origin_shift, prim_std_linear, prim_std_origin_shift) =
compose_std_transformations(
prim_cell.linear,
&std_cell.transformation,
&std_cell.prim_transformation,
);
let hall_symbol = hall_symbol_entry(space_group.hall_number).unwrap();
let arithmetic_entry =
arithmetic_crystal_class_entry(hall_symbol.arithmetic_number).unwrap();
let bravais_class = arithmetic_entry.bravais_class;
let pearson_symbol = format!("{}{}", bravais_class.to_string(), std_cell.cell.num_atoms());
Ok(Self {
number: space_group.number,
hall_number: space_group.hall_number,
hm_symbol: hall_symbol.hm_short.to_string(),
operations,
std_cell: std_cell.cell,
std_linear,
std_origin_shift,
std_rotation_matrix: std_cell.rotation_matrix,
pearson_symbol,
prim_std_cell: std_cell.prim_cell,
prim_std_linear,
prim_std_origin_shift,
mapping_std_prim,
orbits,
wyckoffs: wyckoffs.iter().map(|w| w.letter).collect(),
site_symmetry_symbols: wyckoffs
.iter()
.map(|w| w.site_symmetry.to_string())
.collect(),
symprec,
angle_tolerance,
})
}
pub fn with_default(cell: &Cell, symprec: f64) -> Result<Self, MoyoError> {
Self::new(
cell,
symprec,
AngleTolerance::default(),
Setting::default(),
true,
)
}
pub fn num_operations(&self) -> usize {
self.operations.len()
}
pub fn std_linear_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.std_linear)
}
pub fn std_origin_shift_as_array(&self) -> [f64; 3] {
to_3_slice(&self.std_origin_shift)
}
pub fn std_rotation_matrix_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.std_rotation_matrix)
}
pub fn prim_std_linear_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.prim_std_linear)
}
pub fn prim_std_origin_shift_as_array(&self) -> [f64; 3] {
to_3_slice(&self.prim_std_origin_shift)
}
pub fn hall_symbol(&self) -> HallSymbolEntry {
hall_symbol_entry(self.hall_number).unwrap()
}
pub fn arithmetic_crystal_class(&self) -> ArithmeticCrystalClassEntry {
arithmetic_crystal_class_entry(self.hall_symbol().arithmetic_number).unwrap()
}
pub fn euclidean_normalizer(&self, preserve_chirality: bool) -> Result<Normalizer, MoyoError> {
let hall_symbol = HallSymbol::from_hall_number(self.hall_number).unwrap();
let prim_operations = hall_symbol.primitive_traverse();
let prim_generators = hall_symbol.primitive_generators();
Normalizer::from_lattice(
&self.prim_std_cell.lattice,
&prim_operations,
&prim_generators,
self.symprec,
self.angle_tolerance,
preserve_chirality,
)
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MoyoLayerDataset {
pub number: LayerNumber,
pub hall_number: LayerHallNumber,
pub hm_symbol: String,
pub operations: Operations,
pub orbits: Vec<usize>,
pub wyckoffs: Vec<char>,
pub site_symmetry_symbols: Vec<String>,
pub std_cell: LayerCell,
pub std_linear: Matrix3<f64>,
pub std_origin_shift: OriginShift,
pub std_rotation_matrix: Matrix3<f64>,
pub pearson_symbol: String,
pub prim_std_cell: LayerCell,
pub prim_std_linear: Matrix3<f64>,
pub prim_std_origin_shift: OriginShift,
pub mapping_std_prim: Vec<usize>,
pub symprec: f64,
pub angle_tolerance: AngleTolerance,
}
impl MoyoLayerDataset {
pub fn new(
cell: &Cell,
symprec: f64,
angle_tolerance: AngleTolerance,
setting: LayerSetting,
rotate_basis: bool,
) -> Result<Self, MoyoError> {
let (prim_layer, symmetry_search, symprec, angle_tolerance) =
iterative_layer_symmetry_search(cell, symprec, angle_tolerance)?;
let prim_volume = prim_layer.layer_cell.lattice().basis().determinant().abs();
let epsilon = symprec / prim_volume.powf(1.0 / 3.0);
let layer_group = LayerGroup::new(&symmetry_search.operations, setting, epsilon)?;
let std_layer = StandardizedLayerCell::new(
&prim_layer.layer_cell,
&symmetry_search.operations,
&symmetry_search.permutations,
&layer_group,
symprec,
epsilon,
rotate_basis,
)?;
let operations = layer_operations_in_cell(&prim_layer, &symmetry_search.operations);
let mapping_std_prim = prim_layer.site_mapping.clone();
let orbits = orbits_in_cell(
prim_layer.layer_cell.num_atoms(),
&symmetry_search.permutations,
&prim_layer.site_mapping,
);
let wyckoffs = lift_wyckoffs_to_input_cell(
prim_layer.layer_cell.num_atoms(),
&std_layer.wyckoffs,
&std_layer.site_mapping,
&mapping_std_prim,
)?;
let (std_linear, std_origin_shift, prim_std_linear, prim_std_origin_shift) =
compose_std_transformations(
prim_layer.linear,
&std_layer.transformation,
&std_layer.prim_transformation,
);
let entry = layer_hall_symbol_entry(layer_group.hall_number).unwrap();
let layer_arith = layer_arithmetic_crystal_class_entry(entry.arithmetic_number).unwrap();
let pearson_symbol = format!(
"{}{}",
layer_arith.layer_bravais_class.to_string(),
std_layer.layer_cell.num_atoms()
);
Ok(Self {
number: layer_group.number,
hall_number: layer_group.hall_number,
hm_symbol: entry.hm_short.to_string(),
operations,
orbits,
wyckoffs: wyckoffs.iter().map(|w| w.letter).collect(),
site_symmetry_symbols: wyckoffs
.iter()
.map(|w| w.site_symmetry.to_string())
.collect(),
std_cell: std_layer.layer_cell,
std_linear,
std_origin_shift,
std_rotation_matrix: std_layer.rotation_matrix,
pearson_symbol,
prim_std_cell: std_layer.prim_layer_cell,
prim_std_linear,
prim_std_origin_shift,
mapping_std_prim,
symprec,
angle_tolerance,
})
}
pub fn with_default(cell: &Cell, symprec: f64) -> Result<Self, MoyoError> {
Self::new(
cell,
symprec,
AngleTolerance::default(),
LayerSetting::default(),
true,
)
}
pub fn num_operations(&self) -> usize {
self.operations.len()
}
pub fn std_linear_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.std_linear)
}
pub fn std_origin_shift_as_array(&self) -> [f64; 3] {
to_3_slice(&self.std_origin_shift)
}
pub fn std_rotation_matrix_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.std_rotation_matrix)
}
pub fn prim_std_linear_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.prim_std_linear)
}
pub fn prim_std_origin_shift_as_array(&self) -> [f64; 3] {
to_3_slice(&self.prim_std_origin_shift)
}
pub fn hall_symbol(&self) -> LayerHallSymbolEntry {
layer_hall_symbol_entry(self.hall_number).unwrap().clone()
}
pub fn arithmetic_crystal_class(&self) -> LayerArithmeticCrystalClassEntry {
layer_arithmetic_crystal_class_entry(self.hall_symbol().arithmetic_number).unwrap()
}
}
fn layer_operations_in_cell(prim: &LayerPrimitiveCell, prim_operations: &Operations) -> Operations {
let input_operations =
Transformation::from_linear(prim.linear).transform_operations(prim_operations);
let mut operations = vec![];
for t1 in prim.translations.iter() {
for op2 in input_operations.iter() {
let t12 = (t1 + op2.translation).map(|e| e % 1.);
operations.push(Operation::new(op2.rotation, t12));
}
}
operations
}
fn lift_wyckoffs_to_input_cell<W: Clone>(
prim_num_atoms: usize,
std_wyckoffs: &[W],
std_site_mapping: &[usize],
input_to_prim_mapping: &[usize],
) -> Result<Vec<W>, MoyoError> {
let mut prim_wyckoffs: Vec<Option<W>> = vec![None; prim_num_atoms];
for (i, wyckoff) in std_wyckoffs.iter().enumerate() {
let j = std_site_mapping[i];
if prim_wyckoffs[j].is_none() {
prim_wyckoffs[j] = Some(wyckoff.clone());
}
}
let lifted: Option<Vec<W>> = input_to_prim_mapping
.iter()
.map(|&i| prim_wyckoffs[i].clone())
.collect();
lifted.ok_or(MoyoError::WyckoffPositionAssignmentError)
}
fn compose_std_transformations(
prim_linear: Linear,
std_transformation: &Transformation,
prim_std_transformation: &UnimodularTransformation,
) -> (Matrix3<f64>, OriginShift, Matrix3<f64>, OriginShift) {
let prim_inv = prim_linear.map(|e| e as f64).try_inverse().unwrap();
(
prim_inv * std_transformation.linear_as_f64(),
prim_inv * std_transformation.origin_shift,
prim_inv * prim_std_transformation.linear_as_f64(),
prim_inv * prim_std_transformation.origin_shift,
)
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MoyoMagneticDataset<M: MagneticMoment> {
pub uni_number: UNINumber,
pub magnetic_operations: MagneticOperations,
pub orbits: Vec<usize>,
pub std_mag_cell: MagneticCell<M>,
pub std_linear: Matrix3<f64>,
pub std_origin_shift: OriginShift,
pub std_rotation_matrix: Matrix3<f64>,
pub prim_std_mag_cell: MagneticCell<M>,
pub prim_std_linear: Matrix3<f64>,
pub prim_std_origin_shift: OriginShift,
pub mapping_std_prim: Vec<usize>,
pub symprec: f64,
pub angle_tolerance: AngleTolerance,
pub mag_symprec: f64,
}
impl<M: MagneticMoment> MoyoMagneticDataset<M> {
pub fn new(
magnetic_cell: &MagneticCell<M>,
symprec: f64,
angle_tolerance: AngleTolerance,
mag_symprec: Option<f64>,
action: RotationMagneticMomentAction,
rotate_basis: bool,
) -> Result<Self, MoyoError> {
let (prim_mag_cell, magnetic_symmetry_search, symprec, angle_tolerance, mag_symprec) =
iterative_magnetic_symmetry_search(
magnetic_cell,
symprec,
angle_tolerance,
mag_symprec,
action,
)?;
let magnetic_operations = magnetic_operations_in_magnetic_cell(
&prim_mag_cell,
&magnetic_symmetry_search.magnetic_operations,
);
let epsilon = symprec
/ prim_mag_cell
.magnetic_cell
.cell
.lattice
.volume()
.powf(1.0 / 3.0);
let magnetic_space_group =
MagneticSpaceGroup::new(&magnetic_symmetry_search.magnetic_operations, epsilon)?;
let std_mag_cell = StandardizedMagneticCell::new(
&prim_mag_cell,
&magnetic_symmetry_search,
&magnetic_space_group,
symprec,
mag_symprec,
epsilon,
action,
rotate_basis,
)?;
let mapping_std_prim = prim_mag_cell.site_mapping.clone();
let orbits = orbits_in_cell(
prim_mag_cell.magnetic_cell.num_atoms(),
&magnetic_symmetry_search.permutations,
&mapping_std_prim,
);
let prim_mag_cell_linear_inv = prim_mag_cell
.linear
.map(|e| e as f64)
.try_inverse()
.unwrap();
let std_linear = prim_mag_cell_linear_inv * std_mag_cell.transformation.linear_as_f64();
let std_origin_shift = prim_mag_cell_linear_inv * std_mag_cell.transformation.origin_shift;
let prim_std_linear =
prim_mag_cell_linear_inv * std_mag_cell.prim_transformation.linear_as_f64();
let prim_std_origin_shift =
prim_mag_cell_linear_inv * std_mag_cell.prim_transformation.origin_shift;
Ok(Self {
uni_number: magnetic_space_group.uni_number,
magnetic_operations,
orbits,
std_mag_cell: std_mag_cell.mag_cell,
std_linear,
std_origin_shift,
std_rotation_matrix: std_mag_cell.rotation_matrix,
prim_std_mag_cell: std_mag_cell.prim_mag_cell,
prim_std_linear,
prim_std_origin_shift,
mapping_std_prim,
symprec,
angle_tolerance,
mag_symprec,
})
}
pub fn with_default(
magnetic_cell: &MagneticCell<M>,
symprec: f64,
action: RotationMagneticMomentAction,
) -> Result<Self, MoyoError> {
Self::new(
magnetic_cell,
symprec,
AngleTolerance::default(),
None,
action,
true,
)
}
pub fn num_magnetic_operations(&self) -> usize {
self.magnetic_operations.len()
}
pub fn std_linear_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.std_linear)
}
pub fn std_origin_shift_as_array(&self) -> [f64; 3] {
to_3_slice(&self.std_origin_shift)
}
pub fn std_rotation_matrix_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.std_rotation_matrix)
}
pub fn prim_std_linear_as_array(&self) -> [[f64; 3]; 3] {
to_3x3_slice(&self.prim_std_linear)
}
pub fn prim_std_origin_shift_as_array(&self) -> [f64; 3] {
to_3_slice(&self.prim_std_origin_shift)
}
}