Inside the decomposition¶
The persistence pipeline is
filtration --> Decomposition --> reduce --> diagram
The one-shot helpers (oineus.compute_diagrams_ls(),
oineus.compute_diagrams_vr(), oineus.compute_diagrams_alpha())
collapse the middle two stages, but
you can have more control using a more explicit approach. oineus.Decomposition is the
reduction engine: it holds the boundary matrix \(D\) derived from a
oineus.Filtration, performs the column reduction
\(R = D V\), and exposes the resulting matrices.
The manual workflow¶
import oineus as oin
# 1. Build a filtration (any builder; here, a hand-written one)
simplices = [
oin.Simplex([0], 0.2),
oin.Simplex([1], 0.1),
oin.Simplex([2], 0.3),
oin.Simplex([0, 1], 0.9),
oin.Simplex([0, 2], 0.5),
oin.Simplex([1, 2], 0.8),
oin.Simplex([0, 1, 2], 1.0),
]
fil = oin.Filtration(simplices, negate=False, n_threads=1)
# 2. Construct a Decomposition object (no reduction performed yet)
dcmp = oin.Decomposition(fil, dualize=False)
# 3. Configure and reduce
params = oin.ReductionParams()
params.n_threads = 2 # parallel reduction
params.compute_v = True # we want matrix V (cycle representatives)
params.compute_u = False # cannot directly compute U with parallel reduction
params.clearing_opt = True
dcmp.reduce(params)
# 4. Extract the diagram
dgms = dcmp.diagram(fil, include_inf_points=True)
print(dgms.in_dimension(0)) # H0
print(dgms.in_dimension(1)) # H1
The same pattern works with any oineus.Filtration – swap step 1
for oineus.freudenthal_filtration(), oineus.vr_filtration(),
oineus.cube_filtration(), etc. See Filtration types.
The fast path: oineus.reduce¶
When performance is the priority and you mostly want diagrams (or cycle
representatives), skip the explicit Decomposition(fil) constructor and use
the one-shot oineus.reduce():
params = oin.ReductionParams()
params.n_threads = 8
params.compute_v = False # diagram only; True if you also need V
dcmp = oin.reduce(fil, params, dualize=False)
dgms = dcmp.diagram(fil)
oineus.reduce builds the reduction matrix directly from the filtration
and feeds it straight to the parallel reducer, skipping the intermediate
boundary-matrix copies the explicit Decomposition(fil) + reduce path makes.
The diagrams are identical; this is simply the recommended default when speed
matters. (params is taken by reference, so the per-phase timings land back on
it – see Performance.) dualize=True selects cohomology, exactly as for
Decomposition.
Two post-reduce details, both invisible if you only call dcmp.diagram(fil):
compute_v=False, parallel. Once the pairing is known the reduced columns are freed (“pivots-only” state). The diagram still works – it reads the pivots – butdcmp.r_data/dcmp.r_as_csc()then raise a clear error instead of returning an empty matrix. Usecompute_v=True, or the explicitDecomposition(fil) + reduce, if you need the reduced \(R\) itself.compute_v=True, parallel. \(R\) and \(V\) are kept in a compact working form and materialized lazily: the first access todcmp.r_data/dcmp.v_data, pickling, orsanity_checkreconstructs the at-rest matrices.dcmp.diagram(fil)does not trigger that, so diagram-only callers never pay for it.
The serial path (n_threads=1) reduces in place and always leaves r_data
populated. A fused decomposition does not hold the original boundary \(D\), so
sanity_check needs it passed explicitly:
dcmp.sanity_check(fil.boundary_matrix()).
What the matrices are¶
The reduction maintains
After oineus.Decomposition.reduce():
dcmp.r_data– columns of the reduced boundary matrix \(R\).dcmp.v_data– columns of \(V\), the column operations applied during reduction. Populated whenparams.compute_v = True.dcmp.u_data_t– rows of the inverse \(U\) (such that \(D U^{-1} = R\), in the standard convention). Populated whenparams.compute_u = True.dcmp.r_as_csc(),dcmp.v_as_csc(),dcmp.d_as_csc(),dcmp.u_as_csr()– SciPy-compatible sparse views over \(\mathbb{F}_2\).
compute_u = True cannot be combined with multi-threaded reduction; Oineus
will silently use a single thread if you set both.
Reduction parameters¶
oineus.ReductionParams controls the algorithm.
n_threads– threads for the parallel column reduction. Set to1for deterministic ordering or to debug.clearing_opt– skip columns whose row was already paired in a lower dimension. Usually a big win; turn it off only to compare with literature timings that don’t use it.compute_v,compute_u– see above.col_repr– advanced: the working-column data structure used during reduction. The default is the fastest choice; see Performance for when (rarely) to change it.
Cohomology and the dualize switch¶
Decomposition(fil, dualize=True) reduces the coboundary matrix (cohomology)
instead of the bounday matrix (homology). The diagrams are identical,
but for VR the dual is normally much faster. oineus.compute_diagrams_vr()
sets dualize=True by default for exactly this reason. For grid filtrations
the choice is less clear-cut; both run.
Extracting the diagram¶
dgms = dcmp.diagram(fil, include_inf_points=True)
arr = dgms.in_dimension(1) # (n, 2) NumPy array
pts = dgms.in_dimension(1, as_numpy=False) # list of DiagramPoint
for p in pts:
p.birth, p.death, p.birth_index, p.death_index
birth_index and death_index are positions in fil.simplices() (i.e.,
sorted_id values), so you can map every diagram point back to the pair of
cells that created and killed the homology class.
Zero-persistence diagrams¶
The standard diagram filters out pairs with birth == death – these are
“zero-persistence” pairs, generated and immediately killed by simplices with
the same filtration value (very common on grids with plateaus, or so-called
apparent pairs in VR filtrations). When you actually want them, Oineus
exposes two routes:
# Route 1: just the zero-persistence pairs
zero_dgms = dcmp.zero_pers_diagram(fil)
print(zero_dgms.in_dimension(0))
# Route 2: include them in the regular diagram
params = oin.ReductionParams()
# The flag actually lives on the per-call diagram args / KICR params depending
# on which path you take; for the most common case:
dgms = dcmp.diagram(fil, include_inf_points=True)
# To also include zero-persistence pairs, request them explicitly:
zero_dgms = dcmp.zero_pers_diagram(fil)
Use zero_pers_diagram when you need them as a separate set (for example, to
verify that an apparent absence of features is matched by the filtration’s
zero-pers structure). The oineus.KICRParams.include_zero_persistence
flag does the analogous thing for kernel/image/cokernel diagrams; see
Kernel, image, cokernel persistence.
See also¶
Filtration types – where the input
Filtrationcomes from.Diagram distances and matchings – what to do with the output
Diagrams.Performance – the reduction is the heavy stage; this is where threading and
dualizematter.examples/python/example_manual.py– the full manual snippet in executable form.