Sensor Fusion: Classical to Modern · Lesson 10 of 22

The Particle Filter — When the Belief Isn't a Bell Curve

A robot dropped into a long, symmetric hallway could honestly be in three places at once. No single bell curve can say that. The particle filter throws the bell curve away and lets a swarm of guesses carry the belief — then fuses every new measurement by survival of the fittest.

Prerequisites: the Bayes filter (Lesson 4) + a little arithmetic. That's it.
11
Chapters
6
Simulations
0
Assumed Knowledge

Chapter 0: A Symmetric Hallway and the Belief That Has Three Peaks

A robot wakes up somewhere in a long office corridor and has no idea where. The corridor is almost featureless — identical doors, identical fluorescent panels, identical carpet — except for three fire extinguishers spaced evenly down the hall. The robot's only sensor is a little camera that can spot a fire extinguisher and report "there is one about two meters to my right." That is the entire universe: a hallway, three identical landmarks, and a robot asking "where am I?"

It senses a fire extinguisher two meters to its right. Now — where is it? Honestly, it could be next to the first extinguisher, or the second, or the third. The measurement is perfectly consistent with all three. The robot's belief about its own position is not a single guess with some wiggle room; it is three separate clusters of possibility, one hugging each extinguisher, with empty space between them. The truth lives in one of those clusters, but the robot has no way yet to tell which.

Common misconception. "Just track the position and its uncertainty — a mean and a variance." That is exactly the Kalman filter's belief: a single bell curve (a Gaussian), one peak, symmetric, defined by where its center sits and how wide it is. A bell curve can say "I'm probably here, give or take." It physically cannot say "I'm probably here OR here OR here, but definitely not in between." A distribution with one peak cannot represent three. The hallway breaks the Kalman filter not because the math is wrong, but because the shape of belief it can hold is too simple.

This is the entire reason the particle filter exists. When the fused belief has several peaks — engineers call this multimodal, "many modes" (a mode is a peak) — a single Gaussian is the wrong container. You need a representation flexible enough to hold any shape: three peaks, a banana-shaped ridge, a lopsided blob, whatever the data implies. The particle filter's answer is radically simple: stop trying to describe the belief with a formula. Represent it with a cloud of sample guesses instead.

Why a Gaussian literally cannot fit — one picture in numbers

Make the failure concrete. Say the three extinguishers sit at positions 10 m, 30 m, and 50 m down the hall, and after the sensor reading the robot's honest belief is "I'm right next to one of them," i.e. three tight clusters centered at 10, 30, and 50. What single Gaussian best fits that belief? A Gaussian is forced to put its peak at the average of the clusters:

μ = (10 + 30 + 50) / 3 = 90 / 3 = 30 m

So the best-fit Gaussian peaks at 30 m — the middle extinguisher. That is at least a real possibility. But to cover the clusters at 10 and 50 as well, the Gaussian must spread its width all the way out, putting enormous probability on positions like 20 m and 40 m — the empty hallway between the extinguishers, where the robot provably is not. The Gaussian's single hump smears probability exactly where the truth cannot be, and starves the two outer clusters where the truth might actually live. A one-hump distribution forced onto a three-hump belief is wrong everywhere. No choice of mean and variance fixes it; the container is the wrong shape.

Watch the mismatch directly. Below, the gray curve is the robot's true (multimodal) belief after the sensor reading — three peaks. The orange curve is the best single Gaussian anyone can fit to it. Drag the Gaussian's width and try to make it match. You can't: narrow it and it ignores two peaks; widen it and it floods the empty hallway between them.

A three-peaked belief vs the best single Gaussian

The gray filled curve is the robot's honest belief — three peaks, one per identical landmark. The orange curve is a single Gaussian (a Kalman-style belief) trying to fit it. Drag its width and slide its center. Notice you can never make orange hug all three peaks at once — it either misses peaks or piles probability on the empty gaps where the robot cannot be.

Gaussian width σ 8 Gaussian center 30

The grey belief has structure the orange curve can never copy. That structure — "here OR here OR here, never between" — is exactly the information the robot needs, and it is precisely what a Gaussian throws away. We need a belief that can be any shape.

The idea in one sentence: a cloud of guesses

Here is the whole trick the rest of this lesson unpacks. Instead of a formula, scatter a few hundred dots across the hallway, each dot a hypothesis — one concrete guess of where the robot might be. Each dot is a particle. Where the robot is likely to be, pack many dots; where it is unlikely, leave few. The density of dots — how crowded they are — is the probability. Three clusters of dots, one per extinguisher, with empty gaps between: that is a three-peaked belief, drawn not as a curve but as a swarm.

Density is probability. The single most important idea in this lesson: in a particle filter, the belief is represented by the positions of the particles. Crowded regions are high-probability regions; sparse regions are low-probability. You never write down what shape the belief is — the particles take whatever shape the data demands. Three peaks? Three clusters of particles. A banana? A banana of particles. The representation is non-parametric: it assumes no shape at all, so it can represent every shape.

And because each particle is a complete hypothesis, the filter handles the hallway gracefully. Sense a fire extinguisher and the clusters near each extinguisher get reinforced while particles in the empty hallway get starved. Drive forward, and every dot drives forward too. After enough driving — say the robot reaches the end wall — only one cluster will still be consistent with everything seen, and the swarm collapses onto it. The robot figures out which extinguisher it was next to, not by clever formula, but by letting the wrong hypotheses die off. That is the particle filter, and it is the topic of this lesson.

This is still the same recursive Bayesian estimation you met in the Bayes filter (Lesson 4): a loop of predict (move the belief forward through the motion model) and update (fuse the new measurement). The particle filter is just the Bayes filter with a particular, brilliantly flexible way of storing the belief. The algorithm was introduced for tracking by Gordon, Salmond, and Smith in 1993 (the "bootstrap filter"), and is laid out beautifully in Thrun, Burgard & Fox's Probabilistic Robotics, chapter 4 — the two sources this lesson is built on.

A robot in a symmetric hallway senses an identical landmark; honestly it could be next to any of three. Why does a single Gaussian (a Kalman-style belief) fail to represent this?

Chapter 1: Particles — A Belief You Can Count

We decided to throw away the formula and represent the belief with a swarm of guesses. Now let's make "particle" precise, because everything downstream is just operations on this one object. A particle is a single concrete hypothesis about the hidden state — for our robot, one guessed position. A particle set is a collection of N of them, written

𝒳 = { <x⁽¹⁾, w⁽¹⁾>, <x⁽²⁾, w⁽²⁾>, …, <x⁽ᵀ⁾, w⁽ᵀ⁾> }

where each particle i carries two things: a guessed state x⁽ⁱ⁾ (a position, say 23.4 m down the hall) and a weight w⁽ⁱ⁾ — a non-negative number saying "how much should we believe this particular hypothesis right now." High weight = a hypothesis the data supports; low weight = one the data is arguing against. We'll spend Chapter 3 entirely on where weights come from; for now just hold that each dot has a position and a believability score.

Density is the belief; weight is the fine adjustment

There are two ways a particle set expresses probability, and it helps to keep them straight:

The two are convertible — that's what Chapter 4 (resampling) is about: it turns weight back into density by duplicating heavy particles and deleting light ones. After resampling, all weights reset to equal and the density alone carries the belief again. For now, picture the belief as the swarm's shape, with each dot lightly shaded by its weight.

The mental model: a poll, not a measurement. Think of the particles as a focus group of "experts," each insisting "I think the robot is at my spot." The belief is the room: where the experts cluster, that's where you believe the robot is. A measurement comes in and some experts look prescient (high weight) while others look foolish (low weight). You don't argue with them — you just invite the prescient ones back next round and let the foolish ones go. Over rounds, the room's composition is your evolving belief.

How a cloud of dots approximates a smooth curve

It can feel suspicious that a finite pile of dots could stand in for a continuous probability curve. The justification is Monte Carlo approximation: if you draw N samples from a distribution, then for any question you might ask (what's the mean? the probability the robot is past the 25 m mark?), the answer computed from the samples converges to the true answer as N grows. Concretely, to estimate the probability that the robot is in some region, you just count the fraction of particles in that region:

P(robot in region) ≈ (number of particles in region) / N

Let's do it by hand. Suppose N = 10 particles sit at positions 9, 10, 11, 10, 31, 29, 30, 49, 51, 50 (three little clusters around 10, 30, 50). What's the probability the robot is in the window [8, 12]? Count: positions 9, 10, 11, 10 fall inside — that's 4 particles. So:

P(robot in [8,12]) ≈ 4 / 10 = 0.4

And the probability it's near the middle extinguisher, window [28, 32]? Positions 31, 29, 30 — 3 particles → 3/10 = 0.3. And the empty hallway [18, 22]? Zero particles → probability ~0, exactly right: the robot can't be in the empty gap. The dots reproduced the three-peaked belief that defeated the Gaussian, with no formula at all — just counting. That counting is the belief.

Why this beats a Gaussian, in one line

A Gaussian commits to a shape (one symmetric hump) before seeing the data. Particles commit to nothing: they take whatever shape the data carves out of them. The price is that you need many dots to draw a fine curve (more on the cost in Chapter 8), and the dots are random, so the picture flickers a bit. But the payoff is total: a particle set can represent a belief with three peaks, a sharp corner, a long tail, or a banana — anything — because it never assumed a shape to begin with. This is why particle filters are the workhorse whenever the world refuses to be a bell curve.

python
import numpy as np
# a particle set = N positions + N weights. That's the whole belief.
N = 10
x = np.array([9,10,11,10,31,29,30,49,51,50], dtype=float)
w = np.ones(N) / N                 # start with equal belief in every hypothesis

def prob_in(lo, hi):                  # probability the state is in [lo, hi]
    inside = (x >= lo) & (x <= hi)
    return w[inside].sum()           # sum the WEIGHTS inside (= fraction, since equal)

prob_in(8, 12)    # -> 0.4   (4 of 10 particles near the first landmark)
prob_in(18, 22)  # -> 0.0   (empty hallway -- the robot cannot be here)

Notice we summed weights, not counts — with equal weights that's the same as a fraction, but it's the general rule: the probability of a region is the total weight of the particles in it. The belief is two arrays. The next three chapters are just three operations on those two arrays: move them (predict), reweight them (the fusion step), and resample them.

A particle filter has 8 particles at positions 4, 5, 6, 5, 20, 21, 39, 40 (all equal weight). Roughly what probability does it assign to the robot being in the window [3, 7]?

Chapter 2: Predict — Push Every Particle Through the Motion Model

The belief is a swarm of dots. Now the robot moves, and the belief must move with it. This is the first step of the recursive loop — the predict step (also called the motion update or proposal) — and in a particle filter it is almost shockingly literal: take every single particle and drive it forward exactly as if it were the real robot.

If the robot's wheels report "I moved forward 5 meters," then each particle — each hypothesis "maybe the robot is here" — updates to "well then, now it's 5 meters past here." A hypothesis at 10 m becomes a hypothesis at 15 m; one at 30 m becomes 35 m. The whole swarm slides forward together. That's it: the predict step applies the motion model to each particle independently.

But motion is noisy — so we add jitter

If we moved every particle by exactly 5 m, we'd be claiming the wheels are perfect. They aren't: wheels slip, the floor is uneven, "5 meters" is really "about 5 meters, give or take." The Bayes filter taught us that the predict step must grow the uncertainty — the belief should get a little fuzzier each time we move, because motion injects error. In a particle filter we model that fuzziness by adding a small random jitter (noise) to each particle's move. Particle i updates as:

x⁽ⁱ⁾ ← x⁽ⁱ⁾ + u + ε⁽ⁱ⁾,    ε⁽ⁱ⁾ ~ Normal(0, σ²motion)

where u is the commanded/odometry motion (the "move forward 5 m"), and ε⁽ⁱ⁾ is a fresh random nudge drawn independently for each particle from a zero-mean bell curve with spread σmotion (how unreliable the wheels are). Crucially, every particle gets its own nudge, so a swarm that started as a tight clump spreads into a slightly wider clump — the dots fan out exactly as much as the motion is uncertain.

Why each particle gets its own noise. If we added the same nudge to every particle, the whole cloud would just shift rigidly — uncertainty wouldn't grow, and that would be a lie about how sure we are. By giving each particle an independent draw, dots that were on top of each other drift apart. The cloud's spread after the move encodes the accumulated motion uncertainty. This is how the particle filter implements the Bayes filter's "prediction increases entropy": more motion, bigger jitter, wider cloud.

One predict step, by hand

Take five particles, each carrying a guessed position, and command a move of u = 5 meters with motion noise σmotion = 1 m. To do it by hand we'll use a concrete (made-up but plausible) noise draw for each particle. Watch each one move forward 5 and then get its private nudge:

Particle ibefore x⁽ⁱ⁾+ u (5 m)+ noise ε⁽ⁱ⁾after x⁽ⁱ⁾
110.015.0+0.415.4
210.215.2−0.714.5
39.814.8+1.115.9
410.115.1−0.314.8
59.914.9+0.615.5

The clump started tightly packed around 10 (spread of about 0.4 m, from 9.8 to 10.2). After the move it's centered near 15 — correct, that's where 5 m forward lands — but now spread from 14.5 to 15.9, a width of about 1.4 m. The cloud both translated (by the motion) and widened (by the noise). The widening is the point: the robot is now less certain of its position than before it moved, and the swarm honestly shows it.

Two things to notice. First, weights are untouched by the predict step — moving a hypothesis doesn't change how believable it was; only a measurement does that (next chapter). Second, the predict step never looks at any sensor; it only uses the robot's own motion. It's the "best guess from physics alone, before we look out the window" step.

python
import numpy as np
def predict(x, u, sigma_motion):
    # move EVERY particle by u, plus an independent random nudge each
    noise = np.random.normal(0, sigma_motion, size=x.shape)
    return x + u + noise        # the whole swarm slides forward AND fans out

x = np.array([10.0,10.2,9.8,10.1,9.9])
x = predict(x, u=5.0, sigma_motion=1.0)
# -> roughly [15.4, 14.5, 15.9, 14.8, 15.5]: centered ~15, but now WIDER

Play with it below. Set the move distance and the motion noise, then press Move repeatedly and watch the clump march across the hallway, fanning wider with every step. Crank the noise up and a few moves blow the cloud apart; set it to zero and the cloud translates rigidly (and lies about being certain).

Predict — drive the whole swarm forward, with jitter

A clump of particles starts near the left. Press Move to command a step of size u; every particle slides forward by u plus its own random nudge of spread σ. Watch the cloud both translate (by u) and widen (by σ). More motion noise → faster spreading.

move u (m) 6 noise σ 1
The signature of predict. Apply the motion model + independent noise to every particle. The cloud translates (by the motion) and widens (by the noise). Weights are untouched; no sensor is consulted. This step alone makes the belief fuzzier — it is the measurement step (next) that sharpens it back up. Predict and weight are a push-pull pair: predict diffuses, weight focuses.
In the predict step, why does each particle get its OWN independent random nudge rather than all particles getting the same nudge?

Chapter 3: Weight — This Is Where the Measurement Gets Fused In

Predict pushed the cloud forward and made it fuzzier. Now the robot looks — takes a measurement — and we must fold that new evidence in, sharpening the belief. This is the weight step, and it is the heart of the lesson, because weighting is the fusion step. Everything you learned about combining a prior with a measurement happens right here, particle by particle.

The mechanism is delightfully direct. We have a swarm of hypotheses (the prior, from predict). A measurement arrives — say the robot's range sensor reports "the wall is 8 m ahead." We ask of every single particle: "If the robot really were at your position, how likely is it that we'd have seen this exact measurement?" A particle whose position would naturally produce an 8 m reading gets a high score; a particle whose position would predict a 20 m reading gets a tiny score, because the data argues against it. That score is the particle's new weight.

Weighting is fusion: prior × likelihood. Recall Bayes' rule — posterior ∝ prior × likelihood. In a particle filter the prior is carried by the cloud of particles (the predict output); the likelihood is "how well does each particle explain the measurement." Multiplying them is exactly: weight each particle by its measurement likelihood. The cloud (prior belief) times the per-particle likelihood gives the posterior belief. That multiply is the fusion of motion-prior and sensor-measurement into one updated belief. No matrix, no Gaussian assumption — just "score each guess by how well it fits the data."

The measurement model and the likelihood

To score a particle we need two ingredients. First, the measurement model h(x): what reading should we get if the robot were at position x? If the wall is at position 20 m and the sensor measures distance-to-wall, then a robot at x predicts a reading of h(x) = 20 − x. Second, the measurement noise: real sensors are imperfect, so the actual reading z scatters around h(x) with some spread σsense. The standard choice is a Gaussian likelihood — a particle's weight is how tall that bell curve is at the gap between what it predicted and what we actually saw:

w⁽ⁱ⁾ ∝ exp( − ( z − h(x⁽ⁱ⁾) )² / ( 2 σ²sense ) )

Read it in plain words: let rⁱ = z − h(x⁽ⁱ⁾) be the residual (also called the innovation) — how far this particle's prediction missed the actual reading. The weight is large when the residual is near zero (the particle predicted what we saw) and shrinks fast as the residual grows. The σsense sets how forgiving we are: a precise sensor (small σ) punishes mismatches harshly; a sloppy sensor (large σ) is lenient. Note the residual is the same "z − h(x)" quantity that drives the Kalman filter's correction — the particle filter just uses it to score hypotheses instead of to nudge a mean.

Worked example — score five particles, then normalize

Make it fully concrete. Five particles sit at positions after the predict step. The wall is at 20 m, the sensor measures distance-to-wall, so h(x) = 20 − x. The robot's actual reading is z = 8 m (so the truth is near 12 m). Sensor noise σsense = 1 m, so 2σ² = 2. Compute each residual r = z − h(x) = 8 − (20 − x) = x − 12, then the raw weight exp(−r²/2):

ix⁽ⁱ⁾h(x)=20−xr = 8 − h(x) = x−12raw w = exp(−r²/2)
112800exp(0) = 1.000
2119−11exp(−0.5) = 0.607
3137+11exp(−0.5) = 0.607
4146+24exp(−2) = 0.135
5911−39exp(−4.5) = 0.011

Particle 1, which predicted exactly the reading we got, scores a perfect 1.0. Particles 2 and 3, off by one meter, score 0.607. Particle 5, off by three meters, is almost annihilated at 0.011. The data has spoken: it loves the hypotheses near 12 m and despises the one at 9 m. That is the measurement getting fused in — not by moving anything, but by re-scoring every hypothesis.

Normalize so the weights sum to 1

Raw weights are only relative — the formula had a "∝" (proportional-to), because we dropped the constant in front of the exponential. To turn them into a proper probability distribution we normalize: divide each by the total. Sum the raw weights:

S = 1.000 + 0.607 + 0.607 + 0.135 + 0.011 = 2.360

Then each normalized weight is raw / S:

iraw wnormalized w = raw / 2.360
11.0001.000 / 2.360 = 0.424
20.6070.607 / 2.360 = 0.257
30.6070.607 / 2.360 = 0.257
40.1350.135 / 2.360 = 0.057
50.0110.011 / 2.360 = 0.005

Check: 0.424 + 0.257 + 0.257 + 0.057 + 0.005 = 1.000. ✓ Now the weights are a believability distribution over the five hypotheses. Particle 1 holds 42% of the belief; the badly-mismatched particle 5 holds half a percent. This re-weighted cloud is the fused posterior — the predict-step prior, sharpened by the measurement. We did Bayes' rule with arithmetic, no calculus.

This is "importance weighting." The technique — sample from one distribution (the motion-prior cloud), then re-weight by how much you actually care about each sample (its measurement likelihood) — is called importance sampling, and the particle filter is built on it. The motion prior proposes where to put hypotheses; the likelihood says which of them matter. The weight is literally the "importance" of that hypothesis given the data. When you stack predict-then-weight in a loop, the algorithm earns its formal name: Sequential Importance Resampling (SIR), the standard particle filter.

python
import numpy as np
def weight(x, z, sigma_sense, wall=20.0):
    pred = wall - x                              # measurement model h(x): distance to wall
    r = z - pred                                 # residual: how far each particle missed z
    w = np.exp(-(r**2) / (2 * sigma_sense**2))   # Gaussian likelihood = the fusion
    return w / w.sum()                           # NORMALIZE so weights sum to 1

x = np.array([12,11,13,14,9], dtype=float)
w = weight(x, z=8.0, sigma_sense=1.0)
# -> [0.424, 0.257, 0.257, 0.057, 0.005]  (matches the hand table)

Below, place a measurement (a reading of distance-to-wall) and watch every particle get re-scored. Particles near the consistent position glow bright (high weight); particles far away fade (low weight). Drag the sensor-noise slider: a tight sensor makes the weighting brutally selective; a loose one barely distinguishes the particles. This re-scoring is the moment the measurement fuses into the belief.

Weight — score each particle by measurement likelihood

Particles sit across the hallway. The robot reports a distance-to-wall reading (the dashed marker shows the implied position). Each particle's brightness = its weight: bright = "you predicted this reading well." Slide the measurement to move the evidence, and sensor noise to set how sharply mismatches are punished.

implied position 24 sensor noise σ 2
Why is the WEIGHT step the fusion step of a particle filter?

Chapter 4: Resample — Survival of the Fittest

After weighting, our cloud has a problem hiding in plain sight. The belief is now carried by the weights (particle 1 holds 42%, particle 5 holds 0.5%) — but the positions haven't moved. Worse, if we just keep multiplying weights every step, a handful of particles will hoard nearly all the weight while hundreds sit around with near-zero weight, doing nothing but wasting computation. We need to convert the weight back into density — to make the positions reflect the belief again. That conversion is resampling, and the idea is brutal and biological: survival of the fittest.

Resampling builds a fresh set of N particles by drawing from the current set with probability proportional to weight. A heavy particle gets drawn many times — it spawns several copies. A light particle is almost never drawn — it dies out. After resampling, the new cloud has many particles where the heavy ones were (high density — high belief) and none where the light ones were (the wrong hypotheses are gone). Then we reset all weights to equal (1/N), because the belief now lives in the density again, not the weights.

Resampling turns weight into density. Before resampling: belief = positions (fixed) shaded by weights (uneven). After resampling: belief = positions (now clustered where the heavy ones were) with weights all equal. Same belief, different bookkeeping — but the new bookkeeping puts the particles where the action is, so the next predict-weight round spends its computation on plausible hypotheses instead of wasting it on dead ones. It is the filter's way of saying "stop simulating the hypotheses the data already rejected."

The naive way, and why it's wrong

The obvious resampling method: for each of the N new particles, spin a weighted roulette wheel (pick a random number, find which particle's weight-slice it lands in). This works, but it's slow (O(N log N) with a binary search) and, worse, it's noisy — N independent random spins can, by bad luck, over-sample some regions and miss others, adding random error to the belief. There is a far better method that's both faster and lower-noise, and it's the one everybody uses.

Low-variance resampling — the systematic comb

The trick (Thrun, Burgard & Fox call it low-variance sampling) is to use one random number, not N. Imagine laying all the weights end-to-end along a number line of total length 1 — each particle owns a segment as wide as its weight. Now drop a comb of N evenly-spaced teeth (spacing 1/N) onto that line, with the comb's starting offset chosen randomly in [0, 1/N). Each tooth lands in some particle's segment — that particle gets selected once per tooth that lands on it. Heavy particles own wide segments and catch several teeth; light particles own thin segments and may catch none.

Why this is better: the teeth are evenly spaced, so the selection is systematic — it can't accidentally clump or leave gaps the way N independent spins can. Only the single starting offset is random. The result has much lower variance (hence the name) and runs in O(N) with no sorting. One random draw, one linear sweep.

Low-variance resampling of 5 particles, by hand

Take the five normalized weights from Chapter 3: [0.424, 0.257, 0.257, 0.057, 0.005] for particles at positions [12, 11, 13, 14, 9]. We want N = 5 new particles. The comb spacing is 1/N = 0.2. First lay out the cumulative weights — the right edge of each particle's segment:

particlepositionweightcumulative (segment ends at)
1120.4240.424
2110.2570.681
3130.2570.938
4140.0570.995
590.0051.000

Now pick a random start offset in [0, 0.2). Say we draw r₀ = 0.1. The five comb teeth are at r₀ + k·(0.2) for k = 0,1,2,3,4:

teeth = 0.1,   0.3,   0.5,   0.7,   0.9

For each tooth, find the first particle whose cumulative edge is ≥ the tooth (i.e. which segment it lands in):

toothwhich segment?selected particle (position)
0.1≤ 0.424 → particle 1P1 (pos 12)
0.3≤ 0.424 → particle 1P1 (pos 12)
0.5between 0.424 and 0.681 → particle 2P2 (pos 11)
0.7between 0.681 and 0.938 → particle 3P3 (pos 13)
0.9between 0.681 and 0.938 → particle 3P3 (pos 13)

The new particle set is positions [12, 12, 11, 13, 13], all with equal weight 1/5 = 0.2. Read what happened: particle 1 (the heavy 42% one) got copied twice; particles 2 and 3 survived; the near-dead particles 4 (pos 14) and 5 (pos 9) were not selected at all — they're gone. The cloud has collapsed toward the high-belief region around 12 m, exactly where the measurement said the robot is. Survival of the fittest: heavy hypotheses reproduce, light ones go extinct.

A sanity check on fairness. Particle 1 had weight 0.424; with N = 5 teeth, you'd expect it selected about 0.424 × 5 ≈ 2.1 times — we got 2. Particle 2 (weight 0.257) expected 1.3, got 1. Particle 5 (weight 0.005) expected 0.025, got 0. The counts track the weights, which is the whole point: a particle's expected number of copies equals its weight times N. Low-variance sampling guarantees the actual counts stay close to those expectations — that's the "low variance."

The whole resample step in code

python
import numpy as np
def low_variance_resample(x, w):
    N = len(x)
    positions = (np.arange(N) + np.random.uniform()) / N   # the comb: one random offset, even teeth
    cumsum = np.cumsum(w)                                   # segment edges along [0,1]
    cumsum[-1] = 1.0                                  # guard against float round-off
    new_x = np.zeros(N); i = 0
    for j in range(N):                                  # one linear sweep -> O(N)
        while positions[j] > cumsum[i]: i += 1          # find which segment tooth j fell in
        new_x[j] = x[i]                                     # copy that particle
    return new_x, np.ones(N) / N                         # reset weights to equal: density carries belief now

x = np.array([12,11,13,14,9], dtype=float)
w = np.array([0.424,0.257,0.257,0.057,0.005])
x, w = low_variance_resample(x, w)
# -> heavy particles duplicated, light ones dropped; weights all 1/N again

Below, set five (or more) weights and run the comb. The blue ticks are the comb teeth; watch which segment each lands in, and see the resampled cloud collapse onto the heavy particles. Re-run it: only the random offset changes, and the selected counts barely move — that stability is the "low variance."

Low-variance resampling — the systematic comb

Top bar: each particle owns a segment as wide as its weight. The comb drops N evenly-spaced teeth (one random offset). Each tooth selects the particle whose segment it lands in. Bottom: the resampled set — heavy particles duplicated, light ones gone. Press Resample to redraw the offset; press Skew weights to make one particle dominate.

One full loop is now complete: predict (Ch 2) moves the cloud and fuzzes it, weight (Ch 3) fuses the measurement by scoring each particle, resample (Ch 4) collapses onto the survivors. Repeat forever. But resampling has a dark side — do it too aggressively and the cloud collapses too far. That failure mode is the whole next chapter.

After resampling with weights [0.424, 0.257, 0.257, 0.057, 0.005] and N=5, the heavy particle was copied twice and the two lightest were dropped. What did resampling accomplish?

Chapter 5: Degeneracy & the Effective Sample Size

The predict-weight-resample loop has two opposite ways of going wrong, and a single number that diagnoses both. Understanding them is what separates a particle filter that works from one that silently lies. The disease is called degeneracy — the diversity of the swarm collapsing — and it comes in two flavors.

Flavor 1: weight degeneracy

If you weight many times without resampling, the weights become wildly uneven: one particle ends up with weight 0.9999 and the other 999 hold a combined 0.0001. The belief is then carried by essentially one particle — you paid to simulate 1000 hypotheses but only one matters. Your "thousand-sample" estimate is really a one-sample estimate, with all the noise that implies. This is weight degeneracy: the weight piles onto a vanishing fraction of particles.

Flavor 2: sample impoverishment

Resampling cures weight degeneracy — but overdo it and you get the opposite disease. Every resample duplicates heavy particles, and duplicates are identical copies, occupying the exact same point. With no new motion noise to spread them, repeated resampling makes the cloud collapse to a few distinct positions, then eventually to a single point — all N particles stacked on one spot. The filter becomes falsely, catastrophically certain. This is sample impoverishment (or particle impoverishment): diversity is lost not because the data demanded it, but as a side effect of resampling noise.

The push-pull you must balance. Weighting without resampling → weight degeneracy (belief in one particle, the rest dead weight). Resampling too much → sample impoverishment (all particles identical, false certainty). The cure for one is the poison for the other. The art is to resample only when needed — and to know when that is, you need a number that measures "how degenerate am I right now?" That number is the effective sample size.

The effective sample size N₎ₐₐ

The effective sample size, written N₎ₐₐ, answers: "given how uneven my weights are, how many equally-weighted particles would carry the same amount of information?" If all N weights are equal, N₎ₐₐ = N (full diversity). If one particle holds all the weight, N₎ₐₐ = 1 (you effectively have one sample). The standard formula is one over the sum of squared weights:

N₎ₐₐ = 1 / Σⁱ ( w⁽ⁱ⁾ )²

where the weights are normalized (sum to 1). The intuition: squaring punishes unevenness. If weights are spread out, each is small and its square is tiny, so the sum is tiny and 1/sum is large (near N). If one weight is near 1, its square is near 1, the sum is near 1, and 1/sum ≈ 1.

N₎ₐₐ by hand — three cases

Case A — perfectly even weights. Five particles, each w = 0.2. Sum of squares = 5 × (0.2)² = 5 × 0.04 = 0.20.

N₎ₐₐ = 1 / 0.20 = 5.0

All five particles pulling their weight → N₎ₐₐ = 5 = N. Maximum diversity, healthy.

Case B — the Chapter-3 weights. w = [0.424, 0.257, 0.257, 0.057, 0.005]. Square each: 0.1798, 0.0660, 0.0660, 0.0032, 0.0000. Sum:

Σ w² = 0.1798 + 0.0660 + 0.0660 + 0.0032 + 0.0000 = 0.3150
N₎ₐₐ = 1 / 0.3150 = 3.17

Out of 5 particles, only about 3.2 are really contributing — the two near-dead ones barely count. Still healthy-ish (3.2 of 5 is fine), but the number tells you the swarm is starting to concentrate.

Case C — one particle hogs everything. w = [0.97, 0.01, 0.01, 0.005, 0.005]. Squares: 0.9409, 0.0001, 0.0001, 0.000025, 0.000025. Sum ≈ 0.9412.

N₎ₐₐ = 1 / 0.9412 = 1.06

You have 5 particles but effectively 1. This is severe weight degeneracy — resample now, before the belief becomes a single noisy sample.

The resample-when-needed rule. Compute N₎ₐₐ after every weight step. If N₎ₐₐ falls below a threshold (a common choice is N/2), resample; otherwise skip it. This is the cure for the push-pull: resample only when weight degeneracy has set in, so you never resample more than necessary, which minimizes the sample-impoverishment side effect. One number diagnoses one disease and gates the cure for the other. (To further fight impoverishment, practitioners also add a little extra motion jitter — "roughening" — so duplicated particles spread back out instead of stacking.)

python
import numpy as np
def n_eff(w):
    return 1.0 / np.sum(w**2)        # effective sample size (w normalized)

n_eff(np.full(5, 0.2))                 # -> 5.00   even weights, full diversity
n_eff(np.array([.424,.257,.257,.057,.005]))  # -> 3.17   concentrating
n_eff(np.array([.97,.01,.01,.005,.005]))     # -> 1.06   degenerate -- resample!

# the gate, in the main loop:
if n_eff(w) < N / 2:
    x, w = low_variance_resample(x, w)   # resample only when degenerate

Below, drag the weight-skew slider from "even" to "one dominates" and watch N₎ₐₐ slide from N down toward 1. The dashed line is the N/2 resample threshold; cross below it and the readout flags "resample now." Feel how the squared-weight sum reacts to unevenness.

Effective sample size — the degeneracy meter

Bars are the particle weights. Drag skew from 0 (all equal) to 1 (one particle hogs everything) and watch Neff = 1/Σw² fall from N toward 1. The dashed line is the N/2 threshold: below it, the filter is degenerate and you should resample.

weight skew 0.20 particles N 12
You compute Neff = 1/Σw² and get 1.1 out of 500 particles. What does this mean, and what should you do?

Chapter 6: Showcase — Monte Carlo Localization, Live

Every piece is on the table: particles as a sampled belief, predict, weight (the fusion step), resample, and the N₎ₐₐ meter that keeps it honest. Now we run them all at once on the canonical application — Monte Carlo Localization (MCL), a robot finding itself on a 2-D map using a cloud of particles. This is the algorithm inside countless real robots, and the payoff chapter of the lesson: no new theory, just every idea above made visible, interactive, and breakable.

A robot lives on a square map with a handful of landmarks (the orange beacons). It cannot see its own position; it can only measure its distance to each landmark (noisily) and command its own motion. The cloud of blue particles is its belief about where it is. Press Drive + Sense and watch the loop run: every step the cloud predicts (each particle drives like the robot, plus jitter), weights (each particle scored by how well its landmark-distances match the real reading), and resamples (the cloud collapses onto the survivors). Over a few steps the diffuse cloud snaps onto the true robot.

This is the whole lesson in one sim. The blue cloud is the belief (Ch 1). "Drive" runs predict (Ch 2) — watch the cloud slide and fan out. "Sense" runs weight (Ch 3) — the fusion step, particles near a consistent position brighten. The cloud then resamples (Ch 4) — it collapses toward the truth. The Neff readout (Ch 5) tells you when it's healthy. And the Kidnap button reproduces Chapter 0's nightmare: teleport the robot and watch the belief, briefly, become multimodal — the thing no Gaussian could survive.

Monte Carlo Localization — the cloud finds the robot

The green dot is the true robot (hidden from the filter). The blue dots are particles — the belief. Orange beacons are landmarks; the robot measures noisy distances to them. Press Drive + Sense to step the filter; the cloud predicts, weights, resamples, and collapses onto the truth. Sliders set the particle count and the sensor noise. Kidnap teleports the robot — watch the belief scatter and re-converge.

particles N 300 sensor noise 12

Run the loop a few times from a fresh scatter and the three steps announce themselves:

A guided experiment — reproduce every claim in five clicks

Don't just watch — falsify. Run this sequence and confirm each prediction with your own eyes:

  1. Reset (scatter), then press Drive + Sense once. The cloud is everywhere; one step already pulls a rough cluster toward the truth. Read the Neff readout — it jumps from ~N down sharply right after the weight step (degeneracy), then resampling restores diversity.
  2. Step a few more times. The cluster tightens around the green dot. The estimate error (shown in the readout) shrinks each step. This is convergence.
  3. Drag sensor noise to its minimum (2). Step again: convergence is faster and tighter (a sharp sensor punishes mismatches hard — Chapter 3's brutal weighting) — but watch for the cloud collapsing too far (impoverishment).
  4. Drag particle count down to ~20 and step. With too few particles the estimate gets noisy and sometimes latches onto the wrong spot — the curse of too-few samples (Chapter 8/9). Drag it back up to 300 and stability returns.
  5. Press Kidnap! The true robot teleports; the cloud is now centered on the old location, completely wrong. Step repeatedly: weights everywhere go tiny (the readout shows Neff crashing), the belief briefly scatters/goes multimodal, and — if particles are dense enough — it re-converges on the new truth. Too few particles and it may never recover. This is the kidnapped-robot problem live.

The kidnap is the deepest moment: it stages Chapter 0's multimodal belief in real time. Right after the teleport, no particle is near the truth, so the filter must rely on the lucky few that get re-energized once the robot drives near them — or on injected random particles (a real MCL trick). Watch the belief temporarily hold two clusters — the dying old one and the growing new one — the exact two-peaked belief a single Gaussian could never represent. That is why this robot runs a particle filter and not a Kalman filter.

You just watched recursive Bayesian fusion converge. Each cycle fused one more measurement into the belief by re-weighting, and resampling turned that fused belief back into a cloud ready for the next drive. Diffuse → fuse → sharpen, forever. The cloud is doing exactly what the Bayes filter (Lesson 4) prescribed — predict then update — but with a representation flexible enough to scatter, split into modes, and re-converge. No bell curve required.

Chapter 7: Use Cases & Real Products — The Non-Gaussian Workhorse

Every lesson in this series ends with the same four chapters — Use Cases, Practical Application, Debugging, Connections — because a method you can't point at in a shipped product is a method you don't yet own. So before any more theory, let's ground the particle filter in hardware running it right now.

The particle filter earns its keep wherever the belief refuses to be a single bell curve: multimodal localization, heavy-tailed sensor noise, nonlinear measurement models, and the global "where am I, with no prior" problem. Here are the headline deployments.

Robot-vacuum / AMCL localization. Open the navigation stack of almost any modern robot (and the open-source ROS ecosystem) and you'll find AMCLAdaptive Monte Carlo Localization — the exact algorithm from Chapter 6. A robot vacuum or warehouse AMR holds a particle cloud over a pre-built map and matches its LiDAR scan against the map's walls to weight particles. The "adaptive" part is KLD-sampling: it uses many particles when the belief is spread across the whole map (just powered on, or kidnapped) and few once it's localized — spending computation only when uncertainty demands it.

Multi-target tracking. Tracking several objects (radar tracking aircraft, a camera tracking pedestrians, sonar tracking fish) is inherently multimodal: which blip is which target, and is that new blip a target or clutter? Particle filters handle the non-Gaussian, multi-hypothesis nature head-on — each cluster of particles can ride a different target, and the data association ambiguity lives naturally as multiple modes. The 1993 Gordon et al. bootstrap filter was born for exactly this radar-tracking problem.

Global localization & the kidnapped-robot problem. A Kalman filter must be initialized near the truth — it cannot start from "I could be anywhere." A particle filter can: scatter particles uniformly over the whole map and let the measurements winnow them. This makes it the default for global localization (find yourself from scratch) and recovery from kidnapping (someone moved you) — the very scenario the Chapter 6 sim staged.

FastSLAM and visual tracking. FastSLAM factors the SLAM problem so each particle carries its own map hypothesis — a landmark estimate per particle — letting the filter represent ambiguity about both the robot's path and the map. In computer vision, the CONDENSATION algorithm (Isard & Blake, 1998) used particle filters to track contours through clutter where Gaussian trackers lost lock. Anywhere the posterior is genuinely non-Gaussian, particles are the tool.

The pattern across products

Notice why each chose a particle filter over a Kalman filter:

Product / domainWhat's fusedWhy a particle filter (not KF)
Robot-vacuum / AMCLLiDAR scan + odometry + mapmultimodal belief in symmetric rooms; global localization from scratch
Radar / sonar multi-targetdetections over timedata-association ambiguity is inherently multi-hypothesis
FastSLAMlandmarks + patheach particle = one map hypothesis; non-Gaussian joint belief
Visual contour trackingimage edges over framesclutter creates multiple modes; Gaussian tracker loses lock
Financial / signal modelsnonlinear state-space seriesarbitrary, heavy-tailed, nonlinear likelihoods

How to recognize "this needs a particle filter" in the wild

Three diagnostic questions reveal it:

If all three are "no" — a single mode, good initialization, mild nonlinearity, Gaussian noise — a Kalman/EKF/UKF is cheaper and you should prefer it. The particle filter is the tool for the hard, non-Gaussian cases; the next chapter makes that choice precise.

The deployable insight. Reach for a particle filter when the belief is genuinely multimodal, when you must localize globally or recover from a kidnap, or when the noise/measurement model is too non-Gaussian for an EKF/UKF to linearize. Its cost — many particles, especially in high dimensions — is the price you pay for representing any shape of belief. The robot vacuum under your couch is, right now, running the Chapter 6 algorithm to know which room it's in.

A warehouse robot must figure out where it is on a known map immediately after power-on, with NO prior idea of its location. Why is this a textbook particle-filter job rather than a Kalman filter job?

Chapter 8: Practical Application — Tuning a Particle Filter That Works

You know the loop; now we make the engineering decisions mechanical. This "now build it" chapter turns the theory into a small set of choices — how many particles, what measurement model, when to resample, and crucially, when a particle filter beats a Kalman filter at all — so you're never guessing on a real project.

Decision 1: when does a particle filter beat a KF/EKF?

Don't reach for particles reflexively — they're expensive. Walk this checklist; if you can answer "no" to all of it, use a Kalman-family filter instead.

Q1: Is the belief multimodal (several plausible states at once)?
Yes → particle filter (a Gaussian can't hold multiple peaks)
Q2: Must you localize globally / survive a kidnap?
Yes → particle filter (KF needs a good initial guess; can't represent "anywhere")
Q3: Is the model strongly nonlinear or the noise non-Gaussian?
Yes → particle filter (EKF linearization / UKF sigma points break down)
All "no"? Single mode, good init, mild nonlinearity, Gaussian noise
→ use a Kalman / EKF / UKF — far cheaper, closed-form, no sampling noise
The cost you're paying. A Kalman filter represents a belief with a mean and covariance — a handful of numbers, updated in closed form. A particle filter needs hundreds to thousands of samples, each pushed through the motion model and scored every step. For a 1-D or 2-D pose it's cheap; for a 10-D state it's brutal (see Decision 2). You buy the freedom to represent any belief shape with raw compute. If your belief really is a single bell curve, that compute is pure waste — a KF gives the same answer for a fraction of the cost.

Decision 2: how many particles? (and the curse of dimensionality)

The hardest knob. Too few and the cloud can't cover the belief — it under-samples the space, latches onto a wrong mode, or fails to recover from a kidnap. Too many and you waste compute. The brutal fact is that the number of particles needed to cover a region grows exponentially with the state dimension — the curse of dimensionality.

Feel it with numbers. Suppose you need about 10 particles per dimension to tile the space adequately:

State dimension dParticles to cover (~10ᵈ)Verdict
1 (position on a line)10trivial
2 (x, y)100easy
3 (x, y, θ)1,000fine — typical MCL
6 (3-D pose)1,000,000painful
10+ (pose + velocities + biases)10,000,000,000hopeless — don't

This is why particle filters dominate low-dimensional localization (1–4 D) and are rarely used raw for high-dimensional state. The fix when you must go high-D is to factor the problem (FastSLAM samples only the trajectory and solves the rest analytically) or to fall back to a Gaussian filter for the easy dimensions. Rule of thumb: 500–1000 particles for 2-D/3-D MCL; if you're tempted to use 100,000, your state is too big for a vanilla particle filter.

Adaptive particle counts (KLD-sampling). You don't have to pick a single N. Real systems (AMCL) use more particles when the belief is spread out (just powered on, uncertain) and fewer once it's tightly localized — choosing N each step so the sample-based belief is within a bound of the true one. Spend particles only where uncertainty demands them. It's the single biggest practical win after getting the measurement model right.

Decision 3: the measurement model is everything

The weight step is only as good as h(x), the model of "what reading should I get at this state." Two failure modes to engineer around:

Decision 4: when to resample

From Chapter 5: don't resample every step (it causes impoverishment) and don't never resample (weight degeneracy). The standard recipe is the N₎ₐₐ gate — resample only when N₎ₐₐ < N/2 — plus a touch of "roughening" (extra motion jitter on duplicated particles) to keep diversity. This single rule resolves the whole push-pull from Chapter 5.

Worked design example: localize a warehouse robot

The task: an AMR must localize on a known 2-D floor map (state = x, y, heading θ — 3 dimensions) using a LiDAR scan, starting from anywhere. Walk the decisions.

The design reads as a sentence: "Adaptive (KLD) particle filter, ~1000→200 particles over the 3-D pose, likelihood-field LiDAR model with an outlier floor, resample on Neff<N/2 with 2% random injection." Every choice traces to one of the four decisions.

The reusable recipe. (1) PF only if multimodal / global / non-Gaussian — else use a KF. (2) ~10ᵈ particles, so keep the sampled state to ≤3–4 dims (factor or fall back above that), and adapt N to uncertainty. (3) Engineer the measurement model: inflate σ and add an outlier floor for robustness. (4) Resample on the Neff<N/2 gate with a little roughening and random injection. This recipe is the same for a vacuum and a warehouse AMR — only the map and sensors change.

A colleague proposes a vanilla particle filter over a 10-dimensional state with 500 particles. What's the core problem?

Chapter 9: Debugging & Failure Modes — The Four Ways a Particle Filter Lies

A particle filter rarely crashes; it fails by quietly producing a confident wrong answer. The bugs are subtle — a cloud that looks tight and healthy can be catastrophically wrong — and they hide until exactly the wrong moment (a kidnap, an outlier reading, a symmetric room). This recurring chapter catalogs the classic traps, the symptom each shows in the field, and the specific test that exposes it. The master diagnostic is one number you already know: N₎ₐₐ. Log it every step; most of these failures show up there first.

Trap 1 — Particle degeneracy (weight collapse)

You weight many times without resampling (or resample too rarely), and the weight piles onto one or two particles. Your 1000-particle "belief" is really a one-particle estimate — jumpy, noisy, and falsely shaped by whichever particle happened to win.

Symptom & detection. Symptom: the estimate is erratic and over-confident; the cloud has a few bright particles and a sea of invisible ones. Detection: N₎ₐₐ collapses toward 1 while N is large. Fix: resample when N₎ₐₐ < N/2 (Chapter 5's gate). If N₎ₐₐ is always tiny right after weighting, your likelihood is too sharp (Trap 4) — inflate the sensor σ.

Trap 2 — Sample impoverishment (diversity collapse)

The opposite disease. You resample too aggressively (e.g. every step), and because resampling duplicates heavy particles into identical copies, the cloud collapses to a few — eventually one — distinct positions. The filter becomes certain of a single point and can never represent uncertainty again; if that point is wrong, it can never recover.

Symptom & detection. Symptom: the cloud shrinks to a tiny dot (or a few dots) and stays there even when it should be uncertain; the filter ignores conflicting measurements because no particle is near them. Detection: count distinct particle positions — if it's collapsing toward 1 over time, you're impoverished. Fix: resample less (N₎ₐₐ gate, not every step) and add roughening (a small extra jitter on resampled particles) so duplicates spread back out.

Trap 3 — Collapsing onto the wrong mode

The deepest particle-filter trap, straight out of Chapter 0. In a symmetric environment the belief is genuinely multimodal — several clusters, only one correct. With too few particles, or after an unlucky resample, all the particles end up in one cluster — and it's the wrong one. The filter is now tightly, confidently localized at a false position, and because no particle survives near the truth, no future measurement can pull it back.

Symptom & detection. Symptom: the filter is confident and stable, but its estimate is consistently offset by a "symmetry distance" (e.g. exactly one room over, or 180° flipped). Detection: the measurement residuals stay small (the wrong mode also explains the data — that's why it's symmetric!) so residuals alone won't catch it — you need an independent ground-truth check or a disambiguating sensor. Fix: keep enough particles to hold multiple modes until the symmetry is broken, inject random particles, and add a sensor that breaks the symmetry (a unique landmark, a compass).

The N₎ₐₐ signature of each trap — a table you can read off your logs

What N₎ₐₐ doesDistinct particlesDiagnosis
healthy: dips after weight, restored by resamplemany, spreadworking correctly
collapses toward 1, never recoversstill many positionsTrap 1: weight degeneracy — resample more
pinned at N (you resample every step)collapsing toward 1Trap 2: impoverishment — resample less + roughen
healthy-looking, residuals smallone tight clusterTrap 3: wrong mode — check against ground truth

Trap 4 — Resampling noise & an over-sharp likelihood

Two related, common bugs. Resampling noise: each resample injects a little randomness (which particle gets duplicated is partly luck), so resampling every step adds jitter to the estimate — the cure is the N₎ₐₐ gate and low-variance sampling. Over-sharp likelihood: if you set the sensor σ far too small, then after the weight step almost every particle has near-zero weight (only those matching the reading to the millimeter survive), N₎ₐₐ crashes to ~1 every single step, and a single outlier reading can annihilate the entire correct cloud.

Symptom & detection. Symptom: N₎ₐₐ crashes to near 1 immediately after every weight step, regardless of how good the belief is; the filter is hyper-sensitive to single bad readings. Detection: log N₎ₐₐ right after weighting — if it's near 1 even when the cloud is well-placed, your likelihood is too sharp. Fix: inflate the measurement σ (model more noise than the datasheet claims) and add a uniform outlier floor to the likelihood (Chapter 8). A more forgiving likelihood keeps the swarm alive.

A debugging checklist you can run on any particle filter

CheckHowCatches
Log N₎ₐₐ every step1/Σw²; watch dip-then-recoverTraps 1 & 4 (degeneracy, over-sharp likelihood)
Count distinct particleshow many unique positions remain?Trap 2 (impoverishment)
Compare to ground truthoffset by a symmetry distance?Trap 3 (wrong mode)
Kidnap testteleport truth; does it recover?too-few particles, no random injection
Inflate σ / add outlier floordoes N₎ₐₐ stop crashing?Trap 4 (over-sharp likelihood)
The meta-lesson. A particle filter that looks tight and confident may be degenerate (one real particle), impoverished (all identical), or locked onto the wrong symmetric mode — and all three can have small residuals. Confidence is not correctness. Log N₎ₐₐ, count distinct particles, and test against ground truth and a kidnap. The number that diagnoses the most failures is the same one that gates the cure: the effective sample size.

A localization particle filter looks tight and confident, its measurement residuals are small, but it's consistently one room off in a building with identical rooms. Which trap is this, and why won't the residuals catch it?

Chapter 10: Connections, References & Cheat-Sheet

You can now represent a belief of any shape and fuse measurements into it by survival of the fittest. Before we point to what's next, here is everything in one place — the cheat-sheet to screenshot, the real sources, the cross-links, and the motto.

The particle filter — cheat-sheet

Why: when the fused belief is multimodal or non-Gaussian (a robot in a symmetric hallway), a single bell curve (Kalman) can't represent it. Represent the belief as a cloud of weighted samples (particles) instead — density = probability, so it can take any shape.

The three-step loop (recursive Bayesian fusion):
· Predict — push every particle through the motion model + independent noise. Cloud translates and widens. Weights untouched.
· Weight (= the fusion step) — score each particle by measurement likelihood, w ∝ exp(−r²/2σ²) with residual r = z − h(x); normalize. This is prior × likelihood = Bayes.
· Resample — survival of the fittest: draw N new particles ∝ weight (low-variance comb, one random offset). Heavy reproduce, light die. Weights reset to 1/N — turns weight into density.

Health meter: Neff = 1/Σw². Equal weights → Neff=N; one dominant → Neff→1. Resample only when Neff < N/2.

Two diseases: weight degeneracy (resample more) · sample impoverishment (resample less + roughen). The Neff gate balances them.

When to use: multimodal belief, global localization / kidnap recovery, or strongly non-Gaussian noise. Else use a Kalman/EKF/UKF (cheaper). Watch the curse of dimensionality: ~10ᵈ particles — keep the sampled state to ≤3–4 dims.

Particle filter vs the Gaussian filters — one lookup table

Screenshot this. It is the whole "which filter?" decision collapsed into a row:

FilterBelief shapeCostBest when
Kalmanone Gaussian, lineartiny (closed form)single mode, linear, Gaussian, good init
EKF / UKFone Gaussian, nonlinearsmallsingle mode, mild–moderate nonlinearity
Particleany shape (multimodal)high (N samples, curse of dim.)multimodal, global localization, non-Gaussian

The motto

"What I cannot create, I cannot understand." — Richard Feynman.
You can now create a particle filter — scatter the cloud, predict, fuse by weighting, resample — and watch a belief that no bell curve could hold collapse onto the truth.

Where this lesson sits in the series

This was Lesson 10 of 22, the last of the classical estimation filters. We climbed the ladder of belief representations: Lesson 4 gave the general Bayes filter; Lesson 5 the Kalman filter (the Bayes filter when everything is linear and Gaussian); Lessons 6–9 the EKF/UKF (nonlinear, still Gaussian). The particle filter is the rung where we finally drop the Gaussian assumption entirely and represent belief by sampling. Next, Lesson 11 takes these filters into spatial fusion — occupancy grids and the mapping side of SLAM — where the particle filter you just learned reappears as the engine of Monte Carlo Localization and FastSLAM.

Related lessons on Engineermaxxing

These go deeper on machinery we touched:

References

  1. Gordon, N. J., Salmond, D. J., and Smith, A. F. M. "Novel approach to nonlinear/non-Gaussian Bayesian state estimation." IEE Proceedings F (Radar and Signal Processing), vol. 140, no. 2, pp. 107–113, 1993. The original "bootstrap filter" that introduced sequential importance resampling. doi:10.1049/ip-f-2.1993.0015
  2. Thrun, S., Burgard, W., and Fox, D. Probabilistic Robotics, chapter 4 ("Nonparametric Filters") and chapter 8 ("Mobile Robot Localization: Grid and Monte Carlo"). MIT Press, 2005. The definitive treatment of particle filters, low-variance resampling, and Monte Carlo Localization. probabilistic-robotics.org
  3. Doucet, A., de Freitas, N., and Gordon, N. (eds.). Sequential Monte Carlo Methods in Practice. Springer, 2001. The reference compendium on particle filters, including the effective-sample-size criterion and resampling schemes. doi:10.1007/978-1-4757-3437-9
  4. Isard, M. and Blake, A. "CONDENSATION—Conditional Density Propagation for Visual Tracking." International Journal of Computer Vision, vol. 29, no. 1, pp. 5–28, 1998. The particle filter's breakthrough application to non-Gaussian visual tracking in clutter. doi:10.1023/A:1008078328650
A teammate says "let's swap our Kalman filter for a particle filter everywhere — particle filters are strictly better." What's the precise correction?