11"""
2- is_rhs_model(x)
2+ Submodel{M,AutoPrefix}
33
4- Return `true` if `x` is a model or model wrapper, and `false` otherwise.
4+ A wrapper around a model, plus a flag indicating whether it should be automatically
5+ prefixed with the left-hand variable in a `~` statement.
56"""
6- is_rhs_model (x) = false
7-
8- """
9- Distributional
10-
11- Abstract type for type indicating that something is "distributional".
12- """
13- abstract type Distributional end
14-
15- """
16- should_auto_prefix(distributional)
17-
18- Return `true` if the `distributional` should use automatic prefixing, and `false` otherwise.
19- """
20- function should_auto_prefix end
21-
22- """
23- is_rhs_model(x)
24-
25- Return `true` if the `distributional` is a model, and `false` otherwise.
26- """
27- function is_rhs_model end
28-
29- """
30- Sampleable{M} <: Distributional
31-
32- A wrapper around a model indicating it is sampleable.
33- """
34- struct Sampleable{M,AutoPrefix} <: Distributional
35- model:: M
36- end
37-
38- should_auto_prefix (:: Sampleable{<:Any,AutoPrefix} ) where {AutoPrefix} = AutoPrefix
39- is_rhs_model (x:: Sampleable ) = is_rhs_model (x. model)
40-
41- # TODO : Export this if it end up having a purpose beyond `to_submodel`.
42- """
43- to_sampleable(model[, auto_prefix])
44-
45- Return a wrapper around `model` indicating it is sampleable.
46-
47- # Arguments
48- - `model::Model`: the model to wrap.
49- - `auto_prefix::Bool`: whether to prefix the variables in the model. Default: `true`.
50- """
51- to_sampleable (model, auto_prefix:: Bool = true ) = Sampleable {typeof(model),auto_prefix} (model)
52-
53- """
54- rand_like!!(model_wrap, context, varinfo)
55-
56- Returns a tuple with the first element being the realization and the second the updated varinfo.
57-
58- # Arguments
59- - `model_wrap::ReturnedModelWrapper`: the wrapper of the model to use.
60- - `context::AbstractContext`: the context to use for evaluation.
61- - `varinfo::AbstractVarInfo`: the varinfo to use for evaluation.
62- """
63- function rand_like!! (
64- model_wrap:: Sampleable , context:: AbstractContext , varinfo:: AbstractVarInfo
65- )
66- return rand_like!! (model_wrap. model, context, varinfo)
67- end
68-
69- """
70- ReturnedModelWrapper
71-
72- A wrapper around a model indicating it is a model over its return values.
73-
74- This should rarely be constructed explicitly; see [`returned(model)`](@ref) instead.
75- """
76- struct ReturnedModelWrapper{M<: Model }
7+ struct Submodel{M,AutoPrefix}
778 model:: M
789end
7910
80- is_rhs_model (:: ReturnedModelWrapper ) = true
81-
82- function rand_like!! (
83- model_wrap:: ReturnedModelWrapper , context:: AbstractContext , varinfo:: AbstractVarInfo
84- )
85- # Return's the value and the (possibly mutated) varinfo.
86- return _evaluate!! (model_wrap. model, varinfo, context)
87- end
88-
89- """
90- returned(model)
91-
92- Return a `model` wrapper indicating that it is a model over its return-values.
93- """
94- returned (model:: Model ) = ReturnedModelWrapper (model)
95-
9611"""
9712 to_submodel(model::Model[, auto_prefix::Bool])
9813
@@ -106,8 +21,8 @@ the model can be sampled from but not necessarily evaluated for its log density.
10621 `left ~ right` such as [`condition`](@ref), will also not work with `to_submodel`.
10722
10823!!! warning
109- To avoid variable names clashing between models, it is recommend leave argument `auto_prefix` equal to `true`.
110- If one does not use automatic prefixing, then it's recommended to use [`prefix(::Model, input)`](@ref) explicitly.
24+ To avoid variable names clashing between models, it is recommended to leave the argument `auto_prefix` equal to `true`.
25+ If one does not use automatic prefixing, then it's recommended to use [`prefix(::Model, input)`](@ref) explicitly, i.e. `to_submodel(prefix(model, @varname(my_prefix)))`
11126
11227# Arguments
11328- `model::Model`: the model to wrap.
@@ -229,11 +144,51 @@ julia> @model illegal_likelihood() = a ~ to_submodel(inner())
229144illegal_likelihood (generic function with 2 methods)
230145
231146julia> model = illegal_likelihood() | (a = 1.0,);
232-
233147julia> model()
234148ERROR: ArgumentError: `~` with a model on the right-hand side of an observe statement is not supported
235149[...]
236150```
237151"""
238- to_submodel (model:: Model , auto_prefix:: Bool = true ) =
239- to_sampleable (returned (model), auto_prefix)
152+ to_submodel (m:: Model , auto_prefix:: Bool = true ) = Submodel {typeof(m),auto_prefix} (m)
153+
154+ # When automatic prefixing is used, the submodel itself doesn't carry the
155+ # prefix, as the prefix is obtained from the LHS of `~` (whereas the submodel
156+ # is on the RHS). The prefix can only be obtained in `tilde_assume!!`, and then
157+ # passed into this function.
158+ #
159+ # `parent_context` here refers to the context of the model that contains the
160+ # submodel.
161+ function _evaluate!! (
162+ submodel:: Submodel{M,AutoPrefix} ,
163+ vi:: AbstractVarInfo ,
164+ parent_context:: AbstractContext ,
165+ left_vn:: VarName ,
166+ ) where {M<: Model ,AutoPrefix}
167+ # First, we construct the context to be used when evaluating the submodel. There
168+ # are several considerations here:
169+ # (1) We need to apply an appropriate PrefixContext when evaluating the submodel, but
170+ # _only_ if automatic prefixing is supposed to be applied.
171+ submodel_context_prefixed = if AutoPrefix
172+ PrefixContext (left_vn, submodel. model. context)
173+ else
174+ submodel. model. context
175+ end
176+
177+ # (2) We need to respect the leaf-context of the parent model. This, unfortunately,
178+ # means disregarding the leaf-context of the submodel.
179+ submodel_context = setleafcontext (
180+ submodel_context_prefixed, leafcontext (parent_context)
181+ )
182+
183+ # (3) We need to use the parent model's context to wrap the whole thing, so that
184+ # e.g. if the user conditions the parent model, the conditioned variables will be
185+ # correctly picked up when evaluating the submodel.
186+ eval_context = setleafcontext (parent_context, submodel_context)
187+
188+ # (4) Finally, we need to store that context inside the submodel.
189+ model = contextualize (submodel. model, eval_context)
190+
191+ # Once that's all set up nicely, we can just _evaluate!! the wrapped model. This
192+ # returns a tuple of submodel.model's return value and the new varinfo.
193+ return _evaluate!! (model, vi)
194+ end
0 commit comments