API

VarNames

AbstractPPL.VarNameType
VarName{sym}(optic=identity)

A variable identifier for a symbol sym and optic optic.

The Julia variable in the model corresponding to sym can refer to a single value or to a hierarchical array structure of univariate, multivariate or matrix variables. The field lens stores the indices requires to access the random variable from the Julia variable indicated by sym as a tuple of tuples. Each element of the tuple thereby contains the indices of one optic operation.

VarNames can be manually constructed using the VarName{sym}(optic) constructor, or from an optic expression through the @varname convenience macro.

Examples

julia> vn = VarName{:x}(Accessors.IndexLens((Colon(), 1)) ⨟ Accessors.IndexLens((2, )))
x[:, 1][2]

julia> getoptic(vn)
(@o _[Colon(), 1][2])

julia> @varname x[:, 1][1+1]
x[:, 1][2]
source
AbstractPPL.getsymFunction
getsym(vn::VarName)

Return the symbol of the Julia variable used to generate vn.

Examples

julia> getsym(@varname(x[1][2:3]))
:x

julia> getsym(@varname(y))
:y
source
AbstractPPL.getopticFunction
getoptic(vn::VarName)

Return the optic of the Julia variable used to generate vn.

Examples

julia> getoptic(@varname(x[1][2:3]))
(@o _[1][2:3])

julia> getoptic(@varname(y))
identity (generic function with 1 method)
source
AbstractPPL.inspaceFunction
inspace(vn::Union{VarName, Symbol}, space::Tuple)

Check whether vn's variable symbol is in space. The empty tuple counts as the "universal space" containing all variables. Subsumption (see subsumes) is respected.

Examples

julia> inspace(@varname(x[1][2:3]), ())
true

julia> inspace(@varname(x[1][2:3]), (:x,))
true

julia> inspace(@varname(x[1][2:3]), (@varname(x),))
true

julia> inspace(@varname(x[1][2:3]), (@varname(x[1:10]), :y))
true

julia> inspace(@varname(x[1][2:3]), (@varname(x[:][2:4]), :y))
true

julia> inspace(@varname(x[1][2:3]), (@varname(x[1:10]),))
true
source
AbstractPPL.subsumesFunction
subsumes(u::VarName, v::VarName)

Check whether the variable name v describes a sub-range of the variable u. Supported indexing:

  • Scalar:

```jldoctest julia> subsumes(@varname(x), @varname(x[1, 2])) true

julia> subsumes(@varname(x[1, 2]), @varname(x[1, 2][3])) true ```

  • Array of scalar: basically everything that fulfills issubset.

```jldoctest julia> subsumes(@varname(x[[1, 2], 3]), @varname(x[1, 3])) true

julia> subsumes(@varname(x[1:3]), @varname(x[2][1])) true ```

  • Slices:

jldoctest julia> subsumes(@varname(x[2, :]), @varname(x[2, 10][1])) true

Currently not supported are:

  • Boolean indexing, literal CartesianIndex (these could be added, though)
  • Linear indexing of multidimensional arrays: x[4] does not subsume x[2, 2] for a matrix x
  • Trailing ones: x[2, 1] does not subsume x[2] for a vector x
source
AbstractPPL.@varnameMacro
@varname(expr, concretize=false)

A macro that returns an instance of VarName given a symbol or indexing expression expr.

If concretize is true, the resulting expression will be wrapped in a concretize() call.

Note that expressions involving dynamic indexing, i.e. begin and/or end, will always need to be concretized as VarName only supports non-dynamic indexing as determined by is_static_optic. See examples below.

Examples

Dynamic indexing

julia> x = (a = [1.0 2.0; 3.0 4.0; 5.0 6.0], );

julia> @varname(x.a[1:end, end][:], true)
x.a[1:3, 2][:]

julia> @varname(x.a[end], false)  # disable concretization
ERROR: LoadError: Variable name `x.a[end]` is dynamic and requires concretization!
[...]

julia> @varname(x.a[end])  # concretization occurs by default if deemed necessary
x.a[6]

julia> # Note that "dynamic" here refers to usage of `begin` and/or `end`,
       # _not_ "information only available at runtime", i.e. the following works.
       [@varname(x.a[i]) for i = 1:length(x.a)][end]
x.a[6]

julia> # Potentially surprising behaviour, but this is equivalent to what Base does:
       @varname(x[2:2:5]), 2:2:5
(x[2:2:4], 2:2:4)

General indexing

Under the hood optics are used for the indexing:

julia> getoptic(@varname(x))
identity (generic function with 1 method)

julia> getoptic(@varname(x[1]))
(@o _[1])

julia> getoptic(@varname(x[:, 1]))
(@o _[Colon(), 1])

julia> getoptic(@varname(x[:, 1][2]))
(@o _[Colon(), 1][2])

julia> getoptic(@varname(x[1,2][1+5][45][3]))
(@o _[1, 2][6][45][3])

This also means that we support property access:

julia> getoptic(@varname(x.a))
(@o _.a)

julia> getoptic(@varname(x.a[1]))
(@o _.a[1])

julia> x = (a = [(b = rand(2), )], ); getoptic(@varname(x.a[1].b[end], true))
(@o _.a[1].b[2])

Interpolation can be used for variable names, or array name, but not the lhs of a . expression. Variables within indices are always evaluated in the calling scope.

julia> name, i = :a, 10;

julia> @varname(x.$name[i, i+1])
x.a[10, 11]

julia> @varname($name)
a

julia> @varname($name[1])
a[1]

julia> @varname($name.x[1])
a.x[1]

julia> @varname(b.$name.x[1])
b.a.x[1]
source
AbstractPPL.@vsymMacro
@vsym(expr)

A macro that returns the variable symbol given the input variable expression expr. For example, @vsym x[1] returns :x.

Examples

julia> @vsym x
:x

julia> @vsym x[1,1][2,3]
:x

julia> @vsym x[end]
:x
source

VarName serialisation

AbstractPPL.index_to_dictFunction
index_to_dict(::Integer)
index_to_dict(::AbstractVector{Int})
index_to_dict(::UnitRange)
index_to_dict(::StepRange)
index_to_dict(::Colon)
index_to_dict(::ConcretizedSlice{T, Base.OneTo{I}}) where {T, I}
index_to_dict(::Tuple)

Convert an index i to a dictionary representation.

source
AbstractPPL.dict_to_indexFunction
dict_to_index(dict)
dict_to_index(symbol_val, dict)

Convert a dictionary representation of an index dict to an index.

Users can extend the functionality of dict_to_index (and hence VarName de/serialisation) by extending this method along with index_to_dict. Specifically, suppose you have a custom index type MyIndexType and you want to be able to de/serialise a VarName containing this index type. You should then implement the following two methods:

  1. AbstractPPL.index_to_dict(i::MyModule.MyIndexType) should return a dictionary representation of the index i. This dictionary must contain the key "type", and the corresponding value must be a string that uniquely identifies the index type. Generally, it makes sense to use the name of the type (perhaps prefixed with module qualifiers) as this value to avoid clashes. The remainder of the dictionary can have any structure you like.

  2. Suppose the value of index_to_dict(i)["type"] is "MyModule.MyIndexType". You should then implement the corresponding method AbstractPPL.dict_to_index(::Val{Symbol("MyModule.MyIndexType")}, dict), which should take the dictionary representation as the second argument and return the original MyIndexType object.

To see an example of this in action, you can look in the the AbstractPPL test suite, which contains a test for serialising OffsetArrays.

source
AbstractPPL.varname_to_stringFunction
varname_to_string(vn::VarName)

Convert a VarName as a string, via an intermediate dictionary. This differs from string(vn) in that concretised slices are faithfully represented (rather than being pretty-printed as colons).

For VarNames which index into an array, this function will only work if the indices can be serialised. This is true for all standard Julia index types, but if you are using custom index types, you will need to implement the index_to_dict and dict_to_index methods for those types. See the documentation of dict_to_index for instructions on how to do this.

julia> varname_to_string(@varname(x))
"{\"optic\":{\"type\":\"identity\"},\"sym\":\"x\"}"

julia> varname_to_string(@varname(x.a))
"{\"optic\":{\"field\":\"a\",\"type\":\"property\"},\"sym\":\"x\"}"

julia> y = ones(2); varname_to_string(@varname(y[:]))
"{\"optic\":{\"indices\":{\"values\":[{\"type\":\"Base.Colon\"}],\"type\":\"Base.Tuple\"},\"type\":\"index\"},\"sym\":\"y\"}"

julia> y = ones(2); varname_to_string(@varname(y[:], true))
"{\"optic\":{\"indices\":{\"values\":[{\"range\":{\"stop\":2,\"type\":\"Base.OneTo\"},\"type\":\"AbstractPPL.ConcretizedSlice\"}],\"type\":\"Base.Tuple\"},\"type\":\"index\"},\"sym\":\"y\"}"
source
AbstractPPL.string_to_varnameFunction
string_to_varname(str::AbstractString)

Convert a string representation of a VarName back to a VarName. The string should have been generated by varname_to_string.

source

Abstract model functions

AbstractPPL.conditionFunction
condition(model, observations)

Condition the generative model model on some observed data, creating a new model of the (possibly unnormalized) posterior distribution over them.

observations can be of any supported internal trace type, or a fixed probability expression.

The invariant

m = decondition(condition(m, obs))

should hold for generative models m and arbitrary obs.

source
AbstractPPL.deconditionFunction
decondition(conditioned_model)

Remove the conditioning (i.e., observation data) from conditioned_model, turning it into a generative model over prior and observed variables.

The invariant

m == condition(decondition(m), obs)

should hold for models m with conditioned variables obs.

source
AbstractPPL.fixFunction
fix(model, params)

Fix the values of parameters specified in params within the probabilistic model model. This operation is equivalent to treating the fixed parameters as being drawn from a point mass distribution centered at the values specified in params. Thus these parameters no longer contribute to the accumulated log density.

Conceptually, this is similar to Pearl's do-operator in causal inference, where we intervene on variables by setting them to specific values, effectively cutting off their dependencies on their usual causes in the model.

The invariant

m == unfix(fix(m, params))

should hold for any model m and parameters params.

source
AbstractPPL.unfixFunction
unfix(model)

Remove any fixed parameters from the model model, returning a new model without the fixed parameters.

This function reverses the effect of fix by removing parameter constraints that were previously set. It returns a new model where all previously fixed parameters are allowed to vary according to their original distributions in the model.

The invariant

m == unfix(fix(m, params))

should hold for any model m and parameters params.

source
DensityInterface.logdensityofFunction
logdensityof(model, trace)

Evaluate the (possibly unnormalized) density of the model specified by the probabilistic program in model, at specific values for the random variables given through trace.

trace can be of any supported internal trace type, or a fixed probability expression.

logdensityof should interact with conditioning and deconditioning in the way required by probability theory.

source

Abstract traces