Source code for oineus.diff.mapping_cylinder
import numpy as np
import eagerpy as epy
from .. import _oineus, _SLIM_SIMPLEX_FIL_TYPES
from .diff_filtration import DiffFiltration
def _fatten_under_with_values(under_fil, values):
"""Materialize a slim/packed under-filtration to fat, keeping ``values`` aligned.
mapping_cylinder's C++ builder is fat-Simplex-only and returns value indices
aligned to the filtration order it is handed, so a slim/packed under-filtration
must be fattened first. The fat _Filtration constructor re-sorts the cells by the
same (value, dim, id) keys that ``under_fil.cells()`` already emits in sorted order
(id is a total tie-break), so the rebuild is ORDER-PRESERVING: fat cell j is the
materialization of under_fil cell j, and ``values`` (aligned to the under_fil sorted
order) is therefore already aligned to the fat order -- no permutation is needed.
Do NOT reintroduce a per-cell Python permutation here: building one via
under_fil.sorted_id_by_uid(fat.cell(j).uid) is an O(n) loop with ~3 binding
round-trips per cell over a (million-cell) filtration, which dwarfs the actual
reduction (CLAUDE.md: do not wrap a mildly-superlinear C++ core in a linear Python
loop). The identity is asserted by tests/test_diff_vr_packed.py
(test_diff_mapping_cylinder_packed_matches_fat, cell-for-cell vs the fat path). A
hypothetical future encoding whose fat rebuild reorders would need a C++-side
permutation (a vectorized sorted_id_by_uid binding), never a Python loop.
"""
if not isinstance(under_fil, _SLIM_SIMPLEX_FIL_TYPES):
return under_fil, values
return _oineus._Filtration(under_fil.cells(), under_fil.negate), values
[docs]
def mapping_cylinder_filtration(fil_domain: DiffFiltration, fil_codomain: DiffFiltration,
v_domain, v_codomain,
v_domain_value=None, v_codomain_value=None) -> DiffFiltration:
assert type(fil_domain) is DiffFiltration
assert type(fil_codomain) is DiffFiltration
if isinstance(v_domain, _oineus.Simplex):
v_domain = v_domain.combinatorial_cell
if isinstance(v_codomain, _oineus.Simplex):
v_codomain = v_codomain.combinatorial_cell
# The cylinder is a fat ProductCell<Simplex, Simplex> filtration, so materialize slim/packed
# under-filtrations to fat and reorder the diff value tensors into the fat sorted order.
under_fil_dom, vals_dom = _fatten_under_with_values(
fil_domain.under_fil, epy.astensor(fil_domain.values))
under_fil_cod, vals_cod = _fatten_under_with_values(
fil_codomain.under_fil, epy.astensor(fil_codomain.values))
if v_domain_value is None:
v_domain_value = under_fil_dom.neg_infinity()
if v_codomain_value is None:
v_codomain_value = under_fil_cod.neg_infinity()
under_cyl_fil, cyl_val_inds = _oineus._mapping_cylinder_with_indices(
under_fil_dom, under_fil_cod, v_domain, v_codomain, v_domain_value, v_codomain_value
)
cyl_val_inds = epy.astensor(np.array(cyl_val_inds, dtype=np.int64))
concat_vals = epy.concatenate((vals_dom, vals_cod))
assert concat_vals.ndim == 1 and concat_vals.shape[0] == fil_domain.size() + fil_codomain.size()
cyl_values = concat_vals[cyl_val_inds].raw
under_cyl_fil.kind = _oineus.FiltrationKind.MappingCylinder
return DiffFiltration(under_cyl_fil, cyl_values)