|
1 | | -# CLAUDE.md |
2 | | - |
3 | | -This file provides guidance to Claude Code (claude.ai/code) when working with |
4 | | -code in this repository. |
5 | | - |
6 | | -## Project Overview |
7 | | - |
8 | | -DynamicPPL.jl is the core probabilistic programming language and backend for |
9 | | -the [Turing.jl](https://github.com/TuringLang/Turing.jl) ecosystem. It provides |
10 | | -the `@model` macro for defining probabilistic models with tilde (`~`) |
11 | | -statements, and infrastructure for evaluating, conditioning, fixing, |
12 | | -transforming, and inspecting those models. |
13 | | - |
14 | | -DynamicPPL builds on AbstractPPL.jl for shared PPL interfaces such as |
15 | | -`VarName`, contexts, conditioning/fixing, and evaluator protocols. For |
16 | | -contributor-facing context extracted from DynamicPPL and AbstractPPL project |
17 | | -history, see `docs/src/onboarding.md`. |
18 | | - |
19 | | -## Test Structure |
20 | | - |
21 | | -Tests are split into Group1 and Group2 for CI parallelism, controlled by the |
22 | | -`GROUP` environment variable in `test/runtests.jl`. CI also runs Aqua.jl |
23 | | -quality checks and doctests. |
24 | | - |
25 | | -**Important**: Each test file should be self-contained. Dependencies should |
26 | | -come from package imports, not relative imports or `include()` statements. This |
27 | | -allows individual test files to be run with tools such as |
28 | | -[TestPicker.jl](https://github.com/theogf/TestPicker.jl). |
29 | | - |
30 | | -## Formatting |
31 | | - |
32 | | -Code formatting uses |
33 | | -[JuliaFormatter.jl](https://github.com/domluna/JuliaFormatter.jl) v1, not v2, |
34 | | -with the **Blue style** configured in `.JuliaFormatter.toml`. CI enforces |
35 | | -formatting on all PRs. |
36 | | - |
37 | | -```bash |
38 | | -julia --project -e 'using JuliaFormatter; format(".")' |
39 | | -``` |
40 | | - |
41 | | -## Architecture |
42 | | - |
43 | | -For how things work, see the |
44 | | -[docs](https://turinglang.org/DynamicPPL.jl/stable/): model evaluation, the |
45 | | -tilde pipeline, init strategies, transform strategies, accumulators, |
46 | | -conditioning/fixing, threading, `VarNamedTuple`, and `LogDensityFunction`. |
47 | | - |
48 | | -### Key Types |
49 | | - |
50 | | - - **`Model`** (`src/model.jl`): wraps a model function with its arguments and |
51 | | - context. Created by the `@model` macro in `src/compiler.jl`. |
52 | | - - **`AbstractVarInfo`** (`src/abstract_varinfo.jl`): interface for tracking |
53 | | - random variables and accumulated quantities during model execution. |
54 | | - - **`VarName`** (from AbstractPPL): address for model variables, including |
55 | | - nested fields and indices. |
56 | | - - **`VarNamedTuple`** (`src/varnamedtuple.jl`): a named-tuple-like structure |
57 | | - keyed by `VarName`s. Used as the primary representation for named parameter |
58 | | - values where supported. |
59 | | - - **`LogDensityFunction`** (`src/logdensityfunction.jl`): translation layer |
60 | | - between named model parameters and flat `AbstractVector{<:Real}` inputs for |
61 | | - optimisers, samplers, and AD. Implements the `LogDensityProblems.jl` |
62 | | - interface. |
63 | | - |
64 | | -### Extensions (`ext/`) |
65 | | - |
66 | | -Optional AD backends and integrations, loaded via Julia's package extension |
67 | | -system: |
68 | | - |
69 | | - - `DynamicPPLForwardDiffExt`: ForwardDiff AD |
70 | | - - `DynamicPPLMooncakeExt`: Mooncake AD, with precompilation workload |
71 | | - - `DynamicPPLReverseDiffExt`: ReverseDiff AD |
72 | | - - `DynamicPPLEnzymeCoreExt`: EnzymeCore AD support |
73 | | - - `DynamicPPLMCMCChainsExt`: MCMCChains integration |
74 | | - - `DynamicPPLMarginalLogDensitiesExt`: marginalization support |
75 | | - |
76 | | -### Testing Utilities (`src/test_utils/`) |
77 | | - |
78 | | -`DynamicPPL.TestUtils` provides test models with known analytical solutions |
79 | | -(`logprior_true`, `loglikelihood_true`, etc.) and an AD testing framework |
80 | | -(`run_ad`, `ADResult`) used across the Turing ecosystem. |
81 | | - |
82 | | -## Review Guidelines |
83 | | - |
84 | | -Common pitfalls and non-obvious constraints when writing or reviewing |
85 | | -DynamicPPL code. |
86 | | - |
87 | | -### Prefer `OnlyAccsVarInfo` over `VarInfo` |
88 | | - |
89 | | -For new evaluation code, prefer `OnlyAccsVarInfo` plus `init!!` over adding |
90 | | -more behaviour to `VarInfo`. `VarInfo` is still important, but it combines |
91 | | -vector values, transform state, metadata, and accumulators, while many fast |
92 | | -paths need only a subset of that state. |
93 | | - |
94 | | -A common migration shape is: |
95 | | - |
96 | | -```julia |
97 | | -evaluate!!(model, vi) |
98 | | -``` |
99 | | - |
100 | | -to: |
101 | | - |
102 | | -```julia |
103 | | -init!!(model, oavi, InitFromParams(vi.values), vi.transform_strategy) |
104 | | -``` |
105 | | - |
106 | | -Choose the actual init strategy and accumulator set from the caller's needs. |
107 | | - |
108 | | -### BangBang (`!!`) Return Values |
109 | | - |
110 | | -Functions suffixed with `!!` follow BangBang.jl semantics: they may mutate in |
111 | | -place, but they may also return a replacement object. **Always use the return |
112 | | -value.** Discarding the return value can silently drop updates, especially for |
113 | | -immutable wrappers such as `VarInfo` and `AccumulatorTuple`. |
114 | | - |
115 | | -```julia |
116 | | -# WRONG: mutation may not happen; vi may be unchanged. |
117 | | -accumulate_assume!!(vi, x, tval, logjac, vn, dist, template) |
118 | | - |
119 | | -# RIGHT |
120 | | -vi = accumulate_assume!!(vi, x, tval, logjac, vn, dist, template) |
121 | | -``` |
122 | | - |
123 | | -This applies transitively: if your function calls a `!!` function, it usually |
124 | | -must also return the updated state. |
125 | | - |
126 | | -### Accumulator Pitfalls |
127 | | - |
128 | | -See the accumulator docs for the full protocol. Common mistakes: |
129 | | - |
130 | | - - **`val` vs `tval` in `accumulate_assume!!`**: `val` is the original |
131 | | - model-space value and is what `logpdf` should see. `tval` is the |
132 | | - `TransformedValue`, which may hold linked values. `logjac` is the |
133 | | - log-Jacobian of the forward link transform, or zero if unlinked. |
134 | | - - **`logpdf` vs `loglikelihood` for observations**: |
135 | | - `LogLikelihoodAccumulator` uses `Distributions.loglikelihood`, not `logpdf`. |
136 | | - For array-valued or product-like observations, the two can have different |
137 | | - shapes or aggregation semantics. Use the one the accumulator protocol |
138 | | - expects. |
139 | | - - **Aliased `copy`**: `copy(acc)` must not share mutable internal state unless |
140 | | - that sharing is intentional and documented. Aliased containers can corrupt |
141 | | - results when accumulators are copied for threaded evaluation. |
142 | | - |
143 | | -### TransformedValue |
144 | | - |
145 | | - - **`get_raw_value(tv)` needs a distribution for dynamic transforms.** |
146 | | - `DynamicLink` and `Unlink` derive their transform from the distribution, so |
147 | | - use `get_raw_value(tv, dist)`. The one-argument form is for cases where the |
148 | | - transform is already fully known. |
149 | | - - **`DynamicLink` re-derives the bijection from `dist` during evaluation.** |
150 | | - This is necessary because support can depend on earlier random variables, for |
151 | | - example `y ~ truncated(Normal(); lower=x)`. If support is known to be |
152 | | - constant, consider `FixedTransform` via `WithTransforms`. |
153 | | - - **`FixedTransform` must match the target transform.** Do not assume fixed |
154 | | - transforms compose with re-derived dynamic transforms. |
155 | | - |
156 | | -### LogDensityFunction |
157 | | - |
158 | | - - **`getlogjoint_internal` vs `getlogjoint`**: samplers operating in |
159 | | - unconstrained space usually need `getlogjoint_internal`. `getlogjoint` is the |
160 | | - constrained-space log joint. Using the wrong one changes Jacobian handling. |
161 | | - - **Compiled ReverseDiff tapes are input-dependent.** If model control flow |
162 | | - depends on parameter values, compiled ReverseDiff only gives correct |
163 | | - gradients for inputs that follow the same branch as the compilation input. |
164 | | - Do not use `AutoReverseDiff(; compile=true)` for parameter-dependent |
165 | | - branching. |
166 | | - - Keep evaluator APIs split into structural preparation and AD-specific |
167 | | - preparation. Put backend-specific gradient code in extensions where possible. |
168 | | - - Check aliasing in evaluator and AD APIs. `!!` methods may return buffers that |
169 | | - alias internal caches; copy before exposing or storing results long term. |
170 | | - |
171 | | -### `VarNamedTuple` as Primary Data Structure |
172 | | - |
173 | | -`VarNamedTuple` is the canonical internal representation for named parameter |
174 | | -collections in new DynamicPPL code: conditioning/fixing values, parameter |
175 | | -storage, and accumulator values. `NamedTuple` and `Dict{VarName}` are accepted |
176 | | -as user-facing input, but should usually be converted to `VarNamedTuple` at API |
177 | | -boundaries rather than propagated internally. |
178 | | - |
179 | | -Preserve templates, shapes, and index structure when working with |
180 | | -`VarNamedTuple`. Array-valued variables, slices, and nested fields need enough |
181 | | -template information to round-trip between named values and flat vectors. Avoid |
182 | | -large mostly-empty shadow arrays for sparse indexed variables, and keep eltypes |
183 | | -concrete in hot paths. |
184 | | - |
185 | | -See the VarNamedTuple docs for motivation: it is performant, general, and gives |
186 | | -one representation for named parameter collections. |
187 | | - |
188 | | -### VarName |
189 | | - |
190 | | - - **Use `@varname(x)`, not `:x` or `VarName(:x)`.** The macro constructs the |
191 | | - correct optic for indexed access. `@varname(x[1])` creates a `VarName` with |
192 | | - an index lens; constructing this manually is error-prone. |
193 | | - - **Use subsumption for containment checks.** `subsumes(@varname(x), @varname(x[1]))` is `true`, but the two names are not equal. Conditioning |
194 | | - on `@varname(x)` matches sub-indices; conditioning on `@varname(x[1])` |
195 | | - matches only that index. |
196 | | - - Treat `VarName` display, sorting, prefixing, unprefixing, and serialization |
197 | | - as downstream-facing interface behaviour. Chains, saved results, and |
198 | | - external packages can depend on stable names. |
199 | | - - Test nested fields, indices, ranges, `Colon`, and non-standard indices when |
200 | | - changing `VarName` optics. |
201 | | - |
202 | | -### `@model` Compiler Changes |
203 | | - |
204 | | -`@model` lowering must preserve ordinary Julia semantics, not only |
205 | | -probabilistic statements. For compiler changes, test positional and keyword |
206 | | -arguments, default values, splatting, closures, interpolation, return values, |
207 | | -no-observation models, and data- or parameter-dependent control flow. |
208 | | - |
209 | | -Keep macro hygiene explicit. User variables, generated temporaries, and globals |
210 | | -should not capture each other accidentally. Inspect expanded code when changing |
211 | | -compiler paths. Preserve model return values; returned quantities are |
212 | | -user-visible and distinct from accumulated random variables. |
213 | | - |
214 | | -### Threading |
215 | | - |
216 | | -See the threading docs. Key edge case: |
217 | | -`promote_for_threadsafe_eval(acc, T)` must be implemented if an accumulator |
218 | | -stores typed containers that need to hold AD tracer types, such as ForwardDiff |
219 | | -`Dual`s. The default is a no-op, which is wrong for accumulators with concrete |
220 | | -float fields. |
221 | | - |
222 | | -Avoid designs that index storage by `Threads.threadid()`. Julia scheduling and |
223 | | -thread IDs are not a stable ownership model. |
224 | | - |
225 | | -## Julia Engineering Practices |
226 | | - |
227 | | - - Check type stability with `@inferred`, `@code_warntype`, and focused tests |
228 | | - when changing compiler output, VNTs, accumulators, transforms, or log-density |
229 | | - paths. |
230 | | - - Avoid unnecessary static parameters. Julia specializes on most ordinary |
231 | | - argument types, but is conservative for `Type`, `Function`, and `Vararg`. |
232 | | - Use `f(x, ::Type{T}) where {T}` when the type itself must specialize. |
233 | | - - Benchmark generated functions, macro output, and hot-path refactors before |
234 | | - assuming simpler code is equivalent. |
235 | | - - Prefer dispatch and small protocol functions over large conditional blocks. |
236 | | - - Avoid broad overloads of Base functions for arbitrary input types; they can |
237 | | - create method ambiguities and accidental API. |
238 | | - - Put backend-specific behaviour in package extensions or narrow integration |
239 | | - layers when possible. |
240 | | - - Make direct dependencies explicit enough to version-bound and test. Do not |
241 | | - rely on packages being loaded transitively. |
242 | | - - Use accessor functions for values downstream packages need. Direct field |
243 | | - access from Turing or other packages turns internal representation into |
244 | | - accidental API. |
245 | | - - Prefer `Base.maybeview` over eager slicing when indexed access should avoid |
246 | | - allocations but still support tuples and scalar indexing. |
247 | | - - Avoid fragile output-type prediction. When possible, compute an initial value |
248 | | - and allocate caches from the observed value. |
249 | | - - Keep doctests deterministic. Use `StableRNGs` when examples print random |
250 | | - values. |
251 | | - |
252 | | -## Contributing |
253 | | - |
254 | | - - Non-breaking changes target `main`; breaking changes target the `breaking` |
255 | | - branch. |
256 | | - - CI runs tests on Ubuntu, Windows, and macOS, across stable, minimum, and |
257 | | - selected Julia versions, with both one and two threads. |
258 | | - - Identify whether the change is user-facing, internal, or downstream-facing |
259 | | - through Turing.jl. |
260 | | - - Add the smallest tests that exercise the behaviour. |
261 | | - - Add nested-submodel tests for context, prefix, conditioning, or fixing |
262 | | - changes. |
263 | | - - Add AD backend tests for log-density, transform, vector-parameter, or |
264 | | - `run_ad` changes. |
265 | | - - Add round-trip tests for flattening and unflattening changes, including |
266 | | - scalars, arrays, tuples, `NamedTuple`s, nested values, and mixed element |
267 | | - types. |
268 | | - - Check type stability and allocations for hot paths. |
269 | | - - Check dependency placement and compat bounds when touching Project files, |
270 | | - extensions, docs, or tests. |
271 | | - - Include benchmark numbers for performance-sensitive changes. |
272 | | - - Document and test new user-facing API. |
| 1 | +@AGENTS.md |
| 2 | +@JULIA.md |
0 commit comments