DynamicPPL
DynamicPPL.AbstractVarInfo
— TypeAbstractVarInfo
Abstract supertype for data structures that capture random variables when executing a probabilistic model and accumulate log densities such as the log likelihood or the log joint probability of the model.
See also: VarInfo
DynamicPPL.DefaultContext
— Typestruct DefaultContext <: AbstractContext end
The DefaultContext
is used by default to compute log the joint probability of the data and parameters when running the model.
DynamicPPL.IsLeaf
— TypeIsLeaf
Specifies that the context is a leaf in the context-tree.
DynamicPPL.IsParent
— TypeIsParent
Specifies that the context is a parent in the context-tree.
DynamicPPL.LikelihoodContext
— Typestruct LikelihoodContext{Tvars} <: AbstractContext
vars::Tvars
end
The LikelihoodContext
enables the computation of the log likelihood of the parameters when running the model. vars
can be used to evaluate the log likelihood for specific values of the model's parameters. If vars
is nothing
, the parameter values inside the VarInfo
will be used by default.
DynamicPPL.Metadata
— TypeThe Metadata
struct stores some metadata about the parameters of the model. This helps query certain information about a variable, such as its distribution, which samplers sample this variable, its value and whether this value is transformed to real space or not.
Let md
be an instance of Metadata
:
md.vns
is the vector of allVarName
instances.md.idcs
is the dictionary that maps eachVarName
instance to its index in
md.vns
, md.ranges
md.dists
, md.orders
and md.flags
.
md.vns[md.idcs[vn]] == vn
.md.dists[md.idcs[vn]]
is the distribution ofvn
.md.gids[md.idcs[vn]]
is the set of algorithms used to samplevn
. This is used in
the Gibbs sampling process.
md.orders[md.idcs[vn]]
is the number ofobserve
statements beforevn
is sampled.md.ranges[md.idcs[vn]]
is the index range ofvn
inmd.vals
.md.vals[md.ranges[md.idcs[vn]]]
is the vector of values of corresponding tovn
.md.flags
is a dictionary of true/false flags.md.flags[flag][md.idcs[vn]]
is the
value of flag
corresponding to vn
.
To make md::Metadata
type stable, all the md.vns
must have the same symbol and distribution type. However, one can have a Julia variable, say x
, that is a matrix or a hierarchical array sampled in partitions, e.g. x[1][:] ~ MvNormal(zeros(2), I); x[2][:] ~ MvNormal(ones(2), I)
, and is managed by a single md::Metadata
so long as all the distributions on the RHS of ~
are of the same type. Type unstable Metadata
will still work but will have inferior performance. When sampling, the first iteration uses a type unstable Metadata
for all the variables then a specialized Metadata
is used for each symbol along with a function barrier to make the rest of the sampling type stable.
DynamicPPL.Metadata
— MethodMetadata()
Construct an empty type unstable instance of Metadata
.
DynamicPPL.MiniBatchContext
— Typestruct MiniBatchContext{Tctx, T} <: AbstractContext
context::Tctx
loglike_scalar::T
end
The MiniBatchContext
enables the computation of log(prior) + s * log(likelihood of a batch)
when running the model, where s
is the loglike_scalar
field, typically equal to the number of data points / batch size
. This is useful in batch-based stochastic gradient descent algorithms to be optimizing log(prior) + log(likelihood of all the data points)
in the expectation.
DynamicPPL.Model
— Typestruct Model{F,argnames,defaultnames,missings,Targs,Tdefaults}
name::Symbol
f::F
args::NamedTuple{argnames,Targs}
defaults::NamedTuple{defaultnames,Tdefaults}
end
A Model
struct with model evaluation function of type F
, arguments of names argnames
types Targs
, default arguments of names defaultnames
with types Tdefaults
, and missing arguments missings
.
Here argnames
, defaultargnames
, and missings
are tuples of symbols, e.g. (:a, :b)
.
An argument with a type of Missing
will be in missings
by default. However, in non-traditional use-cases missings
can be defined differently. All variables in missings
are treated as random variables rather than observations.
The default arguments are used internally when constructing instances of the same model with different arguments.
Examples
julia> Model(f, (x = 1.0, y = 2.0))
Model{typeof(f),(:x, :y),(),(),Tuple{Float64,Float64},Tuple{}}(f, (x = 1.0, y = 2.0), NamedTuple())
julia> Model(f, (x = 1.0, y = 2.0), (x = 42,))
Model{typeof(f),(:x, :y),(:x,),(),Tuple{Float64,Float64},Tuple{Int64}}(f, (x = 1.0, y = 2.0), (x = 42,))
julia> Model{(:y,)}(f, (x = 1.0, y = 2.0), (x = 42,)) # with special definition of missings
Model{typeof(f),(:x, :y),(:x,),(:y,),Tuple{Float64,Float64},Tuple{Int64}}(f, (x = 1.0, y = 2.0), (x = 42,))
DynamicPPL.Model
— Method(model::Model)([rng, varinfo, sampler, context])
Sample from the model
using the sampler
with random number generator rng
and the context
, and store the sample and log joint probability in varinfo
.
The method resets the log joint probability of varinfo
and increases the evaluation number of sampler
.
DynamicPPL.Model
— MethodModel{missings}(name::Symbol, f, args::NamedTuple, defaults::NamedTuple)
Create a model of name name
with evaluation function f
and missing arguments overwritten by missings
.
DynamicPPL.Model
— MethodModel(name::Symbol, f, args::NamedTuple[, defaults::NamedTuple = ()])
Create a model of name name
with evaluation function f
and missing arguments deduced from args
.
Default arguments defaults
are used internally when constructing instances of the same model with different arguments.
DynamicPPL.NamedDist
— TypeA named distribution that carries the name of the random variable with it.
DynamicPPL.NodeTrait
— TypeNodeTrait(context)
NodeTrait(f, context)
Specifies the role of context
in the context-tree.
The officially supported traits are:
IsLeaf
:context
does not have any decendants.IsParent
:context
has a child context to which we often defer. Expects the following methods to be implemented:
DynamicPPL.PrefixContext
— TypePrefixContext{Prefix}(context)
Create a context that allows you to use the wrapped context
when running the model and adds the Prefix
to all parameters.
This context is useful in nested models to ensure that the names of the parameters are unique.
See also: @submodel
DynamicPPL.PriorContext
— Typestruct PriorContext{Tvars} <: AbstractContext
vars::Tvars
end
The PriorContext
enables the computation of the log prior of the parameters vars
when running the model.
DynamicPPL.SampleFromPrior
— TypeSampleFromPrior
Sampling algorithm that samples unobserved random variables from their prior distribution.
DynamicPPL.SampleFromUniform
— TypeSampleFromUniform
Sampling algorithm that samples unobserved random variables from a uniform distribution.
References
DynamicPPL.Sampler
— TypeSampler{T}
Generic sampler type for inference algorithms of type T
in DynamicPPL.
Sampler
should implement the AbstractMCMC interface, and in particular AbstractMCMC.step
. A default implementation of the initial sampling step is provided that supports resuming sampling from a previous state and setting initial parameter values. It requires to overload loadstate
and initialstep
for loading previous states and actually performing the initial sampling step, respectively. Additionally, sometimes one might want to implement initialsampler
that specifies how the initial parameter values are sampled if they are not provided. By default, values are sampled from the prior.
DynamicPPL.SamplingContext
— TypeSamplingContext(rng, sampler, context)
Create a context that allows you to sample parameters with the sampler
when running the model. The context
determines how the returned log density is computed when running the model.
See also: DefaultContext
, LikelihoodContext
, PriorContext
DynamicPPL.SimpleVarInfo
— TypeSimpleVarInfo{NT,T} <: AbstractVarInfo
A simple wrapper of the parameters with a logp
field for accumulation of the logdensity.
Currently only implemented for NT<:NamedTuple
and NT<:Dict
.
Notes
The major differences between this and TypedVarInfo
are:
SimpleVarInfo
does not require linearization.SimpleVarInfo
can use more efficient bijectors.SimpleVarInfo
is only type-stable ifNT<:NamedTuple
and either a) no indexing is used in tilde-statements, or b) the values have been specified with the correct shapes.
Examples
General usage
julia> using StableRNGs
julia> @model function demo()
m ~ Normal()
x = Vector{Float64}(undef, 2)
for i in eachindex(x)
x[i] ~ Normal()
end
return x
end
demo (generic function with 2 methods)
julia> m = demo();
julia> rng = StableRNG(42);
julia> ### Sampling ###
ctx = SamplingContext(rng, SampleFromPrior(), DefaultContext());
julia> # In the `NamedTuple` version we need to provide the place-holder values for
# the variables which are using "containers", e.g. `Array`.
# In this case, this means that we need to specify `x` but not `m`.
_, vi = DynamicPPL.evaluate!!(m, SimpleVarInfo((x = ones(2), )), ctx);
julia> # (✓) Vroom, vroom! FAST!!!
vi[@varname(x[1])]
0.4471218424633827
julia> # We can also access arbitrary varnames pointing to `x`, e.g.
vi[@varname(x)]
2-element Vector{Float64}:
0.4471218424633827
1.3736306979834252
julia> vi[@varname(x[1:2])]
2-element Vector{Float64}:
0.4471218424633827
1.3736306979834252
julia> # (×) If we don't provide the container...
_, vi = DynamicPPL.evaluate!!(m, SimpleVarInfo(), ctx); vi
ERROR: type NamedTuple has no field x
[...]
julia> # If one does not know the varnames, we can use a `Dict` instead.
_, vi = DynamicPPL.evaluate!!(m, SimpleVarInfo{Float64}(Dict()), ctx);
julia> # (✓) Sort of fast, but only possible at runtime.
vi[@varname(x[1])]
-1.019202452456547
julia> # In addtion, we can only access varnames as they appear in the model!
vi[@varname(x)]
ERROR: KeyError: key x not found
[...]
julia> vi[@varname(x[1:2])]
ERROR: KeyError: key x[1:2] not found
[...]
Indexing
Using NamedTuple
as underlying storage.
julia> svi_nt = SimpleVarInfo((m = (a = [1.0], ), ));
julia> svi_nt[@varname(m)]
(a = [1.0],)
julia> svi_nt[@varname(m.a)]
1-element Vector{Float64}:
1.0
julia> svi_nt[@varname(m.a[1])]
1.0
julia> svi_nt[@varname(m.a[2])]
ERROR: BoundsError: attempt to access 1-element Vector{Float64} at index [2]
[...]
julia> svi_nt[@varname(m.b)]
ERROR: type NamedTuple has no field b
[...]
Using Dict
as underlying storage.
julia> svi_dict = SimpleVarInfo(Dict(@varname(m) => (a = [1.0], )));
julia> svi_dict[@varname(m)]
(a = [1.0],)
julia> svi_dict[@varname(m.a)]
1-element Vector{Float64}:
1.0
julia> svi_dict[@varname(m.a[1])]
1.0
julia> svi_dict[@varname(m.a[2])]
ERROR: BoundsError: attempt to access 1-element Vector{Float64} at index [2]
[...]
julia> svi_dict[@varname(m.b)]
ERROR: type NamedTuple has no field b
[...]
DynamicPPL.ThreadSafeVarInfo
— TypeThreadSafeVarInfo
A ThreadSafeVarInfo
object wraps an AbstractVarInfo
object and an array of log probabilities for thread-safe execution of a probabilistic model.
DynamicPPL.TypedVarInfo
— MethodTypedVarInfo(vi::UntypedVarInfo)
This function finds all the unique sym
s from the instances of VarName{sym}
found in vi.metadata.vns
. It then extracts the metadata associated with each symbol from the global vi.metadata
field. Finally, a new VarInfo
is created with a new metadata
as a NamedTuple
mapping from symbols to type-stable Metadata
instances, one for each symbol.
DynamicPPL.VarInfo
— Typestruct VarInfo{Tmeta, Tlogp} <: AbstractVarInfo
metadata::Tmeta
logp::Base.RefValue{Tlogp}
num_produce::Base.RefValue{Int}
end
A light wrapper over one or more instances of Metadata
. Let vi
be an instance of VarInfo
. If vi isa VarInfo{<:Metadata}
, then only one Metadata
instance is used for all the sybmols. VarInfo{<:Metadata}
is aliased UntypedVarInfo
. If vi isa VarInfo{<:NamedTuple}
, then vi.metadata
is a NamedTuple
that maps each symbol used on the LHS of ~
in the model to its Metadata
instance. The latter allows for the type specialization of vi
after the first sampling iteration when all the symbols have been observed. VarInfo{<:NamedTuple}
is aliased TypedVarInfo
.
Note: It is the user's responsibility to ensure that each "symbol" is visited at least once whenever the model is called, regardless of any stochastic branching. Each symbol refers to a Julia variable and can be a hierarchical array of many random variables, e.g. x[1] ~ ...
and x[2] ~ ...
both have the same symbol x
.
AbstractPPL.condition
— Methodcondition(model::Model; values...)
condition(model::Model, values::NamedTuple)
Return a Model
which now treats the variables in values
as observations.
See also: decondition
, conditioned
Limitations
This does currently not work with variables that are provided to the model as arguments, e.g. @model function demo(x) ... end
means that condition
will not affect the variable x
.
Therefore if one wants to make use of condition
and decondition
one should not be specifying any random variables as arguments.
This is done for the sake of backwards compatibility.
Examples
Simple univariate model
julia> using Distributions; using StableRNGs; rng = StableRNG(42); # For reproducibility.
julia> @model function demo()
m ~ Normal()
x ~ Normal(m, 1)
return (; m=m, x=x)
end
demo (generic function with 2 methods)
julia> model = demo();
julia> model(rng)
(m = -0.6702516921145671, x = -0.22312984965118443)
julia> # Create a new instance which treats `x` as observed
# with value `100.0`, and similarly for `m=1.0`.
conditioned_model = condition(model, x=100.0, m=1.0);
julia> conditioned_model(rng)
(m = 1.0, x = 100.0)
julia> # Let's only condition on `x = 100.0`.
conditioned_model = condition(model, x = 100.0);
julia> conditioned_model(rng)
(m = 1.3736306979834252, x = 100.0)
julia> # We can also use the nicer `|` syntax.
conditioned_model = model | (x = 100.0, );
julia> conditioned_model(rng)
(m = 1.3095394956381083, x = 100.0)
Condition only a part of a multivariate variable
Not only can be condition on multivariate random variables, but we can also use the standard mechanism of setting something to missing
in the call to condition
to only condition on a part of the variable.
julia> @model function demo_mv(::Type{TV}=Float64) where {TV}
m = Vector{TV}(undef, 2)
m[1] ~ Normal()
m[2] ~ Normal()
return m
end
demo_mv (generic function with 3 methods)
julia> model = demo_mv();
julia> conditioned_model = condition(model, m = [missing, 1.0]);
julia> conditioned_model(rng) # (✓) `m[1]` sampled, `m[2]` is fixed
2-element Vector{Float64}:
0.12607002180931043
1.0
Intuitively one might also expect to be able to write model | (x[1] = 1.0, )
. Unfortunately this is not supported due to performance.
julia> condition(model, var"x[2]" = 1.0)(rng) # (×) `x[2]` is not set to 1.0.
2-element Vector{Float64}:
0.683947930996541
-1.019202452456547
We will likely provide some syntactic sugar for this in the future.
Nested models
condition
of course also supports the use of nested models through the use of @submodel
.
julia> @model demo_inner() = m ~ Normal()
demo_inner (generic function with 2 methods)
julia> @model function demo_outer()
@submodel m = demo_inner()
return m
end
demo_outer (generic function with 2 methods)
julia> model = demo_outer();
julia> model(rng)
-0.7935128416361353
julia> conditioned_model = model | (m = 1.0, );
julia> conditioned_model(rng)
1.0
But one needs to be careful when prefixing variables in the nested models:
julia> @model function demo_outer_prefix()
@submodel prefix="inner" m = demo_inner()
return m
end
demo_outer_prefix (generic function with 2 methods)
julia> # This doesn't work now!
conditioned_model = demo_outer_prefix() | (m = 1.0, );
julia> conditioned_model(rng)
1.7747246334368165
julia> # `m` in `demo_inner` is referred to as `inner.m` internally, so we do:
conditioned_model = demo_outer_prefix() | (var"inner.m" = 1.0, );
julia> conditioned_model(rng)
1.0
julia> # Note that the above `var"..."` is just standard Julia syntax:
keys((var"inner.m" = 1.0, ))
(Symbol("inner.m"),)
The difference is maybe more obvious once we look at how these different in their trace/VarInfo
:
julia> keys(VarInfo(demo_outer()))
1-element Vector{VarName{:m, Tuple{}}}:
m
julia> keys(VarInfo(demo_outer_prefix()))
1-element Vector{VarName{Symbol("inner.m"), Tuple{}}}:
inner.m
From this we can tell what the correct way to condition m
within demo_inner
is in the two different models.
AbstractPPL.condition
— Methodcondition([context::AbstractContext,] values::NamedTuple)
condition([context::AbstractContext]; values...)
Return ConditionContext
with values
and context
if values
is non-empty, otherwise return context
which is DefaultContext
by default.
See also: decondition
AbstractPPL.decondition
— Methoddecondition(context::AbstractContext, syms...)
Return context
but with syms
no longer conditioned on.
Note that this recursively traverses contexts, deconditioning all along the way.
See also: condition
AbstractPPL.decondition
— Methoddecondition(model::Model)
decondition(model::Model, syms...)
Return a Model
for which syms...
are not considered observations. If no syms
are provided, then all variables currently considered observations will no longer be.
This is essentially the inverse of condition
. This also means that it suffers from the same limitiations.
Examples
julia> using Distributions; using StableRNGs; rng = StableRNG(42); # For reproducibility.
julia> @model function demo()
m ~ Normal()
x ~ Normal(m, 1)
return (; m=m, x=x)
end
demo (generic function with 2 methods)
julia> conditioned_model = condition(demo(), m = 1.0, x = 10.0);
julia> conditioned_model(rng)
(m = 1.0, x = 10.0)
julia> model = decondition(conditioned_model, :m);
julia> model(rng)
(m = -0.6702516921145671, x = 10.0)
julia> # `decondition` multiple at once:
decondition(model, :m, :x)(rng)
(m = 0.4471218424633827, x = 1.820752540446808)
julia> # `decondition` without any symbols will `decondition` all variables.
decondition(model)(rng)
(m = 1.3095394956381083, x = 1.4356095174474188)
julia> # Usage of `Val` to perform `decondition` at compile-time if possible
# is also supported.
model = decondition(conditioned_model, Val{:m}());
julia> model(rng)
(m = 0.683947930996541, x = 10.0)
BangBang.empty!!
— Methodempty!!(vi::VarInfo)
Empty the fields of vi.metadata
and reset vi.logp[]
and vi.num_produce[]
to zeros.
This is useful when using a sampling algorithm that assumes an empty vi
, e.g. SMC
.
BangBang.push!!
— Methodpush!!(vi::VarInfo, vn::VarName, r, dist::Distribution, gid::Selector)
Push a new random variable vn
with a sampled value r
sampled with a sampler of selector gid
from a distribution dist
to VarInfo
vi
.
BangBang.push!!
— Methodpush!!(vi::VarInfo, vn::VarName, r, dist::Distribution, spl::AbstractSampler)
Push a new random variable vn
with a sampled value r
sampled with a sampler spl
from a distribution dist
to VarInfo
vi
, if it makes sense.
The sampler is passed here to invalidate its cache where defined.
BangBang.push!!
— Methodpush!!(vi::VarInfo, vn::VarName, r, dist::Distribution)
Push a new random variable vn
with a sampled value r
from a distribution dist
to the VarInfo
vi
, mutating if it makes sense.
Base.:|
— Methodmodel | (x = 1.0, ...)
Return a Model
which now treats variables on the right-hand side as observations.
See condition
for more information and examples.
Base.empty!
— Methodempty!(meta::Metadata)
Empty the fields of meta
.
This is useful when using a sampling algorithm that assumes an empty meta
, e.g. SMC
.
Base.getindex
— Methodgetindex(vi::VarInfo, spl::Union{SampleFromPrior, Sampler})
Return the current value(s) of the random variables sampled by spl
in vi
.
The value(s) may or may not be transformed to Euclidean space.
Base.getindex
— Methodgetindex(vi::VarInfo, vn::VarName)
getindex(vi::VarInfo, vns::Vector{<:VarName})
Return the current value(s) of vn
(vns
) in vi
in the support of its (their) distribution(s).
If the value(s) is (are) transformed to the Euclidean space, it is (they are) transformed back.
Base.haskey
— Methodhaskey(vi::VarInfo, vn::VarName)
Check whether vn
has been sampled in vi
.
Base.isempty
— Methodisempty(vi::VarInfo)
Return true if vi
is empty and false otherwise.
Base.keys
— Methodkeys(vi::SimpleVarInfo)
Return an iterator of keys present in vi
.
Base.keys
— Methodkeys(vi::AbstractVarInfo)
Return an iterator over all vns
in vi
.
Base.nameof
— Methodnameof(model::Model)
Get the name of the model
as Symbol
.
Base.setindex!
— Methodsetindex!(vi::VarInfo, val, spl::Union{SampleFromPrior, Sampler})
Set the current value(s) of the random variables sampled by spl
in vi
to val
.
The value(s) may or may not be transformed to Euclidean space.
Base.setindex!
— Methodsetindex!(vi::VarInfo, val, vn::VarName)
Set the current value(s) of the random variable vn
in vi
to val
.
The value(s) may or may not be transformed to Euclidean space.
DynamicPPL._apply!
— Method_apply!(kernel!, vi::AbstractVarInfo, values, keys)
Calls kernel!(vi, vn, values, keys)
for every vn
in vi
.
DynamicPPL._evaluate!!
— Method_evaluate!!(model::Model, varinfo, context)
Evaluate the model
with the arguments matching the given context
and varinfo
object.
DynamicPPL.acclogp!!
— Methodacclogp!!(vi::VarInfo, logp)
Add logp
to the value of the log of the joint probability of the observed data and parameters sampled in vi
, mutating if it makes sense.
DynamicPPL.build_model_info
— Methodbuild_model_info(input_expr)
Builds the model_info
dictionary from the model's expression.
DynamicPPL.build_output
— Methodbuild_output(modelinfo, linenumbernode)
Builds the output expression.
DynamicPPL.canview
— Methodcanview(lens, container)
Return true
if lens
can be used to view container
, and false
otherwise.
Examples
julia> canview(@lens(_.a), (a = 1.0, ))
true
julia> canview(@lens(_.a), (b = 1.0, )) # property `a` does not exist
false
julia> canview(@lens(_.a[1]), (a = [1.0, 2.0], ))
true
julia> canview(@lens(_.a[3]), (a = [1.0, 2.0], )) # out of bounds
false
DynamicPPL.check_tilde_rhs
— Methodcheck_tilde_rhs(x)
Check if the right-hand side x
of a ~
is a Distribution
or an array of Distributions
, then return x
.
DynamicPPL.childcontext
— Functionchildcontext(context)
Return the descendant context of context
.
DynamicPPL.conditioned
— Methodconditioned(context::AbstractContext)
Return NamedTuple
of values that are conditioned on under context`.
Note that this will recursively traverse the context stack and return a merged version of the condition values.
DynamicPPL.conditioned
— Methodconditioned(model::Model)
Return NamedTuple
of values that are conditioned on under model
.
Examples
julia> using Distributions
julia> using DynamicPPL: conditioned, contextualize
julia> @model function demo()
m ~ Normal()
x ~ Normal(m, 1)
end
demo (generic function with 2 methods)
julia> m = demo();
julia> # Returns all the variables we have conditioned on + their values.
conditioned(condition(m, x=100.0, m=1.0))
(x = 100.0, m = 1.0)
julia> # Nested ones also work (note that `PrefixContext` does nothing to the result).
cm = condition(contextualize(m, PrefixContext{:a}(condition(m=1.0))), x=100.0);
julia> conditioned(cm)
(x = 100.0, m = 1.0)
julia> # Since we conditioned on `m`, not `a.m` as it will appear after prefixed,
# `a.m` is treated as a random variable.
keys(VarInfo(cm))
1-element Vector{VarName{Symbol("a.m"), Tuple{}}}:
a.m
julia> # If we instead condition on `a.m`, `m` in the model will be considered an observation.
cm = condition(contextualize(m, PrefixContext{:a}(condition(var"a.m"=1.0))), x=100.0);
julia> conditioned(cm)
(x = 100.0, a.m = 1.0)
julia> keys(VarInfo(cm)) # <= no variables are sampled
Any[]
DynamicPPL.contextual_isassumption
— Methodcontextual_isassumption(context, vn)
Return true
if vn
is considered an assumption by context
.
The default implementation for AbstractContext
always returns true
.
DynamicPPL.dot_tilde_assume!!
— Methoddot_tilde_assume!!(context, right, left, vn, vi)
Handle broadcasted assumed variables, e.g., x .~ MvNormal()
(where x
does not occur in the model inputs), accumulate the log probability, and return the sampled value and updated vi
.
Falls back to dot_tilde_assume(context, right, left, vn, vi)
.
DynamicPPL.dot_tilde_assume
— Methoddot_tilde_assume(context::SamplingContext, right, left, vn, vi)
Handle broadcasted assumed variables, e.g., x .~ MvNormal()
(where x
does not occur in the model inputs), accumulate the log probability, and return the sampled value for a context associated with a sampler.
Falls back to
dot_tilde_assume(context.rng, context.context, context.sampler, right, left, vn, vi)
DynamicPPL.dot_tilde_observe!!
— Methoddot_tilde_observe!!(context, right, left, vi)
Handle broadcasted observed constants, e.g., [1.0] .~ MvNormal()
, accumulate the log probability, and return the observed value and updated vi
.
Falls back to dot_tilde_observe(context, right, left, vi)
.
DynamicPPL.dot_tilde_observe!!
— Methoddot_tilde_observe!!(context, right, left, vname, vi)
Handle broadcasted observed values, e.g., x .~ MvNormal()
(where x
does occur in the model inputs), accumulate the log probability, and return the observed value and updated vi
.
Falls back to dot_tilde_observe!!(context, right, left, vi)
ignoring the information about variable name and indices; if needed, these can be accessed through this function, though.
DynamicPPL.dot_tilde_observe
— Methoddot_tilde_observe(context::SamplingContext, right, left, vi)
Handle broadcasted observed constants, e.g., [1.0] .~ MvNormal()
, accumulate the log probability, and return the observed value for a context associated with a sampler.
Falls back to dot_tilde_observe(context.context, context.sampler, right, left, vi)
.
DynamicPPL.evaluate!!
— Methodevaluate!!(model::Model[, rng, varinfo, sampler, context])
Sample from the model
using the sampler
with random number generator rng
and the context
, and store the sample and log joint probability in varinfo
.
Returns both the return-value of the original model, and the resulting varinfo.
The method resets the log joint probability of varinfo
and increases the evaluation number of sampler
.
DynamicPPL.evaluate_threadsafe!!
— Methodevaluate_threadsafe!!(model, varinfo, context)
Evaluate the model
with varinfo
wrapped inside a ThreadSafeVarInfo
.
With the wrapper, Julia's multithreading can be used for observe statements in the model
but parallel sampling will lead to undefined behaviour. This method is not exposed and supposed to be used only internally in DynamicPPL.
See also: evaluate_threadunsafe!!
DynamicPPL.evaluate_threadunsafe!!
— Methodevaluate_threadunsafe!!(model, varinfo, context)
Evaluate the model
without wrapping varinfo
inside a ThreadSafeVarInfo
.
If the model
makes use of Julia's multithreading this will lead to undefined behaviour. This method is not exposed and supposed to be used only internally in DynamicPPL.
See also: evaluate_threadsafe!!
DynamicPPL.generate_dot_tilde
— Methodgenerate_dot_tilde(left, right)
Generate the expression that replaces left .~ right
in the model body.
DynamicPPL.generate_mainbody
— Methodgenerate_mainbody(mod, expr, warn)
Generate the body of the main evaluation function from expression expr
and arguments args
.
If warn
is true, a warning is displayed if internal variables are used in the model definition.
DynamicPPL.generate_tilde
— Methodgenerate_tilde(left, right)
Generate an observe
expression for data variables and assume
expression for parameter variables.
DynamicPPL.generated_quantities
— Methodgenerated_quantities(model::Model, chain::AbstractChains)
Execute model
for each of the samples in chain
and return an array of the values returned by the model
for each sample.
Examples
General
Often you might have additional quantities computed inside the model that you want to inspect, e.g.
@model function demo(x)
# sample and observe
θ ~ Prior()
x ~ Likelihood()
return interesting_quantity(θ, x)
end
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
# from `interesting_quantity(θ, x)`
Concrete (and simple)
julia> using DynamicPPL, Turing
julia> @model function demo(xs)
s ~ InverseGamma(2, 3)
m_shifted ~ Normal(10, √s)
m = m_shifted - 10
for i in eachindex(xs)
xs[i] ~ Normal(m, √s)
end
return (m, )
end
demo (generic function with 1 method)
julia> model = demo(randn(10));
julia> chain = sample(model, MH(), 10);
julia> generated_quantities(model, chain)
10×1 Array{Tuple{Float64},2}:
(2.1964758025119338,)
(2.1964758025119338,)
(0.09270081916291417,)
(0.09270081916291417,)
(0.09270081916291417,)
(0.09270081916291417,)
(0.09270081916291417,)
(0.043088571494005024,)
(-0.16489786710222099,)
(-0.16489786710222099,)
DynamicPPL.generated_quantities
— Methodgenerated_quantities(model::Model, parameters::NamedTuple)
generated_quantities(model::Model, values, keys)
generated_quantities(model::Model, values, keys)
Execute model
with variables keys
set to values
and return the values returned by the model
.
If a NamedTuple
is given, keys=keys(parameters)
and values=values(parameters)
.
Example
julia> using DynamicPPL, Distributions
julia> @model function demo(xs)
s ~ InverseGamma(2, 3)
m_shifted ~ Normal(10, √s)
m = m_shifted - 10
for i in eachindex(xs)
xs[i] ~ Normal(m, √s)
end
return (m, )
end
demo (generic function with 2 methods)
julia> model = demo(randn(10));
julia> parameters = (; s = 1.0, m_shifted=10);
julia> generated_quantities(model, parameters)
(0.0,)
julia> generated_quantities(model, values(parameters), keys(parameters))
(0.0,)
DynamicPPL.get_matching_type
— Methodget_matching_type(spl::AbstractSampler, vi, ::Type{T}) where {T}
Get the specialized version of type T
for sampler spl
.
For example, if T === Float64
and spl::Hamiltonian
, the matching type is eltype(vi[spl])
.
DynamicPPL.get_num_produce
— Methodget_num_produce(vi::VarInfo)
Return the num_produce
of vi
.
DynamicPPL.getall
— Methodgetall(vi::VarInfo)
Return the values of all the variables in vi
.
The values may or may not be transformed to Euclidean space.
DynamicPPL.getargnames
— Methodgetargnames(model::Model)
Get a tuple of the argument names of the model
.
DynamicPPL.getargs_assignment
— Methodgetargs_assignment(x)
Return the arguments L
and R
, if x
is an expression of the form L = R
, or nothing
otherwise.
DynamicPPL.getargs_dottilde
— Methodgetargs_dottilde(x)
Return the arguments L
and R
, if x
is an expression of the form L .~ R
or (~).(L, R)
, or nothing
otherwise.
DynamicPPL.getargs_tilde
— Methodgetargs_tilde(x)
Return the arguments L
and R
, if x
is an expression of the form L ~ R
, or nothing
otherwise.
DynamicPPL.getdist
— Methodgetdist(vi::VarInfo, vn::VarName)
Return the distribution from which vn
was sampled in vi
.
DynamicPPL.getgid
— Methodgetgid(vi::VarInfo, vn::VarName)
Return the set of sampler selectors associated with vn
in vi
.
DynamicPPL.getidx
— Methodgetidx(vi::VarInfo, vn::VarName)
Return the index of vn
in the metadata of vi
corresponding to vn
.
DynamicPPL.getlogp
— Methodgetlogp(vi::VarInfo)
Return the log of the joint probability of the observed data and parameters sampled in vi
.
DynamicPPL.getmetadata
— Methodgetmetadata(vi::VarInfo, vn::VarName)
Return the metadata in vi
that belongs to vn
.
DynamicPPL.getmissings
— Methodgetmissings(model::Model)
Get a tuple of the names of the missing arguments of the model
.
DynamicPPL.getrange
— Methodgetrange(vi::VarInfo, vn::VarName)
Return the index range of vn
in the metadata of vi
.
DynamicPPL.getranges
— Methodgetranges(vi::AbstractVarInfo, vns::Vector{<:VarName})
Return the indices of vns
in the metadata of vi
corresponding to vn
.
DynamicPPL.getval
— Methodgetval(vi::VarInfo, vns::Vector{<:VarName})
Return the value(s) of vns
.
The values may or may not be transformed to Euclidean space.
DynamicPPL.getval
— Methodgetval(vi::UntypedVarInfo, vview::Union{Int, UnitRange, Vector{Int}})
Return a view vi.vals[vview]
.
DynamicPPL.getval
— Methodgetval(vi::VarInfo, vn::VarName)
Return the value(s) of vn
.
The values may or may not be transformed to Euclidean space.
DynamicPPL.getvalue
— Methodgetvalue(context, vn)
Return value of vn
in context
.
DynamicPPL.getvalue_nested
— Methodgetvalue_nested(context, vn)
Return the value of the parameter corresponding to vn
from context
or its descendants.
This is contrast to getvalue
which only returns the value vn
in context
, not recursively looking into its descendants.
DynamicPPL.hasvalue
— Methodhasvalue(context, vn)
Return true
if vn
is found in context
.
DynamicPPL.hasvalue_nested
— Methodhasvalue_nested(context, vn)
Return true
if vn
is found in context
or any of its descendants.
This is contrast to hasvalue
which only checks for vn
in context
, not recursively checking if vn
is in any of its descendants.
DynamicPPL.inargnames
— Methodinargnames(varname::VarName, model::Model)
Statically check whether the variable of name varname
is an argument of the model
.
Possibly existing indices of varname
are neglected.
DynamicPPL.increment_num_produce!
— Methodincrement_num_produce!(vi::VarInfo)
Add 1 to num_produce
in vi
.
DynamicPPL.initialsampler
— Methodinitialsampler(sampler::Sampler)
Return the sampler that is used for generating the initial parameters when sampling with sampler
.
By default, it returns an instance of SampleFromPrior
.
DynamicPPL.initialstep
— Functioninitialstep(rng, model, sampler, varinfo; kwargs...)
Perform the initial sampling step of the sampler
for the model
.
The varinfo
contains the initial samples, which can be provided by the user or sampled randomly.
DynamicPPL.inmissings
— Methodinmissings(varname::VarName, model::Model)
Statically check whether the variable of name varname
is a statically declared unobserved variable of the model
.
Possibly existing indices of varname
are neglected.
DynamicPPL.invlink!
— Methodinvlink!(vi::VarInfo, spl::AbstractSampler)
Transform the values of the random variables sampled by spl
in vi
from the Euclidean space back to the support of their distributions and sets their corresponding "trans"
flag values to false
.
DynamicPPL.is_flagged
— Methodis_flagged(vi::VarInfo, vn::VarName, flag::String)
Check whether vn
has a true value for flag
in vi
.
DynamicPPL.isassumption
— Functionisassumption(expr[, vn])
Return an expression that can be evaluated to check if expr
is an assumption in the model.
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
.
When expr
is not an expression or symbol (i.e., a literal), this expands to false
.
If vn
is specified, it will be assumed to refer to a expression which evaluates to a VarName
, and this will be used in the subsequent checks. If vn
is not specified, AbstractPPL.drop_escape(varname(expr))
will be used in its place.
DynamicPPL.isfuncdef
— Methodisfuncdef(expr)
Return true
if expr
is any form of function definition, and false
otherwise.
DynamicPPL.islinked
— Methodislinked(vi::VarInfo, spl::Union{Sampler, SampleFromPrior})
Check whether vi
is in the transformed space for a particular sampler spl
.
Turing's Hamiltonian samplers use the link
and invlink
functions from Bijectors.jl to map a constrained variable (for example, one bounded to the space [0, 1]
) from its constrained space to the set of real numbers. islinked
checks if the number is in the constrained space or the real space.
DynamicPPL.isliteral
— Methodisliteral(expr)
Return true
if expr
is a literal, e.g. 1.0
or [1.0, ]
, and false
otherwise.
DynamicPPL.istrans
— Methodistrans(vi::VarInfo, vn::VarName)
Return true if vn
's values in vi
are transformed to Euclidean space, and false if they are in the support of vn
's distribution.
DynamicPPL.leafcontext
— Methodleafcontext(context)
Return the leaf of context
, i.e. the first descendant context that IsLeaf
.
DynamicPPL.link!
— Methodlink!(vi::VarInfo, spl::Sampler)
Transform the values of the random variables sampled by spl
in vi
from the support of their distributions to the Euclidean space and set their corresponding "trans"
flag values to true
.
DynamicPPL.loadstate
— Functionloadstate(data)
Load sampler state from data
.
DynamicPPL.logjoint
— Methodlogjoint(model::Model, varinfo::AbstractVarInfo)
Return the log joint probability of variables varinfo
for the probabilistic model
.
See logjoint
and loglikelihood
.
DynamicPPL.logjoint
— Methodlogjoint(model::Model, θ)
Return the log joint probability of variables θ
for the probabilistic model
.
See logjoint
and loglikelihood
.
Examples
julia> @model function demo(x)
m ~ Normal()
for i in eachindex(x)
x[i] ~ Normal(m, 1.0)
end
end
demo (generic function with 2 methods)
julia> # Using a `NamedTuple`.
logjoint(demo([1.0]), (m = 100.0, ))
-9902.33787706641
julia> # Using a `Dict`.
logjoint(demo([1.0]), Dict(@varname(m) => 100.0))
-9902.33787706641
julia> # Truth.
logpdf(Normal(100.0, 1.0), 1.0) + logpdf(Normal(), 100.0)
-9902.33787706641
DynamicPPL.logprior
— Methodlogprior(model::Model, varinfo::AbstractVarInfo)
Return the log prior probability of variables varinfo
for the probabilistic model
.
See also logjoint
and loglikelihood
.
DynamicPPL.logprior
— Methodlogprior(model::Model, θ)
Return the log prior probability of variables θ
for the probabilistic model
.
See also logjoint
and loglikelihood
.
Examples
julia> @model function demo(x)
m ~ Normal()
for i in eachindex(x)
x[i] ~ Normal(m, 1.0)
end
end
demo (generic function with 2 methods)
julia> # Using a `NamedTuple`.
logprior(demo([1.0]), (m = 100.0, ))
-5000.918938533205
julia> # Using a `Dict`.
logprior(demo([1.0]), Dict(@varname(m) => 100.0))
-5000.918938533205
julia> # Truth.
logpdf(Normal(), 100.0)
-5000.918938533205
DynamicPPL.matchingvalue
— Methodmatchingvalue(sampler, vi, value)
matchingvalue(context::AbstractContext, vi, value)
Convert the value
to the correct type for the sampler
or context
and the vi
object.
For a context
that is not a SamplingContext
, we fall back to matchingvalue(SampleFromPrior(), vi, value)
.
DynamicPPL.observations
— Methodobservations(model::Model)
Alias for conditioned
.
DynamicPPL.parent
— Methodparent(lens::Setfield.Lens)
Return the parent lens. If lens
doesn't have a parent, nothing
is returned.
See also: [parent_and_child
].
Examples
julia> parent(@lens(_.a[1]))
(@lens _.a)
julia> # Parent of lens without parents results in `nothing`.
(parent ∘ parent)(@lens(_.a[1])) === nothing
true
DynamicPPL.parent
— Methodparent(vn::VarName)
Return the parent VarName
.
Examples
julia> parent(@varname(x.a[1]))
x.a
julia> (parent ∘ parent)(@varname(x.a[1]))
x
julia> (parent ∘ parent ∘ parent)(@varname(x.a[1]))
x
DynamicPPL.parent_and_child
— Methodparent_and_child(lens::Setfield.Lens)
Return a 2-tuple of lenses (parent, child)
where parent
is the parent lens of lens
and child
is the child lens of lens
.
If lens
does not have a parent, we return (nothing, lens)
.
See also: [parent
].
Examples
julia> parent_and_child(@lens(_.a[1]))
((@lens _.a), (@lens _[1]))
julia> parent_and_child(@lens(_.a))
(nothing, (@lens _.a))
DynamicPPL.pointwise_loglikelihoods
— Methodpointwise_loglikelihoods(model::Model, chain::Chains, keytype = String)
Runs model
on each sample in chain
returning a Dict{String, Matrix{Float64}}
with keys corresponding to symbols of the observations, and values being matrices of shape (num_chains, num_samples)
.
keytype
specifies what the type of the keys used in the returned Dict
are. Currently, only String
and VarName
are supported.
Notes
Say y
is a Vector
of n
i.i.d. Normal(μ, σ)
variables, with μ
and σ
both being <:Real
. Then the observe (i.e. when the left-hand side is an observation) statements can be implemented in three ways:
- using a
for
loop:
for i in eachindex(y)
y[i] ~ Normal(μ, σ)
end
- using
.~
:
y .~ Normal(μ, σ)
- using
MvNormal
:
y ~ MvNormal(fill(μ, n), σ^2 * I)
In (1) and (2), y
will be treated as a collection of n
i.i.d. 1-dimensional variables, while in (3) y
will be treated as a single n-dimensional observation.
This is important to keep in mind, in particular if the computation is used for downstream computations.
Examples
From chain
julia> using DynamicPPL, Turing
julia> @model function demo(xs, y)
s ~ InverseGamma(2, 3)
m ~ Normal(0, √s)
for i in eachindex(xs)
xs[i] ~ Normal(m, √s)
end
y ~ Normal(m, √s)
end
demo (generic function with 1 method)
julia> model = demo(randn(3), randn());
julia> chain = sample(model, MH(), 10);
julia> pointwise_loglikelihoods(model, chain)
Dict{String,Array{Float64,2}} with 4 entries:
"xs[3]" => [-1.42862; -2.67573; … ; -1.66251; -1.66251]
"xs[1]" => [-1.42932; -2.68123; … ; -1.66333; -1.66333]
"xs[2]" => [-1.6724; -0.861339; … ; -1.62359; -1.62359]
"y" => [-1.51265; -0.914129; … ; -1.5499; -1.5499]
julia> pointwise_loglikelihoods(model, chain, String)
Dict{String,Array{Float64,2}} with 4 entries:
"xs[3]" => [-1.42862; -2.67573; … ; -1.66251; -1.66251]
"xs[1]" => [-1.42932; -2.68123; … ; -1.66333; -1.66333]
"xs[2]" => [-1.6724; -0.861339; … ; -1.62359; -1.62359]
"y" => [-1.51265; -0.914129; … ; -1.5499; -1.5499]
julia> pointwise_loglikelihoods(model, chain, VarName)
Dict{VarName,Array{Float64,2}} with 4 entries:
xs[2] => [-1.6724; -0.861339; … ; -1.62359; -1.62359]
y => [-1.51265; -0.914129; … ; -1.5499; -1.5499]
xs[1] => [-1.42932; -2.68123; … ; -1.66333; -1.66333]
xs[3] => [-1.42862; -2.67573; … ; -1.66251; -1.66251]
Broadcasting
Note that x .~ Dist()
will treat x
as a collection of independent observations rather than as a single observation.
julia> @model function demo(x)
x .~ Normal()
end;
julia> m = demo([1.0, ]);
julia> ℓ = pointwise_loglikelihoods(m, VarInfo(m)); first(ℓ[@varname(x[1])])
-1.4189385332046727
julia> m = demo([1.0; 1.0]);
julia> ℓ = pointwise_loglikelihoods(m, VarInfo(m)); first.((ℓ[@varname(x[1])], ℓ[@varname(x[2])]))
(-1.4189385332046727, -1.4189385332046727)
DynamicPPL.replace_returns
— Methodreplace_returns(expr)
Return Expr
with all return ...
statements replaced with return ..., DynamicPPL.return_values(__varinfo__)
.
Note that this method will not replace return
statements within function definitions. This is checked using isfuncdef
.
DynamicPPL.reset_num_produce!
— Methodreset_num_produce!(vi::AbstractVarInfo)
Reset the value of num_produce
the log of the joint probability of the observed data and parameters sampled in vi
to 0.
DynamicPPL.resetlogp!!
— Methodresetlogp!!(vi::AbstractVarInfo)
Reset the value of the log of the joint probability of the observed data and parameters sampled in vi
to 0, mutating if it makes sense.
DynamicPPL.set_flag!
— Methodset_flag!(vi::VarInfo, vn::VarName, flag::String)
Set vn
's value for flag
to true
in vi
.
DynamicPPL.set_num_produce!
— Methodset_num_produce!(vi::VarInfo, n::Int)
Set the num_produce
field of vi
to n
.
DynamicPPL.set_retained_vns_del_by_spl!
— Methodset_retained_vns_del_by_spl!(vi::VarInfo, spl::Sampler)
Set the "del"
flag of variables in vi
with order > vi.num_produce[]
to true
.
DynamicPPL.setall!
— Methodsetall!(vi::VarInfo, val)
Set the values of all the variables in vi
to val
.
The values may or may not be transformed to Euclidean space.
DynamicPPL.setchildcontext
— Functionsetchildcontext(parent::AbstractContext, child::AbstractContext)
Reconstruct parent
but now using child
is its childcontext
, effectively updating the child context.
Examples
julia> ctx = SamplingContext();
julia> DynamicPPL.childcontext(ctx)
DefaultContext()
julia> ctx_prior = DynamicPPL.setchildcontext(ctx, PriorContext()); # only compute the logprior
julia> DynamicPPL.childcontext(ctx_prior)
PriorContext{Nothing}(nothing)
DynamicPPL.setgid!
— Methodsetgid!(vi::VarInfo, gid::Selector, vn::VarName)
Add gid
to the set of sampler selectors associated with vn
in vi
.
DynamicPPL.setleafcontext
— Methodsetleafcontext(left, right)
Return left
but now with its leaf context replaced by right
.
Note that this also works even if right
is not a leaf context, in which case effectively append right
to left
, dropping the original leaf context of left
.
Examples
julia> using DynamicPPL: leafcontext, setleafcontext, childcontext, setchildcontext, AbstractContext
julia> struct ParentContext{C} <: AbstractContext
context::C
end
julia> DynamicPPL.NodeTrait(::ParentContext) = DynamicPPL.IsParent()
julia> DynamicPPL.childcontext(context::ParentContext) = context.context
julia> DynamicPPL.setchildcontext(::ParentContext, child) = ParentContext(child)
julia> Base.show(io::IO, c::ParentContext) = print(io, "ParentContext(", childcontext(c), ")")
julia> ctx = ParentContext(ParentContext(DefaultContext()))
ParentContext(ParentContext(DefaultContext()))
julia> # Replace the leaf context with another leaf.
leafcontext(setleafcontext(ctx, PriorContext()))
PriorContext{Nothing}(nothing)
julia> # Append another parent context.
setleafcontext(ctx, ParentContext(DefaultContext()))
ParentContext(ParentContext(ParentContext(DefaultContext())))
DynamicPPL.setlogp!!
— Methodsetlogp!!(vi::VarInfo, logp)
Set the log of the joint probability of the observed data and parameters sampled in vi
to logp
, mutating if it makes sense.
DynamicPPL.setorder!
— Methodsetorder!(vi::VarInfo, vn::VarName, index::Int)
Set the order
of vn
in vi
to index
, where order
is the number of observe statements run before sampling
vn`.
DynamicPPL.settrans!
— Methodsettrans!(vi::VarInfo, trans::Bool, vn::VarName)
Set the trans
flag value of vn
in vi
.
DynamicPPL.setval!
— Methodsetval!(vi::AbstractVarInfo, x)
setval!(vi::AbstractVarInfo, values, keys)
setval!(vi::AbstractVarInfo, chains::AbstractChains, sample_idx::Int, chain_idx::Int)
Set the values in vi
to the provided values and leave those which are not present in x
or chains
unchanged.
Notes
This is rather limited for two reasons:
- It uses
subsumes_string(string(vn), map(string, keys))
under the hood, and therefore suffers from the same limitations assubsumes_string
. - It will set every
vn
present inkeys
. It will NOT however set everyk
present inkeys
. This means that ifvn == [m[1], m[2]]
, representing some variablem
, callingsetval!(vi, (m = [1.0, 2.0]))
will be a no-op since it will try to findm[1]
andm[2]
inkeys((m = [1.0, 2.0]))
.
Example
julia> using DynamicPPL, Distributions, StableRNGs
julia> @model function demo(x)
m ~ Normal()
for i in eachindex(x)
x[i] ~ Normal(m, 1)
end
end;
julia> rng = StableRNG(42);
julia> m = demo([missing]);
julia> var_info = DynamicPPL.VarInfo(rng, m);
julia> var_info[@varname(m)]
-0.6702516921145671
julia> var_info[@varname(x[1])]
-0.22312984965118443
julia> DynamicPPL.setval!(var_info, (m = 100.0, )); # set `m` and and keep `x[1]`
julia> var_info[@varname(m)] # [✓] changed
100.0
julia> var_info[@varname(x[1])] # [✓] unchanged
-0.22312984965118443
julia> m(rng, var_info); # rerun model
julia> var_info[@varname(m)] # [✓] unchanged
100.0
julia> var_info[@varname(x[1])] # [✓] unchanged
-0.22312984965118443
DynamicPPL.setval!
— Methodsetval!(vi::UntypedVarInfo, val, vview::Union{Int, UnitRange, Vector{Int}})
Set the value of vi.vals[vview]
to val
.
DynamicPPL.setval!
— Methodsetval!(vi::VarInfo, val, vn::VarName)
Set the value(s) of vn
in the metadata of vi
to val
.
The values may or may not be transformed to Euclidean space.
DynamicPPL.setval_and_resample!
— Methodsetval_and_resample!(vi::AbstractVarInfo, x)
setval_and_resample!(vi::AbstractVarInfo, values, keys)
setval_and_resample!(vi::AbstractVarInfo, chains::AbstractChains, sample_idx, chain_idx)
Set the values in vi
to the provided values and those which are not present in x
or chains
to be resampled.
Note that this does not resample the values not provided! It will call setflag!(vi, vn, "del")
for variables vn
for which no values are provided, which means that the next time we call model(vi)
these variables will be resampled.
Note
- This suffers from the same limitations as
setval!
. Seesetval!
for more info.
Example
julia> using DynamicPPL, Distributions, StableRNGs
julia> @model function demo(x)
m ~ Normal()
for i in eachindex(x)
x[i] ~ Normal(m, 1)
end
end;
julia> rng = StableRNG(42);
julia> m = demo([missing]);
julia> var_info = DynamicPPL.VarInfo(rng, m);
julia> var_info[@varname(m)]
-0.6702516921145671
julia> var_info[@varname(x[1])]
-0.22312984965118443
julia> DynamicPPL.setval_and_resample!(var_info, (m = 100.0, )); # set `m` and ready `x[1]` for resampling
julia> var_info[@varname(m)] # [✓] changed
100.0
julia> var_info[@varname(x[1])] # [✓] unchanged
-0.22312984965118443
julia> m(rng, var_info); # sample `x[1]` conditioned on `m = 100.0`
julia> var_info[@varname(m)] # [✓] unchanged
100.0
julia> var_info[@varname(x[1])] # [✓] changed
101.37363069798343
See also
DynamicPPL.splitlens
— Methodsplitlens(condition, lens)
Return a 3-tuple (parent, child, issuccess)
where, if issuccess
is true
, parent
is a lens such that condition(parent)
is true
and parent ∘ child == lens
.
If issuccess
is false
, then no such split could be found.
Examples
julia> p, c, issucesss = splitlens(@lens(_.a[1])) do parent
# Succeeds!
parent == @lens(_.a)
end
((@lens _.a), (@lens _[1]), true)
julia> p ∘ c
(@lens _.a[1])
julia> splitlens(@lens(_.a[1])) do parent
# Fails!
parent == @lens(_.b)
end
(nothing, (@lens _.a[1]), false)
DynamicPPL.subsumes_string
— Functionsubsumes_string(u::String, v::String[, u_indexing])
Check whether stringified variable name v
describes a sub-range of stringified variable u
.
This is a very restricted version subumes(u::VarName, v::VarName)
only really supporting:
- Scalar:
x
subsumesx[1, 2]
,x[1, 2]
subsumesx[1, 2][3]
, etc.
Note
- To get same matching capabilities as
AbstractPPL.subumes(u::VarName, v::VarName)
for strings, one can always doeval(varname(Meta.parse(u))
to getVarName
ofu
, and similarly tov
. But this is slow.
DynamicPPL.syms
— Methodsyms(vi::VarInfo)
Returns a tuple of the unique symbols of random variables sampled in vi
.
DynamicPPL.tilde_assume!!
— Methodtilde_assume!!(context, right, vn, vi)
Handle assumed variables, e.g., x ~ Normal()
(where x
does occur in the model inputs), accumulate the log probability, and return the sampled value and updated vi
.
By default, calls tilde_assume(context, right, vn, vi)
and accumulates the log probability of vi
with the returned value.
DynamicPPL.tilde_assume
— Methodtilde_assume(context::SamplingContext, right, vn, vi)
Handle assumed variables, e.g., x ~ Normal()
(where x
does occur in the model inputs), accumulate the log probability, and return the sampled value with a context associated with a sampler.
Falls back to
tilde_assume(context.rng, context.context, context.sampler, right, vn, vi)
DynamicPPL.tilde_observe!!
— Methodtilde_observe(context, right, left, vi)
Handle observed constants, e.g., 1.0 ~ Normal()
, accumulate the log probability, and return the observed value.
By default, calls tilde_observe(context, right, left, vi)
and accumulates the log probability of vi
with the returned value.
DynamicPPL.tilde_observe!!
— Methodtilde_observe!!(context, right, left, vname, vi)
Handle observed variables, e.g., x ~ Normal()
(where x
does occur in the model inputs), accumulate the log probability, and return the observed value and updated vi
.
Falls back to tilde_observe!!(context, right, left, vi)
ignoring the information about variable name and indices; if needed, these can be accessed through this function, though.
DynamicPPL.tilde_observe
— Methodtilde_observe(context::SamplingContext, right, left, vi)
Handle observed constants with a context
associated with a sampler.
Falls back to tilde_observe(context.context, context.sampler, right, left, vi)
.
DynamicPPL.tonamedtuple
— Methodtonamedtuple(vi::VarInfo)
Convert a vi
into a NamedTuple
where each variable symbol maps to the values and indexing string of the variable.
For example, a model that had a vector of vector-valued variables x
would return
(x = ([1.5, 2.0], [3.0, 1.0], ["x[1]", "x[2]"]), )
DynamicPPL.unset_flag!
— Methodunset_flag!(vi::VarInfo, vn::VarName, flag::String)
Set vn
's value for flag
to false
in vi
.
DynamicPPL.unwrap_right_left_vns
— Methodunwrap_right_left_vns(right, left, vns)
Return the unwrapped distributions on the right-hand side and values and variable names on the left-hand side of a .~
expression such as x .~ Normal()
.
This is used mainly to unwrap NamedDist
distributions and adjust the indices of the variables.
Example
julia> _, _, vns = DynamicPPL.unwrap_right_left_vns(MvNormal(ones(2), I), randn(2, 2), @varname(x)); vns[end]
x[:,2]
julia> _, _, vns = DynamicPPL.unwrap_right_left_vns(Normal(), randn(1, 2), @varname(x)); vns[end]
x[1,2]
julia> _, _, vns = DynamicPPL.unwrap_right_left_vns(Normal(), randn(1, 2), @varname(x[:])); vns[end]
x[:][1,2]
julia> _, _, vns = DynamicPPL.unwrap_right_left_vns(Normal(), randn(3), @varname(x[1])); vns[end]
x[1][3]
DynamicPPL.unwrap_right_vn
— Methodunwrap_right_vn(right, vn)
Return the unwrapped distribution on the right-hand side and variable name on the left-hand side of a ~
expression such as x ~ Normal()
.
This is used mainly to unwrap NamedDist
distributions.
DynamicPPL.updategid!
— Methodupdategid!(vi::VarInfo, vn::VarName, spl::Sampler)
Set vn
's gid
to Set([spl.selector])
, if vn
does not have a sampler selector linked and vn
's symbol is in the space of spl
.
DynamicPPL.values_as
— Methodvalues_as(varinfo[, Type])
Return the values/realizations in varinfo
as Type
, if implemented.
If no Type
is provided, return values as stored in varinfo
.
DynamicPPL.values_as
— Methodvalues_as(vi::AbstractVarInfo, ::Type{NamedTuple})
values_as(vi::AbstractVarInfo, ::Type{Dict})
Return values in vi
as the specified type.
DynamicPPL.values_as
— Methodvalues_as(vi::AbstractVarInfo)
StatsBase.loglikelihood
— MethodStatsBase.loglikelihood
— Methodloglikelihood(model::Model, θ)
Return the log likelihood of variables θ
for the probabilistic model
.
See also logjoint
and logprior
.
Examples
julia> @model function demo(x)
m ~ Normal()
for i in eachindex(x)
x[i] ~ Normal(m, 1.0)
end
end
demo (generic function with 2 methods)
julia> # Using a `NamedTuple`.
loglikelihood(demo([1.0]), (m = 100.0, ))
-4901.418938533205
julia> # Using a `Dict`.
loglikelihood(demo([1.0]), Dict(@varname(m) => 100.0))
-4901.418938533205
julia> # Truth.
logpdf(Normal(100.0, 1.0), 1.0)
-4901.418938533205
DynamicPPL.@addlogprob!
— Macro@addlogprob!(ex)
Add the result of the evaluation of ex
to the joint log probability.
DynamicPPL.@model
— Macro@model(expr[, warn = false])
Macro to specify a probabilistic model.
If warn
is true
, a warning is displayed if internal variable names are used in the model definition.
Examples
Model definition:
@model function model(x, y = 42)
...
end
To generate a Model
, call model(xvalue)
or model(xvalue, yvalue)
.
DynamicPPL.@submodel
— Macro@submodel prefix=... model
@submodel prefix=... ... = model
Run a Turing model
nested inside of a Turing model and add "prefix
." as a prefix to all random variables inside of the model
.
Valid expressions for prefix=...
are:
prefix=false
: no prefix is used.prefix=true
: attempt to automatically determine the prefix from the left-hand side... = model
by first converting into aVarName
, and then callingSymbol
on this.prefix=expression
: results in the prefixSymbol(expression)
.
The prefix makes it possible to run the same Turing model multiple times while keeping track of all random variables correctly.
Examples
Example models
julia> @model function demo1(x)
x ~ Normal()
return 1 + abs(x)
end;
julia> @model function demo2(x, y, z)
@submodel prefix="sub1" a = demo1(x)
@submodel prefix="sub2" b = demo1(y)
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:
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 since they can be computed from the random variables sub1.x
and sub2.x
that were tracked when running demo1
:
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:
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
julia> @model inner() = x ~ Normal()
inner (generic function with 2 methods)
julia> # When `prefix` is unspecified, no prefix is used.
@model outer() = @submodel a = inner()
outer (generic function with 2 methods)
julia> @varname(x) in keys(VarInfo(outer()))
true
julia> # Explicitely don't use any prefix.
@model outer() = @submodel prefix=false a = inner()
outer (generic function with 2 methods)
julia> @varname(x) in keys(VarInfo(outer()))
true
julia> # Automatically determined from `a`.
@model outer() = @submodel prefix=true a = inner()
outer (generic function with 2 methods)
julia> @varname(var"a.x") in keys(VarInfo(outer()))
true
julia> # Using a static string.
@model outer() = @submodel prefix="my prefix" a = inner()
outer (generic function with 2 methods)
julia> @varname(var"my prefix.x") in keys(VarInfo(outer()))
true
julia> # Using string interpolation.
@model outer() = @submodel prefix="$(inner().name)" a = inner()
outer (generic function with 2 methods)
julia> @varname(var"inner.x") in keys(VarInfo(outer()))
true
julia> # Or using some arbitrary expression.
@model outer() = @submodel prefix=1 + 2 a = inner()
outer (generic function with 2 methods)
julia> @varname(var"3.x") in keys(VarInfo(outer()))
true
julia> # (×) Automatic prefixing without a left-hand side expression does not work!
@model outer() = @submodel prefix=true inner()
ERROR: LoadError: cannot automatically prefix with no left-hand side
[...]
Notes
- The choice
prefix=expression
means that the prefixing will incur a runtime cost. This is also the case forprefix=true
, depending on whether the expression on the the right-hand side of... = model
requires runtime-information or not, e.g.x = model
will result in the static prefixx
, whilex[i] = model
will be resolved at runtime.
DynamicPPL.@submodel
— Macro@submodel model
@submodel ... = model
Run a Turing model
nested inside of a Turing model.
Examples
julia> @model function demo1(x)
x ~ Normal()
return 1 + abs(x)
end;
julia> @model function demo2(x, y)
@submodel a = demo1(x)
return y ~ Uniform(0, a)
end;
When we sample from the model demo2(missing, 0.4)
random variable x
will be sampled:
julia> vi = VarInfo(demo2(missing, 0.4));
julia> @varname(x) in keys(vi)
true
Variable a
is not tracked since it can be computed from the random variable x
that was tracked when running demo1
:
julia> @varname(a) in keys(vi)
false
We can check that the log joint probability of the model accumulated in vi
is correct:
julia> x = vi[@varname(x)];
julia> getlogp(vi) ≈ logpdf(Normal(), x) + logpdf(Uniform(0, 1 + abs(x)), 0.4)
true