DynamicPPL

DynamicPPL.AbstractVarInfoType
AbstractVarInfo

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.DefaultContextType
struct 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.IsLeafType
IsLeaf

Specifies that the context is a leaf in the context-tree.

DynamicPPL.IsParentType
IsParent

Specifies that the context is a parent in the context-tree.

DynamicPPL.LikelihoodContextType
struct 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.MetadataType

The 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 all VarName instances.
  • md.idcs is the dictionary that maps each VarName 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 of vn.
  • md.gids[md.idcs[vn]] is the set of algorithms used to sample vn. This is used in

the Gibbs sampling process.

  • md.orders[md.idcs[vn]] is the number of observe statements before vn is sampled.
  • md.ranges[md.idcs[vn]] is the index range of vn in md.vals.
  • md.vals[md.ranges[md.idcs[vn]]] is the vector of values of corresponding to vn.
  • 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.MiniBatchContextType
struct 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.ModelType
struct 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.ModelMethod
(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.ModelMethod
Model{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.ModelMethod
Model(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.NamedDistType

A named distribution that carries the name of the random variable with it.

DynamicPPL.NodeTraitType
NodeTrait(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.PrefixContextType
PrefixContext{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.PriorContextType
struct PriorContext{Tvars} <: AbstractContext
    vars::Tvars
end

The PriorContext enables the computation of the log prior of the parameters vars when running the model.

DynamicPPL.SampleFromPriorType
SampleFromPrior

Sampling algorithm that samples unobserved random variables from their prior distribution.

DynamicPPL.SamplerType
Sampler{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.SamplingContextType
SamplingContext(
        [rng::Random.AbstractRNG=Random.GLOBAL_RNG],
        [sampler::AbstractSampler=SampleFromPrior()],
        [context::AbstractContext=DefaultContext()],
)

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.SimpleVarInfoType
SimpleVarInfo{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:

  1. SimpleVarInfo does not require linearization.
  2. SimpleVarInfo can use more efficient bijectors.
  3. SimpleVarInfo is only type-stable if NT<: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.TypedVarInfoMethod
TypedVarInfo(vi::UntypedVarInfo)

This function finds all the unique syms 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.VarInfoType
struct 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.conditionMethod
condition(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.conditionMethod
condition([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.deconditionMethod
decondition(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.deconditionMethod
decondition(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)
AbstractPPL.evaluate!!Method
evaluate!!(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.

BangBang.empty!!Method
empty!!(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!!Method
push!!(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!!Method
push!!(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!!Method
push!!(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.:|Method
model | (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!Method
empty!(meta::Metadata)

Empty the fields of meta.

This is useful when using a sampling algorithm that assumes an empty meta, e.g. SMC.

Base.getindexMethod
getindex(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.getindexMethod
getindex(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.haskeyMethod
haskey(vi::VarInfo, vn::VarName)

Check whether vn has been sampled in vi.

Base.isemptyMethod
isempty(vi::VarInfo)

Return true if vi is empty and false otherwise.

Base.keysMethod
keys(vi::SimpleVarInfo)

Return an iterator of keys present in vi.

Base.keysMethod
keys(vi::AbstractVarInfo)

Return an iterator over all vns in vi.

Base.nameofMethod
nameof(model::Model)

Get the name of the model as Symbol.

Base.randMethod
rand([rng=Random.GLOBAL_RNG], [T=NamedTuple], model::Model)

Generate a sample of type T from the prior distribution of the model.

Base.setindex!Method
setindex!(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!Method
setindex!(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!!Method
acclogp!!(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.canviewMethod
canview(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_rhsMethod
check_tilde_rhs(x)

Check if the right-hand side x of a ~ is a Distribution or an array of Distributions, then return x.

DynamicPPL.conditionedMethod
conditioned(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.conditionedMethod
conditioned(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_isassumptionMethod
contextual_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!!Method
dot_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_assumeMethod
dot_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!!Method
dot_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!!Method
dot_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_observeMethod
dot_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_threadsafe!!Method
evaluate_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!!Method
evaluate_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_mainbodyMethod
generate_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_tildeMethod
generate_tilde(left, right)

Generate an observe expression for data variables and assume expression for parameter variables.

DynamicPPL.generated_quantitiesMethod
generated_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_quantitiesMethod
generated_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_typeMethod
get_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.getallMethod
getall(vi::VarInfo)

Return the values of all the variables in vi.

The values may or may not be transformed to Euclidean space.

DynamicPPL.getargs_assignmentMethod
getargs_assignment(x)

Return the arguments L and R, if x is an expression of the form L = R, or nothing otherwise.

DynamicPPL.getargs_dottildeMethod
getargs_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_tildeMethod
getargs_tilde(x)

Return the arguments L and R, if x is an expression of the form L ~ R, or nothing otherwise.

DynamicPPL.getdistMethod
getdist(vi::VarInfo, vn::VarName)

Return the distribution from which vn was sampled in vi.

DynamicPPL.getgidMethod
getgid(vi::VarInfo, vn::VarName)

Return the set of sampler selectors associated with vn in vi.

DynamicPPL.getidxMethod
getidx(vi::VarInfo, vn::VarName)

Return the index of vn in the metadata of vi corresponding to vn.

DynamicPPL.getlogpMethod
getlogp(vi::VarInfo)

Return the log of the joint probability of the observed data and parameters sampled in vi.

DynamicPPL.getmetadataMethod
getmetadata(vi::VarInfo, vn::VarName)

Return the metadata in vi that belongs to vn.

DynamicPPL.getmissingsMethod
getmissings(model::Model)

Get a tuple of the names of the missing arguments of the model.

DynamicPPL.getrangeMethod
getrange(vi::VarInfo, vn::VarName)

Return the index range of vn in the metadata of vi.

DynamicPPL.getrangesMethod
getranges(vi::AbstractVarInfo, vns::Vector{<:VarName})

Return the indices of vns in the metadata of vi corresponding to vn.

DynamicPPL.getvalMethod
getval(vi::VarInfo, vns::Vector{<:VarName})

Return the value(s) of vns.

The values may or may not be transformed to Euclidean space.

DynamicPPL.getvalMethod
getval(vi::UntypedVarInfo, vview::Union{Int, UnitRange, Vector{Int}})

Return a view vi.vals[vview].

DynamicPPL.getvalMethod
getval(vi::VarInfo, vn::VarName)

Return the value(s) of vn.

The values may or may not be transformed to Euclidean space.

DynamicPPL.getvalue_nestedMethod
getvalue_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_nestedMethod
hasvalue_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.inargnamesMethod
inargnames(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.initialsamplerMethod
initialsampler(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.initialstepFunction
initialstep(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.inmissingsMethod
inmissings(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!Method
invlink!(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_flaggedMethod
is_flagged(vi::VarInfo, vn::VarName, flag::String)

Check whether vn has a true value for flag in vi.

DynamicPPL.isassumptionFunction
isassumption(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.isfuncdefMethod
isfuncdef(expr)

Return true if expr is any form of function definition, and false otherwise.

DynamicPPL.islinkedMethod
islinked(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.isliteralMethod
isliteral(expr)

Return true if expr is a literal, e.g. 1.0 or [1.0, ], and false otherwise.

DynamicPPL.istransMethod
istrans(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.leafcontextMethod
leafcontext(context)

Return the leaf of context, i.e. the first descendant context that IsLeaf.

DynamicPPL.link!Method
link!(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.logjointMethod
logjoint(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.logpriorMethod
logprior(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.matchingvalueMethod
matchingvalue(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.parentMethod
parent(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.parentMethod
parent(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_childMethod
parent_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_loglikelihoodsMethod
pointwise_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:

  1. using a for loop:
for i in eachindex(y)
    y[i] ~ Normal(μ, σ)
end
  1. using .~:
y .~ Normal(μ, σ)
  1. 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_returnsMethod
replace_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!Method
reset_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!!Method
resetlogp!!(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!Method
set_flag!(vi::VarInfo, vn::VarName, flag::String)

Set vn's value for flag to true in vi.

DynamicPPL.setall!Method
setall!(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.setchildcontextFunction
setchildcontext(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!Method
setgid!(vi::VarInfo, gid::Selector, vn::VarName)

Add gid to the set of sampler selectors associated with vn in vi.

DynamicPPL.setleafcontextMethod
setleafcontext(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!!Method
setlogp!!(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!Method
setorder!(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 samplingvn`.

DynamicPPL.settrans!Method
settrans!(vi::VarInfo, trans::Bool, vn::VarName)

Set the trans flag value of vn in vi.

DynamicPPL.setval!Method
setval!(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:

  1. It uses subsumes_string(string(vn), map(string, keys)) under the hood, and therefore suffers from the same limitations as subsumes_string.
  2. It will set every vn present in keys. It will NOT however set every k present in keys. This means that if vn == [m[1], m[2]], representing some variable m, calling setval!(vi, (m = [1.0, 2.0])) will be a no-op since it will try to find m[1] and m[2] in keys((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!Method
setval!(vi::UntypedVarInfo, val, vview::Union{Int, UnitRange, Vector{Int}})

Set the value of vi.vals[vview] to val.

DynamicPPL.setval!Method
setval!(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!Method
setval_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!. See setval! 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.splitlensMethod
splitlens(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_stringFunction
subsumes_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 subsumes x[1, 2], x[1, 2] subsumes x[1, 2][3], etc.

Note

  • To get same matching capabilities as AbstractPPL.subumes(u::VarName, v::VarName) for strings, one can always do eval(varname(Meta.parse(u)) to get VarName of u, and similarly to v. But this is slow.
DynamicPPL.symsMethod
syms(vi::VarInfo)

Returns a tuple of the unique symbols of random variables sampled in vi.

DynamicPPL.tilde_assume!!Method
tilde_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_assumeMethod
tilde_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!!Method
tilde_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!!Method
tilde_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_observeMethod
tilde_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.tonamedtupleMethod
tonamedtuple(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!Method
unset_flag!(vi::VarInfo, vn::VarName, flag::String)

Set vn's value for flag to false in vi.

DynamicPPL.unwrap_right_left_vnsMethod
unwrap_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_vnMethod
unwrap_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!Method
updategid!(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.use_threadsafe_evalMethod
use_threadsafe_eval(context::AbstractContext, varinfo::AbstractVarInfo)

Return true if evaluation of a model using context and varinfo should wrap varinfo in ThreadSafeVarInfo, i.e. threadsafe evaluation, and false otherwise.

DynamicPPL.values_asMethod
values_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_asMethod
values_as(vi::AbstractVarInfo, ::Type{NamedTuple})
values_as(vi::AbstractVarInfo, ::Type{Dict})

Return values in vi as the specified type.

StatsAPI.loglikelihoodMethod
loglikelihood(model::Model, varinfo::AbstractVarInfo)

Return the log likelihood of variables varinfo for the probabilistic model.

See also logjoint and logprior.

StatsAPI.loglikelihoodMethod
loglikelihood(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.@modelMacro
@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.@submodelMacro
@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 a VarName, and then calling Symbol on this.
  • prefix=expression: results in the prefix Symbol(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 for prefix=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 prefix x, while x[i] = model will be resolved at runtime.
DynamicPPL.@submodelMacro
@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