Algebraic topology including fundamental groups, homology, homotopy theory, and computational topology
Scope: Fundamental groups, homology, homotopy, covering spaces, computational topology Lines: ~420 Last Updated: 2025-10-25
Activate this skill when:
Homotopy: Continuous deformation between continuous maps
Fundamental group: π₁(X, x₀) = homotopy classes of loops based at x₀
import numpy as np
from collections import defaultdict
class Loop:
"""Represent a loop in a topological space"""
def __init__(self, path, basepoint):
self.path = path # Function [0,1] → X
self.basepoint = basepoint
def compose(self, other):
"""Loop composition: traverse self, then other"""
def composed(t):
if t <= 0.5:
return self.path(2 * t)
else:
return other.path(2 * (t - 0.5))
return Loop(composed, self.basepoint)
def inverse(self):
"""Reverse the loop"""
return Loop(lambda t: self.path(1 - t), self.basepoint)
def fundamental_group_circle():
"""
π₁(S¹) ≅ ℤ
Loops classified by winding number
"""
class S1Loop:
def __init__(self, winding_number):
self.n = winding_number # How many times around
def compose(self, other):
return S1Loop(self.n + other.n)
def inverse(self):
return S1Loop(-self.n)
def __eq__(self, other):
return self.n == other.n
def __repr__(self):
return f"[loop with winding {self.n}]"
# Identity element
trivial_loop = S1Loop(0)
# Generator (loop once around)
generator = S1Loop(1)
return {
'identity': trivial_loop,
'generator': generator,
'group_structure': 'ℤ (integers under addition)'
}
# Example
pi1_circle = fundamental_group_circle()
print(f"π₁(S¹) ≅ {pi1_circle['group_structure']}")
Van Kampen's Theorem: For path-connected spaces X = A ∪ B with A ∩ B path-connected: π₁(X) ≅ π₁(A) *_{π₁(A∩B)} π₁(B) (amalgamated free product)
def van_kampen_example():
"""
Figure-eight space (two circles joined at a point)
X = S¹ ∨ S¹ = two circles joined at basepoint
"""
# π₁(S¹) ≅ ℤ for each circle
# A ∩ B = {basepoint}, π₁({pt}) = 0 (trivial)
# By Van Kampen: π₁(S¹ ∨ S¹) ≅ ℤ * ℤ (free group on 2 generators)
class FreeGroupElement:
def __init__(self, word):
"""word: list of ('a', n) or ('b', m) for generators"""
self.word = word
def compose(self, other):
return FreeGroupElement(self.word + other.word)
def reduce(self):
"""Cancel inverse pairs like a¹a⁻¹"""
reduced = []
for gen, power in self.word:
if reduced and reduced[-1][0] == gen:
reduced[-1] = (gen, reduced[-1][1] + power)
if reduced[-1][1] == 0:
reduced.pop()
else:
if power != 0:
reduced.append((gen, power))
return FreeGroupElement(reduced)
# Generators: a (first circle), b (second circle)
a = FreeGroupElement([('a', 1)])
b = FreeGroupElement([('b', 1)])
# Example element: aba⁻¹b²
element = FreeGroupElement([('a', 1), ('b', 1), ('a', -1), ('b', 2)])
return {'generators': ['a', 'b'], 'structure': 'F₂ (free group on 2 generators)'}
Simplicial homology: Algebraic invariant computed from simplicial complex
Chain complex: C_n → C_{n-1} → ... → C_0
Homology: H_n = ker(∂_n) / im(∂_{n+1})
import numpy as np
from scipy.sparse import lil_matrix
class SimplicialComplex:
"""Represent simplicial complex for homology computation"""
def __init__(self):
self.simplices = {0: set(), 1: set(), 2: set()} # dim → simplices
def add_vertex(self, v):
self.simplices[0].add((v,))
def add_edge(self, v1, v2):
# Ensure vertices exist
self.add_vertex(v1)
self.add_vertex(v2)
edge = tuple(sorted([v1, v2]))
self.simplices[1].add(edge)
def add_triangle(self, v1, v2, v3):
# Ensure edges exist
self.add_edge(v1, v2)
self.add_edge(v2, v3)
self.add_edge(v3, v1)
triangle = tuple(sorted([v1, v2, v3]))
self.simplices[2].add(triangle)
def boundary_matrix(self, dim):
"""
Compute boundary matrix ∂_dim: C_dim → C_{dim-1}
Rows: (dim-1)-simplices, Cols: dim-simplices
"""
if dim == 0:
return None
simplices_n = sorted(self.simplices[dim])
simplices_n_minus_1 = sorted(self.simplices[dim-1])
# Create index maps
idx_n = {s: i for i, s in enumerate(simplices_n)}
idx_n_minus_1 = {s: i for i, s in enumerate(simplices_n_minus_1)}
# Build boundary matrix
rows, cols = len(simplices_n_minus_1), len(simplices_n)
boundary = lil_matrix((rows, cols), dtype=int)
for col_idx, simplex in enumerate(simplices_n):
# Compute boundary of simplex
for i, vertex in enumerate(simplex):
# Remove i-th vertex
face = tuple(v for j, v in enumerate(simplex) if j != i)
row_idx = idx_n_minus_1[face]
# Sign: (-1)^i
boundary[row_idx, col_idx] = (-1) ** i
return boundary.tocsr()
def compute_homology(self, dim):
"""Compute H_dim using boundary matrices"""
import numpy as np
from numpy.linalg import matrix_rank
∂_n_plus_1 = self.boundary_matrix(dim + 1)
∂_n = self.boundary_matrix(dim)
if ∂_n is None:
# H_0
Z_n = len(self.simplices[0]) # All vertices
else:
# ker(∂_n) dimension
Z_n = len(self.simplices[dim]) - matrix_rank(∂_n.toarray())
if ∂_n_plus_1 is None:
B_n = 0
else:
# im(∂_{n+1}) dimension
B_n = matrix_rank(∂_n_plus_1.toarray())
# Betti number: dim(H_n) = dim(ker) - dim(im)
betti_n = Z_n - B_n
return {'betti_number': betti_n, 'rank': betti_n}
# Example: Compute H_* of a circle (triangulated as hexagon)
circle = SimplicialComplex()
for i in range(6):
circle.add_edge(i, (i+1) % 6)
h0 = circle.compute_homology(0)
h1 = circle.compute_homology(1)
print(f"H_0(S¹) has Betti number {h0['betti_number']}") # 1 (connected)
print(f"H_1(S¹) has Betti number {h1['betti_number']}") # 1 (one hole)
Definition: χ(X) = Σ (-1)^i · rank(H_i(X)) = Σ (-1)^i · b_i
For finite simplicial complex: χ = V - E + F (vertices - edges + faces)
def euler_characteristic(complex):
"""χ = Σ(-1)^i · #(i-simplices)"""
chi = 0
for dim, simplices in complex.simplices.items():
chi += (-1) ** dim * len(simplices)
return chi
# Examples
# Sphere: χ(S²) = 2
# Torus: χ(T²) = 0
# Genus-g surface: χ = 2 - 2g
Idea: Track homology as parameter varies (e.g., scale in point cloud)
# Using gudhi library for computational topology
try:
import gudhi
except ImportError:
print("Install: pip install gudhi")
def compute_persistent_homology(points, max_dim=2):
"""
Compute persistence diagram from point cloud
"""
# Build Vietoris-Rips complex
rips_complex = gudhi.RipsComplex(points=points, max_edge_length=2.0)
simplex_tree = rips_complex.create_simplex_tree(max_dimension=max_dim)
# Compute persistence
persistence = simplex_tree.persistence()
# Extract diagrams by dimension
diagrams = {}
for dim in range(max_dim + 1):
diagrams[dim] = simplex_tree.persistence_intervals_in_dimension(dim)
return diagrams
# Example: Point cloud sampled from circle
theta = np.linspace(0, 2*np.pi, 50, endpoint=False)
points_circle = np.column_stack([np.cos(theta), np.sin(theta)])
# Persistence detects circular structure
# diagrams = compute_persistent_homology(points_circle, max_dim=1)
# H_1 has persistent feature (the circle)
Deformation retracts: X deformation retracts to A ⟹ π₁(X) ≅ π₁(A)
def fundamental_group_examples():
"""Common fundamental groups"""
examples = {
'S¹ (circle)': 'ℤ',
'S² (sphere)': '0 (trivial)',
'Sⁿ (n≥2)': '0 (simply connected)',
'T² (torus)': 'ℤ × ℤ',
'ℝPⁿ (real projective, n≥2)': 'ℤ/2ℤ',
'S¹ ∨ S¹ (figure-eight)': 'F₂ (free group, 2 generators)',
'genus-g surface': 'generators: 2g, relations: [a₁,b₁]···[aₐ,bₐ]=1'
}
return examples
Long exact sequence relating homology of X, A, B, A∩B when X = A ∪ B:
... → H_n(A∩B) → H_n(A) ⊕ H_n(B) → H_n(X) → H_{n-1}(A∩B) → ...
def mayer_vietoris_torus():
"""
Compute H_*(T²) using Mayer-Vietoris
Decompose torus as T² = A ∪ B where A, B are cylinders
"""
# A ∩ B ≃ S¹ ∨ S¹ (two circles)
# A ≃ S¹, B ≃ S¹
# H_0: all are ℤ, sequence gives H_0(T²) ≅ ℤ
# H_1: from sequence, H_1(T²) ≅ ℤ ⊕ ℤ
# H_2: H_2(T²) ≅ ℤ
return {
'H_0': 'ℤ',
'H_1': 'ℤ ⊕ ℤ',
'H_2': 'ℤ',
'euler_char': 0 # 1 - 2 + 1 = 0
}
Definition: Build space by attaching cells
class CWComplex:
"""Represent CW complex structure"""
def __init__(self):
self.cells = defaultdict(list) # dim → list of cells
self.attaching_maps = {} # cell → (skeleton, attaching map)
def add_cell(self, dim, cell_id, attaching_info=None):
self.cells[dim].append(cell_id)
if attaching_info:
self.attaching_maps[cell_id] = attaching_info
def euler_characteristic(self):
"""χ = Σ (-1)^i · c_i where c_i = number of i-cells"""
chi = 0
for dim, cells in self.cells.items():
chi += (-1) ** dim * len(cells)
return chi
# Example: S² as CW complex
sphere_cw = CWComplex()
sphere_cw.add_cell(0, 'v0') # One 0-cell
sphere_cw.add_cell(2, 'e1') # One 2-cell attached to point
chi_sphere = sphere_cw.euler_characteristic() # χ = 1 - 0 + 1 = 2
| Space | π₁(X) | Note | |-------|-------|------| | S¹ | ℤ | Winding number | | Sⁿ (n≥2) | 0 | Simply connected | | T² | ℤ × ℤ | Torus | | ℝPⁿ (n≥2) | ℤ/2ℤ | Projective space | | S¹ ∨ S¹ | F₂ | Free on 2 generators |
| Space | H_0 | H_1 | H_2 | χ | |-------|-----|-----|-----|---| | S¹ | ℤ | ℤ | 0 | 0 | | S² | ℤ | 0 | ℤ | 2 | | T² | ℤ | ℤ² | ℤ | 0 | | ℝP² | ℤ | ℤ/2ℤ | 0 | 1 |
b_i = rank(H_i(X)) = dimension of i-th homology group
❌ Confusing homotopy and homeomorphism: S² and ℝ² not homeomorphic but ℝ² ≃ {point} ✅ Homotopy equivalence weaker than homeomorphism
❌ Assuming π₁ determines space: Lens spaces have same π₁ but different H₂ ✅ Need full homology/homotopy groups for classification
❌ Computing homology by hand for large complexes: Exponential complexity ✅ Use computational tools (gudhi, javaplex, PHAT) for practical problems
❌ Ignoring coefficients: H_*(ℝP²; ℤ) ≠ H_*(ℝP²; ℤ/2ℤ) ✅ Specify coefficient group for homology
topology-point-set.md - Foundation for algebraic topologycategory-theory-foundations.md - Functorial nature of π₁ and H_*abstract-algebra.md - Group theory for fundamental groupslinear-algebra-computation.md - Matrix computations for homologyformal/lean-mathlib4.md - Formalizing algebraic topology in LeanLast Updated: 2025-10-25 Format Version: 1.0 (Atomic)