use crate::elements::element_by_symbol;
use crate::{Error, Structure};
pub fn bond_lengths(structure: &Structure) -> Result<Vec<f64>, Error> {
Ok(structure
.bond_graph()?
.bonds
.iter()
.map(|bond| bond.distance)
.collect())
}
pub fn coordination_numbers(structure: &Structure) -> Result<Vec<usize>, Error> {
let graph = structure.bond_graph()?;
Ok((0..graph.n_atoms())
.map(|index| graph.degree(index))
.collect())
}
#[must_use]
pub fn atom_charges(structure: &Structure) -> Vec<Option<f64>> {
structure.sites().iter().map(|site| site.charge()).collect()
}
pub fn atom_masses(structure: &Structure) -> Result<Vec<f64>, Error> {
structure
.sites()
.iter()
.map(|site| Ok(element_by_symbol(site.element())?.mass))
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScalarInfo {
pub name: String,
pub description: String,
pub unit: String,
}
pub type ScalarFn = fn(&Structure) -> Result<Vec<f64>, Error>;
struct ScalarEntry {
info: ScalarInfo,
compute: ScalarFn,
}
#[derive(Default)]
pub struct ScalarRegistry {
entries: std::collections::BTreeMap<String, ScalarEntry>,
}
impl std::fmt::Debug for ScalarRegistry {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ScalarRegistry")
.field("names", &self.names())
.finish()
}
}
fn charges(structure: &Structure) -> Result<Vec<f64>, Error> {
Ok(atom_charges(structure)
.into_iter()
.map(|charge| charge.unwrap_or(f64::NAN))
.collect())
}
fn coordination(structure: &Structure) -> Result<Vec<f64>, Error> {
Ok(coordination_numbers(structure)?
.into_iter()
.map(|number| number as f64)
.collect())
}
fn builtin(name: &str, description: &str, unit: &str, compute: ScalarFn) -> ScalarEntry {
ScalarEntry {
info: ScalarInfo {
name: name.to_owned(),
description: description.to_owned(),
unit: unit.to_owned(),
},
compute,
}
}
impl ScalarRegistry {
#[must_use]
pub fn new() -> Self {
let mut registry = Self::default();
for entry in [
builtin(
"bond_lengths",
"Perceived bond lengths",
"Angstrom",
bond_lengths,
),
builtin(
"coordination_numbers",
"Perceived coordination number per P1 atom",
"",
coordination,
),
builtin(
"atom_charges",
"Stored charge per asymmetric site; NaN when absent",
"e",
charges,
),
builtin(
"atom_masses",
"Standard atomic mass per asymmetric site",
"u",
atom_masses,
),
] {
registry.entries.insert(entry.info.name.clone(), entry);
}
registry
}
#[must_use]
pub fn empty() -> Self {
Self::default()
}
pub fn register(&mut self, info: ScalarInfo, compute: ScalarFn) -> Result<(), Error> {
if info.name.trim().is_empty() {
return Err(Error::InvalidInput(
"a scalar needs a non-empty name".to_owned(),
));
}
if self.entries.contains_key(&info.name) {
return Err(Error::InvalidInput(format!(
"scalar {} is already registered",
info.name
)));
}
self.entries
.insert(info.name.clone(), ScalarEntry { info, compute });
Ok(())
}
pub fn compute(&self, name: &str, structure: &Structure) -> Result<Vec<f64>, Error> {
let entry = self
.entries
.get(name)
.ok_or_else(|| Error::InvalidInput(format!("unknown scalar {name}")))?;
(entry.compute)(structure)
}
#[must_use]
pub fn info(&self, name: &str) -> Option<&ScalarInfo> {
self.entries.get(name).map(|entry| &entry.info)
}
#[must_use]
pub fn names(&self) -> Vec<&str> {
self.entries.keys().map(String::as_str).collect()
}
}