Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
62a6524
isassumption no longer acts on expressions
torfjelde Jul 8, 2021
3e84c8d
simple unnecessary indexing fixed
torfjelde Jul 8, 2021
1cd2ed1
bumped version
torfjelde Jul 8, 2021
960035f
added convenient macro
torfjelde Jul 8, 2021
ec2dfd3
Update src/compiler.jl
torfjelde Jul 8, 2021
faa0f49
use isassumption-macro in model instead of hardcoded check
torfjelde Jul 8, 2021
938d4f9
use === instead of ismissing
torfjelde Jul 8, 2021
7c9d7eb
removed incorrect statement in docs of isassumption
torfjelde Jul 8, 2021
0da3e06
added more descriptive docs to isassumption macro
torfjelde Jul 8, 2021
ba98fba
export isassumption macro
torfjelde Jul 8, 2021
fdd0a59
further improvements to isassumption macro docstring
torfjelde Jul 8, 2021
4783b6c
make example in isassumption a doctest
torfjelde Jul 8, 2021
d56f950
implemented the isassumption macro properly
torfjelde Jul 8, 2021
e994391
improved isassumption further
torfjelde Jul 9, 2021
a647f29
use macro within model
torfjelde Jul 9, 2021
d25b7ad
removed now-redundant code
torfjelde Jul 9, 2021
a7dcb56
add support missing support for indexing using ranges and colons
torfjelde Jul 9, 2021
edc9002
Update src/compiler.jl
torfjelde Jul 9, 2021
b3bf0a3
incorporated improvement suggested by @devmotion
torfjelde Jul 10, 2021
b33494c
Merge branch 'master' into tor/isassumption
torfjelde Jul 19, 2021
eb98a04
dont use macro within in macro, instead use the isassumption call
torfjelde Jul 19, 2021
f6e60ed
improved doctests for isassumption macro
torfjelde Jul 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/DynamicPPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export AbstractVarInfo,
pointwise_loglikelihoods,
# Convenience macros
@addlogprob!,
@isassumption,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to export @isassumption? It seems it is only used internally by the compiler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation of this PR is to provide something that the end-user can use, e.g. if you really want to squeeze out performance you might want to do something like

if @isassumption(x)
    x ~ Dist()
else
    @addlogprob! f(x)
end

There currently is no good way of doing this, hence this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think it's useful to expose a macro that users can use to check whether or not something is treated as an assumption, even when not used within @model. People are often confused by this 😕 But this is not the main-motivation behind the PR; the above is.

@submodel

# Reexport
Expand Down
128 changes: 97 additions & 31 deletions src/compiler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,107 @@ const INTERNALNAMES = (:__model__, :__sampler__, :__context__, :__varinfo__, :__
const DEPRECATED_INTERNALNAMES = (:_model, :_sampler, :_context, :_varinfo, :_rng)

"""
isassumption(expr)
@isassumption x
@isassumption model x[, varname]

Return an expression that can be evaluated to check if `expr` is an assumption in the
model.
Return `true` if `x` is an assumption and `false` otherwise.

Let `expr` be `:(x[1])`. It is an assumption in the following cases:
1. `x` is not among the input data to the model,
2. `x` is among the input data to the model but with a value `missing`, or
3. `x` is among the input data to the model with a value other than missing,
but `x[1] === missing`.
E.g. `x[1]` is an assumption in the following cases:
1. `x` is not among the input data to the model, or
2. `x` is among the input data to the model but with `value === missing`.!

When `expr` is not an expression or symbol (i.e., a literal), this expands to `false`.
A literal, e.g. `1.0`, results in `false`.

# Examples
```jldoctest
julia> @model demo(x) = x ~ Normal(); # univariate

julia> @isassumption(demo(1.0), x)
false

julia> @isassumption(demo(1.0), y)
true

julia> x = missing; @isassumption(demo(1.0), x)
true

julia> @model demov(x) = x .~ Normal(); # multivariate

julia> x = [1.0, 1.0];

julia> @isassumption(demov(x), x)
false

julia> @isassumption(demov(x), y)
true

julia> @isassumption(demov(x), missing)
true

julia> x = [1.0, missing]; # partially missing not supported for multivariate

julia> @isassumption(demov(x), x)
ERROR: x have some `missing` and some not; this is currently not supported

julia> @isassumption(demov(x), y)
true

julia> x = [missing, missing]; # fully missing supported for multivariate

julia> @isassumption(demov(x), x)
true
```

See also: [`isassumption`](@ref)
"""
function isassumption(expr::Union{Symbol,Expr})
vn = gensym(:vn)
macro isassumption(left)
return esc(isassumption(:(__model__), left))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use the other approach and only escape whatever has to be escaped? Usually this is safer and one does not require to interpolate DynamicPPL in the expressions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did that initially but kept finding cases where things would break so I just gave up 😅

I guess I'll try again.

Copy link
Member Author

@torfjelde torfjelde Jul 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can avoid escaping the full expression if we want to use it within another macro 😕
E.g. addlogprob! and others also do this.

end
macro isassumption(model, left, vn=varname(left))
return esc(isassumption(model, left, vn))
end

return quote
let $vn = $(varname(expr))
# This branch should compile nicely in all cases except for partial missing data
# For example, when `expr` is `:(x[i])` and `x isa Vector{Union{Missing, Float64}}`
if !$(DynamicPPL.inargnames)($vn, __model__) ||
$(DynamicPPL.inmissings)($vn, __model__)
true
else
# Evaluate the LHS
$(maybe_view(expr)) === missing
end
end
"""
isassumption(model, left[, vn])

Return an expression evaluating to `true` if expression `left` is considered
as an assumption in `model`, where `model` evaluates to a [`Model`](@ref).

If `vn` is specified, is is assumed to evaluate to `varname(left)`.
If `vn` is not specified, `varname(left)` is used in instead.

See also: [`@isassumption`](@ref)
"""
function isassumption(model, left, vn=varname(left))
if isliteral(left)
return :(false)
end

sym = vsym(left)
return :(
(!$(DynamicPPL.inargnames)($vn, $model) || $(DynamicPPL.inmissings)($vn, $model)) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, maybe we could avoid having to interpolate DynamicPPL if we only escape the relevant parts?

(
@isdefined($sym) &&
($left === $(missing) || $(DynamicPPL.is_entirely_missing)($vn, $left))
)
)
end

# failsafe: a literal is never an assumption
isassumption(expr) = :(false)
is_entirely_missing(vn, x) = false
function is_entirely_missing(vn::VarName, x::AbstractArray{>:Missing})
num_missing = count(x -> x === missing, x)
if num_missing == length(x)
# All are `missing`.
return true
end

if num_missing > 0
# Only some are `missing` => we don't know what to do.
error("$(vn) have some `missing` and some not; this is currently not supported")
end

return false
end

# If we're working with, say, a `Symbol`, then we're not going to `view`.
maybe_view(x) = x
Expand Down Expand Up @@ -317,12 +385,11 @@ function generate_tilde(left, right)

# Otherwise it is determined by the model or its value,
# if the LHS represents an observation
@gensym vn inds isassumption
@gensym vn inds
return quote
$vn = $(varname(left))
$inds = $(vinds(left))
$isassumption = $(DynamicPPL.isassumption(left))
if $isassumption
if $(isassumption(:__model__, left, vn))
$left = $(DynamicPPL.tilde_assume!)(
__context__,
$(DynamicPPL.unwrap_right_vn)(
Expand Down Expand Up @@ -361,12 +428,11 @@ function generate_dot_tilde(left, right)

# Otherwise it is determined by the model or its value,
# if the LHS represents an observation
@gensym vn inds isassumption
@gensym vn inds
return quote
$vn = $(varname(left))
$inds = $(vinds(left))
$isassumption = $(DynamicPPL.isassumption(left))
if $isassumption
if $(isassumption(:__model__, left, vn))
$left .= $(DynamicPPL.dot_tilde_assume!)(
__context__,
$(DynamicPPL.unwrap_right_left_vns)(
Expand Down
2 changes: 1 addition & 1 deletion src/context_implementations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ function assume(dist::Distribution, vn::VarName, vi)
error("variable $vn does not exist")
end
r = vi[vn]
return r, Bijectors.logpdf_with_trans(dist, vi[vn], istrans(vi, vn))
return r, Bijectors.logpdf_with_trans(dist, r, istrans(vi, vn))
end

# SampleFromPrior and SampleFromUniform
Expand Down