///|
/// All public validation and computation errors carry an actionable message.
pub(all) suberror TopologyError {
TopologyError(String)
} derive(@debug.Debug)
///|
pub fn TopologyError::message(self : TopologyError) -> String {
match self {
TopologyError(message) => message
}
}
///|
/// A filtered cell. Boundary entries are indices of earlier cells.
pub struct Cell {
key : String
dimension : Int
value : Double
boundary : Array[Int]
vertices : Array[Int]
} derive(@debug.Debug)
///|
/// A finite filtration, ordered by value, dimension, then stable cell key.
pub struct Filtration {
cells : Array[Cell]
vertex_count : Int
kind : String
cutoff : Double
positions : Array[Array[Double]]
} derive(@debug.Debug)
///|
/// An interval [birth, death); None means alive at the filtration cutoff.
pub struct Interval {
dimension : Int
birth : Double
death : Double?
birth_cell : Int
death_cell : Int?
representative : Array[Int]
} derive(@debug.Debug)
///|
/// Reduction results retain the filtration so representatives can be decoded.
pub struct Analysis {
filtration : Filtration
intervals : Array[Interval]
column_additions : Int
} derive(@debug.Debug)
///|
pub extend TopologyError with @debug.Debug::{to_repr}
///|
pub extend Cell with @debug.Debug::{to_repr}
///|
pub extend Filtration with @debug.Debug::{to_repr}
///|
pub extend Interval with @debug.Debug::{to_repr}
///|
pub extend Analysis with @debug.Debug::{to_repr}
///|
priv struct RawCell {
key : String
dimension : Int
value : Double
faces : Array[String]
vertices : Array[Int]
}
///|
fn finite(x : Double) -> Bool {
!x.is_nan() && !x.is_inf()
}
///|
fn maximum(a : Double, b : Double) -> Double {
if a > b {
a
} else {
b
}
}
///|
fn check_budget(size : Int, limit : Int) -> Unit raise TopologyError {
if limit < 1 || limit > 10000 {
raise TopologyError("max_cells must be between 1 and 10000")
}
if size > limit {
raise TopologyError(
"cell budget exceeded; lower threshold or reduce input size",
)
}
}
///|
fn vertex_key(v : Int) -> String {
"v:\{v}"
}
///|
fn edge_key(a : Int, b : Int) -> String {
"e:\{a}:\{b}"
}
///|
fn triangle_key(a : Int, b : Int, c : Int) -> String {
"t:\{a}:\{b}:\{c}"
}
///|
fn finalize(
raw : Array[RawCell],
count : Int,
kind : String,
cutoff : Double,
positions? : Array[Array[Double]] = [],
) -> Filtration raise TopologyError {
raw.sort_by(fn(a, b) {
if a.value < b.value {
-1
} else if a.value > b.value {
1
} else if a.dimension != b.dimension {
a.dimension - b.dimension
} else {
a.key.compare(b.key)
}
})
let index : Map[String, Int] = Map([])
let cells : Array[Cell] = []
for cell in raw {
if index.contains(cell.key) {
raise TopologyError("duplicate cell key: " + cell.key)
}
let boundary : Array[Int] = []
for face in cell.faces {
match index.get(face) {
Some(i) => {
if cells[i].dimension + 1 != cell.dimension {
raise TopologyError("invalid face dimension")
}
boundary.push(i)
}
None => raise TopologyError("missing or later face: " + face)
}
}
boundary.sort()
index.set(cell.key, cells.length())
cells.push({
key: cell.key,
dimension: cell.dimension,
value: cell.value,
boundary,
vertices: cell.vertices.copy(),
})
}
{ cells, vertex_count: count, kind, cutoff, positions, }
}