holos_tda/bipersistence/
circular.rs1use crate::circular::{
4 CircularCoordinate, CircularCoordinateFailure, CircularCoordinateParams,
5 circular_coordinate_with_failure_stage, validate_params,
6};
7use crate::{Error, Result};
8
9use super::{BipersistenceModule, BipersistenceTerm, ClassExtensionKind, CohomologyClassAtlas};
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum CircularCoordinateFamilyStatus {
14 NotAttempted,
16 LiftFailed,
21 SolveFailed,
23 Success(Box<CircularCoordinate>),
25}
26
27impl CircularCoordinateFamilyStatus {
28 pub fn coordinate(&self) -> Option<&CircularCoordinate> {
30 match self {
31 Self::Success(coordinate) => Some(coordinate),
32 Self::NotAttempted | Self::LiftFailed | Self::SolveFailed => None,
33 }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct CircularCoordinateFamilyEntry {
40 pub grade: super::Bigrade,
42 pub extension: ClassExtensionKind,
44 pub status: CircularCoordinateFamilyStatus,
46}
47
48#[derive(Debug, Clone, PartialEq)]
50pub struct CircularCoordinateFamily {
51 pub base_grade: super::Bigrade,
53 pub base_class: Vec<BipersistenceTerm>,
55 pub entries: Vec<CircularCoordinateFamilyEntry>,
57}
58
59impl BipersistenceModule {
60 pub fn circular_coordinate_family(
70 &self,
71 atlas: &CohomologyClassAtlas,
72 params: CircularCoordinateParams,
73 ) -> Result<CircularCoordinateFamily> {
74 validate_params(params)?;
75 if self.modulus() == 2 {
76 return Err(Error::InvalidInput(
77 "automatic circular families require an odd prime modulus".into(),
78 ));
79 }
80 if atlas.base_grade
81 != atlas
82 .extensions
83 .first()
84 .map(|extension| extension.grade)
85 .unwrap_or(atlas.base_grade)
86 {
87 return Err(Error::InvalidInput(
88 "the class atlas is not in canonical grid order".into(),
89 ));
90 }
91 let rebuilt = self.class_atlas(atlas.base_grade, &atlas.base_class)?;
92 if rebuilt != *atlas {
93 return Err(Error::InvalidInput(
94 "the class atlas belongs to a different bipersistence module".into(),
95 ));
96 }
97 let mut entries = Vec::with_capacity(atlas.extensions.len());
98 for extension in &atlas.extensions {
99 let status = if extension.kind == ClassExtensionKind::Unique {
100 self.coordinate_status(extension, params)?
101 } else {
102 CircularCoordinateFamilyStatus::NotAttempted
103 };
104 entries.push(CircularCoordinateFamilyEntry {
105 grade: extension.grade,
106 extension: extension.kind,
107 status,
108 });
109 }
110 Ok(CircularCoordinateFamily {
111 base_grade: atlas.base_grade,
112 base_class: atlas.base_class.clone(),
113 entries,
114 })
115 }
116
117 fn coordinate_status(
118 &self,
119 extension: &super::ClassExtension,
120 params: CircularCoordinateParams,
121 ) -> Result<CircularCoordinateFamilyStatus> {
122 let position = self.node_index(extension.grade);
123 let coordinates = extension
124 .class
125 .iter()
126 .map(|term| (term.basis_index, term.coefficient))
127 .collect::<Vec<_>>();
128 let rows = self.spaces[position]
129 .cocycle_from_coordinates(&coordinates)?
130 .into_iter()
131 .map(|term| (term.simplex[0], term.simplex[1], term.coefficient))
132 .collect::<Vec<_>>();
133 let cocycle =
134 crate::cocycle_from_ripser_terms(&self.graphs[position], self.modulus(), 0.0, &rows)?;
135 let status = match circular_coordinate_with_failure_stage(
136 &self.graphs[position],
137 &cocycle,
138 params,
139 ) {
140 Ok(coordinate) => CircularCoordinateFamilyStatus::Success(Box::new(coordinate)),
141 Err(CircularCoordinateFailure::Lift(_)) => CircularCoordinateFamilyStatus::LiftFailed,
142 Err(CircularCoordinateFailure::Solve(_)) => CircularCoordinateFamilyStatus::SolveFailed,
143 Err(CircularCoordinateFailure::Other(error)) => return Err(error),
144 };
145 Ok(status)
146 }
147}
148
149#[cfg(test)]
150#[path = "circular_tests.rs"]
151mod tests;