use crate::coords::Aabb;
use crate::element::Element;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
#[repr(transparent)]
pub struct ElementMask(u128);
impl ElementMask {
pub const EMPTY: Self = Self(0);
pub const fn insert(&mut self, element: Element) {
self.0 |= 1u128 << element.atomic_number();
}
#[must_use]
pub const fn contains(self, element: Element) -> bool {
self.0 >> element.atomic_number() & 1 == 1
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn union_with(&mut self, other: Self) {
self.0 |= other.0;
}
#[must_use]
pub const fn intersects(self, other: Self) -> bool {
self.0 & other.0 != 0
}
#[must_use]
pub const fn len(self) -> u32 {
self.0.count_ones()
}
}
impl FromIterator<Element> for ElementMask {
fn from_iter<T: IntoIterator<Item = Element>>(iter: T) -> Self {
let mut mask = Self::EMPTY;
for element in iter {
mask.insert(element);
}
mask
}
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct Extremes {
min: Option<f32>,
max: Option<f32>,
}
impl Extremes {
pub fn observe(&mut self, value: f32) {
if !value.is_finite() {
return;
}
self.min = Some(match self.min {
Some(current) => current.min(value),
None => value,
});
self.max = Some(match self.max {
Some(current) => current.max(value),
None => value,
});
}
#[must_use]
pub const fn min(self) -> Option<f32> {
self.min
}
#[must_use]
pub const fn max(self) -> Option<f32> {
self.max
}
#[must_use]
pub fn excludes_above(self, at_least: f32) -> bool {
self.max.is_some_and(|max| max < at_least)
}
#[must_use]
pub fn excludes_below(self, at_most: f32) -> bool {
self.min.is_some_and(|min| min > at_most)
}
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub struct AtomChunkStats {
pub bounds: Aabb,
pub elements: ElementMask,
pub model: u32,
pub residue_min: u32,
pub residue_max: u32,
pub b_factor: Extremes,
pub occupancy: Extremes,
pub has_altloc: bool,
pub has_missing_coords: bool,
pub has_hydrogen: bool,
}
impl AtomChunkStats {
#[must_use]
pub const fn excludes_element(&self, element: Element) -> bool {
!self.elements.contains(element)
}
#[must_use]
pub fn excludes_region(&self, region: &Aabb, distance: f32) -> bool {
!self.bounds.within(region, distance)
}
#[must_use]
pub const fn excludes_residues(&self, first: u32, last: u32) -> bool {
self.residue_max < first || self.residue_min > last
}
}
#[cfg(test)]
#[path = "stats_tests.rs"]
mod tests;