mathematicskit.topology#

Simplicial complexes with signed boundary matrices, standard triangulations (spheres, torus, Klein bottle, Möbius strip, projective plane), barycentric subdivision and products; the Smith normal form, Betti numbers over \(\mathbb{Q}\) or \(\mathbb{Z}/p\), integer homology with torsion, and the Mayer-Vietoris sequence; the fundamental group; orientability, the classification of surfaces and Banchoff’s critical points; Sperner’s lemma, Brouwer fixed points, vector-field indices and Lefschetz numbers; Gauss’s linking number and Hopf’s turning number; and Vietoris-Rips and Čech complexes, persistent homology, the bottleneck distance and Mapper.

mathematicskit.topology: simplicial complexes, homology, the fundamental group, surfaces, fixed points, and persistent homology.

Simplicial complexes with signed boundary matrices; standard triangulations of spheres, the torus, Klein bottle, Möbius strip and projective plane, barycentric subdivision and products; the Smith normal form, Betti numbers over Q (via numpy.linalg.matrix_rank) or Z/p, integer homology with torsion, and the Mayer-Vietoris sequence; the fundamental group as a presentation and its abelianization; orientability, the classification of surfaces and Banchoff’s critical points; Sperner’s lemma, Brouwer fixed points, vector-field indices and Lefschetz numbers; Gauss’s linking number and Hopf’s turning number; and Vietoris-Rips and Čech complexes, persistent homology, the bottleneck distance and Mapper. Euler’s polyhedron formula lives in mathematicskit.geometry.

class mathematicskit.topology.CriticalPointsResult(index, minima, saddles, maxima)[source]#

Bases: object

Banchoff’s critical points of a height function on the vertices of a complex.

The index of a vertex is the Euler characteristic of its lower star (the simplices whose highest vertex it is), \(1 - \chi(\text{lower link})\).

Parameters:
property euler_characteristic: int#

The total index, equal to the Euler characteristic of the complex.

Type:

int

index: ndarray#

The index of each vertex (0 for regular points).

Type:

ndarray of int

maxima: list#

Vertices whose lower link is their whole link, a sphere.

Type:

list of int

minima: list#

Vertices whose lower link is empty.

Type:

list of int

saddles: list#

Vertices of negative index.

Type:

list of int

class mathematicskit.topology.Filtration(simplices, values)[source]#

Bases: object

A nested sequence of simplicial complexes, as simplices with the value at which each appears.

Every face enters no later than its cofaces, and simplices are kept sorted by (value, dimension), so any prefix of simplices is a simplicial complex.

Parameters:
complex_at(t)[source]#

The subcomplex of simplices with value at most t.

Parameters:

t (float)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

simplices: list#

The simplices in order of appearance.

Type:

list of tuple of int

values: ndarray#

The filtration value at which each simplex enters.

Type:

ndarray

class mathematicskit.topology.FixedPointResult(point, triangle, n, residual)[source]#

Bases: object

An approximate fixed point from a fully labelled triangle of a Sperner labelling.

Parameters:
n: int#

Subdivisions per side; the triangle has side 1/n.

Type:

int

point: ndarray#

Barycentric coordinates of the approximate fixed point.

Type:

ndarray, shape (3,)

residual: float#

\(\max_i |f(x)_i - x_i|\) at point.

Type:

float

triangle: ndarray#

Barycentric corners of the fully labelled triangle containing it.

Type:

ndarray, shape (3, 3)

class mathematicskit.topology.FundamentalGroupPresentation(generators, relations=<factory>)[source]#

Bases: object

A presentation \(\langle g_1, \dots, g_n \mid r_1, \dots, r_m \rangle\) of \(\pi_1\).

Each relation is a word, a list of (generator, exponent) letters with exponent \(\pm 1\). str() writes generators as a, b, c, ... and inverses as capitals.

Parameters:
generators: list#

The edge (or derived label) behind each generator.

Type:

list

relations: list#

The relators, each equal to the identity.

Type:

list of list of (int, int)

word(relation)[source]#

Write a relation as a string, inverses in capitals (g12 / G12 past z).

Parameters:

relation (list of (int, int))

Return type:

str

Returns:

str

class mathematicskit.topology.HomologyResult(betti_numbers, torsion)[source]#

Bases: object

Integer homology \(H_k \cong \mathbb{Z}^{\beta_k} \oplus \mathbb{Z}/t_1 \oplus \dots\) in each dimension.

Parameters:
betti_numbers: tuple#

\(\beta_k\), the rank of the free part of \(H_k\).

Type:

tuple of int

property euler_characteristic: int#

\(\sum_k (-1)^k \beta_k\), equal to the alternating simplex count.

Type:

int

group(k)[source]#

Human-readable \(H_k\), e.g. "Z + Z/2".

Parameters:

k (int)

Return type:

str

Returns:

str

torsion: tuple#

The torsion coefficients of each \(H_k\) (invariant factors greater than 1).

Type:

tuple of tuple of int

class mathematicskit.topology.MapperResult(nodes, edges, node_values)[source]#

Bases: object

The Mapper graph of a point cloud: one node per cluster of each cover set, an edge where clusters share points.

Parameters:
edges: list#

Pairs of nodes whose clusters overlap.

Type:

list of (int, int)

property graph: SimplicialComplex#

The Mapper graph as a 1-dimensional complex.

Type:

SimplicialComplex

node_values: ndarray#

Mean lens value of each node.

Type:

ndarray

nodes: list#

The point indices in each node’s cluster.

Type:

list of ndarray

class mathematicskit.topology.MayerVietorisResult(betti_a, betti_b, betti_intersection, betti_union, rank_inclusion)[source]#

Bases: object

The Betti numbers in the Mayer-Vietoris sequence of \(X = A \cup B\) over \(\mathbb{Q}\).

Exactness at each term gives \(\beta_k(A \cup B) = \beta_k(A) + \beta_k(B) - \operatorname{rk} i_k + \beta_{k-1}(A \cap B) - \operatorname{rk} i_{k-1}\), where \(i_k: H_k(A \cap B) \to H_k(A) \oplus H_k(B)\) is induced by the inclusions.

Parameters:
betti_a: tuple#
betti_b: tuple#
betti_intersection: tuple#
betti_union: tuple#
property predicted_union: tuple[int, ...]#

The Betti numbers of the union predicted by exactness of the sequence.

Type:

tuple of int

rank_inclusion: tuple#

\(\operatorname{rk} i_k\) for each \(k\).

Type:

tuple of int

class mathematicskit.topology.PersistenceDiagram(pairs)[source]#

Bases: object

Birth-death pairs of a filtration’s homology classes, per dimension.

A class that never dies has death \(+\infty\).

Parameters:

pairs (dict)

betti_numbers(t)[source]#

The Betti numbers of the complex at filtration value t: intervals with birth \(\le t <\) death.

Parameters:

t (float)

Return type:

tuple[int, ...]

Returns:

tuple of int

diagram(k)[source]#

The (birth, death) pairs in dimension k, shape (n, 2).

Parameters:

k (int)

Return type:

ndarray

Returns:

ndarray

property max_dimension: int#

Highest homology dimension recorded.

Type:

int

pairs: dict#

{k: ndarray of shape (n, 2)}, the (birth, death) pairs of \(H_k\).

Type:

dict

persistence(k)[source]#

Lifetimes death - birth in dimension k.

Parameters:

k (int)

Return type:

ndarray

Returns:

ndarray

class mathematicskit.topology.SimplicialComplex(simplices=(), coordinates=None)[source]#

Bases: object

A finite abstract simplicial complex on integer vertices.

Built from any collection of simplices (vertex lists); every face of every simplex is added, so SimplicialComplex([[0, 1, 2]]) is the filled triangle with its three edges and three vertices. Simplices are stored as sorted tuples, which fixes the orientation used by boundary_matrix():

\[\partial_k [v_0, \dots, v_k] = \sum_{i=0}^{k} (-1)^i [v_0, \dots, \hat v_i, \dots, v_k].\]
Parameters:
  • simplices (Iterable) – The simplices (typically the maximal ones); faces are added automatically.

  • coordinates (array_like, shape (n_vertices, d), optional) – A position for each vertex label (row v for vertex v), used only for plotting and geometric constructions.

Examples

>>> hollow_triangle = SimplicialComplex([[0, 1], [1, 2], [0, 2]])
>>> hollow_triangle.f_vector, hollow_triangle.euler_characteristic
((3, 3), 0)
>>> hollow_triangle.boundary_matrix(1)
array([[-1, -1,  0],
       [ 1,  0, -1],
       [ 0,  1,  1]])
boundary_matrix(k)[source]#

The signed boundary matrix \(\partial_k: C_k \to C_{k-1}\), shape \((f_{k-1}, f_k)\).

Column j holds the boundary of the j-th k-simplex. \(\partial_0\) is the zero map to the trivial group, with no rows. Consecutive boundary matrices multiply to zero, \(\partial_{k-1}\partial_k = 0\).

Parameters:

k (int)

Return type:

ndarray

Returns:

ndarray of int

property coordinates: ndarray | None#

Vertex positions, row v for vertex v (assignable from any array-like).

Type:

ndarray or None

property dimension: int#

Largest simplex dimension (-1 for the empty complex).

Type:

int

property euler_characteristic: int#

\(\chi = \sum_k (-1)^k f_k\), the alternating count of simplices.

Type:

int

property f_vector: tuple[int, ...]#

\((f_0, f_1, \dots)\), the number of simplices of each dimension.

Type:

tuple of int

index(simplex)[source]#

Position of simplex among the simplices of its dimension (the row/column of the boundary matrices).

Parameters:

simplex (iterable of int)

Return type:

int

Returns:

int

induced_subcomplex(vertices)[source]#

The full subcomplex on vertices: every simplex all of whose vertices are in the set.

Parameters:

vertices (iterable of int)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

The link of a vertex: the faces \(\tau\) with \(v \notin \tau\) and \(\tau \cup \{v\}\) in the complex.

Parameters:

vertex (int)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

property maximal_simplices: list[tuple[int, ...]]#

The simplices that are not a face of any other simplex.

Type:

list of tuple of int

simplices(k=None)[source]#

The k-simplices in lexicographic order, or all simplices by dimension if k is omitted.

Parameters:

k (int | None)

Return type:

list[tuple[int, ...]]

Returns:

list of tuple of int

skeleton(k)[source]#

The subcomplex of all simplices of dimension at most k.

Parameters:

k (int)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

property vertices: list[int]#

The vertex labels, in increasing order.

Type:

list of int

class mathematicskit.topology.SmithNormalFormResult(D, U, V)[source]#

Bases: object

The Smith normal form \(D = UAV\) of an integer matrix.

\(U\) and \(V\) are unimodular (integer, determinant \(\pm 1\)) and \(D\) is diagonal with \(d_1 \mid d_2 \mid \dots \mid d_r\), all positive.

Parameters:
D: ndarray#

The diagonal form, same shape as the input.

Type:

ndarray

U: ndarray#

Unimodular row transformation.

Type:

ndarray

V: ndarray#

Unimodular column transformation.

Type:

ndarray

property invariant_factors: list[int]#

The nonzero diagonal entries \(d_1 \mid d_2 \mid \dots\).

Type:

list of int

property rank: int#

Number of nonzero invariant factors.

Type:

int

class mathematicskit.topology.SurfaceClassification(name, orientable, euler_characteristic, boundary_components, genus)[source]#

Bases: object

Where a compact connected surface sits in the classification theorem.

Parameters:
  • name (str)

  • orientable (bool)

  • euler_characteristic (int)

  • boundary_components (int)

  • genus (int)

boundary_components: int#

Number of boundary circles.

Type:

int

euler_characteristic: int#

\(\chi = V - E + F\).

Type:

int

genus: int#

Number of handles (orientable) or of cross-caps (non-orientable).

Type:

int

name: str#

E.g. "torus", "Klein bottle", "Möbius strip".

Type:

str

orientable: bool#

Whether a consistent orientation of the triangles exists.

Type:

bool

class mathematicskit.topology.TriangleGrid(n, points, triangles)[source]#

Bases: object

The subdivision of the standard triangle into \(n^2\) small triangles.

Vertices are integer barycentric coordinates \((i, j, k)\) with \(i + j + k = n\).

Parameters:
property cartesian: ndarray#

Vertex positions in the plane, corners at \((0, 0), (1, 0), (\tfrac12, \tfrac{\sqrt3}{2})\).

Type:

ndarray, shape (N, 2)

n: int#

Number of subdivisions per side.

Type:

int

points: ndarray#

Barycentric coordinates times n.

Type:

ndarray of int, shape (N, 3)

triangles: ndarray#

Vertex indices of each small triangle.

Type:

ndarray of int, shape (n**2, 3)

mathematicskit.topology.abelianization(presentation)[source]#

The abelianized group \(\pi_1^{ab} \cong H_1\), e.g. "Z + Z/2".

Abelianizing turns each relation into a row of exponent sums; the group is \(\mathbb{Z}^n\) modulo the row space, read off from the Smith normal form of that matrix (Hurewicz: it equals \(H_1\)).

Parameters:

presentation (FundamentalGroupPresentation)

Return type:

str

Returns:

str

Examples

>>> from mathematicskit.topology.systems.complexes import klein_bottle
>>> abelianization(fundamental_group(klein_bottle(3, 3)))
'Z + Z/2'
mathematicskit.topology.barycentric_subdivision(K)[source]#

The barycentric subdivision \(\operatorname{sd} K\): one vertex per simplex, one simplex per chain of faces.

A \(k\)-simplex of \(\operatorname{sd} K\) is a chain \(\sigma_0 \subsetneq \sigma_1 \subsetneq \dots \subsetneq \sigma_k\). Vertex i of the result is the i-th simplex of K.simplices(), placed at its barycenter when K has coordinates. The space is unchanged, and so is its homology; that homology is a topological invariant was proved by refining any map to a simplicial one on a fine enough subdivision (Brouwer 1911, Alexander 1915).

Parameters:

K (SimplicialComplex)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> barycentric_subdivision(simplex(2)).f_vector
(7, 12, 6)
mathematicskit.topology.betti_numbers(K, field=0)[source]#

The Betti numbers \(\beta_k = f_k - \operatorname{rk}\partial_k - \operatorname{rk}\partial_{k+1}\).

Enrico Betti’s connectivity numbers (1871), in Poincaré’s homological form. Over \(\mathbb{Q}\) (field=0) they count the free part of integer homology; over \(\mathbb{Z}/p\) they also pick up \(p\)-torsion, so the Klein bottle has \(\beta_1 = 1\) over \(\mathbb{Q}\) but 2 over \(\mathbb{Z}/2\). See E. Betti, “Sopra gli spazi di un numero qualunque di dimensioni,” Annali di Matematica Pura ed Applicata 4 (1871), 140-158.

Parameters:
Return type:

tuple[int, ...]

Returns:

tuple of int – \((\beta_0, \dots, \beta_{\dim K})\).

Examples

>>> from mathematicskit.topology.systems.complexes import sphere, torus
>>> betti_numbers(sphere(2)), betti_numbers(torus())
((1, 0, 1), (1, 2, 1))
mathematicskit.topology.bottleneck_distance(diagram1, diagram2)[source]#

The bottleneck distance \(\inf_\gamma \sup_x \|x - \gamma(x)\|_\infty\) between two persistence diagrams.

Points may be matched to each other or to their nearest point on the diagonal, at distance \((d - b)/2\). Classes that never die must be matched among themselves (otherwise the distance is infinite). The stability theorem bounds it by the size of a perturbation: for sublevel-set filtrations of \(f\) and \(g\), \(d_B \le \|f - g\|_\infty\). See D. Cohen-Steiner, H. Edelsbrunner and J. Harer, “Stability of persistence diagrams,” Discrete & Computational Geometry 37 (2007), 103-120.

Parameters:
  • diagram1 (array_like, shape (n, 2)) – (birth, death) pairs in one dimension, e.g. PersistenceDiagram.diagram(k).

  • diagram2 (array_like, shape (n, 2)) – (birth, death) pairs in one dimension, e.g. PersistenceDiagram.diagram(k).

Return type:

float

Returns:

float

Examples

>>> bottleneck_distance([[0, 4], [1, 2]], [[0, 3.5]])
0.5
mathematicskit.topology.boundary_components(K)[source]#

Number of boundary circles of a triangulated surface: components of the edges that lie in a single triangle.

Parameters:

K (SimplicialComplex) – A 2-dimensional pseudomanifold.

Return type:

int

Returns:

int

Examples

>>> from mathematicskit.topology.systems.complexes import mobius_strip, simplex
>>> boundary_components(simplex(2)), boundary_components(mobius_strip())
(1, 1)
mathematicskit.topology.brouwer_fixed_point(f, n=64)[source]#

An approximate fixed point of a continuous self-map of the triangle, via Sperner’s lemma.

Each grid vertex \(x\) is labelled with the first coordinate the map does not increase, \(\min\{i : x_i > 0,\ f(x)_i \le x_i\}\). That is a Sperner labelling (a label exists because both \(x\) and \(f(x)\) sum to 1), so a fully labelled small triangle exists, and on it every coordinate is decreased somewhere. As \(n \to \infty\) such triangles shrink onto fixed points, which is Brouwer’s theorem: every continuous map of a disc to itself has a fixed point. See L. E. J. Brouwer, “Über Abbildung von Mannigfaltigkeiten,” Mathematische Annalen 71 (1911), 97-115.

Parameters:
  • f (Callable) – f(x) -> y on barycentric coordinates: x and y are length-3 arrays of nonnegative numbers summing to 1.

  • n (int) – Subdivisions per side.

Return type:

FixedPointResult

Returns:

FixedPointResult – The centroid of the first fully labelled triangle.

Examples

>>> rotate = lambda x: np.roll(x, 1)
>>> result = brouwer_fixed_point(rotate, n=30)
>>> bool(np.allclose(result.point, 1 / 3, atol=2 / 30))
True
mathematicskit.topology.cech_complex(points, radius, max_dim=2)[source]#

The Čech complex of planar points: every set whose discs of radius \(r\) share a common point.

Discs meet exactly when the smallest circle enclosing their centres has radius at most \(r\), which Welzl’s algorithm decides. The candidates are the Vietoris-Rips simplices at the same radius, since Čech \(\subseteq\) Rips; conversely Rips at \(r\) is inside Čech at \(2r/\sqrt3\) (Jung’s theorem). By the nerve theorem the Čech complex has the homotopy type of the union of the discs. See E. Čech, “Théorie générale de l’homologie dans un espace quelconque,” Fundamenta Mathematicae 19 (1932), 149-183; K. Borsuk, “On the imbedding of systems of compacta in simplicial complexes,” ibid. 35 (1948), 217-234.

Parameters:
  • points (array_like, shape (n, 2))

  • radius (float)

  • max_dim (int)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> triangle = [[0, 0], [1, 0], [0.5, np.sqrt(3) / 2]]
>>> cech_complex(triangle, 0.55).f_vector, vietoris_rips_complex(triangle, 0.55).f_vector
((3, 3), (3, 3, 1))
mathematicskit.topology.circle(n=6)[source]#

The n-gon: the circle \(S^1\) with n vertices on the unit circle.

Parameters:

n (int) – Number of vertices, at least 3.

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> circle(5).f_vector
(5, 5)
mathematicskit.topology.classify_surface(K)[source]#

Identify a triangulated compact connected surface by the classification theorem.

Every such surface is a sphere with \(g\) handles (orientable) or with \(g\) cross-caps (non-orientable), with some number \(b\) of open discs removed. The triple (orientability, \(\chi\), \(b\)) determines it, since capping the boundary gives a closed surface with \(\chi + b = 2 - 2g\) or \(2 - g\). See H. R. Brahana, “Systems of circuits on two-dimensional manifolds,” Annals of Mathematics 23(2) (1921), 144-168.

Parameters:

K (SimplicialComplex) – Connected, 2-dimensional, with every vertex link a circle (interior) or an arc (boundary).

Return type:

SurfaceClassification

Returns:

SurfaceClassification

Examples

>>> from mathematicskit.topology.systems.complexes import klein_bottle, projective_plane, torus
>>> [classify_surface(K).name for K in (torus(), klein_bottle(), projective_plane())]
['torus', 'Klein bottle', 'projective plane']
mathematicskit.topology.clique_simplices(adjacency, max_dim)[source]#

All cliques of a graph with at most max_dim + 1 vertices, as sorted vertex tuples.

Parameters:
  • adjacency (array_like of bool, shape (n, n)) – Symmetric adjacency matrix (the diagonal is ignored).

  • max_dim (int) – Largest simplex dimension to return.

Return type:

list[tuple[int, ...]]

Returns:

list of tuple of int

Examples

>>> square_with_diagonal = np.array([[0, 1, 1, 1], [1, 0, 1, 0], [1, 1, 0, 1], [1, 0, 1, 0]], dtype=bool)
>>> [s for s in clique_simplices(square_with_diagonal, 2) if len(s) == 3]
[(0, 1, 2), (0, 2, 3)]
mathematicskit.topology.critical_points(K, heights)[source]#

Banchoff’s critical points of a height function given on the vertices.

Each simplex is charged to its highest vertex, so the simplices are partitioned into lower stars, and the index of vertex \(v\) is \(\sum_{\sigma \in \text{lower star}(v)} (-1)^{\dim \sigma}\). On a surface a minimum has index \(+1\), a maximum \(+1\), a saddle \(-1\) (a monkey saddle \(-2\)), and a regular point 0; summing over all vertices gives Euler’s \(\chi\) – the polyhedral form of Morse’s relation between critical points and topology. Ties in height are broken by vertex label. See T. F. Banchoff, “Critical points and curvature for embedded polyhedra,” Journal of Differential Geometry 1 (1967), 245-256.

Parameters:
  • K (SimplicialComplex)

  • heights (array_like) – heights[v] is the height of vertex v.

Return type:

CriticalPointsResult

Returns:

CriticalPointsResult

Examples

>>> from mathematicskit.topology.systems.complexes import torus
>>> T = torus(8, 6)
>>> result = critical_points(T, T.coordinates[:, 0])
>>> len(result.minima), len(result.saddles), len(result.maxima), result.euler_characteristic
(1, 2, 1, 0)
mathematicskit.topology.fully_labeled_triangles(grid, labels)[source]#

Indices of the small triangles whose corners carry all three labels.

Sperner’s lemma: for a Sperner labelling their number is odd, hence at least one. Counting “doors” (edges labelled {0, 1}) shows it: the bottom side has an odd number, and every small triangle has 0 or 2 doors unless it is fully labelled. See E. Sperner, “Neuer Beweis für die Invarianz der Dimensionszahl und des Gebietes,” Abhandlungen aus dem Mathematischen Seminar der Universität Hamburg 6 (1928), 265-272.

Parameters:
Return type:

ndarray

Returns:

ndarray of int

Examples

>>> grid = triangle_grid(1)
>>> fully_labeled_triangles(grid, [0, 1, 2]).tolist()
[0]
mathematicskit.topology.fundamental_group(K, base=None, simplify=True)[source]#

A presentation of \(\pi_1(K, v_0)\) from a spanning tree of the 1-skeleton.

Every edge outside a breadth-first spanning tree of the base point’s component is a generator (oriented from its lower to its higher vertex); every triangle \([a, b, c]\) gives the relation \([ab][bc][ac]^{-1} = 1\), with tree edges deleted. With simplify, Tietze moves remove generators that a relation expresses in terms of the others. See H. Poincaré, “Analysis situs,” Journal de l’École Polytechnique (2) 1 (1895), 1-121, Sec. 12.

Parameters:
  • K (SimplicialComplex)

  • base (int | None) – Base vertex; the smallest vertex label if omitted.

  • simplify (bool) – Apply Tietze transformations to shorten the presentation.

Return type:

FundamentalGroupPresentation

Returns:

FundamentalGroupPresentation

Examples

>>> from mathematicskit.topology.systems.complexes import projective_plane, sphere, torus
>>> str(fundamental_group(sphere(2))), str(fundamental_group(projective_plane()))
('1', '< a | aa >')
>>> P = fundamental_group(torus(3, 3))
>>> len(P.generators), len(P.relations), len(P.relations[0])
(2, 1, 4)
mathematicskit.topology.homology(K)[source]#

Integer homology \(H_k(K; \mathbb{Z}) \cong \mathbb{Z}^{\beta_k} \oplus \mathbb{Z}/t_1 \oplus \cdots\) by Smith normal form.

The rank of each boundary matrix gives the Betti numbers; the invariant factors of \(\partial_{k+1}\) that exceed 1 are the torsion coefficients of \(H_k\), which Poincaré introduced in 1900 after Heegaard showed that Betti numbers alone miss them. The alternating sum of the Betti numbers equals that of the simplex counts (the Euler-Poincaré formula).

Parameters:

K (SimplicialComplex) – Small enough for exact integer elimination (hundreds of simplices).

Return type:

HomologyResult

Returns:

HomologyResult

Examples

>>> from mathematicskit.topology.systems.complexes import klein_bottle, projective_plane
>>> str(homology(projective_plane()))
'H_0 = Z, H_1 = Z/2, H_2 = 0'
>>> homology(klein_bottle(3, 3)).group(1)
'Z + Z/2'
mathematicskit.topology.is_orientable(K)[source]#

Whether a pure n-dimensional pseudomanifold can be oriented coherently.

Orientations are propagated across shared \((n-1)\)-faces by breadth-first search: a top simplex with orientation \(s\) induces \(s(-1)^i\) on its face opposite vertex i, and two simplices sharing a face must induce opposite orientations on it. The surface is non-orientable exactly when some closed path of triangles returns with the orientation reversed, which is the one-sidedness that A. F. Möbius described in “Über die Bestimmung des Inhaltes eines Polyëders,” Berichte über die Verhandlungen der Königlich Sächsischen Gesellschaft der Wissenschaften 17 (1865), 31-68.

Parameters:

K (SimplicialComplex) – Pure (every maximal simplex has the top dimension), with every codimension-1 face in at most two top simplices.

Return type:

bool

Returns:

bool

Examples

>>> from mathematicskit.topology.systems.complexes import mobius_strip, torus
>>> is_orientable(torus()), is_orientable(mobius_strip())
(True, False)
mathematicskit.topology.is_sperner_labeling(grid, labels)[source]#

Whether every vertex is labelled with a corner of its carrier face (a nonzero barycentric coordinate).

Parameters:
  • grid (TriangleGrid)

  • labels (array_like of int) – A label in {0, 1, 2} per grid vertex.

Return type:

bool

Returns:

bool

Examples

>>> grid = triangle_grid(2)
>>> is_sperner_labeling(grid, grid.points.argmax(axis=1))
True
mathematicskit.topology.klein_bottle(n=6, m=4, R=2.0)[source]#

A triangulated Klein bottle, with the self-intersecting “figure-8” immersion in \(\mathbb{R}^3\) as coordinates.

The square’s sides are glued as a torus’s, except that one pair is glued with a flip, \((2\pi, v) \sim (0, -v)\).

Parameters:
  • n (int) – Grid squares in each direction, each at least 3.

  • m (int) – Grid squares in each direction, each at least 3.

  • R (float) – Radius of the immersion.

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> klein_bottle(3, 3).euler_characteristic
0
mathematicskit.topology.lefschetz_number(K, vertex_map)[source]#

The Lefschetz number \(L(f) = \sum_k (-1)^k \operatorname{tr}(f_\#: C_k \to C_k)\) of a simplicial self-map.

By the Hopf trace formula the alternating trace on chains equals the alternating trace on homology, the Lefschetz number. A simplex that f maps onto itself contributes the sign of the permutation of its vertices, so \(L(f) \ne 0\) forces a fixed simplex, and by Brouwer’s barycentre argument a fixed point: the Lefschetz fixed-point theorem. The identity gives \(L = \chi(K)\). See S. Lefschetz, “Intersections and transformations of complexes and manifolds,” Transactions of the American Mathematical Society 28 (1926), 1-49; H. Hopf, “Über die algebraische Anzahl von Fixpunkten,” Mathematische Zeitschrift 29 (1929), 493-524.

Parameters:
  • K (SimplicialComplex)

  • vertex_map (dict or array_like) – The image of each vertex; every simplex must map onto a simplex of K.

Return type:

int

Returns:

int

Examples

>>> from mathematicskit.topology.systems.complexes import circle
>>> C = circle(6)
>>> rotation = [(v + 1) % 6 for v in range(6)]
>>> reflection = [(-v) % 6 for v in range(6)]
>>> lefschetz_number(C, rotation), lefschetz_number(C, reflection)
(0, 2)
mathematicskit.topology.linking_number(curve1, curve2)[source]#

Gauss’s linking integral \(\frac{1}{4\pi}\oint\oint \frac{(r_1 - r_2)\cdot(dr_1 \times dr_2)}{|r_1 - r_2|^3}\) of two closed polygons.

Each pair of segments contributes the solid angle it subtends, computed exactly from the four unit normals of the tetrahedron the two segments span (K. Klenin and J. Langowski, “Computation of writhe in modeling of supercoiled DNA,” Biopolymers 54 (2000), 307-317), so the sum is an integer up to rounding error. The sign follows the curves’ orientations. See C. F. Gauss, “Zur mathematischen Theorie der electrodynamischen Wirkungen” (1833), in Werke V (Göttingen, 1867), 605.

Parameters:
  • curve1 (array_like, shape (n, 3)) – Vertices of two disjoint closed polygons (the last vertex joins the first).

  • curve2 (array_like, shape (n, 3)) – Vertices of two disjoint closed polygons (the last vertex joins the first).

Return type:

float

Returns:

float – Close to an integer.

Examples

>>> t = np.linspace(0, 2 * np.pi, 60, endpoint=False)
>>> ring = np.column_stack([np.cos(t), np.sin(t), 0 * t])
>>> hooked = np.column_stack([1 + np.cos(t), 0 * t, np.sin(t)])
>>> far = ring + [5, 0, 0]
>>> round(linking_number(ring, hooked)), round(linking_number(ring, hooked[::-1])), round(linking_number(ring, far))
(-1, 1, 0)
mathematicskit.topology.lower_star_filtration(K, vertex_values)[source]#

The sublevel-set filtration of a function on the vertices: each simplex enters at its highest vertex value.

The complex at value \(t\) is the full subcomplex on the vertices with \(f \le t\), the simplicial version of the sublevel sets \(f^{-1}(-\infty, t]\) of Morse theory.

Parameters:
  • K (SimplicialComplex)

  • vertex_values (array_like) – vertex_values[v] for each vertex label v.

Return type:

Filtration

Returns:

Filtration

Examples

>>> from mathematicskit.topology.systems.complexes import circle
>>> lower_star_filtration(circle(3), [2.0, 0.0, 1.0]).values.tolist()
[0.0, 1.0, 1.0, 2.0, 2.0, 2.0]
mathematicskit.topology.mapper_graph(points, lens, eps, n_intervals=10, overlap=0.3)[source]#

The Mapper graph: cluster the points in each slice of a lens function, join clusters that share points.

The range of lens is covered by n_intervals intervals that overlap by the fraction overlap; the points in each interval are split into clusters by single linkage at distance eps; the nerve of the clusters is the graph. A discrete analogue of the Reeb graph that summarises the shape of high-dimensional data. See G. Singh, F. Mémoli and G. Carlsson, “Topological methods for the analysis of high dimensional data sets and 3D object recognition,” Eurographics Symposium on Point-Based Graphics (2007), 91-100.

Parameters:
  • points (array_like, shape (n, d))

  • lens (array_like, shape (n,)) – The filter function, e.g. one coordinate or an eccentricity.

  • eps (float) – Single-linkage merge distance.

  • n_intervals (int)

  • overlap (float) – In [0, 1).

Return type:

MapperResult

Returns:

MapperResult

Examples

>>> t = np.linspace(0, 2 * np.pi, 200, endpoint=False)
>>> loop = np.column_stack([np.cos(t), np.sin(t)])
>>> result = mapper_graph(loop, loop[:, 0], eps=0.2, n_intervals=6)
>>> len(result.nodes), len(result.edges)
(10, 10)
mathematicskit.topology.mayer_vietoris(A, B)[source]#

The ranks in the Mayer-Vietoris sequence of \(A \cup B\), over \(\mathbb{Q}\).

\[\cdots \to H_k(A \cap B) \xrightarrow{i_k} H_k(A) \oplus H_k(B) \to H_k(A \cup B) \xrightarrow{\delta} H_{k-1}(A \cap B) \to \cdots\]

The rank of \(i_k\) is computed directly: the cycles of \(A \cap B\) (a basis from scipy.linalg.null_space()) pushed into \(A\) and \(B\), counted modulo the boundaries there. Exactness then predicts the Betti numbers of the union from those of the pieces (MayerVietorisResult.predicted_union), which the result also computes directly. See W. Mayer, “Über abstrakte Topologie,” Monatshefte für Mathematik und Physik 36 (1929), 1-42; L. Vietoris, “Über die Homologiegruppen der Vereinigung zweier Komplexe,” ibid. 37 (1930), 159-162.

Parameters:
Return type:

MayerVietorisResult

Returns:

MayerVietorisResult

Examples

>>> from mathematicskit.topology.systems.complexes import circle
>>> C = circle(6)
>>> upper, lower = C.induced_subcomplex([0, 1, 2, 3]), C.induced_subcomplex([3, 4, 5, 0])
>>> result = mayer_vietoris(upper, lower)
>>> result.betti_intersection, result.betti_union, result.predicted_union
((2,), (1, 1), (1, 1))
mathematicskit.topology.mobius_strip(n=8, m=1, width=1.0)[source]#

A triangulated Möbius strip in \(\mathbb{R}^3\): a band whose ends are glued with a half twist.

Parameters:
  • n (int) – Squares along the strip, at least 3.

  • m (int) – Squares across the strip.

  • width (float)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> mobius_strip(5).f_vector
(10, 20, 10)
mathematicskit.topology.persistent_homology(filtration, max_dim=None, include_zero=False)[source]#

The persistence diagram of a filtration, by the standard column reduction over \(\mathbb{Z}/2\).

See H. Edelsbrunner, D. Letscher and A. Zomorodian, “Topological persistence and simplification,” Discrete & Computational Geometry 28 (2002), 511-533; A. Zomorodian and G. Carlsson, “Computing persistent homology,” ibid. 33 (2005), 249-274.

Parameters:
  • filtration (Filtration)

  • max_dim (int | None) – Highest homology dimension to report; by default the filtration’s top dimension.

  • include_zero (bool) – Keep pairs born and killed at the same value.

Return type:

PersistenceDiagram

Returns:

PersistenceDiagram

Examples

>>> from mathematicskit.topology.systems.point_clouds import vietoris_rips_filtration
>>> square = [[0, 0], [1, 0], [1, 1], [0, 1]]
>>> diagram = persistent_homology(vietoris_rips_filtration(square), max_dim=1)
>>> diagram.diagram(0).tolist()
[[0.0, 0.5], [0.0, 0.5], [0.0, 0.5], [0.0, inf]]
>>> np.round(diagram.diagram(1), 4).tolist()
[[0.5, 0.7071]]
mathematicskit.topology.planar_betti_numbers(mask)[source]#

The Betti numbers \((\beta_0, \beta_1)\) of the region drawn in a 2D boolean image.

Parameters:

mask (array_like of bool, shape (rows, cols)) – True on the region.

Return type:

tuple[int, int]

Returns:

tuple of int – Components and holes.

Examples

>>> ring = np.ones((5, 5), dtype=bool)
>>> ring[2, 2] = False
>>> planar_betti_numbers(ring)
(1, 1)
mathematicskit.topology.projective_plane()[source]#

The 6-vertex real projective plane \(\mathbb{RP}^2\): the icosahedron with antipodal points identified.

It is the smallest triangulation of \(\mathbb{RP}^2\): 6 vertices, 15 edges and 10 triangles, so \(\chi = 1\). Vertex 0 is the centre of a disc whose boundary pentagon 1, ..., 5 is glued to itself antipodally. It has no coordinates: \(\mathbb{RP}^2\) does not embed in \(\mathbb{R}^3\).

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> projective_plane().f_vector
(6, 15, 10)
mathematicskit.topology.random_sperner_labeling(grid, seed=None)[source]#

A Sperner labelling chosen uniformly at random: each vertex gets a random corner of its carrier face.

Parameters:
Return type:

ndarray

Returns:

ndarray of int

Examples

>>> grid = triangle_grid(5)
>>> is_sperner_labeling(grid, random_sperner_labeling(grid, seed=0))
True
mathematicskit.topology.rank_mod_p(A, p)[source]#

Rank of an integer matrix over the field GF(p), by Gaussian elimination.

Parameters:
  • A (array_like of int, shape (m, n))

  • p (int) – A prime.

Return type:

int

Returns:

int

Examples

>>> rank_mod_p([[2, 0], [0, 1]], 2), rank_mod_p([[2, 0], [0, 1]], 3)
(1, 2)
mathematicskit.topology.simplex(n)[source]#

The full n-simplex: all \(2^{n+1} - 1\) faces of \(\{0, \dots, n\}\).

It is contractible, so \(\chi = 1\).

Parameters:

n (int) – Dimension, at least 0.

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> simplex(2).f_vector
(3, 3, 1)
mathematicskit.topology.simplicial_product(K, L)[source]#

A triangulation of \(|K| \times |L|\), by staircase paths through each product of simplices.

With the vertices of each complex ordered, the product \(\sigma \times \tau\) of a \(p\)-simplex and a \(q\)-simplex is cut into \(\binom{p+q}{p}\) simplices of dimension \(p + q\), one per monotone lattice path from \((\sigma_0, \tau_0)\) to \((\sigma_p, \tau_q)\); the ordering makes the pieces agree on shared faces. Vertex \((v, w)\) gets label i * len(L.vertices) + j, where v and w are the i-th and j-th vertices of K and L. See S. Eilenberg and J. A. Zilber, “Semi-simplicial complexes and singular homology,” Annals of Mathematics 51 (1950), 499-513.

Parameters:
Return type:

SimplicialComplex

Returns:

SimplicialComplex – Coordinates are the concatenated coordinates of both factors, when both have them.

Examples

>>> simplicial_product(sphere(1), sphere(1)).f_vector
(9, 27, 18)
mathematicskit.topology.smith_normal_form(A)[source]#

The Smith normal form \(D = UAV\) of an integer matrix.

Repeatedly moves the smallest nonzero entry to the pivot, clears its row and column by integer row and column operations (the Euclidean algorithm in matrix form), and adds a row back whenever the pivot does not divide the rest, until \(D\) is diagonal with \(d_1 \mid d_2 \mid \cdots\). The invariant factors classify the finitely generated abelian group \(\mathbb{Z}^m / A\mathbb{Z}^n\). Arithmetic is in exact Python integers. See H. J. S. Smith, “On systems of linear indeterminate equations and congruences,” Philosophical Transactions of the Royal Society 151 (1861), 293-326.

Parameters:

A (array_like of int, shape (m, n))

Return type:

SmithNormalFormResult

Returns:

SmithNormalFormResult

Examples

>>> result = smith_normal_form([[2, 4, 4], [-6, 6, 12], [10, -4, -16]])
>>> result.invariant_factors
[2, 6, 12]
>>> bool(np.array_equal(result.U @ np.array([[2, 4, 4], [-6, 6, 12], [10, -4, -16]]) @ result.V, result.D))
True
mathematicskit.topology.sphere(n, kind='simplex')[source]#

A triangulation of the n-sphere \(S^n\).

kind="simplex" gives the boundary of the \((n+1)\)-simplex (\(n + 2\) vertices, the fewest possible); kind="cross_polytope" gives the boundary of the cross-polytope (the octahedron for \(n = 2\)), with vertex \(2i\) at \(+e_i\) and \(2i+1\) at \(-e_i\), so that \(v \mapsto v \oplus 1\) is the antipodal map.

Parameters:
  • n (int) – Dimension, at least 0.

  • kind (str)

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> sphere(2).f_vector, sphere(2, kind="cross_polytope").f_vector
((4, 6, 4), (6, 12, 8))
mathematicskit.topology.torus(n=6, m=4, R=2.0, r=1.0)[source]#

A triangulated torus \(T^2 = S^1 \times S^1\), embedded as a ring in \(\mathbb{R}^3\).

Parameters:
  • n (int) – Squares around the ring and around the tube, each at least 3.

  • m (int) – Squares around the ring and around the tube, each at least 3.

  • R (float) – Ring and tube radii of the embedding.

  • r (float) – Ring and tube radii of the embedding.

Return type:

SimplicialComplex

Returns:

SimplicialComplex

Examples

>>> torus(3, 3).f_vector
(9, 27, 18)
mathematicskit.topology.triangle_grid(n)[source]#

Subdivide the standard triangle into \(n^2\) congruent small triangles.

Parameters:

n (int) – Subdivisions per side, at least 1.

Return type:

TriangleGrid

Returns:

TriangleGrid

Examples

>>> grid = triangle_grid(3)
>>> grid.points.shape, grid.triangles.shape
((10, 3), (9, 3))
mathematicskit.topology.turning_number(curve)[source]#

The turning number of a closed plane polygon: the total turning of its tangent divided by \(2\pi\).

Sums the signed exterior angle at each vertex. Hopf’s Umlaufsatz: every simple closed curve has turning number \(+1\) (counter-clockwise) or \(-1\) (clockwise); a figure eight has 0. By Whitney and Graustein (1937), two closed curves can be deformed into each other through immersed curves exactly when their turning numbers agree. See H. Hopf, “Über die Drehung der Tangenten und Sehnen ebener Kurven,” Compositio Mathematica 2 (1935), 50-62.

Parameters:

curve (array_like, shape (n, 2)) – Vertices of a closed polygon with no zero-length edges and no reversal (exterior angle \(\pm\pi\)) at any vertex.

Return type:

int

Returns:

int

Examples

>>> t = np.linspace(0, 2 * np.pi, 200, endpoint=False)
>>> turning_number(np.column_stack([np.cos(t), np.sin(t)]))
1
>>> turning_number(np.column_stack([np.sin(t), np.sin(2 * t)]))
0
mathematicskit.topology.vector_field_index(field, center=(0.0, 0.0), radius=1.0, n_points=2000)[source]#

The index of a planar vector field around a circle: how many times the field turns as the circle is traversed.

The field’s direction is followed around \(n\) points of the circle and its total change of angle divided by \(2\pi\). The result is the sum of the indices of the zeros inside: \(+1\) for a source, sink or centre, \(-1\) for a saddle, \(k\) for \(z^k\). By the Poincaré-Hopf theorem the total index of a field pointing outward along the boundary of a region equals the region’s Euler characteristic, 1 for a disc. See H. Poincaré, “Sur les courbes définies par les équations différentielles (3e partie),” Journal de Mathématiques Pures et Appliquées (4) 1 (1885), 167-244; H. Hopf, “Vektorfelder in n-dimensionalen Mannigfaltigkeiten,” Mathematische Annalen 96 (1927), 225-249.

Parameters:
  • field (Callable) – field(x, y) -> (u, v), vectorized over arrays.

  • center (array_like, shape (2,))

  • radius (float)

  • n_points (int) – Sample points on the circle; the field must turn by less than \(\pi\) between neighbours.

Return type:

int

Returns:

int

Examples

>>> saddle = lambda x, y: (x, -y)
>>> vector_field_index(saddle), vector_field_index(lambda x, y: (x * x - y * y, 2 * x * y))
(-1, 2)
mathematicskit.topology.vietoris_rips_complex(points, radius, max_dim=2)[source]#

The Vietoris-Rips complex: every set of points with pairwise distances at most \(2r\).

It is the clique complex of the graph joining points closer than \(2r\). Vietoris introduced such complexes in 1927 to define homology for compact metric spaces, as a limit over ever finer scales; Rips used them for hyperbolic groups. See L. Vietoris, “Über den höheren Zusammenhang kompakter Räume und eine Klasse von zusammenhangstreuen Abbildungen,” Mathematische Annalen 97 (1927), 454-472.

Parameters:
  • points (array_like, shape (n, d))

  • radius (float) – Ball radius \(r\).

  • max_dim (int) – Largest simplex dimension built.

Return type:

SimplicialComplex

Returns:

SimplicialComplex – With points as coordinates.

Examples

>>> square = [[0, 0], [1, 0], [1, 1], [0, 1]]
>>> vietoris_rips_complex(square, 0.5).f_vector, vietoris_rips_complex(square, 0.75).f_vector
((4, 4), (4, 6, 4))
mathematicskit.topology.vietoris_rips_filtration(points, max_radius=inf, max_dim=2)[source]#

The Vietoris-Rips filtration: each simplex enters at half its diameter.

Homology of the filtration is reliable up to dimension max_dim - 1; max_dim-dimensional classes cannot die, since no higher simplices are built.

Parameters:
  • points (array_like, shape (n, d))

  • max_radius (float) – Stop at this radius.

  • max_dim (int)

Return type:

Filtration

Returns:

Filtration

Examples

>>> F = vietoris_rips_filtration([[0, 0], [2, 0]])
>>> F.simplices, F.values.tolist()
([(0,), (1,), (0, 1)], [0.0, 0.0, 1.0])