Note
Go to the end to download the full example code or to run this example in your browser via JupyterLite.
Poincaré’s fundamental group (1895)#
Loops from a base point, up to deformation, form a group, \(\pi_1\). For a complex it can be read off combinatorially: contract a spanning tree of the edges, keep one generator per remaining edge, and impose one relation per triangle. Tietze moves then shrink the presentation. The torus gives \(\langle a, b \mid aba^{-1}b^{-1}\rangle\) (up to naming), the projective plane \(\langle a \mid a^2\rangle\), and abelianizing gives back \(H_1\).
import matplotlib.pyplot as plt
from mathematicskit.topology import (
SimplicialComplex,
abelianization,
circle,
fundamental_group,
homology,
klein_bottle,
mobius_strip,
projective_plane,
sphere,
torus,
)
wedge = SimplicialComplex([[0, 1], [1, 2], [2, 0], [0, 3], [3, 4], [4, 0]])
spaces = {
"circle": circle(5),
"figure eight": wedge,
"sphere": sphere(2),
"torus": torus(3, 3),
"Möbius strip": mobius_strip(5),
"projective plane": projective_plane(),
"Klein bottle": klein_bottle(3, 3),
}
for name, K in spaces.items():
raw = fundamental_group(K, simplify=False)
P = fundamental_group(K)
print(f"{name:>16}: {len(raw.generators):2d} edge generators, {len(raw.relations):2d} relations -> {P}")
print(f"{'':>16} abelianized: {abelianization(P)}, H_1 = {homology(K).group(1)}")
circle: 1 edge generators, 0 relations -> < a >
abelianized: Z, H_1 = Z
figure eight: 2 edge generators, 0 relations -> < a, b >
abelianized: Z^2, H_1 = Z^2
sphere: 3 edge generators, 4 relations -> 1
abelianized: 0, H_1 = 0
torus: 19 edge generators, 18 relations -> < a, b | BabA >
abelianized: Z^2, H_1 = Z^2
Möbius strip: 11 edge generators, 10 relations -> < a >
abelianized: Z, H_1 = Z
projective plane: 10 edge generators, 10 relations -> < a | aa >
abelianized: Z/2, H_1 = Z/2
Klein bottle: 19 edge generators, 18 relations -> < a, b | babA >
abelianized: Z + Z/2, H_1 = Z + Z/2
The spanning tree on the 3 x 3 torus#
Drawn on the square with opposite sides glued: the 8 tree edges are contracted, and the other 19 edges are generators. The 18 triangles then cut them down to two, \(a\) and \(b\), with one relation.
T = torus(3, 3)
pos = {i * 3 + j: (i, j) for i in range(3) for j in range(3)}
P_raw = fundamental_group(T, simplify=False)
fig, ax = plt.subplots(figsize=(5.5, 5.5))
for i in range(3):
for j in range(3):
for di, dj in ((1, 0), (0, 1), (1, 1)):
a, b = i * 3 + j, ((i + di) % 3) * 3 + (j + dj) % 3
edge = (min(a, b), max(a, b))
is_generator = edge in P_raw.generators
ax.plot([i, i + di], [j, j + dj], color="0.7" if is_generator else "tab:green", lw=1 if is_generator else 3)
for v, (i, j) in pos.items():
ax.annotate(str(v), (i, j), ha="center", va="center", bbox={"boxstyle": "circle", "fc": "w"})
ax.add_patch(plt.Rectangle((0, 0), 3, 3, fill=False, ls="--"))
ax.set_aspect("equal")
ax.axis("off")
ax.set_title(f"spanning tree (green) of the torus; π₁ = {fundamental_group(T)}")

Text(0.5, 1.0, 'spanning tree (green) of the torus; π₁ = < a, b | BabA >')
Total running time of the script: (0 minutes 0.027 seconds)