SimplicialComplex: An abstract topological space

class simplicial.SimplicialComplex(rep=None)

The base class for a finite abstract simplicial complex.

A simplicial complex is a generalisation of a network in which vertices (0-simplices) and edges (1-simplices) can be composed into triangles (2-simplices), tetrahedra (3-simplices) and so forth. This class actually implements closed simplicial complexes that contain every simplex, every face of that simplex, every face of those simplices, and so forth. Operations to add and remove simplices cascade to keep the complex closed: if a simplex is an element of a complex, then all its faces are also elements, and so on recursively.

This class includes the entire interface for simplicial complexes, but defers the implementation to a representation class. By default this is ReferenceRepresentation, but this can be changed to use alternate representations if required.

Parameters

rep (Optional[Representation]) – (optional) the representation to use for this complex

Adding simplices

A simplex is added to a complex by providing some or all of the following information: a name for the simplex, a list of its faces, and a dict of attributes.

SimplicialComplex.addSimplex(fs=[], id=None, attr=None)

Add a simplex to the complex whose faces are the elements of fs. The faces must all be distinct. If no faces are given then the simplex is a 0-simplex (point). If no id is provided one is created. If present, attr should be a dict of attributes for the simplex.

To add a simplex from its basis (rather than its faces) use addSimplexByBasis().

Parameters
  • fs (List[Any]) – (optional) a list of faces of the simplex

  • id (Optional[Any]) – (optional) name for the simplex

  • attr (Optional[Dict[str, Any]]) – (optional) dict of attributes

Return type

Any

Returns

the name of the new simplex

Since a simplex is uniquely defined by its basis, we can simply provide the basis and let simplicial work out all the other simplices that need to be added. This can be a major simplification for higher-order simplices.

SimplicialComplex.ensureBasis(bs, attr=None)

For a given set of simplices, ensure they constitute a basis. This means that any simplices already present must be 0-simplices, and any missing ones will be created.

Parameters
  • bs (List[Any]) – the simplices

  • attr (Optional[Dict[str, Any]]) – (optional) attributes for any simplices added

SimplicialComplex.addSimplexWithBasis(bs, id=None, attr=None)

Add a simplex by providing its basis, which uniquely defines it. This method adds all the simplices necessary to define the new simplex, using simplexByBasis() to find and re-use any that are already in the complex. All simplices created (including the top-most one) are given the attributes provided.

To add a simplex defined by its faces, use addSimplex().

Defining a k-simplex requires a basis of (k + 1) 0-simplices.

Parameters
  • bs (List[Any]) – the basis

  • id (Optional[Any]) – (optional) the name of the new simplex (synthesised if omitted)

  • attr (Optional[Dict[str, Any]]) – (optional) dict of attributes (defaults to none)

Return type

Any

Returns

the name of the new simplex (which will be id if provided)

There are some operations that create new simplices from old.

SimplicialComplex.barycentricSubdivide(simplex)

Performs Barycentric subdivision on a simplex. This deletes the \(k\)-simplex, introduces a new 0-simplex, and creates \((k + 1)\) new \(k\)-simplices from the original basis and the new base point.

Parameters

simplex (Any) – the simplex to subdivide

Return type

Any

Returns

the new basis point

Finally, it is also possible to copy simplices from one complex to another. This will cause problems if the two complexes share simplices with common names (since names must be unique), in which case a renaming mapping can be applied automatically during the copy process.

SimplicialComplex.addSimplicesFrom(c, rename=None)

Add simplices from the given complex. The rename parameter is an optional mapping of the names in c that can be provided as a dict of old names to new names or a function from names to names.

If the relabeling is a dict it may be incomplete, in which case simplices retain their names. (If the relabeling is a function, it has to handle all names.)

This operation is equivalent to copying the other complex, re-labeling it using relabel() and then copying it into this complex directly. The caveats on attributes containing simplex names mentioned in respect to relabel() apply to addSimplicesFrom() too.

Parameters
  • c (SimplicialComplex) – the other complex

  • rename (Union[Dict[Any, Any], Callable[[Any], Any], None]) – (optional) renaming dict or function

Return type

List[Any]

Returns

a list of simplex names

Simplex attributes

Each simplex can have a dict of attributes associated with it.

SimplicialComplex.getAttributes(s)

Return the attributes associated with the given simplex.

Parameters

s (Any) – the simplex

Return type

Dict[str, Any]

Returns

a dict of attributes

SimplicialComplex.setAttributes(s, attr)

Set the attributes associated with a simplex.

Parameters
  • s (Any) – the simplex

  • attr (Dict[str, Any]) – a dict of attributes

SimplicialComplex.__getitem__(s)

Return the attributes associated with the given simplex. Equivalent to getAttributes().

Parameters

s (Any) – the simplex

Return type

Dict[str, Any]

Returns

a dict of attributes

SimplicialComplex.__setitem__(s, attr)

Set the attributes associated with a simplex. Equivalent to setAttributes().

Parameters
  • s (Any) – the simplex

  • attr (Dict[str, Any]) – a dict of attributes

Deleting simplices

Since SimplicialComplex represents closed simplicial complexes – in which all the faces of all the simplices in a complex are also in the complex – deletion requires that we cascade deletions to delete all the simplices of which the to-be-deleted simplex is a part (a face, or the face of a face, and so forth).

SimplicialComplex.deleteSimplex(s)

Delete a simplex and all simplices of which it is a part.

Parameters

s (Any) – the simplex

SimplicialComplex.deleteSimplices(ss)

Delete all simplices in the given list.

Parameters

ss (List[Any]) – the simplices

SimplicialComplex.deleteSimplexWithBasis(bs)

Delete the simplex with the given basis.

Parameters

bs (List[Any]) – the basis

Relabelling simplices

Simplex labels are often meaningful – but not always. They can be relabelled using either functions or dicts, or simply to make them all unique relative to another complex. This is handy when composing complexes.

SimplicialComplex.relabelSimplex(s, q)

Relabel a simplex. This is a simple form of relabel() working on individual simplices. The simplex q mustn’t exist in the complex.

Parameters
  • s (Any) – the simplex to rename

  • q (Any) – the new name

SimplicialComplex.relabel(rename)

Re-label simplices using the given relabeling, which may be a dict from old names to new names or a function taking a name and returning a new name.

If the relabeling is a dict it may be incomplete, in which case unmentioned simplices retain their names. (If the relabeling is a function, it has to handle all names.)

The relabelling function is guaranteed to only ever be called once for every simplex, so it’s completely acceptable to pass a function that just returns a sequence of unique identifiers.

In both cases, relabel() will complain if the relabeling generates as a “new” name a name already in the complex. (This detection isn’t completely foolproof: just don’t do it.)

(Be careful with attributes: if a simplex has an attribute the value of which is the name of another simplex, then renaming will destroy the connection and lead to problems.)

The dict returned maps old simplex names to new ones for only those simp[lices whose names were actually changed. All other simplices will have their names left alone.

Parameters

rename (Union[Dict[Any, Any], Callable[[Any], Any]]) – the relabeling, a dict or function

Return type

Dict[Any, Any]

Returns

a mapping from old to new simplex names, for those that changed

SimplicialComplex.relabelDisjointFrom(c)

Relabel this complex to have no labels in common with the complex given. This ensures that, if the two complexes are composed using compose(), they will not have any simplices merged.

The labels really will be meaningless, but the dict returned is a mapping from original to new names for those simplices whose names were actually changed: others are left as they were. The change set should be minimal, changing only those names that need to be changed to avoid collisions.

Parameters

c (SimplicialComplex) – a complex

Return type

Dict[Any, Any]

Returns

a mapping of old to new simplex names, for those that actually changed

Copying and equality

Complexes can be freely copied, creating independent versions of the same complex.

SimplicialComplex.copy(c=None)

Return a copy of this complex. The two complexes have the same simplices, attributes, and topology, and can be modified independently.

If the copy is put into an existing complex, there must not be any simplex names in common. To deal with the case of two complexes with simplices that should be fused on the basis of their labels, use compose().

If no target complex is provided, a new complex is created using the same representation as ourselves.

Parameters

c (Optional[SimplicialComplex]) – (optional) the complex to copy to (defaults to a new complex)

Return type

SimplicialComplex

Returns

a copy of this complex

They can also be tested for equality.

SimplicialComplex.__eq__(c)

True if the two complexes have the same simplices with the same relationships. Note that this does not compare simplex attributes: it’s purely topological equality.

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if the two complexes are equal

SimplicialComplex.__ne__(c)

True if the two complexes differ topologically. The dual of __eq__(). Note that this does not compare simplex attributes.

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if the two complexes are unequal

(See later for other comparison operators relating to sub-complexes.)

Querying the structure of the complex

SimplicialComplex.orderOf(s)

Return the order of a simplex.

Parameters

s (Any) – the simplex

Return type

int

Returns

the order of the simplex

SimplicialComplex.indexOf(s)

Return the unique-within-its-order index of the given simplex. Indices form a dense sequence. The index isn’t robust to changes in the complex, so the index returned for a simplex may change if simplices are added or removed.

Note that indices are unique only within the same order, not across all simplices; simplex names are however globally unique.

Parameters

s (Any) – the simplex

Return type

int

Returns

an index

SimplicialComplex.maxOrder()

Return the largest order of simplices in the complex, that is to say, the largest order for which a call to simplicesOfOrder() will return a non-empty list.

Return type

int

Returns

the largest order that contains at least one simplex, or -1

SimplicialComplex.numberOfSimplices()

Return the number of simplices in the complex.

Return type

int

Returns

the number of simplices

SimplicialComplex.__len__()

Return the size of the complex, a synonym for numberOfSimplices().

Return type

int

Returns

the size of the complex

SimplicialComplex.numberOfSimplicesOfOrder()

Return a list of the number of simplices of each order in the complex. Use simplicesOfOrder() to retrieve the actual simplices.

Return type

List[int]

Returns

a list of number of simplices at each order

SimplicialComplex.isBasis(bs, fatal=False)

Return True if the given set of simplices is a basis, that is, a set of 0-simplices. The simplices must already exist in the complex. The operation returns a boolean unless fatal is True, in which case a missing or non-basis element will raise an exception.

Parameters
  • bs (List[Any]) – the simplices

  • fatal (bool) – (optional) make missing or higher-order simplices fatal (defaults to False)

Returns

True if the given simplices form a basis

Retrieving simplices

Simplices can be retrieved in a large number of ways. When we return a collection of simplices that are always of the same order (such as faces or basis) we return a Python set; if we potentially return simplices of different orders we return a list, sorted in terms of the simplex order (low-order simplices first by default).

SimplicialComplex.simplices(reverse=False)

Return all the simplices in the complex. The simplices are returned in order of their orders, 0-simplices first unless the reverse parameter is True, in which case 0-simplices will be last.

Parameters

reverse (bool) – (optional) reverse the sort order if True

Return type

List[Any]

Returns

a list of simplices

SimplicialComplex.simplicesOfOrder(k)

Return all the simplices of the given order. This will be empty for any order greater than that returned by maxOrder().

The simplices are returned in “canonical” order, meaning the order they appear in the boiundary operator matrices.

Parameters

k (int) – the desired order

Return type

List[Any]

Returns

a set of simplices, which may be empty

SimplicialComplex.allSimplices(p, reverse=False)

Return all the simplices that match the given predicate, which should be a function from a complex and a simplex to a boolean. The simplices are sorted according to their orders, but with no guarantees of ordering between simplices of the same order.

Parameters
  • p (Callable[[SimplicialComplex, Any], bool]) – a predicate

  • reverse (bool) – (optional) reverse the order

Return type

List[Any]

Returns

the set of simplices satisfying the predicate

SimplicialComplex.anySimplex(p)

Return some simplex for which the predicate is true. The predicate should be a function from a complex and a simplex to a boolean. There are no guarantees as to what simplex, of what order, will be simplex chosen, just that it matches the predicate.

Parameters

p (Callable[[SimplicialComplex, Any], bool]) – a predicate

Return type

Optional[Any]

Returns

a simplex matching the predicate, or None

SimplicialComplex.simplexWithBasis(bs, fatal=False)

Return the simplex with the given basis, if it exists in the complex. If no such simplex exists, or if the given set is not a basis, then None is returned; if fatal is True, then an exception is raised instead.

Parameters
  • bs (List[Any]) – the basis

  • fatal (bool) – (optional) make failure raise an exception (defaults to False)

Return type

Any

Returns

the simplex or None

SimplicialComplex.containsSimplex(s)

Test whether the complex contains the given simplex.

Parameters

s (Any) – the simplex

Return type

bool

Returns

True if the simplex is in the complex

SimplicialComplex.containsSimplexWithBasis(bs)

Test whether the complex contains a simplex with the given basis.

Params bs

the basis

Return type

bool

Returns

True is the complex contains a simplex with this basis

SimplicialComplex.faces(s)

Return the faces of a simplex.

Parameters

s (Any) – the simplex

Return type

Set[Any]

Returns

a set of faces

SimplicialComplex.cofaces(s)

Return the simplices the given simnplex is a face of. This is not transitive: all the simplices returned will be of an order one greater than the given simplex.

This is a synonym for faceOf(). The transitive closure of cofaces() is partOf().

Parameters

s (Any) – the simplex

Return type

Set[Any]

Returns

a list of simplices

SimplicialComplex.faceOf(s)

Return the simplices that the given simplex is a face of. A synonym of cofaces().

Parameters

s (Any) – the simplex

Return type

Set[Any]

Returns

a list of simplices

SimplicialComplex.partOf(s, reverse=False, exclude_self=False)

Return the transitive closure of all simplices of which the simplex is part: a face of, or a face of a face of, and so forth. This is the dual of closureOf(). If exclude_self is False (the default), the set include the simplex itself. The simplices are returned in increasing order unless reverse is True, in which case they are returned largest order first.

In some of the topology literature this operation is called the star.

This method is essentially the dual of closureOf(), looking up the simplex orders rather than down.

Parameters
  • s (Any) – the simplex

  • reverse (bool) – (optional) reverse the sort order (defaults to False)

  • exclude_self (bool) – (optional) exclude the simplex itself (default to False)

Return type

Set[Any]

Returns

the list of simplices the simplex is part of

SimplicialComplex.basisOf(s)

Return the basis of a simplex, the set of 0-simplices that define it. Is s is an 0-simplex then it is its own basis

Parameters

s (Any) – the simplex

Return type

Set[Any]

Returns

the set of 0-simplices that form the basis of s

SimplicialComplex.closureOf(s, reverse=False, exclude_self=False)

Return the closure of a simplex. The closure is defined as the simplex plus all its faces, transitively down to its basis. If exclude_self is True, the closure excludes the simplex itself.

This method is essentially the dual of partOf(), looking down the simplex orders rather than up.

By default the simplices are returned in increasing order, basis first: setting reverse to True will generate the simplices in decreasing order, highest order first.

Parameters
  • s (Any) – the simplex

  • reverse (bool) – (optional) return simplex in decreasing order (defaults to False)

  • exclude_self (bool) – (optional) exclude the simplex itself (defaults to False)

Return type

Set[Any]

Returns

the closure of the simplex

Sub-complexes

A sub-complex is created by removing some (or all) of the simplices from an existing complex. The most common approach is to restrict the basis of the complex to some sub-set of 0-simplices; another common approach is to select this basis using some predicate (for example using allSimplices()) and then restricting the complex to all simplices that can be built from this basis.

SimplicialComplex.restrictBasisTo(bs)

Restrict the complex to include only those simplices whose bases are wholly contained in the given set of 0-simplices. All other simplices, including other 0-simplices, are deleted.

Parameters

bs (List[Any]) – the basis

Return type

SimplicialComplex

Returns

the complex

We define several relationships related to sub-complexes, all based around the idea of one complex being “less than” another when the latter contains all for former’s simplices in the same topological rrelationships.

SimplicialComplex.isSubComplexOf(c)

True if this complex is a (possibly equal) sub-complex of c. This is defined as all the simplices in this complex appear in c, with the same topological relationships.

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if this is a sub-complex of c

SimplicialComplex.__lt__(c)

True if this complex is a strictly smaller sub-complex of c.

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if this is a sub-complex of c and with fewer simplices

SimplicialComplex.__le__(c)

True if this complex is a (possibly equal) sub-complex of c.

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if this is a sub-complex of c

SimplicialComplex.__gt__(c)

True if c is a structly smaller sub-complex of this one. The dual of __lt__().

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if c is a sub-complex of this and has fewer simplices

SimplicialComplex.__ge__(c)

True if c is a sub-complex of this one. The dual of __le__().

Parameters

c (SimplicialComplex) – the other complex

Return type

bool

Returns

True if c is a sub-complex of this

Global properties

Various global properties of complexes can be defined that provide an overview of its topological structure.

SimplicialComplex.eulerCharacteristic()

Return the Euler characteristic of this complex.

Return type

int

Returns

the Euler characteristic

Homology

One can regard homology simply as a more sophisticated version of the Euler characteristic: a way of counting the holes in a structure at various dimensions. Unlike the Euler characteristic, homology is able to differentiate between a triangulated portion of the plane and two triangulated “islands” one of which contains a hole. The Euler characteristic of both these structures is 1, but they have different homology groups and therefore different Betti numbers.

In simplicial we implement the simplest possible version of simplicial homology in which simplices are treated as un-oriented, which leads to chain coefficients over a field \([0, 1]\). This massively simplifies both the calculations and the explanations.

SimplicialComplex.isChain(ss, p=None, fatal=False)

Test whether the given set of simplices is a p-chain for some order p. A p-chain is a collection of simplices of order p. If fatal is True then any missing or wrong-order simplices raise an exception.

A basis is an 0-chain (see isBasis()).

Parameters
  • ss (Set[Any]) – the simplices

  • p (Optional[int]) – (optional) the order (defaults to the order of the first simplex in the chain)

  • fatal (bool) – (optional) raise an exception for invalid chains (defaults to False)

Return type

bool

Returns

True if the simplices form a p-chain

SimplicialComplex.boundary(ss)

Return the boundary of the given p-chain. This will be a (p - 1)-chain of simplices from the complex.

Parameters

ss (Set[Any]) – a chain (list) of simplices

Return type

Set[Any]

Returns

the boundary of the chain

SimplicialComplex.boundaryOperator(k)

Return the boundary operator of the k-simplices in the complex (usually denoted \(\partial_k\)) as a numpy matrix. The columns correspond to simplices of order k while rows correspond to simplices of order (k - 1). The matrix has a 1 when a (k - 1) simplex is a face of the corresponding k-simplex, and 0 otherwise.

The boundary of the 0-simplices is a matrix with one row, all zeros. The boundary of an order greater than the maximum order of the complex is a 0x0 matrix.

Parameters

k (int) – the order of simplices

Return type

ndarray

Returns

the boundary matrix

SimplicialComplex.disjoint(ss)

Test whether the elements of a set of simplices are disjoint, defined as if they share no common simplices in their closures. (This doesn’t mean that they aren’t part of a common super-simplex, however.) The simplices need not be of the same order, i.e., need not form a p-chain.

Parameters

ss (Set[Any]) – the simplices

Return type

bool

Returns

True if the simplices are disjoint

SimplicialComplex.smithNormalForm(k)

Reduce a boundary operator matrix to Smith Normal Form, which has a leading diagonal of ones for some prefix of its leading diagonal and is zero everywhere else.

Parameters

k (int) – the order of the boundary operator

Return type

ndarray

Returns

the Smith Normal Form of the boundary operator matrix

SimplicialComplex.bettiNumbers(ks=None)

Return a dict of Betti numbers for the different dimensions of the complex.

Parameters

ks (Optional[List[int]]) – (optional) dimensions to compute (defaults to all)

Return type

List[int]

Returns

a dict of Betti numbers

SimplicialComplex.Z(ks=None)

Return a list of the basis of the group of non-boundary k-chains for the requested orders, usually denoted \(Z_k\).

Parameters

ks (Optional[List[int]]) – (optional) dimensions of holes (defaults to all)

Return type

Dict[int, Set[Any]]

Returns

a dict of lists of p-chains

Deriving new complexes

New complexes can be derived from old ones by various processes. These operations are also used to create abstract complexes from “concrete” complexes that have an associated geometric embedding via the Embedding class.

The flag complex is a complex that includes all higher simplices whose faces are present. Essentially it “fills in the holes”: three sides of a triangle will result in the triangle 2-simplex being created, and so on. It is possible to create the flag complex from scratch, or incrementally by filling-in any holes left after creating new simplices.

SimplicialComplex.flagComplex()

Generate the flag complex of this complex. The flag complex is formed by creating all the “implied” simplices for which all faces are present. For example, three 1-simplices forming an (empty) triangle will be “closed” by creating a 2-simplex with them as its faces. This may in turn allow a further 3-simplex to be formed if the new 2-simplex closes a tetrahedron, and so forth.

Return type

SimplicialComplex

Returns

the flag complex

SimplicialComplex.growFlagComplex(newSimplices)

Grow the flag complex incrementally by filling-in any higher-order simplices formed by the addition of a set of new simplices to the complex.

The set of new simplices can contain simplices of any order, although 0-simplices will have no effect. For a lot of applications (for example for building a Vietoris-Rips complex) the new simplices will all be 1-simplices (edges).

Parameters

newSimplices (Set[Any]) – a collection of new simplices

Composing complexes

Complexes can be combined to form larger complexes. The basic operation works by fusing two complexes on the basis of simplex labels: use relabelling to make disjoint complexes, and then set the corresponding simplices.

SimplicialComplex.compose(c, d=None)

Compose the complex c with us, generating a new complex. The complex labels need not be disjoint: if two k-simplices have the same label and basis, they will be unified. If the simplex names are all disjoint, then this is equivalent to copy().

If the target complex d is provided, the current complex is first copied into it using copy().

If two simplices are merged their attributes are also merged, with those of the simplex in c overwriting those in us.

Parameters
Return type

SimplicialComplex

Returns

a new complex

Representation

The representation of the complex is occasionally needed when sub-classing, but in general should be transparent once set.

SimplicialComplex.representation()

Returns the representation used by this complex,

Return type

Representation

Returns

the representation

Use of exceptions

SimplicialComplex does not define any new exceptions. It raises KeyError if a simplex if referenced that is not present, or if a basis is used to retrieve a simplex that isn’t defined for it. It raises ValueError for problems with the orders of simplices and other structure errors.