Skip to content

Commit d65fc74

Browse files
torfjeldegithub-actions[bot]phipsgabler
authored
Improvements to @submodel in #309 (#348)
* added prefix keyword argument to submodel-macro * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * converted example in docs into test * fixed docstring * Apply suggestions from code review Co-authored-by: Philipp Gabler <[email protected]> * removed redundant prefix_submodel_context def and added another example to docstring * fixed doctests * attempt at fixing doctests * another attempt at fixing doctests * had a typo in docstring Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Philipp Gabler <[email protected]>
1 parent 6a63e15 commit d65fc74

File tree

3 files changed

+128
-15
lines changed

3 files changed

+128
-15
lines changed

src/model.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ But one needs to be careful when prefixing variables in the nested models:
215215
216216
```jldoctest condition
217217
julia> @model function demo_outer_prefix()
218-
@submodel inner m = demo_inner()
218+
@submodel prefix="inner" m = demo_inner()
219219
return m
220220
end
221221
demo_outer_prefix (generic function with 2 methods)

src/submodel_macro.jl

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""
22
@submodel model
3+
@submodel ... = model
34
45
Run a Turing `model` nested inside of a Turing model.
56
@@ -44,31 +45,41 @@ true
4445
```
4546
"""
4647
macro submodel(expr)
47-
return submodel(expr)
48+
return submodel(:(prefix = false), expr)
4849
end
4950

5051
"""
51-
@submodel prefix model
52+
@submodel prefix=... model
53+
@submodel prefix=... ... = model
5254
5355
Run a Turing `model` nested inside of a Turing model and add "`prefix`." as a prefix
5456
to all random variables inside of the `model`.
5557
58+
Valid expressions for `prefix=...` are:
59+
- `prefix=false`: no prefix is used.
60+
- `prefix=true`: _attempt_ to automatically determine the prefix from the left-hand side
61+
`... = model` by first converting into a `VarName`, and then calling `Symbol` on this.
62+
- `prefix="my prefix"`: prefix is taken to be the static string "my prefix".
63+
- `prefix=expression`: `expression` is evaluated at runtime, resulting in
64+
the prefix `Symbol(expression)`. Note that this also includes string-interpolation,
65+
e.g. `prefix="x[\$i]"` as it requires runtime information.
66+
5667
The prefix makes it possible to run the same Turing model multiple times while
5768
keeping track of all random variables correctly.
5869
5970
The return value can be assigned to a variable.
6071
6172
# Examples
62-
73+
## Example models
6374
```jldoctest submodelprefix; setup=:(using Distributions)
6475
julia> @model function demo1(x)
6576
x ~ Normal()
6677
return 1 + abs(x)
6778
end;
6879
6980
julia> @model function demo2(x, y, z)
70-
@submodel sub1 a = demo1(x)
71-
@submodel sub2 b = demo1(y)
81+
@submodel prefix="sub1" a = demo1(x)
82+
@submodel prefix="sub2" b = demo1(y)
7283
return z ~ Uniform(-a, b)
7384
end;
7485
```
@@ -109,27 +120,129 @@ julia> loglikelihood = logpdf(Uniform(-1 - abs(sub1_x), 1 + abs(sub2_x)), 0.4);
109120
julia> getlogp(vi) ≈ logprior + loglikelihood
110121
true
111122
```
123+
124+
## Different ways of setting the prefix
125+
```jldoctest submodel-prefix-alternatives; setup=:(using DynamicPPL, Distributions)
126+
julia> @model inner() = x ~ Normal()
127+
inner (generic function with 2 methods)
128+
129+
julia> # When `prefix` is unspecified, no prefix is used.
130+
@model outer() = @submodel a = inner()
131+
outer (generic function with 2 methods)
132+
133+
julia> @varname(x) in keys(VarInfo(outer()))
134+
true
135+
136+
julia> # Explicitely don't use any prefix.
137+
@model outer() = @submodel prefix=false a = inner()
138+
outer (generic function with 2 methods)
139+
140+
julia> @varname(x) in keys(VarInfo(outer()))
141+
true
142+
143+
julia> # Automatically determined from `a`.
144+
@model outer() = @submodel prefix=true a = inner()
145+
outer (generic function with 2 methods)
146+
147+
julia> @varname(var"a.x") in keys(VarInfo(outer()))
148+
true
149+
150+
julia> # Using a static string.
151+
@model outer() = @submodel prefix="my prefix" a = inner()
152+
outer (generic function with 2 methods)
153+
154+
julia> @varname(var"my prefix.x") in keys(VarInfo(outer()))
155+
true
156+
157+
julia> # Using string interpolation.
158+
@model outer() = @submodel prefix="\$(inner().name)" a = inner()
159+
outer (generic function with 2 methods)
160+
161+
julia> @varname(var"inner.x") in keys(VarInfo(outer()))
162+
true
163+
164+
julia> # Or using some arbitrary expression.
165+
@model outer() = @submodel prefix=1 + 2 a = inner()
166+
outer (generic function with 2 methods)
167+
168+
julia> @varname(var"3.x") in keys(VarInfo(outer()))
169+
true
170+
171+
julia> # (×) Automatic prefixing without a left-hand side expression does not work!
172+
@model outer() = @submodel prefix=true inner()
173+
ERROR: LoadError: cannot automatically prefix with no left-hand side
174+
[...]
175+
```
176+
177+
# Notes
178+
- The choice `prefix=expression` means that the prefixing will incur a runtime cost.
179+
This is also the case for `prefix=true`, depending on whether the expression on the
180+
the right-hand side of `... = model` requires runtime-information or not, e.g.
181+
`x = model` will result in the _static_ prefix `x`, while `x[i] = model` will be
182+
resolved at runtime.
112183
"""
113-
macro submodel(prefix, expr)
114-
ctx = :(PrefixContext{$(esc(Meta.quot(prefix)))}($(esc(:__context__))))
115-
return submodel(expr, ctx)
184+
macro submodel(prefix_expr, expr)
185+
return submodel(prefix_expr, expr, esc(:__context__))
186+
end
187+
188+
# Automatic prefixing.
189+
function prefix_submodel_context(prefix::Bool, left::Symbol, ctx)
190+
return prefix ? prefix_submodel_context(left, ctx) : ctx
191+
end
192+
193+
function prefix_submodel_context(prefix::Bool, left::Expr, ctx)
194+
return prefix ? prefix_submodel_context(varname(left), ctx) : ctx
195+
end
196+
197+
# Manual prefixing.
198+
prefix_submodel_context(prefix, left, ctx) = prefix_submodel_context(prefix, ctx)
199+
function prefix_submodel_context(prefix, ctx)
200+
# E.g. `prefix="asd[$i]"` or `prefix=asd` with `asd` to be evaluated.
201+
return :($(DynamicPPL.PrefixContext){$(Symbol)($(esc(prefix)))}($ctx))
116202
end
117203

118-
function submodel(expr, ctx=esc(:__context__))
204+
function prefix_submodel_context(prefix::Union{AbstractString,Symbol}, ctx)
205+
# E.g. `prefix="asd"`.
206+
return :($(DynamicPPL.PrefixContext){$(esc(Meta.quot(Symbol(prefix))))}($ctx))
207+
end
208+
209+
function prefix_submodel_context(prefix::Bool, ctx)
210+
if prefix
211+
error("cannot automatically prefix with no left-hand side")
212+
end
213+
214+
return ctx
215+
end
216+
217+
function submodel(prefix_expr, expr, ctx=esc(:__context__))
218+
prefix_left, prefix = getargs_assignment(prefix_expr)
219+
if prefix_left !== :prefix
220+
error("$(prefix_left) is not a valid kwarg")
221+
end
222+
# `prefix=false` => don't prefix, i.e. do nothing to `ctx`.
223+
# `prefix=true` => automatically determine prefix.
224+
# `prefix=...` => use it.
119225
args_assign = getargs_assignment(expr)
120226
return if args_assign === nothing
227+
ctx = prefix_submodel_context(prefix, ctx)
121228
# In this case we only want to get the `__varinfo__`.
122229
quote
123230
$(esc(:__varinfo__)) = last(
124-
_evaluate!!($(esc(expr)), $(esc(:__varinfo__)), $(ctx))
231+
$(DynamicPPL._evaluate!!)($(esc(expr)), $(esc(:__varinfo__)), $(ctx))
125232
)
126233
end
127234
else
128-
# Here we also want the return-variable.
129-
# TODO: Should we prefix by `L` by default?
130235
L, R = args_assign
236+
# Now that we have `L` and `R`, we can prefix automagically.
237+
try
238+
ctx = prefix_submodel_context(prefix, L, ctx)
239+
catch e
240+
error(
241+
"failed to determine prefix from $(L); please specify prefix using the `@submodel prefix=\"your prefix\" ...` syntax",
242+
)
243+
end
131244
quote
132-
$(esc(L)), $(esc(:__varinfo__)) = _evaluate!!(
245+
$(esc(L)), $(esc(:__varinfo__)) = $(DynamicPPL._evaluate!!)(
133246
$(esc(R)), $(esc(:__varinfo__)), $(ctx)
134247
)
135248
end

test/compiler.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ end
455455
num_steps = length(y[1])
456456
num_obs = length(y)
457457
@inbounds for i in 1:num_obs
458-
@submodel $(Symbol("ar1_$i")) x = AR1(num_steps, α, μ, σ)
458+
@submodel prefix = "ar1_$i" x = AR1(num_steps, α, μ, σ)
459459
y[i] ~ MvNormal(x, 0.1)
460460
end
461461
end

0 commit comments

Comments
 (0)