API
VarNames
AbstractPPL.VarName
— TypeVarName{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.
VarName
s 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]
AbstractPPL.getsym
— Functiongetsym(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
AbstractPPL.getoptic
— Functiongetoptic(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)
AbstractPPL.inspace
— Functioninspace(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
AbstractPPL.subsumes
— Functionsubsumes(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 subsumex[2, 2]
for a matrixx
- Trailing ones:
x[2, 1]
does not subsumex[2]
for a vectorx
AbstractPPL.subsumedby
— Functionsubsumedby(t, u)
True if t
is subsumed by u
, i.e., if subsumes(u, t)
is true.
AbstractPPL.vsym
— Functionvsym(expr)
Return name part of the @varname
-compatible expression expr
as a symbol for input of the VarName
constructor.
AbstractPPL.@varname
— Macro@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 optic
s 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]
AbstractPPL.@vsym
— Macro@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
VarName serialisation
AbstractPPL.index_to_dict
— Functionindex_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.
AbstractPPL.dict_to_index
— Functiondict_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:
AbstractPPL.index_to_dict(i::MyModule.MyIndexType)
should return a dictionary representation of the indexi
. 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.Suppose the value of
index_to_dict(i)["type"]
is"MyModule.MyIndexType"
. You should then implement the corresponding methodAbstractPPL.dict_to_index(::Val{Symbol("MyModule.MyIndexType")}, dict)
, which should take the dictionary representation as the second argument and return the originalMyIndexType
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.
AbstractPPL.varname_to_string
— Functionvarname_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 VarName
s 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\"}"
AbstractPPL.string_to_varname
— Functionstring_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
.
Abstract model functions
AbstractPPL.AbstractProbabilisticProgram
— TypeAbstractProbabilisticProgram
Common base type for models expressed as probabilistic programs.
AbstractPPL.condition
— Functioncondition(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
.
AbstractPPL.decondition
— Functiondecondition(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
.
AbstractPPL.fix
— Functionfix(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
.
AbstractPPL.unfix
— Functionunfix(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
.
DensityInterface.logdensityof
— Functionlogdensityof(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.
AbstractPPL.AbstractContext
— TypeAbstractContext
Common base type for evaluation contexts.
AbstractPPL.evaluate!!
— Functionevaluate!!
General API for model operations, e.g. prior evaluation, log density, log joint etc.
Abstract traces
AbstractPPL.AbstractModelTrace
— TypeAbstractModelTrace
Common base class for various trace or "variable info" types.