Vineyards

Vineyards track how persistence pairings and diagram points change as filtration order changes. Dionysus exposes two related interfaces:

Adjacent transpositions

The low-level vineyard state can be initialized directly from a filtration. The stable ids are the current filtration indices. For a simple edge, the two vertices have ids 0 and 1, and the edge has id 2:

>>> import dionysus as d
>>> f = d.Filtration([[0], [1], [0, 1]])
>>> v = d.Vineyard(f, field=d.Zp(2))
>>> isinstance(v, d.VineyardV)
True
>>> [v.pair(i) == v.unpaired for i in range(len(v))]
[True, False, False]
>>> (v.pair(1), v.pair(2))
(2, 1)

The default Vineyard factory uses the matrix_v method. Pass method='matrix_u' to maintain trails instead:

>>> u = d.Vineyard(f, field=d.Zp(2), method='matrix_u')
>>> isinstance(u, d.VineyardU)
True

The constructor does not sort the filtration for you; call sort() first if you want the usual data/dimension/lexicographic order.

>>> [v.cell_at(i) for i in range(len(v))]
[0, 1, 2]

For lower-level uses, Vineyard also accepts explicit boundary columns in stable cell ids, e.g. d.Vineyard([[], [], [(1, 0), (1, 1)]], field=d.Zp(2)).

Both vineyard states support adjacent swaps by current filtration position. The state updates the reduced matrix, the V chains or U trails, and the cached persistence pairing:

>>> v.transpose(0)
(0, 1)
>>> [v.cell_at(i) for i in range(len(v))]
[1, 0, 2]

The stable ids do not change during transpositions. Methods such as pair(), low(), pivot(), reduced_column(), chain(), and trail() use those stable ids.

Linear homotopy

vineyard_linear_homotopy takes a filtration and two scalar functions on its simplices. It follows the straight-line interpolation

\[f_t(\sigma) = (1-t) f_0(\sigma) + t f_1(\sigma), \quad 0 \leq t \leq 1,\]

performs the adjacent transpositions where neighboring simplices exchange order, and records both the events and the persistence vines.

Both endpoint functions must be valid filtrations: every simplex must have a value at least as large as the values of its faces. When multiple simplices have the same value, Dionysus breaks ties by dimension and then lexicographically so each intermediate order remains a filtration.

>>> f = d.Filtration([[0], [1], [0, 1]])
>>> result = d.vineyard_linear_homotopy(f,
...                                     [0.0, 1.0, 2.0],
...                                     [1.0, 0.0, 2.0],
...                                     field=d.Zp(2))
>>> [(round(e.time, 1), e.first, e.second) for e in result.events]
[(0.5, 0, 1)]
>>> result.final_order
[1, 0, 2]

The result stores:

  • vineyard: the final VineyardV or VineyardU state.

  • events: adjacent transpositions with local pairing information before and after the swap.

  • vines: piecewise-linear persistence vines. Each vine contains segments with endpoint times, birth/death values, birth/death cell ids, and the events that opened or closed the segment.

  • final_order: stable cell ids in the final filtration order.

Use method='matrix_u' to run the same linear homotopy with a MatrixU-backed vineyard state:

>>> result = d.vineyard_linear_homotopy(f,
...                                     [0.0, 1.0, 2.0],
...                                     [1.0, 0.0, 2.0],
...                                     field=d.Zp(2),
...                                     method='matrix_u')
>>> isinstance(result.vineyard, d.VineyardU)
True

The vines are stored as piecewise-linear segments. Each segment records its time interval, birth and death values at the interval endpoints, and the stable cell ids that define the feature:

>>> result = d.vineyard_linear_homotopy(f,
...                                     [0.0, 1.0, 2.0],
...                                     [1.0, 0.0, 2.0],
...                                     field=d.Zp(2))
>>> unpaired = result.vineyard.unpaired
>>> for i, vine in enumerate(result.vines):
...     for segment in vine.segments:
...         if segment.death_cell == unpaired:
...             death_cell = 'inf'
...             endpoint0 = (round(segment.t0, 1), round(segment.birth0, 1), 'inf')
...             endpoint1 = (round(segment.t1, 1), round(segment.birth1, 1), 'inf')
...         else:
...             death_cell = segment.death_cell
...             endpoint0 = (round(segment.t0, 1), round(segment.birth0, 1), round(segment.death0, 1))
...             endpoint1 = (round(segment.t1, 1), round(segment.birth1, 1), round(segment.death1, 1))
...         print(i, segment.birth_cell, death_cell, endpoint0, endpoint1)
0 0 inf (0.0, 0.0, 'inf') (0.5, 0.5, 'inf')
0 1 inf (0.5, 0.5, 'inf') (1.0, 0.0, 'inf')
1 1 2 (0.0, 1.0, 2.0) (0.5, 0.5, 2.0)
1 0 2 (0.5, 0.5, 2.0) (1.0, 1.0, 2.0)