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.
Files changed (62) hide show
  1. bayeswire-0.3.0/PKG-INFO +153 -0
  2. bayeswire-0.3.0/README.md +146 -0
  3. bayeswire-0.3.0/pyproject.toml +31 -0
  4. bayeswire-0.3.0/src/bayeswire/__init__.py +23 -0
  5. bayeswire-0.3.0/src/bayeswire/_ir_registry.py +188 -0
  6. bayeswire-0.3.0/src/bayeswire/constraints/__init__.py +22 -0
  7. bayeswire-0.3.0/src/bayeswire/constraints/core.py +11 -0
  8. bayeswire-0.3.0/src/bayeswire/constraints/interval.py +33 -0
  9. bayeswire-0.3.0/src/bayeswire/constraints/ordered.py +8 -0
  10. bayeswire-0.3.0/src/bayeswire/constraints/positive.py +8 -0
  11. bayeswire-0.3.0/src/bayeswire/corpus/README.md +38 -0
  12. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/data.json +44 -0
  13. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/diagnostics.json +1 -0
  14. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/dims.json +28 -0
  15. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/manifest.json +9 -0
  16. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/model.ir.json +1 -0
  17. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/eight_schools_non_centered/posterior.ndjson +602 -0
  18. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/data.json +54 -0
  19. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/diagnostics.json +1 -0
  20. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/dims.json +8 -0
  21. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/manifest.json +9 -0
  22. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/model.ir.json +1 -0
  23. bayeswire-0.3.0/src/bayeswire/corpus/artifacts/varying_intercepts_poisson/posterior.ndjson +602 -0
  24. bayeswire-0.3.0/src/bayeswire/corpus/bounded_rates.json +168 -0
  25. bayeswire-0.3.0/src/bayeswire/corpus/data/bounded_rates.json +18 -0
  26. bayeswire-0.3.0/src/bayeswire/corpus/data/eight_schools_non_centered.json +44 -0
  27. bayeswire-0.3.0/src/bayeswire/corpus/data/linear_regression.json +31 -0
  28. bayeswire-0.3.0/src/bayeswire/corpus/data/ordinal_regression.json +38 -0
  29. bayeswire-0.3.0/src/bayeswire/corpus/data/partially_observed_mvn.json +73 -0
  30. bayeswire-0.3.0/src/bayeswire/corpus/data/varying_intercepts_poisson.json +54 -0
  31. bayeswire-0.3.0/src/bayeswire/corpus/eight_schools_non_centered.json +272 -0
  32. bayeswire-0.3.0/src/bayeswire/corpus/fingerprints.json +8 -0
  33. bayeswire-0.3.0/src/bayeswire/corpus/fixtures/bounded_rates.json +221 -0
  34. bayeswire-0.3.0/src/bayeswire/corpus/fixtures/eight_schools_non_centered.json +399 -0
  35. bayeswire-0.3.0/src/bayeswire/corpus/fixtures/linear_regression.json +347 -0
  36. bayeswire-0.3.0/src/bayeswire/corpus/fixtures/ordinal_regression.json +284 -0
  37. bayeswire-0.3.0/src/bayeswire/corpus/fixtures/partially_observed_mvn.json +260 -0
  38. bayeswire-0.3.0/src/bayeswire/corpus/fixtures/varying_intercepts_poisson.json +485 -0
  39. bayeswire-0.3.0/src/bayeswire/corpus/hashes.json +8 -0
  40. bayeswire-0.3.0/src/bayeswire/corpus/linear_regression.json +275 -0
  41. bayeswire-0.3.0/src/bayeswire/corpus/ordinal_regression.json +205 -0
  42. bayeswire-0.3.0/src/bayeswire/corpus/partially_observed_mvn.json +158 -0
  43. bayeswire-0.3.0/src/bayeswire/corpus/varying_intercepts_poisson.json +378 -0
  44. bayeswire-0.3.0/src/bayeswire/distributions/__init__.py +53 -0
  45. bayeswire-0.3.0/src/bayeswire/distributions/_capabilities.py +22 -0
  46. bayeswire-0.3.0/src/bayeswire/distributions/_symbolic_validation.py +108 -0
  47. bayeswire-0.3.0/src/bayeswire/distributions/continuous.py +54 -0
  48. bayeswire-0.3.0/src/bayeswire/distributions/core.py +72 -0
  49. bayeswire-0.3.0/src/bayeswire/distributions/counts.py +46 -0
  50. bayeswire-0.3.0/src/bayeswire/distributions/multivariate.py +15 -0
  51. bayeswire-0.3.0/src/bayeswire/distributions/ordinal.py +15 -0
  52. bayeswire-0.3.0/src/bayeswire/distributions/truncated.py +23 -0
  53. bayeswire-0.3.0/src/bayeswire/ir.py +361 -0
  54. bayeswire-0.3.0/src/bayeswire/math.py +15 -0
  55. bayeswire-0.3.0/src/bayeswire/model/__init__.py +48 -0
  56. bayeswire-0.3.0/src/bayeswire/model/_data_schema.py +68 -0
  57. bayeswire-0.3.0/src/bayeswire/model/_deferred.py +143 -0
  58. bayeswire-0.3.0/src/bayeswire/model/_expression_errors.py +48 -0
  59. bayeswire-0.3.0/src/bayeswire/model/core.py +390 -0
  60. bayeswire-0.3.0/src/bayeswire/model/decorator.py +898 -0
  61. bayeswire-0.3.0/src/bayeswire/model/dimensions.py +148 -0
  62. bayeswire-0.3.0/src/bayeswire/model/expr.py +332 -0
@@ -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,8 @@
1
+ """Ordered-vector constraint metadata."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Ordered:
8
+ """Constraint metadata for strictly increasing vectors along the last axis."""
@@ -0,0 +1,8 @@
1
+ """Positive constraint metadata."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Positive:
8
+ """Constraint metadata for strictly positive values."""
@@ -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.