use super::model::{
CollapseSchedule, Diagram, Engine, Error, GraphFactorization, Result, RipsParams,
};
use super::routing::{graph_routes, may_route, resolved_threshold, square_selected};
use crate::classes::{self, ExplainedDiagram};
use crate::collapse;
use crate::distances;
use crate::distances::{DistanceMatrix, SparseDistanceMatrix};
use crate::factorization;
use crate::solver;
fn solve_dense(
dist: &DistanceMatrix,
params: &RipsParams,
threshold: f64,
edges: Option<usize>,
) -> Result<Diagram> {
if square_selected(dist, params, threshold, edges) {
return solver::compute(&dist.to_square(), params);
}
solver::compute(dist, params)
}
fn solve_thresholded(
dist: &DistanceMatrix,
params: &RipsParams,
threshold: f64,
) -> Result<Diagram> {
let sparse = dist.to_sparse_at(threshold)?;
let mut inner = params.clone();
inner.threshold = Some(threshold);
factorization::compute_sparse(&sparse, &inner)
}
pub fn rips_persistence(dist: &DistanceMatrix, params: &RipsParams) -> Result<Diagram> {
if params.collapse_edges {
return collapse_and_solve(dist, params, |_| Ok(()));
}
let threshold = resolved_threshold(dist, params);
let mut resolved = params.clone();
resolved.threshold = Some(threshold);
match params.engine {
Engine::Dense => solve_dense(dist, &resolved, threshold, None),
Engine::Sparse => solve_thresholded(dist, params, threshold),
Engine::Auto => {
let mut counted = None;
if may_route(dist.len(), threshold) {
let edges = dist.count_edges_at(threshold);
if graph_routes(dist.len(), edges) {
return solve_thresholded(dist, &resolved, threshold);
}
counted = Some(edges);
}
solve_dense(dist, &resolved, threshold, counted)
}
}
}
pub fn rips_persistence_with_classes(
dist: &DistanceMatrix,
params: &RipsParams,
) -> Result<ExplainedDiagram> {
let threshold = resolved_threshold(dist, params);
if params.collapse_edges {
return dense_collapsed_classes(dist, params);
}
let sparse = dist.to_sparse_at(threshold)?;
let mut fixed = params.clone();
fixed.threshold = Some(threshold);
classes::rips_persistence_with_classes_sparse(&sparse, &fixed)
}
fn dense_collapsed_classes(dist: &DistanceMatrix, params: &RipsParams) -> Result<ExplainedDiagram> {
let collapsed = match params.collapse_schedule {
CollapseSchedule::Serial => collapse::collapse_dense(dist, params.threshold)?,
CollapseSchedule::Ordered => {
collapse::collapse_dense_ordered_parallel(dist, params.threshold, params.threads)?
}
CollapseSchedule::Rounds => {
collapse::collapse_dense_rounds_parallel(dist, params.threshold, params.threads)?
}
CollapseSchedule::Adaptive => {
collapse::collapse_dense_adaptive(dist, params.threshold, params.adaptive_collapse)?
}
};
let mut inner = params.clone();
inner.collapse_edges = false;
inner.threshold = Some(collapsed.certificate.terminal_level());
let explained = classes::rips_persistence_with_classes_sparse(&collapsed.matrix, &inner)?;
classes::lift_h1_classes(&collapsed, explained)
}
pub fn rips_persistence_sparse(
dist: &SparseDistanceMatrix,
params: &RipsParams,
) -> Result<Diagram> {
if params.collapse_edges {
return collapse_and_solve(dist, params, |_| Ok(()));
}
factorization::compute_sparse(dist, params)
}
pub(crate) fn collapse_and_solve<D: distances::Distances + Sync>(
dist: &D,
params: &RipsParams,
report: impl FnOnce(&collapse::CollapsedRips) -> Result<()>,
) -> Result<Diagram> {
let (collapsed, pool) = execute_collapse(dist, params)?;
report(&collapsed)?;
solve_collapsed(collapsed, pool, params)
}
fn collapse_pool(threads: usize) -> Result<Option<rayon::ThreadPool>> {
if threads <= 1 {
return Ok(None);
}
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.map(Some)
.map_err(|error| Error::Io(format!("thread pool: {error}")))
}
fn execute_collapse<D: distances::Distances + Sync>(
dist: &D,
params: &RipsParams,
) -> Result<(collapse::CollapsedRips, Option<rayon::ThreadPool>)> {
match params.collapse_schedule {
CollapseSchedule::Serial => {
collapse_without_pool(dist, params, collapse::collapse_serial_in)
}
CollapseSchedule::Ordered => {
collapse_with_pool(dist, params, collapse::collapse_ordered_in)
}
CollapseSchedule::Rounds => collapse_with_pool(dist, params, collapse::collapse_rounds_in),
CollapseSchedule::Adaptive => collapse_without_pool(dist, params, |dist, threshold| {
collapse::collapse_adaptive_in(dist, threshold, params.adaptive_collapse)
}),
}
}
fn collapse_without_pool<D, F>(
dist: &D,
params: &RipsParams,
collapse: F,
) -> Result<(collapse::CollapsedRips, Option<rayon::ThreadPool>)>
where
D: distances::Distances,
F: FnOnce(&D, Option<f64>) -> Result<collapse::CollapsedRips>,
{
let collapsed = collapse(dist, params.threshold)?;
Ok((collapsed, collapse_pool(params.threads)?))
}
fn collapse_with_pool<D, F>(
dist: &D,
params: &RipsParams,
collapse: F,
) -> Result<(collapse::CollapsedRips, Option<rayon::ThreadPool>)>
where
D: distances::Distances + Sync,
F: FnOnce(&D, Option<f64>, Option<&rayon::ThreadPool>) -> Result<collapse::CollapsedRips>,
{
let pool = collapse_pool(params.threads)?;
let collapsed = collapse(dist, params.threshold, pool.as_ref())?;
Ok((collapsed, pool))
}
fn solve_collapsed(
collapsed: collapse::CollapsedRips,
pool: Option<rayon::ThreadPool>,
params: &RipsParams,
) -> Result<Diagram> {
let mut inner = params.clone();
inner.collapse_edges = false;
inner.threshold = Some(collapsed.certificate.terminal_level());
if inner.factorization == GraphFactorization::Off {
solver::compute_in(&collapsed.matrix, &inner, pool)
} else {
drop(pool);
factorization::compute_sparse(&collapsed.matrix, &inner)
}
}