The classic engine, and when to prefer it
The package ships two race engines. The
front door (race_probabilities /
abilities_from_race) is preferred for most purposes; the
classic engine (winning.classic, the
2021 SIAM paper's implementation) is retained, maintained, and for
one kind of input it is the only correct choice. The rule that
decides between them is not new-versus-old. It is
provenance: what format does your distribution arrive in?
The two primitives
The engines disagree about what a probability distribution is.
To the front door, a distribution is a formula: a standardized survival/density callable evaluated exactly, at machine precision, wherever the engine asks. The lattice discretizes only the one-dimensional integral over the winning value, and because that integrand is smooth and dies in both tails, the quadrature is spectrally accurate — machine precision by 33 lattice points on asymmetric, nearly tied fields. Ties have probability zero by construction, so nothing about them needs tracking.
To the classic engine, the atom vector on the lattice is the distribution. Any probabilities from anywhere — an empirical histogram of finish times, integer scores, bootstrap output — are a first-class citizen, with no smoothness assumed. Atoms tie with real, fixed, positive probability, and the engine's multiplicity calculus (the conditional expected number of runners sharing the winning value) prices those dead heats exactly. That calculus is not overhead; it is what exactness means when ties are genuine events.
The only error is format conversion
Each engine is exact-or-spectral for its own primitive. Error enters an analysis exactly once: when a distribution crosses from one format to the other.
| Your distribution arrives as… | Front door | Classic |
|---|---|---|
| a formula (normal, Gumbel, skew-normal, any standardized callable) | native; win probabilities at 1e-16 from 65 points (measured) | must be sampled onto atoms first: ~1e-3 error at 129 points, improving only at second order (measured) |
| atoms (empirical data, integer scores, real dead-heat mass) | must be smoothed into a callable, which deletes the dead-heat mass — a model error no refinement recovers | native and exact, ties priced by the multiplicity calculus |
That whole table is one sentence: formulas to the front door, atoms to classic. Both directions of conversion cost are pinned in the test suite, not just asserted here.
Why the front door is preferred for most purposes
Most statistical work starts from a formula — a parametric
noise law with locations to estimate — and on that side the
front door brings everything the classic engine does not have:
the five correlation grammars (factor, block, nested, tree) with one
O(nLQ) shared-field pass; exact Jacobians whose
off-diagonals are the photo-finish tie densities (the substitution
graph); removal counterfactuals from the same field; inversion
measured to a million alternatives; dense-covariance intake
(cov=); and the softmax/Gumbel members in closed form.
The classic engine is independent-only and prices one thing:
the race it was given.
Why classic is retained
- It is the only correct engine for atomic data. Real dead heats are events with mass; the front door cannot represent them at all.
- Exotics and betting-market vocabulary. State prices, dividends, place/show pricing on genuinely discrete results live here.
- It is the reference implementation of the SIAM 2021 paper, parity-locked across the R and JavaScript ports, and the historical root of everything else in the package.
Fine print
Two places inside the front door do fall back to grid-represented curves rather than exact evaluation: the finite-temperature path (numeric convolution with the Gumbel kernel) and the block/tree kernels' factor shifts (linear interpolation of gridded messages). Those are exactly the spots where cross-language parity sits at 1e-6–1e-7 instead of 1e-15 — the residue of tabulation inside an otherwise formula-based engine, and consistent with everything above.
The deprecation aliases (winning.lattice_calibration
and friends) forward to winning.classic unchanged, so
pre-2.0 code keeps working while saying, once per import, where its
engine now lives.