diff --git a/docs/src/api.md b/docs/src/api.md index d7531af0f..7448812bf 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -14,12 +14,6 @@ These statements are rewritten by `@model` as calls of [internal functions](@ref @model ``` -One can nest models and call another model inside the model function with [`@submodel`](@ref). - -```@docs -@submodel -``` - ### Type A [`Model`](@ref) can be created by calling the model function, as defined by [`@model`](@ref). @@ -110,6 +104,34 @@ Similarly, we can [`unfix`](@ref) variables, i.e. return them to their original unfix ``` +## Models within models + +One can include models and call another model inside the model function with `left ~ to_submodel(model)`. + +```@docs +to_submodel +``` + +Note that a `[to_submodel](@ref)` is only sampleable; one cannot compute `logpdf` for its realizations. + +In the past, one would instead embed sub-models using [`@submodel`](@ref), which has been deprecated since the introduction of [`to_submodel(model)`](@ref) + +```@docs +@submodel +``` + +In the context of including models within models, it's also useful to prefix the variables in sub-models to avoid variable names clashing: + +```@docs +prefix +``` + +Under the hood, [`to_submodel`](@ref) makes use of the following method to indicate that the model it's wrapping is a model over its return-values rather than something else + +```@docs +returned(::Model) +``` + ## Utilities It is possible to manually increase (or decrease) the accumulated log density from within a model function. @@ -118,10 +140,10 @@ It is possible to manually increase (or decrease) the accumulated log density fr @addlogprob! ``` -Return values of the model function for a collection of samples can be obtained with [`generated_quantities`](@ref). +Return values of the model function for a collection of samples can be obtained with [`returned(model, chain)`](@ref). ```@docs -generated_quantities +returned(::DynamicPPL.Model, ::NamedTuple) ``` For a chain of samples, one can compute the pointwise log-likelihoods of each observed random variable with [`pointwise_loglikelihoods`](@ref). Similarly, the log-densities of the priors using diff --git a/ext/DynamicPPLMCMCChainsExt.jl b/ext/DynamicPPLMCMCChainsExt.jl index 8a2679d09..52200171d 100644 --- a/ext/DynamicPPLMCMCChainsExt.jl +++ b/ext/DynamicPPLMCMCChainsExt.jl @@ -43,7 +43,7 @@ function DynamicPPL.varnames(c::MCMCChains.Chains) end """ - generated_quantities(model::Model, chain::MCMCChains.Chains) + returned(model::Model, chain::MCMCChains.Chains) Execute `model` for each of the samples in `chain` and return an array of the values returned by the `model` for each sample. @@ -63,12 +63,12 @@ m = demo(data) chain = sample(m, alg, n) # To inspect the `interesting_quantity(θ, x)` where `θ` is replaced by samples # from the posterior/`chain`: -generated_quantities(m, chain) # <= results in a `Vector` of returned values +returned(m, chain) # <= results in a `Vector` of returned values # from `interesting_quantity(θ, x)` ``` ## Concrete (and simple) ```julia -julia> using DynamicPPL, Turing +julia> using Turing julia> @model function demo(xs) s ~ InverseGamma(2, 3) @@ -87,7 +87,7 @@ julia> model = demo(randn(10)); julia> chain = sample(model, MH(), 10); -julia> generated_quantities(model, chain) +julia> returned(model, chain) 10×1 Array{Tuple{Float64},2}: (2.1964758025119338,) (2.1964758025119338,) @@ -101,9 +101,7 @@ julia> generated_quantities(model, chain) (-0.16489786710222099,) ``` """ -function DynamicPPL.generated_quantities( - model::DynamicPPL.Model, chain_full::MCMCChains.Chains -) +function DynamicPPL.returned(model::DynamicPPL.Model, chain_full::MCMCChains.Chains) chain = MCMCChains.get_sections(chain_full, :parameters) varinfo = DynamicPPL.VarInfo(model) iters = Iterators.product(1:size(chain, 1), 1:size(chain, 3)) diff --git a/src/DynamicPPL.jl b/src/DynamicPPL.jl index a5d178125..d4e13d456 100644 --- a/src/DynamicPPL.jl +++ b/src/DynamicPPL.jl @@ -86,7 +86,6 @@ export AbstractVarInfo, Model, getmissings, getargnames, - generated_quantities, extract_priors, values_as_in_model, # Samplers @@ -122,6 +121,9 @@ export AbstractVarInfo, decondition, fix, unfix, + prefix, + returned, + to_submodel, # Convenience macros @addlogprob!, @submodel, @@ -130,7 +132,8 @@ export AbstractVarInfo, check_model_and_trace, # Deprecated. @logprob_str, - @prob_str + @prob_str, + generated_quantities # Reexport using Distributions: loglikelihood @@ -196,6 +199,8 @@ include("values_as_in_model.jl") include("debug_utils.jl") using .DebugUtils +include("deprecated.jl") + if !isdefined(Base, :get_extension) using Requires end diff --git a/src/compiler.jl b/src/compiler.jl index 90220cbf5..bb066e431 100644 --- a/src/compiler.jl +++ b/src/compiler.jl @@ -178,6 +178,8 @@ function check_tilde_rhs(@nospecialize(x)) end check_tilde_rhs(x::Distribution) = x check_tilde_rhs(x::AbstractArray{<:Distribution}) = x +check_tilde_rhs(x::ReturnedModelWrapper) = x +check_tilde_rhs(x::Sampleable) = Sampleable(check_tilde_rhs(x.model)) """ unwrap_right_vn(right, vn) diff --git a/src/context_implementations.jl b/src/context_implementations.jl index f3c5171b0..6ccd4aa01 100644 --- a/src/context_implementations.jl +++ b/src/context_implementations.jl @@ -141,8 +141,12 @@ By default, calls `tilde_assume(context, right, vn, vi)` and accumulates the log probability of `vi` with the returned value. """ function tilde_assume!!(context, right, vn, vi) - value, logp, vi = tilde_assume(context, right, vn, vi) - return value, acclogp_assume!!(context, vi, logp) + return if is_rhs_model(right) + rand_like!!(right, context, vi) + else + value, logp, vi = tilde_assume(context, right, vn, vi) + value, acclogp_assume!!(context, vi, logp) + end end # observe @@ -197,6 +201,11 @@ Falls back to `tilde_observe!!(context, right, left, vi)` ignoring the informati and indices; if needed, these can be accessed through this function, though. """ function tilde_observe!!(context, right, left, vname, vi) + is_rhs_model(right) && throw( + ArgumentError( + "`~` with a model on the right-hand side of an observe statement is not supported", + ), + ) return tilde_observe!!(context, right, left, vi) end @@ -210,6 +219,11 @@ By default, calls `tilde_observe(context, right, left, vi)` and accumulates the probability of `vi` with the returned value. """ function tilde_observe!!(context, right, left, vi) + is_rhs_model(right) && throw( + ArgumentError( + "`~` with a model on the right-hand side of an observe statement is not supported", + ), + ) logp, vi = tilde_observe(context, right, left, vi) return left, acclogp_observe!!(context, vi, logp) end @@ -420,8 +434,12 @@ model inputs), accumulate the log probability, and return the sampled value and Falls back to `dot_tilde_assume(context, right, left, vn, vi)`. """ function dot_tilde_assume!!(context, right, left, vn, vi) - value, logp, vi = dot_tilde_assume(context, right, left, vn, vi) - return value, acclogp_assume!!(context, vi, logp), vi + return if is_rhs_model(right) + rand_like!!(right, context, vi) + else + value, logp, vi = dot_tilde_assume(context, right, left, vn, vi) + value, acclogp_assume!!(context, vi, logp) + end end # `dot_assume` @@ -672,6 +690,11 @@ Falls back to `dot_tilde_observe!!(context, right, left, vi)` ignoring the infor name and indices; if needed, these can be accessed through this function, though. """ function dot_tilde_observe!!(context, right, left, vn, vi) + is_rhs_model(right) && throw( + ArgumentError( + "`~` with a model on the right-hand side of an observe statement is not supported", + ), + ) return dot_tilde_observe!!(context, right, left, vi) end @@ -684,6 +707,11 @@ probability, and return the observed value and updated `vi`. Falls back to `dot_tilde_observe(context, right, left, vi)`. """ function dot_tilde_observe!!(context, right, left, vi) + is_rhs_model(right) && throw( + ArgumentError( + "`~` with a model on the right-hand side of an observe statement is not supported", + ), + ) logp, vi = dot_tilde_observe(context, right, left, vi) return left, acclogp_observe!!(context, vi, logp) end diff --git a/src/contexts.jl b/src/contexts.jl index 53b454df6..e38302875 100644 --- a/src/contexts.jl +++ b/src/contexts.jl @@ -294,6 +294,34 @@ function prefix(::PrefixContext{Prefix}, vn::VarName{Sym}) where {Prefix,Sym} end end +""" + prefix(model::Model, x) + +Return `model` but with all random variables prefixed by `x`. + +If `x` is known at compile-time, use `Val{x}()` to avoid runtime overheads for prefixing. + +# Examples + +```jldoctest +julia> using DynamicPPL: prefix + +julia> @model demo() = x ~ Dirac(1) +demo (generic function with 2 methods) + +julia> rand(prefix(demo(), :my_prefix)) +(var"my_prefix.x" = 1,) + +julia> # One can also use `Val` to avoid runtime overheads. + rand(prefix(demo(), Val(:my_prefix))) +(var"my_prefix.x" = 1,) +``` +""" +prefix(model::Model, x) = contextualize(model, PrefixContext{Symbol(x)}(model.context)) +function prefix(model::Model, ::Val{x}) where {x} + return contextualize(model, PrefixContext{Symbol(x)}(model.context)) +end + struct ConditionContext{Values,Ctx<:AbstractContext} <: AbstractContext values::Values context::Ctx diff --git a/src/deprecated.jl b/src/deprecated.jl new file mode 100644 index 000000000..0bcaae9b7 --- /dev/null +++ b/src/deprecated.jl @@ -0,0 +1 @@ +@deprecate generated_quantities(model, params) returned(model, params) diff --git a/src/model.jl b/src/model.jl index 2a1a6db88..eb44f3880 100644 --- a/src/model.jl +++ b/src/model.jl @@ -223,14 +223,14 @@ true ## Nested models `condition` of course also supports the use of nested models through -the use of [`@submodel`](@ref). +the use of [`to_submodel`](@ref). ```jldoctest condition julia> @model demo_inner() = m ~ Normal() demo_inner (generic function with 2 methods) julia> @model function demo_outer() - @submodel m = demo_inner() + m ~ to_submodel(demo_inner()) return m end demo_outer (generic function with 2 methods) @@ -250,7 +250,7 @@ But one needs to be careful when prefixing variables in the nested models: ```jldoctest condition julia> @model function demo_outer_prefix() - @submodel prefix="inner" m = demo_inner() + m ~ to_submodel(prefix(demo_inner(), "inner")) return m end demo_outer_prefix (generic function with 2 methods) @@ -578,14 +578,14 @@ true ## Nested models `fix` of course also supports the use of nested models through -the use of [`@submodel`](@ref). +the use of [`to_submodel`](@ref). ```jldoctest fix julia> @model demo_inner() = m ~ Normal() demo_inner (generic function with 2 methods) julia> @model function demo_outer() - @submodel m = demo_inner() + m ~ to_submodel(demo_inner()) return m end demo_outer (generic function with 2 methods) @@ -605,7 +605,7 @@ But one needs to be careful when prefixing variables in the nested models: ```jldoctest fix julia> @model function demo_outer_prefix() - @submodel prefix="inner" m = demo_inner() + m ~ to_submodel(prefix(demo_inner(), "inner")) return m end demo_outer_prefix (generic function with 2 methods) @@ -1051,7 +1051,9 @@ function Base.rand(rng::Random.AbstractRNG, ::Type{T}, model::Model) where {T} evaluate!!( model, SimpleVarInfo{Float64}(OrderedDict()), - SamplingContext(rng, SampleFromPrior(), model.context), + # NOTE: Use `leafcontext` here so we a) avoid overriding the leaf context of `model`, + # and b) avoid double-stacking the parent contexts. + SamplingContext(rng, SampleFromPrior(), leafcontext(model.context)), ), ) return values_as(x, T) @@ -1204,9 +1206,9 @@ function Distributions.loglikelihood(model::Model, chain::AbstractMCMC.AbstractC end """ - generated_quantities(model::Model, parameters::NamedTuple) - generated_quantities(model::Model, values, keys) - generated_quantities(model::Model, values, keys) + returned(model::Model, parameters::NamedTuple) + returned(model::Model, values, keys) + returned(model::Model, values, keys) Execute `model` with variables `keys` set to `values` and return the values returned by the `model`. @@ -1231,18 +1233,252 @@ julia> model = demo(randn(10)); julia> parameters = (; s = 1.0, m_shifted=10.0); -julia> generated_quantities(model, parameters) +julia> returned(model, parameters) (0.0,) -julia> generated_quantities(model, values(parameters), keys(parameters)) +julia> returned(model, values(parameters), keys(parameters)) (0.0,) ``` """ -function generated_quantities(model::Model, parameters::NamedTuple) +function returned(model::Model, parameters::NamedTuple) fixed_model = fix(model, parameters) return fixed_model() end -function generated_quantities(model::Model, values, keys) - return generated_quantities(model, NamedTuple{keys}(values)) +function returned(model::Model, values, keys) + return returned(model, NamedTuple{keys}(values)) end + +""" + is_rhs_model(x) + +Return `true` if `x` is a model or model wrapper, and `false` otherwise. +""" +is_rhs_model(x) = false + +""" + Distributional + +Abstract type for type indicating that something is "distributional". +""" +abstract type Distributional end + +""" + Sampleable{M} <: Distributional + +A wrapper around a model indicating it is sampleable. +""" +struct Sampleable{M} <: Distributional + model::M +end + +is_rhs_model(x::Sampleable) = true + +# TODO: Export this if it end up having a purpose beyond `to_submodel`. +""" + to_sampleable(model) + +Return a wrapper around `model` indicating it is sampleable. +""" +to_sampleable(model) = Sampleable(model) + +""" + rand_like!!(model_wrap, context, varinfo) + +Returns a tuple with the first element being the realization and the second the updated varinfo. + +# Arguments +- `model_wrap::ReturnedModelWrapper`: the wrapper of the model to use. +- `context::AbstractContext`: the context to use for evaluation. +- `varinfo::AbstractVarInfo`: the varinfo to use for evaluation. + """ +function rand_like!!( + model_wrap::Sampleable, context::AbstractContext, varinfo::AbstractVarInfo +) + return rand_like!!(model_wrap.model, context, varinfo) +end + +""" + ReturnedModelWrapper + +A wrapper around a model indicating it is a model over its return values. + +This should rarely be constructed explicitly; see [`returned(model)`](@ref) instead. +""" +struct ReturnedModelWrapper{M<:Model} + model::M +end + +function rand_like!!( + model_wrap::ReturnedModelWrapper, context::AbstractContext, varinfo::AbstractVarInfo +) + # Return's the value and the (possibly mutated) varinfo. + return _evaluate!!(model_wrap.model, varinfo, context) +end + +""" + returned(model) + +Return a `model` wrapper indicating that it is a model over its return-values. +""" +returned(model::Model) = ReturnedModelWrapper(model) + +""" + to_submodel(model::Model) + +Return a model wrapper indicating that it is a sampleable model over the return-values. + +This is mainly meant to be used on the right-hand side of a `~` operator to indicate that +the model can be sampled from but not necessarily evaluated for its log density. + +!!! warning + Note that other operations that one typically associate with expressions of the form `left ~ right` + such as [`condition`](@ref) or [`fix`](@ref), will also not work with `to_submodel`. + +!!! warning + It's generally recommended to use [`prefix(::Model, input)`](@ref) when working with submodels + to ensure that the variables in `model` are unique and do not clash with other variables in the + parent model or in other submodels. + +# Examples + +## Simple example +```jldoctest submodel-to_submodel; setup=:(using Distributions) +julia> @model function demo1(x) + x ~ Normal() + return 1 + abs(x) + end; + +julia> @model function demo2(x, y) + a ~ to_submodel(demo1(x)) + return y ~ Uniform(0, a) + end; +``` + +When we sample from the model `demo2(missing, 0.4)` random variable `x` will be sampled: +```jldoctest submodel-to_submodel +julia> vi = VarInfo(demo2(missing, 0.4)); + +julia> @varname(x) in keys(vi) +true +``` + +The variable `a` is not tracked. However, it will be assigned the return value of `demo1`, +and can be used in subsequent lines of the model, as shown above. +```jldoctest submodel-to_submodel +julia> @varname(a) in keys(vi) +false +``` + +We can check that the log joint probability of the model accumulated in `vi` is correct: + +```jldoctest submodel-to_submodel +julia> x = vi[@varname(x)]; + +julia> getlogp(vi) ≈ logpdf(Normal(), x) + logpdf(Uniform(0, 1 + abs(x)), 0.4) +true +``` + +## With prefixing +```jldoctest submodel-to_submodel-prefix; setup=:(using Distributions) +julia> @model function demo1(x) + x ~ Normal() + return 1 + abs(x) + end; + +julia> @model function demo2(x, y, z) + a ~ to_submodel(prefix(demo1(x), :sub1)) + b ~ to_submodel(prefix(demo1(y), :sub2)) + return z ~ Uniform(-a, b) + end; +``` + +When we sample from the model `demo2(missing, missing, 0.4)` random variables `sub1.x` and +`sub2.x` will be sampled: +```jldoctest submodel-to_submodel-prefix +julia> vi = VarInfo(demo2(missing, missing, 0.4)); + +julia> @varname(var"sub1.x") in keys(vi) +true + +julia> @varname(var"sub2.x") in keys(vi) +true +``` + +Variables `a` and `b` are not tracked, but are assigned the return values of the respective +calls to `demo1`: +```jldoctest submodel-to_submodel-prefix +julia> @varname(a) in keys(vi) +false + +julia> @varname(b) in keys(vi) +false +``` + +We can check that the log joint probability of the model accumulated in `vi` is correct: + +```jldoctest submodel-to_submodel-prefix +julia> sub1_x = vi[@varname(var"sub1.x")]; + +julia> sub2_x = vi[@varname(var"sub2.x")]; + +julia> logprior = logpdf(Normal(), sub1_x) + logpdf(Normal(), sub2_x); + +julia> loglikelihood = logpdf(Uniform(-1 - abs(sub1_x), 1 + abs(sub2_x)), 0.4); + +julia> getlogp(vi) ≈ logprior + loglikelihood +true +``` + +## Different ways of setting the prefix +```jldoctest submodel-to_submodel-prefix-alts; setup=:(using DynamicPPL, Distributions) +julia> @model inner() = x ~ Normal() +inner (generic function with 2 methods) + +julia> # When `prefix` is unspecified, no prefix is used. + @model submodel_noprefix() = a ~ to_submodel(inner()) +submodel_noprefix (generic function with 2 methods) + +julia> @varname(x) in keys(VarInfo(submodel_noprefix())) +true + +julia> # Using a static string. + @model submodel_prefix_string() = a ~ to_submodel(prefix(inner(), "my prefix")) +submodel_prefix_string (generic function with 2 methods) + +julia> @varname(var"my prefix.x") in keys(VarInfo(submodel_prefix_string())) +true + +julia> # Using string interpolation. + @model submodel_prefix_interpolation() = a ~ to_submodel(prefix(inner(), "\$(nameof(inner()))")) +submodel_prefix_interpolation (generic function with 2 methods) + +julia> @varname(var"inner.x") in keys(VarInfo(submodel_prefix_interpolation())) +true + +julia> # Or using some arbitrary expression. + @model submodel_prefix_expr() = a ~ to_submodel(prefix(inner(), 1 + 2)) +submodel_prefix_expr (generic function with 2 methods) + +julia> @varname(var"3.x") in keys(VarInfo(submodel_prefix_expr())) +true +``` + +## Usage as likelihood is illegal + +Note that it is illegal to use a `to_submodel` model as a likelihood in another model: + +```jldoctest submodel-to_submodel-illegal; setup=:(using Distributions) +julia> @model inner() = x ~ Normal() +inner (generic function with 2 methods) + +julia> @model illegal_likelihood() = a ~ to_submodel(inner()) +illegal_likelihood (generic function with 2 methods) + +julia> model = illegal_likelihood() | (a = 1.0,); + +julia> model() +ERROR: ArgumentError: `~` with a model on the right-hand side of an observe statement is not supported +[...] +""" +to_submodel(model::Model) = to_sampleable(returned(model)) diff --git a/src/submodel_macro.jl b/src/submodel_macro.jl index 050bf31fc..5e1eb987c 100644 --- a/src/submodel_macro.jl +++ b/src/submodel_macro.jl @@ -4,6 +4,10 @@ Run a Turing `model` nested inside of a Turing model. +!!! warning + This is deprecated and will be removed in a future release. + Use `left ~ to_submodel(model)` instead (see [`to_submodel(model)`](@ref)). + # Examples ```jldoctest submodel; setup=:(using Distributions) @@ -21,6 +25,9 @@ julia> @model function demo2(x, y) When we sample from the model `demo2(missing, 0.4)` random variable `x` will be sampled: ```jldoctest submodel julia> vi = VarInfo(demo2(missing, 0.4)); +┌ Warning: `@submodel model` is deprecated, use `left ~ to_submodel(model)` instead. +│ caller = ip:0x0 +└ @ Core :-1 julia> @varname(x) in keys(vi) true @@ -62,6 +69,10 @@ Valid expressions for `prefix=...` are: The prefix makes it possible to run the same Turing model multiple times while keeping track of all random variables correctly. +!!! warning + This is deprecated and will be removed in a future release. + Use `left ~ to_submodel(model)` instead (see [`to_submodel(model)`](@ref)). + # Examples ## Example models ```jldoctest submodelprefix; setup=:(using Distributions) @@ -81,6 +92,9 @@ When we sample from the model `demo2(missing, missing, 0.4)` random variables `s `sub2.x` will be sampled: ```jldoctest submodelprefix julia> vi = VarInfo(demo2(missing, missing, 0.4)); +┌ Warning: `@submodel model` is deprecated, use `to_submodel(model)` instead. +│ caller = ip:0x0 +└ @ Core :-1 julia> @varname(var"sub1.x") in keys(vi) true @@ -124,6 +138,9 @@ julia> # When `prefix` is unspecified, no prefix is used. submodel_noprefix (generic function with 2 methods) julia> @varname(x) in keys(VarInfo(submodel_noprefix())) +┌ Warning: `@submodel model` is deprecated, use `left ~ to_submodel(model)` instead. +│ caller = ip:0x0 +└ @ Core :-1 true julia> # Explicitely don't use any prefix. @@ -207,6 +224,8 @@ function prefix_submodel_context(prefix::Bool, ctx) return ctx end +const SUBMODEL_DEPWARN_MSG = "`@submodel model` and `@submodel prefix=... model` are deprecated, use `left ~ to_submodel(model)` and `left ~ to_submodel(prefix(model, ...))`, respectively, instead." + function submodel(prefix_expr, expr, ctx=esc(:__context__)) prefix_left, prefix = getargs_assignment(prefix_expr) if prefix_left !== :prefix @@ -225,6 +244,9 @@ function submodel(prefix_expr, expr, ctx=esc(:__context__)) return if args_assign === nothing ctx = prefix_submodel_context(prefix, ctx) quote + # Raise deprecation warning to let user know that we recommend using `left ~ to_submodel(model)`. + $(Base.depwarn)(SUBMODEL_DEPWARN_MSG, Symbol("@submodel")) + $retval, $(esc(:__varinfo__)) = $(_evaluate!!)( $(esc(expr)), $(esc(:__varinfo__)), $(ctx) ) @@ -241,6 +263,9 @@ function submodel(prefix_expr, expr, ctx=esc(:__context__)) ) end quote + # Raise deprecation warning to let user know that we recommend using `left ~ to_submodel(model)`. + $(Base.depwarn)(SUBMODEL_DEPWARN_MSG, Symbol("@submodel")) + $retval, $(esc(:__varinfo__)) = $(_evaluate!!)( $(esc(R)), $(esc(:__varinfo__)), $(ctx) ) diff --git a/test/compiler.jl b/test/compiler.jl index f2d7e5852..0537d9a6d 100644 --- a/test/compiler.jl +++ b/test/compiler.jl @@ -382,6 +382,27 @@ module Issue537 end @test demo2()() == 42 end + @testset "@submodel is deprecated" begin + @model inner() = x ~ Normal() + @model outer() = @submodel x = inner() + @test_logs( + ( + :warn, + "`@submodel model` and `@submodel prefix=... model` are deprecated, use `left ~ to_submodel(model)` and `left ~ to_submodel(prefix(model, ...))`, respectively, instead.", + ), + outer()() + ) + + @model outer_with_prefix() = @submodel prefix = "sub" x = inner() + @test_logs( + ( + :warn, + "`@submodel model` and `@submodel prefix=... model` are deprecated, use `left ~ to_submodel(model)` and `left ~ to_submodel(prefix(model, ...))`, respectively, instead.", + ), + outer_with_prefix()() + ) + end + @testset "submodel" begin # No prefix, 1 level. @model function demo1(x) @@ -469,7 +490,7 @@ module Issue537 end num_steps = length(y[1]) num_obs = length(y) @inbounds for i in 1:num_obs - @submodel prefix = "ar1_$i" x = AR1(num_steps, α, μ, σ) + x ~ to_submodel(prefix(AR1(num_steps, α, μ, σ), "ar1_$i")) y[i] ~ MvNormal(x, 0.01 * I) end end diff --git a/test/debug_utils.jl b/test/debug_utils.jl index 50bb5d4be..e800ebe30 100644 --- a/test/debug_utils.jl +++ b/test/debug_utils.jl @@ -45,14 +45,14 @@ @testset "submodel" begin @model ModelInner() = x ~ Normal() @model function ModelOuterBroken() - @submodel z = ModelInner() + z ~ to_submodel(ModelInner()) return x ~ Normal() end model = ModelOuterBroken() @test_throws ErrorException check_model(model; error_on_failure=true) @model function ModelOuterWorking() - @submodel prefix = true z = ModelInner() + z = to_submodel(prefix(ModelInner(), "z")) x ~ Normal() return z end