Skip to content

Door 1: precomputed score events

This page walks both public tasks — sample partitioning (optimize_partition) and space quantization (fit_quantizer) — through Door 1: you already have (event, score) rows and their weights, so no provider is needed. It replaces the old "first analytic partition" tutorial and answers a question that comes up immediately: once you have a stable partition of your sample, when do you compile it into a rule, and when do you just fit a rule directly?

Problem

Each event is one measurement \(x\sim\mathcal N(\mu,1)\), and the downstream task is to estimate the unknown location \(\mu\). At the reference point \(\mu_0=0\), the score is \(s(x)=x\), so the observation and its score coincide — a useful analytic check, since grouping nearby score values should preserve information about \(\mu\) and the amount lost can be measured exactly.

Data

examples.synthetic_problems.gaussian_location returns deterministic train/validation/test splits of this model. The scores are declared exact at the reference point, so information_kind is later allowed to read "exact_fisher".

import numpy as np

import scorequant as sq
from examples.synthetic_problems import gaussian_location

problem = gaussian_location()
train, test = problem.train, problem.test

provenance = sq.ScoreProvenance(kind="exact", reference_point=(0.0,))
sample = sq.ScoreSample(train.scores, train.weights, provenance=provenance)
assert sample.scores.shape == (2000, 1)

Training histogram and the compiled test-set partition

API walkthrough

optimize_partition, with a stability report

optimize_partition assigns labels to exactly the 2000 rows supplied. exchange_stable and best_remaining_gain are that assignment's own certificate: they report whether any single admissible relocation could still improve the exact objective.

partition = sq.optimize_partition(
    sample.scores,
    weights=sample.weights,
    n_bins=4,
    criterion=sq.DOptimality(),
    config=sq.DExchangeConfig(seed=12),
    provenance=sample.provenance,
)
assert partition.exchange_stable is True
assert partition.best_remaining_gain < 0.0  # no move would raise the objective
train_efficiency = float(partition.train_report.geometric_mean_retention)
assert 0.85 < train_efficiency < 0.92

The same certificate is checkable independently of how the labels were produced, through exchange_stability_report:

stability = sq.exchange_stability_report(
    partition.training_scores, partition.labels, weights=sample.weights
)
assert stability.stable == partition.exchange_stable

partition.labels belongs to these 2000 rows and nothing else — PartitionResult has no predict_scores.

The compile bridge

Because the partition is exchange-stable and the criterion is DOptimality, it is already a strict self-consistent Voronoi partition in the retained Mahalanobis metric (Chapter 6 proves why). compile_quantizer() turns that fact into a QuantizerResult and verifies it reproduces every training label before returning:

compiled = partition.compile_quantizer()
compiled_test = compiled.evaluate_scores(test.scores, test.weights)
compiled_retention = float(compiled_test.geometric_mean_retention)
assert 0.85 < compiled_retention < 0.92
future_bins = compiled.predict_scores(np.array([[0.4], [-1.2]]))

fit_quantizer directly, as the alternative

If a reusable rule is the goal from the start, fit one directly instead of routing through a partition. This also opens criteria and solvers compile_quantizer cannot reach — here, normalized-trace k-means instead of D-optimal exchange:

direct = sq.fit_quantizer(
    sample,
    n_bins=4,
    criterion=sq.NormalizedTrace(),
    config=sq.KMeansConfig(seed=12, solver_restarts=4),
)
direct_retention = float(direct.evaluate_scores(test.scores, test.weights).geometric_mean_retention)
assert 0.85 < direct_retention < 0.92
assert round(compiled_retention, 6) == round(direct_retention, 6)

On this one-dimensional problem the two routes land on the same test-set D-efficiency to six decimal places — both are approximating the same optimal interval partition. That agreement is a property of this simple problem, not a guarantee; a later solver-shootout page compares every solver against every baseline on a harder, two-parameter problem where they separate.

Analysis

Route Criterion / solver Certificate Reusable?
optimize_partition alone any exchange_stable, best_remaining_gain no — PartitionResult has no predict method
optimize_partitioncompile_quantizer() DOptimality only, exchange-stable, nonsingular Theorem-backed Mahalanobis rule yes
fit_quantizer directly DOptimality, ProfiledDOptimality, or NormalizedTrace, any solver the fit's own diagnostics yes

Discussion

Task: both — sample partitioning first, then either the compile bridge or a direct fit into space quantization. Door: 1, precomputed (event, score) rows via ScoreSample. Criterion / solver: DOptimality with exact exchange for the partition and its compiled rule; NormalizedTrace with k-means for the direct alternative.

Use optimize_partitioncompile_quantizer() when you specifically want the D-optimal exchange result and its exchange-stability certificate, and only need a reusable rule as a by-product of a result you were already computing. Use fit_quantizer directly when you know from the start you want a rule, or when you want a criterion or solver the compile bridge does not reach — NormalizedTrace, ProfiledDOptimality, or the soft-Voronoi and scalar dynamic-programming solvers all skip the partition step entirely. See Chapter 4 for where scores and doors come from, and Chapter 6 for the theorem behind the compile bridge.

The matching notebook, door1_score_events.ipynb, runs the fuller study with more restarts and a closer look at where the two routes would disagree on a harder problem.