use crate::circular::{
CircularCoordinate, CircularCoordinateFailure, CircularCoordinateParams,
circular_coordinate_with_failure_stage, validate_params,
};
use crate::{Error, Result};
use super::{BipersistenceModule, BipersistenceTerm, ClassExtensionKind, CohomologyClassAtlas};
#[derive(Debug, Clone, PartialEq)]
pub enum CircularCoordinateFamilyStatus {
NotAttempted,
LiftFailed,
SolveFailed,
Success(Box<CircularCoordinate>),
}
impl CircularCoordinateFamilyStatus {
pub fn coordinate(&self) -> Option<&CircularCoordinate> {
match self {
Self::Success(coordinate) => Some(coordinate),
Self::NotAttempted | Self::LiftFailed | Self::SolveFailed => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CircularCoordinateFamilyEntry {
pub grade: super::Bigrade,
pub extension: ClassExtensionKind,
pub status: CircularCoordinateFamilyStatus,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CircularCoordinateFamily {
pub base_grade: super::Bigrade,
pub base_class: Vec<BipersistenceTerm>,
pub entries: Vec<CircularCoordinateFamilyEntry>,
}
impl BipersistenceModule {
pub fn circular_coordinate_family(
&self,
atlas: &CohomologyClassAtlas,
params: CircularCoordinateParams,
) -> Result<CircularCoordinateFamily> {
validate_params(params)?;
if self.modulus() == 2 {
return Err(Error::InvalidInput(
"automatic circular families require an odd prime modulus".into(),
));
}
if atlas.base_grade
!= atlas
.extensions
.first()
.map(|extension| extension.grade)
.unwrap_or(atlas.base_grade)
{
return Err(Error::InvalidInput(
"the class atlas is not in canonical grid order".into(),
));
}
let rebuilt = self.class_atlas(atlas.base_grade, &atlas.base_class)?;
if rebuilt != *atlas {
return Err(Error::InvalidInput(
"the class atlas belongs to a different bipersistence module".into(),
));
}
let mut entries = Vec::with_capacity(atlas.extensions.len());
for extension in &atlas.extensions {
let status = if extension.kind == ClassExtensionKind::Unique {
self.coordinate_status(extension, params)?
} else {
CircularCoordinateFamilyStatus::NotAttempted
};
entries.push(CircularCoordinateFamilyEntry {
grade: extension.grade,
extension: extension.kind,
status,
});
}
Ok(CircularCoordinateFamily {
base_grade: atlas.base_grade,
base_class: atlas.base_class.clone(),
entries,
})
}
fn coordinate_status(
&self,
extension: &super::ClassExtension,
params: CircularCoordinateParams,
) -> Result<CircularCoordinateFamilyStatus> {
let position = self.node_index(extension.grade);
let coordinates = extension
.class
.iter()
.map(|term| (term.basis_index, term.coefficient))
.collect::<Vec<_>>();
let rows = self.spaces[position]
.cocycle_from_coordinates(&coordinates)?
.into_iter()
.map(|term| (term.simplex[0], term.simplex[1], term.coefficient))
.collect::<Vec<_>>();
let cocycle =
crate::cocycle_from_ripser_terms(&self.graphs[position], self.modulus(), 0.0, &rows)?;
let status = match circular_coordinate_with_failure_stage(
&self.graphs[position],
&cocycle,
params,
) {
Ok(coordinate) => CircularCoordinateFamilyStatus::Success(Box::new(coordinate)),
Err(CircularCoordinateFailure::Lift(_)) => CircularCoordinateFamilyStatus::LiftFailed,
Err(CircularCoordinateFailure::Solve(_)) => CircularCoordinateFamilyStatus::SolveFailed,
Err(CircularCoordinateFailure::Other(error)) => return Err(error),
};
Ok(status)
}
}
#[cfg(test)]
#[path = "circular_tests.rs"]
mod tests;