Skip to content

Commit 1882f72

Browse files
authored
Move submodel code to submodel.jl; remove @submodel (#959)
* Move submodel code to submodel.jl * Remove `@submodel`
1 parent 8b67e96 commit 1882f72

File tree

8 files changed

+244
-596
lines changed

8 files changed

+244
-596
lines changed

HISTORY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
**Breaking changes**
66

7+
### Submodel macro
8+
9+
The `@submodel` macro is fully removed; please use `to_submodel` instead.
10+
711
### Accumulators
812

913
This release overhauls how VarInfo objects track variables such as the log joint probability. The new approach is to use what we call accumulators: Objects that the VarInfo carries on it that may change their state at each `tilde_assume!!` and `tilde_observe!!` call based on the value of the variable in question. They replace both variables that were previously hard-coded in the `VarInfo` object (`logp` and `num_produce`) and some contexts. This brings with it a number of breaking changes:

docs/src/api.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,6 @@ to_submodel
146146

147147
Note that a `[to_submodel](@ref)` is only sampleable; one cannot compute `logpdf` for its realizations.
148148

149-
In the past, one would instead embed sub-models using [`@submodel`](@ref), which has been deprecated since the introduction of [`to_submodel(model)`](@ref)
150-
151-
```@docs
152-
@submodel
153-
```
154-
155149
In the context of including models within models, it's also useful to prefix the variables in sub-models to avoid variable names clashing:
156150

157151
```@docs

src/DynamicPPL.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ export AbstractVarInfo,
128128
to_submodel,
129129
# Convenience macros
130130
@addlogprob!,
131-
@submodel,
132131
value_iterator_from_chain,
133132
check_model,
134133
check_model_and_trace,
@@ -172,6 +171,7 @@ abstract type AbstractVarInfo <: AbstractModelTrace end
172171
include("utils.jl")
173172
include("chains.jl")
174173
include("model.jl")
174+
include("submodel.jl")
175175
include("sampler.jl")
176176
include("varname.jl")
177177
include("distribution_wrappers.jl")
@@ -186,7 +186,6 @@ include("simple_varinfo.jl")
186186
include("context_implementations.jl")
187187
include("compiler.jl")
188188
include("pointwise_logdensities.jl")
189-
include("submodel_macro.jl")
190189
include("transforming.jl")
191190
include("logdensityfunction.jl")
192191
include("model_utils.jl")

src/model.jl

Lines changed: 0 additions & 240 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,243 +1265,3 @@ end
12651265
function returned(model::Model, values, keys)
12661266
return returned(model, NamedTuple{keys}(values))
12671267
end
1268-
1269-
"""
1270-
is_rhs_model(x)
1271-
1272-
Return `true` if `x` is a model or model wrapper, and `false` otherwise.
1273-
"""
1274-
is_rhs_model(x) = false
1275-
1276-
"""
1277-
Distributional
1278-
1279-
Abstract type for type indicating that something is "distributional".
1280-
"""
1281-
abstract type Distributional end
1282-
1283-
"""
1284-
should_auto_prefix(distributional)
1285-
1286-
Return `true` if the `distributional` should use automatic prefixing, and `false` otherwise.
1287-
"""
1288-
function should_auto_prefix end
1289-
1290-
"""
1291-
is_rhs_model(x)
1292-
1293-
Return `true` if the `distributional` is a model, and `false` otherwise.
1294-
"""
1295-
function is_rhs_model end
1296-
1297-
"""
1298-
Sampleable{M} <: Distributional
1299-
1300-
A wrapper around a model indicating it is sampleable.
1301-
"""
1302-
struct Sampleable{M,AutoPrefix} <: Distributional
1303-
model::M
1304-
end
1305-
1306-
should_auto_prefix(::Sampleable{<:Any,AutoPrefix}) where {AutoPrefix} = AutoPrefix
1307-
is_rhs_model(x::Sampleable) = is_rhs_model(x.model)
1308-
1309-
# TODO: Export this if it end up having a purpose beyond `to_submodel`.
1310-
"""
1311-
to_sampleable(model[, auto_prefix])
1312-
1313-
Return a wrapper around `model` indicating it is sampleable.
1314-
1315-
# Arguments
1316-
- `model::Model`: the model to wrap.
1317-
- `auto_prefix::Bool`: whether to prefix the variables in the model. Default: `true`.
1318-
"""
1319-
to_sampleable(model, auto_prefix::Bool=true) = Sampleable{typeof(model),auto_prefix}(model)
1320-
1321-
"""
1322-
rand_like!!(model_wrap, context, varinfo)
1323-
1324-
Returns a tuple with the first element being the realization and the second the updated varinfo.
1325-
1326-
# Arguments
1327-
- `model_wrap::ReturnedModelWrapper`: the wrapper of the model to use.
1328-
- `context::AbstractContext`: the context to use for evaluation.
1329-
- `varinfo::AbstractVarInfo`: the varinfo to use for evaluation.
1330-
"""
1331-
function rand_like!!(
1332-
model_wrap::Sampleable, context::AbstractContext, varinfo::AbstractVarInfo
1333-
)
1334-
return rand_like!!(model_wrap.model, context, varinfo)
1335-
end
1336-
1337-
"""
1338-
ReturnedModelWrapper
1339-
1340-
A wrapper around a model indicating it is a model over its return values.
1341-
1342-
This should rarely be constructed explicitly; see [`returned(model)`](@ref) instead.
1343-
"""
1344-
struct ReturnedModelWrapper{M<:Model}
1345-
model::M
1346-
end
1347-
1348-
is_rhs_model(::ReturnedModelWrapper) = true
1349-
1350-
function rand_like!!(
1351-
model_wrap::ReturnedModelWrapper, context::AbstractContext, varinfo::AbstractVarInfo
1352-
)
1353-
# Return's the value and the (possibly mutated) varinfo.
1354-
return _evaluate!!(model_wrap.model, varinfo, context)
1355-
end
1356-
1357-
"""
1358-
returned(model)
1359-
1360-
Return a `model` wrapper indicating that it is a model over its return-values.
1361-
"""
1362-
returned(model::Model) = ReturnedModelWrapper(model)
1363-
1364-
"""
1365-
to_submodel(model::Model[, auto_prefix::Bool])
1366-
1367-
Return a model wrapper indicating that it is a sampleable model over the return-values.
1368-
1369-
This is mainly meant to be used on the right-hand side of a `~` operator to indicate that
1370-
the model can be sampled from but not necessarily evaluated for its log density.
1371-
1372-
!!! warning
1373-
Note that some other operations that one typically associate with expressions of the form
1374-
`left ~ right` such as [`condition`](@ref), will also not work with `to_submodel`.
1375-
1376-
!!! warning
1377-
To avoid variable names clashing between models, it is recommend leave argument `auto_prefix` equal to `true`.
1378-
If one does not use automatic prefixing, then it's recommended to use [`prefix(::Model, input)`](@ref) explicitly.
1379-
1380-
# Arguments
1381-
- `model::Model`: the model to wrap.
1382-
- `auto_prefix::Bool`: whether to automatically prefix the variables in the model using the left-hand
1383-
side of the `~` statement. Default: `true`.
1384-
1385-
# Examples
1386-
1387-
## Simple example
1388-
```jldoctest submodel-to_submodel; setup=:(using Distributions)
1389-
julia> @model function demo1(x)
1390-
x ~ Normal()
1391-
return 1 + abs(x)
1392-
end;
1393-
1394-
julia> @model function demo2(x, y)
1395-
a ~ to_submodel(demo1(x))
1396-
return y ~ Uniform(0, a)
1397-
end;
1398-
```
1399-
1400-
When we sample from the model `demo2(missing, 0.4)` random variable `x` will be sampled:
1401-
```jldoctest submodel-to_submodel
1402-
julia> vi = VarInfo(demo2(missing, 0.4));
1403-
1404-
julia> @varname(a.x) in keys(vi)
1405-
true
1406-
```
1407-
1408-
The variable `a` is not tracked. However, it will be assigned the return value of `demo1`,
1409-
and can be used in subsequent lines of the model, as shown above.
1410-
```jldoctest submodel-to_submodel
1411-
julia> @varname(a) in keys(vi)
1412-
false
1413-
```
1414-
1415-
We can check that the log joint probability of the model accumulated in `vi` is correct:
1416-
1417-
```jldoctest submodel-to_submodel
1418-
julia> x = vi[@varname(a.x)];
1419-
1420-
julia> getlogjoint(vi) ≈ logpdf(Normal(), x) + logpdf(Uniform(0, 1 + abs(x)), 0.4)
1421-
true
1422-
```
1423-
1424-
## Without automatic prefixing
1425-
As mentioned earlier, by default, the `auto_prefix` argument specifies whether to automatically
1426-
prefix the variables in the submodel. If `auto_prefix=false`, then the variables in the submodel
1427-
will not be prefixed.
1428-
```jldoctest submodel-to_submodel-prefix; setup=:(using Distributions)
1429-
julia> @model function demo1(x)
1430-
x ~ Normal()
1431-
return 1 + abs(x)
1432-
end;
1433-
1434-
julia> @model function demo2_no_prefix(x, z)
1435-
a ~ to_submodel(demo1(x), false)
1436-
return z ~ Uniform(-a, 1)
1437-
end;
1438-
1439-
julia> vi = VarInfo(demo2_no_prefix(missing, 0.4));
1440-
1441-
julia> @varname(x) in keys(vi) # here we just use `x` instead of `a.x`
1442-
true
1443-
```
1444-
However, not using prefixing is generally not recommended as it can lead to variable name clashes
1445-
unless one is careful. For example, if we're re-using the same model twice in a model, not using prefixing
1446-
will lead to variable name clashes: However, one can manually prefix using the [`prefix(::Model, input)`](@ref):
1447-
```jldoctest submodel-to_submodel-prefix
1448-
julia> @model function demo2(x, y, z)
1449-
a ~ to_submodel(prefix(demo1(x), :sub1), false)
1450-
b ~ to_submodel(prefix(demo1(y), :sub2), false)
1451-
return z ~ Uniform(-a, b)
1452-
end;
1453-
1454-
julia> vi = VarInfo(demo2(missing, missing, 0.4));
1455-
1456-
julia> @varname(sub1.x) in keys(vi)
1457-
true
1458-
1459-
julia> @varname(sub2.x) in keys(vi)
1460-
true
1461-
```
1462-
1463-
Variables `a` and `b` are not tracked, but are assigned the return values of the respective
1464-
calls to `demo1`:
1465-
```jldoctest submodel-to_submodel-prefix
1466-
julia> @varname(a) in keys(vi)
1467-
false
1468-
1469-
julia> @varname(b) in keys(vi)
1470-
false
1471-
```
1472-
1473-
We can check that the log joint probability of the model accumulated in `vi` is correct:
1474-
1475-
```jldoctest submodel-to_submodel-prefix
1476-
julia> sub1_x = vi[@varname(sub1.x)];
1477-
1478-
julia> sub2_x = vi[@varname(sub2.x)];
1479-
1480-
julia> logprior = logpdf(Normal(), sub1_x) + logpdf(Normal(), sub2_x);
1481-
1482-
julia> loglikelihood = logpdf(Uniform(-1 - abs(sub1_x), 1 + abs(sub2_x)), 0.4);
1483-
1484-
julia> getlogjoint(vi) ≈ logprior + loglikelihood
1485-
true
1486-
```
1487-
1488-
## Usage as likelihood is illegal
1489-
1490-
Note that it is illegal to use a `to_submodel` model as a likelihood in another model:
1491-
1492-
```jldoctest submodel-to_submodel-illegal; setup=:(using Distributions)
1493-
julia> @model inner() = x ~ Normal()
1494-
inner (generic function with 2 methods)
1495-
1496-
julia> @model illegal_likelihood() = a ~ to_submodel(inner())
1497-
illegal_likelihood (generic function with 2 methods)
1498-
1499-
julia> model = illegal_likelihood() | (a = 1.0,);
1500-
1501-
julia> model()
1502-
ERROR: ArgumentError: `~` with a model on the right-hand side of an observe statement is not supported
1503-
[...]
1504-
```
1505-
"""
1506-
to_submodel(model::Model, auto_prefix::Bool=true) =
1507-
to_sampleable(returned(model), auto_prefix)

0 commit comments

Comments
 (0)