Using Aleph to calculate the homology of 2-manifolds
Tags: programming, projects, research
In a previous article, I discussed the distribution of Betti numbers of triangulations of 2-manifolds. This article now discusses the code. Coincidentally, it also demonstrates how to use Aleph, my (forthcoming) library for exploring persistent homology and its uses.
Aleph is header-only, so it can be readily deployed in your own
projects. In the following, I am assuming that you have installed Aleph,
following the official instructions.
There are several tutorials available that cover different parts of the persistent homology calculation process. For our purpose, viz. the calculation of homology of triangulated spaces, no large machinery is needed. We start with some includes and using
directives:
#include <aleph/topology/io/LexicographicTriangulation.hh>
#include <aleph/persistentHomology/Calculation.hh>
#include <aleph/topology/Simplex.hh>
#include <aleph/topology/SimplicialComplex.hh>
#include <iostream>
#include <string>
#include <vector>
using DataType = bool;
using VertexType = unsigned short;
using Simplex = aleph::topology::Simplex<DataType, VertexType>;
using SimplicialComplex = aleph::topology::SimplicialComplex<Simplex>;
Nothing fancy happened so far. We set up a simplex class, which is the
basic data type in most applications, as well as a simplicial complex,
which is the basic storage class for multiple simplices. The only
interesting thing is our choice of DataType
and VertexType
. Since
our simplices need not store any additional data except for their
vertices, we select bool
as a data type in order to make the class
smaller. This could potentially be achieved with EBCO
as well, but I did not yet have time to test it adequately. In addition
to the data type, we use unsigned short
as the vertex type—the
triangulations that we want to analyse only feature 9 or 10 vertices, so
unsigned short
is the best solution for storing vertex identifiers.
Next, we need some I/O code to read a lexicographic triangulation:
int main(int argc, char* argv[])
{
if( argc <= 1 )
return -1;
std::string filename = argv[1];
std::vector<SimplicialComplex> simplicialComplexes;
aleph::topology::io::LexicographicTriangulationReader reader;
reader( filename, simplicialComplexes );
for( auto&& K : simplicialComplexes )
{
K.createMissingFaces();
K.sort();
}
}
Here, we used the LexicographicTriangulationReader
, a reader class that supports reading files in the format defined by Frank H. Lutz. However, this format only provides the top-level simplices of a triangulation. Hence, for a 2-manifold, only the 2-simplices are specified. For calculating homology groups, however, all simplices are required. Luckily, the SimplicialComplex
class offers a method for just this purpose. By calling createMissingFaces()
, all missing faces of the simplicial complex are calculated and added to the simplicial complex. Afterwards, we use sort()
to sort simplices in lexicographical order. This order is required to ensure that the homology groups can be calculated correctly—the calculation routines assume that the complex is being "filtrated", so faces need to precede co-faces.
Having created and stored a list of simplicial complexes, we may now finally calculate their homology groups by adding the following code after the last for
-loop:
for( auto&& K : simplicialComplexes )
{
bool dualize = true;
bool includeAllUnpairedCreators = true;
auto diagrams
= aleph::calculatePersistenceDiagrams( K,
dualize,
includeAllUnpairedCreators );
}
This code calls calculatePersistenceDiagrams()
, which is usually
employed to calculate, well, a set of persistence diagrams. The two
flags dualize
and includeAllUnpairedCreators
also deserve some
explanation. The first flag only instructs the convenience function as
to whether the boundary matrix that is required for (persistent)
homology calculations should be dualized or not. Dualization was
shown to result
in faster computations, so of course we want to use it as well. The
second parameter depends on our particular application scenario.
Normally, the persistent homology calculation ignores all creator
simplices in the top dimension. The reason for this is simple: if we
expand a neighbourhood graph to a Vietoris–Rips complex, the
top-level simplices are an artefact of the expansion process. Most of
these simplices cannot be paired with higher-dimensional simplices,
hence they will appear to create a large number of high-dimensional
holes. The persistent homology calculation convenience function hence
ignores those simplices for the creation of persistence diagrams by
default. For our application, however, keeping those simplices is
actually the desired behaviour—the top-level simplices are
well-defined and an integral part of the triangulation.
As a last step, we want to print the signature of Betti numbers for the
given simplicial complex. This is easily achieved by adding a nested
loop at the end of the for
-loop:
for( auto&& diagram : diagrams )
{
auto d = diagram.dimension();
std::cout << "b_" << d << " = " << diagram.betti() << "\n";
}
This will give us all non-zero Betti numbers of the given triangulation. The output format is obviously not optimal—in a research setting, I like to use JSON for creating human-readable and machine-readable results. If you are interested in how this may look, please take a look at a more involved tool for dealing with triangulations. Be aware, however, that this tool is still a work in progress.
This concludes our little foray into Aleph. I hope that I piqued your interest! By the way, I am always looking for contributors…