AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Optimal Transport Theory: Geometry of Probability Distributions

🔬
Beyond Syllabus — Enrichment Content

This chapter covers advanced research topics beyond standard CBSE/NCERT scope. It's designed for curious minds preparing for IIT-JEE Advanced, KVPY, or research-track studies. Core exam preparation does not require this material.

📚 Programming & Coding⏱️ 25 min read🎓 Grade 10🔬 Beyond Syllabus
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 25 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Picture a quick-commerce dark store — a small, no-storefront warehouse tucked into a residential lane in Bengaluru, stocked only to fulfil app orders within minutes. One evening, the inventory dashboard flags a mismatch: two micro-warehouses on the same stretch of road are sitting on surplus stock of a fast-moving item after a forecasting error, while two dark stores nearby are running low before the dinner rush. The dispatcher's job isn't to figure out how much stock exists — that's already known from the app. It's to decide exactly how many units travel from each warehouse to each store so that the total cost of moving everything — distance times quantity times fuel and rider time — is as small as possible.

That question — how do you reshape one pile of "stuff" into another pile of "stuff" at the lowest possible total cost — is not just a logistics puzzle. It has a name that is almost two and a half centuries old: Optimal Transport. Once you can answer it precisely, you also get, almost for free, a rigorous way to measure how different two probability distributions are — a tool that now sits quietly inside some of the most important machine learning systems built in the last decade, including the training procedure behind Generative Adversarial Networks. That geometry is built from the ground up below, starting with a two-warehouse delivery problem small enough to solve by hand.

From Piles of Earth to Distributions of Probability

In 1781, the French mathematician Gaspard Monge posed a problem to the French Royal Academy of Sciences that reads like a construction-site logistics question. Suppose you have a pile of excavated earth and a pit that needs filling. Every shovel-load has to move from somewhere in the pile to somewhere in the pit, and moving a shovel-load a longer distance costs more effort than moving it a shorter distance. Given the shapes of the pile and the pit, which shovel-load should go where, so that the total effort is as small as possible?

Monge demanded a strict kind of answer: a single function that sends every point of the source pile to exactly one point in the destination pit. You are not allowed to split a shovel-load and send half of it to two different places. That restriction makes the problem mathematically unforgiving — for some pairs of piles, no such all-or-nothing function even exists, and even when one does, proving it is the cheapest one is hard.

The breakthrough came more than a century and a half later, from an entirely different direction. In the 1940s, the Soviet mathematician and economist Leonid Kantorovich was working on how to allocate scarce industrial resources efficiently — which factory should ship how much of which raw material to which plant. His key move was to relax Monge's all-or-nothing rule: instead of insisting that every unit of mass travel to exactly one destination, allow it to be split across several destinations, as long as you keep exact track of how much goes where. This "split allowed" version is called a transport plan, and it turns the problem into a linear program — an optimisation with a cost that is linear in the unknowns, and constraints that are linear equations. Linear programs are not just guaranteed to have a solution whenever total supply equals total demand; they can be solved efficiently, with well-understood algorithms. In 1975, Kantorovich shared the Nobel Memorial Prize in Economic Sciences with Tjalling Koopmans "for their contributions to the theory of optimum allocation of resources" — the same allocation ideas that began with a pile of dirt.

Stated in general terms, here is the modern Optimal Transport problem:

  • A source distribution p: a way of spreading a fixed total amount of mass over a set of locations — crates across warehouses, probability across outcomes, or ink density across pixels.
  • A target distribution q: a different way of spreading that same total amount of mass over a set of locations.
  • A cost function that says how expensive it is to move one unit of mass from any given source location to any given target location — usually distance, but not always.
  • A transport plan that reshapes p into q at the lowest possible total cost — this is what you are solving for.

Written out with numbers, if p assigns weights across m source locations and q assigns weights across n target locations, a transport plan is an m by n table T, where T[i][j] is the amount of mass moved from source i to target j. Every row of T must sum to the corresponding source weight (all of source i's mass is accounted for), and every column must sum to the corresponding target weight (target j ends up with exactly what it needs). Subject to those two conditions, you minimise the total cost: every cell's amount multiplied by its cost, summed over the whole table. That is precisely the dispatcher's problem at the dark store. Let's solve one.

A Fully Worked Example — Two Warehouses, Two Dark Stores

Warehouse W1 has a surplus of 30 crates; warehouse W2 has a surplus of 20 crates — 50 crates in total. Dark store S1 needs 25 crates; dark store S2 needs 25 crates — also 50 in total, so a valid plan is guaranteed to exist. Road distances are: W1 to S1 is 2 km, W1 to S2 is 6 km, W2 to S1 is 5 km, and W2 to S2 is 1 km. Delivery costs ₹3 per crate per kilometre, covering fuel, rider time, and handling. Multiplying distance by rate gives a cost per crate for every route:

cost_matrix = [
    [6, 18],   # from W1: to S1, to S2
    [15, 3],   # from W2: to S1, to S2
]
supply = [30, 20]   # W1, W2
demand = [25, 25]   # S1, S2

A tempting first instinct is to send every warehouse's stock to its nearest store. W1's nearer store is S1 (2 km beats 6 km), so send all 30 crates there. W2's nearer store is S2 (1 km beats 5 km), so send all 20 crates there. This "nearest neighbour" instinct does not even produce a valid plan: S1 receives 30 crates when it only needs 25, and S2 receives 20 when it needs 25. Supply and demand have to line up exactly, and nearest-store routing alone cannot guarantee that.

Because there are only two warehouses and two stores, once you choose one number, the rest of the table is forced. Let x be the number of crates sent from W1 to S1. Then W1 sends its remaining 30 − x crates to S2. S1's remaining need of 25 − x must come from W2. Whatever W2 has left after that, x − 5, goes to S2. For every quantity to stay non-negative, x must be at least 5 (otherwise W2's leftover goes negative) and at most 25 (otherwise S1's remaining need goes negative). So x can be any whole number from 5 to 25.

Total cost as a function of x is 6x + 18(30 − x) + 15(25 − x) + 3(x − 5). Expanding term by term: 6x + 540 − 18x + 375 − 15x + 3x − 15. Collecting the x terms (6 − 18 − 15 + 3 = −24) and the constants (540 + 375 − 15 = 900) gives cost(x) = 900 − 24x — a straight line that falls as x rises. Because crates are whole units, the honest way to find the minimum is to check every whole-number value of x, not to reach for calculus on a line that only exists at integers:

best_cost = None
best_x = None
for x in range(5, 26):          # x = crates sent from W1 to S1
    t11, t12 = x, 30 - x
    t21, t22 = 25 - x, x - 5
    cost = (t11 * cost_matrix[0][0] + t12 * cost_matrix[0][1]
            + t21 * cost_matrix[1][0] + t22 * cost_matrix[1][1])
    if best_cost is None or cost < best_cost:
        best_cost, best_x = cost, x

print(best_x, best_cost)

range(5, 26) checks 21 candidate values of x — 5, 6, 7, all the way to 25 — one at a time. Tracing three of them by hand: at x = 5, the plan is (5, 25, 20, 0) and the cost is 5(6) + 25(18) + 20(15) + 0(3) = 30 + 450 + 300 + 0 = 780. At x = 15, the plan is (15, 15, 10, 10) and the cost is 15(6) + 15(18) + 10(15) + 10(3) = 90 + 270 + 150 + 30 = 540. At x = 25, the plan is (25, 5, 0, 20) and the cost is 25(6) + 5(18) + 0(15) + 20(3) = 150 + 90 + 0 + 60 = 300. Each of these matches the formula 900 − 24x exactly — 900 minus 120 is 780, minus 360 is 540, minus 600 is 300 — a cross-check worth making a habit of, since it catches arithmetic slips before they turn into an undelivered order. Because cost(x) falls as x rises, the cheapest option sits at the largest allowed value, x = 25, and the loop prints 25 300.

The optimal plan sends 25 crates from W1 to S1, 5 crates from W1 to S2, 0 crates from W2 to S1, and 20 crates from W2 to S2, for a total cost of ₹300 — ₹480 cheaper than the worst valid plan and, notably, still ₹240 cheaper than the evenly-split plan at x = 15. Notice that one of the four possible routes, W2 to S1, carries nothing at all. That is not a coincidence of these particular numbers: an optimal transport plan between m sources and n destinations can always be achieved using at most m + n − 1 non-zero routes — here, 2 + 2 − 1 = 3. This matters at real scale: a quick-commerce network with 40 warehouses and 200 dark stores has 8,000 possible routes, but its optimal plan will only ever need a few hundred of them.

You don't have to take the loop's word for it, either. Feeding the same cost matrix, supply, and demand into a general-purpose linear program solver — one that knows nothing about transport plans specifically — confirms it:

from scipy.optimize import linprog

c = [6, 18, 15, 3]                       # cost per crate: T11, T12, T21, T22
A_eq = [[1, 1, 0, 0],                    # W1 supply
        [0, 0, 1, 1],                    # W2 supply
        [1, 0, 1, 0],                    # S1 demand
        [0, 1, 0, 1]]                    # S2 demand
b_eq = [30, 20, 25, 25]

result = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=(0, None), method="highs")
print(result.x, result.fun)

This prints [25. 5. 0. 20.] 300.0 — the identical plan and the identical cost, confirming that the brute-force loop did not just find the best of 21 candidates; it found the true minimum over every conceivable way of splitting the crates, fractional amounts included. Whenever supply and demand are whole numbers, as they are here, a transportation problem's optimal solution can always be achieved with whole-number shipments too, so a whole-number sweep is never a shortcut that risks missing a better fractional plan — it is guaranteed to land on the exact answer.

One more step turns this into the language of probability. Divide every number by the grand total, 50 crates. Supply [30, 20] becomes p = (0.6, 0.4) — a probability distribution over the two warehouses. Demand [25, 25] becomes q = (0.5, 0.5) — a probability distribution over the two stores. This is the leap Optimal Transport asks you to make: p and q don't have to represent "chance" in the everyday sense. They can be any two ways of spreading a fixed total amount of mass across a set of locations — crates, probability, or, as you'll see shortly, pixel brightness. The mathematics doesn't care which one it is; only the bookkeeping of where the mass sits matters. That's why the same machinery that solved a delivery problem also gives a way to measure how different two probability distributions are.

The Number Line — Why Sorting Wins

Now shrink the problem to one dimension: every source point and every target point sits on a single number line. When the cost of moving one unit of mass from position x to position y is just the distance |x − y|, the full linear-program machinery above still applies — but it's overkill. There is a shortcut: sort both sets of points and match them up in order. The smallest source pairs with the smallest target, the second-smallest with the second-smallest, and so on. This sorted, non-crossing pairing is always optimal, and it is easy to see why.

Suppose two routes cross: mass at position 3 is sent to position 7, while mass at position 9 — further right — is sent to position 5, which lands to the left of where the first route ends up. Swap their destinations instead, sending 3 to 5 and 9 to 7:

sorted_pairing_cost = abs(3 - 5) + abs(9 - 7)   # equals 4
crossed_pairing_cost = abs(3 - 7) + abs(9 - 5)  # equals 8

The sorted pairing costs 4; the crossed one costs 8 — twice as much, for moving the exact same two units of mass. This is not a quirk of these four numbers: whenever two routes cross on a line, swapping their endpoints so they no longer cross never increases the total cost, and strictly decreases it unless both routes were already headed to the same place. Keep uncrossing every crossed pair in any candidate plan and you eventually reach the one arrangement with no crossings left — which, for points on a line, is always the sorted, in-order matching. That argument is a complete proof, not just a suggestive example: no amount of clever crossing can ever beat sorting.

Wasserstein Distance — Measuring the Gap Between Distributions

The minimum total cost produced by this sorted matching has its own name: the Wasserstein-1 distance, also called the Earth Mover's Distance — a direct nod back to Monge's piles of earth. It behaves like a genuine distance: it is zero exactly when the two distributions are identical, it is symmetric, and it obeys the triangle inequality. Unlike comparing two distributions bucket by bucket, it respects the geometry of where the mass actually sits — two distributions that are simply shifted slightly apart get a small distance, not a large one, even if they share no buckets in common.

Suppose that, across two different T20 innings, wickets fell in these overs:

innings_a = [9, 3, 14, 6, 8]
innings_b = [7, 2, 12, 5, 9]

def wasserstein_1d(p, q):
    return sum(abs(x - y) for x, y in zip(sorted(p), sorted(q))) / len(p)

print(wasserstein_1d(innings_a, innings_b))

Sorting gives [3, 6, 8, 9, 14] for innings A and [2, 5, 7, 9, 12] for innings B. Pairing them in order and taking absolute differences: |3 − 2| = 1, |6 − 5| = 1, |8 − 7| = 1, |9 − 9| = 0, and |14 − 12| = 2. These sum to 5, and dividing by the 5 wickets gives a Wasserstein-1 distance of exactly 1.0: on average, each wicket in innings A sits one over away from its matched wicket in innings B.

Is the sorted pairing really the best of every possible way to match five wickets against five wickets, not just better than one crossed alternative? With 5 wickets a side there are 5! = 120 ways to pair them up. Checking every single one — a brute-force sweep, exactly like the warehouse loop, just over permutations instead of integers — confirms that the sorted pairing's total cost of 5 is the smallest of all 120, while the worst possible pairing costs 29, nearly six times as much.

SciPy ships a purpose-built version of this same calculation. Calling scipy.stats.wasserstein_distance(innings_a, innings_b) and printing the result gives 0.9999999999999999 — a floating-point hair's-breadth away from the 1.0 computed by hand above. SciPy reaches the same answer by a different but mathematically equivalent route, integrating the gap between the two distributions' cumulative curves rather than summing paired differences, and the tiny binary rounding is a normal, harmless side effect of that route, not a bug in either method. It is a useful reminder that "the code disagrees with my hand calculation by 0.0000000000000001" usually means floating-point arithmetic, not a wrong answer.

Why This Geometry Powers Modern AI

The step from a two-warehouse example to a genuinely important idea is that p and q can be distributions over anything with a sensible notion of distance — not just positions on a road.

Generative Adversarial Networks train a generator network by comparing the distribution of its fake images to the distribution of real ones. The original GAN formulation (Goodfellow et al., 2014) compares those two distributions with a measure that goes flat — an uninformative, near-zero gradient — whenever the distributions barely overlap, which is almost always true early in training, when the generator is still producing noise. In a January 2017 paper, Martin Arjovsky, Soumith Chintala, and Léon Bottou proposed the Wasserstein GAN, replacing that comparison with the Wasserstein distance. Because Wasserstein distance measures how far apart two distributions are in the underlying space rather than how much they overlap, its gradient stays informative even when the generator's output and the real images share almost no common ground — and this one substitution measurably stabilised GAN training, which had a reputation for being finicky and prone to collapse.

Word Mover's Distance (Kusner et al., ICML 2015) treats each document as a probability distribution — not over positions on a road, but over points in a word-embedding space, one point per word, weighted by how often that word appears. The "cost" of moving from one word to another is the distance between their embedding vectors, and because words with similar meaning sit close together in embedding space, that cost is small for synonyms and large for unrelated words. The Wasserstein distance between two documents' word distributions then becomes a similarity measure that understands "the movie was great" and "the film was excellent" describe almost the same thing, something plain keyword overlap cannot do.

Solving the exact linear program gets expensive fast as the number of source and target points grows — the warehouse example was a tiny 2 by 2 table, but a real application might compare distributions with thousands of points each. In a 2013 NeurIPS paper titled "Sinkhorn Distances," Marco Cuturi showed that adding a small entropy term to the transport-plan objective turns the linear program into a problem solvable by repeatedly rescaling the rows and then the columns of a matrix — the Sinkhorn algorithm — until both match their target sums. This is dramatically faster in practice and runs comfortably on a GPU, which is why entropy-regularised optimal transport, rather than the exact linear program, is the version actually used inside most large-scale machine learning systems today, including tools for domain adaptation and colour transfer between images.

One name is worth a careful note. Cédric Villani, one of the field's best-known figures and the author of its standard graduate textbooks, won the 2010 Fields Medal — but for related work on the Boltzmann equation and Landau damping, not for optimal transport itself. His textbooks are part of why "optimal transport" and "Villani" are so often mentioned in the same breath, even though the medal recognised different work.

Back to the Dark Store

The dispatcher at the Bengaluru dark store never needed to know the words "Kantorovich" or "Wasserstein" to solve tonight's problem — the brute-force loop from a few pages back finds the ₹300 answer in a fraction of a second, and a general-purpose linear-program solver confirms it without even needing to check every candidate. But the same idea, scaled from 2 warehouses and 2 stores to millions of pixels or millions of words, is what lets a generative model learn to produce a convincing image it was never directly shown, and lets a search system recognise that "car" and "automobile" occupy almost the same place in the geometry of meaning even though they share no letters. Every time a problem can be framed as reshaping one pile of mass into another as cheaply as possible — inventory into orders, random noise into images, one document's vocabulary into another's — it is standing on the geometry Monge sketched for piles of dirt in 1781, and that Kantorovich turned into a solvable line of mathematics a century and a half later, one row and column sum at a time.

Think About It

Think about this: How would you explain optimal transport theory: geometry of probability distributions 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.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind optimal transport theory: geometry of probability distributions, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← Neural ODEs: Learning Continuous-Time Dynamics with Neural NetworksSpectral Graph Theory: Eigenstructure of Network Adjacency and Laplacian Matrices →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn