Quickstart Guide

Once you have successfully installed bats, you can follow this guide to help you get started. You can download this guide as a Jupyter notebook here.

You can find additional information and examples in the tutorials and examples, and ultimately the API reference.

First, import bats:

[1]:
import bats

Simplicial Complexes and Homology

BATS offers two implementations of simplicial complexes: SimplicialComplex and LightSimplicialComplex. While the internal representations differ, they both have the same interface which can be used.

Simplices in bats should generally be assumed to be ordered, meaning that [0,1,2] is not the same as [1,2,0]. If you want to use unordered simplices, you can either add vertices in sorted order, or use a sorting algorithm before adding simplices to complexes.

The add method will add simplices, assuming that all faces have previously been added. The add_recursive method will recursively add faces as needed.

[7]:
X = bats.SimplicialComplex()
X.add_recursive([0,1,2])
X.add_recursive([2,3])
X.add([1,3])

X.get_simplices()
[7]:
[[0], [1], [2], [3], [0, 1], [0, 2], [1, 2], [2, 3], [1, 3], [0, 1, 2]]

Now let’s compute homology

[15]:
R = bats.reduce(X, bats.F2()) # F2 is coefficient field

for k in range(R.maxdim()):
    print("dim H_{}: {}".format(k, R.hdim(k)))
dim H_0: 1
dim H_1: 1

The output of bats.reduce is a ReducedChainComplex which holds information used to compute homology.

For LightSimplicialComplex, you need to provide an upper bound on the number of vertices and maximum simplex dimension.

[13]:
n = 4 # number of vertices
d = 2 # max simplex dimension
X = bats.LightSimplicialComplex(n, d)
X.add_recursive([0,1,2])
X.add_recursive([2,3])
X.add([1,3])

X.get_simplices()
[13]:
[[0], [1], [2], [3], [0, 1], [0, 2], [1, 2], [2, 3], [1, 3], [0, 1, 2]]
[14]:
R = bats.reduce(X, bats.F2())

for k in range(R.maxdim()):
    print("dim H_{}: {}".format(k, R.hdim(k)))
dim H_0: 1
dim H_1: 1

Persistent Homology

You can add simplices to a filtration by providing a parameter at which they first appear.

[17]:
F = bats.FilteredSimplicialComplex()
F.add_recursive(0.0, [0,1,2])
F.add_recursive(1.0, [2, 3])
F.add(2.0, [1,3])

F.complex().get_simplices()
[17]:
[[0], [1], [2], [3], [0, 1], [0, 2], [1, 2], [2, 3], [1, 3], [0, 1, 2]]

again, we can use the reduce function, but now we get a ReducedFilteredChainComplex

[20]:
R = bats.reduce(F, bats.F2())

for k in range(R.maxdim()):
    for p in R.persistence_pairs(k):
        print(p)
0 : (0,inf) <0,-1>
0 : (0,0) <1,0>
0 : (0,0) <2,1>
0 : (1,1) <3,3>
1 : (0,0) <2,0>
1 : (2,inf) <4,-1>

The output of R.persistence_pairs(k) is a vector of PersistencePairs for k-dimensional persistent homology.

A PersistencePair includes 5 pieces of information: * The dimension of the homology class. * The birth and death parameters of the homology class. * The simplex indices responsible for birth and death.

[28]:
p = R.persistence_pairs(1)[-1]
print(p)
print(p.dim(), p.birth(), p.death(), p.birth_ind(), p.death_ind(), sep=', ')
1 : (2,inf) <4,-1>
1, 2.0, inf, 4, 18446744073709551615

infinite bars have a death index set to 2**64 - 1

Maps

BATS makes dealing with maps between topological spaces and associated chain maps and induced maps on homology easy. The relevant class is a CellularMap which keeps track of what cells in one complex map to what cells in another.

We’ll just look at a wrapper for CellularMap, called SimplcialMap which can be used to extend a map on the vertex set of a SimplicialComplex to a map of simplices.

First, we’ll build identical simplicial complexes X and Y which are both cycle graphs on four vertices.

[30]:
X = bats.SimplicialComplex()
X.add_recursive([0,1])
X.add_recursive([1,2])
X.add_recursive([2,3])
X.add_recursive([0,3])

Y = X

We then build a simplicial map from X to Y which is extended from a reflection of the vertices.

[31]:
f0 = [2, 1, 0, 3]
F = bats.SimplicialMap(X, Y, f0)

The map is extended by sending vertex i in X to vertex f0[i] in Y. Next, we can apply the chain functor. We’ll use F3 coefficients.

[32]:
CX = bats.Chain(X, bats.F3())
CY = bats.Chain(Y, bats.F3())
CF = bats.Chain(F, bats.F3())

Finally, we can compute homology of the chain complexes and the induced maps.

[41]:
RX = bats.reduce(CX)
RY = bats.reduce(CY)

for k in range(RX.maxdim()+1):
    HFk = bats.InducedMap(CF, RX, RY, k)
    print("induced map in dimension {}:".format(k))
    print(HFk.tolist())
induced map in dimension 0:
[[1]]
induced map in dimension 1:
[[2]]

The induced map in dimension 0 is the identity. The induced map in dimension 1 is multiplication by 2 = -1 mod 3

Zigzag Homology

We’ll now compute a simple zigzag barcode, using the above example. We’ll consider a diagram with two (identical) spaces, connected by a single edge which applies the reflection map in the above example.

[43]:
D = bats.SimplicialComplexDiagram(2,1) # diagram with 2 nodes, 1 edge
D.set_node(0, X)
D.set_node(1, Y)
D.set_edge(0, 0, 1, F) # edge 0: F maps from node 0 to node 1

We can now apply the Chain and Hom functors to obtain a diagram of homology vector spaces and maps between them

[47]:
CD = bats.ChainFunctor(D, bats.F3())

[51]:
HD = bats.Hom(CD, 1) # computes homology in dimension 1
ps = bats.barcode(HD, 1) # extracts barcode
for p in ps:
    print(p)
1 : (0,1) <0,0>

This indicates there is a 1-dimensional homology bar, which is born in the space with index 0 and survives until the space with index 1. The <0,0> indicates which generators are associated with the homology class in the diagram.