A package for dealing with races, correlated or not.
winning began as the reference implementation of the lattice ability
transform (SIAM J. Financial Mathematics, 2021) and owns the whole line:
the original density-agnostic engine, the factor-correlated
generalization developed for "Scalable Share Calibration for Factor
Multinomial Probit Models", an arena of competing methods, and a
standing benchmark database.
winning.thurstone— the core engine, vendored home from the thurstone package (now a compatibility shim). Densities on a lattice, winner-of-many, dead heats, and the ability transform, for any base distribution.winning.factor— the correlated extension: all-share forward pass, share calibration, Jacobian-vector products, and factor fitting. One general race,race_probabilities, takes the distribution and the factor rank as parameters; factor probit, the classic independent transform, Luce/softmax, and correlated softmax are named special cases, and custom standardized bases plug in as callables. The Gaussian specialization keeps its dedicated tail-exact kernel.winning.methods— every contestant behind one interface: the lattice transform, direct and Sobol simulation, per-alternative factor-RQMC, GHK / Genz separation-of-variables, minimax tilting. Each passes closed-form and Monte Carlo anchors before admission.winning.bench— a seeded problem grid, cached references, and append-only accuracy-time records:python -m winning.bench.runner.
pip install winning # core depends only on numpy and scipy
Independent race: shares from abilities, and back.
import numpy as np
from winning import race_probabilities, calibrate_abilities
mu = np.array([-0.5, 0.0, 0.2, 0.3]) # lower is better (min-wins)
p = race_probabilities(mu) # array([0.443, 0.232, 0.175, 0.150])
mu_back = calibrate_abilities(p) # recovers mu (mean zero)Correlated race: a hundred runners moved by two common factors, all shares in one pass over a shared survival field, then inverted.
rng = np.random.default_rng(0)
N, k = 100, 2
mu = rng.normal(0, 1, N); mu -= mu.mean()
V = rng.normal(0, 0.4, (N, k)) # factor loadings
D = rng.uniform(0.5, 1.5, N) # idiosyncratic variances
p = race_probabilities(mu, V=V, D=D) # all N shares, O(QNL)
mu_hat = calibrate_abilities(p, V=V, D=D) # inversionCounterfactuals and structure from the same shared field:
from winning import removal_shares, tie_densities
q = removal_shares(mu, V=V, D=D) # q[i][j] = P(j wins | i removed)
w = tie_densities(mu, V=V, D=D) # photo-finish weights: the Jacobian's
# graph-Laplacian (circuit) conductancesFor the probit literature, winning.probit speaks max-wins utilities
and shares directly — the paper's own conventions — and is the one
audited reflection onto the internal min-wins race. Both of the paper's
calibrations live here: utilities from observed shares, and the factor
structure itself from a supplied covariance.
from winning.probit import shares, utilities_from_shares, fit_factor_model
utilities = -mu # higher is better on this side
p = shares(utilities, V=V, D=D) # all N choice probabilities
u = utilities_from_shares(p, V=V, D=D) # the paper's calibration
Sigma = V @ V.T + np.diag(D)
V_hat, D_hat = fit_factor_model(Sigma, k=2) # certified rank-k contrast fit
p2 = shares(utilities, Sigma=Sigma, k=2) # same fit applied en routeOne race, everything a parameter: distribution and correlation chosen per call, with factor probit just one named point in the family.
from winning.factor import race_probabilities
race_probabilities(mu) # the classic independent race
race_probabilities(mu, V=V, D=D) # factor probit
race_probabilities(mu, base="gumbel") # Luce / softmax, exactly
race_probabilities(mu, V=V, base="gumbel") # correlated softmax
race_probabilities(mu, temperature=0.7) # E[softmin(X/tau)]: soft creditTemperature is exact, not approximate: by the Gumbel-argmin identity the softmin expectation equals the hard race with each base convolved with the tau-scaled Gumbel kernel, so the same engine serves it. It is not identifiable from a single race, so inversion holds it fixed.
Arbitrary densities (skewed, multimodal, empirical) run through custom
bases or winning.thurstone — see the module docs and research/demos/.
The correlated calibration is documented in Scalable Share Calibration
for Factor Multinomial Probit Models
(papers/factor-probit-transform,
submitted): all shares of a correlated Gaussian race in one O(QNL)
pass, matrix-free graph-Laplacian derivatives, and inversion at ten
thousand alternatives in under a minute. Every number comes from a
committed, seeded script in
research/experiments (index in its README);
research/experiments/run_all_paper.py regenerates the lot.
research/demos holds explanatory scripts (the shared
survival field, the cavity downdate). js/factor is a
dependency-free JavaScript port at machine-precision parity with the
Python, for browser demos; r/winning is a pure-R package;
rust/fastrace holds the optional compiled kernels —
build with pip install maturin && maturin develop --release, and
winning.methods uses them automatically. Julia is on the roadmap.
The renovation-era ratings layer — whole-density beliefs, exact
full-finish-order updates, benchmarked against TrueSkill, OpenSkill,
Glicko-2 and Elo on twelve datasets — lives in src/ pending
integration, with results in BENCHMARKS.md. Headlines:
decisive win on Formula 1 (1,158 grands prix), best calibration on
chess (ECE 0.0047), statistical ties atop WTA/ATP/EPL, and markets
remaining the ceiling wherever they exist. The ThurstoneRating API
documented there ships with a future release; it is not importable from
the current package.
Versions 1.x were the SIAM paper's reference implementation, and those
imports still work. A 2.0 renovation explored splitting the numerical
core into the separate thurstone package with winning as an
applications layer; the decision went the other way. winning owns the
core — heritage and name — the thurstone implementation is vendored
here as winning.thurstone, and the thurstone package is a
compatibility shim whose imports resolve to this one. The renovation's
migration notes and unported ideas are preserved in
planning/ and attic/.
@article{doi:10.1137/19M1276261,
author = {Cotton, Peter},
title = {Inferring Relative Ability from Winning Probability in Multientrant Contests},
journal = {SIAM Journal on Financial Mathematics},
volume = {12},
number = {1},
pages = {295-317},
year = {2021},
doi = {10.1137/19M1276261},
URL = {https://doi.org/10.1137/19M1276261}
}
