binference 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 (92) hide show
  1. binference-0.1.0/.gitignore +109 -0
  2. binference-0.1.0/CHANGELOG.md +342 -0
  3. binference-0.1.0/CITATION.cff +53 -0
  4. binference-0.1.0/LICENSE +21 -0
  5. binference-0.1.0/PKG-INFO +130 -0
  6. binference-0.1.0/README.md +69 -0
  7. binference-0.1.0/notes/review/README.md +39 -0
  8. binference-0.1.0/notes/review/audit/README.md +38 -0
  9. binference-0.1.0/pyproject.toml +115 -0
  10. binference-0.1.0/src/binference/__init__.py +85 -0
  11. binference-0.1.0/src/binference/_kernels.py +252 -0
  12. binference-0.1.0/src/binference/_quadrature.py +202 -0
  13. binference-0.1.0/src/binference/_simplex.py +54 -0
  14. binference-0.1.0/src/binference/_utils.py +139 -0
  15. binference-0.1.0/src/binference/_version.py +1 -0
  16. binference-0.1.0/src/binference/basis.py +314 -0
  17. binference-0.1.0/src/binference/bins.py +645 -0
  18. binference-0.1.0/src/binference/density.py +323 -0
  19. binference-0.1.0/src/binference/diagnostics.py +400 -0
  20. binference-0.1.0/src/binference/estimator.py +197 -0
  21. binference-0.1.0/src/binference/inference.py +368 -0
  22. binference-0.1.0/src/binference/io.py +309 -0
  23. binference-0.1.0/src/binference/likelihoods.py +1684 -0
  24. binference-0.1.0/src/binference/measure.py +246 -0
  25. binference-0.1.0/src/binference/model.py +833 -0
  26. binference-0.1.0/src/binference/plotting.py +538 -0
  27. binference-0.1.0/src/binference/priors/__init__.py +47 -0
  28. binference-0.1.0/src/binference/priors/base.py +248 -0
  29. binference-0.1.0/src/binference/priors/dirichlet.py +640 -0
  30. binference-0.1.0/src/binference/priors/hyper.py +240 -0
  31. binference-0.1.0/src/binference/priors/logistic.py +676 -0
  32. binference-0.1.0/src/binference/priors/rates.py +139 -0
  33. binference-0.1.0/src/binference/response.py +539 -0
  34. binference-0.1.0/src/binference/results.py +2271 -0
  35. binference-0.1.0/src/binference/samplers/__init__.py +19 -0
  36. binference-0.1.0/src/binference/samplers/_curvature.py +67 -0
  37. binference-0.1.0/src/binference/samplers/_nuts_core.py +284 -0
  38. binference-0.1.0/src/binference/samplers/base.py +118 -0
  39. binference-0.1.0/src/binference/samplers/bootstrap.py +136 -0
  40. binference-0.1.0/src/binference/samplers/gibbs.py +287 -0
  41. binference-0.1.0/src/binference/samplers/laplace.py +200 -0
  42. binference-0.1.0/src/binference/samplers/nuts.py +681 -0
  43. binference-0.1.0/src/binference/samplers/optimize.py +488 -0
  44. binference-0.1.0/src/binference/samplers/vb.py +118 -0
  45. binference-0.1.0/src/binference/selection.py +621 -0
  46. binference-0.1.0/src/binference/simulate.py +720 -0
  47. binference-0.1.0/src/binference/validation.py +477 -0
  48. binference-0.1.0/src/binference/warnings.py +48 -0
  49. binference-0.1.0/tests/__init__.py +0 -0
  50. binference-0.1.0/tests/conftest.py +84 -0
  51. binference-0.1.0/tests/data/matrix_reference.json +566 -0
  52. binference-0.1.0/tests/test_api.py +108 -0
  53. binference-0.1.0/tests/test_api_docs.py +59 -0
  54. binference-0.1.0/tests/test_auto.py +210 -0
  55. binference-0.1.0/tests/test_basis.py +271 -0
  56. binference-0.1.0/tests/test_benchmark_matrix.py +16 -0
  57. binference-0.1.0/tests/test_best_loo.py +140 -0
  58. binference-0.1.0/tests/test_binning_bias.py +78 -0
  59. binference-0.1.0/tests/test_bins.py +116 -0
  60. binference-0.1.0/tests/test_constructors.py +151 -0
  61. binference-0.1.0/tests/test_correlated.py +102 -0
  62. binference-0.1.0/tests/test_density.py +130 -0
  63. binference-0.1.0/tests/test_derived_rates.py +91 -0
  64. binference-0.1.0/tests/test_diagnostics.py +144 -0
  65. binference-0.1.0/tests/test_engines.py +602 -0
  66. binference-0.1.0/tests/test_explainers.py +190 -0
  67. binference-0.1.0/tests/test_gibbs_fixes.py +101 -0
  68. binference-0.1.0/tests/test_hygiene.py +142 -0
  69. binference-0.1.0/tests/test_injections.py +311 -0
  70. binference-0.1.0/tests/test_joint_response.py +228 -0
  71. binference-0.1.0/tests/test_likelihoods.py +347 -0
  72. binference-0.1.0/tests/test_map_fixes.py +170 -0
  73. binference-0.1.0/tests/test_matrix_regression.py +77 -0
  74. binference-0.1.0/tests/test_measures_masks.py +221 -0
  75. binference-0.1.0/tests/test_model.py +228 -0
  76. binference-0.1.0/tests/test_model_fixes.py +173 -0
  77. binference-0.1.0/tests/test_nuts_fixes.py +111 -0
  78. binference-0.1.0/tests/test_plotting_io.py +137 -0
  79. binference-0.1.0/tests/test_prior_fixes.py +221 -0
  80. binference-0.1.0/tests/test_priors.py +475 -0
  81. binference-0.1.0/tests/test_response.py +156 -0
  82. binference-0.1.0/tests/test_results.py +219 -0
  83. binference-0.1.0/tests/test_results_audit.py +396 -0
  84. binference-0.1.0/tests/test_results_outputs.py +112 -0
  85. binference-0.1.0/tests/test_review_regressions.py +316 -0
  86. binference-0.1.0/tests/test_samples.py +173 -0
  87. binference-0.1.0/tests/test_sbc_cases.py +91 -0
  88. binference-0.1.0/tests/test_selection.py +169 -0
  89. binference-0.1.0/tests/test_simplex.py +51 -0
  90. binference-0.1.0/tests/test_simulate_validation.py +317 -0
  91. binference-0.1.0/tests/test_slow.py +319 -0
  92. binference-0.1.0/tests/test_values.py +206 -0
@@ -0,0 +1,109 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # PyInstaller
29
+ # Usually these files are written by a python script from a template
30
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *.cover
47
+ .hypothesis/
48
+ .pytest_cache/
49
+
50
+ # Translations
51
+ *.mo
52
+ *.pot
53
+
54
+ # Django stuff:
55
+ *.log
56
+ local_settings.py
57
+ db.sqlite3
58
+
59
+ # Flask stuff:
60
+ instance/
61
+ .webassets-cache
62
+
63
+ # Scrapy stuff:
64
+ .scrapy
65
+
66
+ # Sphinx documentation
67
+ docs/_build/
68
+
69
+ # PyBuilder
70
+ target/
71
+
72
+ # Jupyter Notebook
73
+ .ipynb_checkpoints
74
+
75
+ # pyenv
76
+ .python-version
77
+
78
+ # celery beat schedule file
79
+ celerybeat-schedule
80
+
81
+ # SageMath parsed files
82
+ *.sage.py
83
+
84
+ # Environments
85
+ .env
86
+ .venv
87
+ env/
88
+ venv/
89
+ ENV/
90
+ env.bak/
91
+ venv.bak/
92
+
93
+ # Spyder project settings
94
+ .spyderproject
95
+ .spyproject
96
+
97
+ # Rope project settings
98
+ .ropeproject
99
+
100
+ # mkdocs documentation
101
+ /site
102
+
103
+ # mypy
104
+ .mypy_cache/
105
+
106
+ # Sphinx autosummary output and notebook execution cache
107
+ docs/reference/api/generated/
108
+ docs/jupyter_execute/
109
+ .jupyter_cache/
@@ -0,0 +1,342 @@
1
+ # Changelog
2
+
3
+ All notable changes to binference are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
5
+ [Semantic Versioning](https://semver.org/) (with the usual 0.x caveat that minor
6
+ releases may break the API).
7
+
8
+ ## [0.1.0] - 2026-10-29
9
+
10
+ First release.
11
+
12
+ ### Changed (landing on `main`)
13
+
14
+ - Renamed from `pastry` to `binference` (package, import and PyPI name; DECISIONS #45).
15
+ The warning base class is `BinferenceWarning` and the environment variables start
16
+ with `BINFERENCE_`.
17
+
18
+ - Python ≥ 3.13, NumPy ≥ 2.1, SciPy ≥ 1.14.1, matplotlib ≥ 3.9.2 (DECISIONS #41, #61).
19
+ - CI runs the pre-commit hooks, the fast suite on three systems with Python 3.13 and
20
+ 3.14, minimum dependencies, the README and user-guide code, the docs build and a
21
+ clean-wheel import; pushes to `main` publish the docs to GitHub Pages.
22
+ - Releases: a tag `vX.Y.Z` on `main` publishes to PyPI by trusted publishing
23
+ (DECISIONS #104, #105).
24
+ - Development process: issue and pull-request templates and the rules in `CLAUDE.md` and
25
+ `CONTRIBUTING.md`.
26
+ - The prior base class is now `binference.priors.PopulationPrior` (was `Prior`).
27
+ - On log bins, the default base measures of `Dirichlet.from_base_measure` and
28
+ `PolyaTree`, the default mean of the logistic priors and the "flat" interim prior
29
+ are flat in log z (they were flat in z; DECISIONS #63).
30
+ - Results (audit O-1–O-21, DECISIONS #82): a result keeps its selection through `save`,
31
+ `load` and `attach`, which refuses other bins or another selection;
32
+ `result_to_dict` is what `save` writes (`meta` plus arrays, `label_draws` apart from
33
+ the bin labels); `check()` also flags tail ESS, E-BFMI, one-chain R-hat and
34
+ unconverged optimisers and raises `DiagnosticsError` when strict; `summary` reports
35
+ the mixing of the quantity it summarises; `which=` takes `"fraction(s)"`/`"rate(s)"`
36
+ everywhere, including `functional`, `rebin`, `cov`, `corr`, `plot_corner` and
37
+ `plot_trace`; `method="em"` runs EM on the simplex, and an explicit
38
+ `MAP(algorithm=)` raises where it cannot run instead of falling back to L-BFGS
39
+ (#100); `sample_assignments`, `sample_values` and `realized_counts` default to 500
40
+ draws; `binference.plotting` and `binference.io` load on first use;
41
+ `result.plot_individual`; `PopulationEstimator` works with
42
+ scikit-learn model selection (`Likelihoods.shape`); the xarray export uses ArviZ's
43
+ statistic names, numeric bin centres and hyperparameter draws.
44
+ - `infer` defaults to `method="auto"` (was `"nuts"`; DECISIONS #14, #88): Gibbs for
45
+ joint conjugate models and for conjugate priors with at most 0.98 missing
46
+ information (run long enough for a bulk ESS of about 400), else Laplace when its
47
+ importance check passes with an ESS of at least 400, else NUTS. The choice and
48
+ reason are in `result.info["auto_method"]` and `["auto_reason"]`. The rules are one
49
+ engine, `binference.inference.Auto`, whose thresholds (`target_ess`,
50
+ `gibbs_max_missing`, `nnz_limit`) are options; `PopulationEstimator` defaults to it
51
+ too. Pass `method="nuts"` for the previous behaviour. Without `n_samples`, a Gibbs run
52
+ chosen by auto is extended until its ESS and R-hat pass, or gives way to the next
53
+ engine beyond `max_gibbs_draws` (DECISIONS #89). Laplace draws that auto keeps
54
+ without passing its check (on very large inputs, or as the last candidate) are flagged
55
+ `approximate`.
56
+ - `Laplace` finds its mode and curvature once per engine (and `step`), not on every
57
+ `run`.
58
+ - `PopulationResult.log_predictive` works through the objects `chunk` at a time (a new
59
+ `chunk` argument, 4096 by default, as `predict_proba` and
60
+ `assignment_probabilities` already did), so its memory no longer grows with the
61
+ number of objects times the number of draws.
62
+
63
+ - Names (DECISIONS #90): `sbc` and `coverage` take `progress=` (was `progress_bar=`);
64
+ `CoverageResult.coverage_rates()` (was `rates()`); `Likelihoods.with_weights(
65
+ fractional_weights=)` (was `fractional=`); `simulate.mock_unfolding(n_objects=)` (was
66
+ `n_events=`). Plots label rates `lambda[k]`, as SBC names them (was `rate[k]`). The
67
+ `"se"` kernel alias and the rename hints for the old selection modes are gone.
68
+ - `Gibbs` and `MeanFieldVB` raise `ValueError` (was `TypeError`) for a model they cannot
69
+ run, with the reason `Engine.unsupported(model)` gives.
70
+ - `PopulationModel.stack()` is removed; `likelihoods.stack()` is the one stacking.
71
+ - Engines declare `Engine.markov` (draws are Markov chains, checked by R-hat and ESS)
72
+ and `Engine.unsupported(model)`; `PopulationResult(kind=)` defaults to the model's
73
+ kind; `diagnostics.importance_ess(weights)` is Kish's effective sample size.
74
+ - The chunked kernels work through 4096 dense rows at a time (was 65,536): Gibbs drew
75
+ labels as fast at either size (N = 2·10⁴ to 2·10⁵, K = 20 to 80) with 16 times less
76
+ memory per block. Sparse rows are sampled all at once, since slicing CSR rows per chunk
77
+ cost time (0.030 s per label draw, from 0.035 s, at N = 2·10⁵, K = 200, 2% filled).
78
+ - Faster: Laplace's importance step evaluates its proposals in blocks (5.3 s, from
79
+ 28.4 s, for 8000 proposals at N = 10⁵, K = 50, one thread); EM computes `L·ρ` once per
80
+ point; leave-one-out smooths all objects' weights together (3.1 s, from 7.4 s, at
81
+ N = 2·10⁴ with 2000 draws); Gibbs draws labels in place (35 ms, from 47 ms, per draw
82
+ at N = 10⁵, K = 50). `method="auto"` keeps the MAP it computed as `result.best` when that raised no
83
+ warning.
84
+ - Plot colours follow the active matplotlib style instead of fixed hex colours; new
85
+ `sample_color` argument on `plot_population` and `plot_individual`; `color` defaults to
86
+ the style and is added to `plot_individual` and `plot_sbc`; 2-D maps and the corner
87
+ plot's 2-D histograms are drawn on ramps to the population colour (relative uncertainty
88
+ to the text colour) instead of viridis, magma and Blues; `POSTERIOR_COLOR`, `STACK_COLOR` and `INK` are removed (DECISIONS #98).
89
+
90
+ ### Fixed (phase-wide review)
91
+
92
+ - PSIS compared weights with the cut as `log(exp(cut))`, so a weight tied with the cut
93
+ could enter the tail with no exceedance; with duplicated draws (resampled Laplace) the
94
+ fit divided by zero. The cut is compared in logs, as ArviZ does; k-hat changes slightly
95
+ where the old comparison included the cut weight.
96
+ - A degenerate generalised-Pareto fit gives k-hat = inf in `diagnostics.psis` (was NaN,
97
+ which compared False with any threshold), so leave-one-out flags such objects.
98
+ - Bootstrap and other resampling of a model with an inferred response kept each row's
99
+ measured bin from before rows were dropped.
100
+ - `attach` clears everything computed from the previous likelihoods (the total, the
101
+ missing information); a model with another prior than the fit's no longer stands in for
102
+ it in `best` and `binning_bias` (compared by a digest of the prior's arguments, saved
103
+ with the result, since its repr shortens arrays); a saved selection on a basis
104
+ re-attaches after load.
105
+ - `save` keeps the unconstrained draws, so `prior_information_ratio` is the same after
106
+ `load`.
107
+ - `PopulationResult.from_samples` refuses a model that infers its selection or response
108
+ unless their draws are given.
109
+ - Diagnostics of a jointly inferred response are named `*_response` (were `*_selection`).
110
+ - Posterior-sample mocks on unequal bins weighted every grid step alike, whatever its
111
+ width (with bins [0, 0.1, 3], half the samples fell in the narrow bin). Their grid is
112
+ now 50 equal steps per bin in the bins' measure (in log z on log bins; was in z).
113
+ - `Selection.draw` floors its Beta draws as Gibbs does, so none underflows to zero.
114
+ - Pickling likelihoods whose within-bin data cannot be pickled warns (they were dropped
115
+ silently).
116
+
117
+ ### Added
118
+
119
+ - `benchmarks/matrix.py`: the benchmark matrix of scenario × prior × engine against
120
+ long reference runs (DECISIONS #42), with its results in `benchmarks/results/`.
121
+
122
+ - **Results** (DECISIONS #12, #41, #49, #53, #83): `result.selected` (the selected
123
+ population, also `which="selected"`), `realized_counts(full=True)` (with the missed
124
+ objects), `data_support` and the `support` summary column, `prior_information_ratio()`
125
+ (Efron's ratio), `check(support_min=)`, and `PointEstimate.check()` for the KKT gap.
126
+ - **Binning-bias check** (DECISIONS #6, #20, #86): `result.binning_bias()` refits the
127
+ data on linear B-spline components over the same bins (Gaussian inputs rebuild
128
+ themselves; others take `build`) and reports each bin's shift in posterior s.d. and,
129
+ for flagged bins, `n_crit`, the number of objects at which the binning bias equals
130
+ the statistical error.
131
+ - **Best fit and leave-one-out** (DECISIONS #35, #51, #52, #85): every result has
132
+ `result.best` (the MAP estimate, computed on first use) and per-object outputs at it
133
+ with a `_best` suffix; `assignment_probabilities_loo` gives leave-one-out posteriors
134
+ by Pareto-smoothed importance sampling, with the Pareto k-hat per object.
135
+ - **Value draws from within-bin information** (DECISIONS #50, #84): `sample_values`
136
+ resamples the samples of `from_samples` inside the drawn bin (`keep_samples=`),
137
+ evaluates `from_density` densities inside 1-D bins (interim divided out for
138
+ posteriors), and draws correlated Gaussians jointly by Gibbs sweeps (`n_sweeps=`);
139
+ tables, binned inputs, compressed rows, densities on grids or without a pdf, samples
140
+ not kept and density functions that could not be pickled fall back to flat within
141
+ the bin, with a warning naming the source.
142
+
143
+ - **Measures** (DECISIONS #21): each grid axis has a `Measure`, the coordinate
144
+ `u = T(z)` in which the population is flat within a bin: linear, log, a transform with
145
+ its inverse, or the cumulative integral of a positive density. `Bins.log` uses the log
146
+ measure; bin centres are midpoints in `u`; `Bins.from_centers` takes midpoints in `u`
147
+ (positive edges for log-spaced centres, I-16). Geometric hand-built edges warn.
148
+ - **Selection from injections** (DECISIONS #10, #30, #79, #80): `Selection.from_injections`
149
+ gives a completeness with Beta posteriors per bin, inferred jointly with the population
150
+ by Gibbs, NUTS, Laplace and `MAP(space="unconstrained")` (logit S as extra
151
+ coordinates); `smooth=τ` ties neighbouring bins by a random walk on logit S;
152
+ `at_mean()` fixes it for VB and the bootstrap.
153
+ - **Responses inferred with the population** (DECISIONS #28, #81): a response tallied
154
+ from events (`Response.from_counts`, `from_simulation`, `from_labelled`) has a
155
+ Dirichlet posterior per column (`prior=β`, default 1 per cell) and is inferred jointly
156
+ by Gibbs, NUTS, Laplace and unconstrained MAP; `response.fixed()` takes it as known.
157
+ - **Totals and rates from fractions** (DECISIONS #9, #78): every fractions result has
158
+ `total` (draws of Λ ~ Gamma(W, S·ρ), exact under a scale-free prior) and `rates`
159
+ (Λρ), with an absolute or no selection; point estimates give W/(S·ρ̂).
160
+ - **Mixture basis and internal resolution** (DECISIONS #4, #5, #47, #76):
161
+ `binference.Basis(bins, order=0|1, resolution=r)` fits components on the bins refined
162
+ `r` times, flat (`order=0`) or linear B-spline hats (`order=1`), and reports in the
163
+ bins. Every constructor takes a `Basis` for `bins` (`Likelihoods.on_basis` for
164
+ per-bin likelihoods); callable selections are averaged with each component as the
165
+ weight. Results report per bin and keep `component_draws`; save and load keep the
166
+ basis. `Bins.refine` is the counterpart of `coarsen`. `sample_values` on non-linear
167
+ measures now draws flat in `u` within a bin (it ignored `T'(z)`).
168
+ - `from_samples` stores likelihoods sparsely below 10% non-zero entries (was 30%; DECISIONS
169
+ #77).
170
+ - **Masked grids** (DECISIONS #19): `mask=` on `Bins`, `Bins.grid`, `Bins.linear` and
171
+ `Bins.log`; masked cells leave the support and the model. Saved and loaded with
172
+ results.
173
+ - `Bins` with edges `-0.0` and `0.0` now hash alike (I-12); `Bins.index` refuses
174
+ multi-column values for 1-D bins (I-22).
175
+ - **`Density`** (DECISIONS #26, #63): one fixed density over the bin coordinate for
176
+ interim priors, the Dirichlet base measure and simulation truth. `Density.flat()` is
177
+ flat in the measure coordinate. An array interim prior on unequal bins needs
178
+ `interim_representation`, so every constructor reads it the same way (I-1); a
179
+ callable on categories receives the labels (I-17).
180
+ - `Likelihoods.stack()` uses the interim prior the likelihoods were built with
181
+ (I-5) and raises on rows with no support under it (I-6).
182
+ - **Constructors by input form** (DECISIONS #32, #64): `Likelihoods.from_table` and
183
+ `Likelihoods.from_density` replace `from_posteriors`, `from_function` and
184
+ `from_distributions`; each needs `kind="likelihood"` or `interim=`. `interim_prior`
185
+ is now `interim` (also in `stack()` and `simulate.mock_1d`).
186
+ - **Adaptive quadrature** (DECISIONS #23): bin averages of functions and distributions
187
+ are refined to `tol` (default 1e-3), in the measure coordinate for likelihoods
188
+ (DECISIONS #21); `from_gaussian` averages in u on non-linear axes. Far tails no longer
189
+ underflow to empty rows (I-2, I-3).
190
+ - **Samples** (DECISIONS #31, #67): one estimator for every interim prior (per-sample
191
+ weights T'(z)/π(z); a per-bin prior is flat in u within bins), an opt-in Dirichlet
192
+ `floor` in pseudo-samples,
193
+ `n_eff` inside the bins with a warning on the share of noisy objects (I-23),
194
+ `Likelihoods.sample_variance` and a Monte Carlo variance check in `result.check()`;
195
+ `Measure.log_jacobian`.
196
+ - **Binned measurements** (DECISIONS #27, #29, #68): `binference.Response` holds
197
+ P(measured bin, observed | true bin) with real truth bins, from a matrix, tallies
198
+ (`Response.from_counts`), simulated events (`Response.from_simulation`) or a labelled
199
+ validation set (`Response.from_labelled`: open-ended score bins or a confusion matrix).
200
+ `Likelihoods.from_binned` replaces `from_response`; `from_classifier` is removed
201
+ (route A is `from_table` on categories, route C is `from_binned` with a labelled
202
+ response). `summary()` names the inputs, and `result.check()` runs a posterior
203
+ predictive test of the measured counts (`Response.predictive_pvalue`) that flags label
204
+ shift. `check()` raises `InputWarning` for problems with the inputs (this test and the
205
+ samples' Monte Carlo variance) and `ConvergenceWarning` for sampler problems. `simulate.mock_categorical` gains `temperature` and a labelled `mock.response`;
206
+ `simulate.mock_unfolding` returns `mock.response`.
207
+ - **Selection** (DECISIONS #7, #8, #10, #46, #47, #48, #69): the modes are
208
+ `"on_truth"` and `"on_measurement"` (the old names raise an error naming the new
209
+ ones); `Selection.combined(on_truth, on_measurement)` selects on both; a selection may
210
+ be a function of the true value, averaged over each bin (`Selection.from_function`,
211
+ or a callable `selection=`); values are detection probabilities in (0, 1] unless
212
+ declared `scale="exposure"` or `"relative"`, and rates refuse a relative selection
213
+ (audit G-4); an array that changes by more than a factor of two between neighbouring
214
+ bins raises an `InputWarning` (a `Selection` object or a callable is taken as averaged
215
+ within bins).
216
+ `simulate.mock_1d(measured_range=...)` simulates selection on the measurement, alone
217
+ or with `selection=` (combined). `sbc`/`coverage` name rates `lambda[k]` (G-10).
218
+ - **Model** (DECISIONS #15, #56, #57, #70): `PopulationModel.log_prob` and
219
+ `log_prob_and_grad` are renamed `log_density_unconstrained` and
220
+ `log_density_unconstrained_and_grad`; the theory page explains the two densities
221
+ and their modes. `kkt_gap` is infinite where an object is impossible and, for rates,
222
+ relative to the exposure (audit M-3, M-4); `missing_information` is correct with a
223
+ selection function and on the support, with `include_prior=` for the MAP EM (M-2).
224
+ Bootstrap replicates drop zero-weight objects in every engine, so EM no longer
225
+ resets to the prior's mode or collapses rates to zero (M-1); EM stops with a warning
226
+ where an object becomes impossible. The rates starting point is W ρ / S (M-7).
227
+ `LogisticGP()` works on two bins (M-14). `PopulationResult.log_prob` is now
228
+ `log_density_unconstrained` (old files still load); `MAP.run` no longer takes
229
+ `weights=`.
230
+ - **MAP** (DECISIONS #71): `tol` (default 1e-8, was 1e-10 on the objective) is the
231
+ tolerance on the KKT gap for every simplex algorithm and on the largest gradient over
232
+ `max(1, W)` for L-BFGS (mix-SQP bounds the gap of the problem it solves), and EM is
233
+ converged only when the gap is below it, or below `1e3 * tol` once the objective
234
+ stops improving (M-6, M-9).
235
+ `GammaRates` with shapes of at least one use mix-SQP, with the total rate profiled
236
+ out (M-6). `PointEstimate.log_posterior` is `model.log_posterior(value)` for every
237
+ algorithm (NaN for priors without a closed-form density); L-BFGS adds
238
+ `info["log_density_unconstrained"]` (M-8). mix-SQP drops its SVD and starts after
239
+ 10 EM steps instead of 50, 2–3 times faster on large dense inputs (M-5); a failed
240
+ quadratic step hands over to EM instead of raising (M-13); Dirichlet priors
241
+ mixing concentrations of one and above one no longer warn (T-4). The rates KKT gap
242
+ is one-sided, so a rate EM drives towards zero no longer reads as a violation.
243
+ - **Priors** (DECISIONS #72): `LogisticRandomWalk(trend_scale=3.0)` is the standard
244
+ deviation of each unpenalised mode's root-mean-square size across the grid, so its
245
+ meaning no longer depends on K (was 10 in basis units, which tightened as 1/√K; P-6).
246
+ Arguments are checked when a prior is made: `DirichletTree` per-node alphas of
247
+ different lengths now work (P-4), `LogisticNormal(cov=)` must be a positive variance
248
+ or a matrix positive semi-definite on zero-sum vectors (P-8), `LogisticGP` `n_basis`
249
+ and `boundary_factor` are checked and `length_scale` takes an array (P-10, P-11),
250
+ `PolyaTree` needs two bins and a non-negative power (P-9), `GeneralizedDirichlet`
251
+ `a`, `b` and `order` are checked (P-22). Reprs show every argument (P-14); tree VB
252
+ draws have no exact zeros (P-12); `GammaRates` has a `prior_transform` (P-15).
253
+ `GeneralizedDirichlet` and `PolyaTree` are no longer subclasses of `DirichletTree`
254
+ (P-13). `LogisticRandomWalk(2).bind` at K = 1000 and 20,000 GP draws are about twice
255
+ as fast (P-20).
256
+ - **GP prior** (DECISIONS #73): `LogisticGP` is stationary in the measure coordinate
257
+ (log z on `Bins.log`; length scales are in that coordinate; P-1). Defaults:
258
+ `boundary_factor=3` (was 1.5; P-3), `n_basis` one per bin up to 1024 modes in all
259
+ (was at most 48 per axis), and a
260
+ length-scale prior whose lower 1% quantile is the shortest length scale that basis
261
+ resolves (P-2), or `LogNormal(log(range/2), 1)` on coarse grids (P-7). Partially
262
+ centred GP densities and gradients agree where scales underflow (P-5).
263
+ - **NUTS and Laplace** (DECISIONS #74): NUTS results (`x`, `log_density_unconstrained`,
264
+ `model`) and the positions `sample()` yields are in the model's own coordinates, not
265
+ the partly centred sampling ones (`info["centring"]` holds the weights; N-1, N-2).
266
+ `diagnostics.psis_threshold(n)` gives the k̂ threshold for n importance weights. Parallel workers receive the model
267
+ once, reuse one pool per run, never raise a BLAS thread limit already set, respect
268
+ CPU affinity, and errors from the model are raised instead of triggering a serial
269
+ rerun (N-3, N-7, N-13). `max_depth`, `n_warmup`, `jitter` and `init` are checked
270
+ (N-5); Laplace returns every draw asked for without importance resampling (N-4) and
271
+ judges k̂ against min(1 − 1/log10 S, 0.7) (N-6). Dense momentum draws use a
272
+ precomputed triangular factor (N-9).
273
+ - **Validation, Gibbs, bootstrap** (DECISIONS #75): SBC p-values and bands use the
274
+ expected count per rank bin (G-1) and `sbc` warns when the ESS is below `n_draws` (G-9);
275
+ `exact_posterior` integrates by stick-breaking Gauss–Legendre quadrature that resolves
276
+ the simplex boundary, also for concentrations below one, adds `log_density`, and bounds
277
+ its memory, with a default `n_grid` of 200 (G-3, G-5); weighted Gibbs on sparse
278
+ likelihoods draws counts over each row's nonzeros only (G-2); Gibbs checks `chunk`,
279
+ `store_labels` and `init`, and chains start apart, from counts that include missed
280
+ objects (G-6, G-7); SBC rejects `n_bins` above `n_draws + 1`; `Bootstrap(kind=)` is now
281
+ `scheme=`, non-converged replicates are counted and the base fit warns (G-8).
282
+ - **Correlated Gaussians** (DECISIONS #2, #25, #66): `from_gaussian(..., cov=)` takes full
283
+ per-object covariances, averaged over N-D bins by adaptive cubature in log space.
284
+ - `weights` are integer multiplicities; non-integer weights need `fractional_weights=True`
285
+ and warn that the result is a pseudo-likelihood (DECISIONS #1, #65). Stored arrays
286
+ are read-only (I-14); `compress()` keeps sparse storage and is fast on it (I-7, I-19);
287
+ warnings point at the caller's line; clearer errors for bad `rtol`, Gaussian shapes,
288
+ zero objects and 2-D arrays passed to `Bins.grid` (I-8, I-10, I-11, I-20); broad
289
+ Gaussians keep full precision near the mean (I-15).
290
+ - Each object's likelihood mass outside the bins is recorded in
291
+ `meta["outside_fraction"]`, with a warning when many objects lose most of it
292
+ (DECISIONS #18).
293
+
294
+ - **Inputs.** `Bins` (linear, log, from centres, N-D grids, categories) and
295
+ `Likelihoods` with constructors for:
296
+ - likelihood matrices (density or mass representation, dense or sparse,
297
+ log-space input);
298
+ - Gaussian measurements;
299
+ - per-object posteriors with their interim prior;
300
+ - posterior samples;
301
+ - SciPy distributions and arbitrary likelihood functions;
302
+ - response matrices (unfolding) and classifier outputs (quantification).
303
+ - **Selection.** `Selection` functions with explicit `"observed"` and `"true"`
304
+ modes, attached to the data or passed to the model.
305
+ - **Model.** `PopulationModel` for bin fractions or Poisson rates, with
306
+ log-density and gradient in unconstrained coordinates, expected counts,
307
+ responsibilities, an optimality (KKT) gap and the missing-information fraction.
308
+ - **Priors.**
309
+ - Simplex: `Dirichlet` (with reference, count-based and base-measure
310
+ constructors), `GeneralizedDirichlet`, `DirichletTree`, `PolyaTree`.
311
+ - Smoothness: `LogisticNormal`, `LogisticRandomWalk` and `LogisticGP`
312
+ (Hilbert-space GP) on N-D grids, with inferred hyperparameters.
313
+ - Rates: `GammaRates`.
314
+ - **Engines.**
315
+ - `NUTS` (default): multinomial NUTS with windowed metric adaptation.
316
+ Smoothness priors get partial non-centring (where the data constrain
317
+ their scales) and a curvature-based dense metric. Chains are resumable
318
+ and can run in parallel processes, which share the cores' BLAS threads
319
+ (via `threadpoolctl`) rather than oversubscribing them.
320
+ - Data-augmentation `Gibbs` for conjugate priors, with selection augmentation.
321
+ - `MAP` with an optimality certificate: mix-SQP for Dirichlet priors
322
+ (including maximum likelihood under selection), SQUAREM-accelerated EM
323
+ for the other conjugate priors, L-BFGS otherwise.
324
+ - `Laplace` with PSIS importance correction.
325
+ - `MeanFieldVB` (flagged as approximate) and `Bootstrap` (nonparametric or
326
+ Bayesian, over objects, with variable totals for rate models).
327
+ - **Interfaces.** `binference.infer` and the scikit-learn-style `PopulationEstimator`.
328
+ - **Results.** `PopulationResult` and `PointEstimate` provide:
329
+ - summaries, intervals (central and HDI), functionals and rebinning;
330
+ - per-object assignment probabilities, values and realised counts;
331
+ - posterior predictive scores, diagnostics and checks with hints;
332
+ - plots, save/load and xarray `DataTree` export.
333
+ - **Diagnostics.** Rank-normalised split R-hat, bulk and tail ESS,
334
+ autocorrelation time, MCSE and PSIS.
335
+ - **Validation.** Exact quadrature posteriors (K <= 3), simulation-based
336
+ calibration and coverage studies. Mock catalogues in 1-D and 2-D, plus
337
+ categorical, unfolding and prior-predictive mocks.
338
+ - **Design documents, tests, docs.**
339
+ - Specification, test plan, implementation plan, and a survey of related
340
+ software, including an audit of the `frankenz` samplers.
341
+ - A fast and a slow (statistical) test suite.
342
+ - Sphinx documentation with tutorials.
@@ -0,0 +1,53 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use binference, please cite it as below, together with the method papers relevant to your application."
3
+ title: "binference: population and hierarchical inference with PDFs"
4
+ abstract: >-
5
+ binference infers population distributions over bins, and the posteriors of
6
+ individual objects, from heteroscedastic per-object likelihoods, posteriors
7
+ or posterior samples, using an exact hierarchical Bayesian model with
8
+ smoothness priors, selection effects and built-in validation.
9
+ type: software
10
+ authors:
11
+ - family-names: Speagle
12
+ given-names: Joshua S.
13
+ version: 0.1.0
14
+ date-released: 2026-10-29
15
+ license: MIT
16
+ repository-code: "https://github.com/joshspeagle/binference"
17
+ keywords:
18
+ - Bayesian inference
19
+ - hierarchical models
20
+ - population inference
21
+ - deconvolution
22
+ - Dirichlet
23
+ - photometric redshifts
24
+ - unfolding
25
+ references:
26
+ - type: article
27
+ title: "Hierarchical Bayesian inference of galaxy redshift distributions from photometric surveys"
28
+ authors:
29
+ - family-names: Leistedt
30
+ given-names: Boris
31
+ - family-names: Mortlock
32
+ given-names: Daniel J.
33
+ - family-names: Peiris
34
+ given-names: Hiranya V.
35
+ journal: "Monthly Notices of the Royal Astronomical Society"
36
+ volume: 460
37
+ start: 4258
38
+ year: 2016
39
+ doi: "10.1093/mnras/stw1304"
40
+ - type: article
41
+ title: "Inferring the eccentricity distribution"
42
+ authors:
43
+ - family-names: Hogg
44
+ given-names: David W.
45
+ - family-names: Myers
46
+ given-names: Adam D.
47
+ - family-names: Bovy
48
+ given-names: Jo
49
+ journal: "The Astrophysical Journal"
50
+ volume: 725
51
+ start: 2166
52
+ year: 2010
53
+ doi: "10.1088/0004-637X/725/2/2166"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Josh Speagle
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,130 @@
1
+ Metadata-Version: 2.5
2
+ Name: binference
3
+ Version: 0.1.0
4
+ Summary: Population and hierarchical inference with PDFs: honest population distributions from noisy per-object PDFs over bins.
5
+ Project-URL: Homepage, https://github.com/joshspeagle/binference
6
+ Project-URL: Source, https://github.com/joshspeagle/binference
7
+ Project-URL: Documentation, https://joshspeagle.github.io/binference/
8
+ Project-URL: Issues, https://github.com/joshspeagle/binference/issues
9
+ Author: Joshua S. Speagle
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: bayesian,deconvolution,dirichlet,hierarchical,mcmc,photometric redshifts,population inference,quantification,unfolding
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
21
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
22
+ Classifier: Topic :: Scientific/Engineering :: Physics
23
+ Requires-Python: >=3.13
24
+ Requires-Dist: numpy>=2.1
25
+ Requires-Dist: scipy>=1.14.1
26
+ Requires-Dist: threadpoolctl>=3.1
27
+ Provides-Extra: all
28
+ Requires-Dist: matplotlib>=3.9.2; extra == 'all'
29
+ Requires-Dist: xarray>=2024.10; extra == 'all'
30
+ Provides-Extra: dev
31
+ Requires-Dist: fonttools>=4.47; extra == 'dev'
32
+ Requires-Dist: ipykernel>=6; extra == 'dev'
33
+ Requires-Dist: matplotlib>=3.9.2; extra == 'dev'
34
+ Requires-Dist: myst-nb>=1.1; extra == 'dev'
35
+ Requires-Dist: numpydoc>=1.6; extra == 'dev'
36
+ Requires-Dist: pre-commit>=3; extra == 'dev'
37
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
38
+ Requires-Dist: pytest>=8; extra == 'dev'
39
+ Requires-Dist: sphinx-copybutton>=0.5; extra == 'dev'
40
+ Requires-Dist: sphinx>=7.2; extra == 'dev'
41
+ Requires-Dist: sphinxcontrib-bibtex>=2.6; extra == 'dev'
42
+ Requires-Dist: xarray>=2024.10; extra == 'dev'
43
+ Provides-Extra: docs
44
+ Requires-Dist: fonttools>=4.47; extra == 'docs'
45
+ Requires-Dist: ipykernel>=6; extra == 'docs'
46
+ Requires-Dist: matplotlib>=3.9.2; extra == 'docs'
47
+ Requires-Dist: myst-nb>=1.1; extra == 'docs'
48
+ Requires-Dist: numpydoc>=1.6; extra == 'docs'
49
+ Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
50
+ Requires-Dist: sphinx>=7.2; extra == 'docs'
51
+ Requires-Dist: sphinxcontrib-bibtex>=2.6; extra == 'docs'
52
+ Provides-Extra: plot
53
+ Requires-Dist: matplotlib>=3.9.2; extra == 'plot'
54
+ Provides-Extra: test
55
+ Requires-Dist: matplotlib>=3.9.2; extra == 'test'
56
+ Requires-Dist: pytest-cov>=5; extra == 'test'
57
+ Requires-Dist: pytest>=8; extra == 'test'
58
+ Provides-Extra: xarray
59
+ Requires-Dist: xarray>=2024.10; extra == 'xarray'
60
+ Description-Content-Type: text/markdown
61
+
62
+ # binference
63
+
64
+ **Population and hierarchical inference with PDFs.**
65
+
66
+ Many objects are each measured with noise: galaxies, merger events, patients,
67
+ classified documents. binference reads each object's likelihood, posterior, samples,
68
+ binned measurement or classifier output. From these it infers the population the objects
69
+ come from, with calibrated uncertainty, and each object's posterior under that
70
+ population.
71
+
72
+ ![Population posteriors for a mock catalogue][figure]
73
+
74
+ [figure]: https://raw.githubusercontent.com/joshspeagle/binference/main/docs/_static/quickstart.png
75
+
76
+ *The population inferred from 2000 simulated objects, under the default Dirichlet prior
77
+ (top) and a Gaussian-process prior (bottom). Each panel shows the posterior median, its
78
+ 68% and 95% intervals, the true density (dashed) and the stacked estimate (dotted).*
79
+
80
+ ## Install
81
+
82
+ ```bash
83
+ pip install "binference[plot]"
84
+ ```
85
+
86
+ The [installation page](https://joshspeagle.github.io/binference/start/install.html)
87
+ lists the extras and the supported Python versions.
88
+
89
+ ## Example
90
+
91
+ ```python
92
+ import binference
93
+
94
+ bins = binference.Bins.linear(0.0, 3.0, 15)
95
+
96
+ # a simulated stand-in for your arrays: measured values and their Gaussian errors
97
+ mock = binference.simulate.mock_1d(2000, bins, rng=42)
98
+ z_obs, z_err = mock.measurements["mean"], mock.measurements["sigma"]
99
+
100
+ lk = binference.Likelihoods.from_gaussian(z_obs, z_err, bins)
101
+ result = binference.infer(lk, rng=1) # method="auto" chooses the engine
102
+ print(result.summary())
103
+ ```
104
+
105
+ ## Documentation
106
+
107
+ - [Quick start][quickstart]
108
+ - [Is binference right for my data?][is-it-right]
109
+ - [Tutorials][tutorials]
110
+ - [Full documentation][docs]
111
+
112
+ [quickstart]: https://joshspeagle.github.io/binference/start/quickstart.html
113
+ [is-it-right]: https://joshspeagle.github.io/binference/start/is-it-right.html
114
+ [tutorials]: https://joshspeagle.github.io/binference/tutorials/index.html
115
+ [docs]: https://joshspeagle.github.io/binference/
116
+
117
+ To contribute, see [`CONTRIBUTING.md`][contributing].
118
+
119
+ [contributing]: https://github.com/joshspeagle/binference/blob/main/CONTRIBUTING.md
120
+
121
+ ## Status and citing
122
+
123
+ binference is alpha software, and its API may change before 1.0. If you use it, please
124
+ cite it as described on the [citing page][citing].
125
+
126
+ [citing]: https://joshspeagle.github.io/binference/reference/citing.html
127
+
128
+ ## License
129
+
130
+ MIT; see [LICENSE](https://github.com/joshspeagle/binference/blob/main/LICENSE).