ScoreQuant¶
ScoreQuant compresses events into a small number of hard bins while preserving the Fisher information that downstream parameter estimation depends on.
Analyses that need counts in named categories — template fits, trigger tiers, gated populations, binned likelihoods — usually pick those categories for readability and lose parameter sensitivity without measuring how much. ScoreQuant works in score space, where each event is represented by its local log-likelihood gradient \(s(x)=\nabla_\theta\log p(x\mid\theta)\big|_{\theta_0}\), and optimizes a matrix criterion of the information that survives the labels. The loss becomes a number you choose and report instead of one you inherit from the axis ticks.
Two tasks¶
scores -> optimize_partition() -> PartitionResult
ScoreSample | ObservationSample
| IntegrationSource (+ score provider) -> fit_quantizer() -> QuantizerResult
Sample partitioning answers "what are the best labels for the weighted sample in front of me?"
It is transductive, and PartitionResult deliberately has no predict method: a labeling of one
table does not determine what happens to an event you have not seen.
Space quantization answers "what reusable rule should I apply to future events?" It is
inductive, and prediction is the explicit QuantizerResult.predict_scores(scores).
The one sanctioned crossing between them is a theorem rather than a convenience: an
exchange-stable, nonsingular D-optimal partition is already a strict self-consistent Voronoi
partition in the \(I_B^{-1}\)-Mahalanobis metric, so PartitionResult.compile_quantizer() returns
exactly that rule — and refuses when the partition is unstable or geometrically degenerate.
Three doors¶
| Sample partitioning | Space quantization | |
|---|---|---|
Door 1 — precomputed (event, score) rows |
optimize_partition(scores, weights=w, n_bins=k) |
fit_quantizer(ScoreSample(scores, w), n_bins=k) |
| Door 2 — component densities or an analytic score model | optimize_partition(provider.score(X), weights=w, n_bins=k) |
fit_quantizer(source, provider=provider, n_bins=k) with an ObservationSample or IntegrationSource |
| Door 3 — density ratios (estimated by a calibrated classifier or a direct ratio estimator, or analytic) | optimize_partition(provider.score(X), weights=w, n_bins=k) |
fit_quantizer(ObservationSample(X, w), provider=provider, n_bins=k) |
optimize_partition always takes score rows, so doors 2 and 3 reach it through an explicit
provider.score(X) call. Observation-to-score conversion never hides inside fitting or prediction.
Three doors treats each regime in full.
Install¶
or, outside a uv project, pip install scorequant. Python 3.12 or newer. JAX and Optax are the
required numerical dependencies; NumPy is a supported portable runtime, which is what lets a saved
rule predict where JAX is absent. ScoreQuant never sets global JAX configuration at import, so
64-bit precision stays your application's call (JAX_ENABLE_X64=1).
Quickstart¶
A Gaussian location model \(x\sim\mathcal N(\mu, I_2)\) has score \(s(x)=x-\mu_0\), so at \(\mu_0=0\) the events are already the scores. First, the fixed-sample task:
import numpy as np
import scorequant as sq
rng = np.random.default_rng(3)
scores = rng.normal(size=(2_000, 2))
partition = sq.optimize_partition(
scores,
n_bins=5,
criterion=sq.DOptimality(),
config=sq.DExchangeConfig(seed=3),
)
stable = bool(partition.exchange_stable)
efficiency = float(partition.train_report.geometric_mean_retention)
partition.labels belongs to those 2000 rows and nothing else. This run ended exchange-stable
(stable is True), and only in that case do the same labels also define a reusable rule:
if partition.exchange_stable:
quantizer = partition.compile_quantizer()
future_bins = quantizer.predict_scores(rng.normal(loc=0.2, size=(500, 2)))
If a reusable rule is what you wanted from the start, fit one directly and let the criterion and solver be your choice rather than a by-product:
quantizer = sq.fit_quantizer(
sq.ScoreSample(scores),
n_bins=5,
criterion=sq.NormalizedTrace(),
config=sq.KMeansConfig(seed=3, solver_restarts=4),
)
future_bins = quantizer.predict_scores(rng.normal(loc=0.2, size=(500, 2)))
The three fast starts in the README do the same for each door, and Choosing your workflow walks through the decision.
Site map¶
- Why ScoreQuant — the problem, why naive binning loses information, why score space, and why the two tasks stay apart.
- Method overview — the pipeline from score to certificate, with the criteria and solver families named.
- Three doors — the input regimes, the source-versus-provider contract, and the validation rules.
- Choosing your workflow — which task, which door, which criterion and solver.
- The book — the statistical theory developed independently of this package's API.
- Examples — ten runnable pages: the three doors, a solver shootout against three baselines, theory demonstrations (profiled \(D_s\), soft rules, two counterexamples, global certification), and a teaser into the real-data study below.
- Evidence — the synthetic gallery and the reproducible FlowCyt study.
- API guide and reference — released contracts and errors.
- Related work — the four research traditions this sits in, an honest known-versus-new table, and a software comparison.
- Glossary and bibliography.