holos_tda/filtration/
grade.rs1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct FiltrationError {
6 message: String,
7}
8
9impl FiltrationError {
10 pub(crate) fn new(message: impl Into<String>) -> Self {
11 Self {
12 message: message.into(),
13 }
14 }
15
16 pub fn message(&self) -> &str {
18 &self.message
19 }
20}
21
22impl fmt::Display for FiltrationError {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 write!(formatter, "filtered complex: {}", self.message)
25 }
26}
27
28impl std::error::Error for FiltrationError {}
29
30pub trait FiltrationGrade: Clone + Eq {
32 fn precedes(&self, other: &Self) -> bool;
34}
35
36pub trait LinearFiltrationGrade: FiltrationGrade + Ord {}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub struct ScalarGrade(u64);
45
46impl ScalarGrade {
47 pub fn new(value: f64) -> Result<Self, FiltrationError> {
49 if !value.is_finite() || value < 0.0 {
50 return Err(FiltrationError::new(
51 "a scalar grade must be finite and non-negative",
52 ));
53 }
54 Ok(Self(if value == 0.0 {
55 0.0f64.to_bits()
56 } else {
57 value.to_bits()
58 }))
59 }
60
61 pub fn value(self) -> f64 {
63 f64::from_bits(self.0)
64 }
65
66 pub fn bits(self) -> u64 {
68 self.0
69 }
70}
71
72impl FiltrationGrade for ScalarGrade {
73 fn precedes(&self, other: &Self) -> bool {
74 self <= other
75 }
76}
77
78impl LinearFiltrationGrade for ScalarGrade {}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub struct ProductGrade<const N: usize> {
86 coordinates: [ScalarGrade; N],
87}
88
89impl<const N: usize> ProductGrade<N> {
90 pub fn new(coordinates: [f64; N]) -> Result<Self, FiltrationError> {
92 if N == 0 {
93 return Err(FiltrationError::new(
94 "a product grade requires at least one coordinate",
95 ));
96 }
97 let mut checked = [ScalarGrade(0); N];
98 for (index, value) in coordinates.into_iter().enumerate() {
99 checked[index] = ScalarGrade::new(value)?;
100 }
101 Ok(Self {
102 coordinates: checked,
103 })
104 }
105
106 pub fn coordinates(&self) -> &[ScalarGrade; N] {
108 &self.coordinates
109 }
110}
111
112impl<const N: usize> FiltrationGrade for ProductGrade<N> {
113 fn precedes(&self, other: &Self) -> bool {
114 self.coordinates
115 .iter()
116 .zip(other.coordinates.iter())
117 .all(|(left, right)| left.precedes(right))
118 }
119}
120
121pub trait ScalarProjection<G: FiltrationGrade> {
123 fn project(&self, grade: &G) -> Result<ScalarGrade, FiltrationError>;
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub struct CoordinateProjection {
130 coordinate: usize,
131}
132
133impl CoordinateProjection {
134 pub fn new(coordinate: usize) -> Self {
136 Self { coordinate }
137 }
138}
139
140impl<const N: usize> ScalarProjection<ProductGrade<N>> for CoordinateProjection {
141 fn project(&self, grade: &ProductGrade<N>) -> Result<ScalarGrade, FiltrationError> {
142 grade
143 .coordinates
144 .get(self.coordinate)
145 .copied()
146 .ok_or_else(|| {
147 FiltrationError::new(format!(
148 "coordinate {} is outside a {N}-parameter grade",
149 self.coordinate
150 ))
151 })
152 }
153}