A coverage problem you cannot solve by staring at a map
A food-delivery startup in Bengaluru equips forty riders with GPS beacons that talk to each other over short-range radio. Two riders' phones register each other whenever they come within about 300 metres — nothing more, no GPS coordinates are logged anywhere central, only a list of "rider 7 saw rider 19" pings collected over a shift. At the end of the day, the operations team wants one yes/no answer: is there a pocket inside the delivery zone that no rider ever patrolled near — a gap surrounded on all sides by covered territory, sitting there silently losing the company orders? They do not have a map. They only have the list of who-saw-whom.
This is not a toy question. It is a real, named problem in applied topology — sensor and robot coverage verification — because in real deployments (Wi-Fi mesh planning, wildlife-camera grids, robot-swarm patrol) you frequently know local proximity but not global geometry, and building or maintaining an accurate floor plan is expensive or impossible. What you'll learn in this chapter is a rigorous method that answers the gap question directly from the proximity list, with a proof that the answer is correct — no map required. That method is homology, and the field that develops and computes it algorithmically for exactly this kind of data is topological data analysis (TDA).
From a proximity list to a shape: simplicial complexes
Treat each rider as a point. Whenever two riders' beacons detected each other within radius ε (epsilon, the sensing range), draw an edge between them. This graph alone is already useful — its connected components tell you which riders belong to which cluster of coverage. But a graph cannot tell you about a gap, because a graph has no notion of an "inside." Three riders arranged in a triangle, all pairwise within range, look identical in graph terms whether or not there is a genuine gap in the middle of that triangle — a graph only records edges, not whether the triangular patch between them is actually covered.
To fix this, we add a rule: whenever three riders are all pairwise within range of each other, we additionally mark the solid triangle they span as "filled" — meaning we're asserting that this triple's territory is jointly patrolled and has no gap in it. This upgraded object — points, edges between close pairs, filled triangles between close triples, and in principle filled tetrahedra between close quadruples, and so on — is called a simplicial complex. A single point is a 0-simplex, an edge is a 1-simplex, a filled triangle is a 2-simplex, a filled tetrahedron is a 3-simplex. Building the complex this way — connect if close, fill if all pairs are close — is called the Vietoris–Rips construction, and a complex built by this "fill in every clique" rule is called a flag complex. It is the standard way to turn a raw point cloud or proximity graph into a shape you can do topology on.
Here is the key idea the rest of the chapter formalizes: if three riders are mutually close but the triangle between them is not filled — because, say, a fourth rider's territory should have covered that triangle but the fourth rider never showed up — the boundary of that triangle (the three edges, traversed as a loop) is a genuine "hole" in the complex. If instead the triangle is filled, that same loop is not a hole; it is the edge of a solid patch. Homology is the machinery that counts, with a proof, exactly how many independent holes of each dimension a complex has — completely mechanically, from the list of which simplices exist.
Chains: turning shapes into bit-vectors
Before we can compute anything we need one small but essential piece of machinery, built from scratch so nothing is assumed. Work over the two-element number system {0, 1} with addition done modulo 2 (so 1 + 1 = 0): this is called the field GF(2) (or ℤ/2), and using it lets us ignore direction/orientation issues entirely, which is exactly right for a first treatment.
Fix the list of all edges in a complex, say three edges ab, bc, ac. A 1-chain is simply a choice, for each edge, of "included" (1) or "not included" (0) — equivalently, a bit-vector like (1,1,0) meaning "include ab and bc, not ac." Two chains are added by XOR-ing bit by bit: (1,1,0) + (1,0,1) = (0,1,1), i.e. take the symmetric difference of the two edge-sets. This addition — together with only two possible "scalars," 0 and 1 — satisfies every law a vector space is required to satisfy: there's a zero vector (all-zeros, the empty chain), every chain is its own additive inverse (add anything to itself and you get zero, since x + x = 0 mod 2), and addition is associative and commutative. That is the entire content of "vector space" that we need — a collection of objects you can add together and combine with scalars, following the ordinary rules of arithmetic. The set of all 1-chains on our three edges is written C₁; similarly C₀ is the vector space of 0-chains (bit-vectors over vertices) and C₂ is the vector space of 2-chains (bit-vectors over filled triangles).
A subspace is just a smaller collection of chains that is itself closed under this addition and contains the zero chain — for instance, "all chains that happen to form closed loops" turns out to be a subspace of C₁, as you'll see below. A quotient space groups the vectors of a space into batches, where two vectors land in the same batch exactly when they differ by an element of the subspace being divided out. Keep that one sentence in mind — "same batch if they differ by something in the subspace" — because it is literally the definition of homology, stated three sections from now, and nothing more mysterious than that.
The boundary operator, and why applying it twice gives zero
Define the boundary operator ∂ on the basic building blocks and extend it to all chains by linearity (apply it to each simplex separately, then add the results mod 2).
∂₁sends an edge to its two endpoints:∂₁{a,b} = {a} + {b}.∂₂sends a filled triangle to its three bounding edges:∂₂{a,b,c} = {a,b} + {b,c} + {a,c}.
Now compute what happens when you apply both in sequence to a filled triangle abc — first take its boundary (the three edges), then take the boundary of that:
∂₁(∂₂{a,b,c}) = ∂₁({a,b} + {b,c} + {a,c})
= ∂₁{a,b} + ∂₁{b,c} + ∂₁{a,c}
= ({a}+{b}) + ({b}+{c}) + ({a}+{c})
= 2·{a} + 2·{b} + 2·{c}
= 0 (mod 2, since 2 = 0)
Every vertex appears exactly twice — once from each of the two edges meeting at it — and mod 2 that's zero. This is not a coincidence for this one triangle; the same cancellation happens for any simplex, because every vertex of a boundary appears in exactly two of the bounding faces. The identity ∂ ∘ ∂ = 0 ("the boundary of a boundary is always empty") is the single algebraic fact the entire theory of homology is built on.
Cycles, boundaries, and homology
A cycle is any chain whose boundary is zero — a closed loop with nothing sticking out. The space of all k-cycles is written Zₖ = ker(∂ₖ) (everything the boundary operator sends to zero). A boundary is any chain that is itself the boundary of something one dimension up — Bₖ = im(∂ₖ₊₁) (everything you can reach by applying ∂ₖ₊₁ to something in the next dimension). Because ∂∂ = 0, every boundary is automatically a cycle (applying ∂ₖ to something already in the image of ∂ₖ₊₁ gives zero by the identity above), so Bₖ is always a subspace of Zₖ — exactly the "subspace inside a bigger space" situation the quotient idea needs.
The k-th homology group is defined as
Hₖ = Zₖ / Bₖ
Using the plain-language definition of quotient from two sections ago: two cycles are declared "the same homology class" exactly when they differ by a boundary — i.e., when one can be turned into the other by filling in some collection of (k+1)-dimensional simplices from the complex. A cycle that is not a boundary of anything represents a genuine, unfillable hole. The Betti number bₖ is simply the dimension of Hₖ — how many independent, unfillable k-dimensional holes the complex has. b₀ counts connected components, b₁ counts independent loops/tunnels, b₂ counts independent enclosed voids, and so on. Using rank–nullity for the linear map ∂ₖ (dimension of the domain equals dimension of the kernel plus dimension of the image), we get the formula we'll actually compute with:
bₖ = dim(Zₖ) − dim(Bₖ) = ( nₖ − rank(∂ₖ) ) − rank(∂ₖ₊₁)
where nₖ is the number of k-simplices in the complex.
Worked example: the hollow triangle versus the filled triangle
Take three riders a, b, c, all mutually within range, so all three edges ab, bc, ac exist. Compare two situations.
Case 1 — hollow (no fourth rider ever confirmed the interior; no 2-simplex). C₀ has dimension 3 (vertices a,b,c), C₁ has dimension 3 (edges ab,bc,ac), C₂ has dimension 0.
Write ∂₁ as a matrix with one row per vertex and one column per edge, entry 1 if that vertex is an endpoint of that edge, columns ordered (ab, bc, ac):
ab bc ac
a [ 1 0 1 ]
b [ 1 1 0 ]
c [ 0 1 1 ]
Row-reduce mod 2: add row a to row b (row b becomes [0,1,1]), then that new row b equals row c exactly, so adding it to row c zeroes row c out. Two nonzero rows survive, so rank(∂₁) = 2.
Then b₀ = (n₀ − rank(∂₀)) − rank(∂₁) = (3 − 0) − 2 = 1 (one connected component — correct, all three riders are linked). And b₁ = (n₁ − rank(∂₁)) − rank(∂₂) = (3 − 2) − 0 = 1, since there are no triangles so rank(∂₂) = 0. One hole — the loop ab + bc + ac is a cycle (check: its boundary is 2a+2b+2c=0) that is not the boundary of anything, because there is nothing in C₂ to be its source.
Case 2 — filled (a fourth rider confirmed the interior; the 2-simplex abc is added). Now n₂ = 1 and ∂₂{a,b,c} = ab+bc+ac, a single nonzero column, so rank(∂₂) = 1. Recompute: b₁ = (3 − 2) − 1 = 0. The loop is still a cycle, but now it is a boundary (of the filled triangle), so it collapses to the zero class — no hole. And b₂ = (n₂ − rank(∂₂)) − rank(∂₃) = (1 − 1) − 0 = 0: the single triangle's boundary is nonzero (it doesn't vanish), so it isn't itself an unfilled cycle, meaning no 2-dimensional void either. b₀ is unchanged at 1.
Computing Betti numbers in code
The row-reduction we just did by hand is exactly Gaussian elimination over GF(2) — the same elementary row operations you already use in Class 12 to find a matrix's inverse or test whether a system of three linear equations is consistent, except every entry is either 0 or 1 and "subtract" becomes XOR. Here is a rank function that performs it, followed by a function that assembles Betti numbers from ranks using the formula derived above.
def gf2_rank(matrix):
mat = [row[:] for row in matrix]
rows = len(mat)
if rows == 0:
return 0
cols = len(mat[0])
rank = 0
for col in range(cols):
pivot_row = None
for r in range(rank, rows):
if mat[r][col] == 1:
pivot_row = r
break
if pivot_row is None:
continue
mat[rank], mat[pivot_row] = mat[pivot_row], mat[rank]
for r in range(rows):
if r != rank and mat[r][col] == 1:
mat[r] = [a ^ b for a, b in zip(mat[r], mat[rank])]
rank += 1
if rank == rows:
break
return rank
def betti_numbers(n_simplices, boundary_matrices, max_dim):
ranks = {0: 0}
for k in range(1, max_dim + 1):
ranks[k] = gf2_rank(boundary_matrices[k]) if k in boundary_matrices else 0
betti = []
for k in range(max_dim + 1):
dim_Z = n_simplices[k] - ranks.get(k, 0)
dim_B = ranks.get(k + 1, 0)
betti.append(dim_Z - dim_B)
return betti
EDGE_BOUNDARY = [[1, 0, 1], [1, 1, 0], [0, 1, 1]] # d1, rows a,b,c cols ab,bc,ac
TRIANGLE_BOUNDARY = [[1], [1], [1]] # d2, rows ab,bc,ac col abc
hollow = betti_numbers([3, 3, 0], {1: EDGE_BOUNDARY}, 1)
filled = betti_numbers([3, 3, 1], {1: EDGE_BOUNDARY, 2: TRIANGLE_BOUNDARY}, 2)
print("hollow triangle:", hollow[0], hollow[1])
print("filled triangle:", filled[0], filled[1])
Tracing gf2_rank(EDGE_BOUNDARY): pivot found at row a, column ab; row b gets XORed with row a, becoming [0,1,1], matching row c exactly; the next pivot search finds row b's new form at column bc; XOR-ing row c with it zeroes row c completely; no pivot exists in the last column, so the loop ends with rank = 2. Tracing gf2_rank(TRIANGLE_BOUNDARY): a single column of three 1s — the first becomes the pivot, XOR-ing zeroes out the other two, giving rank = 1. Feeding these ranks through betti_numbers reproduces exactly the hand computation above: the program prints hollow triangle: 1 1 and filled triangle: 1 0.
Persistent homology: which holes are real, and which are noise?
Real coordinates are never exact, and choosing a single sensing radius ε is guesswork — pick it too small and everything looks disconnected with fake holes everywhere; pick it too large and everything collapses into one solid blob with no information left. The fix is to stop picking one ε and instead sweep it from 0 to infinity, building the Vietoris–Rips complex at every radius and tracking, for each hole, the ε at which it is born (first appears as an unfilled cycle) and the ε at which it dies (gets filled in). The lifetime death − birth is called its persistence. Long-lived features are structurally real; short-lived ones are noise from the exact point positions and should be ignored. This is persistent homology, and it is the actual algorithm run by TDA software — computing Betti numbers at a single fixed ε, as we did above, is only the warm-up.
Take four riders at the corners of a unit square, side length 1, so the two diagonals have length √2. At ε = 0 there are no edges: four isolated points, b₀ = 4. As ε crosses 1, all four side edges appear simultaneously, linking the four points into a single 4-cycle: three of the four components merge away (three b₀-bars die at ε=1, one survives forever), and at the same instant a loop is born (b₁ jumps from 0 to 1) — the hollow square. As ε crosses √2, both diagonals appear at once; each diagonal, combined with two existing side edges, completes a triangle that becomes a filled 2-simplex, so the loop dies. Its persistence is √2 − 1 ≈ 0.414 — a clearly nonzero lifetime, telling you this loop is a genuine structural feature of the square, not sampling noise.
Two misconceptions worth correcting now
Misconception 1: "homology finds holes you can see." That is true only for shapes drawn in the plane or in 3-D space that you can look at directly. The delivery-rider complex has no picture at all — it exists purely as a list of which subsets of riders are mutually close. Homology still computes b₁ correctly from the boundary matrices alone, with no drawing involved, and the same machinery works unchanged in dimension 50 for a dataset with 50 measured features per point, where there is nothing to visually inspect. What homology actually detects is algebraic: a cycle that is not a boundary. "Looks like a hole" is a helpful mental picture only in the two or three dimensions where pictures are possible.
Misconception 2: "more data points or a finer complex is always more accurate." A denser Vietoris–Rips complex built from noisy real-world coordinates typically has dozens of short-lived spurious loops that appear and die almost immediately as ε sweeps past — artifacts of exactly where the noisy points happened to land, not features of the underlying shape. Persistence is precisely the tool that separates signal from this noise: a feature living for a long stretch of ε is structural; one that is born and dies within a tiny sliver of ε is discarded. Reading off Betti numbers at a single arbitrarily chosen ε, without checking persistence, is the actual beginner mistake — not the amount of data itself.
Cohomology: the same information, viewed as functions
A k-cochain is a function from k-chains to GF(2) that respects addition — concretely, since a chain is a bit-vector, a cochain is just another bit-vector paired with it via a dot product mod 2. The coboundary operator δ is defined to be the "transpose" of ∂: it goes the other way, from k-cochains to (k+1)-cochains, and satisfies δ∘δ = 0 for exactly the same reason ∂∘∂ = 0 did — it's built from the same matrix, just read in the transposed direction. Cocycles, coboundaries, and cohomology groups Hᵏ = ker(δₖ) / im(δₖ₋₁) are then defined by direct analogy. Over a field such as GF(2), transposing a matrix never changes its rank, so dim(Hᵏ) = dim(Hₖ) — cohomology and homology carry the exact same numerical information (the same Betti numbers) in this setting, which is why, for Betti-number purposes, you never need both.
Cohomology earns its keep once you ask a sharper question than "how many holes": how do the holes relate to each other? Cohomology classes can be multiplied via the cup product, giving each space a ring structure that plain Betti numbers cannot see. The textbook example: a torus (the surface of a doughnut) and the disjoint-looking space "figure-eight union a sphere" (written S¹∨S¹∨S², two loops glued at a point, with a separate sphere also glued there) have identical Betti numbers — b₀=1, b₁=2, b₂=1 for both. Homology alone genuinely cannot tell them apart. But on the torus, cupping the two independent 1-dimensional loop-classes together produces the nonzero 2-dimensional class (the two loops, swept against each other, trace out the whole surface); on S¹∨S¹∨S², the two loops live in physically separate parts of the space and their cup product is forced to be zero. Same Betti numbers, different ring — genuinely different shapes, and cohomology is the tool that proves it.
Where this actually sits relative to your exams
Be precise about what is, and is not, examinable here. Algebraic topology — simplicial complexes, homology, Betti numbers, cohomology — is not part of the CBSE Class 10–12 syllabus, JEE Main/Advanced, BITSAT, or the GATE Computer Science syllabus; none of these list it, and you will not be asked to compute a Betti number in any of them. What you should take from this chapter for those exams is narrower and honest: the row-reduction you traced by hand and in gf2_rank is the identical elimination technique CBSE Class 12 uses for matrix inverses and for testing whether a system of three linear equations has a solution — practising it here, over GF(2) instead of the reals, sharpens exactly that mechanical skill. Beyond school exams, this subject is genuinely used: topological data analysis is an active research and industry tool for analysing high-dimensional biological and sensor data (recovering loop structure in gene-expression data, verifying sensor-network coverage as in the opening example, characterising the shape of neural-activity data), and the combinatorial precision it demands — is this cycle really not a boundary, can you prove it — is the same kind of rigour rewarded in olympiad mathematics and in competitive-programming contests like ICPC, where graph and invariant arguments are graded on whether the proof actually closes, not on the right final number.
Summary
- A simplicial complex is built from a point cloud by connecting nearby pairs (edges) and filling in triangles/tetrahedra whenever every pair within a group is close — the Vietoris–Rips or flag-complex rule.
- Chains are bit-vectors over simplices (vectors over
GF(2), addition = XOR); the boundary operator∂sends each simplex to the mod-2 sum of its faces, and∂∘∂ = 0always, because every vertex of a boundary is hit exactly twice. - Cycles are chains with zero boundary (
Zₖ); boundaries are chains that are themselves boundaries of something one dimension up (Bₖ⊆Zₖ); homology is the quotientHₖ=Zₖ/Bₖ— cycles counted as "the same" whenever they differ by a fillable patch.bₖ = dim Hₖcounts unfillablek-dimensional holes. - Betti numbers reduce entirely to matrix-rank computation over
GF(2):bₖ = (nₖ − rank ∂ₖ) − rank ∂ₖ₊₁, computable by ordinary Gaussian elimination, as verified in the hollow-triangle (b₀=1,b₁=1) versus filled-triangle (b₀=1,b₁=0) example. - Persistent homology sweeps the connection radius
εand tracks each hole's birth and death; long persistence (death − birth) means a real structural feature, short persistence means noise from exact point placement. - Cohomology is built from functions on chains via the coboundary
δ, has the same Betti numbers as homology over a field, but carries extra ring structure (the cup product) that can distinguish spaces — like a torus versusS¹∨S¹∨S²— that homology alone sees as identical.
Active recall
- Five riders form a pentagon: each is close only to its two neighbours around the ring, no diagonals, no filled triangles. Without building a matrix, predict
b₀andb₁, then verify with the rank formula (n₀=5, n₁=5, n₂=0;rank ∂₁for any connected cycle graph onnvertices isn−1). - Prove, the same way the chapter did for a triangle, that
∂₁(∂₂{a,b,c,d})is zero mod 2 for a filled tetrahedron's four triangular faces — i.e. show every edge appears an even number of times across the four faces' boundaries. - In the four-corner square example, what happens to
b₂atε=√2if the two diagonal points are pulled slightly so the two diagonals have different lengths instead of both exactly√2? Describe the resulting barcode forb₁in that case. - A dataset has one loop with persistence 0.02 and another with persistence 1.8, on a filtration where
εranges up to 5. Which one would you report as a real topological feature of the underlying data, and why? - Explain in one sentence why cohomology's Betti numbers equal homology's Betti numbers over a field, but the two still are not "the same information" once you bring in the cup product.
Think About It
Think about this: How would you explain algebraic topology in data: homology, cohomology, and topological data analysis to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.
Practice Exercises
Now it is time to practice! Complete these challenges to solidify your understanding:
- Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
- Exercise 2: Find a real-world example where algebraic topology in data: homology, cohomology, and topological data analysis is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
- Exercise 3: Create a mind-map connecting algebraic topology in data: homology, cohomology, and topological data analysis to at least 3 other topics you have studied.