Skip to content

Commit cf8f630

Browse files
committed
julia, agents and claude files
1 parent 7eeae00 commit cf8f630

3 files changed

Lines changed: 258 additions & 272 deletions

File tree

AGENTS.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# CLAUDE.md
2+
3+
Guidance for Claude Code when working in this repository.
4+
5+
See also: @JULIA.md for general Julia engineering and review guidance.
6+
7+
## Project Overview
8+
9+
DynamicPPL.jl is the core probabilistic programming language backend for the Turing.jl ecosystem. It provides the `@model` macro for tilde (`~`) statements and infrastructure for evaluating, conditioning, fixing, transforming, and inspecting probabilistic models.
10+
11+
DynamicPPL builds on AbstractPPL.jl for shared PPL interfaces such as `VarName`, contexts, conditioning/fixing, and evaluator protocols. For project history and contributor context, see `docs/src/onboarding.md`.
12+
13+
## Tests And Formatting
14+
15+
- Tests are split into Group1 and Group2 for CI parallelism, controlled by `GROUP` in `test/runtests.jl`.
16+
- CI also runs Aqua.jl quality checks, doctests, formatting, and multi-platform tests across selected Julia versions and thread counts.
17+
- Each test file should be self-contained. Use package imports, not relative imports or `include()` statements, so files can run individually with tools such as TestPicker.jl.
18+
- Formatting uses JuliaFormatter.jl v1, not v2, with Blue style from `.JuliaFormatter.toml`.
19+
20+
```bash
21+
julia --project -e 'using JuliaFormatter; format(".")'
22+
```
23+
24+
## Architecture Pointers
25+
26+
Use the docs for model evaluation, the tilde pipeline, init strategies, transform strategies, accumulators, conditioning/fixing, threading, `VarNamedTuple`, and `LogDensityFunction`.
27+
28+
- `Model` (`src/model.jl`): wraps a model function, arguments, and context; created by `@model` in `src/compiler.jl`.
29+
- `AbstractVarInfo` (`src/abstract_varinfo.jl`): interface for tracking random variables and accumulated quantities during model execution.
30+
- `VarName` (AbstractPPL): address for model variables, including nested fields and indices.
31+
- `VarNamedTuple` (`src/varnamedtuple.jl`): named-tuple-like parameter storage keyed by `VarName`.
32+
- `LogDensityFunction` (`src/logdensityfunction.jl`): bridge between named model parameters and flat `AbstractVector{<:Real}` inputs for samplers, optimizers, and AD.
33+
- Optional integrations live in `ext/`: ForwardDiff, Mooncake, ReverseDiff, EnzymeCore, MCMCChains, and MarginalLogDensities.
34+
- `DynamicPPL.TestUtils` provides analytical test models and AD helpers (`run_ad`, `ADResult`) used across the Turing ecosystem.
35+
36+
## DynamicPPL Review Notes
37+
38+
- Prefer `OnlyAccsVarInfo` plus `init!!` for new evaluation code when fast paths only need accumulators or a subset of VarInfo state.
39+
- `VarInfo` remains important, but it combines vector values, transform state, metadata, and accumulators. Many fast paths need only part of that state, so avoid adding behavior to `VarInfo` by default.
40+
- Functions ending in `!!` follow BangBang.jl semantics: they may mutate or return a replacement object. Always use the return value, and return updated state from callers that invoke `!!`.
41+
42+
```julia
43+
# Wrong: updates may be lost.
44+
accumulate_assume!!(vi, x, tval, logjac, vn, dist, template)
45+
46+
# Right.
47+
vi = accumulate_assume!!(vi, x, tval, logjac, vn, dist, template)
48+
```
49+
50+
- In `accumulate_assume!!`, `val` is the model-space value and should be passed to `logpdf`; `tval` is the transformed value.
51+
- `logjac` is the log-Jacobian of the forward link transform, or zero if unlinked.
52+
- `LogLikelihoodAccumulator` uses `Distributions.loglikelihood`, not `logpdf`; array-valued or product-like observations can differ in shape or aggregation.
53+
- `copy(acc)` must not share mutable internal state unless that sharing is intentional and documented.
54+
- `get_raw_value(tv, dist)` is required for dynamic transforms because `DynamicLink` and `Unlink` derive their transform from the distribution; the raw value cannot be recovered correctly from `tv` alone when support is distribution-dependent.
55+
- The one-argument `get_raw_value(tv)` is only for cases where the transform is already fully known.
56+
- `DynamicLink` re-derives the bijection from `dist` during evaluation because support can depend on earlier random variables, such as `y ~ truncated(Normal(); lower=x)`. Do not cache or reuse a fixed bijection unless support is known to be constant.
57+
- If support is known to be constant, consider `FixedTransform` via `WithTransforms`; fixed transforms must match the target transform.
58+
- Samplers operating in unconstrained space usually need `getlogjoint_internal`; `getlogjoint` is the constrained-space log joint.
59+
- Compiled ReverseDiff tapes are input-dependent; do not use `AutoReverseDiff(; compile=true)` when model control flow depends on parameter values.
60+
- Keep evaluator APIs split into structural preparation and AD-specific preparation. Put backend-specific gradient code in extensions when possible.
61+
- Check aliasing in evaluator and AD APIs. `!!` methods may return buffers that alias internal caches; copy before exposing results to callers, storing them long term, or reusing them after another model evaluation.
62+
63+
## Names And Parameter Storage
64+
65+
- Use `VarNamedTuple` as the canonical internal representation for named parameter collections in new code.
66+
- Accept `NamedTuple` and `Dict{VarName}` at user-facing boundaries, but convert to `VarNamedTuple` rather than propagating them internally.
67+
- Preserve templates, shapes, and index structure when round-tripping between named values and flat vectors.
68+
- Avoid large mostly-empty shadow arrays for sparse indexed variables.
69+
- Use `@varname(x)`, not `:x` or `VarName(:x)`.
70+
- Use subsumption for containment checks: `subsumes(@varname(x), @varname(x[1]))` is true, but the names are not equal.
71+
- Treat VarName display, sorting, prefixing, unprefixing, and serialization as downstream-facing interface behavior.
72+
- Test nested fields, indices, ranges, `Colon`, and non-standard indices when changing VarName optics.
73+
74+
## `@model` Compiler
75+
76+
`@model` lowering must preserve ordinary Julia semantics, not only probabilistic statements.
77+
78+
For compiler changes, test positional and keyword arguments, default values, splatting, closures, interpolation, return values, no-observation models, and data- or parameter-dependent control flow.
79+
80+
Keep macro hygiene explicit. User variables, generated temporaries, and globals should not capture each other accidentally. Inspect expanded code when changing compiler paths. Preserve model return values; they are user-visible and distinct from accumulated random variables.
81+
82+
## Threading
83+
84+
- Implement `promote_for_threadsafe_eval(acc, T)` if an accumulator stores typed containers that need to hold AD tracer types such as ForwardDiff `Dual`s. The default no-op is wrong for accumulators with concrete float fields.
85+
- Avoid designs that index storage by `Threads.threadid()`. Julia scheduling and thread IDs are not a stable ownership model.
86+
87+
## Contributing Checklist
88+
89+
- Non-breaking changes target `main`; breaking changes target `breaking`.
90+
- Identify whether the change is user-facing, internal, or downstream-facing through Turing.jl.
91+
- Add the smallest tests that exercise the behavior.
92+
- Add nested-submodel tests for context, prefix, conditioning, or fixing changes.
93+
- Add AD backend tests for log-density, transform, vector-parameter, or `run_ad` changes.
94+
- Add round-trip tests for flattening and unflattening changes, including scalars, arrays, tuples, `NamedTuple`s, nested values, and mixed element types.
95+
- Check type stability and allocations for hot paths.
96+
- Check dependency placement and compat bounds when touching Project files, extensions, docs, or tests.
97+
- Include benchmark numbers for performance-sensitive changes.
98+
- Document and test new user-facing API.

CLAUDE.md

Lines changed: 2 additions & 272 deletions
Original file line numberDiff line numberDiff line change
@@ -1,272 +1,2 @@
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

Comments
 (0)