Track accuracy is an aperture decision
At X-band, 50 km out, with a 64 by 64 array and 25 dB of integrated signal-to-noise ratio, my radar model measures range to 0.60 m and cross-range to 34.2 m.
A factor of 57 between two coordinates of the same measurement. Both terms carry , so doubling the transmit power improves each by the same amount and leaves the ratio exactly where it was. More SNR cannot cure the anisotropy. Bandwidth moves the ratio, but only by sharpening the coordinate that is already sharp. Beamwidth, set by the size of the aperture, is what attacks the coarse one. That fixes the measurement. How much of a measurement survives into a track is decided by revisit rate and target dynamics, and the second half of this post is the case where almost none of it does.
Until last week my phased-array-systems package computed neither number. The radar models ran range equation, then signal-to-noise ratio, then constant-false-alarm-rate loss, then probability of detection, and stopped at the detection decision. Whether a target is seen was answered. How well it is located was not asked.
The question I started with
I asked whether to add Kalman filtering to the package, or somewhere else in the nine-repository stack I have been building. The answer split into two halves that point in opposite directions.
A recursive filter does not belong there, and my own design document said so before I went looking. Under non-goals:
Not a real-time DSP radar tracker; this is a design-time trade tool.
The architecture agrees with the document. Every model in the package implements one method,
evaluate(arch, scenario, context), which returns a flat dictionary of scalars. There is no
place in that contract for a state vector or a covariance matrix, and no time index for them to
evolve along. A recursive filter is structurally incompatible with the package, which is a
better reason to leave it out than any argument about scope.
The useful half is that the answer a designer wants is not recursive. Under stationary noise and a constant revisit interval, a Kalman filter reaches a steady state whose gains and covariance have closed forms. Scalars go in, scalars come out, and nothing iterates. That fits a flat metrics dictionary exactly, and it is available before any filter has been written.
The measurement covariance nobody derives
The obvious move is to depend on an existing library. I looked at what exists.
filterpy is the teaching standard, MIT licensed, with an excellent companion book. Its last release is 1.4.5, from October 2018. Stone Soup is the serious option: MIT, maintained by the UK Defence Science and Technology Laboratory, version 1.9.1 released in June 2026, with thirty one subpackages covering motion models, predictors, updaters, data associators, OSPA and GOSPA metrics, and a family of radar sensor models.
Stone Soup even has an AESARadar class that takes real design parameters: number of pulses,
duty cycle, bandwidth, receiver noise figure, antenna gain, beamwidth, and target cross-section.
It computes a genuine radar-equation signal-to-noise ratio from them, including a scan loss that
degrades gain as the beam steers off broadside.
Then it uses that SNR for exactly one purpose: probability of detection. Read the measure
method and the measurement itself comes from a separately supplied measurement_model whose
noise_covar the user hard-codes. SNR decides whether a detection appears. It never touches how
precise that detection is.
That is the missing edge in the graph. The relations that close it are old and standard:
Range accuracy is a fraction of a range cell, angle accuracy is a fraction of a beamwidth, and both fractions shrink as the square root of SNR. is the monopulse difference-pattern slope, 1.6 in Curry’s worked comparison and in every number below. The second relation needs the beamwidth. The beamwidth needs the array. So the calculation lands naturally in the package that already models the array, and awkwardly anywhere else.
Both of these are thermal-noise terms and nothing else. Curry builds the full error as , adding fixed random and bias terms in quadrature, and those set a floor that no amount of SNR reaches past. In his own example an 18.9 m noise term becomes a 21.6 m total. Target glint does the same to the angle coordinate at short range. Everything in this post is the first term, which is the part a designer controls by choosing an array and a waveform.
The gap runs deeper than one class. Searching both libraries for the tracking index, the
parameter that sets the whole steady-state solution, returns nothing. Stone Soup has an
AlphaBetaUpdater whose and are free floats the user supplies, documented only
with their valid ranges. The closest prior art I found for mapping design parameters to track
accuracy is a set of Excel VBA functions shipped with Curry’s Radar System Performance
Modeling in 2005, one of which has the signature
AngleError_mR(Beamwidth_mR, SNR_dB, AngleFixEr_mR, ScanAngle_deg, ...). The right calculation
has existed for twenty years in a spreadsheet.
Cross-range dominates
Angle accuracy is a fixed fraction of a beamwidth, and a beamwidth subtends a distance that grows with range. Range accuracy has no such term. So the two errors diverge as the target gets further away, and the aperture is what sets the divergence.
Sweeping array size at 50 km against a fixed waveform, holding transmit power fixed at 10 W per element:
| Array | Integrated SNR | Beamwidth | Ratio | ||
|---|---|---|---|---|---|
| 16 × 16 | −11.2 dB | 6.32° | 38.4 m | 8825 m | 230 |
| 32 × 32 | 6.9 dB | 3.16° | 4.79 m | 550 m | 115 |
| 64 × 64 | 25.0 dB | 1.57° | 0.60 m | 34.2 m | 57 |
| 128 × 128 | 43.0 dB | 0.74° | 0.07 m | 2.0 m | 29 |
Both columns improve as the array grows, and it is worth being exact about why, because the sweep buys more than area. With per-element power held fixed, transmit power rises with element count and each of the two antenna gains rises with it as well, so SNR goes as the cube of element count: 18 dB per doubling of a side, and an eightfold accuracy gain once is applied. Hold total transmit power fixed instead and SNR goes as the square, leaving a fourfold gain. The four rows above span 2.6 kW to 164 kW of peak transmit power, so read the two sigma columns as a power-aperture trade. Cross-range improves faster by one further factor of the array dimension, because the beam narrows as well. That last factor is the one thing here that is purely geometric: the ratio column carries no SNR at all, and it halves on each doubling whichever power convention you choose.
Two rows in that table are outside the validity of the formula that produced them. The angle relation is derived for SNR above about 13 dB, and below that the monopulse ratio is biased and the variance form is optimistic. The 16 by 16 row runs at dB and the 32 by 32 row at 7 dB, so their cross-range figures are indicative at best, and the 16 by 16 case is not a detection in the first place. At the other end, the 0.07 m in the bottom row is a thermal number with no bias floor under it; a real X-band radar is calibration-limited well above that. The beamwidths come from a numerical cut through the pattern on a quarter-degree grid, which is coarse by the bottom row: 0.74 degrees there should be 0.79.
At a fixed signal-to-noise ratio the split is clean. Bandwidth sets range accuracy and does nothing at all for cross-range. Aperture sets cross-range. Since cross-range is the larger error by one or two orders of magnitude, it is the one that dominates the measurement, and waveform design cannot touch it. What buys it is effective aperture, which for a filled half-wave array tracks physical size, though illumination taper trades beamwidth against sidelobes at constant area and breaks that equivalence.
A precise measurement you cannot use
The part that surprised me came from the filter rather than the measurement.
The steady-state solution is governed by a single dimensionless number, introduced by Kalata in 1984:
the position uncertainty from target maneuverability divided by the position uncertainty from the sensor. Everything else follows from it in closed form: the gains, the steady-state covariance, the lag behind a sustained maneuver.
Run the same 50 km case against a target pulling roughly 4 g, entered as 40 m/s², updated once per second, and apply the filter to each coordinate:
| Axis | Measurement | Track error | ||
|---|---|---|---|---|
| Range | 0.60 m | 42.7 | 0.998 | 0.60 m |
| Cross-range | 34.2 m | 1.01 | 0.752 | 29.7 m |
Three conventions sit behind that table. is a process-noise standard deviation, not the maneuver itself; POMR reaches it from a stated maximum acceleration through a fitted coefficient that depends on the deterministic index, which is why the two rows imply different from one 4 g target. That fit is quoted over a deterministic index of 0.01 to 10, and the range axis here sits at 67, so the 42.7 is an extrapolation. And the full 4 g is applied to each axis rather than projected onto it, so the pair of rows bounds both coordinates at once instead of describing one trajectory.
On the range axis the index is 43. The maneuver is enormous compared to a sub-metre measurement, so the optimal gain is 0.998: the filter must throw away almost all of its history and believe each new measurement. Smoothing gains nothing. The track error equals the measurement error to three significant figures.
A 60 cm range measurement, against a maneuvering target at a one-second revisit, buys precision the filter cannot keep. On the coarse cross-range axis the index is almost exactly 1, which puts the gain at the tidy value of 0.75, and filtering does help there, pulling 34.2 m down to 29.7 m.
This is the kind of result I wanted the model for. It is not reachable by looking at a range equation, and a tracking library will not surface it either, because a tracking library never knew the bandwidth or the beamwidth that produced those two numbers.
Three sources disagreed
The interesting work was reconciliation. Three times, published sources conflicted, and each time the resolution came from computing rather than from choosing an authority.
The factor of two. Principles of Modern Radar gives range accuracy as . Curry gives . The formulas differ by and are algebraically identical, because POMR defines SNR as , the matched-filter peak, while Curry uses . My range equation produces the Curry quantity, so the second form is the correct one to pair with it. I had written the first version with the wrong pairing, applying one convention to range and the other to angle, which scales the ratio between them by . That put a cross-range ratio of 51 in an early note, where the corrected figure is 73. That note was written at a different operating point from the one in this post; the ratio quoted throughout here is the 57 of the 64 by 64 row above. This is the same class of error that made me standardise on a single noise-temperature convention two releases ago.
Two covariance formulas. POMR gives the steady-state error covariance of the alpha-beta filter with a position term of . Mahafza gives a variance reduction ratio with a numerator of . At these are 0.750 and 0.667. A third form on a publisher’s site gives 1.667, which is above unity and would mean that filtering amplifies noise.
Rather than pick one, I wrote a fixed-gain covariance recursion in Joseph form, sharing no code with the package, and iterated it to convergence twice: once with process noise present, once with it zeroed. POMR’s formula reproduced the first to a relative error of . Mahafza’s reproduced the second exactly. Both are correct and they answer different questions, total track error against sensor-noise reduction in the absence of a maneuver. The package now emits both, labelled, and a canary test asserts that the third form is not implemented.
Numerical conditioning. POMR gives the optimal gains as explicit functions of . Those expressions build from a difference of large terms, and every downstream formula then needs . As approaches 1 the cancellation gets severe: at the relative error in reaches . Substituting into two other equations in the same chapter yields a quadratic whose smaller root gives the identical solution with carried exactly as , good to across the same range. It is the same mathematics rearranged, derived from equations the book already prints, and both forms are pinned against each other in the tests.
None of these three took long. All three would have shipped silently.
The chain was also broken in the middle
While this was going on I went back to an old graduate report of mine on clutter suppression for an L-band air traffic control radar, and found a second gap in the same chain.
The package computed how much clutter a geometry produces. Nothing computed how much of it a radar can remove. A ground-based radar looking at wooded hills sees a clutter return of 35.6 dBsm against a 3.0 dBsm target, so roughly 33 dB more clutter than target in raw cross-section, which works out to a signal-to-clutter ratio of dB once the geometry is carried through. The detection model scored that as hopeless. Real radars of that description work fine, because they filter clutter in Doppler.
Adding the moving-target-indication step closes it. For the same worked case:
| MTI | Signal-to-clutter | Signal-to-clutter-plus-noise | |
|---|---|---|---|
| off | −21.0 dB | −21.0 dB | 0.000 |
| two-pulse | 9.1 dB | 8.8 dB | 1.000 |
| three-pulse | 36.3 dB | 20.6 dB | 1.000 |
A target the model called undetectable becomes a certain detection with a two-tap filter, at this target Doppler. That qualifier carries weight: a two-pulse canceller nulls at zero Doppler and again at every multiple of the pulse repetition frequency, so the same filter that rescues this target erases one at 46 m/s, the first blind speed for this radar. Staggering the interval is the usual answer, and the model reports the blind speeds rather than hiding them.
Feeding the result back into accuracy takes one more assumption. Signal-to-clutter-plus-noise goes into the same place signal-to-noise did, which treats residual clutter as if it were thermal noise. It is not; what survives a canceller is correlated in range and Doppler, and a noise-limited variance is an approximation for it. Even so the direction is right, and the missing clutter step had been corrupting the new track numbers as well as the detection ones. The two gaps were the same gap seen from two ends.
I computed the improvement factor from the general quadratic form over the binomial canceller weights instead of from the tabulated two-pulse and three-pulse expressions. It reproduces both exactly, extends past three pulses where nothing is tabulated, and is better conditioned than the three-pulse closed form, which differences three terms all near unity.
Verifying against the old report turned up one more thing worth reporting: its parameter table lists the wavelength at 1.3 GHz as 0.321 m, which is a transposition of 0.231 m. Every number downstream used the correct value, so no result moves. It sat unnoticed for twelve years.
What I would tell someone facing the same question
Ask what the calculation is for before asking which library computes it. “Add Kalman filtering” described a technique. The question underneath it was how a designer should choose an aperture, and that question is answered by an algebraic expression rather than by a filter. The distinction between a recursion and its fixed point decided the architecture, the dependencies, and the runtime.
Then, when the sources disagree, compute. I spent longer reading three textbooks about one covariance matrix than I spent writing the twenty-line recursion that settled which of them applied. The recursion also became the strongest test in the suite, because it is an independent implementation of the same physics rather than a number copied out of a book.
The work is in v0.14.0: two modules, 90 new tests, and twelve new rows in the validation table. Every assertion is pinned to a published equation, a worked example from a textbook, or a recursion that shares no code with the thing it checks.
The numbers above assume a filled array at half-wave spacing, a point target, a thermal-noise-limited estimator with , a constant revisit interval, and stationary measurement noise. No calibration, glint, or bias floor is included in any of them.
Frequently asked questions
Why compute steady-state track accuracy instead of running a Kalman filter?
Because a design-time trade tool asks a different question than a tracker does. A Kalman filter needs a time-indexed state and a stream of detections. Under stationary noise and a constant revisit interval, the filter settles to gains and a covariance available in closed form, which is scalar arithmetic with no time loop and no state. A designer choosing an aperture wants that number long before any filter exists, and it fits a flat metrics dictionary that a state vector cannot.
Why does this belong in an array package rather than a tracking library?
Tracking libraries take the measurement covariance as an input. Stone Soup's AESARadar model computes a full radar-equation SNR from transmit power, antenna gain, pulse count and beamwidth, then uses it only to decide probability of detection; the measurement covariance is a separate constant the user supplies. So SNR determines whether a detection occurs and never how precise it is. Deriving that covariance needs the beamwidth, which needs the array, which is why the calculation belongs where the array model lives.
What is the tracking index and why does it matter for design?
It is the ratio of position uncertainty from target maneuverability to position uncertainty from the sensor, Gamma = sigma_v T^2 / sigma_w, introduced by Kalata in 1984. It is the single number that sets the steady-state filter gains. A high index means the target out-maneuvers the measurement noise, so the filter must trust each new measurement almost completely and smoothing buys nothing.
Two textbooks give different steady-state covariance formulas. Which is right?
Both, because they answer different questions. The form in Principles of Modern Radar gives the total steady-state error with process noise included. The form in Mahafza gives the variance reduction ratio with process noise removed, which is the no-maneuver case. I settled it by iterating a fixed-gain covariance recursion to convergence twice, once with process noise and once without; each formula reproduced its own recursion to twelve significant figures. A third form circulating online is simply wrong, since it exceeds unity and claims that filtering amplifies noise.
How much does cross-range error dominate range error in practice?
At X-band, 50 km, 25 dB integrated signal-to-noise ratio, 10 MHz bandwidth and a 64 by 64 array, range accuracy is about 0.6 m and cross-range accuracy is about 34 m, a factor of 57. Both terms carry one over the square root of SNR, so more power improves each equally and leaves the ratio unchanged. The ratio is set by geometry: beamwidth, range, and waveform bandwidth. Aperture is the lever that attacks the cross-range coordinate directly, since bandwidth does nothing for it.
More in Modeling & simulation
- sysml2kit 0.4.0: multi-fidelity verification on a compute budget · 2026-08-21
- Power-aperture, and the heat it hides · 2026-08-13
- pitchphys, a baseball pitch physics simulator · 2026-07-09