use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use xtalforge_core::elements::element_by_symbol;
use xtalforge_core::{Structure, StructureSummary};
use crate::Error;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CellSummary {
pub lengths: [f64; 3],
pub angles: [f64; 3],
pub volume: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SiteRow {
pub index: usize,
pub label: String,
pub element: String,
pub frac: [f64; 3],
pub occupancy: f64,
pub charge: Option<f64>,
pub bonds: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ElementCount {
pub element: String,
pub count: f64,
pub mass_fraction: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Inspection {
pub summary: StructureSummaryView,
pub cell: CellSummary,
pub n_p1_atoms: usize,
pub n_bonds: usize,
pub composition: Vec<ElementCount>,
pub sites: Vec<SiteRow>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StructureSummaryView {
pub name: String,
pub formula: String,
pub hall: String,
pub n_sites: usize,
pub volume: f64,
pub density: f64,
pub total_charge: f64,
pub is_p1: bool,
}
impl From<StructureSummary> for StructureSummaryView {
fn from(summary: StructureSummary) -> Self {
Self {
name: summary.name,
formula: summary.formula,
hall: summary.hall,
n_sites: summary.n_sites,
volume: summary.volume,
density: summary.density,
total_charge: summary.total_charge,
is_p1: summary.is_p1,
}
}
}
pub fn inspect_structure(structure: &Structure) -> Result<Inspection, Error> {
let summary = StructureSummaryView::from(structure.summary()?);
let p1 = structure.p1()?;
let graph = structure.bond_graph()?;
let parameters = structure.lattice().parameters();
let mut counts: BTreeMap<String, f64> = BTreeMap::new();
let mut masses: BTreeMap<String, f64> = BTreeMap::new();
let mut total_mass = 0.0;
for atom in &p1.atoms {
let mass = element_by_symbol(&atom.element).map_or(0.0, |info| info.mass);
*counts.entry(atom.element.clone()).or_insert(0.0) += atom.occupancy;
*masses.entry(atom.element.clone()).or_insert(0.0) += mass * atom.occupancy;
total_mass += mass * atom.occupancy;
}
let mut composition: Vec<ElementCount> = counts
.into_iter()
.map(|(element, count)| ElementCount {
mass_fraction: if total_mass > 0.0 {
masses.get(&element).copied().unwrap_or(0.0) / total_mass
} else {
0.0
},
element,
count,
})
.collect();
composition.sort_by(|left, right| {
right
.mass_fraction
.total_cmp(&left.mass_fraction)
.then_with(|| left.element.cmp(&right.element))
});
let mut sites = Vec::with_capacity(structure.sites().len());
for (index, site) in structure.sites().iter().enumerate() {
let first_image = p1
.atoms
.iter()
.position(|atom| atom.site_idx == index)
.map_or(0, |atom| graph.degree(atom));
sites.push(SiteRow {
index,
label: site.label().to_owned(),
element: site.element().to_owned(),
frac: site.frac(),
occupancy: site.occupancy(),
charge: site.charge(),
bonds: first_image,
});
}
Ok(Inspection {
summary,
cell: CellSummary {
lengths: [parameters[0], parameters[1], parameters[2]],
angles: [parameters[3], parameters[4], parameters[5]],
volume: structure.lattice().volume(),
},
n_p1_atoms: p1.atoms.len(),
n_bonds: graph.bonds.len(),
composition,
sites,
})
}