use crate::{
CircularClassTerm, CircularCoordinate, CircularCoordinateContinuation,
CohomologyContinuationKind, CohomologyLimits, Error, Result, SparseDistanceMatrix,
cohomology_space,
};
use super::{
ArtifactContinuation, ArtifactState, CircularArtifactSummary, CircularCoordinateArtifact,
};
impl CircularCoordinateArtifact {
pub fn from_coordinate(
graph: &SparseDistanceMatrix,
coordinate: &CircularCoordinate,
) -> Result<Self> {
let state = artifact_state(graph, coordinate.scale, Some(coordinate.clone()));
validate_coordinate_binding(&state, coordinate, CohomologyLimits::default())?;
Ok(Self {
modulus: coordinate.modulus,
scale: coordinate.scale,
tolerance: coordinate.tolerance,
states: vec![state],
continuation: None,
})
}
pub fn from_continuation(
old_graph: &SparseDistanceMatrix,
old_coordinate: &CircularCoordinate,
new_graph: &SparseDistanceMatrix,
continuation: &CircularCoordinateContinuation,
limits: CohomologyLimits,
) -> Result<Self> {
let new_space = validate_continuation_inputs(
old_graph,
old_coordinate,
new_graph,
continuation,
limits,
)?;
let (target, ambiguity) = continuation_terms(&new_space, continuation)?;
let old_state = artifact_state(
old_graph,
old_coordinate.scale,
Some(old_coordinate.clone()),
);
validate_coordinate_binding(&old_state, old_coordinate, limits)?;
let new_state = artifact_state(
new_graph,
old_coordinate.scale,
continuation.coordinate.clone(),
);
validate_new_state(&new_state, old_coordinate, &target, limits)?;
Ok(Self {
modulus: old_coordinate.modulus,
scale: old_coordinate.scale,
tolerance: old_coordinate.tolerance,
states: vec![old_state, new_state],
continuation: Some(ArtifactContinuation {
kind: continuation.topology.kind,
target,
ambiguity,
}),
})
}
pub fn summary(&self) -> CircularArtifactSummary {
CircularArtifactSummary {
states: self.states.len(),
coordinates: self
.states
.iter()
.filter(|state| state.coordinate.is_some())
.count(),
edges: self.states.iter().map(|state| state.edges.len()).sum(),
continuation: self.continuation.is_some(),
}
}
}
fn artifact_state(
graph: &SparseDistanceMatrix,
scale: f64,
coordinate: Option<CircularCoordinate>,
) -> ArtifactState {
ArtifactState {
vertex_count: graph.len(),
edges: graph
.edges()
.filter(|edge| edge.2 <= scale)
.map(|(u, v, _)| (u, v))
.collect(),
coordinate,
}
}
fn validate_continuation_inputs(
old_graph: &SparseDistanceMatrix,
old_coordinate: &CircularCoordinate,
new_graph: &SparseDistanceMatrix,
continuation: &CircularCoordinateContinuation,
limits: CohomologyLimits,
) -> Result<crate::CohomologySpace> {
if old_graph.len() != new_graph.len() || continuation.topology.old_space != old_coordinate.space
{
return Err(Error::InvalidInput(
"circular artifact continuation belongs to different inputs".into(),
));
}
let new_space = cohomology_space(
new_graph,
1,
old_coordinate.scale,
old_coordinate.modulus,
limits,
)?;
if continuation.topology.new_space != new_space.id() {
return Err(Error::InvalidInput(
"circular artifact continuation belongs to a different new graph".into(),
));
}
let expected_coordinate = continuation.topology.kind == CohomologyContinuationKind::Unique;
if continuation.coordinate.is_some() != expected_coordinate {
return Err(Error::InvalidInput(
"circular artifact continuation coordinate does not match its status".into(),
));
}
Ok(new_space)
}
fn continuation_terms(
new_space: &crate::CohomologySpace,
continuation: &CircularCoordinateContinuation,
) -> Result<(Vec<CircularClassTerm>, Vec<Vec<CircularClassTerm>>)> {
let positions = new_space
.basis()
.iter()
.enumerate()
.map(|(position, class)| (class.id, position))
.collect::<std::collections::BTreeMap<_, _>>();
let convert = |terms: &[crate::CohomologyRelationTerm]| {
terms
.iter()
.map(|term| continuation_term(&positions, term))
.collect::<Result<Vec<_>>>()
};
let target = convert(&continuation.topology.new)?;
let ambiguity = continuation
.topology
.ambiguity
.iter()
.map(|row| convert(row))
.collect::<Result<Vec<_>>>()?;
Ok((target, ambiguity))
}
fn continuation_term(
positions: &std::collections::BTreeMap<crate::CohomologyClassId, usize>,
term: &crate::CohomologyRelationTerm,
) -> Result<CircularClassTerm> {
positions
.get(&term.class)
.copied()
.map(|basis_index| CircularClassTerm {
basis_index,
coefficient: term.coefficient,
})
.ok_or_else(|| {
Error::InvalidInput("circular continuation names an unknown new class".into())
})
}
fn validate_new_state(
state: &ArtifactState,
old_coordinate: &CircularCoordinate,
target: &[CircularClassTerm],
limits: CohomologyLimits,
) -> Result<()> {
let Some(coordinate) = &state.coordinate else {
return Ok(());
};
if coordinate.modulus != old_coordinate.modulus
|| coordinate.scale.to_bits() != old_coordinate.scale.to_bits()
|| coordinate.tolerance.to_bits() != old_coordinate.tolerance.to_bits()
{
return Err(Error::InvalidInput(
"continued circular coordinate uses different parameters".into(),
));
}
validate_coordinate_binding(state, coordinate, limits)?;
let claimed = target
.iter()
.map(|term| (term.basis_index, term.coefficient))
.collect::<Vec<_>>();
let actual = coordinate
.class
.iter()
.map(|term| (term.basis_index, term.coefficient))
.collect::<Vec<_>>();
if claimed != actual {
return Err(Error::InvalidInput(
"continued coordinate is not the unique target class".into(),
));
}
Ok(())
}
fn validate_coordinate_binding(
state: &ArtifactState,
coordinate: &CircularCoordinate,
limits: CohomologyLimits,
) -> Result<()> {
let graph = SparseDistanceMatrix::from_triplets(
state.vertex_count,
&state
.edges
.iter()
.map(|&(u, v)| (u, v, coordinate.scale))
.collect::<Vec<_>>(),
)?;
let space = cohomology_space(&graph, 1, coordinate.scale, coordinate.modulus, limits)?;
if space.id() != coordinate.space || coordinate.potential.len() != state.vertex_count {
return Err(Error::InvalidInput(
"circular coordinate is bound to a different active graph".into(),
));
}
Ok(())
}