|
1 | 1 | """ |
2 | 2 | @submodel model |
| 3 | + @submodel ... = model |
3 | 4 |
|
4 | 5 | Run a Turing `model` nested inside of a Turing model. |
5 | 6 |
|
|
44 | 45 | ``` |
45 | 46 | """ |
46 | 47 | macro submodel(expr) |
47 | | - return submodel(expr) |
| 48 | + return submodel(:(prefix = false), expr) |
48 | 49 | end |
49 | 50 |
|
50 | 51 | """ |
51 | | - @submodel prefix model |
| 52 | + @submodel prefix=... model |
| 53 | + @submodel prefix=... ... = model |
52 | 54 |
|
53 | 55 | Run a Turing `model` nested inside of a Turing model and add "`prefix`." as a prefix |
54 | 56 | to all random variables inside of the `model`. |
55 | 57 |
|
| 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 | +
|
56 | 67 | The prefix makes it possible to run the same Turing model multiple times while |
57 | 68 | keeping track of all random variables correctly. |
58 | 69 |
|
59 | 70 | The return value can be assigned to a variable. |
60 | 71 |
|
61 | 72 | # Examples |
62 | | -
|
| 73 | +## Example models |
63 | 74 | ```jldoctest submodelprefix; setup=:(using Distributions) |
64 | 75 | julia> @model function demo1(x) |
65 | 76 | x ~ Normal() |
66 | 77 | return 1 + abs(x) |
67 | 78 | end; |
68 | 79 |
|
69 | 80 | 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) |
72 | 83 | return z ~ Uniform(-a, b) |
73 | 84 | end; |
74 | 85 | ``` |
@@ -109,27 +120,129 @@ julia> loglikelihood = logpdf(Uniform(-1 - abs(sub1_x), 1 + abs(sub2_x)), 0.4); |
109 | 120 | julia> getlogp(vi) ≈ logprior + loglikelihood |
110 | 121 | true |
111 | 122 | ``` |
| 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. |
112 | 183 | """ |
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)) |
116 | 202 | end |
117 | 203 |
|
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. |
119 | 225 | args_assign = getargs_assignment(expr) |
120 | 226 | return if args_assign === nothing |
| 227 | + ctx = prefix_submodel_context(prefix, ctx) |
121 | 228 | # In this case we only want to get the `__varinfo__`. |
122 | 229 | quote |
123 | 230 | $(esc(:__varinfo__)) = last( |
124 | | - _evaluate!!($(esc(expr)), $(esc(:__varinfo__)), $(ctx)) |
| 231 | + $(DynamicPPL._evaluate!!)($(esc(expr)), $(esc(:__varinfo__)), $(ctx)) |
125 | 232 | ) |
126 | 233 | end |
127 | 234 | else |
128 | | - # Here we also want the return-variable. |
129 | | - # TODO: Should we prefix by `L` by default? |
130 | 235 | 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 |
131 | 244 | quote |
132 | | - $(esc(L)), $(esc(:__varinfo__)) = _evaluate!!( |
| 245 | + $(esc(L)), $(esc(:__varinfo__)) = $(DynamicPPL._evaluate!!)( |
133 | 246 | $(esc(R)), $(esc(:__varinfo__)), $(ctx) |
134 | 247 | ) |
135 | 248 | end |
|
0 commit comments