pub mod arithmetic;
pub mod cell;
pub mod debug;
pub mod delaunay;
pub mod determination;
pub mod hall_symbol;
pub mod kgrid;
pub mod kpoint;
pub mod magnetic_spacegroup;
pub mod mathfunc;
pub mod msg_database;
#[cfg(test)]
pub mod magnetic_spacegroup_test;
pub mod cof3_test;
pub mod crps4_test;
pub mod la2nio4_test;
pub mod niggli;
pub mod overlap;
pub mod pointgroup;
pub mod primitive;
pub mod refinement;
pub mod site_symmetry;
pub mod sitesym_database;
pub mod spacegroup;
pub mod spg_database;
pub mod spin;
pub mod symmetry;
use crate::cell::{cel_any_overlap_with_same_type, cel_layer_any_overlap_with_same_type, AperiodicAxis, Cell, TensorRank};
use crate::delaunay::del_delaunay_reduce;
use crate::determination::det_determine_all;
use crate::mathfunc::{mat_inverse_matrix_d3, mat_multiply_matrix_d3, Mat3, Mat3I, Vec3};
use crate::niggli::niggli_reduce;
use crate::pointgroup::{ptg_get_pointgroup, ptg_get_transformation_matrix};
use crate::primitive::{Primitive, prm_get_primitive_symmetry};
use crate::spacegroup::{
Spacegroup, spa_search_spacegroup_with_symmetry, spa_transform_from_primitive,
spa_transform_to_primitive,
};
use crate::spg_database::{Centering, spgdb_get_spacegroup_operations, spgdb_get_spacegroup_type};
use crate::symmetry::Symmetry;
pub const SPGLIB_MAJOR_VERSION: i32 = 2;
pub const SPGLIB_MINOR_VERSION: i32 = 5;
pub const SPGLIB_MICRO_VERSION: i32 = 4;
pub const SPGLIB_VERSION: &str = "2.5.4";
pub const SPGLIB_VERSION_FULL: &str = "2.5.4";
pub const SPGLIB_COMMIT: &str = "unknown";
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(i32)]
pub enum SpglibError {
#[error("no error")]
Success = 0,
#[error("spacegroup search failed")]
SpacegroupSearchFailed = 1,
#[error("cell standardization failed")]
CellStandardizationFailed = 2,
#[error("symmetry operation search failed")]
SymmetryOperationSearchFailed = 3,
#[error("too close distance between atoms")]
AtomsTooClose = 4,
#[error("pointgroup not found")]
PointgroupNotFound = 5,
#[error("Niggli reduction failed")]
NiggliFailed = 6,
#[error("Delaunay reduction failed")]
DelaunayFailed = 7,
#[error("array size shortage")]
ArraySizeShortage = 8,
#[error("invalid input format")]
InvalidInput = 9,
#[error("math operation failed")]
MathFailed = 10,
}
#[derive(Debug, Clone)]
pub struct SpglibDataset {
pub spacegroup_number: usize,
pub hall_number: usize,
pub international_symbol: String,
pub hall_symbol: String,
pub choice: String,
pub transformation_matrix: Mat3,
pub origin_shift: Vec3,
pub n_operations: usize,
pub rotations: Vec<Mat3I>,
pub translations: Vec<Vec3>,
pub n_atoms: usize,
pub wyckoffs: Vec<i32>,
pub site_symmetry_symbols: Vec<String>,
pub equivalent_atoms: Vec<i32>,
pub crystallographic_orbits: Vec<i32>,
pub mapping_to_primitive: Vec<i32>,
pub n_std_atoms: usize,
pub std_lattice: Mat3,
pub std_positions: Vec<Vec3>,
pub std_types: Vec<i32>,
pub std_rotation_matrix: Mat3,
pub std_mapping_to_primitive: Vec<i32>,
pub primitive_lattice: Mat3,
pub pointgroup_symbol: String,
}
#[derive(Debug, Clone)]
pub struct SpglibSpacegroupType {
pub number: usize,
pub hall_number: usize,
pub schoenflies: String,
pub hall_symbol: String,
pub choice: String,
pub international: String,
pub international_full: String,
pub international_short: String,
pub pointgroup_international: String,
pub pointgroup_schoenflies: String,
pub arithmetic_crystal_class_number: i32,
pub arithmetic_crystal_class_symbol: String,
}
#[derive(Debug, Clone)]
pub struct SpglibMagneticDataset {
pub uni_number: usize,
pub msg_type: MagneticType,
pub hall_number: usize,
pub tensor_rank: crate::cell::TensorRank,
pub n_operations: usize,
pub rotations: Vec<Mat3I>,
pub translations: Vec<Vec3>,
pub time_reversals: Vec<bool>,
pub n_atoms: usize,
pub equivalent_atoms: Vec<i32>,
pub n_std_atoms: usize,
pub std_types: Vec<i32>,
pub std_positions: Vec<Vec3>,
pub std_tensors: Vec<f64>,
pub origin_shift: Vec3,
pub transformation_matrix: Mat3,
pub std_lattice: Mat3,
pub primitive_lattice: Mat3,
pub std_rotation_matrix: Mat3,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MagneticType {
NonMagnetic = 0,
Ordinary = 1,
Grey = 2,
BlackWhite = 3,
AntiTranslation = 4,
}
#[derive(Debug, Clone)]
pub struct SpglibMagneticSpacegroupType {
pub uni_number: usize,
pub litvin_number: usize,
pub bns_number: String,
pub og_number: String,
pub number: usize,
pub type_: MagneticType,
}
pub fn spg_get_version() -> &'static str {
SPGLIB_VERSION
}
pub fn spg_get_version_full() -> &'static str {
SPGLIB_VERSION_FULL
}
pub fn spg_get_commit() -> &'static str {
SPGLIB_COMMIT
}
pub fn spg_get_major_version() -> i32 {
SPGLIB_MAJOR_VERSION
}
pub fn spg_get_minor_version() -> i32 {
SPGLIB_MINOR_VERSION
}
pub fn spg_get_micro_version() -> i32 {
SPGLIB_MICRO_VERSION
}
pub fn spg_get_error_message(error: SpglibError) -> &'static str {
match error {
SpglibError::Success => "no error",
SpglibError::SpacegroupSearchFailed => "spacegroup search failed",
SpglibError::CellStandardizationFailed => "cell standardization failed",
SpglibError::SymmetryOperationSearchFailed => "symmetry operation search failed",
SpglibError::AtomsTooClose => "too close distance between atoms",
SpglibError::PointgroupNotFound => "pointgroup not found",
SpglibError::NiggliFailed => "Niggli reduction failed",
SpglibError::DelaunayFailed => "Delaunay reduction failed",
SpglibError::ArraySizeShortage => "array size shortage",
SpglibError::InvalidInput => "invalid input format",
SpglibError::MathFailed => "math operation failed",
}
}
pub fn spg_get_dataset(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<SpglibDataset, SpglibError> {
get_dataset(lattice, position, types, None, 0, symprec, -1.0)
}
pub fn spgat_get_dataset(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<SpglibDataset, SpglibError> {
get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)
}
pub fn spg_get_dataset_with_hall_number(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
hall_number: i32,
symprec: f64,
) -> Result<SpglibDataset, SpglibError> {
get_dataset(lattice, position, types, None, hall_number, symprec, -1.0)
}
pub fn spgat_get_dataset_with_hall_number(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
hall_number: i32,
symprec: f64,
angle_tolerance: f64,
) -> Result<SpglibDataset, SpglibError> {
get_dataset(lattice, position, types, None, hall_number, symprec, angle_tolerance)
}
pub fn spg_get_layer_dataset(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
aperiodic_axis: i32,
symprec: f64,
) -> Result<SpglibDataset, SpglibError> {
use crate::cell::AperiodicAxis;
let ap = match aperiodic_axis {
0 => Some(AperiodicAxis::X),
1 => Some(AperiodicAxis::Y),
2 => Some(AperiodicAxis::Z),
_ => return Err(SpglibError::SpacegroupSearchFailed),
};
get_dataset(lattice, position, types, ap, 0, symprec, -1.0)
}
pub fn spg_get_symmetry(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<Symmetry, SpglibError> {
get_symmetry_from_dataset(lattice, position, types, symprec, -1.0)
}
pub fn spgat_get_symmetry(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<Symmetry, SpglibError> {
get_symmetry_from_dataset(lattice, position, types, symprec, angle_tolerance)
}
pub fn spg_get_symmetry_from_database(hall_number: usize) -> Result<Symmetry, SpglibError> {
spgdb_get_spacegroup_operations(hall_number)
.ok_or(SpglibError::SpacegroupSearchFailed)
}
pub fn spg_get_hall_number_from_symmetry(
rotations: &[Mat3I],
translations: &[Vec3],
symprec: f64,
) -> Result<usize, SpglibError> {
let lattice: Mat3 = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let hall_number = get_hall_number_from_symmetry(
rotations, translations, &lattice, false, symprec,
)?;
if hall_number > 0 {
Ok(hall_number)
} else {
Err(SpglibError::SpacegroupSearchFailed)
}
}
pub fn spg_get_spacegroup_type_from_symmetry(
rotations: &[Mat3I],
translations: &[Vec3],
lattice: &Mat3,
symprec: f64,
) -> Result<SpglibSpacegroupType, SpglibError> {
let hall_number = get_hall_number_from_symmetry(
rotations, translations, lattice, true, symprec,
)?;
if hall_number > 0 {
get_spacegroup_type(hall_number)
} else {
Err(SpglibError::SpacegroupSearchFailed)
}
}
pub fn spg_standardize_cell(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
to_primitive: bool,
no_idealize: bool,
symprec: f64,
) -> Result<Cell, SpglibError> {
spgat_standardize_cell(lattice, position, types, to_primitive, no_idealize, symprec, -1.0)
}
pub fn spgat_standardize_cell(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
to_primitive: bool,
no_idealize: bool,
symprec: f64,
angle_tolerance: f64,
) -> Result<Cell, SpglibError> {
if to_primitive {
if no_idealize {
get_standardized_cell(lattice, position, types, true, symprec, angle_tolerance)
} else {
standardize_primitive(lattice, position, types, symprec, angle_tolerance)
}
} else {
if no_idealize {
get_standardized_cell(lattice, position, types, false, symprec, angle_tolerance)
} else {
standardize_cell(lattice, position, types, symprec, angle_tolerance)
}
}
}
pub fn spg_find_primitive(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<Cell, SpglibError> {
standardize_primitive(lattice, position, types, symprec, -1.0)
}
pub fn spgat_find_primitive(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<Cell, SpglibError> {
standardize_primitive(lattice, position, types, symprec, angle_tolerance)
}
pub fn spg_refine_cell(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<Cell, SpglibError> {
standardize_cell(lattice, position, types, symprec, -1.0)
}
pub fn spgat_refine_cell(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<Cell, SpglibError> {
standardize_cell(lattice, position, types, symprec, angle_tolerance)
}
pub fn spg_get_international(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<(usize, String), SpglibError> {
get_international(lattice, position, types, symprec, -1.0)
}
pub fn spgat_get_international(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<(usize, String), SpglibError> {
get_international(lattice, position, types, symprec, angle_tolerance)
}
pub fn spg_get_schoenflies(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<(usize, String), SpglibError> {
get_schoenflies(lattice, position, types, symprec, -1.0)
}
pub fn spgat_get_schoenflies(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<(usize, String), SpglibError> {
get_schoenflies(lattice, position, types, symprec, angle_tolerance)
}
pub fn spg_get_multiplicity(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<usize, SpglibError> {
get_multiplicity(lattice, position, types, symprec, -1.0)
}
pub fn spgat_get_multiplicity(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<usize, SpglibError> {
get_multiplicity(lattice, position, types, symprec, angle_tolerance)
}
pub fn spg_get_spacegroup_type(hall_number: usize) -> Result<SpglibSpacegroupType, SpglibError> {
if hall_number > 0 && hall_number < 531 {
get_spacegroup_type(hall_number)
} else {
Err(SpglibError::SpacegroupSearchFailed)
}
}
pub fn spg_get_pointgroup(
rotations: &[Mat3I],
) -> Result<(String, Mat3I, usize), SpglibError> {
let (transform_mat, pointgroup) = ptg_get_transformation_matrix(rotations, None);
if pointgroup.number == 0 {
return Err(SpglibError::PointgroupNotFound);
}
Ok((pointgroup.symbol.to_string(), transform_mat, pointgroup.number))
}
pub fn spg_get_magnetic_spacegroup_type(
uni_number: usize,
) -> SpglibMagneticSpacegroupType {
let msgtype = crate::msg_database::msgdb_get_magnetic_spacegroup_type(uni_number);
SpglibMagneticSpacegroupType {
uni_number: msgtype.uni_number,
litvin_number: msgtype.litvin_number,
bns_number: msgtype.bns_number.to_string(),
og_number: msgtype.og_number.to_string(),
number: msgtype.number,
type_: msgtype.type_,
}
}
pub fn spg_get_magnetic_spacegroup_type_from_symmetry(
rotations: &[Mat3I],
translations: &[Vec3],
time_reversals: Option<&[bool]>,
lattice: &Mat3,
symprec: f64,
) -> SpglibMagneticSpacegroupType {
let n_ops = rotations.len();
let mut mag_sym = crate::symmetry::MagneticSymmetry::new(n_ops);
for i in 0..n_ops {
mag_sym.rot[i] = rotations[i];
mag_sym.trans[i] = translations[i];
mag_sym.timerev[i] = time_reversals.map_or(false, |tr| tr[i]);
}
match crate::magnetic_spacegroup::msg_identify_magnetic_space_group_type(
lattice, &mag_sym, symprec,
) {
Some(dataset) => spg_get_magnetic_spacegroup_type(dataset.uni_number),
None => SpglibMagneticSpacegroupType {
uni_number: 0,
litvin_number: 0,
bns_number: String::new(),
og_number: String::new(),
number: 0,
type_: MagneticType::NonMagnetic,
},
}
}
pub struct SpglibMagneticSymmetry {
pub spacegroup_number: usize,
pub international_short: String,
pub hall_number: usize,
pub hall_symbol: String,
pub uni_number: usize,
pub magnetic_type: MagneticType,
pub bns_number: String,
pub og_number: String,
pub num_operations: usize,
pub rotations: Vec<Mat3I>,
pub translations: Vec<Vec3>,
pub time_reversals: Vec<bool>,
}
pub fn spg_get_magnetic_dataset(
lattice: &Mat3,
positions: &[Vec3],
types: &[i32],
magnetic_moments: Option<&[[f64; 3]]>,
symprec: f64,
) -> Option<SpglibMagneticSymmetry> {
let n_atoms = positions.len();
let has_mag = magnetic_moments.is_some() && magnetic_moments.unwrap().len() == n_atoms;
let tensor_rank = if has_mag {
crate::cell::TensorRank::NonCollinear
} else {
crate::cell::TensorRank::NoSpin
};
let mut cell = crate::cell::Cell::new(n_atoms, tensor_rank);
cell.set_cell(lattice, positions, types);
if has_mag {
let moments = magnetic_moments.unwrap();
for i in 0..n_atoms {
cell.tensors[i * 3] = moments[i][0];
cell.tensors[i * 3 + 1] = moments[i][1];
cell.tensors[i * 3 + 2] = moments[i][2];
}
}
cell.aperiodic_axis = None;
let primitive = crate::primitive::prm_get_primitive(&cell, symprec, -1.0)?;
let spg = crate::spacegroup::spa_search_spacegroup(&primitive, 0, symprec, -1.0)?;
let hall_number = spg.hall_number;
let nonspin_sym = crate::symmetry::sym_get_operation(&cell, symprec, -1.0)?;
if !has_mag {
let rot = (0..nonspin_sym.size).map(|i| nonspin_sym.rot[i]).collect();
let trans = (0..nonspin_sym.size).map(|i| nonspin_sym.trans[i]).collect();
let timerev = vec![false; nonspin_sym.size];
let spg_type = crate::spg_database::spgdb_get_spacegroup_type(hall_number);
return Some(SpglibMagneticSymmetry {
spacegroup_number: spg.number,
international_short: spg.international_short.trim().to_string(),
hall_number,
hall_symbol: spg_type.hall_symbol.trim().to_string(),
uni_number: 0,
magnetic_type: MagneticType::NonMagnetic,
bns_number: String::new(),
og_number: String::new(),
num_operations: nonspin_sym.size,
rotations: rot,
translations: trans,
time_reversals: timerev,
});
}
let mut equiv_atoms = Vec::new();
let mut permutations = Vec::new();
let mut prim_lat = [[0.0; 3]; 3];
let mag_sym = crate::spin::spn_get_operations_with_site_tensors(
&mut equiv_atoms,
&mut permutations,
&mut prim_lat,
&nonspin_sym,
&cell,
true, true, symprec,
-1.0, -1.0, )?;
let (final_mag_sym, _used_fallback) = if mag_sym.size == 0 {
let crystal_ops: Vec<(Mat3I, Vec3)> = (0..nonspin_sym.size)
.map(|i| (nonspin_sym.rot[i], nonspin_sym.trans[i]))
.collect();
let moments = magnetic_moments.unwrap();
let tr = manual_compute_timerev(positions, moments, &crystal_ops, symprec);
let valid: Vec<usize> = tr.iter().cloned().enumerate().filter(|(_, t)| *t != -1).map(|(i, _)| i).collect();
let n = valid.len();
if n == 0 {
return None;
}
let mut fallback = crate::symmetry::MagneticSymmetry::new(n);
for (j, &idx) in valid.iter().enumerate() {
fallback.rot[j] = nonspin_sym.rot[idx];
fallback.trans[j] = nonspin_sym.trans[idx];
fallback.timerev[j] = tr[idx] != 0;
}
(fallback, true)
} else {
(mag_sym, false)
};
let ds = crate::magnetic_spacegroup::msg_identify_with_parent_hall(
lattice,
&final_mag_sym,
Some(hall_number),
symprec,
);
let (uni_number, magnetic_type, bns_number, og_number) = match ds {
Some(ds) => {
let mt = crate::msg_database::msgdb_get_magnetic_spacegroup_type(ds.uni_number);
(ds.uni_number, mt.type_, mt.bns_number.to_string(), mt.og_number.to_string())
}
None => {
let sym_all = crate::magnetic_spacegroup::extract_symmetry(
&final_mag_sym, true, symprec,
);
let sym_ord = crate::magnetic_spacegroup::extract_symmetry(
&final_mag_sym, false, symprec,
);
let fallback_type = match (sym_all, sym_ord) {
(Some(fsg), Some(xsg)) => {
let n_fsg = fsg.size;
let n_xsg = xsg.size;
let n_msg = final_mag_sym.size;
if n_fsg == n_xsg {
if n_msg == n_fsg { MagneticType::Ordinary } else if n_msg == 2 * n_fsg { MagneticType::Grey } else { MagneticType::NonMagnetic }
} else if n_fsg == 2 * n_xsg { MagneticType::BlackWhite } else { MagneticType::NonMagnetic }
}
_ => MagneticType::NonMagnetic,
};
(0, fallback_type, String::new(), String::new())
}
};
let spg_type = crate::spg_database::spgdb_get_spacegroup_type(hall_number);
let rot_out = (0..final_mag_sym.size).map(|i| final_mag_sym.rot[i]).collect();
let trans_out = (0..final_mag_sym.size).map(|i| final_mag_sym.trans[i]).collect();
let tr_out = (0..final_mag_sym.size).map(|i| final_mag_sym.timerev[i]).collect();
Some(SpglibMagneticSymmetry {
spacegroup_number: spg.number,
international_short: spg.international_short.trim().to_string(),
hall_number,
hall_symbol: spg_type.hall_symbol.trim().to_string(),
uni_number,
magnetic_type,
bns_number,
og_number,
num_operations: final_mag_sym.size,
rotations: rot_out,
translations: trans_out,
time_reversals: tr_out,
})
}
fn manual_compute_timerev(
positions: &[Vec3],
moments: &[[f64; 3]],
ops: &[(Mat3I, Vec3)],
symprec: f64,
) -> Vec<i32> {
use crate::mathfunc::mat_get_determinant_i3;
let snap = |x: f64| (x * 2.0).round() / 2.0;
let snapped_pos: Vec<_> = positions
.iter()
.map(|p| [snap(p[0]), snap(p[1]), snap(p[2])])
.collect();
ops.iter()
.map(|(rot, trans)| {
let det = mat_get_determinant_i3(rot);
let mut global_tr: Option<i32> = None;
for i in 0..positions.len() {
let p_new = [
snap((rot[0][0] as f64 * positions[i][0]
+ rot[0][1] as f64 * positions[i][1]
+ rot[0][2] as f64 * positions[i][2]
+ trans[0])
.rem_euclid(1.0)),
snap((rot[1][0] as f64 * positions[i][0]
+ rot[1][1] as f64 * positions[i][1]
+ rot[1][2] as f64 * positions[i][2]
+ trans[1])
.rem_euclid(1.0)),
snap((rot[2][0] as f64 * positions[i][0]
+ rot[2][1] as f64 * positions[i][1]
+ rot[2][2] as f64 * positions[i][2]
+ trans[2])
.rem_euclid(1.0)),
];
let j = snapped_pos.iter().position(|sp| {
(sp[0] - p_new[0]).abs() < 0.01
&& (sp[1] - p_new[1]).abs() < 0.01
&& (sp[2] - p_new[2]).abs() < 0.01
});
let j = match j {
Some(j) => j,
None => return -1,
};
let m_new = [
(det as f64)
* (rot[0][0] as f64 * moments[i][0]
+ rot[0][1] as f64 * moments[i][1]
+ rot[0][2] as f64 * moments[i][2]),
(det as f64)
* (rot[1][0] as f64 * moments[i][0]
+ rot[1][1] as f64 * moments[i][1]
+ rot[1][2] as f64 * moments[i][2]),
(det as f64)
* (rot[2][0] as f64 * moments[i][0]
+ rot[2][1] as f64 * moments[i][1]
+ rot[2][2] as f64 * moments[i][2]),
];
let preserved = (m_new[0] - moments[j][0]).abs() < symprec
&& (m_new[1] - moments[j][1]).abs() < symprec
&& (m_new[2] - moments[j][2]).abs() < symprec;
let reversed = (m_new[0] + moments[j][0]).abs() < symprec
&& (m_new[1] + moments[j][1]).abs() < symprec
&& (m_new[2] + moments[j][2]).abs() < symprec;
let this_tr = if preserved {
0
} else if reversed {
1
} else {
return -1;
};
match global_tr {
Some(tr) if tr != this_tr => return -1,
_ => global_tr = Some(this_tr),
}
}
global_tr.unwrap_or(-1)
})
.collect()
}
pub fn spg_format_magnetic_symmetry(result: &SpglibMagneticSymmetry) -> String {
use std::fmt::Write;
let mut s = String::new();
let _ = writeln!(s, "--- Space group ---");
let _ = writeln!(s, " Number: {}", result.spacegroup_number);
let _ = writeln!(s, " International: {}", result.international_short);
let _ = writeln!(s, " Hall number: {}", result.hall_number);
let _ = writeln!(s, " Hall symbol: {}", result.hall_symbol);
if result.magnetic_type != MagneticType::NonMagnetic {
let type_str = match result.magnetic_type {
MagneticType::Ordinary => "Type-1 (ordinary, no time reversal)",
MagneticType::Grey => "Type-2 (grey, with pure 1')",
MagneticType::BlackWhite => "Type-3 (black-white, anti-rotation)",
MagneticType::AntiTranslation => "Type-4 (black-white, anti-translation)",
MagneticType::NonMagnetic => "none",
};
let _ = writeln!(s, "--- Magnetic space group ---");
let _ = writeln!(s, " UNI number: {}", result.uni_number);
let _ = writeln!(s, " Magnetic type: {} ({})", result.magnetic_type as i32, type_str);
let _ = writeln!(s, " BNS symbol: {}", result.bns_number);
let _ = writeln!(s, " OG number: {}", result.og_number);
} else {
let _ = writeln!(s, " (non-magnetic)");
}
let _ = writeln!(s, "--- Symmetry operations ({}) ---", result.num_operations);
for i in 0..result.num_operations {
let r = &result.rotations[i];
let t = &result.translations[i];
let tr = result.time_reversals[i];
let timerev_str = if tr { "'" } else { " " };
let _ = writeln!(
s,
" {}. rot=[{:2},{:2},{:2};{:2},{:2},{:2};{:2},{:2},{:2}] trans=[{:.3},{:.3},{:.3}]{}",
i + 1,
r[0][0], r[0][1], r[0][2],
r[1][0], r[1][1], r[1][2],
r[2][0], r[2][1], r[2][2],
t[0], t[1], t[2],
timerev_str,
);
}
s
}
pub fn spg_read_structure(data: &str) -> Option<(Mat3, Vec<Vec3>, Vec<i32>, Option<Vec<[f64; 3]>>)> {
let lines: Vec<&str> = data.lines().collect();
if lines.len() < 6 {
return None;
}
let scale: f64 = lines.get(1)?.trim().parse().ok()?;
let mut rows = [[0.0; 3]; 3];
for i in 0..3 {
let parts: Vec<f64> = lines[i + 2].split_whitespace().filter_map(|x| x.parse().ok()).collect();
if parts.len() < 3 {
return None;
}
rows[i] = [parts[0], parts[1], parts[2]];
}
let mut lattice = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
lattice[i][j] = rows[j][i];
}
}
if scale != 1.0 {
for i in 0..3 {
for j in 0..3 {
lattice[i][j] *= scale;
}
}
}
let type_line = lines.get(5)?;
let counts: Vec<i32> = lines.get(6)?.split_whitespace().filter_map(|x| x.parse().ok()).collect();
if counts.is_empty() {
return None;
}
let n_atoms: usize = counts.iter().map(|&c| c as usize).sum();
let type_names: Vec<&str> = type_line.split_whitespace().collect();
let mut types = Vec::with_capacity(n_atoms);
let mut atom_idx = 0;
for (ti, &cnt) in counts.iter().enumerate() {
let type_num = if type_names.len() > ti && type_names[ti].parse::<i32>().is_err() {
element_to_number(type_names[ti])
} else {
(ti + 1) as i32
};
for _ in 0..cnt {
types.push(type_num);
atom_idx += 1;
}
}
let mode_line = lines.get(7)?;
let is_cartesian = mode_line.trim().to_uppercase().starts_with('C') || mode_line.trim().to_uppercase().starts_with('K');
let mut positions = Vec::with_capacity(n_atoms);
let mut moments = Vec::with_capacity(n_atoms);
let mut has_moments = false;
for i in 0..n_atoms {
let line = lines.get(8 + i)?;
let parts: Vec<f64> = line.split_whitespace().filter_map(|x| x.parse().ok()).collect();
if parts.len() < 3 {
return None;
}
positions.push([parts[0], parts[1], parts[2]]);
if parts.len() >= 6 {
moments.push([parts[3], parts[4], parts[5]]);
has_moments = true;
} else {
moments.push([0.0; 3]);
}
}
if is_cartesian {
let inv_lat = crate::mathfunc::mat_inverse_matrix_d3(&lattice, 1e-10).ok()?;
for i in 0..n_atoms {
let frac = crate::mathfunc::mat_multiply_matrix_vector_d3(&inv_lat, &positions[i]);
positions[i] = frac;
}
}
let mag_opt = if has_moments { Some(moments) } else { None };
Some((lattice, positions, types, mag_opt))
}
fn element_to_number(symbol: &str) -> i32 {
match symbol.trim() {
"H" => 1, "He" => 2, "Li" => 3, "Be" => 4, "B" => 5,
"C" => 6, "N" => 7, "O" => 8, "F" => 9, "Ne" => 10,
"Na" => 11, "Mg" => 12, "Al" => 13, "Si" => 14, "P" => 15,
"S" => 16, "Cl" => 17, "Ar" => 18, "K" => 19, "Ca" => 20,
"Fe" => 26, "Co" => 27, "Ni" => 28, "Cu" => 29, "Zn" => 30,
_ => 1,
}
}
pub fn spg_delaunay_reduce(lattice: &Mat3, symprec: f64) -> Result<Mat3, SpglibError> {
del_delaunay_reduce(lattice, symprec).ok_or(SpglibError::DelaunayFailed)
}
pub fn spg_niggli_reduce(lattice: &Mat3, symprec: f64) -> Result<Mat3, SpglibError> {
let mut reduced = *lattice;
if niggli_reduce(&mut reduced, symprec, None) {
Ok(reduced)
} else {
Err(SpglibError::NiggliFailed)
}
}
pub fn spg_get_grid_point_from_address(grid_address: &[i32; 3], mesh: &[i32; 3]) -> usize {
let mut address_double = [0i32; 3];
let is_shift = [0i32; 3];
crate::kgrid::kgd_get_grid_address_double_mesh(&mut address_double, grid_address, mesh, &is_shift);
crate::kgrid::kgd_get_dense_grid_point_double_mesh(&address_double, mesh)
}
pub fn spg_get_ir_reciprocal_mesh(
grid_address: &mut [[i32; 3]],
ir_mapping_table: &mut [usize],
mesh: &[i32; 3],
is_shift: &[i32; 3],
is_time_reversal: bool,
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<usize, SpglibError> {
get_ir_reciprocal_mesh(
grid_address, ir_mapping_table, mesh, is_shift,
is_time_reversal, lattice, position, types, symprec, -1.0,
)
}
pub fn spg_get_dense_ir_reciprocal_mesh(
grid_address: &mut [[i32; 3]],
ir_mapping_table: &mut [usize],
mesh: &[i32; 3],
is_shift: &[i32; 3],
is_time_reversal: bool,
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
) -> Result<usize, SpglibError> {
get_dense_ir_reciprocal_mesh(
grid_address, ir_mapping_table, mesh, is_shift,
is_time_reversal, lattice, position, types, symprec, -1.0,
)
}
pub fn spg_get_stabilized_reciprocal_mesh(
grid_address: &mut [[i32; 3]],
ir_mapping_table: &mut [usize],
mesh: &[i32; 3],
is_shift: &[i32; 3],
is_time_reversal: bool,
rotations: &[Mat3I],
qpoints: &[[f64; 3]],
) -> usize {
use crate::mathfunc::MatINT;
let mut rot_real = MatINT::new(rotations.len());
for (i, r) in rotations.iter().enumerate() {
rot_real.mat[i] = *r;
}
crate::kpoint::kpt_get_stabilized_reciprocal_mesh(
grid_address, ir_mapping_table, mesh, is_shift,
if is_time_reversal { 1 } else { 0 },
&rot_real, qpoints,
)
}
pub fn spg_get_dense_grid_points_by_rotations(
rot_grid_points: &mut [usize],
address_orig: &[i32; 3],
rot_reciprocal: &[Mat3I],
mesh: &[i32; 3],
is_shift: &[i32; 3],
) {
use crate::mathfunc::MatINT;
let mut rot = MatINT::new(rot_reciprocal.len());
for (i, r) in rot_reciprocal.iter().enumerate() {
rot.mat[i] = *r;
}
crate::kpoint::kpt_get_dense_grid_points_by_rotations(
rot_grid_points, address_orig, &rot, mesh, is_shift,
)
}
pub fn spg_get_dense_BZ_grid_points_by_rotations(
rot_grid_points: &mut [usize],
address_orig: &[i32; 3],
rot_reciprocal: &[Mat3I],
mesh: &[i32; 3],
is_shift: &[i32; 3],
bz_map: &[usize],
) {
use crate::mathfunc::MatINT;
let mut rot = MatINT::new(rot_reciprocal.len());
for (i, r) in rot_reciprocal.iter().enumerate() {
rot.mat[i] = *r;
}
crate::kpoint::kpt_get_dense_BZ_grid_points_by_rotations(
rot_grid_points, address_orig, &rot, mesh, is_shift, bz_map,
)
}
pub fn spg_relocate_BZ_grid_address(
bz_grid_address: &mut [[i32; 3]],
bz_map: &mut [usize],
grid_address: &[[i32; 3]],
mesh: &[i32; 3],
rec_lattice: &Mat3,
is_shift: &[i32; 3],
) -> usize {
crate::kpoint::kpt_relocate_bz_grid_address(
bz_grid_address, bz_map, grid_address, mesh, rec_lattice, is_shift,
)
}
fn get_dataset(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
aperiodic_axis: Option<AperiodicAxis>,
hall_number: i32,
symprec: f64,
angle_tolerance: f64,
) -> Result<SpglibDataset, SpglibError> {
if hall_number > 530 {
return Err(SpglibError::SpacegroupSearchFailed);
}
let num_atom = position.len();
let mut cell = Cell::new(num_atom, TensorRank::NoSpin);
if aperiodic_axis.is_none() {
cel_set_cell(&mut cell, lattice, position, types);
if cel_any_overlap_with_same_type(&cell, symprec) {
return Err(SpglibError::AtomsTooClose);
}
} else {
cel_set_layer_cell(&mut cell, lattice, position, types, aperiodic_axis);
if cel_layer_any_overlap_with_same_type(&cell, aperiodic_axis.unwrap(), symprec) {
return Err(SpglibError::AtomsTooClose);
}
}
let container = det_determine_all(&cell, hall_number, symprec, angle_tolerance)
.ok_or(SpglibError::SpacegroupSearchFailed)?;
let spacegroup = container.spacegroup.as_ref()
.ok_or(SpglibError::SpacegroupSearchFailed)?;
let primitive = container.primitive.as_ref()
.ok_or(SpglibError::SpacegroupSearchFailed)?;
let exstr = container.exact_structure.as_ref()
.ok_or(SpglibError::SpacegroupSearchFailed)?;
let dataset = set_dataset(&cell, primitive, spacegroup, exstr)
.ok_or(SpglibError::SpacegroupSearchFailed)?;
Ok(dataset)
}
fn cel_set_cell(cell: &mut Cell, lattice: &Mat3, position: &[Vec3], types: &[i32]) {
cell.lattice = *lattice;
for i in 0..cell.size {
cell.types[i] = types[i];
cell.position[i] = position[i];
}
}
fn cel_set_layer_cell(
cell: &mut Cell,
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
aperiodic_axis: Option<AperiodicAxis>,
) {
cell.lattice = *lattice;
cell.aperiodic_axis = aperiodic_axis;
for i in 0..cell.size {
cell.types[i] = types[i];
cell.position[i] = position[i];
}
}
fn set_dataset(
cell: &Cell,
primitive: &Primitive,
spacegroup: &Spacegroup,
exstr: &crate::refinement::ExactStructure,
) -> Option<SpglibDataset> {
let n_atoms = cell.size;
let n_operations = exstr.symmetry.size;
let mut dataset = SpglibDataset {
spacegroup_number: spacegroup.number,
hall_number: spacegroup.hall_number,
international_symbol: spacegroup.international_short.clone(),
hall_symbol: spacegroup.hall_symbol.clone(),
choice: spacegroup.choice.clone(),
transformation_matrix: [[0.0; 3]; 3],
origin_shift: spacegroup.origin_shift,
n_operations,
rotations: vec![[[0; 3]; 3]; n_operations],
translations: vec![[0.0; 3]; n_operations],
n_atoms,
wyckoffs: vec![0i32; n_atoms],
site_symmetry_symbols: vec![String::new(); n_atoms],
equivalent_atoms: vec![0i32; n_atoms],
crystallographic_orbits: vec![0i32; n_atoms],
mapping_to_primitive: vec![0i32; n_atoms],
n_std_atoms: exstr.bravais.size,
std_lattice: exstr.bravais.lattice,
std_positions: exstr.bravais.position.clone(),
std_types: exstr.bravais.types.clone(),
std_rotation_matrix: [[0.0; 3]; 3],
std_mapping_to_primitive: vec![0i32; exstr.bravais.size],
primitive_lattice: [[0.0; 3]; 3],
pointgroup_symbol: String::new(),
};
let inv_lat = mat_inverse_matrix_d3(&spacegroup.bravais_lattice, 0.0).ok()?;
dataset.transformation_matrix = mat_multiply_matrix_d3(&inv_lat, &cell.lattice);
for i in 0..n_operations {
dataset.rotations[i] = exstr.symmetry.rot[i];
dataset.translations[i] = exstr.symmetry.trans[i];
}
for i in 0..n_atoms {
dataset.wyckoffs[i] = exstr.wyckoffs[i];
dataset.site_symmetry_symbols[i] = exstr.site_symmetry_symbols[i].clone();
dataset.equivalent_atoms[i] = exstr.equivalent_atoms[i];
dataset.crystallographic_orbits[i] = exstr.crystallographic_orbits[i];
}
if let Some(prim_cell) = &primitive.cell {
dataset.primitive_lattice = prim_cell.lattice;
}
for i in 0..n_atoms {
dataset.mapping_to_primitive[i] = primitive.mapping_table[i];
}
for i in 0..dataset.n_std_atoms {
dataset.std_mapping_to_primitive[i] = exstr.std_mapping_to_primitive[i];
}
dataset.std_rotation_matrix = exstr.rotation;
let pointgroup = ptg_get_pointgroup(spacegroup.pointgroup_number);
dataset.pointgroup_symbol = pointgroup.symbol.to_string();
Some(dataset)
}
fn get_symmetry_from_dataset(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<Symmetry, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
let n_ops = dataset.n_operations;
let mut sym = Symmetry::new(n_ops);
for i in 0..n_ops {
sym.rot[i] = dataset.rotations[i];
sym.trans[i] = dataset.translations[i];
}
Ok(sym)
}
fn get_multiplicity(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<usize, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
Ok(dataset.n_operations)
}
fn standardize_primitive(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<Cell, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
let centering = get_centering(dataset.hall_number)
.ok_or(SpglibError::CellStandardizationFailed)?;
let mut bravais = Cell::new(dataset.n_std_atoms, TensorRank::NoSpin);
bravais.lattice = dataset.std_lattice;
for i in 0..dataset.n_std_atoms {
bravais.types[i] = dataset.std_types[i];
bravais.position[i] = dataset.std_positions[i];
}
let mut mapping_table: Vec<usize> = vec![0; bravais.size];
let identity: Mat3 = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
let primitive = spa_transform_to_primitive(&mut mapping_table, &bravais, &identity, centering, symprec)
.ok_or(SpglibError::CellStandardizationFailed)?;
for i in 0..primitive.size {
if mapping_table[i] != i {
debug::warning_print(format_args!(
"spglib: spa_transform_to_primitive failed ({} != {})\n",
mapping_table[i], i
));
return Err(SpglibError::CellStandardizationFailed);
}
}
Ok(primitive)
}
fn standardize_cell(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<Cell, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
let n_std = dataset.n_std_atoms;
let mut cell = Cell::new(n_std, TensorRank::NoSpin);
cell.lattice = dataset.std_lattice;
for i in 0..n_std {
cell.types[i] = dataset.std_types[i];
cell.position[i] = dataset.std_positions[i];
}
Ok(cell)
}
fn get_standardized_cell(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
to_primitive: bool,
symprec: f64,
angle_tolerance: f64,
) -> Result<Cell, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
let centering = get_centering(dataset.hall_number)
.ok_or(SpglibError::CellStandardizationFailed)?;
let num_atom = position.len();
let mut cell = Cell::new(num_atom, TensorRank::NoSpin);
cell.lattice = *lattice;
for i in 0..num_atom {
cell.types[i] = types[i];
cell.position[i] = position[i];
}
let mut mapping_table: Vec<usize> = vec![0; num_atom];
let primitive = spa_transform_to_primitive(
&mut mapping_table, &cell, &dataset.transformation_matrix, centering, symprec,
).ok_or(SpglibError::CellStandardizationFailed)?;
for i in 0..num_atom {
if mapping_table[i] != dataset.mapping_to_primitive[i] as usize {
debug::warning_print(format_args!(
"spglib: spa_transform_to_primitive failed ({} != {})\n",
mapping_table[i], dataset.mapping_to_primitive[i]
));
return Err(SpglibError::CellStandardizationFailed);
}
}
if to_primitive || matches!(centering, Centering::Primitive) {
return Ok(primitive);
}
let std_cell = spa_transform_from_primitive(&primitive, centering, symprec)
.ok_or(SpglibError::CellStandardizationFailed)?;
Ok(std_cell)
}
fn get_international(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<(usize, String), SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
if dataset.spacegroup_number > 0 {
Ok((dataset.spacegroup_number, dataset.international_symbol))
} else {
Err(SpglibError::SpacegroupSearchFailed)
}
}
fn get_schoenflies(
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<(usize, String), SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
if dataset.spacegroup_number > 0 {
if let Ok(spgtype) = get_spacegroup_type(dataset.hall_number) {
return Ok((dataset.spacegroup_number, spgtype.schoenflies));
}
}
Err(SpglibError::SpacegroupSearchFailed)
}
fn get_centering(hall_number: usize) -> Option<Centering> {
Some(spgdb_get_spacegroup_type(hall_number).centering)
}
fn get_hall_number_from_symmetry(
rotations: &[Mat3I],
translations: &[Vec3],
lattice: &Mat3,
transform_lattice_by_tmat: bool,
symprec: f64,
) -> Result<usize, SpglibError> {
let num_ops = rotations.len();
let mut symmetry = Symmetry::new(num_ops);
for i in 0..num_ops {
symmetry.rot[i] = rotations[i];
symmetry.trans[i] = translations[i];
}
let (t_mat, prim_sym) = prm_get_primitive_symmetry(&symmetry, symprec)
.ok_or(SpglibError::SpacegroupSearchFailed)?;
let prim_lat = if transform_lattice_by_tmat {
let t_mat_inv = mat_inverse_matrix_d3(&t_mat, symprec).ok()
.ok_or(SpglibError::SpacegroupSearchFailed)?;
mat_multiply_matrix_d3(lattice, &t_mat_inv)
} else {
*lattice
};
let spacegroup = spa_search_spacegroup_with_symmetry(&prim_sym, &prim_lat, symprec)
.ok_or(SpglibError::SpacegroupSearchFailed)?;
Ok(spacegroup.hall_number)
}
fn get_spacegroup_type(hall_number: usize) -> Result<SpglibSpacegroupType, SpglibError> {
if hall_number == 0 || hall_number >= 531 {
return Err(SpglibError::SpacegroupSearchFailed);
}
let spgtype = spgdb_get_spacegroup_type(hall_number);
let pointgroup = ptg_get_pointgroup(spgtype.pointgroup_number);
Ok(SpglibSpacegroupType {
number: spgtype.number,
hall_number,
schoenflies: spgtype.schoenflies,
hall_symbol: spgtype.hall_symbol,
choice: spgtype.choice,
international: spgtype.international,
international_full: spgtype.international_full,
international_short: spgtype.international_short,
pointgroup_international: pointgroup.symbol.to_string(),
pointgroup_schoenflies: pointgroup.schoenflies.to_string(),
arithmetic_crystal_class_number: 0, arithmetic_crystal_class_symbol: String::new(),
})
}
fn get_ir_reciprocal_mesh(
grid_address: &mut [[i32; 3]],
ir_mapping_table: &mut [usize],
mesh: &[i32; 3],
is_shift: &[i32; 3],
is_time_reversal: bool,
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<usize, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
use crate::mathfunc::MatINT;
let mut rotations = MatINT::new(dataset.n_operations);
for i in 0..dataset.n_operations {
rotations.mat[i] = dataset.rotations[i];
}
let rot_reciprocal = crate::kpoint::kpt_get_point_group_reciprocal(
&rotations,
if is_time_reversal { 1 } else { 0 },
).ok_or(SpglibError::SpacegroupSearchFailed)?;
let num_ir = crate::kpoint::kpt_get_irreducible_reciprocal_mesh(
grid_address, ir_mapping_table, mesh, is_shift, &rot_reciprocal,
);
Ok(num_ir)
}
fn get_dense_ir_reciprocal_mesh(
grid_address: &mut [[i32; 3]],
ir_mapping_table: &mut [usize],
mesh: &[i32; 3],
is_shift: &[i32; 3],
is_time_reversal: bool,
lattice: &Mat3,
position: &[Vec3],
types: &[i32],
symprec: f64,
angle_tolerance: f64,
) -> Result<usize, SpglibError> {
let dataset = get_dataset(lattice, position, types, None, 0, symprec, angle_tolerance)?;
use crate::mathfunc::MatINT;
let mut rotations = MatINT::new(dataset.n_operations);
for i in 0..dataset.n_operations {
rotations.mat[i] = dataset.rotations[i];
}
let rot_reciprocal = crate::kpoint::kpt_get_point_group_reciprocal(
&rotations,
if is_time_reversal { 1 } else { 0 },
).ok_or(SpglibError::SpacegroupSearchFailed)?;
let num_ir = crate::kpoint::kpt_get_dense_irreducible_reciprocal_mesh(
grid_address, ir_mapping_table, mesh, is_shift, &rot_reciprocal,
);
Ok(num_ir)
}