# Persistence Landscapes¶

Persistence Landscapes were first introduced in Bubenik, 2015. Persistence landscapes were one of the first vectorization schemes introduced for persistence diagrams.

There are two implementations of persistence landscapes in persim. The first is PersLandscapeExact class, which treats the persistence landscape exactly as it is defined–namely, there are no approximations. In this implementation, the landscape functions are stored internally as lists of their critical points (the breaks in the piecewise linear functions). The second is the PersLandscapeApprox class, which instead samples the landscape functions on a grid which the user can specify. This immediately vectorizes the landscape functions so they can be manipulated, averaged, and then quickly passed into other machine learning algorithms.

Depending on the problem at hand, you can choose either class to perform calculations. PersLandscapeApprox tends to be much faster than PersLandscapeExact, at the expense of approximate versus exact outputs. There are methods to convert exact landscapes to approximate ones as well, described below.

## Exact landscapes with PersLandscapeExact¶

[1]:
import numpy as np
from persim import PersLandscapeExact
from persim.landscapes import plot_landscape_simple

[2]:
pd = [np.array([[0,3], [1,4]]), np.array([[1,4]])]

This diagram has two features in degree 0 and one feature in degree 1 (as output by a ripser.py calculation for example). We specify which degree of homology we calculate the landscape in.

[3]:
ple = PersLandscapeExact(dgms=pd,hom_deg=0)
ple
[3]:
Exact persistence landscape in homological degree 0

The critical pairs which define the landscapes are stored internally as a list of lists in the critical_pairs attribute:

[4]:
ple.critical_pairs
[4]:
[[[0, 0], [1.5, 1.5], [2.0, 1.0], [2.5, 1.5], [4, 0]],
[[1, 0], [2.0, 1.0], [3, 0]]]

This list of lists is “outer indexed” by the depth of the landscape function. Equivalently, ple.critical_points[i] provides a list of the critical points of the i-th persistence landscape in degree 0. We can see what these pairs of numbers correspond to by plotting the landscape with plot_landscape_simple.

[5]:
plot_landscape_simple(ple)
[5]:
<AxesSubplot:>

### Arithmetic¶

Basic arithmetic operations are implemented using the traditional +, -, and * operations.

[6]:
pd2 = [ np.array([[0.5,7],[3,5],[4.1,6.5]]), np.array([[1,4]])]
ple2 = PersLandscapeExact(dgms=pd2,hom_deg=0)
ple2
[6]:
Exact persistence landscape in homological degree 0
[7]:
pl_sum = ple + ple2
pl_sum.critical_pairs
[7]:
[[[0, 0],
[0.5, 0.5],
[1.5, 2.5],
[2.0, 2.5],
[2.5, 3.5],
[3.75, 3.5],
[4, 3.0],
[7.0, 0.0]],
[[1, 0],
[2.0, 1.0],
[3, 0.0],
[4.0, 1.0],
[4.55, 0.4500000000000002],
[5.3, 1.2000000000000002],
[6.5, 0.0]],
[[4.1, 0], [4.55, 0.4500000000000002], [5.0, 0]]]
[8]:
(ple - ple2).critical_pairs
[8]:
[[[0, 0],
[0.5, 0.5],
[1.5, 0.5],
[2.0, -0.5],
[2.5, -0.5],
[3.75, -3.0],
[4, -3.0],
[7.0, 0.0]],
[[1, 0],
[2.0, 1.0],
[3, 0.0],
[4.0, -1.0],
[4.55, -0.4500000000000002],
[5.3, -1.2000000000000002],
[6.5, 0.0]],
[[4.1, 0], [4.55, -0.4500000000000002], [5.0, 0]]]
[9]:
(7*ple).critical_pairs
[9]:
[[(0, 0), (1.5, 10.5), (2.0, 7.0), (2.5, 10.5), (4, 0)],
[(1, 0), (2.0, 7.0), (3, 0)]]

### Slicing and indexing¶

Landscapes are sliced by depth.

[10]:
ple[0]
[10]:
[[0, 0], [1.5, 1.5], [2.0, 1.0], [2.5, 1.5], [4, 0]]
[11]:
ple[1:]
[11]:
[[[1, 0], [2.0, 1.0], [3, 0]]]

The standard p norms are implemented for all p, as well as the supremum norm.

[12]:
ple.p_norm(p=2)
[12]:
2.1213203435596424
[13]:
ple.sup_norm()
[13]:
1.5

## Approximate landscapes with PersLandscapeApprox¶

Let’s use the same persistence diagram as above to explore this class and compare it to the exact landscape counterpart.

[14]:
from persim import PersLandscapeApprox
[15]:
pla = PersLandscapeApprox(dgms=pd,hom_deg=0)
pla
[15]:
Approximate persistence landscape in homological degree 0 on grid from 0 to 4 with 500 steps

The approximate class “snaps” the landscape functions onto a grid. The user can specify the grid manually or (as we just did) can leave it unspecified and an optimal grid will be determined. To specify a grid manually, pass start, stop, and num_steps into the constructor.

[16]:
pla_manual_grid = PersLandscapeApprox(dgms=pd, hom_deg=0, start=-1, stop=5, num_steps=10000)
pla_manual_grid
[16]:
Approximate persistence landscape in homological degree 0 on grid from -1 to 5 with 10000 steps

The values interpolated onto the grid are stored in the values attribute.

[17]:
print(pla_manual_grid.values.shape)
print(pla_manual_grid.values)
(2, 10000)
[[0. 0. 0. ... 0. 0. 0.]
[0. 0. 0. ... 0. 0. 0.]]

### Arithmetic¶

Basic arithmetic operations are also implemented using +, -, and *.

Note: Before any arithmetic operations can be carried out, the two approximate landscape classes must be coerced with the same grid parameters. The snap_PL function is provided to take of the snapping.

[18]:
from persim.landscapes import snap_pl

[snapped_pla, snapped_pla_manual_grid] = snap_pl([pla,pla_manual_grid])
[19]:
print(snapped_pla + 4*snapped_pla_manual_grid)
Approximate persistence landscape in homological degree 0 on grid from -1 to 5 with 10000 steps

Once snapped, all arithmetic operations work as expected.

### Slicing and Indexing¶

Slicing and Indexing work as with exact landscapes. Here are the 500 sampled values from the depth 0 landscape in pla.

[20]:
pla[0]
[20]:
array([0.        , 0.00801603, 0.01603206, 0.0240481 , 0.03206413,
0.04008016, 0.04809619, 0.05611222, 0.06412826, 0.07214429,
0.08016032, 0.08817635, 0.09619238, 0.10420842, 0.11222445,
0.12024048, 0.12825651, 0.13627255, 0.14428858, 0.15230461,
0.16032064, 0.16833667, 0.17635271, 0.18436874, 0.19238477,
0.2004008 , 0.20841683, 0.21643287, 0.2244489 , 0.23246493,
0.24048096, 0.24849699, 0.25651303, 0.26452906, 0.27254509,
0.28056112, 0.28857715, 0.29659319, 0.30460922, 0.31262525,
0.32064128, 0.32865731, 0.33667335, 0.34468938, 0.35270541,
0.36072144, 0.36873747, 0.37675351, 0.38476954, 0.39278557,
0.4008016 , 0.40881764, 0.41683367, 0.4248497 , 0.43286573,
0.44088176, 0.4488978 , 0.45691383, 0.46492986, 0.47294589,
0.48096192, 0.48897796, 0.49699399, 0.50501002, 0.51302605,
0.52104208, 0.52905812, 0.53707415, 0.54509018, 0.55310621,
0.56112224, 0.56913828, 0.57715431, 0.58517034, 0.59318637,
0.6012024 , 0.60921844, 0.61723447, 0.6252505 , 0.63326653,
0.64128257, 0.6492986 , 0.65731463, 0.66533066, 0.67334669,
0.68136273, 0.68937876, 0.69739479, 0.70541082, 0.71342685,
0.72144289, 0.72945892, 0.73747495, 0.74549098, 0.75350701,
0.76152305, 0.76953908, 0.77755511, 0.78557114, 0.79358717,
0.80160321, 0.80961924, 0.81763527, 0.8256513 , 0.83366733,
0.84168337, 0.8496994 , 0.85771543, 0.86573146, 0.87374749,
0.88176353, 0.88977956, 0.89779559, 0.90581162, 0.91382766,
0.92184369, 0.92985972, 0.93787575, 0.94589178, 0.95390782,
0.96192385, 0.96993988, 0.97795591, 0.98597194, 0.99398798,
1.00200401, 1.01002004, 1.01803607, 1.0260521 , 1.03406814,
1.04208417, 1.0501002 , 1.05811623, 1.06613226, 1.0741483 ,
1.08216433, 1.09018036, 1.09819639, 1.10621242, 1.11422846,
1.12224449, 1.13026052, 1.13827655, 1.14629259, 1.15430862,
1.16232465, 1.17034068, 1.17835671, 1.18637275, 1.19438878,
1.20240481, 1.21042084, 1.21843687, 1.22645291, 1.23446894,
1.24248497, 1.250501  , 1.25851703, 1.26653307, 1.2745491 ,
1.28256513, 1.29058116, 1.29859719, 1.30661323, 1.31462926,
1.32264529, 1.33066132, 1.33867735, 1.34669339, 1.35470942,
1.36272545, 1.37074148, 1.37875752, 1.38677355, 1.39478958,
1.40280561, 1.41082164, 1.41883768, 1.42685371, 1.43486974,
1.44288577, 1.4509018 , 1.45891784, 1.46693387, 1.4749499 ,
1.48296593, 1.49098196, 1.498998  , 1.49098196, 1.48296593,
1.4749499 , 1.46693387, 1.45891784, 1.4509018 , 1.44288577,
1.43486974, 1.42685371, 1.41883768, 1.41082164, 1.40280561,
1.39478958, 1.38677355, 1.37875752, 1.37074148, 1.36272545,
1.35470942, 1.34669339, 1.33867735, 1.33066132, 1.32264529,
1.31462926, 1.30661323, 1.29859719, 1.29058116, 1.28256513,
1.2745491 , 1.26653307, 1.25851703, 1.250501  , 1.24248497,
1.23446894, 1.22645291, 1.21843687, 1.21042084, 1.20240481,
1.19438878, 1.18637275, 1.17835671, 1.17034068, 1.16232465,
1.15430862, 1.14629259, 1.13827655, 1.13026052, 1.12224449,
1.11422846, 1.10621242, 1.09819639, 1.09018036, 1.08216433,
1.0741483 , 1.06613226, 1.05811623, 1.0501002 , 1.04208417,
1.03406814, 1.0260521 , 1.01803607, 1.01002004, 1.00200401,
1.00200401, 1.01002004, 1.01803607, 1.0260521 , 1.03406814,
1.04208417, 1.0501002 , 1.05811623, 1.06613226, 1.0741483 ,
1.08216433, 1.09018036, 1.09819639, 1.10621242, 1.11422846,
1.12224449, 1.13026052, 1.13827655, 1.14629259, 1.15430862,
1.16232465, 1.17034068, 1.17835671, 1.18637275, 1.19438878,
1.20240481, 1.21042084, 1.21843687, 1.22645291, 1.23446894,
1.24248497, 1.250501  , 1.25851703, 1.26653307, 1.2745491 ,
1.28256513, 1.29058116, 1.29859719, 1.30661323, 1.31462926,
1.32264529, 1.33066132, 1.33867735, 1.34669339, 1.35470942,
1.36272545, 1.37074148, 1.37875752, 1.38677355, 1.39478958,
1.40280561, 1.41082164, 1.41883768, 1.42685371, 1.43486974,
1.44288577, 1.4509018 , 1.45891784, 1.46693387, 1.4749499 ,
1.48296593, 1.49098196, 1.498998  , 1.49098196, 1.48296593,
1.4749499 , 1.46693387, 1.45891784, 1.4509018 , 1.44288577,
1.43486974, 1.42685371, 1.41883768, 1.41082164, 1.40280561,
1.39478958, 1.38677355, 1.37875752, 1.37074148, 1.36272545,
1.35470942, 1.34669339, 1.33867735, 1.33066132, 1.32264529,
1.31462926, 1.30661323, 1.29859719, 1.29058116, 1.28256513,
1.2745491 , 1.26653307, 1.25851703, 1.250501  , 1.24248497,
1.23446894, 1.22645291, 1.21843687, 1.21042084, 1.20240481,
1.19438878, 1.18637275, 1.17835671, 1.17034068, 1.16232465,
1.15430862, 1.14629259, 1.13827655, 1.13026052, 1.12224449,
1.11422846, 1.10621242, 1.09819639, 1.09018036, 1.08216433,
1.0741483 , 1.06613226, 1.05811623, 1.0501002 , 1.04208417,
1.03406814, 1.0260521 , 1.01803607, 1.01002004, 1.00200401,
0.99398798, 0.98597194, 0.97795591, 0.96993988, 0.96192385,
0.95390782, 0.94589178, 0.93787575, 0.92985972, 0.92184369,
0.91382766, 0.90581162, 0.89779559, 0.88977956, 0.88176353,
0.87374749, 0.86573146, 0.85771543, 0.8496994 , 0.84168337,
0.83366733, 0.8256513 , 0.81763527, 0.80961924, 0.80160321,
0.79358717, 0.78557114, 0.77755511, 0.76953908, 0.76152305,
0.75350701, 0.74549098, 0.73747495, 0.72945892, 0.72144289,
0.71342685, 0.70541082, 0.69739479, 0.68937876, 0.68136273,
0.67334669, 0.66533066, 0.65731463, 0.6492986 , 0.64128257,
0.63326653, 0.6252505 , 0.61723447, 0.60921844, 0.6012024 ,
0.59318637, 0.58517034, 0.57715431, 0.56913828, 0.56112224,
0.55310621, 0.54509018, 0.53707415, 0.52905812, 0.52104208,
0.51302605, 0.50501002, 0.49699399, 0.48897796, 0.48096192,
0.47294589, 0.46492986, 0.45691383, 0.4488978 , 0.44088176,
0.43286573, 0.4248497 , 0.41683367, 0.40881764, 0.4008016 ,
0.39278557, 0.38476954, 0.37675351, 0.36873747, 0.36072144,
0.35270541, 0.34468938, 0.33667335, 0.32865731, 0.32064128,
0.31262525, 0.30460922, 0.29659319, 0.28857715, 0.28056112,
0.27254509, 0.26452906, 0.25651303, 0.24849699, 0.24048096,
0.23246493, 0.2244489 , 0.21643287, 0.20841683, 0.2004008 ,
0.19238477, 0.18436874, 0.17635271, 0.16833667, 0.16032064,
0.15230461, 0.14428858, 0.13627255, 0.12825651, 0.12024048,
0.11222445, 0.10420842, 0.09619238, 0.08817635, 0.08016032,
0.07214429, 0.06412826, 0.05611222, 0.04809619, 0.04008016,
0.03206413, 0.0240481 , 0.01603206, 0.00801603, 0.        ])

## Other tools for landscapes¶

Several auxiliary functions have been implemented to facilitate easier analysis with persistence landscapes.

### Death vector¶

For Vietoris-Rips, Cech, or similar filtrations, the birth times in homological degree 0 are not helpful for distinguishing phenomena. For this case in particular, we provide a calculation of the death vector. The death vector is the vector of death times of features in homological degree 0.

[21]:
from persim.landscapes import death_vector
[22]:
dv = death_vector(dgms=pd)
dv
[22]:
[4, 3]

### Linear Combinations¶

We provide methods for efficiently computing linear combinations of persistence landscapes, and in particular, computing averages of a large list of landscapes. These methods will automatically snap the landscapes to a common grid if necessary.

[23]:
from persim.landscapes import average_approx, lc_approx

lc_landscape = lc_approx(landscapes=[pla,2*pla,5*pla], coeffs = [1,1/2,1/5])
np.testing.assert_almost_equal(lc_landscape[0],pla[0]*3)

The above provides a simple example verifying the desired behavior. Here we show an even simpler example with taking an average.

[24]:
avg_landscape = average_approx([pla,pla,pla,pla])
np.testing.assert_almost_equal(avg_landscape[0],pla[0])

## Visualizations¶

Simple 2-dimensional and more visually complex 3-dimensional landscape functions can be plotted.

[25]:
from persim.landscapes import plot_landscape, plot_landscape_simple

The plot_landscape method provides a 3-dimensional visualization of the landscape function. This version is computationally expensive to compute, so a lighter, 2-dimensional version is also provided in plot_landscape_simple.

[26]:
fig = plot_landscape(pla)
[27]:
plot_landscape_simple(pla)
[27]:
<AxesSubplot:>

Here’s one final example to show how all of these methods can be combined. We construct a list of 100 “random” persistence diagrams each with 500 points, their average landscape, and plot it.

[28]:
from numpy.random import default_rng

rng = default_rng(seed=42)

random_pds = [
[
np.array([
[rng.random(), rng.random()+1] for _ in range(500)
])
] # extra bracketing to mimic the output of ripser.py
for _ in range(100)]
[29]:
pls = [PersLandscapeApprox(dgms=dgm, hom_deg=0) for dgm in random_pds]

avg_pl = average_approx(pls)

The plotting methods return matplot figures (or axes objects) for further user modification if necessary. For example, if we plot avg_pl, we will have way too many legend entries, so we can remove those before plotting using standard matplotlib techniques.

[30]:
ax = plot_landscape_simple(avg_pl)
my_handles, my_labels = ax.get_legend_handles_labels()
ax.legend(handles=my_handles[:15])
[30]:
<matplotlib.legend.Legend at 0x7f1aba7f1d90>