scorequant 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. scorequant-0.1.0/LICENSE +21 -0
  2. scorequant-0.1.0/PKG-INFO +394 -0
  3. scorequant-0.1.0/README.md +359 -0
  4. scorequant-0.1.0/pyproject.toml +125 -0
  5. scorequant-0.1.0/pyproject.toml.orig +106 -0
  6. scorequant-0.1.0/src/scorequant/__init__.py +139 -0
  7. scorequant-0.1.0/src/scorequant/_binstats.py +88 -0
  8. scorequant-0.1.0/src/scorequant/_chunking.py +34 -0
  9. scorequant-0.1.0/src/scorequant/_execution.py +414 -0
  10. scorequant-0.1.0/src/scorequant/_json.py +55 -0
  11. scorequant-0.1.0/src/scorequant/_typing.py +9 -0
  12. scorequant-0.1.0/src/scorequant/_validation.py +144 -0
  13. scorequant-0.1.0/src/scorequant/api.py +775 -0
  14. scorequant-0.1.0/src/scorequant/artifact.py +452 -0
  15. scorequant-0.1.0/src/scorequant/certify.py +405 -0
  16. scorequant-0.1.0/src/scorequant/components.py +354 -0
  17. scorequant-0.1.0/src/scorequant/config.py +430 -0
  18. scorequant-0.1.0/src/scorequant/criteria.py +130 -0
  19. scorequant-0.1.0/src/scorequant/information.py +673 -0
  20. scorequant-0.1.0/src/scorequant/partition.py +1786 -0
  21. scorequant-0.1.0/src/scorequant/providers.py +465 -0
  22. scorequant-0.1.0/src/scorequant/py.typed +0 -0
  23. scorequant-0.1.0/src/scorequant/quantizers.py +57 -0
  24. scorequant-0.1.0/src/scorequant/ratios.py +347 -0
  25. scorequant-0.1.0/src/scorequant/reports.py +427 -0
  26. scorequant-0.1.0/src/scorequant/result.py +466 -0
  27. scorequant-0.1.0/src/scorequant/solvers/__init__.py +1 -0
  28. scorequant-0.1.0/src/scorequant/solvers/common.py +72 -0
  29. scorequant-0.1.0/src/scorequant/solvers/kmeans.py +234 -0
  30. scorequant-0.1.0/src/scorequant/solvers/scalar.py +142 -0
  31. scorequant-0.1.0/src/scorequant/solvers/soft.py +374 -0
  32. scorequant-0.1.0/src/scorequant/sources.py +362 -0
  33. scorequant-0.1.0/src/scorequant/transforms.py +154 -0
  34. scorequant-0.1.0/src/scorequant/visualization.py +257 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vitaly Vorobyev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,394 @@
1
+ Metadata-Version: 2.4
2
+ Name: scorequant
3
+ Version: 0.1.0
4
+ Summary: Information-preserving binning for statistical inference
5
+ Keywords: binning,fisher-information,jax,statistics
6
+ Author: Vitaly Vorobyev
7
+ Author-email: Vitaly Vorobyev <vit.vorobiev@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Requires-Dist: jax>=0.7 ; sys_platform != 'emscripten'
17
+ Requires-Dist: numpy>=2.0
18
+ Requires-Dist: optax>=0.2 ; sys_platform != 'emscripten'
19
+ Requires-Dist: ipykernel>=6.29 ; extra == 'examples'
20
+ Requires-Dist: matplotlib>=3.10 ; extra == 'examples'
21
+ Requires-Dist: nbclient>=0.10 ; extra == 'examples'
22
+ Requires-Dist: nbformat>=5.10 ; extra == 'examples'
23
+ Requires-Dist: pandas>=2.3,<4 ; extra == 'examples'
24
+ Requires-Dist: scikit-learn>=1.9,<2 ; extra == 'examples'
25
+ Requires-Dist: matplotlib>=3.10 ; extra == 'viz'
26
+ Requires-Python: >=3.12
27
+ Project-URL: Homepage, https://vitalyvorobyev.github.io/scorequant/
28
+ Project-URL: Documentation, https://vitalyvorobyev.github.io/scorequant/
29
+ Project-URL: Changelog, https://github.com/VitalyVorobyev/scorequant/blob/main/CHANGELOG.md
30
+ Project-URL: Issues, https://github.com/VitalyVorobyev/scorequant/issues
31
+ Project-URL: Repository, https://github.com/VitalyVorobyev/scorequant
32
+ Provides-Extra: examples
33
+ Provides-Extra: viz
34
+ Description-Content-Type: text/markdown
35
+
36
+ # ScoreQuant
37
+
38
+ **Information-optimal hard compression for parametric inference.**
39
+
40
+ ScoreQuant replaces continuous event-level information with a small number of hard bins while
41
+ preserving as much Fisher information as possible for the parameters you actually want to measure.
42
+
43
+ [![CI](https://github.com/VitalyVorobyev/scorequant/actions/workflows/ci.yml/badge.svg)](https://github.com/VitalyVorobyev/scorequant/actions/workflows/ci.yml)
44
+ [![Documentation](https://github.com/VitalyVorobyev/scorequant/actions/workflows/docs.yml/badge.svg)](https://vitalyvorobyev.github.io/scorequant/)
45
+
46
+ Many statistical workflows eventually reduce rich observations to counts in a few named categories:
47
+ template and component fits, gated cell populations, binned likelihoods, trigger tiers, tables a
48
+ collaborator can reproduce by hand. That compression is usually chosen for convenience — equal
49
+ width, equal population, a threshold on one classifier output — even though the goal is parameter
50
+ estimation, and it silently discards sensitivity the experiment already paid for.
51
+
52
+ ScoreQuant chooses the categories from the inference problem instead. For a regular parametric
53
+ model $X\sim p(x\mid\theta)$, the local information an event carries at a reference point
54
+ $\theta_0$ is summarized by its **score**
55
+
56
+ $$
57
+ s(x) = \nabla_\theta \log p(x\mid\theta)\big|_{\theta_0}.
58
+ $$
59
+
60
+ ScoreQuant partitions score space into a few hard cells and optimizes the Fisher information
61
+ retained by their counts. Binning is still lossy; the point is to make the loss a quantity you
62
+ choose, measure and report rather than one you inherit from the axis ticks.
63
+
64
+ The model does **not** have to be a mixture. Mixture fractions, Gaussian means, calibration
65
+ parameters, cross sections, rates, shape and nuisance parameters all enter through the same score
66
+ representation.
67
+
68
+ ## Three independent choices
69
+
70
+ Using ScoreQuant means answering three separate questions:
71
+
72
+ 1. **What are you optimizing?** Labels for one finite sample, or a reusable rule for future events?
73
+ 2. **How do you obtain the score?** Already available, computed from an explicit model, or
74
+ estimated through density ratios?
75
+ 3. **Which parameter information matters?** All of it through $D$-optimality, or only declared
76
+ parameters of interest after profiling nuisance parameters through $D_s$?
77
+
78
+ The axes are independent.
79
+
80
+ ```text
81
+ SCORE ACCESS
82
+ ┌──────────────────────────────┐
83
+ │ precomputed scores │
84
+ TASK │ exact model / score oracle │ OBJECTIVE
85
+ │ density ratios / classifier │
86
+ └──────────────────────────────┘
87
+
88
+ sample partition ─────────────────────────────── D
89
+ space quantizer ─────────────────────────────── Ds
90
+ normalized trace / baselines
91
+ ```
92
+
93
+ The optimizer ultimately sees weighted score vectors. Everything before that is model access;
94
+ everything after it is hard compression.
95
+
96
+ ## 1. Choose the task
97
+
98
+ ### A. Sample partitioning — *the best labels for the sample I have*
99
+
100
+ <!-- snippet: skip -->
101
+ ```python
102
+ partition = sq.optimize_partition(scores, n_bins=8, criterion=sq.DOptimality())
103
+ ```
104
+
105
+ This is **transductive**. The decision variables are the labels of the supplied rows, and the
106
+ result is a `PartitionResult`. A labeling of one finite table does not by itself say what should
107
+ happen to an event you have not seen, so `PartitionResult` deliberately has **no** generic predict
108
+ method.
109
+
110
+ There is one sanctioned crossing, and it is a theorem rather than a convenience: an
111
+ exchange-stable, nonsingular $D$-optimal partition already *is* a strict self-consistent Voronoi
112
+ partition in the $I_B^{-1}$-Mahalanobis metric. Such a result compiles into exactly that rule, and
113
+ refuses when it is unstable or degenerate.
114
+
115
+ <!-- snippet: skip -->
116
+ ```python
117
+ rule = partition.compile_quantizer()
118
+ ```
119
+
120
+ An arbitrary finite $D_s$-optimal partition should **not** be assumed to define a reusable Voronoi
121
+ quantizer.
122
+
123
+ ### B. Space quantization — *the rule I apply to future events*
124
+
125
+ <!-- snippet: skip -->
126
+ ```python
127
+ fit = sq.fit_quantizer(source, provider=provider, n_bins=8, criterion=sq.DOptimality())
128
+ bins = fit.quantizer.predict_scores(provider.score(future_events))
129
+ ```
130
+
131
+ This is the **inductive** problem. Use it when the bins themselves are the deliverable: a histogram
132
+ definition, an event categorizer, a gating rule, or any analysis that must process observations
133
+ that were not present during optimization.
134
+
135
+ ## 2. Choose how the score is obtained
136
+
137
+ The common interface is $x \mapsto s(x)$, and there are three routes to it.
138
+
139
+ | Score access | You already have | Interface |
140
+ | --- | --- | --- |
141
+ | **Precomputed scores** | score vectors $s_i$ | `ScoreSample` |
142
+ | **Exact model / score oracle** | a likelihood, component model, analytic or autodiff score | `ScoreFunction`, `LinearComponentScore` |
143
+ | **Density ratios** | analytic ratios, a direct ratio estimator, a calibrated classifier | `DensityRatioScore`, `CentralLogRatioScore` |
144
+
145
+ These are alternative upstream routes to the *same* downstream optimization problem. Absolute
146
+ densities are never required: the score is the gradient of a log density *ratio*, so a ratio oracle
147
+ is enough — but it must be a calibrated one. A ranking score or an arbitrary monotonic classifier
148
+ output is not, since the construction needs ratios rather than event ordering. See
149
+ [Three doors](https://vitalyvorobyev.github.io/scorequant/three-doors/) for the derivation.
150
+
151
+ ### Sources and providers are different things
152
+
153
+ Fitting a reusable rule needs both a **reference measure** — which observations occur, with what
154
+ weight — and a **score map**.
155
+
156
+ | Source | Meaning |
157
+ | --- | --- |
158
+ | `ScoreSample(scores, weights)` | a finite weighted sample already in score space |
159
+ | `ObservationSample(X, weights)` | finite weighted observations |
160
+ | `IntegrationSource(bounds, density=...)` | deterministic quadrature over a bounded model |
161
+
162
+ Observation-space sources require a provider; a `ScoreSample` rejects one. **Model density ratios**
163
+ construct scores and enter through providers; **importance ratios** modify the reference measure
164
+ and enter as source weights. The two never share an argument.
165
+
166
+ `ScoreProvider` is a public protocol, so an external estimator is a provider without being wrapped:
167
+
168
+ <!-- snippet: skip -->
169
+ ```python
170
+ class MyExternalScore:
171
+ provenance = sq.ScoreProvenance(kind="estimated_ratio")
172
+
173
+ def score(self, observations):
174
+ return my_package.evaluate(observations)
175
+ ```
176
+
177
+ ## 3. Choose what information to preserve
178
+
179
+ For a hard rule with cells $b=1,\ldots,K$, let $W_b=P(q(S)=b)$ and $\mu_b=E[S\mid q(S)=b]$. The
180
+ information retained by the bin label is $I_B=\sum_b W_b\,\mu_b\mu_b^\top$.
181
+
182
+ - **`DOptimality()`** maximizes $\log\det I_B$, treating all score directions symmetrically. Use it
183
+ when the complete parameter vector matters.
184
+ - **`ProfiledDOptimality(interest=...)`** maximizes the log determinant of the Schur complement
185
+ after profiling nuisance parameters. This is **not** a generally better $D$; it answers a
186
+ different question, and can deliberately sacrifice large amounts of nuisance information to
187
+ answer it.
188
+ - **`NormalizedTrace()`** maximizes the Fisher-normalized retained trace. After whitening this is
189
+ weighted $k$-means — an interpretable alternative and a baseline.
190
+
191
+ Parameters of interest can be named rather than indexed, which matters as soon as a model has more
192
+ than a handful of components:
193
+
194
+ <!-- snippet: skip -->
195
+ ```python
196
+ sample = sq.ScoreSample(
197
+ scores, weights, schema=sq.ScoreSchema(("T", "B", "monocyte", "mast", "HSPC"))
198
+ )
199
+ criterion = sq.ProfiledDOptimality(interest=("HSPC",))
200
+ ```
201
+
202
+ Reports then say `interest: HSPC` and `nuisance: T, B, monocyte, mast`.
203
+
204
+ Whichever route supplies the scores, `optimize_partition` always takes score rows — so routes 2
205
+ and 3 reach it through an explicit `provider.score(X)`. The observation-to-score transformation
206
+ never hides inside a fitting call, and prediction never silently recomputes scores.
207
+
208
+ ## Quick start
209
+
210
+ A Gaussian location model $x\sim\mathcal N(\mu, I_2)$ has $s(x)=x-\mu_0$, so at $\mu_0=0$ the
211
+ observations *are* the score vectors.
212
+
213
+ ```python
214
+ import numpy as np
215
+ import scorequant as sq
216
+
217
+ rng = np.random.default_rng(7)
218
+ sample = sq.ScoreSample(
219
+ rng.normal(size=(4_000, 2)),
220
+ schema=sq.ScoreSchema(("mu_x", "mu_y")),
221
+ provenance=sq.ScoreProvenance(kind="exact", reference_point=(0.0, 0.0)),
222
+ )
223
+ ```
224
+
225
+ Partition this finite sample:
226
+
227
+ ```python
228
+ partition = sq.optimize_partition(
229
+ sample, n_bins=6, criterion=sq.DOptimality(), config=sq.DExchangeConfig(seed=7)
230
+ )
231
+ assert partition.exchange_stable
232
+ print(round(float(partition.train_report.geometric_mean_retention), 3))
233
+ ```
234
+
235
+ Six bins retain a $D$-efficiency of about 0.75 — the geometric mean of the retained-information
236
+ eigenvalues. `partition.labels` belongs to these rows and nowhere else.
237
+
238
+ Fit a reusable rule instead, and deploy it:
239
+
240
+ ```python
241
+ fit = sq.fit_quantizer(
242
+ sample, n_bins=6, criterion=sq.DOptimality(), config=sq.DExchangeConfig(seed=7)
243
+ )
244
+ assert fit.information_kind == "exact_fisher"
245
+
246
+ rule = fit.quantizer
247
+ future_bins = rule.predict_scores(rng.normal(loc=0.25, size=(1_000, 2)))
248
+ ```
249
+
250
+ `rule` is the deployable object: a transform, centers and a metric, with no training data attached.
251
+ It saves to a versioned, non-pickle artifact that loads and predicts in a process with no JAX
252
+ installed — fit on the accelerated backend, deploy anywhere.
253
+
254
+ <!-- snippet: skip -->
255
+ ```python
256
+ rule.save("gaussian-6bins.sqz")
257
+ rule = sq.Quantizer.load("gaussian-6bins.sqz")
258
+ ```
259
+
260
+ ## Real-data showcase: FlowCyt
261
+
262
+ The [FlowCyt study](https://vitalyvorobyev.github.io/scorequant/usecases/flowcyt/) is the main
263
+ end-to-end real-data example. Flow cytometry produces individual cells described by twelve marker
264
+ measurements, while the scientific result is a vector of population fractions. The study uses all
265
+ 30 patients: 20 reference, 10 frozen held-out, 600,000 sampled real cells drawn from 21,254,866
266
+ upstream events.
267
+
268
+ ```text
269
+ 12-dimensional cell measurements → calibrated classifier → density ratios
270
+ → 5-dimensional mixture score → ScoreQuant → 8 frozen hard bins
271
+ → integer bin counts → downstream mixture fit → cell-population fractions
272
+ ```
273
+
274
+ At the eight-bin operating point the learned quantizer retains **98.5%** of the supplied-score
275
+ surrogate information and reaches a **0.00193** macro RMSE on the ten held-out patients; the
276
+ selected unbinned classifier-ratio baseline reaches 0.00173.
277
+
278
+ The study is useful beyond cytometry because it draws the boundaries explicitly: *the classifier is
279
+ not ScoreQuant, and the downstream mixture fitter is not ScoreQuant.* ScoreQuant is the
280
+ information-preserving hard-compression layer between them.
281
+
282
+ It also contains a real profiled-$D_s$ experiment, treating one cell fraction as the parameter of
283
+ interest. Interestingly, $D_s$ does **not** materially improve the final measurement there: plain
284
+ $D$ already lies close to a certified efficient-score ceiling. That is a useful negative result,
285
+ and it illustrates why $D_s$ is a different inferential objective rather than an automatically
286
+ superior one.
287
+
288
+ ## Solvers
289
+
290
+ Unsupported task/criterion combinations are rejected before any optimization runs.
291
+
292
+ | Configuration | `optimize_partition` | `fit_quantizer` | Contract |
293
+ | --- | --- | --- | --- |
294
+ | `DExchangeConfig` | `DOptimality`, `ProfiledDOptimality` | `DOptimality` | Exact positive-gain relocations; monotone objective; terminates exchange-stable |
295
+ | `MahalanobisLloydConfig` | `DOptimality`, `ProfiledDOptimality` | `DOptimality` | A batch is adopted only if the exactly rebuilt objective improves; optional exact-exchange guard |
296
+ | `SoftVoronoiConfig` | — | `DOptimality`, `ProfiledDOptimality` | Differentiable soft optimization then hardening, with the hardening gap reported |
297
+ | `KMeansConfig` | — | `NormalizedTrace` | Weighted $k$-means in whitened score space |
298
+ | `ScalarDPConfig` | — | `DOptimality` | The exact global interval solution for rank-one score space |
299
+
300
+ The strong finite-sample bridge *exchange stable $\Rightarrow I_B^{-1}$-Voronoi* is specific to
301
+ full $D$-optimality and should not be assumed for profiled $D_s$.
302
+
303
+ ## Certificates and diagnostics
304
+
305
+ Certificates are explicit operations; none runs silently during fitting.
306
+ `exchange_stability_report` scans any supplied labeling exactly and reports the best remaining
307
+ gain; `certify_partition` gives a branch-and-bound global certificate for full $D$, or an explicit
308
+ outstanding gap when its budget runs out; `efficient_score_bound` gives a certified ceiling on
309
+ profiled information for one parameter of interest; and `PartitionResult.geometry` measures the
310
+ Voronoi violation a result leaves unclaimed. Validation data is diagnostic only — it never touches
311
+ gradients, stopping, or checkpoint selection.
312
+
313
+ ## Score provenance
314
+
315
+ There is a difference between optimizing supplied vectors exactly and claiming those vectors *are*
316
+ the exact statistical score. ScoreQuant records which it has: exact or autodiff provenance lets a
317
+ result report `information_kind == "exact_fisher"`, while classifier- or ratio-derived scores
318
+ produce `"supplied_score_surrogate"`. The optimization can be exact even when the vectors are
319
+ estimates, and the distinction matters when reading a retained-information number.
320
+
321
+ ## Install
322
+
323
+ ```bash
324
+ uv add scorequant
325
+ ```
326
+
327
+ or, outside a `uv` project, `pip install scorequant`. To work on a checkout instead:
328
+
329
+ ```bash
330
+ git clone https://github.com/VitalyVorobyev/scorequant.git
331
+ cd scorequant
332
+ uv sync --all-extras --all-groups
333
+ ```
334
+
335
+ Python 3.12 or newer; JAX and Optax are the required numerical dependencies. ScoreQuant never sets
336
+ global JAX configuration at import, so 64-bit precision is your application's call
337
+ (`JAX_ENABLE_X64=1`). NumPy is a supported portable runtime, which is what lets a saved rule predict
338
+ where JAX is absent.
339
+
340
+ ## Where ScoreQuant sits
341
+
342
+ ```text
343
+ data → likelihood, component model, ratio estimator or classifier → SCORE
344
+ → [ ScoreQuant: score → hard label ] → counts → template fit / profile likelihood / report
345
+ ```
346
+
347
+ ScoreQuant does not train the classifier and does not perform the final parameter fit. It answers
348
+ one question well:
349
+
350
+ > Given a limited number of hard categories, how should they be chosen so the downstream inference
351
+ > loses as little relevant information as possible?
352
+
353
+ ## How it relates to prior work
354
+
355
+ Choosing a quantizer to preserve Fisher information is established territory, and ScoreQuant does
356
+ not claim to have invented it. Venkitasubramaniam, Tong and Swami introduced score-function
357
+ quantizers for distributed estimation ([CISS 2006](https://doi.org/10.1109/CISS.2006.286494));
358
+ Farias and Brossier developed the scalar high-resolution theory of Fisher-optimal quantization
359
+ ([arXiv:1310.6945](https://arxiv.org/abs/1310.6945)); Barnes, Han and Özgür characterized quantized
360
+ Fisher information geometrically through conditional score means
361
+ ([Allerton 2018](https://doi.org/10.1109/ALLERTON.2018.8635899)); Dülek proved convex-polytope
362
+ optimality for sufficient-statistic quantizers under a trace criterion
363
+ ([IEEE TPAMI 2023](https://doi.org/10.1109/TPAMI.2022.3172282)). Determinant criteria for
364
+ partitions date to Friedman and Rubin (1967) and Scott and Symons (1971), and D-optimality itself
365
+ to Kiefer and Wolfowitz (1960). Inference-aware categorization is an active line of its own —
366
+ INFERNO, ThickBrick, and the recent GATO/BOBR binning optimizers.
367
+
368
+ What ScoreQuant contributes is narrower and concrete: the exact finite-sample geometry of
369
+ *full-matrix* D-optimal hard quantization. Relocating one weighted row is a rank-two update whose
370
+ log-determinant gain is available in closed form, which makes the exchange monotone and its
371
+ termination a stability certificate; exchange stability implies a strict self-consistent
372
+ $I_B^{-1}$-Mahalanobis-Voronoi rule, which is what licenses compiling a finite partition into a
373
+ reusable one; profiled $D_s$ comes with certified efficient-score upper bounds; and small instances
374
+ can be closed with branch-and-bound global certificates. See
375
+ [Related work](https://vitalyvorobyev.github.io/scorequant/related-work/) for the full map,
376
+ including which pipeline stage each comparable package occupies.
377
+
378
+ ## Documentation
379
+
380
+ [Why ScoreQuant](https://vitalyvorobyev.github.io/scorequant/motivation/) ·
381
+ [Method overview](https://vitalyvorobyev.github.io/scorequant/method/) ·
382
+ [Three doors](https://vitalyvorobyev.github.io/scorequant/three-doors/) ·
383
+ [Choosing your workflow](https://vitalyvorobyev.github.io/scorequant/user-workflow/) ·
384
+ [The book](https://vitalyvorobyev.github.io/scorequant/book/) ·
385
+ [Examples](https://vitalyvorobyev.github.io/scorequant/examples/) ·
386
+ [API guide](https://vitalyvorobyev.github.io/scorequant/api/) and
387
+ [reference](https://vitalyvorobyev.github.io/scorequant/reference/) ·
388
+ [FlowCyt study](https://vitalyvorobyev.github.io/scorequant/usecases/flowcyt/) ·
389
+ [Related work](https://vitalyvorobyev.github.io/scorequant/related-work/)
390
+
391
+ The book develops the statistical theory independently of this package's API; the FlowCyt study is
392
+ a reproducible end-to-end evaluation on a frozen patient split.
393
+
394
+ ScoreQuant is available under the [MIT license](LICENSE).