bayeswire 0.3.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.
- bayeswire-0.3.0/PKG-INFO +153 -0
- bayeswire-0.3.0/README.md +146 -0
- bayeswire-0.3.0/pyproject.toml +31 -0
- bayeswire-0.3.0/src/bayeswire/__init__.py +23 -0
- bayeswire-0.3.0/src/bayeswire/_ir_registry.py +188 -0
- bayeswire-0.3.0/src/bayeswire/constraints/__init__.py +22 -0
- bayeswire-0.3.0/src/bayeswire/constraints/core.py +11 -0
- bayeswire-0.3.0/src/bayeswire/constraints/interval.py +33 -0
- bayeswire-0.3.0/src/bayeswire/constraints/ordered.py +8 -0
- bayeswire-0.3.0/src/bayeswire/constraints/positive.py +8 -0
- bayeswire-0.3.0/src/bayeswire/corpus/README.md +38 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/data.json +44 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/diagnostics.json +1 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/dims.json +28 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/manifest.json +9 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/model.ir.json +1 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/posterior.ndjson +602 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/data.json +54 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/diagnostics.json +1 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/dims.json +8 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/manifest.json +9 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/model.ir.json +1 -0
- bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/posterior.ndjson +602 -0
- bayeswire-0.3.0/src/bayeswire/corpus/bounded_rates.json +168 -0
- bayeswire-0.3.0/src/bayeswire/corpus/data/bounded_rates.json +18 -0
- bayeswire-0.3.0/src/bayeswire/corpus/data/eight_schools_non_centered.json +44 -0
- bayeswire-0.3.0/src/bayeswire/corpus/data/linear_regression.json +31 -0
- bayeswire-0.3.0/src/bayeswire/corpus/data/ordinal_regression.json +38 -0
- bayeswire-0.3.0/src/bayeswire/corpus/data/partially_observed_mvn.json +73 -0
- bayeswire-0.3.0/src/bayeswire/corpus/data/varying_intercepts_poisson.json +54 -0
- bayeswire-0.3.0/src/bayeswire/corpus/eight_schools_non_centered.json +272 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fingerprints.json +8 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fixtures/bounded_rates.json +221 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fixtures/eight_schools_non_centered.json +399 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fixtures/linear_regression.json +347 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fixtures/ordinal_regression.json +284 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fixtures/partially_observed_mvn.json +260 -0
- bayeswire-0.3.0/src/bayeswire/corpus/fixtures/varying_intercepts_poisson.json +485 -0
- bayeswire-0.3.0/src/bayeswire/corpus/hashes.json +8 -0
- bayeswire-0.3.0/src/bayeswire/corpus/linear_regression.json +275 -0
- bayeswire-0.3.0/src/bayeswire/corpus/ordinal_regression.json +205 -0
- bayeswire-0.3.0/src/bayeswire/corpus/partially_observed_mvn.json +158 -0
- bayeswire-0.3.0/src/bayeswire/corpus/varying_intercepts_poisson.json +378 -0
- bayeswire-0.3.0/src/bayeswire/distributions/__init__.py +53 -0
- bayeswire-0.3.0/src/bayeswire/distributions/_capabilities.py +22 -0
- bayeswire-0.3.0/src/bayeswire/distributions/_symbolic_validation.py +108 -0
- bayeswire-0.3.0/src/bayeswire/distributions/continuous.py +54 -0
- bayeswire-0.3.0/src/bayeswire/distributions/core.py +72 -0
- bayeswire-0.3.0/src/bayeswire/distributions/counts.py +46 -0
- bayeswire-0.3.0/src/bayeswire/distributions/multivariate.py +15 -0
- bayeswire-0.3.0/src/bayeswire/distributions/ordinal.py +15 -0
- bayeswire-0.3.0/src/bayeswire/distributions/truncated.py +23 -0
- bayeswire-0.3.0/src/bayeswire/ir.py +361 -0
- bayeswire-0.3.0/src/bayeswire/math.py +15 -0
- bayeswire-0.3.0/src/bayeswire/model/__init__.py +48 -0
- bayeswire-0.3.0/src/bayeswire/model/_data_schema.py +68 -0
- bayeswire-0.3.0/src/bayeswire/model/_deferred.py +143 -0
- bayeswire-0.3.0/src/bayeswire/model/_expression_errors.py +48 -0
- bayeswire-0.3.0/src/bayeswire/model/core.py +390 -0
- bayeswire-0.3.0/src/bayeswire/model/decorator.py +898 -0
- bayeswire-0.3.0/src/bayeswire/model/dimensions.py +148 -0
- bayeswire-0.3.0/src/bayeswire/model/expr.py +332 -0
bayeswire-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: bayeswire
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Model declaration language and wire format for the bayes* toolchain. Stdlib only.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# bayeswire
|
|
9
|
+
|
|
10
|
+
**The reproducible, agent-verifiable Bayesian workflow — one spec, a reference
|
|
11
|
+
engine, a fast engine that must agree with it, and a harness that refuses to
|
|
12
|
+
let either you or the agent skip a step.**
|
|
13
|
+
|
|
14
|
+
## Quickstart
|
|
15
|
+
|
|
16
|
+
You write a model; `bayescycle` provisions the sampling engine and the
|
|
17
|
+
plotting tool for you.
|
|
18
|
+
|
|
19
|
+
Install the workflow harness (it pins `bayeswire`, so nothing else to
|
|
20
|
+
install yet):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv tool install git+https://github.com/StefanSko/bayescycle.git
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Write `model.py`:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from bayeswire import Data, Observed, Param, model
|
|
30
|
+
from bayeswire.constraints import Positive
|
|
31
|
+
from bayeswire.distributions import Normal, Truncated
|
|
32
|
+
|
|
33
|
+
@model
|
|
34
|
+
class LinearRegression:
|
|
35
|
+
alpha = Param(Normal(0.0, 1.0))
|
|
36
|
+
beta = Param(Normal(0.0, 1.0))
|
|
37
|
+
sigma = Param(Truncated(Normal(0.0, 1.0), lower=0.0), constraint=Positive())
|
|
38
|
+
|
|
39
|
+
x = Data.vector()
|
|
40
|
+
mu = alpha + beta * x
|
|
41
|
+
y = Observed(Normal(mu, sigma))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
and `data.json`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{"x": [-1.0, -0.5, 0.0, 0.5, 1.0], "y": [-1.6, -0.3, 0.4, 1.1, 2.2]}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Sample, then plot:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bayescycle sample model.py --data data.json -o run/
|
|
54
|
+
bayescycle plot trace run/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`sample` downloads and sha256-verifies the pinned Bayesite engine release
|
|
58
|
+
into a local cache the first time it runs; later runs reuse it. `plot`
|
|
59
|
+
reaches `bayesite-viz` through `uvx` against a pinned commit, so there is
|
|
60
|
+
nothing else to `pip install`. See
|
|
61
|
+
[bayescycle](https://github.com/StefanSko/bayescycle) for the rest of the
|
|
62
|
+
CLI: prior-predictive checks, simulation/recovery, SBC, and diagnostics.
|
|
63
|
+
|
|
64
|
+
## The toolchain
|
|
65
|
+
|
|
66
|
+
| Repository | Role |
|
|
67
|
+
|---|---|
|
|
68
|
+
| **bayeswire** (here) | One spec: eDSL, resolved IR, wire codec, dimension sidecars, normative docs, and fixture corpus |
|
|
69
|
+
| [jaxstanv5](https://github.com/StefanSko/jaxstanv5) | Reference engine: JAX/BlackJAX backend for binding models, compiling log densities, running NUTS, and emitting diagnostics |
|
|
70
|
+
| [bayesite](https://github.com/StefanSko/bayesite) | Fast engine that must agree with the reference: zero-dependency Rust engine that vendors specs and fixtures by file |
|
|
71
|
+
| [bayescycle](https://github.com/StefanSko/bayescycle) | Harness that refuses skipped steps: file-based workflow runner for agents and humans |
|
|
72
|
+
| [bayesite-viz](https://github.com/StefanSko/bayesite-viz) | Downstream visualization: fit-artifact exporter and dashboards |
|
|
73
|
+
|
|
74
|
+
Every workflow step is a command that reads files and writes files; every run
|
|
75
|
+
is seeded and replayable; run directories are append-only; provenance records
|
|
76
|
+
what was actually done. The customer is an agent and the human auditing it;
|
|
77
|
+
the product is trustworthy process, not a sampler.
|
|
78
|
+
|
|
79
|
+
Consumers pin bayeswire by exact version and vendor or conformance-test
|
|
80
|
+
against the corpus; the copies of the spec in downstream repositories are
|
|
81
|
+
generated and hash-checked, never edited.
|
|
82
|
+
|
|
83
|
+
## This repository
|
|
84
|
+
|
|
85
|
+
**The model declaration language and wire format for the bayes\* toolchain.**
|
|
86
|
+
|
|
87
|
+
bayeswire owns the language: a declarative Python eDSL for Bayesian models,
|
|
88
|
+
the resolved-metadata IR it compiles to, the `bayeswire_ir` v1 wire format
|
|
89
|
+
that IR serializes as, the dimension-label sidecar format, the normative spec
|
|
90
|
+
for all of it, and the golden fixture corpus every producer and consumer
|
|
91
|
+
conformance-tests against.
|
|
92
|
+
|
|
93
|
+
It contains no inference, no distribution math, no plotting, and no workflow
|
|
94
|
+
orchestration, and it imports nothing outside the Python standard library.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from bayeswire import Data, Observed, Param, model
|
|
98
|
+
from bayeswire.constraints import Positive
|
|
99
|
+
from bayeswire.distributions import Normal, Truncated
|
|
100
|
+
from bayeswire.ir import canonical_bytes, meta_to_dict
|
|
101
|
+
from bayeswire.model import model_meta
|
|
102
|
+
|
|
103
|
+
@model
|
|
104
|
+
class LinearRegression:
|
|
105
|
+
alpha = Param(Normal(0.0, 1.0))
|
|
106
|
+
beta = Param(Normal(0.0, 1.0))
|
|
107
|
+
sigma = Param(Truncated(Normal(0.0, 1.0), lower=0.0), constraint=Positive())
|
|
108
|
+
|
|
109
|
+
x = Data.vector()
|
|
110
|
+
mu = alpha + beta * x
|
|
111
|
+
y = Observed(Normal(mu, sigma))
|
|
112
|
+
|
|
113
|
+
meta = model_meta(LinearRegression)
|
|
114
|
+
document = meta_to_dict(meta) # {"bayeswire_ir": 1, "model": ...}
|
|
115
|
+
wire_bytes = canonical_bytes(meta) # sha256(wire_bytes) is the model hash
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Decoding is code-free: `meta_from_dict(document)` reconstructs resolved
|
|
119
|
+
metadata without executing any user code, and
|
|
120
|
+
`bindable_from_meta(meta, dimensions=...)` returns a pure metadata class that
|
|
121
|
+
backends bind and sample.
|
|
122
|
+
|
|
123
|
+
## Layout
|
|
124
|
+
|
|
125
|
+
- `src/bayeswire/` — the package: `model`, `distributions`, `constraints`,
|
|
126
|
+
`math`, `ir`
|
|
127
|
+
- `spec/` — the normative wire spec: `ir-format-v1.md`, generated
|
|
128
|
+
`ir-v1-tags.md`, `dimension-sidecar-v1.md`, `data-document-v1.md`,
|
|
129
|
+
`model-data-fingerprint-v1.md`
|
|
130
|
+
- `src/bayeswire/corpus/` — golden IR documents, canonical hashes, canonical
|
|
131
|
+
data documents, fingerprint test vectors, and JAX-oracle evaluation
|
|
132
|
+
fixtures, shipped as package data so Python consumers conformance-test
|
|
133
|
+
against the installed pin (see its README)
|
|
134
|
+
- `scripts/regenerate_corpus.py` — regenerates corpus documents, hashes,
|
|
135
|
+
data documents, fingerprints, and the generated tag spec after a
|
|
136
|
+
deliberate format change
|
|
137
|
+
- `docs/releasing.md` — the release procedure: when to tag, how to cut the
|
|
138
|
+
tag, and the consumer pin-bump order
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
uv sync
|
|
144
|
+
uv run ruff format --check .
|
|
145
|
+
uv run ruff check .
|
|
146
|
+
uv run ty check
|
|
147
|
+
uv run pytest
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The test suite includes a no-JAX walk (every module must import with `jax`
|
|
151
|
+
and `blackjax` blocked) and produce-conformance (reference declarations must
|
|
152
|
+
reproduce the corpus byte-for-byte). See `AGENTS.md` for the working
|
|
153
|
+
discipline and `docs/invariants.md` for the invariants.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# bayeswire
|
|
2
|
+
|
|
3
|
+
**The reproducible, agent-verifiable Bayesian workflow — one spec, a reference
|
|
4
|
+
engine, a fast engine that must agree with it, and a harness that refuses to
|
|
5
|
+
let either you or the agent skip a step.**
|
|
6
|
+
|
|
7
|
+
## Quickstart
|
|
8
|
+
|
|
9
|
+
You write a model; `bayescycle` provisions the sampling engine and the
|
|
10
|
+
plotting tool for you.
|
|
11
|
+
|
|
12
|
+
Install the workflow harness (it pins `bayeswire`, so nothing else to
|
|
13
|
+
install yet):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv tool install git+https://github.com/StefanSko/bayescycle.git
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Write `model.py`:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from bayeswire import Data, Observed, Param, model
|
|
23
|
+
from bayeswire.constraints import Positive
|
|
24
|
+
from bayeswire.distributions import Normal, Truncated
|
|
25
|
+
|
|
26
|
+
@model
|
|
27
|
+
class LinearRegression:
|
|
28
|
+
alpha = Param(Normal(0.0, 1.0))
|
|
29
|
+
beta = Param(Normal(0.0, 1.0))
|
|
30
|
+
sigma = Param(Truncated(Normal(0.0, 1.0), lower=0.0), constraint=Positive())
|
|
31
|
+
|
|
32
|
+
x = Data.vector()
|
|
33
|
+
mu = alpha + beta * x
|
|
34
|
+
y = Observed(Normal(mu, sigma))
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
and `data.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{"x": [-1.0, -0.5, 0.0, 0.5, 1.0], "y": [-1.6, -0.3, 0.4, 1.1, 2.2]}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Sample, then plot:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
bayescycle sample model.py --data data.json -o run/
|
|
47
|
+
bayescycle plot trace run/
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`sample` downloads and sha256-verifies the pinned Bayesite engine release
|
|
51
|
+
into a local cache the first time it runs; later runs reuse it. `plot`
|
|
52
|
+
reaches `bayesite-viz` through `uvx` against a pinned commit, so there is
|
|
53
|
+
nothing else to `pip install`. See
|
|
54
|
+
[bayescycle](https://github.com/StefanSko/bayescycle) for the rest of the
|
|
55
|
+
CLI: prior-predictive checks, simulation/recovery, SBC, and diagnostics.
|
|
56
|
+
|
|
57
|
+
## The toolchain
|
|
58
|
+
|
|
59
|
+
| Repository | Role |
|
|
60
|
+
|---|---|
|
|
61
|
+
| **bayeswire** (here) | One spec: eDSL, resolved IR, wire codec, dimension sidecars, normative docs, and fixture corpus |
|
|
62
|
+
| [jaxstanv5](https://github.com/StefanSko/jaxstanv5) | Reference engine: JAX/BlackJAX backend for binding models, compiling log densities, running NUTS, and emitting diagnostics |
|
|
63
|
+
| [bayesite](https://github.com/StefanSko/bayesite) | Fast engine that must agree with the reference: zero-dependency Rust engine that vendors specs and fixtures by file |
|
|
64
|
+
| [bayescycle](https://github.com/StefanSko/bayescycle) | Harness that refuses skipped steps: file-based workflow runner for agents and humans |
|
|
65
|
+
| [bayesite-viz](https://github.com/StefanSko/bayesite-viz) | Downstream visualization: fit-artifact exporter and dashboards |
|
|
66
|
+
|
|
67
|
+
Every workflow step is a command that reads files and writes files; every run
|
|
68
|
+
is seeded and replayable; run directories are append-only; provenance records
|
|
69
|
+
what was actually done. The customer is an agent and the human auditing it;
|
|
70
|
+
the product is trustworthy process, not a sampler.
|
|
71
|
+
|
|
72
|
+
Consumers pin bayeswire by exact version and vendor or conformance-test
|
|
73
|
+
against the corpus; the copies of the spec in downstream repositories are
|
|
74
|
+
generated and hash-checked, never edited.
|
|
75
|
+
|
|
76
|
+
## This repository
|
|
77
|
+
|
|
78
|
+
**The model declaration language and wire format for the bayes\* toolchain.**
|
|
79
|
+
|
|
80
|
+
bayeswire owns the language: a declarative Python eDSL for Bayesian models,
|
|
81
|
+
the resolved-metadata IR it compiles to, the `bayeswire_ir` v1 wire format
|
|
82
|
+
that IR serializes as, the dimension-label sidecar format, the normative spec
|
|
83
|
+
for all of it, and the golden fixture corpus every producer and consumer
|
|
84
|
+
conformance-tests against.
|
|
85
|
+
|
|
86
|
+
It contains no inference, no distribution math, no plotting, and no workflow
|
|
87
|
+
orchestration, and it imports nothing outside the Python standard library.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from bayeswire import Data, Observed, Param, model
|
|
91
|
+
from bayeswire.constraints import Positive
|
|
92
|
+
from bayeswire.distributions import Normal, Truncated
|
|
93
|
+
from bayeswire.ir import canonical_bytes, meta_to_dict
|
|
94
|
+
from bayeswire.model import model_meta
|
|
95
|
+
|
|
96
|
+
@model
|
|
97
|
+
class LinearRegression:
|
|
98
|
+
alpha = Param(Normal(0.0, 1.0))
|
|
99
|
+
beta = Param(Normal(0.0, 1.0))
|
|
100
|
+
sigma = Param(Truncated(Normal(0.0, 1.0), lower=0.0), constraint=Positive())
|
|
101
|
+
|
|
102
|
+
x = Data.vector()
|
|
103
|
+
mu = alpha + beta * x
|
|
104
|
+
y = Observed(Normal(mu, sigma))
|
|
105
|
+
|
|
106
|
+
meta = model_meta(LinearRegression)
|
|
107
|
+
document = meta_to_dict(meta) # {"bayeswire_ir": 1, "model": ...}
|
|
108
|
+
wire_bytes = canonical_bytes(meta) # sha256(wire_bytes) is the model hash
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Decoding is code-free: `meta_from_dict(document)` reconstructs resolved
|
|
112
|
+
metadata without executing any user code, and
|
|
113
|
+
`bindable_from_meta(meta, dimensions=...)` returns a pure metadata class that
|
|
114
|
+
backends bind and sample.
|
|
115
|
+
|
|
116
|
+
## Layout
|
|
117
|
+
|
|
118
|
+
- `src/bayeswire/` — the package: `model`, `distributions`, `constraints`,
|
|
119
|
+
`math`, `ir`
|
|
120
|
+
- `spec/` — the normative wire spec: `ir-format-v1.md`, generated
|
|
121
|
+
`ir-v1-tags.md`, `dimension-sidecar-v1.md`, `data-document-v1.md`,
|
|
122
|
+
`model-data-fingerprint-v1.md`
|
|
123
|
+
- `src/bayeswire/corpus/` — golden IR documents, canonical hashes, canonical
|
|
124
|
+
data documents, fingerprint test vectors, and JAX-oracle evaluation
|
|
125
|
+
fixtures, shipped as package data so Python consumers conformance-test
|
|
126
|
+
against the installed pin (see its README)
|
|
127
|
+
- `scripts/regenerate_corpus.py` — regenerates corpus documents, hashes,
|
|
128
|
+
data documents, fingerprints, and the generated tag spec after a
|
|
129
|
+
deliberate format change
|
|
130
|
+
- `docs/releasing.md` — the release procedure: when to tag, how to cut the
|
|
131
|
+
tag, and the consumer pin-bump order
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
uv sync
|
|
137
|
+
uv run ruff format --check .
|
|
138
|
+
uv run ruff check .
|
|
139
|
+
uv run ty check
|
|
140
|
+
uv run pytest
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The test suite includes a no-JAX walk (every module must import with `jax`
|
|
144
|
+
and `blackjax` blocked) and produce-conformance (reference declarations must
|
|
145
|
+
reproduce the corpus byte-for-byte). See `AGENTS.md` for the working
|
|
146
|
+
discipline and `docs/invariants.md` for the invariants.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bayeswire"
|
|
3
|
+
version = "0.3.0"
|
|
4
|
+
description = "Model declaration language and wire format for the bayes* toolchain. Stdlib only."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[dependency-groups]
|
|
10
|
+
dev = [
|
|
11
|
+
"pytest>=8.0.0",
|
|
12
|
+
"ruff>=0.13.0",
|
|
13
|
+
"ty==0.0.49",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.8.0,<0.9.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
19
|
+
|
|
20
|
+
[tool.ruff]
|
|
21
|
+
line-length = 100
|
|
22
|
+
target-version = "py312"
|
|
23
|
+
src = ["src", "tests"]
|
|
24
|
+
|
|
25
|
+
[tool.ruff.lint]
|
|
26
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
27
|
+
|
|
28
|
+
[tool.ty.environment]
|
|
29
|
+
python = "../../.venv"
|
|
30
|
+
python-version = "3.12"
|
|
31
|
+
root = ["./src", "./tests"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""bayeswire — the model declaration language and wire format for the bayes* toolchain."""
|
|
2
|
+
|
|
3
|
+
from bayeswire.model import (
|
|
4
|
+
Data,
|
|
5
|
+
Dim,
|
|
6
|
+
Observed,
|
|
7
|
+
Param,
|
|
8
|
+
PartiallyObserved,
|
|
9
|
+
dimension_metadata_to_dict,
|
|
10
|
+
model,
|
|
11
|
+
model_dimensions,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Data",
|
|
16
|
+
"Dim",
|
|
17
|
+
"Observed",
|
|
18
|
+
"Param",
|
|
19
|
+
"PartiallyObserved",
|
|
20
|
+
"dimension_metadata_to_dict",
|
|
21
|
+
"model",
|
|
22
|
+
"model_dimensions",
|
|
23
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Private IR node registry: tag vocabulary and field-kind classification.
|
|
2
|
+
|
|
3
|
+
The registry maps the closed inventory of resolved model-metadata dataclasses
|
|
4
|
+
to wire tags and records, once per class, how each constructor field is
|
|
5
|
+
represented in the IR document. ``bayeswire.ir`` is the public surface.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import get_origin, get_type_hints
|
|
13
|
+
|
|
14
|
+
from bayeswire.constraints import Interval, Ordered, Positive, UnitInterval
|
|
15
|
+
from bayeswire.distributions import (
|
|
16
|
+
Bernoulli,
|
|
17
|
+
Beta,
|
|
18
|
+
BetaBinomial,
|
|
19
|
+
Binomial,
|
|
20
|
+
Exponential,
|
|
21
|
+
HalfNormal,
|
|
22
|
+
MultivariateNormal,
|
|
23
|
+
NegativeBinomial,
|
|
24
|
+
Normal,
|
|
25
|
+
OrderedLogistic,
|
|
26
|
+
Poisson,
|
|
27
|
+
StudentT,
|
|
28
|
+
Truncated,
|
|
29
|
+
Uniform,
|
|
30
|
+
)
|
|
31
|
+
from bayeswire.model._data_schema import (
|
|
32
|
+
DataDimRef,
|
|
33
|
+
ResolvedDataRankSchema,
|
|
34
|
+
ResolvedDataShapeSchema,
|
|
35
|
+
)
|
|
36
|
+
from bayeswire.model.decorator import (
|
|
37
|
+
ModelMeta,
|
|
38
|
+
ResolvedData,
|
|
39
|
+
ResolvedFreeValue,
|
|
40
|
+
ResolvedObserved,
|
|
41
|
+
ResolvedParam,
|
|
42
|
+
ResolvedStochasticSite,
|
|
43
|
+
)
|
|
44
|
+
from bayeswire.model.expr import (
|
|
45
|
+
BinOp,
|
|
46
|
+
ConstNode,
|
|
47
|
+
DataRef,
|
|
48
|
+
FullSlice,
|
|
49
|
+
IndexOp,
|
|
50
|
+
IndexTuple,
|
|
51
|
+
ParamRef,
|
|
52
|
+
ScalarIndex,
|
|
53
|
+
UnaryOp,
|
|
54
|
+
VectorScatterOp,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
NODE_KEY = "node"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FieldKind(Enum):
|
|
61
|
+
"""How a registered node field is represented in the IR document."""
|
|
62
|
+
|
|
63
|
+
MAP = "map"
|
|
64
|
+
TUPLE = "tuple"
|
|
65
|
+
VALUE = "value"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True)
|
|
69
|
+
class NodeSpec:
|
|
70
|
+
"""Registered IR node class with its wire tag and field kinds."""
|
|
71
|
+
|
|
72
|
+
cls: type
|
|
73
|
+
tag: str
|
|
74
|
+
field_kinds: tuple[tuple[str, FieldKind], ...]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
NODE_SPECS_BY_TAG: dict[str, NodeSpec] = {}
|
|
78
|
+
NODE_SPECS_BY_CLASS: dict[type, NodeSpec] = {}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def register_node(cls: type, *, tag: str | None = None) -> None:
|
|
82
|
+
"""Register a dataclass as a tagged IR node.
|
|
83
|
+
|
|
84
|
+
The tag defaults to the class name; pass ``tag=...`` to keep the wire
|
|
85
|
+
vocabulary stable across Python class renames.
|
|
86
|
+
"""
|
|
87
|
+
if not is_dataclass(cls):
|
|
88
|
+
raise TypeError(
|
|
89
|
+
f"IR node classes must be dataclasses; {cls.__name__} is not. "
|
|
90
|
+
"Decorate it with @dataclass(frozen=True)."
|
|
91
|
+
)
|
|
92
|
+
node_tag = cls.__name__ if tag is None else tag
|
|
93
|
+
existing = NODE_SPECS_BY_TAG.get(node_tag)
|
|
94
|
+
if existing is not None:
|
|
95
|
+
if existing.cls is cls:
|
|
96
|
+
return
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"IR node tag {node_tag!r} is already registered to {existing.cls.__name__}. "
|
|
99
|
+
"Pass a unique IR tag when registering the class."
|
|
100
|
+
)
|
|
101
|
+
registered = NODE_SPECS_BY_CLASS.get(cls)
|
|
102
|
+
if registered is not None:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"{cls.__name__} is already registered as IR tag {registered.tag!r}; "
|
|
105
|
+
"one class maps to one tag."
|
|
106
|
+
)
|
|
107
|
+
spec = NodeSpec(cls=cls, tag=node_tag, field_kinds=_classify_fields(cls))
|
|
108
|
+
NODE_SPECS_BY_TAG[node_tag] = spec
|
|
109
|
+
NODE_SPECS_BY_CLASS[cls] = spec
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _classify_fields(cls: type) -> tuple[tuple[str, FieldKind], ...]:
|
|
113
|
+
"""Record each constructor field's wire representation once, at registration."""
|
|
114
|
+
if not is_dataclass(cls):
|
|
115
|
+
raise TypeError(f"Cannot classify fields of non-dataclass {cls.__name__}")
|
|
116
|
+
hints = get_type_hints(cls)
|
|
117
|
+
kinds: list[tuple[str, FieldKind]] = []
|
|
118
|
+
for field in fields(cls):
|
|
119
|
+
if not field.init:
|
|
120
|
+
continue
|
|
121
|
+
if field.name == NODE_KEY:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"IR node classes cannot have a field named {NODE_KEY!r}: {cls.__name__}. "
|
|
124
|
+
"Rename the field before registering the class."
|
|
125
|
+
)
|
|
126
|
+
kinds.append((field.name, _field_kind(hints[field.name])))
|
|
127
|
+
return tuple(kinds)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _field_kind(hint: object) -> FieldKind:
|
|
131
|
+
origin = get_origin(hint)
|
|
132
|
+
if origin is dict:
|
|
133
|
+
return FieldKind.MAP
|
|
134
|
+
if origin is tuple:
|
|
135
|
+
return FieldKind.TUPLE
|
|
136
|
+
return FieldKind.VALUE
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
_BUILTIN_NODE_CLASSES: tuple[type, ...] = (
|
|
140
|
+
# Expression nodes
|
|
141
|
+
ParamRef,
|
|
142
|
+
DataRef,
|
|
143
|
+
ConstNode,
|
|
144
|
+
BinOp,
|
|
145
|
+
UnaryOp,
|
|
146
|
+
IndexOp,
|
|
147
|
+
VectorScatterOp,
|
|
148
|
+
# Index specifications
|
|
149
|
+
ScalarIndex,
|
|
150
|
+
FullSlice,
|
|
151
|
+
IndexTuple,
|
|
152
|
+
# Resolved data schemas
|
|
153
|
+
DataDimRef,
|
|
154
|
+
ResolvedDataShapeSchema,
|
|
155
|
+
ResolvedDataRankSchema,
|
|
156
|
+
# Resolved declarations
|
|
157
|
+
ResolvedParam,
|
|
158
|
+
ResolvedData,
|
|
159
|
+
ResolvedObserved,
|
|
160
|
+
ResolvedFreeValue,
|
|
161
|
+
ResolvedStochasticSite,
|
|
162
|
+
ModelMeta,
|
|
163
|
+
# Constraints
|
|
164
|
+
Positive,
|
|
165
|
+
Interval,
|
|
166
|
+
UnitInterval,
|
|
167
|
+
Ordered,
|
|
168
|
+
# Built-in distributions
|
|
169
|
+
Normal,
|
|
170
|
+
HalfNormal,
|
|
171
|
+
StudentT,
|
|
172
|
+
Exponential,
|
|
173
|
+
Uniform,
|
|
174
|
+
Beta,
|
|
175
|
+
Truncated,
|
|
176
|
+
Bernoulli,
|
|
177
|
+
Poisson,
|
|
178
|
+
Binomial,
|
|
179
|
+
BetaBinomial,
|
|
180
|
+
NegativeBinomial,
|
|
181
|
+
MultivariateNormal,
|
|
182
|
+
OrderedLogistic,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
for _builtin in _BUILTIN_NODE_CLASSES:
|
|
186
|
+
register_node(_builtin)
|
|
187
|
+
|
|
188
|
+
CORE_PROFILE_TAGS: tuple[str, ...] = tuple(NODE_SPECS_BY_TAG)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Parameter constraints and transforms (unconstrained <-> constrained)."""
|
|
2
|
+
|
|
3
|
+
from bayeswire.constraints.core import (
|
|
4
|
+
ConstrainedValue,
|
|
5
|
+
Constraint,
|
|
6
|
+
LogAbsDetJacobian,
|
|
7
|
+
UnconstrainedValue,
|
|
8
|
+
)
|
|
9
|
+
from bayeswire.constraints.interval import Interval, UnitInterval
|
|
10
|
+
from bayeswire.constraints.ordered import Ordered
|
|
11
|
+
from bayeswire.constraints.positive import Positive
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Constraint",
|
|
15
|
+
"ConstrainedValue",
|
|
16
|
+
"Interval",
|
|
17
|
+
"LogAbsDetJacobian",
|
|
18
|
+
"Ordered",
|
|
19
|
+
"Positive",
|
|
20
|
+
"UnitInterval",
|
|
21
|
+
"UnconstrainedValue",
|
|
22
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Core constraint metadata protocols and value aliases."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
type ConstrainedValue = object
|
|
6
|
+
type UnconstrainedValue = object
|
|
7
|
+
type LogAbsDetJacobian = object
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Constraint(Protocol):
|
|
11
|
+
"""Parameter constraint metadata consumed by numerical backends."""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Finite interval constraint metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class Interval:
|
|
11
|
+
"""Constraint metadata for values in a finite open interval."""
|
|
12
|
+
|
|
13
|
+
lower: float
|
|
14
|
+
upper: float
|
|
15
|
+
|
|
16
|
+
def __post_init__(self) -> None:
|
|
17
|
+
"""Validate finite ordered interval bounds."""
|
|
18
|
+
if isinstance(self.lower, bool) or isinstance(self.upper, bool):
|
|
19
|
+
raise TypeError("Interval bounds must be finite real numbers, not bool")
|
|
20
|
+
if not math.isfinite(self.lower) or not math.isfinite(self.upper):
|
|
21
|
+
raise ValueError("Interval bounds must be finite")
|
|
22
|
+
if self.lower >= self.upper:
|
|
23
|
+
raise ValueError("Interval lower bound must be less than upper bound")
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def width(self) -> float:
|
|
27
|
+
"""Return finite interval width."""
|
|
28
|
+
return self.upper - self.lower
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class UnitInterval:
|
|
33
|
+
"""Constraint metadata for values in the open unit interval."""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Golden fixture corpus
|
|
2
|
+
|
|
3
|
+
This directory is the conformance corpus for the `bayeswire_ir` v1 wire
|
|
4
|
+
format. Every producer and consumer in the toolchain tests against these
|
|
5
|
+
files; none may substitute private fixtures.
|
|
6
|
+
|
|
7
|
+
- `<model>.json` — golden IR documents for the reference models declared in
|
|
8
|
+
`tests/conformance/reference_models.py`, pretty-printed for diffability.
|
|
9
|
+
- `hashes.json` — `sha256(canonical_bytes(meta))` for each reference model:
|
|
10
|
+
the pinned canonical encoding.
|
|
11
|
+
- `fixtures/<model>.json` — cross-backend evaluation fixtures bundling each
|
|
12
|
+
IR document with concrete bind data and log-density plus gradient values
|
|
13
|
+
at deterministic unconstrained points.
|
|
14
|
+
- `data/<model>.json` — the canonical data document
|
|
15
|
+
(`spec/data-document-v1.md`) for each reference model, wrapping the same
|
|
16
|
+
values as the fixture's `data` block.
|
|
17
|
+
- `fingerprints.json` — pinned `model_data_fingerprint` test vectors
|
|
18
|
+
(`spec/model-data-fingerprint-v1.md`) over the exact committed bytes of
|
|
19
|
+
`<model>.json` and `data/<model>.json`.
|
|
20
|
+
|
|
21
|
+
The evaluation values in `fixtures/` are **JAX-oracle outputs**: computed by
|
|
22
|
+
the jaxstanv5 backend in float64 (`jax_enable_x64`), regenerated with
|
|
23
|
+
jaxstanv5's `scripts/generate_ir_fixtures.py` against this corpus. Consumers
|
|
24
|
+
reproduce them within the tolerance policy stated in
|
|
25
|
+
`spec/ir-format-v1.md` — the tolerances live in the spec, not in any
|
|
26
|
+
consumer's tests.
|
|
27
|
+
|
|
28
|
+
- `artifacts/<model>/` — golden **run artifacts**: real run directories
|
|
29
|
+
(`model.ir.json`, `data.json`, optional `dims.json`, `posterior.ndjson`
|
|
30
|
+
with `per_draw_v2` sample stats, `diagnostics.json`) produced by a real
|
|
31
|
+
bayesite binary from corpus models via
|
|
32
|
+
`scripts/generate_artifact_corpus.py`. Downstream exporters
|
|
33
|
+
(`bayesite_idata`) consume them as test fixtures instead of synthesizing
|
|
34
|
+
their own run directories.
|
|
35
|
+
|
|
36
|
+
Regenerate the documents and hashes with `scripts/regenerate_corpus.py`.
|
|
37
|
+
Any corpus diff is a wire-format change: it requires a spec changelog entry,
|
|
38
|
+
a version decision, and a coordinated consumer-pin bump.
|