Counting Operations in BATS

Counting field arithmetic operations and column operations can be useful when investigating performance of different algorithms.

To compile BATS to count operations, you need to pass the compile flag -DBATS_OPCOUNT when installing BATS.

CFLAGS="-DBATS_OPCOUNT" python setup.py build_ext --force -j8
python setup.py install

Note that this currently will only work with the F2 field, and you must produce a ReducedChainComplex without going through the reduce interface. It will also slow down the code, so it is not turned on by default.

[1]:
import numpy as np
import matplotlib.pyplot as plt
import scipy.spatial.distance
import bats

np.random.seed(0)
[2]:
bats.reset_field_ops()
bats.reset_column_ops()
bats.get_field_ops(), bats.get_column_ops()
[2]:
(0, 0)
[3]:
a = bats.F2(1)
b = a + a
bats.get_field_ops()
[3]:
1

Example with Rips Reduction

[4]:
# first, generate a circle
n = 100
X = np.random.normal(size=(n,2))
X = X / np.linalg.norm(X, axis=1).reshape(-1,1)
X = X + np.random.normal(size=(n,2), scale = 0.1 )
fig = plt.scatter(X[:,0], X[:,1])
fig.axes.set_aspect('equal')
# plt.savefig('RipsEx_data.png')
plt.show(fig)
../_images/tutorials_opcount_5_0.png
[5]:
data = bats.DataSet(bats.Matrix(X)) # put into a bats.DataSet
dist = bats.Euclidean()  # distance we would like to use
F = bats.RipsFiltration(data, dist, np.inf, 2) # generate a RipsFiltration
[6]:
bats.reset_field_ops(), bats.reset_column_ops()
C = bats.FilteredF2ChainComplex(F)
bats.get_field_ops(), bats.get_column_ops()
[6]:
(0, 0)

Options for the Reduction Algorithm

First, we’ll try the standard reduction algorithm (with basis)

[7]:
bats.reset_field_ops(), bats.reset_column_ops()
RC = bats.ReducedFilteredF2ChainComplex(C)
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 19759887
column operations: 4984028
[8]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_10_0.png

Next the standard reduction algorithm with no basis - the column operations are cut in half.

[9]:
bats.reset_field_ops(), bats.reset_column_ops()
RC = bats.ReducedFilteredF2ChainComplex(C, bats.standard_reduction_flag())
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 15465396
column operations: 2492014
[10]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_13_0.png

Now, let’s try the clearing optimization

[11]:
bats.reset_field_ops(), bats.reset_column_ops()
RC = bats.ReducedFilteredF2ChainComplex(C, bats.standard_reduction_flag(), bats.clearing_flag())
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 15341005
column operations: 2462129
[12]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_16_0.png

And finally, the extra reduction flag with clearing really decreases the number of column operations

[13]:
bats.reset_field_ops(), bats.reset_column_ops()
RC = bats.ReducedFilteredF2ChainComplex(C, bats.extra_reduction_flag(), bats.clearing_flag())
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 19686497
column operations: 496491
[14]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_19_0.png

Example Updating Persistence

In this example, we update the level set persistence of an image

[15]:
n = 100

img = np.empty((n,n), dtype=np.float64)
for i in range(n):
    for j in range(n):
        img[i,j] = np.sin(10* np.pi * i / n) + np.cos(10* np.pi * j/n)

img2 = img + 0.1 * np.random.randn(n,n)
fig, ax = plt.subplots(1,2)
ax[0].imshow(img)
ax[1].imshow(img2)
plt.show()
../_images/tutorials_opcount_21_0.png

First, we compute the reduced chain complex for the original image

[16]:
X = bats.Freudenthal(n,n)

# extend image filtration to Freudenthal triangulation
vals, imap = bats.lower_star_filtration(X, img.flatten())
F = bats.FilteredSimplicialComplex(X, vals)

bats.reset_field_ops(), bats.reset_column_ops()
C = bats.FilteredF2ChainComplex(F)
RC = bats.ReducedFilteredF2ChainComplex(C)
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 1501302
column operations: 530266
[17]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_24_0.png

Now, let’s update persistence

[18]:
vals, imap = bats.lower_star_filtration(X, img2.flatten())

bats.reset_field_ops(), bats.reset_column_ops()
RC.update_filtration(vals)
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 325913
column operations: 40158
[19]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_27_0.png

And compare to running the updated reduction from scratch

[20]:
bats.reset_field_ops(), bats.reset_column_ops()
vals, imap = bats.lower_star_filtration(X, img2.flatten())
F = bats.FilteredSimplicialComplex(X, vals)
C = bats.FilteredF2ChainComplex(F)
RC = bats.ReducedFilteredF2ChainComplex(C)
print("field operations: {}".format(bats.get_field_ops()))
print("column operations: {}".format(bats.get_column_ops()))
field operations: 1779741
column operations: 523044
[21]:
ps = RC.persistence_pairs(0) + RC.persistence_pairs(1)
bats.persistence_diagram(ps)
plt.show()
../_images/tutorials_opcount_30_0.png
[ ]: