use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FiltrationError {
message: String,
}
impl FiltrationError {
pub(crate) fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for FiltrationError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "filtered complex: {}", self.message)
}
}
impl std::error::Error for FiltrationError {}
pub trait FiltrationGrade: Clone + Eq {
fn precedes(&self, other: &Self) -> bool;
}
pub trait LinearFiltrationGrade: FiltrationGrade + Ord {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ScalarGrade(u64);
impl ScalarGrade {
pub fn new(value: f64) -> Result<Self, FiltrationError> {
if !value.is_finite() || value < 0.0 {
return Err(FiltrationError::new(
"a scalar grade must be finite and non-negative",
));
}
Ok(Self(if value == 0.0 {
0.0f64.to_bits()
} else {
value.to_bits()
}))
}
pub fn value(self) -> f64 {
f64::from_bits(self.0)
}
pub fn bits(self) -> u64 {
self.0
}
}
impl FiltrationGrade for ScalarGrade {
fn precedes(&self, other: &Self) -> bool {
self <= other
}
}
impl LinearFiltrationGrade for ScalarGrade {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ProductGrade<const N: usize> {
coordinates: [ScalarGrade; N],
}
impl<const N: usize> ProductGrade<N> {
pub fn new(coordinates: [f64; N]) -> Result<Self, FiltrationError> {
if N == 0 {
return Err(FiltrationError::new(
"a product grade requires at least one coordinate",
));
}
let mut checked = [ScalarGrade(0); N];
for (index, value) in coordinates.into_iter().enumerate() {
checked[index] = ScalarGrade::new(value)?;
}
Ok(Self {
coordinates: checked,
})
}
pub fn coordinates(&self) -> &[ScalarGrade; N] {
&self.coordinates
}
}
impl<const N: usize> FiltrationGrade for ProductGrade<N> {
fn precedes(&self, other: &Self) -> bool {
self.coordinates
.iter()
.zip(other.coordinates.iter())
.all(|(left, right)| left.precedes(right))
}
}
pub trait ScalarProjection<G: FiltrationGrade> {
fn project(&self, grade: &G) -> Result<ScalarGrade, FiltrationError>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CoordinateProjection {
coordinate: usize,
}
impl CoordinateProjection {
pub fn new(coordinate: usize) -> Self {
Self { coordinate }
}
}
impl<const N: usize> ScalarProjection<ProductGrade<N>> for CoordinateProjection {
fn project(&self, grade: &ProductGrade<N>) -> Result<ScalarGrade, FiltrationError> {
grade
.coordinates
.get(self.coordinate)
.copied()
.ok_or_else(|| {
FiltrationError::new(format!(
"coordinate {} is outside a {N}-parameter grade",
self.coordinate
))
})
}
}