Skip to content

Commit 011c123

Browse files
yebaiclaude
andcommitted
Add evaluator API section to docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fbcde33 commit 011c123

13 files changed

Lines changed: 102 additions & 48 deletions

File tree

AGENTS.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# AGENTS.md
2+
3+
AbstractPPL.jl is a Julia interface package for probabilistic programming. It is used by `DynamicPPL.jl` (`https://github.com/TuringLang/DynamicPPL.jl`) and `JuliaBUGS.jl` (`https://github.com/TuringLang/JuliaBUGS.jl`). Most of the package is contract surface: a small model API, a prepared-evaluator API, and a nontrivial `VarName`/optic subsystem used across the Turing ecosystem.
4+
5+
## Conventions
6+
7+
- Make the smallest correct change.
8+
- Treat exported behaviour, examples, and tests as the package contract.
9+
- Keep source, tests, and docs aligned for public behaviour changes.
10+
- Prefer narrow, explicit methods over broad signatures.
11+
- Keep weakdep-specific behaviour in `ext/`.
12+
- Prefer targeted fixes over broad refactors.
13+
- Avoid new dependencies unless clearly justified.
14+
15+
## Package-Specific Notes
16+
17+
- Preserve the model invariants documented in `src/abstractprobprog.jl`: `condition`/`decondition` and `fix`/`unfix` are intended to round-trip when supported.
18+
- `rand(model)` and `predict(model, params)` have default RNG/type forwarding behaviour covered by tests; changes here should stay consistent with `AbstractMCMC` expectations.
19+
- The evaluator API in `src/evaluator.jl` is structural. `prepare(..., prototype::NamedTuple)` fixes field structure, `capabilities` defaults conservatively to `DerivativeOrder{0}()`, and AD-aware prepared objects are expected to return gradients with the same named structure as inputs.
20+
- `VarName` and optics are the main complexity in this repo. Preserve equality, hashing, pretty-printing, composition/decomposition, and type-stability behaviour.
21+
- Dynamic indices (`begin`, `end`, expressions containing them) are intentionally deferred until `concretize`; do not silently erase that distinction.
22+
- Unconcretized dynamic indices must not be serialised. If serialization changes, keep `varname_to_string` / `string_to_varname` round-tripping for supported index types.
23+
- `@varname(..., true)` cannot auto-concretize when the top-level symbol is interpolated; preserve that error path unless the design is intentionally changed.
24+
- `subsumes` is conservative by design. Do not broaden it casually for ambiguous indexing forms.
25+
- `getvalue` / `hasvalue` on `AbstractDict{<:VarName}` intentionally prefer exact matches, then walk up to more general parents when possible.
26+
- The Distributions extension exists to reconstruct structured values from elementwise `VarName` entries. Keep that logic in `ext/`, and prefer the simpler two-argument `getvalue` / `hasvalue` methods unless distribution-shaped reconstruction is actually needed.
27+
- There is explicit code to keep optic equality JET-friendly and to work around Julia tuple-equality issues; changes around optic equality need extra care.
28+
29+
## Testing
30+
31+
- Start with a minimal reproducer and run the smallest relevant test scope.
32+
- Main test command: `julia --project=. -e 'using Pkg; Pkg.test()'`
33+
- CI-style variants:
34+
`GROUP=Tests julia --project=. -e 'using Pkg; Pkg.test()'`
35+
`GROUP=Doctests julia --project=. -e 'using Pkg; Pkg.test()'`
36+
- VarName or optic changes usually need coverage for equality/hash, concretization, `hasvalue`/`getvalue`, serialization, and JET-sensitive cases.
37+
- Public API changes should keep Aqua and doctests passing.
38+
- If a change may affect ecosystem compatibility, consider the downstream `DynamicPPL.jl` integration workflow as part of validation.
39+
- Do not weaken tests just to make CI pass without explicit confirmation.
40+
41+
## Docs
42+
43+
- Keep `docs/src/` aligned with public API and examples.
44+
- `docs/src/interface.md` is explicitly marked outdated and aspirational; prefer current source, tests, and docstrings over that page when they conflict.
45+
- Build docs with: `julia --project=docs -e 'using Pkg; Pkg.instantiate(); include("docs/make.jl")'`
46+
- Follow `.JuliaFormatter.toml` when formatting is part of the task.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
55
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
66

77
[sources]
8-
AbstractPPL = {path = "../"}
8+
AbstractPPL = {path = ".."}

docs/src/interface.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ There are two things that make them more special, though:
316316
vi[@varname(x[1])] = 1
317317
vi[@varname(x[2])] = 2
318318
keys(vi) == [x[1], x[2]]
319-
319+
320320
vi[@varname(x)] = [1, 2]
321321
keys(vi) == [x]
322322
```

docs/src/pplapi.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ evaluate!!
1818
```@docs
1919
AbstractModelTrace
2020
```
21+
22+
## Evaluator interface
23+
24+
```@docs
25+
DerivativeOrder
26+
capabilities
27+
prepare
28+
value_and_gradient
29+
dimension
30+
```

src/varname/serialize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,5 +182,5 @@ Convert a string representation of a `VarName` back to a `VarName`. The string
182182
should have been generated by `varname_to_string`.
183183
"""
184184
function string_to_varname(str::AbstractString)
185-
dict_to_varname(JSON.parse(str; dicttype=OrderedDict{String,Any}))
185+
return dict_to_varname(JSON.parse(str; dicttype=OrderedDict{String,Any}))
186186
end

test/ext/differentiation_interface/differentiation_interface.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ const adtype = ADTypes.AutoFiniteDifferences(; fdm)
4040
@test prepared([3.0, 1.0, 2.0]) 14.0
4141

4242
val, grad = AbstractPPL.value_and_gradient(prepared, values)
43-
@test val 14.0 atol=1e-6
44-
@test grad.x 6.0 atol=1e-6
45-
@test grad.y [2.0, 4.0] atol=1e-6
43+
@test val 14.0 atol = 1e-6
44+
@test grad.x 6.0 atol = 1e-6
45+
@test grad.y [2.0, 4.0] atol = 1e-6
4646
test_autograd(prepared, values; atol=1e-4, rtol=1e-4)
4747

4848
# Overlong vector is rejected

test/test_utils.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ function _fd_gradient(f, x::AbstractVector)
1010
h = cbrt(eps(T))
1111
grad = Vector{T}(undef, length(x))
1212
for i in eachindex(x)
13-
xp = copy(x);
13+
xp = copy(x)
1414
xp[i] += h
15-
xm = copy(x);
15+
xm = copy(x)
1616
xm[i] -= h
1717
grad[i] = (f(xp) - f(xm)) / (2h)
1818
end
@@ -34,6 +34,6 @@ function test_autograd(prepared, values::NamedTuple; atol=1e-5, rtol=1e-5)
3434
grad_fd_nt = AbstractPPL.unflatten_from_vec(values, _fd_gradient(f, x))
3535
@test val_ad f(x)
3636
for k in keys(values)
37-
@test getfield(grad_ad, k) getfield(grad_fd_nt, k) atol=atol rtol=rtol
37+
@test getfield(grad_ad, k) getfield(grad_fd_nt, k) atol = atol rtol = rtol
3838
end
3939
end

test/varname/hasvalue.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ using Test
3434
@test canview(@opticof(_[1:2]), x)
3535
@test canview(@opticof(_[:]), x)
3636
@test !canview(@opticof(_[4]), x)
37-
@test canview(@opticof(_[i = 1]), x)
37+
@test canview(@opticof(_[i=1]), x)
3838
# For some weird reason DimData does not error on these two but just warns that
3939
# there's no index j!
40-
@test canview(@opticof(_[j = 2]), x)
41-
@test canview(@opticof(_[i = 1, j = 2]), x)
40+
@test canview(@opticof(_[j=2]), x)
41+
@test canview(@opticof(_[i=1, j=2]), x)
4242
end
4343

4444
@testset "Dict" begin
@@ -246,14 +246,14 @@ end
246246
@test getvalue(x, @varname(a[1, 2])) == x.a[1, 2]
247247
@test hasvalue(x, @varname(a[:]))
248248
@test getvalue(x, @varname(a[:])) == x.a[:]
249-
@test canview(@opticof(_[i = 1]), x.a)
250-
@test hasvalue(x, @varname(a[i = 1]))
251-
@test getvalue(x, @varname(a[i = 1])) == x.a[i = 1]
252-
@test canview(@opticof(_[i = 1, j = 2]), x.a)
253-
@test hasvalue(x, @varname(a[i = 1, j = 2]))
254-
@test getvalue(x, @varname(a[i = 1, j = 2])) == x.a[i = 1, j = 2]
255-
@test hasvalue(x, @varname(a[i = DD.Not(1)]))
256-
@test getvalue(x, @varname(a[i = DD.Not(1)])) == x.a[i = DD.Not(1)]
249+
@test canview(@opticof(_[i=1]), x.a)
250+
@test hasvalue(x, @varname(a[i=1]))
251+
@test getvalue(x, @varname(a[i=1])) == x.a[i=1]
252+
@test canview(@opticof(_[i=1, j=2]), x.a)
253+
@test hasvalue(x, @varname(a[i=1, j=2]))
254+
@test getvalue(x, @varname(a[i=1, j=2])) == x.a[i=1, j=2]
255+
@test hasvalue(x, @varname(a[i=DD.Not(1)]))
256+
@test getvalue(x, @varname(a[i=DD.Not(1)])) == x.a[i=DD.Not(1)]
257257

258258
y = (; b=DD.DimArray(randn(2, 3), (DD.X, DD.Y)))
259259
@test hasvalue(y, @varname(b))

test/varname/optic.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ using AbstractPPL
3131
@opticof(_.a[2]),
3232
@opticof(_.a[1, :]),
3333
@opticof(_[1].a),
34-
@opticof(_[1, x = 1].a),
34+
@opticof(_[1, x=1].a),
3535
)
3636
for (i, optic1) in enumerate(optics)
3737
for (j, optic2) in enumerate(optics)
@@ -103,7 +103,7 @@ using AbstractPPL
103103
@opticof(_.a),
104104
@opticof(_.a.b),
105105
@opticof(_[1].a),
106-
@opticof(_[1, x = 1].a),
106+
@opticof(_[1, x=1].a),
107107
@opticof(_[].a[:]),
108108
)
109109
for optic in optics
@@ -196,8 +196,8 @@ using AbstractPPL
196196

197197
@testset "keyword arguments to getindex" begin
198198
dimarray = DD.DimArray([0.0 1.0; 2.0 3.0], (:x, :y))
199-
@test @opticof(_[x = 1])(dimarray) == dimarray[x = 1]
200-
@test set(dimarray, @opticof(_[y = 2]), [9.0; 8.0]) ==
199+
@test @opticof(_[x=1])(dimarray) == dimarray[x=1]
200+
@test set(dimarray, @opticof(_[y=2]), [9.0; 8.0]) ==
201201
DD.DimArray([0.0 9.0; 2.0 8.0], (:x, :y))
202202
end
203203

@@ -288,10 +288,10 @@ using AbstractPPL
288288
@testset "keyword index" begin
289289
x = DD.DimArray(zeros(2, 2), (:x, :y))
290290
old_objid = objectid(x)
291-
optic = with_mutation(@opticof(_[x = 1, y = 2]))
292-
@test optic(x) === x[x = 1, y = 2]
291+
optic = with_mutation(@opticof(_[x=1, y=2]))
292+
@test optic(x) === x[x=1, y=2]
293293
set(x, optic, 2.0)
294-
@test x[x = 1, y = 2] == 2.0
294+
@test x[x=1, y=2] == 2.0
295295
@test collect(x) == [0.0 2.0; 0.0 0.0]
296296
@test objectid(x) == old_objid
297297
end

0 commit comments

Comments
 (0)