Betti Numbers
Key Points
- •Betti numbers count independent k-dimensional holes: counts connected components, counts independent loops/tunnels, and counts voids.
- •Formally, is the rank (dimension) of the k-th homology group , usually computed over a field like or .
- •For a finite simplicial complex, you can compute Betti numbers from boundary matrices using Gaussian elimination modulo 2.
- •A practical formula is \( = - () - ()\), where is the number of k-simplices.
- •In graphs, \( = - + \) follows directly from Euler characteristic, and \(\) is the number of connected components.
- •Computations can be done efficiently over , avoiding orientation issues and making boundary matrices binary.
- •Euler characteristic relates cells and Betti numbers: \( = (-1)^k = (-1)^k \), providing a strong consistency check.
- •C++ implementations typically build boundary matrices and compute ranks with mod-2 Gaussian elimination or use DSU for \(\) on graphs.
Prerequisites
- →Set theory and combinatorics — To understand simplices as subsets and how complexes are built from faces.
- →Graphs and basic graph algorithms — β₀ and β₁ for graphs illustrate homology in 1D and motivate DSU.
- →Linear algebra over fields — Homology uses kernels, images, ranks; computations rely on Gaussian elimination.
- →Matrix representations and Gaussian elimination — Boundary matrices are central; rank/nullity is computed via elimination.
- →Modular arithmetic (GF(2)) — We compute over \mathbb{Z}_2 to avoid orientations and use bit operations.
- →Simplicial complexes — Provide the discrete model of spaces required to define boundary operators.
- →Euler characteristic — Relates cell counts and Betti numbers; useful for checks and graph β₁.
- →Disjoint Set Union (Union-Find) — Efficiently computes β₀ on graphs, demonstrating fast topological queries.
Detailed Explanation
Tap terms for definitions01Overview
Betti numbers are numerical fingerprints that describe the shape of a space by counting holes in different dimensions. They arise from algebraic topology, where spaces are studied by converting geometric questions into algebraic ones. For a given space (often discretized as a simplicial or cubical complex), the k-th Betti number, β_k, measures the number of independent k-dimensional cycles that do not bound a (k+1)-dimensional piece. Intuitively, β_0 counts connected components, β_1 counts loops or tunnels, and β_2 counts voids or cavities. These numbers are robust under continuous deformations (stretching, bending) that do not tear or glue the space, making them topological invariants. In computation, we usually work with finite combinatorial models of spaces: graphs for 1D, triangle meshes for 2D surfaces, and higher-dimensional simplicial/cubical complexes for general shapes or point-cloud filtrations. We assemble boundary matrices encoding how k-simplices are attached to (k−1)-simplices, then apply linear algebra over a field (commonly \mathbb{Z}_2) to compute ranks and nullities. Efficient software packages do this at scale, but understanding the core mechanics—boundary operators, cycles, boundaries, and rank computations—provides deep insight and allows you to implement small, educational solvers in C++. Betti numbers have applications across data analysis (topological data analysis, persistence), graphics and geometry processing, robotics (configuration spaces), sensor networks (coverage holes), and materials science (porous media). They summarize essential topological structure in a compact, interpretable form.
02Intuition & Analogies
Imagine exploring structures with a drone that can fly through rooms, around pillars, and into tunnels.
- β₀ (connected components): Count how many separate buildings your drone would need to exit and re-enter to visit. Each disconnected piece is one component.
- β₁ (loops/tunnels): Inside a single building, imagine hallways forming loops. Ask: how many independent circular routes exist that are not just obvious detours around the same obstacle? That number is β₁. If a loop is the boundary of a filled-in sheet (like the rim of a disk), it doesn’t count because it’s not a true tunnel—your loop can be “shrunk” across the sheet.
- β₂ (voids): Think of sealed rooms with no doors—air pockets inside a 3D object. Each independent cavity is one void counted by β₂.
To make this concrete with LEGO:
- Build several separate piles: that’s β₀.
- Connect bricks to form a ring: that ring contributes to β₁. Add a thin film filling the ring’s hole (like a disk) and the loop disappears topologically.
- Make a hollow ball (like an Easter egg): the inside counts as one β₂.
Computationally, we break a shape into simple pieces: points (0-simplices), edges (1-simplices), triangles (2-simplices), and tetrahedra (3-simplices). We record which piece touches which using a boundary matrix. Over \mathbb{Z}_2 (bits), this matrix just notes presence/absence of a face. Linear algebra on this matrix tells us how many cycles exist (loops made of pieces) and how many are trivial (those that bound something). The difference between all cycles and trivial cycles is exactly the Betti number. This perspective turns counting holes into counting independent solutions of linear equations modulo 2.
03Formal Definition
04When to Use
Use Betti numbers when you need a robust, scale-independent summary of shape topology.
- Graphs and networks: β₀ is the number of connected components; β₁ measures independent cycles in the network, relevant to redundancy in communication or power grids.
- 2D/3D meshes: β₁ reveals handles and tunnels in surfaces (e.g., a coffee mug has β₁=1), and β₂ reveals enclosed voids (e.g., a hollow sphere has β₂=1).
- Topological Data Analysis (TDA): Compute Betti numbers across a filtration (varying a distance threshold) to get persistence diagrams, capturing features that persist across scales and are likely to be signal rather than noise.
- Robotics and motion planning: Homology of configuration spaces suggests the presence of unavoidable loops/obstacles in feasible motion.
- Medical and materials imaging: Quantify porosity and connectivity by computing β₁ and β₂ of segmented volumes.
- Sanity checks for meshes: Use Euler characteristic and Betti numbers to check watertightness and genus. Choose computations over \mathbb{Z}_2 for simplicity and speed, and move to persistent homology when features at multiple scales matter. For pure graphs, formulas via Euler characteristic and DSU are fastest; for general complexes, build boundary matrices and compute ranks.
⚠️Common Mistakes
- Confusing any cycle with β₁: A loop that bounds a filled-in triangle does not contribute to β₁. Only cycles not bounding a 2-chain are counted.
- Ignoring orientation issues over \mathbb{Z}: Over integers, boundary signs matter; working over \mathbb{Z}_2 avoids sign bookkeeping. If you implement over \mathbb{Z} but treat entries as unsigned, you get wrong ranks.
- Double-counting simplices: Failing to deduplicate edges or triangles inflates m_k and corrupts Betti counts. Always canonicalize (e.g., sort vertices of an edge so (u,v) and (v,u) are the same).
- Misapplying the graph formula: (\beta_1 = |E| - |V| + \beta_0) holds for graphs (1-dimensional complexes), not for 2D or 3D meshes with filled faces.
- Forgetting (\partial^2=0): If your boundary assembly allows a triangle’s edge to appear twice in its boundary over \mathbb{Z}_2), it cancels to zero—this is correct. Over-counting without mod-2 leads to violations of (\partial \circ \partial = 0).
- Numerical pitfalls: Implementing Gaussian elimination but using toggling instead of setting bits in the boundary matrix can silently introduce errors. Similarly, mixing dense and sparse representations without care can blow up memory.
- Misinterpreting β over different fields: Betti numbers are invariant across fields for finite complexes (they’re ranks), but torsion information is lost; don’t expect (\mathbb{Z}_2) computations to reveal torsion.
Key Formulas
Betti Number Definition
Explanation: The k-th Betti number is the dimension (rank) of the k-th homology group over a field F. It counts independent k-dimensional holes.
Homology Group
Explanation: k-cycles are those with zero boundary, and k-boundaries are those that are themselves boundaries of (k+1)-chains. Homology is cycles modulo boundaries.
Betti via Boundary Ranks
Explanation: For a finite complex with k-simplices, Betti numbers can be computed by counting columns and subtracting ranks of adjacent boundary matrices.
Euler Characteristic
Explanation: The alternating sum of the number of simplices equals the alternating sum of Betti numbers. This is a powerful consistency check for computations.
Rank–Nullity
Explanation: For an n-column matrix A, the dimension of its null space equals n minus its rank. This underlies the Betti formula using boundary ranks.
Boundary of a Boundary is Zero
Explanation: Applying two consecutive boundary operators always yields zero. Algebraically, this ensures that boundaries are cycles.
Graph Betti-1
Explanation: For 1-dimensional complexes (graphs), the first Betti number equals edges minus vertices plus components. It counts independent cycles in the graph.
Instantaneous Betti in Persistence
Explanation: At a filtration value t, the k-th Betti number equals the number of persistence intervals covering t. This connects homology with barcodes.
Cellular Euler Characteristic
Explanation: In cell complexes, the Euler characteristic equals the alternating sum of the number of i-cells. It matches the alternating sum of Betti numbers.
Poincaré Duality (Betti Symmetry)
Explanation: On such manifolds, Betti numbers are symmetric around the middle dimension. This constrains possible values and serves as a check.
Complexity Analysis
Code Examples
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct DSU { 5 int n; vector<int> p, r; 6 DSU(int n=0): n(n), p(n), r(n,0) { iota(p.begin(), p.end(), 0); } 7 int find(int x){ return p[x]==x? x: p[x]=find(p[x]); } 8 bool unite(int a, int b){ a=find(a); b=find(b); if(a==b) return false; if(r[a]<r[b]) swap(a,b); p[b]=a; if(r[a]==r[b]) r[a]++; return true; } 9 }; 10 11 // Example graph: two components, one contains a triangle (one independent cycle) 12 // V = {0,1,2,3,4}, E = {(0,1),(1,2),(2,0),(3,4)} 13 int main(){ 14 int V = 5; 15 vector<pair<int,int>> edges = {{0,1},{1,2},{2,0},{3,4}}; 16 17 DSU dsu(V); 18 for (auto [u,v] : edges) dsu.unite(u,v); 19 20 // Count components (β0) 21 unordered_set<int> reps; 22 for (int v=0; v<V; ++v) reps.insert(dsu.find(v)); 23 int beta0 = (int)reps.size(); 24 25 // β1 for a graph: |E| - |V| + β0 26 int beta1 = (int)edges.size() - V + beta0; 27 28 cout << "Graph Betti numbers:\n"; 29 cout << "beta0 (components) = " << beta0 << "\n"; 30 cout << "beta1 (independent cycles) = " << beta1 << "\n"; 31 32 return 0; 33 } 34
This program computes Betti numbers for a 1-dimensional complex (a graph). We use a Disjoint Set Union (Union-Find) to count connected components (β₀). For graphs, β₁ equals the number of edges minus vertices plus components, following the Euler characteristic for 1D complexes. The example graph has two components and one independent cycle (the triangle).
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 // Bit-packed matrix over GF(2), stored as rows of uint64_t words 5 struct BitMatrix { 6 int rows, cols, W; // W = words per row 7 vector<vector<uint64_t>> a; // a[rows][W] 8 BitMatrix(int r=0, int c=0): rows(r), cols(c) { 9 W = (cols + 63) / 64; 10 a.assign(rows, vector<uint64_t>(W, 0ULL)); 11 } 12 inline void set1(int r, int c){ 13 int w = c >> 6, b = c & 63; 14 a[r][w] |= (1ULL << b); 15 } 16 inline bool get(int r, int c) const { 17 int w = c >> 6, b = c & 63; 18 return (a[r][w] >> b) & 1ULL; 19 } 20 inline void xor_row(int i, const vector<uint64_t>& pivot){ 21 for(int w=0; w<W; ++w) a[i][w] ^= pivot[w]; 22 } 23 }; 24 25 int rank_mod2(BitMatrix M){ 26 int r = 0; // current pivot row 27 for(int col=0; col<M.cols && r < M.rows; ++col){ 28 int sel = -1; 29 for(int i=r; i<M.rows; ++i){ if (M.get(i,col)) { sel = i; break; } } 30 if (sel == -1) continue; // no pivot in this column 31 swap(M.a[sel], M.a[r]); 32 // Eliminate this column in all other rows 33 for(int i=0; i<M.rows; ++i){ if (i!=r && M.get(i,col)) M.xor_row(i, M.a[r]); } 34 ++r; 35 } 36 return r; // number of pivots = rank 37 } 38 39 struct PairHash { size_t operator()(const pair<int,int>& p) const noexcept { return (uint64_t)p.first<<32 ^ (uint64_t)p.second; } }; 40 41 // Build edges and boundary matrices for a 2D simplicial complex 42 // Input: V vertices labeled 0..V-1, list of triangles (each as 3 vertex indices) 43 // Output: beta0, beta1, beta2 over Z2 44 struct Betti2D { 45 int V, E, T; 46 int beta0, beta1, beta2; 47 }; 48 49 Betti2D compute_betti_2D(int V, const vector<array<int,3>>& triangles){ 50 // Deduplicate edges from triangles 51 unordered_map<pair<int,int>, int, PairHash> edge_id; 52 vector<pair<int,int>> edges; 53 auto add_edge = [&](int u, int v){ if(u>v) swap(u,v); auto key = make_pair(u,v); if(!edge_id.count(key)){ int id = (int)edges.size(); edge_id[key]=id; edges.push_back(key); } }; 54 55 for (auto tri : triangles){ 56 array<int,3> t = tri; 57 sort(t.begin(), t.end()); // canonicalize triangle vertex order (not strictly needed over Z2) 58 add_edge(t[0], t[1]); add_edge(t[0], t[2]); add_edge(t[1], t[2]); 59 } 60 int E = (int)edges.size(); 61 int T = (int)triangles.size(); 62 63 // Build boundary matrices over Z2: \partial_1: C1->C0 is V x E; \partial_2: C2->C1 is E x T 64 BitMatrix d1(V, E); 65 for (int e=0; e<E; ++e){ 66 auto [u,v] = edges[e]; 67 d1.set1(u, e); 68 d1.set1(v, e); 69 } 70 71 BitMatrix d2(E, T); 72 for (int t=0; t<T; ++t){ 73 int a = triangles[t][0], b = triangles[t][1], c = triangles[t][2]; 74 // canonicalize edges (u<v) to match edge_id keys 75 auto key1 = make_pair(min(a,b), max(a,b)); 76 auto key2 = make_pair(min(a,c), max(a,c)); 77 auto key3 = make_pair(min(b,c), max(b,c)); 78 int e1 = edge_id[key1]; 79 int e2 = edge_id[key2]; 80 int e3 = edge_id[key3]; 81 d2.set1(e1, t); d2.set1(e2, t); d2.set1(e3, t); 82 } 83 84 int rank_d1 = rank_mod2(d1); 85 int rank_d2 = rank_mod2(d2); 86 87 // Betti numbers over Z2 for a 2D complex (no 3-simplices): 88 // beta0 = m0 - rank(d1), beta1 = m1 - rank(d1) - rank(d2), beta2 = m2 - rank(d2) 89 Betti2D out; out.V=V; out.E=E; out.T=T; 90 out.beta0 = V - rank_d1; 91 out.beta1 = E - rank_d1 - rank_d2; 92 out.beta2 = T - rank_d2; // since \partial_3 = 0 (no tetrahedra) 93 return out; 94 } 95 96 int main(){ 97 // Example: boundary of a tetrahedron (a sphere S^2) 98 // Vertices: 0,1,2,3; Triangles: all 4 faces 99 int V = 4; 100 vector<array<int,3>> tris = { 101 {0,1,2}, {0,1,3}, {0,2,3}, {1,2,3} 102 }; 103 auto betti = compute_betti_2D(V, tris); 104 105 cout << "2D complex stats: V=" << betti.V << ", E=" << betti.E << ", T=" << betti.T << "\n"; 106 cout << "Betti numbers over Z2:" << "\n"; 107 cout << "beta0 (components) = " << betti.beta0 << "\n"; 108 cout << "beta1 (tunnels) = " << betti.beta1 << "\n"; 109 cout << "beta2 (voids) = " << betti.beta2 << "\n"; 110 111 // Expected for sphere: beta0=1, beta1=0, beta2=1 112 return 0; 113 } 114
We construct boundary matrices ∂₁ (edges→vertices) and ∂₂ (triangles→edges) over Z₂ using bit-packed rows. Gaussian elimination modulo 2 yields their ranks. Betti numbers then follow from β₀ = m₀ − rank(∂₁), β₁ = m₁ − rank(∂₁) − rank(∂₂), and β₂ = m₂ − rank(∂₂) (since there are no 3-simplices). The example is the surface of a tetrahedron (a sphere), which has β₀=1, β₁=0, β₂=1.