use std::collections::{BTreeMap, BTreeSet, VecDeque};
use crate::Result;
use super::linear::{affine_solution, coordinate_vector, public_terms};
use super::{Bigrade, BipersistenceModule, BipersistenceTerm};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ClassExtensionKind {
Unique,
Ambiguous,
NoExtension,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassExtension {
pub grade: Bigrade,
pub kind: ClassExtensionKind,
pub class: Vec<BipersistenceTerm>,
pub ambiguity: Vec<Vec<BipersistenceTerm>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClassExtensionRegion {
pub region_index: usize,
pub kind: ClassExtensionKind,
pub ambiguity_rank: usize,
pub grades: Vec<Bigrade>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CohomologyClassAtlas {
pub base_grade: Bigrade,
pub base_class: Vec<BipersistenceTerm>,
pub extensions: Vec<ClassExtension>,
pub regions: Vec<ClassExtensionRegion>,
}
impl BipersistenceModule {
pub fn class_atlas(
&self,
base_grade: Bigrade,
base_class: &[BipersistenceTerm],
) -> Result<CohomologyClassAtlas> {
self.validate_grade(base_grade)?;
let base = coordinate_vector(
base_class,
self.node(base_grade)?.rank,
self.modulus(),
"bipersistence base class coordinates are not canonical",
)?;
if base.is_zero() {
return Err(crate::Error::InvalidInput(
"a bipersistence class atlas needs a nonzero base class".into(),
));
}
let mut extensions = Vec::new();
for scale in base_grade.scale()..self.scale_count() {
for density in base_grade.density()..self.density_count() {
let grade = Bigrade::new(scale, density);
extensions.push(self.extension_at_grade(base_grade, grade, &base)?);
}
}
let regions = extension_regions(
base_grade,
self.scale_count(),
self.density_count(),
&extensions,
);
Ok(CohomologyClassAtlas {
base_grade,
base_class: public_terms(&base),
extensions,
regions,
})
}
fn extension_at_grade(
&self,
base_grade: Bigrade,
grade: Bigrade,
base: &super::linear::SparseVector,
) -> Result<ClassExtension> {
let map = self.map(base_grade, grade)?;
let linear = self.linear_from_public(&map)?;
let (kind, class, ambiguity) = match affine_solution(&linear.columns, base, self.modulus())
{
None => (ClassExtensionKind::NoExtension, Vec::new(), Vec::new()),
Some((particular, kernel)) if kernel.is_empty() => (
ClassExtensionKind::Unique,
public_terms(&particular),
Vec::new(),
),
Some((particular, kernel)) => (
ClassExtensionKind::Ambiguous,
public_terms(&particular),
kernel.iter().map(public_terms).collect(),
),
};
Ok(ClassExtension {
grade,
kind,
class,
ambiguity,
})
}
}
fn extension_regions(
base: Bigrade,
scale_count: usize,
density_count: usize,
extensions: &[ClassExtension],
) -> Vec<ClassExtensionRegion> {
let by_grade = extensions
.iter()
.map(|extension| (extension.grade, extension))
.collect::<BTreeMap<_, _>>();
let mut visited = BTreeSet::new();
let mut regions = Vec::new();
for extension in extensions {
if !visited.insert(extension.grade) {
continue;
}
let label = (extension.kind, extension.ambiguity.len());
let mut queue = VecDeque::from([extension.grade]);
let mut grades = Vec::new();
while let Some(grade) = queue.pop_front() {
grades.push(grade);
for neighbor in grade_neighbors(grade, base, scale_count, density_count) {
let candidate = by_grade[&neighbor];
if (candidate.kind, candidate.ambiguity.len()) == label && visited.insert(neighbor)
{
queue.push_back(neighbor);
}
}
}
grades.sort_unstable();
regions.push(ClassExtensionRegion {
region_index: regions.len(),
kind: label.0,
ambiguity_rank: label.1,
grades,
});
}
regions
}
fn grade_neighbors(
grade: Bigrade,
base: Bigrade,
scale_count: usize,
density_count: usize,
) -> Vec<Bigrade> {
let mut neighbors = Vec::with_capacity(4);
if grade.scale() > base.scale() {
neighbors.push(Bigrade::new(grade.scale() - 1, grade.density()));
}
if grade.scale() + 1 < scale_count {
neighbors.push(Bigrade::new(grade.scale() + 1, grade.density()));
}
if grade.density() > base.density() {
neighbors.push(Bigrade::new(grade.scale(), grade.density() - 1));
}
if grade.density() + 1 < density_count {
neighbors.push(Bigrade::new(grade.scale(), grade.density() + 1));
}
neighbors
}